Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -39,11 +39,11 @@ jobs:
VMODULES: ${{ github.workspace }}/modules
defaults:
run:
working-directory: modules/antono2/mem
working-directory: modules/antono2/memory
steps:
- uses: actions/checkout@v7
with:
path: modules/antono2/mem
path: modules/antono2/memory
- uses: prantlf/setup-v-action@v4
with:
version: 0.5.2
Expand Down
10 changes: 5 additions & 5 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,8 @@ All notable changes to this project will be documented in this file.

## Unreleased

- Restore `antono2.memory` as the canonical module, import, repository, and
installed-directory identity.
- Add a deterministic power-of-two `BuddyAllocator` with checked allocation
records, recursive coalescing, occupancy statistics, tests, and an example.
- Add an independent bitmap-model stress test for deterministic first-fit range
Expand All @@ -13,17 +15,15 @@ All notable changes to this project will be documented in this file.
including fragmentation and occupancy diagnostics.
- Replay an identical bounded allocation trace for first-fit and buddy benchmark
comparisons, with success, failure, peak-use, and trace-integrity diagnostics.
- Run the allocator test suite and canonical `antono2.mem` public-import
- Run the allocator test suite and canonical `antono2.memory` public-import
examples on Linux, macOS, and Windows in CI.
- Add a task-oriented allocator selection guide covering lifetime, release
order, and fragmentation tradeoffs.

## 1.0.3 - 2026-09-10

- Use `antono2.mem` consistently as the VPM package, import, repository, and
installed-directory identity.
- Rename the source module declaration from `memory` to `mem` to match V's
required leaf-directory name.
- Record the superseded short-name packaging experiment performed before VPM
publication.

## 1.0.2 - 2026-09-10

Expand Down
42 changes: 21 additions & 21 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Memory management building blocks for V

