mirror of
https://github.com/quickwit-oss/tantivy.git
synced 2026-10-19 02:02:41 +00:00
`VersatileBuffer<A>` (tantivy-common) keeps an allocation and lends it as an empty `Vec<T>`, for any `T` with the size and alignment of `A`. The allocation goes back to the buffer when the guard is dropped, including on panic. If the guard is leaked, the buffer starts again from an empty `Vec`. This makes it possible to reuse a buffer of values borrowing data with a short lifetime, without self-referential structs. The jitexpr predicate uses it for its argument slots. It replaces the `ClearOnDrop` guard, which transmuted a `Vec<VariableValue<'static>>` to a shorter lifetime. Co-authored-by: Paul Masurel <paul.masurel@datadoghq.com>
189 lines
5.9 KiB
Rust
189 lines
5.9 KiB
Rust
use std::fmt::Debug;
|
|
use std::mem::ManuallyDrop;
|
|
use std::ops::{Deref, DerefMut};
|
|
|
|
/// A reusable allocation that can be lent as an empty `Vec<T>` for any `T` with the same size
|
|
/// and alignment as `A`.
|
|
///
|
|
/// Example: `VersatileBuffer<u64>` can lend `Vec<u64>`, `Vec<i64>`, `Vec<f64>`, ...
|
|
///
|
|
/// Hidden contract: `buffer.len()` is always 0. No `A` value is ever built: `A` only sets the
|
|
/// size and alignment of the allocation. Only the allocation is reused.
|
|
pub struct VersatileBuffer<A> {
|
|
buffer: Vec<A>,
|
|
}
|
|
|
|
impl<A> Default for VersatileBuffer<A> {
|
|
#[inline(always)]
|
|
fn default() -> Self {
|
|
VersatileBuffer { buffer: Vec::new() }
|
|
}
|
|
}
|
|
|
|
impl<A> VersatileBuffer<A> {
|
|
#[inline(always)]
|
|
pub fn new() -> Self {
|
|
Self::default()
|
|
}
|
|
|
|
#[inline(always)]
|
|
pub fn with_capacity(capacity: usize) -> Self {
|
|
VersatileBuffer {
|
|
buffer: Vec::with_capacity(capacity),
|
|
}
|
|
}
|
|
|
|
/// Lends the allocation as an empty `Vec<T>`.
|
|
///
|
|
/// The allocation goes back to `self` when the returned guard is dropped.
|
|
/// If the guard is leaked (e.g. `mem::forget`), the allocation is leaked too and `self`
|
|
/// starts again from an empty `Vec`.
|
|
#[inline(always)]
|
|
pub fn borrow<T>(&mut self) -> ClearOnDrop<'_, T, A> {
|
|
const {
|
|
assert!(std::mem::size_of::<T>() == std::mem::size_of::<A>());
|
|
assert!(std::mem::align_of::<T>() == std::mem::align_of::<A>());
|
|
}
|
|
let mut storage = ManuallyDrop::new(std::mem::take(&mut self.buffer));
|
|
let capacity = storage.capacity();
|
|
let ptr = storage.as_mut_ptr().cast::<T>();
|
|
// SAFETY: `ptr` and `capacity` come from a `Vec<A>` we own. `T` and `A` have the same
|
|
// size and alignment (checked above), so the allocation is valid for a `Vec<T>` with the
|
|
// same capacity. Length is 0, so nothing is read.
|
|
let values: Vec<T> = unsafe { Vec::from_raw_parts(ptr, 0, capacity) };
|
|
ClearOnDrop {
|
|
home: &mut self.buffer,
|
|
values,
|
|
}
|
|
}
|
|
}
|
|
|
|
/// An empty `Vec<T>` lent by a `VersatileBuffer`.
|
|
///
|
|
/// On drop, the values are dropped and the allocation goes back to the `VersatileBuffer`.
|
|
/// This also happens if the code using the vec panics.
|
|
pub struct ClearOnDrop<'a, T, A> {
|
|
home: &'a mut Vec<A>,
|
|
values: Vec<T>,
|
|
}
|
|
|
|
impl<T: Debug, A> Debug for ClearOnDrop<'_, T, A> {
|
|
#[inline(always)]
|
|
fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
|
|
self.values.fmt(f)
|
|
}
|
|
}
|
|
|
|
impl<T, A> Drop for ClearOnDrop<'_, T, A> {
|
|
#[inline(always)]
|
|
fn drop(&mut self) {
|
|
self.values.clear();
|
|
let mut values = ManuallyDrop::new(std::mem::take(&mut self.values));
|
|
let capacity = values.capacity();
|
|
let ptr = values.as_mut_ptr().cast::<A>();
|
|
// SAFETY: same layout argument as in `VersatileBuffer::borrow`. `values` may have been
|
|
// reallocated by `push`, but always with `T`'s layout, which equals `A`'s.
|
|
let buffer: Vec<A> = unsafe { Vec::from_raw_parts(ptr, 0, capacity) };
|
|
// `home` still holds the empty `Vec` left by `mem::take` in `borrow`. Forgetting it
|
|
// instead of dropping it removes a dead dealloc branch, which the compiler cannot prove
|
|
// dead on its own.
|
|
std::mem::forget(std::mem::replace(self.home, buffer));
|
|
}
|
|
}
|
|
|
|
impl<T, A> Deref for ClearOnDrop<'_, T, A> {
|
|
type Target = Vec<T>;
|
|
|
|
#[inline(always)]
|
|
fn deref(&self) -> &Self::Target {
|
|
&self.values
|
|
}
|
|
}
|
|
|
|
impl<T, A> DerefMut for ClearOnDrop<'_, T, A> {
|
|
#[inline(always)]
|
|
fn deref_mut(&mut self) -> &mut Self::Target {
|
|
&mut self.values
|
|
}
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use std::cell::Cell;
|
|
|
|
use crate::versatile_buffer::{ClearOnDrop, VersatileBuffer};
|
|
|
|
#[test]
|
|
fn test_versatile_borrowable_buffer() {
|
|
let mut my_buff: VersatileBuffer<u64> = VersatileBuffer::default();
|
|
|
|
{
|
|
let mut arr: ClearOnDrop<u64, u64> = my_buff.borrow();
|
|
arr.push(1u64);
|
|
arr.push(2u64);
|
|
assert_eq!(&arr[..], &[1u64, 2u64]);
|
|
}
|
|
{
|
|
let arr: ClearOnDrop<i64, u64> = my_buff.borrow();
|
|
assert!(arr.is_empty());
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_versatile_buffer_keeps_capacity() {
|
|
let mut buffer: VersatileBuffer<u64> = VersatileBuffer::with_capacity(16);
|
|
{
|
|
let mut values = buffer.borrow::<u64>();
|
|
assert!(values.capacity() >= 16);
|
|
values.extend(0..100u64);
|
|
}
|
|
let values = buffer.borrow::<f64>();
|
|
assert!(values.is_empty());
|
|
assert!(values.capacity() >= 100);
|
|
}
|
|
|
|
#[test]
|
|
fn test_versatile_buffer_forgotten_guard() {
|
|
let mut buffer: VersatileBuffer<u64> = VersatileBuffer::new();
|
|
let mut values = buffer.borrow::<u64>();
|
|
values.extend([1u64, 2u64, 3u64]);
|
|
std::mem::forget(values);
|
|
let values = buffer.borrow::<f64>();
|
|
assert!(values.is_empty());
|
|
}
|
|
|
|
#[test]
|
|
fn test_versatile_buffer_16_bytes() {
|
|
let mut buffer: VersatileBuffer<(u64, u64)> = VersatileBuffer::new();
|
|
{
|
|
let mut values = buffer.borrow::<(u64, u32, u32)>();
|
|
values.push((1, 2, 3));
|
|
assert_eq!(&values[..], &[(1, 2, 3)]);
|
|
}
|
|
let values = buffer.borrow::<[u64; 2]>();
|
|
assert!(values.is_empty());
|
|
}
|
|
|
|
struct DropCounter<'a>(&'a Cell<u32>);
|
|
|
|
impl Drop for DropCounter<'_> {
|
|
fn drop(&mut self) {
|
|
self.0.set(self.0.get() + 1);
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn test_versatile_buffer_drops_values() {
|
|
let drop_count = Cell::new(0u32);
|
|
let mut buffer: VersatileBuffer<usize> = VersatileBuffer::new();
|
|
{
|
|
let mut values = buffer.borrow::<DropCounter>();
|
|
for _ in 0..5 {
|
|
values.push(DropCounter(&drop_count));
|
|
}
|
|
assert_eq!(drop_count.get(), 0);
|
|
}
|
|
assert_eq!(drop_count.get(), 5);
|
|
}
|
|
}
|