authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2020-02-10 00:22:59-05:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2020-02-10 00:22:59-05:00
log014f66e6de4aaf81f32c796b12f981326a479397
tree6ed4a6e6776b160fcfdc53c0a2afa5722d7c9237
parent3b622f4494b8fd899abc20e75e46726344f2d20c
parent27575d19c805e166d8393e3c586613256eb0c6e3

Merge pull request #4404 from ziglang/async-std

a big step towards std lib integration with async I/O

43 files changed, 2202 insertions(+), 1845 deletions(-)

build.zig+3-4
......@@ -72,14 +72,13 @@ pub fn build(b: *Builder) !void {
7272 const skip_release_safe = b.option(bool, "skip-release-safe", "Main test suite skips release-safe builds") orelse skip_release;
7373 const skip_non_native = b.option(bool, "skip-non-native", "Main test suite skips non-native builds") orelse false;
7474 const skip_libc = b.option(bool, "skip-libc", "Main test suite skips tests that link libc") orelse false;
75 const skip_self_hosted = b.option(bool, "skip-self-hosted", "Main test suite skips building self hosted compiler") orelse false;
76 if (!skip_self_hosted and builtin.os == .linux) {
77 // TODO evented I/O other OS's
75 const skip_self_hosted = (b.option(bool, "skip-self-hosted", "Main test suite skips building self hosted compiler") orelse false) or true; // TODO evented I/O good enough that this passes everywhere
76 if (!skip_self_hosted) {
7877 test_step.dependOn(&exe.step);
7978 }
8079
8180 const only_install_lib_files = b.option(bool, "lib-files-only", "Only install library files") orelse false;
82 if (!only_install_lib_files) {
81 if (!only_install_lib_files and !skip_self_hosted) {
8382 b.default_step.dependOn(&exe.step);
8483 exe.install();
8584 }
doc/docgen.zig+2-2
......@@ -34,10 +34,10 @@ pub fn main() !void {
3434 const out_file_name = try (args_it.next(allocator) orelse @panic("expected output arg"));
3535 defer allocator.free(out_file_name);
3636
37 var in_file = try fs.File.openRead(in_file_name);
37 var in_file = try fs.cwd().openFile(in_file_name, .{ .read = true });
3838 defer in_file.close();
3939
40 var out_file = try fs.File.openWrite(out_file_name);
40 var out_file = try fs.cwd().createFile(out_file_name, .{});
4141 defer out_file.close();
4242
4343 var file_in_stream = in_file.inStream();
lib/std/atomic/queue.zig+13-4
......@@ -113,11 +113,20 @@ pub fn Queue(comptime T: type) type {
113113
114114 pub fn dumpToStream(self: *Self, comptime Error: type, stream: *std.io.OutStream(Error)) Error!void {
115115 const S = struct {
116 fn dumpRecursive(s: *std.io.OutStream(Error), optional_node: ?*Node, indent: usize) Error!void {
116 fn dumpRecursive(
117 s: *std.io.OutStream(Error),
118 optional_node: ?*Node,
119 indent: usize,
120 comptime depth: comptime_int,
121 ) Error!void {
117122 try s.writeByteNTimes(' ', indent);
118123 if (optional_node) |node| {
119124 try s.print("0x{x}={}\n", .{ @ptrToInt(node), node.data });
120 try dumpRecursive(s, node.next, indent + 1);
125 if (depth == 0) {
126 try s.print("(max depth)\n", .{});
127 return;
128 }
129 try dumpRecursive(s, node.next, indent + 1, depth - 1);
121130 } else {
122131 try s.print("(null)\n", .{});
123132 }
......@@ -127,9 +136,9 @@ pub fn Queue(comptime T: type) type {
127136 defer held.release();
128137
129138 try stream.print("head: ", .{});
130 try S.dumpRecursive(stream, self.head, 0);
139 try S.dumpRecursive(stream, self.head, 0, 4);
131140 try stream.print("tail: ", .{});
132 try S.dumpRecursive(stream, self.tail, 0);
141 try S.dumpRecursive(stream, self.tail, 0, 4);
133142 }
134143 };
135144}
lib/std/builtin.zig+1
......@@ -458,6 +458,7 @@ pub const ExportOptions = struct {
458458pub const TestFn = struct {
459459 name: []const u8,
460460 func: fn () anyerror!void,
461 async_frame_size: ?usize,
461462};
462463
463464/// This function type is used by the Zig language code generation and
lib/std/c.zig+1
......@@ -121,6 +121,7 @@ pub extern "c" fn sysctlbyname(name: [*:0]const u8, oldp: ?*c_void, oldlenp: ?*u
121121pub extern "c" fn sysctlnametomib(name: [*:0]const u8, mibp: ?*c_int, sizep: ?*usize) c_int;
122122pub extern "c" fn tcgetattr(fd: fd_t, termios_p: *termios) c_int;
123123pub extern "c" fn tcsetattr(fd: fd_t, optional_action: TCSA, termios_p: *const termios) c_int;
124pub extern "c" fn fcntl(fd: fd_t, cmd: c_int, ...) c_int;
124125
125126pub extern "c" fn gethostname(name: [*]u8, len: usize) c_int;
126127pub extern "c" fn bind(socket: fd_t, address: ?*const sockaddr, address_len: socklen_t) c_int;
lib/std/child_process.zig+44-15
......@@ -329,17 +329,18 @@ pub const ChildProcess = struct {
329329 }
330330
331331 fn spawnPosix(self: *ChildProcess) SpawnError!void {
332 const stdin_pipe = if (self.stdin_behavior == StdIo.Pipe) try os.pipe() else undefined;
332 const pipe_flags = if (io.is_async) os.O_NONBLOCK else 0;
333 const stdin_pipe = if (self.stdin_behavior == StdIo.Pipe) try os.pipe2(pipe_flags) else undefined;
333334 errdefer if (self.stdin_behavior == StdIo.Pipe) {
334335 destroyPipe(stdin_pipe);
335336 };
336337
337 const stdout_pipe = if (self.stdout_behavior == StdIo.Pipe) try os.pipe() else undefined;
338 const stdout_pipe = if (self.stdout_behavior == StdIo.Pipe) try os.pipe2(pipe_flags) else undefined;
338339 errdefer if (self.stdout_behavior == StdIo.Pipe) {
339340 destroyPipe(stdout_pipe);
340341 };
341342
342 const stderr_pipe = if (self.stderr_behavior == StdIo.Pipe) try os.pipe() else undefined;
343 const stderr_pipe = if (self.stderr_behavior == StdIo.Pipe) try os.pipe2(pipe_flags) else undefined;
343344 errdefer if (self.stderr_behavior == StdIo.Pipe) {
344345 destroyPipe(stderr_pipe);
345346 };
......@@ -426,17 +427,26 @@ pub const ChildProcess = struct {
426427 // we are the parent
427428 const pid = @intCast(i32, pid_result);
428429 if (self.stdin_behavior == StdIo.Pipe) {
429 self.stdin = File.openHandle(stdin_pipe[1]);
430 self.stdin = File{
431 .handle = stdin_pipe[1],
432 .io_mode = std.io.mode,
433 };
430434 } else {
431435 self.stdin = null;
432436 }
433437 if (self.stdout_behavior == StdIo.Pipe) {
434 self.stdout = File.openHandle(stdout_pipe[0]);
438 self.stdout = File{
439 .handle = stdout_pipe[0],
440 .io_mode = std.io.mode,
441 };
435442 } else {
436443 self.stdout = null;
437444 }
438445 if (self.stderr_behavior == StdIo.Pipe) {
439 self.stderr = File.openHandle(stderr_pipe[0]);
446 self.stderr = File{
447 .handle = stderr_pipe[0],
448 .io_mode = std.io.mode,
449 };
440450 } else {
441451 self.stderr = null;
442452 }
......@@ -661,17 +671,26 @@ pub const ChildProcess = struct {
661671 };
662672
663673 if (g_hChildStd_IN_Wr) |h| {
664 self.stdin = File.openHandle(h);
674 self.stdin = File{
675 .handle = h,
676 .io_mode = io.mode,
677 };
665678 } else {
666679 self.stdin = null;
667680 }
668681 if (g_hChildStd_OUT_Rd) |h| {
669 self.stdout = File.openHandle(h);
682 self.stdout = File{
683 .handle = h,
684 .io_mode = io.mode,
685 };
670686 } else {
671687 self.stdout = null;
672688 }
673689 if (g_hChildStd_ERR_Rd) |h| {
674 self.stderr = File.openHandle(h);
690 self.stderr = File{
691 .handle = h,
692 .io_mode = io.mode,
693 };
675694 } else {
676695 self.stderr = null;
677696 }
......@@ -693,10 +712,10 @@ pub const ChildProcess = struct {
693712
694713 fn setUpChildIo(stdio: StdIo, pipe_fd: i32, std_fileno: i32, dev_null_fd: i32) !void {
695714 switch (stdio) {
696 StdIo.Pipe => try os.dup2(pipe_fd, std_fileno),
697 StdIo.Close => os.close(std_fileno),
698 StdIo.Inherit => {},
699 StdIo.Ignore => try os.dup2(dev_null_fd, std_fileno),
715 .Pipe => try os.dup2(pipe_fd, std_fileno),
716 .Close => os.close(std_fileno),
717 .Inherit => {},
718 .Ignore => try os.dup2(dev_null_fd, std_fileno),
700719 }
701720 }
702721};
......@@ -811,12 +830,22 @@ fn forkChildErrReport(fd: i32, err: ChildProcess.SpawnError) noreturn {
811830const ErrInt = @IntType(false, @sizeOf(anyerror) * 8);
812831
813832fn writeIntFd(fd: i32, value: ErrInt) !void {
814 const stream = &File.openHandle(fd).outStream().stream;
833 const file = File{
834 .handle = fd,
835 .io_mode = .blocking,
836 .async_block_allowed = File.async_block_allowed_yes,
837 };
838 const stream = &file.outStream().stream;
815839 stream.writeIntNative(u64, @intCast(u64, value)) catch return error.SystemResources;
816840}
817841
818842fn readIntFd(fd: i32) !ErrInt {
819 const stream = &File.openHandle(fd).inStream().stream;
843 const file = File{
844 .handle = fd,
845 .io_mode = .blocking,
846 .async_block_allowed = File.async_block_allowed_yes,
847 };
848 const stream = &file.inStream().stream;
820849 return @intCast(ErrInt, stream.readIntNative(u64) catch return error.SystemResources);
821850}
822851
lib/std/debug.zig+24-24
......@@ -50,7 +50,7 @@ pub fn warn(comptime fmt: []const u8, args: var) void {
5050 const held = stderr_mutex.acquire();
5151 defer held.release();
5252 const stderr = getStderrStream();
53 stderr.print(fmt, args) catch return;
53 noasync stderr.print(fmt, args) catch return;
5454}
5555
5656pub fn getStderrStream() *io.OutStream(File.WriteError) {
......@@ -102,15 +102,15 @@ pub fn detectTTYConfig() TTY.Config {
102102pub fn dumpCurrentStackTrace(start_addr: ?usize) void {
103103 const stderr = getStderrStream();
104104 if (builtin.strip_debug_info) {
105 stderr.print("Unable to dump stack trace: debug info stripped\n", .{}) catch return;
105 noasync stderr.print("Unable to dump stack trace: debug info stripped\n", .{}) catch return;
106106 return;
107107 }
108108 const debug_info = getSelfDebugInfo() catch |err| {
109 stderr.print("Unable to dump stack trace: Unable to open debug info: {}\n", .{@errorName(err)}) catch return;
109 noasync stderr.print("Unable to dump stack trace: Unable to open debug info: {}\n", .{@errorName(err)}) catch return;
110110 return;
111111 };
112112 writeCurrentStackTrace(stderr, debug_info, detectTTYConfig(), start_addr) catch |err| {
113 stderr.print("Unable to dump stack trace: {}\n", .{@errorName(err)}) catch return;
113 noasync stderr.print("Unable to dump stack trace: {}\n", .{@errorName(err)}) catch return;
114114 return;
115115 };
116116}
......@@ -121,11 +121,11 @@ pub fn dumpCurrentStackTrace(start_addr: ?usize) void {
121121pub fn dumpStackTraceFromBase(bp: usize, ip: usize) void {
122122 const stderr = getStderrStream();
123123 if (builtin.strip_debug_info) {
124 stderr.print("Unable to dump stack trace: debug info stripped\n", .{}) catch return;
124 noasync stderr.print("Unable to dump stack trace: debug info stripped\n", .{}) catch return;
125125 return;
126126 }
127127 const debug_info = getSelfDebugInfo() catch |err| {
128 stderr.print("Unable to dump stack trace: Unable to open debug info: {}\n", .{@errorName(err)}) catch return;
128 noasync stderr.print("Unable to dump stack trace: Unable to open debug info: {}\n", .{@errorName(err)}) catch return;
129129 return;
130130 };
131131 const tty_config = detectTTYConfig();
......@@ -189,15 +189,15 @@ pub fn captureStackTrace(first_address: ?usize, stack_trace: *builtin.StackTrace
189189pub fn dumpStackTrace(stack_trace: builtin.StackTrace) void {
190190 const stderr = getStderrStream();
191191 if (builtin.strip_debug_info) {
192 stderr.print("Unable to dump stack trace: debug info stripped\n", .{}) catch return;
192 noasync stderr.print("Unable to dump stack trace: debug info stripped\n", .{}) catch return;
193193 return;
194194 }
195195 const debug_info = getSelfDebugInfo() catch |err| {
196 stderr.print("Unable to dump stack trace: Unable to open debug info: {}\n", .{@errorName(err)}) catch return;
196 noasync stderr.print("Unable to dump stack trace: Unable to open debug info: {}\n", .{@errorName(err)}) catch return;
197197 return;
198198 };
199199 writeStackTrace(stack_trace, stderr, getDebugInfoAllocator(), debug_info, detectTTYConfig()) catch |err| {
200 stderr.print("Unable to dump stack trace: {}\n", .{@errorName(err)}) catch return;
200 noasync stderr.print("Unable to dump stack trace: {}\n", .{@errorName(err)}) catch return;
201201 return;
202202 };
203203}
......@@ -238,7 +238,7 @@ pub fn panicExtra(trace: ?*const builtin.StackTrace, first_trace_addr: ?usize, c
238238 switch (@atomicRmw(u8, &panicking, .Add, 1, .SeqCst)) {
239239 0 => {
240240 const stderr = getStderrStream();
241 stderr.print(format ++ "\n", args) catch os.abort();
241 noasync stderr.print(format ++ "\n", args) catch os.abort();
242242 if (trace) |t| {
243243 dumpStackTrace(t.*);
244244 }
......@@ -568,12 +568,12 @@ pub const TTY = struct {
568568 switch (conf) {
569569 .no_color => return,
570570 .escape_codes => switch (color) {
571 .Red => out_stream.write(RED) catch return,
572 .Green => out_stream.write(GREEN) catch return,
573 .Cyan => out_stream.write(CYAN) catch return,
574 .White, .Bold => out_stream.write(WHITE) catch return,
575 .Dim => out_stream.write(DIM) catch return,
576 .Reset => out_stream.write(RESET) catch return,
571 .Red => noasync out_stream.write(RED) catch return,
572 .Green => noasync out_stream.write(GREEN) catch return,
573 .Cyan => noasync out_stream.write(CYAN) catch return,
574 .White, .Bold => noasync out_stream.write(WHITE) catch return,
575 .Dim => noasync out_stream.write(DIM) catch return,
576 .Reset => noasync out_stream.write(RESET) catch return,
577577 },
578578 .windows_api => if (builtin.os == .windows) {
579579 const S = struct {
......@@ -729,17 +729,17 @@ fn printLineInfo(
729729 tty_config.setColor(out_stream, .White);
730730
731731 if (line_info) |*li| {
732 try out_stream.print("{}:{}:{}", .{ li.file_name, li.line, li.column });
732 try noasync out_stream.print("{}:{}:{}", .{ li.file_name, li.line, li.column });
733733 } else {
734 try out_stream.print("???:?:?", .{});
734 try noasync out_stream.write("???:?:?");
735735 }
736736
737737 tty_config.setColor(out_stream, .Reset);
738 try out_stream.write(": ");
738 try noasync out_stream.write(": ");
739739 tty_config.setColor(out_stream, .Dim);
740 try out_stream.print("0x{x} in {} ({})", .{ address, symbol_name, compile_unit_name });
740 try noasync out_stream.print("0x{x} in {} ({})", .{ address, symbol_name, compile_unit_name });
741741 tty_config.setColor(out_stream, .Reset);
742 try out_stream.write("\n");
742 try noasync out_stream.write("\n");
743743
744744 // Show the matching source code line if possible
745745 if (line_info) |li| {
......@@ -748,12 +748,12 @@ fn printLineInfo(
748748 // The caret already takes one char
749749 const space_needed = @intCast(usize, li.column - 1);
750750
751 try out_stream.writeByteNTimes(' ', space_needed);
751 try noasync out_stream.writeByteNTimes(' ', space_needed);
752752 tty_config.setColor(out_stream, .Green);
753 try out_stream.write("^");
753 try noasync out_stream.write("^");
754754 tty_config.setColor(out_stream, .Reset);
755755 }
756 try out_stream.write("\n");
756 try noasync out_stream.write("\n");
757757 } else |err| switch (err) {
758758 error.EndOfFile, error.FileNotFound => {},
759759 error.BadPathName => {},
lib/std/event.zig-2
......@@ -6,11 +6,9 @@ pub const Locked = @import("event/locked.zig").Locked;
66pub const RwLock = @import("event/rwlock.zig").RwLock;
77pub const RwLocked = @import("event/rwlocked.zig").RwLocked;
88pub const Loop = @import("event/loop.zig").Loop;
9pub const fs = @import("event/fs.zig");
109
1110test "import event tests" {
1211 _ = @import("event/channel.zig");
13 _ = @import("event/fs.zig");
1412 _ = @import("event/future.zig");
1513 _ = @import("event/group.zig");
1614 _ = @import("event/lock.zig");
lib/std/event/channel.zig+3-4
......@@ -267,17 +267,16 @@ pub fn Channel(comptime T: type) type {
267267}
268268
269269test "std.event.Channel" {
270 if (!std.io.is_async) return error.SkipZigTest;
271
270272 // https://github.com/ziglang/zig/issues/1908
271273 if (builtin.single_threaded) return error.SkipZigTest;
272274
273275 // https://github.com/ziglang/zig/issues/3251
274276 if (builtin.os == .freebsd) return error.SkipZigTest;
275277
276 // TODO provide a way to run tests in evented I/O mode
277 if (!std.io.is_async) return error.SkipZigTest;
278
279278 var channel: Channel(i32) = undefined;
280 channel.init([0]i32{});
279 channel.init(&[0]i32{});
281280 defer channel.deinit();
282281
283282 var handle = async testChannelGetter(&channel);
lib/std/event/fs.zig deleted-1418
......@@ -1,1418 +0,0 @@
1const builtin = @import("builtin");
2const std = @import("../std.zig");
3const event = std.event;
4const assert = std.debug.assert;
5const testing = std.testing;
6const os = std.os;
7const mem = std.mem;
8const windows = os.windows;
9const Loop = event.Loop;
10const fd_t = os.fd_t;
11const File = std.fs.File;
12const Allocator = mem.Allocator;
13
14//! TODO mege this with `std.fs`
15
16const global_event_loop = Loop.instance orelse
17 @compileError("std.event.fs currently only works with event-based I/O");
18
19pub const RequestNode = std.atomic.Queue(Request).Node;
20
21pub const Request = struct {
22 msg: Msg,
23 finish: Finish,
24
25 pub const Finish = union(enum) {
26 TickNode: Loop.NextTickNode,
27 DeallocCloseOperation: *CloseOperation,
28 NoAction,
29 };
30
31 pub const Msg = union(enum) {
32 WriteV: WriteV,
33 PWriteV: PWriteV,
34 PReadV: PReadV,
35 Open: Open,
36 Close: Close,
37 WriteFile: WriteFile,
38 End, // special - means the fs thread should exit
39
40 pub const WriteV = struct {
41 fd: fd_t,
42 iov: []const os.iovec_const,
43 result: Error!void,
44
45 pub const Error = os.WriteError;
46 };
47
48 pub const PWriteV = struct {
49 fd: fd_t,
50 iov: []const os.iovec_const,
51 offset: usize,
52 result: Error!void,
53
54 pub const Error = os.WriteError;
55 };
56
57 pub const PReadV = struct {
58 fd: fd_t,
59 iov: []const os.iovec,
60 offset: usize,
61 result: Error!usize,
62
63 pub const Error = os.ReadError;
64 };
65
66 pub const Open = struct {
67 path: [:0]const u8,
68 flags: u32,
69 mode: File.Mode,
70 result: Error!fd_t,
71
72 pub const Error = File.OpenError;
73 };
74
75 pub const WriteFile = struct {
76 path: [:0]const u8,
77 contents: []const u8,
78 mode: File.Mode,
79 result: Error!void,
80
81 pub const Error = File.OpenError || File.WriteError;
82 };
83
84 pub const Close = struct {
85 fd: fd_t,
86 };
87 };
88};
89
90pub const PWriteVError = error{OutOfMemory} || File.WriteError;
91
92/// data - just the inner references - must live until pwritev frame completes.
93pub fn pwritev(allocator: *Allocator, fd: fd_t, data: []const []const u8, offset: usize) PWriteVError!void {
94 switch (builtin.os) {
95 .macosx,
96 .linux,
97 .freebsd,
98 .netbsd,
99 .dragonfly,
100 => {
101 const iovecs = try allocator.alloc(os.iovec_const, data.len);
102 defer allocator.free(iovecs);
103
104 for (data) |buf, i| {
105 iovecs[i] = os.iovec_const{
106 .iov_base = buf.ptr,
107 .iov_len = buf.len,
108 };
109 }
110
111 return pwritevPosix(fd, iovecs, offset);
112 },
113 .windows => {
114 const data_copy = try std.mem.dupe(allocator, []const u8, data);
115 defer allocator.free(data_copy);
116 return pwritevWindows(fd, data, offset);
117 },
118 else => @compileError("Unsupported OS"),
119 }
120}
121
122/// data must outlive the returned frame
123pub fn pwritevWindows(fd: fd_t, data: []const []const u8, offset: usize) os.WindowsWriteError!void {
124 if (data.len == 0) return;
125 if (data.len == 1) return pwriteWindows(fd, data[0], offset);
126
127 // TODO do these in parallel
128 var off = offset;
129 for (data) |buf| {
130 try pwriteWindows(fd, buf, off);
131 off += buf.len;
132 }
133}
134
135pub fn pwriteWindows(fd: fd_t, data: []const u8, offset: u64) os.WindowsWriteError!void {
136 var resume_node = Loop.ResumeNode.Basic{
137 .base = Loop.ResumeNode{
138 .id = Loop.ResumeNode.Id.Basic,
139 .handle = @frame(),
140 .overlapped = windows.OVERLAPPED{
141 .Internal = 0,
142 .InternalHigh = 0,
143 .Offset = @truncate(u32, offset),
144 .OffsetHigh = @truncate(u32, offset >> 32),
145 .hEvent = null,
146 },
147 },
148 };
149 // TODO only call create io completion port once per fd
150 _ = windows.CreateIoCompletionPort(fd, global_event_loop.os_data.io_port, undefined, undefined);
151 global_event_loop.beginOneEvent();
152 errdefer global_event_loop.finishOneEvent();
153
154 errdefer {
155 _ = windows.kernel32.CancelIoEx(fd, &resume_node.base.overlapped);
156 }
157 suspend {
158 _ = windows.kernel32.WriteFile(fd, data.ptr, @intCast(windows.DWORD, data.len), null, &resume_node.base.overlapped);
159 }
160 var bytes_transferred: windows.DWORD = undefined;
161 if (windows.kernel32.GetOverlappedResult(fd, &resume_node.base.overlapped, &bytes_transferred, windows.FALSE) == 0) {
162 switch (windows.kernel32.GetLastError()) {
163 .IO_PENDING => unreachable,
164 .INVALID_USER_BUFFER => return error.SystemResources,
165 .NOT_ENOUGH_MEMORY => return error.SystemResources,
166 .OPERATION_ABORTED => return error.OperationAborted,
167 .NOT_ENOUGH_QUOTA => return error.SystemResources,
168 .BROKEN_PIPE => return error.BrokenPipe,
169 else => |err| return windows.unexpectedError(err),
170 }
171 }
172}
173
174/// iovecs must live until pwritev frame completes.
175pub fn pwritevPosix(fd: fd_t, iovecs: []const os.iovec_const, offset: usize) os.WriteError!void {
176 var req_node = RequestNode{
177 .prev = null,
178 .next = null,
179 .data = Request{
180 .msg = Request.Msg{
181 .PWriteV = Request.Msg.PWriteV{
182 .fd = fd,
183 .iov = iovecs,
184 .offset = offset,
185 .result = undefined,
186 },
187 },
188 .finish = Request.Finish{
189 .TickNode = Loop.NextTickNode{
190 .prev = null,
191 .next = null,
192 .data = @frame(),
193 },
194 },
195 },
196 };
197
198 errdefer global_event_loop.posixFsCancel(&req_node);
199
200 suspend {
201 global_event_loop.posixFsRequest(&req_node);
202 }
203
204 return req_node.data.msg.PWriteV.result;
205}
206
207/// iovecs must live until pwritev frame completes.
208pub fn writevPosix(fd: fd_t, iovecs: []const os.iovec_const) os.WriteError!void {
209 var req_node = RequestNode{
210 .prev = null,
211 .next = null,
212 .data = Request{
213 .msg = Request.Msg{
214 .WriteV = Request.Msg.WriteV{
215 .fd = fd,
216 .iov = iovecs,
217 .result = undefined,
218 },
219 },
220 .finish = Request.Finish{
221 .TickNode = Loop.NextTickNode{
222 .prev = null,
223 .next = null,
224 .data = @frame(),
225 },
226 },
227 },
228 };
229
230 suspend {
231 global_event_loop.posixFsRequest(&req_node);
232 }
233
234 return req_node.data.msg.WriteV.result;
235}
236
237pub const PReadVError = error{OutOfMemory} || File.ReadError;
238
239/// data - just the inner references - must live until preadv frame completes.
240pub fn preadv(allocator: *Allocator, fd: fd_t, data: []const []u8, offset: usize) PReadVError!usize {
241 assert(data.len != 0);
242 switch (builtin.os) {
243 .macosx,
244 .linux,
245 .freebsd,
246 .netbsd,
247 .dragonfly,
248 => {
249 const iovecs = try allocator.alloc(os.iovec, data.len);
250 defer allocator.free(iovecs);
251
252 for (data) |buf, i| {
253 iovecs[i] = os.iovec{
254 .iov_base = buf.ptr,
255 .iov_len = buf.len,
256 };
257 }
258
259 return preadvPosix(fd, iovecs, offset);
260 },
261 .windows => {
262 const data_copy = try std.mem.dupe(allocator, []u8, data);
263 defer allocator.free(data_copy);
264 return preadvWindows(fd, data_copy, offset);
265 },
266 else => @compileError("Unsupported OS"),
267 }
268}
269
270/// data must outlive the returned frame
271pub fn preadvWindows(fd: fd_t, data: []const []u8, offset: u64) !usize {
272 assert(data.len != 0);
273 if (data.len == 1) return preadWindows(fd, data[0], offset);
274
275 // TODO do these in parallel?
276 var off: usize = 0;
277 var iov_i: usize = 0;
278 var inner_off: usize = 0;
279 while (true) {
280 const v = data[iov_i];
281 const amt_read = try preadWindows(fd, v[inner_off .. v.len - inner_off], offset + off);
282 off += amt_read;
283 inner_off += amt_read;
284 if (inner_off == v.len) {
285 iov_i += 1;
286 inner_off = 0;
287 if (iov_i == data.len) {
288 return off;
289 }
290 }
291 if (amt_read == 0) return off; // EOF
292 }
293}
294
295pub fn preadWindows(fd: fd_t, data: []u8, offset: u64) !usize {
296 var resume_node = Loop.ResumeNode.Basic{
297 .base = Loop.ResumeNode{
298 .id = Loop.ResumeNode.Id.Basic,
299 .handle = @frame(),
300 .overlapped = windows.OVERLAPPED{
301 .Internal = 0,
302 .InternalHigh = 0,
303 .Offset = @truncate(u32, offset),
304 .OffsetHigh = @truncate(u32, offset >> 32),
305 .hEvent = null,
306 },
307 },
308 };
309 // TODO only call create io completion port once per fd
310 _ = windows.CreateIoCompletionPort(fd, global_event_loop.os_data.io_port, undefined, undefined) catch undefined;
311 global_event_loop.beginOneEvent();
312 errdefer global_event_loop.finishOneEvent();
313
314 errdefer {
315 _ = windows.kernel32.CancelIoEx(fd, &resume_node.base.overlapped);
316 }
317 suspend {
318 _ = windows.kernel32.ReadFile(fd, data.ptr, @intCast(windows.DWORD, data.len), null, &resume_node.base.overlapped);
319 }
320 var bytes_transferred: windows.DWORD = undefined;
321 if (windows.kernel32.GetOverlappedResult(fd, &resume_node.base.overlapped, &bytes_transferred, windows.FALSE) == 0) {
322 switch (windows.kernel32.GetLastError()) {
323 .IO_PENDING => unreachable,
324 .OPERATION_ABORTED => return error.OperationAborted,
325 .BROKEN_PIPE => return error.BrokenPipe,
326 .HANDLE_EOF => return @as(usize, bytes_transferred),
327 else => |err| return windows.unexpectedError(err),
328 }
329 }
330 return @as(usize, bytes_transferred);
331}
332
333/// iovecs must live until preadv frame completes
334pub fn preadvPosix(fd: fd_t, iovecs: []const os.iovec, offset: usize) os.ReadError!usize {
335 var req_node = RequestNode{
336 .prev = null,
337 .next = null,
338 .data = Request{
339 .msg = Request.Msg{
340 .PReadV = Request.Msg.PReadV{
341 .fd = fd,
342 .iov = iovecs,
343 .offset = offset,
344 .result = undefined,
345 },
346 },
347 .finish = Request.Finish{
348 .TickNode = Loop.NextTickNode{
349 .prev = null,
350 .next = null,
351 .data = @frame(),
352 },
353 },
354 },
355 };
356
357 errdefer global_event_loop.posixFsCancel(&req_node);
358
359 suspend {
360 global_event_loop.posixFsRequest(&req_node);
361 }
362
363 return req_node.data.msg.PReadV.result;
364}
365
366pub fn openPosix(path: []const u8, flags: u32, mode: File.Mode) File.OpenError!fd_t {
367 const path_c = try std.os.toPosixPath(path);
368
369 var req_node = RequestNode{
370 .prev = null,
371 .next = null,
372 .data = Request{
373 .msg = Request.Msg{
374 .Open = Request.Msg.Open{
375 .path = path_c[0..path.len],
376 .flags = flags,
377 .mode = mode,
378 .result = undefined,
379 },
380 },
381 .finish = Request.Finish{
382 .TickNode = Loop.NextTickNode{
383 .prev = null,
384 .next = null,
385 .data = @frame(),
386 },
387 },
388 },
389 };
390
391 errdefer global_event_loop.posixFsCancel(&req_node);
392
393 suspend {
394 global_event_loop.posixFsRequest(&req_node);
395 }
396
397 return req_node.data.msg.Open.result;
398}
399
400pub fn openRead(path: []const u8) File.OpenError!fd_t {
401 switch (builtin.os) {
402 .macosx, .linux, .freebsd, .netbsd, .dragonfly => {
403 const O_LARGEFILE = if (@hasDecl(os, "O_LARGEFILE")) os.O_LARGEFILE else 0;
404 const flags = O_LARGEFILE | os.O_RDONLY | os.O_CLOEXEC;
405 return openPosix(path, flags, File.default_mode);
406 },
407
408 .windows => return windows.CreateFile(
409 path,
410 windows.GENERIC_READ,
411 windows.FILE_SHARE_READ,
412 null,
413 windows.OPEN_EXISTING,
414 windows.FILE_ATTRIBUTE_NORMAL | windows.FILE_FLAG_OVERLAPPED,
415 null,
416 ),
417
418 else => @compileError("Unsupported OS"),
419 }
420}
421
422/// Creates if does not exist. Truncates the file if it exists.
423/// Uses the default mode.
424pub fn openWrite(path: []const u8) File.OpenError!fd_t {
425 return openWriteMode(path, File.default_mode);
426}
427
428/// Creates if does not exist. Truncates the file if it exists.
429pub fn openWriteMode(path: []const u8, mode: File.Mode) File.OpenError!fd_t {
430 switch (builtin.os) {
431 .macosx,
432 .linux,
433 .freebsd,
434 .netbsd,
435 .dragonfly,
436 => {
437 const O_LARGEFILE = if (@hasDecl(os, "O_LARGEFILE")) os.O_LARGEFILE else 0;
438 const flags = O_LARGEFILE | os.O_WRONLY | os.O_CREAT | os.O_CLOEXEC | os.O_TRUNC;
439 return openPosix(path, flags, File.default_mode);
440 },
441 .windows => return windows.CreateFile(
442 path,
443 windows.GENERIC_WRITE,
444 windows.FILE_SHARE_WRITE | windows.FILE_SHARE_READ | windows.FILE_SHARE_DELETE,
445 null,
446 windows.CREATE_ALWAYS,
447 windows.FILE_ATTRIBUTE_NORMAL | windows.FILE_FLAG_OVERLAPPED,
448 null,
449 ),
450 else => @compileError("Unsupported OS"),
451 }
452}
453
454/// Creates if does not exist. Does not truncate.
455pub fn openReadWrite(path: []const u8, mode: File.Mode) File.OpenError!fd_t {
456 switch (builtin.os) {
457 .macosx, .linux, .freebsd, .netbsd, .dragonfly => {
458 const O_LARGEFILE = if (@hasDecl(os, "O_LARGEFILE")) os.O_LARGEFILE else 0;
459 const flags = O_LARGEFILE | os.O_RDWR | os.O_CREAT | os.O_CLOEXEC;
460 return openPosix(path, flags, mode);
461 },
462
463 .windows => return windows.CreateFile(
464 path,
465 windows.GENERIC_WRITE | windows.GENERIC_READ,
466 windows.FILE_SHARE_WRITE | windows.FILE_SHARE_READ | windows.FILE_SHARE_DELETE,
467 null,
468 windows.OPEN_ALWAYS,
469 windows.FILE_ATTRIBUTE_NORMAL | windows.FILE_FLAG_OVERLAPPED,
470 null,
471 ),
472
473 else => @compileError("Unsupported OS"),
474 }
475}
476
477/// This abstraction helps to close file handles in defer expressions
478/// without the possibility of failure and without the use of suspend points.
479/// Start a `CloseOperation` before opening a file, so that you can defer
480/// `CloseOperation.finish`.
481/// If you call `setHandle` then finishing will close the fd; otherwise finishing
482/// will deallocate the `CloseOperation`.
483pub const CloseOperation = struct {
484 allocator: *Allocator,
485 os_data: OsData,
486
487 const OsData = switch (builtin.os) {
488 .linux, .macosx, .freebsd, .netbsd, .dragonfly => OsDataPosix,
489
490 .windows => struct {
491 handle: ?fd_t,
492 },
493
494 else => @compileError("Unsupported OS"),
495 };
496
497 const OsDataPosix = struct {
498 have_fd: bool,
499 close_req_node: RequestNode,
500 };
501
502 pub fn start(allocator: *Allocator) (error{OutOfMemory}!*CloseOperation) {
503 const self = try allocator.create(CloseOperation);
504 self.* = CloseOperation{
505 .allocator = allocator,
506 .os_data = switch (builtin.os) {
507 .linux, .macosx, .freebsd, .netbsd, .dragonfly => initOsDataPosix(self),
508 .windows => OsData{ .handle = null },
509 else => @compileError("Unsupported OS"),
510 },
511 };
512 return self;
513 }
514
515 fn initOsDataPosix(self: *CloseOperation) OsData {
516 return OsData{
517 .have_fd = false,
518 .close_req_node = RequestNode{
519 .prev = null,
520 .next = null,
521 .data = Request{
522 .msg = Request.Msg{
523 .Close = Request.Msg.Close{ .fd = undefined },
524 },
525 .finish = Request.Finish{ .DeallocCloseOperation = self },
526 },
527 },
528 };
529 }
530
531 /// Defer this after creating.
532 pub fn finish(self: *CloseOperation) void {
533 switch (builtin.os) {
534 .linux,
535 .macosx,
536 .freebsd,
537 .netbsd,
538 .dragonfly,
539 => {
540 if (self.os_data.have_fd) {
541 global_event_loop.posixFsRequest(&self.os_data.close_req_node);
542 } else {
543 self.allocator.destroy(self);
544 }
545 },
546 .windows => {
547 if (self.os_data.handle) |handle| {
548 os.close(handle);
549 }
550 self.allocator.destroy(self);
551 },
552 else => @compileError("Unsupported OS"),
553 }
554 }
555
556 pub fn setHandle(self: *CloseOperation, handle: fd_t) void {
557 switch (builtin.os) {
558 .linux,
559 .macosx,
560 .freebsd,
561 .netbsd,
562 .dragonfly,
563 => {
564 self.os_data.close_req_node.data.msg.Close.fd = handle;
565 self.os_data.have_fd = true;
566 },
567 .windows => {
568 self.os_data.handle = handle;
569 },
570 else => @compileError("Unsupported OS"),
571 }
572 }
573
574 /// Undo a `setHandle`.
575 pub fn clearHandle(self: *CloseOperation) void {
576 switch (builtin.os) {
577 .linux,
578 .macosx,
579 .freebsd,
580 .netbsd,
581 .dragonfly,
582 => {
583 self.os_data.have_fd = false;
584 },
585 .windows => {
586 self.os_data.handle = null;
587 },
588 else => @compileError("Unsupported OS"),
589 }
590 }
591
592 pub fn getHandle(self: *CloseOperation) fd_t {
593 switch (builtin.os) {
594 .linux,
595 .macosx,
596 .freebsd,
597 .netbsd,
598 .dragonfly,
599 => {
600 assert(self.os_data.have_fd);
601 return self.os_data.close_req_node.data.msg.Close.fd;
602 },
603 .windows => {
604 return self.os_data.handle.?;
605 },
606 else => @compileError("Unsupported OS"),
607 }
608 }
609};
610
611/// contents must remain alive until writeFile completes.
612/// TODO make this atomic or provide writeFileAtomic and rename this one to writeFileTruncate
613pub fn writeFile(allocator: *Allocator, path: []const u8, contents: []const u8) !void {
614 return writeFileMode(allocator, path, contents, File.default_mode);
615}
616
617/// contents must remain alive until writeFile completes.
618pub fn writeFileMode(allocator: *Allocator, path: []const u8, contents: []const u8, mode: File.Mode) !void {
619 switch (builtin.os) {
620 .linux,
621 .macosx,
622 .freebsd,
623 .netbsd,
624 .dragonfly,
625 => return writeFileModeThread(allocator, path, contents, mode),
626 .windows => return writeFileWindows(path, contents),
627 else => @compileError("Unsupported OS"),
628 }
629}
630
631fn writeFileWindows(path: []const u8, contents: []const u8) !void {
632 const handle = try windows.CreateFile(
633 path,
634 windows.GENERIC_WRITE,
635 windows.FILE_SHARE_WRITE | windows.FILE_SHARE_READ | windows.FILE_SHARE_DELETE,
636 null,
637 windows.CREATE_ALWAYS,
638 windows.FILE_ATTRIBUTE_NORMAL | windows.FILE_FLAG_OVERLAPPED,
639 null,
640 );
641 defer os.close(handle);
642
643 try pwriteWindows(handle, contents, 0);
644}
645
646fn writeFileModeThread(allocator: *Allocator, path: []const u8, contents: []const u8, mode: File.Mode) !void {
647 const path_with_null = try std.cstr.addNullByte(allocator, path);
648 defer allocator.free(path_with_null);
649
650 var req_node = RequestNode{
651 .prev = null,
652 .next = null,
653 .data = Request{
654 .msg = Request.Msg{
655 .WriteFile = Request.Msg.WriteFile{
656 .path = path_with_null[0..path.len],
657 .contents = contents,
658 .mode = mode,
659 .result = undefined,
660 },
661 },
662 .finish = Request.Finish{
663 .TickNode = Loop.NextTickNode{
664 .prev = null,
665 .next = null,
666 .data = @frame(),
667 },
668 },
669 },
670 };
671
672 errdefer global_event_loop.posixFsCancel(&req_node);
673
674 suspend {
675 global_event_loop.posixFsRequest(&req_node);
676 }
677
678 return req_node.data.msg.WriteFile.result;
679}
680
681/// The frame resumes when the last data has been confirmed written, but before the file handle
682/// is closed.
683/// Caller owns returned memory.
684pub fn readFile(allocator: *Allocator, file_path: []const u8, max_size: usize) ![]u8 {
685 var close_op = try CloseOperation.start(allocator);
686 defer close_op.finish();
687
688 const fd = try openRead(file_path);
689 close_op.setHandle(fd);
690
691 var list = std.ArrayList(u8).init(allocator);
692 defer list.deinit();
693
694 while (true) {
695 try list.ensureCapacity(list.len + mem.page_size);
696 const buf = list.items[list.len..];
697 const buf_array = [_][]u8{buf};
698 const amt = try preadv(allocator, fd, &buf_array, list.len);
699 list.len += amt;
700 if (list.len > max_size) {
701 return error.FileTooBig;
702 }
703 if (amt < buf.len) {
704 return list.toOwnedSlice();
705 }
706 }
707}
708
709pub const WatchEventId = enum {
710 CloseWrite,
711 Delete,
712};
713
714fn eqlString(a: []const u16, b: []const u16) bool {
715 if (a.len != b.len) return false;
716 if (a.ptr == b.ptr) return true;
717 return mem.compare(u16, a, b) == .Equal;
718}
719
720fn hashString(s: []const u16) u32 {
721 return @truncate(u32, std.hash.Wyhash.hash(0, @sliceToBytes(s)));
722}
723
724pub const WatchEventError = error{
725 UserResourceLimitReached,
726 SystemResources,
727 AccessDenied,
728 Unexpected, // TODO remove this possibility
729};
730
731pub fn Watch(comptime V: type) type {
732 return struct {
733 channel: *event.Channel(Event.Error!Event),
734 os_data: OsData,
735 allocator: *Allocator,
736
737 const OsData = switch (builtin.os) {
738 // TODO https://github.com/ziglang/zig/issues/3778
739 .macosx, .freebsd, .netbsd, .dragonfly => KqOsData,
740 .linux => LinuxOsData,
741 .windows => WindowsOsData,
742
743 else => @compileError("Unsupported OS"),
744 };
745
746 const KqOsData = struct {
747 file_table: FileTable,
748 table_lock: event.Lock,
749
750 const FileTable = std.StringHashMap(*Put);
751 const Put = struct {
752 putter_frame: @Frame(kqPutEvents),
753 cancelled: bool = false,
754 value: V,
755 };
756 };
757
758 const WindowsOsData = struct {
759 table_lock: event.Lock,
760 dir_table: DirTable,
761 all_putters: std.atomic.Queue(Put),
762 ref_count: std.atomic.Int(usize),
763
764 const Put = struct {
765 putter: anyframe,
766 cancelled: bool = false,
767 };
768
769 const DirTable = std.StringHashMap(*Dir);
770 const FileTable = std.HashMap([]const u16, V, hashString, eqlString);
771
772 const Dir = struct {
773 putter_frame: @Frame(windowsDirReader),
774 file_table: FileTable,
775 table_lock: event.Lock,
776 };
777 };
778
779 const LinuxOsData = struct {
780 putter_frame: @Frame(linuxEventPutter),
781 inotify_fd: i32,
782 wd_table: WdTable,
783 table_lock: event.Lock,
784 cancelled: bool = false,
785
786 const WdTable = std.AutoHashMap(i32, Dir);
787 const FileTable = std.StringHashMap(V);
788
789 const Dir = struct {
790 dirname: []const u8,
791 file_table: FileTable,
792 };
793 };
794
795 const Self = @This();
796
797 pub const Event = struct {
798 id: Id,
799 data: V,
800
801 pub const Id = WatchEventId;
802 pub const Error = WatchEventError;
803 };
804
805 pub fn init(allocator: *Allocator, event_buf_count: usize) !*Self {
806 const channel = try allocator.create(event.Channel(Event.Error!Event));
807 errdefer allocator.destroy(channel);
808 var buf = try allocator.alloc(Event.Error!Event, event_buf_count);
809 errdefer allocator.free(buf);
810 channel.init(buf);
811 errdefer channel.deinit();
812
813 const self = try allocator.create(Self);
814 errdefer allocator.destroy(self);
815
816 switch (builtin.os) {
817 .linux => {
818 const inotify_fd = try os.inotify_init1(os.linux.IN_NONBLOCK | os.linux.IN_CLOEXEC);
819 errdefer os.close(inotify_fd);
820
821 self.* = Self{
822 .allocator = allocator,
823 .channel = channel,
824 .os_data = OsData{
825 .putter_frame = undefined,
826 .inotify_fd = inotify_fd,
827 .wd_table = OsData.WdTable.init(allocator),
828 .table_lock = event.Lock.init(),
829 },
830 };
831
832 self.os_data.putter_frame = async self.linuxEventPutter();
833 return self;
834 },
835
836 .windows => {
837 self.* = Self{
838 .allocator = allocator,
839 .channel = channel,
840 .os_data = OsData{
841 .table_lock = event.Lock.init(),
842 .dir_table = OsData.DirTable.init(allocator),
843 .ref_count = std.atomic.Int(usize).init(1),
844 .all_putters = std.atomic.Queue(anyframe).init(),
845 },
846 };
847 return self;
848 },
849
850 .macosx, .freebsd, .netbsd, .dragonfly => {
851 self.* = Self{
852 .allocator = allocator,
853 .channel = channel,
854 .os_data = OsData{
855 .table_lock = event.Lock.init(),
856 .file_table = OsData.FileTable.init(allocator),
857 },
858 };
859 return self;
860 },
861 else => @compileError("Unsupported OS"),
862 }
863 }
864
865 /// All addFile calls and removeFile calls must have completed.
866 pub fn deinit(self: *Self) void {
867 switch (builtin.os) {
868 .macosx, .freebsd, .netbsd, .dragonfly => {
869 // TODO we need to cancel the frames before destroying the lock
870 self.os_data.table_lock.deinit();
871 var it = self.os_data.file_table.iterator();
872 while (it.next()) |entry| {
873 entry.cancelled = true;
874 await entry.value.putter;
875 self.allocator.free(entry.key);
876 self.allocator.free(entry.value);
877 }
878 self.channel.deinit();
879 self.allocator.destroy(self.channel.buffer_nodes);
880 self.allocator.destroy(self);
881 },
882 .linux => {
883 self.os_data.cancelled = true;
884 await self.os_data.putter_frame;
885 self.allocator.destroy(self);
886 },
887 .windows => {
888 while (self.os_data.all_putters.get()) |putter_node| {
889 putter_node.cancelled = true;
890 await putter_node.frame;
891 }
892 self.deref();
893 },
894 else => @compileError("Unsupported OS"),
895 }
896 }
897
898 fn ref(self: *Self) void {
899 _ = self.os_data.ref_count.incr();
900 }
901
902 fn deref(self: *Self) void {
903 if (self.os_data.ref_count.decr() == 1) {
904 self.os_data.table_lock.deinit();
905 var it = self.os_data.dir_table.iterator();
906 while (it.next()) |entry| {
907 self.allocator.free(entry.key);
908 self.allocator.destroy(entry.value);
909 }
910 self.os_data.dir_table.deinit();
911 self.channel.deinit();
912 self.allocator.destroy(self.channel.buffer_nodes);
913 self.allocator.destroy(self);
914 }
915 }
916
917 pub fn addFile(self: *Self, file_path: []const u8, value: V) !?V {
918 switch (builtin.os) {
919 .macosx, .freebsd, .netbsd, .dragonfly => return addFileKEvent(self, file_path, value),
920 .linux => return addFileLinux(self, file_path, value),
921 .windows => return addFileWindows(self, file_path, value),
922 else => @compileError("Unsupported OS"),
923 }
924 }
925
926 fn addFileKEvent(self: *Self, file_path: []const u8, value: V) !?V {
927 const resolved_path = try std.fs.path.resolve(self.allocator, [_][]const u8{file_path});
928 var resolved_path_consumed = false;
929 defer if (!resolved_path_consumed) self.allocator.free(resolved_path);
930
931 var close_op = try CloseOperation.start(self.allocator);
932 var close_op_consumed = false;
933 defer if (!close_op_consumed) close_op.finish();
934
935 const flags = if (comptime std.Target.current.isDarwin()) os.O_SYMLINK | os.O_EVTONLY else 0;
936 const mode = 0;
937 const fd = try openPosix(self.allocator, resolved_path, flags, mode);
938 close_op.setHandle(fd);
939
940 var put = try self.allocator.create(OsData.Put);
941 errdefer self.allocator.destroy(put);
942 put.* = OsData.Put{
943 .value = value,
944 .putter_frame = undefined,
945 };
946 put.putter_frame = async self.kqPutEvents(close_op, put);
947 close_op_consumed = true;
948 errdefer {
949 put.cancelled = true;
950 await put.putter_frame;
951 }
952
953 const result = blk: {
954 const held = self.os_data.table_lock.acquire();
955 defer held.release();
956
957 const gop = try self.os_data.file_table.getOrPut(resolved_path);
958 if (gop.found_existing) {
959 const prev_value = gop.kv.value.value;
960 await gop.kv.value.putter_frame;
961 gop.kv.value = put;
962 break :blk prev_value;
963 } else {
964 resolved_path_consumed = true;
965 gop.kv.value = put;
966 break :blk null;
967 }
968 };
969
970 return result;
971 }
972
973 fn kqPutEvents(self: *Self, close_op: *CloseOperation, put: *OsData.Put) void {
974 global_event_loop.beginOneEvent();
975
976 defer {
977 close_op.finish();
978 global_event_loop.finishOneEvent();
979 }
980
981 while (!put.cancelled) {
982 if (global_event_loop.bsdWaitKev(
983 @intCast(usize, close_op.getHandle()),
984 os.EVFILT_VNODE,
985 os.NOTE_WRITE | os.NOTE_DELETE,
986 )) |kev| {
987 // TODO handle EV_ERROR
988 if (kev.fflags & os.NOTE_DELETE != 0) {
989 self.channel.put(Self.Event{
990 .id = Event.Id.Delete,
991 .data = put.value,
992 });
993 } else if (kev.fflags & os.NOTE_WRITE != 0) {
994 self.channel.put(Self.Event{
995 .id = Event.Id.CloseWrite,
996 .data = put.value,
997 });
998 }
999 } else |err| switch (err) {
1000 error.EventNotFound => unreachable,
1001 error.ProcessNotFound => unreachable,
1002 error.Overflow => unreachable,
1003 error.AccessDenied, error.SystemResources => |casted_err| {
1004 self.channel.put(casted_err);
1005 },
1006 }
1007 }
1008 }
1009
1010 fn addFileLinux(self: *Self, file_path: []const u8, value: V) !?V {
1011 const dirname = std.fs.path.dirname(file_path) orelse ".";
1012 const dirname_with_null = try std.cstr.addNullByte(self.allocator, dirname);
1013 var dirname_with_null_consumed = false;
1014 defer if (!dirname_with_null_consumed) self.channel.free(dirname_with_null);
1015
1016 const basename = std.fs.path.basename(file_path);
1017 const basename_with_null = try std.cstr.addNullByte(self.allocator, basename);
1018 var basename_with_null_consumed = false;
1019 defer if (!basename_with_null_consumed) self.allocator.free(basename_with_null);
1020
1021 const wd = try os.inotify_add_watchC(
1022 self.os_data.inotify_fd,
1023 dirname_with_null.ptr,
1024 os.linux.IN_CLOSE_WRITE | os.linux.IN_ONLYDIR | os.linux.IN_EXCL_UNLINK,
1025 );
1026 // wd is either a newly created watch or an existing one.
1027
1028 const held = self.os_data.table_lock.acquire();
1029 defer held.release();
1030
1031 const gop = try self.os_data.wd_table.getOrPut(wd);
1032 if (!gop.found_existing) {
1033 gop.kv.value = OsData.Dir{
1034 .dirname = dirname_with_null,
1035 .file_table = OsData.FileTable.init(self.allocator),
1036 };
1037 dirname_with_null_consumed = true;
1038 }
1039 const dir = &gop.kv.value;
1040
1041 const file_table_gop = try dir.file_table.getOrPut(basename_with_null);
1042 if (file_table_gop.found_existing) {
1043 const prev_value = file_table_gop.kv.value;
1044 file_table_gop.kv.value = value;
1045 return prev_value;
1046 } else {
1047 file_table_gop.kv.value = value;
1048 basename_with_null_consumed = true;
1049 return null;
1050 }
1051 }
1052
1053 fn addFileWindows(self: *Self, file_path: []const u8, value: V) !?V {
1054 // TODO we might need to convert dirname and basename to canonical file paths ("short"?)
1055 const dirname = try std.mem.dupe(self.allocator, u8, std.fs.path.dirname(file_path) orelse ".");
1056 var dirname_consumed = false;
1057 defer if (!dirname_consumed) self.allocator.free(dirname);
1058
1059 const dirname_utf16le = try std.unicode.utf8ToUtf16LeWithNull(self.allocator, dirname);
1060 defer self.allocator.free(dirname_utf16le);
1061
1062 // TODO https://github.com/ziglang/zig/issues/265
1063 const basename = std.fs.path.basename(file_path);
1064 const basename_utf16le_null = try std.unicode.utf8ToUtf16LeWithNull(self.allocator, basename);
1065 var basename_utf16le_null_consumed = false;
1066 defer if (!basename_utf16le_null_consumed) self.allocator.free(basename_utf16le_null);
1067 const basename_utf16le_no_null = basename_utf16le_null[0 .. basename_utf16le_null.len - 1];
1068
1069 const dir_handle = try windows.CreateFileW(
1070 dirname_utf16le.ptr,
1071 windows.FILE_LIST_DIRECTORY,
1072 windows.FILE_SHARE_READ | windows.FILE_SHARE_DELETE | windows.FILE_SHARE_WRITE,
1073 null,
1074 windows.OPEN_EXISTING,
1075 windows.FILE_FLAG_BACKUP_SEMANTICS | windows.FILE_FLAG_OVERLAPPED,
1076 null,
1077 );
1078 var dir_handle_consumed = false;
1079 defer if (!dir_handle_consumed) windows.CloseHandle(dir_handle);
1080
1081 const held = self.os_data.table_lock.acquire();
1082 defer held.release();
1083
1084 const gop = try self.os_data.dir_table.getOrPut(dirname);
1085 if (gop.found_existing) {
1086 const dir = gop.kv.value;
1087 const held_dir_lock = dir.table_lock.acquire();
1088 defer held_dir_lock.release();
1089
1090 const file_gop = try dir.file_table.getOrPut(basename_utf16le_no_null);
1091 if (file_gop.found_existing) {
1092 const prev_value = file_gop.kv.value;
1093 file_gop.kv.value = value;
1094 return prev_value;
1095 } else {
1096 file_gop.kv.value = value;
1097 basename_utf16le_null_consumed = true;
1098 return null;
1099 }
1100 } else {
1101 errdefer _ = self.os_data.dir_table.remove(dirname);
1102 const dir = try self.allocator.create(OsData.Dir);
1103 errdefer self.allocator.destroy(dir);
1104
1105 dir.* = OsData.Dir{
1106 .file_table = OsData.FileTable.init(self.allocator),
1107 .table_lock = event.Lock.init(),
1108 .putter_frame = undefined,
1109 };
1110 gop.kv.value = dir;
1111 assert((try dir.file_table.put(basename_utf16le_no_null, value)) == null);
1112 basename_utf16le_null_consumed = true;
1113
1114 dir.putter_frame = async self.windowsDirReader(dir_handle, dir);
1115 dir_handle_consumed = true;
1116
1117 dirname_consumed = true;
1118
1119 return null;
1120 }
1121 }
1122
1123 fn windowsDirReader(self: *Self, dir_handle: windows.HANDLE, dir: *OsData.Dir) void {
1124 self.ref();
1125 defer self.deref();
1126
1127 defer os.close(dir_handle);
1128
1129 var putter_node = std.atomic.Queue(anyframe).Node{
1130 .data = .{ .putter = @frame() },
1131 .prev = null,
1132 .next = null,
1133 };
1134 self.os_data.all_putters.put(&putter_node);
1135 defer _ = self.os_data.all_putters.remove(&putter_node);
1136
1137 var resume_node = Loop.ResumeNode.Basic{
1138 .base = Loop.ResumeNode{
1139 .id = Loop.ResumeNode.Id.Basic,
1140 .handle = @frame(),
1141 .overlapped = windows.OVERLAPPED{
1142 .Internal = 0,
1143 .InternalHigh = 0,
1144 .Offset = 0,
1145 .OffsetHigh = 0,
1146 .hEvent = null,
1147 },
1148 },
1149 };
1150 var event_buf: [4096]u8 align(@alignOf(windows.FILE_NOTIFY_INFORMATION)) = undefined;
1151
1152 // TODO handle this error not in the channel but in the setup
1153 _ = windows.CreateIoCompletionPort(
1154 dir_handle,
1155 global_event_loop.os_data.io_port,
1156 undefined,
1157 undefined,
1158 ) catch |err| {
1159 self.channel.put(err);
1160 return;
1161 };
1162
1163 while (!putter_node.data.cancelled) {
1164 {
1165 // TODO only 1 beginOneEvent for the whole function
1166 global_event_loop.beginOneEvent();
1167 errdefer global_event_loop.finishOneEvent();
1168 errdefer {
1169 _ = windows.kernel32.CancelIoEx(dir_handle, &resume_node.base.overlapped);
1170 }
1171 suspend {
1172 _ = windows.kernel32.ReadDirectoryChangesW(
1173 dir_handle,
1174 &event_buf,
1175 @intCast(windows.DWORD, event_buf.len),
1176 windows.FALSE, // watch subtree
1177 windows.FILE_NOTIFY_CHANGE_FILE_NAME | windows.FILE_NOTIFY_CHANGE_DIR_NAME |
1178 windows.FILE_NOTIFY_CHANGE_ATTRIBUTES | windows.FILE_NOTIFY_CHANGE_SIZE |
1179 windows.FILE_NOTIFY_CHANGE_LAST_WRITE | windows.FILE_NOTIFY_CHANGE_LAST_ACCESS |
1180 windows.FILE_NOTIFY_CHANGE_CREATION | windows.FILE_NOTIFY_CHANGE_SECURITY,
1181 null, // number of bytes transferred (unused for async)
1182 &resume_node.base.overlapped,
1183 null, // completion routine - unused because we use IOCP
1184 );
1185 }
1186 }
1187 var bytes_transferred: windows.DWORD = undefined;
1188 if (windows.kernel32.GetOverlappedResult(dir_handle, &resume_node.base.overlapped, &bytes_transferred, windows.FALSE) == 0) {
1189 const err = switch (windows.kernel32.GetLastError()) {
1190 else => |err| windows.unexpectedError(err),
1191 };
1192 self.channel.put(err);
1193 } else {
1194 // can't use @bytesToSlice because of the special variable length name field
1195 var ptr = event_buf[0..].ptr;
1196 const end_ptr = ptr + bytes_transferred;
1197 var ev: *windows.FILE_NOTIFY_INFORMATION = undefined;
1198 while (@ptrToInt(ptr) < @ptrToInt(end_ptr)) : (ptr += ev.NextEntryOffset) {
1199 ev = @ptrCast(*windows.FILE_NOTIFY_INFORMATION, ptr);
1200 const emit = switch (ev.Action) {
1201 windows.FILE_ACTION_REMOVED => WatchEventId.Delete,
1202 windows.FILE_ACTION_MODIFIED => WatchEventId.CloseWrite,
1203 else => null,
1204 };
1205 if (emit) |id| {
1206 const basename_utf16le = ([*]u16)(&ev.FileName)[0 .. ev.FileNameLength / 2];
1207 const user_value = blk: {
1208 const held = dir.table_lock.acquire();
1209 defer held.release();
1210
1211 if (dir.file_table.get(basename_utf16le)) |entry| {
1212 break :blk entry.value;
1213 } else {
1214 break :blk null;
1215 }
1216 };
1217 if (user_value) |v| {
1218 self.channel.put(Event{
1219 .id = id,
1220 .data = v,
1221 });
1222 }
1223 }
1224 if (ev.NextEntryOffset == 0) break;
1225 }
1226 }
1227 }
1228 }
1229
1230 pub fn removeFile(self: *Self, file_path: []const u8) ?V {
1231 @panic("TODO");
1232 }
1233
1234 fn linuxEventPutter(self: *Self) void {
1235 global_event_loop.beginOneEvent();
1236
1237 defer {
1238 self.os_data.table_lock.deinit();
1239 var wd_it = self.os_data.wd_table.iterator();
1240 while (wd_it.next()) |wd_entry| {
1241 var file_it = wd_entry.value.file_table.iterator();
1242 while (file_it.next()) |file_entry| {
1243 self.allocator.free(file_entry.key);
1244 }
1245 self.allocator.free(wd_entry.value.dirname);
1246 wd_entry.value.file_table.deinit();
1247 }
1248 self.os_data.wd_table.deinit();
1249 global_event_loop.finishOneEvent();
1250 os.close(self.os_data.inotify_fd);
1251 self.channel.deinit();
1252 self.allocator.free(self.channel.buffer_nodes);
1253 }
1254
1255 var event_buf: [4096]u8 align(@alignOf(os.linux.inotify_event)) = undefined;
1256
1257 while (!self.os_data.cancelled) {
1258 const rc = os.linux.read(self.os_data.inotify_fd, &event_buf, event_buf.len);
1259 const errno = os.linux.getErrno(rc);
1260 switch (errno) {
1261 0 => {
1262 // can't use @bytesToSlice because of the special variable length name field
1263 var ptr = event_buf[0..].ptr;
1264 const end_ptr = ptr + event_buf.len;
1265 var ev: *os.linux.inotify_event = undefined;
1266 while (@ptrToInt(ptr) < @ptrToInt(end_ptr)) {
1267 ev = @ptrCast(*os.linux.inotify_event, ptr);
1268 if (ev.mask & os.linux.IN_CLOSE_WRITE == os.linux.IN_CLOSE_WRITE) {
1269 const basename_ptr = ptr + @sizeOf(os.linux.inotify_event);
1270 // `ev.len` counts all bytes in `ev.name` including terminating null byte.
1271 const basename_with_null = basename_ptr[0..ev.len];
1272 const user_value = blk: {
1273 const held = self.os_data.table_lock.acquire();
1274 defer held.release();
1275
1276 const dir = &self.os_data.wd_table.get(ev.wd).?.value;
1277 if (dir.file_table.get(basename_with_null)) |entry| {
1278 break :blk entry.value;
1279 } else {
1280 break :blk null;
1281 }
1282 };
1283 if (user_value) |v| {
1284 self.channel.put(Event{
1285 .id = WatchEventId.CloseWrite,
1286 .data = v,
1287 });
1288 }
1289 }
1290
1291 ptr = @alignCast(@alignOf(os.linux.inotify_event), ptr + @sizeOf(os.linux.inotify_event) + ev.len);
1292 }
1293 },
1294 os.linux.EINTR => continue,
1295 os.linux.EINVAL => unreachable,
1296 os.linux.EFAULT => unreachable,
1297 os.linux.EAGAIN => {
1298 global_event_loop.linuxWaitFd(self.os_data.inotify_fd, os.linux.EPOLLET | os.linux.EPOLLIN | os.EPOLLONESHOT);
1299 },
1300 else => unreachable,
1301 }
1302 }
1303 }
1304 };
1305}
1306
1307const test_tmp_dir = "std_event_fs_test";
1308
1309test "write a file, watch it, write it again" {
1310 // TODO provide a way to run tests in evented I/O mode
1311 if (!std.io.is_async) return error.SkipZigTest;
1312
1313 const allocator = std.heap.page_allocator;
1314
1315 // TODO move this into event loop too
1316 try os.makePath(allocator, test_tmp_dir);
1317 defer os.deleteTree(test_tmp_dir) catch {};
1318
1319 return testFsWatch(&allocator);
1320}
1321
1322fn testFsWatch(allocator: *Allocator) !void {
1323 const file_path = try std.fs.path.join(allocator, [_][]const u8{ test_tmp_dir, "file.txt" });
1324 defer allocator.free(file_path);
1325
1326 const contents =
1327 \\line 1
1328 \\line 2
1329 ;
1330 const line2_offset = 7;
1331
1332 // first just write then read the file
1333 try writeFile(allocator, file_path, contents);
1334
1335 const read_contents = try readFile(allocator, file_path, 1024 * 1024);
1336 testing.expectEqualSlices(u8, contents, read_contents);
1337
1338 // now watch the file
1339 var watch = try Watch(void).init(allocator, 0);
1340 defer watch.deinit();
1341
1342 testing.expect((try watch.addFile(file_path, {})) == null);
1343
1344 const ev = watch.channel.get();
1345 var ev_consumed = false;
1346 defer if (!ev_consumed) await ev;
1347
1348 // overwrite line 2
1349 const fd = try await openReadWrite(file_path, File.default_mode);
1350 {
1351 defer os.close(fd);
1352
1353 try pwritev(allocator, fd, []const []const u8{"lorem ipsum"}, line2_offset);
1354 }
1355
1356 ev_consumed = true;
1357 switch ((try await ev).id) {
1358 WatchEventId.CloseWrite => {},
1359 WatchEventId.Delete => @panic("wrong event"),
1360 }
1361 const contents_updated = try readFile(allocator, file_path, 1024 * 1024);
1362 testing.expectEqualSlices(u8,
1363 \\line 1
1364 \\lorem ipsum
1365 , contents_updated);
1366
1367 // TODO test deleting the file and then re-adding it. we should get events for both
1368}
1369
1370pub const OutStream = struct {
1371 fd: fd_t,
1372 stream: Stream,
1373 allocator: *Allocator,
1374 offset: usize,
1375
1376 pub const Error = File.WriteError;
1377 pub const Stream = event.io.OutStream(Error);
1378
1379 pub fn init(allocator: *Allocator, fd: fd_t, offset: usize) OutStream {
1380 return OutStream{
1381 .fd = fd,
1382 .offset = offset,
1383 .stream = Stream{ .writeFn = writeFn },
1384 };
1385 }
1386
1387 fn writeFn(out_stream: *Stream, bytes: []const u8) Error!void {
1388 const self = @fieldParentPtr(OutStream, "stream", out_stream);
1389 const offset = self.offset;
1390 self.offset += bytes.len;
1391 return pwritev(self.allocator, self.fd, [_][]const u8{bytes}, offset);
1392 }
1393};
1394
1395pub const InStream = struct {
1396 fd: fd_t,
1397 stream: Stream,
1398 allocator: *Allocator,
1399 offset: usize,
1400
1401 pub const Error = PReadVError; // TODO make this not have OutOfMemory
1402 pub const Stream = event.io.InStream(Error);
1403
1404 pub fn init(allocator: *Allocator, fd: fd_t, offset: usize) InStream {
1405 return InStream{
1406 .fd = fd,
1407 .offset = offset,
1408 .stream = Stream{ .readFn = readFn },
1409 };
1410 }
1411
1412 fn readFn(in_stream: *Stream, bytes: []u8) Error!usize {
1413 const self = @fieldParentPtr(InStream, "stream", in_stream);
1414 const amt = try preadv(self.allocator, self.fd, [_][]u8{bytes}, self.offset);
1415 self.offset += amt;
1416 return amt;
1417 }
1418};
lib/std/event/group.zig+1-1
......@@ -22,7 +22,7 @@ pub fn Group(comptime ReturnType: type) type {
2222 const AllocStack = std.atomic.Stack(Node);
2323
2424 pub const Node = struct {
25 bytes: []const u8 = [0]u8{},
25 bytes: []const u8 = &[0]u8{},
2626 handle: anyframe->ReturnType,
2727 };
2828
lib/std/event/lock.zig+4-4
......@@ -117,21 +117,21 @@ pub const Lock = struct {
117117};
118118
119119test "std.event.Lock" {
120 if (!std.io.is_async) return error.SkipZigTest;
121
120122 // TODO https://github.com/ziglang/zig/issues/1908
121123 if (builtin.single_threaded) return error.SkipZigTest;
122124
123125 // TODO https://github.com/ziglang/zig/issues/3251
124126 if (builtin.os == .freebsd) return error.SkipZigTest;
125127
126 // TODO provide a way to run tests in evented I/O mode
127 if (!std.io.is_async) return error.SkipZigTest;
128
129128 var lock = Lock.init();
130129 defer lock.deinit();
131130
132131 _ = async testLock(&lock);
133132
134 testing.expectEqualSlices(i32, [1]i32{3 * @intCast(i32, shared_test_data.len)} ** shared_test_data.len, shared_test_data);
133 const expected_result = [1]i32{3 * @intCast(i32, shared_test_data.len)} ** shared_test_data.len;
134 testing.expectEqualSlices(i32, &expected_result, &shared_test_data);
135135}
136136
137137async fn testLock(lock: *Lock) void {
lib/std/event/loop.zig+317-50
......@@ -6,7 +6,6 @@ const testing = std.testing;
66const mem = std.mem;
77const AtomicRmwOp = builtin.AtomicRmwOp;
88const AtomicOrder = builtin.AtomicOrder;
9const fs = std.event.fs;
109const os = std.os;
1110const windows = os.windows;
1211const maxInt = std.math.maxInt;
......@@ -174,21 +173,19 @@ pub const Loop = struct {
174173 fn initOsData(self: *Loop, extra_thread_count: usize) InitOsDataError!void {
175174 switch (builtin.os) {
176175 .linux => {
177 self.os_data.fs_queue = std.atomic.Queue(fs.Request).init();
176 self.os_data.fs_queue = std.atomic.Queue(Request).init();
178177 self.os_data.fs_queue_item = 0;
179178 // we need another thread for the file system because Linux does not have an async
180179 // file system I/O API.
181 self.os_data.fs_end_request = fs.RequestNode{
182 .prev = undefined,
183 .next = undefined,
184 .data = fs.Request{
185 .msg = fs.Request.Msg.End,
186 .finish = fs.Request.Finish.NoAction,
180 self.os_data.fs_end_request = Request.Node{
181 .data = Request{
182 .msg = .end,
183 .finish = .NoAction,
187184 },
188185 };
189186
190187 errdefer {
191 while (self.available_eventfd_resume_nodes.pop()) |node| os.close(node.data.eventfd);
188 while (self.available_eventfd_resume_nodes.pop()) |node| noasync os.close(node.data.eventfd);
192189 }
193190 for (self.eventfd_resume_nodes) |*eventfd_node| {
194191 eventfd_node.* = std.atomic.Stack(ResumeNode.EventFd).Node{
......@@ -207,10 +204,10 @@ pub const Loop = struct {
207204 }
208205
209206 self.os_data.epollfd = try os.epoll_create1(os.EPOLL_CLOEXEC);
210 errdefer os.close(self.os_data.epollfd);
207 errdefer noasync os.close(self.os_data.epollfd);
211208
212209 self.os_data.final_eventfd = try os.eventfd(0, os.EFD_CLOEXEC | os.EFD_NONBLOCK);
213 errdefer os.close(self.os_data.final_eventfd);
210 errdefer noasync os.close(self.os_data.final_eventfd);
214211
215212 self.os_data.final_eventfd_event = os.epoll_event{
216213 .events = os.EPOLLIN,
......@@ -237,7 +234,7 @@ pub const Loop = struct {
237234 var extra_thread_index: usize = 0;
238235 errdefer {
239236 // writing 8 bytes to an eventfd cannot fail
240 os.write(self.os_data.final_eventfd, &wakeup_bytes) catch unreachable;
237 noasync os.write(self.os_data.final_eventfd, &wakeup_bytes) catch unreachable;
241238 while (extra_thread_index != 0) {
242239 extra_thread_index -= 1;
243240 self.extra_threads[extra_thread_index].wait();
......@@ -249,20 +246,20 @@ pub const Loop = struct {
249246 },
250247 .macosx, .freebsd, .netbsd, .dragonfly => {
251248 self.os_data.kqfd = try os.kqueue();
252 errdefer os.close(self.os_data.kqfd);
249 errdefer noasync os.close(self.os_data.kqfd);
253250
254251 self.os_data.fs_kqfd = try os.kqueue();
255 errdefer os.close(self.os_data.fs_kqfd);
252 errdefer noasync os.close(self.os_data.fs_kqfd);
256253
257 self.os_data.fs_queue = std.atomic.Queue(fs.Request).init();
254 self.os_data.fs_queue = std.atomic.Queue(Request).init();
258255 // we need another thread for the file system because Darwin does not have an async
259256 // file system I/O API.
260 self.os_data.fs_end_request = fs.RequestNode{
257 self.os_data.fs_end_request = Request.Node{
261258 .prev = undefined,
262259 .next = undefined,
263 .data = fs.Request{
264 .msg = fs.Request.Msg.End,
265 .finish = fs.Request.Finish.NoAction,
260 .data = Request{
261 .msg = .end,
262 .finish = .NoAction,
266263 },
267264 };
268265
......@@ -407,14 +404,14 @@ pub const Loop = struct {
407404 fn deinitOsData(self: *Loop) void {
408405 switch (builtin.os) {
409406 .linux => {
410 os.close(self.os_data.final_eventfd);
411 while (self.available_eventfd_resume_nodes.pop()) |node| os.close(node.data.eventfd);
412 os.close(self.os_data.epollfd);
407 noasync os.close(self.os_data.final_eventfd);
408 while (self.available_eventfd_resume_nodes.pop()) |node| noasync os.close(node.data.eventfd);
409 noasync os.close(self.os_data.epollfd);
413410 self.allocator.free(self.eventfd_resume_nodes);
414411 },
415412 .macosx, .freebsd, .netbsd, .dragonfly => {
416 os.close(self.os_data.kqfd);
417 os.close(self.os_data.fs_kqfd);
413 noasync os.close(self.os_data.kqfd);
414 noasync os.close(self.os_data.fs_kqfd);
418415 },
419416 .windows => {
420417 windows.CloseHandle(self.os_data.io_port);
......@@ -711,6 +708,190 @@ pub const Loop = struct {
711708 }
712709 }
713710
711 /// Performs an async `os.open` using a separate thread.
712 pub fn openZ(self: *Loop, file_path: [*:0]const u8, flags: u32, mode: usize) os.OpenError!os.fd_t {
713 var req_node = Request.Node{
714 .data = .{
715 .msg = .{
716 .open = .{
717 .path = file_path,
718 .flags = flags,
719 .mode = mode,
720 .result = undefined,
721 },
722 },
723 .finish = .{ .TickNode = .{ .data = @frame() } },
724 },
725 };
726 suspend {
727 self.posixFsRequest(&req_node);
728 }
729 return req_node.data.msg.open.result;
730 }
731
732 /// Performs an async `os.opent` using a separate thread.
733 pub fn openatZ(self: *Loop, fd: os.fd_t, file_path: [*:0]const u8, flags: u32, mode: usize) os.OpenError!os.fd_t {
734 var req_node = Request.Node{
735 .data = .{
736 .msg = .{
737 .openat = .{
738 .fd = fd,
739 .path = file_path,
740 .flags = flags,
741 .mode = mode,
742 .result = undefined,
743 },
744 },
745 .finish = .{ .TickNode = .{ .data = @frame() } },
746 },
747 };
748 suspend {
749 self.posixFsRequest(&req_node);
750 }
751 return req_node.data.msg.openat.result;
752 }
753
754 /// Performs an async `os.close` using a separate thread.
755 pub fn close(self: *Loop, fd: os.fd_t) void {
756 var req_node = Request.Node{
757 .data = .{
758 .msg = .{ .close = .{ .fd = fd } },
759 .finish = .{ .TickNode = .{ .data = @frame() } },
760 },
761 };
762 suspend {
763 self.posixFsRequest(&req_node);
764 }
765 }
766
767 /// Performs an async `os.read` using a separate thread.
768 /// `fd` must block and not return EAGAIN.
769 pub fn read(self: *Loop, fd: os.fd_t, buf: []u8) os.ReadError!usize {
770 var req_node = Request.Node{
771 .data = .{
772 .msg = .{
773 .read = .{
774 .fd = fd,
775 .buf = buf,
776 .result = undefined,
777 },
778 },
779 .finish = .{ .TickNode = .{ .data = @frame() } },
780 },
781 };
782 suspend {
783 self.posixFsRequest(&req_node);
784 }
785 return req_node.data.msg.read.result;
786 }
787
788 /// Performs an async `os.readv` using a separate thread.
789 /// `fd` must block and not return EAGAIN.
790 pub fn readv(self: *Loop, fd: os.fd_t, iov: []const os.iovec) os.ReadError!usize {
791 var req_node = Request.Node{
792 .data = .{
793 .msg = .{
794 .readv = .{
795 .fd = fd,
796 .iov = iov,
797 .result = undefined,
798 },
799 },
800 .finish = .{ .TickNode = .{ .data = @frame() } },
801 },
802 };
803 suspend {
804 self.posixFsRequest(&req_node);
805 }
806 return req_node.data.msg.readv.result;
807 }
808
809 /// Performs an async `os.preadv` using a separate thread.
810 /// `fd` must block and not return EAGAIN.
811 pub fn preadv(self: *Loop, fd: os.fd_t, iov: []const os.iovec, offset: u64) os.ReadError!usize {
812 var req_node = Request.Node{
813 .data = .{
814 .msg = .{
815 .preadv = .{
816 .fd = fd,
817 .iov = iov,
818 .offset = offset,
819 .result = undefined,
820 },
821 },
822 .finish = .{ .TickNode = .{ .data = @frame() } },
823 },
824 };
825 suspend {
826 self.posixFsRequest(&req_node);
827 }
828 return req_node.data.msg.preadv.result;
829 }
830
831 /// Performs an async `os.write` using a separate thread.
832 /// `fd` must block and not return EAGAIN.
833 pub fn write(self: *Loop, fd: os.fd_t, bytes: []const u8) os.WriteError!void {
834 var req_node = Request.Node{
835 .data = .{
836 .msg = .{
837 .write = .{
838 .fd = fd,
839 .bytes = bytes,
840 .result = undefined,
841 },
842 },
843 .finish = .{ .TickNode = .{ .data = @frame() } },
844 },
845 };
846 suspend {
847 self.posixFsRequest(&req_node);
848 }
849 return req_node.data.msg.write.result;
850 }
851
852 /// Performs an async `os.writev` using a separate thread.
853 /// `fd` must block and not return EAGAIN.
854 pub fn writev(self: *Loop, fd: os.fd_t, iov: []const os.iovec_const) os.WriteError!void {
855 var req_node = Request.Node{
856 .data = .{
857 .msg = .{
858 .writev = .{
859 .fd = fd,
860 .iov = iov,
861 .result = undefined,
862 },
863 },
864 .finish = .{ .TickNode = .{ .data = @frame() } },
865 },
866 };
867 suspend {
868 self.posixFsRequest(&req_node);
869 }
870 return req_node.data.msg.writev.result;
871 }
872
873 /// Performs an async `os.pwritev` using a separate thread.
874 /// `fd` must block and not return EAGAIN.
875 pub fn pwritev(self: *Loop, fd: os.fd_t, iov: []const os.iovec_const, offset: u64) os.WriteError!void {
876 var req_node = Request.Node{
877 .data = .{
878 .msg = .{
879 .pwritev = .{
880 .fd = fd,
881 .iov = iov,
882 .offset = offset,
883 .result = undefined,
884 },
885 },
886 .finish = .{ .TickNode = .{ .data = @frame() } },
887 },
888 };
889 suspend {
890 self.posixFsRequest(&req_node);
891 }
892 return req_node.data.msg.pwritev.result;
893 }
894
714895 fn workerRun(self: *Loop) void {
715896 while (true) {
716897 while (true) {
......@@ -804,7 +985,7 @@ pub const Loop = struct {
804985 }
805986 }
806987
807 fn posixFsRequest(self: *Loop, request_node: *fs.RequestNode) void {
988 fn posixFsRequest(self: *Loop, request_node: *Request.Node) void {
808989 self.beginOneEvent(); // finished in posixFsRun after processing the msg
809990 self.os_data.fs_queue.put(request_node);
810991 switch (builtin.os) {
......@@ -826,7 +1007,7 @@ pub const Loop = struct {
8261007 }
8271008 }
8281009
829 fn posixFsCancel(self: *Loop, request_node: *fs.RequestNode) void {
1010 fn posixFsCancel(self: *Loop, request_node: *Request.Node) void {
8301011 if (self.os_data.fs_queue.remove(request_node)) {
8311012 self.finishOneEvent();
8321013 }
......@@ -841,37 +1022,32 @@ pub const Loop = struct {
8411022 }
8421023 while (self.os_data.fs_queue.get()) |node| {
8431024 switch (node.data.msg) {
844 .End => return,
845 .WriteV => |*msg| {
1025 .end => return,
1026 .read => |*msg| {
1027 msg.result = noasync os.read(msg.fd, msg.buf);
1028 },
1029 .write => |*msg| {
1030 msg.result = noasync os.write(msg.fd, msg.bytes);
1031 },
1032 .writev => |*msg| {
8461033 msg.result = noasync os.writev(msg.fd, msg.iov);
8471034 },
848 .PWriteV => |*msg| {
1035 .pwritev => |*msg| {
8491036 msg.result = noasync os.pwritev(msg.fd, msg.iov, msg.offset);
8501037 },
851 .PReadV => |*msg| {
1038 .preadv => |*msg| {
8521039 msg.result = noasync os.preadv(msg.fd, msg.iov, msg.offset);
8531040 },
854 .Open => |*msg| {
855 msg.result = noasync os.openC(msg.path.ptr, msg.flags, msg.mode);
1041 .open => |*msg| {
1042 msg.result = noasync os.openC(msg.path, msg.flags, msg.mode);
8561043 },
857 .Close => |*msg| noasync os.close(msg.fd),
858 .WriteFile => |*msg| blk: {
859 const O_LARGEFILE = if (@hasDecl(os, "O_LARGEFILE")) os.O_LARGEFILE else 0;
860 const flags = O_LARGEFILE | os.O_WRONLY | os.O_CREAT |
861 os.O_CLOEXEC | os.O_TRUNC;
862 const fd = noasync os.openC(msg.path.ptr, flags, msg.mode) catch |err| {
863 msg.result = err;
864 break :blk;
865 };
866 defer noasync os.close(fd);
867 msg.result = noasync os.write(fd, msg.contents);
1044 .openat => |*msg| {
1045 msg.result = noasync os.openatC(msg.fd, msg.path, msg.flags, msg.mode);
8681046 },
1047 .close => |*msg| noasync os.close(msg.fd),
8691048 }
8701049 switch (node.data.finish) {
8711050 .TickNode => |*tick_node| self.onNextTick(tick_node),
872 .DeallocCloseOperation => |close_op| {
873 self.allocator.destroy(close_op);
874 },
8751051 .NoAction => {},
8761052 }
8771053 self.finishOneEvent();
......@@ -911,8 +1087,8 @@ pub const Loop = struct {
9111087 fs_kevent_wait: os.Kevent,
9121088 fs_thread: *Thread,
9131089 fs_kqfd: i32,
914 fs_queue: std.atomic.Queue(fs.Request),
915 fs_end_request: fs.RequestNode,
1090 fs_queue: std.atomic.Queue(Request),
1091 fs_end_request: Request.Node,
9161092 };
9171093
9181094 const LinuxOsData = struct {
......@@ -921,8 +1097,99 @@ pub const Loop = struct {
9211097 final_eventfd_event: os.linux.epoll_event,
9221098 fs_thread: *Thread,
9231099 fs_queue_item: i32,
924 fs_queue: std.atomic.Queue(fs.Request),
925 fs_end_request: fs.RequestNode,
1100 fs_queue: std.atomic.Queue(Request),
1101 fs_end_request: Request.Node,
1102 };
1103
1104 pub const Request = struct {
1105 msg: Msg,
1106 finish: Finish,
1107
1108 pub const Node = std.atomic.Queue(Request).Node;
1109
1110 pub const Finish = union(enum) {
1111 TickNode: Loop.NextTickNode,
1112 NoAction,
1113 };
1114
1115 pub const Msg = union(enum) {
1116 read: Read,
1117 write: Write,
1118 writev: WriteV,
1119 pwritev: PWriteV,
1120 preadv: PReadV,
1121 open: Open,
1122 openat: OpenAt,
1123 close: Close,
1124
1125 /// special - means the fs thread should exit
1126 end,
1127
1128 pub const Read = struct {
1129 fd: os.fd_t,
1130 buf: []u8,
1131 result: Error!usize,
1132
1133 pub const Error = os.ReadError;
1134 };
1135
1136 pub const Write = struct {
1137 fd: os.fd_t,
1138 bytes: []const u8,
1139 result: Error!void,
1140
1141 pub const Error = os.WriteError;
1142 };
1143
1144 pub const WriteV = struct {
1145 fd: os.fd_t,
1146 iov: []const os.iovec_const,
1147 result: Error!void,
1148
1149 pub const Error = os.WriteError;
1150 };
1151
1152 pub const PWriteV = struct {
1153 fd: os.fd_t,
1154 iov: []const os.iovec_const,
1155 offset: usize,
1156 result: Error!void,
1157
1158 pub const Error = os.WriteError;
1159 };
1160
1161 pub const PReadV = struct {
1162 fd: os.fd_t,
1163 iov: []const os.iovec,
1164 offset: usize,
1165 result: Error!usize,
1166
1167 pub const Error = os.ReadError;
1168 };
1169
1170 pub const Open = struct {
1171 path: [*:0]const u8,
1172 flags: u32,
1173 mode: os.mode_t,
1174 result: Error!os.fd_t,
1175
1176 pub const Error = os.OpenError;
1177 };
1178
1179 pub const OpenAt = struct {
1180 fd: os.fd_t,
1181 path: [*:0]const u8,
1182 flags: u32,
1183 mode: os.mode_t,
1184 result: Error!os.fd_t,
1185
1186 pub const Error = os.OpenError;
1187 };
1188
1189 pub const Close = struct {
1190 fd: os.fd_t,
1191 };
1192 };
9261193 };
9271194};
9281195
lib/std/fmt.zig+16-16
......@@ -78,7 +78,7 @@ fn peekIsAlign(comptime fmt: []const u8) bool {
7878pub fn format(
7979 context: var,
8080 comptime Errors: type,
81 output: fn (@TypeOf(context), []const u8) Errors!void,
81 comptime output: fn (@TypeOf(context), []const u8) Errors!void,
8282 comptime fmt: []const u8,
8383 args: var,
8484) Errors!void {
......@@ -326,7 +326,7 @@ pub fn formatType(
326326 options: FormatOptions,
327327 context: var,
328328 comptime Errors: type,
329 output: fn (@TypeOf(context), []const u8) Errors!void,
329 comptime output: fn (@TypeOf(context), []const u8) Errors!void,
330330 max_depth: usize,
331331) Errors!void {
332332 if (comptime std.mem.eql(u8, fmt, "*")) {
......@@ -488,7 +488,7 @@ fn formatValue(
488488 options: FormatOptions,
489489 context: var,
490490 comptime Errors: type,
491 output: fn (@TypeOf(context), []const u8) Errors!void,
491 comptime output: fn (@TypeOf(context), []const u8) Errors!void,
492492) Errors!void {
493493 if (comptime std.mem.eql(u8, fmt, "B")) {
494494 return formatBytes(value, options, 1000, context, Errors, output);
......@@ -510,7 +510,7 @@ pub fn formatIntValue(
510510 options: FormatOptions,
511511 context: var,
512512 comptime Errors: type,
513 output: fn (@TypeOf(context), []const u8) Errors!void,
513 comptime output: fn (@TypeOf(context), []const u8) Errors!void,
514514) Errors!void {
515515 comptime var radix = 10;
516516 comptime var uppercase = false;
......@@ -552,7 +552,7 @@ fn formatFloatValue(
552552 options: FormatOptions,
553553 context: var,
554554 comptime Errors: type,
555 output: fn (@TypeOf(context), []const u8) Errors!void,
555 comptime output: fn (@TypeOf(context), []const u8) Errors!void,
556556) Errors!void {
557557 if (fmt.len == 0 or comptime std.mem.eql(u8, fmt, "e")) {
558558 return formatFloatScientific(value, options, context, Errors, output);
......@@ -569,7 +569,7 @@ pub fn formatText(
569569 options: FormatOptions,
570570 context: var,
571571 comptime Errors: type,
572 output: fn (@TypeOf(context), []const u8) Errors!void,
572 comptime output: fn (@TypeOf(context), []const u8) Errors!void,
573573) Errors!void {
574574 if (fmt.len == 0) {
575575 return output(context, bytes);
......@@ -590,7 +590,7 @@ pub fn formatAsciiChar(
590590 options: FormatOptions,
591591 context: var,
592592 comptime Errors: type,
593 output: fn (@TypeOf(context), []const u8) Errors!void,
593 comptime output: fn (@TypeOf(context), []const u8) Errors!void,
594594) Errors!void {
595595 return output(context, @as(*const [1]u8, &c)[0..]);
596596}
......@@ -600,7 +600,7 @@ pub fn formatBuf(
600600 options: FormatOptions,
601601 context: var,
602602 comptime Errors: type,
603 output: fn (@TypeOf(context), []const u8) Errors!void,
603 comptime output: fn (@TypeOf(context), []const u8) Errors!void,
604604) Errors!void {
605605 try output(context, buf);
606606
......@@ -620,7 +620,7 @@ pub fn formatFloatScientific(
620620 options: FormatOptions,
621621 context: var,
622622 comptime Errors: type,
623 output: fn (@TypeOf(context), []const u8) Errors!void,
623 comptime output: fn (@TypeOf(context), []const u8) Errors!void,
624624) Errors!void {
625625 var x = @floatCast(f64, value);
626626
......@@ -715,7 +715,7 @@ pub fn formatFloatDecimal(
715715 options: FormatOptions,
716716 context: var,
717717 comptime Errors: type,
718 output: fn (@TypeOf(context), []const u8) Errors!void,
718 comptime output: fn (@TypeOf(context), []const u8) Errors!void,
719719) Errors!void {
720720 var x = @as(f64, value);
721721
......@@ -861,7 +861,7 @@ pub fn formatBytes(
861861 comptime radix: usize,
862862 context: var,
863863 comptime Errors: type,
864 output: fn (@TypeOf(context), []const u8) Errors!void,
864 comptime output: fn (@TypeOf(context), []const u8) Errors!void,
865865) Errors!void {
866866 if (value == 0) {
867867 return output(context, "0B");
......@@ -902,7 +902,7 @@ pub fn formatInt(
902902 options: FormatOptions,
903903 context: var,
904904 comptime Errors: type,
905 output: fn (@TypeOf(context), []const u8) Errors!void,
905 comptime output: fn (@TypeOf(context), []const u8) Errors!void,
906906) Errors!void {
907907 const int_value = if (@TypeOf(value) == comptime_int) blk: {
908908 const Int = math.IntFittingRange(value, value);
......@@ -924,7 +924,7 @@ fn formatIntSigned(
924924 options: FormatOptions,
925925 context: var,
926926 comptime Errors: type,
927 output: fn (@TypeOf(context), []const u8) Errors!void,
927 comptime output: fn (@TypeOf(context), []const u8) Errors!void,
928928) Errors!void {
929929 const new_options = FormatOptions{
930930 .width = if (options.width) |w| (if (w == 0) 0 else w - 1) else null,
......@@ -955,7 +955,7 @@ fn formatIntUnsigned(
955955 options: FormatOptions,
956956 context: var,
957957 comptime Errors: type,
958 output: fn (@TypeOf(context), []const u8) Errors!void,
958 comptime output: fn (@TypeOf(context), []const u8) Errors!void,
959959) Errors!void {
960960 assert(base >= 2);
961961 var buf: [math.max(@TypeOf(value).bit_count, 1)]u8 = undefined;
......@@ -1419,7 +1419,7 @@ test "custom" {
14191419 options: FormatOptions,
14201420 context: var,
14211421 comptime Errors: type,
1422 output: fn (@TypeOf(context), []const u8) Errors!void,
1422 comptime output: fn (@TypeOf(context), []const u8) Errors!void,
14231423 ) Errors!void {
14241424 if (fmt.len == 0 or comptime std.mem.eql(u8, fmt, "p")) {
14251425 return std.fmt.format(context, Errors, output, "({d:.3},{d:.3})", .{ self.x, self.y });
......@@ -1626,7 +1626,7 @@ test "formatType max_depth" {
16261626 options: FormatOptions,
16271627 context: var,
16281628 comptime Errors: type,
1629 output: fn (@TypeOf(context), []const u8) Errors!void,
1629 comptime output: fn (@TypeOf(context), []const u8) Errors!void,
16301630 ) Errors!void {
16311631 if (fmt.len == 0) {
16321632 return std.fmt.format(context, Errors, output, "({d:.3},{d:.3})", .{ self.x, self.y });
lib/std/fs.zig+36-7
......@@ -23,6 +23,8 @@ pub const realpathW = os.realpathW;
2323pub const getAppDataDir = @import("fs/get_app_data_dir.zig").getAppDataDir;
2424pub const GetAppDataDirError = @import("fs/get_app_data_dir.zig").GetAppDataDirError;
2525
26pub const Watch = @import("fs/watch.zig").Watch;
27
2628/// This represents the maximum size of a UTF-8 encoded file path.
2729/// All file system operations which return a path are guaranteed to
2830/// fit into a UTF-8 encoded array of this length.
......@@ -43,6 +45,13 @@ pub const base64_encoder = base64.Base64Encoder.init(
4345 base64.standard_pad_char,
4446);
4547
48/// Whether or not async file system syscalls need a dedicated thread because the operating
49/// system does not support non-blocking I/O on the file system.
50pub const need_async_thread = std.io.is_async and switch (builtin.os) {
51 .windows, .other => false,
52 else => true,
53};
54
4655/// TODO remove the allocator requirement from this API
4756pub fn atomicSymLink(allocator: *Allocator, existing_path: []const u8, new_path: []const u8) !void {
4857 if (symLink(existing_path, new_path)) {
......@@ -688,11 +697,16 @@ pub const Dir = struct {
688697 }
689698
690699 pub fn close(self: *Dir) void {
691 os.close(self.fd);
700 if (need_async_thread) {
701 std.event.Loop.instance.?.close(self.fd);
702 } else {
703 os.close(self.fd);
704 }
692705 self.* = undefined;
693706 }
694707
695708 /// Opens a file for reading or writing, without attempting to create a new file.
709 /// To create a new file, see `createFile`.
696710 /// Call `File.close` to release the resource.
697711 /// Asserts that the path parameter has no null bytes.
698712 pub fn openFile(self: Dir, sub_path: []const u8, flags: File.OpenFlags) File.OpenError!File {
......@@ -718,8 +732,11 @@ pub const Dir = struct {
718732 @as(u32, os.O_WRONLY)
719733 else
720734 @as(u32, os.O_RDONLY);
721 const fd = try os.openatC(self.fd, sub_path, os_flags, 0);
722 return File{ .handle = fd };
735 const fd = if (need_async_thread)
736 try std.event.Loop.instance.?.openatZ(self.fd, sub_path, os_flags, 0)
737 else
738 try os.openatC(self.fd, sub_path, os_flags, 0);
739 return File{ .handle = fd, .io_mode = .blocking };
723740 }
724741
725742 /// Same as `openFile` but Windows-only and the path parameter is
......@@ -756,8 +773,11 @@ pub const Dir = struct {
756773 (if (flags.truncate) @as(u32, os.O_TRUNC) else 0) |
757774 (if (flags.read) @as(u32, os.O_RDWR) else os.O_WRONLY) |
758775 (if (flags.exclusive) @as(u32, os.O_EXCL) else 0);
759 const fd = try os.openatC(self.fd, sub_path_c, os_flags, flags.mode);
760 return File{ .handle = fd };
776 const fd = if (need_async_thread)
777 try std.event.Loop.instance.?.openatZ(self.fd, sub_path_c, os_flags, flags.mode)
778 else
779 try os.openatC(self.fd, sub_path_c, os_flags, flags.mode);
780 return File{ .handle = fd, .io_mode = .blocking };
761781 }
762782
763783 /// Same as `createFile` but Windows-only and the path parameter is
......@@ -798,7 +818,10 @@ pub const Dir = struct {
798818 ) File.OpenError!File {
799819 const w = os.windows;
800820
801 var result = File{ .handle = undefined };
821 var result = File{
822 .handle = undefined,
823 .io_mode = .blocking,
824 };
802825
803826 const path_len_bytes = math.cast(u16, mem.toSliceConst(u16, sub_path_w).len * 2) catch |err| switch (err) {
804827 error.Overflow => return error.NameTooLong,
......@@ -919,7 +942,12 @@ pub const Dir = struct {
919942 }
920943
921944 fn openDirFlagsC(self: Dir, sub_path_c: [*:0]const u8, flags: u32) OpenError!Dir {
922 const fd = os.openatC(self.fd, sub_path_c, flags | os.O_DIRECTORY, 0) catch |err| switch (err) {
945 const os_flags = flags | os.O_DIRECTORY;
946 const result = if (need_async_thread)
947 std.event.Loop.instance.?.openatZ(self.fd, sub_path_c, os_flags, 0)
948 else
949 os.openatC(self.fd, sub_path_c, os_flags, 0);
950 const fd = result catch |err| switch (err) {
923951 error.FileTooBig => unreachable, // can't happen for directories
924952 error.IsDir => unreachable, // we're providing O_DIRECTORY
925953 error.NoSpaceLeft => unreachable, // not providing O_CREAT
......@@ -1588,4 +1616,5 @@ test "" {
15881616 _ = @import("fs/path.zig");
15891617 _ = @import("fs/file.zig");
15901618 _ = @import("fs/get_app_data_dir.zig");
1619 _ = @import("fs/watch.zig");
15911620}
lib/std/fs/file.zig+78-75
......@@ -8,18 +8,29 @@ const assert = std.debug.assert;
88const windows = os.windows;
99const Os = builtin.Os;
1010const maxInt = std.math.maxInt;
11const need_async_thread = std.fs.need_async_thread;
1112
1213pub const File = struct {
1314 /// The OS-specific file descriptor or file handle.
1415 handle: os.fd_t,
1516
16 pub const Mode = switch (builtin.os) {
17 Os.windows => void,
18 else => u32,
19 };
17 /// On some systems, such as Linux, file system file descriptors are incapable of non-blocking I/O.
18 /// This forces us to perform asynchronous I/O on a dedicated thread, to achieve non-blocking
19 /// file-system I/O. To do this, `File` must be aware of whether it is a file system file descriptor,
20 /// or, more specifically, whether the I/O is blocking.
21 io_mode: io.Mode,
22
23 /// Even when std.io.mode is async, it is still sometimes desirable to perform blocking I/O, although
24 /// not by default. For example, when printing a stack trace to stderr.
25 async_block_allowed: @TypeOf(async_block_allowed_no) = async_block_allowed_no,
26
27 pub const async_block_allowed_yes = if (io.is_async) true else {};
28 pub const async_block_allowed_no = if (io.is_async) false else {};
29
30 pub const Mode = os.mode_t;
2031
2132 pub const default_mode = switch (builtin.os) {
22 Os.windows => {},
33 .windows => 0,
2334 else => 0o666,
2435 };
2536
......@@ -49,87 +60,27 @@ pub const File = struct {
4960 mode: Mode = default_mode,
5061 };
5162
52 /// Deprecated; call `std.fs.Dir.openFile` directly.
53 pub fn openRead(path: []const u8) OpenError!File {
54 return std.fs.cwd().openFile(path, .{});
55 }
56
57 /// Deprecated; call `std.fs.Dir.openFileC` directly.
58 pub fn openReadC(path_c: [*:0]const u8) OpenError!File {
59 return std.fs.cwd().openFileC(path_c, .{});
60 }
61
62 /// Deprecated; call `std.fs.Dir.openFileW` directly.
63 pub fn openReadW(path_w: [*:0]const u16) OpenError!File {
64 return std.fs.cwd().openFileW(path_w, .{});
65 }
66
67 /// Deprecated; call `std.fs.Dir.createFile` directly.
68 pub fn openWrite(path: []const u8) OpenError!File {
69 return std.fs.cwd().createFile(path, .{});
70 }
71
72 /// Deprecated; call `std.fs.Dir.createFile` directly.
73 pub fn openWriteMode(path: []const u8, file_mode: Mode) OpenError!File {
74 return std.fs.cwd().createFile(path, .{ .mode = file_mode });
75 }
76
77 /// Deprecated; call `std.fs.Dir.createFileC` directly.
78 pub fn openWriteModeC(path_c: [*:0]const u8, file_mode: Mode) OpenError!File {
79 return std.fs.cwd().createFileC(path_c, .{ .mode = file_mode });
80 }
81
82 /// Deprecated; call `std.fs.Dir.createFileW` directly.
83 pub fn openWriteModeW(path_w: [*:0]const u16, file_mode: Mode) OpenError!File {
84 return std.fs.cwd().createFileW(path_w, .{ .mode = file_mode });
85 }
86
87 /// Deprecated; call `std.fs.Dir.createFile` directly.
88 pub fn openWriteNoClobber(path: []const u8, file_mode: Mode) OpenError!File {
89 return std.fs.cwd().createFile(path, .{
90 .mode = file_mode,
91 .exclusive = true,
92 });
93 }
94
95 /// Deprecated; call `std.fs.Dir.createFileC` directly.
96 pub fn openWriteNoClobberC(path_c: [*:0]const u8, file_mode: Mode) OpenError!File {
97 return std.fs.cwd().createFileC(path_c, .{
98 .mode = file_mode,
99 .exclusive = true,
100 });
101 }
102
103 /// Deprecated; call `std.fs.Dir.createFileW` directly.
104 pub fn openWriteNoClobberW(path_w: [*:0]const u16, file_mode: Mode) OpenError!File {
105 return std.fs.cwd().createFileW(path_w, .{
106 .mode = file_mode,
107 .exclusive = true,
108 });
109 }
110
111 pub fn openHandle(handle: os.fd_t) File {
112 return File{ .handle = handle };
113 }
114
11563 /// Test for the existence of `path`.
11664 /// `path` is UTF8-encoded.
11765 /// In general it is recommended to avoid this function. For example,
11866 /// instead of testing if a file exists and then opening it, just
11967 /// open it and handle the error for file not found.
12068 /// TODO: deprecate this and move it to `std.fs.Dir`.
69 /// TODO: integrate with async I/O
12170 pub fn access(path: []const u8) !void {
12271 return os.access(path, os.F_OK);
12372 }
12473
12574 /// Same as `access` except the parameter is null-terminated.
12675 /// TODO: deprecate this and move it to `std.fs.Dir`.
76 /// TODO: integrate with async I/O
12777 pub fn accessC(path: [*:0]const u8) !void {
12878 return os.accessC(path, os.F_OK);
12979 }
13080
13181 /// Same as `access` except the parameter is null-terminated UTF16LE-encoded.
13282 /// TODO: deprecate this and move it to `std.fs.Dir`.
83 /// TODO: integrate with async I/O
13384 pub fn accessW(path: [*:0]const u16) !void {
13485 return os.accessW(path, os.F_OK);
13586 }
......@@ -137,7 +88,11 @@ pub const File = struct {
13788 /// Upon success, the stream is in an uninitialized state. To continue using it,
13889 /// you must use the open() function.
13990 pub fn close(self: File) void {
140 return os.close(self.handle);
91 if (need_async_thread and self.io_mode == .blocking and !self.async_block_allowed) {
92 std.event.Loop.instance.?.close(self.handle);
93 } else {
94 return os.close(self.handle);
95 }
14196 }
14297
14398 /// Test whether the file refers to a terminal.
......@@ -167,26 +122,31 @@ pub const File = struct {
167122 pub const SeekError = os.SeekError;
168123
169124 /// Repositions read/write file offset relative to the current offset.
125 /// TODO: integrate with async I/O
170126 pub fn seekBy(self: File, offset: i64) SeekError!void {
171127 return os.lseek_CUR(self.handle, offset);
172128 }
173129
174130 /// Repositions read/write file offset relative to the end.
131 /// TODO: integrate with async I/O
175132 pub fn seekFromEnd(self: File, offset: i64) SeekError!void {
176133 return os.lseek_END(self.handle, offset);
177134 }
178135
179136 /// Repositions read/write file offset relative to the beginning.
137 /// TODO: integrate with async I/O
180138 pub fn seekTo(self: File, offset: u64) SeekError!void {
181139 return os.lseek_SET(self.handle, offset);
182140 }
183141
184142 pub const GetPosError = os.SeekError || os.FStatError;
185143
144 /// TODO: integrate with async I/O
186145 pub fn getPos(self: File) GetPosError!u64 {
187146 return os.lseek_CUR_get(self.handle);
188147 }
189148
149 /// TODO: integrate with async I/O
190150 pub fn getEndPos(self: File) GetPosError!u64 {
191151 if (builtin.os == .windows) {
192152 return windows.GetFileSizeEx(self.handle);
......@@ -196,6 +156,7 @@ pub const File = struct {
196156
197157 pub const ModeError = os.FStatError;
198158
159 /// TODO: integrate with async I/O
199160 pub fn mode(self: File) ModeError!Mode {
200161 if (builtin.os == .windows) {
201162 return {};
......@@ -219,6 +180,7 @@ pub const File = struct {
219180
220181 pub const StatError = os.FStatError;
221182
183 /// TODO: integrate with async I/O
222184 pub fn stat(self: File) StatError!Stat {
223185 if (builtin.os == .windows) {
224186 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
......@@ -233,7 +195,7 @@ pub const File = struct {
233195 }
234196 return Stat{
235197 .size = @bitCast(u64, info.StandardInformation.EndOfFile),
236 .mode = {},
198 .mode = 0,
237199 .atime = windows.fromSysTime(info.BasicInformation.LastAccessTime),
238200 .mtime = windows.fromSysTime(info.BasicInformation.LastWriteTime),
239201 .ctime = windows.fromSysTime(info.BasicInformation.CreationTime),
......@@ -259,6 +221,7 @@ pub const File = struct {
259221 /// and therefore this function cannot guarantee any precision will be stored.
260222 /// Further, the maximum value is limited by the system ABI. When a value is provided
261223 /// that exceeds this range, the value is clamped to the maximum.
224 /// TODO: integrate with async I/O
262225 pub fn updateTimes(
263226 self: File,
264227 /// access timestamp in nanoseconds
......@@ -287,21 +250,61 @@ pub const File = struct {
287250 pub const ReadError = os.ReadError;
288251
289252 pub fn read(self: File, buffer: []u8) ReadError!usize {
253 if (need_async_thread and self.io_mode == .blocking and !self.async_block_allowed) {
254 return std.event.Loop.instance.?.read(self.handle, buffer);
255 }
290256 return os.read(self.handle, buffer);
291257 }
292258
259 pub fn pread(self: File, buffer: []u8, offset: u64) ReadError!usize {
260 if (need_async_thread and self.io_mode == .blocking and !self.async_block_allowed) {
261 return std.event.Loop.instance.?.pread(self.handle, buffer);
262 }
263 return os.pread(self.handle, buffer, offset);
264 }
265
266 pub fn readv(self: File, iovecs: []const os.iovec) ReadError!usize {
267 if (need_async_thread and self.io_mode == .blocking and !self.async_block_allowed) {
268 return std.event.Loop.instance.?.readv(self.handle, iovecs);
269 }
270 return os.readv(self.handle, iovecs);
271 }
272
273 pub fn preadv(self: File, iovecs: []const os.iovec, offset: u64) ReadError!usize {
274 if (need_async_thread and self.io_mode == .blocking and !self.async_block_allowed) {
275 return std.event.Loop.instance.?.preadv(self.handle, iovecs, offset);
276 }
277 return os.preadv(self.handle, iovecs, offset);
278 }
279
293280 pub const WriteError = os.WriteError;
294281
295282 pub fn write(self: File, bytes: []const u8) WriteError!void {
283 if (need_async_thread and self.io_mode == .blocking and !self.async_block_allowed) {
284 return std.event.Loop.instance.?.write(self.handle, bytes);
285 }
296286 return os.write(self.handle, bytes);
297287 }
298288
299 pub fn writev_iovec(self: File, iovecs: []const os.iovec_const) WriteError!void {
300 if (std.event.Loop.instance) |loop| {
301 return std.event.fs.writevPosix(loop, self.handle, iovecs);
302 } else {
303 return os.writev(self.handle, iovecs);
289 pub fn pwrite(self: File, bytes: []const u8, offset: u64) WriteError!void {
290 if (need_async_thread and self.io_mode == .blocking and !self.async_block_allowed) {
291 return std.event.Loop.instance.?.pwrite(self.handle, bytes, offset);
292 }
293 return os.pwrite(self.handle, bytes, offset);
294 }
295
296 pub fn writev(self: File, iovecs: []const os.iovec_const) WriteError!void {
297 if (need_async_thread and self.io_mode == .blocking and !self.async_block_allowed) {
298 return std.event.Loop.instance.?.writev(self.handle, iovecs);
299 }
300 return os.writev(self.handle, iovecs);
301 }
302
303 pub fn pwritev(self: File, iovecs: []const os.iovec_const, offset: usize) WriteError!void {
304 if (need_async_thread and self.io_mode == .blocking and !self.async_block_allowed) {
305 return std.event.Loop.instance.?.pwritev(self.handle, iovecs);
304306 }
307 return os.pwritev(self.handle, iovecs);
305308 }
306309
307310 pub fn inStream(file: File) InStream {
lib/std/fs/watch.zig created+675
......@@ -0,0 +1,675 @@
1const std = @import("../std.zig");
2const builtin = @import("builtin");
3const event = std.event;
4const assert = std.debug.assert;
5const testing = std.testing;
6const os = std.os;
7const mem = std.mem;
8const windows = os.windows;
9const Loop = event.Loop;
10const fd_t = os.fd_t;
11const File = std.fs.File;
12const Allocator = mem.Allocator;
13
14const global_event_loop = Loop.instance orelse
15 @compileError("std.fs.Watch currently only works with event-based I/O");
16
17const WatchEventId = enum {
18 CloseWrite,
19 Delete,
20};
21
22fn eqlString(a: []const u16, b: []const u16) bool {
23 if (a.len != b.len) return false;
24 if (a.ptr == b.ptr) return true;
25 return mem.compare(u16, a, b) == .Equal;
26}
27
28fn hashString(s: []const u16) u32 {
29 return @truncate(u32, std.hash.Wyhash.hash(0, @sliceToBytes(s)));
30}
31
32const WatchEventError = error{
33 UserResourceLimitReached,
34 SystemResources,
35 AccessDenied,
36 Unexpected, // TODO remove this possibility
37};
38
39pub fn Watch(comptime V: type) type {
40 return struct {
41 channel: *event.Channel(Event.Error!Event),
42 os_data: OsData,
43 allocator: *Allocator,
44
45 const OsData = switch (builtin.os) {
46 // TODO https://github.com/ziglang/zig/issues/3778
47 .macosx, .freebsd, .netbsd, .dragonfly => KqOsData,
48 .linux => LinuxOsData,
49 .windows => WindowsOsData,
50
51 else => @compileError("Unsupported OS"),
52 };
53
54 const KqOsData = struct {
55 file_table: FileTable,
56 table_lock: event.Lock,
57
58 const FileTable = std.StringHashMap(*Put);
59 const Put = struct {
60 putter_frame: @Frame(kqPutEvents),
61 cancelled: bool = false,
62 value: V,
63 };
64 };
65
66 const WindowsOsData = struct {
67 table_lock: event.Lock,
68 dir_table: DirTable,
69 all_putters: std.atomic.Queue(Put),
70 ref_count: std.atomic.Int(usize),
71
72 const Put = struct {
73 putter: anyframe,
74 cancelled: bool = false,
75 };
76
77 const DirTable = std.StringHashMap(*Dir);
78 const FileTable = std.HashMap([]const u16, V, hashString, eqlString);
79
80 const Dir = struct {
81 putter_frame: @Frame(windowsDirReader),
82 file_table: FileTable,
83 table_lock: event.Lock,
84 };
85 };
86
87 const LinuxOsData = struct {
88 putter_frame: @Frame(linuxEventPutter),
89 inotify_fd: i32,
90 wd_table: WdTable,
91 table_lock: event.Lock,
92 cancelled: bool = false,
93
94 const WdTable = std.AutoHashMap(i32, Dir);
95 const FileTable = std.StringHashMap(V);
96
97 const Dir = struct {
98 dirname: []const u8,
99 file_table: FileTable,
100 };
101 };
102
103 const Self = @This();
104
105 pub const Event = struct {
106 id: Id,
107 data: V,
108
109 pub const Id = WatchEventId;
110 pub const Error = WatchEventError;
111 };
112
113 pub fn init(allocator: *Allocator, event_buf_count: usize) !*Self {
114 const channel = try allocator.create(event.Channel(Event.Error!Event));
115 errdefer allocator.destroy(channel);
116 var buf = try allocator.alloc(Event.Error!Event, event_buf_count);
117 errdefer allocator.free(buf);
118 channel.init(buf);
119 errdefer channel.deinit();
120
121 const self = try allocator.create(Self);
122 errdefer allocator.destroy(self);
123
124 switch (builtin.os) {
125 .linux => {
126 const inotify_fd = try os.inotify_init1(os.linux.IN_NONBLOCK | os.linux.IN_CLOEXEC);
127 errdefer os.close(inotify_fd);
128
129 self.* = Self{
130 .allocator = allocator,
131 .channel = channel,
132 .os_data = OsData{
133 .putter_frame = undefined,
134 .inotify_fd = inotify_fd,
135 .wd_table = OsData.WdTable.init(allocator),
136 .table_lock = event.Lock.init(),
137 },
138 };
139
140 self.os_data.putter_frame = async self.linuxEventPutter();
141 return self;
142 },
143
144 .windows => {
145 self.* = Self{
146 .allocator = allocator,
147 .channel = channel,
148 .os_data = OsData{
149 .table_lock = event.Lock.init(),
150 .dir_table = OsData.DirTable.init(allocator),
151 .ref_count = std.atomic.Int(usize).init(1),
152 .all_putters = std.atomic.Queue(anyframe).init(),
153 },
154 };
155 return self;
156 },
157
158 .macosx, .freebsd, .netbsd, .dragonfly => {
159 self.* = Self{
160 .allocator = allocator,
161 .channel = channel,
162 .os_data = OsData{
163 .table_lock = event.Lock.init(),
164 .file_table = OsData.FileTable.init(allocator),
165 },
166 };
167 return self;
168 },
169 else => @compileError("Unsupported OS"),
170 }
171 }
172
173 /// All addFile calls and removeFile calls must have completed.
174 pub fn deinit(self: *Self) void {
175 switch (builtin.os) {
176 .macosx, .freebsd, .netbsd, .dragonfly => {
177 // TODO we need to cancel the frames before destroying the lock
178 self.os_data.table_lock.deinit();
179 var it = self.os_data.file_table.iterator();
180 while (it.next()) |entry| {
181 entry.cancelled = true;
182 await entry.value.putter;
183 self.allocator.free(entry.key);
184 self.allocator.free(entry.value);
185 }
186 self.channel.deinit();
187 self.allocator.destroy(self.channel.buffer_nodes);
188 self.allocator.destroy(self);
189 },
190 .linux => {
191 self.os_data.cancelled = true;
192 await self.os_data.putter_frame;
193 self.allocator.destroy(self);
194 },
195 .windows => {
196 while (self.os_data.all_putters.get()) |putter_node| {
197 putter_node.cancelled = true;
198 await putter_node.frame;
199 }
200 self.deref();
201 },
202 else => @compileError("Unsupported OS"),
203 }
204 }
205
206 fn ref(self: *Self) void {
207 _ = self.os_data.ref_count.incr();
208 }
209
210 fn deref(self: *Self) void {
211 if (self.os_data.ref_count.decr() == 1) {
212 self.os_data.table_lock.deinit();
213 var it = self.os_data.dir_table.iterator();
214 while (it.next()) |entry| {
215 self.allocator.free(entry.key);
216 self.allocator.destroy(entry.value);
217 }
218 self.os_data.dir_table.deinit();
219 self.channel.deinit();
220 self.allocator.destroy(self.channel.buffer_nodes);
221 self.allocator.destroy(self);
222 }
223 }
224
225 pub fn addFile(self: *Self, file_path: []const u8, value: V) !?V {
226 switch (builtin.os) {
227 .macosx, .freebsd, .netbsd, .dragonfly => return addFileKEvent(self, file_path, value),
228 .linux => return addFileLinux(self, file_path, value),
229 .windows => return addFileWindows(self, file_path, value),
230 else => @compileError("Unsupported OS"),
231 }
232 }
233
234 fn addFileKEvent(self: *Self, file_path: []const u8, value: V) !?V {
235 const resolved_path = try std.fs.path.resolve(self.allocator, [_][]const u8{file_path});
236 var resolved_path_consumed = false;
237 defer if (!resolved_path_consumed) self.allocator.free(resolved_path);
238
239 var close_op = try CloseOperation.start(self.allocator);
240 var close_op_consumed = false;
241 defer if (!close_op_consumed) close_op.finish();
242
243 const flags = if (comptime std.Target.current.isDarwin()) os.O_SYMLINK | os.O_EVTONLY else 0;
244 const mode = 0;
245 const fd = try openPosix(self.allocator, resolved_path, flags, mode);
246 close_op.setHandle(fd);
247
248 var put = try self.allocator.create(OsData.Put);
249 errdefer self.allocator.destroy(put);
250 put.* = OsData.Put{
251 .value = value,
252 .putter_frame = undefined,
253 };
254 put.putter_frame = async self.kqPutEvents(close_op, put);
255 close_op_consumed = true;
256 errdefer {
257 put.cancelled = true;
258 await put.putter_frame;
259 }
260
261 const result = blk: {
262 const held = self.os_data.table_lock.acquire();
263 defer held.release();
264
265 const gop = try self.os_data.file_table.getOrPut(resolved_path);
266 if (gop.found_existing) {
267 const prev_value = gop.kv.value.value;
268 await gop.kv.value.putter_frame;
269 gop.kv.value = put;
270 break :blk prev_value;
271 } else {
272 resolved_path_consumed = true;
273 gop.kv.value = put;
274 break :blk null;
275 }
276 };
277
278 return result;
279 }
280
281 fn kqPutEvents(self: *Self, close_op: *CloseOperation, put: *OsData.Put) void {
282 global_event_loop.beginOneEvent();
283
284 defer {
285 close_op.finish();
286 global_event_loop.finishOneEvent();
287 }
288
289 while (!put.cancelled) {
290 if (global_event_loop.bsdWaitKev(
291 @intCast(usize, close_op.getHandle()),
292 os.EVFILT_VNODE,
293 os.NOTE_WRITE | os.NOTE_DELETE,
294 )) |kev| {
295 // TODO handle EV_ERROR
296 if (kev.fflags & os.NOTE_DELETE != 0) {
297 self.channel.put(Self.Event{
298 .id = Event.Id.Delete,
299 .data = put.value,
300 });
301 } else if (kev.fflags & os.NOTE_WRITE != 0) {
302 self.channel.put(Self.Event{
303 .id = Event.Id.CloseWrite,
304 .data = put.value,
305 });
306 }
307 } else |err| switch (err) {
308 error.EventNotFound => unreachable,
309 error.ProcessNotFound => unreachable,
310 error.Overflow => unreachable,
311 error.AccessDenied, error.SystemResources => |casted_err| {
312 self.channel.put(casted_err);
313 },
314 }
315 }
316 }
317
318 fn addFileLinux(self: *Self, file_path: []const u8, value: V) !?V {
319 const dirname = std.fs.path.dirname(file_path) orelse ".";
320 const dirname_with_null = try std.cstr.addNullByte(self.allocator, dirname);
321 var dirname_with_null_consumed = false;
322 defer if (!dirname_with_null_consumed) self.channel.free(dirname_with_null);
323
324 const basename = std.fs.path.basename(file_path);
325 const basename_with_null = try std.cstr.addNullByte(self.allocator, basename);
326 var basename_with_null_consumed = false;
327 defer if (!basename_with_null_consumed) self.allocator.free(basename_with_null);
328
329 const wd = try os.inotify_add_watchC(
330 self.os_data.inotify_fd,
331 dirname_with_null.ptr,
332 os.linux.IN_CLOSE_WRITE | os.linux.IN_ONLYDIR | os.linux.IN_EXCL_UNLINK,
333 );
334 // wd is either a newly created watch or an existing one.
335
336 const held = self.os_data.table_lock.acquire();
337 defer held.release();
338
339 const gop = try self.os_data.wd_table.getOrPut(wd);
340 if (!gop.found_existing) {
341 gop.kv.value = OsData.Dir{
342 .dirname = dirname_with_null,
343 .file_table = OsData.FileTable.init(self.allocator),
344 };
345 dirname_with_null_consumed = true;
346 }
347 const dir = &gop.kv.value;
348
349 const file_table_gop = try dir.file_table.getOrPut(basename_with_null);
350 if (file_table_gop.found_existing) {
351 const prev_value = file_table_gop.kv.value;
352 file_table_gop.kv.value = value;
353 return prev_value;
354 } else {
355 file_table_gop.kv.value = value;
356 basename_with_null_consumed = true;
357 return null;
358 }
359 }
360
361 fn addFileWindows(self: *Self, file_path: []const u8, value: V) !?V {
362 // TODO we might need to convert dirname and basename to canonical file paths ("short"?)
363 const dirname = try std.mem.dupe(self.allocator, u8, std.fs.path.dirname(file_path) orelse ".");
364 var dirname_consumed = false;
365 defer if (!dirname_consumed) self.allocator.free(dirname);
366
367 const dirname_utf16le = try std.unicode.utf8ToUtf16LeWithNull(self.allocator, dirname);
368 defer self.allocator.free(dirname_utf16le);
369
370 // TODO https://github.com/ziglang/zig/issues/265
371 const basename = std.fs.path.basename(file_path);
372 const basename_utf16le_null = try std.unicode.utf8ToUtf16LeWithNull(self.allocator, basename);
373 var basename_utf16le_null_consumed = false;
374 defer if (!basename_utf16le_null_consumed) self.allocator.free(basename_utf16le_null);
375 const basename_utf16le_no_null = basename_utf16le_null[0 .. basename_utf16le_null.len - 1];
376
377 const dir_handle = try windows.CreateFileW(
378 dirname_utf16le.ptr,
379 windows.FILE_LIST_DIRECTORY,
380 windows.FILE_SHARE_READ | windows.FILE_SHARE_DELETE | windows.FILE_SHARE_WRITE,
381 null,
382 windows.OPEN_EXISTING,
383 windows.FILE_FLAG_BACKUP_SEMANTICS | windows.FILE_FLAG_OVERLAPPED,
384 null,
385 );
386 var dir_handle_consumed = false;
387 defer if (!dir_handle_consumed) windows.CloseHandle(dir_handle);
388
389 const held = self.os_data.table_lock.acquire();
390 defer held.release();
391
392 const gop = try self.os_data.dir_table.getOrPut(dirname);
393 if (gop.found_existing) {
394 const dir = gop.kv.value;
395 const held_dir_lock = dir.table_lock.acquire();
396 defer held_dir_lock.release();
397
398 const file_gop = try dir.file_table.getOrPut(basename_utf16le_no_null);
399 if (file_gop.found_existing) {
400 const prev_value = file_gop.kv.value;
401 file_gop.kv.value = value;
402 return prev_value;
403 } else {
404 file_gop.kv.value = value;
405 basename_utf16le_null_consumed = true;
406 return null;
407 }
408 } else {
409 errdefer _ = self.os_data.dir_table.remove(dirname);
410 const dir = try self.allocator.create(OsData.Dir);
411 errdefer self.allocator.destroy(dir);
412
413 dir.* = OsData.Dir{
414 .file_table = OsData.FileTable.init(self.allocator),
415 .table_lock = event.Lock.init(),
416 .putter_frame = undefined,
417 };
418 gop.kv.value = dir;
419 assert((try dir.file_table.put(basename_utf16le_no_null, value)) == null);
420 basename_utf16le_null_consumed = true;
421
422 dir.putter_frame = async self.windowsDirReader(dir_handle, dir);
423 dir_handle_consumed = true;
424
425 dirname_consumed = true;
426
427 return null;
428 }
429 }
430
431 fn windowsDirReader(self: *Self, dir_handle: windows.HANDLE, dir: *OsData.Dir) void {
432 self.ref();
433 defer self.deref();
434
435 defer os.close(dir_handle);
436
437 var putter_node = std.atomic.Queue(anyframe).Node{
438 .data = .{ .putter = @frame() },
439 .prev = null,
440 .next = null,
441 };
442 self.os_data.all_putters.put(&putter_node);
443 defer _ = self.os_data.all_putters.remove(&putter_node);
444
445 var resume_node = Loop.ResumeNode.Basic{
446 .base = Loop.ResumeNode{
447 .id = Loop.ResumeNode.Id.Basic,
448 .handle = @frame(),
449 .overlapped = windows.OVERLAPPED{
450 .Internal = 0,
451 .InternalHigh = 0,
452 .Offset = 0,
453 .OffsetHigh = 0,
454 .hEvent = null,
455 },
456 },
457 };
458 var event_buf: [4096]u8 align(@alignOf(windows.FILE_NOTIFY_INFORMATION)) = undefined;
459
460 // TODO handle this error not in the channel but in the setup
461 _ = windows.CreateIoCompletionPort(
462 dir_handle,
463 global_event_loop.os_data.io_port,
464 undefined,
465 undefined,
466 ) catch |err| {
467 self.channel.put(err);
468 return;
469 };
470
471 while (!putter_node.data.cancelled) {
472 {
473 // TODO only 1 beginOneEvent for the whole function
474 global_event_loop.beginOneEvent();
475 errdefer global_event_loop.finishOneEvent();
476 errdefer {
477 _ = windows.kernel32.CancelIoEx(dir_handle, &resume_node.base.overlapped);
478 }
479 suspend {
480 _ = windows.kernel32.ReadDirectoryChangesW(
481 dir_handle,
482 &event_buf,
483 @intCast(windows.DWORD, event_buf.len),
484 windows.FALSE, // watch subtree
485 windows.FILE_NOTIFY_CHANGE_FILE_NAME | windows.FILE_NOTIFY_CHANGE_DIR_NAME |
486 windows.FILE_NOTIFY_CHANGE_ATTRIBUTES | windows.FILE_NOTIFY_CHANGE_SIZE |
487 windows.FILE_NOTIFY_CHANGE_LAST_WRITE | windows.FILE_NOTIFY_CHANGE_LAST_ACCESS |
488 windows.FILE_NOTIFY_CHANGE_CREATION | windows.FILE_NOTIFY_CHANGE_SECURITY,
489 null, // number of bytes transferred (unused for async)
490 &resume_node.base.overlapped,
491 null, // completion routine - unused because we use IOCP
492 );
493 }
494 }
495 var bytes_transferred: windows.DWORD = undefined;
496 if (windows.kernel32.GetOverlappedResult(dir_handle, &resume_node.base.overlapped, &bytes_transferred, windows.FALSE) == 0) {
497 const err = switch (windows.kernel32.GetLastError()) {
498 else => |err| windows.unexpectedError(err),
499 };
500 self.channel.put(err);
501 } else {
502 // can't use @bytesToSlice because of the special variable length name field
503 var ptr = event_buf[0..].ptr;
504 const end_ptr = ptr + bytes_transferred;
505 var ev: *windows.FILE_NOTIFY_INFORMATION = undefined;
506 while (@ptrToInt(ptr) < @ptrToInt(end_ptr)) : (ptr += ev.NextEntryOffset) {
507 ev = @ptrCast(*windows.FILE_NOTIFY_INFORMATION, ptr);
508 const emit = switch (ev.Action) {
509 windows.FILE_ACTION_REMOVED => WatchEventId.Delete,
510 windows.FILE_ACTION_MODIFIED => WatchEventId.CloseWrite,
511 else => null,
512 };
513 if (emit) |id| {
514 const basename_utf16le = ([*]u16)(&ev.FileName)[0 .. ev.FileNameLength / 2];
515 const user_value = blk: {
516 const held = dir.table_lock.acquire();
517 defer held.release();
518
519 if (dir.file_table.get(basename_utf16le)) |entry| {
520 break :blk entry.value;
521 } else {
522 break :blk null;
523 }
524 };
525 if (user_value) |v| {
526 self.channel.put(Event{
527 .id = id,
528 .data = v,
529 });
530 }
531 }
532 if (ev.NextEntryOffset == 0) break;
533 }
534 }
535 }
536 }
537
538 pub fn removeFile(self: *Self, file_path: []const u8) ?V {
539 @panic("TODO");
540 }
541
542 fn linuxEventPutter(self: *Self) void {
543 global_event_loop.beginOneEvent();
544
545 defer {
546 self.os_data.table_lock.deinit();
547 var wd_it = self.os_data.wd_table.iterator();
548 while (wd_it.next()) |wd_entry| {
549 var file_it = wd_entry.value.file_table.iterator();
550 while (file_it.next()) |file_entry| {
551 self.allocator.free(file_entry.key);
552 }
553 self.allocator.free(wd_entry.value.dirname);
554 wd_entry.value.file_table.deinit();
555 }
556 self.os_data.wd_table.deinit();
557 global_event_loop.finishOneEvent();
558 os.close(self.os_data.inotify_fd);
559 self.channel.deinit();
560 self.allocator.free(self.channel.buffer_nodes);
561 }
562
563 var event_buf: [4096]u8 align(@alignOf(os.linux.inotify_event)) = undefined;
564
565 while (!self.os_data.cancelled) {
566 const rc = os.linux.read(self.os_data.inotify_fd, &event_buf, event_buf.len);
567 const errno = os.linux.getErrno(rc);
568 switch (errno) {
569 0 => {
570 // can't use @bytesToSlice because of the special variable length name field
571 var ptr = event_buf[0..].ptr;
572 const end_ptr = ptr + event_buf.len;
573 var ev: *os.linux.inotify_event = undefined;
574 while (@ptrToInt(ptr) < @ptrToInt(end_ptr)) {
575 ev = @ptrCast(*os.linux.inotify_event, ptr);
576 if (ev.mask & os.linux.IN_CLOSE_WRITE == os.linux.IN_CLOSE_WRITE) {
577 const basename_ptr = ptr + @sizeOf(os.linux.inotify_event);
578 // `ev.len` counts all bytes in `ev.name` including terminating null byte.
579 const basename_with_null = basename_ptr[0..ev.len];
580 const user_value = blk: {
581 const held = self.os_data.table_lock.acquire();
582 defer held.release();
583
584 const dir = &self.os_data.wd_table.get(ev.wd).?.value;
585 if (dir.file_table.get(basename_with_null)) |entry| {
586 break :blk entry.value;
587 } else {
588 break :blk null;
589 }
590 };
591 if (user_value) |v| {
592 self.channel.put(Event{
593 .id = WatchEventId.CloseWrite,
594 .data = v,
595 });
596 }
597 }
598
599 ptr = @alignCast(@alignOf(os.linux.inotify_event), ptr + @sizeOf(os.linux.inotify_event) + ev.len);
600 }
601 },
602 os.linux.EINTR => continue,
603 os.linux.EINVAL => unreachable,
604 os.linux.EFAULT => unreachable,
605 os.linux.EAGAIN => {
606 global_event_loop.linuxWaitFd(self.os_data.inotify_fd, os.linux.EPOLLET | os.linux.EPOLLIN | os.EPOLLONESHOT);
607 },
608 else => unreachable,
609 }
610 }
611 }
612 };
613}
614
615const test_tmp_dir = "std_event_fs_test";
616
617test "write a file, watch it, write it again" {
618 // TODO re-enable this test
619 if (true) return error.SkipZigTest;
620
621 const allocator = std.heap.page_allocator;
622
623 try os.makePath(allocator, test_tmp_dir);
624 defer os.deleteTree(test_tmp_dir) catch {};
625
626 return testFsWatch(&allocator);
627}
628
629fn testFsWatch(allocator: *Allocator) !void {
630 const file_path = try std.fs.path.join(allocator, [_][]const u8{ test_tmp_dir, "file.txt" });
631 defer allocator.free(file_path);
632
633 const contents =
634 \\line 1
635 \\line 2
636 ;
637 const line2_offset = 7;
638
639 // first just write then read the file
640 try writeFile(allocator, file_path, contents);
641
642 const read_contents = try readFile(allocator, file_path, 1024 * 1024);
643 testing.expectEqualSlices(u8, contents, read_contents);
644
645 // now watch the file
646 var watch = try Watch(void).init(allocator, 0);
647 defer watch.deinit();
648
649 testing.expect((try watch.addFile(file_path, {})) == null);
650
651 const ev = watch.channel.get();
652 var ev_consumed = false;
653 defer if (!ev_consumed) await ev;
654
655 // overwrite line 2
656 const fd = try await openReadWrite(file_path, File.default_mode);
657 {
658 defer os.close(fd);
659
660 try pwritev(allocator, fd, []const []const u8{"lorem ipsum"}, line2_offset);
661 }
662
663 ev_consumed = true;
664 switch ((try await ev).id) {
665 WatchEventId.CloseWrite => {},
666 WatchEventId.Delete => @panic("wrong event"),
667 }
668 const contents_updated = try readFile(allocator, file_path, 1024 * 1024);
669 testing.expectEqualSlices(u8,
670 \\line 1
671 \\lorem ipsum
672 , contents_updated);
673
674 // TODO test deleting the file and then re-adding it. we should get events for both
675}
lib/std/io.zig+13-3
......@@ -47,7 +47,10 @@ fn getStdOutHandle() os.fd_t {
4747}
4848
4949pub fn getStdOut() File {
50 return File.openHandle(getStdOutHandle());
50 return File{
51 .handle = getStdOutHandle(),
52 .io_mode = .blocking,
53 };
5154}
5255
5356fn getStdErrHandle() os.fd_t {
......@@ -63,7 +66,11 @@ fn getStdErrHandle() os.fd_t {
6366}
6467
6568pub fn getStdErr() File {
66 return File.openHandle(getStdErrHandle());
69 return File{
70 .handle = getStdErrHandle(),
71 .io_mode = .blocking,
72 .async_block_allowed = File.async_block_allowed_yes,
73 };
6774}
6875
6976fn getStdInHandle() os.fd_t {
......@@ -79,7 +86,10 @@ fn getStdInHandle() os.fd_t {
7986}
8087
8188pub fn getStdIn() File {
82 return File.openHandle(getStdInHandle());
89 return File{
90 .handle = getStdInHandle(),
91 .io_mode = .blocking,
92 };
8393}
8494
8595pub const SeekableStream = @import("io/seekable_stream.zig").SeekableStream;
lib/std/io/out_stream.zig+11-15
......@@ -9,14 +9,11 @@ pub const stack_size: usize = if (@hasDecl(root, "stack_size_std_io_OutStream"))
99else
1010 default_stack_size;
1111
12/// TODO this is not integrated with evented I/O yet.
13/// https://github.com/ziglang/zig/issues/3557
1412pub fn OutStream(comptime WriteError: type) type {
1513 return struct {
1614 const Self = @This();
1715 pub const Error = WriteError;
18 // TODO https://github.com/ziglang/zig/issues/3557
19 pub const WriteFn = if (std.io.is_async and false)
16 pub const WriteFn = if (std.io.is_async)
2017 async fn (self: *Self, bytes: []const u8) Error!void
2118 else
2219 fn (self: *Self, bytes: []const u8) Error!void;
......@@ -24,8 +21,7 @@ pub fn OutStream(comptime WriteError: type) type {
2421 writeFn: WriteFn,
2522
2623 pub fn write(self: *Self, bytes: []const u8) Error!void {
27 // TODO https://github.com/ziglang/zig/issues/3557
28 if (std.io.is_async and false) {
24 if (std.io.is_async) {
2925 // Let's not be writing 0xaa in safe modes for upwards of 4 MiB for every stream write.
3026 @setRuntimeSafety(false);
3127 var stack_frame: [stack_size]u8 align(std.Target.stack_align) = undefined;
......@@ -36,12 +32,12 @@ pub fn OutStream(comptime WriteError: type) type {
3632 }
3733
3834 pub fn print(self: *Self, comptime format: []const u8, args: var) Error!void {
39 return std.fmt.format(self, Error, self.writeFn, format, args);
35 return std.fmt.format(self, Error, write, format, args);
4036 }
4137
4238 pub fn writeByte(self: *Self, byte: u8) Error!void {
43 const slice = @as(*const [1]u8, &byte)[0..];
44 return self.writeFn(self, slice);
39 const array = [1]u8{byte};
40 return self.write(&array);
4541 }
4642
4743 pub fn writeByteNTimes(self: *Self, byte: u8, n: usize) Error!void {
......@@ -51,7 +47,7 @@ pub fn OutStream(comptime WriteError: type) type {
5147 var remaining: usize = n;
5248 while (remaining > 0) {
5349 const to_write = std.math.min(remaining, bytes.len);
54 try self.writeFn(self, bytes[0..to_write]);
50 try self.write(bytes[0..to_write]);
5551 remaining -= to_write;
5652 }
5753 }
......@@ -60,32 +56,32 @@ pub fn OutStream(comptime WriteError: type) type {
6056 pub fn writeIntNative(self: *Self, comptime T: type, value: T) Error!void {
6157 var bytes: [(T.bit_count + 7) / 8]u8 = undefined;
6258 mem.writeIntNative(T, &bytes, value);
63 return self.writeFn(self, &bytes);
59 return self.write(&bytes);
6460 }
6561
6662 /// Write a foreign-endian integer.
6763 pub fn writeIntForeign(self: *Self, comptime T: type, value: T) Error!void {
6864 var bytes: [(T.bit_count + 7) / 8]u8 = undefined;
6965 mem.writeIntForeign(T, &bytes, value);
70 return self.writeFn(self, &bytes);
66 return self.write(&bytes);
7167 }
7268
7369 pub fn writeIntLittle(self: *Self, comptime T: type, value: T) Error!void {
7470 var bytes: [(T.bit_count + 7) / 8]u8 = undefined;
7571 mem.writeIntLittle(T, &bytes, value);
76 return self.writeFn(self, &bytes);
72 return self.write(&bytes);
7773 }
7874
7975 pub fn writeIntBig(self: *Self, comptime T: type, value: T) Error!void {
8076 var bytes: [(T.bit_count + 7) / 8]u8 = undefined;
8177 mem.writeIntBig(T, &bytes, value);
82 return self.writeFn(self, &bytes);
78 return self.write(&bytes);
8379 }
8480
8581 pub fn writeInt(self: *Self, comptime T: type, value: T, endian: builtin.Endian) Error!void {
8682 var bytes: [(T.bit_count + 7) / 8]u8 = undefined;
8783 mem.writeInt(T, &bytes, value, endian);
88 return self.writeFn(self, &bytes);
84 return self.write(&bytes);
8985 }
9086 };
9187}
lib/std/linked_list.zig+3-6
......@@ -18,12 +18,11 @@ pub fn SinglyLinkedList(comptime T: type) type {
1818
1919 /// Node inside the linked list wrapping the actual data.
2020 pub const Node = struct {
21 next: ?*Node,
21 next: ?*Node = null,
2222 data: T,
2323
2424 pub fn init(data: T) Node {
2525 return Node{
26 .next = null,
2726 .data = data,
2827 };
2928 }
......@@ -196,14 +195,12 @@ pub fn TailQueue(comptime T: type) type {
196195
197196 /// Node inside the linked list wrapping the actual data.
198197 pub const Node = struct {
199 prev: ?*Node,
200 next: ?*Node,
198 prev: ?*Node = null,
199 next: ?*Node = null,
201200 data: T,
202201
203202 pub fn init(data: T) Node {
204203 return Node{
205 .prev = null,
206 .next = null,
207204 .data = data,
208205 };
209206 }
lib/std/net.zig+11-5
......@@ -271,7 +271,7 @@ pub const Address = extern union {
271271 options: std.fmt.FormatOptions,
272272 context: var,
273273 comptime Errors: type,
274 output: fn (@TypeOf(context), []const u8) Errors!void,
274 comptime output: fn (@TypeOf(context), []const u8) Errors!void,
275275 ) !void {
276276 switch (self.any.family) {
277277 os.AF_INET => {
......@@ -361,7 +361,7 @@ pub const Address = extern union {
361361};
362362
363363pub fn connectUnixSocket(path: []const u8) !fs.File {
364 const opt_non_block = if (std.io.mode == .evented) os.SOCK_NONBLOCK else 0;
364 const opt_non_block = if (std.io.is_async) os.SOCK_NONBLOCK else 0;
365365 const sockfd = try os.socket(
366366 os.AF_UNIX,
367367 os.SOCK_STREAM | os.SOCK_CLOEXEC | opt_non_block,
......@@ -377,7 +377,10 @@ pub fn connectUnixSocket(path: []const u8) !fs.File {
377377 addr.getOsSockLen(),
378378 );
379379
380 return fs.File.openHandle(sockfd);
380 return fs.File{
381 .handle = sockfd,
382 .io_mode = std.io.mode,
383 };
381384}
382385
383386pub const AddressList = struct {
......@@ -412,7 +415,7 @@ pub fn tcpConnectToAddress(address: Address) !fs.File {
412415 errdefer os.close(sockfd);
413416 try os.connect(sockfd, &address.any, address.getOsSockLen());
414417
415 return fs.File{ .handle = sockfd };
418 return fs.File{ .handle = sockfd, .io_mode = std.io.mode };
416419}
417420
418421/// Call `AddressList.deinit` on the result.
......@@ -1379,7 +1382,10 @@ pub const StreamServer = struct {
13791382 var adr_len: os.socklen_t = @sizeOf(Address);
13801383 if (os.accept4(self.sockfd.?, &accepted_addr.any, &adr_len, accept_flags)) |fd| {
13811384 return Connection{
1382 .file = fs.File.openHandle(fd),
1385 .file = fs.File{
1386 .handle = fd,
1387 .io_mode = std.io.mode,
1388 },
13831389 .address = accepted_addr,
13841390 };
13851391 } else |err| switch (err) {
lib/std/net/test.zig+3-5
......@@ -81,17 +81,15 @@ test "resolve DNS" {
8181}
8282
8383test "listen on a port, send bytes, receive bytes" {
84 if (!std.io.is_async) return error.SkipZigTest;
85
8486 if (std.builtin.os != .linux) {
8587 // TODO build abstractions for other operating systems
8688 return error.SkipZigTest;
8789 }
88 if (std.io.mode != .evented) {
89 // TODO add ability to run tests in non-blocking I/O mode
90 return error.SkipZigTest;
91 }
9290
9391 // TODO doing this at comptime crashed the compiler
94 const localhost = net.Address.parseIp("127.0.0.1", 0);
92 const localhost = try net.Address.parseIp("127.0.0.1", 0);
9593
9694 var server = net.StreamServer.init(net.StreamServer.Options{});
9795 defer server.deinit();
lib/std/os.zig+193-20
......@@ -169,7 +169,12 @@ fn getRandomBytesDevURandom(buf: []u8) !void {
169169 return error.NoDevice;
170170 }
171171
172 const stream = &std.fs.File.openHandle(fd).inStream().stream;
172 const file = std.fs.File{
173 .handle = fd,
174 .io_mode = .blocking,
175 .async_block_allowed = std.fs.File.async_block_allowed_yes,
176 };
177 const stream = &file.inStream().stream;
173178 stream.readNoEof(buf) catch return error.Unexpected;
174179}
175180
......@@ -293,7 +298,7 @@ pub const ReadError = error{
293298/// via the event loop. Otherwise EAGAIN results in error.WouldBlock.
294299pub fn read(fd: fd_t, buf: []u8) ReadError!usize {
295300 if (builtin.os == .windows) {
296 return windows.ReadFile(fd, buf);
301 return windows.ReadFile(fd, buf, null);
297302 }
298303
299304 if (builtin.os == .wasi and !builtin.link_libc) {
......@@ -335,9 +340,37 @@ pub fn read(fd: fd_t, buf: []u8) ReadError!usize {
335340}
336341
337342/// Number of bytes read is returned. Upon reading end-of-file, zero is returned.
338/// If the application has a global event loop enabled, EAGAIN is handled
339/// via the event loop. Otherwise EAGAIN results in error.WouldBlock.
343///
344/// For POSIX systems, if the application has a global event loop enabled, EAGAIN is handled
345/// via the event loop. Otherwise EAGAIN results in `error.WouldBlock`.
346/// On Windows, if the application has a global event loop enabled, I/O Completion Ports are
347/// used to perform the I/O. `error.WouldBlock` is not possible on Windows.
348///
349/// This operation is non-atomic on the following systems:
350/// * Windows
351/// On these systems, the read races with concurrent writes to the same file descriptor.
340352pub fn readv(fd: fd_t, iov: []const iovec) ReadError!usize {
353 if (builtin.os == .windows) {
354 // TODO batch these into parallel requests
355 var off: usize = 0;
356 var iov_i: usize = 0;
357 var inner_off: usize = 0;
358 while (true) {
359 const v = iov[iov_i];
360 const amt_read = try read(fd, v.iov_base[inner_off .. v.iov_len - inner_off]);
361 off += amt_read;
362 inner_off += amt_read;
363 if (inner_off == v.len) {
364 iov_i += 1;
365 inner_off = 0;
366 if (iov_i == iov.len) {
367 return off;
368 }
369 }
370 if (amt_read == 0) return off; // EOF
371 } else unreachable; // TODO https://github.com/ziglang/zig/issues/707
372 }
373
341374 while (true) {
342375 // TODO handle the case when iov_len is too large and get rid of this @intCast
343376 const rc = system.readv(fd, iov.ptr, @intCast(u32, iov.len));
......@@ -363,8 +396,56 @@ pub fn readv(fd: fd_t, iov: []const iovec) ReadError!usize {
363396}
364397
365398/// Number of bytes read is returned. Upon reading end-of-file, zero is returned.
366/// If the application has a global event loop enabled, EAGAIN is handled
367/// via the event loop. Otherwise EAGAIN results in error.WouldBlock.
399///
400/// Retries when interrupted by a signal.
401///
402/// For POSIX systems, if the application has a global event loop enabled, EAGAIN is handled
403/// via the event loop. Otherwise EAGAIN results in `error.WouldBlock`.
404/// On Windows, if the application has a global event loop enabled, I/O Completion Ports are
405/// used to perform the I/O. `error.WouldBlock` is not possible on Windows.
406pub fn pread(fd: fd_t, buf: []u8, offset: u64) ReadError!usize {
407 if (builtin.os == .windows) {
408 return windows.ReadFile(fd, buf, offset);
409 }
410
411 while (true) {
412 const rc = system.pread(fd, buf.ptr, buf.len, offset);
413 switch (errno(rc)) {
414 0 => return @intCast(usize, rc),
415 EINTR => continue,
416 EINVAL => unreachable,
417 EFAULT => unreachable,
418 EAGAIN => if (std.event.Loop.instance) |loop| {
419 loop.waitUntilFdReadable(fd);
420 continue;
421 } else {
422 return error.WouldBlock;
423 },
424 EBADF => unreachable, // Always a race condition.
425 EIO => return error.InputOutput,
426 EISDIR => return error.IsDir,
427 ENOBUFS => return error.SystemResources,
428 ENOMEM => return error.SystemResources,
429 ECONNRESET => return error.ConnectionResetByPeer,
430 else => |err| return unexpectedErrno(err),
431 }
432 }
433 return index;
434}
435
436/// Number of bytes read is returned. Upon reading end-of-file, zero is returned.
437///
438/// Retries when interrupted by a signal.
439///
440/// For POSIX systems, if the application has a global event loop enabled, EAGAIN is handled
441/// via the event loop. Otherwise EAGAIN results in `error.WouldBlock`.
442/// On Windows, if the application has a global event loop enabled, I/O Completion Ports are
443/// used to perform the I/O. `error.WouldBlock` is not possible on Windows.
444///
445/// This operation is non-atomic on the following systems:
446/// * Darwin
447/// * Windows
448/// On these systems, the read races with concurrent writes to the same file descriptor.
368449pub fn preadv(fd: fd_t, iov: []const iovec, offset: u64) ReadError!usize {
369450 if (comptime std.Target.current.isDarwin()) {
370451 // Darwin does not have preadv but it does have pread.
......@@ -409,6 +490,28 @@ pub fn preadv(fd: fd_t, iov: []const iovec, offset: u64) ReadError!usize {
409490 }
410491 }
411492 }
493
494 if (builtin.os == .windows) {
495 // TODO batch these into parallel requests
496 var off: usize = 0;
497 var iov_i: usize = 0;
498 var inner_off: usize = 0;
499 while (true) {
500 const v = iov[iov_i];
501 const amt_read = try pread(fd, v.iov_base[inner_off .. v.iov_len - inner_off], offset + off);
502 off += amt_read;
503 inner_off += amt_read;
504 if (inner_off == v.len) {
505 iov_i += 1;
506 inner_off = 0;
507 if (iov_i == iov.len) {
508 return off;
509 }
510 }
511 if (amt_read == 0) return off; // EOF
512 } else unreachable; // TODO https://github.com/ziglang/zig/issues/707
513 }
514
412515 while (true) {
413516 // TODO handle the case when iov_len is too large and get rid of this @intCast
414517 const rc = system.preadv(fd, iov.ptr, @intCast(u32, iov.len), offset);
......@@ -451,11 +554,9 @@ pub const WriteError = error{
451554/// Write to a file descriptor. Keeps trying if it gets interrupted.
452555/// If the application has a global event loop enabled, EAGAIN is handled
453556/// via the event loop. Otherwise EAGAIN results in error.WouldBlock.
454/// TODO evented I/O integration is disabled until
455/// https://github.com/ziglang/zig/issues/3557 is solved.
456557pub fn write(fd: fd_t, bytes: []const u8) WriteError!void {
457558 if (builtin.os == .windows) {
458 return windows.WriteFile(fd, bytes);
559 return windows.WriteFile(fd, bytes, null);
459560 }
460561
461562 if (builtin.os == .wasi and !builtin.link_libc) {
......@@ -488,14 +589,12 @@ pub fn write(fd: fd_t, bytes: []const u8) WriteError!void {
488589 EINTR => continue,
489590 EINVAL => unreachable,
490591 EFAULT => unreachable,
491 // TODO https://github.com/ziglang/zig/issues/3557
492 EAGAIN => return error.WouldBlock,
493 //EAGAIN => if (std.event.Loop.instance) |loop| {
494 // loop.waitUntilFdWritable(fd);
495 // continue;
496 //} else {
497 // return error.WouldBlock;
498 //},
592 EAGAIN => if (std.event.Loop.instance) |loop| {
593 loop.waitUntilFdWritable(fd);
594 continue;
595 } else {
596 return error.WouldBlock;
597 },
499598 EBADF => unreachable, // Always a race condition.
500599 EDESTADDRREQ => unreachable, // `connect` was never called.
501600 EDQUOT => return error.DiskQuota,
......@@ -540,8 +639,57 @@ pub fn writev(fd: fd_t, iov: []const iovec_const) WriteError!void {
540639 }
541640}
542641
642/// Write to a file descriptor, with a position offset.
643///
644/// Retries when interrupted by a signal.
645///
646/// For POSIX systems, if the application has a global event loop enabled, EAGAIN is handled
647/// via the event loop. Otherwise EAGAIN results in `error.WouldBlock`.
648/// On Windows, if the application has a global event loop enabled, I/O Completion Ports are
649/// used to perform the I/O. `error.WouldBlock` is not possible on Windows.
650pub fn pwrite(fd: fd_t, bytes: []const u8, offset: u64) WriteError!void {
651 if (comptime std.Target.current.isWindows()) {
652 return windows.WriteFile(fd, bytes, offset);
653 }
654
655 while (true) {
656 const rc = system.pwrite(fd, bytes.ptr, bytes.len, offset);
657 switch (errno(rc)) {
658 0 => return,
659 EINTR => continue,
660 EINVAL => unreachable,
661 EFAULT => unreachable,
662 EAGAIN => if (std.event.Loop.instance) |loop| {
663 loop.waitUntilFdWritable(fd);
664 continue;
665 } else {
666 return error.WouldBlock;
667 },
668 EBADF => unreachable, // Always a race condition.
669 EDESTADDRREQ => unreachable, // `connect` was never called.
670 EDQUOT => return error.DiskQuota,
671 EFBIG => return error.FileTooBig,
672 EIO => return error.InputOutput,
673 ENOSPC => return error.NoSpaceLeft,
674 EPERM => return error.AccessDenied,
675 EPIPE => return error.BrokenPipe,
676 else => |err| return unexpectedErrno(err),
677 }
678 }
679}
680
543681/// Write multiple buffers to a file descriptor, with a position offset.
544/// Keeps trying if it gets interrupted.
682///
683/// Retries when interrupted by a signal.
684///
685/// If the application has a global event loop enabled, EAGAIN is handled
686/// via the event loop. Otherwise EAGAIN results in `error.WouldBlock`.
687///
688/// This operation is non-atomic on the following systems:
689/// * Darwin
690/// * Windows
691/// On these systems, the write races with concurrent writes to the same file descriptor, and
692/// the file can be in a partially written state when an error occurs.
545693pub fn pwritev(fd: fd_t, iov: []const iovec_const, offset: u64) WriteError!void {
546694 if (comptime std.Target.current.isDarwin()) {
547695 // Darwin does not have pwritev but it does have pwrite.
......@@ -589,6 +737,15 @@ pub fn pwritev(fd: fd_t, iov: []const iovec_const, offset: u64) WriteError!void
589737 }
590738 }
591739
740 if (comptime std.Target.current.isWindows()) {
741 var off = offset;
742 for (iov) |item| {
743 try pwrite(fd, item.iov_base[0..item.iov_len], off);
744 off += buf.len;
745 }
746 return;
747 }
748
592749 while (true) {
593750 // TODO handle the case when iov_len is too large and get rid of this @intCast
594751 const rc = system.pwritev(fd, iov.ptr, @intCast(u32, iov.len), offset);
......@@ -694,7 +851,7 @@ pub fn openC(file_path: [*:0]const u8, flags: u32, perm: usize) OpenError!fd_t {
694851/// Open and possibly create a file. Keeps trying if it gets interrupted.
695852/// `file_path` is relative to the open directory handle `dir_fd`.
696853/// See also `openatC`.
697pub fn openat(dir_fd: fd_t, file_path: []const u8, flags: u32, mode: usize) OpenError!fd_t {
854pub fn openat(dir_fd: fd_t, file_path: []const u8, flags: u32, mode: mode_t) OpenError!fd_t {
698855 const file_path_c = try toPosixPath(file_path);
699856 return openatC(dir_fd, &file_path_c, flags, mode);
700857}
......@@ -702,7 +859,7 @@ pub fn openat(dir_fd: fd_t, file_path: []const u8, flags: u32, mode: usize) Open
702859/// Open and possibly create a file. Keeps trying if it gets interrupted.
703860/// `file_path` is relative to the open directory handle `dir_fd`.
704861/// See also `openat`.
705pub fn openatC(dir_fd: fd_t, file_path: [*:0]const u8, flags: u32, mode: usize) OpenError!fd_t {
862pub fn openatC(dir_fd: fd_t, file_path: [*:0]const u8, flags: u32, mode: mode_t) OpenError!fd_t {
706863 while (true) {
707864 const rc = system.openat(dir_fd, file_path, flags, mode);
708865 switch (errno(rc)) {
......@@ -2372,6 +2529,22 @@ pub fn pipe() PipeError![2]fd_t {
23722529}
23732530
23742531pub fn pipe2(flags: u32) PipeError![2]fd_t {
2532 if (comptime std.Target.current.isDarwin()) {
2533 var fds: [2]fd_t = try pipe();
2534 if (flags == 0) return fds;
2535 errdefer {
2536 close(fds[0]);
2537 close(fds[1]);
2538 }
2539 for (fds) |fd| switch (errno(system.fcntl(fd, F_SETFL, flags))) {
2540 0 => {},
2541 EINVAL => unreachable, // Invalid flags
2542 EBADF => unreachable, // Always a race condition
2543 else => |err| return unexpectedErrno(err),
2544 };
2545 return fds;
2546 }
2547
23752548 var fds: [2]fd_t = undefined;
23762549 switch (errno(system.pipe2(&fds, flags))) {
23772550 0 => return fds,
lib/std/os/bits/darwin.zig+159
......@@ -4,6 +4,7 @@ const maxInt = std.math.maxInt;
44
55pub const fd_t = c_int;
66pub const pid_t = c_int;
7pub const mode_t = c_uint;
78
89pub const in_port_t = u16;
910pub const sa_family_t = u8;
......@@ -1223,3 +1224,161 @@ pub const RTLD_NEXT = @intToPtr(*c_void, ~maxInt(usize));
12231224pub const RTLD_DEFAULT = @intToPtr(*c_void, ~maxInt(usize) - 1);
12241225pub const RTLD_SELF = @intToPtr(*c_void, ~maxInt(usize) - 2);
12251226pub const RTLD_MAIN_ONLY = @intToPtr(*c_void, ~maxInt(usize) - 4);
1227
1228/// duplicate file descriptor
1229pub const F_DUPFD = 0;
1230
1231/// get file descriptor flags
1232pub const F_GETFD = 1;
1233
1234/// set file descriptor flags
1235pub const F_SETFD = 2;
1236
1237/// get file status flags
1238pub const F_GETFL = 3;
1239
1240/// set file status flags
1241pub const F_SETFL = 4;
1242
1243/// get SIGIO/SIGURG proc/pgrp
1244pub const F_GETOWN = 5;
1245
1246/// set SIGIO/SIGURG proc/pgrp
1247pub const F_SETOWN = 6;
1248
1249/// get record locking information
1250pub const F_GETLK = 7;
1251
1252/// set record locking information
1253pub const F_SETLK = 8;
1254
1255/// F_SETLK; wait if blocked
1256pub const F_SETLKW = 9;
1257
1258/// F_SETLK; wait if blocked, return on timeout
1259pub const F_SETLKWTIMEOUT = 10;
1260pub const F_FLUSH_DATA = 40;
1261
1262/// Used for regression test
1263pub const F_CHKCLEAN = 41;
1264
1265/// Preallocate storage
1266pub const F_PREALLOCATE = 42;
1267
1268/// Truncate a file without zeroing space
1269pub const F_SETSIZE = 43;
1270
1271/// Issue an advisory read async with no copy to user
1272pub const F_RDADVISE = 44;
1273
1274/// turn read ahead off/on for this fd
1275pub const F_RDAHEAD = 45;
1276
1277/// turn data caching off/on for this fd
1278pub const F_NOCACHE = 48;
1279
1280/// file offset to device offset
1281pub const F_LOG2PHYS = 49;
1282
1283/// return the full path of the fd
1284pub const F_GETPATH = 50;
1285
1286/// fsync + ask the drive to flush to the media
1287pub const F_FULLFSYNC = 51;
1288
1289/// find which component (if any) is a package
1290pub const F_PATHPKG_CHECK = 52;
1291
1292/// "freeze" all fs operations
1293pub const F_FREEZE_FS = 53;
1294
1295/// "thaw" all fs operations
1296pub const F_THAW_FS = 54;
1297
1298/// turn data caching off/on (globally) for this file
1299pub const F_GLOBAL_NOCACHE = 55;
1300
1301/// add detached signatures
1302pub const F_ADDSIGS = 59;
1303
1304/// add signature from same file (used by dyld for shared libs)
1305pub const F_ADDFILESIGS = 61;
1306
1307/// used in conjunction with F_NOCACHE to indicate that DIRECT, synchonous writes
1308/// should not be used (i.e. its ok to temporaily create cached pages)
1309pub const F_NODIRECT = 62;
1310
1311///Get the protection class of a file from the EA, returns int
1312pub const F_GETPROTECTIONCLASS = 63;
1313
1314///Set the protection class of a file for the EA, requires int
1315pub const F_SETPROTECTIONCLASS = 64;
1316
1317///file offset to device offset, extended
1318pub const F_LOG2PHYS_EXT = 65;
1319
1320///get record locking information, per-process
1321pub const F_GETLKPID = 66;
1322
1323///Mark the file as being the backing store for another filesystem
1324pub const F_SETBACKINGSTORE = 70;
1325
1326///return the full path of the FD, but error in specific mtmd circumstances
1327pub const F_GETPATH_MTMINFO = 71;
1328
1329///Returns the code directory, with associated hashes, to the caller
1330pub const F_GETCODEDIR = 72;
1331
1332///No SIGPIPE generated on EPIPE
1333pub const F_SETNOSIGPIPE = 73;
1334
1335///Status of SIGPIPE for this fd
1336pub const F_GETNOSIGPIPE = 74;
1337
1338///For some cases, we need to rewrap the key for AKS/MKB
1339pub const F_TRANSCODEKEY = 75;
1340
1341///file being written to a by single writer... if throttling enabled, writes
1342///may be broken into smaller chunks with throttling in between
1343pub const F_SINGLE_WRITER = 76;
1344
1345///Get the protection version number for this filesystem
1346pub const F_GETPROTECTIONLEVEL = 77;
1347
1348///Add detached code signatures (used by dyld for shared libs)
1349pub const F_FINDSIGS = 78;
1350
1351///Add signature from same file, only if it is signed by Apple (used by dyld for simulator)
1352pub const F_ADDFILESIGS_FOR_DYLD_SIM = 83;
1353
1354///fsync + issue barrier to drive
1355pub const F_BARRIERFSYNC = 85;
1356
1357///Add signature from same file, return end offset in structure on success
1358pub const F_ADDFILESIGS_RETURN = 97;
1359
1360///Check if Library Validation allows this Mach-O file to be mapped into the calling process
1361pub const F_CHECK_LV = 98;
1362
1363///Deallocate a range of the file
1364pub const F_PUNCHHOLE = 99;
1365
1366///Trim an active file
1367pub const F_TRIM_ACTIVE_FILE = 100;
1368
1369pub const FCNTL_FS_SPECIFIC_BASE = 0x00010000;
1370
1371///mark the dup with FD_CLOEXEC
1372pub const F_DUPFD_CLOEXEC = 67;
1373
1374///close-on-exec flag
1375pub const FD_CLOEXEC = 1;
1376
1377/// shared or read lock
1378pub const F_RDLCK = 1;
1379
1380/// unlock
1381pub const F_UNLCK = 2;
1382
1383/// exclusive or write lock
1384pub const F_WRLCK = 3;
lib/std/os/bits/dragonfly.zig+1
......@@ -7,6 +7,7 @@ pub fn S_ISCHR(m: u32) bool {
77pub const fd_t = c_int;
88pub const pid_t = c_int;
99pub const off_t = c_long;
10pub const mode_t = c_uint;
1011
1112pub const ENOTSUP = EOPNOTSUPP;
1213pub const EWOULDBLOCK = EAGAIN;
lib/std/os/bits/freebsd.zig+1
......@@ -3,6 +3,7 @@ const maxInt = std.math.maxInt;
33
44pub const fd_t = c_int;
55pub const pid_t = c_int;
6pub const mode_t = c_uint;
67
78pub const socklen_t = u32;
89
lib/std/os/bits/linux/x86_64.zig+2
......@@ -12,6 +12,8 @@ const socklen_t = linux.socklen_t;
1212const iovec = linux.iovec;
1313const iovec_const = linux.iovec_const;
1414
15pub const mode_t = usize;
16
1517pub const SYS_read = 0;
1618pub const SYS_write = 1;
1719pub const SYS_open = 2;
lib/std/os/bits/netbsd.zig+1
......@@ -3,6 +3,7 @@ const maxInt = std.math.maxInt;
33
44pub const fd_t = c_int;
55pub const pid_t = c_int;
6pub const mode_t = c_uint;
67
78/// Renamed from `kevent` to `Kevent` to avoid conflict with function name.
89pub const Kevent = extern struct {
lib/std/os/bits/wasi.zig+1
......@@ -130,6 +130,7 @@ pub const EVENTTYPE_FD_WRITE: eventtype_t = 2;
130130pub const exitcode_t = u32;
131131
132132pub const fd_t = u32;
133pub const mode_t = u32;
133134
134135pub const fdflags_t = u16;
135136pub const FDFLAG_APPEND: fdflags_t = 0x0001;
lib/std/os/bits/windows.zig+1
......@@ -5,6 +5,7 @@ const ws2_32 = @import("../windows/ws2_32.zig");
55
66pub const fd_t = HANDLE;
77pub const pid_t = HANDLE;
8pub const mode_t = u0;
89
910pub const PATH_MAX = 260;
1011
lib/std/os/windows.zig+128-29
......@@ -344,24 +344,77 @@ pub fn FindClose(hFindFile: HANDLE) void {
344344 assert(kernel32.FindClose(hFindFile) != 0);
345345}
346346
347pub const ReadFileError = error{Unexpected};
348
349pub fn ReadFile(in_hFile: HANDLE, buffer: []u8) ReadFileError!usize {
350 var index: usize = 0;
351 while (index < buffer.len) {
352 const want_read_count = @intCast(DWORD, math.min(@as(DWORD, maxInt(DWORD)), buffer.len - index));
353 var amt_read: DWORD = undefined;
354 if (kernel32.ReadFile(in_hFile, buffer.ptr + index, want_read_count, &amt_read, null) == 0) {
355 switch (kernel32.GetLastError()) {
356 .OPERATION_ABORTED => continue,
357 .BROKEN_PIPE => return index,
358 else => |err| return unexpectedError(err),
347pub const ReadFileError = error{
348 OperationAborted,
349 BrokenPipe,
350 Unexpected,
351};
352
353/// If buffer's length exceeds what a Windows DWORD integer can hold, it will be broken into
354/// multiple non-atomic reads.
355pub fn ReadFile(in_hFile: HANDLE, buffer: []u8, offset: ?u64) ReadFileError!usize {
356 if (std.event.Loop.instance) |loop| {
357 // TODO support async ReadFile with no offset
358 const off = offset.?;
359 var resume_node = std.event.Loop.ResumeNode.Basic{
360 .base = .{
361 .id = .Basic,
362 .handle = @frame(),
363 .overlapped = OVERLAPPED{
364 .Internal = 0,
365 .InternalHigh = 0,
366 .Offset = @truncate(u32, off),
367 .OffsetHigh = @truncate(u32, off >> 32),
368 .hEvent = null,
369 },
370 },
371 };
372 // TODO only call create io completion port once per fd
373 _ = windows.CreateIoCompletionPort(fd, loop.os_data.io_port, undefined, undefined) catch undefined;
374 loop.beginOneEvent();
375 suspend {
376 // TODO handle buffer bigger than DWORD can hold
377 _ = windows.kernel32.ReadFile(fd, buffer.ptr, @intCast(windows.DWORD, buffer.len), null, &resume_node.base.overlapped);
378 }
379 var bytes_transferred: windows.DWORD = undefined;
380 if (windows.kernel32.GetOverlappedResult(fd, &resume_node.base.overlapped, &bytes_transferred, windows.FALSE) == 0) {
381 switch (windows.kernel32.GetLastError()) {
382 .IO_PENDING => unreachable,
383 .OPERATION_ABORTED => return error.OperationAborted,
384 .BROKEN_PIPE => return error.BrokenPipe,
385 .HANDLE_EOF => return @as(usize, bytes_transferred),
386 else => |err| return windows.unexpectedError(err),
387 }
388 }
389 return @as(usize, bytes_transferred);
390 } else {
391 var index: usize = 0;
392 while (index < buffer.len) {
393 const want_read_count = @intCast(DWORD, math.min(@as(DWORD, maxInt(DWORD)), buffer.len - index));
394 var amt_read: DWORD = undefined;
395 var overlapped_data: OVERLAPPED = undefined;
396 const overlapped: ?*OVERLAPPED = if (offset) |off| blk: {
397 overlapped_data = .{
398 .Internal = 0,
399 .InternalHigh = 0,
400 .Offset = @truncate(u32, off + index),
401 .OffsetHigh = @truncate(u32, (off + index) >> 32),
402 .hEvent = null,
403 };
404 break :blk &overlapped_data;
405 } else null;
406 if (kernel32.ReadFile(in_hFile, buffer.ptr + index, want_read_count, &amt_read, overlapped) == 0) {
407 switch (kernel32.GetLastError()) {
408 .OPERATION_ABORTED => continue,
409 .BROKEN_PIPE => return index,
410 else => |err| return unexpectedError(err),
411 }
359412 }
413 if (amt_read == 0) return index;
414 index += amt_read;
360415 }
361 if (amt_read == 0) return index;
362 index += amt_read;
416 return index;
363417 }
364 return index;
365418}
366419
367420pub const WriteFileError = error{
......@@ -371,20 +424,66 @@ pub const WriteFileError = error{
371424 Unexpected,
372425};
373426
374/// This function is for blocking file descriptors only. For non-blocking, see
375/// `WriteFileAsync`.
376pub fn WriteFile(handle: HANDLE, bytes: []const u8) WriteFileError!void {
377 var bytes_written: DWORD = undefined;
378 // TODO replace this @intCast with a loop that writes all the bytes
379 if (kernel32.WriteFile(handle, bytes.ptr, @intCast(u32, bytes.len), &bytes_written, null) == 0) {
380 switch (kernel32.GetLastError()) {
381 .INVALID_USER_BUFFER => return error.SystemResources,
382 .NOT_ENOUGH_MEMORY => return error.SystemResources,
383 .OPERATION_ABORTED => return error.OperationAborted,
384 .NOT_ENOUGH_QUOTA => return error.SystemResources,
385 .IO_PENDING => unreachable, // this function is for blocking files only
386 .BROKEN_PIPE => return error.BrokenPipe,
387 else => |err| return unexpectedError(err),
427pub fn WriteFile(handle: HANDLE, bytes: []const u8, offset: ?u64) WriteFileError!void {
428 if (std.event.Loop.instance) |loop| {
429 // TODO support async WriteFile with no offset
430 const off = offset.?;
431 var resume_node = std.event.Loop.ResumeNode.Basic{
432 .base = .{
433 .id = .Basic,
434 .handle = @frame(),
435 .overlapped = OVERLAPPED{
436 .Internal = 0,
437 .InternalHigh = 0,
438 .Offset = @truncate(u32, off),
439 .OffsetHigh = @truncate(u32, off >> 32),
440 .hEvent = null,
441 },
442 },
443 };
444 // TODO only call create io completion port once per fd
445 _ = CreateIoCompletionPort(fd, loop.os_data.io_port, undefined, undefined);
446 loop.beginOneEvent();
447 suspend {
448 // TODO replace this @intCast with a loop that writes all the bytes
449 _ = kernel32.WriteFile(fd, bytes.ptr, @intCast(windows.DWORD, bytes.len), null, &resume_node.base.overlapped);
450 }
451 var bytes_transferred: windows.DWORD = undefined;
452 if (kernel32.GetOverlappedResult(fd, &resume_node.base.overlapped, &bytes_transferred, FALSE) == 0) {
453 switch (kernel32.GetLastError()) {
454 .IO_PENDING => unreachable,
455 .INVALID_USER_BUFFER => return error.SystemResources,
456 .NOT_ENOUGH_MEMORY => return error.SystemResources,
457 .OPERATION_ABORTED => return error.OperationAborted,
458 .NOT_ENOUGH_QUOTA => return error.SystemResources,
459 .BROKEN_PIPE => return error.BrokenPipe,
460 else => |err| return windows.unexpectedError(err),
461 }
462 }
463 } else {
464 var bytes_written: DWORD = undefined;
465 var overlapped_data: OVERLAPPED = undefined;
466 const overlapped: ?*OVERLAPPED = if (offset) |off| blk: {
467 overlapped_data = .{
468 .Internal = 0,
469 .InternalHigh = 0,
470 .Offset = @truncate(u32, off),
471 .OffsetHigh = @truncate(u32, off >> 32),
472 .hEvent = null,
473 };
474 break :blk &overlapped_data;
475 } else null;
476 // TODO replace this @intCast with a loop that writes all the bytes
477 if (kernel32.WriteFile(handle, bytes.ptr, @intCast(u32, bytes.len), &bytes_written, overlapped) == 0) {
478 switch (kernel32.GetLastError()) {
479 .INVALID_USER_BUFFER => return error.SystemResources,
480 .NOT_ENOUGH_MEMORY => return error.SystemResources,
481 .OPERATION_ABORTED => return error.OperationAborted,
482 .NOT_ENOUGH_QUOTA => return error.SystemResources,
483 .IO_PENDING => unreachable, // this function is for blocking files only
484 .BROKEN_PIPE => return error.BrokenPipe,
485 else => |err| return unexpectedError(err),
486 }
388487 }
389488 }
390489}
lib/std/special/test_runner.zig+25-1
......@@ -2,6 +2,8 @@ const std = @import("std");
22const io = std.io;
33const builtin = @import("builtin");
44
5pub const io_mode: io.Mode = builtin.test_io_mode;
6
57pub fn main() anyerror!void {
68 const test_fn_list = builtin.test_functions;
79 var ok_count: usize = 0;
......@@ -12,6 +14,11 @@ pub fn main() anyerror!void {
1214 error.TimerUnsupported => @panic("timer unsupported"),
1315 };
1416
17 var async_frame_buffer: []align(std.Target.stack_align) u8 = undefined;
18 // TODO this is on the next line (using `undefined` above) because otherwise zig incorrectly
19 // ignores the alignment of the slice.
20 async_frame_buffer = &[_]u8{};
21
1522 for (test_fn_list) |test_fn, i| {
1623 std.testing.base_allocator_instance.reset();
1724
......@@ -21,7 +28,24 @@ pub fn main() anyerror!void {
2128 if (progress.terminal == null) {
2229 std.debug.warn("{}/{} {}...", .{ i + 1, test_fn_list.len, test_fn.name });
2330 }
24 if (test_fn.func()) |_| {
31 const result = if (test_fn.async_frame_size) |size| switch (io_mode) {
32 .evented => blk: {
33 if (async_frame_buffer.len < size) {
34 std.heap.page_allocator.free(async_frame_buffer);
35 async_frame_buffer = try std.heap.page_allocator.alignedAlloc(u8, std.Target.stack_align, size);
36 }
37 const casted_fn = @ptrCast(async fn () anyerror!void, test_fn.func);
38 break :blk await @asyncCall(async_frame_buffer, {}, casted_fn);
39 },
40 .blocking => {
41 skip_count += 1;
42 test_node.end();
43 progress.log("{}...SKIP (async test)\n", .{test_fn.name});
44 if (progress.terminal == null) std.debug.warn("SKIP (async test)\n", .{});
45 continue;
46 },
47 } else test_fn.func();
48 if (result) |_| {
2549 ok_count += 1;
2650 test_node.end();
2751 std.testing.allocator_instance.validate() catch |err| switch (err) {
src-self-hosted/compilation.zig+12-12
......@@ -29,7 +29,7 @@ const Package = @import("package.zig").Package;
2929const link = @import("link.zig").link;
3030const LibCInstallation = @import("libc_installation.zig").LibCInstallation;
3131const CInt = @import("c_int.zig").CInt;
32const fs = event.fs;
32const fs = std.fs;
3333const util = @import("util.zig");
3434
3535const max_src_size = 2 * 1024 * 1024 * 1024; // 2 GiB
......@@ -442,7 +442,7 @@ pub const Compilation = struct {
442442 comp.name = try Buffer.init(comp.arena(), name);
443443 comp.llvm_triple = try util.getTriple(comp.arena(), target);
444444 comp.llvm_target = try util.llvmTargetFromTriple(comp.llvm_triple);
445 comp.zig_std_dir = try std.fs.path.join(comp.arena(), &[_][]const u8{ zig_lib_dir, "std" });
445 comp.zig_std_dir = try fs.path.join(comp.arena(), &[_][]const u8{ zig_lib_dir, "std" });
446446
447447 const opt_level = switch (build_mode) {
448448 .Debug => llvm.CodeGenLevelNone,
......@@ -488,8 +488,8 @@ pub const Compilation = struct {
488488 defer comp.events.deinit();
489489
490490 if (root_src_path) |root_src| {
491 const dirname = std.fs.path.dirname(root_src) orelse ".";
492 const basename = std.fs.path.basename(root_src);
491 const dirname = fs.path.dirname(root_src) orelse ".";
492 const basename = fs.path.basename(root_src);
493493
494494 comp.root_package = try Package.create(comp.arena(), dirname, basename);
495495 comp.std_package = try Package.create(comp.arena(), comp.zig_std_dir, "std.zig");
......@@ -521,7 +521,7 @@ pub const Compilation = struct {
521521 if (comp.tmp_dir.getOrNull()) |tmp_dir_result|
522522 if (tmp_dir_result.*) |tmp_dir| {
523523 // TODO evented I/O?
524 std.fs.deleteTree(tmp_dir) catch {};
524 fs.deleteTree(tmp_dir) catch {};
525525 } else |_| {};
526526 }
527527
......@@ -797,7 +797,7 @@ pub const Compilation = struct {
797797
798798 async fn rebuildFile(self: *Compilation, root_scope: *Scope.Root) BuildError!void {
799799 const tree_scope = blk: {
800 const source_code = fs.readFile(
800 const source_code = fs.cwd().readFileAlloc(
801801 self.gpa(),
802802 root_scope.realpath,
803803 max_src_size,
......@@ -935,8 +935,8 @@ pub const Compilation = struct {
935935 fn initialCompile(self: *Compilation) !void {
936936 if (self.root_src_path) |root_src_path| {
937937 const root_scope = blk: {
938 // TODO async/await std.fs.realpath
939 const root_src_real_path = std.fs.realpathAlloc(self.gpa(), root_src_path) catch |err| {
938 // TODO async/await fs.realpath
939 const root_src_real_path = fs.realpathAlloc(self.gpa(), root_src_path) catch |err| {
940940 try self.addCompileErrorCli(root_src_path, "unable to open: {}", .{@errorName(err)});
941941 return;
942942 };
......@@ -1157,7 +1157,7 @@ pub const Compilation = struct {
11571157 const file_name = try std.fmt.allocPrint(self.gpa(), "{}{}", .{ file_prefix[0..], suffix });
11581158 defer self.gpa().free(file_name);
11591159
1160 const full_path = try std.fs.path.join(self.gpa(), &[_][]const u8{ tmp_dir, file_name[0..] });
1160 const full_path = try fs.path.join(self.gpa(), &[_][]const u8{ tmp_dir, file_name[0..] });
11611161 errdefer self.gpa().free(full_path);
11621162
11631163 return Buffer.fromOwnedSlice(self.gpa(), full_path);
......@@ -1178,8 +1178,8 @@ pub const Compilation = struct {
11781178 const zig_dir_path = try getZigDir(self.gpa());
11791179 defer self.gpa().free(zig_dir_path);
11801180
1181 const tmp_dir = try std.fs.path.join(self.arena(), &[_][]const u8{ zig_dir_path, comp_dir_name[0..] });
1182 try std.fs.makePath(self.gpa(), tmp_dir);
1181 const tmp_dir = try fs.path.join(self.arena(), &[_][]const u8{ zig_dir_path, comp_dir_name[0..] });
1182 try fs.makePath(self.gpa(), tmp_dir);
11831183 return tmp_dir;
11841184 }
11851185
......@@ -1351,7 +1351,7 @@ async fn addFnToLinkSet(comp: *Compilation, fn_val: *Value.Fn) Compilation.Build
13511351}
13521352
13531353fn getZigDir(allocator: *mem.Allocator) ![]u8 {
1354 return std.fs.getAppDataDir(allocator, "zig");
1354 return fs.getAppDataDir(allocator, "zig");
13551355}
13561356
13571357fn analyzeFnType(
src-self-hosted/dep_tokenizer.zig+10-23
......@@ -998,7 +998,8 @@ fn printCharValues(out: var, bytes: []const u8) !void {
998998
999999fn printUnderstandableChar(out: var, char: u8) !void {
10001000 if (!std.ascii.isPrint(char) or char == ' ') {
1001 std.fmt.format(out.context, anyerror, out.output, "\\x{X:2}", .{char}) catch {};
1001 const output = @typeInfo(@TypeOf(out)).Pointer.child.output;
1002 std.fmt.format(out.context, anyerror, output, "\\x{X:2}", .{char}) catch {};
10021003 } else {
10031004 try out.write("'");
10041005 try out.write(&[_]u8{printable_char_tab[char]});
......@@ -1021,34 +1022,20 @@ comptime {
10211022// output: must be a function that takes a `self` idiom parameter
10221023// and a bytes parameter
10231024// context: must be that self
1024fn makeOutput(output: var, context: var) Output(@TypeOf(output)) {
1025 return Output(@TypeOf(output)){
1026 .output = output,
1025fn makeOutput(comptime output: var, context: var) Output(output, @TypeOf(context)) {
1026 return Output(output, @TypeOf(context)){
10271027 .context = context,
10281028 };
10291029}
10301030
1031fn Output(comptime T: type) type {
1032 const args = switch (@typeInfo(T)) {
1033 .Fn => |f| f.args,
1034 else => @compileError("output parameter is not a function"),
1035 };
1036 if (args.len != 2) {
1037 @compileError("output function must take 2 arguments");
1038 }
1039 const at0 = args[0].arg_type orelse @compileError("output arg[0] does not have a type");
1040 const at1 = args[1].arg_type orelse @compileError("output arg[1] does not have a type");
1041 const arg1p = switch (@typeInfo(at1)) {
1042 .Pointer => |p| p,
1043 else => @compileError("output arg[1] is not a slice"),
1044 };
1045 if (arg1p.child != u8) @compileError("output arg[1] is not a u8 slice");
1031fn Output(comptime output_func: var, comptime Context: type) type {
10461032 return struct {
1047 output: T,
1048 context: at0,
1033 context: Context,
1034
1035 pub const output = output_func;
10491036
1050 fn write(self: *@This(), bytes: []const u8) !void {
1051 try self.output(self.context, bytes);
1037 fn write(self: @This(), bytes: []const u8) !void {
1038 try output_func(self.context, bytes);
10521039 }
10531040 };
10541041}
src-self-hosted/introspect.zig+1-1
......@@ -14,7 +14,7 @@ pub fn testZigInstallPrefix(allocator: *mem.Allocator, test_path: []const u8) ![
1414 const test_index_file = try fs.path.join(allocator, &[_][]const u8{ test_zig_dir, "std", "std.zig" });
1515 defer allocator.free(test_index_file);
1616
17 var file = try fs.File.openRead(test_index_file);
17 var file = try fs.cwd().openRead(test_index_file);
1818 file.close();
1919
2020 return test_zig_dir;
src-self-hosted/main.zig+1-1
......@@ -724,7 +724,7 @@ async fn fmtPath(fmt: *Fmt, file_path_ref: []const u8, check_mode: bool) FmtErro
724724 if (try held.value.put(file_path, {})) |_| return;
725725 }
726726
727 const source_code = event.fs.readFile(
727 const source_code = fs.cwd().readFileAlloc(
728728 fmt.allocator,
729729 file_path,
730730 max_src_size,
src/all_types.hpp+4
......@@ -2246,6 +2246,7 @@ struct CodeGen {
22462246 bool enable_dump_analysis;
22472247 bool enable_doc_generation;
22482248 bool disable_bin_generation;
2249 bool test_is_evented;
22492250 CodeModel code_model;
22502251
22512252 Buf *mmacosx_version_min;
......@@ -2491,6 +2492,9 @@ struct ScopeExpr {
24912492 size_t children_len;
24922493
24932494 MemoizedBool need_spill;
2495 // This is a hack. I apologize for this, I need this to work so that I
2496 // can make progress on other fronts. I'll pay off this tech debt eventually.
2497 bool spill_harder;
24942498};
24952499
24962500// synchronized with code in define_builtin_compile_vars
src/analyze.cpp+29-6
......@@ -6108,11 +6108,14 @@ static void mark_suspension_point(Scope *scope) {
61086108 continue;
61096109 }
61106110 case ScopeIdExpr: {
6111 ScopeExpr *parent_expr_scope = reinterpret_cast<ScopeExpr *>(scope);
61116112 if (!looking_for_exprs) {
6113 if (parent_expr_scope->spill_harder) {
6114 parent_expr_scope->need_spill = MemoizedBoolTrue;
6115 }
61126116 // Now we're only looking for a block, to see if it's in a loop (see the case ScopeIdBlock)
61136117 continue;
61146118 }
6115 ScopeExpr *parent_expr_scope = reinterpret_cast<ScopeExpr *>(scope);
61166119 if (child_expr_scope != nullptr) {
61176120 for (size_t i = 0; parent_expr_scope->children_ptr[i] != child_expr_scope; i += 1) {
61186121 assert(i < parent_expr_scope->children_len);
......@@ -6148,6 +6151,15 @@ static bool scope_needs_spill(Scope *scope) {
61486151 zig_unreachable();
61496152}
61506153
6154static ZigType *resolve_type_isf(ZigType *ty) {
6155 if (ty->id != ZigTypeIdPointer) return ty;
6156 InferredStructField *isf = ty->data.pointer.inferred_struct_field;
6157 if (isf == nullptr) return ty;
6158 TypeStructField *field = find_struct_type_field(isf->inferred_struct_type, isf->field_name);
6159 assert(field != nullptr);
6160 return field->type_entry;
6161}
6162
61516163static Error resolve_async_frame(CodeGen *g, ZigType *frame_type) {
61526164 Error err;
61536165
......@@ -6249,6 +6261,9 @@ static Error resolve_async_frame(CodeGen *g, ZigType *frame_type) {
62496261 }
62506262 ZigFn *callee = call->fn_entry;
62516263 if (callee == nullptr) {
6264 if (call->fn_ref->value->type->data.fn.fn_type_id.cc != CallingConventionAsync) {
6265 continue;
6266 }
62526267 add_node_error(g, call->base.base.source_node,
62536268 buf_sprintf("function is not comptime-known; @asyncCall required"));
62546269 return ErrorSemanticAnalyzeFail;
......@@ -6356,11 +6371,19 @@ static Error resolve_async_frame(CodeGen *g, ZigType *frame_type) {
63566371 IrInstGen *instruction = block->instruction_list.at(instr_i);
63576372 if (instruction->id == IrInstGenIdAwait ||
63586373 instruction->id == IrInstGenIdVarPtr ||
6359 instruction->id == IrInstGenIdAlloca)
6374 instruction->id == IrInstGenIdAlloca ||
6375 instruction->id == IrInstGenIdSpillBegin ||
6376 instruction->id == IrInstGenIdSpillEnd)
63606377 {
63616378 // This instruction does its own spilling specially, or otherwise doesn't need it.
63626379 continue;
63636380 }
6381 if (instruction->id == IrInstGenIdCast &&
6382 reinterpret_cast<IrInstGenCast *>(instruction)->cast_op == CastOpNoop)
6383 {
6384 // The IR instruction exists only to change the type according to Zig. No spill needed.
6385 continue;
6386 }
63646387 if (instruction->value->special != ConstValSpecialRuntime)
63656388 continue;
63666389 if (instruction->base.ref_count == 0)
......@@ -6406,7 +6429,7 @@ static Error resolve_async_frame(CodeGen *g, ZigType *frame_type) {
64066429 } else {
64076430 param_name = buf_sprintf("@arg%" ZIG_PRI_usize, arg_i);
64086431 }
6409 ZigType *param_type = param_info->type;
6432 ZigType *param_type = resolve_type_isf(param_info->type);
64106433 if ((err = type_resolve(g, param_type, ResolveStatusSizeKnown))) {
64116434 return err;
64126435 }
......@@ -6425,7 +6448,7 @@ static Error resolve_async_frame(CodeGen *g, ZigType *frame_type) {
64256448 instruction->field_index = SIZE_MAX;
64266449 ZigType *ptr_type = instruction->base.value->type;
64276450 assert(ptr_type->id == ZigTypeIdPointer);
6428 ZigType *child_type = ptr_type->data.pointer.child_type;
6451 ZigType *child_type = resolve_type_isf(ptr_type->data.pointer.child_type);
64296452 if (!type_has_bits(child_type))
64306453 continue;
64316454 if (instruction->base.base.ref_count == 0)
......@@ -6452,8 +6475,6 @@ static Error resolve_async_frame(CodeGen *g, ZigType *frame_type) {
64526475 }
64536476 instruction->field_index = fields.length;
64546477
6455 src_assert(child_type->id != ZigTypeIdPointer || child_type->data.pointer.inferred_struct_field == nullptr,
6456 instruction->base.base.source_node);
64576478 fields.append({name, child_type, instruction->align});
64586479 }
64596480
......@@ -8255,6 +8276,8 @@ static void resolve_llvm_types_struct(CodeGen *g, ZigType *struct_type, ResolveS
82558276 size_t debug_field_index = 0;
82568277 for (size_t i = 0; i < field_count; i += 1) {
82578278 TypeStructField *field = struct_type->data.structure.fields[i];
8279 //fprintf(stderr, "%s at gen index %zu\n", buf_ptr(field->name), field->gen_index);
8280
82588281 size_t gen_field_index = field->gen_index;
82598282 if (gen_field_index == SIZE_MAX) {
82608283 continue;
src/codegen.cpp+147-54
......@@ -343,33 +343,67 @@ static LLVMLinkage to_llvm_linkage(GlobalLinkageId id) {
343343 zig_unreachable();
344344}
345345
346struct CalcLLVMFieldIndex {
347 uint32_t offset;
348 uint32_t field_index;
349};
350
351static void calc_llvm_field_index_add(CodeGen *g, CalcLLVMFieldIndex *calc, ZigType *ty) {
352 if (!type_has_bits(ty)) return;
353 uint32_t ty_align = get_abi_alignment(g, ty);
354 if (calc->offset % ty_align != 0) {
355 uint32_t llvm_align = LLVMABIAlignmentOfType(g->target_data_ref, get_llvm_type(g, ty));
356 if (llvm_align >= ty_align) {
357 ty_align = llvm_align; // llvm's padding is sufficient
358 } else if (calc->offset) {
359 calc->field_index += 1; // zig will insert an extra padding field here
360 }
361 calc->offset += ty_align - (calc->offset % ty_align); // padding bytes
362 }
363 calc->offset += ty->abi_size;
364 calc->field_index += 1;
365}
366
346367// label (grep this): [fn_frame_struct_layout]
368static void frame_index_trace_arg_calc(CodeGen *g, CalcLLVMFieldIndex *calc, ZigType *return_type) {
369 calc_llvm_field_index_add(g, calc, g->builtin_types.entry_usize); // function pointer
370 calc_llvm_field_index_add(g, calc, g->builtin_types.entry_usize); // resume index
371 calc_llvm_field_index_add(g, calc, g->builtin_types.entry_usize); // awaiter index
372
373 if (type_has_bits(return_type)) {
374 calc_llvm_field_index_add(g, calc, g->builtin_types.entry_usize); // *ReturnType (callee's)
375 calc_llvm_field_index_add(g, calc, g->builtin_types.entry_usize); // *ReturnType (awaiter's)
376 calc_llvm_field_index_add(g, calc, return_type); // ReturnType
377 }
378}
379
347380static uint32_t frame_index_trace_arg(CodeGen *g, ZigType *return_type) {
348 // [0] *ReturnType (callee's)
349 // [1] *ReturnType (awaiter's)
350 // [2] ReturnType
351 uint32_t return_field_count = type_has_bits(return_type) ? 3 : 0;
352 return frame_ret_start + return_field_count;
381 CalcLLVMFieldIndex calc = {0};
382 frame_index_trace_arg_calc(g, &calc, return_type);
383 return calc.field_index;
353384}
354385
355386// label (grep this): [fn_frame_struct_layout]
356static uint32_t frame_index_arg(CodeGen *g, ZigType *return_type) {
357 bool have_stack_trace = codegen_fn_has_err_ret_tracing_arg(g, return_type);
358 // [0] *StackTrace (callee's)
359 // [1] *StackTrace (awaiter's)
360 uint32_t trace_field_count = have_stack_trace ? 2 : 0;
361 return frame_index_trace_arg(g, return_type) + trace_field_count;
387static void frame_index_arg_calc(CodeGen *g, CalcLLVMFieldIndex *calc, ZigType *return_type) {
388 frame_index_trace_arg_calc(g, calc, return_type);
389
390 if (codegen_fn_has_err_ret_tracing_arg(g, return_type)) {
391 calc_llvm_field_index_add(g, calc, g->builtin_types.entry_usize); // *StackTrace (callee's)
392 calc_llvm_field_index_add(g, calc, g->builtin_types.entry_usize); // *StackTrace (awaiter's)
393 }
362394}
363395
364396// label (grep this): [fn_frame_struct_layout]
365static uint32_t frame_index_trace_stack(CodeGen *g, FnTypeId *fn_type_id) {
366 uint32_t result = frame_index_arg(g, fn_type_id->return_type);
367 for (size_t i = 0; i < fn_type_id->param_count; i += 1) {
368 if (type_has_bits(fn_type_id->param_info->type)) {
369 result += 1;
370 }
397static uint32_t frame_index_trace_stack(CodeGen *g, ZigFn *fn) {
398 size_t field_index = 6;
399 bool have_stack_trace = codegen_fn_has_err_ret_tracing_arg(g, fn->type_entry->data.fn.fn_type_id.return_type);
400 if (have_stack_trace) {
401 field_index += 2;
371402 }
372 return result;
403 field_index += fn->type_entry->data.fn.fn_type_id.param_count;
404 ZigType *locals_struct = fn->frame_type->data.frame.locals_struct;
405 TypeStructField *field = locals_struct->data.structure.fields[field_index];
406 return field->gen_index;
373407}
374408
375409
......@@ -2527,7 +2561,12 @@ static LLVMValueRef ir_render_return(CodeGen *g, IrExecutableGen *executable, Ir
25272561 LLVMBuildRet(g->builder, by_val_value);
25282562 }
25292563 } else if (instruction->operand == nullptr) {
2530 LLVMBuildRetVoid(g->builder);
2564 if (g->cur_ret_ptr == nullptr) {
2565 LLVMBuildRetVoid(g->builder);
2566 } else {
2567 LLVMValueRef by_val_value = gen_load_untyped(g, g->cur_ret_ptr, 0, false, "");
2568 LLVMBuildRet(g->builder, by_val_value);
2569 }
25312570 } else {
25322571 LLVMValueRef value = ir_llvm_value(g, instruction->operand);
25332572 LLVMBuildRet(g->builder, value);
......@@ -3920,7 +3959,9 @@ static void set_call_instr_sret(CodeGen *g, LLVMValueRef call_instr) {
39203959static void render_async_spills(CodeGen *g) {
39213960 ZigType *fn_type = g->cur_fn->type_entry;
39223961 ZigType *import = get_scope_import(&g->cur_fn->fndef_scope->base);
3923 uint32_t async_var_index = frame_index_arg(g, fn_type->data.fn.fn_type_id.return_type);
3962
3963 CalcLLVMFieldIndex arg_calc = {0};
3964 frame_index_arg_calc(g, &arg_calc, fn_type->data.fn.fn_type_id.return_type);
39243965 for (size_t var_i = 0; var_i < g->cur_fn->variable_list.length; var_i += 1) {
39253966 ZigVar *var = g->cur_fn->variable_list.at(var_i);
39263967
......@@ -3941,8 +3982,8 @@ static void render_async_spills(CodeGen *g) {
39413982 continue;
39423983 }
39433984
3944 var->value_ref = LLVMBuildStructGEP(g->builder, g->cur_frame_ptr, async_var_index, var->name);
3945 async_var_index += 1;
3985 calc_llvm_field_index_add(g, &arg_calc, var->var_type);
3986 var->value_ref = LLVMBuildStructGEP(g->builder, g->cur_frame_ptr, arg_calc.field_index - 1, var->name);
39463987 if (var->decl_node) {
39473988 var->di_loc_var = ZigLLVMCreateAutoVariable(g->dbuilder, get_di_scope(g, var->parent_scope),
39483989 var->name, import->data.structure.root_struct->di_file,
......@@ -4023,6 +4064,8 @@ static void gen_init_stack_trace(CodeGen *g, LLVMValueRef trace_field_ptr, LLVMV
40234064}
40244065
40254066static LLVMValueRef ir_render_call(CodeGen *g, IrExecutableGen *executable, IrInstGenCall *instruction) {
4067 Error err;
4068
40264069 LLVMTypeRef usize_type_ref = g->builtin_types.entry_usize->llvm_type;
40274070
40284071 LLVMValueRef fn_val;
......@@ -4053,6 +4096,8 @@ static LLVMValueRef ir_render_call(CodeGen *g, IrExecutableGen *executable, IrIn
40534096 ZigList<ZigType *> gen_param_types = {};
40544097 LLVMValueRef result_loc = instruction->result_loc ? ir_llvm_value(g, instruction->result_loc) : nullptr;
40554098 LLVMValueRef zero = LLVMConstNull(usize_type_ref);
4099 bool need_frame_ptr_ptr_spill = false;
4100 ZigType *anyframe_type = nullptr;
40564101 LLVMValueRef frame_result_loc_uncasted = nullptr;
40574102 LLVMValueRef frame_result_loc;
40584103 LLVMValueRef awaiter_init_val;
......@@ -4091,14 +4136,17 @@ static LLVMValueRef ir_render_call(CodeGen *g, IrExecutableGen *executable, IrIn
40914136
40924137 LLVMPositionBuilderAtEnd(g->builder, ok_block);
40934138 }
4139 need_frame_ptr_ptr_spill = true;
40944140 LLVMValueRef frame_ptr_ptr = LLVMBuildStructGEP(g->builder, frame_slice_ptr, slice_ptr_index, "");
40954141 LLVMValueRef frame_ptr = LLVMBuildLoad(g->builder, frame_ptr_ptr, "");
40964142 if (instruction->fn_entry == nullptr) {
4097 ZigType *anyframe_type = get_any_frame_type(g, src_return_type);
4143 anyframe_type = get_any_frame_type(g, src_return_type);
40984144 frame_result_loc = LLVMBuildBitCast(g->builder, frame_ptr, get_llvm_type(g, anyframe_type), "");
40994145 } else {
4100 ZigType *ptr_frame_type = get_pointer_to_type(g,
4101 get_fn_frame_type(g, instruction->fn_entry), false);
4146 ZigType *frame_type = get_fn_frame_type(g, instruction->fn_entry);
4147 if ((err = type_resolve(g, frame_type, ResolveStatusLLVMFull)))
4148 codegen_report_errors_and_exit(g);
4149 ZigType *ptr_frame_type = get_pointer_to_type(g, frame_type, false);
41024150 frame_result_loc = LLVMBuildBitCast(g->builder, frame_ptr,
41034151 get_llvm_type(g, ptr_frame_type), "");
41044152 }
......@@ -4265,17 +4313,35 @@ static LLVMValueRef ir_render_call(CodeGen *g, IrExecutableGen *executable, IrIn
42654313 LLVMValueRef result;
42664314
42674315 if (callee_is_async) {
4268 uint32_t arg_start_i = frame_index_arg(g, fn_type->data.fn.fn_type_id.return_type);
4316 CalcLLVMFieldIndex arg_calc_start = {0};
4317 frame_index_arg_calc(g, &arg_calc_start, fn_type->data.fn.fn_type_id.return_type);
42694318
42704319 LLVMValueRef casted_frame;
42714320 if (instruction->new_stack != nullptr && instruction->fn_entry == nullptr) {
42724321 // We need the frame type to be a pointer to a struct that includes the args
4273 size_t field_count = arg_start_i + gen_param_values.length;
4322
4323 // Count ahead to determine how many llvm struct fields we need.
4324 CalcLLVMFieldIndex arg_calc = arg_calc_start;
4325 for (size_t i = 0; i < gen_param_types.length; i += 1) {
4326 calc_llvm_field_index_add(g, &arg_calc, gen_param_types.at(i));
4327 }
4328 size_t field_count = arg_calc.field_index;
4329
42744330 LLVMTypeRef *field_types = allocate_nonzero<LLVMTypeRef>(field_count);
42754331 LLVMGetStructElementTypes(LLVMGetElementType(LLVMTypeOf(frame_result_loc)), field_types);
4276 assert(LLVMCountStructElementTypes(LLVMGetElementType(LLVMTypeOf(frame_result_loc))) == arg_start_i);
4332 assert(LLVMCountStructElementTypes(LLVMGetElementType(LLVMTypeOf(frame_result_loc))) == arg_calc_start.field_index);
4333
4334 arg_calc = arg_calc_start;
42774335 for (size_t arg_i = 0; arg_i < gen_param_values.length; arg_i += 1) {
4278 field_types[arg_start_i + arg_i] = LLVMTypeOf(gen_param_values.at(arg_i));
4336 CalcLLVMFieldIndex prev = arg_calc;
4337 calc_llvm_field_index_add(g, &arg_calc, gen_param_types.at(arg_i));
4338 field_types[arg_calc.field_index - 1] = LLVMTypeOf(gen_param_values.at(arg_i));
4339 if (arg_calc.field_index - prev.field_index > 1) {
4340 // Padding field
4341 uint32_t pad_bytes = arg_calc.offset - prev.offset - gen_param_types.at(arg_i)->abi_size;
4342 LLVMTypeRef pad_llvm_type = LLVMArrayType(LLVMInt8Type(), pad_bytes);
4343 field_types[arg_calc.field_index - 2] = pad_llvm_type;
4344 }
42794345 }
42804346 LLVMTypeRef frame_with_args_type = LLVMStructType(field_types, field_count, false);
42814347 LLVMTypeRef ptr_frame_with_args_type = LLVMPointerType(frame_with_args_type, 0);
......@@ -4285,8 +4351,10 @@ static LLVMValueRef ir_render_call(CodeGen *g, IrExecutableGen *executable, IrIn
42854351 casted_frame = frame_result_loc;
42864352 }
42874353
4354 CalcLLVMFieldIndex arg_calc = arg_calc_start;
42884355 for (size_t arg_i = 0; arg_i < gen_param_values.length; arg_i += 1) {
4289 LLVMValueRef arg_ptr = LLVMBuildStructGEP(g->builder, casted_frame, arg_start_i + arg_i, "");
4356 calc_llvm_field_index_add(g, &arg_calc, gen_param_types.at(arg_i));
4357 LLVMValueRef arg_ptr = LLVMBuildStructGEP(g->builder, casted_frame, arg_calc.field_index - 1, "");
42904358 gen_assign_raw(g, arg_ptr, get_pointer_to_type(g, gen_param_types.at(arg_i), true),
42914359 gen_param_values.at(arg_i));
42924360 }
......@@ -4349,11 +4417,19 @@ static LLVMValueRef ir_render_call(CodeGen *g, IrExecutableGen *executable, IrIn
43494417 }
43504418 }
43514419
4352 if (frame_result_loc_uncasted != nullptr && instruction->fn_entry != nullptr) {
4353 // Instead of a spill, we do the bitcast again. The uncasted LLVM IR instruction will
4354 // be an Alloca from the entry block, so it does not need to be spilled.
4355 frame_result_loc = LLVMBuildBitCast(g->builder, frame_result_loc_uncasted,
4356 LLVMPointerType(get_llvm_type(g, instruction->fn_entry->frame_type), 0), "");
4420 if (need_frame_ptr_ptr_spill) {
4421 LLVMValueRef frame_slice_ptr = ir_llvm_value(g, instruction->new_stack);
4422 LLVMValueRef frame_ptr_ptr = LLVMBuildStructGEP(g->builder, frame_slice_ptr, slice_ptr_index, "");
4423 frame_result_loc_uncasted = LLVMBuildLoad(g->builder, frame_ptr_ptr, "");
4424 }
4425 if (frame_result_loc_uncasted != nullptr) {
4426 if (instruction->fn_entry != nullptr) {
4427 frame_result_loc = LLVMBuildBitCast(g->builder, frame_result_loc_uncasted,
4428 LLVMPointerType(get_llvm_type(g, instruction->fn_entry->frame_type), 0), "");
4429 } else {
4430 frame_result_loc = LLVMBuildBitCast(g->builder, frame_result_loc_uncasted,
4431 get_llvm_type(g, anyframe_type), "");
4432 }
43574433 }
43584434
43594435 LLVMValueRef result_ptr = LLVMBuildStructGEP(g->builder, frame_result_loc, frame_ret_start + 2, "");
......@@ -5644,18 +5720,24 @@ static LLVMValueRef ir_render_unwrap_err_payload(CodeGen *g, IrExecutableGen *ex
56445720 bool want_safety = instruction->safety_check_on && ir_want_runtime_safety(g, &instruction->base) &&
56455721 g->errors_by_index.length > 1;
56465722
5647 bool value_has_bits;
5648 if ((err = type_has_bits2(g, instruction->base.value->type, &value_has_bits)))
5649 codegen_report_errors_and_exit(g);
5650
5651 if (!want_safety && !value_has_bits)
5652 return nullptr;
5653
56545723 ZigType *ptr_type = instruction->value->value->type;
56555724 assert(ptr_type->id == ZigTypeIdPointer);
56565725 ZigType *err_union_type = ptr_type->data.pointer.child_type;
56575726 ZigType *payload_type = err_union_type->data.error_union.payload_type;
56585727 LLVMValueRef err_union_ptr = ir_llvm_value(g, instruction->value);
5728
5729 LLVMValueRef zero = LLVMConstNull(get_llvm_type(g, g->err_tag_type));
5730 bool value_has_bits;
5731 if ((err = type_has_bits2(g, instruction->base.value->type, &value_has_bits)))
5732 codegen_report_errors_and_exit(g);
5733 if (!want_safety && !value_has_bits) {
5734 if (instruction->initializing) {
5735 gen_store_untyped(g, zero, err_union_ptr, 0, false);
5736 }
5737 return nullptr;
5738 }
5739
5740
56595741 LLVMValueRef err_union_handle = get_handle_value(g, err_union_ptr, err_union_type, ptr_type);
56605742
56615743 if (!type_has_bits(err_union_type->data.error_union.err_set_type)) {
......@@ -5670,7 +5752,6 @@ static LLVMValueRef ir_render_unwrap_err_payload(CodeGen *g, IrExecutableGen *ex
56705752 } else {
56715753 err_val = err_union_handle;
56725754 }
5673 LLVMValueRef zero = LLVMConstNull(get_llvm_type(g, g->err_tag_type));
56745755 LLVMValueRef cond_val = LLVMBuildICmp(g->builder, LLVMIntEQ, err_val, zero, "");
56755756 LLVMBasicBlockRef err_block = LLVMAppendBasicBlock(g->cur_fn_val, "UnwrapErrError");
56765757 LLVMBasicBlockRef ok_block = LLVMAppendBasicBlock(g->cur_fn_val, "UnwrapErrOk");
......@@ -5690,6 +5771,9 @@ static LLVMValueRef ir_render_unwrap_err_payload(CodeGen *g, IrExecutableGen *ex
56905771 }
56915772 return LLVMBuildStructGEP(g->builder, err_union_handle, err_union_payload_index, "");
56925773 } else {
5774 if (instruction->initializing) {
5775 gen_store_untyped(g, zero, err_union_ptr, 0, false);
5776 }
56935777 return nullptr;
56945778 }
56955779}
......@@ -7742,7 +7826,7 @@ static void do_code_gen(CodeGen *g) {
77427826 }
77437827 uint32_t trace_field_index_stack = UINT32_MAX;
77447828 if (codegen_fn_has_err_ret_tracing_stack(g, fn_table_entry, true)) {
7745 trace_field_index_stack = frame_index_trace_stack(g, fn_type_id);
7829 trace_field_index_stack = frame_index_trace_stack(g, fn_table_entry);
77467830 g->cur_err_ret_trace_val_stack = LLVMBuildStructGEP(g->builder, g->cur_frame_ptr,
77477831 trace_field_index_stack, "");
77487832 }
......@@ -8602,6 +8686,9 @@ Buf *codegen_generate_builtin_source(CodeGen *g) {
86028686 buf_appendf(contents,
86038687 "pub var test_functions: []TestFn = undefined; // overwritten later\n"
86048688 );
8689
8690 buf_appendf(contents, "pub const test_io_mode = %s;\n",
8691 g->test_is_evented ? ".evented" : ".blocking");
86058692 }
86068693
86078694 return contents;
......@@ -8635,6 +8722,7 @@ static Error define_builtin_compile_vars(CodeGen *g) {
86358722 cache_bool(&cache_hash, g->is_dynamic);
86368723 cache_bool(&cache_hash, g->is_test_build);
86378724 cache_bool(&cache_hash, g->is_single_threaded);
8725 cache_bool(&cache_hash, g->test_is_evented);
86388726 cache_int(&cache_hash, g->code_model);
86398727 cache_int(&cache_hash, g->zig_target->is_native);
86408728 cache_int(&cache_hash, g->zig_target->arch);
......@@ -9392,22 +9480,13 @@ static void update_test_functions_builtin_decl(CodeGen *g) {
93929480 for (size_t i = 0; i < g->test_fns.length; i += 1) {
93939481 ZigFn *test_fn_entry = g->test_fns.at(i);
93949482
9395 if (fn_is_async(test_fn_entry)) {
9396 ErrorMsg *msg = add_node_error(g, test_fn_entry->proto_node,
9397 buf_create_from_str("test functions cannot be async"));
9398 add_error_note(g, msg, test_fn_entry->proto_node,
9399 buf_sprintf("this restriction may be lifted in the future. See https://github.com/ziglang/zig/issues/3117 for more details"));
9400 add_async_error_notes(g, msg, test_fn_entry);
9401 continue;
9402 }
9403
94049483 ZigValue *this_val = &test_fn_array->data.x_array.data.s_none.elements[i];
94059484 this_val->special = ConstValSpecialStatic;
94069485 this_val->type = struct_type;
94079486 this_val->parent.id = ConstParentIdArray;
94089487 this_val->parent.data.p_array.array_val = test_fn_array;
94099488 this_val->parent.data.p_array.elem_index = i;
9410 this_val->data.x_struct.fields = alloc_const_vals_ptrs(2);
9489 this_val->data.x_struct.fields = alloc_const_vals_ptrs(3);
94119490
94129491 ZigValue *name_field = this_val->data.x_struct.fields[0];
94139492 ZigValue *name_array_val = create_const_str_lit(g, &test_fn_entry->symbol_name)->data.x_ptr.data.ref.pointee;
......@@ -9419,6 +9498,19 @@ static void update_test_functions_builtin_decl(CodeGen *g) {
94199498 fn_field->data.x_ptr.special = ConstPtrSpecialFunction;
94209499 fn_field->data.x_ptr.mut = ConstPtrMutComptimeConst;
94219500 fn_field->data.x_ptr.data.fn.fn_entry = test_fn_entry;
9501
9502 ZigValue *frame_size_field = this_val->data.x_struct.fields[2];
9503 frame_size_field->type = get_optional_type(g, g->builtin_types.entry_usize);
9504 frame_size_field->special = ConstValSpecialStatic;
9505 frame_size_field->data.x_optional = nullptr;
9506
9507 if (fn_is_async(test_fn_entry)) {
9508 frame_size_field->data.x_optional = create_const_vals(1);
9509 frame_size_field->data.x_optional->special = ConstValSpecialStatic;
9510 frame_size_field->data.x_optional->type = g->builtin_types.entry_usize;
9511 bigint_init_unsigned(&frame_size_field->data.x_optional->data.x_bigint,
9512 test_fn_entry->frame_type->abi_size);
9513 }
94229514 }
94239515 report_errors_and_maybe_exit(g);
94249516
......@@ -10350,6 +10442,7 @@ static Error check_cache(CodeGen *g, Buf *manifest_dir, Buf *digest) {
1035010442 if (g->is_test_build) {
1035110443 cache_buf_opt(ch, g->test_filter);
1035210444 cache_buf_opt(ch, g->test_name_prefix);
10445 cache_bool(ch, g->test_is_evented);
1035310446 }
1035410447 cache_bool(ch, g->link_eh_frame_hdr);
1035510448 cache_bool(ch, g->is_single_threaded);
src/ir.cpp+45-19
......@@ -5252,6 +5252,7 @@ static IrInstSrc *ir_gen_return(IrBuilderSrc *irb, Scope *scope, AstNode *node,
52525252 return irb->codegen->invalid_inst_src;
52535253 } else {
52545254 return_value = ir_build_const_void(irb, scope, node);
5255 ir_build_end_expr(irb, scope, node, return_value, &result_loc_ret->base);
52555256 }
52565257
52575258 ir_mark_gen(ir_build_add_implicit_return_type(irb, scope, node, return_value, result_loc_ret));
......@@ -5262,7 +5263,7 @@ static IrInstSrc *ir_gen_return(IrBuilderSrc *irb, Scope *scope, AstNode *node,
52625263 if (!have_err_defers && !irb->codegen->have_err_ret_tracing) {
52635264 // only generate unconditional defers
52645265 ir_gen_defers_for_block(irb, scope, outer_scope, false);
5265 IrInstSrc *result = ir_build_return_src(irb, scope, node, return_value);
5266 IrInstSrc *result = ir_build_return_src(irb, scope, node, nullptr);
52665267 result_loc_ret->base.source_instruction = result;
52675268 return result;
52685269 }
......@@ -5271,10 +5272,6 @@ static IrInstSrc *ir_gen_return(IrBuilderSrc *irb, Scope *scope, AstNode *node,
52715272 IrBasicBlockSrc *err_block = ir_create_basic_block(irb, scope, "ErrRetErr");
52725273 IrBasicBlockSrc *ok_block = ir_create_basic_block(irb, scope, "ErrRetOk");
52735274
5274 if (!have_err_defers) {
5275 ir_gen_defers_for_block(irb, scope, outer_scope, false);
5276 }
5277
52785275 IrInstSrc *is_err = ir_build_test_err_src(irb, scope, node, return_value, false, true);
52795276
52805277 IrInstSrc *is_comptime;
......@@ -5288,22 +5285,18 @@ static IrInstSrc *ir_gen_return(IrBuilderSrc *irb, Scope *scope, AstNode *node,
52885285 IrBasicBlockSrc *ret_stmt_block = ir_create_basic_block(irb, scope, "RetStmt");
52895286
52905287 ir_set_cursor_at_end_and_append_block(irb, err_block);
5291 if (have_err_defers) {
5292 ir_gen_defers_for_block(irb, scope, outer_scope, true);
5293 }
5288 ir_gen_defers_for_block(irb, scope, outer_scope, true);
52945289 if (irb->codegen->have_err_ret_tracing && !should_inline) {
52955290 ir_build_save_err_ret_addr_src(irb, scope, node);
52965291 }
52975292 ir_build_br(irb, scope, node, ret_stmt_block, is_comptime);
52985293
52995294 ir_set_cursor_at_end_and_append_block(irb, ok_block);
5300 if (have_err_defers) {
5301 ir_gen_defers_for_block(irb, scope, outer_scope, false);
5302 }
5295 ir_gen_defers_for_block(irb, scope, outer_scope, false);
53035296 ir_build_br(irb, scope, node, ret_stmt_block, is_comptime);
53045297
53055298 ir_set_cursor_at_end_and_append_block(irb, ret_stmt_block);
5306 IrInstSrc *result = ir_build_return_src(irb, scope, node, return_value);
5299 IrInstSrc *result = ir_build_return_src(irb, scope, node, nullptr);
53075300 result_loc_ret->base.source_instruction = result;
53085301 return result;
53095302 }
......@@ -8874,7 +8867,10 @@ static IrInstSrc *ir_gen_if_optional_expr(IrBuilderSrc *irb, Scope *scope, AstNo
88748867 AstNode *else_node = node->data.test_expr.else_node;
88758868 bool var_is_ptr = node->data.test_expr.var_is_ptr;
88768869
8877 IrInstSrc *maybe_val_ptr = ir_gen_node_extra(irb, expr_node, scope, LValPtr, nullptr);
8870 ScopeExpr *spill_scope = create_expr_scope(irb->codegen, expr_node, scope);
8871 spill_scope->spill_harder = true;
8872
8873 IrInstSrc *maybe_val_ptr = ir_gen_node_extra(irb, expr_node, &spill_scope->base, LValPtr, nullptr);
88788874 if (maybe_val_ptr == irb->codegen->invalid_inst_src)
88798875 return maybe_val_ptr;
88808876
......@@ -8899,7 +8895,7 @@ static IrInstSrc *ir_gen_if_optional_expr(IrBuilderSrc *irb, Scope *scope, AstNo
88998895
89008896 ir_set_cursor_at_end_and_append_block(irb, then_block);
89018897
8902 Scope *subexpr_scope = create_runtime_scope(irb->codegen, node, scope, is_comptime);
8898 Scope *subexpr_scope = create_runtime_scope(irb->codegen, node, &spill_scope->base, is_comptime);
89038899 Scope *var_scope;
89048900 if (var_symbol) {
89058901 bool is_shadowable = false;
......@@ -9619,7 +9615,10 @@ static IrInstSrc *ir_gen_catch(IrBuilderSrc *irb, Scope *parent_scope, AstNode *
96199615 }
96209616
96219617
9622 IrInstSrc *err_union_ptr = ir_gen_node_extra(irb, op1_node, parent_scope, LValPtr, nullptr);
9618 ScopeExpr *spill_scope = create_expr_scope(irb->codegen, op1_node, parent_scope);
9619 spill_scope->spill_harder = true;
9620
9621 IrInstSrc *err_union_ptr = ir_gen_node_extra(irb, op1_node, &spill_scope->base, LValPtr, nullptr);
96239622 if (err_union_ptr == irb->codegen->invalid_inst_src)
96249623 return irb->codegen->invalid_inst_src;
96259624
......@@ -9641,7 +9640,7 @@ static IrInstSrc *ir_gen_catch(IrBuilderSrc *irb, Scope *parent_scope, AstNode *
96419640 is_comptime);
96429641
96439642 ir_set_cursor_at_end_and_append_block(irb, err_block);
9644 Scope *subexpr_scope = create_runtime_scope(irb->codegen, node, parent_scope, is_comptime);
9643 Scope *subexpr_scope = create_runtime_scope(irb->codegen, node, &spill_scope->base, is_comptime);
96459644 Scope *err_scope;
96469645 if (var_node) {
96479646 assert(var_node->type == NodeTypeSymbol);
......@@ -15494,6 +15493,12 @@ static IrInstGen *ir_analyze_instruction_add_implicit_return_type(IrAnalyze *ira
1549415493}
1549515494
1549615495static IrInstGen *ir_analyze_instruction_return(IrAnalyze *ira, IrInstSrcReturn *instruction) {
15496 if (instruction->operand == nullptr) {
15497 // result location mechanism took care of it.
15498 IrInstGen *result = ir_build_return_gen(ira, &instruction->base.base, nullptr);
15499 return ir_finish_anal(ira, result);
15500 }
15501
1549715502 IrInstGen *operand = instruction->operand->child;
1549815503 if (type_is_invalid(operand->value->type))
1549915504 return ir_unreach_error(ira);
......@@ -19586,6 +19591,12 @@ static IrInstGen *ir_analyze_fn_call(IrAnalyze *ira, IrInst* source_instr,
1958619591 if (type_is_invalid(result_loc->value->type) || result_loc->value->type->id == ZigTypeIdUnreachable) {
1958719592 return result_loc;
1958819593 }
19594 IrInstGen *dummy_value = ir_const(ira, source_instr, impl_fn_type_id->return_type);
19595 dummy_value->value->special = ConstValSpecialRuntime;
19596 IrInstGen *dummy_result = ir_implicit_cast2(ira, source_instr,
19597 dummy_value, result_loc->value->type->data.pointer.child_type);
19598 if (type_is_invalid(dummy_result->value->type))
19599 return ira->codegen->invalid_inst_gen;
1958919600 ZigType *res_child_type = result_loc->value->type->data.pointer.child_type;
1959019601 if (res_child_type == ira->codegen->builtin_types.entry_var) {
1959119602 res_child_type = impl_fn_type_id->return_type;
......@@ -19718,6 +19729,12 @@ static IrInstGen *ir_analyze_fn_call(IrAnalyze *ira, IrInst* source_instr,
1971819729 if (type_is_invalid(result_loc->value->type) || result_loc->value->type->id == ZigTypeIdUnreachable) {
1971919730 return result_loc;
1972019731 }
19732 IrInstGen *dummy_value = ir_const(ira, source_instr, return_type);
19733 dummy_value->value->special = ConstValSpecialRuntime;
19734 IrInstGen *dummy_result = ir_implicit_cast2(ira, source_instr,
19735 dummy_value, result_loc->value->type->data.pointer.child_type);
19736 if (type_is_invalid(dummy_result->value->type))
19737 return ira->codegen->invalid_inst_gen;
1972119738 ZigType *res_child_type = result_loc->value->type->data.pointer.child_type;
1972219739 if (res_child_type == ira->codegen->builtin_types.entry_var) {
1972319740 res_child_type = return_type;
......@@ -29548,8 +29565,13 @@ static IrInstGen *ir_analyze_instruction_spill_begin(IrAnalyze *ira, IrInstSrcSp
2954829565 if (!type_has_bits(operand->value->type))
2954929566 return ir_const_void(ira, &instruction->base.base);
2955029567
29551 ir_assert(instruction->spill_id == SpillIdRetErrCode, &instruction->base.base);
29552 ira->new_irb.exec->need_err_code_spill = true;
29568 switch (instruction->spill_id) {
29569 case SpillIdInvalid:
29570 zig_unreachable();
29571 case SpillIdRetErrCode:
29572 ira->new_irb.exec->need_err_code_spill = true;
29573 break;
29574 }
2955329575
2955429576 return ir_build_spill_begin_gen(ira, &instruction->base.base, operand, instruction->spill_id);
2955529577}
......@@ -29559,8 +29581,12 @@ static IrInstGen *ir_analyze_instruction_spill_end(IrAnalyze *ira, IrInstSrcSpil
2955929581 if (type_is_invalid(operand->value->type))
2956029582 return ira->codegen->invalid_inst_gen;
2956129583
29562 if (ir_should_inline(ira->old_irb.exec, instruction->base.base.scope) || !type_has_bits(operand->value->type))
29584 if (ir_should_inline(ira->old_irb.exec, instruction->base.base.scope) ||
29585 !type_has_bits(operand->value->type) ||
29586 instr_is_comptime(operand))
29587 {
2956329588 return operand;
29589 }
2956429590
2956529591 ir_assert(instruction->begin->base.child->id == IrInstGenIdSpillBegin, &instruction->base.base);
2956629592 IrInstGenSpillBegin *begin = reinterpret_cast<IrInstGenSpillBegin *>(instruction->begin->base.child);
src/main.cpp+6
......@@ -135,6 +135,7 @@ static int print_full_usage(const char *arg0, FILE *file, int return_code) {
135135 " --test-name-prefix [text] add prefix to all tests\n"
136136 " --test-cmd [arg] specify test execution command one arg at a time\n"
137137 " --test-cmd-bin appends test binary path to test cmd args\n"
138 " --test-evented-io runs the test in evented I/O mode\n"
138139 , arg0);
139140 return return_code;
140141}
......@@ -428,6 +429,7 @@ int main(int argc, char **argv) {
428429 ZigList<CFile *> c_source_files = {0};
429430 const char *test_filter = nullptr;
430431 const char *test_name_prefix = nullptr;
432 bool test_evented_io = false;
431433 size_t ver_major = 0;
432434 size_t ver_minor = 0;
433435 size_t ver_patch = 0;
......@@ -709,6 +711,8 @@ int main(int argc, char **argv) {
709711 cur_pkg = cur_pkg->parent;
710712 } else if (strcmp(arg, "-ffunction-sections") == 0) {
711713 function_sections = true;
714 } else if (strcmp(arg, "--test-evented-io") == 0) {
715 test_evented_io = true;
712716 } else if (i + 1 >= argc) {
713717 fprintf(stderr, "Expected another argument after %s\n", arg);
714718 return print_error_usage(arg0);
......@@ -1059,6 +1063,7 @@ int main(int argc, char **argv) {
10591063 g->want_stack_check = want_stack_check;
10601064 g->want_sanitize_c = want_sanitize_c;
10611065 g->want_single_threaded = want_single_threaded;
1066 g->test_is_evented = test_evented_io;
10621067 Buf *builtin_source = codegen_generate_builtin_source(g);
10631068 if (fwrite(buf_ptr(builtin_source), 1, buf_len(builtin_source), stdout) != buf_len(builtin_source)) {
10641069 fprintf(stderr, "unable to write to stdout: %s\n", strerror(ferror(stdout)));
......@@ -1232,6 +1237,7 @@ int main(int argc, char **argv) {
12321237 if (test_filter) {
12331238 codegen_set_test_filter(g, buf_create_from_str(test_filter));
12341239 }
1240 g->test_is_evented = test_evented_io;
12351241
12361242 if (test_name_prefix) {
12371243 codegen_set_test_name_prefix(g, buf_create_from_str(test_name_prefix));
test/compile_errors.zig+24-19
......@@ -20,6 +20,30 @@ pub fn addCases(cases: *tests.CompileErrorContext) void {
2020 "tmp.zig:1:20: error: dependency loop detected",
2121 });
2222
23 cases.add("function call assigned to incorrect type",
24 \\export fn entry() void {
25 \\ var arr: [4]f32 = undefined;
26 \\ arr = concat();
27 \\}
28 \\fn concat() [16]f32 {
29 \\ return [1]f32{0}**16;
30 \\}
31 , &[_][]const u8{
32 "tmp.zig:3:17: error: expected type '[4]f32', found '[16]f32'",
33 });
34
35 cases.add("generic function call assigned to incorrect type",
36 \\pub export fn entry() void {
37 \\ var res: []i32 = undefined;
38 \\ res = myAlloc(i32);
39 \\}
40 \\fn myAlloc(comptime arg: type) anyerror!arg{
41 \\ unreachable;
42 \\}
43 , &[_][]const u8{
44 "tmp.zig:3:18: error: expected type '[]i32', found 'anyerror!i32",
45 });
46
2347 cases.addTest("non-exhaustive enums",
2448 \\const A = enum {
2549 \\ a,
......@@ -5279,25 +5303,6 @@ pub fn addCases(cases: *tests.CompileErrorContext) void {
52795303 "tmp.zig:2:30: error: cannot set section of local variable 'foo'",
52805304 });
52815305
5282 cases.add("returning address of local variable - simple",
5283 \\export fn foo() *i32 {
5284 \\ var a: i32 = undefined;
5285 \\ return &a;
5286 \\}
5287 , &[_][]const u8{
5288 "tmp.zig:3:13: error: function returns address of local variable",
5289 });
5290
5291 cases.add("returning address of local variable - phi",
5292 \\export fn foo(c: bool) *i32 {
5293 \\ var a: i32 = undefined;
5294 \\ var b: i32 = undefined;
5295 \\ return if (c) &a else &b;
5296 \\}
5297 , &[_][]const u8{
5298 "tmp.zig:4:12: error: function returns address of local variable",
5299 });
5300
53015306 cases.add("inner struct member shadowing outer struct member",
53025307 \\fn A() type {
53035308 \\ return struct {
test/stage1/behavior/async_fn.zig+152
......@@ -2,6 +2,7 @@ const std = @import("std");
22const builtin = @import("builtin");
33const expect = std.testing.expect;
44const expectEqual = std.testing.expectEqual;
5const expectError = std.testing.expectError;
56
67var global_x: i32 = 1;
78
......@@ -1329,3 +1330,154 @@ test "async call with @call" {
13291330 };
13301331 S.doTheTest();
13311332}
1333
1334test "async function passed 0-bit arg after non-0-bit arg" {
1335 const S = struct {
1336 var global_frame: anyframe = undefined;
1337 var global_int: i32 = 0;
1338
1339 fn foo() void {
1340 bar(1, .{}) catch unreachable;
1341 }
1342
1343 fn bar(x: i32, args: var) anyerror!void {
1344 global_frame = @frame();
1345 suspend;
1346 global_int = x;
1347 }
1348 };
1349 _ = async S.foo();
1350 resume S.global_frame;
1351 expect(S.global_int == 1);
1352}
1353
1354test "async function passed align(16) arg after align(8) arg" {
1355 const S = struct {
1356 var global_frame: anyframe = undefined;
1357 var global_int: u128 = 0;
1358
1359 fn foo() void {
1360 var a: u128 = 99;
1361 bar(10, .{a}) catch unreachable;
1362 }
1363
1364 fn bar(x: u64, args: var) anyerror!void {
1365 expect(x == 10);
1366 global_frame = @frame();
1367 suspend;
1368 global_int = args[0];
1369 }
1370 };
1371 _ = async S.foo();
1372 resume S.global_frame;
1373 expect(S.global_int == 99);
1374}
1375
1376test "async function call resolves target fn frame, comptime func" {
1377 const S = struct {
1378 var global_frame: anyframe = undefined;
1379 var global_int: i32 = 9;
1380
1381 fn foo() anyerror!void {
1382 const stack_size = 1000;
1383 var stack_frame: [stack_size]u8 align(std.Target.stack_align) = undefined;
1384 return await @asyncCall(&stack_frame, {}, bar);
1385 }
1386
1387 fn bar() anyerror!void {
1388 global_frame = @frame();
1389 suspend;
1390 global_int += 1;
1391 }
1392 };
1393 _ = async S.foo();
1394 resume S.global_frame;
1395 expect(S.global_int == 10);
1396}
1397
1398test "async function call resolves target fn frame, runtime func" {
1399 const S = struct {
1400 var global_frame: anyframe = undefined;
1401 var global_int: i32 = 9;
1402
1403 fn foo() anyerror!void {
1404 const stack_size = 1000;
1405 var stack_frame: [stack_size]u8 align(std.Target.stack_align) = undefined;
1406 var func: async fn () anyerror!void = bar;
1407 return await @asyncCall(&stack_frame, {}, func);
1408 }
1409
1410 fn bar() anyerror!void {
1411 global_frame = @frame();
1412 suspend;
1413 global_int += 1;
1414 }
1415 };
1416 _ = async S.foo();
1417 resume S.global_frame;
1418 expect(S.global_int == 10);
1419}
1420
1421test "properly spill optional payload capture value" {
1422 const S = struct {
1423 var global_frame: anyframe = undefined;
1424 var global_int: usize = 2;
1425
1426 fn foo() void {
1427 var opt: ?usize = 1234;
1428 if (opt) |x| {
1429 bar();
1430 global_int += x;
1431 }
1432 }
1433
1434 fn bar() void {
1435 global_frame = @frame();
1436 suspend;
1437 global_int += 1;
1438 }
1439 };
1440 _ = async S.foo();
1441 resume S.global_frame;
1442 expect(S.global_int == 1237);
1443}
1444
1445test "handle defer interfering with return value spill" {
1446 const S = struct {
1447 var global_frame1: anyframe = undefined;
1448 var global_frame2: anyframe = undefined;
1449 var finished = false;
1450 var baz_happened = false;
1451
1452 fn doTheTest() void {
1453 _ = async testFoo();
1454 resume global_frame1;
1455 resume global_frame2;
1456 expect(baz_happened);
1457 expect(finished);
1458 }
1459
1460 fn testFoo() void {
1461 expectError(error.Bad, foo());
1462 finished = true;
1463 }
1464
1465 fn foo() anyerror!void {
1466 defer baz();
1467 return bar() catch |err| return err;
1468 }
1469
1470 fn bar() anyerror!void {
1471 global_frame1 = @frame();
1472 suspend;
1473 return error.Bad;
1474 }
1475
1476 fn baz() void {
1477 global_frame2 = @frame();
1478 suspend;
1479 baz_happened = true;
1480 }
1481 };
1482 S.doTheTest();
1483}