use std::alloc::Layout;
use std::fmt::Debug;
use std::ptr::NonNull;
use std::sync::Arc;
use crate::BufferBuilder;
use crate::alloc::{Allocation, Deallocation};
use crate::util::bit_chunk_iterator::{BitChunks, UnalignedBitChunk};
use crate::{bit_util, bytes::Bytes, native::ArrowNativeType};
#[cfg(feature = "pool")]
use crate::pool::MemoryPool;
use super::ops::bitwise_unary_op_helper;
use super::{MutableBuffer, ScalarBuffer};
#[derive(Clone, Debug)]
pub struct Buffer {
data: Arc<Bytes>,
ptr: *const u8,
length: usize,
}
impl Default for Buffer {
#[inline]
fn default() -> Self {
MutableBuffer::default().into()
}
}
impl PartialEq for Buffer {
fn eq(&self, other: &Self) -> bool {
self.as_slice().eq(other.as_slice())
}
}
impl Eq for Buffer {}
unsafe impl Send for Buffer where Bytes: Send {}
unsafe impl Sync for Buffer where Bytes: Sync {}
impl Buffer {
#[deprecated(since = "54.1.0", note = "Use Buffer::from instead")]
pub fn from_bytes(bytes: Bytes) -> Self {
Self::from(bytes)
}
pub fn ptr_offset(&self) -> usize {
unsafe { self.ptr.offset_from(self.data.ptr().as_ptr()) as usize }
}
pub fn data_ptr(&self) -> NonNull<u8> {
self.data.ptr()
}
pub fn strong_count(&self) -> usize {
Arc::strong_count(&self.data)
}
#[inline]
pub fn from_vec<T: ArrowNativeType>(vec: Vec<T>) -> Self {
MutableBuffer::from(vec).into()
}
pub fn from_slice_ref<U: ArrowNativeType, T: AsRef<[U]>>(items: T) -> Self {
let slice = items.as_ref();
let capacity = std::mem::size_of_val(slice);
let mut buffer = MutableBuffer::with_capacity(capacity);
buffer.extend_from_slice(slice);
buffer.into()
}
pub unsafe fn from_custom_allocation(
ptr: NonNull<u8>,
len: usize,
owner: Arc<dyn Allocation>,
) -> Self {
unsafe { Buffer::build_with_arguments(ptr, len, Deallocation::Custom(owner, len)) }
}
unsafe fn build_with_arguments(
ptr: NonNull<u8>,
len: usize,
deallocation: Deallocation,
) -> Self {
let bytes = unsafe { Bytes::new(ptr, len, deallocation) };
let ptr = bytes.as_ptr();
Buffer {
ptr,
data: Arc::new(bytes),
length: len,
}
}
#[inline]
pub fn len(&self) -> usize {
self.length
}
#[inline]
pub fn capacity(&self) -> usize {
self.data.capacity()
}
pub fn shrink_to_fit(&mut self) {
let offset = self.ptr_offset();
let is_empty = self.is_empty();
let desired_capacity = if is_empty {
0
} else {
offset + self.len()
};
if desired_capacity < self.capacity() {
if let Some(bytes) = Arc::get_mut(&mut self.data) {
if bytes.try_realloc(desired_capacity).is_ok() {
self.ptr = if is_empty {
bytes.as_ptr()
} else {
unsafe { bytes.as_ptr().add(offset) }
}
} else {
}
}
}
}
#[inline]
pub fn is_empty(&self) -> bool {
self.length == 0
}
pub fn as_slice(&self) -> &[u8] {
unsafe { std::slice::from_raw_parts(self.ptr, self.length) }
}
pub(crate) fn deallocation(&self) -> &Deallocation {
self.data.deallocation()
}
pub fn slice(&self, offset: usize) -> Self {
let mut s = self.clone();
s.advance(offset);
s
}
#[inline]
pub fn advance(&mut self, offset: usize) {
assert!(
offset <= self.length,
"the offset of the new Buffer cannot exceed the existing length: offset={} length={}",
offset,
self.length
);
self.length -= offset;
self.ptr = unsafe { self.ptr.add(offset) };
}
pub fn slice_with_length(&self, offset: usize, length: usize) -> Self {
assert!(
offset.saturating_add(length) <= self.length,
"the offset of the new Buffer cannot exceed the existing length: slice offset={offset} length={length} selflen={}",
self.length
);
let ptr = unsafe { self.ptr.add(offset) };
Self {
data: self.data.clone(),
ptr,
length,
}
}
#[inline]
pub fn as_ptr(&self) -> *const u8 {
self.ptr
}
pub fn typed_data<T: ArrowNativeType>(&self) -> &[T] {
let (prefix, offsets, suffix) = unsafe { self.as_slice().align_to::<T>() };
assert!(prefix.is_empty() && suffix.is_empty());
offsets
}
pub fn bit_slice(&self, offset: usize, len: usize) -> Self {
if offset % 8 == 0 {
return self.slice_with_length(offset / 8, bit_util::ceil(len, 8));
}
bitwise_unary_op_helper(self, offset, len, |a| a)
}
pub fn bit_chunks(&self, offset: usize, len: usize) -> BitChunks<'_> {
BitChunks::new(self.as_slice(), offset, len)
}
pub fn count_set_bits_offset(&self, offset: usize, len: usize) -> usize {
UnalignedBitChunk::new(self.as_slice(), offset, len).count_ones()
}
pub fn into_mutable(self) -> Result<MutableBuffer, Self> {
let ptr = self.ptr;
let length = self.length;
Arc::try_unwrap(self.data)
.and_then(|bytes| {
assert_eq!(ptr, bytes.ptr().as_ptr());
MutableBuffer::from_bytes(bytes).map_err(Arc::new)
})
.map_err(|bytes| Buffer {
data: bytes,
ptr,
length,
})
}
pub fn into_vec<T: ArrowNativeType>(self) -> Result<Vec<T>, Self> {
let layout = match self.data.deallocation() {
Deallocation::Standard(l) => l,
_ => return Err(self), };
if self.ptr != self.data.as_ptr() {
return Err(self); }
let v_capacity = layout.size() / std::mem::size_of::<T>();
match Layout::array::<T>(v_capacity) {
Ok(expected) if layout == &expected => {}
_ => return Err(self), }
let length = self.length;
let ptr = self.ptr;
let v_len = self.length / std::mem::size_of::<T>();
Arc::try_unwrap(self.data)
.map(|bytes| unsafe {
let ptr = bytes.ptr().as_ptr() as _;
std::mem::forget(bytes);
Vec::from_raw_parts(ptr, v_len, v_capacity)
})
.map_err(|bytes| Buffer {
data: bytes,
ptr,
length,
})
}
#[inline]
pub fn ptr_eq(&self, other: &Self) -> bool {
self.ptr == other.ptr && self.length == other.length
}
#[cfg(feature = "pool")]
pub fn claim(&self, pool: &dyn MemoryPool) {
self.data.claim(pool)
}
}
impl From<&[u8]> for Buffer {
fn from(p: &[u8]) -> Self {
Self::from_slice_ref(p)
}
}
impl<const N: usize> From<[u8; N]> for Buffer {
fn from(p: [u8; N]) -> Self {
Self::from_slice_ref(p)
}
}
impl<const N: usize> From<&[u8; N]> for Buffer {
fn from(p: &[u8; N]) -> Self {
Self::from_slice_ref(p)
}
}
impl<T: ArrowNativeType> From<Vec<T>> for Buffer {
fn from(value: Vec<T>) -> Self {
Self::from_vec(value)
}
}
impl<T: ArrowNativeType> From<ScalarBuffer<T>> for Buffer {
fn from(value: ScalarBuffer<T>) -> Self {
value.into_inner()
}
}
impl From<Bytes> for Buffer {
#[inline]
fn from(bytes: Bytes) -> Self {
let length = bytes.len();
let ptr = bytes.as_ptr();
Self {
data: Arc::new(bytes),
ptr,
length,
}
}
}
impl From<bytes::Bytes> for Buffer {
fn from(bytes: bytes::Bytes) -> Self {
let bytes: Bytes = bytes.into();
Self::from(bytes)
}
}
impl FromIterator<bool> for Buffer {
fn from_iter<I>(iter: I) -> Self
where
I: IntoIterator<Item = bool>,
{
MutableBuffer::from_iter(iter).into()
}
}
impl std::ops::Deref for Buffer {
type Target = [u8];
fn deref(&self) -> &[u8] {
unsafe { std::slice::from_raw_parts(self.as_ptr(), self.len()) }
}
}
impl AsRef<[u8]> for &Buffer {
fn as_ref(&self) -> &[u8] {
self.as_slice()
}
}
impl From<MutableBuffer> for Buffer {
#[inline]
fn from(buffer: MutableBuffer) -> Self {
buffer.into_buffer()
}
}
impl<T: ArrowNativeType> From<BufferBuilder<T>> for Buffer {
fn from(mut value: BufferBuilder<T>) -> Self {
value.finish()
}
}
impl Buffer {
#[inline]
pub unsafe fn from_trusted_len_iter<T: ArrowNativeType, I: Iterator<Item = T>>(
iterator: I,
) -> Self {
unsafe { MutableBuffer::from_trusted_len_iter(iterator).into() }
}
#[inline]
pub unsafe fn try_from_trusted_len_iter<
E,
T: ArrowNativeType,
I: Iterator<Item = Result<T, E>>,
>(
iterator: I,
) -> Result<Self, E> {
unsafe { Ok(MutableBuffer::try_from_trusted_len_iter(iterator)?.into()) }
}
}
impl<T: ArrowNativeType> FromIterator<T> for Buffer {
fn from_iter<I: IntoIterator<Item = T>>(iter: I) -> Self {
let vec = Vec::from_iter(iter);
Buffer::from_vec(vec)
}
}
#[cfg(test)]
mod tests {
use crate::i256;
use std::panic::{RefUnwindSafe, UnwindSafe};
use std::thread;
use super::*;
#[test]
fn test_buffer_data_equality() {
let buf1 = Buffer::from(&[0, 1, 2, 3, 4]);
let buf2 = Buffer::from(&[0, 1, 2, 3, 4]);
assert_eq!(buf1, buf2);
let buf3 = buf1.slice(2);
assert_ne!(buf1, buf3);
let buf4 = buf2.slice_with_length(2, 3);
assert_eq!(buf3, buf4);
let mut buf2 = MutableBuffer::new(65);
buf2.extend_from_slice(&[0u8, 1, 2, 3, 4]);
let buf2 = buf2.into();
assert_eq!(buf1, buf2);
let buf2 = Buffer::from(&[0, 0, 2, 3, 4]);
assert_ne!(buf1, buf2);
let buf2 = Buffer::from(&[0, 1, 2, 3]);
assert_ne!(buf1, buf2);
}
#[test]
fn test_from_raw_parts() {
let buf = Buffer::from(&[0, 1, 2, 3, 4]);
assert_eq!(5, buf.len());
assert!(!buf.as_ptr().is_null());
assert_eq!([0, 1, 2, 3, 4], buf.as_slice());
}
#[test]
fn test_from_vec() {
let buf = Buffer::from(&[0, 1, 2, 3, 4]);
assert_eq!(5, buf.len());
assert!(!buf.as_ptr().is_null());
assert_eq!([0, 1, 2, 3, 4], buf.as_slice());
}
#[test]
fn test_copy() {
let buf = Buffer::from(&[0, 1, 2, 3, 4]);
let buf2 = buf;
assert_eq!(5, buf2.len());
assert_eq!(64, buf2.capacity());
assert!(!buf2.as_ptr().is_null());
assert_eq!([0, 1, 2, 3, 4], buf2.as_slice());
}
#[test]
fn test_slice() {
let buf = Buffer::from(&[2, 4, 6, 8, 10]);
let buf2 = buf.slice(2);
assert_eq!([6, 8, 10], buf2.as_slice());
assert_eq!(3, buf2.len());
assert_eq!(unsafe { buf.as_ptr().offset(2) }, buf2.as_ptr());
let buf3 = buf2.slice_with_length(1, 2);
assert_eq!([8, 10], buf3.as_slice());
assert_eq!(2, buf3.len());
assert_eq!(unsafe { buf.as_ptr().offset(3) }, buf3.as_ptr());
let buf4 = buf.slice(5);
let empty_slice: [u8; 0] = [];
assert_eq!(empty_slice, buf4.as_slice());
assert_eq!(0, buf4.len());
assert!(buf4.is_empty());
assert_eq!(buf2.slice_with_length(2, 1).as_slice(), &[10]);
}
#[test]
fn test_shrink_to_fit() {
let original = Buffer::from(&[0, 1, 2, 3, 4, 5, 6, 7]);
assert_eq!(original.as_slice(), &[0, 1, 2, 3, 4, 5, 6, 7]);
assert_eq!(original.capacity(), 64);
let slice = original.slice_with_length(2, 3);
drop(original); assert_eq!(slice.as_slice(), &[2, 3, 4]);
assert_eq!(slice.capacity(), 64);
let mut shrunk = slice;
shrunk.shrink_to_fit();
assert_eq!(shrunk.as_slice(), &[2, 3, 4]);
assert_eq!(shrunk.capacity(), 5);
let empty_slice = shrunk.slice_with_length(1, 0);
drop(shrunk); assert_eq!(empty_slice.as_slice(), &[]);
assert_eq!(empty_slice.capacity(), 5);
let mut shrunk_empty = empty_slice;
shrunk_empty.shrink_to_fit();
assert_eq!(shrunk_empty.as_slice(), &[]);
assert_eq!(shrunk_empty.capacity(), 0);
}
#[test]
#[should_panic(expected = "the offset of the new Buffer cannot exceed the existing length")]
fn test_slice_offset_out_of_bound() {
let buf = Buffer::from(&[2, 4, 6, 8, 10]);
buf.slice(6);
}
#[test]
fn test_access_concurrently() {
let buffer = Buffer::from([1, 2, 3, 4, 5]);
let buffer2 = buffer.clone();
assert_eq!([1, 2, 3, 4, 5], buffer.as_slice());
let buffer_copy = thread::spawn(move || {
buffer
})
.join();
assert!(buffer_copy.is_ok());
assert_eq!(buffer2, buffer_copy.ok().unwrap());
}
macro_rules! check_as_typed_data {
($input: expr, $native_t: ty) => {{
let buffer = Buffer::from_slice_ref($input);
let slice: &[$native_t] = buffer.typed_data::<$native_t>();
assert_eq!($input, slice);
}};
}
#[test]
#[allow(clippy::float_cmp)]
fn test_as_typed_data() {
check_as_typed_data!(&[1i8, 3i8, 6i8], i8);
check_as_typed_data!(&[1u8, 3u8, 6u8], u8);
check_as_typed_data!(&[1i16, 3i16, 6i16], i16);
check_as_typed_data!(&[1i32, 3i32, 6i32], i32);
check_as_typed_data!(&[1i64, 3i64, 6i64], i64);
check_as_typed_data!(&[1u16, 3u16, 6u16], u16);
check_as_typed_data!(&[1u32, 3u32, 6u32], u32);
check_as_typed_data!(&[1u64, 3u64, 6u64], u64);
check_as_typed_data!(&[1f32, 3f32, 6f32], f32);
check_as_typed_data!(&[1f64, 3f64, 6f64], f64);
}
#[test]
fn test_count_bits() {
assert_eq!(0, Buffer::from(&[0b00000000]).count_set_bits_offset(0, 8));
assert_eq!(8, Buffer::from(&[0b11111111]).count_set_bits_offset(0, 8));
assert_eq!(3, Buffer::from(&[0b00001101]).count_set_bits_offset(0, 8));
assert_eq!(
6,
Buffer::from(&[0b01001001, 0b01010010]).count_set_bits_offset(0, 16)
);
assert_eq!(
16,
Buffer::from(&[0b11111111, 0b11111111]).count_set_bits_offset(0, 16)
);
}
#[test]
fn test_count_bits_slice() {
assert_eq!(
0,
Buffer::from(&[0b11111111, 0b00000000])
.slice(1)
.count_set_bits_offset(0, 8)
);
assert_eq!(
8,
Buffer::from(&[0b11111111, 0b11111111])
.slice_with_length(1, 1)
.count_set_bits_offset(0, 8)
);
assert_eq!(
3,
Buffer::from(&[0b11111111, 0b11111111, 0b00001101])
.slice(2)
.count_set_bits_offset(0, 8)
);
assert_eq!(
6,
Buffer::from(&[0b11111111, 0b01001001, 0b01010010])
.slice_with_length(1, 2)
.count_set_bits_offset(0, 16)
);
assert_eq!(
16,
Buffer::from(&[0b11111111, 0b11111111, 0b11111111, 0b11111111])
.slice(2)
.count_set_bits_offset(0, 16)
);
}
#[test]
fn test_count_bits_offset_slice() {
assert_eq!(8, Buffer::from(&[0b11111111]).count_set_bits_offset(0, 8));
assert_eq!(3, Buffer::from(&[0b11111111]).count_set_bits_offset(0, 3));
assert_eq!(5, Buffer::from(&[0b11111111]).count_set_bits_offset(3, 5));
assert_eq!(1, Buffer::from(&[0b11111111]).count_set_bits_offset(3, 1));
assert_eq!(0, Buffer::from(&[0b11111111]).count_set_bits_offset(8, 0));
assert_eq!(2, Buffer::from(&[0b01010101]).count_set_bits_offset(0, 3));
assert_eq!(
16,
Buffer::from(&[0b11111111, 0b11111111]).count_set_bits_offset(0, 16)
);
assert_eq!(
10,
Buffer::from(&[0b11111111, 0b11111111]).count_set_bits_offset(0, 10)
);
assert_eq!(
10,
Buffer::from(&[0b11111111, 0b11111111]).count_set_bits_offset(3, 10)
);
assert_eq!(
8,
Buffer::from(&[0b11111111, 0b11111111]).count_set_bits_offset(8, 8)
);
assert_eq!(
5,
Buffer::from(&[0b11111111, 0b11111111]).count_set_bits_offset(11, 5)
);
assert_eq!(
0,
Buffer::from(&[0b11111111, 0b11111111]).count_set_bits_offset(16, 0)
);
assert_eq!(
2,
Buffer::from(&[0b01101101, 0b10101010]).count_set_bits_offset(7, 5)
);
assert_eq!(
4,
Buffer::from(&[0b01101101, 0b10101010]).count_set_bits_offset(7, 9)
);
}
#[test]
fn test_unwind_safe() {
fn assert_unwind_safe<T: RefUnwindSafe + UnwindSafe>() {}
assert_unwind_safe::<Buffer>()
}
#[test]
fn test_from_foreign_vec() {
let mut vector = vec![1_i32, 2, 3, 4, 5];
let buffer = unsafe {
Buffer::from_custom_allocation(
NonNull::new_unchecked(vector.as_mut_ptr() as *mut u8),
vector.len() * std::mem::size_of::<i32>(),
Arc::new(vector),
)
};
let slice = buffer.typed_data::<i32>();
assert_eq!(slice, &[1, 2, 3, 4, 5]);
let buffer = buffer.slice(std::mem::size_of::<i32>());
let slice = buffer.typed_data::<i32>();
assert_eq!(slice, &[2, 3, 4, 5]);
}
#[test]
#[should_panic(expected = "the offset of the new Buffer cannot exceed the existing length")]
fn slice_overflow() {
let buffer = Buffer::from(MutableBuffer::from_len_zeroed(12));
buffer.slice_with_length(2, usize::MAX);
}
#[test]
fn test_vec_interop() {
let a: Vec<i128> = Vec::new();
let b = Buffer::from_vec(a);
b.into_vec::<i128>().unwrap();
let a: Vec<i128> = Vec::with_capacity(20);
let b = Buffer::from_vec(a);
let back = b.into_vec::<i128>().unwrap();
assert_eq!(back.len(), 0);
assert_eq!(back.capacity(), 20);
let mut a: Vec<i128> = Vec::with_capacity(3);
a.extend_from_slice(&[1, 2, 3]);
let b = Buffer::from_vec(a);
let back = b.into_vec::<i128>().unwrap();
assert_eq!(back.len(), 3);
assert_eq!(back.capacity(), 3);
let mut a: Vec<i128> = Vec::with_capacity(20);
a.extend_from_slice(&[1, 4, 7, 8, 9, 3, 6]);
let b = Buffer::from_vec(a);
let back = b.into_vec::<i128>().unwrap();
assert_eq!(back.len(), 7);
assert_eq!(back.capacity(), 20);
let a: Vec<i128> = Vec::new();
let b = Buffer::from_vec(a);
let b = b.into_vec::<i32>().unwrap_err();
b.into_vec::<i8>().unwrap_err();
let a: Vec<i64> = vec![1, 2, 3, 4];
let b = Buffer::from_vec(a);
let back = b.into_vec::<u64>().unwrap();
assert_eq!(back.len(), 4);
assert_eq!(back.capacity(), 4);
let mut b: Vec<i128> = Vec::with_capacity(4);
b.extend_from_slice(&[1, 2, 3, 4]);
let b = Buffer::from_vec(b);
let back = b.into_vec::<i256>().unwrap();
assert_eq!(back.len(), 2);
assert_eq!(back.capacity(), 2);
let b: Vec<i128> = vec![1, 2, 3];
let b = Buffer::from_vec(b);
b.into_vec::<i256>().unwrap_err();
let mut b: Vec<i128> = Vec::with_capacity(5);
b.extend_from_slice(&[1, 2, 3, 4]);
let b = Buffer::from_vec(b);
b.into_vec::<i256>().unwrap_err();
let mut b: Vec<i128> = Vec::with_capacity(4);
b.extend_from_slice(&[1, 2, 3]);
let b = Buffer::from_vec(b);
let back = b.into_vec::<i256>().unwrap();
assert_eq!(back.len(), 1);
assert_eq!(back.capacity(), 2);
let b = Buffer::from(MutableBuffer::new(10));
let b = b.into_vec::<u8>().unwrap_err();
b.into_vec::<u64>().unwrap_err();
let mut a: Vec<i128> = Vec::with_capacity(20);
a.extend_from_slice(&[1, 4, 7, 8, 9, 3, 6]);
let b = Buffer::from_vec(a);
let slice = b.slice_with_length(0, 64);
let slice = slice.into_vec::<i128>().unwrap_err();
drop(b);
let back = slice.into_vec::<i128>().unwrap();
assert_eq!(&back, &[1, 4, 7, 8]);
assert_eq!(back.capacity(), 20);
let mut a: Vec<i128> = Vec::with_capacity(8);
a.extend_from_slice(&[1, 4, 7, 3]);
let b = Buffer::from_vec(a);
let slice = b.slice_with_length(0, 34);
drop(b);
let back = slice.into_vec::<i128>().unwrap();
assert_eq!(&back, &[1, 4]);
assert_eq!(back.capacity(), 8);
let a: Vec<u32> = vec![1, 3, 4, 6];
let b = Buffer::from_vec(a).slice(2);
b.into_vec::<u32>().unwrap_err();
let b = MutableBuffer::new(16).into_buffer();
let b = b.into_vec::<u8>().unwrap_err(); let b = b.into_vec::<u32>().unwrap_err(); b.into_mutable().unwrap();
let b = Buffer::from_vec(vec![1_u32, 3, 5]);
let b = b.into_mutable().unwrap();
let b = Buffer::from(b);
let b = b.into_vec::<u32>().unwrap();
assert_eq!(b, &[1, 3, 5]);
}
#[test]
#[should_panic(expected = "capacity overflow")]
fn test_from_iter_overflow() {
let iter_len = usize::MAX / std::mem::size_of::<u64>() + 1;
let _ = Buffer::from_iter(std::iter::repeat_n(0_u64, iter_len));
}
#[test]
fn bit_slice_length_preserved() {
let buf = Buffer::from_iter(std::iter::repeat_n(true, 64));
let assert_preserved = |offset: usize, len: usize| {
let new_buf = buf.bit_slice(offset, len);
assert_eq!(new_buf.len(), bit_util::ceil(len, 8));
if offset % 8 == 0 {
assert_eq!(new_buf.ptr_offset(), offset / 8);
} else {
assert_eq!(new_buf.ptr_offset(), 0);
}
};
for o in 0..=64 {
for l in (o..=64).map(|l| l - o) {
assert_preserved(o, l);
}
}
}
#[test]
fn test_strong_count() {
let buffer = Buffer::from_iter(std::iter::repeat_n(0_u8, 100));
assert_eq!(buffer.strong_count(), 1);
let buffer2 = buffer.clone();
assert_eq!(buffer.strong_count(), 2);
let buffer3 = buffer2.clone();
assert_eq!(buffer.strong_count(), 3);
drop(buffer);
assert_eq!(buffer2.strong_count(), 2);
assert_eq!(buffer3.strong_count(), 2);
let capture = move || {
assert_eq!(buffer3.strong_count(), 2);
};
capture();
assert_eq!(buffer2.strong_count(), 2);
drop(capture);
assert_eq!(buffer2.strong_count(), 1);
}
}