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
130
131
132
133
134
//! 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 {
// #899: `self.capacity * 2` could overflow `usize` and wrap to a tiny
// value, defeating the `.max(required_capacity)` guard. Saturate the
// doubling so growth always satisfies `required_capacity` and the
// final layout-size check (in `resize`) still rejects true overflow.
let doubled = self.capacity.saturating_mul(2);
let new_capacity = required_capacity.max(doubled);
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)
}
}