abaddon 0.2.0-rc.1

Blazingly fast LLM inference engine - The Destroyer renders judgment
Documentation
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
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
//! Arena Allocator for HoloTensor Fragment Allocations
//!
//! Provides a bump allocator optimized for fragment buffer allocations.
//! Reduces heap pressure by batching allocations into a single large buffer.
//!
//! # Usage
//!
//! ```ignore
//! use abaddon::holotensor::arena::FragmentArena;
//!
//! let arena = FragmentArena::new(1024 * 1024); // 1MB arena
//!
//! // Allocate fragment buffers
//! let ptr = arena.alloc(4096);
//!
//! // Reset for next batch (reuses memory)
//! arena.reset();
//! ```

use std::alloc::{alloc, dealloc, Layout};
use std::cell::UnsafeCell;
use std::ptr::NonNull;
use std::sync::atomic::{AtomicUsize, Ordering};

/// Configuration for arena allocation.
#[derive(Debug, Clone)]
pub struct ArenaConfig {
    /// Initial capacity in bytes.
    pub initial_capacity: usize,
    /// Maximum capacity (for growing arenas).
    pub max_capacity: usize,
    /// Minimum alignment for allocations.
    pub alignment: usize,
    /// Growth factor when arena needs to expand.
    pub grow_factor: f64,
}

impl Default for ArenaConfig {
    fn default() -> Self {
        Self {
            initial_capacity: 256 * 1024,   // 256KB
            max_capacity: 64 * 1024 * 1024, // 64MB
            alignment: 8,
            grow_factor: 2.0,
        }
    }
}

impl ArenaConfig {
    /// Create config for small allocations (fragment metadata).
    pub fn small() -> Self {
        Self {
            initial_capacity: 64 * 1024,
            max_capacity: 1024 * 1024,
            ..Default::default()
        }
    }

    /// Create config for large allocations (fragment data).
    pub fn large() -> Self {
        Self {
            initial_capacity: 1024 * 1024,
            max_capacity: 256 * 1024 * 1024,
            ..Default::default()
        }
    }
}

/// Statistics for arena usage.
#[derive(Debug, Clone, Default)]
pub struct ArenaStats {
    /// Total bytes allocated from arena.
    pub bytes_allocated: usize,
    /// Number of allocations.
    pub allocation_count: usize,
    /// Number of times arena was reset.
    pub reset_count: usize,
    /// Peak bytes used before any reset.
    pub peak_bytes: usize,
}

/// Bump allocator for fragment buffers.
///
/// This allocator maintains a single contiguous buffer and allocates
/// by bumping a pointer. Individual deallocations are not supported;
/// instead, call `reset()` to reclaim all memory at once.
///
/// This is ideal for batch processing where many temporary allocations
/// are made and then discarded together.
pub struct FragmentArena {
    /// Base pointer of the allocated buffer.
    buffer: NonNull<u8>,
    /// Total capacity in bytes.
    capacity: usize,
    /// Current allocation offset (atomic for interior mutability).
    offset: AtomicUsize,
    /// Configuration.
    config: ArenaConfig,
    /// Statistics (interior mutability).
    stats: UnsafeCell<ArenaStats>,
    /// Number of system allocations made (should be 1).
    system_alloc_count: AtomicUsize,
}

// Safety: FragmentArena can be shared between threads because:
// - offset uses AtomicUsize for thread-safe bumping
// - buffer is never reallocated while shared
// - stats are only updated through atomic operations or single-threaded access
unsafe impl Send for FragmentArena {}
unsafe impl Sync for FragmentArena {}

impl FragmentArena {
    /// Create a new arena with the specified capacity.
    pub fn new(capacity: usize) -> Self {
        Self::with_config(ArenaConfig {
            initial_capacity: capacity,
            max_capacity: capacity,
            ..Default::default()
        })
    }

