Skip to content

Commit 381efc8

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 30a769a commit 381efc8

File tree

3 files changed

+184
-2
lines changed

3 files changed

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

lib/std/os/windows.zig

Lines changed: 5 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -201,8 +201,11 @@ pub fn MapViewOfFile(
201201
size,
202202
hint,
203203
);
204-
if (ptr) |p|
205-
return @alignCast(p[0..size]);
204+
if (ptr) |p| {
205+
const cast: [*]align(std.mem.page_size) volatile u8 =
206+
@alignCast(@ptrCast(p));
207+
return @alignCast(cast[0..size]);
208+
}
206209
switch (GetLastError()) {
207210
else => |err| return unexpectedError(err),
208211
}

0 commit comments

Comments
 (0)