Skip to content

Commit 65cedb5

Browse files
committed
Address Kleene boolean review feedback
Signed-off-by: Nicholas Gates <nick@nickgates.com>
1 parent aec7150 commit 65cedb5

4 files changed

Lines changed: 69 additions & 56 deletions

File tree

vortex-array/src/scalar_fn/fns/binary/boolean.rs

Lines changed: 18 additions & 45 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ use std::iter::repeat_n;
66
use arrow_array::cast::AsArray;
77
use vortex_buffer::BitBuffer;
88
use vortex_buffer::BufferMut;
9+
use vortex_buffer::read_u64_le;
910
use vortex_error::VortexResult;
1011
use vortex_error::vortex_err;
1112
use vortex_mask::AllOr;
@@ -37,11 +38,14 @@ use crate::scalar_fn::fns::binary::Binary;
3738
use crate::scalar_fn::fns::operators::Operator;
3839
use crate::validity::Validity;
3940

40-
/// Trait for encoding-specific Kleene boolean kernels that operate in encoded space.
41+
/// Trait for encoding-specific boolean kernels that operate in encoded space.
4142
///
4243
/// Implementations receive the encoded array as the left operand. `rhs` may be any boolean array
4344
/// encoding or a constant; implementations should return `Ok(None)` when they cannot handle that
4445
/// operand without falling back to ordinary execution.
46+
///
47+
/// Vortex's boolean [`Operator::And`] and [`Operator::Or`] variants use Kleene semantics; there is
48+
/// no separate two-valued boolean operator path to dispatch here.
4549
pub trait BooleanKernel: VTable {
4650
/// Execute `lhs <operator> rhs` using Kleene boolean semantics.
4751
fn boolean(
@@ -268,7 +272,7 @@ pub fn boolean_buffers(
268272
Operator::Or => lhs_values | &rhs_values,
269273
other => return Err(vortex_err!("Not a boolean operator: {other}")),
270274
};
271-
return Ok(BoolArray::try_new(values, all_validity(nullability))?.into_array());
275+
return Ok(BoolArray::try_new(values, Validity::from(nullability))?.into_array());
272276
}
273277

274278
let lhs_valid = lhs_validity.execute_mask(len, ctx)?;
@@ -318,14 +322,14 @@ pub fn boolean_buffer_scalar(
318322
.bitand_not(&Mask::from_buffer(values));
319323
BoolArray::try_new(
320324
BitBuffer::new_unset(len),
321-
mask_to_validity(valid, nullability),
325+
Validity::from_mask(valid, nullability),
322326
)?
323327
}
324328
(Operator::Or, None) => {
325329
let valid = validity.execute_mask(len, ctx)? & &Mask::from_buffer(values);
326330
BoolArray::try_new(
327331
BitBuffer::new_set(len),
328-
mask_to_validity(valid, nullability),
332+
Validity::from_mask(valid, nullability),
329333
)?
330334
}
331335
(other, _) => return Err(vortex_err!("Not a boolean operator: {other}")),
@@ -430,16 +434,16 @@ fn fused_boolean_buffers_aligned(
430434
operator: Operator,
431435
nullability: Nullability,
432436
) -> VortexResult<Option<ArrayRef>> {
433-
let Some(lhs_values) = word_source_from_bit_buffer(lhs_values, len) else {
437+
let Some(lhs_values) = word_source_from_bit_buffer(lhs_values) else {
434438
return Ok(None);
435439
};
436-
let Some(rhs_values) = word_source_from_bit_buffer(rhs_values, len) else {
440+
let Some(rhs_values) = word_source_from_bit_buffer(rhs_values) else {
437441
return Ok(None);
438442
};
439-
let Some(lhs_validity) = word_source_from_mask(lhs_validity, len) else {
443+
let Some(lhs_validity) = word_source_from_mask(lhs_validity) else {
440444
return Ok(None);
441445
};
442-
let Some(rhs_validity) = word_source_from_mask(rhs_validity, len) else {
446+
let Some(rhs_validity) = word_source_from_mask(rhs_validity) else {
443447
return Ok(None);
444448
};
445449

@@ -454,32 +458,18 @@ fn fused_boolean_buffers_aligned(
454458
)?))
455459
}
456460

457-
fn word_source_from_bit_buffer(buffer: &BitBuffer, len: usize) -> Option<WordSource<'_>> {
458-
if !buffer.offset().is_multiple_of(8) {
459-
return None;
460-
}
461-
462-
let n_bytes = len.div_ceil(8);
463-
let start = buffer.offset() / 8;
464-
let end = start + n_bytes;
465-
Some(WordSource::Bytes(&buffer.inner().as_slice()[start..end]))
461+
fn word_source_from_bit_buffer(buffer: &BitBuffer) -> Option<WordSource<'_>> {
462+
buffer.byte_aligned_bytes().map(WordSource::Bytes)
466463
}
467464

468-
fn word_source_from_mask(mask: &Mask, len: usize) -> Option<WordSource<'_>> {
465+
fn word_source_from_mask(mask: &Mask) -> Option<WordSource<'_>> {
469466
match mask.bit_buffer() {
470467
AllOr::All => Some(WordSource::Fill(u64::MAX)),
471468
AllOr::None => Some(WordSource::Fill(0)),
472-
AllOr::Some(buffer) => word_source_from_bit_buffer(buffer, len),
469+
AllOr::Some(buffer) => word_source_from_bit_buffer(buffer),
473470
}
474471
}
475472

476-
fn read_u64_le(bytes: &[u8]) -> u64 {
477-
debug_assert!(bytes.len() <= 8);
478-
let mut buf = [0; 8];
479-
buf[..bytes.len()].copy_from_slice(bytes);
480-
u64::from_le_bytes(buf)
481-
}
482-
483473
fn fused_boolean_word_sources(
484474
len: usize,
485475
lhs_words: WordSource<'_>,
@@ -581,7 +571,7 @@ fn finish_fused_boolean_words(
581571
validity.truncate(n_bytes);
582572
Ok(BoolArray::try_new(
583573
BitBuffer::new(values.freeze(), len),
584-
mask_to_validity(
574+
Validity::from_mask(
585575
Mask::from_buffer(BitBuffer::new(validity.freeze(), len)),
586576
nullability,
587577
),
@@ -651,7 +641,7 @@ where
651641

652642
Ok(BoolArray::try_new(
653643
values,
654-
mask_to_validity(Mask::from_buffer(validity), nullability),
644+
Validity::from_mask(Mask::from_buffer(validity), nullability),
655645
)?
656646
.into_array())
657647
}
@@ -677,23 +667,6 @@ fn boolean_nullability(lhs: &ArrayRef, rhs: &ArrayRef) -> Nullability {
677667
lhs.dtype().nullability() | rhs.dtype().nullability()
678668
}
679669

680-
fn all_validity(nullability: Nullability) -> Validity {
681-
match nullability {
682-
Nullability::NonNullable => Validity::NonNullable,
683-
Nullability::Nullable => Validity::AllValid,
684-
}
685-
}
686-
687-
fn mask_to_validity(mask: Mask, nullability: Nullability) -> Validity {
688-
match nullability {
689-
Nullability::NonNullable => {
690-
debug_assert!(mask.all_true());
691-
Validity::NonNullable
692-
}
693-
Nullability::Nullable => Validity::from(mask.into_bit_buffer()),
694-
}
695-
}
696-
697670
#[inline]
698671
fn is_boolean_operator(operator: Operator) -> bool {
699672
matches!(operator, Operator::And | Operator::Or)

vortex-buffer/src/bit/buf.rs

Lines changed: 30 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -272,6 +272,23 @@ impl BitBuffer {
272272
&self.buffer
273273
}
274274

275+
/// Return the backing bytes for this bit buffer when its logical offset is byte-aligned.
276+
///
277+
/// The returned slice contains exactly `self.len().div_ceil(8)` bytes. Bits past the logical
278+
/// length in the final byte are outside the buffer's logical range and should be ignored by
279+
/// callers.
280+
#[inline]
281+
pub fn byte_aligned_bytes(&self) -> Option<&[u8]> {
282+
if !self.offset.is_multiple_of(8) {
283+
return None;
284+
}
285+
286+
let n_bytes = self.len.div_ceil(8);
287+
let start = self.offset / 8;
288+
let end = start + n_bytes;
289+
Some(&self.buffer.as_slice()[start..end])
290+
}
291+
275292
/// Retrieve the value at the given index.
276293
///
277294
/// Panics if the index is out of bounds.
@@ -692,6 +709,19 @@ mod tests {
692709
assert_eq!(sliced.offset(), 2);
693710
}
694711

712+
#[test]
713+
fn test_byte_aligned_bytes() {
714+
let bytes: ByteBuffer = buffer![0b1010_0101u8, 0b0000_0011];
715+
let buf = BitBuffer::new(bytes.clone(), 10);
716+
assert_eq!(buf.byte_aligned_bytes(), Some(bytes.as_slice()));
717+
718+
let byte_sliced = buf.slice(8..10);
719+
assert_eq!(byte_sliced.byte_aligned_bytes(), Some(&[0b0000_0011][..]));
720+
721+
let bit_sliced = buf.slice(1..9);
722+
assert!(bit_sliced.byte_aligned_bytes().is_none());
723+
}
724+
695725
#[test]
696726
fn test_from_indices_dense_crosses_words() {
697727
let len = 130;

vortex-buffer/src/bit/mod.rs

Lines changed: 20 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -74,6 +74,19 @@ where
7474
}
7575
}
7676

77+
/// Read up to 8 bytes as a little-endian `u64`, zero-padding the high bytes when fewer than 8
78+
/// bytes are supplied.
79+
///
80+
/// This preserves Vortex's least-significant-bit-first bitmap numbering on little- and big-endian
81+
/// targets. For a full 8-byte slice it lowers to a single word load.
82+
#[inline]
83+
pub fn read_u64_le(bytes: &[u8]) -> u64 {
84+
debug_assert!(bytes.len() <= 8);
85+
let mut buf = [0u8; 8];
86+
buf[..bytes.len()].copy_from_slice(bytes);
87+
u64::from_le_bytes(buf)
88+
}
89+
7790
/// Splice a packed word `w` (whose bits above the highest valid bit are zero) into
7891
/// `words` at the given bit position.
7992
///
@@ -178,6 +191,7 @@ pub unsafe fn unset_bit_unchecked(buf: *mut u8, index: usize) {
178191
mod tests {
179192
use super::collect_bool_word;
180193
use super::pack_bools_into_words;
194+
use super::read_u64_le;
181195

182196
#[test]
183197
fn collect_bool_word_packs_lsb_first() {
@@ -190,6 +204,12 @@ mod tests {
190204
assert_eq!(collect_bool_word(0, |_| true), 0);
191205
}
192206

207+
#[test]
208+
fn read_u64_le_zero_pads_tail() {
209+
assert_eq!(read_u64_le(&[0x34, 0x12]), 0x1234);
210+
assert_eq!(read_u64_le(&[0xff; 8]), u64::MAX);
211+
}
212+
193213
#[test]
194214
#[should_panic(expected = "cannot pack 65 bits into a u64 word")]
195215
fn collect_bool_word_rejects_too_many_bits() {

vortex-buffer/src/bit/ops.rs

Lines changed: 1 addition & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -7,17 +7,7 @@ use crate::BitBuffer;
77
use crate::BitBufferMut;
88
use crate::BufferMut;
99
use crate::ByteBufferMut;
10-
11-
/// Read up to 8 bytes as a little-endian `u64`, zero-padding the high bytes when fewer than 8 are
12-
/// supplied. Using [`u64::from_le_bytes`] keeps the bit-numbering identical on little- and
13-
/// big-endian targets; for a full 8-byte slice it lowers to a single word load.
14-
#[inline]
15-
fn read_u64_le(bytes: &[u8]) -> u64 {
16-
debug_assert!(bytes.len() <= 8);
17-
let mut buf = [0u8; 8];
18-
buf[..bytes.len()].copy_from_slice(bytes);
19-
u64::from_le_bytes(buf)
20-
}
10+
use crate::read_u64_le;
2111

2212
trait BitWordTarget {
2313
fn byte_len(&self) -> usize;

0 commit comments

Comments
 (0)