    /// Create a new arena with the specified configuration.
    pub fn with_config(config: ArenaConfig) -> Self {
        let capacity = config.initial_capacity;
        let layout =
            Layout::from_size_align(capacity, config.alignment).expect("Invalid arena layout");

        // Allocate the backing buffer
        let ptr = unsafe { alloc(layout) };
        let buffer = NonNull::new(ptr).expect("Arena allocation failed");

        Self {
            buffer,
            capacity,
            offset: AtomicUsize::new(0),
            config,
            stats: UnsafeCell::new(ArenaStats::default()),
            system_alloc_count: AtomicUsize::new(1),
        }
    }

    /// Allocate bytes from the arena.
    ///
    /// Returns a pointer to the allocated memory, or null if
    /// the arena doesn't have enough capacity.
    ///
    /// The returned pointer is aligned to the arena's alignment setting.
    pub fn alloc(&self, size: usize) -> *mut u8 {
        if size == 0 {
            return std::ptr::null_mut();
        }

        let alignment = self.config.alignment;

        loop {
            let current = self.offset.load(Ordering::Relaxed);

            // Align the current offset
            let aligned = (current + alignment - 1) & !(alignment - 1);
            let new_offset = aligned + size;

            if new_offset > self.capacity {
                return std::ptr::null_mut();
            }

            // Try to bump the offset
            match self.offset.compare_exchange_weak(
                current,
                new_offset,
                Ordering::SeqCst,
                Ordering::Relaxed,
            ) {
                Ok(_) => {
                    // Update stats (relaxed because exact counts aren't critical)
                    unsafe {
                        let stats = &mut *self.stats.get();
                        stats.bytes_allocated += size;
                        stats.allocation_count += 1;
                        if new_offset > stats.peak_bytes {
                            stats.peak_bytes = new_offset;
                        }
                    }

                    // Return pointer at aligned offset
                    return unsafe { self.buffer.as_ptr().add(aligned) };
                },
                Err(_) => {
                    // Another thread beat us, retry
                    continue;
                },
            }
        }
    }

    /// Allocate and zero-initialize bytes from the arena.
    pub fn alloc_zeroed(&self, size: usize) -> *mut u8 {
        let ptr = self.alloc(size);
        if !ptr.is_null() {
            unsafe {
                std::ptr::write_bytes(ptr, 0, size);
            }
        }
        ptr
    }

    /// Reset the arena, reclaiming all allocations.
    ///
    /// This doesn't deallocate the underlying buffer; it just
    /// resets the bump pointer to the beginning.
    pub fn reset(&self) {
        self.offset.store(0, Ordering::SeqCst);
        unsafe {
            let stats = &mut *self.stats.get();
            stats.reset_count += 1;
            stats.bytes_allocated = 0;
            stats.allocation_count = 0;
        }
    }

    /// Get the base pointer of the arena buffer.
    pub fn base_ptr(&self) -> *mut u8 {
        self.buffer.as_ptr()
    }

    /// Get the total capacity of the arena.
    pub fn capacity(&self) -> usize {
        self.capacity
    }

    /// Get the current bytes used in the arena.
    pub fn bytes_used(&self) -> usize {
        self.offset.load(Ordering::Relaxed)
    }

    /// Get the remaining capacity.
    pub fn bytes_remaining(&self) -> usize {
        self.capacity.saturating_sub(self.bytes_used())
    }

    /// Get arena statistics.
    pub fn stats(&self) -> ArenaStats {
        unsafe { (*self.stats.get()).clone() }
    }

    /// Get number of system allocations (should be 1).
    pub fn system_alloc_count(&self) -> usize {
        self.system_alloc_count.load(Ordering::Relaxed)
    }
}

impl Drop for FragmentArena {
    fn drop(&mut self) {
        let layout = Layout::from_size_align(self.capacity, self.config.alignment)
            .expect("Invalid arena layout");
        unsafe {
            dealloc(self.buffer.as_ptr(), layout);
        }
    }
}

/// Thread-local arena storage.
///
/// Each thread gets its own arena to avoid contention.
pub struct ThreadLocalArena;

