| author | |
| committer | |
| log | 014f66e6de4aaf81f32c796b12f981326a479397 |
| tree | 6ed4a6e6776b160fcfdc53c0a2afa5722d7c9237 |
| parent | 3b622f4494b8fd899abc20e75e46726344f2d20c |
| parent | 27575d19c805e166d8393e3c586613256eb0c6e3 |
a big step towards std lib integration with async I/O43 files changed, 2202 insertions(+), 1845 deletions(-)
build.zig+3-4| ... | ... | @@ -72,14 +72,13 @@ pub fn build(b: *Builder) !void { |
| 72 | 72 | const skip_release_safe = b.option(bool, "skip-release-safe", "Main test suite skips release-safe builds") orelse skip_release; |
| 73 | 73 | const skip_non_native = b.option(bool, "skip-non-native", "Main test suite skips non-native builds") orelse false; |
| 74 | 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; | |
| 76 | if (!skip_self_hosted and builtin.os == .linux) { | |
| 77 | // TODO evented I/O other OS's | |
| 75 | const skip_self_hosted = (b.option(bool, "skip-self-hosted", "Main test suite skips building self hosted compiler") orelse false) or true; // TODO evented I/O good enough that this passes everywhere | |
| 76 | if (!skip_self_hosted) { | |
| 78 | 77 | test_step.dependOn(&exe.step); |
| 79 | 78 | } |
| 80 | 79 | |
| 81 | 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 | 82 | b.default_step.dependOn(&exe.step); |
| 84 | 83 | exe.install(); |
| 85 | 84 | } |
doc/docgen.zig+2-2| ... | ... | @@ -34,10 +34,10 @@ pub fn main() !void { |
| 34 | 34 | const out_file_name = try (args_it.next(allocator) orelse @panic("expected output arg")); |
| 35 | 35 | defer allocator.free(out_file_name); |
| 36 | 36 | |
| 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 | 38 | defer in_file.close(); |
| 39 | 39 | |
| 40 | var out_file = try fs.File.openWrite(out_file_name); | |
| 40 | var out_file = try fs.cwd().createFile(out_file_name, .{}); | |
| 41 | 41 | defer out_file.close(); |
| 42 | 42 | |
| 43 | 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 | 113 | |
| 114 | 114 | pub fn dumpToStream(self: *Self, comptime Error: type, stream: *std.io.OutStream(Error)) Error!void { |
| 115 | 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 | 122 | try s.writeByteNTimes(' ', indent); |
| 118 | 123 | if (optional_node) |node| { |
| 119 | 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 | 130 | } else { |
| 122 | 131 | try s.print("(null)\n", .{}); |
| 123 | 132 | } |
| ... | ... | @@ -127,9 +136,9 @@ pub fn Queue(comptime T: type) type { |
| 127 | 136 | defer held.release(); |
| 128 | 137 | |
| 129 | 138 | try stream.print("head: ", .{}); |
| 130 | try S.dumpRecursive(stream, self.head, 0); | |
| 139 | try S.dumpRecursive(stream, self.head, 0, 4); | |
| 131 | 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 | 458 | pub const TestFn = struct { |
| 459 | 459 | name: []const u8, |
| 460 | 460 | func: fn () anyerror!void, |
| 461 | async_frame_size: ?usize, | |
| 461 | 462 | }; |
| 462 | 463 | |
| 463 | 464 | /// 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 | 121 | pub extern "c" fn sysctlnametomib(name: [*:0]const u8, mibp: ?*c_int, sizep: ?*usize) c_int; |
| 122 | 122 | pub extern "c" fn tcgetattr(fd: fd_t, termios_p: *termios) c_int; |
| 123 | 123 | pub extern "c" fn tcsetattr(fd: fd_t, optional_action: TCSA, termios_p: *const termios) c_int; |
| 124 | pub extern "c" fn fcntl(fd: fd_t, cmd: c_int, ...) c_int; | |
| 124 | 125 | |
| 125 | 126 | pub extern "c" fn gethostname(name: [*]u8, len: usize) c_int; |
| 126 | 127 | pub 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 | 329 | } |
| 330 | 330 | |
| 331 | 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 | 334 | errdefer if (self.stdin_behavior == StdIo.Pipe) { |
| 334 | 335 | destroyPipe(stdin_pipe); |
| 335 | 336 | }; |
| 336 | 337 | |
| 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 | 339 | errdefer if (self.stdout_behavior == StdIo.Pipe) { |
| 339 | 340 | destroyPipe(stdout_pipe); |
| 340 | 341 | }; |
| 341 | 342 | |
| 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 | 344 | errdefer if (self.stderr_behavior == StdIo.Pipe) { |
| 344 | 345 | destroyPipe(stderr_pipe); |
| 345 | 346 | }; |
| ... | ... | @@ -426,17 +427,26 @@ pub const ChildProcess = struct { |
| 426 | 427 | // we are the parent |
| 427 | 428 | const pid = @intCast(i32, pid_result); |
| 428 | 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 | 434 | } else { |
| 431 | 435 | self.stdin = null; |
| 432 | 436 | } |
| 433 | 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 | 442 | } else { |
| 436 | 443 | self.stdout = null; |
| 437 | 444 | } |
| 438 | 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 | 450 | } else { |
| 441 | 451 | self.stderr = null; |
| 442 | 452 | } |
| ... | ... | @@ -661,17 +671,26 @@ pub const ChildProcess = struct { |
| 661 | 671 | }; |
| 662 | 672 | |
| 663 | 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 | 678 | } else { |
| 666 | 679 | self.stdin = null; |
| 667 | 680 | } |
| 668 | 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 | 686 | } else { |
| 671 | 687 | self.stdout = null; |
| 672 | 688 | } |
| 673 | 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 | 694 | } else { |
| 676 | 695 | self.stderr = null; |
| 677 | 696 | } |
| ... | ... | @@ -693,10 +712,10 @@ pub const ChildProcess = struct { |
| 693 | 712 | |
| 694 | 713 | fn setUpChildIo(stdio: StdIo, pipe_fd: i32, std_fileno: i32, dev_null_fd: i32) !void { |
| 695 | 714 | switch (stdio) { |
| 696 | StdIo.Pipe => try os.dup2(pipe_fd, std_fileno), | |
| 697 | StdIo.Close => os.close(std_fileno), | |
| 698 | StdIo.Inherit => {}, | |
| 699 | StdIo.Ignore => try os.dup2(dev_null_fd, std_fileno), | |
| 715 | .Pipe => try os.dup2(pipe_fd, std_fileno), | |
| 716 | .Close => os.close(std_fileno), | |
| 717 | .Inherit => {}, | |
| 718 | .Ignore => try os.dup2(dev_null_fd, std_fileno), | |
| 700 | 719 | } |
| 701 | 720 | } |
| 702 | 721 | }; |
| ... | ... | @@ -811,12 +830,22 @@ fn forkChildErrReport(fd: i32, err: ChildProcess.SpawnError) noreturn { |
| 811 | 830 | const ErrInt = @IntType(false, @sizeOf(anyerror) * 8); |
| 812 | 831 | |
| 813 | 832 | fn 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 | 839 | stream.writeIntNative(u64, @intCast(u64, value)) catch return error.SystemResources; |
| 816 | 840 | } |
| 817 | 841 | |
| 818 | 842 | fn 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 | 849 | return @intCast(ErrInt, stream.readIntNative(u64) catch return error.SystemResources); |
| 821 | 850 | } |
| 822 | 851 |
lib/std/debug.zig+24-24| ... | ... | @@ -50,7 +50,7 @@ pub fn warn(comptime fmt: []const u8, args: var) void { |
| 50 | 50 | const held = stderr_mutex.acquire(); |
| 51 | 51 | defer held.release(); |
| 52 | 52 | const stderr = getStderrStream(); |
| 53 | stderr.print(fmt, args) catch return; | |
| 53 | noasync stderr.print(fmt, args) catch return; | |
| 54 | 54 | } |
| 55 | 55 | |
| 56 | 56 | pub fn getStderrStream() *io.OutStream(File.WriteError) { |
| ... | ... | @@ -102,15 +102,15 @@ pub fn detectTTYConfig() TTY.Config { |
| 102 | 102 | pub fn dumpCurrentStackTrace(start_addr: ?usize) void { |
| 103 | 103 | const stderr = getStderrStream(); |
| 104 | 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 | 106 | return; |
| 107 | 107 | } |
| 108 | 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 | 110 | return; |
| 111 | 111 | }; |
| 112 | 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 | 114 | return; |
| 115 | 115 | }; |
| 116 | 116 | } |
| ... | ... | @@ -121,11 +121,11 @@ pub fn dumpCurrentStackTrace(start_addr: ?usize) void { |
| 121 | 121 | pub fn dumpStackTraceFromBase(bp: usize, ip: usize) void { |
| 122 | 122 | const stderr = getStderrStream(); |
| 123 | 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 | 125 | return; |
| 126 | 126 | } |
| 127 | 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 | 129 | return; |
| 130 | 130 | }; |
| 131 | 131 | const tty_config = detectTTYConfig(); |
| ... | ... | @@ -189,15 +189,15 @@ pub fn captureStackTrace(first_address: ?usize, stack_trace: *builtin.StackTrace |
| 189 | 189 | pub fn dumpStackTrace(stack_trace: builtin.StackTrace) void { |
| 190 | 190 | const stderr = getStderrStream(); |
| 191 | 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 | 193 | return; |
| 194 | 194 | } |
| 195 | 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 | 197 | return; |
| 198 | 198 | }; |
| 199 | 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 | 201 | return; |
| 202 | 202 | }; |
| 203 | 203 | } |
| ... | ... | @@ -238,7 +238,7 @@ pub fn panicExtra(trace: ?*const builtin.StackTrace, first_trace_addr: ?usize, c |
| 238 | 238 | switch (@atomicRmw(u8, &panicking, .Add, 1, .SeqCst)) { |
| 239 | 239 | 0 => { |
| 240 | 240 | const stderr = getStderrStream(); |
| 241 | stderr.print(format ++ "\n", args) catch os.abort(); | |
| 241 | noasync stderr.print(format ++ "\n", args) catch os.abort(); | |
| 242 | 242 | if (trace) |t| { |
| 243 | 243 | dumpStackTrace(t.*); |
| 244 | 244 | } |
| ... | ... | @@ -568,12 +568,12 @@ pub const TTY = struct { |
| 568 | 568 | switch (conf) { |
| 569 | 569 | .no_color => return, |
| 570 | 570 | .escape_codes => switch (color) { |
| 571 | .Red => out_stream.write(RED) catch return, | |
| 572 | .Green => out_stream.write(GREEN) catch return, | |
| 573 | .Cyan => out_stream.write(CYAN) catch return, | |
| 574 | .White, .Bold => out_stream.write(WHITE) catch return, | |
| 575 | .Dim => out_stream.write(DIM) catch return, | |
| 576 | .Reset => out_stream.write(RESET) catch return, | |
| 571 | .Red => noasync out_stream.write(RED) catch return, | |
| 572 | .Green => noasync out_stream.write(GREEN) catch return, | |
| 573 | .Cyan => noasync out_stream.write(CYAN) catch return, | |
| 574 | .White, .Bold => noasync out_stream.write(WHITE) catch return, | |
| 575 | .Dim => noasync out_stream.write(DIM) catch return, | |
| 576 | .Reset => noasync out_stream.write(RESET) catch return, | |
| 577 | 577 | }, |
| 578 | 578 | .windows_api => if (builtin.os == .windows) { |
| 579 | 579 | const S = struct { |
| ... | ... | @@ -729,17 +729,17 @@ fn printLineInfo( |
| 729 | 729 | tty_config.setColor(out_stream, .White); |
| 730 | 730 | |
| 731 | 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 | 733 | } else { |
| 734 | try out_stream.print("???:?:?", .{}); | |
| 734 | try noasync out_stream.write("???:?:?"); | |
| 735 | 735 | } |
| 736 | 736 | |
| 737 | 737 | tty_config.setColor(out_stream, .Reset); |
| 738 | try out_stream.write(": "); | |
| 738 | try noasync out_stream.write(": "); | |
| 739 | 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 | 741 | tty_config.setColor(out_stream, .Reset); |
| 742 | try out_stream.write("\n"); | |
| 742 | try noasync out_stream.write("\n"); | |
| 743 | 743 | |
| 744 | 744 | // Show the matching source code line if possible |
| 745 | 745 | if (line_info) |li| { |
| ... | ... | @@ -748,12 +748,12 @@ fn printLineInfo( |
| 748 | 748 | // The caret already takes one char |
| 749 | 749 | const space_needed = @intCast(usize, li.column - 1); |
| 750 | 750 | |
| 751 | try out_stream.writeByteNTimes(' ', space_needed); | |
| 751 | try noasync out_stream.writeByteNTimes(' ', space_needed); | |
| 752 | 752 | tty_config.setColor(out_stream, .Green); |
| 753 | try out_stream.write("^"); | |
| 753 | try noasync out_stream.write("^"); | |
| 754 | 754 | tty_config.setColor(out_stream, .Reset); |
| 755 | 755 | } |
| 756 | try out_stream.write("\n"); | |
| 756 | try noasync out_stream.write("\n"); | |
| 757 | 757 | } else |err| switch (err) { |
| 758 | 758 | error.EndOfFile, error.FileNotFound => {}, |
| 759 | 759 | error.BadPathName => {}, |
lib/std/event.zig-2| ... | ... | @@ -6,11 +6,9 @@ pub const Locked = @import("event/locked.zig").Locked; |
| 6 | 6 | pub const RwLock = @import("event/rwlock.zig").RwLock; |
| 7 | 7 | pub const RwLocked = @import("event/rwlocked.zig").RwLocked; |
| 8 | 8 | pub const Loop = @import("event/loop.zig").Loop; |
| 9 | pub const fs = @import("event/fs.zig"); | |
| 10 | 9 | |
| 11 | 10 | test "import event tests" { |
| 12 | 11 | _ = @import("event/channel.zig"); |
| 13 | _ = @import("event/fs.zig"); | |
| 14 | 12 | _ = @import("event/future.zig"); |
| 15 | 13 | _ = @import("event/group.zig"); |
| 16 | 14 | _ = @import("event/lock.zig"); |
lib/std/event/channel.zig+3-4| ... | ... | @@ -267,17 +267,16 @@ pub fn Channel(comptime T: type) type { |
| 267 | 267 | } |
| 268 | 268 | |
| 269 | 269 | test "std.event.Channel" { |
| 270 | if (!std.io.is_async) return error.SkipZigTest; | |
| 271 | ||
| 270 | 272 | // https://github.com/ziglang/zig/issues/1908 |
| 271 | 273 | if (builtin.single_threaded) return error.SkipZigTest; |
| 272 | 274 | |
| 273 | 275 | // https://github.com/ziglang/zig/issues/3251 |
| 274 | 276 | if (builtin.os == .freebsd) return error.SkipZigTest; |
| 275 | 277 | |
| 276 | // TODO provide a way to run tests in evented I/O mode | |
| 277 | if (!std.io.is_async) return error.SkipZigTest; | |
| 278 | ||
| 279 | 278 | var channel: Channel(i32) = undefined; |
| 280 | channel.init([0]i32{}); | |
| 279 | channel.init(&[0]i32{}); | |
| 281 | 280 | defer channel.deinit(); |
| 282 | 281 | |
| 283 | 282 | var handle = async testChannelGetter(&channel); |
lib/std/event/fs.zig deleted-1418| ... | ... | @@ -1,1418 +0,0 @@ |
| 1 | const builtin = @import("builtin"); | |
| 2 | const std = @import("../std.zig"); | |
| 3 | const event = std.event; | |
| 4 | const assert = std.debug.assert; | |
| 5 | const testing = std.testing; | |
| 6 | const os = std.os; | |
| 7 | const mem = std.mem; | |
| 8 | const windows = os.windows; | |
| 9 | const Loop = event.Loop; | |
| 10 | const fd_t = os.fd_t; | |
| 11 | const File = std.fs.File; | |
| 12 | const Allocator = mem.Allocator; | |
| 13 | ||
| 14 | //! TODO mege this with `std.fs` | |
| 15 | ||
| 16 | const global_event_loop = Loop.instance orelse | |
| 17 | @compileError("std.event.fs currently only works with event-based I/O"); | |
| 18 | ||
| 19 | pub const RequestNode = std.atomic.Queue(Request).Node; | |
| 20 | ||
| 21 | pub 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 | ||
| 90 | pub const PWriteVError = error{OutOfMemory} || File.WriteError; | |
| 91 | ||
| 92 | /// data - just the inner references - must live until pwritev frame completes. | |
| 93 | pub 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 | |
| 123 | pub 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 | ||
| 135 | pub 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. | |
| 175 | pub 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. | |
| 208 | pub 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 | ||
| 237 | pub const PReadVError = error{OutOfMemory} || File.ReadError; | |
| 238 | ||
| 239 | /// data - just the inner references - must live until preadv frame completes. | |
| 240 | pub 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 | |
| 271 | pub 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 | ||
| 295 | pub 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 | |
| 334 | pub 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 | ||
| 366 | pub 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 | ||
| 400 | pub 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. | |
| 424 | pub 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. | |
| 429 | pub 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. | |
| 455 | pub 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`. | |
| 483 | pub 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 | |
| 613 | pub 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. | |
| 618 | pub 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 | ||
| 631 | fn 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 | ||
| 646 | fn 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. | |
| 684 | pub 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 | ||
| 709 | pub const WatchEventId = enum { | |
| 710 | CloseWrite, | |
| 711 | Delete, | |
| 712 | }; | |
| 713 | ||
| 714 | fn 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 | ||
| 720 | fn hashString(s: []const u16) u32 { | |
| 721 | return @truncate(u32, std.hash.Wyhash.hash(0, @sliceToBytes(s))); | |
| 722 | } | |
| 723 | ||
| 724 | pub const WatchEventError = error{ | |
| 725 | UserResourceLimitReached, | |
| 726 | SystemResources, | |
| 727 | AccessDenied, | |
| 728 | Unexpected, // TODO remove this possibility | |
| 729 | }; | |
| 730 | ||
| 731 | pub 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 | ||
| 1307 | const test_tmp_dir = "std_event_fs_test"; | |
| 1308 | ||
| 1309 | test "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 | ||
| 1322 | fn 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 | ||
| 1370 | pub 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 | ||
| 1395 | pub 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 | 22 | const AllocStack = std.atomic.Stack(Node); |
| 23 | 23 | |
| 24 | 24 | pub const Node = struct { |
| 25 | bytes: []const u8 = [0]u8{}, | |
| 25 | bytes: []const u8 = &[0]u8{}, | |
| 26 | 26 | handle: anyframe->ReturnType, |
| 27 | 27 | }; |
| 28 | 28 |
lib/std/event/lock.zig+4-4| ... | ... | @@ -117,21 +117,21 @@ pub const Lock = struct { |
| 117 | 117 | }; |
| 118 | 118 | |
| 119 | 119 | test "std.event.Lock" { |
| 120 | if (!std.io.is_async) return error.SkipZigTest; | |
| 121 | ||
| 120 | 122 | // TODO https://github.com/ziglang/zig/issues/1908 |
| 121 | 123 | if (builtin.single_threaded) return error.SkipZigTest; |
| 122 | 124 | |
| 123 | 125 | // TODO https://github.com/ziglang/zig/issues/3251 |
| 124 | 126 | if (builtin.os == .freebsd) return error.SkipZigTest; |
| 125 | 127 | |
| 126 | // TODO provide a way to run tests in evented I/O mode | |
| 127 | if (!std.io.is_async) return error.SkipZigTest; | |
| 128 | ||
| 129 | 128 | var lock = Lock.init(); |
| 130 | 129 | defer lock.deinit(); |
| 131 | 130 | |
| 132 | 131 | _ = async testLock(&lock); |
| 133 | 132 | |
| 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 | } |
| 136 | 136 | |
| 137 | 137 | async fn testLock(lock: *Lock) void { |
lib/std/event/loop.zig+317-50| ... | ... | @@ -6,7 +6,6 @@ const testing = std.testing; |
| 6 | 6 | const mem = std.mem; |
| 7 | 7 | const AtomicRmwOp = builtin.AtomicRmwOp; |
| 8 | 8 | const AtomicOrder = builtin.AtomicOrder; |
| 9 | const fs = std.event.fs; | |
| 10 | 9 | const os = std.os; |
| 11 | 10 | const windows = os.windows; |
| 12 | 11 | const maxInt = std.math.maxInt; |
| ... | ... | @@ -174,21 +173,19 @@ pub const Loop = struct { |
| 174 | 173 | fn initOsData(self: *Loop, extra_thread_count: usize) InitOsDataError!void { |
| 175 | 174 | switch (builtin.os) { |
| 176 | 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 | 177 | self.os_data.fs_queue_item = 0; |
| 179 | 178 | // we need another thread for the file system because Linux does not have an async |
| 180 | 179 | // file system I/O API. |
| 181 | self.os_data.fs_end_request = fs.RequestNode{ | |
| 182 | .prev = undefined, | |
| 183 | .next = undefined, | |
| 184 | .data = fs.Request{ | |
| 185 | .msg = fs.Request.Msg.End, | |
| 186 | .finish = fs.Request.Finish.NoAction, | |
| 180 | self.os_data.fs_end_request = Request.Node{ | |
| 181 | .data = Request{ | |
| 182 | .msg = .end, | |
| 183 | .finish = .NoAction, | |
| 187 | 184 | }, |
| 188 | 185 | }; |
| 189 | 186 | |
| 190 | 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 | 190 | for (self.eventfd_resume_nodes) |*eventfd_node| { |
| 194 | 191 | eventfd_node.* = std.atomic.Stack(ResumeNode.EventFd).Node{ |
| ... | ... | @@ -207,10 +204,10 @@ pub const Loop = struct { |
| 207 | 204 | } |
| 208 | 205 | |
| 209 | 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); | |
| 211 | 208 | |
| 212 | 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); | |
| 214 | 211 | |
| 215 | 212 | self.os_data.final_eventfd_event = os.epoll_event{ |
| 216 | 213 | .events = os.EPOLLIN, |
| ... | ... | @@ -237,7 +234,7 @@ pub const Loop = struct { |
| 237 | 234 | var extra_thread_index: usize = 0; |
| 238 | 235 | errdefer { |
| 239 | 236 | // 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 | 238 | while (extra_thread_index != 0) { |
| 242 | 239 | extra_thread_index -= 1; |
| 243 | 240 | self.extra_threads[extra_thread_index].wait(); |
| ... | ... | @@ -249,20 +246,20 @@ pub const Loop = struct { |
| 249 | 246 | }, |
| 250 | 247 | .macosx, .freebsd, .netbsd, .dragonfly => { |
| 251 | 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); | |
| 253 | 250 | |
| 254 | 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); | |
| 256 | 253 | |
| 257 | self.os_data.fs_queue = std.atomic.Queue(fs.Request).init(); | |
| 254 | self.os_data.fs_queue = std.atomic.Queue(Request).init(); | |
| 258 | 255 | // we need another thread for the file system because Darwin does not have an async |
| 259 | 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 | 258 | .prev = undefined, |
| 262 | 259 | .next = undefined, |
| 263 | .data = fs.Request{ | |
| 264 | .msg = fs.Request.Msg.End, | |
| 265 | .finish = fs.Request.Finish.NoAction, | |
| 260 | .data = Request{ | |
| 261 | .msg = .end, | |
| 262 | .finish = .NoAction, | |
| 266 | 263 | }, |
| 267 | 264 | }; |
| 268 | 265 | |
| ... | ... | @@ -407,14 +404,14 @@ pub const Loop = struct { |
| 407 | 404 | fn deinitOsData(self: *Loop) void { |
| 408 | 405 | switch (builtin.os) { |
| 409 | 406 | .linux => { |
| 410 | os.close(self.os_data.final_eventfd); | |
| 411 | while (self.available_eventfd_resume_nodes.pop()) |node| os.close(node.data.eventfd); | |
| 412 | os.close(self.os_data.epollfd); | |
| 407 | noasync os.close(self.os_data.final_eventfd); | |
| 408 | while (self.available_eventfd_resume_nodes.pop()) |node| noasync os.close(node.data.eventfd); | |
| 409 | noasync os.close(self.os_data.epollfd); | |
| 413 | 410 | self.allocator.free(self.eventfd_resume_nodes); |
| 414 | 411 | }, |
| 415 | 412 | .macosx, .freebsd, .netbsd, .dragonfly => { |
| 416 | os.close(self.os_data.kqfd); | |
| 417 | os.close(self.os_data.fs_kqfd); | |
| 413 | noasync os.close(self.os_data.kqfd); | |
| 414 | noasync os.close(self.os_data.fs_kqfd); | |
| 418 | 415 | }, |
| 419 | 416 | .windows => { |
| 420 | 417 | windows.CloseHandle(self.os_data.io_port); |
| ... | ... | @@ -711,6 +708,190 @@ pub const Loop = struct { |
| 711 | 708 | } |
| 712 | 709 | } |
| 713 | 710 | |
| 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 | 895 | fn workerRun(self: *Loop) void { |
| 715 | 896 | while (true) { |
| 716 | 897 | while (true) { |
| ... | ... | @@ -804,7 +985,7 @@ pub const Loop = struct { |
| 804 | 985 | } |
| 805 | 986 | } |
| 806 | 987 | |
| 807 | fn posixFsRequest(self: *Loop, request_node: *fs.RequestNode) void { | |
| 988 | fn posixFsRequest(self: *Loop, request_node: *Request.Node) void { | |
| 808 | 989 | self.beginOneEvent(); // finished in posixFsRun after processing the msg |
| 809 | 990 | self.os_data.fs_queue.put(request_node); |
| 810 | 991 | switch (builtin.os) { |
| ... | ... | @@ -826,7 +1007,7 @@ pub const Loop = struct { |
| 826 | 1007 | } |
| 827 | 1008 | } |
| 828 | 1009 | |
| 829 | fn posixFsCancel(self: *Loop, request_node: *fs.RequestNode) void { | |
| 1010 | fn posixFsCancel(self: *Loop, request_node: *Request.Node) void { | |
| 830 | 1011 | if (self.os_data.fs_queue.remove(request_node)) { |
| 831 | 1012 | self.finishOneEvent(); |
| 832 | 1013 | } |
| ... | ... | @@ -841,37 +1022,32 @@ pub const Loop = struct { |
| 841 | 1022 | } |
| 842 | 1023 | while (self.os_data.fs_queue.get()) |node| { |
| 843 | 1024 | switch (node.data.msg) { |
| 844 | .End => return, | |
| 845 | .WriteV => |*msg| { | |
| 1025 | .end => return, | |
| 1026 | .read => |*msg| { | |
| 1027 | msg.result = noasync os.read(msg.fd, msg.buf); | |
| 1028 | }, | |
| 1029 | .write => |*msg| { | |
| 1030 | msg.result = noasync os.write(msg.fd, msg.bytes); | |
| 1031 | }, | |
| 1032 | .writev => |*msg| { | |
| 846 | 1033 | msg.result = noasync os.writev(msg.fd, msg.iov); |
| 847 | 1034 | }, |
| 848 | .PWriteV => |*msg| { | |
| 1035 | .pwritev => |*msg| { | |
| 849 | 1036 | msg.result = noasync os.pwritev(msg.fd, msg.iov, msg.offset); |
| 850 | 1037 | }, |
| 851 | .PReadV => |*msg| { | |
| 1038 | .preadv => |*msg| { | |
| 852 | 1039 | msg.result = noasync os.preadv(msg.fd, msg.iov, msg.offset); |
| 853 | 1040 | }, |
| 854 | .Open => |*msg| { | |
| 855 | msg.result = noasync os.openC(msg.path.ptr, msg.flags, msg.mode); | |
| 1041 | .open => |*msg| { | |
| 1042 | msg.result = noasync os.openC(msg.path, msg.flags, msg.mode); | |
| 856 | 1043 | }, |
| 857 | .Close => |*msg| noasync os.close(msg.fd), | |
| 858 | .WriteFile => |*msg| blk: { | |
| 859 | const O_LARGEFILE = if (@hasDecl(os, "O_LARGEFILE")) os.O_LARGEFILE else 0; | |
| 860 | const flags = O_LARGEFILE | os.O_WRONLY | os.O_CREAT | | |
| 861 | os.O_CLOEXEC | os.O_TRUNC; | |
| 862 | const fd = noasync os.openC(msg.path.ptr, flags, msg.mode) catch |err| { | |
| 863 | msg.result = err; | |
| 864 | break :blk; | |
| 865 | }; | |
| 866 | defer noasync os.close(fd); | |
| 867 | msg.result = noasync os.write(fd, msg.contents); | |
| 1044 | .openat => |*msg| { | |
| 1045 | msg.result = noasync os.openatC(msg.fd, msg.path, msg.flags, msg.mode); | |
| 868 | 1046 | }, |
| 1047 | .close => |*msg| noasync os.close(msg.fd), | |
| 869 | 1048 | } |
| 870 | 1049 | switch (node.data.finish) { |
| 871 | 1050 | .TickNode => |*tick_node| self.onNextTick(tick_node), |
| 872 | .DeallocCloseOperation => |close_op| { | |
| 873 | self.allocator.destroy(close_op); | |
| 874 | }, | |
| 875 | 1051 | .NoAction => {}, |
| 876 | 1052 | } |
| 877 | 1053 | self.finishOneEvent(); |
| ... | ... | @@ -911,8 +1087,8 @@ pub const Loop = struct { |
| 911 | 1087 | fs_kevent_wait: os.Kevent, |
| 912 | 1088 | fs_thread: *Thread, |
| 913 | 1089 | fs_kqfd: i32, |
| 914 | fs_queue: std.atomic.Queue(fs.Request), | |
| 915 | fs_end_request: fs.RequestNode, | |
| 1090 | fs_queue: std.atomic.Queue(Request), | |
| 1091 | fs_end_request: Request.Node, | |
| 916 | 1092 | }; |
| 917 | 1093 | |
| 918 | 1094 | const LinuxOsData = struct { |
| ... | ... | @@ -921,8 +1097,99 @@ pub const Loop = struct { |
| 921 | 1097 | final_eventfd_event: os.linux.epoll_event, |
| 922 | 1098 | fs_thread: *Thread, |
| 923 | 1099 | fs_queue_item: i32, |
| 924 | fs_queue: std.atomic.Queue(fs.Request), | |
| 925 | fs_end_request: fs.RequestNode, | |
| 1100 | fs_queue: std.atomic.Queue(Request), | |
| 1101 | fs_end_request: Request.Node, | |
| 1102 | }; | |
| 1103 | ||
| 1104 | pub const Request = struct { | |
| 1105 | msg: Msg, | |
| 1106 | finish: Finish, | |
| 1107 | ||
| 1108 | pub const Node = std.atomic.Queue(Request).Node; | |
| 1109 | ||
| 1110 | pub const Finish = union(enum) { | |
| 1111 | TickNode: Loop.NextTickNode, | |
| 1112 | NoAction, | |
| 1113 | }; | |
| 1114 | ||
| 1115 | pub const Msg = union(enum) { | |
| 1116 | read: Read, | |
| 1117 | write: Write, | |
| 1118 | writev: WriteV, | |
| 1119 | pwritev: PWriteV, | |
| 1120 | preadv: PReadV, | |
| 1121 | open: Open, | |
| 1122 | openat: OpenAt, | |
| 1123 | close: Close, | |
| 1124 | ||
| 1125 | /// special - means the fs thread should exit | |
| 1126 | end, | |
| 1127 | ||
| 1128 | pub const Read = struct { | |
| 1129 | fd: os.fd_t, | |
| 1130 | buf: []u8, | |
| 1131 | result: Error!usize, | |
| 1132 | ||
| 1133 | pub const Error = os.ReadError; | |
| 1134 | }; | |
| 1135 | ||
| 1136 | pub const Write = struct { | |
| 1137 | fd: os.fd_t, | |
| 1138 | bytes: []const u8, | |
| 1139 | result: Error!void, | |
| 1140 | ||
| 1141 | pub const Error = os.WriteError; | |
| 1142 | }; | |
| 1143 | ||
| 1144 | pub const WriteV = struct { | |
| 1145 | fd: os.fd_t, | |
| 1146 | iov: []const os.iovec_const, | |
| 1147 | result: Error!void, | |
| 1148 | ||
| 1149 | pub const Error = os.WriteError; | |
| 1150 | }; | |
| 1151 | ||
| 1152 | pub const PWriteV = struct { | |
| 1153 | fd: os.fd_t, | |
| 1154 | iov: []const os.iovec_const, | |
| 1155 | offset: usize, | |
| 1156 | result: Error!void, | |
| 1157 | ||
| 1158 | pub const Error = os.WriteError; | |
| 1159 | }; | |
| 1160 | ||
| 1161 | pub const PReadV = struct { | |
| 1162 | fd: os.fd_t, | |
| 1163 | iov: []const os.iovec, | |
| 1164 | offset: usize, | |
| 1165 | result: Error!usize, | |
| 1166 | ||
| 1167 | pub const Error = os.ReadError; | |
| 1168 | }; | |
| 1169 | ||
| 1170 | pub const Open = struct { | |
| 1171 | path: [*:0]const u8, | |
| 1172 | flags: u32, | |
| 1173 | mode: os.mode_t, | |
| 1174 | result: Error!os.fd_t, | |
| 1175 | ||
| 1176 | pub const Error = os.OpenError; | |
| 1177 | }; | |
| 1178 | ||
| 1179 | pub const OpenAt = struct { | |
| 1180 | fd: os.fd_t, | |
| 1181 | path: [*:0]const u8, | |
| 1182 | flags: u32, | |
| 1183 | mode: os.mode_t, | |
| 1184 | result: Error!os.fd_t, | |
| 1185 | ||
| 1186 | pub const Error = os.OpenError; | |
| 1187 | }; | |
| 1188 | ||
| 1189 | pub const Close = struct { | |
| 1190 | fd: os.fd_t, | |
| 1191 | }; | |
| 1192 | }; | |
| 926 | 1193 | }; |
| 927 | 1194 | }; |
| 928 | 1195 |
lib/std/fmt.zig+16-16| ... | ... | @@ -78,7 +78,7 @@ fn peekIsAlign(comptime fmt: []const u8) bool { |
| 78 | 78 | pub fn format( |
| 79 | 79 | context: var, |
| 80 | 80 | comptime Errors: type, |
| 81 | output: fn (@TypeOf(context), []const u8) Errors!void, | |
| 81 | comptime output: fn (@TypeOf(context), []const u8) Errors!void, | |
| 82 | 82 | comptime fmt: []const u8, |
| 83 | 83 | args: var, |
| 84 | 84 | ) Errors!void { |
| ... | ... | @@ -326,7 +326,7 @@ pub fn formatType( |
| 326 | 326 | options: FormatOptions, |
| 327 | 327 | context: var, |
| 328 | 328 | comptime Errors: type, |
| 329 | output: fn (@TypeOf(context), []const u8) Errors!void, | |
| 329 | comptime output: fn (@TypeOf(context), []const u8) Errors!void, | |
| 330 | 330 | max_depth: usize, |
| 331 | 331 | ) Errors!void { |
| 332 | 332 | if (comptime std.mem.eql(u8, fmt, "*")) { |
| ... | ... | @@ -488,7 +488,7 @@ fn formatValue( |
| 488 | 488 | options: FormatOptions, |
| 489 | 489 | context: var, |
| 490 | 490 | comptime Errors: type, |
| 491 | output: fn (@TypeOf(context), []const u8) Errors!void, | |
| 491 | comptime output: fn (@TypeOf(context), []const u8) Errors!void, | |
| 492 | 492 | ) Errors!void { |
| 493 | 493 | if (comptime std.mem.eql(u8, fmt, "B")) { |
| 494 | 494 | return formatBytes(value, options, 1000, context, Errors, output); |
| ... | ... | @@ -510,7 +510,7 @@ pub fn formatIntValue( |
| 510 | 510 | options: FormatOptions, |
| 511 | 511 | context: var, |
| 512 | 512 | comptime Errors: type, |
| 513 | output: fn (@TypeOf(context), []const u8) Errors!void, | |
| 513 | comptime output: fn (@TypeOf(context), []const u8) Errors!void, | |
| 514 | 514 | ) Errors!void { |
| 515 | 515 | comptime var radix = 10; |
| 516 | 516 | comptime var uppercase = false; |
| ... | ... | @@ -552,7 +552,7 @@ fn formatFloatValue( |
| 552 | 552 | options: FormatOptions, |
| 553 | 553 | context: var, |
| 554 | 554 | comptime Errors: type, |
| 555 | output: fn (@TypeOf(context), []const u8) Errors!void, | |
| 555 | comptime output: fn (@TypeOf(context), []const u8) Errors!void, | |
| 556 | 556 | ) Errors!void { |
| 557 | 557 | if (fmt.len == 0 or comptime std.mem.eql(u8, fmt, "e")) { |
| 558 | 558 | return formatFloatScientific(value, options, context, Errors, output); |
| ... | ... | @@ -569,7 +569,7 @@ pub fn formatText( |
| 569 | 569 | options: FormatOptions, |
| 570 | 570 | context: var, |
| 571 | 571 | comptime Errors: type, |
| 572 | output: fn (@TypeOf(context), []const u8) Errors!void, | |
| 572 | comptime output: fn (@TypeOf(context), []const u8) Errors!void, | |
| 573 | 573 | ) Errors!void { |
| 574 | 574 | if (fmt.len == 0) { |
| 575 | 575 | return output(context, bytes); |
| ... | ... | @@ -590,7 +590,7 @@ pub fn formatAsciiChar( |
| 590 | 590 | options: FormatOptions, |
| 591 | 591 | context: var, |
| 592 | 592 | comptime Errors: type, |
| 593 | output: fn (@TypeOf(context), []const u8) Errors!void, | |
| 593 | comptime output: fn (@TypeOf(context), []const u8) Errors!void, | |
| 594 | 594 | ) Errors!void { |
| 595 | 595 | return output(context, @as(*const [1]u8, &c)[0..]); |
| 596 | 596 | } |
| ... | ... | @@ -600,7 +600,7 @@ pub fn formatBuf( |
| 600 | 600 | options: FormatOptions, |
| 601 | 601 | context: var, |
| 602 | 602 | comptime Errors: type, |
| 603 | output: fn (@TypeOf(context), []const u8) Errors!void, | |
| 603 | comptime output: fn (@TypeOf(context), []const u8) Errors!void, | |
| 604 | 604 | ) Errors!void { |
| 605 | 605 | try output(context, buf); |
| 606 | 606 | |
| ... | ... | @@ -620,7 +620,7 @@ pub fn formatFloatScientific( |
| 620 | 620 | options: FormatOptions, |
| 621 | 621 | context: var, |
| 622 | 622 | comptime Errors: type, |
| 623 | output: fn (@TypeOf(context), []const u8) Errors!void, | |
| 623 | comptime output: fn (@TypeOf(context), []const u8) Errors!void, | |
| 624 | 624 | ) Errors!void { |
| 625 | 625 | var x = @floatCast(f64, value); |
| 626 | 626 | |
| ... | ... | @@ -715,7 +715,7 @@ pub fn formatFloatDecimal( |
| 715 | 715 | options: FormatOptions, |
| 716 | 716 | context: var, |
| 717 | 717 | comptime Errors: type, |
| 718 | output: fn (@TypeOf(context), []const u8) Errors!void, | |
| 718 | comptime output: fn (@TypeOf(context), []const u8) Errors!void, | |
| 719 | 719 | ) Errors!void { |
| 720 | 720 | var x = @as(f64, value); |
| 721 | 721 | |
| ... | ... | @@ -861,7 +861,7 @@ pub fn formatBytes( |
| 861 | 861 | comptime radix: usize, |
| 862 | 862 | context: var, |
| 863 | 863 | comptime Errors: type, |
| 864 | output: fn (@TypeOf(context), []const u8) Errors!void, | |
| 864 | comptime output: fn (@TypeOf(context), []const u8) Errors!void, | |
| 865 | 865 | ) Errors!void { |
| 866 | 866 | if (value == 0) { |
| 867 | 867 | return output(context, "0B"); |
| ... | ... | @@ -902,7 +902,7 @@ pub fn formatInt( |
| 902 | 902 | options: FormatOptions, |
| 903 | 903 | context: var, |
| 904 | 904 | comptime Errors: type, |
| 905 | output: fn (@TypeOf(context), []const u8) Errors!void, | |
| 905 | comptime output: fn (@TypeOf(context), []const u8) Errors!void, | |
| 906 | 906 | ) Errors!void { |
| 907 | 907 | const int_value = if (@TypeOf(value) == comptime_int) blk: { |
| 908 | 908 | const Int = math.IntFittingRange(value, value); |
| ... | ... | @@ -924,7 +924,7 @@ fn formatIntSigned( |
| 924 | 924 | options: FormatOptions, |
| 925 | 925 | context: var, |
| 926 | 926 | comptime Errors: type, |
| 927 | output: fn (@TypeOf(context), []const u8) Errors!void, | |
| 927 | comptime output: fn (@TypeOf(context), []const u8) Errors!void, | |
| 928 | 928 | ) Errors!void { |
| 929 | 929 | const new_options = FormatOptions{ |
| 930 | 930 | .width = if (options.width) |w| (if (w == 0) 0 else w - 1) else null, |
| ... | ... | @@ -955,7 +955,7 @@ fn formatIntUnsigned( |
| 955 | 955 | options: FormatOptions, |
| 956 | 956 | context: var, |
| 957 | 957 | comptime Errors: type, |
| 958 | output: fn (@TypeOf(context), []const u8) Errors!void, | |
| 958 | comptime output: fn (@TypeOf(context), []const u8) Errors!void, | |
| 959 | 959 | ) Errors!void { |
| 960 | 960 | assert(base >= 2); |
| 961 | 961 | var buf: [math.max(@TypeOf(value).bit_count, 1)]u8 = undefined; |
| ... | ... | @@ -1419,7 +1419,7 @@ test "custom" { |
| 1419 | 1419 | options: FormatOptions, |
| 1420 | 1420 | context: var, |
| 1421 | 1421 | comptime Errors: type, |
| 1422 | output: fn (@TypeOf(context), []const u8) Errors!void, | |
| 1422 | comptime output: fn (@TypeOf(context), []const u8) Errors!void, | |
| 1423 | 1423 | ) Errors!void { |
| 1424 | 1424 | if (fmt.len == 0 or comptime std.mem.eql(u8, fmt, "p")) { |
| 1425 | 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 | 1626 | options: FormatOptions, |
| 1627 | 1627 | context: var, |
| 1628 | 1628 | comptime Errors: type, |
| 1629 | output: fn (@TypeOf(context), []const u8) Errors!void, | |
| 1629 | comptime output: fn (@TypeOf(context), []const u8) Errors!void, | |
| 1630 | 1630 | ) Errors!void { |
| 1631 | 1631 | if (fmt.len == 0) { |
| 1632 | 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 | 23 | pub const getAppDataDir = @import("fs/get_app_data_dir.zig").getAppDataDir; |
| 24 | 24 | pub const GetAppDataDirError = @import("fs/get_app_data_dir.zig").GetAppDataDirError; |
| 25 | 25 | |
| 26 | pub const Watch = @import("fs/watch.zig").Watch; | |
| 27 | ||
| 26 | 28 | /// This represents the maximum size of a UTF-8 encoded file path. |
| 27 | 29 | /// All file system operations which return a path are guaranteed to |
| 28 | 30 | /// fit into a UTF-8 encoded array of this length. |
| ... | ... | @@ -43,6 +45,13 @@ pub const base64_encoder = base64.Base64Encoder.init( |
| 43 | 45 | base64.standard_pad_char, |
| 44 | 46 | ); |
| 45 | 47 | |
| 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. | |
| 50 | pub const need_async_thread = std.io.is_async and switch (builtin.os) { | |
| 51 | .windows, .other => false, | |
| 52 | else => true, | |
| 53 | }; | |
| 54 | ||
| 46 | 55 | /// TODO remove the allocator requirement from this API |
| 47 | 56 | pub fn atomicSymLink(allocator: *Allocator, existing_path: []const u8, new_path: []const u8) !void { |
| 48 | 57 | if (symLink(existing_path, new_path)) { |
| ... | ... | @@ -688,11 +697,16 @@ pub const Dir = struct { |
| 688 | 697 | } |
| 689 | 698 | |
| 690 | 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 | 705 | self.* = undefined; |
| 693 | 706 | } |
| 694 | 707 | |
| 695 | 708 | /// Opens a file for reading or writing, without attempting to create a new file. |
| 709 | /// To create a new file, see `createFile`. | |
| 696 | 710 | /// Call `File.close` to release the resource. |
| 697 | 711 | /// Asserts that the path parameter has no null bytes. |
| 698 | 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 | 732 | @as(u32, os.O_WRONLY) |
| 719 | 733 | else |
| 720 | 734 | @as(u32, os.O_RDONLY); |
| 721 | const fd = try os.openatC(self.fd, sub_path, os_flags, 0); | |
| 722 | return File{ .handle = fd }; | |
| 735 | const fd = if (need_async_thread) | |
| 736 | try std.event.Loop.instance.?.openatZ(self.fd, sub_path, os_flags, 0) | |
| 737 | else | |
| 738 | try os.openatC(self.fd, sub_path, os_flags, 0); | |
| 739 | return File{ .handle = fd, .io_mode = .blocking }; | |
| 723 | 740 | } |
| 724 | 741 | |
| 725 | 742 | /// Same as `openFile` but Windows-only and the path parameter is |
| ... | ... | @@ -756,8 +773,11 @@ pub const Dir = struct { |
| 756 | 773 | (if (flags.truncate) @as(u32, os.O_TRUNC) else 0) | |
| 757 | 774 | (if (flags.read) @as(u32, os.O_RDWR) else os.O_WRONLY) | |
| 758 | 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); | |
| 760 | return File{ .handle = fd }; | |
| 776 | const fd = if (need_async_thread) | |
| 777 | try std.event.Loop.instance.?.openatZ(self.fd, sub_path_c, os_flags, flags.mode) | |
| 778 | else | |
| 779 | try os.openatC(self.fd, sub_path_c, os_flags, flags.mode); | |
| 780 | return File{ .handle = fd, .io_mode = .blocking }; | |
| 761 | 781 | } |
| 762 | 782 | |
| 763 | 783 | /// Same as `createFile` but Windows-only and the path parameter is |
| ... | ... | @@ -798,7 +818,10 @@ pub const Dir = struct { |
| 798 | 818 | ) File.OpenError!File { |
| 799 | 819 | const w = os.windows; |
| 800 | 820 | |
| 801 | var result = File{ .handle = undefined }; | |
| 821 | var result = File{ | |
| 822 | .handle = undefined, | |
| 823 | .io_mode = .blocking, | |
| 824 | }; | |
| 802 | 825 | |
| 803 | 826 | const path_len_bytes = math.cast(u16, mem.toSliceConst(u16, sub_path_w).len * 2) catch |err| switch (err) { |
| 804 | 827 | error.Overflow => return error.NameTooLong, |
| ... | ... | @@ -919,7 +942,12 @@ pub const Dir = struct { |
| 919 | 942 | } |
| 920 | 943 | |
| 921 | 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 | 951 | error.FileTooBig => unreachable, // can't happen for directories |
| 924 | 952 | error.IsDir => unreachable, // we're providing O_DIRECTORY |
| 925 | 953 | error.NoSpaceLeft => unreachable, // not providing O_CREAT |
| ... | ... | @@ -1588,4 +1616,5 @@ test "" { |
| 1588 | 1616 | _ = @import("fs/path.zig"); |
| 1589 | 1617 | _ = @import("fs/file.zig"); |
| 1590 | 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 | 8 | const windows = os.windows; |
| 9 | 9 | const Os = builtin.Os; |
| 10 | 10 | const maxInt = std.math.maxInt; |
| 11 | const need_async_thread = std.fs.need_async_thread; | |
| 11 | 12 | |
| 12 | 13 | pub const File = struct { |
| 13 | 14 | /// The OS-specific file descriptor or file handle. |
| 14 | 15 | handle: os.fd_t, |
| 15 | 16 | |
| 16 | pub const Mode = switch (builtin.os) { | |
| 17 | Os.windows => void, | |
| 18 | else => u32, | |
| 19 | }; | |
| 17 | /// On some systems, such as Linux, file system file descriptors are incapable of non-blocking I/O. | |
| 18 | /// This forces us to perform asynchronous I/O on a dedicated thread, to achieve non-blocking | |
| 19 | /// file-system I/O. To do this, `File` must be aware of whether it is a file system file descriptor, | |
| 20 | /// or, more specifically, whether the I/O is blocking. | |
| 21 | io_mode: io.Mode, | |
| 22 | ||
| 23 | /// Even when std.io.mode is async, it is still sometimes desirable to perform blocking I/O, although | |
| 24 | /// not by default. For example, when printing a stack trace to stderr. | |
| 25 | async_block_allowed: @TypeOf(async_block_allowed_no) = async_block_allowed_no, | |
| 26 | ||
| 27 | pub const async_block_allowed_yes = if (io.is_async) true else {}; | |
| 28 | pub const async_block_allowed_no = if (io.is_async) false else {}; | |
| 29 | ||
| 30 | pub const Mode = os.mode_t; | |
| 20 | 31 | |
| 21 | 32 | pub const default_mode = switch (builtin.os) { |
| 22 | Os.windows => {}, | |
| 33 | .windows => 0, | |
| 23 | 34 | else => 0o666, |
| 24 | 35 | }; |
| 25 | 36 | |
| ... | ... | @@ -49,87 +60,27 @@ pub const File = struct { |
| 49 | 60 | mode: Mode = default_mode, |
| 50 | 61 | }; |
| 51 | 62 | |
| 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 | 63 | /// Test for the existence of `path`. |
| 116 | 64 | /// `path` is UTF8-encoded. |
| 117 | 65 | /// In general it is recommended to avoid this function. For example, |
| 118 | 66 | /// instead of testing if a file exists and then opening it, just |
| 119 | 67 | /// open it and handle the error for file not found. |
| 120 | 68 | /// TODO: deprecate this and move it to `std.fs.Dir`. |
| 69 | /// TODO: integrate with async I/O | |
| 121 | 70 | pub fn access(path: []const u8) !void { |
| 122 | 71 | return os.access(path, os.F_OK); |
| 123 | 72 | } |
| 124 | 73 | |
| 125 | 74 | /// Same as `access` except the parameter is null-terminated. |
| 126 | 75 | /// TODO: deprecate this and move it to `std.fs.Dir`. |
| 76 | /// TODO: integrate with async I/O | |
| 127 | 77 | pub fn accessC(path: [*:0]const u8) !void { |
| 128 | 78 | return os.accessC(path, os.F_OK); |
| 129 | 79 | } |
| 130 | 80 | |
| 131 | 81 | /// Same as `access` except the parameter is null-terminated UTF16LE-encoded. |
| 132 | 82 | /// TODO: deprecate this and move it to `std.fs.Dir`. |
| 83 | /// TODO: integrate with async I/O | |
| 133 | 84 | pub fn accessW(path: [*:0]const u16) !void { |
| 134 | 85 | return os.accessW(path, os.F_OK); |
| 135 | 86 | } |
| ... | ... | @@ -137,7 +88,11 @@ pub const File = struct { |
| 137 | 88 | /// Upon success, the stream is in an uninitialized state. To continue using it, |
| 138 | 89 | /// you must use the open() function. |
| 139 | 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 | } |
| 142 | 97 | |
| 143 | 98 | /// Test whether the file refers to a terminal. |
| ... | ... | @@ -167,26 +122,31 @@ pub const File = struct { |
| 167 | 122 | pub const SeekError = os.SeekError; |
| 168 | 123 | |
| 169 | 124 | /// Repositions read/write file offset relative to the current offset. |
| 125 | /// TODO: integrate with async I/O | |
| 170 | 126 | pub fn seekBy(self: File, offset: i64) SeekError!void { |
| 171 | 127 | return os.lseek_CUR(self.handle, offset); |
| 172 | 128 | } |
| 173 | 129 | |
| 174 | 130 | /// Repositions read/write file offset relative to the end. |
| 131 | /// TODO: integrate with async I/O | |
| 175 | 132 | pub fn seekFromEnd(self: File, offset: i64) SeekError!void { |
| 176 | 133 | return os.lseek_END(self.handle, offset); |
| 177 | 134 | } |
| 178 | 135 | |
| 179 | 136 | /// Repositions read/write file offset relative to the beginning. |
| 137 | /// TODO: integrate with async I/O | |
| 180 | 138 | pub fn seekTo(self: File, offset: u64) SeekError!void { |
| 181 | 139 | return os.lseek_SET(self.handle, offset); |
| 182 | 140 | } |
| 183 | 141 | |
| 184 | 142 | pub const GetPosError = os.SeekError || os.FStatError; |
| 185 | 143 | |
| 144 | /// TODO: integrate with async I/O | |
| 186 | 145 | pub fn getPos(self: File) GetPosError!u64 { |
| 187 | 146 | return os.lseek_CUR_get(self.handle); |
| 188 | 147 | } |
| 189 | 148 | |
| 149 | /// TODO: integrate with async I/O | |
| 190 | 150 | pub fn getEndPos(self: File) GetPosError!u64 { |
| 191 | 151 | if (builtin.os == .windows) { |
| 192 | 152 | return windows.GetFileSizeEx(self.handle); |
| ... | ... | @@ -196,6 +156,7 @@ pub const File = struct { |
| 196 | 156 | |
| 197 | 157 | pub const ModeError = os.FStatError; |
| 198 | 158 | |
| 159 | /// TODO: integrate with async I/O | |
| 199 | 160 | pub fn mode(self: File) ModeError!Mode { |
| 200 | 161 | if (builtin.os == .windows) { |
| 201 | 162 | return {}; |
| ... | ... | @@ -219,6 +180,7 @@ pub const File = struct { |
| 219 | 180 | |
| 220 | 181 | pub const StatError = os.FStatError; |
| 221 | 182 | |
| 183 | /// TODO: integrate with async I/O | |
| 222 | 184 | pub fn stat(self: File) StatError!Stat { |
| 223 | 185 | if (builtin.os == .windows) { |
| 224 | 186 | var io_status_block: windows.IO_STATUS_BLOCK = undefined; |
| ... | ... | @@ -233,7 +195,7 @@ pub const File = struct { |
| 233 | 195 | } |
| 234 | 196 | return Stat{ |
| 235 | 197 | .size = @bitCast(u64, info.StandardInformation.EndOfFile), |
| 236 | .mode = {}, | |
| 198 | .mode = 0, | |
| 237 | 199 | .atime = windows.fromSysTime(info.BasicInformation.LastAccessTime), |
| 238 | 200 | .mtime = windows.fromSysTime(info.BasicInformation.LastWriteTime), |
| 239 | 201 | .ctime = windows.fromSysTime(info.BasicInformation.CreationTime), |
| ... | ... | @@ -259,6 +221,7 @@ pub const File = struct { |
| 259 | 221 | /// and therefore this function cannot guarantee any precision will be stored. |
| 260 | 222 | /// Further, the maximum value is limited by the system ABI. When a value is provided |
| 261 | 223 | /// that exceeds this range, the value is clamped to the maximum. |
| 224 | /// TODO: integrate with async I/O | |
| 262 | 225 | pub fn updateTimes( |
| 263 | 226 | self: File, |
| 264 | 227 | /// access timestamp in nanoseconds |
| ... | ... | @@ -287,21 +250,61 @@ pub const File = struct { |
| 287 | 250 | pub const ReadError = os.ReadError; |
| 288 | 251 | |
| 289 | 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 | 256 | return os.read(self.handle, buffer); |
| 291 | 257 | } |
| 292 | 258 | |
| 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 | 280 | pub const WriteError = os.WriteError; |
| 294 | 281 | |
| 295 | 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 | 286 | return os.write(self.handle, bytes); |
| 297 | 287 | } |
| 298 | 288 | |
| 299 | pub fn writev_iovec(self: File, iovecs: []const os.iovec_const) WriteError!void { | |
| 300 | if (std.event.Loop.instance) |loop| { | |
| 301 | return std.event.fs.writevPosix(loop, self.handle, iovecs); | |
| 302 | } else { | |
| 303 | return os.writev(self.handle, iovecs); | |
| 289 | pub fn pwrite(self: File, bytes: []const u8, offset: u64) WriteError!void { | |
| 290 | if (need_async_thread and self.io_mode == .blocking and !self.async_block_allowed) { | |
| 291 | return std.event.Loop.instance.?.pwrite(self.handle, bytes, offset); | |
| 292 | } | |
| 293 | return os.pwrite(self.handle, bytes, offset); | |
| 294 | } | |
| 295 | ||
| 296 | pub fn writev(self: File, iovecs: []const os.iovec_const) WriteError!void { | |
| 297 | if (need_async_thread and self.io_mode == .blocking and !self.async_block_allowed) { | |
| 298 | return std.event.Loop.instance.?.writev(self.handle, iovecs); | |
| 299 | } | |
| 300 | return os.writev(self.handle, iovecs); | |
| 301 | } | |
| 302 | ||
| 303 | pub fn pwritev(self: File, iovecs: []const os.iovec_const, offset: usize) WriteError!void { | |
| 304 | if (need_async_thread and self.io_mode == .blocking and !self.async_block_allowed) { | |
| 305 | return std.event.Loop.instance.?.pwritev(self.handle, iovecs); | |
| 304 | 306 | } |
| 307 | return os.pwritev(self.handle, iovecs); | |
| 305 | 308 | } |
| 306 | 309 | |
| 307 | 310 | pub fn inStream(file: File) InStream { |
lib/std/fs/watch.zig created+675| ... | ... | @@ -0,0 +1,675 @@ |
| 1 | const std = @import("../std.zig"); | |
| 2 | const builtin = @import("builtin"); | |
| 3 | const event = std.event; | |
| 4 | const assert = std.debug.assert; | |
| 5 | const testing = std.testing; | |
| 6 | const os = std.os; | |
| 7 | const mem = std.mem; | |
| 8 | const windows = os.windows; | |
| 9 | const Loop = event.Loop; | |
| 10 | const fd_t = os.fd_t; | |
| 11 | const File = std.fs.File; | |
| 12 | const Allocator = mem.Allocator; | |
| 13 | ||
| 14 | const global_event_loop = Loop.instance orelse | |
| 15 | @compileError("std.fs.Watch currently only works with event-based I/O"); | |
| 16 | ||
| 17 | const WatchEventId = enum { | |
| 18 | CloseWrite, | |
| 19 | Delete, | |
| 20 | }; | |
| 21 | ||
| 22 | fn 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 | ||
| 28 | fn hashString(s: []const u16) u32 { | |
| 29 | return @truncate(u32, std.hash.Wyhash.hash(0, @sliceToBytes(s))); | |
| 30 | } | |
| 31 | ||
| 32 | const WatchEventError = error{ | |
| 33 | UserResourceLimitReached, | |
| 34 | SystemResources, | |
| 35 | AccessDenied, | |
| 36 | Unexpected, // TODO remove this possibility | |
| 37 | }; | |
| 38 | ||
| 39 | pub 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 | ||
| 615 | const test_tmp_dir = "std_event_fs_test"; | |
| 616 | ||
| 617 | test "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 | ||
| 629 | fn 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 | 47 | } |
| 48 | 48 | |
| 49 | 49 | pub fn getStdOut() File { |
| 50 | return File.openHandle(getStdOutHandle()); | |
| 50 | return File{ | |
| 51 | .handle = getStdOutHandle(), | |
| 52 | .io_mode = .blocking, | |
| 53 | }; | |
| 51 | 54 | } |
| 52 | 55 | |
| 53 | 56 | fn getStdErrHandle() os.fd_t { |
| ... | ... | @@ -63,7 +66,11 @@ fn getStdErrHandle() os.fd_t { |
| 63 | 66 | } |
| 64 | 67 | |
| 65 | 68 | pub 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 | } |
| 68 | 75 | |
| 69 | 76 | fn getStdInHandle() os.fd_t { |
| ... | ... | @@ -79,7 +86,10 @@ fn getStdInHandle() os.fd_t { |
| 79 | 86 | } |
| 80 | 87 | |
| 81 | 88 | pub fn getStdIn() File { |
| 82 | return File.openHandle(getStdInHandle()); | |
| 89 | return File{ | |
| 90 | .handle = getStdInHandle(), | |
| 91 | .io_mode = .blocking, | |
| 92 | }; | |
| 83 | 93 | } |
| 84 | 94 | |
| 85 | 95 | pub 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 | 9 | else |
| 10 | 10 | default_stack_size; |
| 11 | 11 | |
| 12 | /// TODO this is not integrated with evented I/O yet. | |
| 13 | /// https://github.com/ziglang/zig/issues/3557 | |
| 14 | 12 | pub fn OutStream(comptime WriteError: type) type { |
| 15 | 13 | return struct { |
| 16 | 14 | const Self = @This(); |
| 17 | 15 | pub const Error = WriteError; |
| 18 | // TODO https://github.com/ziglang/zig/issues/3557 | |
| 19 | pub const WriteFn = if (std.io.is_async and false) | |
| 16 | pub const WriteFn = if (std.io.is_async) | |
| 20 | 17 | async fn (self: *Self, bytes: []const u8) Error!void |
| 21 | 18 | else |
| 22 | 19 | fn (self: *Self, bytes: []const u8) Error!void; |
| ... | ... | @@ -24,8 +21,7 @@ pub fn OutStream(comptime WriteError: type) type { |
| 24 | 21 | writeFn: WriteFn, |
| 25 | 22 | |
| 26 | 23 | pub fn write(self: *Self, bytes: []const u8) Error!void { |
| 27 | // TODO https://github.com/ziglang/zig/issues/3557 | |
| 28 | if (std.io.is_async and false) { | |
| 24 | if (std.io.is_async) { | |
| 29 | 25 | // Let's not be writing 0xaa in safe modes for upwards of 4 MiB for every stream write. |
| 30 | 26 | @setRuntimeSafety(false); |
| 31 | 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 | 32 | } |
| 37 | 33 | |
| 38 | 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 | } |
| 41 | 37 | |
| 42 | 38 | pub fn writeByte(self: *Self, byte: u8) Error!void { |
| 43 | const slice = @as(*const [1]u8, &byte)[0..]; | |
| 44 | return self.writeFn(self, slice); | |
| 39 | const array = [1]u8{byte}; | |
| 40 | return self.write(&array); | |
| 45 | 41 | } |
| 46 | 42 | |
| 47 | 43 | pub fn writeByteNTimes(self: *Self, byte: u8, n: usize) Error!void { |
| ... | ... | @@ -51,7 +47,7 @@ pub fn OutStream(comptime WriteError: type) type { |
| 51 | 47 | var remaining: usize = n; |
| 52 | 48 | while (remaining > 0) { |
| 53 | 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 | 51 | remaining -= to_write; |
| 56 | 52 | } |
| 57 | 53 | } |
| ... | ... | @@ -60,32 +56,32 @@ pub fn OutStream(comptime WriteError: type) type { |
| 60 | 56 | pub fn writeIntNative(self: *Self, comptime T: type, value: T) Error!void { |
| 61 | 57 | var bytes: [(T.bit_count + 7) / 8]u8 = undefined; |
| 62 | 58 | mem.writeIntNative(T, &bytes, value); |
| 63 | return self.writeFn(self, &bytes); | |
| 59 | return self.write(&bytes); | |
| 64 | 60 | } |
| 65 | 61 | |
| 66 | 62 | /// Write a foreign-endian integer. |
| 67 | 63 | pub fn writeIntForeign(self: *Self, comptime T: type, value: T) Error!void { |
| 68 | 64 | var bytes: [(T.bit_count + 7) / 8]u8 = undefined; |
| 69 | 65 | mem.writeIntForeign(T, &bytes, value); |
| 70 | return self.writeFn(self, &bytes); | |
| 66 | return self.write(&bytes); | |
| 71 | 67 | } |
| 72 | 68 | |
| 73 | 69 | pub fn writeIntLittle(self: *Self, comptime T: type, value: T) Error!void { |
| 74 | 70 | var bytes: [(T.bit_count + 7) / 8]u8 = undefined; |
| 75 | 71 | mem.writeIntLittle(T, &bytes, value); |
| 76 | return self.writeFn(self, &bytes); | |
| 72 | return self.write(&bytes); | |
| 77 | 73 | } |
| 78 | 74 | |
| 79 | 75 | pub fn writeIntBig(self: *Self, comptime T: type, value: T) Error!void { |
| 80 | 76 | var bytes: [(T.bit_count + 7) / 8]u8 = undefined; |
| 81 | 77 | mem.writeIntBig(T, &bytes, value); |
| 82 | return self.writeFn(self, &bytes); | |
| 78 | return self.write(&bytes); | |
| 83 | 79 | } |
| 84 | 80 | |
| 85 | 81 | pub fn writeInt(self: *Self, comptime T: type, value: T, endian: builtin.Endian) Error!void { |
| 86 | 82 | var bytes: [(T.bit_count + 7) / 8]u8 = undefined; |
| 87 | 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 | 18 | |
| 19 | 19 | /// Node inside the linked list wrapping the actual data. |
| 20 | 20 | pub const Node = struct { |
| 21 | next: ?*Node, | |
| 21 | next: ?*Node = null, | |
| 22 | 22 | data: T, |
| 23 | 23 | |
| 24 | 24 | pub fn init(data: T) Node { |
| 25 | 25 | return Node{ |
| 26 | .next = null, | |
| 27 | 26 | .data = data, |
| 28 | 27 | }; |
| 29 | 28 | } |
| ... | ... | @@ -196,14 +195,12 @@ pub fn TailQueue(comptime T: type) type { |
| 196 | 195 | |
| 197 | 196 | /// Node inside the linked list wrapping the actual data. |
| 198 | 197 | pub const Node = struct { |
| 199 | prev: ?*Node, | |
| 200 | next: ?*Node, | |
| 198 | prev: ?*Node = null, | |
| 199 | next: ?*Node = null, | |
| 201 | 200 | data: T, |
| 202 | 201 | |
| 203 | 202 | pub fn init(data: T) Node { |
| 204 | 203 | return Node{ |
| 205 | .prev = null, | |
| 206 | .next = null, | |
| 207 | 204 | .data = data, |
| 208 | 205 | }; |
| 209 | 206 | } |
lib/std/net.zig+11-5| ... | ... | @@ -271,7 +271,7 @@ pub const Address = extern union { |
| 271 | 271 | options: std.fmt.FormatOptions, |
| 272 | 272 | context: var, |
| 273 | 273 | comptime Errors: type, |
| 274 | output: fn (@TypeOf(context), []const u8) Errors!void, | |
| 274 | comptime output: fn (@TypeOf(context), []const u8) Errors!void, | |
| 275 | 275 | ) !void { |
| 276 | 276 | switch (self.any.family) { |
| 277 | 277 | os.AF_INET => { |
| ... | ... | @@ -361,7 +361,7 @@ pub const Address = extern union { |
| 361 | 361 | }; |
| 362 | 362 | |
| 363 | 363 | pub 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 | 365 | const sockfd = try os.socket( |
| 366 | 366 | os.AF_UNIX, |
| 367 | 367 | os.SOCK_STREAM | os.SOCK_CLOEXEC | opt_non_block, |
| ... | ... | @@ -377,7 +377,10 @@ pub fn connectUnixSocket(path: []const u8) !fs.File { |
| 377 | 377 | addr.getOsSockLen(), |
| 378 | 378 | ); |
| 379 | 379 | |
| 380 | return fs.File.openHandle(sockfd); | |
| 380 | return fs.File{ | |
| 381 | .handle = sockfd, | |
| 382 | .io_mode = std.io.mode, | |
| 383 | }; | |
| 381 | 384 | } |
| 382 | 385 | |
| 383 | 386 | pub const AddressList = struct { |
| ... | ... | @@ -412,7 +415,7 @@ pub fn tcpConnectToAddress(address: Address) !fs.File { |
| 412 | 415 | errdefer os.close(sockfd); |
| 413 | 416 | try os.connect(sockfd, &address.any, address.getOsSockLen()); |
| 414 | 417 | |
| 415 | return fs.File{ .handle = sockfd }; | |
| 418 | return fs.File{ .handle = sockfd, .io_mode = std.io.mode }; | |
| 416 | 419 | } |
| 417 | 420 | |
| 418 | 421 | /// Call `AddressList.deinit` on the result. |
| ... | ... | @@ -1379,7 +1382,10 @@ pub const StreamServer = struct { |
| 1379 | 1382 | var adr_len: os.socklen_t = @sizeOf(Address); |
| 1380 | 1383 | if (os.accept4(self.sockfd.?, &accepted_addr.any, &adr_len, accept_flags)) |fd| { |
| 1381 | 1384 | return Connection{ |
| 1382 | .file = fs.File.openHandle(fd), | |
| 1385 | .file = fs.File{ | |
| 1386 | .handle = fd, | |
| 1387 | .io_mode = std.io.mode, | |
| 1388 | }, | |
| 1383 | 1389 | .address = accepted_addr, |
| 1384 | 1390 | }; |
| 1385 | 1391 | } else |err| switch (err) { |
lib/std/net/test.zig+3-5| ... | ... | @@ -81,17 +81,15 @@ test "resolve DNS" { |
| 81 | 81 | } |
| 82 | 82 | |
| 83 | 83 | test "listen on a port, send bytes, receive bytes" { |
| 84 | if (!std.io.is_async) return error.SkipZigTest; | |
| 85 | ||
| 84 | 86 | if (std.builtin.os != .linux) { |
| 85 | 87 | // TODO build abstractions for other operating systems |
| 86 | 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 | } | |
| 92 | 90 | |
| 93 | 91 | // 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); | |
| 95 | 93 | |
| 96 | 94 | var server = net.StreamServer.init(net.StreamServer.Options{}); |
| 97 | 95 | defer server.deinit(); |
lib/std/os.zig+193-20| ... | ... | @@ -169,7 +169,12 @@ fn getRandomBytesDevURandom(buf: []u8) !void { |
| 169 | 169 | return error.NoDevice; |
| 170 | 170 | } |
| 171 | 171 | |
| 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 | 178 | stream.readNoEof(buf) catch return error.Unexpected; |
| 174 | 179 | } |
| 175 | 180 | |
| ... | ... | @@ -293,7 +298,7 @@ pub const ReadError = error{ |
| 293 | 298 | /// via the event loop. Otherwise EAGAIN results in error.WouldBlock. |
| 294 | 299 | pub fn read(fd: fd_t, buf: []u8) ReadError!usize { |
| 295 | 300 | if (builtin.os == .windows) { |
| 296 | return windows.ReadFile(fd, buf); | |
| 301 | return windows.ReadFile(fd, buf, null); | |
| 297 | 302 | } |
| 298 | 303 | |
| 299 | 304 | if (builtin.os == .wasi and !builtin.link_libc) { |
| ... | ... | @@ -335,9 +340,37 @@ pub fn read(fd: fd_t, buf: []u8) ReadError!usize { |
| 335 | 340 | } |
| 336 | 341 | |
| 337 | 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 handled | |
| 339 | /// via the event loop. Otherwise EAGAIN results in error.WouldBlock. | |
| 343 | /// | |
| 344 | /// For POSIX systems, if the application has a global event loop enabled, EAGAIN is handled | |
| 345 | /// via the event loop. Otherwise EAGAIN results in `error.WouldBlock`. | |
| 346 | /// On Windows, if the application has a global event loop enabled, I/O Completion Ports are | |
| 347 | /// used to perform the I/O. `error.WouldBlock` is not possible on Windows. | |
| 348 | /// | |
| 349 | /// This operation is non-atomic on the following systems: | |
| 350 | /// * Windows | |
| 351 | /// On these systems, the read races with concurrent writes to the same file descriptor. | |
| 340 | 352 | pub 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 | 374 | while (true) { |
| 342 | 375 | // TODO handle the case when iov_len is too large and get rid of this @intCast |
| 343 | 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 | 396 | } |
| 364 | 397 | |
| 365 | 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 handled | |
| 367 | /// via the event loop. Otherwise EAGAIN results in error.WouldBlock. | |
| 399 | /// | |
| 400 | /// Retries when interrupted by a signal. | |
| 401 | /// | |
| 402 | /// For POSIX systems, if the application has a global event loop enabled, EAGAIN is handled | |
| 403 | /// via the event loop. Otherwise EAGAIN results in `error.WouldBlock`. | |
| 404 | /// On Windows, if the application has a global event loop enabled, I/O Completion Ports are | |
| 405 | /// used to perform the I/O. `error.WouldBlock` is not possible on Windows. | |
| 406 | pub 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. | |
| 368 | 449 | pub fn preadv(fd: fd_t, iov: []const iovec, offset: u64) ReadError!usize { |
| 369 | 450 | if (comptime std.Target.current.isDarwin()) { |
| 370 | 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 | 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 | 515 | while (true) { |
| 413 | 516 | // TODO handle the case when iov_len is too large and get rid of this @intCast |
| 414 | 517 | const rc = system.preadv(fd, iov.ptr, @intCast(u32, iov.len), offset); |
| ... | ... | @@ -451,11 +554,9 @@ pub const WriteError = error{ |
| 451 | 554 | /// Write to a file descriptor. Keeps trying if it gets interrupted. |
| 452 | 555 | /// If the application has a global event loop enabled, EAGAIN is handled |
| 453 | 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. | |
| 456 | 557 | pub fn write(fd: fd_t, bytes: []const u8) WriteError!void { |
| 457 | 558 | if (builtin.os == .windows) { |
| 458 | return windows.WriteFile(fd, bytes); | |
| 559 | return windows.WriteFile(fd, bytes, null); | |
| 459 | 560 | } |
| 460 | 561 | |
| 461 | 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 | 589 | EINTR => continue, |
| 489 | 590 | EINVAL => unreachable, |
| 490 | 591 | EFAULT => unreachable, |
| 491 | // TODO https://github.com/ziglang/zig/issues/3557 | |
| 492 | EAGAIN => return error.WouldBlock, | |
| 493 | //EAGAIN => if (std.event.Loop.instance) |loop| { | |
| 494 | // loop.waitUntilFdWritable(fd); | |
| 495 | // continue; | |
| 496 | //} else { | |
| 497 | // return error.WouldBlock; | |
| 498 | //}, | |
| 592 | EAGAIN => if (std.event.Loop.instance) |loop| { | |
| 593 | loop.waitUntilFdWritable(fd); | |
| 594 | continue; | |
| 595 | } else { | |
| 596 | return error.WouldBlock; | |
| 597 | }, | |
| 499 | 598 | EBADF => unreachable, // Always a race condition. |
| 500 | 599 | EDESTADDRREQ => unreachable, // `connect` was never called. |
| 501 | 600 | EDQUOT => return error.DiskQuota, |
| ... | ... | @@ -540,8 +639,57 @@ pub fn writev(fd: fd_t, iov: []const iovec_const) WriteError!void { |
| 540 | 639 | } |
| 541 | 640 | } |
| 542 | 641 | |
| 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. | |
| 650 | pub 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 | 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. | |
| 545 | 693 | pub fn pwritev(fd: fd_t, iov: []const iovec_const, offset: u64) WriteError!void { |
| 546 | 694 | if (comptime std.Target.current.isDarwin()) { |
| 547 | 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 | 737 | } |
| 590 | 738 | } |
| 591 | 739 | |
| 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 | 749 | while (true) { |
| 593 | 750 | // TODO handle the case when iov_len is too large and get rid of this @intCast |
| 594 | 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 | 851 | /// Open and possibly create a file. Keeps trying if it gets interrupted. |
| 695 | 852 | /// `file_path` is relative to the open directory handle `dir_fd`. |
| 696 | 853 | /// See also `openatC`. |
| 697 | pub fn openat(dir_fd: fd_t, file_path: []const u8, flags: u32, mode: usize) OpenError!fd_t { | |
| 854 | pub fn openat(dir_fd: fd_t, file_path: []const u8, flags: u32, mode: mode_t) OpenError!fd_t { | |
| 698 | 855 | const file_path_c = try toPosixPath(file_path); |
| 699 | 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 | 859 | /// Open and possibly create a file. Keeps trying if it gets interrupted. |
| 703 | 860 | /// `file_path` is relative to the open directory handle `dir_fd`. |
| 704 | 861 | /// See also `openat`. |
| 705 | pub fn openatC(dir_fd: fd_t, file_path: [*:0]const u8, flags: u32, mode: usize) OpenError!fd_t { | |
| 862 | pub fn openatC(dir_fd: fd_t, file_path: [*:0]const u8, flags: u32, mode: mode_t) OpenError!fd_t { | |
| 706 | 863 | while (true) { |
| 707 | 864 | const rc = system.openat(dir_fd, file_path, flags, mode); |
| 708 | 865 | switch (errno(rc)) { |
| ... | ... | @@ -2372,6 +2529,22 @@ pub fn pipe() PipeError![2]fd_t { |
| 2372 | 2529 | } |
| 2373 | 2530 | |
| 2374 | 2531 | pub 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 | 2548 | var fds: [2]fd_t = undefined; |
| 2376 | 2549 | switch (errno(system.pipe2(&fds, flags))) { |
| 2377 | 2550 | 0 => return fds, |
lib/std/os/bits/darwin.zig+159| ... | ... | @@ -4,6 +4,7 @@ const maxInt = std.math.maxInt; |
| 4 | 4 | |
| 5 | 5 | pub const fd_t = c_int; |
| 6 | 6 | pub const pid_t = c_int; |
| 7 | pub const mode_t = c_uint; | |
| 7 | 8 | |
| 8 | 9 | pub const in_port_t = u16; |
| 9 | 10 | pub const sa_family_t = u8; |
| ... | ... | @@ -1223,3 +1224,161 @@ pub const RTLD_NEXT = @intToPtr(*c_void, ~maxInt(usize)); |
| 1223 | 1224 | pub const RTLD_DEFAULT = @intToPtr(*c_void, ~maxInt(usize) - 1); |
| 1224 | 1225 | pub const RTLD_SELF = @intToPtr(*c_void, ~maxInt(usize) - 2); |
| 1225 | 1226 | pub const RTLD_MAIN_ONLY = @intToPtr(*c_void, ~maxInt(usize) - 4); |
| 1227 | ||
| 1228 | /// duplicate file descriptor | |
| 1229 | pub const F_DUPFD = 0; | |
| 1230 | ||
| 1231 | /// get file descriptor flags | |
| 1232 | pub const F_GETFD = 1; | |
| 1233 | ||
| 1234 | /// set file descriptor flags | |
| 1235 | pub const F_SETFD = 2; | |
| 1236 | ||
| 1237 | /// get file status flags | |
| 1238 | pub const F_GETFL = 3; | |
| 1239 | ||
| 1240 | /// set file status flags | |
| 1241 | pub const F_SETFL = 4; | |
| 1242 | ||
| 1243 | /// get SIGIO/SIGURG proc/pgrp | |
| 1244 | pub const F_GETOWN = 5; | |
| 1245 | ||
| 1246 | /// set SIGIO/SIGURG proc/pgrp | |
| 1247 | pub const F_SETOWN = 6; | |
| 1248 | ||
| 1249 | /// get record locking information | |
| 1250 | pub const F_GETLK = 7; | |
| 1251 | ||
| 1252 | /// set record locking information | |
| 1253 | pub const F_SETLK = 8; | |
| 1254 | ||
| 1255 | /// F_SETLK; wait if blocked | |
| 1256 | pub const F_SETLKW = 9; | |
| 1257 | ||
| 1258 | /// F_SETLK; wait if blocked, return on timeout | |
| 1259 | pub const F_SETLKWTIMEOUT = 10; | |
| 1260 | pub const F_FLUSH_DATA = 40; | |
| 1261 | ||
| 1262 | /// Used for regression test | |
| 1263 | pub const F_CHKCLEAN = 41; | |
| 1264 | ||
| 1265 | /// Preallocate storage | |
| 1266 | pub const F_PREALLOCATE = 42; | |
| 1267 | ||
| 1268 | /// Truncate a file without zeroing space | |
| 1269 | pub const F_SETSIZE = 43; | |
| 1270 | ||
| 1271 | /// Issue an advisory read async with no copy to user | |
| 1272 | pub const F_RDADVISE = 44; | |
| 1273 | ||
| 1274 | /// turn read ahead off/on for this fd | |
| 1275 | pub const F_RDAHEAD = 45; | |
| 1276 | ||
| 1277 | /// turn data caching off/on for this fd | |
| 1278 | pub const F_NOCACHE = 48; | |
| 1279 | ||
| 1280 | /// file offset to device offset | |
| 1281 | pub const F_LOG2PHYS = 49; | |
| 1282 | ||
| 1283 | /// return the full path of the fd | |
| 1284 | pub const F_GETPATH = 50; | |
| 1285 | ||
| 1286 | /// fsync + ask the drive to flush to the media | |
| 1287 | pub const F_FULLFSYNC = 51; | |
| 1288 | ||
| 1289 | /// find which component (if any) is a package | |
| 1290 | pub const F_PATHPKG_CHECK = 52; | |
| 1291 | ||
| 1292 | /// "freeze" all fs operations | |
| 1293 | pub const F_FREEZE_FS = 53; | |
| 1294 | ||
| 1295 | /// "thaw" all fs operations | |
| 1296 | pub const F_THAW_FS = 54; | |
| 1297 | ||
| 1298 | /// turn data caching off/on (globally) for this file | |
| 1299 | pub const F_GLOBAL_NOCACHE = 55; | |
| 1300 | ||
| 1301 | /// add detached signatures | |
| 1302 | pub const F_ADDSIGS = 59; | |
| 1303 | ||
| 1304 | /// add signature from same file (used by dyld for shared libs) | |
| 1305 | pub 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) | |
| 1309 | pub const F_NODIRECT = 62; | |
| 1310 | ||
| 1311 | ///Get the protection class of a file from the EA, returns int | |
| 1312 | pub const F_GETPROTECTIONCLASS = 63; | |
| 1313 | ||
| 1314 | ///Set the protection class of a file for the EA, requires int | |
| 1315 | pub const F_SETPROTECTIONCLASS = 64; | |
| 1316 | ||
| 1317 | ///file offset to device offset, extended | |
| 1318 | pub const F_LOG2PHYS_EXT = 65; | |
| 1319 | ||
| 1320 | ///get record locking information, per-process | |
| 1321 | pub const F_GETLKPID = 66; | |
| 1322 | ||
| 1323 | ///Mark the file as being the backing store for another filesystem | |
| 1324 | pub const F_SETBACKINGSTORE = 70; | |
| 1325 | ||
| 1326 | ///return the full path of the FD, but error in specific mtmd circumstances | |
| 1327 | pub const F_GETPATH_MTMINFO = 71; | |
| 1328 | ||
| 1329 | ///Returns the code directory, with associated hashes, to the caller | |
| 1330 | pub const F_GETCODEDIR = 72; | |
| 1331 | ||
| 1332 | ///No SIGPIPE generated on EPIPE | |
| 1333 | pub const F_SETNOSIGPIPE = 73; | |
| 1334 | ||
| 1335 | ///Status of SIGPIPE for this fd | |
| 1336 | pub const F_GETNOSIGPIPE = 74; | |
| 1337 | ||
| 1338 | ///For some cases, we need to rewrap the key for AKS/MKB | |
| 1339 | pub 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 | |
| 1343 | pub const F_SINGLE_WRITER = 76; | |
| 1344 | ||
| 1345 | ///Get the protection version number for this filesystem | |
| 1346 | pub const F_GETPROTECTIONLEVEL = 77; | |
| 1347 | ||
| 1348 | ///Add detached code signatures (used by dyld for shared libs) | |
| 1349 | pub const F_FINDSIGS = 78; | |
| 1350 | ||
| 1351 | ///Add signature from same file, only if it is signed by Apple (used by dyld for simulator) | |
| 1352 | pub const F_ADDFILESIGS_FOR_DYLD_SIM = 83; | |
| 1353 | ||
| 1354 | ///fsync + issue barrier to drive | |
| 1355 | pub const F_BARRIERFSYNC = 85; | |
| 1356 | ||
| 1357 | ///Add signature from same file, return end offset in structure on success | |
| 1358 | pub const F_ADDFILESIGS_RETURN = 97; | |
| 1359 | ||
| 1360 | ///Check if Library Validation allows this Mach-O file to be mapped into the calling process | |
| 1361 | pub const F_CHECK_LV = 98; | |
| 1362 | ||
| 1363 | ///Deallocate a range of the file | |
| 1364 | pub const F_PUNCHHOLE = 99; | |
| 1365 | ||
| 1366 | ///Trim an active file | |
| 1367 | pub const F_TRIM_ACTIVE_FILE = 100; | |
| 1368 | ||
| 1369 | pub const FCNTL_FS_SPECIFIC_BASE = 0x00010000; | |
| 1370 | ||
| 1371 | ///mark the dup with FD_CLOEXEC | |
| 1372 | pub const F_DUPFD_CLOEXEC = 67; | |
| 1373 | ||
| 1374 | ///close-on-exec flag | |
| 1375 | pub const FD_CLOEXEC = 1; | |
| 1376 | ||
| 1377 | /// shared or read lock | |
| 1378 | pub const F_RDLCK = 1; | |
| 1379 | ||
| 1380 | /// unlock | |
| 1381 | pub const F_UNLCK = 2; | |
| 1382 | ||
| 1383 | /// exclusive or write lock | |
| 1384 | pub const F_WRLCK = 3; |
lib/std/os/bits/dragonfly.zig+1| ... | ... | @@ -7,6 +7,7 @@ pub fn S_ISCHR(m: u32) bool { |
| 7 | 7 | pub const fd_t = c_int; |
| 8 | 8 | pub const pid_t = c_int; |
| 9 | 9 | pub const off_t = c_long; |
| 10 | pub const mode_t = c_uint; | |
| 10 | 11 | |
| 11 | 12 | pub const ENOTSUP = EOPNOTSUPP; |
| 12 | 13 | pub const EWOULDBLOCK = EAGAIN; |
lib/std/os/bits/freebsd.zig+1| ... | ... | @@ -3,6 +3,7 @@ const maxInt = std.math.maxInt; |
| 3 | 3 | |
| 4 | 4 | pub const fd_t = c_int; |
| 5 | 5 | pub const pid_t = c_int; |
| 6 | pub const mode_t = c_uint; | |
| 6 | 7 | |
| 7 | 8 | pub const socklen_t = u32; |
| 8 | 9 |
lib/std/os/bits/linux/x86_64.zig+2| ... | ... | @@ -12,6 +12,8 @@ const socklen_t = linux.socklen_t; |
| 12 | 12 | const iovec = linux.iovec; |
| 13 | 13 | const iovec_const = linux.iovec_const; |
| 14 | 14 | |
| 15 | pub const mode_t = usize; | |
| 16 | ||
| 15 | 17 | pub const SYS_read = 0; |
| 16 | 18 | pub const SYS_write = 1; |
| 17 | 19 | pub const SYS_open = 2; |
lib/std/os/bits/netbsd.zig+1| ... | ... | @@ -3,6 +3,7 @@ const maxInt = std.math.maxInt; |
| 3 | 3 | |
| 4 | 4 | pub const fd_t = c_int; |
| 5 | 5 | pub const pid_t = c_int; |
| 6 | pub const mode_t = c_uint; | |
| 6 | 7 | |
| 7 | 8 | /// Renamed from `kevent` to `Kevent` to avoid conflict with function name. |
| 8 | 9 | pub const Kevent = extern struct { |
lib/std/os/bits/wasi.zig+1| ... | ... | @@ -130,6 +130,7 @@ pub const EVENTTYPE_FD_WRITE: eventtype_t = 2; |
| 130 | 130 | pub const exitcode_t = u32; |
| 131 | 131 | |
| 132 | 132 | pub const fd_t = u32; |
| 133 | pub const mode_t = u32; | |
| 133 | 134 | |
| 134 | 135 | pub const fdflags_t = u16; |
| 135 | 136 | pub 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 | 5 | |
| 6 | 6 | pub const fd_t = HANDLE; |
| 7 | 7 | pub const pid_t = HANDLE; |
| 8 | pub const mode_t = u0; | |
| 8 | 9 | |
| 9 | 10 | pub const PATH_MAX = 260; |
| 10 | 11 |
lib/std/os/windows.zig+128-29| ... | ... | @@ -344,24 +344,77 @@ pub fn FindClose(hFindFile: HANDLE) void { |
| 344 | 344 | assert(kernel32.FindClose(hFindFile) != 0); |
| 345 | 345 | } |
| 346 | 346 | |
| 347 | pub const ReadFileError = error{Unexpected}; | |
| 348 | ||
| 349 | pub fn ReadFile(in_hFile: HANDLE, buffer: []u8) ReadFileError!usize { | |
| 350 | var index: usize = 0; | |
| 351 | while (index < buffer.len) { | |
| 352 | const want_read_count = @intCast(DWORD, math.min(@as(DWORD, maxInt(DWORD)), buffer.len - index)); | |
| 353 | var amt_read: DWORD = undefined; | |
| 354 | if (kernel32.ReadFile(in_hFile, buffer.ptr + index, want_read_count, &amt_read, null) == 0) { | |
| 355 | switch (kernel32.GetLastError()) { | |
| 356 | .OPERATION_ABORTED => continue, | |
| 357 | .BROKEN_PIPE => return index, | |
| 358 | else => |err| return unexpectedError(err), | |
| 347 | pub const ReadFileError = error{ | |
| 348 | OperationAborted, | |
| 349 | BrokenPipe, | |
| 350 | Unexpected, | |
| 351 | }; | |
| 352 | ||
| 353 | /// If buffer's length exceeds what a Windows DWORD integer can hold, it will be broken into | |
| 354 | /// multiple non-atomic reads. | |
| 355 | pub fn ReadFile(in_hFile: HANDLE, buffer: []u8, offset: ?u64) ReadFileError!usize { | |
| 356 | if (std.event.Loop.instance) |loop| { | |
| 357 | // TODO support async ReadFile with no offset | |
| 358 | const off = offset.?; | |
| 359 | var resume_node = std.event.Loop.ResumeNode.Basic{ | |
| 360 | .base = .{ | |
| 361 | .id = .Basic, | |
| 362 | .handle = @frame(), | |
| 363 | .overlapped = OVERLAPPED{ | |
| 364 | .Internal = 0, | |
| 365 | .InternalHigh = 0, | |
| 366 | .Offset = @truncate(u32, off), | |
| 367 | .OffsetHigh = @truncate(u32, off >> 32), | |
| 368 | .hEvent = null, | |
| 369 | }, | |
| 370 | }, | |
| 371 | }; | |
| 372 | // TODO only call create io completion port once per fd | |
| 373 | _ = windows.CreateIoCompletionPort(fd, loop.os_data.io_port, undefined, undefined) catch undefined; | |
| 374 | loop.beginOneEvent(); | |
| 375 | suspend { | |
| 376 | // TODO handle buffer bigger than DWORD can hold | |
| 377 | _ = windows.kernel32.ReadFile(fd, buffer.ptr, @intCast(windows.DWORD, buffer.len), null, &resume_node.base.overlapped); | |
| 378 | } | |
| 379 | var bytes_transferred: windows.DWORD = undefined; | |
| 380 | if (windows.kernel32.GetOverlappedResult(fd, &resume_node.base.overlapped, &bytes_transferred, windows.FALSE) == 0) { | |
| 381 | switch (windows.kernel32.GetLastError()) { | |
| 382 | .IO_PENDING => unreachable, | |
| 383 | .OPERATION_ABORTED => return error.OperationAborted, | |
| 384 | .BROKEN_PIPE => return error.BrokenPipe, | |
| 385 | .HANDLE_EOF => return @as(usize, bytes_transferred), | |
| 386 | else => |err| return windows.unexpectedError(err), | |
| 387 | } | |
| 388 | } | |
| 389 | return @as(usize, bytes_transferred); | |
| 390 | } else { | |
| 391 | var index: usize = 0; | |
| 392 | while (index < buffer.len) { | |
| 393 | const want_read_count = @intCast(DWORD, math.min(@as(DWORD, maxInt(DWORD)), buffer.len - index)); | |
| 394 | var amt_read: DWORD = undefined; | |
| 395 | var overlapped_data: OVERLAPPED = undefined; | |
| 396 | const overlapped: ?*OVERLAPPED = if (offset) |off| blk: { | |
| 397 | overlapped_data = .{ | |
| 398 | .Internal = 0, | |
| 399 | .InternalHigh = 0, | |
| 400 | .Offset = @truncate(u32, off + index), | |
| 401 | .OffsetHigh = @truncate(u32, (off + index) >> 32), | |
| 402 | .hEvent = null, | |
| 403 | }; | |
| 404 | break :blk &overlapped_data; | |
| 405 | } else null; | |
| 406 | if (kernel32.ReadFile(in_hFile, buffer.ptr + index, want_read_count, &amt_read, overlapped) == 0) { | |
| 407 | switch (kernel32.GetLastError()) { | |
| 408 | .OPERATION_ABORTED => continue, | |
| 409 | .BROKEN_PIPE => return index, | |
| 410 | else => |err| return unexpectedError(err), | |
| 411 | } | |
| 359 | 412 | } |
| 413 | if (amt_read == 0) return index; | |
| 414 | index += amt_read; | |
| 360 | 415 | } |
| 361 | if (amt_read == 0) return index; | |
| 362 | index += amt_read; | |
| 416 | return index; | |
| 363 | 417 | } |
| 364 | return index; | |
| 365 | 418 | } |
| 366 | 419 | |
| 367 | 420 | pub const WriteFileError = error{ |
| ... | ... | @@ -371,20 +424,66 @@ pub const WriteFileError = error{ |
| 371 | 424 | Unexpected, |
| 372 | 425 | }; |
| 373 | 426 | |
| 374 | /// This function is for blocking file descriptors only. For non-blocking, see | |
| 375 | /// `WriteFileAsync`. | |
| 376 | pub fn WriteFile(handle: HANDLE, bytes: []const u8) WriteFileError!void { | |
| 377 | var bytes_written: DWORD = undefined; | |
| 378 | // TODO replace this @intCast with a loop that writes all the bytes | |
| 379 | if (kernel32.WriteFile(handle, bytes.ptr, @intCast(u32, bytes.len), &bytes_written, null) == 0) { | |
| 380 | switch (kernel32.GetLastError()) { | |
| 381 | .INVALID_USER_BUFFER => return error.SystemResources, | |
| 382 | .NOT_ENOUGH_MEMORY => return error.SystemResources, | |
| 383 | .OPERATION_ABORTED => return error.OperationAborted, | |
| 384 | .NOT_ENOUGH_QUOTA => return error.SystemResources, | |
| 385 | .IO_PENDING => unreachable, // this function is for blocking files only | |
| 386 | .BROKEN_PIPE => return error.BrokenPipe, | |
| 387 | else => |err| return unexpectedError(err), | |
| 427 | pub fn WriteFile(handle: HANDLE, bytes: []const u8, offset: ?u64) WriteFileError!void { | |
| 428 | if (std.event.Loop.instance) |loop| { | |
| 429 | // TODO support async WriteFile with no offset | |
| 430 | const off = offset.?; | |
| 431 | var resume_node = std.event.Loop.ResumeNode.Basic{ | |
| 432 | .base = .{ | |
| 433 | .id = .Basic, | |
| 434 | .handle = @frame(), | |
| 435 | .overlapped = OVERLAPPED{ | |
| 436 | .Internal = 0, | |
| 437 | .InternalHigh = 0, | |
| 438 | .Offset = @truncate(u32, off), | |
| 439 | .OffsetHigh = @truncate(u32, off >> 32), | |
| 440 | .hEvent = null, | |
| 441 | }, | |
| 442 | }, | |
| 443 | }; | |
| 444 | // TODO only call create io completion port once per fd | |
| 445 | _ = CreateIoCompletionPort(fd, loop.os_data.io_port, undefined, undefined); | |
| 446 | loop.beginOneEvent(); | |
| 447 | suspend { | |
| 448 | // TODO replace this @intCast with a loop that writes all the bytes | |
| 449 | _ = kernel32.WriteFile(fd, bytes.ptr, @intCast(windows.DWORD, bytes.len), null, &resume_node.base.overlapped); | |
| 450 | } | |
| 451 | var bytes_transferred: windows.DWORD = undefined; | |
| 452 | if (kernel32.GetOverlappedResult(fd, &resume_node.base.overlapped, &bytes_transferred, FALSE) == 0) { | |
| 453 | switch (kernel32.GetLastError()) { | |
| 454 | .IO_PENDING => unreachable, | |
| 455 | .INVALID_USER_BUFFER => return error.SystemResources, | |
| 456 | .NOT_ENOUGH_MEMORY => return error.SystemResources, | |
| 457 | .OPERATION_ABORTED => return error.OperationAborted, | |
| 458 | .NOT_ENOUGH_QUOTA => return error.SystemResources, | |
| 459 | .BROKEN_PIPE => return error.BrokenPipe, | |
| 460 | else => |err| return windows.unexpectedError(err), | |
| 461 | } | |
| 462 | } | |
| 463 | } else { | |
| 464 | var bytes_written: DWORD = undefined; | |
| 465 | var overlapped_data: OVERLAPPED = undefined; | |
| 466 | const overlapped: ?*OVERLAPPED = if (offset) |off| blk: { | |
| 467 | overlapped_data = .{ | |
| 468 | .Internal = 0, | |
| 469 | .InternalHigh = 0, | |
| 470 | .Offset = @truncate(u32, off), | |
| 471 | .OffsetHigh = @truncate(u32, off >> 32), | |
| 472 | .hEvent = null, | |
| 473 | }; | |
| 474 | break :blk &overlapped_data; | |
| 475 | } else null; | |
| 476 | // TODO replace this @intCast with a loop that writes all the bytes | |
| 477 | if (kernel32.WriteFile(handle, bytes.ptr, @intCast(u32, bytes.len), &bytes_written, overlapped) == 0) { | |
| 478 | switch (kernel32.GetLastError()) { | |
| 479 | .INVALID_USER_BUFFER => return error.SystemResources, | |
| 480 | .NOT_ENOUGH_MEMORY => return error.SystemResources, | |
| 481 | .OPERATION_ABORTED => return error.OperationAborted, | |
| 482 | .NOT_ENOUGH_QUOTA => return error.SystemResources, | |
| 483 | .IO_PENDING => unreachable, // this function is for blocking files only | |
| 484 | .BROKEN_PIPE => return error.BrokenPipe, | |
| 485 | else => |err| return unexpectedError(err), | |
| 486 | } | |
| 388 | 487 | } |
| 389 | 488 | } |
| 390 | 489 | } |
lib/std/special/test_runner.zig+25-1| ... | ... | @@ -2,6 +2,8 @@ const std = @import("std"); |
| 2 | 2 | const io = std.io; |
| 3 | 3 | const builtin = @import("builtin"); |
| 4 | 4 | |
| 5 | pub const io_mode: io.Mode = builtin.test_io_mode; | |
| 6 | ||
| 5 | 7 | pub fn main() anyerror!void { |
| 6 | 8 | const test_fn_list = builtin.test_functions; |
| 7 | 9 | var ok_count: usize = 0; |
| ... | ... | @@ -12,6 +14,11 @@ pub fn main() anyerror!void { |
| 12 | 14 | error.TimerUnsupported => @panic("timer unsupported"), |
| 13 | 15 | }; |
| 14 | 16 | |
| 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 | 22 | for (test_fn_list) |test_fn, i| { |
| 16 | 23 | std.testing.base_allocator_instance.reset(); |
| 17 | 24 | |
| ... | ... | @@ -21,7 +28,24 @@ pub fn main() anyerror!void { |
| 21 | 28 | if (progress.terminal == null) { |
| 22 | 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 | 49 | ok_count += 1; |
| 26 | 50 | test_node.end(); |
| 27 | 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 | 29 | const link = @import("link.zig").link; |
| 30 | 30 | const LibCInstallation = @import("libc_installation.zig").LibCInstallation; |
| 31 | 31 | const CInt = @import("c_int.zig").CInt; |
| 32 | const fs = event.fs; | |
| 32 | const fs = std.fs; | |
| 33 | 33 | const util = @import("util.zig"); |
| 34 | 34 | |
| 35 | 35 | const max_src_size = 2 * 1024 * 1024 * 1024; // 2 GiB |
| ... | ... | @@ -442,7 +442,7 @@ pub const Compilation = struct { |
| 442 | 442 | comp.name = try Buffer.init(comp.arena(), name); |
| 443 | 443 | comp.llvm_triple = try util.getTriple(comp.arena(), target); |
| 444 | 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" }); | |
| 446 | 446 | |
| 447 | 447 | const opt_level = switch (build_mode) { |
| 448 | 448 | .Debug => llvm.CodeGenLevelNone, |
| ... | ... | @@ -488,8 +488,8 @@ pub const Compilation = struct { |
| 488 | 488 | defer comp.events.deinit(); |
| 489 | 489 | |
| 490 | 490 | if (root_src_path) |root_src| { |
| 491 | const dirname = std.fs.path.dirname(root_src) orelse "."; | |
| 492 | const basename = std.fs.path.basename(root_src); | |
| 491 | const dirname = fs.path.dirname(root_src) orelse "."; | |
| 492 | const basename = fs.path.basename(root_src); | |
| 493 | 493 | |
| 494 | 494 | comp.root_package = try Package.create(comp.arena(), dirname, basename); |
| 495 | 495 | comp.std_package = try Package.create(comp.arena(), comp.zig_std_dir, "std.zig"); |
| ... | ... | @@ -521,7 +521,7 @@ pub const Compilation = struct { |
| 521 | 521 | if (comp.tmp_dir.getOrNull()) |tmp_dir_result| |
| 522 | 522 | if (tmp_dir_result.*) |tmp_dir| { |
| 523 | 523 | // TODO evented I/O? |
| 524 | std.fs.deleteTree(tmp_dir) catch {}; | |
| 524 | fs.deleteTree(tmp_dir) catch {}; | |
| 525 | 525 | } else |_| {}; |
| 526 | 526 | } |
| 527 | 527 | |
| ... | ... | @@ -797,7 +797,7 @@ pub const Compilation = struct { |
| 797 | 797 | |
| 798 | 798 | async fn rebuildFile(self: *Compilation, root_scope: *Scope.Root) BuildError!void { |
| 799 | 799 | const tree_scope = blk: { |
| 800 | const source_code = fs.readFile( | |
| 800 | const source_code = fs.cwd().readFileAlloc( | |
| 801 | 801 | self.gpa(), |
| 802 | 802 | root_scope.realpath, |
| 803 | 803 | max_src_size, |
| ... | ... | @@ -935,8 +935,8 @@ pub const Compilation = struct { |
| 935 | 935 | fn initialCompile(self: *Compilation) !void { |
| 936 | 936 | if (self.root_src_path) |root_src_path| { |
| 937 | 937 | const root_scope = blk: { |
| 938 | // TODO async/await std.fs.realpath | |
| 939 | const root_src_real_path = std.fs.realpathAlloc(self.gpa(), root_src_path) catch |err| { | |
| 938 | // TODO async/await fs.realpath | |
| 939 | const root_src_real_path = fs.realpathAlloc(self.gpa(), root_src_path) catch |err| { | |
| 940 | 940 | try self.addCompileErrorCli(root_src_path, "unable to open: {}", .{@errorName(err)}); |
| 941 | 941 | return; |
| 942 | 942 | }; |
| ... | ... | @@ -1157,7 +1157,7 @@ pub const Compilation = struct { |
| 1157 | 1157 | const file_name = try std.fmt.allocPrint(self.gpa(), "{}{}", .{ file_prefix[0..], suffix }); |
| 1158 | 1158 | defer self.gpa().free(file_name); |
| 1159 | 1159 | |
| 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 | 1161 | errdefer self.gpa().free(full_path); |
| 1162 | 1162 | |
| 1163 | 1163 | return Buffer.fromOwnedSlice(self.gpa(), full_path); |
| ... | ... | @@ -1178,8 +1178,8 @@ pub const Compilation = struct { |
| 1178 | 1178 | const zig_dir_path = try getZigDir(self.gpa()); |
| 1179 | 1179 | defer self.gpa().free(zig_dir_path); |
| 1180 | 1180 | |
| 1181 | const tmp_dir = try std.fs.path.join(self.arena(), &[_][]const u8{ zig_dir_path, comp_dir_name[0..] }); | |
| 1182 | try std.fs.makePath(self.gpa(), tmp_dir); | |
| 1181 | const tmp_dir = try fs.path.join(self.arena(), &[_][]const u8{ zig_dir_path, comp_dir_name[0..] }); | |
| 1182 | try fs.makePath(self.gpa(), tmp_dir); | |
| 1183 | 1183 | return tmp_dir; |
| 1184 | 1184 | } |
| 1185 | 1185 | |
| ... | ... | @@ -1351,7 +1351,7 @@ async fn addFnToLinkSet(comp: *Compilation, fn_val: *Value.Fn) Compilation.Build |
| 1351 | 1351 | } |
| 1352 | 1352 | |
| 1353 | 1353 | fn getZigDir(allocator: *mem.Allocator) ![]u8 { |
| 1354 | return std.fs.getAppDataDir(allocator, "zig"); | |
| 1354 | return fs.getAppDataDir(allocator, "zig"); | |
| 1355 | 1355 | } |
| 1356 | 1356 | |
| 1357 | 1357 | fn analyzeFnType( |
src-self-hosted/dep_tokenizer.zig+10-23| ... | ... | @@ -998,7 +998,8 @@ fn printCharValues(out: var, bytes: []const u8) !void { |
| 998 | 998 | |
| 999 | 999 | fn printUnderstandableChar(out: var, char: u8) !void { |
| 1000 | 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 | 1003 | } else { |
| 1003 | 1004 | try out.write("'"); |
| 1004 | 1005 | try out.write(&[_]u8{printable_char_tab[char]}); |
| ... | ... | @@ -1021,34 +1022,20 @@ comptime { |
| 1021 | 1022 | // output: must be a function that takes a `self` idiom parameter |
| 1022 | 1023 | // and a bytes parameter |
| 1023 | 1024 | // context: must be that self |
| 1024 | fn makeOutput(output: var, context: var) Output(@TypeOf(output)) { | |
| 1025 | return Output(@TypeOf(output)){ | |
| 1026 | .output = output, | |
| 1025 | fn makeOutput(comptime output: var, context: var) Output(output, @TypeOf(context)) { | |
| 1026 | return Output(output, @TypeOf(context)){ | |
| 1027 | 1027 | .context = context, |
| 1028 | 1028 | }; |
| 1029 | 1029 | } |
| 1030 | 1030 | |
| 1031 | fn Output(comptime T: type) type { | |
| 1032 | const args = switch (@typeInfo(T)) { | |
| 1033 | .Fn => |f| f.args, | |
| 1034 | else => @compileError("output parameter is not a function"), | |
| 1035 | }; | |
| 1036 | if (args.len != 2) { | |
| 1037 | @compileError("output function must take 2 arguments"); | |
| 1038 | } | |
| 1039 | const at0 = args[0].arg_type orelse @compileError("output arg[0] does not have a type"); | |
| 1040 | const at1 = args[1].arg_type orelse @compileError("output arg[1] does not have a type"); | |
| 1041 | const arg1p = switch (@typeInfo(at1)) { | |
| 1042 | .Pointer => |p| p, | |
| 1043 | else => @compileError("output arg[1] is not a slice"), | |
| 1044 | }; | |
| 1045 | if (arg1p.child != u8) @compileError("output arg[1] is not a u8 slice"); | |
| 1031 | fn Output(comptime output_func: var, comptime Context: type) type { | |
| 1046 | 1032 | return struct { |
| 1047 | output: T, | |
| 1048 | context: at0, | |
| 1033 | context: Context, | |
| 1034 | ||
| 1035 | pub const output = output_func; | |
| 1049 | 1036 | |
| 1050 | fn write(self: *@This(), bytes: []const u8) !void { | |
| 1051 | try self.output(self.context, bytes); | |
| 1037 | fn write(self: @This(), bytes: []const u8) !void { | |
| 1038 | try output_func(self.context, bytes); | |
| 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 | 14 | const test_index_file = try fs.path.join(allocator, &[_][]const u8{ test_zig_dir, "std", "std.zig" }); |
| 15 | 15 | defer allocator.free(test_index_file); |
| 16 | 16 | |
| 17 | var file = try fs.File.openRead(test_index_file); | |
| 17 | var file = try fs.cwd().openRead(test_index_file); | |
| 18 | 18 | file.close(); |
| 19 | 19 | |
| 20 | 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 | 724 | if (try held.value.put(file_path, {})) |_| return; |
| 725 | 725 | } |
| 726 | 726 | |
| 727 | const source_code = event.fs.readFile( | |
| 727 | const source_code = fs.cwd().readFileAlloc( | |
| 728 | 728 | fmt.allocator, |
| 729 | 729 | file_path, |
| 730 | 730 | max_src_size, |
src/all_types.hpp+4| ... | ... | @@ -2246,6 +2246,7 @@ struct CodeGen { |
| 2246 | 2246 | bool enable_dump_analysis; |
| 2247 | 2247 | bool enable_doc_generation; |
| 2248 | 2248 | bool disable_bin_generation; |
| 2249 | bool test_is_evented; | |
| 2249 | 2250 | CodeModel code_model; |
| 2250 | 2251 | |
| 2251 | 2252 | Buf *mmacosx_version_min; |
| ... | ... | @@ -2491,6 +2492,9 @@ struct ScopeExpr { |
| 2491 | 2492 | size_t children_len; |
| 2492 | 2493 | |
| 2493 | 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 | }; |
| 2495 | 2499 | |
| 2496 | 2500 | // 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 | 6108 | continue; |
| 6109 | 6109 | } |
| 6110 | 6110 | case ScopeIdExpr: { |
| 6111 | ScopeExpr *parent_expr_scope = reinterpret_cast<ScopeExpr *>(scope); | |
| 6111 | 6112 | if (!looking_for_exprs) { |
| 6113 | if (parent_expr_scope->spill_harder) { | |
| 6114 | parent_expr_scope->need_spill = MemoizedBoolTrue; | |
| 6115 | } | |
| 6112 | 6116 | // Now we're only looking for a block, to see if it's in a loop (see the case ScopeIdBlock) |
| 6113 | 6117 | continue; |
| 6114 | 6118 | } |
| 6115 | ScopeExpr *parent_expr_scope = reinterpret_cast<ScopeExpr *>(scope); | |
| 6116 | 6119 | if (child_expr_scope != nullptr) { |
| 6117 | 6120 | for (size_t i = 0; parent_expr_scope->children_ptr[i] != child_expr_scope; i += 1) { |
| 6118 | 6121 | assert(i < parent_expr_scope->children_len); |
| ... | ... | @@ -6148,6 +6151,15 @@ static bool scope_needs_spill(Scope *scope) { |
| 6148 | 6151 | zig_unreachable(); |
| 6149 | 6152 | } |
| 6150 | 6153 | |
| 6154 | static 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 | ||
| 6151 | 6163 | static Error resolve_async_frame(CodeGen *g, ZigType *frame_type) { |
| 6152 | 6164 | Error err; |
| 6153 | 6165 | |
| ... | ... | @@ -6249,6 +6261,9 @@ static Error resolve_async_frame(CodeGen *g, ZigType *frame_type) { |
| 6249 | 6261 | } |
| 6250 | 6262 | ZigFn *callee = call->fn_entry; |
| 6251 | 6263 | if (callee == nullptr) { |
| 6264 | if (call->fn_ref->value->type->data.fn.fn_type_id.cc != CallingConventionAsync) { | |
| 6265 | continue; | |
| 6266 | } | |
| 6252 | 6267 | add_node_error(g, call->base.base.source_node, |
| 6253 | 6268 | buf_sprintf("function is not comptime-known; @asyncCall required")); |
| 6254 | 6269 | return ErrorSemanticAnalyzeFail; |
| ... | ... | @@ -6356,11 +6371,19 @@ static Error resolve_async_frame(CodeGen *g, ZigType *frame_type) { |
| 6356 | 6371 | IrInstGen *instruction = block->instruction_list.at(instr_i); |
| 6357 | 6372 | if (instruction->id == IrInstGenIdAwait || |
| 6358 | 6373 | instruction->id == IrInstGenIdVarPtr || |
| 6359 | instruction->id == IrInstGenIdAlloca) | |
| 6374 | instruction->id == IrInstGenIdAlloca || | |
| 6375 | instruction->id == IrInstGenIdSpillBegin || | |
| 6376 | instruction->id == IrInstGenIdSpillEnd) | |
| 6360 | 6377 | { |
| 6361 | 6378 | // This instruction does its own spilling specially, or otherwise doesn't need it. |
| 6362 | 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 | 6387 | if (instruction->value->special != ConstValSpecialRuntime) |
| 6365 | 6388 | continue; |
| 6366 | 6389 | if (instruction->base.ref_count == 0) |
| ... | ... | @@ -6406,7 +6429,7 @@ static Error resolve_async_frame(CodeGen *g, ZigType *frame_type) { |
| 6406 | 6429 | } else { |
| 6407 | 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 | 6433 | if ((err = type_resolve(g, param_type, ResolveStatusSizeKnown))) { |
| 6411 | 6434 | return err; |
| 6412 | 6435 | } |
| ... | ... | @@ -6425,7 +6448,7 @@ static Error resolve_async_frame(CodeGen *g, ZigType *frame_type) { |
| 6425 | 6448 | instruction->field_index = SIZE_MAX; |
| 6426 | 6449 | ZigType *ptr_type = instruction->base.value->type; |
| 6427 | 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 | 6452 | if (!type_has_bits(child_type)) |
| 6430 | 6453 | continue; |
| 6431 | 6454 | if (instruction->base.base.ref_count == 0) |
| ... | ... | @@ -6452,8 +6475,6 @@ static Error resolve_async_frame(CodeGen *g, ZigType *frame_type) { |
| 6452 | 6475 | } |
| 6453 | 6476 | instruction->field_index = fields.length; |
| 6454 | 6477 | |
| 6455 | src_assert(child_type->id != ZigTypeIdPointer || child_type->data.pointer.inferred_struct_field == nullptr, | |
| 6456 | instruction->base.base.source_node); | |
| 6457 | 6478 | fields.append({name, child_type, instruction->align}); |
| 6458 | 6479 | } |
| 6459 | 6480 | |
| ... | ... | @@ -8255,6 +8276,8 @@ static void resolve_llvm_types_struct(CodeGen *g, ZigType *struct_type, ResolveS |
| 8255 | 8276 | size_t debug_field_index = 0; |
| 8256 | 8277 | for (size_t i = 0; i < field_count; i += 1) { |
| 8257 | 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 | 8281 | size_t gen_field_index = field->gen_index; |
| 8259 | 8282 | if (gen_field_index == SIZE_MAX) { |
| 8260 | 8283 | continue; |
src/codegen.cpp+147-54| ... | ... | @@ -343,33 +343,67 @@ static LLVMLinkage to_llvm_linkage(GlobalLinkageId id) { |
| 343 | 343 | zig_unreachable(); |
| 344 | 344 | } |
| 345 | 345 | |
| 346 | struct CalcLLVMFieldIndex { | |
| 347 | uint32_t offset; | |
| 348 | uint32_t field_index; | |
| 349 | }; | |
| 350 | ||
| 351 | static 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 | 367 | // label (grep this): [fn_frame_struct_layout] |
| 368 | static 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 | ||
| 347 | 380 | static uint32_t frame_index_trace_arg(CodeGen *g, ZigType *return_type) { |
| 348 | // [0] *ReturnType (callee's) | |
| 349 | // [1] *ReturnType (awaiter's) | |
| 350 | // [2] ReturnType | |
| 351 | uint32_t return_field_count = type_has_bits(return_type) ? 3 : 0; | |
| 352 | return frame_ret_start + return_field_count; | |
| 381 | CalcLLVMFieldIndex calc = {0}; | |
| 382 | frame_index_trace_arg_calc(g, &calc, return_type); | |
| 383 | return calc.field_index; | |
| 353 | 384 | } |
| 354 | 385 | |
| 355 | 386 | // label (grep this): [fn_frame_struct_layout] |
| 356 | static uint32_t frame_index_arg(CodeGen *g, ZigType *return_type) { | |
| 357 | bool have_stack_trace = codegen_fn_has_err_ret_tracing_arg(g, return_type); | |
| 358 | // [0] *StackTrace (callee's) | |
| 359 | // [1] *StackTrace (awaiter's) | |
| 360 | uint32_t trace_field_count = have_stack_trace ? 2 : 0; | |
| 361 | return frame_index_trace_arg(g, return_type) + trace_field_count; | |
| 387 | static void frame_index_arg_calc(CodeGen *g, CalcLLVMFieldIndex *calc, ZigType *return_type) { | |
| 388 | frame_index_trace_arg_calc(g, calc, return_type); | |
| 389 | ||
| 390 | if (codegen_fn_has_err_ret_tracing_arg(g, return_type)) { | |
| 391 | calc_llvm_field_index_add(g, calc, g->builtin_types.entry_usize); // *StackTrace (callee's) | |
| 392 | calc_llvm_field_index_add(g, calc, g->builtin_types.entry_usize); // *StackTrace (awaiter's) | |
| 393 | } | |
| 362 | 394 | } |
| 363 | 395 | |
| 364 | 396 | // label (grep this): [fn_frame_struct_layout] |
| 365 | static uint32_t frame_index_trace_stack(CodeGen *g, FnTypeId *fn_type_id) { | |
| 366 | uint32_t result = frame_index_arg(g, fn_type_id->return_type); | |
| 367 | for (size_t i = 0; i < fn_type_id->param_count; i += 1) { | |
| 368 | if (type_has_bits(fn_type_id->param_info->type)) { | |
| 369 | result += 1; | |
| 370 | } | |
| 397 | static uint32_t frame_index_trace_stack(CodeGen *g, ZigFn *fn) { | |
| 398 | size_t field_index = 6; | |
| 399 | bool have_stack_trace = codegen_fn_has_err_ret_tracing_arg(g, fn->type_entry->data.fn.fn_type_id.return_type); | |
| 400 | if (have_stack_trace) { | |
| 401 | field_index += 2; | |
| 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 | } |
| 374 | 408 | |
| 375 | 409 | |
| ... | ... | @@ -2527,7 +2561,12 @@ static LLVMValueRef ir_render_return(CodeGen *g, IrExecutableGen *executable, Ir |
| 2527 | 2561 | LLVMBuildRet(g->builder, by_val_value); |
| 2528 | 2562 | } |
| 2529 | 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 | 2570 | } else { |
| 2532 | 2571 | LLVMValueRef value = ir_llvm_value(g, instruction->operand); |
| 2533 | 2572 | LLVMBuildRet(g->builder, value); |
| ... | ... | @@ -3920,7 +3959,9 @@ static void set_call_instr_sret(CodeGen *g, LLVMValueRef call_instr) { |
| 3920 | 3959 | static void render_async_spills(CodeGen *g) { |
| 3921 | 3960 | ZigType *fn_type = g->cur_fn->type_entry; |
| 3922 | 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 | 3965 | for (size_t var_i = 0; var_i < g->cur_fn->variable_list.length; var_i += 1) { |
| 3925 | 3966 | ZigVar *var = g->cur_fn->variable_list.at(var_i); |
| 3926 | 3967 | |
| ... | ... | @@ -3941,8 +3982,8 @@ static void render_async_spills(CodeGen *g) { |
| 3941 | 3982 | continue; |
| 3942 | 3983 | } |
| 3943 | 3984 | |
| 3944 | var->value_ref = LLVMBuildStructGEP(g->builder, g->cur_frame_ptr, async_var_index, var->name); | |
| 3945 | async_var_index += 1; | |
| 3985 | calc_llvm_field_index_add(g, &arg_calc, var->var_type); | |
| 3986 | var->value_ref = LLVMBuildStructGEP(g->builder, g->cur_frame_ptr, arg_calc.field_index - 1, var->name); | |
| 3946 | 3987 | if (var->decl_node) { |
| 3947 | 3988 | var->di_loc_var = ZigLLVMCreateAutoVariable(g->dbuilder, get_di_scope(g, var->parent_scope), |
| 3948 | 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 | 4064 | } |
| 4024 | 4065 | |
| 4025 | 4066 | static LLVMValueRef ir_render_call(CodeGen *g, IrExecutableGen *executable, IrInstGenCall *instruction) { |
| 4067 | Error err; | |
| 4068 | ||
| 4026 | 4069 | LLVMTypeRef usize_type_ref = g->builtin_types.entry_usize->llvm_type; |
| 4027 | 4070 | |
| 4028 | 4071 | LLVMValueRef fn_val; |
| ... | ... | @@ -4053,6 +4096,8 @@ static LLVMValueRef ir_render_call(CodeGen *g, IrExecutableGen *executable, IrIn |
| 4053 | 4096 | ZigList<ZigType *> gen_param_types = {}; |
| 4054 | 4097 | LLVMValueRef result_loc = instruction->result_loc ? ir_llvm_value(g, instruction->result_loc) : nullptr; |
| 4055 | 4098 | LLVMValueRef zero = LLVMConstNull(usize_type_ref); |
| 4099 | bool need_frame_ptr_ptr_spill = false; | |
| 4100 | ZigType *anyframe_type = nullptr; | |
| 4056 | 4101 | LLVMValueRef frame_result_loc_uncasted = nullptr; |
| 4057 | 4102 | LLVMValueRef frame_result_loc; |
| 4058 | 4103 | LLVMValueRef awaiter_init_val; |
| ... | ... | @@ -4091,14 +4136,17 @@ static LLVMValueRef ir_render_call(CodeGen *g, IrExecutableGen *executable, IrIn |
| 4091 | 4136 | |
| 4092 | 4137 | LLVMPositionBuilderAtEnd(g->builder, ok_block); |
| 4093 | 4138 | } |
| 4139 | need_frame_ptr_ptr_spill = true; | |
| 4094 | 4140 | LLVMValueRef frame_ptr_ptr = LLVMBuildStructGEP(g->builder, frame_slice_ptr, slice_ptr_index, ""); |
| 4095 | 4141 | LLVMValueRef frame_ptr = LLVMBuildLoad(g->builder, frame_ptr_ptr, ""); |
| 4096 | 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 | 4144 | frame_result_loc = LLVMBuildBitCast(g->builder, frame_ptr, get_llvm_type(g, anyframe_type), ""); |
| 4099 | 4145 | } else { |
| 4100 | ZigType *ptr_frame_type = get_pointer_to_type(g, | |
| 4101 | get_fn_frame_type(g, instruction->fn_entry), false); | |
| 4146 | ZigType *frame_type = get_fn_frame_type(g, instruction->fn_entry); | |
| 4147 | if ((err = type_resolve(g, frame_type, ResolveStatusLLVMFull))) | |
| 4148 | codegen_report_errors_and_exit(g); | |
| 4149 | ZigType *ptr_frame_type = get_pointer_to_type(g, frame_type, false); | |
| 4102 | 4150 | frame_result_loc = LLVMBuildBitCast(g->builder, frame_ptr, |
| 4103 | 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 | 4313 | LLVMValueRef result; |
| 4266 | 4314 | |
| 4267 | 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); | |
| 4269 | 4318 | |
| 4270 | 4319 | LLVMValueRef casted_frame; |
| 4271 | 4320 | if (instruction->new_stack != nullptr && instruction->fn_entry == nullptr) { |
| 4272 | 4321 | // 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 | 4330 | LLVMTypeRef *field_types = allocate_nonzero<LLVMTypeRef>(field_count); |
| 4275 | 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 | 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 | 4346 | LLVMTypeRef frame_with_args_type = LLVMStructType(field_types, field_count, false); |
| 4281 | 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 | 4351 | casted_frame = frame_result_loc; |
| 4286 | 4352 | } |
| 4287 | 4353 | |
| 4354 | CalcLLVMFieldIndex arg_calc = arg_calc_start; | |
| 4288 | 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 | 4358 | gen_assign_raw(g, arg_ptr, get_pointer_to_type(g, gen_param_types.at(arg_i), true), |
| 4291 | 4359 | gen_param_values.at(arg_i)); |
| 4292 | 4360 | } |
| ... | ... | @@ -4349,11 +4417,19 @@ static LLVMValueRef ir_render_call(CodeGen *g, IrExecutableGen *executable, IrIn |
| 4349 | 4417 | } |
| 4350 | 4418 | } |
| 4351 | 4419 | |
| 4352 | if (frame_result_loc_uncasted != nullptr && instruction->fn_entry != nullptr) { | |
| 4353 | // Instead of a spill, we do the bitcast again. The uncasted LLVM IR instruction will | |
| 4354 | // be an Alloca from the entry block, so it does not need to be spilled. | |
| 4355 | frame_result_loc = LLVMBuildBitCast(g->builder, frame_result_loc_uncasted, | |
| 4356 | LLVMPointerType(get_llvm_type(g, instruction->fn_entry->frame_type), 0), ""); | |
| 4420 | if (need_frame_ptr_ptr_spill) { | |
| 4421 | LLVMValueRef frame_slice_ptr = ir_llvm_value(g, instruction->new_stack); | |
| 4422 | LLVMValueRef frame_ptr_ptr = LLVMBuildStructGEP(g->builder, frame_slice_ptr, slice_ptr_index, ""); | |
| 4423 | frame_result_loc_uncasted = LLVMBuildLoad(g->builder, frame_ptr_ptr, ""); | |
| 4424 | } | |
| 4425 | if (frame_result_loc_uncasted != nullptr) { | |
| 4426 | if (instruction->fn_entry != nullptr) { | |
| 4427 | frame_result_loc = LLVMBuildBitCast(g->builder, frame_result_loc_uncasted, | |
| 4428 | LLVMPointerType(get_llvm_type(g, instruction->fn_entry->frame_type), 0), ""); | |
| 4429 | } else { | |
| 4430 | frame_result_loc = LLVMBuildBitCast(g->builder, frame_result_loc_uncasted, | |
| 4431 | get_llvm_type(g, anyframe_type), ""); | |
| 4432 | } | |
| 4357 | 4433 | } |
| 4358 | 4434 | |
| 4359 | 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 | 5720 | bool want_safety = instruction->safety_check_on && ir_want_runtime_safety(g, &instruction->base) && |
| 5645 | 5721 | g->errors_by_index.length > 1; |
| 5646 | 5722 | |
| 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 | 5723 | ZigType *ptr_type = instruction->value->value->type; |
| 5655 | 5724 | assert(ptr_type->id == ZigTypeIdPointer); |
| 5656 | 5725 | ZigType *err_union_type = ptr_type->data.pointer.child_type; |
| 5657 | 5726 | ZigType *payload_type = err_union_type->data.error_union.payload_type; |
| 5658 | 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 | 5741 | LLVMValueRef err_union_handle = get_handle_value(g, err_union_ptr, err_union_type, ptr_type); |
| 5660 | 5742 | |
| 5661 | 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 | 5752 | } else { |
| 5671 | 5753 | err_val = err_union_handle; |
| 5672 | 5754 | } |
| 5673 | LLVMValueRef zero = LLVMConstNull(get_llvm_type(g, g->err_tag_type)); | |
| 5674 | 5755 | LLVMValueRef cond_val = LLVMBuildICmp(g->builder, LLVMIntEQ, err_val, zero, ""); |
| 5675 | 5756 | LLVMBasicBlockRef err_block = LLVMAppendBasicBlock(g->cur_fn_val, "UnwrapErrError"); |
| 5676 | 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 | 5771 | } |
| 5691 | 5772 | return LLVMBuildStructGEP(g->builder, err_union_handle, err_union_payload_index, ""); |
| 5692 | 5773 | } else { |
| 5774 | if (instruction->initializing) { | |
| 5775 | gen_store_untyped(g, zero, err_union_ptr, 0, false); | |
| 5776 | } | |
| 5693 | 5777 | return nullptr; |
| 5694 | 5778 | } |
| 5695 | 5779 | } |
| ... | ... | @@ -7742,7 +7826,7 @@ static void do_code_gen(CodeGen *g) { |
| 7742 | 7826 | } |
| 7743 | 7827 | uint32_t trace_field_index_stack = UINT32_MAX; |
| 7744 | 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 | 7830 | g->cur_err_ret_trace_val_stack = LLVMBuildStructGEP(g->builder, g->cur_frame_ptr, |
| 7747 | 7831 | trace_field_index_stack, ""); |
| 7748 | 7832 | } |
| ... | ... | @@ -8602,6 +8686,9 @@ Buf *codegen_generate_builtin_source(CodeGen *g) { |
| 8602 | 8686 | buf_appendf(contents, |
| 8603 | 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 | } |
| 8606 | 8693 | |
| 8607 | 8694 | return contents; |
| ... | ... | @@ -8635,6 +8722,7 @@ static Error define_builtin_compile_vars(CodeGen *g) { |
| 8635 | 8722 | cache_bool(&cache_hash, g->is_dynamic); |
| 8636 | 8723 | cache_bool(&cache_hash, g->is_test_build); |
| 8637 | 8724 | cache_bool(&cache_hash, g->is_single_threaded); |
| 8725 | cache_bool(&cache_hash, g->test_is_evented); | |
| 8638 | 8726 | cache_int(&cache_hash, g->code_model); |
| 8639 | 8727 | cache_int(&cache_hash, g->zig_target->is_native); |
| 8640 | 8728 | cache_int(&cache_hash, g->zig_target->arch); |
| ... | ... | @@ -9392,22 +9480,13 @@ static void update_test_functions_builtin_decl(CodeGen *g) { |
| 9392 | 9480 | for (size_t i = 0; i < g->test_fns.length; i += 1) { |
| 9393 | 9481 | ZigFn *test_fn_entry = g->test_fns.at(i); |
| 9394 | 9482 | |
| 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 | 9483 | ZigValue *this_val = &test_fn_array->data.x_array.data.s_none.elements[i]; |
| 9405 | 9484 | this_val->special = ConstValSpecialStatic; |
| 9406 | 9485 | this_val->type = struct_type; |
| 9407 | 9486 | this_val->parent.id = ConstParentIdArray; |
| 9408 | 9487 | this_val->parent.data.p_array.array_val = test_fn_array; |
| 9409 | 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); | |
| 9411 | 9490 | |
| 9412 | 9491 | ZigValue *name_field = this_val->data.x_struct.fields[0]; |
| 9413 | 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 | 9498 | fn_field->data.x_ptr.special = ConstPtrSpecialFunction; |
| 9420 | 9499 | fn_field->data.x_ptr.mut = ConstPtrMutComptimeConst; |
| 9421 | 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 | 9515 | report_errors_and_maybe_exit(g); |
| 9424 | 9516 | |
| ... | ... | @@ -10350,6 +10442,7 @@ static Error check_cache(CodeGen *g, Buf *manifest_dir, Buf *digest) { |
| 10350 | 10442 | if (g->is_test_build) { |
| 10351 | 10443 | cache_buf_opt(ch, g->test_filter); |
| 10352 | 10444 | cache_buf_opt(ch, g->test_name_prefix); |
| 10445 | cache_bool(ch, g->test_is_evented); | |
| 10353 | 10446 | } |
| 10354 | 10447 | cache_bool(ch, g->link_eh_frame_hdr); |
| 10355 | 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 | 5252 | return irb->codegen->invalid_inst_src; |
| 5253 | 5253 | } else { |
| 5254 | 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 | } |
| 5256 | 5257 | |
| 5257 | 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 | 5263 | if (!have_err_defers && !irb->codegen->have_err_ret_tracing) { |
| 5263 | 5264 | // only generate unconditional defers |
| 5264 | 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 | 5267 | result_loc_ret->base.source_instruction = result; |
| 5267 | 5268 | return result; |
| 5268 | 5269 | } |
| ... | ... | @@ -5271,10 +5272,6 @@ static IrInstSrc *ir_gen_return(IrBuilderSrc *irb, Scope *scope, AstNode *node, |
| 5271 | 5272 | IrBasicBlockSrc *err_block = ir_create_basic_block(irb, scope, "ErrRetErr"); |
| 5272 | 5273 | IrBasicBlockSrc *ok_block = ir_create_basic_block(irb, scope, "ErrRetOk"); |
| 5273 | 5274 | |
| 5274 | if (!have_err_defers) { | |
| 5275 | ir_gen_defers_for_block(irb, scope, outer_scope, false); | |
| 5276 | } | |
| 5277 | ||
| 5278 | 5275 | IrInstSrc *is_err = ir_build_test_err_src(irb, scope, node, return_value, false, true); |
| 5279 | 5276 | |
| 5280 | 5277 | IrInstSrc *is_comptime; |
| ... | ... | @@ -5288,22 +5285,18 @@ static IrInstSrc *ir_gen_return(IrBuilderSrc *irb, Scope *scope, AstNode *node, |
| 5288 | 5285 | IrBasicBlockSrc *ret_stmt_block = ir_create_basic_block(irb, scope, "RetStmt"); |
| 5289 | 5286 | |
| 5290 | 5287 | ir_set_cursor_at_end_and_append_block(irb, err_block); |
| 5291 | if (have_err_defers) { | |
| 5292 | ir_gen_defers_for_block(irb, scope, outer_scope, true); | |
| 5293 | } | |
| 5288 | ir_gen_defers_for_block(irb, scope, outer_scope, true); | |
| 5294 | 5289 | if (irb->codegen->have_err_ret_tracing && !should_inline) { |
| 5295 | 5290 | ir_build_save_err_ret_addr_src(irb, scope, node); |
| 5296 | 5291 | } |
| 5297 | 5292 | ir_build_br(irb, scope, node, ret_stmt_block, is_comptime); |
| 5298 | 5293 | |
| 5299 | 5294 | ir_set_cursor_at_end_and_append_block(irb, ok_block); |
| 5300 | if (have_err_defers) { | |
| 5301 | ir_gen_defers_for_block(irb, scope, outer_scope, false); | |
| 5302 | } | |
| 5295 | ir_gen_defers_for_block(irb, scope, outer_scope, false); | |
| 5303 | 5296 | ir_build_br(irb, scope, node, ret_stmt_block, is_comptime); |
| 5304 | 5297 | |
| 5305 | 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 | 5300 | result_loc_ret->base.source_instruction = result; |
| 5308 | 5301 | return result; |
| 5309 | 5302 | } |
| ... | ... | @@ -8874,7 +8867,10 @@ static IrInstSrc *ir_gen_if_optional_expr(IrBuilderSrc *irb, Scope *scope, AstNo |
| 8874 | 8867 | AstNode *else_node = node->data.test_expr.else_node; |
| 8875 | 8868 | bool var_is_ptr = node->data.test_expr.var_is_ptr; |
| 8876 | 8869 | |
| 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 | 8874 | if (maybe_val_ptr == irb->codegen->invalid_inst_src) |
| 8879 | 8875 | return maybe_val_ptr; |
| 8880 | 8876 | |
| ... | ... | @@ -8899,7 +8895,7 @@ static IrInstSrc *ir_gen_if_optional_expr(IrBuilderSrc *irb, Scope *scope, AstNo |
| 8899 | 8895 | |
| 8900 | 8896 | ir_set_cursor_at_end_and_append_block(irb, then_block); |
| 8901 | 8897 | |
| 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 | 8899 | Scope *var_scope; |
| 8904 | 8900 | if (var_symbol) { |
| 8905 | 8901 | bool is_shadowable = false; |
| ... | ... | @@ -9619,7 +9615,10 @@ static IrInstSrc *ir_gen_catch(IrBuilderSrc *irb, Scope *parent_scope, AstNode * |
| 9619 | 9615 | } |
| 9620 | 9616 | |
| 9621 | 9617 | |
| 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 | 9622 | if (err_union_ptr == irb->codegen->invalid_inst_src) |
| 9624 | 9623 | return irb->codegen->invalid_inst_src; |
| 9625 | 9624 | |
| ... | ... | @@ -9641,7 +9640,7 @@ static IrInstSrc *ir_gen_catch(IrBuilderSrc *irb, Scope *parent_scope, AstNode * |
| 9641 | 9640 | is_comptime); |
| 9642 | 9641 | |
| 9643 | 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 | 9644 | Scope *err_scope; |
| 9646 | 9645 | if (var_node) { |
| 9647 | 9646 | assert(var_node->type == NodeTypeSymbol); |
| ... | ... | @@ -15494,6 +15493,12 @@ static IrInstGen *ir_analyze_instruction_add_implicit_return_type(IrAnalyze *ira |
| 15494 | 15493 | } |
| 15495 | 15494 | |
| 15496 | 15495 | static 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 | 15502 | IrInstGen *operand = instruction->operand->child; |
| 15498 | 15503 | if (type_is_invalid(operand->value->type)) |
| 15499 | 15504 | return ir_unreach_error(ira); |
| ... | ... | @@ -19586,6 +19591,12 @@ static IrInstGen *ir_analyze_fn_call(IrAnalyze *ira, IrInst* source_instr, |
| 19586 | 19591 | if (type_is_invalid(result_loc->value->type) || result_loc->value->type->id == ZigTypeIdUnreachable) { |
| 19587 | 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 | 19600 | ZigType *res_child_type = result_loc->value->type->data.pointer.child_type; |
| 19590 | 19601 | if (res_child_type == ira->codegen->builtin_types.entry_var) { |
| 19591 | 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 | 19729 | if (type_is_invalid(result_loc->value->type) || result_loc->value->type->id == ZigTypeIdUnreachable) { |
| 19719 | 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 | 19738 | ZigType *res_child_type = result_loc->value->type->data.pointer.child_type; |
| 19722 | 19739 | if (res_child_type == ira->codegen->builtin_types.entry_var) { |
| 19723 | 19740 | res_child_type = return_type; |
| ... | ... | @@ -29548,8 +29565,13 @@ static IrInstGen *ir_analyze_instruction_spill_begin(IrAnalyze *ira, IrInstSrcSp |
| 29548 | 29565 | if (!type_has_bits(operand->value->type)) |
| 29549 | 29566 | return ir_const_void(ira, &instruction->base.base); |
| 29550 | 29567 | |
| 29551 | ir_assert(instruction->spill_id == SpillIdRetErrCode, &instruction->base.base); | |
| 29552 | ira->new_irb.exec->need_err_code_spill = true; | |
| 29568 | switch (instruction->spill_id) { | |
| 29569 | case SpillIdInvalid: | |
| 29570 | zig_unreachable(); | |
| 29571 | case SpillIdRetErrCode: | |
| 29572 | ira->new_irb.exec->need_err_code_spill = true; | |
| 29573 | break; | |
| 29574 | } | |
| 29553 | 29575 | |
| 29554 | 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 | 29581 | if (type_is_invalid(operand->value->type)) |
| 29560 | 29582 | return ira->codegen->invalid_inst_gen; |
| 29561 | 29583 | |
| 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 | 29588 | return operand; |
| 29589 | } | |
| 29564 | 29590 | |
| 29565 | 29591 | ir_assert(instruction->begin->base.child->id == IrInstGenIdSpillBegin, &instruction->base.base); |
| 29566 | 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 | 135 | " --test-name-prefix [text] add prefix to all tests\n" |
| 136 | 136 | " --test-cmd [arg] specify test execution command one arg at a time\n" |
| 137 | 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 | 139 | , arg0); |
| 139 | 140 | return return_code; |
| 140 | 141 | } |
| ... | ... | @@ -428,6 +429,7 @@ int main(int argc, char **argv) { |
| 428 | 429 | ZigList<CFile *> c_source_files = {0}; |
| 429 | 430 | const char *test_filter = nullptr; |
| 430 | 431 | const char *test_name_prefix = nullptr; |
| 432 | bool test_evented_io = false; | |
| 431 | 433 | size_t ver_major = 0; |
| 432 | 434 | size_t ver_minor = 0; |
| 433 | 435 | size_t ver_patch = 0; |
| ... | ... | @@ -709,6 +711,8 @@ int main(int argc, char **argv) { |
| 709 | 711 | cur_pkg = cur_pkg->parent; |
| 710 | 712 | } else if (strcmp(arg, "-ffunction-sections") == 0) { |
| 711 | 713 | function_sections = true; |
| 714 | } else if (strcmp(arg, "--test-evented-io") == 0) { | |
| 715 | test_evented_io = true; | |
| 712 | 716 | } else if (i + 1 >= argc) { |
| 713 | 717 | fprintf(stderr, "Expected another argument after %s\n", arg); |
| 714 | 718 | return print_error_usage(arg0); |
| ... | ... | @@ -1059,6 +1063,7 @@ int main(int argc, char **argv) { |
| 1059 | 1063 | g->want_stack_check = want_stack_check; |
| 1060 | 1064 | g->want_sanitize_c = want_sanitize_c; |
| 1061 | 1065 | g->want_single_threaded = want_single_threaded; |
| 1066 | g->test_is_evented = test_evented_io; | |
| 1062 | 1067 | Buf *builtin_source = codegen_generate_builtin_source(g); |
| 1063 | 1068 | if (fwrite(buf_ptr(builtin_source), 1, buf_len(builtin_source), stdout) != buf_len(builtin_source)) { |
| 1064 | 1069 | fprintf(stderr, "unable to write to stdout: %s\n", strerror(ferror(stdout))); |
| ... | ... | @@ -1232,6 +1237,7 @@ int main(int argc, char **argv) { |
| 1232 | 1237 | if (test_filter) { |
| 1233 | 1238 | codegen_set_test_filter(g, buf_create_from_str(test_filter)); |
| 1234 | 1239 | } |
| 1240 | g->test_is_evented = test_evented_io; | |
| 1235 | 1241 | |
| 1236 | 1242 | if (test_name_prefix) { |
| 1237 | 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 | 20 | "tmp.zig:1:20: error: dependency loop detected", |
| 21 | 21 | }); |
| 22 | 22 | |
| 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 | 47 | cases.addTest("non-exhaustive enums", |
| 24 | 48 | \\const A = enum { |
| 25 | 49 | \\ a, |
| ... | ... | @@ -5279,25 +5303,6 @@ pub fn addCases(cases: *tests.CompileErrorContext) void { |
| 5279 | 5303 | "tmp.zig:2:30: error: cannot set section of local variable 'foo'", |
| 5280 | 5304 | }); |
| 5281 | 5305 | |
| 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 | 5306 | cases.add("inner struct member shadowing outer struct member", |
| 5302 | 5307 | \\fn A() type { |
| 5303 | 5308 | \\ return struct { |
test/stage1/behavior/async_fn.zig+152| ... | ... | @@ -2,6 +2,7 @@ const std = @import("std"); |
| 2 | 2 | const builtin = @import("builtin"); |
| 3 | 3 | const expect = std.testing.expect; |
| 4 | 4 | const expectEqual = std.testing.expectEqual; |
| 5 | const expectError = std.testing.expectError; | |
| 5 | 6 | |
| 6 | 7 | var global_x: i32 = 1; |
| 7 | 8 | |
| ... | ... | @@ -1329,3 +1330,154 @@ test "async call with @call" { |
| 1329 | 1330 | }; |
| 1330 | 1331 | S.doTheTest(); |
| 1331 | 1332 | } |
| 1333 | ||
| 1334 | test "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 | ||
| 1354 | test "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 | ||
| 1376 | test "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 | ||
| 1398 | test "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 | ||
| 1421 | test "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 | ||
| 1445 | test "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 | } |