[![CI](https://github.com/antono2/mem/actions/workflows/ci.yml/badge.svg)](https://github.com/antono2/mem/actions/workflows/ci.yml)
[![CI](https://github.com/antono2/memory/actions/workflows/ci.yml/badge.svg)](https://github.com/antono2/memory/actions/workflows/ci.yml)

`memory` provides small, documented, and tested building blocks for reusing
objects and managing bounded memory and resource ranges in V.
Expand All @@ -16,28 +16,28 @@ Windows.
## Install

```sh
v install antono2.mem
v install antono2.memory
```

Before the VPM entry is published, clone the release into V's canonical nested
module path:
Before the VPM entry is published, clone the repository into V's canonical
nested module path:

```sh
mkdir -p ~/.vmodules/antono2
git clone --branch v1.0.3 https://github.com/antono2/mem \
~/.vmodules/antono2/mem
git clone https://github.com/antono2/memory \
~/.vmodules/antono2/memory
```

V 0.5.2 cannot infer that nested path from an unregistered Git URL alone.

Then import it using its canonical VPM name:

```v
import antono2.mem
import antono2.memory
```

The current 1.x release uses `antono2.mem` as its VPM and import name, matching
the distribution repository and installed directory.
The canonical VPM and import name is `antono2.memory`, matching the repository
and installed directory.
Projects that still import `generic_pool` should pin the 0.2.0 release until
they are ready to update their imports.

Expand All @@ -64,7 +64,7 @@ Create a pool once, insert values until it reaches its fixed capacity, and use
the returned handle for later access or release:

```v
import antono2.mem
import antono2.memory

struct Particle {
mut:
Expand All @@ -73,7 +73,7 @@ mut:
}

fn main() {
mut particles := mem.new_slot_pool[Particle](128) or { panic(err) }
mut particles := memory.new_slot_pool[Particle](128) or { panic(err) }
handle := particles.insert(Particle{x: 10, y: 20}) or { panic(err) }

mut particle := particles.get_mut(handle) or { panic('stale particle handle') }
Expand Down Expand Up @@ -108,7 +108,7 @@ top of checked leases. Its factory runs only when no reusable value is available
and the fixed capacity has not been reached.

```v
import antono2.mem
import antono2.memory

struct Buffer {
mut:
Expand All @@ -126,7 +126,7 @@ fn reset_buffer(buffer Buffer) Buffer {
}

fn main() {
mut buffers := mem.new_object_pool[Buffer](16, 4, make_buffer,
mut buffers := memory.new_object_pool[Buffer](16, 4, make_buffer,
reset_buffer) or { panic(err) }

handle := buffers.acquire() or { panic(err) }
Expand All @@ -152,10 +152,10 @@ owning the resource itself. It uses deterministic first fit, returns checked
allocation records, and coalesces adjacent ranges when they are released.

```v
import antono2.mem
import antono2.memory

fn main() {
mut block := mem.new_range_allocator(256 * 1024 * 1024)
mut block := memory.new_range_allocator(256 * 1024 * 1024)
vertex_memory := block.allocate(48 * 1024, 256) or { panic(err) }

println('bind at offset ${vertex_memory.offset}')
Expand Down Expand Up @@ -183,10 +183,10 @@ lifetime. Individual ranges are not released; `reset()` invalidates all of them
at once while preserving the peak-use statistic.

```v
import antono2.mem
import antono2.memory

fn main() {
mut frame_arena := mem.new_linear_allocator(4 * 1024 * 1024)
mut frame_arena := memory.new_linear_allocator(4 * 1024 * 1024)
vertices := frame_arena.allocate(96 * 1024, 16) or { panic(err) }
uniforms := frame_arena.allocate(256, 256) or { panic(err) }

Expand All @@ -210,10 +210,10 @@ the same order they were created. It is designed for staging buffers, streaming
data, and resources whose lifetime follows a GPU submission or producer queue.

```v
import antono2.mem
import antono2.memory

fn main() {
mut uploads := mem.new_ring_allocator(64 * 1024 * 1024)
mut uploads := memory.new_ring_allocator(64 * 1024 * 1024)
frame_0 := uploads.allocate(4 * 1024, 256) or { panic(err) }
frame_1 := uploads.allocate(8 * 1024, 256) or { panic(err) }

Expand Down Expand Up @@ -241,10 +241,10 @@ size. Allocation splits larger blocks; release recursively coalesces free
buddies.

```v
import antono2.mem
import antono2.memory

fn main() {
mut pages := mem.new_buddy_allocator(64 * 1024 * 1024, 256) or { panic(err) }
mut pages := memory.new_buddy_allocator(64 * 1024 * 1024, 256) or { panic(err) }
allocation := pages.allocate(6000, 4096) or { panic(err) }

assert allocation.offset % 4096 == 0
Expand Down
2 changes: 1 addition & 1 deletion alignment.v
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
module mem
module memory

// align_forward returns the first value at or after value that is divisible by
// alignment. Callers validate that alignment is non-zero before using it.
Expand Down
27 changes: 13 additions & 14 deletions benchmarks/main.v
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module main

import antono2.mem
import antono2.memory
import os
import time

Expand Down Expand Up @@ -89,8 +89,8 @@ fn (mut trace AllocationTrace) mix_hash(value u64) {
}

fn benchmark_slot_pool(operations int) BenchmarkResult {
mut pool := mem.new_slot_pool[u64](1024) or { panic(err) }
mut handles := []mem.Handle{cap: 1024}
mut pool := memory.new_slot_pool[u64](1024) or { panic(err) }
mut handles := []memory.Handle{cap: 1024}
mut random_source := BenchmarkRandom{
state: 0x5107cafe
}
Expand Down Expand Up @@ -124,10 +124,9 @@ fn reset_benchmark_object(_ u64) u64 {
}

fn benchmark_object_pool(operations int) BenchmarkResult {
mut pool := mem.new_object_pool[u64](1024, 1024, make_benchmark_object, reset_benchmark_object) or {
panic(err)
}
mut handles := []mem.Handle{cap: 1024}
mut pool := memory.new_object_pool[u64](1024, 1024, make_benchmark_object,
reset_benchmark_object) or { panic(err) }
mut handles := []memory.Handle{cap: 1024}
mut random_source := BenchmarkRandom{
state: 0x0b1ec700
}
Expand Down Expand Up @@ -159,9 +158,9 @@ fn benchmark_object_pool(operations int) BenchmarkResult {
}

fn benchmark_range_allocator(operations int) BenchmarkResult {
mut allocator := mem.new_range_allocator(1024 * 1024)
mut allocator := memory.new_range_allocator(1024 * 1024)
mut trace := new_allocation_trace()
mut allocations := []mem.RangeAllocation{len: allocator_trace_max_live}
mut allocations := []memory.RangeAllocation{len: allocator_trace_max_live}
mut allocated := []bool{len: allocator_trace_max_live}
mut checksum := u64(0)
mut allocation_attempts := 0
Expand Down Expand Up @@ -206,9 +205,9 @@ fn benchmark_range_allocator(operations int) BenchmarkResult {
}

fn benchmark_buddy_allocator(operations int) BenchmarkResult {
mut allocator := mem.new_buddy_allocator(1024 * 1024, 16) or { panic(err) }
mut allocator := memory.new_buddy_allocator(1024 * 1024, 16) or { panic(err) }
mut trace := new_allocation_trace()
mut allocations := []mem.BuddyAllocation{len: allocator_trace_max_live}
mut allocations := []memory.BuddyAllocation{len: allocator_trace_max_live}
mut allocated := []bool{len: allocator_trace_max_live}
mut checksum := u64(0)
mut allocation_attempts := 0
Expand Down Expand Up @@ -249,7 +248,7 @@ fn benchmark_buddy_allocator(operations int) BenchmarkResult {
}

fn benchmark_linear_allocator(operations int) BenchmarkResult {
mut allocator := mem.new_linear_allocator(1024 * 1024)
mut allocator := memory.new_linear_allocator(1024 * 1024)
mut random_source := BenchmarkRandom{
state: 0x1a2b3c4d
}
Expand Down Expand Up @@ -278,8 +277,8 @@ fn benchmark_linear_allocator(operations int) BenchmarkResult {
}

fn benchmark_ring_allocator(operations int) BenchmarkResult {
mut allocator := mem.new_ring_allocator(1024 * 1024)
mut active := []mem.RingAllocation{cap: 2048}
mut allocator := memory.new_ring_allocator(1024 * 1024)
mut active := []memory.RingAllocation{cap: 2048}
mut first_active := 0
mut random_source := BenchmarkRandom{
state: 0x71f01234
Expand Down
2 changes: 1 addition & 1 deletion buddy_allocator.v
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
module mem
module memory

const buddy_node_free = u8(0)
const buddy_node_split = u8(1)
Expand Down
2 changes: 1 addition & 1 deletion buddy_allocator_test.v
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
module mem
module memory

fn test_buddy_allocator_validates_configuration() {
if _ := new_buddy_allocator(0, 1) {
Expand Down
4 changes: 2 additions & 2 deletions examples/buddy_allocator/main.v
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
module main

import antono2.mem
import antono2.memory

fn main() {
mut pages := mem.new_buddy_allocator(64 * 1024, 256) or { panic(err) }
mut pages := memory.new_buddy_allocator(64 * 1024, 256) or { panic(err) }
uniforms := pages.allocate(700, 256) or { panic(err) }
texture_staging := pages.allocate(5000, 4096) or { panic(err) }

Expand Down
6 changes: 4 additions & 2 deletions examples/buffer_pool/main.v
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module main

import antono2.mem
import antono2.memory

struct Buffer {
mut:
Expand All @@ -20,7 +20,9 @@ fn reset_buffer(buffer Buffer) Buffer {
}

fn main() {
mut buffers := mem.new_object_pool[Buffer](16, 4, make_buffer, reset_buffer) or { panic(err) }
mut buffers := memory.new_object_pool[Buffer](16, 4, make_buffer, reset_buffer) or {
panic(err)
}

handle := buffers.acquire() or { panic(err) }
mut buffer := buffers.get_mut(handle) or { panic('stale buffer handle') }
Expand Down
4 changes: 2 additions & 2 deletions examples/linear_allocator/main.v
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
module main

import antono2.mem
import antono2.memory

fn main() {
mut frame_memory := []u8{len: 1024}
mut arena := mem.new_linear_allocator(u64(frame_memory.len))
mut arena := memory.new_linear_allocator(u64(frame_memory.len))

for frame in 0 .. 3 {
vertices := arena.allocate(300, 16) or { panic(err) }
Expand Down
4 changes: 2 additions & 2 deletions examples/object_pool/main.v
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
module main

import antono2.mem
import antono2.memory

struct MovingActor {
name string = 'MovingActor'
Expand All @@ -20,7 +20,7 @@ fn reset_moving_actor(actor MovingActor) MovingActor {
}

fn main() {
mut actors := mem.new_object_pool[MovingActor](2, 1, make_moving_actor, reset_moving_actor) or {
mut actors := memory.new_object_pool[MovingActor](2, 1, make_moving_actor, reset_moving_actor) or {
panic(err)
}

Expand Down
4 changes: 2 additions & 2 deletions examples/range_allocator/main.v
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
module main

import antono2.mem
import antono2.memory

fn main() {
mut backing_buffer := []u8{len: 256}
mut ranges := mem.new_range_allocator(u64(backing_buffer.len))
mut ranges := memory.new_range_allocator(u64(backing_buffer.len))

vertices := ranges.allocate(36, 16) or { panic(err) }
indices := ranges.allocate(12, 4) or { panic(err) }
Expand Down
4 changes: 2 additions & 2 deletions examples/ring_allocator/main.v
Original file line number Diff line number Diff line change
@@ -1,10 +1,10 @@
module main

import antono2.mem
import antono2.memory

fn main() {
mut staging_buffer := []u8{len: 64}
mut uploads := mem.new_ring_allocator(u64(staging_buffer.len))
mut uploads := memory.new_ring_allocator(u64(staging_buffer.len))

frame_0 := uploads.allocate(24, 16) or { panic(err) }
frame_1 := uploads.allocate(24, 16) or { panic(err) }
Expand Down
2 changes: 1 addition & 1 deletion linear_allocator.v
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
module mem
module memory

// LinearAllocation identifies one aligned range from a LinearAllocator. It is
// valid until the allocator is reset.
Expand Down
2 changes: 1 addition & 1 deletion linear_allocator_test.v
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
module mem
module memory

fn test_linear_allocator_aligns_and_accounts_for_padding() {
mut allocator := new_linear_allocator(64)
Expand Down
2 changes: 1 addition & 1 deletion object_pool.v
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
module mem
module memory

// ObjectFactory creates one value when an ObjectPool has no reusable value
// available.
Expand Down
2 changes: 1 addition & 1 deletion object_pool_test.v
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
module mem
module memory

struct ReusableItem {
mut:
Expand Down
2 changes: 1 addition & 1 deletion range_allocator.v
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
module mem
module memory

struct FreeRange {
mut:
Expand Down
2 changes: 1 addition & 1 deletion range_allocator_test.v
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
module mem
module memory

fn test_range_allocator_aligns_tracks_and_coalesces() {
mut allocator := new_range_allocator(128)
Expand Down
Loading