selftests/bpf: Add buddy allocator for libarena

Add a byte-oriented buddy allocator for libarena. The buddy
allocator provides an alloc/free interface for small arena allocations
ranging from 16 bytes to 512 KiB. Lower allocations values are rounded
up to 16 bytes. The buddy allocator does not handle larger allocations
that can instead use the existing bpf_arena_{alloc, free}_pages() kfunc.

Signed-off-by: Emil Tsalapatis <emil@etsalapatis.com>
Link: https://lore.kernel.org/r/20260426190338.4615-7-emil@etsalapatis.com
Signed-off-by: Alexei Starovoitov <ast@kernel.org>
This commit is contained in:
Emil Tsalapatis
2026-04-26 18:12:22 -07:00
committed by Alexei Starovoitov
parent cfc00618b9
commit 86426a28c5
6 changed files with 1033 additions and 1 deletions
Binary file not shown.
@@ -51,6 +51,8 @@ ASAN_FLAGS += -mllvm -asan-destructor-kind=none
override BPF_CFLAGS += -DENABLE_ATOMICS_TESTS
override BPF_CFLAGS += -O2 -g
override BPF_CFLAGS += -Wno-incompatible-pointer-types-discards-qualifiers
# Required to define our own arena-based free()
override BPF_CFLAGS += -Wno-incompatible-library-redeclaration
# Required for suppressing harmless vmlinux.h-related warnings.
override BPF_CFLAGS += -Wno-missing-declarations
override BPF_CFLAGS += $(INCLUDES)
@@ -0,0 +1,92 @@
// SPDX-License-Identifier: LGPL-2.1 OR BSD-2-Clause
/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
#pragma once
struct buddy_chunk;
typedef struct buddy_chunk __arena buddy_chunk_t;
struct buddy_header;
typedef struct buddy_header __arena buddy_header_t;
enum buddy_consts {
/*
* Minimum allocation is 1 << BUDDY_MIN_ALLOC_SHIFT.
* Larger sizes increase internal fragmentation, but smaller
* sizes increase the space overhead of the block metadata.
*/
BUDDY_MIN_ALLOC_SHIFT = 4,
BUDDY_MIN_ALLOC_BYTES = 1 << BUDDY_MIN_ALLOC_SHIFT,
/*
* How many orders the buddy allocator can serve. Minimum block
* size is 1 << BUDDY_MIN_ALLOC_SHIFT, maximum block size is
* 1 << (BUDDY_MIN_ALLOC_SHIFT + BUDDY_CHUNK_NUM_ORDERS - 1):
* Each block has size 1 << BUDDY_MIN_ALLOC_SHIFT, and the
* allocation orders are in [0, BUDDY_CHUNK_NUM_ORDERS).
* We keep two blocks of the maximum size to retain the
* property in the code that all blocks have a buddy.
* Higher values increase the maximum allocation size,
* but also the size of the metadata for each block.
*/
BUDDY_CHUNK_NUM_ORDERS = 1 << 4,
BUDDY_CHUNK_BYTES = BUDDY_MIN_ALLOC_BYTES << (BUDDY_CHUNK_NUM_ORDERS),
/* Offset of the buddy header within a free block, see buddy.bpf.c for details */
BUDDY_HEADER_OFF = 8,
/* The maximum number of blocks a chunk may have to track. */
BUDDY_CHUNK_ITEMS = 1 << (BUDDY_CHUNK_NUM_ORDERS),
BUDDY_CHUNK_OFFSET_MASK = BUDDY_CHUNK_BYTES - 1,
/*
* Alignment for chunk allocations based on bpf_arena_alloc_pages.
* The arena allocation kfunc does not have an alignment argument,
* but that is required for all block calculations in the chunk to
* work.
*/
BUDDY_VADDR_OFFSET = BUDDY_CHUNK_BYTES,
/* Total arena virtual address space the allocator can consume. */
BUDDY_VADDR_SIZE = BUDDY_CHUNK_BYTES << 10
};
struct buddy_header {
u32 prev_index; /* "Pointer" to the previous available allocation of the same size. */
u32 next_index; /* Same for the next allocation. */
};
/*
* We bring memory into the allocator 1 MiB at a time.
*/
struct buddy_chunk {
/* The order of the current allocation for a item. 4 bits per order. */
u8 orders[BUDDY_CHUNK_ITEMS / 2];
/*
* Bit to denote whether chunk is allocated. Size of the allocated/free
* chunk found from the orders array.
*/
u8 allocated[BUDDY_CHUNK_ITEMS / 8];
/* Freelists for O(1) allocation. */
u64 freelists[BUDDY_CHUNK_NUM_ORDERS];
buddy_chunk_t *next;
};
struct buddy {
buddy_chunk_t *first_chunk; /* Pointer to the chunk linked list. */
arena_spinlock_t lock; /* Allocator lock */
u64 vaddr; /* Allocation into reserved vaddr */
};
typedef struct buddy __arena buddy_t;
#ifdef __BPF__
int buddy_init(buddy_t *buddy);
int buddy_destroy(buddy_t *buddy);
int buddy_free_internal(buddy_t *buddy, u64 free);
#define buddy_free(buddy, ptr) buddy_free_internal((buddy), (u64)(ptr))
u64 buddy_alloc_internal(buddy_t *buddy, size_t size);
#define buddy_alloc(alloc, size) ((void __arena *)buddy_alloc_internal((alloc), (size)))
#endif /* __BPF__ */
@@ -48,6 +48,20 @@ extern volatile u64 asan_violated;
int arena_fls(__u64 word);
u64 malloc_internal(size_t size);
#define malloc(size) ((void __arena *)malloc_internal((size)))
void free(void __arena *ptr);
/*
* The verifier associates arenas with programs by checking LD.IMM
* instruction operands for an arena and populating the program state
* with the first instance it finds. This requires accessing our global
* arena variable, but subprogs do not necessarily do so while still
* using pointers from that arena. Insert an LD.IMM instruction to
* access the arena and help the verifier.
*/
#define arena_subprog_init() do { asm volatile ("" :: "r"(&arena)); } while (0)
#else /* ! __BPF__ */
#include <stdint.h>
File diff suppressed because it is too large Load Diff
@@ -1,11 +1,13 @@
// SPDX-License-Identifier: LGPL-2.1 OR BSD-2-Clause
/* Copyright (c) 2026 Meta Platforms, Inc. and affiliates. */
#include <libarena/common.h>
#include <libarena/asan.h>
#include <libarena/buddy.h>
const volatile u32 zero = 0;
buddy_t buddy;
int arena_fls(__u64 word)
{
if (!word)
@@ -28,4 +30,23 @@ __weak int arena_alloc_reserve(struct arena_alloc_reserve_args *args)
return bpf_arena_reserve_pages(&arena, NULL, args->nr_pages);
}
SEC("syscall")
__weak int arena_buddy_reset(void)
{
buddy_destroy(&buddy);
return buddy_init(&buddy);
}
__weak u64 malloc_internal(size_t size)
{
return buddy_alloc_internal(&buddy, size);
}
__weak void free(void __arg_arena __arena *ptr)
{
buddy_free_internal(&buddy, (u64)ptr);
}
char _license[] SEC("license") = "GPL";