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 {...@@ -72,14 +72,13 @@ pub fn build(b: *Builder) !void {
72 const skip_release_safe = b.option(bool, "skip-release-safe", "Main test suite skips release-safe builds") orelse skip_release;72 const skip_release_safe = b.option(bool, "skip-release-safe", "Main test suite skips release-safe builds") orelse skip_release;
73 const skip_non_native = b.option(bool, "skip-non-native", "Main test suite skips non-native builds") orelse false;73 const skip_non_native = b.option(bool, "skip-non-native", "Main test suite skips non-native builds") orelse false;
74 const skip_libc = b.option(bool, "skip-libc", "Main test suite skips tests that link libc") orelse false;74 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;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 and builtin.os == .linux) {76 if (!skip_self_hosted) {
77 // TODO evented I/O other OS's
78 test_step.dependOn(&exe.step);77 test_step.dependOn(&exe.step);
79 }78 }
8079
81 const only_install_lib_files = b.option(bool, "lib-files-only", "Only install library files") orelse false;80 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) {
83 b.default_step.dependOn(&exe.step);82 b.default_step.dependOn(&exe.step);
84 exe.install();83 exe.install();
85 }84 }
doc/docgen.zig+2-2
...@@ -34,10 +34,10 @@ pub fn main() !void {...@@ -34,10 +34,10 @@ pub fn main() !void {
34 const out_file_name = try (args_it.next(allocator) orelse @panic("expected output arg"));34 const out_file_name = try (args_it.next(allocator) orelse @panic("expected output arg"));
35 defer allocator.free(out_file_name);35 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 });
38 defer in_file.close();38 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, .{});
41 defer out_file.close();41 defer out_file.close();
4242
43 var file_in_stream = in_file.inStream();43 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 {...@@ -113,11 +113,20 @@ pub fn Queue(comptime T: type) type {
113113
114 pub fn dumpToStream(self: *Self, comptime Error: type, stream: *std.io.OutStream(Error)) Error!void {114 pub fn dumpToStream(self: *Self, comptime Error: type, stream: *std.io.OutStream(Error)) Error!void {
115 const S = struct {115 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 {
117 try s.writeByteNTimes(' ', indent);122 try s.writeByteNTimes(' ', indent);
118 if (optional_node) |node| {123 if (optional_node) |node| {
119 try s.print("0x{x}={}\n", .{ @ptrToInt(node), node.data });124 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);
121 } else {130 } else {
122 try s.print("(null)\n", .{});131 try s.print("(null)\n", .{});
123 }132 }
...@@ -127,9 +136,9 @@ pub fn Queue(comptime T: type) type {...@@ -127,9 +136,9 @@ pub fn Queue(comptime T: type) type {
127 defer held.release();136 defer held.release();
128137
129 try stream.print("head: ", .{});138 try stream.print("head: ", .{});
130 try S.dumpRecursive(stream, self.head, 0);139 try S.dumpRecursive(stream, self.head, 0, 4);
131 try stream.print("tail: ", .{});140 try stream.print("tail: ", .{});
132 try S.dumpRecursive(stream, self.tail, 0);141 try S.dumpRecursive(stream, self.tail, 0, 4);
133 }142 }
134 };143 };
135}144}
lib/std/builtin.zig+1
...@@ -458,6 +458,7 @@ pub const ExportOptions = struct {...@@ -458,6 +458,7 @@ pub const ExportOptions = struct {
458pub const TestFn = struct {458pub const TestFn = struct {
459 name: []const u8,459 name: []const u8,
460 func: fn () anyerror!void,460 func: fn () anyerror!void,
461 async_frame_size: ?usize,
461};462};
462463
463/// This function type is used by the Zig language code generation and464/// 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...@@ -121,6 +121,7 @@ pub extern "c" fn sysctlbyname(name: [*:0]const u8, oldp: ?*c_void, oldlenp: ?*u
121pub extern "c" fn sysctlnametomib(name: [*:0]const u8, mibp: ?*c_int, sizep: ?*usize) c_int;121pub extern "c" fn sysctlnametomib(name: [*:0]const u8, mibp: ?*c_int, sizep: ?*usize) c_int;
122pub extern "c" fn tcgetattr(fd: fd_t, termios_p: *termios) c_int;122pub extern "c" fn tcgetattr(fd: fd_t, termios_p: *termios) c_int;
123pub extern "c" fn tcsetattr(fd: fd_t, optional_action: TCSA, termios_p: *const termios) c_int;123pub 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
125pub extern "c" fn gethostname(name: [*]u8, len: usize) c_int;126pub extern "c" fn gethostname(name: [*]u8, len: usize) c_int;
126pub extern "c" fn bind(socket: fd_t, address: ?*const sockaddr, address_len: socklen_t) c_int;127pub 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 {...@@ -329,17 +329,18 @@ pub const ChildProcess = struct {
329 }329 }
330330
331 fn spawnPosix(self: *ChildProcess) SpawnError!void {331 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;
333 errdefer if (self.stdin_behavior == StdIo.Pipe) {334 errdefer if (self.stdin_behavior == StdIo.Pipe) {
334 destroyPipe(stdin_pipe);335 destroyPipe(stdin_pipe);
335 };336 };
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;
338 errdefer if (self.stdout_behavior == StdIo.Pipe) {339 errdefer if (self.stdout_behavior == StdIo.Pipe) {
339 destroyPipe(stdout_pipe);340 destroyPipe(stdout_pipe);
340 };341 };
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;
343 errdefer if (self.stderr_behavior == StdIo.Pipe) {344 errdefer if (self.stderr_behavior == StdIo.Pipe) {
344 destroyPipe(stderr_pipe);345 destroyPipe(stderr_pipe);
345 };346 };
...@@ -426,17 +427,26 @@ pub const ChildProcess = struct {...@@ -426,17 +427,26 @@ pub const ChildProcess = struct {
426 // we are the parent427 // we are the parent
427 const pid = @intCast(i32, pid_result);428 const pid = @intCast(i32, pid_result);
428 if (self.stdin_behavior == StdIo.Pipe) {429 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 };
430 } else {434 } else {
431 self.stdin = null;435 self.stdin = null;
432 }436 }
433 if (self.stdout_behavior == StdIo.Pipe) {437 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 };
435 } else {442 } else {
436 self.stdout = null;443 self.stdout = null;
437 }444 }
438 if (self.stderr_behavior == StdIo.Pipe) {445 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 };
440 } else {450 } else {
441 self.stderr = null;451 self.stderr = null;
442 }452 }
...@@ -661,17 +671,26 @@ pub const ChildProcess = struct {...@@ -661,17 +671,26 @@ pub const ChildProcess = struct {
661 };671 };
662672
663 if (g_hChildStd_IN_Wr) |h| {673 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 };
665 } else {678 } else {
666 self.stdin = null;679 self.stdin = null;
667 }680 }
668 if (g_hChildStd_OUT_Rd) |h| {681 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 };
670 } else {686 } else {
671 self.stdout = null;687 self.stdout = null;
672 }688 }
673 if (g_hChildStd_ERR_Rd) |h| {689 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 };
675 } else {694 } else {
676 self.stderr = null;695 self.stderr = null;
677 }696 }
...@@ -693,10 +712,10 @@ pub const ChildProcess = struct {...@@ -693,10 +712,10 @@ pub const ChildProcess = struct {
693712
694 fn setUpChildIo(stdio: StdIo, pipe_fd: i32, std_fileno: i32, dev_null_fd: i32) !void {713 fn setUpChildIo(stdio: StdIo, pipe_fd: i32, std_fileno: i32, dev_null_fd: i32) !void {
695 switch (stdio) {714 switch (stdio) {
696 StdIo.Pipe => try os.dup2(pipe_fd, std_fileno),715 .Pipe => try os.dup2(pipe_fd, std_fileno),
697 StdIo.Close => os.close(std_fileno),716 .Close => os.close(std_fileno),
698 StdIo.Inherit => {},717 .Inherit => {},
699 StdIo.Ignore => try os.dup2(dev_null_fd, std_fileno),718 .Ignore => try os.dup2(dev_null_fd, std_fileno),
700 }719 }
701 }720 }
702};721};
...@@ -811,12 +830,22 @@ fn forkChildErrReport(fd: i32, err: ChildProcess.SpawnError) noreturn {...@@ -811,12 +830,22 @@ fn forkChildErrReport(fd: i32, err: ChildProcess.SpawnError) noreturn {
811const ErrInt = @IntType(false, @sizeOf(anyerror) * 8);830const ErrInt = @IntType(false, @sizeOf(anyerror) * 8);
812831
813fn writeIntFd(fd: i32, value: ErrInt) !void {832fn 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;
815 stream.writeIntNative(u64, @intCast(u64, value)) catch return error.SystemResources;839 stream.writeIntNative(u64, @intCast(u64, value)) catch return error.SystemResources;
816}840}
817841
818fn readIntFd(fd: i32) !ErrInt {842fn 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;
820 return @intCast(ErrInt, stream.readIntNative(u64) catch return error.SystemResources);849 return @intCast(ErrInt, stream.readIntNative(u64) catch return error.SystemResources);
821}850}
822851
lib/std/debug.zig+24-24
...@@ -50,7 +50,7 @@ pub fn warn(comptime fmt: []const u8, args: var) void {...@@ -50,7 +50,7 @@ pub fn warn(comptime fmt: []const u8, args: var) void {
50 const held = stderr_mutex.acquire();50 const held = stderr_mutex.acquire();
51 defer held.release();51 defer held.release();
52 const stderr = getStderrStream();52 const stderr = getStderrStream();
53 stderr.print(fmt, args) catch return;53 noasync stderr.print(fmt, args) catch return;
54}54}
5555
56pub fn getStderrStream() *io.OutStream(File.WriteError) {56pub fn getStderrStream() *io.OutStream(File.WriteError) {
...@@ -102,15 +102,15 @@ pub fn detectTTYConfig() TTY.Config {...@@ -102,15 +102,15 @@ pub fn detectTTYConfig() TTY.Config {
102pub fn dumpCurrentStackTrace(start_addr: ?usize) void {102pub fn dumpCurrentStackTrace(start_addr: ?usize) void {
103 const stderr = getStderrStream();103 const stderr = getStderrStream();
104 if (builtin.strip_debug_info) {104 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;
106 return;106 return;
107 }107 }
108 const debug_info = getSelfDebugInfo() catch |err| {108 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;
110 return;110 return;
111 };111 };
112 writeCurrentStackTrace(stderr, debug_info, detectTTYConfig(), start_addr) catch |err| {112 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;
114 return;114 return;
115 };115 };
116}116}
...@@ -121,11 +121,11 @@ pub fn dumpCurrentStackTrace(start_addr: ?usize) void {...@@ -121,11 +121,11 @@ pub fn dumpCurrentStackTrace(start_addr: ?usize) void {
121pub fn dumpStackTraceFromBase(bp: usize, ip: usize) void {121pub fn dumpStackTraceFromBase(bp: usize, ip: usize) void {
122 const stderr = getStderrStream();122 const stderr = getStderrStream();
123 if (builtin.strip_debug_info) {123 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;
125 return;125 return;
126 }126 }
127 const debug_info = getSelfDebugInfo() catch |err| {127 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;
129 return;129 return;
130 };130 };
131 const tty_config = detectTTYConfig();131 const tty_config = detectTTYConfig();
...@@ -189,15 +189,15 @@ pub fn captureStackTrace(first_address: ?usize, stack_trace: *builtin.StackTrace...@@ -189,15 +189,15 @@ pub fn captureStackTrace(first_address: ?usize, stack_trace: *builtin.StackTrace
189pub fn dumpStackTrace(stack_trace: builtin.StackTrace) void {189pub fn dumpStackTrace(stack_trace: builtin.StackTrace) void {
190 const stderr = getStderrStream();190 const stderr = getStderrStream();
191 if (builtin.strip_debug_info) {191 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;
193 return;193 return;
194 }194 }
195 const debug_info = getSelfDebugInfo() catch |err| {195 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;
197 return;197 return;
198 };198 };
199 writeStackTrace(stack_trace, stderr, getDebugInfoAllocator(), debug_info, detectTTYConfig()) catch |err| {199 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;
201 return;201 return;
202 };202 };
203}203}
...@@ -238,7 +238,7 @@ pub fn panicExtra(trace: ?*const builtin.StackTrace, first_trace_addr: ?usize, c...@@ -238,7 +238,7 @@ pub fn panicExtra(trace: ?*const builtin.StackTrace, first_trace_addr: ?usize, c
238 switch (@atomicRmw(u8, &panicking, .Add, 1, .SeqCst)) {238 switch (@atomicRmw(u8, &panicking, .Add, 1, .SeqCst)) {
239 0 => {239 0 => {
240 const stderr = getStderrStream();240 const stderr = getStderrStream();
241 stderr.print(format ++ "\n", args) catch os.abort();241 noasync stderr.print(format ++ "\n", args) catch os.abort();
242 if (trace) |t| {242 if (trace) |t| {
243 dumpStackTrace(t.*);243 dumpStackTrace(t.*);
244 }244 }
...@@ -568,12 +568,12 @@ pub const TTY = struct {...@@ -568,12 +568,12 @@ pub const TTY = struct {
568 switch (conf) {568 switch (conf) {
569 .no_color => return,569 .no_color => return,
570 .escape_codes => switch (color) {570 .escape_codes => switch (color) {
571 .Red => out_stream.write(RED) catch return,571 .Red => noasync out_stream.write(RED) catch return,
572 .Green => out_stream.write(GREEN) catch return,572 .Green => noasync out_stream.write(GREEN) catch return,
573 .Cyan => out_stream.write(CYAN) catch return,573 .Cyan => noasync out_stream.write(CYAN) catch return,
574 .White, .Bold => out_stream.write(WHITE) catch return,574 .White, .Bold => noasync out_stream.write(WHITE) catch return,
575 .Dim => out_stream.write(DIM) catch return,575 .Dim => noasync out_stream.write(DIM) catch return,
576 .Reset => out_stream.write(RESET) catch return,576 .Reset => noasync out_stream.write(RESET) catch return,
577 },577 },
578 .windows_api => if (builtin.os == .windows) {578 .windows_api => if (builtin.os == .windows) {
579 const S = struct {579 const S = struct {
...@@ -729,17 +729,17 @@ fn printLineInfo(...@@ -729,17 +729,17 @@ fn printLineInfo(
729 tty_config.setColor(out_stream, .White);729 tty_config.setColor(out_stream, .White);
730730
731 if (line_info) |*li| {731 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 });
733 } else {733 } else {
734 try out_stream.print("???:?:?", .{});734 try noasync out_stream.write("???:?:?");
735 }735 }
736736
737 tty_config.setColor(out_stream, .Reset);737 tty_config.setColor(out_stream, .Reset);
738 try out_stream.write(": ");738 try noasync out_stream.write(": ");
739 tty_config.setColor(out_stream, .Dim);739 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 });
741 tty_config.setColor(out_stream, .Reset);741 tty_config.setColor(out_stream, .Reset);
742 try out_stream.write("\n");742 try noasync out_stream.write("\n");
743743
744 // Show the matching source code line if possible744 // Show the matching source code line if possible
745 if (line_info) |li| {745 if (line_info) |li| {
...@@ -748,12 +748,12 @@ fn printLineInfo(...@@ -748,12 +748,12 @@ fn printLineInfo(
748 // The caret already takes one char748 // The caret already takes one char
749 const space_needed = @intCast(usize, li.column - 1);749 const space_needed = @intCast(usize, li.column - 1);
750750
751 try out_stream.writeByteNTimes(' ', space_needed);751 try noasync out_stream.writeByteNTimes(' ', space_needed);
752 tty_config.setColor(out_stream, .Green);752 tty_config.setColor(out_stream, .Green);
753 try out_stream.write("^");753 try noasync out_stream.write("^");
754 tty_config.setColor(out_stream, .Reset);754 tty_config.setColor(out_stream, .Reset);
755 }755 }
756 try out_stream.write("\n");756 try noasync out_stream.write("\n");
757 } else |err| switch (err) {757 } else |err| switch (err) {
758 error.EndOfFile, error.FileNotFound => {},758 error.EndOfFile, error.FileNotFound => {},
759 error.BadPathName => {},759 error.BadPathName => {},
lib/std/event.zig-2
...@@ -6,11 +6,9 @@ pub const Locked = @import("event/locked.zig").Locked;...@@ -6,11 +6,9 @@ pub const Locked = @import("event/locked.zig").Locked;
6pub const RwLock = @import("event/rwlock.zig").RwLock;6pub const RwLock = @import("event/rwlock.zig").RwLock;
7pub const RwLocked = @import("event/rwlocked.zig").RwLocked;7pub const RwLocked = @import("event/rwlocked.zig").RwLocked;
8pub const Loop = @import("event/loop.zig").Loop;8pub const Loop = @import("event/loop.zig").Loop;
9pub const fs = @import("event/fs.zig");
109
11test "import event tests" {10test "import event tests" {
12 _ = @import("event/channel.zig");11 _ = @import("event/channel.zig");
13 _ = @import("event/fs.zig");
14 _ = @import("event/future.zig");12 _ = @import("event/future.zig");
15 _ = @import("event/group.zig");13 _ = @import("event/group.zig");
16 _ = @import("event/lock.zig");14 _ = @import("event/lock.zig");
lib/std/event/channel.zig+3-4
...@@ -267,17 +267,16 @@ pub fn Channel(comptime T: type) type {...@@ -267,17 +267,16 @@ pub fn Channel(comptime T: type) type {
267}267}
268268
269test "std.event.Channel" {269test "std.event.Channel" {
270 if (!std.io.is_async) return error.SkipZigTest;
271
270 // https://github.com/ziglang/zig/issues/1908272 // https://github.com/ziglang/zig/issues/1908
271 if (builtin.single_threaded) return error.SkipZigTest;273 if (builtin.single_threaded) return error.SkipZigTest;
272274
273 // https://github.com/ziglang/zig/issues/3251275 // https://github.com/ziglang/zig/issues/3251
274 if (builtin.os == .freebsd) return error.SkipZigTest;276 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
279 var channel: Channel(i32) = undefined;278 var channel: Channel(i32) = undefined;
280 channel.init([0]i32{});279 channel.init(&[0]i32{});
281 defer channel.deinit();280 defer channel.deinit();
282281
283 var handle = async testChannelGetter(&channel);282 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 {...@@ -22,7 +22,7 @@ pub fn Group(comptime ReturnType: type) type {
22 const AllocStack = std.atomic.Stack(Node);22 const AllocStack = std.atomic.Stack(Node);
2323
24 pub const Node = struct {24 pub const Node = struct {
25 bytes: []const u8 = [0]u8{},25 bytes: []const u8 = &[0]u8{},
26 handle: anyframe->ReturnType,26 handle: anyframe->ReturnType,
27 };27 };
2828
lib/std/event/lock.zig+4-4
...@@ -117,21 +117,21 @@ pub const Lock = struct {...@@ -117,21 +117,21 @@ pub const Lock = struct {
117};117};
118118
119test "std.event.Lock" {119test "std.event.Lock" {
120 if (!std.io.is_async) return error.SkipZigTest;
121
120 // TODO https://github.com/ziglang/zig/issues/1908122 // TODO https://github.com/ziglang/zig/issues/1908
121 if (builtin.single_threaded) return error.SkipZigTest;123 if (builtin.single_threaded) return error.SkipZigTest;
122124
123 // TODO https://github.com/ziglang/zig/issues/3251125 // TODO https://github.com/ziglang/zig/issues/3251
124 if (builtin.os == .freebsd) return error.SkipZigTest;126 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
129 var lock = Lock.init();128 var lock = Lock.init();
130 defer lock.deinit();129 defer lock.deinit();
131130
132 _ = async testLock(&lock);131 _ = 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);
135}135}
136136
137async fn testLock(lock: *Lock) void {137async fn testLock(lock: *Lock) void {
lib/std/event/loop.zig+317-50
...@@ -6,7 +6,6 @@ const testing = std.testing;...@@ -6,7 +6,6 @@ const testing = std.testing;
6const mem = std.mem;6const mem = std.mem;
7const AtomicRmwOp = builtin.AtomicRmwOp;7const AtomicRmwOp = builtin.AtomicRmwOp;
8const AtomicOrder = builtin.AtomicOrder;8const AtomicOrder = builtin.AtomicOrder;
9const fs = std.event.fs;
10const os = std.os;9const os = std.os;
11const windows = os.windows;10const windows = os.windows;
12const maxInt = std.math.maxInt;11const maxInt = std.math.maxInt;
...@@ -174,21 +173,19 @@ pub const Loop = struct {...@@ -174,21 +173,19 @@ pub const Loop = struct {
174 fn initOsData(self: *Loop, extra_thread_count: usize) InitOsDataError!void {173 fn initOsData(self: *Loop, extra_thread_count: usize) InitOsDataError!void {
175 switch (builtin.os) {174 switch (builtin.os) {
176 .linux => {175 .linux => {
177 self.os_data.fs_queue = std.atomic.Queue(fs.Request).init();176 self.os_data.fs_queue = std.atomic.Queue(Request).init();
178 self.os_data.fs_queue_item = 0;177 self.os_data.fs_queue_item = 0;
179 // we need another thread for the file system because Linux does not have an async178 // we need another thread for the file system because Linux does not have an async
180 // file system I/O API.179 // file system I/O API.
181 self.os_data.fs_end_request = fs.RequestNode{180 self.os_data.fs_end_request = Request.Node{
182 .prev = undefined,181 .data = Request{
183 .next = undefined,182 .msg = .end,
184 .data = fs.Request{183 .finish = .NoAction,
185 .msg = fs.Request.Msg.End,
186 .finish = fs.Request.Finish.NoAction,
187 },184 },
188 };185 };
189186
190 errdefer {187 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);
192 }189 }
193 for (self.eventfd_resume_nodes) |*eventfd_node| {190 for (self.eventfd_resume_nodes) |*eventfd_node| {
194 eventfd_node.* = std.atomic.Stack(ResumeNode.EventFd).Node{191 eventfd_node.* = std.atomic.Stack(ResumeNode.EventFd).Node{
...@@ -207,10 +204,10 @@ pub const Loop = struct {...@@ -207,10 +204,10 @@ pub const Loop = struct {
207 }204 }
208205
209 self.os_data.epollfd = try os.epoll_create1(os.EPOLL_CLOEXEC);206 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
212 self.os_data.final_eventfd = try os.eventfd(0, os.EFD_CLOEXEC | os.EFD_NONBLOCK);209 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
215 self.os_data.final_eventfd_event = os.epoll_event{212 self.os_data.final_eventfd_event = os.epoll_event{
216 .events = os.EPOLLIN,213 .events = os.EPOLLIN,
...@@ -237,7 +234,7 @@ pub const Loop = struct {...@@ -237,7 +234,7 @@ pub const Loop = struct {
237 var extra_thread_index: usize = 0;234 var extra_thread_index: usize = 0;
238 errdefer {235 errdefer {
239 // writing 8 bytes to an eventfd cannot fail236 // 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;
241 while (extra_thread_index != 0) {238 while (extra_thread_index != 0) {
242 extra_thread_index -= 1;239 extra_thread_index -= 1;
243 self.extra_threads[extra_thread_index].wait();240 self.extra_threads[extra_thread_index].wait();
...@@ -249,20 +246,20 @@ pub const Loop = struct {...@@ -249,20 +246,20 @@ pub const Loop = struct {
249 },246 },
250 .macosx, .freebsd, .netbsd, .dragonfly => {247 .macosx, .freebsd, .netbsd, .dragonfly => {
251 self.os_data.kqfd = try os.kqueue();248 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
254 self.os_data.fs_kqfd = try os.kqueue();251 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();
258 // we need another thread for the file system because Darwin does not have an async255 // we need another thread for the file system because Darwin does not have an async
259 // file system I/O API.256 // file system I/O API.
260 self.os_data.fs_end_request = fs.RequestNode{257 self.os_data.fs_end_request = Request.Node{
261 .prev = undefined,258 .prev = undefined,
262 .next = undefined,259 .next = undefined,
263 .data = fs.Request{260 .data = Request{
264 .msg = fs.Request.Msg.End,261 .msg = .end,
265 .finish = fs.Request.Finish.NoAction,262 .finish = .NoAction,
266 },263 },
267 };264 };
268265
...@@ -407,14 +404,14 @@ pub const Loop = struct {...@@ -407,14 +404,14 @@ pub const Loop = struct {
407 fn deinitOsData(self: *Loop) void {404 fn deinitOsData(self: *Loop) void {
408 switch (builtin.os) {405 switch (builtin.os) {
409 .linux => {406 .linux => {
410 os.close(self.os_data.final_eventfd);407 noasync os.close(self.os_data.final_eventfd);
411 while (self.available_eventfd_resume_nodes.pop()) |node| os.close(node.data.eventfd);408 while (self.available_eventfd_resume_nodes.pop()) |node| noasync os.close(node.data.eventfd);
412 os.close(self.os_data.epollfd);409 noasync os.close(self.os_data.epollfd);
413 self.allocator.free(self.eventfd_resume_nodes);410 self.allocator.free(self.eventfd_resume_nodes);
414 },411 },
415 .macosx, .freebsd, .netbsd, .dragonfly => {412 .macosx, .freebsd, .netbsd, .dragonfly => {
416 os.close(self.os_data.kqfd);413 noasync os.close(self.os_data.kqfd);
417 os.close(self.os_data.fs_kqfd);414 noasync os.close(self.os_data.fs_kqfd);
418 },415 },
419 .windows => {416 .windows => {
420 windows.CloseHandle(self.os_data.io_port);417 windows.CloseHandle(self.os_data.io_port);
...@@ -711,6 +708,190 @@ pub const Loop = struct {...@@ -711,6 +708,190 @@ pub const Loop = struct {
711 }708 }
712 }709 }
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
714 fn workerRun(self: *Loop) void {895 fn workerRun(self: *Loop) void {
715 while (true) {896 while (true) {
716 while (true) {897 while (true) {
...@@ -804,7 +985,7 @@ pub const Loop = struct {...@@ -804,7 +985,7 @@ pub const Loop = struct {
804 }985 }
805 }986 }
806987
807 fn posixFsRequest(self: *Loop, request_node: *fs.RequestNode) void {988 fn posixFsRequest(self: *Loop, request_node: *Request.Node) void {
808 self.beginOneEvent(); // finished in posixFsRun after processing the msg989 self.beginOneEvent(); // finished in posixFsRun after processing the msg
809 self.os_data.fs_queue.put(request_node);990 self.os_data.fs_queue.put(request_node);
810 switch (builtin.os) {991 switch (builtin.os) {
...@@ -826,7 +1007,7 @@ pub const Loop = struct {...@@ -826,7 +1007,7 @@ pub const Loop = struct {
826 }1007 }
827 }1008 }
8281009
829 fn posixFsCancel(self: *Loop, request_node: *fs.RequestNode) void {1010 fn posixFsCancel(self: *Loop, request_node: *Request.Node) void {
830 if (self.os_data.fs_queue.remove(request_node)) {1011 if (self.os_data.fs_queue.remove(request_node)) {
831 self.finishOneEvent();1012 self.finishOneEvent();
832 }1013 }
...@@ -841,37 +1022,32 @@ pub const Loop = struct {...@@ -841,37 +1022,32 @@ pub const Loop = struct {
841 }1022 }
842 while (self.os_data.fs_queue.get()) |node| {1023 while (self.os_data.fs_queue.get()) |node| {
843 switch (node.data.msg) {1024 switch (node.data.msg) {
844 .End => return,1025 .end => return,
845 .WriteV => |*msg| {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| {
846 msg.result = noasync os.writev(msg.fd, msg.iov);1033 msg.result = noasync os.writev(msg.fd, msg.iov);
847 },1034 },
848 .PWriteV => |*msg| {1035 .pwritev => |*msg| {
849 msg.result = noasync os.pwritev(msg.fd, msg.iov, msg.offset);1036 msg.result = noasync os.pwritev(msg.fd, msg.iov, msg.offset);
850 },1037 },
851 .PReadV => |*msg| {1038 .preadv => |*msg| {
852 msg.result = noasync os.preadv(msg.fd, msg.iov, msg.offset);1039 msg.result = noasync os.preadv(msg.fd, msg.iov, msg.offset);
853 },1040 },
854 .Open => |*msg| {1041 .open => |*msg| {
855 msg.result = noasync os.openC(msg.path.ptr, msg.flags, msg.mode);1042 msg.result = noasync os.openC(msg.path, msg.flags, msg.mode);
856 },1043 },
857 .Close => |*msg| noasync os.close(msg.fd),1044 .openat => |*msg| {
858 .WriteFile => |*msg| blk: {1045 msg.result = noasync os.openatC(msg.fd, msg.path, msg.flags, msg.mode);
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);
868 },1046 },
1047 .close => |*msg| noasync os.close(msg.fd),
869 }1048 }
870 switch (node.data.finish) {1049 switch (node.data.finish) {
871 .TickNode => |*tick_node| self.onNextTick(tick_node),1050 .TickNode => |*tick_node| self.onNextTick(tick_node),
872 .DeallocCloseOperation => |close_op| {
873 self.allocator.destroy(close_op);
874 },
875 .NoAction => {},1051 .NoAction => {},
876 }1052 }
877 self.finishOneEvent();1053 self.finishOneEvent();
...@@ -911,8 +1087,8 @@ pub const Loop = struct {...@@ -911,8 +1087,8 @@ pub const Loop = struct {
911 fs_kevent_wait: os.Kevent,1087 fs_kevent_wait: os.Kevent,
912 fs_thread: *Thread,1088 fs_thread: *Thread,
913 fs_kqfd: i32,1089 fs_kqfd: i32,
914 fs_queue: std.atomic.Queue(fs.Request),1090 fs_queue: std.atomic.Queue(Request),
915 fs_end_request: fs.RequestNode,1091 fs_end_request: Request.Node,
916 };1092 };
9171093
918 const LinuxOsData = struct {1094 const LinuxOsData = struct {
...@@ -921,8 +1097,99 @@ pub const Loop = struct {...@@ -921,8 +1097,99 @@ pub const Loop = struct {
921 final_eventfd_event: os.linux.epoll_event,1097 final_eventfd_event: os.linux.epoll_event,
922 fs_thread: *Thread,1098 fs_thread: *Thread,
923 fs_queue_item: i32,1099 fs_queue_item: i32,
924 fs_queue: std.atomic.Queue(fs.Request),1100 fs_queue: std.atomic.Queue(Request),
925 fs_end_request: fs.RequestNode,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 };
926 };1193 };
927};1194};
9281195
lib/std/fmt.zig+16-16
...@@ -78,7 +78,7 @@ fn peekIsAlign(comptime fmt: []const u8) bool {...@@ -78,7 +78,7 @@ fn peekIsAlign(comptime fmt: []const u8) bool {
78pub fn format(78pub fn format(
79 context: var,79 context: var,
80 comptime Errors: type,80 comptime Errors: type,
81 output: fn (@TypeOf(context), []const u8) Errors!void,81 comptime output: fn (@TypeOf(context), []const u8) Errors!void,
82 comptime fmt: []const u8,82 comptime fmt: []const u8,
83 args: var,83 args: var,
84) Errors!void {84) Errors!void {
...@@ -326,7 +326,7 @@ pub fn formatType(...@@ -326,7 +326,7 @@ pub fn formatType(
326 options: FormatOptions,326 options: FormatOptions,
327 context: var,327 context: var,
328 comptime Errors: type,328 comptime Errors: type,
329 output: fn (@TypeOf(context), []const u8) Errors!void,329 comptime output: fn (@TypeOf(context), []const u8) Errors!void,
330 max_depth: usize,330 max_depth: usize,
331) Errors!void {331) Errors!void {
332 if (comptime std.mem.eql(u8, fmt, "*")) {332 if (comptime std.mem.eql(u8, fmt, "*")) {
...@@ -488,7 +488,7 @@ fn formatValue(...@@ -488,7 +488,7 @@ fn formatValue(
488 options: FormatOptions,488 options: FormatOptions,
489 context: var,489 context: var,
490 comptime Errors: type,490 comptime Errors: type,
491 output: fn (@TypeOf(context), []const u8) Errors!void,491 comptime output: fn (@TypeOf(context), []const u8) Errors!void,
492) Errors!void {492) Errors!void {
493 if (comptime std.mem.eql(u8, fmt, "B")) {493 if (comptime std.mem.eql(u8, fmt, "B")) {
494 return formatBytes(value, options, 1000, context, Errors, output);494 return formatBytes(value, options, 1000, context, Errors, output);
...@@ -510,7 +510,7 @@ pub fn formatIntValue(...@@ -510,7 +510,7 @@ pub fn formatIntValue(
510 options: FormatOptions,510 options: FormatOptions,
511 context: var,511 context: var,
512 comptime Errors: type,512 comptime Errors: type,
513 output: fn (@TypeOf(context), []const u8) Errors!void,513 comptime output: fn (@TypeOf(context), []const u8) Errors!void,
514) Errors!void {514) Errors!void {
515 comptime var radix = 10;515 comptime var radix = 10;
516 comptime var uppercase = false;516 comptime var uppercase = false;
...@@ -552,7 +552,7 @@ fn formatFloatValue(...@@ -552,7 +552,7 @@ fn formatFloatValue(
552 options: FormatOptions,552 options: FormatOptions,
553 context: var,553 context: var,
554 comptime Errors: type,554 comptime Errors: type,
555 output: fn (@TypeOf(context), []const u8) Errors!void,555 comptime output: fn (@TypeOf(context), []const u8) Errors!void,
556) Errors!void {556) Errors!void {
557 if (fmt.len == 0 or comptime std.mem.eql(u8, fmt, "e")) {557 if (fmt.len == 0 or comptime std.mem.eql(u8, fmt, "e")) {
558 return formatFloatScientific(value, options, context, Errors, output);558 return formatFloatScientific(value, options, context, Errors, output);
...@@ -569,7 +569,7 @@ pub fn formatText(...@@ -569,7 +569,7 @@ pub fn formatText(
569 options: FormatOptions,569 options: FormatOptions,
570 context: var,570 context: var,
571 comptime Errors: type,571 comptime Errors: type,
572 output: fn (@TypeOf(context), []const u8) Errors!void,572 comptime output: fn (@TypeOf(context), []const u8) Errors!void,
573) Errors!void {573) Errors!void {
574 if (fmt.len == 0) {574 if (fmt.len == 0) {
575 return output(context, bytes);575 return output(context, bytes);
...@@ -590,7 +590,7 @@ pub fn formatAsciiChar(...@@ -590,7 +590,7 @@ pub fn formatAsciiChar(
590 options: FormatOptions,590 options: FormatOptions,
591 context: var,591 context: var,
592 comptime Errors: type,592 comptime Errors: type,
593 output: fn (@TypeOf(context), []const u8) Errors!void,593 comptime output: fn (@TypeOf(context), []const u8) Errors!void,
594) Errors!void {594) Errors!void {
595 return output(context, @as(*const [1]u8, &c)[0..]);595 return output(context, @as(*const [1]u8, &c)[0..]);
596}596}
...@@ -600,7 +600,7 @@ pub fn formatBuf(...@@ -600,7 +600,7 @@ pub fn formatBuf(
600 options: FormatOptions,600 options: FormatOptions,
601 context: var,601 context: var,
602 comptime Errors: type,602 comptime Errors: type,
603 output: fn (@TypeOf(context), []const u8) Errors!void,603 comptime output: fn (@TypeOf(context), []const u8) Errors!void,
604) Errors!void {604) Errors!void {
605 try output(context, buf);605 try output(context, buf);
606606
...@@ -620,7 +620,7 @@ pub fn formatFloatScientific(...@@ -620,7 +620,7 @@ pub fn formatFloatScientific(
620 options: FormatOptions,620 options: FormatOptions,
621 context: var,621 context: var,
622 comptime Errors: type,622 comptime Errors: type,
623 output: fn (@TypeOf(context), []const u8) Errors!void,623 comptime output: fn (@TypeOf(context), []const u8) Errors!void,
624) Errors!void {624) Errors!void {
625 var x = @floatCast(f64, value);625 var x = @floatCast(f64, value);
626626
...@@ -715,7 +715,7 @@ pub fn formatFloatDecimal(...@@ -715,7 +715,7 @@ pub fn formatFloatDecimal(
715 options: FormatOptions,715 options: FormatOptions,
716 context: var,716 context: var,
717 comptime Errors: type,717 comptime Errors: type,
718 output: fn (@TypeOf(context), []const u8) Errors!void,718 comptime output: fn (@TypeOf(context), []const u8) Errors!void,
719) Errors!void {719) Errors!void {
720 var x = @as(f64, value);720 var x = @as(f64, value);
721721
...@@ -861,7 +861,7 @@ pub fn formatBytes(...@@ -861,7 +861,7 @@ pub fn formatBytes(
861 comptime radix: usize,861 comptime radix: usize,
862 context: var,862 context: var,
863 comptime Errors: type,863 comptime Errors: type,
864 output: fn (@TypeOf(context), []const u8) Errors!void,864 comptime output: fn (@TypeOf(context), []const u8) Errors!void,
865) Errors!void {865) Errors!void {
866 if (value == 0) {866 if (value == 0) {
867 return output(context, "0B");867 return output(context, "0B");
...@@ -902,7 +902,7 @@ pub fn formatInt(...@@ -902,7 +902,7 @@ pub fn formatInt(
902 options: FormatOptions,902 options: FormatOptions,
903 context: var,903 context: var,
904 comptime Errors: type,904 comptime Errors: type,
905 output: fn (@TypeOf(context), []const u8) Errors!void,905 comptime output: fn (@TypeOf(context), []const u8) Errors!void,
906) Errors!void {906) Errors!void {
907 const int_value = if (@TypeOf(value) == comptime_int) blk: {907 const int_value = if (@TypeOf(value) == comptime_int) blk: {
908 const Int = math.IntFittingRange(value, value);908 const Int = math.IntFittingRange(value, value);
...@@ -924,7 +924,7 @@ fn formatIntSigned(...@@ -924,7 +924,7 @@ fn formatIntSigned(
924 options: FormatOptions,924 options: FormatOptions,
925 context: var,925 context: var,
926 comptime Errors: type,926 comptime Errors: type,
927 output: fn (@TypeOf(context), []const u8) Errors!void,927 comptime output: fn (@TypeOf(context), []const u8) Errors!void,
928) Errors!void {928) Errors!void {
929 const new_options = FormatOptions{929 const new_options = FormatOptions{
930 .width = if (options.width) |w| (if (w == 0) 0 else w - 1) else null,930 .width = if (options.width) |w| (if (w == 0) 0 else w - 1) else null,
...@@ -955,7 +955,7 @@ fn formatIntUnsigned(...@@ -955,7 +955,7 @@ fn formatIntUnsigned(
955 options: FormatOptions,955 options: FormatOptions,
956 context: var,956 context: var,
957 comptime Errors: type,957 comptime Errors: type,
958 output: fn (@TypeOf(context), []const u8) Errors!void,958 comptime output: fn (@TypeOf(context), []const u8) Errors!void,
959) Errors!void {959) Errors!void {
960 assert(base >= 2);960 assert(base >= 2);
961 var buf: [math.max(@TypeOf(value).bit_count, 1)]u8 = undefined;961 var buf: [math.max(@TypeOf(value).bit_count, 1)]u8 = undefined;
...@@ -1419,7 +1419,7 @@ test "custom" {...@@ -1419,7 +1419,7 @@ test "custom" {
1419 options: FormatOptions,1419 options: FormatOptions,
1420 context: var,1420 context: var,
1421 comptime Errors: type,1421 comptime Errors: type,
1422 output: fn (@TypeOf(context), []const u8) Errors!void,1422 comptime output: fn (@TypeOf(context), []const u8) Errors!void,
1423 ) Errors!void {1423 ) Errors!void {
1424 if (fmt.len == 0 or comptime std.mem.eql(u8, fmt, "p")) {1424 if (fmt.len == 0 or comptime std.mem.eql(u8, fmt, "p")) {
1425 return std.fmt.format(context, Errors, output, "({d:.3},{d:.3})", .{ self.x, self.y });1425 return std.fmt.format(context, Errors, output, "({d:.3},{d:.3})", .{ self.x, self.y });
...@@ -1626,7 +1626,7 @@ test "formatType max_depth" {...@@ -1626,7 +1626,7 @@ test "formatType max_depth" {
1626 options: FormatOptions,1626 options: FormatOptions,
1627 context: var,1627 context: var,
1628 comptime Errors: type,1628 comptime Errors: type,
1629 output: fn (@TypeOf(context), []const u8) Errors!void,1629 comptime output: fn (@TypeOf(context), []const u8) Errors!void,
1630 ) Errors!void {1630 ) Errors!void {
1631 if (fmt.len == 0) {1631 if (fmt.len == 0) {
1632 return std.fmt.format(context, Errors, output, "({d:.3},{d:.3})", .{ self.x, self.y });1632 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;...@@ -23,6 +23,8 @@ pub const realpathW = os.realpathW;
23pub const getAppDataDir = @import("fs/get_app_data_dir.zig").getAppDataDir;23pub const getAppDataDir = @import("fs/get_app_data_dir.zig").getAppDataDir;
24pub const GetAppDataDirError = @import("fs/get_app_data_dir.zig").GetAppDataDirError;24pub const GetAppDataDirError = @import("fs/get_app_data_dir.zig").GetAppDataDirError;
2525
26pub const Watch = @import("fs/watch.zig").Watch;
27
26/// This represents the maximum size of a UTF-8 encoded file path.28/// This represents the maximum size of a UTF-8 encoded file path.
27/// All file system operations which return a path are guaranteed to29/// All file system operations which return a path are guaranteed to
28/// fit into a UTF-8 encoded array of this length.30/// fit into a UTF-8 encoded array of this length.
...@@ -43,6 +45,13 @@ pub const base64_encoder = base64.Base64Encoder.init(...@@ -43,6 +45,13 @@ pub const base64_encoder = base64.Base64Encoder.init(
43 base64.standard_pad_char,45 base64.standard_pad_char,
44);46);
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
46/// TODO remove the allocator requirement from this API55/// TODO remove the allocator requirement from this API
47pub fn atomicSymLink(allocator: *Allocator, existing_path: []const u8, new_path: []const u8) !void {56pub fn atomicSymLink(allocator: *Allocator, existing_path: []const u8, new_path: []const u8) !void {
48 if (symLink(existing_path, new_path)) {57 if (symLink(existing_path, new_path)) {
...@@ -688,11 +697,16 @@ pub const Dir = struct {...@@ -688,11 +697,16 @@ pub const Dir = struct {
688 }697 }
689698
690 pub fn close(self: *Dir) void {699 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 }
692 self.* = undefined;705 self.* = undefined;
693 }706 }
694707
695 /// Opens a file for reading or writing, without attempting to create a new file.708 /// Opens a file for reading or writing, without attempting to create a new file.
709 /// To create a new file, see `createFile`.
696 /// Call `File.close` to release the resource.710 /// Call `File.close` to release the resource.
697 /// Asserts that the path parameter has no null bytes.711 /// Asserts that the path parameter has no null bytes.
698 pub fn openFile(self: Dir, sub_path: []const u8, flags: File.OpenFlags) File.OpenError!File {712 pub fn openFile(self: Dir, sub_path: []const u8, flags: File.OpenFlags) File.OpenError!File {
...@@ -718,8 +732,11 @@ pub const Dir = struct {...@@ -718,8 +732,11 @@ pub const Dir = struct {
718 @as(u32, os.O_WRONLY)732 @as(u32, os.O_WRONLY)
719 else733 else
720 @as(u32, os.O_RDONLY);734 @as(u32, os.O_RDONLY);
721 const fd = try os.openatC(self.fd, sub_path, os_flags, 0);735 const fd = if (need_async_thread)
722 return File{ .handle = fd };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 };
723 }740 }
724741
725 /// Same as `openFile` but Windows-only and the path parameter is742 /// Same as `openFile` but Windows-only and the path parameter is
...@@ -756,8 +773,11 @@ pub const Dir = struct {...@@ -756,8 +773,11 @@ pub const Dir = struct {
756 (if (flags.truncate) @as(u32, os.O_TRUNC) else 0) |773 (if (flags.truncate) @as(u32, os.O_TRUNC) else 0) |
757 (if (flags.read) @as(u32, os.O_RDWR) else os.O_WRONLY) |774 (if (flags.read) @as(u32, os.O_RDWR) else os.O_WRONLY) |
758 (if (flags.exclusive) @as(u32, os.O_EXCL) else 0);775 (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);776 const fd = if (need_async_thread)
760 return File{ .handle = fd };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 };
761 }781 }
762782
763 /// Same as `createFile` but Windows-only and the path parameter is783 /// Same as `createFile` but Windows-only and the path parameter is
...@@ -798,7 +818,10 @@ pub const Dir = struct {...@@ -798,7 +818,10 @@ pub const Dir = struct {
798 ) File.OpenError!File {818 ) File.OpenError!File {
799 const w = os.windows;819 const w = os.windows;
800820
801 var result = File{ .handle = undefined };821 var result = File{
822 .handle = undefined,
823 .io_mode = .blocking,
824 };
802825
803 const path_len_bytes = math.cast(u16, mem.toSliceConst(u16, sub_path_w).len * 2) catch |err| switch (err) {826 const path_len_bytes = math.cast(u16, mem.toSliceConst(u16, sub_path_w).len * 2) catch |err| switch (err) {
804 error.Overflow => return error.NameTooLong,827 error.Overflow => return error.NameTooLong,
...@@ -919,7 +942,12 @@ pub const Dir = struct {...@@ -919,7 +942,12 @@ pub const Dir = struct {
919 }942 }
920943
921 fn openDirFlagsC(self: Dir, sub_path_c: [*:0]const u8, flags: u32) OpenError!Dir {944 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) {
923 error.FileTooBig => unreachable, // can't happen for directories951 error.FileTooBig => unreachable, // can't happen for directories
924 error.IsDir => unreachable, // we're providing O_DIRECTORY952 error.IsDir => unreachable, // we're providing O_DIRECTORY
925 error.NoSpaceLeft => unreachable, // not providing O_CREAT953 error.NoSpaceLeft => unreachable, // not providing O_CREAT
...@@ -1588,4 +1616,5 @@ test "" {...@@ -1588,4 +1616,5 @@ test "" {
1588 _ = @import("fs/path.zig");1616 _ = @import("fs/path.zig");
1589 _ = @import("fs/file.zig");1617 _ = @import("fs/file.zig");
1590 _ = @import("fs/get_app_data_dir.zig");1618 _ = @import("fs/get_app_data_dir.zig");
1619 _ = @import("fs/watch.zig");
1591}1620}
lib/std/fs/file.zig+78-75
...@@ -8,18 +8,29 @@ const assert = std.debug.assert;...@@ -8,18 +8,29 @@ const assert = std.debug.assert;
8const windows = os.windows;8const windows = os.windows;
9const Os = builtin.Os;9const Os = builtin.Os;
10const maxInt = std.math.maxInt;10const maxInt = std.math.maxInt;
11const need_async_thread = std.fs.need_async_thread;
1112
12pub const File = struct {13pub const File = struct {
13 /// The OS-specific file descriptor or file handle.14 /// The OS-specific file descriptor or file handle.
14 handle: os.fd_t,15 handle: os.fd_t,
1516
16 pub const Mode = switch (builtin.os) {17 /// On some systems, such as Linux, file system file descriptors are incapable of non-blocking I/O.
17 Os.windows => void,18 /// This forces us to perform asynchronous I/O on a dedicated thread, to achieve non-blocking
18 else => u32,19 /// file-system I/O. To do this, `File` must be aware of whether it is a file system file descriptor,
19 };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
21 pub const default_mode = switch (builtin.os) {32 pub const default_mode = switch (builtin.os) {
22 Os.windows => {},33 .windows => 0,
23 else => 0o666,34 else => 0o666,
24 };35 };
2536
...@@ -49,87 +60,27 @@ pub const File = struct {...@@ -49,87 +60,27 @@ pub const File = struct {
49 mode: Mode = default_mode,60 mode: Mode = default_mode,
50 };61 };
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
115 /// Test for the existence of `path`.63 /// Test for the existence of `path`.
116 /// `path` is UTF8-encoded.64 /// `path` is UTF8-encoded.
117 /// In general it is recommended to avoid this function. For example,65 /// In general it is recommended to avoid this function. For example,
118 /// instead of testing if a file exists and then opening it, just66 /// instead of testing if a file exists and then opening it, just
119 /// open it and handle the error for file not found.67 /// open it and handle the error for file not found.
120 /// TODO: deprecate this and move it to `std.fs.Dir`.68 /// TODO: deprecate this and move it to `std.fs.Dir`.
69 /// TODO: integrate with async I/O
121 pub fn access(path: []const u8) !void {70 pub fn access(path: []const u8) !void {
122 return os.access(path, os.F_OK);71 return os.access(path, os.F_OK);
123 }72 }
12473
125 /// Same as `access` except the parameter is null-terminated.74 /// Same as `access` except the parameter is null-terminated.
126 /// TODO: deprecate this and move it to `std.fs.Dir`.75 /// TODO: deprecate this and move it to `std.fs.Dir`.
76 /// TODO: integrate with async I/O
127 pub fn accessC(path: [*:0]const u8) !void {77 pub fn accessC(path: [*:0]const u8) !void {
128 return os.accessC(path, os.F_OK);78 return os.accessC(path, os.F_OK);
129 }79 }
13080
131 /// Same as `access` except the parameter is null-terminated UTF16LE-encoded.81 /// Same as `access` except the parameter is null-terminated UTF16LE-encoded.
132 /// TODO: deprecate this and move it to `std.fs.Dir`.82 /// TODO: deprecate this and move it to `std.fs.Dir`.
83 /// TODO: integrate with async I/O
133 pub fn accessW(path: [*:0]const u16) !void {84 pub fn accessW(path: [*:0]const u16) !void {
134 return os.accessW(path, os.F_OK);85 return os.accessW(path, os.F_OK);
135 }86 }
...@@ -137,7 +88,11 @@ pub const File = struct {...@@ -137,7 +88,11 @@ pub const File = struct {
137 /// Upon success, the stream is in an uninitialized state. To continue using it,88 /// Upon success, the stream is in an uninitialized state. To continue using it,
138 /// you must use the open() function.89 /// you must use the open() function.
139 pub fn close(self: File) void {90 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 }
141 }96 }
14297
143 /// Test whether the file refers to a terminal.98 /// Test whether the file refers to a terminal.
...@@ -167,26 +122,31 @@ pub const File = struct {...@@ -167,26 +122,31 @@ pub const File = struct {
167 pub const SeekError = os.SeekError;122 pub const SeekError = os.SeekError;
168123
169 /// Repositions read/write file offset relative to the current offset.124 /// Repositions read/write file offset relative to the current offset.
125 /// TODO: integrate with async I/O
170 pub fn seekBy(self: File, offset: i64) SeekError!void {126 pub fn seekBy(self: File, offset: i64) SeekError!void {
171 return os.lseek_CUR(self.handle, offset);127 return os.lseek_CUR(self.handle, offset);
172 }128 }
173129
174 /// Repositions read/write file offset relative to the end.130 /// Repositions read/write file offset relative to the end.
131 /// TODO: integrate with async I/O
175 pub fn seekFromEnd(self: File, offset: i64) SeekError!void {132 pub fn seekFromEnd(self: File, offset: i64) SeekError!void {
176 return os.lseek_END(self.handle, offset);133 return os.lseek_END(self.handle, offset);
177 }134 }
178135
179 /// Repositions read/write file offset relative to the beginning.136 /// Repositions read/write file offset relative to the beginning.
137 /// TODO: integrate with async I/O
180 pub fn seekTo(self: File, offset: u64) SeekError!void {138 pub fn seekTo(self: File, offset: u64) SeekError!void {
181 return os.lseek_SET(self.handle, offset);139 return os.lseek_SET(self.handle, offset);
182 }140 }
183141
184 pub const GetPosError = os.SeekError || os.FStatError;142 pub const GetPosError = os.SeekError || os.FStatError;
185143
144 /// TODO: integrate with async I/O
186 pub fn getPos(self: File) GetPosError!u64 {145 pub fn getPos(self: File) GetPosError!u64 {
187 return os.lseek_CUR_get(self.handle);146 return os.lseek_CUR_get(self.handle);
188 }147 }
189148
149 /// TODO: integrate with async I/O
190 pub fn getEndPos(self: File) GetPosError!u64 {150 pub fn getEndPos(self: File) GetPosError!u64 {
191 if (builtin.os == .windows) {151 if (builtin.os == .windows) {
192 return windows.GetFileSizeEx(self.handle);152 return windows.GetFileSizeEx(self.handle);
...@@ -196,6 +156,7 @@ pub const File = struct {...@@ -196,6 +156,7 @@ pub const File = struct {
196156
197 pub const ModeError = os.FStatError;157 pub const ModeError = os.FStatError;
198158
159 /// TODO: integrate with async I/O
199 pub fn mode(self: File) ModeError!Mode {160 pub fn mode(self: File) ModeError!Mode {
200 if (builtin.os == .windows) {161 if (builtin.os == .windows) {
201 return {};162 return {};
...@@ -219,6 +180,7 @@ pub const File = struct {...@@ -219,6 +180,7 @@ pub const File = struct {
219180
220 pub const StatError = os.FStatError;181 pub const StatError = os.FStatError;
221182
183 /// TODO: integrate with async I/O
222 pub fn stat(self: File) StatError!Stat {184 pub fn stat(self: File) StatError!Stat {
223 if (builtin.os == .windows) {185 if (builtin.os == .windows) {
224 var io_status_block: windows.IO_STATUS_BLOCK = undefined;186 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
...@@ -233,7 +195,7 @@ pub const File = struct {...@@ -233,7 +195,7 @@ pub const File = struct {
233 }195 }
234 return Stat{196 return Stat{
235 .size = @bitCast(u64, info.StandardInformation.EndOfFile),197 .size = @bitCast(u64, info.StandardInformation.EndOfFile),
236 .mode = {},198 .mode = 0,
237 .atime = windows.fromSysTime(info.BasicInformation.LastAccessTime),199 .atime = windows.fromSysTime(info.BasicInformation.LastAccessTime),
238 .mtime = windows.fromSysTime(info.BasicInformation.LastWriteTime),200 .mtime = windows.fromSysTime(info.BasicInformation.LastWriteTime),
239 .ctime = windows.fromSysTime(info.BasicInformation.CreationTime),201 .ctime = windows.fromSysTime(info.BasicInformation.CreationTime),
...@@ -259,6 +221,7 @@ pub const File = struct {...@@ -259,6 +221,7 @@ pub const File = struct {
259 /// and therefore this function cannot guarantee any precision will be stored.221 /// and therefore this function cannot guarantee any precision will be stored.
260 /// Further, the maximum value is limited by the system ABI. When a value is provided222 /// Further, the maximum value is limited by the system ABI. When a value is provided
261 /// that exceeds this range, the value is clamped to the maximum.223 /// that exceeds this range, the value is clamped to the maximum.
224 /// TODO: integrate with async I/O
262 pub fn updateTimes(225 pub fn updateTimes(
263 self: File,226 self: File,
264 /// access timestamp in nanoseconds227 /// access timestamp in nanoseconds
...@@ -287,21 +250,61 @@ pub const File = struct {...@@ -287,21 +250,61 @@ pub const File = struct {
287 pub const ReadError = os.ReadError;250 pub const ReadError = os.ReadError;
288251
289 pub fn read(self: File, buffer: []u8) ReadError!usize {252 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 }
290 return os.read(self.handle, buffer);256 return os.read(self.handle, buffer);
291 }257 }
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
293 pub const WriteError = os.WriteError;280 pub const WriteError = os.WriteError;
294281
295 pub fn write(self: File, bytes: []const u8) WriteError!void {282 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 }
296 return os.write(self.handle, bytes);286 return os.write(self.handle, bytes);
297 }287 }
298288
299 pub fn writev_iovec(self: File, iovecs: []const os.iovec_const) WriteError!void {289 pub fn pwrite(self: File, bytes: []const u8, offset: u64) WriteError!void {
300 if (std.event.Loop.instance) |loop| {290 if (need_async_thread and self.io_mode == .blocking and !self.async_block_allowed) {
301 return std.event.fs.writevPosix(loop, self.handle, iovecs);291 return std.event.Loop.instance.?.pwrite(self.handle, bytes, offset);
302 } else {292 }
303 return os.writev(self.handle, iovecs);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);
304 }306 }
307 return os.pwritev(self.handle, iovecs);
305 }308 }
306309
307 pub fn inStream(file: File) InStream {310 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 {...@@ -47,7 +47,10 @@ fn getStdOutHandle() os.fd_t {
47}47}
4848
49pub fn getStdOut() File {49pub fn getStdOut() File {
50 return File.openHandle(getStdOutHandle());50 return File{
51 .handle = getStdOutHandle(),
52 .io_mode = .blocking,
53 };
51}54}
5255
53fn getStdErrHandle() os.fd_t {56fn getStdErrHandle() os.fd_t {
...@@ -63,7 +66,11 @@ fn getStdErrHandle() os.fd_t {...@@ -63,7 +66,11 @@ fn getStdErrHandle() os.fd_t {
63}66}
6467
65pub fn getStdErr() File {68pub 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 };
67}74}
6875
69fn getStdInHandle() os.fd_t {76fn getStdInHandle() os.fd_t {
...@@ -79,7 +86,10 @@ fn getStdInHandle() os.fd_t {...@@ -79,7 +86,10 @@ fn getStdInHandle() os.fd_t {
79}86}
8087
81pub fn getStdIn() File {88pub fn getStdIn() File {
82 return File.openHandle(getStdInHandle());89 return File{
90 .handle = getStdInHandle(),
91 .io_mode = .blocking,
92 };
83}93}
8494
85pub const SeekableStream = @import("io/seekable_stream.zig").SeekableStream;95pub 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"))...@@ -9,14 +9,11 @@ pub const stack_size: usize = if (@hasDecl(root, "stack_size_std_io_OutStream"))
9else9else
10 default_stack_size;10 default_stack_size;
1111
12/// TODO this is not integrated with evented I/O yet.
13/// https://github.com/ziglang/zig/issues/3557
14pub fn OutStream(comptime WriteError: type) type {12pub fn OutStream(comptime WriteError: type) type {
15 return struct {13 return struct {
16 const Self = @This();14 const Self = @This();
17 pub const Error = WriteError;15 pub const Error = WriteError;
18 // TODO https://github.com/ziglang/zig/issues/355716 pub const WriteFn = if (std.io.is_async)
19 pub const WriteFn = if (std.io.is_async and false)
20 async fn (self: *Self, bytes: []const u8) Error!void17 async fn (self: *Self, bytes: []const u8) Error!void
21 else18 else
22 fn (self: *Self, bytes: []const u8) Error!void;19 fn (self: *Self, bytes: []const u8) Error!void;
...@@ -24,8 +21,7 @@ pub fn OutStream(comptime WriteError: type) type {...@@ -24,8 +21,7 @@ pub fn OutStream(comptime WriteError: type) type {
24 writeFn: WriteFn,21 writeFn: WriteFn,
2522
26 pub fn write(self: *Self, bytes: []const u8) Error!void {23 pub fn write(self: *Self, bytes: []const u8) Error!void {
27 // TODO https://github.com/ziglang/zig/issues/355724 if (std.io.is_async) {
28 if (std.io.is_async and false) {
29 // Let's not be writing 0xaa in safe modes for upwards of 4 MiB for every stream write.25 // Let's not be writing 0xaa in safe modes for upwards of 4 MiB for every stream write.
30 @setRuntimeSafety(false);26 @setRuntimeSafety(false);
31 var stack_frame: [stack_size]u8 align(std.Target.stack_align) = undefined;27 var stack_frame: [stack_size]u8 align(std.Target.stack_align) = undefined;
...@@ -36,12 +32,12 @@ pub fn OutStream(comptime WriteError: type) type {...@@ -36,12 +32,12 @@ pub fn OutStream(comptime WriteError: type) type {
36 }32 }
3733
38 pub fn print(self: *Self, comptime format: []const u8, args: var) Error!void {34 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);
40 }36 }
4137
42 pub fn writeByte(self: *Self, byte: u8) Error!void {38 pub fn writeByte(self: *Self, byte: u8) Error!void {
43 const slice = @as(*const [1]u8, &byte)[0..];39 const array = [1]u8{byte};
44 return self.writeFn(self, slice);40 return self.write(&array);
45 }41 }
4642
47 pub fn writeByteNTimes(self: *Self, byte: u8, n: usize) Error!void {43 pub fn writeByteNTimes(self: *Self, byte: u8, n: usize) Error!void {
...@@ -51,7 +47,7 @@ pub fn OutStream(comptime WriteError: type) type {...@@ -51,7 +47,7 @@ pub fn OutStream(comptime WriteError: type) type {
51 var remaining: usize = n;47 var remaining: usize = n;
52 while (remaining > 0) {48 while (remaining > 0) {
53 const to_write = std.math.min(remaining, bytes.len);49 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]);
55 remaining -= to_write;51 remaining -= to_write;
56 }52 }
57 }53 }
...@@ -60,32 +56,32 @@ pub fn OutStream(comptime WriteError: type) type {...@@ -60,32 +56,32 @@ pub fn OutStream(comptime WriteError: type) type {
60 pub fn writeIntNative(self: *Self, comptime T: type, value: T) Error!void {56 pub fn writeIntNative(self: *Self, comptime T: type, value: T) Error!void {
61 var bytes: [(T.bit_count + 7) / 8]u8 = undefined;57 var bytes: [(T.bit_count + 7) / 8]u8 = undefined;
62 mem.writeIntNative(T, &bytes, value);58 mem.writeIntNative(T, &bytes, value);
63 return self.writeFn(self, &bytes);59 return self.write(&bytes);
64 }60 }
6561
66 /// Write a foreign-endian integer.62 /// Write a foreign-endian integer.
67 pub fn writeIntForeign(self: *Self, comptime T: type, value: T) Error!void {63 pub fn writeIntForeign(self: *Self, comptime T: type, value: T) Error!void {
68 var bytes: [(T.bit_count + 7) / 8]u8 = undefined;64 var bytes: [(T.bit_count + 7) / 8]u8 = undefined;
69 mem.writeIntForeign(T, &bytes, value);65 mem.writeIntForeign(T, &bytes, value);
70 return self.writeFn(self, &bytes);66 return self.write(&bytes);
71 }67 }
7268
73 pub fn writeIntLittle(self: *Self, comptime T: type, value: T) Error!void {69 pub fn writeIntLittle(self: *Self, comptime T: type, value: T) Error!void {
74 var bytes: [(T.bit_count + 7) / 8]u8 = undefined;70 var bytes: [(T.bit_count + 7) / 8]u8 = undefined;
75 mem.writeIntLittle(T, &bytes, value);71 mem.writeIntLittle(T, &bytes, value);
76 return self.writeFn(self, &bytes);72 return self.write(&bytes);
77 }73 }
7874
79 pub fn writeIntBig(self: *Self, comptime T: type, value: T) Error!void {75 pub fn writeIntBig(self: *Self, comptime T: type, value: T) Error!void {
80 var bytes: [(T.bit_count + 7) / 8]u8 = undefined;76 var bytes: [(T.bit_count + 7) / 8]u8 = undefined;
81 mem.writeIntBig(T, &bytes, value);77 mem.writeIntBig(T, &bytes, value);
82 return self.writeFn(self, &bytes);78 return self.write(&bytes);
83 }79 }
8480
85 pub fn writeInt(self: *Self, comptime T: type, value: T, endian: builtin.Endian) Error!void {81 pub fn writeInt(self: *Self, comptime T: type, value: T, endian: builtin.Endian) Error!void {
86 var bytes: [(T.bit_count + 7) / 8]u8 = undefined;82 var bytes: [(T.bit_count + 7) / 8]u8 = undefined;
87 mem.writeInt(T, &bytes, value, endian);83 mem.writeInt(T, &bytes, value, endian);
88 return self.writeFn(self, &bytes);84 return self.write(&bytes);
89 }85 }
90 };86 };
91}87}
lib/std/linked_list.zig+3-6
...@@ -18,12 +18,11 @@ pub fn SinglyLinkedList(comptime T: type) type {...@@ -18,12 +18,11 @@ pub fn SinglyLinkedList(comptime T: type) type {
1818
19 /// Node inside the linked list wrapping the actual data.19 /// Node inside the linked list wrapping the actual data.
20 pub const Node = struct {20 pub const Node = struct {
21 next: ?*Node,21 next: ?*Node = null,
22 data: T,22 data: T,
2323
24 pub fn init(data: T) Node {24 pub fn init(data: T) Node {
25 return Node{25 return Node{
26 .next = null,
27 .data = data,26 .data = data,
28 };27 };
29 }28 }
...@@ -196,14 +195,12 @@ pub fn TailQueue(comptime T: type) type {...@@ -196,14 +195,12 @@ pub fn TailQueue(comptime T: type) type {
196195
197 /// Node inside the linked list wrapping the actual data.196 /// Node inside the linked list wrapping the actual data.
198 pub const Node = struct {197 pub const Node = struct {
199 prev: ?*Node,198 prev: ?*Node = null,
200 next: ?*Node,199 next: ?*Node = null,
201 data: T,200 data: T,
202201
203 pub fn init(data: T) Node {202 pub fn init(data: T) Node {
204 return Node{203 return Node{
205 .prev = null,
206 .next = null,
207 .data = data,204 .data = data,
208 };205 };
209 }206 }
lib/std/net.zig+11-5
...@@ -271,7 +271,7 @@ pub const Address = extern union {...@@ -271,7 +271,7 @@ pub const Address = extern union {
271 options: std.fmt.FormatOptions,271 options: std.fmt.FormatOptions,
272 context: var,272 context: var,
273 comptime Errors: type,273 comptime Errors: type,
274 output: fn (@TypeOf(context), []const u8) Errors!void,274 comptime output: fn (@TypeOf(context), []const u8) Errors!void,
275 ) !void {275 ) !void {
276 switch (self.any.family) {276 switch (self.any.family) {
277 os.AF_INET => {277 os.AF_INET => {
...@@ -361,7 +361,7 @@ pub const Address = extern union {...@@ -361,7 +361,7 @@ pub const Address = extern union {
361};361};
362362
363pub fn connectUnixSocket(path: []const u8) !fs.File {363pub 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;
365 const sockfd = try os.socket(365 const sockfd = try os.socket(
366 os.AF_UNIX,366 os.AF_UNIX,
367 os.SOCK_STREAM | os.SOCK_CLOEXEC | opt_non_block,367 os.SOCK_STREAM | os.SOCK_CLOEXEC | opt_non_block,
...@@ -377,7 +377,10 @@ pub fn connectUnixSocket(path: []const u8) !fs.File {...@@ -377,7 +377,10 @@ pub fn connectUnixSocket(path: []const u8) !fs.File {
377 addr.getOsSockLen(),377 addr.getOsSockLen(),
378 );378 );
379379
380 return fs.File.openHandle(sockfd);380 return fs.File{
381 .handle = sockfd,
382 .io_mode = std.io.mode,
383 };
381}384}
382385
383pub const AddressList = struct {386pub const AddressList = struct {
...@@ -412,7 +415,7 @@ pub fn tcpConnectToAddress(address: Address) !fs.File {...@@ -412,7 +415,7 @@ pub fn tcpConnectToAddress(address: Address) !fs.File {
412 errdefer os.close(sockfd);415 errdefer os.close(sockfd);
413 try os.connect(sockfd, &address.any, address.getOsSockLen());416 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 };
416}419}
417420
418/// Call `AddressList.deinit` on the result.421/// Call `AddressList.deinit` on the result.
...@@ -1379,7 +1382,10 @@ pub const StreamServer = struct {...@@ -1379,7 +1382,10 @@ pub const StreamServer = struct {
1379 var adr_len: os.socklen_t = @sizeOf(Address);1382 var adr_len: os.socklen_t = @sizeOf(Address);
1380 if (os.accept4(self.sockfd.?, &accepted_addr.any, &adr_len, accept_flags)) |fd| {1383 if (os.accept4(self.sockfd.?, &accepted_addr.any, &adr_len, accept_flags)) |fd| {
1381 return Connection{1384 return Connection{
1382 .file = fs.File.openHandle(fd),1385 .file = fs.File{
1386 .handle = fd,
1387 .io_mode = std.io.mode,
1388 },
1383 .address = accepted_addr,1389 .address = accepted_addr,
1384 };1390 };
1385 } else |err| switch (err) {1391 } else |err| switch (err) {
lib/std/net/test.zig+3-5
...@@ -81,17 +81,15 @@ test "resolve DNS" {...@@ -81,17 +81,15 @@ test "resolve DNS" {
81}81}
8282
83test "listen on a port, send bytes, receive bytes" {83test "listen on a port, send bytes, receive bytes" {
84 if (!std.io.is_async) return error.SkipZigTest;
85
84 if (std.builtin.os != .linux) {86 if (std.builtin.os != .linux) {
85 // TODO build abstractions for other operating systems87 // TODO build abstractions for other operating systems
86 return error.SkipZigTest;88 return error.SkipZigTest;
87 }89 }
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
93 // TODO doing this at comptime crashed the compiler91 // 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
96 var server = net.StreamServer.init(net.StreamServer.Options{});94 var server = net.StreamServer.init(net.StreamServer.Options{});
97 defer server.deinit();95 defer server.deinit();
lib/std/os.zig+193-20
...@@ -169,7 +169,12 @@ fn getRandomBytesDevURandom(buf: []u8) !void {...@@ -169,7 +169,12 @@ fn getRandomBytesDevURandom(buf: []u8) !void {
169 return error.NoDevice;169 return error.NoDevice;
170 }170 }
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;
173 stream.readNoEof(buf) catch return error.Unexpected;178 stream.readNoEof(buf) catch return error.Unexpected;
174}179}
175180
...@@ -293,7 +298,7 @@ pub const ReadError = error{...@@ -293,7 +298,7 @@ pub const ReadError = error{
293/// via the event loop. Otherwise EAGAIN results in error.WouldBlock.298/// via the event loop. Otherwise EAGAIN results in error.WouldBlock.
294pub fn read(fd: fd_t, buf: []u8) ReadError!usize {299pub fn read(fd: fd_t, buf: []u8) ReadError!usize {
295 if (builtin.os == .windows) {300 if (builtin.os == .windows) {
296 return windows.ReadFile(fd, buf);301 return windows.ReadFile(fd, buf, null);
297 }302 }
298303
299 if (builtin.os == .wasi and !builtin.link_libc) {304 if (builtin.os == .wasi and !builtin.link_libc) {
...@@ -335,9 +340,37 @@ pub fn read(fd: fd_t, buf: []u8) ReadError!usize {...@@ -335,9 +340,37 @@ pub fn read(fd: fd_t, buf: []u8) ReadError!usize {
335}340}
336341
337/// Number of bytes read is returned. Upon reading end-of-file, zero is returned.342/// 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 handled343///
339/// via the event loop. Otherwise EAGAIN results in error.WouldBlock.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.
340pub fn readv(fd: fd_t, iov: []const iovec) ReadError!usize {352pub 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
341 while (true) {374 while (true) {
342 // TODO handle the case when iov_len is too large and get rid of this @intCast375 // TODO handle the case when iov_len is too large and get rid of this @intCast
343 const rc = system.readv(fd, iov.ptr, @intCast(u32, iov.len));376 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 {...@@ -363,8 +396,56 @@ pub fn readv(fd: fd_t, iov: []const iovec) ReadError!usize {
363}396}
364397
365/// Number of bytes read is returned. Upon reading end-of-file, zero is returned.398/// 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 handled399///
367/// via the event loop. Otherwise EAGAIN results in error.WouldBlock.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.
368pub fn preadv(fd: fd_t, iov: []const iovec, offset: u64) ReadError!usize {449pub fn preadv(fd: fd_t, iov: []const iovec, offset: u64) ReadError!usize {
369 if (comptime std.Target.current.isDarwin()) {450 if (comptime std.Target.current.isDarwin()) {
370 // Darwin does not have preadv but it does have pread.451 // 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 {...@@ -409,6 +490,28 @@ pub fn preadv(fd: fd_t, iov: []const iovec, offset: u64) ReadError!usize {
409 }490 }
410 }491 }
411 }492 }
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
412 while (true) {515 while (true) {
413 // TODO handle the case when iov_len is too large and get rid of this @intCast516 // TODO handle the case when iov_len is too large and get rid of this @intCast
414 const rc = system.preadv(fd, iov.ptr, @intCast(u32, iov.len), offset);517 const rc = system.preadv(fd, iov.ptr, @intCast(u32, iov.len), offset);
...@@ -451,11 +554,9 @@ pub const WriteError = error{...@@ -451,11 +554,9 @@ pub const WriteError = error{
451/// Write to a file descriptor. Keeps trying if it gets interrupted.554/// Write to a file descriptor. Keeps trying if it gets interrupted.
452/// If the application has a global event loop enabled, EAGAIN is handled555/// If the application has a global event loop enabled, EAGAIN is handled
453/// via the event loop. Otherwise EAGAIN results in error.WouldBlock.556/// 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.
456pub fn write(fd: fd_t, bytes: []const u8) WriteError!void {557pub fn write(fd: fd_t, bytes: []const u8) WriteError!void {
457 if (builtin.os == .windows) {558 if (builtin.os == .windows) {
458 return windows.WriteFile(fd, bytes);559 return windows.WriteFile(fd, bytes, null);
459 }560 }
460561
461 if (builtin.os == .wasi and !builtin.link_libc) {562 if (builtin.os == .wasi and !builtin.link_libc) {
...@@ -488,14 +589,12 @@ pub fn write(fd: fd_t, bytes: []const u8) WriteError!void {...@@ -488,14 +589,12 @@ pub fn write(fd: fd_t, bytes: []const u8) WriteError!void {
488 EINTR => continue,589 EINTR => continue,
489 EINVAL => unreachable,590 EINVAL => unreachable,
490 EFAULT => unreachable,591 EFAULT => unreachable,
491 // TODO https://github.com/ziglang/zig/issues/3557592 EAGAIN => if (std.event.Loop.instance) |loop| {
492 EAGAIN => return error.WouldBlock,593 loop.waitUntilFdWritable(fd);
493 //EAGAIN => if (std.event.Loop.instance) |loop| {594 continue;
494 // loop.waitUntilFdWritable(fd);595 } else {
495 // continue;596 return error.WouldBlock;
496 //} else {597 },
497 // return error.WouldBlock;
498 //},
499 EBADF => unreachable, // Always a race condition.598 EBADF => unreachable, // Always a race condition.
500 EDESTADDRREQ => unreachable, // `connect` was never called.599 EDESTADDRREQ => unreachable, // `connect` was never called.
501 EDQUOT => return error.DiskQuota,600 EDQUOT => return error.DiskQuota,
...@@ -540,8 +639,57 @@ pub fn writev(fd: fd_t, iov: []const iovec_const) WriteError!void {...@@ -540,8 +639,57 @@ pub fn writev(fd: fd_t, iov: []const iovec_const) WriteError!void {
540 }639 }
541}640}
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
543/// Write multiple buffers to a file descriptor, with a position offset.681/// 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.
545pub fn pwritev(fd: fd_t, iov: []const iovec_const, offset: u64) WriteError!void {693pub fn pwritev(fd: fd_t, iov: []const iovec_const, offset: u64) WriteError!void {
546 if (comptime std.Target.current.isDarwin()) {694 if (comptime std.Target.current.isDarwin()) {
547 // Darwin does not have pwritev but it does have pwrite.695 // 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...@@ -589,6 +737,15 @@ pub fn pwritev(fd: fd_t, iov: []const iovec_const, offset: u64) WriteError!void
589 }737 }
590 }738 }
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
592 while (true) {749 while (true) {
593 // TODO handle the case when iov_len is too large and get rid of this @intCast750 // TODO handle the case when iov_len is too large and get rid of this @intCast
594 const rc = system.pwritev(fd, iov.ptr, @intCast(u32, iov.len), offset);751 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 {...@@ -694,7 +851,7 @@ pub fn openC(file_path: [*:0]const u8, flags: u32, perm: usize) OpenError!fd_t {
694/// Open and possibly create a file. Keeps trying if it gets interrupted.851/// Open and possibly create a file. Keeps trying if it gets interrupted.
695/// `file_path` is relative to the open directory handle `dir_fd`.852/// `file_path` is relative to the open directory handle `dir_fd`.
696/// See also `openatC`.853/// 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 {
698 const file_path_c = try toPosixPath(file_path);855 const file_path_c = try toPosixPath(file_path);
699 return openatC(dir_fd, &file_path_c, flags, mode);856 return openatC(dir_fd, &file_path_c, flags, mode);
700}857}
...@@ -702,7 +859,7 @@ pub fn openat(dir_fd: fd_t, file_path: []const u8, flags: u32, mode: usize) Open...@@ -702,7 +859,7 @@ pub fn openat(dir_fd: fd_t, file_path: []const u8, flags: u32, mode: usize) Open
702/// Open and possibly create a file. Keeps trying if it gets interrupted.859/// Open and possibly create a file. Keeps trying if it gets interrupted.
703/// `file_path` is relative to the open directory handle `dir_fd`.860/// `file_path` is relative to the open directory handle `dir_fd`.
704/// See also `openat`.861/// 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 {
706 while (true) {863 while (true) {
707 const rc = system.openat(dir_fd, file_path, flags, mode);864 const rc = system.openat(dir_fd, file_path, flags, mode);
708 switch (errno(rc)) {865 switch (errno(rc)) {
...@@ -2372,6 +2529,22 @@ pub fn pipe() PipeError![2]fd_t {...@@ -2372,6 +2529,22 @@ pub fn pipe() PipeError![2]fd_t {
2372}2529}
23732530
2374pub fn pipe2(flags: u32) PipeError![2]fd_t {2531pub 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
2375 var fds: [2]fd_t = undefined;2548 var fds: [2]fd_t = undefined;
2376 switch (errno(system.pipe2(&fds, flags))) {2549 switch (errno(system.pipe2(&fds, flags))) {
2377 0 => return fds,2550 0 => return fds,
lib/std/os/bits/darwin.zig+159
...@@ -4,6 +4,7 @@ const maxInt = std.math.maxInt;...@@ -4,6 +4,7 @@ const maxInt = std.math.maxInt;
44
5pub const fd_t = c_int;5pub const fd_t = c_int;
6pub const pid_t = c_int;6pub const pid_t = c_int;
7pub const mode_t = c_uint;
78
8pub const in_port_t = u16;9pub const in_port_t = u16;
9pub const sa_family_t = u8;10pub const sa_family_t = u8;
...@@ -1223,3 +1224,161 @@ pub const RTLD_NEXT = @intToPtr(*c_void, ~maxInt(usize));...@@ -1223,3 +1224,161 @@ pub const RTLD_NEXT = @intToPtr(*c_void, ~maxInt(usize));
1223pub const RTLD_DEFAULT = @intToPtr(*c_void, ~maxInt(usize) - 1);1224pub const RTLD_DEFAULT = @intToPtr(*c_void, ~maxInt(usize) - 1);
1224pub const RTLD_SELF = @intToPtr(*c_void, ~maxInt(usize) - 2);1225pub const RTLD_SELF = @intToPtr(*c_void, ~maxInt(usize) - 2);
1225pub const RTLD_MAIN_ONLY = @intToPtr(*c_void, ~maxInt(usize) - 4);1226pub 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 {...@@ -7,6 +7,7 @@ pub fn S_ISCHR(m: u32) bool {
7pub const fd_t = c_int;7pub const fd_t = c_int;
8pub const pid_t = c_int;8pub const pid_t = c_int;
9pub const off_t = c_long;9pub const off_t = c_long;
10pub const mode_t = c_uint;
1011
11pub const ENOTSUP = EOPNOTSUPP;12pub const ENOTSUP = EOPNOTSUPP;
12pub const EWOULDBLOCK = EAGAIN;13pub const EWOULDBLOCK = EAGAIN;
lib/std/os/bits/freebsd.zig+1
...@@ -3,6 +3,7 @@ const maxInt = std.math.maxInt;...@@ -3,6 +3,7 @@ const maxInt = std.math.maxInt;
33
4pub const fd_t = c_int;4pub const fd_t = c_int;
5pub const pid_t = c_int;5pub const pid_t = c_int;
6pub const mode_t = c_uint;
67
7pub const socklen_t = u32;8pub const socklen_t = u32;
89
lib/std/os/bits/linux/x86_64.zig+2
...@@ -12,6 +12,8 @@ const socklen_t = linux.socklen_t;...@@ -12,6 +12,8 @@ const socklen_t = linux.socklen_t;
12const iovec = linux.iovec;12const iovec = linux.iovec;
13const iovec_const = linux.iovec_const;13const iovec_const = linux.iovec_const;
1414
15pub const mode_t = usize;
16
15pub const SYS_read = 0;17pub const SYS_read = 0;
16pub const SYS_write = 1;18pub const SYS_write = 1;
17pub const SYS_open = 2;19pub const SYS_open = 2;
lib/std/os/bits/netbsd.zig+1
...@@ -3,6 +3,7 @@ const maxInt = std.math.maxInt;...@@ -3,6 +3,7 @@ const maxInt = std.math.maxInt;
33
4pub const fd_t = c_int;4pub const fd_t = c_int;
5pub const pid_t = c_int;5pub const pid_t = c_int;
6pub const mode_t = c_uint;
67
7/// Renamed from `kevent` to `Kevent` to avoid conflict with function name.8/// Renamed from `kevent` to `Kevent` to avoid conflict with function name.
8pub const Kevent = extern struct {9pub const Kevent = extern struct {
lib/std/os/bits/wasi.zig+1
...@@ -130,6 +130,7 @@ pub const EVENTTYPE_FD_WRITE: eventtype_t = 2;...@@ -130,6 +130,7 @@ pub const EVENTTYPE_FD_WRITE: eventtype_t = 2;
130pub const exitcode_t = u32;130pub const exitcode_t = u32;
131131
132pub const fd_t = u32;132pub const fd_t = u32;
133pub const mode_t = u32;
133134
134pub const fdflags_t = u16;135pub const fdflags_t = u16;
135pub const FDFLAG_APPEND: fdflags_t = 0x0001;136pub 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");...@@ -5,6 +5,7 @@ const ws2_32 = @import("../windows/ws2_32.zig");
55
6pub const fd_t = HANDLE;6pub const fd_t = HANDLE;
7pub const pid_t = HANDLE;7pub const pid_t = HANDLE;
8pub const mode_t = u0;
89
9pub const PATH_MAX = 260;10pub const PATH_MAX = 260;
1011
lib/std/os/windows.zig+128-29
...@@ -344,24 +344,77 @@ pub fn FindClose(hFindFile: HANDLE) void {...@@ -344,24 +344,77 @@ pub fn FindClose(hFindFile: HANDLE) void {
344 assert(kernel32.FindClose(hFindFile) != 0);344 assert(kernel32.FindClose(hFindFile) != 0);
345}345}
346346
347pub const ReadFileError = error{Unexpected};347pub const ReadFileError = error{
348348 OperationAborted,
349pub fn ReadFile(in_hFile: HANDLE, buffer: []u8) ReadFileError!usize {349 BrokenPipe,
350 var index: usize = 0;350 Unexpected,
351 while (index < buffer.len) {351};
352 const want_read_count = @intCast(DWORD, math.min(@as(DWORD, maxInt(DWORD)), buffer.len - index));352
353 var amt_read: DWORD = undefined;353/// If buffer's length exceeds what a Windows DWORD integer can hold, it will be broken into
354 if (kernel32.ReadFile(in_hFile, buffer.ptr + index, want_read_count, &amt_read, null) == 0) {354/// multiple non-atomic reads.
355 switch (kernel32.GetLastError()) {355pub fn ReadFile(in_hFile: HANDLE, buffer: []u8, offset: ?u64) ReadFileError!usize {
356 .OPERATION_ABORTED => continue,356 if (std.event.Loop.instance) |loop| {
357 .BROKEN_PIPE => return index,357 // TODO support async ReadFile with no offset
358 else => |err| return unexpectedError(err),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 }
359 }412 }
413 if (amt_read == 0) return index;
414 index += amt_read;
360 }415 }
361 if (amt_read == 0) return index;416 return index;
362 index += amt_read;
363 }417 }
364 return index;
365}418}
366419
367pub const WriteFileError = error{420pub const WriteFileError = error{
...@@ -371,20 +424,66 @@ pub const WriteFileError = error{...@@ -371,20 +424,66 @@ pub const WriteFileError = error{
371 Unexpected,424 Unexpected,
372};425};
373426
374/// This function is for blocking file descriptors only. For non-blocking, see427pub fn WriteFile(handle: HANDLE, bytes: []const u8, offset: ?u64) WriteFileError!void {
375/// `WriteFileAsync`.428 if (std.event.Loop.instance) |loop| {
376pub fn WriteFile(handle: HANDLE, bytes: []const u8) WriteFileError!void {429 // TODO support async WriteFile with no offset
377 var bytes_written: DWORD = undefined;430 const off = offset.?;
378 // TODO replace this @intCast with a loop that writes all the bytes431 var resume_node = std.event.Loop.ResumeNode.Basic{
379 if (kernel32.WriteFile(handle, bytes.ptr, @intCast(u32, bytes.len), &bytes_written, null) == 0) {432 .base = .{
380 switch (kernel32.GetLastError()) {433 .id = .Basic,
381 .INVALID_USER_BUFFER => return error.SystemResources,434 .handle = @frame(),
382 .NOT_ENOUGH_MEMORY => return error.SystemResources,435 .overlapped = OVERLAPPED{
383 .OPERATION_ABORTED => return error.OperationAborted,436 .Internal = 0,
384 .NOT_ENOUGH_QUOTA => return error.SystemResources,437 .InternalHigh = 0,
385 .IO_PENDING => unreachable, // this function is for blocking files only438 .Offset = @truncate(u32, off),
386 .BROKEN_PIPE => return error.BrokenPipe,439 .OffsetHigh = @truncate(u32, off >> 32),
387 else => |err| return unexpectedError(err),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 }
388 }487 }
389 }488 }
390}489}
lib/std/special/test_runner.zig+25-1
...@@ -2,6 +2,8 @@ const std = @import("std");...@@ -2,6 +2,8 @@ const std = @import("std");
2const io = std.io;2const io = std.io;
3const builtin = @import("builtin");3const builtin = @import("builtin");
44
5pub const io_mode: io.Mode = builtin.test_io_mode;
6
5pub fn main() anyerror!void {7pub fn main() anyerror!void {
6 const test_fn_list = builtin.test_functions;8 const test_fn_list = builtin.test_functions;
7 var ok_count: usize = 0;9 var ok_count: usize = 0;
...@@ -12,6 +14,11 @@ pub fn main() anyerror!void {...@@ -12,6 +14,11 @@ pub fn main() anyerror!void {
12 error.TimerUnsupported => @panic("timer unsupported"),14 error.TimerUnsupported => @panic("timer unsupported"),
13 };15 };
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
15 for (test_fn_list) |test_fn, i| {22 for (test_fn_list) |test_fn, i| {
16 std.testing.base_allocator_instance.reset();23 std.testing.base_allocator_instance.reset();
1724
...@@ -21,7 +28,24 @@ pub fn main() anyerror!void {...@@ -21,7 +28,24 @@ pub fn main() anyerror!void {
21 if (progress.terminal == null) {28 if (progress.terminal == null) {
22 std.debug.warn("{}/{} {}...", .{ i + 1, test_fn_list.len, test_fn.name });29 std.debug.warn("{}/{} {}...", .{ i + 1, test_fn_list.len, test_fn.name });
23 }30 }
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) |_| {
25 ok_count += 1;49 ok_count += 1;
26 test_node.end();50 test_node.end();
27 std.testing.allocator_instance.validate() catch |err| switch (err) {51 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;...@@ -29,7 +29,7 @@ const Package = @import("package.zig").Package;
29const link = @import("link.zig").link;29const link = @import("link.zig").link;
30const LibCInstallation = @import("libc_installation.zig").LibCInstallation;30const LibCInstallation = @import("libc_installation.zig").LibCInstallation;
31const CInt = @import("c_int.zig").CInt;31const CInt = @import("c_int.zig").CInt;
32const fs = event.fs;32const fs = std.fs;
33const util = @import("util.zig");33const util = @import("util.zig");
3434
35const max_src_size = 2 * 1024 * 1024 * 1024; // 2 GiB35const max_src_size = 2 * 1024 * 1024 * 1024; // 2 GiB
...@@ -442,7 +442,7 @@ pub const Compilation = struct {...@@ -442,7 +442,7 @@ pub const Compilation = struct {
442 comp.name = try Buffer.init(comp.arena(), name);442 comp.name = try Buffer.init(comp.arena(), name);
443 comp.llvm_triple = try util.getTriple(comp.arena(), target);443 comp.llvm_triple = try util.getTriple(comp.arena(), target);
444 comp.llvm_target = try util.llvmTargetFromTriple(comp.llvm_triple);444 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
447 const opt_level = switch (build_mode) {447 const opt_level = switch (build_mode) {
448 .Debug => llvm.CodeGenLevelNone,448 .Debug => llvm.CodeGenLevelNone,
...@@ -488,8 +488,8 @@ pub const Compilation = struct {...@@ -488,8 +488,8 @@ pub const Compilation = struct {
488 defer comp.events.deinit();488 defer comp.events.deinit();
489489
490 if (root_src_path) |root_src| {490 if (root_src_path) |root_src| {
491 const dirname = std.fs.path.dirname(root_src) orelse ".";491 const dirname = fs.path.dirname(root_src) orelse ".";
492 const basename = std.fs.path.basename(root_src);492 const basename = fs.path.basename(root_src);
493493
494 comp.root_package = try Package.create(comp.arena(), dirname, basename);494 comp.root_package = try Package.create(comp.arena(), dirname, basename);
495 comp.std_package = try Package.create(comp.arena(), comp.zig_std_dir, "std.zig");495 comp.std_package = try Package.create(comp.arena(), comp.zig_std_dir, "std.zig");
...@@ -521,7 +521,7 @@ pub const Compilation = struct {...@@ -521,7 +521,7 @@ pub const Compilation = struct {
521 if (comp.tmp_dir.getOrNull()) |tmp_dir_result|521 if (comp.tmp_dir.getOrNull()) |tmp_dir_result|
522 if (tmp_dir_result.*) |tmp_dir| {522 if (tmp_dir_result.*) |tmp_dir| {
523 // TODO evented I/O?523 // TODO evented I/O?
524 std.fs.deleteTree(tmp_dir) catch {};524 fs.deleteTree(tmp_dir) catch {};
525 } else |_| {};525 } else |_| {};
526 }526 }
527527
...@@ -797,7 +797,7 @@ pub const Compilation = struct {...@@ -797,7 +797,7 @@ pub const Compilation = struct {
797797
798 async fn rebuildFile(self: *Compilation, root_scope: *Scope.Root) BuildError!void {798 async fn rebuildFile(self: *Compilation, root_scope: *Scope.Root) BuildError!void {
799 const tree_scope = blk: {799 const tree_scope = blk: {
800 const source_code = fs.readFile(800 const source_code = fs.cwd().readFileAlloc(
801 self.gpa(),801 self.gpa(),
802 root_scope.realpath,802 root_scope.realpath,
803 max_src_size,803 max_src_size,
...@@ -935,8 +935,8 @@ pub const Compilation = struct {...@@ -935,8 +935,8 @@ pub const Compilation = struct {
935 fn initialCompile(self: *Compilation) !void {935 fn initialCompile(self: *Compilation) !void {
936 if (self.root_src_path) |root_src_path| {936 if (self.root_src_path) |root_src_path| {
937 const root_scope = blk: {937 const root_scope = blk: {
938 // TODO async/await std.fs.realpath938 // TODO async/await fs.realpath
939 const root_src_real_path = std.fs.realpathAlloc(self.gpa(), root_src_path) catch |err| {939 const root_src_real_path = fs.realpathAlloc(self.gpa(), root_src_path) catch |err| {
940 try self.addCompileErrorCli(root_src_path, "unable to open: {}", .{@errorName(err)});940 try self.addCompileErrorCli(root_src_path, "unable to open: {}", .{@errorName(err)});
941 return;941 return;
942 };942 };
...@@ -1157,7 +1157,7 @@ pub const Compilation = struct {...@@ -1157,7 +1157,7 @@ pub const Compilation = struct {
1157 const file_name = try std.fmt.allocPrint(self.gpa(), "{}{}", .{ file_prefix[0..], suffix });1157 const file_name = try std.fmt.allocPrint(self.gpa(), "{}{}", .{ file_prefix[0..], suffix });
1158 defer self.gpa().free(file_name);1158 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..] });
1161 errdefer self.gpa().free(full_path);1161 errdefer self.gpa().free(full_path);
11621162
1163 return Buffer.fromOwnedSlice(self.gpa(), full_path);1163 return Buffer.fromOwnedSlice(self.gpa(), full_path);
...@@ -1178,8 +1178,8 @@ pub const Compilation = struct {...@@ -1178,8 +1178,8 @@ pub const Compilation = struct {
1178 const zig_dir_path = try getZigDir(self.gpa());1178 const zig_dir_path = try getZigDir(self.gpa());
1179 defer self.gpa().free(zig_dir_path);1179 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..] });1181 const tmp_dir = try fs.path.join(self.arena(), &[_][]const u8{ zig_dir_path, comp_dir_name[0..] });
1182 try std.fs.makePath(self.gpa(), tmp_dir);1182 try fs.makePath(self.gpa(), tmp_dir);
1183 return tmp_dir;1183 return tmp_dir;
1184 }1184 }
11851185
...@@ -1351,7 +1351,7 @@ async fn addFnToLinkSet(comp: *Compilation, fn_val: *Value.Fn) Compilation.Build...@@ -1351,7 +1351,7 @@ async fn addFnToLinkSet(comp: *Compilation, fn_val: *Value.Fn) Compilation.Build
1351}1351}
13521352
1353fn getZigDir(allocator: *mem.Allocator) ![]u8 {1353fn getZigDir(allocator: *mem.Allocator) ![]u8 {
1354 return std.fs.getAppDataDir(allocator, "zig");1354 return fs.getAppDataDir(allocator, "zig");
1355}1355}
13561356
1357fn analyzeFnType(1357fn analyzeFnType(
src-self-hosted/dep_tokenizer.zig+10-23
...@@ -998,7 +998,8 @@ fn printCharValues(out: var, bytes: []const u8) !void {...@@ -998,7 +998,8 @@ fn printCharValues(out: var, bytes: []const u8) !void {
998998
999fn printUnderstandableChar(out: var, char: u8) !void {999fn printUnderstandableChar(out: var, char: u8) !void {
1000 if (!std.ascii.isPrint(char) or char == ' ') {1000 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 {};
1002 } else {1003 } else {
1003 try out.write("'");1004 try out.write("'");
1004 try out.write(&[_]u8{printable_char_tab[char]});1005 try out.write(&[_]u8{printable_char_tab[char]});
...@@ -1021,34 +1022,20 @@ comptime {...@@ -1021,34 +1022,20 @@ comptime {
1021// output: must be a function that takes a `self` idiom parameter1022// output: must be a function that takes a `self` idiom parameter
1022// and a bytes parameter1023// and a bytes parameter
1023// context: must be that self1024// context: must be that self
1024fn makeOutput(output: var, context: var) Output(@TypeOf(output)) {1025fn makeOutput(comptime output: var, context: var) Output(output, @TypeOf(context)) {
1025 return Output(@TypeOf(output)){1026 return Output(output, @TypeOf(context)){
1026 .output = output,
1027 .context = context,1027 .context = context,
1028 };1028 };
1029}1029}
10301030
1031fn Output(comptime T: type) type {1031fn Output(comptime output_func: var, comptime Context: 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");
1046 return struct {1032 return struct {
1047 output: T,1033 context: Context,
1048 context: at0,1034
1035 pub const output = output_func;
10491036
1050 fn write(self: *@This(), bytes: []const u8) !void {1037 fn write(self: @This(), bytes: []const u8) !void {
1051 try self.output(self.context, bytes);1038 try output_func(self.context, bytes);
1052 }1039 }
1053 };1040 };
1054}1041}
src-self-hosted/introspect.zig+1-1
...@@ -14,7 +14,7 @@ pub fn testZigInstallPrefix(allocator: *mem.Allocator, test_path: []const u8) ![...@@ -14,7 +14,7 @@ pub fn testZigInstallPrefix(allocator: *mem.Allocator, test_path: []const u8) ![
14 const test_index_file = try fs.path.join(allocator, &[_][]const u8{ test_zig_dir, "std", "std.zig" });14 const test_index_file = try fs.path.join(allocator, &[_][]const u8{ test_zig_dir, "std", "std.zig" });
15 defer allocator.free(test_index_file);15 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);
18 file.close();18 file.close();
1919
20 return test_zig_dir;20 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...@@ -724,7 +724,7 @@ async fn fmtPath(fmt: *Fmt, file_path_ref: []const u8, check_mode: bool) FmtErro
724 if (try held.value.put(file_path, {})) |_| return;724 if (try held.value.put(file_path, {})) |_| return;
725 }725 }
726726
727 const source_code = event.fs.readFile(727 const source_code = fs.cwd().readFileAlloc(
728 fmt.allocator,728 fmt.allocator,
729 file_path,729 file_path,
730 max_src_size,730 max_src_size,
src/all_types.hpp+4
...@@ -2246,6 +2246,7 @@ struct CodeGen {...@@ -2246,6 +2246,7 @@ struct CodeGen {
2246 bool enable_dump_analysis;2246 bool enable_dump_analysis;
2247 bool enable_doc_generation;2247 bool enable_doc_generation;
2248 bool disable_bin_generation;2248 bool disable_bin_generation;
2249 bool test_is_evented;
2249 CodeModel code_model;2250 CodeModel code_model;
22502251
2251 Buf *mmacosx_version_min;2252 Buf *mmacosx_version_min;
...@@ -2491,6 +2492,9 @@ struct ScopeExpr {...@@ -2491,6 +2492,9 @@ struct ScopeExpr {
2491 size_t children_len;2492 size_t children_len;
24922493
2493 MemoizedBool need_spill;2494 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;
2494};2498};
24952499
2496// synchronized with code in define_builtin_compile_vars2500// synchronized with code in define_builtin_compile_vars
src/analyze.cpp+29-6
...@@ -6108,11 +6108,14 @@ static void mark_suspension_point(Scope *scope) {...@@ -6108,11 +6108,14 @@ static void mark_suspension_point(Scope *scope) {
6108 continue;6108 continue;
6109 }6109 }
6110 case ScopeIdExpr: {6110 case ScopeIdExpr: {
6111 ScopeExpr *parent_expr_scope = reinterpret_cast<ScopeExpr *>(scope);
6111 if (!looking_for_exprs) {6112 if (!looking_for_exprs) {
6113 if (parent_expr_scope->spill_harder) {
6114 parent_expr_scope->need_spill = MemoizedBoolTrue;
6115 }
6112 // Now we're only looking for a block, to see if it's in a loop (see the case ScopeIdBlock)6116 // Now we're only looking for a block, to see if it's in a loop (see the case ScopeIdBlock)
6113 continue;6117 continue;
6114 }6118 }
6115 ScopeExpr *parent_expr_scope = reinterpret_cast<ScopeExpr *>(scope);
6116 if (child_expr_scope != nullptr) {6119 if (child_expr_scope != nullptr) {
6117 for (size_t i = 0; parent_expr_scope->children_ptr[i] != child_expr_scope; i += 1) {6120 for (size_t i = 0; parent_expr_scope->children_ptr[i] != child_expr_scope; i += 1) {
6118 assert(i < parent_expr_scope->children_len);6121 assert(i < parent_expr_scope->children_len);
...@@ -6148,6 +6151,15 @@ static bool scope_needs_spill(Scope *scope) {...@@ -6148,6 +6151,15 @@ static bool scope_needs_spill(Scope *scope) {
6148 zig_unreachable();6151 zig_unreachable();
6149}6152}
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
6151static Error resolve_async_frame(CodeGen *g, ZigType *frame_type) {6163static Error resolve_async_frame(CodeGen *g, ZigType *frame_type) {
6152 Error err;6164 Error err;
61536165
...@@ -6249,6 +6261,9 @@ static Error resolve_async_frame(CodeGen *g, ZigType *frame_type) {...@@ -6249,6 +6261,9 @@ static Error resolve_async_frame(CodeGen *g, ZigType *frame_type) {
6249 }6261 }
6250 ZigFn *callee = call->fn_entry;6262 ZigFn *callee = call->fn_entry;
6251 if (callee == nullptr) {6263 if (callee == nullptr) {
6264 if (call->fn_ref->value->type->data.fn.fn_type_id.cc != CallingConventionAsync) {
6265 continue;
6266 }
6252 add_node_error(g, call->base.base.source_node,6267 add_node_error(g, call->base.base.source_node,
6253 buf_sprintf("function is not comptime-known; @asyncCall required"));6268 buf_sprintf("function is not comptime-known; @asyncCall required"));
6254 return ErrorSemanticAnalyzeFail;6269 return ErrorSemanticAnalyzeFail;
...@@ -6356,11 +6371,19 @@ static Error resolve_async_frame(CodeGen *g, ZigType *frame_type) {...@@ -6356,11 +6371,19 @@ static Error resolve_async_frame(CodeGen *g, ZigType *frame_type) {
6356 IrInstGen *instruction = block->instruction_list.at(instr_i);6371 IrInstGen *instruction = block->instruction_list.at(instr_i);
6357 if (instruction->id == IrInstGenIdAwait ||6372 if (instruction->id == IrInstGenIdAwait ||
6358 instruction->id == IrInstGenIdVarPtr ||6373 instruction->id == IrInstGenIdVarPtr ||
6359 instruction->id == IrInstGenIdAlloca)6374 instruction->id == IrInstGenIdAlloca ||
6375 instruction->id == IrInstGenIdSpillBegin ||
6376 instruction->id == IrInstGenIdSpillEnd)
6360 {6377 {
6361 // This instruction does its own spilling specially, or otherwise doesn't need it.6378 // This instruction does its own spilling specially, or otherwise doesn't need it.
6362 continue;6379 continue;
6363 }6380 }
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 }
6364 if (instruction->value->special != ConstValSpecialRuntime)6387 if (instruction->value->special != ConstValSpecialRuntime)
6365 continue;6388 continue;
6366 if (instruction->base.ref_count == 0)6389 if (instruction->base.ref_count == 0)
...@@ -6406,7 +6429,7 @@ static Error resolve_async_frame(CodeGen *g, ZigType *frame_type) {...@@ -6406,7 +6429,7 @@ static Error resolve_async_frame(CodeGen *g, ZigType *frame_type) {
6406 } else {6429 } else {
6407 param_name = buf_sprintf("@arg%" ZIG_PRI_usize, arg_i);6430 param_name = buf_sprintf("@arg%" ZIG_PRI_usize, arg_i);
6408 }6431 }
6409 ZigType *param_type = param_info->type;6432 ZigType *param_type = resolve_type_isf(param_info->type);
6410 if ((err = type_resolve(g, param_type, ResolveStatusSizeKnown))) {6433 if ((err = type_resolve(g, param_type, ResolveStatusSizeKnown))) {
6411 return err;6434 return err;
6412 }6435 }
...@@ -6425,7 +6448,7 @@ static Error resolve_async_frame(CodeGen *g, ZigType *frame_type) {...@@ -6425,7 +6448,7 @@ static Error resolve_async_frame(CodeGen *g, ZigType *frame_type) {
6425 instruction->field_index = SIZE_MAX;6448 instruction->field_index = SIZE_MAX;
6426 ZigType *ptr_type = instruction->base.value->type;6449 ZigType *ptr_type = instruction->base.value->type;
6427 assert(ptr_type->id == ZigTypeIdPointer);6450 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);
6429 if (!type_has_bits(child_type))6452 if (!type_has_bits(child_type))
6430 continue;6453 continue;
6431 if (instruction->base.base.ref_count == 0)6454 if (instruction->base.base.ref_count == 0)
...@@ -6452,8 +6475,6 @@ static Error resolve_async_frame(CodeGen *g, ZigType *frame_type) {...@@ -6452,8 +6475,6 @@ static Error resolve_async_frame(CodeGen *g, ZigType *frame_type) {
6452 }6475 }
6453 instruction->field_index = fields.length;6476 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);
6457 fields.append({name, child_type, instruction->align});6478 fields.append({name, child_type, instruction->align});
6458 }6479 }
64596480
...@@ -8255,6 +8276,8 @@ static void resolve_llvm_types_struct(CodeGen *g, ZigType *struct_type, ResolveS...@@ -8255,6 +8276,8 @@ static void resolve_llvm_types_struct(CodeGen *g, ZigType *struct_type, ResolveS
8255 size_t debug_field_index = 0;8276 size_t debug_field_index = 0;
8256 for (size_t i = 0; i < field_count; i += 1) {8277 for (size_t i = 0; i < field_count; i += 1) {
8257 TypeStructField *field = struct_type->data.structure.fields[i];8278 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
8258 size_t gen_field_index = field->gen_index;8281 size_t gen_field_index = field->gen_index;
8259 if (gen_field_index == SIZE_MAX) {8282 if (gen_field_index == SIZE_MAX) {
8260 continue;8283 continue;
src/codegen.cpp+147-54
...@@ -343,33 +343,67 @@ static LLVMLinkage to_llvm_linkage(GlobalLinkageId id) {...@@ -343,33 +343,67 @@ static LLVMLinkage to_llvm_linkage(GlobalLinkageId id) {
343 zig_unreachable();343 zig_unreachable();
344}344}
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
346// label (grep this): [fn_frame_struct_layout]367// 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
347static uint32_t frame_index_trace_arg(CodeGen *g, ZigType *return_type) {380static uint32_t frame_index_trace_arg(CodeGen *g, ZigType *return_type) {
348 // [0] *ReturnType (callee's)381 CalcLLVMFieldIndex calc = {0};
349 // [1] *ReturnType (awaiter's)382 frame_index_trace_arg_calc(g, &calc, return_type);
350 // [2] ReturnType383 return calc.field_index;
351 uint32_t return_field_count = type_has_bits(return_type) ? 3 : 0;
352 return frame_ret_start + return_field_count;
353}384}
354385
355// label (grep this): [fn_frame_struct_layout]386// label (grep this): [fn_frame_struct_layout]
356static uint32_t frame_index_arg(CodeGen *g, ZigType *return_type) {387static void frame_index_arg_calc(CodeGen *g, CalcLLVMFieldIndex *calc, ZigType *return_type) {
357 bool have_stack_trace = codegen_fn_has_err_ret_tracing_arg(g, return_type);388 frame_index_trace_arg_calc(g, calc, return_type);
358 // [0] *StackTrace (callee's)389
359 // [1] *StackTrace (awaiter's)390 if (codegen_fn_has_err_ret_tracing_arg(g, return_type)) {
360 uint32_t trace_field_count = have_stack_trace ? 2 : 0;391 calc_llvm_field_index_add(g, calc, g->builtin_types.entry_usize); // *StackTrace (callee's)
361 return frame_index_trace_arg(g, return_type) + trace_field_count;392 calc_llvm_field_index_add(g, calc, g->builtin_types.entry_usize); // *StackTrace (awaiter's)
393 }
362}394}
363395
364// label (grep this): [fn_frame_struct_layout]396// label (grep this): [fn_frame_struct_layout]
365static uint32_t frame_index_trace_stack(CodeGen *g, FnTypeId *fn_type_id) {397static uint32_t frame_index_trace_stack(CodeGen *g, ZigFn *fn) {
366 uint32_t result = frame_index_arg(g, fn_type_id->return_type);398 size_t field_index = 6;
367 for (size_t i = 0; i < fn_type_id->param_count; i += 1) {399 bool have_stack_trace = codegen_fn_has_err_ret_tracing_arg(g, fn->type_entry->data.fn.fn_type_id.return_type);
368 if (type_has_bits(fn_type_id->param_info->type)) {400 if (have_stack_trace) {
369 result += 1;401 field_index += 2;
370 }
371 }402 }
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;
373}407}
374408
375409
...@@ -2527,7 +2561,12 @@ static LLVMValueRef ir_render_return(CodeGen *g, IrExecutableGen *executable, Ir...@@ -2527,7 +2561,12 @@ static LLVMValueRef ir_render_return(CodeGen *g, IrExecutableGen *executable, Ir
2527 LLVMBuildRet(g->builder, by_val_value);2561 LLVMBuildRet(g->builder, by_val_value);
2528 }2562 }
2529 } else if (instruction->operand == nullptr) {2563 } 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 }
2531 } else {2570 } else {
2532 LLVMValueRef value = ir_llvm_value(g, instruction->operand);2571 LLVMValueRef value = ir_llvm_value(g, instruction->operand);
2533 LLVMBuildRet(g->builder, value);2572 LLVMBuildRet(g->builder, value);
...@@ -3920,7 +3959,9 @@ static void set_call_instr_sret(CodeGen *g, LLVMValueRef call_instr) {...@@ -3920,7 +3959,9 @@ static void set_call_instr_sret(CodeGen *g, LLVMValueRef call_instr) {
3920static void render_async_spills(CodeGen *g) {3959static void render_async_spills(CodeGen *g) {
3921 ZigType *fn_type = g->cur_fn->type_entry;3960 ZigType *fn_type = g->cur_fn->type_entry;
3922 ZigType *import = get_scope_import(&g->cur_fn->fndef_scope->base);3961 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);
3924 for (size_t var_i = 0; var_i < g->cur_fn->variable_list.length; var_i += 1) {3965 for (size_t var_i = 0; var_i < g->cur_fn->variable_list.length; var_i += 1) {
3925 ZigVar *var = g->cur_fn->variable_list.at(var_i);3966 ZigVar *var = g->cur_fn->variable_list.at(var_i);
39263967
...@@ -3941,8 +3982,8 @@ static void render_async_spills(CodeGen *g) {...@@ -3941,8 +3982,8 @@ static void render_async_spills(CodeGen *g) {
3941 continue;3982 continue;
3942 }3983 }
39433984
3944 var->value_ref = LLVMBuildStructGEP(g->builder, g->cur_frame_ptr, async_var_index, var->name);3985 calc_llvm_field_index_add(g, &arg_calc, var->var_type);
3945 async_var_index += 1;3986 var->value_ref = LLVMBuildStructGEP(g->builder, g->cur_frame_ptr, arg_calc.field_index - 1, var->name);
3946 if (var->decl_node) {3987 if (var->decl_node) {
3947 var->di_loc_var = ZigLLVMCreateAutoVariable(g->dbuilder, get_di_scope(g, var->parent_scope),3988 var->di_loc_var = ZigLLVMCreateAutoVariable(g->dbuilder, get_di_scope(g, var->parent_scope),
3948 var->name, import->data.structure.root_struct->di_file,3989 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...@@ -4023,6 +4064,8 @@ static void gen_init_stack_trace(CodeGen *g, LLVMValueRef trace_field_ptr, LLVMV
4023}4064}
40244065
4025static LLVMValueRef ir_render_call(CodeGen *g, IrExecutableGen *executable, IrInstGenCall *instruction) {4066static LLVMValueRef ir_render_call(CodeGen *g, IrExecutableGen *executable, IrInstGenCall *instruction) {
4067 Error err;
4068
4026 LLVMTypeRef usize_type_ref = g->builtin_types.entry_usize->llvm_type;4069 LLVMTypeRef usize_type_ref = g->builtin_types.entry_usize->llvm_type;
40274070
4028 LLVMValueRef fn_val;4071 LLVMValueRef fn_val;
...@@ -4053,6 +4096,8 @@ static LLVMValueRef ir_render_call(CodeGen *g, IrExecutableGen *executable, IrIn...@@ -4053,6 +4096,8 @@ static LLVMValueRef ir_render_call(CodeGen *g, IrExecutableGen *executable, IrIn
4053 ZigList<ZigType *> gen_param_types = {};4096 ZigList<ZigType *> gen_param_types = {};
4054 LLVMValueRef result_loc = instruction->result_loc ? ir_llvm_value(g, instruction->result_loc) : nullptr;4097 LLVMValueRef result_loc = instruction->result_loc ? ir_llvm_value(g, instruction->result_loc) : nullptr;
4055 LLVMValueRef zero = LLVMConstNull(usize_type_ref);4098 LLVMValueRef zero = LLVMConstNull(usize_type_ref);
4099 bool need_frame_ptr_ptr_spill = false;
4100 ZigType *anyframe_type = nullptr;
4056 LLVMValueRef frame_result_loc_uncasted = nullptr;4101 LLVMValueRef frame_result_loc_uncasted = nullptr;
4057 LLVMValueRef frame_result_loc;4102 LLVMValueRef frame_result_loc;
4058 LLVMValueRef awaiter_init_val;4103 LLVMValueRef awaiter_init_val;
...@@ -4091,14 +4136,17 @@ static LLVMValueRef ir_render_call(CodeGen *g, IrExecutableGen *executable, IrIn...@@ -4091,14 +4136,17 @@ static LLVMValueRef ir_render_call(CodeGen *g, IrExecutableGen *executable, IrIn
40914136
4092 LLVMPositionBuilderAtEnd(g->builder, ok_block);4137 LLVMPositionBuilderAtEnd(g->builder, ok_block);
4093 }4138 }
4139 need_frame_ptr_ptr_spill = true;
4094 LLVMValueRef frame_ptr_ptr = LLVMBuildStructGEP(g->builder, frame_slice_ptr, slice_ptr_index, "");4140 LLVMValueRef frame_ptr_ptr = LLVMBuildStructGEP(g->builder, frame_slice_ptr, slice_ptr_index, "");
4095 LLVMValueRef frame_ptr = LLVMBuildLoad(g->builder, frame_ptr_ptr, "");4141 LLVMValueRef frame_ptr = LLVMBuildLoad(g->builder, frame_ptr_ptr, "");
4096 if (instruction->fn_entry == nullptr) {4142 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);
4098 frame_result_loc = LLVMBuildBitCast(g->builder, frame_ptr, get_llvm_type(g, anyframe_type), "");4144 frame_result_loc = LLVMBuildBitCast(g->builder, frame_ptr, get_llvm_type(g, anyframe_type), "");
4099 } else {4145 } else {
4100 ZigType *ptr_frame_type = get_pointer_to_type(g,4146 ZigType *frame_type = get_fn_frame_type(g, instruction->fn_entry);
4101 get_fn_frame_type(g, instruction->fn_entry), false);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);
4102 frame_result_loc = LLVMBuildBitCast(g->builder, frame_ptr,4150 frame_result_loc = LLVMBuildBitCast(g->builder, frame_ptr,
4103 get_llvm_type(g, ptr_frame_type), "");4151 get_llvm_type(g, ptr_frame_type), "");
4104 }4152 }
...@@ -4265,17 +4313,35 @@ static LLVMValueRef ir_render_call(CodeGen *g, IrExecutableGen *executable, IrIn...@@ -4265,17 +4313,35 @@ static LLVMValueRef ir_render_call(CodeGen *g, IrExecutableGen *executable, IrIn
4265 LLVMValueRef result;4313 LLVMValueRef result;
42664314
4267 if (callee_is_async) {4315 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
4270 LLVMValueRef casted_frame;4319 LLVMValueRef casted_frame;
4271 if (instruction->new_stack != nullptr && instruction->fn_entry == nullptr) {4320 if (instruction->new_stack != nullptr && instruction->fn_entry == nullptr) {
4272 // We need the frame type to be a pointer to a struct that includes the args4321 // 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
4274 LLVMTypeRef *field_types = allocate_nonzero<LLVMTypeRef>(field_count);4330 LLVMTypeRef *field_types = allocate_nonzero<LLVMTypeRef>(field_count);
4275 LLVMGetStructElementTypes(LLVMGetElementType(LLVMTypeOf(frame_result_loc)), field_types);4331 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;
4277 for (size_t arg_i = 0; arg_i < gen_param_values.length; arg_i += 1) {4335 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 }
4279 }4345 }
4280 LLVMTypeRef frame_with_args_type = LLVMStructType(field_types, field_count, false);4346 LLVMTypeRef frame_with_args_type = LLVMStructType(field_types, field_count, false);
4281 LLVMTypeRef ptr_frame_with_args_type = LLVMPointerType(frame_with_args_type, 0);4347 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...@@ -4285,8 +4351,10 @@ static LLVMValueRef ir_render_call(CodeGen *g, IrExecutableGen *executable, IrIn
4285 casted_frame = frame_result_loc;4351 casted_frame = frame_result_loc;
4286 }4352 }
42874353
4354 CalcLLVMFieldIndex arg_calc = arg_calc_start;
4288 for (size_t arg_i = 0; arg_i < gen_param_values.length; arg_i += 1) {4355 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, "");
4290 gen_assign_raw(g, arg_ptr, get_pointer_to_type(g, gen_param_types.at(arg_i), true),4358 gen_assign_raw(g, arg_ptr, get_pointer_to_type(g, gen_param_types.at(arg_i), true),
4291 gen_param_values.at(arg_i));4359 gen_param_values.at(arg_i));
4292 }4360 }
...@@ -4349,11 +4417,19 @@ static LLVMValueRef ir_render_call(CodeGen *g, IrExecutableGen *executable, IrIn...@@ -4349,11 +4417,19 @@ static LLVMValueRef ir_render_call(CodeGen *g, IrExecutableGen *executable, IrIn
4349 }4417 }
4350 }4418 }
43514419
4352 if (frame_result_loc_uncasted != nullptr && instruction->fn_entry != nullptr) {4420 if (need_frame_ptr_ptr_spill) {
4353 // Instead of a spill, we do the bitcast again. The uncasted LLVM IR instruction will4421 LLVMValueRef frame_slice_ptr = ir_llvm_value(g, instruction->new_stack);
4354 // be an Alloca from the entry block, so it does not need to be spilled.4422 LLVMValueRef frame_ptr_ptr = LLVMBuildStructGEP(g->builder, frame_slice_ptr, slice_ptr_index, "");
4355 frame_result_loc = LLVMBuildBitCast(g->builder, frame_result_loc_uncasted,4423 frame_result_loc_uncasted = LLVMBuildLoad(g->builder, frame_ptr_ptr, "");
4356 LLVMPointerType(get_llvm_type(g, instruction->fn_entry->frame_type), 0), "");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 }
4357 }4433 }
43584434
4359 LLVMValueRef result_ptr = LLVMBuildStructGEP(g->builder, frame_result_loc, frame_ret_start + 2, "");4435 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...@@ -5644,18 +5720,24 @@ static LLVMValueRef ir_render_unwrap_err_payload(CodeGen *g, IrExecutableGen *ex
5644 bool want_safety = instruction->safety_check_on && ir_want_runtime_safety(g, &instruction->base) &&5720 bool want_safety = instruction->safety_check_on && ir_want_runtime_safety(g, &instruction->base) &&
5645 g->errors_by_index.length > 1;5721 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
5654 ZigType *ptr_type = instruction->value->value->type;5723 ZigType *ptr_type = instruction->value->value->type;
5655 assert(ptr_type->id == ZigTypeIdPointer);5724 assert(ptr_type->id == ZigTypeIdPointer);
5656 ZigType *err_union_type = ptr_type->data.pointer.child_type;5725 ZigType *err_union_type = ptr_type->data.pointer.child_type;
5657 ZigType *payload_type = err_union_type->data.error_union.payload_type;5726 ZigType *payload_type = err_union_type->data.error_union.payload_type;
5658 LLVMValueRef err_union_ptr = ir_llvm_value(g, instruction->value);5727 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
5659 LLVMValueRef err_union_handle = get_handle_value(g, err_union_ptr, err_union_type, ptr_type);5741 LLVMValueRef err_union_handle = get_handle_value(g, err_union_ptr, err_union_type, ptr_type);
56605742
5661 if (!type_has_bits(err_union_type->data.error_union.err_set_type)) {5743 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...@@ -5670,7 +5752,6 @@ static LLVMValueRef ir_render_unwrap_err_payload(CodeGen *g, IrExecutableGen *ex
5670 } else {5752 } else {
5671 err_val = err_union_handle;5753 err_val = err_union_handle;
5672 }5754 }
5673 LLVMValueRef zero = LLVMConstNull(get_llvm_type(g, g->err_tag_type));
5674 LLVMValueRef cond_val = LLVMBuildICmp(g->builder, LLVMIntEQ, err_val, zero, "");5755 LLVMValueRef cond_val = LLVMBuildICmp(g->builder, LLVMIntEQ, err_val, zero, "");
5675 LLVMBasicBlockRef err_block = LLVMAppendBasicBlock(g->cur_fn_val, "UnwrapErrError");5756 LLVMBasicBlockRef err_block = LLVMAppendBasicBlock(g->cur_fn_val, "UnwrapErrError");
5676 LLVMBasicBlockRef ok_block = LLVMAppendBasicBlock(g->cur_fn_val, "UnwrapErrOk");5757 LLVMBasicBlockRef ok_block = LLVMAppendBasicBlock(g->cur_fn_val, "UnwrapErrOk");
...@@ -5690,6 +5771,9 @@ static LLVMValueRef ir_render_unwrap_err_payload(CodeGen *g, IrExecutableGen *ex...@@ -5690,6 +5771,9 @@ static LLVMValueRef ir_render_unwrap_err_payload(CodeGen *g, IrExecutableGen *ex
5690 }5771 }
5691 return LLVMBuildStructGEP(g->builder, err_union_handle, err_union_payload_index, "");5772 return LLVMBuildStructGEP(g->builder, err_union_handle, err_union_payload_index, "");
5692 } else {5773 } else {
5774 if (instruction->initializing) {
5775 gen_store_untyped(g, zero, err_union_ptr, 0, false);
5776 }
5693 return nullptr;5777 return nullptr;
5694 }5778 }
5695}5779}
...@@ -7742,7 +7826,7 @@ static void do_code_gen(CodeGen *g) {...@@ -7742,7 +7826,7 @@ static void do_code_gen(CodeGen *g) {
7742 }7826 }
7743 uint32_t trace_field_index_stack = UINT32_MAX;7827 uint32_t trace_field_index_stack = UINT32_MAX;
7744 if (codegen_fn_has_err_ret_tracing_stack(g, fn_table_entry, true)) {7828 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);
7746 g->cur_err_ret_trace_val_stack = LLVMBuildStructGEP(g->builder, g->cur_frame_ptr,7830 g->cur_err_ret_trace_val_stack = LLVMBuildStructGEP(g->builder, g->cur_frame_ptr,
7747 trace_field_index_stack, "");7831 trace_field_index_stack, "");
7748 }7832 }
...@@ -8602,6 +8686,9 @@ Buf *codegen_generate_builtin_source(CodeGen *g) {...@@ -8602,6 +8686,9 @@ Buf *codegen_generate_builtin_source(CodeGen *g) {
8602 buf_appendf(contents,8686 buf_appendf(contents,
8603 "pub var test_functions: []TestFn = undefined; // overwritten later\n"8687 "pub var test_functions: []TestFn = undefined; // overwritten later\n"
8604 );8688 );
8689
8690 buf_appendf(contents, "pub const test_io_mode = %s;\n",
8691 g->test_is_evented ? ".evented" : ".blocking");
8605 }8692 }
86068693
8607 return contents;8694 return contents;
...@@ -8635,6 +8722,7 @@ static Error define_builtin_compile_vars(CodeGen *g) {...@@ -8635,6 +8722,7 @@ static Error define_builtin_compile_vars(CodeGen *g) {
8635 cache_bool(&cache_hash, g->is_dynamic);8722 cache_bool(&cache_hash, g->is_dynamic);
8636 cache_bool(&cache_hash, g->is_test_build);8723 cache_bool(&cache_hash, g->is_test_build);
8637 cache_bool(&cache_hash, g->is_single_threaded);8724 cache_bool(&cache_hash, g->is_single_threaded);
8725 cache_bool(&cache_hash, g->test_is_evented);
8638 cache_int(&cache_hash, g->code_model);8726 cache_int(&cache_hash, g->code_model);
8639 cache_int(&cache_hash, g->zig_target->is_native);8727 cache_int(&cache_hash, g->zig_target->is_native);
8640 cache_int(&cache_hash, g->zig_target->arch);8728 cache_int(&cache_hash, g->zig_target->arch);
...@@ -9392,22 +9480,13 @@ static void update_test_functions_builtin_decl(CodeGen *g) {...@@ -9392,22 +9480,13 @@ static void update_test_functions_builtin_decl(CodeGen *g) {
9392 for (size_t i = 0; i < g->test_fns.length; i += 1) {9480 for (size_t i = 0; i < g->test_fns.length; i += 1) {
9393 ZigFn *test_fn_entry = g->test_fns.at(i);9481 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
9404 ZigValue *this_val = &test_fn_array->data.x_array.data.s_none.elements[i];9483 ZigValue *this_val = &test_fn_array->data.x_array.data.s_none.elements[i];
9405 this_val->special = ConstValSpecialStatic;9484 this_val->special = ConstValSpecialStatic;
9406 this_val->type = struct_type;9485 this_val->type = struct_type;
9407 this_val->parent.id = ConstParentIdArray;9486 this_val->parent.id = ConstParentIdArray;
9408 this_val->parent.data.p_array.array_val = test_fn_array;9487 this_val->parent.data.p_array.array_val = test_fn_array;
9409 this_val->parent.data.p_array.elem_index = i;9488 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
9412 ZigValue *name_field = this_val->data.x_struct.fields[0];9491 ZigValue *name_field = this_val->data.x_struct.fields[0];
9413 ZigValue *name_array_val = create_const_str_lit(g, &test_fn_entry->symbol_name)->data.x_ptr.data.ref.pointee;9492 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) {...@@ -9419,6 +9498,19 @@ static void update_test_functions_builtin_decl(CodeGen *g) {
9419 fn_field->data.x_ptr.special = ConstPtrSpecialFunction;9498 fn_field->data.x_ptr.special = ConstPtrSpecialFunction;
9420 fn_field->data.x_ptr.mut = ConstPtrMutComptimeConst;9499 fn_field->data.x_ptr.mut = ConstPtrMutComptimeConst;
9421 fn_field->data.x_ptr.data.fn.fn_entry = test_fn_entry;9500 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 }
9422 }9514 }
9423 report_errors_and_maybe_exit(g);9515 report_errors_and_maybe_exit(g);
94249516
...@@ -10350,6 +10442,7 @@ static Error check_cache(CodeGen *g, Buf *manifest_dir, Buf *digest) {...@@ -10350,6 +10442,7 @@ static Error check_cache(CodeGen *g, Buf *manifest_dir, Buf *digest) {
10350 if (g->is_test_build) {10442 if (g->is_test_build) {
10351 cache_buf_opt(ch, g->test_filter);10443 cache_buf_opt(ch, g->test_filter);
10352 cache_buf_opt(ch, g->test_name_prefix);10444 cache_buf_opt(ch, g->test_name_prefix);
10445 cache_bool(ch, g->test_is_evented);
10353 }10446 }
10354 cache_bool(ch, g->link_eh_frame_hdr);10447 cache_bool(ch, g->link_eh_frame_hdr);
10355 cache_bool(ch, g->is_single_threaded);10448 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,...@@ -5252,6 +5252,7 @@ static IrInstSrc *ir_gen_return(IrBuilderSrc *irb, Scope *scope, AstNode *node,
5252 return irb->codegen->invalid_inst_src;5252 return irb->codegen->invalid_inst_src;
5253 } else {5253 } else {
5254 return_value = ir_build_const_void(irb, scope, node);5254 return_value = ir_build_const_void(irb, scope, node);
5255 ir_build_end_expr(irb, scope, node, return_value, &result_loc_ret->base);
5255 }5256 }
52565257
5257 ir_mark_gen(ir_build_add_implicit_return_type(irb, scope, node, return_value, result_loc_ret));5258 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,...@@ -5262,7 +5263,7 @@ static IrInstSrc *ir_gen_return(IrBuilderSrc *irb, Scope *scope, AstNode *node,
5262 if (!have_err_defers && !irb->codegen->have_err_ret_tracing) {5263 if (!have_err_defers && !irb->codegen->have_err_ret_tracing) {
5263 // only generate unconditional defers5264 // only generate unconditional defers
5264 ir_gen_defers_for_block(irb, scope, outer_scope, false);5265 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);
5266 result_loc_ret->base.source_instruction = result;5267 result_loc_ret->base.source_instruction = result;
5267 return result;5268 return result;
5268 }5269 }
...@@ -5271,10 +5272,6 @@ static IrInstSrc *ir_gen_return(IrBuilderSrc *irb, Scope *scope, AstNode *node,...@@ -5271,10 +5272,6 @@ static IrInstSrc *ir_gen_return(IrBuilderSrc *irb, Scope *scope, AstNode *node,
5271 IrBasicBlockSrc *err_block = ir_create_basic_block(irb, scope, "ErrRetErr");5272 IrBasicBlockSrc *err_block = ir_create_basic_block(irb, scope, "ErrRetErr");
5272 IrBasicBlockSrc *ok_block = ir_create_basic_block(irb, scope, "ErrRetOk");5273 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
5278 IrInstSrc *is_err = ir_build_test_err_src(irb, scope, node, return_value, false, true);5275 IrInstSrc *is_err = ir_build_test_err_src(irb, scope, node, return_value, false, true);
52795276
5280 IrInstSrc *is_comptime;5277 IrInstSrc *is_comptime;
...@@ -5288,22 +5285,18 @@ static IrInstSrc *ir_gen_return(IrBuilderSrc *irb, Scope *scope, AstNode *node,...@@ -5288,22 +5285,18 @@ static IrInstSrc *ir_gen_return(IrBuilderSrc *irb, Scope *scope, AstNode *node,
5288 IrBasicBlockSrc *ret_stmt_block = ir_create_basic_block(irb, scope, "RetStmt");5285 IrBasicBlockSrc *ret_stmt_block = ir_create_basic_block(irb, scope, "RetStmt");
52895286
5290 ir_set_cursor_at_end_and_append_block(irb, err_block);5287 ir_set_cursor_at_end_and_append_block(irb, err_block);
5291 if (have_err_defers) {5288 ir_gen_defers_for_block(irb, scope, outer_scope, true);
5292 ir_gen_defers_for_block(irb, scope, outer_scope, true);
5293 }
5294 if (irb->codegen->have_err_ret_tracing && !should_inline) {5289 if (irb->codegen->have_err_ret_tracing && !should_inline) {
5295 ir_build_save_err_ret_addr_src(irb, scope, node);5290 ir_build_save_err_ret_addr_src(irb, scope, node);
5296 }5291 }
5297 ir_build_br(irb, scope, node, ret_stmt_block, is_comptime);5292 ir_build_br(irb, scope, node, ret_stmt_block, is_comptime);
52985293
5299 ir_set_cursor_at_end_and_append_block(irb, ok_block);5294 ir_set_cursor_at_end_and_append_block(irb, ok_block);
5300 if (have_err_defers) {5295 ir_gen_defers_for_block(irb, scope, outer_scope, false);
5301 ir_gen_defers_for_block(irb, scope, outer_scope, false);
5302 }
5303 ir_build_br(irb, scope, node, ret_stmt_block, is_comptime);5296 ir_build_br(irb, scope, node, ret_stmt_block, is_comptime);
53045297
5305 ir_set_cursor_at_end_and_append_block(irb, ret_stmt_block);5298 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);
5307 result_loc_ret->base.source_instruction = result;5300 result_loc_ret->base.source_instruction = result;
5308 return result;5301 return result;
5309 }5302 }
...@@ -8874,7 +8867,10 @@ static IrInstSrc *ir_gen_if_optional_expr(IrBuilderSrc *irb, Scope *scope, AstNo...@@ -8874,7 +8867,10 @@ static IrInstSrc *ir_gen_if_optional_expr(IrBuilderSrc *irb, Scope *scope, AstNo
8874 AstNode *else_node = node->data.test_expr.else_node;8867 AstNode *else_node = node->data.test_expr.else_node;
8875 bool var_is_ptr = node->data.test_expr.var_is_ptr;8868 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);
8878 if (maybe_val_ptr == irb->codegen->invalid_inst_src)8874 if (maybe_val_ptr == irb->codegen->invalid_inst_src)
8879 return maybe_val_ptr;8875 return maybe_val_ptr;
88808876
...@@ -8899,7 +8895,7 @@ static IrInstSrc *ir_gen_if_optional_expr(IrBuilderSrc *irb, Scope *scope, AstNo...@@ -8899,7 +8895,7 @@ static IrInstSrc *ir_gen_if_optional_expr(IrBuilderSrc *irb, Scope *scope, AstNo
88998895
8900 ir_set_cursor_at_end_and_append_block(irb, then_block);8896 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);
8903 Scope *var_scope;8899 Scope *var_scope;
8904 if (var_symbol) {8900 if (var_symbol) {
8905 bool is_shadowable = false;8901 bool is_shadowable = false;
...@@ -9619,7 +9615,10 @@ static IrInstSrc *ir_gen_catch(IrBuilderSrc *irb, Scope *parent_scope, AstNode *...@@ -9619,7 +9615,10 @@ static IrInstSrc *ir_gen_catch(IrBuilderSrc *irb, Scope *parent_scope, AstNode *
9619 }9615 }
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);
9623 if (err_union_ptr == irb->codegen->invalid_inst_src)9622 if (err_union_ptr == irb->codegen->invalid_inst_src)
9624 return irb->codegen->invalid_inst_src;9623 return irb->codegen->invalid_inst_src;
96259624
...@@ -9641,7 +9640,7 @@ static IrInstSrc *ir_gen_catch(IrBuilderSrc *irb, Scope *parent_scope, AstNode *...@@ -9641,7 +9640,7 @@ static IrInstSrc *ir_gen_catch(IrBuilderSrc *irb, Scope *parent_scope, AstNode *
9641 is_comptime);9640 is_comptime);
96429641
9643 ir_set_cursor_at_end_and_append_block(irb, err_block);9642 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);
9645 Scope *err_scope;9644 Scope *err_scope;
9646 if (var_node) {9645 if (var_node) {
9647 assert(var_node->type == NodeTypeSymbol);9646 assert(var_node->type == NodeTypeSymbol);
...@@ -15494,6 +15493,12 @@ static IrInstGen *ir_analyze_instruction_add_implicit_return_type(IrAnalyze *ira...@@ -15494,6 +15493,12 @@ static IrInstGen *ir_analyze_instruction_add_implicit_return_type(IrAnalyze *ira
15494}15493}
1549515494
15496static IrInstGen *ir_analyze_instruction_return(IrAnalyze *ira, IrInstSrcReturn *instruction) {15495static 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
15497 IrInstGen *operand = instruction->operand->child;15502 IrInstGen *operand = instruction->operand->child;
15498 if (type_is_invalid(operand->value->type))15503 if (type_is_invalid(operand->value->type))
15499 return ir_unreach_error(ira);15504 return ir_unreach_error(ira);
...@@ -19586,6 +19591,12 @@ static IrInstGen *ir_analyze_fn_call(IrAnalyze *ira, IrInst* source_instr,...@@ -19586,6 +19591,12 @@ static IrInstGen *ir_analyze_fn_call(IrAnalyze *ira, IrInst* source_instr,
19586 if (type_is_invalid(result_loc->value->type) || result_loc->value->type->id == ZigTypeIdUnreachable) {19591 if (type_is_invalid(result_loc->value->type) || result_loc->value->type->id == ZigTypeIdUnreachable) {
19587 return result_loc;19592 return result_loc;
19588 }19593 }
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;
19589 ZigType *res_child_type = result_loc->value->type->data.pointer.child_type;19600 ZigType *res_child_type = result_loc->value->type->data.pointer.child_type;
19590 if (res_child_type == ira->codegen->builtin_types.entry_var) {19601 if (res_child_type == ira->codegen->builtin_types.entry_var) {
19591 res_child_type = impl_fn_type_id->return_type;19602 res_child_type = impl_fn_type_id->return_type;
...@@ -19718,6 +19729,12 @@ static IrInstGen *ir_analyze_fn_call(IrAnalyze *ira, IrInst* source_instr,...@@ -19718,6 +19729,12 @@ static IrInstGen *ir_analyze_fn_call(IrAnalyze *ira, IrInst* source_instr,
19718 if (type_is_invalid(result_loc->value->type) || result_loc->value->type->id == ZigTypeIdUnreachable) {19729 if (type_is_invalid(result_loc->value->type) || result_loc->value->type->id == ZigTypeIdUnreachable) {
19719 return result_loc;19730 return result_loc;
19720 }19731 }
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;
19721 ZigType *res_child_type = result_loc->value->type->data.pointer.child_type;19738 ZigType *res_child_type = result_loc->value->type->data.pointer.child_type;
19722 if (res_child_type == ira->codegen->builtin_types.entry_var) {19739 if (res_child_type == ira->codegen->builtin_types.entry_var) {
19723 res_child_type = return_type;19740 res_child_type = return_type;
...@@ -29548,8 +29565,13 @@ static IrInstGen *ir_analyze_instruction_spill_begin(IrAnalyze *ira, IrInstSrcSp...@@ -29548,8 +29565,13 @@ static IrInstGen *ir_analyze_instruction_spill_begin(IrAnalyze *ira, IrInstSrcSp
29548 if (!type_has_bits(operand->value->type))29565 if (!type_has_bits(operand->value->type))
29549 return ir_const_void(ira, &instruction->base.base);29566 return ir_const_void(ira, &instruction->base.base);
2955029567
29551 ir_assert(instruction->spill_id == SpillIdRetErrCode, &instruction->base.base);29568 switch (instruction->spill_id) {
29552 ira->new_irb.exec->need_err_code_spill = true;29569 case SpillIdInvalid:
29570 zig_unreachable();
29571 case SpillIdRetErrCode:
29572 ira->new_irb.exec->need_err_code_spill = true;
29573 break;
29574 }
2955329575
29554 return ir_build_spill_begin_gen(ira, &instruction->base.base, operand, instruction->spill_id);29576 return ir_build_spill_begin_gen(ira, &instruction->base.base, operand, instruction->spill_id);
29555}29577}
...@@ -29559,8 +29581,12 @@ static IrInstGen *ir_analyze_instruction_spill_end(IrAnalyze *ira, IrInstSrcSpil...@@ -29559,8 +29581,12 @@ static IrInstGen *ir_analyze_instruction_spill_end(IrAnalyze *ira, IrInstSrcSpil
29559 if (type_is_invalid(operand->value->type))29581 if (type_is_invalid(operand->value->type))
29560 return ira->codegen->invalid_inst_gen;29582 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 {
29563 return operand;29588 return operand;
29589 }
2956429590
29565 ir_assert(instruction->begin->base.child->id == IrInstGenIdSpillBegin, &instruction->base.base);29591 ir_assert(instruction->begin->base.child->id == IrInstGenIdSpillBegin, &instruction->base.base);
29566 IrInstGenSpillBegin *begin = reinterpret_cast<IrInstGenSpillBegin *>(instruction->begin->base.child);29592 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) {...@@ -135,6 +135,7 @@ static int print_full_usage(const char *arg0, FILE *file, int return_code) {
135 " --test-name-prefix [text] add prefix to all tests\n"135 " --test-name-prefix [text] add prefix to all tests\n"
136 " --test-cmd [arg] specify test execution command one arg at a time\n"136 " --test-cmd [arg] specify test execution command one arg at a time\n"
137 " --test-cmd-bin appends test binary path to test cmd args\n"137 " --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"
138 , arg0);139 , arg0);
139 return return_code;140 return return_code;
140}141}
...@@ -428,6 +429,7 @@ int main(int argc, char **argv) {...@@ -428,6 +429,7 @@ int main(int argc, char **argv) {
428 ZigList<CFile *> c_source_files = {0};429 ZigList<CFile *> c_source_files = {0};
429 const char *test_filter = nullptr;430 const char *test_filter = nullptr;
430 const char *test_name_prefix = nullptr;431 const char *test_name_prefix = nullptr;
432 bool test_evented_io = false;
431 size_t ver_major = 0;433 size_t ver_major = 0;
432 size_t ver_minor = 0;434 size_t ver_minor = 0;
433 size_t ver_patch = 0;435 size_t ver_patch = 0;
...@@ -709,6 +711,8 @@ int main(int argc, char **argv) {...@@ -709,6 +711,8 @@ int main(int argc, char **argv) {
709 cur_pkg = cur_pkg->parent;711 cur_pkg = cur_pkg->parent;
710 } else if (strcmp(arg, "-ffunction-sections") == 0) {712 } else if (strcmp(arg, "-ffunction-sections") == 0) {
711 function_sections = true;713 function_sections = true;
714 } else if (strcmp(arg, "--test-evented-io") == 0) {
715 test_evented_io = true;
712 } else if (i + 1 >= argc) {716 } else if (i + 1 >= argc) {
713 fprintf(stderr, "Expected another argument after %s\n", arg);717 fprintf(stderr, "Expected another argument after %s\n", arg);
714 return print_error_usage(arg0);718 return print_error_usage(arg0);
...@@ -1059,6 +1063,7 @@ int main(int argc, char **argv) {...@@ -1059,6 +1063,7 @@ int main(int argc, char **argv) {
1059 g->want_stack_check = want_stack_check;1063 g->want_stack_check = want_stack_check;
1060 g->want_sanitize_c = want_sanitize_c;1064 g->want_sanitize_c = want_sanitize_c;
1061 g->want_single_threaded = want_single_threaded;1065 g->want_single_threaded = want_single_threaded;
1066 g->test_is_evented = test_evented_io;
1062 Buf *builtin_source = codegen_generate_builtin_source(g);1067 Buf *builtin_source = codegen_generate_builtin_source(g);
1063 if (fwrite(buf_ptr(builtin_source), 1, buf_len(builtin_source), stdout) != buf_len(builtin_source)) {1068 if (fwrite(buf_ptr(builtin_source), 1, buf_len(builtin_source), stdout) != buf_len(builtin_source)) {
1064 fprintf(stderr, "unable to write to stdout: %s\n", strerror(ferror(stdout)));1069 fprintf(stderr, "unable to write to stdout: %s\n", strerror(ferror(stdout)));
...@@ -1232,6 +1237,7 @@ int main(int argc, char **argv) {...@@ -1232,6 +1237,7 @@ int main(int argc, char **argv) {
1232 if (test_filter) {1237 if (test_filter) {
1233 codegen_set_test_filter(g, buf_create_from_str(test_filter));1238 codegen_set_test_filter(g, buf_create_from_str(test_filter));
1234 }1239 }
1240 g->test_is_evented = test_evented_io;
12351241
1236 if (test_name_prefix) {1242 if (test_name_prefix) {
1237 codegen_set_test_name_prefix(g, buf_create_from_str(test_name_prefix));1243 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 {...@@ -20,6 +20,30 @@ pub fn addCases(cases: *tests.CompileErrorContext) void {
20 "tmp.zig:1:20: error: dependency loop detected",20 "tmp.zig:1:20: error: dependency loop detected",
21 });21 });
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
23 cases.addTest("non-exhaustive enums",47 cases.addTest("non-exhaustive enums",
24 \\const A = enum {48 \\const A = enum {
25 \\ a,49 \\ a,
...@@ -5279,25 +5303,6 @@ pub fn addCases(cases: *tests.CompileErrorContext) void {...@@ -5279,25 +5303,6 @@ pub fn addCases(cases: *tests.CompileErrorContext) void {
5279 "tmp.zig:2:30: error: cannot set section of local variable 'foo'",5303 "tmp.zig:2:30: error: cannot set section of local variable 'foo'",
5280 });5304 });
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
5301 cases.add("inner struct member shadowing outer struct member",5306 cases.add("inner struct member shadowing outer struct member",
5302 \\fn A() type {5307 \\fn A() type {
5303 \\ return struct {5308 \\ return struct {
test/stage1/behavior/async_fn.zig+152
...@@ -2,6 +2,7 @@ const std = @import("std");...@@ -2,6 +2,7 @@ const std = @import("std");
2const builtin = @import("builtin");2const builtin = @import("builtin");
3const expect = std.testing.expect;3const expect = std.testing.expect;
4const expectEqual = std.testing.expectEqual;4const expectEqual = std.testing.expectEqual;
5const expectError = std.testing.expectError;
56
6var global_x: i32 = 1;7var global_x: i32 = 1;
78
...@@ -1329,3 +1330,154 @@ test "async call with @call" {...@@ -1329,3 +1330,154 @@ test "async call with @call" {
1329 };1330 };
1330 S.doTheTest();1331 S.doTheTest();
1331}1332}
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}