diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index e04d64730..8301d6b17 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -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: | diff --git a/lib/std/core/allocators/arena_allocator.c3 b/lib/std/core/allocators/arena_allocator.c3 index 72ae0367d..fc8fa0491 100644 --- a/lib/std/core/allocators/arena_allocator.c3 +++ b/lib/std/core/allocators/arena_allocator.c3 @@ -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; } diff --git a/lib/std/core/allocators/backed_arena_allocator.c3 b/lib/std/core/allocators/backed_arena_allocator.c3 index e0af33a3e..6419582a3 100644 --- a/lib/std/core/allocators/backed_arena_allocator.c3 +++ b/lib/std/core/allocators/backed_arena_allocator.c3 @@ -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; } diff --git a/lib/std/core/allocators/dynamic_arena.c3 b/lib/std/core/allocators/dynamic_arena.c3 index c238dbffc..bb29a9631 100644 --- a/lib/std/core/allocators/dynamic_arena.c3 +++ b/lib/std/core/allocators/dynamic_arena.c3 @@ -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; } diff --git a/lib/std/core/allocators/on_stack_allocator.c3 b/lib/std/core/allocators/on_stack_allocator.c3 index e6e9f55a8..af9d69459 100644 --- a/lib/std/core/allocators/on_stack_allocator.c3 +++ b/lib/std/core/allocators/on_stack_allocator.c3 @@ -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; } diff --git a/lib/std/core/allocators/vmem.c3 b/lib/std/core/allocators/vmem.c3 index f0e507be8..02d3e3359 100644 --- a/lib/std/core/allocators/vmem.c3 +++ b/lib/std/core/allocators/vmem.c3 @@ -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; } diff --git a/lib/std/core/mem.c3 b/lib/std/core/mem.c3 index 40025f456..826aa631e 100644 --- a/lib/std/core/mem.c3 +++ b/lib/std/core/mem.c3 @@ -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); diff --git a/lib/std/core/os/mem_vm.c3 b/lib/std/core/os/mem_vm.c3 new file mode 100644 index 000000000..a74f963c9 --- /dev/null +++ b/lib/std/core/os/mem_vm.c3 @@ -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; + } +} + diff --git a/lib/std/core/os/virtual_mem.c3 b/lib/std/core/os/virtual_mem.c3 deleted file mode 100644 index c09f4f8d4..000000000 --- a/lib/std/core/os/virtual_mem.c3 +++ /dev/null @@ -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; -} - diff --git a/lib/std/os/win32/general.c3 b/lib/std/os/win32/general.c3 index da6db3f64..8457265c4 100644 --- a/lib/std/os/win32/general.c3 +++ b/lib/std/os/win32/general.c3 @@ -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; diff --git a/lib/std/os/win32/memoryapi.c3 b/lib/std/os/win32/memoryapi.c3 index a3e2e47ed..8e5f9db04 100644 --- a/lib/std/os/win32/memoryapi.c3 +++ b/lib/std/os/win32/memoryapi.c3 @@ -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"); \ No newline at end of file +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; +} diff --git a/releasenotes.md b/releasenotes.md index 78564f12f..bdffe6261 100644 --- a/releasenotes.md +++ b/releasenotes.md @@ -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 diff --git a/test/unit/stdlib/hash/whirlpool.c3 b/test/unit/stdlib/hash/whirlpool.c3 index 002b49f20..75cb60afe 100644 --- a/test/unit/stdlib/hash/whirlpool.c3 +++ b/test/unit/stdlib/hash/whirlpool.c3 @@ -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";