- Virtual memory library.

- New virtual emory arena allocator.
- Fixed resize bug when resizing memory down in ArenaAllocator, DynamicArenaAllocator, BackedArenaAllocator.
- Added feature flag "SLOW_TESTS"
This commit is contained in:
Christoffer Lerno
2025-07-14 22:36:43 +02:00
parent f082cac762
commit efaac43248
13 changed files with 383 additions and 214 deletions

View File

@@ -138,7 +138,7 @@ fn void*? ArenaAllocator.resize(&self, void *old_pointer, usz size, usz alignmen
}
// Otherwise just allocate new memory.
void* mem = self.acquire(size, NO_ZERO, alignment)!;
mem::copy(mem, old_pointer, old_size, mem::DEFAULT_MEM_ALIGNMENT, mem::DEFAULT_MEM_ALIGNMENT);
mem::copy(mem, old_pointer, math::min(size, old_size), mem::DEFAULT_MEM_ALIGNMENT, mem::DEFAULT_MEM_ALIGNMENT);
return mem;
}

View File

@@ -137,7 +137,7 @@ fn void*? BackedArenaAllocator.resize(&self, void* pointer, usz size, usz alignm
}
AllocChunk* data = self.acquire(size, NO_ZERO, alignment)!;
mem::copy(data, pointer, chunk.size, mem::DEFAULT_MEM_ALIGNMENT, mem::DEFAULT_MEM_ALIGNMENT);
mem::copy(data, pointer, math::min(size, chunk.size), mem::DEFAULT_MEM_ALIGNMENT, mem::DEFAULT_MEM_ALIGNMENT);
return data;
}

View File

@@ -117,7 +117,7 @@ fn void*? DynamicArenaAllocator.resize(&self, void* old_pointer, usz size, usz a
return old_pointer;
}
void* new_mem = self.acquire(size, NO_ZERO, alignment)!;
mem::copy(new_mem, old_pointer, old_size, mem::DEFAULT_MEM_ALIGNMENT);
mem::copy(new_mem, old_pointer, math::min(old_size, size), mem::DEFAULT_MEM_ALIGNMENT);
return new_mem;
}

View File

@@ -1,5 +1,5 @@
module std::core::mem::allocator;
import std::math;
<*
The OnStackAllocator is similar to the ArenaAllocator: it allocates from a chunk of memory
given to it.
@@ -124,7 +124,7 @@ fn void*? OnStackAllocator.resize(&self, void* old_pointer, usz size, usz alignm
OnStackAllocatorHeader* header = old_pointer - OnStackAllocatorHeader.sizeof;
usz old_size = header.size;
void* mem = self.acquire(size, NO_ZERO, alignment)!;
mem::copy(mem, old_pointer, old_size, mem::DEFAULT_MEM_ALIGNMENT, mem::DEFAULT_MEM_ALIGNMENT);
mem::copy(mem, old_pointer, math::min(old_size, size), mem::DEFAULT_MEM_ALIGNMENT, mem::DEFAULT_MEM_ALIGNMENT);
return mem;
}

View File

