From 7cc4571f4ac565be70caf1c11cf115bfc27c2ed2 Mon Sep 17 00:00:00 2001 From: Min-Gu Yoo Date: Sat, 24 Jan 2026 10:59:38 -0800 Subject: [PATCH 01/15] feat: add BitTypedPool for memory-efficient BitVector pooling - Add BitTypedPool struct extending AbstractTypedPool{Bool, BitVector} - Add `bits` field to AdaptiveArrayPool for BitVector storage - Update FIXED_SLOT_FIELDS to include :bits slot - Include empty N-D cache fields for empty!() compatibility BitTypedPool stores 1 bit per element (~8x memory savings vs Vector{Bool}). Note: unsafe_acquire! not supported - BitArray chunks are immutable. --- src/types.jl | 80 +++++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 79 insertions(+), 1 deletion(-) diff --git a/src/types.jl b/src/types.jl index 9a3ffc7..d271c4a 100644 --- a/src/types.jl +++ b/src/types.jl @@ -210,6 +210,82 @@ TypedPool{T}() where {T} = TypedPool{T}( [0] # _checkpoint_depths: sentinel (depth=0 = no checkpoint) ) +# ============================================================================== +# BitTypedPool - Specialized pool for BitVector/BitArray +# ============================================================================== + +""" + BitTypedPool <: AbstractTypedPool{Bool, BitVector} + +Specialized pool for `BitVector` arrays with memory reuse. + +Unlike `TypedPool{Bool}` which stores `Vector{Bool}` (1 byte per element), +this pool stores `BitVector` (1 bit per element, ~8x memory efficiency). + +## Important Limitation +**`unsafe_acquire!` is NOT supported for BitArray** because Julia's `BitArray` +stores data in a `chunks::Vector{UInt64}` field that cannot be wrapped with +`unsafe_wrap`. Only view-based acquisition via `acquire_bits!` is available. + +## Fields +- `vectors`: Backing `BitVector` storage +- `views`: Cached `SubArray` views for zero-allocation 1D access +- `view_lengths`: Cached lengths for fast comparison +- `nd_*`: Empty N-D cache fields (for `empty!` compatibility, unused) +- `n_active`: Count of currently active arrays +- `_checkpoint_*`: State management stacks (1-based sentinel pattern) + +## Usage +```julia +@with_pool pool begin + bv = acquire_bits!(pool, 100) # SubArray{Bool,1,BitVector,...} + ba = acquire_bits!(pool, 10, 10) # ReshapedArray{Bool,2,...} + t = trues!(pool, 50) # Filled with true + f = falses!(pool, 50) # Filled with false +end +``` + +See also: [`acquire_bits!`](@ref), [`trues!`](@ref), [`falses!`](@ref) +""" +mutable struct BitTypedPool <: AbstractTypedPool{Bool, BitVector} + # --- Storage --- + vectors::Vector{BitVector} + + # --- 1D Cache (1:1 mapping) --- + views::Vector{SubArray{Bool, 1, BitVector, Tuple{UnitRange{Int64}}, true}} + view_lengths::Vector{Int} + + # --- N-D Array Cache (empty, for empty! compatibility) --- + # BitArray cannot use unsafe_wrap, so no N-D caching is possible. + # These fields exist only for compatibility with empty!(::AbstractTypedPool). + nd_arrays::Vector{Any} + nd_dims::Vector{Any} + nd_ptrs::Vector{UInt} + nd_next_way::Vector{Int} + + # --- State Management (1-based sentinel pattern) --- + n_active::Int + _checkpoint_n_active::Vector{Int} + _checkpoint_depths::Vector{Int} +end + +BitTypedPool() = BitTypedPool( + # Storage + BitVector[], + # 1D Cache + SubArray{Bool, 1, BitVector, Tuple{UnitRange{Int64}}, true}[], + Int[], + # N-D Array Cache (empty, for compatibility) + Any[], + Any[], + UInt[], + Int[], + # State Management (1-based sentinel pattern) + 0, # n_active + [0], # _checkpoint_n_active: sentinel + [0] # _checkpoint_depths: sentinel +) + # ============================================================================== # Fixed Slot Configuration # ============================================================================== @@ -222,7 +298,7 @@ Field names for fixed slot TypedPools. Single source of truth for `foreach_fixed When modifying, also update: struct definition, `get_typed_pool!` dispatches, constructor. Tests verify synchronization automatically. """ -const FIXED_SLOT_FIELDS = (:float64, :float32, :int64, :int32, :complexf64, :complexf32, :bool) +const FIXED_SLOT_FIELDS = (:float64, :float32, :int64, :int32, :complexf64, :complexf32, :bool, :bits) # ============================================================================== # AdaptiveArrayPool @@ -243,6 +319,7 @@ mutable struct AdaptiveArrayPool <: AbstractArrayPool complexf64::TypedPool{ComplexF64} complexf32::TypedPool{ComplexF32} bool::TypedPool{Bool} + bits::BitTypedPool # BitVector pool (1 bit per element) # Fallback: rare types others::IdDict{DataType, Any} @@ -261,6 +338,7 @@ function AdaptiveArrayPool() TypedPool{ComplexF64}(), TypedPool{ComplexF32}(), TypedPool{Bool}(), + BitTypedPool(), IdDict{DataType, Any}(), 1, # _current_depth: 1 = global scope (sentinel) [false] # _untracked_flags: sentinel for global scope From 238837325db6ca5238d20c1a89fbd1170053ce1d Mon Sep 17 00:00:00 2001 From: Min-Gu Yoo Date: Sat, 24 Jan 2026 10:59:45 -0800 Subject: [PATCH 02/15] feat: add acquire_bits! API for BitVector acquisition - Add allocate_vector dispatch for BitTypedPool - Implement acquire_bits!(pool, n) for 1D SubArray{Bool,1,BitVector} - Implement acquire_bits!(pool, dims...) for N-D ReshapedArray views - Add _acquire_bits_impl! for macro transformation support - Add DisabledPool fallbacks returning native BitArray types --- src/acquire.jl | 92 ++++++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 92 insertions(+) diff --git a/src/acquire.jl b/src/acquire.jl index 98c2298..0922e93 100644 --- a/src/acquire.jl +++ b/src/acquire.jl @@ -6,6 +6,9 @@ @inline allocate_vector(::AbstractTypedPool{T,Vector{T}}, n::Int) where {T} = Vector{T}(undef, n) +# BitTypedPool allocates BitVector +@inline allocate_vector(::BitTypedPool, n::Int) = BitVector(undef, n) + # Wrap flat view into N-D array (dispatch point for extensions) @inline function wrap_array(::AbstractTypedPool{T,Vector{T}}, flat_view, dims::NTuple{N,Int}) where {T,N} @@ -412,6 +415,84 @@ const acquire_array! = unsafe_acquire! const _acquire_view_impl! = _acquire_impl! const _acquire_array_impl! = _unsafe_acquire_impl! +# ============================================================================== +# BitVector Acquisition API +# ============================================================================== + +""" + acquire_bits!(pool, n) -> SubArray{Bool,1,BitVector,...} + acquire_bits!(pool, dims...) -> ReshapedArray{Bool,N,...} + acquire_bits!(pool, dims::NTuple{N,Int}) -> ReshapedArray{Bool,N,...} + +Acquire a BitVector-backed boolean array from the pool. + +Returns a view backed by BitVector (1 bit per element), which is ~8x more +memory efficient than `acquire!(pool, Bool, n)` which uses Vector{Bool} +(1 byte per element). + +## Return Types +- **1D**: `SubArray{Bool,1,BitVector,Tuple{UnitRange{Int64}},true}` +- **N-D**: `Base.ReshapedArray{Bool,N,...}` (reshaped view of 1D BitVector) + +## Important Limitation +**`unsafe_acquire!` is NOT supported for BitArray** because Julia's `BitArray` +stores data in a `chunks::Vector{UInt64}` field that cannot be wrapped with +`unsafe_wrap`. Only view-based acquisition is available. + +## Type Compatibility +For downstream code expecting `::BitArray`, use a union type: +```julia +const BitArrayLike{N} = Union{ + BitArray{N}, + SubArray{Bool,N,<:BitArray}, + Base.ReshapedArray{Bool,N,<:SubArray{Bool,1,<:BitArray}} +} +``` + +## Example +```julia +@with_pool pool begin + bv = acquire_bits!(pool, 100) # 1D BitVector view + ba = acquire_bits!(pool, 10, 10) # 2D reshaped view + bt = acquire_bits!(pool, (5, 5, 4)) # 3D via tuple + + bv .= true # Set all bits + ba[1,1] = false +end +``` + +See also: [`trues!`](@ref), [`falses!`](@ref), [`acquire!`](@ref) +""" +@inline function acquire_bits!(pool::AbstractArrayPool, n::Int) + _mark_untracked!(pool) + _acquire_bits_impl!(pool, n) +end + +@inline function acquire_bits!(pool::AbstractArrayPool, dims::Vararg{Int, N}) where {N} + _mark_untracked!(pool) + _acquire_bits_impl!(pool, dims...) +end + +@inline function acquire_bits!(pool::AbstractArrayPool, dims::NTuple{N, Int}) where {N} + _mark_untracked!(pool) + _acquire_bits_impl!(pool, dims...) +end + +# Internal implementation (for macro transformation) +@inline function _acquire_bits_impl!(pool::AbstractArrayPool, n::Int) + return get_view!(pool.bits, n) +end + +@inline function _acquire_bits_impl!(pool::AbstractArrayPool, dims::Vararg{Int, N}) where {N} + total = safe_prod(dims) + flat_view = get_view!(pool.bits, total) + return reshape(flat_view, dims) +end + +@inline function _acquire_bits_impl!(pool::AbstractArrayPool, dims::NTuple{N, Int}) where {N} + _acquire_bits_impl!(pool, dims...) +end + # ============================================================================== # DisabledPool Acquire Fallbacks (pooling disabled with backend context) # ============================================================================== @@ -428,9 +509,15 @@ const _acquire_array_impl! = _unsafe_acquire_impl! @inline unsafe_acquire!(::DisabledPool{:cpu}, ::Type{T}, dims::NTuple{N,Int}) where {T,N} = Array{T,N}(undef, dims) @inline unsafe_acquire!(::DisabledPool{:cpu}, x::AbstractArray) = similar(x) +# --- acquire_bits! for DisabledPool{:cpu} --- +@inline acquire_bits!(::DisabledPool{:cpu}, n::Int) = BitVector(undef, n) +@inline acquire_bits!(::DisabledPool{:cpu}, dims::Vararg{Int,N}) where {N} = BitArray{N}(undef, dims) +@inline acquire_bits!(::DisabledPool{:cpu}, dims::NTuple{N,Int}) where {N} = BitArray{N}(undef, dims) + # --- Generic DisabledPool fallbacks (unknown backend → error) --- @inline acquire!(p::DisabledPool{B}, args...) where {B} = _throw_backend_not_loaded(B) @inline unsafe_acquire!(p::DisabledPool{B}, args...) where {B} = _throw_backend_not_loaded(B) +@inline acquire_bits!(p::DisabledPool{B}, args...) where {B} = _throw_backend_not_loaded(B) # --- _impl! delegators for DisabledPool (macro transformation support) --- # Called when: USE_POOLING=true + @maybe_with_pool + MAYBE_POOLING_ENABLED[]=false @@ -444,3 +531,8 @@ const _acquire_array_impl! = _unsafe_acquire_impl! @inline _unsafe_acquire_impl!(p::DisabledPool, ::Type{T}, dims::Vararg{Int,N}) where {T,N} = unsafe_acquire!(p, T, dims...) @inline _unsafe_acquire_impl!(p::DisabledPool, ::Type{T}, dims::NTuple{N,Int}) where {T,N} = unsafe_acquire!(p, T, dims) @inline _unsafe_acquire_impl!(p::DisabledPool, x::AbstractArray) = unsafe_acquire!(p, x) + +# --- _acquire_bits_impl! delegators for DisabledPool --- +@inline _acquire_bits_impl!(p::DisabledPool, n::Int) = acquire_bits!(p, n) +@inline _acquire_bits_impl!(p::DisabledPool, dims::Vararg{Int,N}) where {N} = acquire_bits!(p, dims...) +@inline _acquire_bits_impl!(p::DisabledPool, dims::NTuple{N,Int}) where {N} = acquire_bits!(p, dims) From 2faaaa04a5de37faefacde35ab3e4928fc3bd8a7 Mon Sep 17 00:00:00 2001 From: Min-Gu Yoo Date: Sat, 24 Jan 2026 10:59:51 -0800 Subject: [PATCH 03/15] feat: add trues!/falses! convenience functions - Add trues!(pool, dims...) for BitArray filled with true - Add falses!(pool, dims...) for BitArray filled with false - Include tuple form and DisabledPool fallbacks - Add _trues_impl!/_falses_impl! for macro transformation --- src/convenience.jl | 114 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 114 insertions(+) diff --git a/src/convenience.jl b/src/convenience.jl index 76f615c..67e4edf 100644 --- a/src/convenience.jl +++ b/src/convenience.jl @@ -417,6 +417,102 @@ end _unsafe_acquire_impl!(pool, T, dims...) end +# ============================================================================== +# trues! - Acquire BitArray filled with true values +# ============================================================================== + +""" + trues!(pool, dims...) -> BitArray view + trues!(pool, dims::Tuple) -> BitArray view + +Acquire a BitArray from the pool and fill with `true` values. + +Equivalent to `acquire_bits!(pool, dims...)` followed by `fill!(arr, true)`. + +## Return Type +Returns a view backed by `BitVector`: +- **1D**: `SubArray{Bool,1,BitVector,...}` +- **N-D**: `ReshapedArray{Bool,N,...}` (reshaped view) + +## Example +```julia +@with_pool pool begin + mask = trues!(pool, 100) # 1D, all true + grid = trues!(pool, 10, 10) # 2D, all true +end +``` + +See also: [`falses!`](@ref), [`acquire_bits!`](@ref), [`ones!`](@ref) +""" +@inline function trues!(pool::AbstractArrayPool, dims::Vararg{Int,N}) where {N} + _mark_untracked!(pool) + _trues_impl!(pool, dims...) +end + +@inline function trues!(pool::AbstractArrayPool, dims::NTuple{N,Int}) where {N} + _mark_untracked!(pool) + _trues_impl!(pool, dims...) +end + +# Internal implementation (for macro transformation) +@inline function _trues_impl!(pool::AbstractArrayPool, dims::Vararg{Int,N}) where {N} + arr = _acquire_bits_impl!(pool, dims...) + fill!(arr, true) + arr +end + +@inline function _trues_impl!(pool::AbstractArrayPool, dims::NTuple{N,Int}) where {N} + _trues_impl!(pool, dims...) +end + +# ============================================================================== +# falses! - Acquire BitArray filled with false values +# ============================================================================== + +""" + falses!(pool, dims...) -> BitArray view + falses!(pool, dims::Tuple) -> BitArray view + +Acquire a BitArray from the pool and fill with `false` values. + +Equivalent to `acquire_bits!(pool, dims...)` followed by `fill!(arr, false)`. + +## Return Type +Returns a view backed by `BitVector`: +- **1D**: `SubArray{Bool,1,BitVector,...}` +- **N-D**: `ReshapedArray{Bool,N,...}` (reshaped view) + +## Example +```julia +@with_pool pool begin + mask = falses!(pool, 100) # 1D, all false + grid = falses!(pool, 10, 10) # 2D, all false +end +``` + +See also: [`trues!`](@ref), [`acquire_bits!`](@ref), [`zeros!`](@ref) +""" +@inline function falses!(pool::AbstractArrayPool, dims::Vararg{Int,N}) where {N} + _mark_untracked!(pool) + _falses_impl!(pool, dims...) +end + +@inline function falses!(pool::AbstractArrayPool, dims::NTuple{N,Int}) where {N} + _mark_untracked!(pool) + _falses_impl!(pool, dims...) +end + +# Internal implementation (for macro transformation) +@inline function _falses_impl!(pool::AbstractArrayPool, dims::Vararg{Int,N}) where {N} + arr = _acquire_bits_impl!(pool, dims...) + fill!(arr, false) + arr +end + +@inline function _falses_impl!(pool::AbstractArrayPool, dims::NTuple{N,Int}) where {N} + _falses_impl!(pool, dims...) +end + # ============================================================================== # BackendNotLoadedError - Error for unknown backends # ============================================================================== @@ -477,6 +573,14 @@ end @inline ones!(::DisabledPool{:cpu}, ::Type{T}, dims::NTuple{N,Int}) where {T,N} = ones(T, dims...) @inline ones!(p::DisabledPool{:cpu}, dims::NTuple{N,Int}) where {N} = ones(default_eltype(p), dims...) +# --- trues! for DisabledPool{:cpu} --- +@inline trues!(::DisabledPool{:cpu}, dims::Vararg{Int,N}) where {N} = trues(dims...) +@inline trues!(::DisabledPool{:cpu}, dims::NTuple{N,Int}) where {N} = trues(dims...) + +# --- falses! for DisabledPool{:cpu} --- +@inline falses!(::DisabledPool{:cpu}, dims::Vararg{Int,N}) where {N} = falses(dims...) +@inline falses!(::DisabledPool{:cpu}, dims::NTuple{N,Int}) where {N} = falses(dims...) + # --- similar! for DisabledPool{:cpu} --- @inline similar!(::DisabledPool{:cpu}, x::AbstractArray) = similar(x) @inline similar!(::DisabledPool{:cpu}, x::AbstractArray, ::Type{T}) where {T} = similar(x, T) @@ -508,6 +612,8 @@ end @inline unsafe_zeros!(p::DisabledPool{B}, args...) where {B} = _throw_backend_not_loaded(B) @inline unsafe_ones!(p::DisabledPool{B}, args...) where {B} = _throw_backend_not_loaded(B) @inline unsafe_similar!(p::DisabledPool{B}, args...) where {B} = _throw_backend_not_loaded(B) +@inline trues!(p::DisabledPool{B}, args...) where {B} = _throw_backend_not_loaded(B) +@inline falses!(p::DisabledPool{B}, args...) where {B} = _throw_backend_not_loaded(B) # ============================================================================== # _impl! Delegators for DisabledPool @@ -553,3 +659,11 @@ end @inline _unsafe_similar_impl!(p::DisabledPool, x::AbstractArray, ::Type{T}) where {T} = unsafe_similar!(p, x, T) @inline _unsafe_similar_impl!(p::DisabledPool, x::AbstractArray, dims::Vararg{Int,N}) where {N} = unsafe_similar!(p, x, dims...) @inline _unsafe_similar_impl!(p::DisabledPool, x::AbstractArray, ::Type{T}, dims::Vararg{Int,N}) where {T,N} = unsafe_similar!(p, x, T, dims...) + +# --- _trues_impl! --- +@inline _trues_impl!(p::DisabledPool, dims::Vararg{Int,N}) where {N} = trues!(p, dims...) +@inline _trues_impl!(p::DisabledPool, dims::NTuple{N,Int}) where {N} = trues!(p, dims) + +# --- _falses_impl! --- +@inline _falses_impl!(p::DisabledPool, dims::Vararg{Int,N}) where {N} = falses!(p, dims...) +@inline _falses_impl!(p::DisabledPool, dims::NTuple{N,Int}) where {N} = falses!(p, dims) From 350beb92c9b3f73063927fa8964d22fd987978b4 Mon Sep 17 00:00:00 2001 From: Min-Gu Yoo Date: Sat, 24 Jan 2026 10:59:58 -0800 Subject: [PATCH 04/15] feat: add macro support for BitArray functions - Add GlobalRef constants for _acquire_bits_impl!, _trues_impl!, _falses_impl! - Update _transform_acquire_calls for acquire_bits!/trues!/falses! - Add :_bits_slot marker in _extract_acquire_types for typed checkpoint - Handle :_bits_slot in _filter_static_types to trigger full checkpoint - Export acquire_bits!, trues!, falses! in main module --- src/AdaptiveArrayPools.jl | 1 + src/macros.jl | 25 ++++++++++++++++++++++++- 2 files changed, 25 insertions(+), 1 deletion(-) diff --git a/src/AdaptiveArrayPools.jl b/src/AdaptiveArrayPools.jl index b28aa2e..1102b80 100644 --- a/src/AdaptiveArrayPools.jl +++ b/src/AdaptiveArrayPools.jl @@ -7,6 +7,7 @@ export AdaptiveArrayPool, acquire!, unsafe_acquire!, pool_stats, get_task_local_ export acquire_view!, acquire_array! # Explicit naming aliases export zeros!, ones!, similar!, default_eltype # Convenience functions export unsafe_zeros!, unsafe_ones!, unsafe_similar! # Unsafe convenience functions +export acquire_bits!, trues!, falses! # BitVector/BitArray functions export @with_pool, @maybe_with_pool export USE_POOLING, MAYBE_POOLING_ENABLED, POOL_DEBUG export checkpoint!, rewind!, reset! diff --git a/src/macros.jl b/src/macros.jl index b0a0425..1a97751 100644 --- a/src/macros.jl +++ b/src/macros.jl @@ -854,6 +854,10 @@ function _extract_acquire_types(expr, target_pool, types=Set{Any}()) push!(types, Expr(:call, :eltype, expr.args[3])) end end + # acquire_bits!/trues!/falses! - uses bits slot (not get_typed_pool!) + # Mark as :_bits_slot to trigger has_dynamic=true in _filter_static_types + elseif fn in (:acquire_bits!, :trues!, :falses!) || fn_name in (:acquire_bits!, :trues!, :falses!) + push!(types, :_bits_slot) end end end @@ -930,7 +934,11 @@ function _filter_static_types(types, local_vars=Set{Symbol}()) for t in types if t isa Symbol - if t in local_vars + if t == :_bits_slot + # BitArray functions (acquire_bits!, trues!, falses!) use bits slot + # directly, not via get_typed_pool! - requires full checkpoint + has_dynamic = true + elseif t in local_vars # Local variable like T = eltype(x) - defined after checkpoint! # Must fall back to full checkpoint has_dynamic = true @@ -1038,6 +1046,9 @@ const _SIMILAR_IMPL_REF = GlobalRef(@__MODULE__, :_similar_impl!) const _UNSAFE_ZEROS_IMPL_REF = GlobalRef(@__MODULE__, :_unsafe_zeros_impl!) const _UNSAFE_ONES_IMPL_REF = GlobalRef(@__MODULE__, :_unsafe_ones_impl!) const _UNSAFE_SIMILAR_IMPL_REF = GlobalRef(@__MODULE__, :_unsafe_similar_impl!) +const _ACQUIRE_BITS_IMPL_REF = GlobalRef(@__MODULE__, :_acquire_bits_impl!) +const _TRUES_IMPL_REF = GlobalRef(@__MODULE__, :_trues_impl!) +const _FALSES_IMPL_REF = GlobalRef(@__MODULE__, :_falses_impl!) function _transform_acquire_calls(expr, pool_name) if expr isa Expr @@ -1065,6 +1076,12 @@ function _transform_acquire_calls(expr, pool_name) expr = Expr(:call, _UNSAFE_ONES_IMPL_REF, expr.args[2:end]...) elseif fn == :unsafe_similar! expr = Expr(:call, _UNSAFE_SIMILAR_IMPL_REF, expr.args[2:end]...) + elseif fn == :acquire_bits! + expr = Expr(:call, _ACQUIRE_BITS_IMPL_REF, expr.args[2:end]...) + elseif fn == :trues! + expr = Expr(:call, _TRUES_IMPL_REF, expr.args[2:end]...) + elseif fn == :falses! + expr = Expr(:call, _FALSES_IMPL_REF, expr.args[2:end]...) elseif fn isa Expr && fn.head == :. && length(fn.args) >= 2 # Qualified name: AdaptiveArrayPools.acquire! etc. qn = fn.args[end] @@ -1084,6 +1101,12 @@ function _transform_acquire_calls(expr, pool_name) expr = Expr(:call, _UNSAFE_ONES_IMPL_REF, expr.args[2:end]...) elseif qn == QuoteNode(:unsafe_similar!) expr = Expr(:call, _UNSAFE_SIMILAR_IMPL_REF, expr.args[2:end]...) + elseif qn == QuoteNode(:acquire_bits!) + expr = Expr(:call, _ACQUIRE_BITS_IMPL_REF, expr.args[2:end]...) + elseif qn == QuoteNode(:trues!) + expr = Expr(:call, _TRUES_IMPL_REF, expr.args[2:end]...) + elseif qn == QuoteNode(:falses!) + expr = Expr(:call, _FALSES_IMPL_REF, expr.args[2:end]...) end end end From da406074e134c217aaa80305ccce1b7839b3b9c3 Mon Sep 17 00:00:00 2001 From: Min-Gu Yoo Date: Sat, 24 Jan 2026 11:00:05 -0800 Subject: [PATCH 05/15] test: add comprehensive BitArray tests and fix fixed slot tests - Add test_bitarray.jl with 85 tests covering: - BitTypedPool structure and state management - acquire_bits! 1D and N-D operations - trues!/falses! convenience functions - DisabledPool fallbacks and macro integration - Memory efficiency vs Vector{Bool} - Update test_fixed_slots.jl to use AbstractTypedPool - Add acquire_bits! to empty! integration test --- test/runtests.jl | 1 + test/test_bitarray.jl | 304 +++++++++++++++++++++++++++++++++++++++ test/test_fixed_slots.jl | 18 +-- 3 files changed, 315 insertions(+), 8 deletions(-) create mode 100644 test/test_bitarray.jl diff --git a/test/runtests.jl b/test/runtests.jl index 8ac91c8..c4417de 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -25,6 +25,7 @@ else include("test_fixed_slots.jl") include("test_backend_macro_expansion.jl") include("test_convenience.jl") + include("test_bitarray.jl") include("test_coverage.jl") # CUDA extension tests (auto-detect, skip with TEST_CUDA=false) diff --git a/test/test_bitarray.jl b/test/test_bitarray.jl new file mode 100644 index 0000000..4a3cf34 --- /dev/null +++ b/test/test_bitarray.jl @@ -0,0 +1,304 @@ +@testset "BitArray Support" begin + + @testset "BitTypedPool structure" begin + pool = AdaptiveArrayPool() + + # Verify bits field exists and is correctly typed + @test pool.bits isa AdaptiveArrayPools.BitTypedPool + @test pool.bits.n_active == 0 + @test isempty(pool.bits.vectors) + end + + @testset "acquire_bits! 1D" begin + pool = AdaptiveArrayPool() + + bv = acquire_bits!(pool, 100) + @test length(bv) == 100 + @test eltype(bv) == Bool + @test bv isa SubArray{Bool, 1, BitVector} + @test pool.bits.n_active == 1 + + # Write and read back + bv .= true + @test all(bv) + bv[50] = false + @test !bv[50] + @test count(bv) == 99 + + # Second acquire + bv2 = acquire_bits!(pool, 50) + @test length(bv2) == 50 + @test pool.bits.n_active == 2 + + # Independent values + bv2 .= false + @test !any(bv2) + @test count(bv) == 99 # bv unchanged + end + + @testset "acquire_bits! N-D" begin + pool = AdaptiveArrayPool() + + # 2D + ba2 = acquire_bits!(pool, 10, 10) + @test size(ba2) == (10, 10) + @test eltype(ba2) == Bool + @test ba2 isa Base.ReshapedArray + @test pool.bits.n_active == 1 + + # Test indexing + ba2 .= false + ba2[1, 1] = true + ba2[5, 5] = true + @test count(ba2) == 2 + @test ba2[1, 1] + @test ba2[5, 5] + @test !ba2[2, 2] + + # 3D + ba3 = acquire_bits!(pool, 4, 5, 3) + @test size(ba3) == (4, 5, 3) + @test pool.bits.n_active == 2 + + # Tuple form + ba_tuple = acquire_bits!(pool, (3, 4, 2)) + @test size(ba_tuple) == (3, 4, 2) + @test pool.bits.n_active == 3 + end + + @testset "trues!" begin + pool = AdaptiveArrayPool() + + # 1D + t1 = trues!(pool, 100) + @test length(t1) == 100 + @test all(t1) + @test pool.bits.n_active == 1 + + # 2D + t2 = trues!(pool, 10, 10) + @test size(t2) == (10, 10) + @test all(t2) + @test count(t2) == 100 + + # Tuple form + t3 = trues!(pool, (5, 5, 4)) + @test size(t3) == (5, 5, 4) + @test all(t3) + end + + @testset "falses!" begin + pool = AdaptiveArrayPool() + + # 1D + f1 = falses!(pool, 100) + @test length(f1) == 100 + @test !any(f1) + @test pool.bits.n_active == 1 + + # 2D + f2 = falses!(pool, 10, 10) + @test size(f2) == (10, 10) + @test !any(f2) + @test count(f2) == 0 + + # Tuple form + f3 = falses!(pool, (5, 5, 4)) + @test size(f3) == (5, 5, 4) + @test !any(f3) + end + + @testset "State management" begin + # Use @with_pool which manages checkpoint/rewind automatically + @with_pool outer_pool begin + bv1 = acquire_bits!(outer_pool, 100) + parent1 = parent(bv1) + + @test outer_pool.bits.n_active == 1 + + @with_pool inner_pool begin + bv2 = acquire_bits!(inner_pool, 200) + @test inner_pool.bits.n_active == 2 + end + # After inner scope rewind + @test outer_pool.bits.n_active == 1 + + # bv1 should still be valid (same parent BitVector object) + bv3 = acquire_bits!(outer_pool, 150) + @test parent(bv1) === parent1 # Same object identity + end + # Pool goes back to task-local state after scope ends + end + + @testset "checkpoint!/rewind! integration" begin + pool = AdaptiveArrayPool() + + checkpoint!(pool) + @test pool.bits.n_active == 0 + + bv1 = acquire_bits!(pool, 100) + t1 = trues!(pool, 50) + f1 = falses!(pool, 50) + @test pool.bits.n_active == 3 + + rewind!(pool) + @test pool.bits.n_active == 0 + end + + @testset "reset! and empty!" begin + pool = AdaptiveArrayPool() + + bv1 = acquire_bits!(pool, 100) + bv2 = acquire_bits!(pool, 200) + @test pool.bits.n_active == 2 + @test length(pool.bits.vectors) >= 2 + + # reset! preserves vectors + reset!(pool) + @test pool.bits.n_active == 0 + @test length(pool.bits.vectors) >= 2 # vectors preserved + + # empty! clears everything + bv3 = acquire_bits!(pool, 50) + empty!(pool) + @test pool.bits.n_active == 0 + @test isempty(pool.bits.vectors) + end + + @testset "DisabledPool fallback" begin + # acquire_bits! + bv = acquire_bits!(DISABLED_CPU, 100) + @test bv isa BitVector + @test length(bv) == 100 + + # N-D + ba = acquire_bits!(DISABLED_CPU, 10, 10) + @test ba isa BitArray{2} + @test size(ba) == (10, 10) + + # Tuple form + ba_tuple = acquire_bits!(DISABLED_CPU, (5, 5)) + @test ba_tuple isa BitArray{2} + @test size(ba_tuple) == (5, 5) + + # trues! + t = trues!(DISABLED_CPU, 50) + @test t isa BitVector + @test all(t) + + t2d = trues!(DISABLED_CPU, 5, 5) + @test t2d isa BitArray{2} + @test all(t2d) + + # falses! + f = falses!(DISABLED_CPU, 50) + @test f isa BitVector + @test !any(f) + + f2d = falses!(DISABLED_CPU, 5, 5) + @test f2d isa BitArray{2} + @test !any(f2d) + end + + @testset "Memory efficiency vs Vector{Bool}" begin + pool = AdaptiveArrayPool() + + # BitVector should use ~8x less memory than Vector{Bool} + # (1 bit vs 1 byte per element) + bv = acquire_bits!(pool, 1000) + vb = acquire!(pool, Bool, 1000) + + bv_parent = parent(bv) + vb_parent = parent(vb) + + # BitVector stores 64 bits per chunk (UInt64) + @test sizeof(bv_parent.chunks) < sizeof(vb_parent) + # Approximate: BitVector ~125 bytes (1000/8), Vector{Bool} ~1000 bytes + @test sizeof(bv_parent.chunks) <= div(1000, 8) + 8 # allow some overhead + end + + @testset "@with_pool macro integration" begin + result = @with_pool pool begin + bv = acquire_bits!(pool, 100) + t = trues!(pool, 50) + f = falses!(pool, 50) + + bv .= true + sum_bv = count(bv) + sum_t = count(t) + sum_f = count(f) + + (sum_bv, sum_t, sum_f) + end + + @test result == (100, 50, 0) + end + + @testset "@maybe_with_pool macro integration" begin + # With pooling enabled (default) + result1 = @maybe_with_pool pool begin + bv = acquire_bits!(pool, 100) + bv .= true + count(bv) + end + @test result1 == 100 + + # With pooling disabled + AdaptiveArrayPools.MAYBE_POOLING_ENABLED[] = false + try + result2 = @maybe_with_pool pool begin + bv = acquire_bits!(pool, 100) + @test bv isa BitVector # DisabledPool returns BitVector + bv .= true + count(bv) + end + @test result2 == 100 + finally + AdaptiveArrayPools.MAYBE_POOLING_ENABLED[] = true + end + end + + @testset "Mixed Bool types" begin + pool = AdaptiveArrayPool() + + # Vector{Bool} via acquire! + vb = acquire!(pool, Bool, 100) + @test vb isa SubArray{Bool, 1, Vector{Bool}} + @test pool.bool.n_active == 1 + + # BitVector via acquire_bits! + bv = acquire_bits!(pool, 100) + @test bv isa SubArray{Bool, 1, BitVector} + @test pool.bits.n_active == 1 + + # Both should work independently + vb .= true + bv .= false + @test all(vb) + @test !any(bv) + + # Separate pools + @test pool.bool.n_active == 1 + @test pool.bits.n_active == 1 + end + + @testset "Nested scopes" begin + outer_result = @with_pool outer_pool begin + outer_bv = acquire_bits!(outer_pool, 100) + outer_bv .= true + + inner_result = @with_pool inner_pool begin + inner_bv = acquire_bits!(inner_pool, 50) + inner_bv .= false + count(inner_bv) + end + + # outer_bv should still be valid + @test all(outer_bv) + (count(outer_bv), inner_result) + end + + @test outer_result == (100, 0) + end + +end # BitArray Support diff --git a/test/test_fixed_slots.jl b/test/test_fixed_slots.jl index efe50ac..088b12a 100644 --- a/test/test_fixed_slots.jl +++ b/test/test_fixed_slots.jl @@ -1,25 +1,26 @@ @testset "Fixed Slot Infrastructure" begin - using AdaptiveArrayPools: FIXED_SLOT_FIELDS, foreach_fixed_slot, TypedPool + using AdaptiveArrayPools: FIXED_SLOT_FIELDS, foreach_fixed_slot, TypedPool, AbstractTypedPool @testset "FIXED_SLOT_FIELDS Synchronization" begin pool = AdaptiveArrayPool() - # Forward check: all FIXED_SLOT_FIELDS exist in struct as TypedPool + # Forward check: all FIXED_SLOT_FIELDS exist in struct as AbstractTypedPool + # (includes TypedPool{T} and BitTypedPool) for field in FIXED_SLOT_FIELDS @test hasfield(AdaptiveArrayPool, field) - @test fieldtype(AdaptiveArrayPool, field) <: TypedPool - @test getfield(pool, field) isa TypedPool + @test fieldtype(AdaptiveArrayPool, field) <: AbstractTypedPool + @test getfield(pool, field) isa AbstractTypedPool end - # Reverse check: all TypedPool fields in struct are in FIXED_SLOT_FIELDS + # Reverse check: all AbstractTypedPool fields in struct are in FIXED_SLOT_FIELDS for (name, type) in zip(fieldnames(AdaptiveArrayPool), fieldtypes(AdaptiveArrayPool)) - if type <: TypedPool + if type <: AbstractTypedPool @test name in FIXED_SLOT_FIELDS end end # Count verification - typedpool_count = count(t -> t <: TypedPool, fieldtypes(AdaptiveArrayPool)) + typedpool_count = count(t -> t <: AbstractTypedPool, fieldtypes(AdaptiveArrayPool)) @test typedpool_count == length(FIXED_SLOT_FIELDS) end @@ -99,7 +100,7 @@ @testset "Integration: empty! Uses foreach_fixed_slot" begin pool = AdaptiveArrayPool() - # Acquire arrays of different types + # Acquire arrays of different types (including BitVector via acquire_bits!) acquire!(pool, Float64, 10) acquire!(pool, Float32, 10) acquire!(pool, Int64, 10) @@ -107,6 +108,7 @@ acquire!(pool, ComplexF64, 10) acquire!(pool, ComplexF32, 10) acquire!(pool, Bool, 10) + acquire_bits!(pool, 10) # BitTypedPool # Verify all pools have active arrays for field in FIXED_SLOT_FIELDS From 4888af4c847c01dc71874aadb845ab0583d05ba5 Mon Sep 17 00:00:00 2001 From: Min-Gu Yoo Date: Sat, 24 Jan 2026 11:14:14 -0800 Subject: [PATCH 06/15] refactor!: replace acquire_bits!/trues!/falses! with Bit sentinel type BREAKING CHANGE: Removed acquire_bits!, trues!, falses! functions. Use the unified API with Bit sentinel type instead: - acquire_bits!(pool, n) -> acquire!(pool, Bit, n) - trues!(pool, dims...) -> ones!(pool, Bit, dims...) - falses!(pool, dims...) -> zeros!(pool, Bit, dims...) Changes: - Add Bit sentinel type for bit-packed boolean storage - Add get_typed_pool!(pool, ::Type{Bit}) dispatch - Add zero(Bit)/one(Bit) for fill operations - Add DisabledPool fallbacks for Bit type - Remove acquire_bits!, trues!, falses! and their _impl! variants - Remove macro transformation for removed functions - Update tests to use new unified API This provides a consistent API pattern across all element types: acquire!(pool, Float64, n) # Vector{Float64} acquire!(pool, Bool, n) # Vector{Bool} acquire!(pool, Bit, n) # BitVector --- src/AdaptiveArrayPools.jl | 2 +- src/acquire.jl | 98 +++--------------------------- src/convenience.jl | 118 ++---------------------------------- src/macros.jl | 25 +------- src/types.jl | 41 +++++++++++++ test/test_bitarray.jl | 124 ++++++++++++++++++++++++-------------- test/test_fixed_slots.jl | 2 +- 7 files changed, 137 insertions(+), 273 deletions(-) diff --git a/src/AdaptiveArrayPools.jl b/src/AdaptiveArrayPools.jl index 1102b80..4378919 100644 --- a/src/AdaptiveArrayPools.jl +++ b/src/AdaptiveArrayPools.jl @@ -7,7 +7,7 @@ export AdaptiveArrayPool, acquire!, unsafe_acquire!, pool_stats, get_task_local_ export acquire_view!, acquire_array! # Explicit naming aliases export zeros!, ones!, similar!, default_eltype # Convenience functions export unsafe_zeros!, unsafe_ones!, unsafe_similar! # Unsafe convenience functions -export acquire_bits!, trues!, falses! # BitVector/BitArray functions +export Bit # Sentinel type for BitVector (use with acquire!, zeros!, ones!) export @with_pool, @maybe_with_pool export USE_POOLING, MAYBE_POOLING_ENABLED, POOL_DEBUG export checkpoint!, rewind!, reset! diff --git a/src/acquire.jl b/src/acquire.jl index 0922e93..befcd1f 100644 --- a/src/acquire.jl +++ b/src/acquire.jl @@ -6,9 +6,13 @@ @inline allocate_vector(::AbstractTypedPool{T,Vector{T}}, n::Int) where {T} = Vector{T}(undef, n) -# BitTypedPool allocates BitVector +# BitTypedPool allocates BitVector (used when acquiring with Bit type) @inline allocate_vector(::BitTypedPool, n::Int) = BitVector(undef, n) +# Bit type returns Bool element type for fill operations (zero/one) +@inline Base.zero(::Type{Bit}) = false +@inline Base.one(::Type{Bit}) = true + # Wrap flat view into N-D array (dispatch point for extensions) @inline function wrap_array(::AbstractTypedPool{T,Vector{T}}, flat_view, dims::NTuple{N,Int}) where {T,N} @@ -415,84 +419,6 @@ const acquire_array! = unsafe_acquire! const _acquire_view_impl! = _acquire_impl! const _acquire_array_impl! = _unsafe_acquire_impl! -# ============================================================================== -# BitVector Acquisition API -# ============================================================================== - -""" - acquire_bits!(pool, n) -> SubArray{Bool,1,BitVector,...} - acquire_bits!(pool, dims...) -> ReshapedArray{Bool,N,...} - acquire_bits!(pool, dims::NTuple{N,Int}) -> ReshapedArray{Bool,N,...} - -Acquire a BitVector-backed boolean array from the pool. - -Returns a view backed by BitVector (1 bit per element), which is ~8x more -memory efficient than `acquire!(pool, Bool, n)` which uses Vector{Bool} -(1 byte per element). - -## Return Types -- **1D**: `SubArray{Bool,1,BitVector,Tuple{UnitRange{Int64}},true}` -- **N-D**: `Base.ReshapedArray{Bool,N,...}` (reshaped view of 1D BitVector) - -## Important Limitation -**`unsafe_acquire!` is NOT supported for BitArray** because Julia's `BitArray` -stores data in a `chunks::Vector{UInt64}` field that cannot be wrapped with -`unsafe_wrap`. Only view-based acquisition is available. - -## Type Compatibility -For downstream code expecting `::BitArray`, use a union type: -```julia -const BitArrayLike{N} = Union{ - BitArray{N}, - SubArray{Bool,N,<:BitArray}, - Base.ReshapedArray{Bool,N,<:SubArray{Bool,1,<:BitArray}} -} -``` - -## Example -```julia -@with_pool pool begin - bv = acquire_bits!(pool, 100) # 1D BitVector view - ba = acquire_bits!(pool, 10, 10) # 2D reshaped view - bt = acquire_bits!(pool, (5, 5, 4)) # 3D via tuple - - bv .= true # Set all bits - ba[1,1] = false -end -``` - -See also: [`trues!`](@ref), [`falses!`](@ref), [`acquire!`](@ref) -""" -@inline function acquire_bits!(pool::AbstractArrayPool, n::Int) - _mark_untracked!(pool) - _acquire_bits_impl!(pool, n) -end - -@inline function acquire_bits!(pool::AbstractArrayPool, dims::Vararg{Int, N}) where {N} - _mark_untracked!(pool) - _acquire_bits_impl!(pool, dims...) -end - -@inline function acquire_bits!(pool::AbstractArrayPool, dims::NTuple{N, Int}) where {N} - _mark_untracked!(pool) - _acquire_bits_impl!(pool, dims...) -end - -# Internal implementation (for macro transformation) -@inline function _acquire_bits_impl!(pool::AbstractArrayPool, n::Int) - return get_view!(pool.bits, n) -end - -@inline function _acquire_bits_impl!(pool::AbstractArrayPool, dims::Vararg{Int, N}) where {N} - total = safe_prod(dims) - flat_view = get_view!(pool.bits, total) - return reshape(flat_view, dims) -end - -@inline function _acquire_bits_impl!(pool::AbstractArrayPool, dims::NTuple{N, Int}) where {N} - _acquire_bits_impl!(pool, dims...) -end - # ============================================================================== # DisabledPool Acquire Fallbacks (pooling disabled with backend context) # ============================================================================== @@ -509,15 +435,14 @@ end @inline unsafe_acquire!(::DisabledPool{:cpu}, ::Type{T}, dims::NTuple{N,Int}) where {T,N} = Array{T,N}(undef, dims) @inline unsafe_acquire!(::DisabledPool{:cpu}, x::AbstractArray) = similar(x) -# --- acquire_bits! for DisabledPool{:cpu} --- -@inline acquire_bits!(::DisabledPool{:cpu}, n::Int) = BitVector(undef, n) -@inline acquire_bits!(::DisabledPool{:cpu}, dims::Vararg{Int,N}) where {N} = BitArray{N}(undef, dims) -@inline acquire_bits!(::DisabledPool{:cpu}, dims::NTuple{N,Int}) where {N} = BitArray{N}(undef, dims) +# --- acquire! for DisabledPool{:cpu} with Bit type (returns BitArray) --- +@inline acquire!(::DisabledPool{:cpu}, ::Type{Bit}, n::Int) = BitVector(undef, n) +@inline acquire!(::DisabledPool{:cpu}, ::Type{Bit}, dims::Vararg{Int,N}) where {N} = BitArray{N}(undef, dims) +@inline acquire!(::DisabledPool{:cpu}, ::Type{Bit}, dims::NTuple{N,Int}) where {N} = BitArray{N}(undef, dims) # --- Generic DisabledPool fallbacks (unknown backend → error) --- @inline acquire!(p::DisabledPool{B}, args...) where {B} = _throw_backend_not_loaded(B) @inline unsafe_acquire!(p::DisabledPool{B}, args...) where {B} = _throw_backend_not_loaded(B) -@inline acquire_bits!(p::DisabledPool{B}, args...) where {B} = _throw_backend_not_loaded(B) # --- _impl! delegators for DisabledPool (macro transformation support) --- # Called when: USE_POOLING=true + @maybe_with_pool + MAYBE_POOLING_ENABLED[]=false @@ -531,8 +456,3 @@ end @inline _unsafe_acquire_impl!(p::DisabledPool, ::Type{T}, dims::Vararg{Int,N}) where {T,N} = unsafe_acquire!(p, T, dims...) @inline _unsafe_acquire_impl!(p::DisabledPool, ::Type{T}, dims::NTuple{N,Int}) where {T,N} = unsafe_acquire!(p, T, dims) @inline _unsafe_acquire_impl!(p::DisabledPool, x::AbstractArray) = unsafe_acquire!(p, x) - -# --- _acquire_bits_impl! delegators for DisabledPool --- -@inline _acquire_bits_impl!(p::DisabledPool, n::Int) = acquire_bits!(p, n) -@inline _acquire_bits_impl!(p::DisabledPool, dims::Vararg{Int,N}) where {N} = acquire_bits!(p, dims...) -@inline _acquire_bits_impl!(p::DisabledPool, dims::NTuple{N,Int}) where {N} = acquire_bits!(p, dims) diff --git a/src/convenience.jl b/src/convenience.jl index 67e4edf..bfa7a4b 100644 --- a/src/convenience.jl +++ b/src/convenience.jl @@ -417,102 +417,6 @@ end _unsafe_acquire_impl!(pool, T, dims...) end -# ============================================================================== -# trues! - Acquire BitArray filled with true values -# ============================================================================== - -""" - trues!(pool, dims...) -> BitArray view - trues!(pool, dims::Tuple) -> BitArray view - -Acquire a BitArray from the pool and fill with `true` values. - -Equivalent to `acquire_bits!(pool, dims...)` followed by `fill!(arr, true)`. - -## Return Type -Returns a view backed by `BitVector`: -- **1D**: `SubArray{Bool,1,BitVector,...}` -- **N-D**: `ReshapedArray{Bool,N,...}` (reshaped view) - -## Example -```julia -@with_pool pool begin - mask = trues!(pool, 100) # 1D, all true - grid = trues!(pool, 10, 10) # 2D, all true -end -``` - -See also: [`falses!`](@ref), [`acquire_bits!`](@ref), [`ones!`](@ref) -""" -@inline function trues!(pool::AbstractArrayPool, dims::Vararg{Int,N}) where {N} - _mark_untracked!(pool) - _trues_impl!(pool, dims...) -end - -@inline function trues!(pool::AbstractArrayPool, dims::NTuple{N,Int}) where {N} - _mark_untracked!(pool) - _trues_impl!(pool, dims...) -end - -# Internal implementation (for macro transformation) -@inline function _trues_impl!(pool::AbstractArrayPool, dims::Vararg{Int,N}) where {N} - arr = _acquire_bits_impl!(pool, dims...) - fill!(arr, true) - arr -end - -@inline function _trues_impl!(pool::AbstractArrayPool, dims::NTuple{N,Int}) where {N} - _trues_impl!(pool, dims...) -end - -# ============================================================================== -# falses! - Acquire BitArray filled with false values -# ============================================================================== - -""" - falses!(pool, dims...) -> BitArray view - falses!(pool, dims::Tuple) -> BitArray view - -Acquire a BitArray from the pool and fill with `false` values. - -Equivalent to `acquire_bits!(pool, dims...)` followed by `fill!(arr, false)`. - -## Return Type -Returns a view backed by `BitVector`: -- **1D**: `SubArray{Bool,1,BitVector,...}` -- **N-D**: `ReshapedArray{Bool,N,...}` (reshaped view) - -## Example -```julia -@with_pool pool begin - mask = falses!(pool, 100) # 1D, all false - grid = falses!(pool, 10, 10) # 2D, all false -end -``` - -See also: [`trues!`](@ref), [`acquire_bits!`](@ref), [`zeros!`](@ref) -""" -@inline function falses!(pool::AbstractArrayPool, dims::Vararg{Int,N}) where {N} - _mark_untracked!(pool) - _falses_impl!(pool, dims...) -end - -@inline function falses!(pool::AbstractArrayPool, dims::NTuple{N,Int}) where {N} - _mark_untracked!(pool) - _falses_impl!(pool, dims...) -end - -# Internal implementation (for macro transformation) -@inline function _falses_impl!(pool::AbstractArrayPool, dims::Vararg{Int,N}) where {N} - arr = _acquire_bits_impl!(pool, dims...) - fill!(arr, false) - arr -end - -@inline function _falses_impl!(pool::AbstractArrayPool, dims::NTuple{N,Int}) where {N} - _falses_impl!(pool, dims...) -end - # ============================================================================== # BackendNotLoadedError - Error for unknown backends # ============================================================================== @@ -573,13 +477,11 @@ end @inline ones!(::DisabledPool{:cpu}, ::Type{T}, dims::NTuple{N,Int}) where {T,N} = ones(T, dims...) @inline ones!(p::DisabledPool{:cpu}, dims::NTuple{N,Int}) where {N} = ones(default_eltype(p), dims...) -# --- trues! for DisabledPool{:cpu} --- -@inline trues!(::DisabledPool{:cpu}, dims::Vararg{Int,N}) where {N} = trues(dims...) -@inline trues!(::DisabledPool{:cpu}, dims::NTuple{N,Int}) where {N} = trues(dims...) - -# --- falses! for DisabledPool{:cpu} --- -@inline falses!(::DisabledPool{:cpu}, dims::Vararg{Int,N}) where {N} = falses(dims...) -@inline falses!(::DisabledPool{:cpu}, dims::NTuple{N,Int}) where {N} = falses(dims...) +# --- zeros!/ones! for DisabledPool{:cpu} with Bit type (returns BitArray) --- +@inline zeros!(::DisabledPool{:cpu}, ::Type{Bit}, dims::Vararg{Int,N}) where {N} = falses(dims...) +@inline zeros!(::DisabledPool{:cpu}, ::Type{Bit}, dims::NTuple{N,Int}) where {N} = falses(dims...) +@inline ones!(::DisabledPool{:cpu}, ::Type{Bit}, dims::Vararg{Int,N}) where {N} = trues(dims...) +@inline ones!(::DisabledPool{:cpu}, ::Type{Bit}, dims::NTuple{N,Int}) where {N} = trues(dims...) # --- similar! for DisabledPool{:cpu} --- @inline similar!(::DisabledPool{:cpu}, x::AbstractArray) = similar(x) @@ -612,8 +514,6 @@ end @inline unsafe_zeros!(p::DisabledPool{B}, args...) where {B} = _throw_backend_not_loaded(B) @inline unsafe_ones!(p::DisabledPool{B}, args...) where {B} = _throw_backend_not_loaded(B) @inline unsafe_similar!(p::DisabledPool{B}, args...) where {B} = _throw_backend_not_loaded(B) -@inline trues!(p::DisabledPool{B}, args...) where {B} = _throw_backend_not_loaded(B) -@inline falses!(p::DisabledPool{B}, args...) where {B} = _throw_backend_not_loaded(B) # ============================================================================== # _impl! Delegators for DisabledPool @@ -659,11 +559,3 @@ end @inline _unsafe_similar_impl!(p::DisabledPool, x::AbstractArray, ::Type{T}) where {T} = unsafe_similar!(p, x, T) @inline _unsafe_similar_impl!(p::DisabledPool, x::AbstractArray, dims::Vararg{Int,N}) where {N} = unsafe_similar!(p, x, dims...) @inline _unsafe_similar_impl!(p::DisabledPool, x::AbstractArray, ::Type{T}, dims::Vararg{Int,N}) where {T,N} = unsafe_similar!(p, x, T, dims...) - -# --- _trues_impl! --- -@inline _trues_impl!(p::DisabledPool, dims::Vararg{Int,N}) where {N} = trues!(p, dims...) -@inline _trues_impl!(p::DisabledPool, dims::NTuple{N,Int}) where {N} = trues!(p, dims) - -# --- _falses_impl! --- -@inline _falses_impl!(p::DisabledPool, dims::Vararg{Int,N}) where {N} = falses!(p, dims...) -@inline _falses_impl!(p::DisabledPool, dims::NTuple{N,Int}) where {N} = falses!(p, dims) diff --git a/src/macros.jl b/src/macros.jl index 1a97751..b0a0425 100644 --- a/src/macros.jl +++ b/src/macros.jl @@ -854,10 +854,6 @@ function _extract_acquire_types(expr, target_pool, types=Set{Any}()) push!(types, Expr(:call, :eltype, expr.args[3])) end end - # acquire_bits!/trues!/falses! - uses bits slot (not get_typed_pool!) - # Mark as :_bits_slot to trigger has_dynamic=true in _filter_static_types - elseif fn in (:acquire_bits!, :trues!, :falses!) || fn_name in (:acquire_bits!, :trues!, :falses!) - push!(types, :_bits_slot) end end end @@ -934,11 +930,7 @@ function _filter_static_types(types, local_vars=Set{Symbol}()) for t in types if t isa Symbol - if t == :_bits_slot - # BitArray functions (acquire_bits!, trues!, falses!) use bits slot - # directly, not via get_typed_pool! - requires full checkpoint - has_dynamic = true - elseif t in local_vars + if t in local_vars # Local variable like T = eltype(x) - defined after checkpoint! # Must fall back to full checkpoint has_dynamic = true @@ -1046,9 +1038,6 @@ const _SIMILAR_IMPL_REF = GlobalRef(@__MODULE__, :_similar_impl!) const _UNSAFE_ZEROS_IMPL_REF = GlobalRef(@__MODULE__, :_unsafe_zeros_impl!) const _UNSAFE_ONES_IMPL_REF = GlobalRef(@__MODULE__, :_unsafe_ones_impl!) const _UNSAFE_SIMILAR_IMPL_REF = GlobalRef(@__MODULE__, :_unsafe_similar_impl!) -const _ACQUIRE_BITS_IMPL_REF = GlobalRef(@__MODULE__, :_acquire_bits_impl!) -const _TRUES_IMPL_REF = GlobalRef(@__MODULE__, :_trues_impl!) -const _FALSES_IMPL_REF = GlobalRef(@__MODULE__, :_falses_impl!) function _transform_acquire_calls(expr, pool_name) if expr isa Expr @@ -1076,12 +1065,6 @@ function _transform_acquire_calls(expr, pool_name) expr = Expr(:call, _UNSAFE_ONES_IMPL_REF, expr.args[2:end]...) elseif fn == :unsafe_similar! expr = Expr(:call, _UNSAFE_SIMILAR_IMPL_REF, expr.args[2:end]...) - elseif fn == :acquire_bits! - expr = Expr(:call, _ACQUIRE_BITS_IMPL_REF, expr.args[2:end]...) - elseif fn == :trues! - expr = Expr(:call, _TRUES_IMPL_REF, expr.args[2:end]...) - elseif fn == :falses! - expr = Expr(:call, _FALSES_IMPL_REF, expr.args[2:end]...) elseif fn isa Expr && fn.head == :. && length(fn.args) >= 2 # Qualified name: AdaptiveArrayPools.acquire! etc. qn = fn.args[end] @@ -1101,12 +1084,6 @@ function _transform_acquire_calls(expr, pool_name) expr = Expr(:call, _UNSAFE_ONES_IMPL_REF, expr.args[2:end]...) elseif qn == QuoteNode(:unsafe_similar!) expr = Expr(:call, _UNSAFE_SIMILAR_IMPL_REF, expr.args[2:end]...) - elseif qn == QuoteNode(:acquire_bits!) - expr = Expr(:call, _ACQUIRE_BITS_IMPL_REF, expr.args[2:end]...) - elseif qn == QuoteNode(:trues!) - expr = Expr(:call, _TRUES_IMPL_REF, expr.args[2:end]...) - elseif qn == QuoteNode(:falses!) - expr = Expr(:call, _FALSES_IMPL_REF, expr.args[2:end]...) end end end diff --git a/src/types.jl b/src/types.jl index d271c4a..1283662 100644 --- a/src/types.jl +++ b/src/types.jl @@ -210,6 +210,46 @@ TypedPool{T}() where {T} = TypedPool{T}( [0] # _checkpoint_depths: sentinel (depth=0 = no checkpoint) ) +# ============================================================================== +# Bit Sentinel Type +# ============================================================================== + +""" + Bit + +Sentinel type for bit-packed boolean storage via `BitVector`. + +Use `Bit` instead of `Bool` in pool operations to get memory-efficient +bit-packed arrays (1 bit per element vs 1 byte for `Vector{Bool}`). + +## Usage +```julia +@with_pool pool begin + # BitVector view (1 bit per element, ~8x memory savings) + bv = acquire!(pool, Bit, 1000) + + # vs Vector{Bool} (1 byte per element) + vb = acquire!(pool, Bool, 1000) + + # Convenience functions work too + mask = zeros!(pool, Bit, 100) # BitVector filled with false + flags = ones!(pool, Bit, 100) # BitVector filled with true +end +``` + +## Return Types +- **1D**: `SubArray{Bool,1,BitVector,...}` +- **N-D**: `ReshapedArray{Bool,N,...}` (reshaped view of 1D BitVector) + +## Limitation +`unsafe_acquire!(pool, Bit, ...)` is **not supported** because Julia's +`BitArray` stores data in immutable `chunks::Vector{UInt64}` that cannot +be wrapped with `unsafe_wrap`. + +See also: [`acquire!`](@ref), [`BitTypedPool`](@ref) +""" +struct Bit end + # ============================================================================== # BitTypedPool - Specialized pool for BitVector/BitArray # ============================================================================== @@ -357,6 +397,7 @@ end @inline get_typed_pool!(p::AdaptiveArrayPool, ::Type{ComplexF64}) = p.complexf64 @inline get_typed_pool!(p::AdaptiveArrayPool, ::Type{ComplexF32}) = p.complexf32 @inline get_typed_pool!(p::AdaptiveArrayPool, ::Type{Bool}) = p.bool +@inline get_typed_pool!(p::AdaptiveArrayPool, ::Type{Bit}) = p.bits # Slow Path: rare types via IdDict @inline function get_typed_pool!(p::AdaptiveArrayPool, ::Type{T}) where {T} diff --git a/test/test_bitarray.jl b/test/test_bitarray.jl index 4a3cf34..39b85de 100644 --- a/test/test_bitarray.jl +++ b/test/test_bitarray.jl @@ -1,5 +1,12 @@ @testset "BitArray Support" begin + @testset "Bit sentinel type" begin + # Bit is exported and usable + @test Bit isa DataType + @test zero(Bit) == false + @test one(Bit) == true + end + @testset "BitTypedPool structure" begin pool = AdaptiveArrayPool() @@ -9,10 +16,10 @@ @test isempty(pool.bits.vectors) end - @testset "acquire_bits! 1D" begin + @testset "acquire!(pool, Bit, n) - 1D" begin pool = AdaptiveArrayPool() - bv = acquire_bits!(pool, 100) + bv = acquire!(pool, Bit, 100) @test length(bv) == 100 @test eltype(bv) == Bool @test bv isa SubArray{Bool, 1, BitVector} @@ -26,7 +33,7 @@ @test count(bv) == 99 # Second acquire - bv2 = acquire_bits!(pool, 50) + bv2 = acquire!(pool, Bit, 50) @test length(bv2) == 50 @test pool.bits.n_active == 2 @@ -36,11 +43,11 @@ @test count(bv) == 99 # bv unchanged end - @testset "acquire_bits! N-D" begin + @testset "acquire!(pool, Bit, dims...) - N-D" begin pool = AdaptiveArrayPool() # 2D - ba2 = acquire_bits!(pool, 10, 10) + ba2 = acquire!(pool, Bit, 10, 10) @test size(ba2) == (10, 10) @test eltype(ba2) == Bool @test ba2 isa Base.ReshapedArray @@ -56,54 +63,54 @@ @test !ba2[2, 2] # 3D - ba3 = acquire_bits!(pool, 4, 5, 3) + ba3 = acquire!(pool, Bit, 4, 5, 3) @test size(ba3) == (4, 5, 3) @test pool.bits.n_active == 2 # Tuple form - ba_tuple = acquire_bits!(pool, (3, 4, 2)) + ba_tuple = acquire!(pool, Bit, (3, 4, 2)) @test size(ba_tuple) == (3, 4, 2) @test pool.bits.n_active == 3 end - @testset "trues!" begin + @testset "ones!(pool, Bit, dims...) - filled with true" begin pool = AdaptiveArrayPool() # 1D - t1 = trues!(pool, 100) + t1 = ones!(pool, Bit, 100) @test length(t1) == 100 @test all(t1) @test pool.bits.n_active == 1 # 2D - t2 = trues!(pool, 10, 10) + t2 = ones!(pool, Bit, 10, 10) @test size(t2) == (10, 10) @test all(t2) @test count(t2) == 100 # Tuple form - t3 = trues!(pool, (5, 5, 4)) + t3 = ones!(pool, Bit, (5, 5, 4)) @test size(t3) == (5, 5, 4) @test all(t3) end - @testset "falses!" begin + @testset "zeros!(pool, Bit, dims...) - filled with false" begin pool = AdaptiveArrayPool() # 1D - f1 = falses!(pool, 100) + f1 = zeros!(pool, Bit, 100) @test length(f1) == 100 @test !any(f1) @test pool.bits.n_active == 1 # 2D - f2 = falses!(pool, 10, 10) + f2 = zeros!(pool, Bit, 10, 10) @test size(f2) == (10, 10) @test !any(f2) @test count(f2) == 0 # Tuple form - f3 = falses!(pool, (5, 5, 4)) + f3 = zeros!(pool, Bit, (5, 5, 4)) @test size(f3) == (5, 5, 4) @test !any(f3) end @@ -111,20 +118,20 @@ @testset "State management" begin # Use @with_pool which manages checkpoint/rewind automatically @with_pool outer_pool begin - bv1 = acquire_bits!(outer_pool, 100) + bv1 = acquire!(outer_pool, Bit, 100) parent1 = parent(bv1) @test outer_pool.bits.n_active == 1 @with_pool inner_pool begin - bv2 = acquire_bits!(inner_pool, 200) + bv2 = acquire!(inner_pool, Bit, 200) @test inner_pool.bits.n_active == 2 end # After inner scope rewind @test outer_pool.bits.n_active == 1 # bv1 should still be valid (same parent BitVector object) - bv3 = acquire_bits!(outer_pool, 150) + bv3 = acquire!(outer_pool, Bit, 150) @test parent(bv1) === parent1 # Same object identity end # Pool goes back to task-local state after scope ends @@ -136,9 +143,9 @@ checkpoint!(pool) @test pool.bits.n_active == 0 - bv1 = acquire_bits!(pool, 100) - t1 = trues!(pool, 50) - f1 = falses!(pool, 50) + bv1 = acquire!(pool, Bit, 100) + t1 = ones!(pool, Bit, 50) + f1 = zeros!(pool, Bit, 50) @test pool.bits.n_active == 3 rewind!(pool) @@ -148,8 +155,8 @@ @testset "reset! and empty!" begin pool = AdaptiveArrayPool() - bv1 = acquire_bits!(pool, 100) - bv2 = acquire_bits!(pool, 200) + bv1 = acquire!(pool, Bit, 100) + bv2 = acquire!(pool, Bit, 200) @test pool.bits.n_active == 2 @test length(pool.bits.vectors) >= 2 @@ -159,43 +166,43 @@ @test length(pool.bits.vectors) >= 2 # vectors preserved # empty! clears everything - bv3 = acquire_bits!(pool, 50) + bv3 = acquire!(pool, Bit, 50) empty!(pool) @test pool.bits.n_active == 0 @test isempty(pool.bits.vectors) end @testset "DisabledPool fallback" begin - # acquire_bits! - bv = acquire_bits!(DISABLED_CPU, 100) + # acquire! with Bit + bv = acquire!(DISABLED_CPU, Bit, 100) @test bv isa BitVector @test length(bv) == 100 # N-D - ba = acquire_bits!(DISABLED_CPU, 10, 10) + ba = acquire!(DISABLED_CPU, Bit, 10, 10) @test ba isa BitArray{2} @test size(ba) == (10, 10) # Tuple form - ba_tuple = acquire_bits!(DISABLED_CPU, (5, 5)) + ba_tuple = acquire!(DISABLED_CPU, Bit, (5, 5)) @test ba_tuple isa BitArray{2} @test size(ba_tuple) == (5, 5) - # trues! - t = trues!(DISABLED_CPU, 50) + # ones! with Bit (like trues) + t = ones!(DISABLED_CPU, Bit, 50) @test t isa BitVector @test all(t) - t2d = trues!(DISABLED_CPU, 5, 5) + t2d = ones!(DISABLED_CPU, Bit, 5, 5) @test t2d isa BitArray{2} @test all(t2d) - # falses! - f = falses!(DISABLED_CPU, 50) + # zeros! with Bit (like falses) + f = zeros!(DISABLED_CPU, Bit, 50) @test f isa BitVector @test !any(f) - f2d = falses!(DISABLED_CPU, 5, 5) + f2d = zeros!(DISABLED_CPU, Bit, 5, 5) @test f2d isa BitArray{2} @test !any(f2d) end @@ -205,7 +212,7 @@ # BitVector should use ~8x less memory than Vector{Bool} # (1 bit vs 1 byte per element) - bv = acquire_bits!(pool, 1000) + bv = acquire!(pool, Bit, 1000) vb = acquire!(pool, Bool, 1000) bv_parent = parent(bv) @@ -219,9 +226,9 @@ @testset "@with_pool macro integration" begin result = @with_pool pool begin - bv = acquire_bits!(pool, 100) - t = trues!(pool, 50) - f = falses!(pool, 50) + bv = acquire!(pool, Bit, 100) + t = ones!(pool, Bit, 50) + f = zeros!(pool, Bit, 50) bv .= true sum_bv = count(bv) @@ -237,7 +244,7 @@ @testset "@maybe_with_pool macro integration" begin # With pooling enabled (default) result1 = @maybe_with_pool pool begin - bv = acquire_bits!(pool, 100) + bv = acquire!(pool, Bit, 100) bv .= true count(bv) end @@ -247,7 +254,7 @@ AdaptiveArrayPools.MAYBE_POOLING_ENABLED[] = false try result2 = @maybe_with_pool pool begin - bv = acquire_bits!(pool, 100) + bv = acquire!(pool, Bit, 100) @test bv isa BitVector # DisabledPool returns BitVector bv .= true count(bv) @@ -261,13 +268,13 @@ @testset "Mixed Bool types" begin pool = AdaptiveArrayPool() - # Vector{Bool} via acquire! + # Vector{Bool} via acquire! with Bool vb = acquire!(pool, Bool, 100) @test vb isa SubArray{Bool, 1, Vector{Bool}} @test pool.bool.n_active == 1 - # BitVector via acquire_bits! - bv = acquire_bits!(pool, 100) + # BitVector via acquire! with Bit + bv = acquire!(pool, Bit, 100) @test bv isa SubArray{Bool, 1, BitVector} @test pool.bits.n_active == 1 @@ -284,11 +291,11 @@ @testset "Nested scopes" begin outer_result = @with_pool outer_pool begin - outer_bv = acquire_bits!(outer_pool, 100) + outer_bv = acquire!(outer_pool, Bit, 100) outer_bv .= true inner_result = @with_pool inner_pool begin - inner_bv = acquire_bits!(inner_pool, 50) + inner_bv = acquire!(inner_pool, Bit, 50) inner_bv .= false count(inner_bv) end @@ -301,4 +308,31 @@ @test outer_result == (100, 0) end + @testset "API consistency" begin + # Verify the API is consistent across types + pool = AdaptiveArrayPool() + + # All these should work with the same pattern + v_f64 = acquire!(pool, Float64, 10) + v_i32 = acquire!(pool, Int32, 10) + v_bool = acquire!(pool, Bool, 10) + v_bit = acquire!(pool, Bit, 10) + + @test eltype(v_f64) == Float64 + @test eltype(v_i32) == Int32 + @test eltype(v_bool) == Bool + @test eltype(v_bit) == Bool + + # zeros!/ones! work consistently + z_f64 = zeros!(pool, Float64, 10) + z_bit = zeros!(pool, Bit, 10) + o_f64 = ones!(pool, Float64, 10) + o_bit = ones!(pool, Bit, 10) + + @test all(z_f64 .== 0.0) + @test !any(z_bit) + @test all(o_f64 .== 1.0) + @test all(o_bit) + end + end # BitArray Support diff --git a/test/test_fixed_slots.jl b/test/test_fixed_slots.jl index 088b12a..d21a63b 100644 --- a/test/test_fixed_slots.jl +++ b/test/test_fixed_slots.jl @@ -108,7 +108,7 @@ acquire!(pool, ComplexF64, 10) acquire!(pool, ComplexF32, 10) acquire!(pool, Bool, 10) - acquire_bits!(pool, 10) # BitTypedPool + acquire!(pool, Bit, 10) # BitTypedPool # Verify all pools have active arrays for field in FIXED_SLOT_FIELDS From 0ce0f5faef301a93852c681c0b7c064d745ffdfb Mon Sep 17 00:00:00 2001 From: Min-Gu Yoo Date: Sat, 24 Jan 2026 11:48:20 -0800 Subject: [PATCH 07/15] fix: add clear error for unsafe_acquire!(pool, Bit, ...) - Add _throw_bit_unsafe_error() with helpful message explaining why BitArray cannot use unsafe_wrap (immutable chunks) - Add explicit _unsafe_acquire_impl! dispatches for Bit type that fail-fast before any work is done - Add test verifying error is thrown with helpful message - Fix unused argument warnings in DisabledPool fallbacks --- src/acquire.jl | 19 +++++++++++++++++-- test/test_bitarray.jl | 18 ++++++++++++++++++ 2 files changed, 35 insertions(+), 2 deletions(-) diff --git a/src/acquire.jl b/src/acquire.jl index befcd1f..b8ddcf6 100644 --- a/src/acquire.jl +++ b/src/acquire.jl @@ -19,6 +19,16 @@ unsafe_wrap(Array{T,N}, pointer(flat_view), dims) end +# BitTypedPool cannot use unsafe_wrap - throw clear error +# Called from _unsafe_acquire_impl! dispatches for Bit type +@noinline function _throw_bit_unsafe_error() + throw(ArgumentError( + "unsafe_acquire!(pool, Bit, ...) is not supported. " * + "BitArray stores data in immutable chunks::Vector{UInt64} that cannot be wrapped with unsafe_wrap. " * + "Use acquire!(pool, Bit, ...) instead, which returns a view." + )) +end + # ============================================================================== # Helper: Overflow-Safe Product # ============================================================================== @@ -235,6 +245,11 @@ end # Similar-style @inline _unsafe_acquire_impl!(pool::AbstractArrayPool, x::AbstractArray) = _unsafe_acquire_impl!(pool, eltype(x), size(x)) +# Bit type: unsafe_acquire! not supported (throw clear error early) +@inline _unsafe_acquire_impl!(::AbstractArrayPool, ::Type{Bit}, ::Int) = _throw_bit_unsafe_error() +@inline _unsafe_acquire_impl!(::AbstractArrayPool, ::Type{Bit}, ::Vararg{Int,N}) where {N} = _throw_bit_unsafe_error() +@inline _unsafe_acquire_impl!(::AbstractArrayPool, ::Type{Bit}, ::NTuple{N,Int}) where {N} = _throw_bit_unsafe_error() + # ============================================================================== # Acquisition API (User-facing with untracked marking) # ============================================================================== @@ -441,8 +456,8 @@ const _acquire_array_impl! = _unsafe_acquire_impl! @inline acquire!(::DisabledPool{:cpu}, ::Type{Bit}, dims::NTuple{N,Int}) where {N} = BitArray{N}(undef, dims) # --- Generic DisabledPool fallbacks (unknown backend → error) --- -@inline acquire!(p::DisabledPool{B}, args...) where {B} = _throw_backend_not_loaded(B) -@inline unsafe_acquire!(p::DisabledPool{B}, args...) where {B} = _throw_backend_not_loaded(B) +@inline acquire!(::DisabledPool{B}, _args...) where {B} = _throw_backend_not_loaded(B) +@inline unsafe_acquire!(::DisabledPool{B}, _args...) where {B} = _throw_backend_not_loaded(B) # --- _impl! delegators for DisabledPool (macro transformation support) --- # Called when: USE_POOLING=true + @maybe_with_pool + MAYBE_POOLING_ENABLED[]=false diff --git a/test/test_bitarray.jl b/test/test_bitarray.jl index 39b85de..a748d9b 100644 --- a/test/test_bitarray.jl +++ b/test/test_bitarray.jl @@ -308,6 +308,24 @@ @test outer_result == (100, 0) end + @testset "unsafe_acquire! not supported" begin + pool = AdaptiveArrayPool() + + # unsafe_acquire! with Bit should throw a clear error + @test_throws ArgumentError unsafe_acquire!(pool, Bit, 100) + @test_throws ArgumentError unsafe_acquire!(pool, Bit, 10, 10) + + # Verify the error message is helpful + try + unsafe_acquire!(pool, Bit, 100) + catch e + @test e isa ArgumentError + @test occursin("unsafe_acquire!", e.msg) + @test occursin("Bit", e.msg) + @test occursin("acquire!", e.msg) # Suggests alternative + end + end + @testset "API consistency" begin # Verify the API is consistent across types pool = AdaptiveArrayPool() From dc9afee4bcac6d3bd74db431fcef1837ca6ab261 Mon Sep 17 00:00:00 2001 From: Min-Gu Yoo Date: Sat, 24 Jan 2026 15:34:30 -0800 Subject: [PATCH 08/15] feat: update acquisition methods for BitArray and enhance test coverage --- src/types.jl | 8 ++++---- test/test_bitarray.jl | 27 +++++++++++++++++++++++++-- test/test_coverage.jl | 10 ++++++++++ test/test_fixed_slots.jl | 2 +- 4 files changed, 40 insertions(+), 7 deletions(-) diff --git a/src/types.jl b/src/types.jl index 1283662..2b1a070 100644 --- a/src/types.jl +++ b/src/types.jl @@ -265,7 +265,7 @@ this pool stores `BitVector` (1 bit per element, ~8x memory efficiency). ## Important Limitation **`unsafe_acquire!` is NOT supported for BitArray** because Julia's `BitArray` stores data in a `chunks::Vector{UInt64}` field that cannot be wrapped with -`unsafe_wrap`. Only view-based acquisition via `acquire_bits!` is available. +`unsafe_wrap`. Only view-based acquisition via `acquire!(pool, Bit, ...)` is available. ## Fields - `vectors`: Backing `BitVector` storage @@ -278,14 +278,14 @@ stores data in a `chunks::Vector{UInt64}` field that cannot be wrapped with ## Usage ```julia @with_pool pool begin - bv = acquire_bits!(pool, 100) # SubArray{Bool,1,BitVector,...} - ba = acquire_bits!(pool, 10, 10) # ReshapedArray{Bool,2,...} + bv = acquire!(pool, Bit, 100) # SubArray{Bool,1,BitVector,...} + ba = acquire!(pool, Bit, 10, 10) # ReshapedArray{Bool,2,...} t = trues!(pool, 50) # Filled with true f = falses!(pool, 50) # Filled with false end ``` -See also: [`acquire_bits!`](@ref), [`trues!`](@ref), [`falses!`](@ref) +See also: [`trues!`](@ref), [`falses!`](@ref) """ mutable struct BitTypedPool <: AbstractTypedPool{Bool, BitVector} # --- Storage --- diff --git a/test/test_bitarray.jl b/test/test_bitarray.jl index a748d9b..edca92a 100644 --- a/test/test_bitarray.jl +++ b/test/test_bitarray.jl @@ -3,8 +3,18 @@ @testset "Bit sentinel type" begin # Bit is exported and usable @test Bit isa DataType - @test zero(Bit) == false - @test one(Bit) == true + + # zero(Bit) and one(Bit) are defined for fill operations + # These are used by zeros!(pool, Bit, ...) and ones!(pool, Bit, ...) + @test zero(Bit) === false + @test one(Bit) === true + + # Verify these work with fill! + bv = BitVector(undef, 10) + fill!(bv, zero(Bit)) + @test !any(bv) + fill!(bv, one(Bit)) + @test all(bv) end @testset "BitTypedPool structure" begin @@ -197,6 +207,11 @@ @test t2d isa BitArray{2} @test all(t2d) + # ones! with Bit - Tuple form (covers convenience.jl:484) + t_tuple = ones!(DISABLED_CPU, Bit, (4, 4)) + @test t_tuple isa BitArray{2} + @test all(t_tuple) + # zeros! with Bit (like falses) f = zeros!(DISABLED_CPU, Bit, 50) @test f isa BitVector @@ -205,6 +220,11 @@ f2d = zeros!(DISABLED_CPU, Bit, 5, 5) @test f2d isa BitArray{2} @test !any(f2d) + + # zeros! with Bit - Tuple form (covers convenience.jl:482) + f_tuple = zeros!(DISABLED_CPU, Bit, (4, 4)) + @test f_tuple isa BitArray{2} + @test !any(f_tuple) end @testset "Memory efficiency vs Vector{Bool}" begin @@ -315,6 +335,9 @@ @test_throws ArgumentError unsafe_acquire!(pool, Bit, 100) @test_throws ArgumentError unsafe_acquire!(pool, Bit, 10, 10) + # Tuple form (covers acquire.jl:251) + @test_throws ArgumentError unsafe_acquire!(pool, Bit, (10, 10)) + # Verify the error message is helpful try unsafe_acquire!(pool, Bit, 100) diff --git a/test/test_coverage.jl b/test/test_coverage.jl index 581abc7..5262b42 100644 --- a/test/test_coverage.jl +++ b/test/test_coverage.jl @@ -4,6 +4,16 @@ @testset "Coverage Tests" begin + @testset "pooling_enabled dispatch" begin + # AbstractArrayPool dispatch (types.jl:137) + pool = AdaptiveArrayPool() + @test pooling_enabled(pool) === true + + # DisabledPool dispatch (types.jl:138) + @test pooling_enabled(DISABLED_CPU) === false + @test pooling_enabled(DisabledPool{:cuda}()) === false + end + @testset "DisabledPool convenience functions" begin pool = DISABLED_CPU diff --git a/test/test_fixed_slots.jl b/test/test_fixed_slots.jl index d21a63b..e4a2c93 100644 --- a/test/test_fixed_slots.jl +++ b/test/test_fixed_slots.jl @@ -100,7 +100,7 @@ @testset "Integration: empty! Uses foreach_fixed_slot" begin pool = AdaptiveArrayPool() - # Acquire arrays of different types (including BitVector via acquire_bits!) + # Acquire arrays of different types (including BitVector) acquire!(pool, Float64, 10) acquire!(pool, Float32, 10) acquire!(pool, Int64, 10) From eee17612996f4cbd1dfcd02b14b77c6ceb08548e Mon Sep 17 00:00:00 2001 From: Min-Gu Yoo Date: Sat, 24 Jan 2026 15:36:56 -0800 Subject: [PATCH 09/15] fix: correct comment for Bit sentinel type in exports --- src/AdaptiveArrayPools.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/AdaptiveArrayPools.jl b/src/AdaptiveArrayPools.jl index 4378919..9873cea 100644 --- a/src/AdaptiveArrayPools.jl +++ b/src/AdaptiveArrayPools.jl @@ -7,7 +7,7 @@ export AdaptiveArrayPool, acquire!, unsafe_acquire!, pool_stats, get_task_local_ export acquire_view!, acquire_array! # Explicit naming aliases export zeros!, ones!, similar!, default_eltype # Convenience functions export unsafe_zeros!, unsafe_ones!, unsafe_similar! # Unsafe convenience functions -export Bit # Sentinel type for BitVector (use with acquire!, zeros!, ones!) +export Bit # Sentinel type for BitArray (use with acquire!, zeros!, ones!) export @with_pool, @maybe_with_pool export USE_POOLING, MAYBE_POOLING_ENABLED, POOL_DEBUG export checkpoint!, rewind!, reset! From 58faf65c93b44ce956abdaf575b7210d1e1bde02 Mon Sep 17 00:00:00 2001 From: Min-Gu Yoo Date: Sat, 24 Jan 2026 16:01:45 -0800 Subject: [PATCH 10/15] feat: add trues!/falses! convenience functions for BitArray Add back trues! and falses! as convenience wrappers that mirror Julia's built-in trues() and falses() functions for BitArray creation. Changes: - Add trues!(pool, dims...) as wrapper for ones!(pool, Bit, dims...) - Add falses!(pool, dims...) as wrapper for zeros!(pool, Bit, dims...) - Add _trues_impl!/_falses_impl! for macro transformation - Add DisabledPool fallbacks (trues!/falses! -> trues/falses) - Add macro transformation support in _transform_acquire_calls - Add type extraction for trues!/falses! (always Bit type) - Add comprehensive tests including macro integration This provides both API styles: - Original Julia-style: trues!(pool, dims...) / falses!(pool, dims...) - Unified Bit sentinel: ones!(pool, Bit, dims...) / zeros!(pool, Bit, dims...) --- src/AdaptiveArrayPools.jl | 4 +- src/convenience.jl | 76 +++++++++++++++++++++++++ src/macros.jl | 13 +++++ test/test_bitarray.jl | 117 ++++++++++++++++++++++++++++++++++++++ 4 files changed, 208 insertions(+), 2 deletions(-) diff --git a/src/AdaptiveArrayPools.jl b/src/AdaptiveArrayPools.jl index 9873cea..61f691a 100644 --- a/src/AdaptiveArrayPools.jl +++ b/src/AdaptiveArrayPools.jl @@ -5,9 +5,9 @@ using Printf # Public API export AdaptiveArrayPool, acquire!, unsafe_acquire!, pool_stats, get_task_local_pool export acquire_view!, acquire_array! # Explicit naming aliases -export zeros!, ones!, similar!, default_eltype # Convenience functions +export zeros!, ones!, trues!, falses!, similar!, default_eltype # Convenience functions export unsafe_zeros!, unsafe_ones!, unsafe_similar! # Unsafe convenience functions -export Bit # Sentinel type for BitArray (use with acquire!, zeros!, ones!) +export Bit # Sentinel type for BitArray (use with acquire!, trues!, falses!) export @with_pool, @maybe_with_pool export USE_POOLING, MAYBE_POOLING_ENABLED, POOL_DEBUG export checkpoint!, rewind!, reset! diff --git a/src/convenience.jl b/src/convenience.jl index bfa7a4b..b0587dd 100644 --- a/src/convenience.jl +++ b/src/convenience.jl @@ -150,6 +150,66 @@ end _ones_impl!(pool, default_eltype(pool), dims...) end +# ============================================================================== +# trues! - Acquire BitArray filled with true from pool +# ============================================================================== + +""" + trues!(pool, dims...) -> BitArray view + trues!(pool, dims::Tuple) -> BitArray view + +Acquire a bit-packed boolean array filled with `true` from the pool. + +This is a convenience wrapper for `ones!(pool, Bit, dims...)`. +Uses ~8x less memory than `ones!(pool, Bool, dims...)`. + +## Example +```julia +@with_pool pool begin + bv = trues!(pool, 100) # BitVector-backed view, all true + bm = trues!(pool, 10, 10) # BitMatrix-backed reshaped array +end +``` + +See also: [`falses!`](@ref), [`ones!`](@ref), [`acquire!`](@ref) +""" +@inline trues!(pool::AbstractArrayPool, dims::Vararg{Int,N}) where {N} = ones!(pool, Bit, dims...) +@inline trues!(pool::AbstractArrayPool, dims::NTuple{N,Int}) where {N} = ones!(pool, Bit, dims...) + +# Internal implementation (for macro transformation) +@inline _trues_impl!(pool::AbstractArrayPool, dims::Vararg{Int,N}) where {N} = _ones_impl!(pool, Bit, dims...) +@inline _trues_impl!(pool::AbstractArrayPool, dims::NTuple{N,Int}) where {N} = _ones_impl!(pool, Bit, dims...) + +# ============================================================================== +# falses! - Acquire BitArray filled with false from pool +# ============================================================================== + +""" + falses!(pool, dims...) -> BitArray view + falses!(pool, dims::Tuple) -> BitArray view + +Acquire a bit-packed boolean array filled with `false` from the pool. + +This is a convenience wrapper for `zeros!(pool, Bit, dims...)`. +Uses ~8x less memory than `zeros!(pool, Bool, dims...)`. + +## Example +```julia +@with_pool pool begin + bv = falses!(pool, 100) # BitVector-backed view, all false + bm = falses!(pool, 10, 10) # BitMatrix-backed reshaped array +end +``` + +See also: [`trues!`](@ref), [`zeros!`](@ref), [`acquire!`](@ref) +""" +@inline falses!(pool::AbstractArrayPool, dims::Vararg{Int,N}) where {N} = zeros!(pool, Bit, dims...) +@inline falses!(pool::AbstractArrayPool, dims::NTuple{N,Int}) where {N} = zeros!(pool, Bit, dims...) + +# Internal implementation (for macro transformation) +@inline _falses_impl!(pool::AbstractArrayPool, dims::Vararg{Int,N}) where {N} = _zeros_impl!(pool, Bit, dims...) +@inline _falses_impl!(pool::AbstractArrayPool, dims::NTuple{N,Int}) where {N} = _zeros_impl!(pool, Bit, dims...) + # ============================================================================== # similar! - Acquire arrays with same type/size as template # ============================================================================== @@ -483,6 +543,12 @@ end @inline ones!(::DisabledPool{:cpu}, ::Type{Bit}, dims::Vararg{Int,N}) where {N} = trues(dims...) @inline ones!(::DisabledPool{:cpu}, ::Type{Bit}, dims::NTuple{N,Int}) where {N} = trues(dims...) +# --- trues!/falses! for DisabledPool{:cpu} --- +@inline trues!(::DisabledPool{:cpu}, dims::Vararg{Int,N}) where {N} = trues(dims...) +@inline trues!(::DisabledPool{:cpu}, dims::NTuple{N,Int}) where {N} = trues(dims...) +@inline falses!(::DisabledPool{:cpu}, dims::Vararg{Int,N}) where {N} = falses(dims...) +@inline falses!(::DisabledPool{:cpu}, dims::NTuple{N,Int}) where {N} = falses(dims...) + # --- similar! for DisabledPool{:cpu} --- @inline similar!(::DisabledPool{:cpu}, x::AbstractArray) = similar(x) @inline similar!(::DisabledPool{:cpu}, x::AbstractArray, ::Type{T}) where {T} = similar(x, T) @@ -510,6 +576,8 @@ end # --- Generic DisabledPool fallbacks (unknown backend → error) --- @inline zeros!(p::DisabledPool{B}, args...) where {B} = _throw_backend_not_loaded(B) @inline ones!(p::DisabledPool{B}, args...) where {B} = _throw_backend_not_loaded(B) +@inline trues!(p::DisabledPool{B}, args...) where {B} = _throw_backend_not_loaded(B) +@inline falses!(p::DisabledPool{B}, args...) where {B} = _throw_backend_not_loaded(B) @inline similar!(p::DisabledPool{B}, args...) where {B} = _throw_backend_not_loaded(B) @inline unsafe_zeros!(p::DisabledPool{B}, args...) where {B} = _throw_backend_not_loaded(B) @inline unsafe_ones!(p::DisabledPool{B}, args...) where {B} = _throw_backend_not_loaded(B) @@ -536,6 +604,14 @@ end @inline _ones_impl!(p::DisabledPool, ::Type{T}, dims::NTuple{N,Int}) where {T,N} = ones!(p, T, dims) @inline _ones_impl!(p::DisabledPool, dims::NTuple{N,Int}) where {N} = ones!(p, dims) +# --- _trues_impl! --- +@inline _trues_impl!(p::DisabledPool, dims::Vararg{Int,N}) where {N} = trues!(p, dims...) +@inline _trues_impl!(p::DisabledPool, dims::NTuple{N,Int}) where {N} = trues!(p, dims) + +# --- _falses_impl! --- +@inline _falses_impl!(p::DisabledPool, dims::Vararg{Int,N}) where {N} = falses!(p, dims...) +@inline _falses_impl!(p::DisabledPool, dims::NTuple{N,Int}) where {N} = falses!(p, dims) + # --- _similar_impl! --- @inline _similar_impl!(p::DisabledPool, x::AbstractArray) = similar!(p, x) @inline _similar_impl!(p::DisabledPool, x::AbstractArray, ::Type{T}) where {T} = similar!(p, x, T) diff --git a/src/macros.jl b/src/macros.jl index b0a0425..d79a65e 100644 --- a/src/macros.jl +++ b/src/macros.jl @@ -826,6 +826,9 @@ function _extract_acquire_types(expr, target_pool, types=Set{Any}()) # acquire!(pool, x) - similar-style form push!(types, Expr(:call, :eltype, expr.args[3])) end + # trues!/falses! (always uses Bit type) + elseif fn in (:trues!, :falses!) || fn_name in (:trues!, :falses!) + push!(types, :Bit) # zeros!/ones!/unsafe_zeros!/unsafe_ones! elseif fn in (:zeros!, :ones!, :unsafe_zeros!, :unsafe_ones!) || fn_name in (:zeros!, :ones!, :unsafe_zeros!, :unsafe_ones!) if nargs >= 3 @@ -1034,6 +1037,8 @@ const _ACQUIRE_IMPL_REF = GlobalRef(@__MODULE__, :_acquire_impl!) const _UNSAFE_ACQUIRE_IMPL_REF = GlobalRef(@__MODULE__, :_unsafe_acquire_impl!) const _ZEROS_IMPL_REF = GlobalRef(@__MODULE__, :_zeros_impl!) const _ONES_IMPL_REF = GlobalRef(@__MODULE__, :_ones_impl!) +const _TRUES_IMPL_REF = GlobalRef(@__MODULE__, :_trues_impl!) +const _FALSES_IMPL_REF = GlobalRef(@__MODULE__, :_falses_impl!) const _SIMILAR_IMPL_REF = GlobalRef(@__MODULE__, :_similar_impl!) const _UNSAFE_ZEROS_IMPL_REF = GlobalRef(@__MODULE__, :_unsafe_zeros_impl!) const _UNSAFE_ONES_IMPL_REF = GlobalRef(@__MODULE__, :_unsafe_ones_impl!) @@ -1057,6 +1062,10 @@ function _transform_acquire_calls(expr, pool_name) expr = Expr(:call, _ZEROS_IMPL_REF, expr.args[2:end]...) elseif fn == :ones! expr = Expr(:call, _ONES_IMPL_REF, expr.args[2:end]...) + elseif fn == :trues! + expr = Expr(:call, _TRUES_IMPL_REF, expr.args[2:end]...) + elseif fn == :falses! + expr = Expr(:call, _FALSES_IMPL_REF, expr.args[2:end]...) elseif fn == :similar! expr = Expr(:call, _SIMILAR_IMPL_REF, expr.args[2:end]...) elseif fn == :unsafe_zeros! @@ -1076,6 +1085,10 @@ function _transform_acquire_calls(expr, pool_name) expr = Expr(:call, _ZEROS_IMPL_REF, expr.args[2:end]...) elseif qn == QuoteNode(:ones!) expr = Expr(:call, _ONES_IMPL_REF, expr.args[2:end]...) + elseif qn == QuoteNode(:trues!) + expr = Expr(:call, _TRUES_IMPL_REF, expr.args[2:end]...) + elseif qn == QuoteNode(:falses!) + expr = Expr(:call, _FALSES_IMPL_REF, expr.args[2:end]...) elseif qn == QuoteNode(:similar!) expr = Expr(:call, _SIMILAR_IMPL_REF, expr.args[2:end]...) elseif qn == QuoteNode(:unsafe_zeros!) diff --git a/test/test_bitarray.jl b/test/test_bitarray.jl index edca92a..598b61b 100644 --- a/test/test_bitarray.jl +++ b/test/test_bitarray.jl @@ -125,6 +125,60 @@ @test !any(f3) end + @testset "trues!(pool, dims...) - convenience for BitArray filled with true" begin + pool = AdaptiveArrayPool() + + # 1D + t1 = trues!(pool, 100) + @test length(t1) == 100 + @test all(t1) + @test eltype(t1) == Bool + @test pool.bits.n_active == 1 + + # 2D + t2 = trues!(pool, 10, 10) + @test size(t2) == (10, 10) + @test all(t2) + @test count(t2) == 100 + + # Tuple form + t3 = trues!(pool, (5, 5, 4)) + @test size(t3) == (5, 5, 4) + @test all(t3) + + # Equivalent to ones!(pool, Bit, ...) + t4 = trues!(pool, 50) + t5 = ones!(pool, Bit, 50) + @test all(t4 .== t5) + end + + @testset "falses!(pool, dims...) - convenience for BitArray filled with false" begin + pool = AdaptiveArrayPool() + + # 1D + f1 = falses!(pool, 100) + @test length(f1) == 100 + @test !any(f1) + @test eltype(f1) == Bool + @test pool.bits.n_active == 1 + + # 2D + f2 = falses!(pool, 10, 10) + @test size(f2) == (10, 10) + @test !any(f2) + @test count(f2) == 0 + + # Tuple form + f3 = falses!(pool, (5, 5, 4)) + @test size(f3) == (5, 5, 4) + @test !any(f3) + + # Equivalent to zeros!(pool, Bit, ...) + f4 = falses!(pool, 50) + f5 = zeros!(pool, Bit, 50) + @test all(f4 .== f5) + end + @testset "State management" begin # Use @with_pool which manages checkpoint/rewind automatically @with_pool outer_pool begin @@ -225,6 +279,32 @@ f_tuple = zeros!(DISABLED_CPU, Bit, (4, 4)) @test f_tuple isa BitArray{2} @test !any(f_tuple) + + # trues! (convenience for BitVector filled with true) + t_trues = trues!(DISABLED_CPU, 50) + @test t_trues isa BitVector + @test all(t_trues) + + t_trues_2d = trues!(DISABLED_CPU, 5, 5) + @test t_trues_2d isa BitArray{2} + @test all(t_trues_2d) + + t_trues_tuple = trues!(DISABLED_CPU, (4, 4)) + @test t_trues_tuple isa BitArray{2} + @test all(t_trues_tuple) + + # falses! (convenience for BitVector filled with false) + f_falses = falses!(DISABLED_CPU, 50) + @test f_falses isa BitVector + @test !any(f_falses) + + f_falses_2d = falses!(DISABLED_CPU, 5, 5) + @test f_falses_2d isa BitArray{2} + @test !any(f_falses_2d) + + f_falses_tuple = falses!(DISABLED_CPU, (4, 4)) + @test f_falses_tuple isa BitArray{2} + @test !any(f_falses_tuple) end @testset "Memory efficiency vs Vector{Bool}" begin @@ -259,6 +339,19 @@ end @test result == (100, 50, 0) + + # Test trues!/falses! within @with_pool + result2 = @with_pool pool begin + t = trues!(pool, 100) + f = falses!(pool, 50) + + @test all(t) + @test !any(f) + + (count(t), count(f)) + end + + @test result2 == (100, 0) end @testset "@maybe_with_pool macro integration" begin @@ -280,6 +373,30 @@ count(bv) end @test result2 == 100 + + # Test trues!/falses! with pooling disabled + result3 = @maybe_with_pool pool begin + t = trues!(pool, 50) + f = falses!(pool, 30) + @test t isa BitVector # Falls back to Julia's trues() + @test f isa BitVector # Falls back to Julia's falses() + @test all(t) + @test !any(f) + (count(t), count(f)) + end + @test result3 == (50, 0) + + # Test ones!/zeros! with Bit type, pooling disabled + result4 = @maybe_with_pool pool begin + o = ones!(pool, Bit, 40) + z = zeros!(pool, Bit, 20) + @test o isa BitVector # Falls back to Julia's trues() + @test z isa BitVector # Falls back to Julia's falses() + @test all(o) + @test !any(z) + (count(o), count(z)) + end + @test result4 == (40, 0) finally AdaptiveArrayPools.MAYBE_POOLING_ENABLED[] = true end From 1d76f3b409f7960dadd794475dfddf0fd61bc1a4 Mon Sep 17 00:00:00 2001 From: Min-Gu Yoo Date: Sat, 24 Jan 2026 16:10:12 -0800 Subject: [PATCH 11/15] refactor: make trues!/falses! primary implementations for BitArray Invert the delegation relationship so that trues!/falses! are the canonical BitArray functions (matching Julia's native API), and ones!/zeros! with Bit type delegate to them. Changes: - trues!/falses! now have their own implementations (acquire + fill) - ones!(pool, Bit, ...) delegates to trues!(pool, ...) - zeros!(pool, Bit, ...) delegates to falses!(pool, ...) - Update docstrings to reflect new primary status This follows Julia's semantics where trues/falses are the native BitArray creation functions, not ones/zeros. --- src/convenience.jl | 52 +++++++++++++++++++++++++++++++++++++--------- 1 file changed, 42 insertions(+), 10 deletions(-) diff --git a/src/convenience.jl b/src/convenience.jl index b0587dd..a8dd0ca 100644 --- a/src/convenience.jl +++ b/src/convenience.jl @@ -83,6 +83,12 @@ end _zeros_impl!(pool, default_eltype(pool), dims...) end +# Bit type specialization: zeros!(pool, Bit, ...) delegates to falses!(pool, ...) +@inline zeros!(pool::AbstractArrayPool, ::Type{Bit}, dims::Vararg{Int,N}) where {N} = falses!(pool, dims...) +@inline zeros!(pool::AbstractArrayPool, ::Type{Bit}, dims::NTuple{N,Int}) where {N} = falses!(pool, dims) +@inline _zeros_impl!(pool::AbstractArrayPool, ::Type{Bit}, dims::Vararg{Int,N}) where {N} = _falses_impl!(pool, dims...) +@inline _zeros_impl!(pool::AbstractArrayPool, ::Type{Bit}, dims::NTuple{N,Int}) where {N} = _falses_impl!(pool, dims) + # ============================================================================== # ones! - Acquire one-initialized arrays from pool # ============================================================================== @@ -150,6 +156,12 @@ end _ones_impl!(pool, default_eltype(pool), dims...) end +# Bit type specialization: ones!(pool, Bit, ...) delegates to trues!(pool, ...) +@inline ones!(pool::AbstractArrayPool, ::Type{Bit}, dims::Vararg{Int,N}) where {N} = trues!(pool, dims...) +@inline ones!(pool::AbstractArrayPool, ::Type{Bit}, dims::NTuple{N,Int}) where {N} = trues!(pool, dims) +@inline _ones_impl!(pool::AbstractArrayPool, ::Type{Bit}, dims::Vararg{Int,N}) where {N} = _trues_impl!(pool, dims...) +@inline _ones_impl!(pool::AbstractArrayPool, ::Type{Bit}, dims::NTuple{N,Int}) where {N} = _trues_impl!(pool, dims) + # ============================================================================== # trues! - Acquire BitArray filled with true from pool # ============================================================================== @@ -160,7 +172,7 @@ end Acquire a bit-packed boolean array filled with `true` from the pool. -This is a convenience wrapper for `ones!(pool, Bit, dims...)`. +Equivalent to Julia's `trues(dims...)` but using pooled memory. Uses ~8x less memory than `ones!(pool, Bool, dims...)`. ## Example @@ -173,12 +185,22 @@ end See also: [`falses!`](@ref), [`ones!`](@ref), [`acquire!`](@ref) """ -@inline trues!(pool::AbstractArrayPool, dims::Vararg{Int,N}) where {N} = ones!(pool, Bit, dims...) -@inline trues!(pool::AbstractArrayPool, dims::NTuple{N,Int}) where {N} = ones!(pool, Bit, dims...) +@inline function trues!(pool::AbstractArrayPool, dims::Vararg{Int,N}) where {N} + _mark_untracked!(pool) + _trues_impl!(pool, dims...) +end +@inline function trues!(pool::AbstractArrayPool, dims::NTuple{N,Int}) where {N} + _mark_untracked!(pool) + _trues_impl!(pool, dims...) +end # Internal implementation (for macro transformation) -@inline _trues_impl!(pool::AbstractArrayPool, dims::Vararg{Int,N}) where {N} = _ones_impl!(pool, Bit, dims...) -@inline _trues_impl!(pool::AbstractArrayPool, dims::NTuple{N,Int}) where {N} = _ones_impl!(pool, Bit, dims...) +@inline function _trues_impl!(pool::AbstractArrayPool, dims::Vararg{Int,N}) where {N} + arr = _acquire_impl!(pool, Bit, dims...) + fill!(arr, true) + arr +end +@inline _trues_impl!(pool::AbstractArrayPool, dims::NTuple{N,Int}) where {N} = _trues_impl!(pool, dims...) # ============================================================================== # falses! - Acquire BitArray filled with false from pool @@ -190,7 +212,7 @@ See also: [`falses!`](@ref), [`ones!`](@ref), [`acquire!`](@ref) Acquire a bit-packed boolean array filled with `false` from the pool. -This is a convenience wrapper for `zeros!(pool, Bit, dims...)`. +Equivalent to Julia's `falses(dims...)` but using pooled memory. Uses ~8x less memory than `zeros!(pool, Bool, dims...)`. ## Example @@ -203,12 +225,22 @@ end See also: [`trues!`](@ref), [`zeros!`](@ref), [`acquire!`](@ref) """ -@inline falses!(pool::AbstractArrayPool, dims::Vararg{Int,N}) where {N} = zeros!(pool, Bit, dims...) -@inline falses!(pool::AbstractArrayPool, dims::NTuple{N,Int}) where {N} = zeros!(pool, Bit, dims...) +@inline function falses!(pool::AbstractArrayPool, dims::Vararg{Int,N}) where {N} + _mark_untracked!(pool) + _falses_impl!(pool, dims...) +end +@inline function falses!(pool::AbstractArrayPool, dims::NTuple{N,Int}) where {N} + _mark_untracked!(pool) + _falses_impl!(pool, dims...) +end # Internal implementation (for macro transformation) -@inline _falses_impl!(pool::AbstractArrayPool, dims::Vararg{Int,N}) where {N} = _zeros_impl!(pool, Bit, dims...) -@inline _falses_impl!(pool::AbstractArrayPool, dims::NTuple{N,Int}) where {N} = _zeros_impl!(pool, Bit, dims...) +@inline function _falses_impl!(pool::AbstractArrayPool, dims::Vararg{Int,N}) where {N} + arr = _acquire_impl!(pool, Bit, dims...) + fill!(arr, false) + arr +end +@inline _falses_impl!(pool::AbstractArrayPool, dims::NTuple{N,Int}) where {N} = _falses_impl!(pool, dims...) # ============================================================================== # similar! - Acquire arrays with same type/size as template From 6c2e8dc2cca5ba4bf4a7a66d3480433d000152d9 Mon Sep 17 00:00:00 2001 From: Min-Gu Yoo Date: Sat, 24 Jan 2026 16:10:18 -0800 Subject: [PATCH 12/15] docs: add bit-arrays page to documentation navigation --- docs/make.jl | 1 + 1 file changed, 1 insertion(+) diff --git a/docs/make.jl b/docs/make.jl index d441c21..f177868 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -92,6 +92,7 @@ makedocs( "Multi-threading" => "features/multi-threading.md", ], "Features" => [ + "Bit Arrays" => "features/bit-arrays.md", "`@maybe_with_pool`" => "features/maybe-with-pool.md", "CUDA Support" => "features/cuda-support.md", "Configuration" => "features/configuration.md", From 16998895f8d7631543ef70f6962ccf31504689c9 Mon Sep 17 00:00:00 2001 From: Min-Gu Yoo Date: Sat, 24 Jan 2026 16:11:10 -0800 Subject: [PATCH 13/15] feat: reorder Bit Arrays entry in features list for documentation --- docs/make.jl | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/docs/make.jl b/docs/make.jl index f177868..69ba0af 100644 --- a/docs/make.jl +++ b/docs/make.jl @@ -92,8 +92,8 @@ makedocs( "Multi-threading" => "features/multi-threading.md", ], "Features" => [ - "Bit Arrays" => "features/bit-arrays.md", "`@maybe_with_pool`" => "features/maybe-with-pool.md", + "Bit Arrays" => "features/bit-arrays.md", "CUDA Support" => "features/cuda-support.md", "Configuration" => "features/configuration.md", ], From 1ca759ac3ed04b141b0564489376bb33a176cc2c Mon Sep 17 00:00:00 2001 From: Min-Gu Yoo Date: Sat, 24 Jan 2026 16:13:22 -0800 Subject: [PATCH 14/15] feat: add BitVector support documentation with usage examples --- docs/src/features/bit-arrays.md | 78 +++++++++++++++++++++++++++++++++ 1 file changed, 78 insertions(+) create mode 100644 docs/src/features/bit-arrays.md diff --git a/docs/src/features/bit-arrays.md b/docs/src/features/bit-arrays.md new file mode 100644 index 0000000..ec21d39 --- /dev/null +++ b/docs/src/features/bit-arrays.md @@ -0,0 +1,78 @@ +# BitVector Support + +AdaptiveArrayPools.jl includes specialized support for `BitArray` (specifically `BitVector`), enabling **~8x memory savings** for boolean arrays compared to standard `Vector{Bool}`. + +## The `Bit` Sentinel Type + +To distinguish between standard boolean arrays (`Vector{Bool}`, 1 byte/element) and bit-packed arrays (`BitVector`, 1 bit/element), use the `Bit` sentinel type. + +| Call | Result | Memory | +|------|--------|--------| +| `acquire!(pool, Bool, 1000)` | `Vector{Bool}` | 1000 bytes | +| `acquire!(pool, Bit, 1000)` | `BitVector` | ~125 bytes | + +## Usage + +### 1D Arrays (BitVector) +For 1D arrays, `acquire!` returns a view into a pooled `BitVector`. + +```julia +@with_pool pool begin + # Acquire a BitVector of length 1000 + bv = acquire!(pool, Bit, 1000) + + # Use like normal + bv .= true + bv[1] = false + + # Supports standard operations + count(bv) +end +``` + +### N-D Arrays (BitArray / Reshaped) +For multi-dimensional arrays, `acquire!` returns a `ReshapedArray` wrapper around the linear `BitVector`. This maintains zero-allocation efficiency while providing N-D indexing. + +```julia +@with_pool pool begin + # 100x100 bit matrix + mask = zeros!(pool, Bit, 100, 100) + + mask[5, 5] = true +end +``` + +### Convenience Functions + +For specific `BitVector` operations, prefer `trues!` and `falses!` which mirror Julia's standard functions: + +```julia +@with_pool pool begin + # Filled with false (equivalent to `falses(256)`) + mask = falses!(pool, 256) + + # Filled with true (equivalent to `trues(256)`) + flags = trues!(pool, 256) + + # Multidimensional + grid = trues!(pool, 100, 100) + + # Similar to existing BitArray + A = BitVector(undef, 50) + B = similar!(pool, A) # Reuses eltype(A) -> Bool + + # To explicit get Bit-packed from pool irrespective of source + C = similar!(pool, A, Bit) +end + +Note: `zeros!(pool, Bit, ...)` and `ones!(pool, Bit, ...)` are also supported (aliased to `falses!` and `trues!`). +``` + +## How It Works + +The pool maintains a separate `BitTypedPool` specifically for `BitVector` storage. +- **Sentinel**: `acquire!(..., Bit, ...)` dispatches to this special pool. +- **Views**: 1D returns `SubArray{Bool, 1, BitVector, ...}`. +- **Reshaping**: N-D returns `ReshapedArray{Bool, N, SubArray{...}}`. + +This ensures that even for complex shapes, the underlying storage is always a compact `BitVector` reused from the pool. From 020317d4deceb27fc2b632a8d7351fe56980adc3 Mon Sep 17 00:00:00 2001 From: Min-Gu Yoo Date: Sat, 24 Jan 2026 16:21:17 -0800 Subject: [PATCH 15/15] test: add coverage tests for trues!/falses! NTuple and DisabledPool paths MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Add tests to achieve 100% coverage for convenience.jl BitArray functions: - NTuple form tests for trues!/falses! and zeros!/ones! with Bit type - Generic DisabledPool fallback tests for unknown backends - _trues_impl!/_falses_impl! delegator tests for DisabledPool - _zeros_impl!/_ones_impl! with Bit type NTuple for AbstractArrayPool Coverage: src/convenience.jl 96% → 100% --- test/test_bitarray.jl | 77 +++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 77 insertions(+) diff --git a/test/test_bitarray.jl b/test/test_bitarray.jl index 598b61b..a5dbeca 100644 --- a/test/test_bitarray.jl +++ b/test/test_bitarray.jl @@ -493,4 +493,81 @@ @test all(o_bit) end + @testset "NTuple form coverage" begin + pool = AdaptiveArrayPool() + + # Test NTuple forms for trues!/falses! (covers _trues_impl! and _falses_impl! NTuple overloads) + t_tuple = trues!(pool, (5, 5)) + @test size(t_tuple) == (5, 5) + @test all(t_tuple) + + f_tuple = falses!(pool, (5, 5)) + @test size(f_tuple) == (5, 5) + @test !any(f_tuple) + + # Test NTuple forms for zeros!/ones! with Bit type + # (covers _zeros_impl! and _ones_impl! with Bit NTuple overloads) + z_bit_tuple = zeros!(pool, Bit, (4, 4)) + @test size(z_bit_tuple) == (4, 4) + @test !any(z_bit_tuple) + + o_bit_tuple = ones!(pool, Bit, (4, 4)) + @test size(o_bit_tuple) == (4, 4) + @test all(o_bit_tuple) + end + + @testset "Generic DisabledPool fallback for unknown backend" begin + # Test that trues!/falses! throw BackendNotLoadedError for unknown backends + unknown_pool = DisabledPool{:unknown_backend}() + + @test_throws AdaptiveArrayPools.BackendNotLoadedError trues!(unknown_pool, 10) + @test_throws AdaptiveArrayPools.BackendNotLoadedError falses!(unknown_pool, 10) + + # Verify error message + try + trues!(unknown_pool, 10) + catch e + @test e isa AdaptiveArrayPools.BackendNotLoadedError + @test e.backend == :unknown_backend + end + end + + @testset "_impl! delegators for DisabledPool" begin + # Test _trues_impl! and _falses_impl! for DisabledPool (macro transformation path) + # These are called when @maybe_with_pool transforms trues!/falses! calls + + # Vararg form + t = AdaptiveArrayPools._trues_impl!(DISABLED_CPU, 10) + @test t isa BitVector + @test all(t) + + f = AdaptiveArrayPools._falses_impl!(DISABLED_CPU, 10) + @test f isa BitVector + @test !any(f) + + # NTuple form + t_tuple = AdaptiveArrayPools._trues_impl!(DISABLED_CPU, (5, 5)) + @test t_tuple isa BitArray{2} + @test all(t_tuple) + + f_tuple = AdaptiveArrayPools._falses_impl!(DISABLED_CPU, (5, 5)) + @test f_tuple isa BitArray{2} + @test !any(f_tuple) + end + + @testset "_impl! with Bit type NTuple for AbstractArrayPool" begin + # Test _zeros_impl! and _ones_impl! with Bit type NTuple form + # These are internal functions called by macro transformation + pool = AdaptiveArrayPool() + + # Direct calls to _impl! functions with Bit type and NTuple + z = AdaptiveArrayPools._zeros_impl!(pool, Bit, (3, 3)) + @test size(z) == (3, 3) + @test !any(z) + + o = AdaptiveArrayPools._ones_impl!(pool, Bit, (3, 3)) + @test size(o) == (3, 3) + @test all(o) + end + end # BitArray Support