From ce5fa758d17b3d4ce0e26adbee05ad66774d890e Mon Sep 17 00:00:00 2001 From: Michael Kleen Date: Tue, 2 Dec 2025 14:56:41 +0100 Subject: [PATCH 01/13] Add special implementation for zip for Utf8View scalars --- arrow-select/src/zip.rs | 331 +++++++++++++++++++++++++++++++++++++++- 1 file changed, 327 insertions(+), 4 deletions(-) diff --git a/arrow-select/src/zip.rs b/arrow-select/src/zip.rs index e45b817dc6e8..c9a173919718 100644 --- a/arrow-select/src/zip.rs +++ b/arrow-select/src/zip.rs @@ -19,14 +19,17 @@ use crate::filter::{SlicesIterator, prep_null_mask_filter}; use arrow_array::cast::AsArray; -use arrow_array::types::{BinaryType, ByteArrayType, LargeBinaryType, LargeUtf8Type, Utf8Type}; +use arrow_array::types::{ + BinaryType, ByteArrayType, ByteViewType, LargeBinaryType, LargeUtf8Type, StringViewType, + Utf8Type, +}; use arrow_array::*; use arrow_buffer::{ BooleanBuffer, Buffer, MutableBuffer, NullBuffer, OffsetBuffer, OffsetBufferBuilder, - ScalarBuffer, + ScalarBuffer, ToByteSlice, }; -use arrow_data::ArrayData; use arrow_data::transform::MutableArrayData; +use arrow_data::{ArrayData, ByteView}; use arrow_schema::{ArrowError, DataType}; use std::fmt::{Debug, Formatter}; use std::hash::Hash; @@ -284,7 +287,9 @@ impl ScalarZipper { DataType::LargeBinary => { Arc::new(BytesScalarImpl::::new(truthy, falsy)) as Arc }, - // TODO: Handle Utf8View https://github.com/apache/arrow-rs/issues/8724 + DataType::Utf8View => { + Arc::new(ByteViewScalarImpl::::new(truthy, falsy)) as Arc + }, _ => { Arc::new(FallbackImpl::new(truthy, falsy)) as Arc }, @@ -657,6 +662,182 @@ fn maybe_prep_null_mask_filter(predicate: &BooleanArray) -> BooleanBuffer { } } +struct ByteViewScalarImpl { + truthy: Option>, + falsy: Option>, + phantom: PhantomData, +} + +impl ByteViewScalarImpl { + fn new(truthy: &dyn Array, falsy: &dyn Array) -> Self { + Self { + truthy: Self::get_value_from_scalar(truthy), + falsy: Self::get_value_from_scalar(falsy), + phantom: PhantomData, + } + } + + fn get_value_from_scalar(scalar: &dyn Array) -> Option> { + if scalar.is_null(0) { + None + } else { + Some(scalar.as_byte_view().clone()) + } + } + + fn get_scalar_buffers_and_nulls_for_all_values_null( + len: usize, + ) -> (ScalarBuffer, Vec, Option) { + let mut mutable = MutableBuffer::with_capacity(0); + mutable.repeat_slice_n_times((0u128).to_byte_slice(), len); + + (mutable.into(), vec![], Some(NullBuffer::new_null(len))) + } + + fn get_scalar_buffers_and_nulls_for_single_non_nullable( + predicate: BooleanBuffer, + value: &GenericByteViewArray, + ) -> (ScalarBuffer, Vec, Option) { + let number_of_true = predicate.count_set_bits(); + let number_of_values = predicate.len(); + + // Fast path for all nulls + if number_of_true == 0 { + // All values are null + return Self::get_scalar_buffers_and_nulls_for_all_values_null(number_of_values); + } + let view = value.views()[0].to_byte_slice(); + let mut bytes = MutableBuffer::with_capacity(0); + bytes.repeat_slice_n_times(view, number_of_values); + + let bytes = Buffer::from(bytes); + + // If a value is true we need the TRUTHY and the null buffer will have 1 (meaning not null) + // If a value is false we need the FALSY and the null buffer will have 0 (meaning null) + let nulls = NullBuffer::new(predicate); + (bytes.into(), value.data_buffers().into(), Some(nulls)) + } + + fn get_scalar_buffers_and_nulls_non_nullable( + predicate: BooleanBuffer, + truthy: &GenericByteViewArray, + falsy: &GenericByteViewArray, + ) -> (ScalarBuffer, Vec, Option) { + let true_count = predicate.count_set_bits(); + let view_truthy = truthy.views()[0].to_byte_slice(); + let mut buffers: Vec = truthy.data_buffers().to_vec(); + + // if falsy has non-inlined values in the buffer, + // include the buffers and recalculate the view, + // otherwise, we simply use the view. + let view_falsy = if falsy.total_buffer_bytes_used() > 0 { + let byte_view_falsy = ByteView::from(falsy.views()[0]); + let new_index_falsy_buffers = buffers.len() as u32; + buffers.extend(falsy.data_buffers().to_vec()); + let byte_view_falsy = byte_view_falsy.with_buffer_index(new_index_falsy_buffers); + byte_view_falsy.as_u128() + } else { + falsy.views()[0] + }; + + let total_number_of_bytes = true_count * view_truthy.len() + + (predicate.len() - true_count) * view_falsy.to_byte_slice().len(); + let mut mutable = MutableBuffer::new(total_number_of_bytes); + let mut filled = 0; + + SlicesIterator::from(&predicate).for_each(|(start, end)| { + if start > filled { + let false_repeat_count = start - filled; + mutable.repeat_slice_n_times(view_falsy.to_byte_slice(), false_repeat_count); + } + let true_repeat_count = end - start; + mutable.repeat_slice_n_times(view_truthy, true_repeat_count); + filled = end; + }); + + if filled < predicate.len() { + let false_repeat_count = predicate.len() - filled; + mutable.repeat_slice_n_times(view_falsy.to_byte_slice(), false_repeat_count); + } + + let bytes = Buffer::from(mutable); + + ( + bytes.into(), + buffers, + Some(NullBuffer::new_valid(predicate.len())), + ) + } + + fn get_scalar_buffers_and_nulls_for_all_same_value( + length: usize, + value: &GenericByteViewArray, + ) -> (ScalarBuffer, Vec, Option) { + let (views, buffers, _) = value.clone().into_parts(); + let mut mutable = MutableBuffer::with_capacity(0); + mutable.repeat_slice_n_times(views[0].to_byte_slice(), length); + + let bytes = Buffer::from(mutable); + + (bytes.into(), buffers, Some(NullBuffer::new_valid(length))) + } + + fn create_output_on_non_nulls( + predicate: BooleanBuffer, + result_len: usize, + truthy: &GenericByteViewArray, + falsy: &GenericByteViewArray, + ) -> (ScalarBuffer, Vec, Option) { + let true_count = predicate.count_set_bits(); + match true_count { + 0 => { + // all values are falsy + Self::get_scalar_buffers_and_nulls_for_all_same_value(result_len, falsy) + } + n if n == predicate.len() => { + // all values are truthy + Self::get_scalar_buffers_and_nulls_for_all_same_value(result_len, truthy) + } + _ => Self::get_scalar_buffers_and_nulls_non_nullable(predicate, truthy, falsy), + } + } +} + +impl Debug for ByteViewScalarImpl { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + f.debug_struct("ByteViewScalarImpl") + .field("truthy", &self.truthy) + .field("falsy", &self.falsy) + .finish() + } +} + +impl ZipImpl for ByteViewScalarImpl { + fn create_output(&self, predicate: &BooleanArray) -> Result { + let result_len = predicate.len(); + // Nulls are treated as false + let predicate = maybe_prep_null_mask_filter(predicate); + + let (views, buffers, nulls) = match (self.truthy.as_ref(), self.falsy.as_ref()) { + (Some(truthy), Some(falsy)) => { + Self::create_output_on_non_nulls(predicate, result_len, truthy, falsy) + } + (Some(truthy), None) => { + Self::get_scalar_buffers_and_nulls_for_single_non_nullable(predicate, truthy) + } + + (None, Some(falsy)) => { + let predicate = predicate.not(); + Self::get_scalar_buffers_and_nulls_for_single_non_nullable(predicate, falsy) + } + (None, None) => Self::get_scalar_buffers_and_nulls_for_all_values_null(result_len), + }; + + let result = unsafe { GenericByteViewArray::::new_unchecked(views, buffers, nulls) }; + Ok(Arc::new(result)) + } +} + #[cfg(test)] mod test { use super::*; @@ -1222,4 +1403,146 @@ mod test { ]); assert_eq!(actual, &expected); } + + #[test] + fn test_zip_kernel_scalar_strings_array_view() { + let scalar_truthy = Scalar::new(StringViewArray::from(vec!["hello"])); + let scalar_falsy = Scalar::new(StringViewArray::from(vec!["world"])); + + let mask = BooleanArray::from(vec![true, false, true, false]); + let out = zip(&mask, &scalar_truthy, &scalar_falsy).unwrap(); + let actual = out.as_string_view(); + let expected = StringViewArray::from(vec![ + Some("hello"), + Some("world"), + Some("hello"), + Some("world"), + ]); + assert_eq!(actual, &expected); + } + + #[test] + fn test_zip_kernel_scalar_strings_array_view_with_nulls() { + let scalar_truthy = Scalar::new(StringViewArray::from_iter_values(["hello"])); + let scalar_falsy = Scalar::new(StringViewArray::new_null(1)); + + let mask = BooleanArray::from(vec![true, true, false, false, true]); + let out = zip(&mask, &scalar_truthy, &scalar_falsy).unwrap(); + let actual = out.as_any().downcast_ref::().unwrap(); + let expected = StringViewArray::from_iter(vec![ + Some("hello"), + Some("hello"), + None, + None, + Some("hello"), + ]); + assert_eq!(actual, &expected); + } + + #[test] + fn test_zip_kernel_scalar_strings_array_view_all_true_null() { + let scalar_truthy = Scalar::new(StringViewArray::new_null(1)); + let scalar_falsy = Scalar::new(StringViewArray::new_null(1)); + let mask = BooleanArray::from(vec![true, true]); + let out = zip(&mask, &scalar_truthy, &scalar_falsy).unwrap(); + let actual = out.as_any().downcast_ref::().unwrap(); + let expected = StringViewArray::from_iter(vec![None::, None]); + assert_eq!(actual, &expected); + } + + #[test] + fn test_zip_kernel_scalar_strings_array_view_all_false_null() { + let scalar_truthy = Scalar::new(StringViewArray::new_null(1)); + let scalar_falsy = Scalar::new(StringViewArray::new_null(1)); + let mask = BooleanArray::from(vec![false, false]); + let out = zip(&mask, &scalar_truthy, &scalar_falsy).unwrap(); + let actual = out.as_any().downcast_ref::().unwrap(); + let expected = StringViewArray::from_iter(vec![None::, None]); + assert_eq!(actual, &expected); + } + + #[test] + fn test_zip_kernel_scalar_string_array_view_all_true() { + let scalar_truthy = Scalar::new(StringViewArray::from(vec!["hello"])); + let scalar_falsy = Scalar::new(StringViewArray::from(vec!["world"])); + + let mask = BooleanArray::from(vec![true, true]); + let out = zip(&mask, &scalar_truthy, &scalar_falsy).unwrap(); + let actual = out.as_string_view(); + let expected = StringViewArray::from(vec![Some("hello"), Some("hello")]); + assert_eq!(actual, &expected); + } + + #[test] + fn test_zip_kernel_scalar_string_array_view_all_false() { + let scalar_truthy = Scalar::new(StringViewArray::from(vec!["hello"])); + let scalar_falsy = Scalar::new(StringViewArray::from(vec!["world"])); + + let mask = BooleanArray::from(vec![false, false]); + let out = zip(&mask, &scalar_truthy, &scalar_falsy).unwrap(); + let actual = out.as_string_view(); + let expected = StringViewArray::from(vec![Some("world"), Some("world")]); + assert_eq!(actual, &expected); + } + + #[test] + fn test_zip_kernel_scalar_strings_large_strings() { + let scalar_truthy = Scalar::new(StringViewArray::from(vec!["longer than 12 bytes"])); + let scalar_falsy = Scalar::new(StringViewArray::from(vec!["another longer than 12 bytes"])); + + let mask = BooleanArray::from(vec![true, false]); + let out = zip(&mask, &scalar_truthy, &scalar_falsy).unwrap(); + let actual = out.as_string_view(); + let expected = StringViewArray::from(vec![ + Some("longer than 12 bytes"), + Some("another longer than 12 bytes"), + ]); + assert_eq!(actual, &expected); + } + + #[test] + fn test_zip_kernel_scalar_strings_array_view_large_short_strings() { + let scalar_truthy = Scalar::new(StringViewArray::from(vec!["hello"])); + let scalar_falsy = Scalar::new(StringViewArray::from(vec!["longer than 12 bytes"])); + + let mask = BooleanArray::from(vec![true, false, true, false]); + let out = zip(&mask, &scalar_truthy, &scalar_falsy).unwrap(); + let actual = out.as_string_view(); + let expected = StringViewArray::from(vec![ + Some("hello"), + Some("longer than 12 bytes"), + Some("hello"), + Some("longer than 12 bytes"), + ]); + assert_eq!(actual, &expected); + } + #[test] + fn test_zip_kernel_scalar_strings_array_view_large_all_true() { + let scalar_truthy = Scalar::new(StringViewArray::from(vec!["longer than 12 bytes"])); + let scalar_falsy = Scalar::new(StringViewArray::from(vec!["another longer than 12 bytes"])); + + let mask = BooleanArray::from(vec![true, true]); + let out = zip(&mask, &scalar_truthy, &scalar_falsy).unwrap(); + let actual = out.as_string_view(); + let expected = StringViewArray::from(vec![ + Some("longer than 12 bytes"), + Some("longer than 12 bytes"), + ]); + assert_eq!(actual, &expected); + } + + #[test] + fn test_zip_kernel_scalar_strings_array_view_large_all_false() { + let scalar_truthy = Scalar::new(StringViewArray::from(vec!["longer than 12 bytes"])); + let scalar_falsy = Scalar::new(StringViewArray::from(vec!["another longer than 12 bytes"])); + + let mask = BooleanArray::from(vec![false, false]); + let out = zip(&mask, &scalar_truthy, &scalar_falsy).unwrap(); + let actual = out.as_string_view(); + let expected = StringViewArray::from(vec![ + Some("another longer than 12 bytes"), + Some("another longer than 12 bytes"), + ]); + assert_eq!(actual, &expected); + } } From 4d4f39821994b1c9d465ecd930d71120f0621e03 Mon Sep 17 00:00:00 2001 From: Michael Kleen Date: Mon, 8 Dec 2025 15:17:58 +0100 Subject: [PATCH 02/13] Support both byte-types --- arrow-select/src/zip.rs | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/arrow-select/src/zip.rs b/arrow-select/src/zip.rs index c9a173919718..926c8c1bb32a 100644 --- a/arrow-select/src/zip.rs +++ b/arrow-select/src/zip.rs @@ -19,10 +19,7 @@ use crate::filter::{SlicesIterator, prep_null_mask_filter}; use arrow_array::cast::AsArray; -use arrow_array::types::{ - BinaryType, ByteArrayType, ByteViewType, LargeBinaryType, LargeUtf8Type, StringViewType, - Utf8Type, -}; +use arrow_array::types::{BinaryType, BinaryViewType, ByteArrayType, ByteViewType, LargeBinaryType, LargeUtf8Type, StringViewType, Utf8Type}; use arrow_array::*; use arrow_buffer::{ BooleanBuffer, Buffer, MutableBuffer, NullBuffer, OffsetBuffer, OffsetBufferBuilder, @@ -290,6 +287,9 @@ impl ScalarZipper { DataType::Utf8View => { Arc::new(ByteViewScalarImpl::::new(truthy, falsy)) as Arc }, + DataType::BinaryView => { + Arc::new(ByteViewScalarImpl::::new(truthy, falsy)) as Arc + }, _ => { Arc::new(FallbackImpl::new(truthy, falsy)) as Arc }, @@ -1421,6 +1421,21 @@ mod test { assert_eq!(actual, &expected); } + #[test] + fn test_zip_kernel_scalar_binary_array_view() { + let scalar_truthy = Scalar::new(BinaryViewArray::from_iter_values(vec![b"hello"])); + let scalar_falsy = Scalar::new(BinaryViewArray::from_iter_values(vec![b"world"])); + + let mask = BooleanArray::from(vec![true, false]); + let out = zip(&mask, &scalar_truthy, &scalar_falsy).unwrap(); + let actual = out.as_byte_view(); + let expected = BinaryViewArray::from_iter_values(vec![ + b"hello", + b"world", + ]); + assert_eq!(actual, &expected); + } + #[test] fn test_zip_kernel_scalar_strings_array_view_with_nulls() { let scalar_truthy = Scalar::new(StringViewArray::from_iter_values(["hello"])); From c815c33873307706d710903643a0009221e16b44 Mon Sep 17 00:00:00 2001 From: Michael Kleen Date: Mon, 8 Dec 2025 15:23:01 +0100 Subject: [PATCH 03/13] Fix formatting --- arrow-select/src/zip.rs | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/arrow-select/src/zip.rs b/arrow-select/src/zip.rs index 926c8c1bb32a..f351077f622f 100644 --- a/arrow-select/src/zip.rs +++ b/arrow-select/src/zip.rs @@ -19,7 +19,10 @@ use crate::filter::{SlicesIterator, prep_null_mask_filter}; use arrow_array::cast::AsArray; -use arrow_array::types::{BinaryType, BinaryViewType, ByteArrayType, ByteViewType, LargeBinaryType, LargeUtf8Type, StringViewType, Utf8Type}; +use arrow_array::types::{ + BinaryType, BinaryViewType, ByteArrayType, ByteViewType, LargeBinaryType, LargeUtf8Type, + StringViewType, Utf8Type, +}; use arrow_array::*; use arrow_buffer::{ BooleanBuffer, Buffer, MutableBuffer, NullBuffer, OffsetBuffer, OffsetBufferBuilder, @@ -1429,10 +1432,7 @@ mod test { let mask = BooleanArray::from(vec![true, false]); let out = zip(&mask, &scalar_truthy, &scalar_falsy).unwrap(); let actual = out.as_byte_view(); - let expected = BinaryViewArray::from_iter_values(vec![ - b"hello", - b"world", - ]); + let expected = BinaryViewArray::from_iter_values(vec![b"hello", b"world"]); assert_eq!(actual, &expected); } From 4cd5f91e7483b2a31d2d12d2c3a30494330b1c1b Mon Sep 17 00:00:00 2001 From: Michael Kleen Date: Sat, 13 Dec 2025 19:42:54 +0100 Subject: [PATCH 04/13] Rename function --- arrow-select/src/zip.rs | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/arrow-select/src/zip.rs b/arrow-select/src/zip.rs index f351077f622f..1f07dbc56706 100644 --- a/arrow-select/src/zip.rs +++ b/arrow-select/src/zip.rs @@ -785,7 +785,7 @@ impl ByteViewScalarImpl { (bytes.into(), buffers, Some(NullBuffer::new_valid(length))) } - fn create_output_on_non_nulls( + fn get_scalar_buffers_and_nulls_for_non_nullable( predicate: BooleanBuffer, result_len: usize, truthy: &GenericByteViewArray, @@ -822,13 +822,12 @@ impl ZipImpl for ByteViewScalarImpl { let predicate = maybe_prep_null_mask_filter(predicate); let (views, buffers, nulls) = match (self.truthy.as_ref(), self.falsy.as_ref()) { - (Some(truthy), Some(falsy)) => { - Self::create_output_on_non_nulls(predicate, result_len, truthy, falsy) - } + (Some(truthy), Some(falsy)) => Self::get_scalar_buffers_and_nulls_for_non_nullable( + predicate, result_len, truthy, falsy, + ), (Some(truthy), None) => { Self::get_scalar_buffers_and_nulls_for_single_non_nullable(predicate, truthy) } - (None, Some(falsy)) => { let predicate = predicate.not(); Self::get_scalar_buffers_and_nulls_for_single_non_nullable(predicate, falsy) From 738174f1d6266a670f169154a0bca948b9799c65 Mon Sep 17 00:00:00 2001 From: Michael Kleen Date: Mon, 29 Dec 2025 08:36:11 +0100 Subject: [PATCH 05/13] Only use views and buffer, no generic arrays --- arrow-select/src/zip.rs | 90 +++++++++++++++++++++++------------------ 1 file changed, 50 insertions(+), 40 deletions(-) diff --git a/arrow-select/src/zip.rs b/arrow-select/src/zip.rs index 1f07dbc56706..545e25743f72 100644 --- a/arrow-select/src/zip.rs +++ b/arrow-select/src/zip.rs @@ -666,25 +666,33 @@ fn maybe_prep_null_mask_filter(predicate: &BooleanArray) -> BooleanBuffer { } struct ByteViewScalarImpl { - truthy: Option>, - falsy: Option>, + truthy_view: Option, + truthy_buffers:Vec, + falsy_view: Option, + falsy_buffers: Vec, phantom: PhantomData, } impl ByteViewScalarImpl { fn new(truthy: &dyn Array, falsy: &dyn Array) -> Self { + let (truthy_view, truthy_buffers) = Self::get_value_from_scalar(truthy); + let (falsy_view, falsy_buffers) = Self::get_value_from_scalar(falsy); Self { - truthy: Self::get_value_from_scalar(truthy), - falsy: Self::get_value_from_scalar(falsy), + truthy_view, + truthy_buffers, + falsy_view, + falsy_buffers, phantom: PhantomData, } } - fn get_value_from_scalar(scalar: &dyn Array) -> Option> { + fn get_value_from_scalar(scalar: &dyn Array) -> (Option, Vec) { if scalar.is_null(0) { - None + (None, vec![]) } else { - Some(scalar.as_byte_view().clone()) + let byte_view: &GenericByteViewArray = scalar.as_byte_view(); + let (views, buffers, _) = byte_view.clone().into_parts(); + (views.first().copied(), buffers) } } @@ -699,7 +707,8 @@ impl ByteViewScalarImpl { fn get_scalar_buffers_and_nulls_for_single_non_nullable( predicate: BooleanBuffer, - value: &GenericByteViewArray, + value: u128, + buffers: Vec, ) -> (ScalarBuffer, Vec, Option) { let number_of_true = predicate.count_set_bits(); let number_of_values = predicate.len(); @@ -709,42 +718,41 @@ impl ByteViewScalarImpl { // All values are null return Self::get_scalar_buffers_and_nulls_for_all_values_null(number_of_values); } - let view = value.views()[0].to_byte_slice(); + let view = value.to_byte_slice(); let mut bytes = MutableBuffer::with_capacity(0); bytes.repeat_slice_n_times(view, number_of_values); let bytes = Buffer::from(bytes); - // If a value is true we need the TRUTHY and the null buffer will have 1 (meaning not null) - // If a value is false we need the FALSY and the null buffer will have 0 (meaning null) + // If value is true and we want to handle the TRUTHY case, the null buffer will have 1 (meaning not null) + // If value is false and we want to handle the FALSY case, the null buffer will have 0 (meaning null) let nulls = NullBuffer::new(predicate); - (bytes.into(), value.data_buffers().into(), Some(nulls)) + (bytes.into(), buffers, Some(nulls)) } fn get_scalar_buffers_and_nulls_non_nullable( predicate: BooleanBuffer, - truthy: &GenericByteViewArray, - falsy: &GenericByteViewArray, + truthy_view: u128, + truthy_buffers: Vec, + falsy_view: u128, + falsy_buffers: Vec, ) -> (ScalarBuffer, Vec, Option) { let true_count = predicate.count_set_bits(); - let view_truthy = truthy.views()[0].to_byte_slice(); - let mut buffers: Vec = truthy.data_buffers().to_vec(); - - // if falsy has non-inlined values in the buffer, + let mut buffers: Vec = truthy_buffers.to_vec(); + // If falsy has non-inlined values in the buffer, // include the buffers and recalculate the view, - // otherwise, we simply use the view. - let view_falsy = if falsy.total_buffer_bytes_used() > 0 { - let byte_view_falsy = ByteView::from(falsy.views()[0]); + // otherwise, we use the view. + let view_falsy = if falsy_buffers.len() > 0 { + let byte_view_falsy = ByteView::from(falsy_view); let new_index_falsy_buffers = buffers.len() as u32; - buffers.extend(falsy.data_buffers().to_vec()); + buffers.extend(falsy_buffers); let byte_view_falsy = byte_view_falsy.with_buffer_index(new_index_falsy_buffers); byte_view_falsy.as_u128() } else { - falsy.views()[0] + falsy_view }; - let total_number_of_bytes = true_count * view_truthy.len() - + (predicate.len() - true_count) * view_falsy.to_byte_slice().len(); + let total_number_of_bytes = true_count * 16 + (predicate.len() - true_count) * 16; let mut mutable = MutableBuffer::new(total_number_of_bytes); let mut filled = 0; @@ -754,7 +762,7 @@ impl ByteViewScalarImpl { mutable.repeat_slice_n_times(view_falsy.to_byte_slice(), false_repeat_count); } let true_repeat_count = end - start; - mutable.repeat_slice_n_times(view_truthy, true_repeat_count); + mutable.repeat_slice_n_times(truthy_view.to_byte_slice(), true_repeat_count); filled = end; }); @@ -774,11 +782,11 @@ impl ByteViewScalarImpl { fn get_scalar_buffers_and_nulls_for_all_same_value( length: usize, - value: &GenericByteViewArray, + view: u128, + buffers: Vec, ) -> (ScalarBuffer, Vec, Option) { - let (views, buffers, _) = value.clone().into_parts(); let mut mutable = MutableBuffer::with_capacity(0); - mutable.repeat_slice_n_times(views[0].to_byte_slice(), length); + mutable.repeat_slice_n_times(view.to_byte_slice(), length); let bytes = Buffer::from(mutable); @@ -788,20 +796,22 @@ impl ByteViewScalarImpl { fn get_scalar_buffers_and_nulls_for_non_nullable( predicate: BooleanBuffer, result_len: usize, - truthy: &GenericByteViewArray, - falsy: &GenericByteViewArray, + truthy_view: u128, + truthy_buffers: Vec, + falsy_view:u128, + falsy_buffers: Vec, ) -> (ScalarBuffer, Vec, Option) { let true_count = predicate.count_set_bits(); match true_count { 0 => { // all values are falsy - Self::get_scalar_buffers_and_nulls_for_all_same_value(result_len, falsy) + Self::get_scalar_buffers_and_nulls_for_all_same_value(result_len, falsy_view, falsy_buffers) } n if n == predicate.len() => { // all values are truthy - Self::get_scalar_buffers_and_nulls_for_all_same_value(result_len, truthy) + Self::get_scalar_buffers_and_nulls_for_all_same_value(result_len, truthy_view, truthy_buffers) } - _ => Self::get_scalar_buffers_and_nulls_non_nullable(predicate, truthy, falsy), + _ => Self::get_scalar_buffers_and_nulls_non_nullable(predicate, truthy_view, truthy_buffers, falsy_view, falsy_buffers), } } } @@ -809,8 +819,8 @@ impl ByteViewScalarImpl { impl Debug for ByteViewScalarImpl { fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { f.debug_struct("ByteViewScalarImpl") - .field("truthy", &self.truthy) - .field("falsy", &self.falsy) + .field("truthy", &self.truthy_view) + .field("falsy", &self.falsy_view) .finish() } } @@ -821,16 +831,16 @@ impl ZipImpl for ByteViewScalarImpl { // Nulls are treated as false let predicate = maybe_prep_null_mask_filter(predicate); - let (views, buffers, nulls) = match (self.truthy.as_ref(), self.falsy.as_ref()) { + let (views, buffers, nulls) = match (self.truthy_view, self.falsy_view) { (Some(truthy), Some(falsy)) => Self::get_scalar_buffers_and_nulls_for_non_nullable( - predicate, result_len, truthy, falsy, + predicate, result_len, truthy, self.truthy_buffers.clone(), falsy, self.falsy_buffers.clone() ), (Some(truthy), None) => { - Self::get_scalar_buffers_and_nulls_for_single_non_nullable(predicate, truthy) + Self::get_scalar_buffers_and_nulls_for_single_non_nullable(predicate, truthy, self.truthy_buffers.clone()) } (None, Some(falsy)) => { let predicate = predicate.not(); - Self::get_scalar_buffers_and_nulls_for_single_non_nullable(predicate, falsy) + Self::get_scalar_buffers_and_nulls_for_single_non_nullable(predicate, falsy, self.falsy_buffers.clone()) } (None, None) => Self::get_scalar_buffers_and_nulls_for_all_values_null(result_len), }; From 2cee75ce5599d31c4335aea9981e5839eefade47 Mon Sep 17 00:00:00 2001 From: Michael Kleen Date: Mon, 29 Dec 2025 22:26:51 +0100 Subject: [PATCH 06/13] Simplify case where all values are null --- arrow-select/src/zip.rs | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/arrow-select/src/zip.rs b/arrow-select/src/zip.rs index 545e25743f72..51a7082aedd7 100644 --- a/arrow-select/src/zip.rs +++ b/arrow-select/src/zip.rs @@ -699,10 +699,7 @@ impl ByteViewScalarImpl { fn get_scalar_buffers_and_nulls_for_all_values_null( len: usize, ) -> (ScalarBuffer, Vec, Option) { - let mut mutable = MutableBuffer::with_capacity(0); - mutable.repeat_slice_n_times((0u128).to_byte_slice(), len); - - (mutable.into(), vec![], Some(NullBuffer::new_null(len))) + (vec![0; len].into(), vec![], Some(NullBuffer::new_null(len))) } fn get_scalar_buffers_and_nulls_for_single_non_nullable( From 1d256a4f9ff1e35181c76d54635109d046cabd66 Mon Sep 17 00:00:00 2001 From: Michael Kleen Date: Mon, 29 Dec 2025 22:32:14 +0100 Subject: [PATCH 07/13] Remove mutable buffer for single non nullable --- arrow-select/src/zip.rs | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/arrow-select/src/zip.rs b/arrow-select/src/zip.rs index 51a7082aedd7..e3a3ddc100e3 100644 --- a/arrow-select/src/zip.rs +++ b/arrow-select/src/zip.rs @@ -715,11 +715,7 @@ impl ByteViewScalarImpl { // All values are null return Self::get_scalar_buffers_and_nulls_for_all_values_null(number_of_values); } - let view = value.to_byte_slice(); - let mut bytes = MutableBuffer::with_capacity(0); - bytes.repeat_slice_n_times(view, number_of_values); - - let bytes = Buffer::from(bytes); + let bytes = vec![value; number_of_values]; // If value is true and we want to handle the TRUTHY case, the null buffer will have 1 (meaning not null) // If value is false and we want to handle the FALSY case, the null buffer will have 0 (meaning null) From 06359d6da764905d0af9e5662df7f09506e2a2d9 Mon Sep 17 00:00:00 2001 From: Michael Kleen Date: Mon, 29 Dec 2025 22:33:15 +0100 Subject: [PATCH 08/13] Fix new_index_falsy_buffers --- arrow-select/src/zip.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/arrow-select/src/zip.rs b/arrow-select/src/zip.rs index e3a3ddc100e3..bfd3fa6ad260 100644 --- a/arrow-select/src/zip.rs +++ b/arrow-select/src/zip.rs @@ -737,7 +737,7 @@ impl ByteViewScalarImpl { // otherwise, we use the view. let view_falsy = if falsy_buffers.len() > 0 { let byte_view_falsy = ByteView::from(falsy_view); - let new_index_falsy_buffers = buffers.len() as u32; + let new_index_falsy_buffers = buffers.len() as u32 + byte_view_falsy.buffer_index; buffers.extend(falsy_buffers); let byte_view_falsy = byte_view_falsy.with_buffer_index(new_index_falsy_buffers); byte_view_falsy.as_u128() From cbda3a41037660f4b09e6f6859fb485df14c5139 Mon Sep 17 00:00:00 2001 From: Michael Kleen Date: Mon, 29 Dec 2025 22:35:46 +0100 Subject: [PATCH 09/13] Remove mutable buffer for all the same value case --- arrow-select/src/zip.rs | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/arrow-select/src/zip.rs b/arrow-select/src/zip.rs index bfd3fa6ad260..89785f7352fc 100644 --- a/arrow-select/src/zip.rs +++ b/arrow-select/src/zip.rs @@ -769,7 +769,7 @@ impl ByteViewScalarImpl { ( bytes.into(), buffers, - Some(NullBuffer::new_valid(predicate.len())), + None, ) } @@ -778,12 +778,7 @@ impl ByteViewScalarImpl { view: u128, buffers: Vec, ) -> (ScalarBuffer, Vec, Option) { - let mut mutable = MutableBuffer::with_capacity(0); - mutable.repeat_slice_n_times(view.to_byte_slice(), length); - - let bytes = Buffer::from(mutable); - - (bytes.into(), buffers, Some(NullBuffer::new_valid(length))) + (vec![view; length].into(), buffers, None) } fn get_scalar_buffers_and_nulls_for_non_nullable( From f65bb3468ae6cc6d259a135c4c2b190cf191db33 Mon Sep 17 00:00:00 2001 From: Michael Kleen Date: Mon, 29 Dec 2025 23:46:20 +0100 Subject: [PATCH 10/13] Fix formatting --- arrow-select/src/zip.rs | 74 ++++++++++++++++++++++++++++------------- 1 file changed, 50 insertions(+), 24 deletions(-) diff --git a/arrow-select/src/zip.rs b/arrow-select/src/zip.rs index 89785f7352fc..b2349d1bcfa3 100644 --- a/arrow-select/src/zip.rs +++ b/arrow-select/src/zip.rs @@ -667,7 +667,7 @@ fn maybe_prep_null_mask_filter(predicate: &BooleanArray) -> BooleanBuffer { struct ByteViewScalarImpl { truthy_view: Option, - truthy_buffers:Vec, + truthy_buffers: Vec, falsy_view: Option, falsy_buffers: Vec, phantom: PhantomData, @@ -696,15 +696,9 @@ impl ByteViewScalarImpl { } } - fn get_scalar_buffers_and_nulls_for_all_values_null( - len: usize, - ) -> (ScalarBuffer, Vec, Option) { - (vec![0; len].into(), vec![], Some(NullBuffer::new_null(len))) - } - fn get_scalar_buffers_and_nulls_for_single_non_nullable( predicate: BooleanBuffer, - value: u128, + value: u128, buffers: Vec, ) -> (ScalarBuffer, Vec, Option) { let number_of_true = predicate.count_set_bits(); @@ -713,7 +707,11 @@ impl ByteViewScalarImpl { // Fast path for all nulls if number_of_true == 0 { // All values are null - return Self::get_scalar_buffers_and_nulls_for_all_values_null(number_of_values); + return ( + vec![0; number_of_values].into(), + vec![], + Some(NullBuffer::new_null(number_of_values)), + ); } let bytes = vec![value; number_of_values]; @@ -766,11 +764,7 @@ impl ByteViewScalarImpl { let bytes = Buffer::from(mutable); - ( - bytes.into(), - buffers, - None, - ) + (bytes.into(), buffers, None) } fn get_scalar_buffers_and_nulls_for_all_same_value( @@ -786,20 +780,34 @@ impl ByteViewScalarImpl { result_len: usize, truthy_view: u128, truthy_buffers: Vec, - falsy_view:u128, + falsy_view: u128, falsy_buffers: Vec, ) -> (ScalarBuffer, Vec, Option) { let true_count = predicate.count_set_bits(); match true_count { 0 => { // all values are falsy - Self::get_scalar_buffers_and_nulls_for_all_same_value(result_len, falsy_view, falsy_buffers) + Self::get_scalar_buffers_and_nulls_for_all_same_value( + result_len, + falsy_view, + falsy_buffers, + ) } n if n == predicate.len() => { // all values are truthy - Self::get_scalar_buffers_and_nulls_for_all_same_value(result_len, truthy_view, truthy_buffers) + Self::get_scalar_buffers_and_nulls_for_all_same_value( + result_len, + truthy_view, + truthy_buffers, + ) } - _ => Self::get_scalar_buffers_and_nulls_non_nullable(predicate, truthy_view, truthy_buffers, falsy_view, falsy_buffers), + _ => Self::get_scalar_buffers_and_nulls_non_nullable( + predicate, + truthy_view, + truthy_buffers, + falsy_view, + falsy_buffers, + ), } } } @@ -821,16 +829,34 @@ impl ZipImpl for ByteViewScalarImpl { let (views, buffers, nulls) = match (self.truthy_view, self.falsy_view) { (Some(truthy), Some(falsy)) => Self::get_scalar_buffers_and_nulls_for_non_nullable( - predicate, result_len, truthy, self.truthy_buffers.clone(), falsy, self.falsy_buffers.clone() + predicate, + result_len, + truthy, + self.truthy_buffers.clone(), + falsy, + self.falsy_buffers.clone(), + ), + (Some(truthy), None) => Self::get_scalar_buffers_and_nulls_for_single_non_nullable( + predicate, + truthy, + self.truthy_buffers.clone(), ), - (Some(truthy), None) => { - Self::get_scalar_buffers_and_nulls_for_single_non_nullable(predicate, truthy, self.truthy_buffers.clone()) - } (None, Some(falsy)) => { let predicate = predicate.not(); - Self::get_scalar_buffers_and_nulls_for_single_non_nullable(predicate, falsy, self.falsy_buffers.clone()) + Self::get_scalar_buffers_and_nulls_for_single_non_nullable( + predicate, + falsy, + self.falsy_buffers.clone(), + ) + } + (None, None) => { + // All values are null + ( + vec![0; result_len].into(), + vec![], + Some(NullBuffer::new_null(result_len)), + ) } - (None, None) => Self::get_scalar_buffers_and_nulls_for_all_values_null(result_len), }; let result = unsafe { GenericByteViewArray::::new_unchecked(views, buffers, nulls) }; From 9025aa9ac48a28ec8666d031085788b14b41ace0 Mon Sep 17 00:00:00 2001 From: Michael Kleen Date: Tue, 30 Dec 2025 00:24:03 +0100 Subject: [PATCH 11/13] More inlining --- arrow-select/src/zip.rs | 37 ++++++++++--------------------------- 1 file changed, 10 insertions(+), 27 deletions(-) diff --git a/arrow-select/src/zip.rs b/arrow-select/src/zip.rs index b2349d1bcfa3..432431f6e328 100644 --- a/arrow-select/src/zip.rs +++ b/arrow-select/src/zip.rs @@ -690,8 +690,8 @@ impl ByteViewScalarImpl { if scalar.is_null(0) { (None, vec![]) } else { - let byte_view: &GenericByteViewArray = scalar.as_byte_view(); - let (views, buffers, _) = byte_view.clone().into_parts(); + let byte_view: GenericByteViewArray = scalar.as_byte_view().clone(); + let (views, buffers, _) = byte_view.into_parts(); (views.first().copied(), buffers) } } @@ -730,17 +730,17 @@ impl ByteViewScalarImpl { ) -> (ScalarBuffer, Vec, Option) { let true_count = predicate.count_set_bits(); let mut buffers: Vec = truthy_buffers.to_vec(); - // If falsy has non-inlined values in the buffer, - // include the buffers and recalculate the view, - // otherwise, we use the view. - let view_falsy = if falsy_buffers.len() > 0 { + + // if the falsy buffers are empty, we can use the falsy view as it is, + // other we have non-inlined values in the buffer and we need to recalculate the falsy view + let view_falsy = if falsy_buffers.is_empty() { + falsy_view + } else { let byte_view_falsy = ByteView::from(falsy_view); let new_index_falsy_buffers = buffers.len() as u32 + byte_view_falsy.buffer_index; buffers.extend(falsy_buffers); let byte_view_falsy = byte_view_falsy.with_buffer_index(new_index_falsy_buffers); byte_view_falsy.as_u128() - } else { - falsy_view }; let total_number_of_bytes = true_count * 16 + (predicate.len() - true_count) * 16; @@ -763,18 +763,9 @@ impl ByteViewScalarImpl { } let bytes = Buffer::from(mutable); - (bytes.into(), buffers, None) } - fn get_scalar_buffers_and_nulls_for_all_same_value( - length: usize, - view: u128, - buffers: Vec, - ) -> (ScalarBuffer, Vec, Option) { - (vec![view; length].into(), buffers, None) - } - fn get_scalar_buffers_and_nulls_for_non_nullable( predicate: BooleanBuffer, result_len: usize, @@ -787,19 +778,11 @@ impl ByteViewScalarImpl { match true_count { 0 => { // all values are falsy - Self::get_scalar_buffers_and_nulls_for_all_same_value( - result_len, - falsy_view, - falsy_buffers, - ) + (vec![falsy_view; result_len].into(), falsy_buffers, None) } n if n == predicate.len() => { // all values are truthy - Self::get_scalar_buffers_and_nulls_for_all_same_value( - result_len, - truthy_view, - truthy_buffers, - ) + (vec![truthy_view; result_len].into(), truthy_buffers, None) } _ => Self::get_scalar_buffers_and_nulls_non_nullable( predicate, From 0cc2aa05b02715c31a43e7d6693be9d5f0328031 Mon Sep 17 00:00:00 2001 From: Michael Kleen Date: Tue, 30 Dec 2025 09:05:49 +0100 Subject: [PATCH 12/13] iter --- arrow-select/src/zip.rs | 106 +++++++++++++++++----------------------- 1 file changed, 46 insertions(+), 60 deletions(-) diff --git a/arrow-select/src/zip.rs b/arrow-select/src/zip.rs index 432431f6e328..22c9894e452b 100644 --- a/arrow-select/src/zip.rs +++ b/arrow-select/src/zip.rs @@ -690,13 +690,12 @@ impl ByteViewScalarImpl { if scalar.is_null(0) { (None, vec![]) } else { - let byte_view: GenericByteViewArray = scalar.as_byte_view().clone(); - let (views, buffers, _) = byte_view.into_parts(); + let (views, buffers, _) = scalar.as_byte_view::().clone().into_parts(); (views.first().copied(), buffers) } } - fn get_scalar_buffers_and_nulls_for_single_non_nullable( + fn get_views_for_single_non_nullable( predicate: BooleanBuffer, value: u128, buffers: Vec, @@ -720,53 +719,8 @@ impl ByteViewScalarImpl { let nulls = NullBuffer::new(predicate); (bytes.into(), buffers, Some(nulls)) } - - fn get_scalar_buffers_and_nulls_non_nullable( - predicate: BooleanBuffer, - truthy_view: u128, - truthy_buffers: Vec, - falsy_view: u128, - falsy_buffers: Vec, - ) -> (ScalarBuffer, Vec, Option) { - let true_count = predicate.count_set_bits(); - let mut buffers: Vec = truthy_buffers.to_vec(); - - // if the falsy buffers are empty, we can use the falsy view as it is, - // other we have non-inlined values in the buffer and we need to recalculate the falsy view - let view_falsy = if falsy_buffers.is_empty() { - falsy_view - } else { - let byte_view_falsy = ByteView::from(falsy_view); - let new_index_falsy_buffers = buffers.len() as u32 + byte_view_falsy.buffer_index; - buffers.extend(falsy_buffers); - let byte_view_falsy = byte_view_falsy.with_buffer_index(new_index_falsy_buffers); - byte_view_falsy.as_u128() - }; - - let total_number_of_bytes = true_count * 16 + (predicate.len() - true_count) * 16; - let mut mutable = MutableBuffer::new(total_number_of_bytes); - let mut filled = 0; - - SlicesIterator::from(&predicate).for_each(|(start, end)| { - if start > filled { - let false_repeat_count = start - filled; - mutable.repeat_slice_n_times(view_falsy.to_byte_slice(), false_repeat_count); - } - let true_repeat_count = end - start; - mutable.repeat_slice_n_times(truthy_view.to_byte_slice(), true_repeat_count); - filled = end; - }); - - if filled < predicate.len() { - let false_repeat_count = predicate.len() - filled; - mutable.repeat_slice_n_times(view_falsy.to_byte_slice(), false_repeat_count); - } - - let bytes = Buffer::from(mutable); - (bytes.into(), buffers, None) - } - - fn get_scalar_buffers_and_nulls_for_non_nullable( + + fn get_views_for_non_nullable( predicate: BooleanBuffer, result_len: usize, truthy_view: u128, @@ -784,13 +738,45 @@ impl ByteViewScalarImpl { // all values are truthy (vec![truthy_view; result_len].into(), truthy_buffers, None) } - _ => Self::get_scalar_buffers_and_nulls_non_nullable( - predicate, - truthy_view, - truthy_buffers, - falsy_view, - falsy_buffers, - ), + _ => { + let true_count = predicate.count_set_bits(); + let mut buffers: Vec = truthy_buffers.to_vec(); + + // If the falsy buffers are empty, we can use the falsy view as it is, because the value + // is completely inlined. Otherwise, we have non-inlined values in the buffer, and we need + // to recalculate the falsy view + let view_falsy = if falsy_buffers.is_empty() { + falsy_view + } else { + let byte_view_falsy = ByteView::from(falsy_view); + let new_index_falsy_buffers = buffers.len() as u32 + byte_view_falsy.buffer_index; + buffers.extend(falsy_buffers); + let byte_view_falsy = byte_view_falsy.with_buffer_index(new_index_falsy_buffers); + byte_view_falsy.as_u128() + }; + + let total_number_of_bytes = true_count * 16 + (predicate.len() - true_count) * 16; + let mut mutable = MutableBuffer::new(total_number_of_bytes); + let mut filled = 0; + + SlicesIterator::from(&predicate).for_each(|(start, end)| { + if start > filled { + let false_repeat_count = start - filled; + mutable.repeat_slice_n_times(view_falsy.to_byte_slice(), false_repeat_count); + } + let true_repeat_count = end - start; + mutable.repeat_slice_n_times(truthy_view.to_byte_slice(), true_repeat_count); + filled = end; + }); + + if filled < predicate.len() { + let false_repeat_count = predicate.len() - filled; + mutable.repeat_slice_n_times(view_falsy.to_byte_slice(), false_repeat_count); + } + + let bytes = Buffer::from(mutable); + (bytes.into(), buffers, None) + } } } } @@ -811,7 +797,7 @@ impl ZipImpl for ByteViewScalarImpl { let predicate = maybe_prep_null_mask_filter(predicate); let (views, buffers, nulls) = match (self.truthy_view, self.falsy_view) { - (Some(truthy), Some(falsy)) => Self::get_scalar_buffers_and_nulls_for_non_nullable( + (Some(truthy), Some(falsy)) => Self::get_views_for_non_nullable( predicate, result_len, truthy, @@ -819,14 +805,14 @@ impl ZipImpl for ByteViewScalarImpl { falsy, self.falsy_buffers.clone(), ), - (Some(truthy), None) => Self::get_scalar_buffers_and_nulls_for_single_non_nullable( + (Some(truthy), None) => Self::get_views_for_single_non_nullable( predicate, truthy, self.truthy_buffers.clone(), ), (None, Some(falsy)) => { let predicate = predicate.not(); - Self::get_scalar_buffers_and_nulls_for_single_non_nullable( + Self::get_views_for_single_non_nullable( predicate, falsy, self.falsy_buffers.clone(), From fc7390a2625941fac5be9263e42167cbec33ece6 Mon Sep 17 00:00:00 2001 From: Michael Kleen Date: Wed, 31 Dec 2025 06:39:41 +0100 Subject: [PATCH 13/13] Fix formatting --- arrow-select/src/zip.rs | 11 +++++++---- 1 file changed, 7 insertions(+), 4 deletions(-) diff --git a/arrow-select/src/zip.rs b/arrow-select/src/zip.rs index 22c9894e452b..6be034fca23d 100644 --- a/arrow-select/src/zip.rs +++ b/arrow-select/src/zip.rs @@ -719,7 +719,7 @@ impl ByteViewScalarImpl { let nulls = NullBuffer::new(predicate); (bytes.into(), buffers, Some(nulls)) } - + fn get_views_for_non_nullable( predicate: BooleanBuffer, result_len: usize, @@ -749,9 +749,11 @@ impl ByteViewScalarImpl { falsy_view } else { let byte_view_falsy = ByteView::from(falsy_view); - let new_index_falsy_buffers = buffers.len() as u32 + byte_view_falsy.buffer_index; + let new_index_falsy_buffers = + buffers.len() as u32 + byte_view_falsy.buffer_index; buffers.extend(falsy_buffers); - let byte_view_falsy = byte_view_falsy.with_buffer_index(new_index_falsy_buffers); + let byte_view_falsy = + byte_view_falsy.with_buffer_index(new_index_falsy_buffers); byte_view_falsy.as_u128() }; @@ -762,7 +764,8 @@ impl ByteViewScalarImpl { SlicesIterator::from(&predicate).for_each(|(start, end)| { if start > filled { let false_repeat_count = start - filled; - mutable.repeat_slice_n_times(view_falsy.to_byte_slice(), false_repeat_count); + mutable + .repeat_slice_n_times(view_falsy.to_byte_slice(), false_repeat_count); } let true_repeat_count = end - start; mutable.repeat_slice_n_times(truthy_view.to_byte_slice(), true_repeat_count);