Skip to content

Commit 182567a

Browse files
committed
Move spare buffer writer to vortex-buffer
Signed-off-by: Daniel King <dan@spiraldb.com>
1 parent 82bdd51 commit 182567a

7 files changed

Lines changed: 149 additions & 80 deletions

File tree

vortex-array/src/arrays/decimal/compute/take.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -4,6 +4,7 @@
44
use itertools::Itertools as _;
55
use vortex_buffer::Buffer;
66
use vortex_buffer::BufferMut;
7+
use vortex_buffer::SpareBufferWriter;
78
use vortex_error::VortexResult;
89
use vortex_error::vortex_ensure;
910
use vortex_error::vortex_err;
@@ -17,7 +18,6 @@ use crate::arrays::DecimalArray;
1718
use crate::arrays::PiecewiseSequence;
1819
use crate::arrays::PrimitiveArray;
1920
use crate::arrays::dict::TakeExecute;
20-
use crate::arrays::piecewise_sequence::SpareBufferWriter;
2121
use crate::arrays::piecewise_sequence::constant_unsigned_usize;
2222
use crate::arrays::piecewise_sequence::maybe_contiguous_slices;
2323
use crate::dtype::IntegerPType;

vortex-array/src/arrays/piecewise_sequence/mod.rs

Lines changed: 0 additions & 76 deletions
Original file line numberDiff line numberDiff line change
@@ -8,8 +8,6 @@
88
//! intended for take operations that can gather regular runs without materializing one index per
99
//! element.
1010
11-
use std::ptr;
12-
1311
use itertools::Itertools;
1412
use num_traits::AsPrimitive;
1513
use vortex_buffer::BufferMut;
@@ -94,80 +92,6 @@ pub(crate) fn is_constant_one(multipliers: &ArrayRef) -> bool {
9492
)
9593
}
9694