@@ -1,11 +1,13 @@
module std::core::mem::allocator @if(env::POSIX || env::WIN32);
import std::math, std::os::posix, libc, std::bits;
import std::core::mem;
import std::core::env;
// Virtual Memory allocator
faultdef VMEM_RESERVE_FAILED, VMEM_PROTECT_FAILED;
faultdef VMEM_RESERVE_FAILED;
struct Vmem (Allocator)
{
@@ -43,13 +45,13 @@ fn void? Vmem.init(&self, usz preferred_size, usz reserve_page_size = 0, VmemOpt
VirtualMemory? memory = mem::OUT_OF_MEMORY?;
while (preferred_size >= min_size)
{
memory = mem::virtual_alloc(preferred_size, PROTECTED);
memory = vm::virtual_alloc(preferred_size, PROTECTED);
// It worked?
if (try memory) break;
switch (@catch(memory))
{
case mem::OUT_OF_MEMORY:
case mem::VMEM_OVERFLOW:
case vm::RANGE_OVERFLOW:
// Try a smaller size.
preferred_size /= 2;
continue;
@@ -119,11 +121,11 @@ fn void*? Vmem.resize(&self, void *old_pointer, usz size, usz alignment) @dynami
assert(self.owns_pointer(old_pointer), "Pointer originates from a different allocator: %p, not in %p - %p", old_pointer, self.memory.ptr, self.memory.ptr + self.allocated);
VmemHeader* header = old_pointer - VmemHeader.sizeof;
usz old_size = header.size;
if (old_size == size) return old_pointer;
// Do last allocation and alignment match?
if (self.memory.ptr + self.allocated == old_pointer + old_size && mem::ptr_is_aligned(old_pointer, alignment))
{
if (old_size == size) return old_pointer;
if (old_size >= size)
if (old_size > size)
{
unprotect(self, self.allocated + size - old_size);
}
@@ -136,8 +138,17 @@ fn void*? Vmem.resize(&self, void *old_pointer, usz size, usz alignment) @dynami
header.size = size;
return old_pointer;
}
if (old_size > size)
{
$if env::ADDRESS_SANITIZER:
asan::poison_memory_region(old_pointer + size, old_size - size);
$endif
header.size = size;
return old_pointer;
}
// Otherwise just allocate new memory.
void* mem = self.acquire(size, NO_ZERO, alignment)!;
assert(size > old_size);
mem::copy(mem, old_pointer, old_size, mem::DEFAULT_MEM_ALIGNMENT, mem::DEFAULT_MEM_ALIGNMENT);
return mem;
}
@@ -181,7 +192,7 @@ fn void Vmem.free(&self)
$case env::COMPILER_SAFE_MODE:
((char*)self.memory.ptr)[0:self.allocated] = 0xAA;
$endswitch
self.memory.destroy();
(void)self.memory.destroy();
*self = {};
}
@@ -234,8 +245,8 @@ macro void unprotect(Vmem* mem, usz after) @local
{
usz start = page_after << shift;
usz len = (last_page - page_after) << shift;
if (mem.options.shrink_on_reset) (void)mem.memory.decommit(start, len, false);
if (mem.options.protect_unused_pages) (void)mem.memory.protect(start, len, PROTECTED);
if (mem.options.shrink_on_reset) (void)mem.memory.decommit(start, len);
}
mem.allocated = after;
}

View File

@@ -3,7 +3,7 @@
// a copy of which can be found in the LICENSE_STDLIB file.
module std::core::mem;
import std::core::mem::allocator @public;
import std::os::posix;
import std::os::posix, std::os::win32;
import std::math;
const MAX_MEMORY_ALIGNMENT = 0x1000_0000;
@@ -24,10 +24,15 @@ fn usz os_pagesize()
{
$switch:
$case env::POSIX:
return posix::getpagesize();
static usz pagesize;
if (pagesize) return pagesize;
return pagesize = posix::getpagesize();
$case env::WIN32:
// Possibly improve this
return 4096;
static usz pagesize;
if (pagesize) return pagesize;
Win32_SYSTEM_INFO info;
win32::getSystemInfo(&info);
return pagesize = info.dwPageSize;
$default:
return 4096;
$endswitch
@@ -316,6 +321,11 @@ fn bool ptr_is_aligned(void* ptr, usz alignment) @inline
return (uptr)ptr & ((uptr)alignment - 1) == 0;
}
fn bool ptr_is_page_aligned(void* ptr) @inline
{
return (uptr)ptr & ((uptr)os_pagesize() - 1) == 0;
}
macro void zero_volatile(char[] data)
{
$$memset(data.ptr, (char)0, data.len, true, (usz)1);

323
lib/std/core/os/mem_vm.c3 Normal file
View File

@@ -0,0 +1,323 @@
<*
The VM module holds code for working with virtual memory on supported platforms (currently Win32 and Posix)
*>
module std::core::mem::vm;
import std::os::win32, std::os::posix, libc;
<*
VirtualMemory is an abstraction for working with an allocated virtual memory area. It will invoke vm:: functions
but will perform more checks and track its size (required to unmap the memory on Posix)
*>
struct VirtualMemory
{
void* ptr;
usz size;
VirtualMemoryAccess default_access;
}
faultdef RANGE_OVERFLOW, UNKNOWN_ERROR, ACCESS_DENIED, UNMAPPED_ACCESS, UNALIGNED_ADDRESS, RELEASE_FAILED, UPDATE_FAILED, INVALID_ARGS;
enum VirtualMemoryAccess
{
PROTECTED,
READ,
WRITE,
READWRITE,
EXEC,
EXECREAD,
EXECWRITE,
ANY
}
fn usz aligned_alloc_size(usz size)
{
$if env::WIN32:
return size > 0 ? mem::aligned_offset(size, win32::allocation_granularity()) : win32::allocation_granularity();
$else
return size > 0 ? mem::aligned_offset(size, mem::os_pagesize()) : mem::os_pagesize();
$endif
}
<*
Allocate virtual memory, size is rounded up to platform granularity (Win32) / page size (Posix).
@param size : "The size of the memory to allocate, will be rounded up"
@param access : "The initial access permissions."
@return? mem::OUT_OF_MEMORY, RANGE_OVERFLOW, UNKNOWN_ERROR, ACCESS_DENIED, INVALID_ARGS
@return "Pointer to the allocated memory, page aligned"
*>
fn void*? alloc(usz size, VirtualMemoryAccess access)
{
$switch:
$case env::POSIX:
void* ptr = posix::mmap(null, aligned_alloc_size(size), access.to_posix(), posix::MAP_PRIVATE | posix::MAP_ANONYMOUS, -1, 0);
if (ptr != posix::MAP_FAILED) return ptr;
switch (libc::errno())
{
case errno::ENOMEM: return mem::OUT_OF_MEMORY?;
case errno::EOVERFLOW: return RANGE_OVERFLOW?;
case errno::EPERM: return ACCESS_DENIED?;
case errno::EINVAL: return INVALID_ARGS?;
default: return UNKNOWN_ERROR?;
}
$case env::WIN32:
void* ptr = win32::virtualAlloc(null, aligned_alloc_size(size), MEM_RESERVE, access.to_win32());
if (ptr) return ptr;
switch (win32::getLastError())
{
case win32::ERROR_NOT_ENOUGH_MEMORY:
case win32::ERROR_COMMITMENT_LIMIT: return mem::OUT_OF_MEMORY?;
default: return UNKNOWN_ERROR?;
}
$default:
unsupported("Virtual alloc only available on Win32 and Posix");
$endswitch
}
<*
Release memory allocated with "alloc".
@param [&inout] ptr : "Pointer to page to release, should be allocated using vm::alloc"
@param size : "The size of the allocated pointer"
@require mem::ptr_is_page_aligned(ptr) : "The pointer should be page aligned"
*>
fn void? release(void* ptr, usz size)
{
$switch:
$case env::POSIX:
if (posix::munmap(ptr, aligned_alloc_size(size)))
{
switch (libc::errno())
{
case errno::EINVAL: return INVALID_ARGS?; // Not a valid mapping or size
case errno::ENOMEM: return UNMAPPED_ACCESS?; // Address not mapped
default: return RELEASE_FAILED?;
}
}
$case env::WIN32:
if (win32::virtualFree(ptr, 0, MEM_RELEASE)) return;
switch (win32::getLastError())
{
case win32::ERROR_INVALID_ADDRESS: return INVALID_ARGS?;
case win32::ERROR_NOT_ENOUGH_MEMORY: return mem::OUT_OF_MEMORY?;
default: return RELEASE_FAILED?;
}
$default:
unsupported("Virtual free only available on Win32 and Posix");
$endswitch
}
<*
Change the access protection of a region in memory. The region must be page aligned.
@param [&inout] ptr : "Pointer to page to update, must be page aligned"
@param len : "To what len to update, must be page aligned"
@param access : "The new access"
@require mem::ptr_is_page_aligned(ptr) : "The pointer should be page aligned"
@require mem::ptr_is_page_aligned(ptr + len) : "The length must be page aligned"
@return? ACCESS_DENIED, UNALIGNED_ADDRESS, RANGE_OVERFLOW, UPDATE_FAILED, UNMAPPED_ACCESS, INVALID_ARGS
*>
fn void? protect(void* ptr, usz len, VirtualMemoryAccess access)
{
$switch:
$case env::POSIX:
if (!posix::mprotect(ptr, len, access.to_posix())) return;
switch (libc::errno())
{
case errno::EACCES: return ACCESS_DENIED?;
case errno::EINVAL: return UNALIGNED_ADDRESS?;
case errno::EOVERFLOW: return RANGE_OVERFLOW?;
case errno::ENOMEM: return UNMAPPED_ACCESS?;
default: return UPDATE_FAILED?;
}
$case env::WIN32:
Win32_Protect old;
if (win32::virtualProtect(ptr, len, access.to_win32(), &old)) return;
switch (win32::getLastError())
{
case win32::ERROR_INVALID_ADDRESS: return UNALIGNED_ADDRESS?;
case win32::ERROR_ACCESS_DENIED: return ACCESS_DENIED?;
case win32::ERROR_INVALID_PARAMETER: return INVALID_ARGS?;
default: return UPDATE_FAILED?;
}
$default:
unsupported("'virtual_protect' is only available on Win32 and Posix.");
$endswitch
}
<*
Makes a region of memory available that was previously retrieved using 'alloc'. This is necessary on Win32,
but optional on Posix.
@param [&inout] ptr : "Pointer to page to update, must be page aligned"
@param len : "To what len to commit, must be page aligned"
@require mem::ptr_is_page_aligned(ptr) : "The pointer should be page aligned"
@require mem::ptr_is_page_aligned(ptr + len) : "The length must be page aligned"
@return? UNKNOWN_ERROR, mem::OUT_OF_MEMORY, ACCESS_DENIED, UNALIGNED_ADDRESS, RANGE_OVERFLOW, UPDATE_FAILED, UNMAPPED_ACCESS, INVALID_ARGS
*>
fn void? commit(void* ptr, usz len, VirtualMemoryAccess access = READWRITE)
{
$switch:
$case env::POSIX:
return protect(ptr, len, READWRITE) @inline;
$case env::WIN32:
void* result = win32::virtualAlloc(ptr, len, MEM_COMMIT, access.to_win32());
if (result) return;
switch (win32::getLastError())
{
case win32::ERROR_INVALID_ADDRESS: return UNALIGNED_ADDRESS?;
case win32::ERROR_ACCESS_DENIED: return ACCESS_DENIED?;
case win32::ERROR_COMMITMENT_LIMIT:
case win32::ERROR_NOT_ENOUGH_MEMORY: return mem::OUT_OF_MEMORY?;
case win32::ERROR_INVALID_PARAMETER: return INVALID_ARGS?;
default: return UNKNOWN_ERROR?;
}
$default:
unsupported("'virtual_commit' is only available on Win32 and Posix.");
$endswitch
}
<*
Notifies that the memory in the region can be released back to the OS. On Win32 this decommits the region,
whereas on Posix it tells the system that it may be reused using madvise. The "block" parameter is only
respected on Posix, and protects the region from read/write/exec. On Win32 this always happens.
@param [&inout] ptr : "Pointer to page to update, must be page aligned"
@param len : "To what len to commit, must be page aligned"
@param block : "Set the released memory to protected"
@require mem::ptr_is_page_aligned(ptr) : "The pointer should be page aligned"
@require mem::ptr_is_page_aligned(ptr + len) : "The length must be page aligned"
@return? ACCESS_DENIED, UNALIGNED_ADDRESS, RANGE_OVERFLOW, UPDATE_FAILED, UNMAPPED_ACCESS, INVALID_ARGS
*>
fn void? decommit(void* ptr, usz len, bool block = true)
{
$switch:
$case env::POSIX:
if (posix::madvise(ptr, len, posix::MADV_DONTNEED))
{
switch (libc::errno())
{
case errno::EINVAL: return UNALIGNED_ADDRESS?;
case errno::ENOMEM: return UNMAPPED_ACCESS?;
default: return UPDATE_FAILED?;
}
}
if (block) (void)protect(ptr, len, PROTECTED) @inline;
$case env::WIN32:
if (!win32::virtualFree(ptr, len, MEM_DECOMMIT))
{
switch (win32::getLastError())
{
case win32::ERROR_INVALID_ADDRESS: return UNALIGNED_ADDRESS?;
case win32::ERROR_INVALID_PARAMETER: return INVALID_ARGS?;
case win32::ERROR_ACCESS_DENIED: return ACCESS_DENIED?;
default: return UPDATE_FAILED?;
}
}
$default:
unsupported("'virtual_decommit' is only available on Win32 and Posix.");
$endswitch
}
<*
Create a VirtualMemory using
@param size : "The size of the memory to allocate."
@require size > 0 : "The size must be non-zero"
@return? mem::OUT_OF_MEMORY, RANGE_OVERFLOW, UNKNOWN_ERROR, ACCESS_DENIED, INVALID_ARGS
*>
fn VirtualMemory? virtual_alloc(usz size, VirtualMemoryAccess access = PROTECTED)
{
size = aligned_alloc_size(size);
void* ptr = alloc(size, access)!;
return { ptr, size, access };
}
<*
Commits memory, using vm::commit
@param offset : "Starting from what offset to commit"
@param len : "To what len to commit"
@require mem::ptr_is_page_aligned(self.ptr + offset) : "The offset should be page aligned"
@require mem::ptr_is_page_aligned(self.ptr + offset + len) : "The length must be page aligned"
@require offset < self.size : "Offset out of range"
@require offset + len <= self.size : "Length out of range"
@return? UPDATE_FAILED, ACCESS_DENIED, UNALIGNED_ADDRESS, RANGE_OVERFLOW, UNKNOWN_ERROR
*>
macro void? VirtualMemory.commit(self, usz offset, usz len)
{
return commit(self.ptr + offset, len, self.default_access);
}
<*
Changes protection of a part of memory using vm::protect
@param offset : "Starting from what offset to update"
@param len : "To what len to update"
@require mem::ptr_is_page_aligned(self.ptr + offset) : "The offset should be page aligned"
@require mem::ptr_is_page_aligned(self.ptr + offset + len) : "The length must be page aligned"
@require offset < self.size : "Offset out of range"
@require offset + len < self.size : "Length out of range"
@return? UPDATE_FAILED, ACCESS_DENIED, UNALIGNED_ADDRESS, RANGE_OVERFLOW, UNKNOWN_ERROR
*>
macro void? VirtualMemory.protect(self, usz offset, usz len, VirtualMemoryAccess access)
{
return protect(self.ptr + offset, len, access);
}
<*
Decommits a part of memory using vm::decommit
@param offset : "Starting from what offset to decommit"
@param len : "To what len to decommit"
@param block : "Should the memory be blocked from access after decommit"
@require mem::ptr_is_page_aligned(self.ptr + offset) : "The offset should be page aligned"
@require mem::ptr_is_page_aligned(self.ptr + offset + len) : "The length must be page aligned"
@require offset < self.size : "Offset out of range"
@require offset + len < self.size : "Length out of range"
@return? UPDATE_FAILED
*>
fn void? VirtualMemory.decommit(self, usz offset, usz len, bool block = true)
{
return decommit(self.ptr + offset, len, block);
}
<*
Releases the memory region
@require self.ptr != null : "Virtual memory must be initialized to call destroy"
*>
fn void? VirtualMemory.destroy(&self)
{
return release(self.ptr, self.size);
}
fn CInt VirtualMemoryAccess.to_posix(self) @if(env::POSIX) @private
{
switch (self)
{
case PROTECTED: return posix::PROT_NONE;
case READ: return posix::PROT_READ;
case WRITE: return posix::PROT_WRITE;
case EXEC: return posix::PROT_EXEC;
case READWRITE: return posix::PROT_READ | posix::PROT_WRITE;
case EXECREAD: return posix::PROT_READ | posix::PROT_EXEC;
case EXECWRITE: return posix::PROT_WRITE | posix::PROT_EXEC;
case ANY: return posix::PROT_WRITE | posix::PROT_READ | posix::PROT_EXEC;
}
}
fn Win32_Protect VirtualMemoryAccess.to_win32(self) @if(env::WIN32) @private
{
switch (self)
{
case PROTECTED: return PAGE_NOACCESS;
case READ: return PAGE_READONLY;
case WRITE: return PAGE_READWRITE;
case EXEC: return PAGE_EXECUTE;
case READWRITE: return PAGE_READWRITE;
case EXECWRITE: return PAGE_EXECUTE_READWRITE;
case EXECREAD: return PAGE_EXECUTE_READ;
case ANY: return PAGE_EXECUTE_READWRITE;
}
}

View File

@@ -1,188 +0,0 @@
module std::core::mem;
import std::core::env;
struct VirtualMemory
{
void* ptr;
usz size;
}
faultdef VMEM_OVERFLOW, VMEM_UNKNOWN_ERROR, VMEM_ACCESS_NOT_ALLOWED, VMEM_PAGE_NOT_ALIGNED, VMEM_UPDATE_FAILED;
module std::core::mem @if(env::POSIX);
import std::os::posix, libc::errno, std::math;
enum VirtualMemoryAccess : int(int posix_val)
{
PROTECTED = posix::PROT_NONE,
READABLE = posix::PROT_READ,
WRITABLE = posix::PROT_WRITE,
READWRITE = posix::PROT_READ | posix::PROT_WRITE
}
<*
@param size : "The size of the memory to allocate."
@param access : "The initial access."
@require size > 0 : "The size must be non-zero"
@return? OUT_OF_MEMORY, VMEM_OVERFLOW, VMEM_UNKNOWN_ERROR
*>
fn VirtualMemory? virtual_alloc(usz size, VirtualMemoryAccess access)
{
void *ptr = posix::mmap(null, size, access.posix_val, posix::MAP_PRIVATE | posix::MAP_ANONYMOUS, -1, 0);
if (ptr != posix::MAP_FAILED && ptr) return { ptr, size };
switch (libc::errno())
{
case errno::ENOMEM: return OUT_OF_MEMORY?;
case errno::EOVERFLOW: return VMEM_OVERFLOW?;
default: return VMEM_UNKNOWN_ERROR?;
}
}
<*
@param offset : "Starting from what offset to commit"
@param len : "To what len to commit"
@require offset < self.size : "Offset out of range"
@require offset + len < self.size : "Length out of range"
@require offset == 0 || math::is_power_of_2(offset) : "Offset should be a power of 2"
@require math::is_power_of_2(len) : "Length should be a multiple of the page size"
@return? VMEM_UPDATE_FAILED, VMEM_ACCESS_NOT_ALLOWED, VMEM_PAGE_NOT_ALIGNED, VMEM_OVERFLOW, VMEM_UNKNOWN_ERROR
*>
macro void? VirtualMemory.commit(self, usz offset, usz len)
{
return self.protect(offset, len, READWRITE);
}
<*
@param offset : "Starting from what offset to update"
@param len : "To what len to update"
@param access : "The new access"
@require offset < self.size : "Offset out of range"
@require offset + len < self.size : "Length out of range"
@require offset == 0 || math::is_power_of_2(offset) : "Offset should be a power of 2"
@require math::is_power_of_2(len) : "Length should be a multiple of the page size"
@return? VMEM_ACCESS_NOT_ALLOWED, VMEM_PAGE_NOT_ALIGNED, VMEM_OVERFLOW, VMEM_UNKNOWN_ERROR
*>
fn void? VirtualMemory.protect(self, usz offset, usz len, VirtualMemoryAccess access)
{
if (posix::mprotect(self.ptr + offset, len, access.posix_val))
{
switch (libc::errno())
{
case errno::EACCES: return VMEM_ACCESS_NOT_ALLOWED?;
case errno::EINVAL: return VMEM_PAGE_NOT_ALIGNED?;
case errno::EOVERFLOW: return VMEM_OVERFLOW?;
case errno::ENOMEM: abort("Vmem access out of range");
default: return VMEM_UNKNOWN_ERROR?;
}
}
}
<*
@param offset : "Starting from what offset to release"
@param len : "To what len to release"
@require offset < self.size : "Offset out of range"
@require offset + len < self.size : "Length out of range"
@require offset == 0 || math::is_power_of_2(offset) : "Offset should be a power of 2"
@require math::is_power_of_2(len) : "Length should be a multiple of the page size"
@return? VMEM_UPDATE_FAILED
*>
fn void? VirtualMemory.decommit(self, usz offset, usz len)
{
if (posix::madvise(self.ptr + offset, len, posix::MADV_DONTNEED)) return VMEM_UPDATE_FAILED?;
}
<*
@require self.ptr != null : "Virtual memory must be initialized to call destroy"
*>
fn void VirtualMemory.destroy(&self)
{
posix::munmap(self.ptr, self.size);
self.ptr = null;
}
module std::core::mem @if(env::WIN32);
import std::os::win32, std::math;
enum VirtualMemoryAccess : int(Win32_Protect win32_val)
{
PROTECTED = PAGE_NOACCESS,
READABLE = PAGE_READONLY,
WRITABLE = PAGE_READWRITE,
READWRITE = PAGE_READWRITE
}
<*
@param size : "The size of the memory to allocate."
@param access : "The initial access."
@require size > 0 : "The size must be non-zero"
@return? OUT_OF_MEMORY, VMEM_UNKNOWN_ERROR
*>
fn VirtualMemory? virtual_alloc(usz size, VirtualMemoryAccess access)
{
void *ptr = win32::virtualAlloc(null, size, MEM_RESERVE, access.win32_val);
if (ptr) return { ptr, size };
switch (win32::getLastError())
{
case win32::ERROR_NOT_ENOUGH_MEMORY:
case win32::ERROR_COMMITMENT_LIMIT: return OUT_OF_MEMORY?;
default: return VMEM_UNKNOWN_ERROR?;
}
}
<*
@param offset : "Starting from what offset to commit"
@param len : "To what len to commit"
@require offset < self.size : "Offset out of range"
@require offset + len < self.size : "Length out of range"
@require offset == 0 || math::is_power_of_2(offset) : "Offset should be a power of 2"
@require math::is_power_of_2(len) : "Length should be a multiple of the page size"
@return? VMEM_UPDATE_FAILED
*>
macro void? VirtualMemory.commit(self, usz offset, usz len)
{
void *res = win32::virtualAlloc(self.ptr + offset, len, MEM_COMMIT, PAGE_READWRITE);
if (!res) return VMEM_UPDATE_FAILED?;
}
<*
@param offset : "Starting from what offset to commit"
@param len : "To what len to commit"
@require offset < self.size : "Offset out of range"
@require offset + len < self.size : "Length out of range"
@require offset == 0 || math::is_power_of_2(offset) : "Offset should be a power of 2"
@require math::is_power_of_2(len) : "Length should be a multiple of the page size"
@return? VMEM_UPDATE_FAILED
*>
macro void? VirtualMemory.protect(self, usz offset, usz len, VirtualMemoryAccess access)
{
Win32_Protect old;
if (!win32::virtualProtect(self.ptr + offset, len, access.win32_val, &old))
{
return VMEM_UPDATE_FAILED?;
}
}
<*
@param offset : "Starting from what offset to decommit"
@param len : "To what len to decommit"
@require offset < self.size : "Offset out of range"
@require offset + len < self.size : "Length out of range"
@require offset == 0 || math::is_power_of_2(offset) : "Offset should be a power of 2"
@require math::is_power_of_2(len) : "Length should be a multiple of the page size"
@return? VMEM_UPDATE_FAILED
*>
fn void? VirtualMemory.decommit(self, usz offset, usz len)
{
if (!win32::virtualFree(self.ptr + offset, len, MEM_DECOMMIT)) return VMEM_UPDATE_FAILED?;
}
<*
@require self.ptr != null : "Virtual memory must be initialized to call destroy"
*>
fn void VirtualMemory.destroy(&self)
{
win32::virtualFree(self.ptr, 0, MEM_RELEASE);
self.ptr = null;
}

View File

@@ -221,6 +221,7 @@ const Win32_DWORD ERROR_DEVICE_FEATURE_NOT_SUPPORTED = 0x13C;
const Win32_DWORD ERROR_MR_MID_NOT_FOUND = 0x13D;
const Win32_DWORD ERROR_SCOPE_NOT_FOUND = 0x13E;
const Win32_DWORD ERROR_UNDEFINED_SCOPE = 0x13F;
const Win32_DWORD ERROR_INVALID_ADDRESS = 0x1E7;
const Win32_DWORD ERROR_IO_INCOMPLETE = 0x3E4;
const Win32_DWORD ERROR_IO_PENDING = 0x3E5;
const Win32_DWORD ERROR_TIMEOUT = 0x5B4;

View File

@@ -39,4 +39,14 @@ enum Win32_FreeType : const Win32_DWORD
extern fn Win32_LPVOID virtualAlloc(Win32_LPVOID lpAddres, Win32_SIZE_T dwSize, Win32_AllocationType flAllocationType, Win32_Protect flProtect) @extern("VirtualAlloc");
extern fn Win32_PVOID virtualAlloc2(Win32_HANDLE process, Win32_PVOID baseAddress, Win32_SIZE_T size, Win32_AllocationType allocationType, Win32_ULONG pageProtection, Win32_MEM_EXTENDED_PARAMETER* extendedParameters, Win32_ULONG parameterCount) @extern("VirtualAlloc2");
extern fn Win32_BOOL virtualFree(Win32_LPVOID lpAddress, Win32_SIZE_T dwSize, Win32_FreeType dwFreeType) @extern("VirtualFree");
extern fn Win32_BOOL virtualProtect(Win32_LPVOID lpAddress, Win32_SIZE_T dwSize, Win32_Protect flNewProtect, Win32_Protect* lpflOldProtect) @extern("VirtualProtect");
extern fn Win32_BOOL virtualProtect(Win32_LPVOID lpAddress, Win32_SIZE_T dwSize, Win32_Protect flNewProtect, Win32_Protect* lpflOldProtect) @extern("VirtualProtect");
fn usz allocation_granularity()
{
static usz granularity;
if (granularity) return granularity;
Win32_SYSTEM_INFO info;
win32::getSystemInfo(&info);
return granularity = (usz)info.dwAllocationGranularity;
}