1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
//! Resize and reallocation logic for [`ContiguousVectors`].
//!
//! Extracted from [`super::perf_optimizations`] to isolate the allocation-growth
//! concern from the core storage API. Uses [`AllocGuard`] for panic-safe buffer
//! migration during capacity changes.
//!
//! [`AllocGuard`]: crate::alloc_guard::AllocGuard
use std::alloc::dealloc;
use std::ptr::{self, NonNull};
use super::perf_optimizations::ContiguousVectors;
impl ContiguousVectors {
/// Ensures the storage has capacity for at least `required_capacity` vectors.
///
/// # Errors
///
/// Returns [`Error::AllocationFailed`] if reallocation fails.
///
/// [`Error::AllocationFailed`]: crate::error::Error::AllocationFailed
pub fn ensure_capacity(&mut self, required_capacity: usize) -> crate::error::Result<()> {
if required_capacity > self.capacity {
let new_capacity = required_capacity.max(self.capacity * 2);
self.resize(new_capacity)?;
}
Ok(())
}
/// Pre-allocates capacity for `additional` more vectors beyond the current length.
///
/// Analogous to [`Vec::reserve`]: ensures the buffer can hold
/// `self.len() + additional` vectors without reallocating. No-op if
/// sufficient capacity already exists.
///
/// Call before a batch push to guarantee `push_batch` won't resize.
///
/// # Errors
///
/// Returns [`Error::AllocationFailed`] if reallocation fails.
///
/// [`Error::AllocationFailed`]: crate::error::Error::AllocationFailed
pub fn reserve_additional(&mut self, additional: usize) -> crate::error::Result<()> {
let required = self.count.saturating_add(additional);
self.ensure_capacity(required)
}
/// Resizes the internal buffer.
///
/// # P2 Audit + PERF-002: Panic-Safety with RAII Guard
///
/// This function uses `AllocGuard` for panic-safe allocation:
/// 1. New buffer is allocated via RAII guard (auto-freed on panic)
/// 2. Data is copied to new buffer
/// 3. Guard ownership is transferred (no auto-free)
/// 4. Old buffer is deallocated
/// 5. State is updated atomically
///
/// If panic occurs during copy, the guard ensures new buffer is freed.
pub(crate) fn resize(&mut self, new_capacity: usize) -> crate::error::Result<()> {
if new_capacity <= self.capacity {
return Ok(());
}
let old_layout = Self::layout(self.dimension, self.capacity)?;
let new_layout = Self::layout(self.dimension, new_capacity)?;
let new_data = Self::alloc_and_copy(new_layout, self.data, self.count, self.dimension)?;
// Deallocate old buffer
// SAFETY: self.data was allocated with old_layout, is non-null (NonNull invariant)
// - Condition 1: old_layout matches the allocation parameters.
// - Condition 2: Pointer is non-null per NonNull invariant.
// SAFETY: Free old buffer after data migration to new buffer.
unsafe {
dealloc(self.data.as_ptr().cast::<u8>(), old_layout);
}
// Update state (all-or-nothing)
self.data = new_data;
self.capacity = new_capacity;
Ok(())
}
/// Allocates a new buffer and copies existing data into it.
///
/// Uses `AllocGuard` for panic-safety: if copy panics, the guard drops
/// and frees the new buffer automatically.
#[allow(clippy::cast_ptr_alignment)] // Layout is 64-byte aligned
fn alloc_and_copy(
new_layout: std::alloc::Layout,
src: NonNull<f32>,
count: usize,
dimension: usize,
) -> crate::error::Result<NonNull<f32>> {
use crate::alloc_guard::AllocGuard;
// Allocate zero-initialized buffer with RAII guard (PERF-002)
let guard = AllocGuard::new_zeroed(new_layout).ok_or_else(|| {
crate::error::Error::AllocationFailed(format!(
"Failed to allocate {} bytes for ContiguousVectors resize",
new_layout.size()
))
})?;
// EPIC-032/US-002: Use NonNull for type-level guarantee
let new_data = NonNull::new(guard.cast::<f32>()).ok_or_else(|| {
crate::error::Error::AllocationFailed("AllocGuard returned null pointer".to_string())
})?;
// Copy existing data to new buffer
if count > 0 {
let copy_size = count * dimension;
// SAFETY: Both pointers are valid (NonNull), non-overlapping, and properly aligned
// - Condition 1: Source pointer (src) is valid and properly aligned.
// - Condition 2: Destination pointer (new_data) is valid and properly aligned.
// - Condition 3: Pointers are non-overlapping (old and new allocations are distinct).
// SAFETY: Migrate data to newly allocated buffer during resize.
unsafe {
ptr::copy_nonoverlapping(src.as_ptr(), new_data.as_ptr(), copy_size);
}
}
// Transfer ownership - guard won't free on drop anymore
let _ = guard.into_raw();
Ok(new_data)
}
}