@@ -222,6 +222,137 @@ pub fn allocate(numa_node_index: usize, bytes: usize) ?[*]u8 {
222222 return @ptrCast (@alignCast (ptr ));
223223}
224224
225+ /// NUMA-aware allocator compatible with Zig's allocator interface.
226+ pub const NumaAllocator = struct {
227+ node_index : usize ,
228+
229+ const Self = @This ();
230+ const Allocator = std .mem .Allocator ;
231+
232+ const Header = packed struct {
233+ base_addr : usize ,
234+ allocated_bytes : usize ,
235+ };
236+
237+ const vtable = Allocator.VTable {
238+ .alloc = alloc ,
239+ .resize = resize ,
240+ .remap = remap ,
241+ .free = free ,
242+ };
243+
244+ pub fn init (node_index : usize ) Self {
245+ return .{ .node_index = node_index };
246+ }
247+
248+ pub fn allocator (self : * Self ) Allocator {
249+ return .{ .ptr = self , .vtable = & vtable };
250+ }
251+
252+ fn alloc (ctx : * anyopaque , len : usize , alignment : std.mem.Alignment , ret_addr : usize ) ? [* ]u8 {
253+ _ = ret_addr ;
254+ const self : * Self = @ptrCast (@alignCast (ctx ));
255+ const effective_len = if (len == 0 ) 1 else len ;
256+ const slice = self .allocSlice (effective_len , alignment ) orelse return null ;
257+ return slice .ptr ;
258+ }
259+
260+ fn resize (
261+ ctx : * anyopaque ,
262+ buf : []u8 ,
263+ alignment : std.mem.Alignment ,
264+ new_len : usize ,
265+ ret_addr : usize ,
266+ ) bool {
267+ _ = ret_addr ;
268+ const self : * Self = @ptrCast (@alignCast (ctx ));
269+ return self .resizeInPlace (buf , alignment , new_len );
270+ }
271+
272+ fn remap (
273+ ctx : * anyopaque ,
274+ buf : []u8 ,
275+ alignment : std.mem.Alignment ,
276+ new_len : usize ,
277+ ret_addr : usize ,
278+ ) ? [* ]u8 {
279+ _ = ret_addr ;
280+ const self : * Self = @ptrCast (@alignCast (ctx ));
281+ const result = self .remapSlice (buf , alignment , new_len ) orelse return null ;
282+ return result .ptr ;
283+ }
284+
285+ fn free (ctx : * anyopaque , buf : []u8 , alignment : std.mem.Alignment , ret_addr : usize ) void {
286+ _ = alignment ;
287+ _ = ret_addr ;
288+ const self : * Self = @ptrCast (@alignCast (ctx ));
289+ self .freeSlice (buf );
290+ }
291+
292+ fn allocSlice (self : * Self , len : usize , alignment : std.mem.Alignment ) ? []u8 {
293+ const header_size = @sizeOf (Header );
294+ const alignment_bytes = alignment .toByteUnits ();
295+ const with_header = std .math .add (usize , len , header_size ) catch return null ;
296+ const request_bytes = std .math .add (usize , with_header , alignment_bytes ) catch return null ;
297+
298+ var allocated_bytes : usize = undefined ;
299+ var bytes_per_page : usize = undefined ;
300+ const raw_ptr = c .fu_allocate_at_least (
301+ self .node_index ,
302+ request_bytes ,
303+ & allocated_bytes ,
304+ & bytes_per_page ,
305+ ) orelse return null ;
306+
307+ const base_addr = @intFromPtr (raw_ptr );
308+ const data_addr = alignment .forward (base_addr + header_size );
309+ if (data_addr + len > base_addr + allocated_bytes ) {
310+ c .fu_free (self .node_index , raw_ptr , allocated_bytes );
311+ return null ;
312+ }
313+
314+ const header_ptr = @as (* Header , @ptrFromInt (data_addr - header_size ));
315+ header_ptr .* = .{
316+ .base_addr = base_addr ,
317+ .allocated_bytes = allocated_bytes ,
318+ };
319+
320+ const data_ptr = @as ([* ]u8 , @ptrFromInt (data_addr ));
321+ return data_ptr [0.. len ];
322+ }
323+
324+ fn resizeInPlace (self : * Self , buf : []u8 , alignment : std.mem.Alignment , new_len : usize ) bool {
325+ _ = self ;
326+ _ = alignment ;
327+ if (buf .len == 0 ) return false ;
328+ if (new_len == 0 ) return false ;
329+ if (new_len <= buf .len ) return true ;
330+ return false ;
331+ }
332+
333+ fn remapSlice (self : * Self , buf : []u8 , alignment : std.mem.Alignment , new_len : usize ) ? []u8 {
334+ if (buf .len == 0 ) return null ;
335+ if (new_len == 0 ) {
336+ self .freeSlice (buf );
337+ return buf [0.. 0];
338+ }
339+ if (new_len <= buf .len ) return buf [0.. new_len ];
340+
341+ const new_slice = self .allocSlice (new_len , alignment ) orelse return null ;
342+ @memcpy (new_slice [0.. buf .len ], buf );
343+ self .freeSlice (buf );
344+ return new_slice ;
345+ }
346+
347+ fn freeSlice (self : * Self , buf : []u8 ) void {
348+ if (buf .len == 0 ) return ;
349+ const header_ptr = @as (* Header , @ptrFromInt (@intFromPtr (buf .ptr ) - @sizeOf (Header )));
350+ const header = header_ptr .* ;
351+ const base_ptr = @as (* anyopaque , @ptrFromInt (header .base_addr ));
352+ c .fu_free (self .node_index , base_ptr , header .allocated_bytes );
353+ }
354+ };
355+
225356/// Thread pool for fork-join parallelism
226357pub const Pool = struct {
227358 handle : * anyopaque ,
@@ -367,7 +498,7 @@ pub const Pool = struct {
367498 func (prong , typed_ctx .* );
368499 }
369500 };
370- c .fu_pool_for_n (self .handle , n , Wrapper .callback , @constCast ( @ptrCast (& context )));
501+ c .fu_pool_for_n (self .handle , n , Wrapper .callback , @ptrCast ( @constCast (& context )));
371502 }
372503 }
373504
@@ -431,7 +562,7 @@ pub const Pool = struct {
431562 func (prong , typed_ctx .* );
432563 }
433564 };
434- c .fu_pool_for_n_dynamic (self .handle , n , Wrapper .callback , @constCast ( @ptrCast (& context )));
565+ c .fu_pool_for_n_dynamic (self .handle , n , Wrapper .callback , @ptrCast ( @constCast (& context )));
435566 }
436567 }
437568
@@ -499,7 +630,7 @@ pub const Pool = struct {
499630 func (prong , count , typed_ctx .* );
500631 }
501632 };
502- c .fu_pool_for_slices (self .handle , n , Wrapper .callback , @constCast ( @ptrCast (& context )));
633+ c .fu_pool_for_slices (self .handle , n , Wrapper .callback , @ptrCast ( @constCast (& context )));
503634 }
504635 }
505636
@@ -691,3 +822,33 @@ test "NUMA allocation" {
691822 slice [i ] = @intCast (i & 0xFF );
692823 }
693824}
825+
826+ test "NUMA allocator integrates with std collections" {
827+ std .debug .print ("Running test: NUMA allocator integrates with std collections\n " , .{});
828+ if (! numaEnabled ()) return error .SkipZigTest ;
829+
830+ var numa_alloc = NumaAllocator .init (0 );
831+ const allocator = numa_alloc .allocator ();
832+
833+ var list = try std .ArrayList (u64 ).initCapacity (allocator , 0 );
834+ defer list .deinit (allocator );
835+ try list .appendSlice (allocator , &[_ ]u64 { 1 , 2 , 3 , 4 , 5 });
836+ try std .testing .expectEqual (@as (usize , 5 ), list .items .len );
837+ try std .testing .expectEqual (@as (u64 , 3 ), list .items [2 ]);
838+
839+ var map = std .AutoHashMap (u32 , u32 ).init (allocator );
840+ defer map .deinit ();
841+ try map .put (10 , 100 );
842+ try map .put (20 , 200 );
843+ try map .put (30 , 300 );
844+ try std .testing .expectEqual (@as (usize , 3 ), map .count ());
845+ try std .testing .expectEqual (@as (u32 , 200 ), map .get (20 ).? );
846+
847+ var buf = try allocator .alloc (u8 , 128 );
848+ defer allocator .free (buf );
849+ @memset (buf , 0xAB );
850+
851+ buf = try allocator .realloc (buf , 512 );
852+ try std .testing .expectEqual (@as (usize , 512 ), buf .len );
853+ try std .testing .expectEqual (@as (u8 , 0xAB ), buf [0 ]);
854+ }
0 commit comments