mem: add windows memory allocation primitives, add basic tests
This commit is contained in:
parent
f83a1f323f
commit
c6bd701216
4 changed files with 194 additions and 12 deletions
33
mem/mem.go
33
mem/mem.go
|
|
@ -119,3 +119,36 @@ func ExtendSlice[T any](slice []T, amount uintptr) []T {
|
|||
|
||||
return slice[: uintptr(len(slice))+amount : cap(slice)]
|
||||
}
|
||||
|
||||
// Access describes memory access permissions.
|
||||
type Access int
|
||||
|
||||
const (
|
||||
AccessNone Access = 1 << iota
|
||||
AccessRead
|
||||
AccessWrite
|
||||
AccessExecute
|
||||
)
|
||||
|
||||
// Reserve returns a slice of bytes pointing to uncommitted virtual memory.
|
||||
// The length and capacity of the slice will be total_address_space bytes.
|
||||
//
|
||||
// The underlying memory of the slice must be comitted to phyiscal memory before being accessed (see: Commit).
|
||||
//
|
||||
// Use Release to return the virtual address space back to the operating system.
|
||||
func Reserve(total_address_space uintptr) ([]byte, error) { return reserve(total_address_space) }
|
||||
|
||||
// Release returns reserved virtual address space back to the operating system.
|
||||
//
|
||||
// Note: Any committed memory within its address space will be freed as well.
|
||||
func Release(reserved []byte) error { return release(reserved) }
|
||||
|
||||
// Commit maps virtual memory to physical memory.
|
||||
func Commit(reserved []byte, access Access) error { return commit(reserved, access) }
|
||||
|
||||
// Decommit unmaps committed memory, leaving the underlying addresss space intact.
|
||||
//
|
||||
// Decommitted memory can be re-committed at a later time using Commit.
|
||||
//
|
||||
// Note: Accessing the memory after calling Decommit is unsafe and may cause a panic.
|
||||
func Decommit(committed []byte) (err error) { return decommit(committed) }
|
||||
|
|
|
|||
|
|
@ -2,8 +2,10 @@ package mem_test
|
|||
|
||||
import (
|
||||
"testing"
|
||||
"unsafe"
|
||||
|
||||
"git.brut.systems/judah/xx/mem"
|
||||
"git.brut.systems/judah/xx/testx"
|
||||
)
|
||||
|
||||
func TestBitCast(t *testing.T) {
|
||||
|
|
@ -24,3 +26,53 @@ func TestBitCast(t *testing.T) {
|
|||
t.Fail()
|
||||
}
|
||||
}
|
||||
|
||||
func TestAllocationPrimitives(t *testing.T) {
|
||||
t.Run("reserve, unreserve", func(t *testing.T) {
|
||||
data, err := mem.Reserve(1 * mem.Gigabyte)
|
||||
testx.Expect(t, err == nil, "mem.Reserve returned an error - %s", err)
|
||||
|
||||
testx.Expect(t, len(data) == 1*mem.Gigabyte, "len was %d", len(data))
|
||||
testx.Expect(t, cap(data) == 1*mem.Gigabyte, "len was %d", cap(data))
|
||||
|
||||
err = mem.Release(data)
|
||||
testx.Expect(t, err == nil, "mem.Unreserve returned an error - %s", err)
|
||||
})
|
||||
|
||||
t.Run("commit", func(t *testing.T) {
|
||||
data, err := mem.Reserve(1 * mem.Gigabyte)
|
||||
testx.Expect(t, err == nil, "mem.Reserve returned an error - %s", err)
|
||||
|
||||
err = mem.Commit(data, mem.AccessRead|mem.AccessWrite)
|
||||
testx.Expect(t, err == nil, "mem.Commit returned an error - %s", err)
|
||||
|
||||
for i := range data {
|
||||
data[i] = byte(i * i)
|
||||
}
|
||||
})
|
||||
|
||||
t.Run("decommit", func(t *testing.T) {
|
||||
data, err := mem.Reserve(1 * mem.Gigabyte)
|
||||
testx.Expect(t, err == nil, "mem.Reserve returned an error - %s", err)
|
||||
|
||||
err = mem.Commit(data, mem.AccessRead|mem.AccessWrite)
|
||||
testx.Expect(t, err == nil, "mem.Commit returned an error - %s", err)
|
||||
|
||||
before := uintptr(unsafe.Pointer(&data[0]))
|
||||
|
||||
err = mem.Decommit(data)
|
||||
testx.Expect(t, err == nil, "mem.Decommit returned an error - %s", err)
|
||||
|
||||
// accessing data before recommitting it will fail
|
||||
|
||||
err = mem.Commit(data, mem.AccessRead|mem.AccessWrite)
|
||||
testx.Expect(t, err == nil, "mem.Commit returned an error - %s", err)
|
||||
|
||||
after := uintptr(unsafe.Pointer(&data[0]))
|
||||
testx.Expect(t, before == after, "base pointers did not match between after recommit %d != %d", before, after)
|
||||
|
||||
for i := range data {
|
||||
data[i] = byte(i * i)
|
||||
}
|
||||
})
|
||||
}
|
||||
|
|
|
|||
|
|
@ -7,14 +7,7 @@ import (
|
|||
"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) {
|
||||
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
|
||||
|
|
@ -23,15 +16,15 @@ func Reserve(total_address_space uintptr) ([]byte, error) {
|
|||
return data, nil
|
||||
}
|
||||
|
||||
func Unreserve(reserved []byte) error {
|
||||
func release(reserved []byte) error {
|
||||
return syscall.Munmap(reserved)
|
||||
}
|
||||
|
||||
func Commit(reserved []byte, perms int) error {
|
||||
return syscall.Mprotect(reserved, perms)
|
||||
func commit(reserved []byte, access Access) error {
|
||||
return syscall.Mprotect(reserved, access_to_prot(access))
|
||||
}
|
||||
|
||||
func Decommit(committed []byte) (err error) {
|
||||
func decommit(committed []byte) (err error) {
|
||||
err = syscall.Mprotect(committed, syscall.PROT_NONE)
|
||||
if err != nil {
|
||||
return
|
||||
|
|
@ -57,3 +50,19 @@ func madvise(b []byte, advice int) (err error) {
|
|||
|
||||
return
|
||||
}
|
||||
|
||||
func access_to_prot(access Access) (prot int) {
|
||||
prot = syscall.PROT_NONE
|
||||
|
||||
if access&AccessRead != 0 {
|
||||
prot |= syscall.PROT_READ
|
||||
}
|
||||
if access&AccessWrite != 0 {
|
||||
prot |= syscall.PROT_WRITE
|
||||
}
|
||||
if access&AccessExecute != 0 {
|
||||
prot |= syscall.PROT_EXEC
|
||||
}
|
||||
|
||||
return
|
||||
}
|
||||
|
|
|
|||
88
mem/mem_windows.go
Normal file
88
mem/mem_windows.go
Normal file
|
|
@ -0,0 +1,88 @@
|
|||
//go:build windows
|
||||
|
||||
package mem
|
||||
|
||||
import (
|
||||
"syscall"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
func reserve(total_address_space uintptr) ([]byte, error) {
|
||||
addr, _, err := _VirtualAlloc.Call(0, total_address_space, _MEM_RESERVE, _PAGE_NOACCESS)
|
||||
if addr == 0 {
|
||||
return nil, err
|
||||
}
|
||||
|
||||
return unsafe.Slice((*byte)(unsafe.Pointer(addr)), total_address_space), nil
|
||||
}
|
||||
|
||||
func release(reserved []byte) error {
|
||||
res, _, err := _VirtualFree.Call(uintptr(unsafe.Pointer(&reserved[0])), 0, _MEM_RELEASE)
|
||||
if res == 0 {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func commit(reserved []byte, access Access) error {
|
||||
ret, _, err := _VirtualAlloc.Call(
|
||||
uintptr(unsafe.Pointer(&reserved[0])),
|
||||
uintptr(len(reserved)),
|
||||
_MEM_COMMIT,
|
||||
uintptr(access_to_prot(access)),
|
||||
)
|
||||
if ret == 0 {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func decommit(committed []byte) error {
|
||||
ret, _, err := _VirtualFree.Call(
|
||||
uintptr(unsafe.Pointer(&committed[0])),
|
||||
uintptr(len(committed)),
|
||||
_MEM_DECOMMIT,
|
||||
)
|
||||
if ret == 0 {
|
||||
return err
|
||||
}
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
var (
|
||||
kernel32 = syscall.NewLazyDLL("kernel32.dll")
|
||||
_VirtualAlloc = kernel32.NewProc("VirtualAlloc")
|
||||
_VirtualFree = kernel32.NewProc("VirtualFree")
|
||||
)
|
||||
|
||||
const (
|
||||
_MEM_COMMIT = 0x1000
|
||||
_MEM_RESERVE = 0x2000
|
||||
_MEM_DECOMMIT = 0x4000
|
||||
_MEM_RELEASE = 0x8000
|
||||
|
||||
_PAGE_NOACCESS = 0x01
|
||||
_PAGE_READONLY = 0x02
|
||||
_PAGE_READWRITE = 0x04
|
||||
|
||||
_PAGE_EXECUTE_READ = 0x20
|
||||
_PAGE_EXECUTE_READWRITE = 0x40
|
||||
)
|
||||
|
||||
func access_to_prot(access Access) uint32 {
|
||||
switch access {
|
||||
case AccessRead | AccessWrite | AccessExecute:
|
||||
return _PAGE_EXECUTE_READWRITE
|
||||
case AccessRead | AccessExecute:
|
||||
return _PAGE_EXECUTE_READ
|
||||
case AccessRead | AccessWrite:
|
||||
return _PAGE_READWRITE
|
||||
case AccessRead:
|
||||
return _PAGE_READONLY
|
||||
default:
|
||||
return _PAGE_NOACCESS
|
||||
}
|
||||
}
|
||||
Loading…
Reference in a new issue