mirror of
https://github.com/c3lang/c3c.git
synced 2026-02-27 03:51:18 +00:00
- 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:
10
.github/workflows/main.yml
vendored
10
.github/workflows/main.yml
vendored
@@ -90,7 +90,7 @@ jobs:
|
||||
- name: Compile run unit tests
|
||||
run: |
|
||||
cd test
|
||||
..\build\${{ matrix.build_type }}\c3c.exe compile-test unit -O1
|
||||
..\build\${{ matrix.build_type }}\c3c.exe compile-test unit -O1 -D SLOW_TESTS
|
||||
|
||||
- name: run compiler tests
|
||||
run: |
|
||||
@@ -347,7 +347,7 @@ jobs:
|
||||
- name: Compile run unit tests
|
||||
run: |
|
||||
cd test
|
||||
../build/c3c compile-test unit
|
||||
../build/c3c compile-test unit -D SLOW_TESTS
|
||||
|
||||
- name: Build testproject
|
||||
run: |
|
||||
@@ -493,7 +493,7 @@ jobs:
|
||||
- name: Compile run unit tests
|
||||
run: |
|
||||
cd test
|
||||
../build/c3c compile-test unit --sanitize=address
|
||||
../build/c3c compile-test unit --sanitize=address -D SLOW_TESTS
|
||||
|
||||
- name: Build testproject
|
||||
run: |
|
||||
@@ -587,7 +587,7 @@ jobs:
|
||||
- name: Compile run unit tests
|
||||
run: |
|
||||
cd test
|
||||
../build/c3c compile-test unit
|
||||
../build/c3c compile-test unit -D SLOW_TESTS
|
||||
|
||||
- name: Build testproject
|
||||
run: |
|
||||
@@ -669,7 +669,7 @@ jobs:
|
||||
- name: Compile run unit tests
|
||||
run: |
|
||||
cd test
|
||||
../build/c3c compile-test unit -O1
|
||||
../build/c3c compile-test unit -O1 -D SLOW_TESTS
|
||||
|
||||
- name: Test WASM
|
||||
run: |
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
323
lib/std/core/os/mem_vm.c3
Normal 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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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;
|
||||
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -49,13 +49,14 @@
|
||||
- `$foo[0] = ...` was incorrectly requiring that the assigned values were compile time constants.
|
||||
- "Inlined at" would sometimes show the current location.
|
||||
- Fixed bug splatting constants into constants.
|
||||
- New Virtual Memory arena allocator
|
||||
- Resize bug when resizing memory down in ArenaAllocator, DynamicArenaAllocator, BackedArenaAllocator.
|
||||
|
||||
### Stdlib changes
|
||||
- Improve contract for readline. #2280
|
||||
- Added Whirlpool hash.
|
||||
- Added string::bformat.
|
||||
- VirtualMemory type and functions.
|
||||
- Virtual memory library.
|
||||
- New virtual emory arena allocator.
|
||||
|
||||
## 0.7.3 Change list
|
||||
|
||||
|
||||
@@ -22,7 +22,8 @@ fn void test_whirlpool_test_vector()
|
||||
assert(w.final() == x'B97DE512E91E3828B40D2B0FDCE9CEB3C4A71F9BEA8D88E75C4FA854DF36725FD2B52EB6544EDCACD6F8BEDDFEA403CB55AE31F03AD62A5EF54E42EE82C3FB35');
|
||||
}
|
||||
|
||||
fn void test_whirlpool_gigahash_zeroes()
|
||||
|
||||
fn void test_whirlpool_gigahash_zeroes() @if($feature(SLOW_TESTS))
|
||||
{
|
||||
char[] c = calloc(257 * (1024*1024))[:(257*1024*1024)]; // 257 MiB
|
||||
defer free(c);
|
||||
@@ -79,7 +80,7 @@ fn void test_whirlpool_pbkdf2_3()
|
||||
}
|
||||
|
||||
|
||||
fn void test_whirlpool_pbkdf2_4()
|
||||
fn void test_whirlpool_pbkdf2_4() @if($feature(SLOW_TESTS))
|
||||
{
|
||||
char[] pw = "i have a secret that i truly truly truly don't want you to be able to find out - it's a really long key and it's used to keep all my cat pictures safe and undisturbed. thank you very much for understanding, and have a great evening";
|
||||
char[] salt = "cumin_turmeric_curry";
|
||||
|
||||
Reference in New Issue
Block a user