File size: 5,452 Bytes
f7ccd08 32b4893 | 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 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162 163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188 189 190 191 192 193 194 195 196 | // SPDX-License-Identifier: LicenseRef-Sovereign-Leviathan-SL-AGPL3-001
// Licensing: see LICENSE in the repository root.
//! Buffer management with generation counters and reference tracking
//!
//! Each buffer has:
//! - Unique 64-bit ID
//! - Generation counter (increments on allocation/materialization)
//! - Shared Arc-based ownership
//! - Reference count for exclusivity checking
use crate::error::{HardwareError, Result};
use std::sync::Arc;
/// Unique 64-bit buffer identifier
pub type BufferId = u64;
/// Generation counter for stale descriptor detection
pub type Generation = u64;
/// Hardware buffer with generation tracking
#[derive(Clone)]
pub struct Buffer {
/// Physical storage (shared ownership)
pub data: Arc<Vec<f32>>,
/// Current generation counter
pub generation: Generation,
/// Unique buffer ID
pub id: BufferId,
/// True if buffer is the canonical owner (used for COW decisions)
pub is_owner: bool,
}
impl Buffer {
/// Create new buffer with fresh generation counter
///
/// **Governance**: B_own=1 (exclusive), generation=1
pub fn allocate(id: BufferId, size: usize) -> Result<Self> {
if size == 0 {
return Err(HardwareError::ZeroElementShape);
}
let data = Arc::new(vec![0.0; size]);
Ok(Buffer {
data,
generation: 1,
id,
is_owner: true,
})
}
/// Strong reference count (number of Arc holders)
#[inline]
pub fn strong_count(&self) -> usize {
Arc::strong_count(&self.data)
}
/// Check if this buffer has exclusive ownership
///
/// Returns true only if strong_count == 1 AND is_owner flag is set
#[inline]
pub fn is_exclusive(&self) -> bool {
self.is_owner && self.strong_count() == 1
}
/// Create a shared view (does not copy data)
///
/// **Governance**: B_own → 0, generation unchanged
pub fn create_view(&self) -> Self {
Buffer {
data: Arc::clone(&self.data),
generation: self.generation,
id: self.id,
is_owner: false, // Shared view
}
}
/// Increment generation counter for new materialization
///
/// **State Transition**: (gen, ownership) → (gen+1, exclusive=1)
pub fn next_generation(&self) -> Generation {
self.generation.saturating_add(1)
}
/// Validate that descriptor generation matches buffer generation
///
/// **Safety**: Rejects use-after-free by stale descriptor
pub fn validate_generation(&self, descriptor_gen: Generation) -> Result<()> {
if descriptor_gen != self.generation {
return Err(HardwareError::StaleGeneration {
buffer_id: self.id,
expected: self.generation,
actual: descriptor_gen,
});
}
Ok(())
}
/// Check mutability: exclusive ownership + non-shared
pub fn check_mutable(&self) -> Result<()> {
if !self.is_exclusive() {
return Err(HardwareError::SharedTensorMutation);
}
Ok(())
}
/// Total buffer capacity in elements
#[inline]
pub fn capacity(&self) -> usize {
self.data.len()
}
/// Direct element access (unchecked - use with bounds validation)
#[inline]
pub fn get_unchecked(&self, index: usize) -> f32 {
self.data[index]
}
/// Direct element write (unchecked - use with bounds/mutability validation)
#[inline]
pub fn set_unchecked(&self, index: usize, value: f32) {
// SAFETY: We use unsafe to bypass Arc immutability for exclusive buffers
// The caller must ensure:
// 1. is_exclusive() is true
// 2. index is in bounds
unsafe {
let ptr = self.data.as_ptr() as *mut f32;
*ptr.add(index) = value;
}
}
}
/// Buffer handle with metadata for efficient lookups
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub struct BufferHandle {
pub id: BufferId,
pub generation: Generation,
}
impl BufferHandle {
/// Create handle from buffer
pub fn from_buffer(buf: &Buffer) -> Self {
BufferHandle {
id: buf.id,
generation: buf.generation,
}
}
/// Validate handle against current buffer state
pub fn validate(&self, buffer: &Buffer) -> Result<()> {
if self.id != buffer.id {
return Err(HardwareError::BufferNotFound(self.id));
}
buffer.validate_generation(self.generation)
}
}
#[cfg(test)]
mod tests {
use super::*;
#[test]
fn test_allocate_sets_generation() {
let buf = Buffer::allocate(1, 100).unwrap();
assert_eq!(buf.generation, 1);
assert!(buf.is_exclusive());
}
#[test]
fn test_view_shares_data_not_ownership() {
let buf = Buffer::allocate(1, 100).unwrap();
let view = buf.create_view();
assert_eq!(buf.strong_count(), 2);
assert!(!view.is_owner);
assert!(buf.is_owner);
assert!(!view.is_exclusive());
}
#[test]
fn test_generation_validation() {
let buf = Buffer::allocate(1, 100).unwrap();
assert!(buf.validate_generation(1).is_ok());
assert!(buf.validate_generation(2).is_err());
}
#[test]
fn test_zero_size_allocation_fails() {
assert!(Buffer::allocate(1, 0).is_err());
}
}
|