impl ThreadLocalArena {
    /// Get or initialize the thread-local arena.
    ///
    /// The provided closure is called to create the arena if it doesn't exist.
    pub fn get_or_init<F>(init: F) -> &'static FragmentArena
    where
        F: FnOnce() -> FragmentArena,
    {
        thread_local! {
            static ARENA: std::cell::OnceCell<FragmentArena> = std::cell::OnceCell::new();
        }

        ARENA.with(|cell| {
            let arena = cell.get_or_init(init);
            // Safety: The arena lives for the lifetime of the thread
            unsafe { &*(arena as *const FragmentArena) }
        })
    }
}

/// Custom allocator wrapper that uses a FragmentArena.
///
/// This provides a simple interface for allocating from an arena
/// without needing nightly Rust's allocator_api.
pub struct ArenaAllocator<'a> {
    arena: &'a FragmentArena,
}

impl<'a> ArenaAllocator<'a> {
    /// Create a new arena allocator wrapping the given arena.
    pub fn new(arena: &'a FragmentArena) -> Self {
        Self { arena }
    }

    /// Allocate memory from the arena.
    pub fn alloc(&self, size: usize) -> *mut u8 {
        self.arena.alloc(size)
    }

    /// Allocate and zero-initialize memory from the arena.
    pub fn alloc_zeroed(&self, size: usize) -> *mut u8 {
        self.arena.alloc_zeroed(size)
    }

    /// Get the underlying arena.
    pub fn arena(&self) -> &FragmentArena {
        self.arena
    }
}

/// Scoped arena guard that automatically resets on drop.
pub struct ArenaScope<'a> {
    arena: &'a FragmentArena,
    start_offset: usize,
}

impl<'a> ArenaScope<'a> {
    /// Create a new scope for the arena.
    pub fn new(arena: &'a FragmentArena) -> Self {
        Self {
            arena,
            start_offset: arena.bytes_used(),
        }
    }

    /// Allocate from the arena within this scope.
    pub fn alloc(&self, size: usize) -> *mut u8 {
        self.arena.alloc(size)
    }

    /// Get the allocator for this scope.
    pub fn allocator(&self) -> ArenaAllocator<'a> {
        ArenaAllocator::new(self.arena)
    }
}

impl<'a> Drop for ArenaScope<'a> {
    fn drop(&mut self) {
        // Rewind to start offset (partial reset)
        self.arena.offset.store(self.start_offset, Ordering::SeqCst);
    }
}

#[cfg(test)]
mod tests {
    use super::*;

    #[test]
    fn test_basic_alloc() {
        let arena = FragmentArena::new(4096);
        let ptr = arena.alloc(100);
        assert!(!ptr.is_null());
        assert_eq!(arena.stats().allocation_count, 1);
    }

    #[test]
    fn test_alloc_alignment() {
        let config = ArenaConfig {
            initial_capacity: 4096,
            alignment: 64,
            ..Default::default()
        };
        let arena = FragmentArena::with_config(config);

        for _ in 0..10 {
            let ptr = arena.alloc(17); // Odd size
            assert_eq!(ptr as usize % 64, 0);
        }
    }

    #[test]
    fn test_alloc_zeroed() {
        let arena = FragmentArena::new(4096);
        let ptr = arena.alloc_zeroed(100);
        assert!(!ptr.is_null());

        let slice = unsafe { std::slice::from_raw_parts(ptr, 100) };
        assert!(slice.iter().all(|&b| b == 0));
    }

    #[test]
    fn test_reset() {
        let arena = FragmentArena::new(4096);
        arena.alloc(100);
        arena.alloc(200);
        assert!(arena.bytes_used() >= 300);

        arena.reset();
        assert_eq!(arena.bytes_used(), 0);
        assert_eq!(arena.stats().reset_count, 1);
    }

    #[test]
    fn test_scope() {
        let arena = FragmentArena::new(4096);
        arena.alloc(100);
        let before = arena.bytes_used();

        {
            let _scope = ArenaScope::new(&arena);
            arena.alloc(200);
            assert!(arena.bytes_used() >= before + 200);
        }

        // Scope dropped, should be back to before
        assert_eq!(arena.bytes_used(), before);
    }
}