From 499ba5d55c00b9d32691dc9ff49db49ba6bbded6 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 22 Jan 2026 19:41:13 -0800 Subject: [PATCH] compiler: use Io.MemoryMap Also make setLength return error.OperationUnsupported when it cannot be done atomically. --- lib/std/Io.zig | 2 +- lib/std/Io/File/MemoryMap.zig | 16 +-- lib/std/Io/Threaded.zig | 36 +----- lib/std/Io/Threaded/test.zig | 9 +- lib/std/Io/test.zig | 11 +- src/link.zig | 10 +- src/link/Elf2.zig | 15 ++- src/link/MappedFile.zig | 203 +++++++++------------------------- 8 files changed, 93 insertions(+), 209 deletions(-) diff --git a/lib/std/Io.zig b/lib/std/Io.zig index bf12ee6c6c..80a0e24807 100644 --- a/lib/std/Io.zig +++ b/lib/std/Io.zig @@ -656,7 +656,7 @@ pub const VTable = struct { fileMemoryMapCreate: *const fn (?*anyopaque, File, File.MemoryMap.CreateOptions) File.MemoryMap.CreateError!File.MemoryMap, fileMemoryMapDestroy: *const fn (?*anyopaque, *File.MemoryMap) void, - fileMemoryMapSetLength: *const fn (?*anyopaque, *File.MemoryMap, File.MemoryMap.CreateOptions) File.MemoryMap.SetLengthError!void, + fileMemoryMapSetLength: *const fn (?*anyopaque, *File.MemoryMap, usize) File.MemoryMap.SetLengthError!void, fileMemoryMapRead: *const fn (?*anyopaque, *File.MemoryMap) File.ReadPositionalError!void, fileMemoryMapWrite: *const fn (?*anyopaque, *File.MemoryMap) File.WritePositionalError!void, diff --git a/lib/std/Io/File/MemoryMap.zig b/lib/std/Io/File/MemoryMap.zig index b3196aab3e..2c1265d05a 100644 --- a/lib/std/Io/File/MemoryMap.zig +++ b/lib/std/Io/File/MemoryMap.zig @@ -74,6 +74,9 @@ pub fn destroy(mm: *MemoryMap, io: Io) void { } pub const SetLengthError = error{ + /// Changing the mapping length could not be done atomically. Caller must + /// use `destroy` and `create` to resize the mapping. + OperationUnsupported, /// One of the following: /// * The `File.Kind` is not `file`. /// * The file is not open for reading and read access protections enabled. @@ -91,17 +94,8 @@ pub const SetLengthError = error{ /// of the file after calling this is unspecified until `write` is called. /// /// May change the pointer address of `memory`. -/// -/// `options` is needed because the mapping may need to be destroyed and -/// re-created. All the same options must be provided except for `len` which is -/// the new length. -/// -/// This operation cannot be completed atomically on all operating systems. -/// When this function fails, the `MemoryMap` may be left in an unmapped state, -/// which can be detected by checking if `memory.len` is zero. In such case it -/// is safe to call `destroy` which will have no effect. -pub fn setLength(mm: *MemoryMap, io: Io, options: CreateOptions) SetLengthError!void { - return io.vtable.fileMemoryMapSetLength(io.userdata, mm, options); +pub fn setLength(mm: *MemoryMap, io: Io, new_len: usize) SetLengthError!void { + return io.vtable.fileMemoryMapSetLength(io.userdata, mm, new_len); } /// Synchronizes the contents of `memory` from `file`. diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 42816b5276..1c588436ce 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -16466,27 +16466,21 @@ fn fileMemoryMapDestroy(userdata: ?*anyopaque, mm: *File.MemoryMap) void { fn fileMemoryMapSetLength( userdata: ?*anyopaque, mm: *File.MemoryMap, - options: File.MemoryMap.CreateOptions, + new_len: usize, ) File.MemoryMap.SetLengthError!void { const t: *Threaded = @ptrCast(@alignCast(userdata)); const page_size = std.heap.pageSize(); const alignment: Alignment = .fromByteUnits(page_size); const page_align = std.heap.page_size_min; const old_memory = mm.memory; - const new_len = options.len; if (mm.section) |section| { + _ = section; if (alignment.forward(new_len) == alignment.forward(old_memory.len)) { mm.memory.len = new_len; return; } switch (native_os) { - .windows => { - _ = windows.ntdll.NtUnmapViewOfSection(windows.current_process, old_memory.ptr); - windows.CloseHandle(section); - mm.section = windows.INVALID_HANDLE_VALUE; - mm.memory = &.{}; - }, .wasi => unreachable, .linux => { const flags: posix.MREMAP = .{ .MAYMOVE = true }; @@ -16516,31 +16510,7 @@ fn fileMemoryMapSetLength( mm.memory = new_memory; return; }, - else => { - switch (posix.errno(posix.system.munmap(old_memory.ptr, old_memory.len))) { - .SUCCESS => {}, - else => |e| { - if (builtin.mode == .Debug) std.log.err("failed to unmap {d} bytes at {*}: {t}", .{ - old_memory.len, old_memory.ptr, e, - }); - // munmap must be infallible, or we cannot design reliable software. - return error.Unexpected; - }, - } - mm.memory = &.{}; - }, - } - if (createFileMap(mm.file, options.protection, mm.offset, options.populate, new_len)) |result| { - mm.* = result; - return; - } else |err| switch (err) { - error.OperationUnsupported, - error.Unseekable, - error.SectionOversize, - error.MappingAlreadyExists, - error.FileLockConflict, - => return error.Unexpected, // It worked before on the same open file. - else => |e| return e, + else => return error.OperationUnsupported, } } else { const gpa = t.allocator; diff --git a/lib/std/Io/Threaded/test.zig b/lib/std/Io/Threaded/test.zig index 9ab9c6dcd9..ffda1e7601 100644 --- a/lib/std/Io/Threaded/test.zig +++ b/lib/std/Io/Threaded/test.zig @@ -260,7 +260,14 @@ test "memory mapping fallback" { try testing.expectEqualStrings("this9is9my", mm.memory); - try mm.setLength(io, .{ .len = "this9is9my data123".len }); + const new_len = "this9is9my data123".len; + mm.setLength(io, new_len) catch |err| switch (err) { + error.OperationUnsupported => { + mm.destroy(io); + mm = try file.createMemoryMap(io, .{ .len = new_len }); + }, + else => |e| return e, + }; try mm.read(io); try testing.expectEqualStrings("this9is9my data123", mm.memory); diff --git a/lib/std/Io/test.zig b/lib/std/Io/test.zig index 0842f5d092..b022b10e6e 100644 --- a/lib/std/Io/test.zig +++ b/lib/std/Io/test.zig @@ -643,9 +643,14 @@ test "memory mapping" { try expectEqualStrings("this9is9my", mm.memory); // Cross a page boundary to require an actual remap. - try mm.setLength(io, .{ - .len = std.heap.pageSize() * 2, - }); + const new_len = std.heap.pageSize() * 2; + mm.setLength(io, new_len) catch |err| switch (err) { + error.OperationUnsupported => { + mm.destroy(io); + mm = try file.createMemoryMap(io, .{ .len = new_len }); + }, + else => |e| return e, + }; try mm.read(io); try expectEqualStrings("this9is9my data123\x00\x00", mm.memory[0.."this9is9my data123\x00\x00".len]); diff --git a/src/link.zig b/src/link.zig index 0e89723296..6f19ec0e58 100644 --- a/src/link.zig +++ b/src/link.zig @@ -651,10 +651,10 @@ pub const File = struct { &coff.mf else unreachable; - mf.file = try base.emit.root_dir.handle.openFile(io, base.emit.sub_path, .{ + mf.memory_map.file = try base.emit.root_dir.handle.openFile(io, base.emit.sub_path, .{ .mode = .read_write, }); - base.file = mf.file; + base.file = mf.memory_map.file; try mf.ensureTotalCapacity(@intCast(mf.nodes.items[0].location().resolve(mf)[1])); }, .c, .spirv => dev.checkAny(&.{ .c_linker, .spirv_linker }), @@ -729,9 +729,9 @@ pub const File = struct { else unreachable; mf.unmap(); - assert(mf.file.handle == f.handle); - mf.file.close(io); - mf.file = undefined; + assert(mf.memory_map.file.handle == f.handle); + mf.memory_map.file.close(io); + mf.memory_map.file = undefined; base.file = null; }, .c, .spirv => dev.checkAny(&.{ .c_linker, .spirv_linker }), diff --git a/src/link/Elf2.zig b/src/link/Elf2.zig index e33cf86696..81e6c23af8 100644 --- a/src/link/Elf2.zig +++ b/src/link/Elf2.zig @@ -1691,10 +1691,10 @@ fn computeNodeVAddr(elf: *Elf, ni: MappedFile.Node.Index) u64 { } pub fn identClass(elf: *const Elf) std.elf.CLASS { - return @enumFromInt(elf.mf.contents[std.elf.EI.CLASS]); + return @enumFromInt(elf.mf.memory_map.memory[std.elf.EI.CLASS]); } pub fn identData(elf: *const Elf) std.elf.DATA { - return @enumFromInt(elf.mf.contents[std.elf.EI.DATA]); + return @enumFromInt(elf.mf.memory_map.memory[std.elf.EI.DATA]); } pub fn targetEndian(elf: *const Elf) std.builtin.Endian { @@ -2102,7 +2102,7 @@ fn loadObject( log.debug("loadObject({f}{f})", .{ path.fmtEscapeString(), fmtMemberString(member) }); const ident = try r.peek(std.elf.EI.OSABI); if (!std.mem.eql(u8, ident[0..std.elf.MAGIC.len], std.elf.MAGIC)) return error.BadMagic; - if (!std.mem.eql(u8, ident[std.elf.MAGIC.len..], elf.mf.contents[std.elf.MAGIC.len..ident.len])) + if (!std.mem.eql(u8, ident[std.elf.MAGIC.len..], elf.mf.memory_map.memory[std.elf.MAGIC.len..ident.len])) return diags.failParse(path, "bad ident", .{}); try elf.symtab.ensureUnusedCapacity(gpa, 1); try elf.inputs.ensureUnusedCapacity(gpa, 1); @@ -2341,7 +2341,7 @@ fn loadDso(elf: *Elf, path: std.Build.Cache.Path, fr: *Io.File.Reader) !void { log.debug("loadDso({f})", .{path.fmtEscapeString()}); const ident = try r.peek(std.elf.EI.NIDENT); if (!std.mem.eql(u8, ident[0..std.elf.MAGIC.len], std.elf.MAGIC)) return error.BadMagic; - if (!std.mem.eql(u8, ident[std.elf.MAGIC.len..], elf.mf.contents[std.elf.MAGIC.len..ident.len])) + if (!std.mem.eql(u8, ident[std.elf.MAGIC.len..], elf.mf.memory_map.memory[std.elf.MAGIC.len..ident.len])) return diags.failParse(path, "bad ident", .{}); const target_endian = elf.targetEndian(); switch (elf.identClass()) { @@ -3090,9 +3090,14 @@ pub fn flush( tid: Zcu.PerThread.Id, prog_node: std.Progress.Node, ) !void { + const comp = elf.base.comp; _ = arena; _ = prog_node; while (try elf.idle(tid)) {} + elf.mf.flush() catch |err| switch (err) { + error.Canceled => |e| return e, + else => |e| return comp.link_diags.fail("flush write failed: {t}", .{e}), + }; } pub fn idle(elf: *Elf, tid: Zcu.PerThread.Id) !bool { @@ -3839,7 +3844,7 @@ pub fn printNode( const line_len = 0x10; var line_it = std.mem.window( u8, - elf.mf.contents[@intCast(file_loc.offset)..][0..@intCast(file_loc.size)], + elf.mf.memory_map.memory[@intCast(file_loc.offset)..][0..@intCast(file_loc.size)], line_len, line_len, ); diff --git a/src/link/MappedFile.zig b/src/link/MappedFile.zig index 773959fae6..8ac2e0c6a5 100644 --- a/src/link/MappedFile.zig +++ b/src/link/MappedFile.zig @@ -11,15 +11,13 @@ const linux = std.os.linux; const windows = std.os.windows; io: Io, -file: Io.File, flags: packed struct { block_size: std.mem.Alignment, copy_file_range_unsupported: bool, fallocate_punch_hole_unsupported: bool, fallocate_insert_range_unsupported: bool, }, -section: if (is_windows) windows.HANDLE else void, -contents: []align(std.heap.page_size_min) u8, +memory_map: Io.File.MemoryMap, nodes: std.ArrayList(Node), free_ni: Node.Index, large: std.ArrayList(u64), @@ -29,26 +27,20 @@ writers: std.SinglyLinkedList, pub const growth_factor = 4; -pub const Error = Io.File.MemoryMap.CreateError || Io.File.LengthError || error{ +pub const Error = error{ NotFile, - SystemResources, - IsDir, - Unseekable, - NoSpaceLeft, - - InputOutput, - FileTooBig, - FileBusy, - NonResizable, -}; +} || Io.File.MemoryMap.CreateError || Io.File.MemoryMap.SetLengthError || Io.File.WritePositionalError; pub fn init(file: Io.File, gpa: std.mem.Allocator, io: Io) !MappedFile { var mf: MappedFile = .{ .io = io, - .file = file, .flags = undefined, - .section = if (is_windows) windows.INVALID_HANDLE_VALUE else {}, - .contents = &.{}, + .memory_map = .{ + .file = file, + .memory = &.{}, + .offset = 0, + .section = null, + }, .nodes = .empty, .free_ni = .none, .large = .empty, @@ -58,61 +50,9 @@ pub fn init(file: Io.File, gpa: std.mem.Allocator, io: Io) !MappedFile { }; errdefer mf.deinit(gpa); const size: u64, const block_size = stat: { - if (is_windows) { - var sbi: windows.SYSTEM_BASIC_INFORMATION = undefined; - break :stat .{ - try windows.GetFileSizeEx(file.handle), - switch (windows.ntdll.NtQuerySystemInformation( - .SystemBasicInformation, - &sbi, - @sizeOf(windows.SYSTEM_BASIC_INFORMATION), - null, - )) { - .SUCCESS => @max(sbi.PageSize, sbi.AllocationGranularity), - else => std.heap.page_size_max, - }, - }; - } - if (is_linux) { - const use_c = std.c.versionCheck(if (builtin.abi.isAndroid()) - .{ .major = 30, .minor = 0, .patch = 0 } - else - .{ .major = 2, .minor = 28, .patch = 0 }); - const sys = if (use_c) std.c else std.os.linux; - while (true) { - var statx = std.mem.zeroes(linux.Statx); - const rc = sys.statx( - mf.file.handle, - "", - std.posix.AT.EMPTY_PATH, - .{ .TYPE = true, .SIZE = true, .BLOCKS = true }, - &statx, - ); - switch (sys.errno(rc)) { - .SUCCESS => { - assert(statx.mask.TYPE); - assert(statx.mask.SIZE); - assert(statx.mask.BLOCKS); - if (!std.posix.S.ISREG(statx.mode)) return error.PathAlreadyExists; - break :stat .{ statx.size, @max(std.heap.pageSize(), statx.blksize) }; - }, - .INTR => continue, - .ACCES => return error.AccessDenied, - .BADF => if (std.debug.runtime_safety) unreachable else return error.Unexpected, - .FAULT => if (std.debug.runtime_safety) unreachable else return error.Unexpected, - .INVAL => if (std.debug.runtime_safety) unreachable else return error.Unexpected, - .LOOP => return error.SymLinkLoop, - .NAMETOOLONG => return error.NameTooLong, - .NOENT => return error.FileNotFound, - .NOTDIR => return error.FileNotFound, - .NOMEM => return error.SystemResources, - else => |err| return std.posix.unexpectedErrno(err), - } - } - } - const stat = try std.posix.fstat(mf.file.handle); - if (!std.posix.S.ISREG(stat.mode)) return error.PathAlreadyExists; - break :stat .{ @bitCast(stat.size), @max(std.heap.pageSize(), stat.blksize) }; + const stat = try file.stat(io); + if (stat.kind != .file) return error.PathAlreadyExists; + break :stat .{ stat.size, @max(std.heap.pageSize(), stat.block_size) }; }; mf.flags = .{ .block_size = .fromByteUnits(std.math.ceilPowerOfTwoAssert(usize, block_size)), @@ -348,12 +288,12 @@ pub const Node = extern struct { pub fn slice(ni: Node.Index, mf: *const MappedFile) []u8 { const file_loc = ni.fileLocation(mf, true); - return mf.contents[@intCast(file_loc.offset)..][0..@intCast(file_loc.size)]; + return mf.memory_map.memory[@intCast(file_loc.offset)..][0..@intCast(file_loc.size)]; } pub fn sliceConst(ni: Node.Index, mf: *const MappedFile) []const u8 { const file_loc = ni.fileLocation(mf, false); - return mf.contents[@intCast(file_loc.offset)..][0..@intCast(file_loc.size)]; + return mf.memory_map.memory[@intCast(file_loc.offset)..][0..@intCast(file_loc.size)]; } pub fn resize(ni: Node.Index, mf: *MappedFile, gpa: std.mem.Allocator, size: u64) !void { @@ -661,7 +601,8 @@ fn resizeNode(mf: *MappedFile, gpa: std.mem.Allocator, ni: Node.Index, requested // Resize the entire file if (ni == Node.Index.root) { try mf.ensureCapacityForSetLocation(gpa); - try mf.file.setLength(io, new_size); + try mf.memory_map.write(io); + try mf.memory_map.file.setLength(io, new_size); try mf.ensureTotalCapacity(@intCast(new_size)); ni.setLocationAssumeCapacity(mf, old_offset, new_size); return; @@ -685,6 +626,7 @@ fn resizeNode(mf: *MappedFile, gpa: std.mem.Allocator, ni: Node.Index, requested if (is_linux and !mf.flags.fallocate_insert_range_unsupported and node.flags.alignment.order(mf.flags.block_size).compare(.gte)) insert_range: { + try mf.memory_map.write(io); // Ask the filesystem driver to insert extents into the file without copying any data const last_offset, const last_size = parent.last.location(mf).resolve(mf); const last_end = last_offset + last_size; @@ -696,12 +638,12 @@ fn resizeNode(mf: *MappedFile, gpa: std.mem.Allocator, ni: Node.Index, requested _, const file_size = Node.Index.root.location(mf).resolve(mf); while (true) switch (linux.errno(switch (std.math.order(range_file_offset, file_size)) { .lt => linux.fallocate( - mf.file.handle, + mf.memory_map.file.handle, linux.FALLOC.FL_INSERT_RANGE, @intCast(range_file_offset), @intCast(range_size), ), - .eq => linux.ftruncate(mf.file.handle, @intCast(range_file_offset + range_size)), + .eq => linux.ftruncate(mf.memory_map.file.handle, @intCast(range_file_offset + range_size)), .gt => unreachable, })) { .SUCCESS => { @@ -908,7 +850,7 @@ fn moveRange(mf: *MappedFile, old_file_offset: u64, new_file_offset: u64, size: if (is_linux and !mf.flags.fallocate_punch_hole_unsupported and size >= mf.flags.block_size.toByteUnits() * 2 - 1) while (true) switch (linux.errno(linux.fallocate( - mf.file.handle, + mf.memory_map.file.handle, linux.FALLOC.FL_PUNCH_HOLE | linux.FALLOC.FL_KEEP_SIZE, @intCast(old_file_offset), @intCast(size), @@ -928,14 +870,14 @@ fn moveRange(mf: *MappedFile, old_file_offset: u64, new_file_offset: u64, size: .TXTBSY => return error.FileBusy, else => |e| return std.posix.unexpectedErrno(e), }; - @memset(mf.contents[@intCast(old_file_offset)..][0..@intCast(size)], 0); + @memset(mf.memory_map.memory[@intCast(old_file_offset)..][0..@intCast(size)], 0); } fn copyRange(mf: *MappedFile, old_file_offset: u64, new_file_offset: u64, size: u64) !void { - const copy_size = try mf.copyFileRange(mf.file, old_file_offset, new_file_offset, size); + const copy_size = try mf.copyFileRange(mf.memory_map.file, old_file_offset, new_file_offset, size); if (copy_size < size) @memcpy( - mf.contents[@intCast(new_file_offset + copy_size)..][0..@intCast(size - copy_size)], - mf.contents[@intCast(old_file_offset + copy_size)..][0..@intCast(size - copy_size)], + mf.memory_map.memory[@intCast(new_file_offset + copy_size)..][0..@intCast(size - copy_size)], + mf.memory_map.memory[@intCast(old_file_offset + copy_size)..][0..@intCast(size - copy_size)], ); } @@ -946,6 +888,8 @@ fn copyFileRange( new_file_offset: u64, size: u64, ) !u64 { + const io = mf.io; + try mf.memory_map.write(io); var remaining_size = size; if (is_linux and !mf.flags.copy_file_range_unsupported) { var old_file_offset_mut: i64 = @intCast(old_file_offset); @@ -954,7 +898,7 @@ fn copyFileRange( const copy_len = linux.copy_file_range( old_file.handle, &old_file_offset_mut, - mf.file.handle, + mf.memory_map.file.handle, &new_file_offset_mut, @intCast(remaining_size), 0, @@ -990,82 +934,41 @@ fn ensureCapacityForSetLocation(mf: *MappedFile, gpa: std.mem.Allocator) !void { } pub fn ensureTotalCapacity(mf: *MappedFile, new_capacity: usize) !void { - if (mf.contents.len >= new_capacity) return; + if (mf.memory_map.memory.len >= new_capacity) return; try mf.ensureTotalCapacityPrecise(new_capacity +| new_capacity / growth_factor); } pub fn ensureTotalCapacityPrecise(mf: *MappedFile, new_capacity: usize) !void { - if (mf.contents.len >= new_capacity) return; + if (mf.memory_map.memory.len >= new_capacity) return; + const io = mf.io; const aligned_capacity = mf.flags.block_size.forward(new_capacity); - if (!is_linux) mf.unmap() else if (mf.contents.len > 0) { - mf.contents = try std.posix.mremap( - mf.contents.ptr, - mf.contents.len, - aligned_capacity, - .{ .MAYMOVE = true }, - null, - ); - return; - } - if (is_windows) { - if (mf.section == windows.INVALID_HANDLE_VALUE) switch (windows.ntdll.NtCreateSection( - &mf.section, - .{ - .SPECIFIC = .{ .SECTION = .{ - .QUERY = true, - .MAP_WRITE = true, - .MAP_READ = true, - .EXTEND_SIZE = true, - } }, - .STANDARD = .{ .RIGHTS = .REQUIRED }, - }, - null, - @constCast(&@as(i64, @intCast(aligned_capacity))), - .{ .READWRITE = true }, - .{ .COMMIT = true }, - mf.file.handle, - )) { - .SUCCESS => {}, - else => return error.MemoryMappingNotSupported, - }; - var contents_ptr: ?[*]align(std.heap.page_size_min) u8 = null; - var contents_len = aligned_capacity; - switch (windows.ntdll.NtMapViewOfSection( - mf.section, - windows.GetCurrentProcess(), - @ptrCast(&contents_ptr), - null, - 0, - null, - &contents_len, - .Unmap, - .{}, - .{ .READWRITE = true }, - )) { - .SUCCESS => mf.contents = contents_ptr.?[0..contents_len], - else => return error.MemoryMappingNotSupported, + + if (mf.memory_map.memory.len > 0) { + if (mf.memory_map.setLength(io, aligned_capacity)) |_| { + return; + } else |err| switch (err) { + error.OperationUnsupported => {}, + else => |e| return e, } - } else mf.contents = try std.posix.mmap( - null, - aligned_capacity, - .{ .READ = true, .WRITE = true }, - .{ .TYPE = if (is_linux) .SHARED_VALIDATE else .SHARED }, - mf.file.handle, - 0, - ); + unmap(mf); + } + + const file = mf.memory_map.file; + mf.memory_map = try .create(io, file, .{ .len = aligned_capacity }); } pub fn unmap(mf: *MappedFile) void { - if (mf.contents.len == 0) return; - if (is_windows) - _ = windows.ntdll.NtUnmapViewOfSection(windows.GetCurrentProcess(), mf.contents.ptr) - else - std.posix.munmap(mf.contents); - mf.contents = &.{}; - if (is_windows and mf.section != windows.INVALID_HANDLE_VALUE) { - windows.CloseHandle(mf.section); - mf.section = windows.INVALID_HANDLE_VALUE; - } + if (mf.memory_map.memory.len == 0) return; + const io = mf.io; + const file = mf.memory_map.file; + mf.memory_map.destroy(io); + mf.memory_map.memory = &.{}; + mf.memory_map.file = file; +} + +pub fn flush(mf: *MappedFile) Io.File.WritePositionalError!void { + const io = mf.io; + try mf.memory_map.write(io); } fn verify(mf: *MappedFile) void {