From 070294b36ca0922aaffdc0b7c669b189f5f147c1 Mon Sep 17 00:00:00 2001 From: Ryan Liptak Date: Fri, 25 Sep 2026 17:47:35 -0700 Subject: [PATCH] Threaded.createFileMap: fix handling of `populate` on Windows This both fixes populate=false, since NtCreateSection needs COMMIT=true when called with a file handle (the higher level API sets this flag for the user), and also signals the intention to prefetch when populate=true, which wasn't happening before. However, PrefetchVirtualMemory (and the ntdll equivalent NtSetInformationVirtualMemory with MEMORY_PREFETCH_INFORMATION) doesn't appear to actually do anything to decrease the number of page/hard faults. We still do the call though since it accurately signals intent, and future version of Windows may be able to do something with that. --- lib/std/Io/File/MemoryMap.zig | 2 +- lib/std/Io/Threaded.zig | 36 ++++++++++++++++++++++++++++++++++- lib/std/os/windows.zig | 33 ++++++++++++++++++++++++++++++++ lib/std/os/windows/ntdll.zig | 11 +++++++++++ 4 files changed, 80 insertions(+), 2 deletions(-) diff --git a/lib/std/Io/File/MemoryMap.zig b/lib/std/Io/File/MemoryMap.zig index 2c1265d05a3a205bf28f803deae6107ed9ccce05..9b81f13b916fff865ddecdf5f6130bf584c384aa 100644 --- a/lib/std/Io/File/MemoryMap.zig +++ b/lib/std/Io/File/MemoryMap.zig @@ -54,7 +54,7 @@ pub const CreateOptions = struct { undefined_contents: bool = false, /// Prefault the pages. If this option is unsupported, it is silently /// ignored. Aside from custom Io implementations, this option is only - /// supported on Linux. + /// supported on Linux and Windows. populate: bool = true, /// Asserted to be a multiple of page size which can be obtained via /// `std.heap.pageSize`. diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index ab888c5e6ee58558a7aa65d1cac8bb263b8a4d2a..1095778cefc253851b223fe707a400c5f6832982 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -19087,7 +19087,13 @@ fn createFileMap( null, §ion_size, page, - .{ .COMMIT = populate }, + .{ + // Required when calling NtCreateSection with a file handle, otherwise + // this call will fail with INVALID_PARAMETER. The higher level + // `CreateFileMapping` function will set this bit for you when it + // calls NtCreateSection. + .COMMIT = true, + }, file.handle, )) { .SUCCESS => {}, @@ -19123,6 +19129,34 @@ fn createFileMap( const alignment: Alignment = .fromByteUnits(page_size); assert(contents_len == alignment.forward(len)); } + if (populate) { + const addresses = [_]windows.MEMORY_RANGE_ENTRY{ + .{ .VirtualAddress = contents_ptr.?, .NumberOfBytes = len }, + }; + var info: windows.VIRTUAL_MEMORY.MEMORY_PREFETCH_INFORMATION = .{ + .Flags = .{ + // This flag requires Windows 11 >= 24H4. On earlier versions, this being set + // will cause NtSetInformationVirtualMemory to fail with INVALID_PARAMETER_5. + .TO_WORKING_SET = false, + }, + }; + // It appears that this is effectively a no-op, as it doesn't decrease page/hard faults. + // https://github.com/microsoft/Windows-Dev-Performance/issues/108 + // + // We still call it, though, to signal the intention of doing something useful. + // It doesn't appear to make anything *worse*, so there's no harm in doing so. + switch (windows.ntdll.NtSetInformationVirtualMemory( + windows.current_process, + .Prefetch, + 1, + &addresses, + &info, + @sizeOf(windows.VIRTUAL_MEMORY.MEMORY_PREFETCH_INFORMATION), + )) { + .SUCCESS => {}, + else => |status| return windows.unexpectedStatus(status), + } + } return .{ .file = file, .offset = offset, diff --git a/lib/std/os/windows.zig b/lib/std/os/windows.zig index 5476c17147677692bbe2422a99f21456f11c88a1..bdac980816ff8b87a9be355b7f1c69971f12185e 100644 --- a/lib/std/os/windows.zig +++ b/lib/std/os/windows.zig @@ -3517,6 +3517,39 @@ pub const SEC = packed struct(ULONG) { pub const ERESOURCE = opaque {}; +pub const VIRTUAL_MEMORY = struct { + pub const INFORMATION_CLASS = enum(c_int) { + Prefetch = 0, + PagePriority = 1, + CfgCallTarget = 2, + PageDirtyState = 3, + ImageHotPatch = 4, + PhysicalContiguity = 5, + VirtualMachinePrepopulate = 6, + RemoveFromWorkingSet = 7, + _, + + pub const Max: @typeInfo(@This()).@"enum".tag_type = @typeInfo(@This()).@"enum".field_names.len; + }; + + pub const MEMORY_PREFETCH_INFORMATION = extern struct { + Flags: VM_PREFETCH, + + pub const VM_PREFETCH = packed struct(ULONG) { + /// Introduced in Windows 11 24H4. + /// Attempt to populate specified single or multiple address ranges + /// into the process working set (bring pages into physical memory). + TO_WORKING_SET: bool, + Reserved1: u31 = 0, + }; + }; +}; + +pub const MEMORY_RANGE_ENTRY = extern struct { + VirtualAddress: PVOID, + NumberOfBytes: SIZE_T, +}; + // ref: shared/ntdef.h pub const EVENT_TYPE = enum(c_int) { diff --git a/lib/std/os/windows/ntdll.zig b/lib/std/os/windows/ntdll.zig index 16b3f57f21a7f55f2ddb3188d180673022e7bee7..cef623cd39b18db3f1dbd7d3d670da5021141e0e 100644 --- a/lib/std/os/windows/ntdll.zig +++ b/lib/std/os/windows/ntdll.zig @@ -58,6 +58,8 @@ const WORD = windows.WORD; const USER_THREAD_START_ROUTINE = windows.USER_THREAD_START_ROUTINE; const PS = windows.PS; const TEB = windows.TEB; +const VIRTUAL_MEMORY = windows.VIRTUAL_MEMORY; +const MEMORY_RANGE_ENTRY = windows.MEMORY_RANGE_ENTRY; // ref: km/ntifs.h @@ -631,6 +633,15 @@ pub extern "ntdll" fn NtProtectVirtualMemory( OldAccessProtection: *PAGE, ) callconv(.winapi) NTSTATUS; +pub extern "ntdll" fn NtSetInformationVirtualMemory( + ProcessHandle: HANDLE, + VmInformationClass: VIRTUAL_MEMORY.INFORMATION_CLASS, + NumberOfExtries: SIZE_T, + VirtualAddresses: [*]const MEMORY_RANGE_ENTRY, + VmInformation: *anyopaque, + VmInformationLength: DWORD, +) callconv(.winapi) NTSTATUS; + pub extern "ntdll" fn NtWaitForAlertByThreadId( Address: ?*const anyopaque, Timeout: ?*const LARGE_INTEGER, -- 2.54.0