Skip to content

Remove VirtualAlloc & VirtualFree from standard library + update std.heap.PageAllocator #23154

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 3 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions lib/std/heap.zig
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ pub var next_mmap_addr_hint: ?[*]align(page_size_min) u8 = null;

/// comptime-known minimum page size of the target.
///
/// All pointers from `mmap` or `VirtualAlloc` are aligned to at least
/// All pointers from `mmap` or `NtAllocateVirtualMemory` are aligned to at least
/// `page_size_min`, but their actual alignment may be bigger.
///
/// This value can be overridden via `std.options.page_size_min`.
Expand Down Expand Up @@ -671,7 +671,7 @@ pub fn testAllocatorAlignedShrink(base_allocator: mem.Allocator) !void {
defer allocator.free(slice);

var stuff_to_free = std.ArrayList([]align(16) u8).init(debug_allocator);
// On Windows, VirtualAlloc returns addresses aligned to a 64K boundary,
// On Windows, NtAllocateVirtualMemory returns addresses aligned to a 64K boundary,
// which is 16 pages, hence the 32. This test may require to increase
// the size of the allocations feeding the `allocator` parameter if they
// fail, because of this high over-alignment we want to have.
Expand Down
26 changes: 14 additions & 12 deletions lib/std/heap/PageAllocator.zig
Original file line number Diff line number Diff line change
Expand Up @@ -27,27 +27,29 @@ pub fn map(n: usize, alignment: mem.Alignment) ?[*]u8 {
const alignment_bytes = alignment.toByteUnits();

if (native_os == .windows) {
var base_addr: ?*anyopaque = null;
var size: windows.SIZE_T = n;
if (alignment_bytes < page_size_min) {
var base_addr: ?*anyopaque = null;
var size: windows.SIZE_T = n;

var status = ntdll.NtAllocateVirtualMemory(windows.GetCurrentProcess(), @ptrCast(&base_addr), 0, &size, windows.MEM_COMMIT | windows.MEM_RESERVE, windows.PAGE_READWRITE);
const status = ntdll.NtAllocateVirtualMemory(windows.GetCurrentProcess(), @ptrCast(&base_addr), 0, &size, windows.MEM_COMMIT | windows.MEM_RESERVE, windows.PAGE_READWRITE);

if (status == SUCCESS and mem.isAligned(@intFromPtr(base_addr), alignment_bytes)) {
return @ptrCast(base_addr);
}
if (status == SUCCESS and mem.isAligned(@intFromPtr(base_addr), alignment_bytes)) {
return @ptrCast(base_addr);
}

if (status == SUCCESS) {
var region_size: windows.SIZE_T = 0;
_ = ntdll.NtFreeVirtualMemory(windows.GetCurrentProcess(), @ptrCast(&base_addr), &region_size, windows.MEM_RELEASE);
if (status == SUCCESS) {
var region_size: windows.SIZE_T = 0;
_ = ntdll.NtFreeVirtualMemory(windows.GetCurrentProcess(), @ptrCast(&base_addr), &region_size, windows.MEM_RELEASE);
}
}

const overalloc_len = n + alignment_bytes - page_size;
const aligned_len = mem.alignForward(usize, n, page_size);

base_addr = null;
size = overalloc_len;
var base_addr: ?*anyopaque = null;
var size: windows.SIZE_T = overalloc_len;

status = ntdll.NtAllocateVirtualMemory(windows.GetCurrentProcess(), @ptrCast(&base_addr), 0, &size, windows.MEM_RESERVE | MEM_RESERVE_PLACEHOLDER, windows.PAGE_NOACCESS);
var status = ntdll.NtAllocateVirtualMemory(windows.GetCurrentProcess(), @ptrCast(&base_addr), 0, &size, windows.MEM_RESERVE | MEM_RESERVE_PLACEHOLDER, windows.PAGE_NOACCESS);

if (status != SUCCESS) return null;

Expand Down
14 changes: 0 additions & 14 deletions lib/std/os/windows.zig
Original file line number Diff line number Diff line change
Expand Up @@ -1790,20 +1790,6 @@ pub fn NtFreeVirtualMemory(hProcess: HANDLE, addr: ?*PVOID, size: *SIZE_T, free_
};
}

pub const VirtualAllocError = error{Unexpected};

pub fn VirtualAlloc(addr: ?LPVOID, size: usize, alloc_type: DWORD, flProtect: DWORD) VirtualAllocError!LPVOID {
return kernel32.VirtualAlloc(addr, size, alloc_type, flProtect) orelse {
switch (GetLastError()) {
else => |err| return unexpectedError(err),
}
};
}

pub fn VirtualFree(lpAddress: ?LPVOID, dwSize: usize, dwFreeType: DWORD) void {
assert(kernel32.VirtualFree(lpAddress, dwSize, dwFreeType) != 0);
}

pub const VirtualProtectError = error{
InvalidAddress,
Unexpected,
Expand Down
16 changes: 0 additions & 16 deletions lib/std/os/windows/kernel32.zig
Original file line number Diff line number Diff line change
Expand Up @@ -557,22 +557,6 @@ pub extern "kernel32" fn HeapValidate(
lpMem: ?*const anyopaque,
) callconv(.winapi) BOOL;

// TODO: Wrapper around NtAllocateVirtualMemory.
pub extern "kernel32" fn VirtualAlloc(
lpAddress: ?LPVOID,
dwSize: SIZE_T,
flAllocationType: DWORD,
flProtect: DWORD,
) callconv(.winapi) ?LPVOID;

// TODO: Wrapper around NtFreeVirtualMemory.
// If the return value is .INVALID_PAGE_PROTECTION, calls RtlFlushSecureMemoryCache and try again.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This seems worth it to do, just to make sure we don't lose any functionality here. VirtualFree() is basically (without error handling minutiae) this:

BOOL __stdcall VirtualFree(LPVOID lpAddress, SIZE_T dwSize, DWORD dwFreeType)
{
    PVOID BaseAddress = lpAddress;
    ULONG_PTR RegionSize = dwSize;
    
    NTSTATUS Status = NtFreeVirtualMemory(NtCurrentProcess(), &BaseAddress, &RegionSize, dwFreeType);
    if (Status == STATUS_INVALID_PAGE_PROTECTION) {
        if (!RtlFlushSecureMemoryCache(BaseAddress, RegionSize)) {
            return FALSE;
        }

        Status = NtFreeVirtualMemory(NtCurrentProcess(), &BaseAddress, &RegionSize, dwFreeType);
    }

    return NT_SUCCESS(Status);
}

pub extern "kernel32" fn VirtualFree(
lpAddress: ?LPVOID,
dwSize: SIZE_T,
dwFreeType: DWORD,
) callconv(.winapi) BOOL;

// TODO: Wrapper around NtQueryVirtualMemory.
pub extern "kernel32" fn VirtualQuery(
lpAddress: ?LPVOID,
Expand Down
2 changes: 1 addition & 1 deletion lib/std/posix.zig
Original file line number Diff line number Diff line change
Expand Up @@ -4805,7 +4805,7 @@ pub fn mmap(
/// Note that while POSIX allows unmapping a region in the middle of an existing mapping,
/// Zig's munmap function does not, for two reasons:
/// * It violates the Zig principle that resource deallocation must succeed.
/// * The Windows function, VirtualFree, has this restriction.
/// * The Windows function, NtFreeVirtualMemory, has this restriction.
pub fn munmap(memory: []align(page_size_min) const u8) void {
switch (errno(system.munmap(memory.ptr, memory.len))) {
.SUCCESS => return,
Expand Down
Loading