129 lines
3.3 KiB
Go
129 lines
3.3 KiB
Go
package alloc
|
|
|
|
import (
|
|
"errors"
|
|
"fmt"
|
|
"log"
|
|
"runtime"
|
|
"sync"
|
|
"unsafe"
|
|
|
|
"git.brut.systems/judah/xx/mem"
|
|
)
|
|
|
|
// Linear is a simple bump allocator with a fixed amount of backing memory.
|
|
func Linear(maxsize uintptr) Allocator {
|
|
var (
|
|
data = make([]byte, maxsize)
|
|
offset uintptr
|
|
)
|
|
|
|
return func(a Action, size, align uintptr, watermark *uintptr) (unsafe.Pointer, error) {
|
|
switch a {
|
|
case ActionAlloc:
|
|
aligned := mem.AlignForward(size, align)
|
|
if offset+aligned > maxsize {
|
|
return nil, fmt.Errorf("linear: out of memory - %d bytes requested, %d bytes free", size, maxsize-offset)
|
|
}
|
|
|
|
ptr := &data[offset]
|
|
offset += aligned
|
|
return unsafe.Pointer(ptr), nil
|
|
|
|
case ActionReset:
|
|
clear(data)
|
|
offset = 0
|
|
|
|
case ActionSave:
|
|
*watermark = offset
|
|
case ActionRestore:
|
|
offset = *watermark
|
|
|
|
default:
|
|
panic("unimplemented action: " + a.String())
|
|
}
|
|
|
|
return nil, nil
|
|
}
|
|
}
|
|
|
|
// Nil is an Allocator that always returns an error.
|
|
//
|
|
// This is useful for tracking usage locations
|
|
func Nil() Allocator {
|
|
return func(a Action, size, align uintptr, watermark *uintptr) (unsafe.Pointer, error) {
|
|
return nil, errors.New("use of nil allocator")
|
|
}
|
|
}
|
|
|
|
// Temporary wraps an Allocator, restoring it to its previous state when Reset is called.
|
|
func Temporary(alloc Allocator) Allocator {
|
|
watermark := new(uintptr)
|
|
*watermark = Save(alloc)
|
|
return func(a Action, size, align uintptr, wm *uintptr) (unsafe.Pointer, error) {
|
|
if a == ActionReset {
|
|
Restore(alloc, *watermark)
|
|
return nil, nil
|
|
}
|
|
|
|
return alloc(a, size, align, wm)
|
|
}
|
|
}
|
|
|
|
// Split wraps two [[Allocator]]s, dispatching actions based on the size of the allocation.
|
|
func Split(split_size uintptr, smaller, larger Allocator) Allocator {
|
|
return func(a Action, size, align uintptr, watermark *uintptr) (unsafe.Pointer, error) {
|
|
if size <= split_size {
|
|
return smaller(a, size, align, watermark)
|
|
}
|
|
return larger(a, size, align, watermark)
|
|
}
|
|
}
|
|
|
|
// Logger wraps an Allocator, logging its usage locations.
|
|
func Logger(alloc Allocator) Allocator {
|
|
return func(a Action, size, align uintptr, watermark *uintptr) (unsafe.Pointer, error) {
|
|
// We expect allocators to be used via the high-level API, so we grab the caller location relative to that.
|
|
// @todo(judah): can we determine this dynamically?
|
|
_, file, line, ok := runtime.Caller(2)
|
|
if !ok {
|
|
file = "<unknown>"
|
|
line = 0
|
|
}
|
|
|
|
log.Printf("%s:%d - %s (size: %d, align: %d, watermark: %p)", file, line, a, size, align, watermark)
|
|
return alloc(a, size, align, watermark)
|
|
}
|
|
}
|
|
|
|
// Concurrent wraps an Allocator, ensuring it is safe for concurrent use.
|
|
func Concurrent(alloc Allocator) Allocator {
|
|
mtx := new(sync.Mutex)
|
|
return func(a Action, size, align uintptr, watermark *uintptr) (unsafe.Pointer, error) {
|
|
mtx.Lock()
|
|
ptr, err := alloc(a, size, align, watermark)
|
|
mtx.Unlock()
|
|
return ptr, err
|
|
}
|
|
}
|
|
|
|
// Pinned wraps an Allocator, ensuring the memory returned is stable until Reset is called.
|
|
//
|
|
// The memory returned by Pinned is safe to pass over cgo boundaries.
|
|
func Pinned(alloc Allocator) Allocator {
|
|
var pinner runtime.Pinner
|
|
return func(a Action, size, align uintptr, watermark *uintptr) (unsafe.Pointer, error) {
|
|
ptr, err := alloc(a, size, align, watermark)
|
|
if err != nil {
|
|
return ptr, err
|
|
}
|
|
|
|
if a == ActionReset {
|
|
pinner.Unpin()
|
|
} else {
|
|
pinner.Pin(ptr)
|
|
}
|
|
|
|
return ptr, err
|
|
}
|
|
}
|