97-
pub(crate) struct SpareBufferWriter<'a, T> {
98-
buffer: &'a mut BufferMut<T>,
99-
next: *mut T,
100-
remaining: usize,
101-
output_len: usize,
102-
}
103-
104-
impl<'a, T: Copy> SpareBufferWriter<'a, T> {
105-
pub(crate) fn new(buffer: &'a mut BufferMut<T>, output_len: usize) -> VortexResult<Self> {
106-
vortex_ensure!(
107-
buffer.is_empty(),
108-
"slice copy buffer already has {} initialized values",
109-
buffer.len()
110-
);
111-
vortex_ensure!(
112-
output_len <= buffer.capacity(),
113-
"slice copy output length {output_len} exceeds buffer capacity {}",
114-
buffer.capacity()
115-
);
116-
117-
let next = buffer.spare_capacity_mut().as_mut_ptr().cast();
118-
Ok(Self {
119-
buffer,
120-
next,
121-
remaining: output_len,
122-
output_len,
123-
})
124-
}
125-
126-
#[inline]
127-
pub(crate) fn copy_slice(&mut self, source: &[T]) -> VortexResult<()> {
128-
vortex_ensure!(
129-
source.len() <= self.remaining,
130-
"slice copy length {} exceeds remaining output length {}",
131-
source.len(),
132-
self.remaining
133-
);
134-
// SAFETY: the check above proves that `source` fits in the remaining output slots.
135-
unsafe { self.copy_slice_unchecked(source) };
136-
Ok(())
137-
}
138-
139-
/// Copies `source` to the next output slots without checking the remaining output length.
140-
///
141-
/// # Safety
142-
///
143-
/// The caller must ensure that `source.len()` does not exceed the remaining output length.
144-
#[inline]
145-
pub(crate) unsafe fn copy_slice_unchecked(&mut self, source: &[T]) {
146-
debug_assert!(source.len() <= self.remaining);
147-
// SAFETY: `next` points to the first unwritten slot, the caller guarantees the source fits
148-
// in the remaining capacity, and the mutable buffer borrow prevents reallocation.
149-
unsafe {
150-
ptr::copy_nonoverlapping(source.as_ptr(), self.next, source.len());
151-
self.next = self.next.add(source.len());
152-
}
153-
self.remaining -= source.len();
154-
}
155-
156-
pub(crate) fn finish(self) -> VortexResult<()> {
157-
vortex_ensure!(
158-
self.remaining == 0,
159-
"slice copy length {} does not match declared output length {}",
160-
self.output_len - self.remaining,
161-
self.output_len
162-
);
163-
// SAFETY: successful calls to the copy methods initialized exactly `output_len` slots.
164-
unsafe {
165-
self.buffer.set_len(self.output_len);
166-
}
167-
Ok(())
168-
}
169-
}
170-
17195
pub(crate) fn constant_unsigned_usize(array: &ConstantArray) -> usize {
17296
let pvalue = array
17397
.scalar()

vortex-array/src/arrays/primitive/compute/take/mod.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ use std::sync::LazyLock;
99
use itertools::Itertools as _;
1010
use vortex_buffer::Buffer;
1111
use vortex_buffer::BufferMut;
12+
use vortex_buffer::SpareBufferWriter;
1213
use vortex_error::VortexResult;
1314
use vortex_error::vortex_bail;
1415
use vortex_error::vortex_ensure;
@@ -24,7 +25,6 @@ use crate::arrays::PiecewiseSequence;
2425
use crate::arrays::Primitive;
2526
use crate::arrays::PrimitiveArray;
2627
use crate::arrays::dict::TakeExecute;
27-
use crate::arrays::piecewise_sequence::SpareBufferWriter;
2828
use crate::arrays::piecewise_sequence::constant_unsigned_usize;
2929
use crate::arrays::piecewise_sequence::maybe_contiguous_slices;
3030
use crate::builtins::ArrayBuiltins;

vortex-array/src/arrays/varbin/compute/take.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,7 @@ use itertools::Itertools as _;
55
use vortex_buffer::BitBufferMut;
66
use vortex_buffer::BufferMut;
77
use vortex_buffer::ByteBufferMut;
8+
use vortex_buffer::SpareBufferWriter;
89
use vortex_error::VortexExpect;
910
use vortex_error::VortexResult;
1011
use vortex_error::vortex_ensure;
@@ -21,7 +22,6 @@ use crate::arrays::PrimitiveArray;
2122
use crate::arrays::VarBin;
2223
use crate::arrays::VarBinArray;
2324
use crate::arrays::dict::TakeExecute;
24-
use crate::arrays::piecewise_sequence::SpareBufferWriter;
2525
use crate::arrays::piecewise_sequence::constant_unsigned_usize;
2626
use crate::arrays::piecewise_sequence::maybe_contiguous_slices;
2727
use crate::arrays::primitive::PrimitiveArrayExt;

vortex-array/src/arrays/varbinview/compute/take.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -8,6 +8,7 @@ use itertools::Itertools as _;
88
use num_traits::AsPrimitive;
99
use vortex_buffer::Buffer;
1010
use vortex_buffer::BufferMut;
11+
use vortex_buffer::SpareBufferWriter;
1112
use vortex_error::VortexResult;
1213
use vortex_error::vortex_ensure;
1314
use vortex_error::vortex_err;
@@ -23,7 +24,6 @@ use crate::arrays::PrimitiveArray;
2324
use crate::arrays::VarBinView;
2425
use crate::arrays::VarBinViewArray;
2526
use crate::arrays::dict::TakeExecute;
26-
use crate::arrays::piecewise_sequence::SpareBufferWriter;
2727
use crate::arrays::piecewise_sequence::constant_unsigned_usize;
2828
use crate::arrays::piecewise_sequence::maybe_contiguous_slices;
2929
use crate::arrays::varbinview::BinaryView;

vortex-buffer/src/lib.rs

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,7 @@ pub use buffer_mut::*;
5353
pub use bytes::*;
5454
pub use r#const::*;
5555
pub use dispatch::*;
56+
pub use spare_buffer_writer::*;
5657
pub use string::*;
5758
mod alignment;
5859
#[cfg(feature = "arrow")]
@@ -69,6 +70,7 @@ mod macros;
6970
mod memmap2;
7071
#[cfg(feature = "serde")]
7172
mod serde;
73+
mod spare_buffer_writer;
7274
mod string;
7375
/// Trusted-length iterator trait and adapters for safe pre-allocation.
7476
pub mod trusted_len;
Lines changed: 143 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,143 @@
1+
// SPDX-License-Identifier: Apache-2.0
2+
// SPDX-FileCopyrightText: Copyright the Vortex contributors
3+
4+
use std::ptr;
5+
6+
use vortex_error::VortexResult;
7+
use vortex_error::vortex_ensure;
8+
9+
use crate::BufferMut;
10+
11+
/// Writes slices sequentially into the spare capacity of an empty [`BufferMut`].
12+
///
13+
/// This is useful when the caller knows the final output length up front and wants to avoid
14+
/// repeatedly growing the initialized length while copying contiguous source slices.
15+
pub struct SpareBufferWriter<'a, T> {
16+
buffer: &'a mut BufferMut<T>,
17+
next: *mut T,
18+
remaining: usize,
19+
output_len: usize,
20+
}
21+
22+
impl<'a, T: Copy> SpareBufferWriter<'a, T> {
23+
/// Creates a writer for `output_len` values in `buffer`'s spare capacity.
24+
///
25+
/// The target buffer must be empty and have capacity for at least `output_len` values.
26+
pub fn new(buffer: &'a mut BufferMut<T>, output_len: usize) -> VortexResult<Self> {
27+
vortex_ensure!(
28+
buffer.is_empty(),
29+
"slice copy buffer already has {} initialized values",
30+
buffer.len()
31+
);
32+
vortex_ensure!(
33+
output_len <= buffer.capacity(),
34+
"slice copy output length {output_len} exceeds buffer capacity {}",
35+
buffer.capacity()
36+
);
37+
38+
let next = buffer.spare_capacity_mut().as_mut_ptr().cast();
39+
Ok(Self {
40+
buffer,
41+
next,
42+
remaining: output_len,
43+
output_len,
44+
})
45+
}
46+
47+
/// Copies `source` into the next output slots.
48+
#[inline]
49+
pub fn copy_slice(&mut self, source: &[T]) -> VortexResult<()> {
50+
vortex_ensure!(
51+
source.len() <= self.remaining,
52+
"slice copy length {} exceeds remaining output length {}",
53+
source.len(),
54+
self.remaining
55+
);
56+
// SAFETY: the check above proves that `source` fits in the remaining output slots.
57+
unsafe { self.copy_slice_unchecked(source) };
58+
Ok(())
59+
}
60+
61+
/// Copies `source` to the next output slots without checking the remaining output length.
62+
///
63+
/// # Safety
64+
///
65+
/// The caller must ensure that `source.len()` does not exceed the remaining output length.
66+
#[inline]
67+
pub unsafe fn copy_slice_unchecked(&mut self, source: &[T]) {
68+
debug_assert!(source.len() <= self.remaining);
69+
// SAFETY: `next` points to the first unwritten slot, the caller guarantees the source fits
70+
// in the remaining capacity, and the mutable buffer borrow prevents reallocation.
71+
unsafe {
72+
ptr::copy_nonoverlapping(source.as_ptr(), self.next, source.len());
73+
self.next = self.next.add(source.len());
74+
}
75+
self.remaining -= source.len();
76+
}
77+
78+
/// Sets the target buffer length after exactly `output_len` values have been written.
79+
pub fn finish(self) -> VortexResult<()> {
80+
vortex_ensure!(
81+
self.remaining == 0,
82+
"slice copy length {} does not match declared output length {}",
83+
self.output_len - self.remaining,
84+
self.output_len
85+
);
86+
// SAFETY: successful calls to the copy methods initialized exactly `output_len` slots.
87+
unsafe {
88+
self.buffer.set_len(self.output_len);
89+
}
90+
Ok(())
91+
}
92+
}
93+
94+
#[cfg(test)]
95+
mod tests {
96+
use vortex_error::VortexResult;
97+
98+
use super::SpareBufferWriter;
99+
use crate::BufferMut;
100+
101+
#[test]
102+
fn writes_slices_into_spare_capacity() -> VortexResult<()> {
103+
let mut buffer = BufferMut::with_capacity(4);
104+
105+
let mut writer = SpareBufferWriter::new(&mut buffer, 4)?;
106+
writer.copy_slice(&[1u16, 2])?;
107+
writer.copy_slice(&[3u16, 4])?;
108+
writer.finish()?;
109+
110+
assert_eq!(buffer.as_slice(), &[1u16, 2, 3, 4]);
111+
Ok(())
112+
}
113+
114+
#[test]
115+
fn rejects_overlong_copy() -> VortexResult<()> {
116+
let mut buffer = BufferMut::with_capacity(2);
117+
118+
let mut writer = SpareBufferWriter::new(&mut buffer, 2)?;
119+
let error = writer.copy_slice(&[1u16, 2, 3]).unwrap_err();
120+
121+
assert!(
122+
error
123+
.to_string()
124+
.contains("exceeds remaining output length")
125+
);
126+
Ok(())
127+
}
128+
129+
#[test]
130+
fn rejects_incomplete_finish() -> VortexResult<()> {
131+
let mut buffer = BufferMut::with_capacity(2);
132+
133+
let writer = SpareBufferWriter::<u16>::new(&mut buffer, 2)?;
134+
let error = writer.finish().unwrap_err();
135+
136+
assert!(
137+
error
138+
.to_string()
139+
.contains("does not match declared output length")
140+
);
141+
Ok(())
142+
}
143+
}

0 commit comments

Comments
 (0)