Skip to content

Commit 01efb34

Browse files
committed
std: Add std.fs.MemoryMap.
This new type provides a cross platform memory mapping API which encompasses the common subset of both POSIX and Windows APIs.
1 parent a765418 commit 01efb34

File tree

2 files changed

+190
-0
lines changed

2 files changed

+190
-0
lines changed

lib/std/fs.zig

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ const is_darwin = native_os.isDarwin();
1717
pub const AtomicFile = @import("fs/AtomicFile.zig");
1818
pub const Dir = @import("fs/Dir.zig");
1919
pub const File = @import("fs/File.zig");
20+
pub const MemoryMap = @import("fs/MemoryMap.zig");
2021
pub const path = @import("fs/path.zig");
2122

2223
pub const has_executable_bit = switch (native_os) {
@@ -710,6 +711,7 @@ test {
710711
_ = &AtomicFile;
711712
_ = &Dir;
712713
_ = &File;
714+
_ = &MemoryMap;
713715
_ = &path;
714716
_ = @import("fs/test.zig");
715717
_ = @import("fs/get_app_data_dir.zig");

lib/std/fs/MemoryMap.zig

Lines changed: 188 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,188 @@
1+
const std = @import("../std.zig");
2+
const builtin = @import("builtin");
3+
4+
const MemoryMap = @This();
5+
6+
/// An OS-specific reference to a kernel object for this mapping.
7+
handle: switch (builtin.os.tag) {
8+
.windows => std.os.windows.HANDLE,
9+
else => void,
10+
},
11+
/// The region of virtual memory in which the file is mapped.
12+
///
13+
/// Accesses to this are subject to the protection semantics specified upon
14+
/// initialization of the mapping. Failure to abide by those semantics has undefined
15+
/// behavior (though should be well-defined by the OS).
16+
mapped: []align(std.mem.page_size) volatile u8,
17+
18+
test MemoryMap {
19+
if (builtin.os.tag == .wasi) return error.SkipZigTest;
20+
21+
var tmp = std.testing.tmpDir(.{});
22+
defer tmp.cleanup();
23+
24+
var file = try tmp.dir.createFile("mmap.bin", .{
25+
.exclusive = true,
26+
.truncate = true,
27+
.read = true,
28+
});
29+
defer file.close();
30+
31+
const magic = "\xde\xca\xfb\xad";
32+
try file.writeAll(magic);
33+
34+
const len = try file.getEndPos();
35+
36+
var view = try MemoryMap.init(file, .{ .length = @intCast(len) });
37+
defer view.deinit();
38+
39+
try std.testing.expectEqualSlices(u8, magic, @volatileCast(view.mapped));
40+
}
41+
42+
pub const InitOptions = struct {
43+
protection: ProtectionFlags = .{},
44+
exclusivity: Exclusivity = .private,
45+
/// The desired length of the mapping.
46+
///
47+
/// The backing file must be of at least `offset + length` size.
48+
length: usize,
49+
/// The desired offset of the mapping.
50+
///
51+
/// The backing file must be of at least `offset` size.
52+
offset: usize = 0,
53+
hint: ?[*]align(std.mem.page_size) u8 = null,
54+
};
55+
56+
/// A description of OS protections to be applied to a memory-mapped region.
57+
pub const ProtectionFlags = struct {
58+
write: bool = false,
59+
execute: bool = false,
60+
};
61+
62+
pub const Exclusivity = enum {
63+
/// The file's content may be read or written by external processes.
64+
shared,
65+
/// The file's content is exclusive to this process.
66+
private,
67+
};
68+
69+
/// Create a memory-mapped view into `file`.
70+
///
71+
/// Asserts `opts.length` is non-zero.
72+
pub fn init(file: std.fs.File, opts: InitOptions) !MemoryMap {
73+
std.debug.assert(opts.length > 0);
74+
switch (builtin.os.tag) {
75+
.wasi => @compileError("MemoryMap not supported on WASI OS; see also " ++
76+
"https://github.com/WebAssembly/WASI/issues/304"),
77+
.windows => {
78+
// Create the kernel resource for the memory mapping.
79+
var access: std.os.windows.ACCESS_MASK =
80+
std.os.windows.STANDARD_RIGHTS_REQUIRED |
81+
std.os.windows.SECTION_QUERY |
82+
std.os.windows.SECTION_MAP_READ;
83+
var page_attributes: std.os.windows.ULONG = 0;
84+
if (opts.protection.execute) {
85+
access |= std.os.windows.SECTION_MAP_EXECUTE;
86+
if (opts.protection.write) {
87+
access |= std.os.windows.SECTION_MAP_WRITE;
88+
page_attributes = switch (opts.exclusivity) {
89+
.shared => std.os.windows.PAGE_EXECUTE_READWRITE,
90+
.private => std.os.windows.PAGE_EXECUTE_WRITECOPY,
91+
};
92+
} else {
93+
page_attributes = std.os.windows.PAGE_EXECUTE_READ;
94+
}
95+
} else {
96+
if (opts.protection.write) {
97+
access |= std.os.windows.SECTION_MAP_WRITE;
98+
page_attributes = switch (opts.exclusivity) {
99+
.shared => std.os.windows.PAGE_READWRITE,
100+
.private => std.os.windows.PAGE_WRITECOPY,
101+
};
102+
} else {
103+
page_attributes = std.os.windows.PAGE_READONLY;
104+
}
105+
}
106+
const handle = try std.os.windows.CreateSection(.{
107+
.file = file.handle,
108+
.access = access,
109+
.size = opts.length,
110+
.page_attributes = page_attributes,
111+
});
112+
errdefer std.os.windows.CloseHandle(handle);
113+
114+
// Create the mapping.
115+
const mapped = try std.os.windows.MapViewOfSection(handle, .{
116+
.inheritance = .ViewUnmap,
117+
.protection = page_attributes,
118+
.offset = opts.offset,
119+
.length = opts.length,
120+
.hint = opts.hint,
121+
});
122+
123+
return .{
124+
.handle = handle,
125+
.mapped = mapped,
126+
};
127+
},
128+
else => {
129+
// The man page indicates the flags must be either `NONE` or an OR of the
130+
// flags. That doesn't explicitly state that the absence of those flags is
131+
// the same as `NONE`, so this static assertion is made. That'll break the
132+
// build rather than behaving unexpectedly if some weird system comes up.
133+
comptime std.debug.assert(std.posix.PROT.NONE == 0);
134+
135+
// Convert the public options into POSIX specific options.
136+
var prot: u32 = std.posix.PROT.READ;
137+
if (opts.protection.write)
138+
prot |= std.posix.PROT.WRITE;
139+
if (opts.protection.execute)
140+
prot |= std.posix.PROT.EXEC;
141+
const flags: std.posix.MAP = .{
142+
.TYPE = switch (opts.exclusivity) {
143+
.shared => .SHARED,
144+
.private => .PRIVATE,
145+
},
146+
};
147+
148+
// Create the mapping.
149+
const mapped = try std.posix.mmap(
150+
opts.hint,
151+
opts.length,
152+
prot,
153+
@bitCast(flags),
154+
file.handle,
155+
opts.offset,
156+
);
157+
158+
return .{
159+
.handle = {},
160+
.mapped = mapped,
161+
};
162+
},
163+
}
164+
}
165+
166+
/// Unmap the file from virtual memory and deallocate kernel resources.
167+
///
168+
/// Invalidates references to `self.mapped`.
169+
pub fn deinit(self: MemoryMap) void {
170+
switch (builtin.os.tag) {
171+
.windows => {
172+
std.os.windows.UnmapViewOfSection(@volatileCast(self.mapped.ptr));
173+
std.os.windows.CloseHandle(self.handle);
174+
},
175+
else => {
176+
std.posix.munmap(@volatileCast(self.mapped));
177+
},
178+
}
179+
}
180+
181+
/// Reinterpret `self.mapped` as `T`.
182+
///
183+
/// The returned pointer is aligned to the beginning of the mapping. The mapping may be
184+
/// larger than `T`. The caller is responsible for determining whether volatility can be
185+
/// stripped away through external synchronization.
186+
pub inline fn cast(self: MemoryMap, comptime T: type) *align(std.mem.page_size) volatile T {
187+
return std.mem.bytesAsValue(T, self.mapped[0..@sizeOf(T)]);
188+
}

0 commit comments

Comments
 (0)