From f83a1f323f47afa5b71ebd311cf20724ca4af6c6 Mon Sep 17 00:00:00 2001 From: Judah Caruso Date: Sat, 31 Jan 2026 01:01:15 -0700 Subject: [PATCH] mem: memory allocation primitives; arena: paging arena --- arena/arenas.go | 84 +++++++++++++++++++++++++++++++++++++++++++++++++ mem/mem_unix.go | 59 ++++++++++++++++++++++++++++++++++ 2 files changed, 143 insertions(+) create mode 100644 mem/mem_unix.go diff --git a/arena/arenas.go b/arena/arenas.go index 89ead7e..c95a9fc 100644 --- a/arena/arenas.go +++ b/arena/arenas.go @@ -2,6 +2,7 @@ package arena import ( "errors" + "fmt" "log" "math/bits" "runtime" @@ -171,6 +172,89 @@ func Chunked(max_allocs_per_chunk uintptr) Arena { } } +// Paging is a linear arena that allocates pages of virtual memory. +// The memory allocated is only committed to physical memory as it is used, +// so total_reserved_in_bytes should is the total amount of addressable memory to reserve. +// +// Note: resetting a Paging arena will cause the currently commited memory to be decommited (i.e. unmapped from physical memory). +func Paging(page_size, total_reserved_in_bytes uintptr) Arena { + var ( + committed uintptr + offset uintptr + ) + + base, err := mem.Reserve(total_reserved_in_bytes) + if err != nil { + panic(fmt.Sprintf("paging: failed to reserve address space - %s", err)) + } + + // @todo(judah): is this needed? + runtime.AddCleanup(&base, func(_ struct{}) { + if err := mem.Unreserve(base); err != nil { + panic(fmt.Sprintf("paging: failed to release memory - %s", err)) + } + }, struct{}{}) + + return func(a Action, size, align uintptr, watermark *uintptr) (unsafe.Pointer, error) { + switch a { + case ACTION_ALLOC: + aligned := mem.AlignForward(size, align) + if offset+aligned > total_reserved_in_bytes { + return nil, errors.New("paging: out of addressable memory") + } + + if offset+aligned > committed { + required := offset + aligned + to_commit := mem.AlignForward(required, page_size) + + if err := mem.Commit(base[committed:to_commit-committed], mem.PERM_READ|mem.PERM_WRITE); err != nil { + return nil, fmt.Errorf("paging: failed to commit memory - %w", err) + } + + committed = to_commit + } + + ptr := &base[offset] + offset += aligned + return unsafe.Pointer(ptr), nil + + case ACTION_RESET: + if committed > 0 { + if err := mem.Decommit(base[:mem.AlignForward(committed, page_size)]); err != nil { + return nil, fmt.Errorf("paging: failed to decommit memory - %w", err) + } + } + + offset = 0 + committed = 0 + return nil, nil + + // @todo(judah): should save/restore also decommit memory? + + case ACTION_SAVE: + if watermark == nil { + return nil, errors.New("paging: cannot save to nil watermark") + } + + *watermark = offset + return nil, nil + + case ACTION_RESTORE: + if watermark == nil { + return nil, errors.New("paging: cannot restore nil watermark") + } + + clear(base[*watermark:offset]) + offset = *watermark + + return nil, nil + + default: + panic("paging: unimplemented action - " + a.String()) + } + } +} + // Nil is an Arena that always returns an error. // // Note: This is useful for tracking usage locations diff --git a/mem/mem_unix.go b/mem/mem_unix.go new file mode 100644 index 0000000..d85d428 --- /dev/null +++ b/mem/mem_unix.go @@ -0,0 +1,59 @@ +//go:build unix + +package mem + +import ( + "syscall" + "unsafe" +) + +const ( + PERM_NONE = syscall.PROT_NONE + PERM_READ = syscall.PROT_READ + PERM_WRITE = syscall.PROT_WRITE + PERM_EXECUTE = syscall.PROT_EXEC +) + +func Reserve(total_address_space uintptr) ([]byte, error) { + data, err := syscall.Mmap(-1, 0, int(total_address_space), syscall.PROT_NONE, syscall.MAP_PRIVATE|syscall.MAP_ANON) + if err != nil { + return nil, err + } + + return data, nil +} + +func Unreserve(reserved []byte) error { + return syscall.Munmap(reserved) +} + +func Commit(reserved []byte, perms int) error { + return syscall.Mprotect(reserved, perms) +} + +func Decommit(committed []byte) (err error) { + err = syscall.Mprotect(committed, syscall.PROT_NONE) + if err != nil { + return + } + + return madvise(committed, syscall.MADV_DONTNEED) +} + +var _zero uintptr + +func madvise(b []byte, advice int) (err error) { + var _p0 unsafe.Pointer + if len(b) > 0 { + _p0 = unsafe.Pointer(&b[0]) + } else { + _p0 = unsafe.Pointer(&_zero) + } + + _, _, e := syscall.Syscall(syscall.SYS_MADVISE, uintptr(_p0), uintptr(len(b)), uintptr(advice)) + if e != 0 { + err = syscall.Errno(e) + } + + return +}