authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2020-02-10 00:26:33-05:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2020-02-10 00:26:33-05:00
logcdc5070f216a924d24588b8d0fe06400e036e6bf
treec1943e1831725e41810ea4db4eb1785a130e18e1
parent9e5b2489913f72764ded2089bccd7e612a3cc347
parent014f66e6de4aaf81f32c796b12f981326a479397
signaturelock-open Commit is signed but in an unrecognized format.

Merge remote-tracking branch 'origin/master' into llvm10


64 files changed, 3158 insertions(+), 3018 deletions(-)

CMakeLists.txt+13-1
...@@ -2,7 +2,19 @@ cmake_minimum_required(VERSION 2.8.5)...@@ -2,7 +2,19 @@ cmake_minimum_required(VERSION 2.8.5)
22
3if(NOT CMAKE_BUILD_TYPE)3if(NOT CMAKE_BUILD_TYPE)
4 set(CMAKE_BUILD_TYPE "Debug" CACHE STRING4 set(CMAKE_BUILD_TYPE "Debug" CACHE STRING
5 "Choose the type of build, options are: Debug Release RelWithDebInfo MinSizeRel." FORCE)5 "Choose the type of build, options are: None Debug Release RelWithDebInfo MinSizeRel." FORCE)
6endif()
7
8set(_list "None;Debug;Release;RelWithDebInfo;MinSizeRel")
9list(FIND _list ${CMAKE_BUILD_TYPE} _index)
10if(${_index} EQUAL -1)
11 string(REPLACE ";" ", " _list_pretty "${_list}")
12 message("::")
13 message(":: ERROR: Invalid build type: ${CMAKE_BUILD_TYPE}")
14 message("::")
15 message(":: valid types: { ${_list_pretty} }")
16 message("::")
17 message(FATAL_ERROR)
6endif()18endif()
719
8if(NOT CMAKE_INSTALL_PREFIX)20if(NOT CMAKE_INSTALL_PREFIX)
build.zig+3-4
...@@ -73,14 +73,13 @@ pub fn build(b: *Builder) !void {...@@ -73,14 +73,13 @@ pub fn build(b: *Builder) !void {
73 const skip_release_safe = b.option(bool, "skip-release-safe", "Main test suite skips release-safe builds") orelse skip_release;73 const skip_release_safe = b.option(bool, "skip-release-safe", "Main test suite skips release-safe builds") orelse skip_release;
74 const skip_non_native = b.option(bool, "skip-non-native", "Main test suite skips non-native builds") orelse false;74 const skip_non_native = b.option(bool, "skip-non-native", "Main test suite skips non-native builds") orelse false;
75 const skip_libc = b.option(bool, "skip-libc", "Main test suite skips tests that link libc") orelse false;75 const skip_libc = b.option(bool, "skip-libc", "Main test suite skips tests that link libc") orelse false;
76 const skip_self_hosted = b.option(bool, "skip-self-hosted", "Main test suite skips building self hosted compiler") orelse false;76 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
77 if (!skip_self_hosted and builtin.os == .linux) {77 if (!skip_self_hosted) {
78 // TODO evented I/O other OS's
79 test_step.dependOn(&exe.step);78 test_step.dependOn(&exe.step);
80 }79 }
8180
82 const only_install_lib_files = b.option(bool, "lib-files-only", "Only install library files") orelse false;81 const only_install_lib_files = b.option(bool, "lib-files-only", "Only install library files") orelse false;
83 if (!only_install_lib_files) {82 if (!only_install_lib_files and !skip_self_hosted) {
84 b.default_step.dependOn(&exe.step);83 b.default_step.dependOn(&exe.step);
85 exe.install();84 exe.install();
86 }85 }
doc/docgen.zig+2-2
...@@ -34,10 +34,10 @@ pub fn main() !void {...@@ -34,10 +34,10 @@ pub fn main() !void {
34 const out_file_name = try (args_it.next(allocator) orelse @panic("expected output arg"));34 const out_file_name = try (args_it.next(allocator) orelse @panic("expected output arg"));
35 defer allocator.free(out_file_name);35 defer allocator.free(out_file_name);
3636
37 var in_file = try fs.File.openRead(in_file_name);37 var in_file = try fs.cwd().openFile(in_file_name, .{ .read = true });
38 defer in_file.close();38 defer in_file.close();
3939
40 var out_file = try fs.File.openWrite(out_file_name);40 var out_file = try fs.cwd().createFile(out_file_name, .{});
41 defer out_file.close();41 defer out_file.close();
4242
43 var file_in_stream = in_file.inStream();43 var file_in_stream = in_file.inStream();
lib/std/atomic/queue.zig+13-4
...@@ -113,11 +113,20 @@ pub fn Queue(comptime T: type) type {...@@ -113,11 +113,20 @@ pub fn Queue(comptime T: type) type {
113113
114 pub fn dumpToStream(self: *Self, comptime Error: type, stream: *std.io.OutStream(Error)) Error!void {114 pub fn dumpToStream(self: *Self, comptime Error: type, stream: *std.io.OutStream(Error)) Error!void {
115 const S = struct {115 const S = struct {
116 fn dumpRecursive(s: *std.io.OutStream(Error), optional_node: ?*Node, indent: usize) Error!void {116 fn dumpRecursive(
117 s: *std.io.OutStream(Error),
118 optional_node: ?*Node,
119 indent: usize,
120 comptime depth: comptime_int,
121 ) Error!void {
117 try s.writeByteNTimes(' ', indent);122 try s.writeByteNTimes(' ', indent);
118 if (optional_node) |node| {123 if (optional_node) |node| {
119 try s.print("0x{x}={}\n", .{ @ptrToInt(node), node.data });124 try s.print("0x{x}={}\n", .{ @ptrToInt(node), node.data });
120 try dumpRecursive(s, node.next, indent + 1);125 if (depth == 0) {
126 try s.print("(max depth)\n", .{});
127 return;
128 }
129 try dumpRecursive(s, node.next, indent + 1, depth - 1);
121 } else {130 } else {
122 try s.print("(null)\n", .{});131 try s.print("(null)\n", .{});
123 }132 }
...@@ -127,9 +136,9 @@ pub fn Queue(comptime T: type) type {...@@ -127,9 +136,9 @@ pub fn Queue(comptime T: type) type {
127 defer held.release();136 defer held.release();
128137
129 try stream.print("head: ", .{});138 try stream.print("head: ", .{});
130 try S.dumpRecursive(stream, self.head, 0);139 try S.dumpRecursive(stream, self.head, 0, 4);
131 try stream.print("tail: ", .{});140 try stream.print("tail: ", .{});
132 try S.dumpRecursive(stream, self.tail, 0);141 try S.dumpRecursive(stream, self.tail, 0, 4);
133 }142 }
134 };143 };
135}144}
lib/std/build.zig+6-2
...@@ -495,12 +495,16 @@ pub const Builder = struct {...@@ -495,12 +495,16 @@ pub const Builder = struct {
495495
496 self.addNativeSystemIncludeDir("/usr/local/include");496 self.addNativeSystemIncludeDir("/usr/local/include");
497 self.addNativeSystemLibPath("/usr/local/lib");497 self.addNativeSystemLibPath("/usr/local/lib");
498 self.addNativeSystemLibPath("/usr/local/lib64");
498499
499 self.addNativeSystemIncludeDir(self.fmt("/usr/include/{}", .{triple}));500 self.addNativeSystemIncludeDir(self.fmt("/usr/include/{}", .{triple}));
500 self.addNativeSystemLibPath(self.fmt("/usr/lib/{}", .{triple}));501 self.addNativeSystemLibPath(self.fmt("/usr/lib/{}", .{triple}));
501502
502 self.addNativeSystemIncludeDir("/usr/include");503 self.addNativeSystemIncludeDir("/usr/include");
504 self.addNativeSystemLibPath("/lib");
505 self.addNativeSystemLibPath("/lib64");
503 self.addNativeSystemLibPath("/usr/lib");506 self.addNativeSystemLibPath("/usr/lib");
507 self.addNativeSystemLibPath("/usr/lib64");
504508
505 // example: on a 64-bit debian-based linux distro, with zlib installed from apt:509 // example: on a 64-bit debian-based linux distro, with zlib installed from apt:
506 // zlib.h is in /usr/include (added above)510 // zlib.h is in /usr/include (added above)
...@@ -1416,7 +1420,7 @@ pub const LibExeObjStep = struct {...@@ -1416,7 +1420,7 @@ pub const LibExeObjStep = struct {
1416 self.builder.installArtifact(self);1420 self.builder.installArtifact(self);
1417 }1421 }
14181422
1419 pub fn installRaw(self: *LibExeObjStep, dest_filename: [] const u8) void {1423 pub fn installRaw(self: *LibExeObjStep, dest_filename: []const u8) void {
1420 self.builder.installRaw(self, dest_filename);1424 self.builder.installRaw(self, dest_filename);
1421 }1425 }
14221426
...@@ -2135,7 +2139,7 @@ pub const LibExeObjStep = struct {...@@ -2135,7 +2139,7 @@ pub const LibExeObjStep = struct {
2135 try zig_args.append("-isystem");2139 try zig_args.append("-isystem");
2136 try zig_args.append(self.builder.pathFromRoot(include_path));2140 try zig_args.append(self.builder.pathFromRoot(include_path));
2137 },2141 },
2138 .OtherStep => |other| {2142 .OtherStep => |other| if (!other.disable_gen_h) {
2139 const h_path = other.getOutputHPath();2143 const h_path = other.getOutputHPath();
2140 try zig_args.append("-isystem");2144 try zig_args.append("-isystem");
2141 try zig_args.append(fs.path.dirname(h_path).?);2145 try zig_args.append(fs.path.dirname(h_path).?);
lib/std/builtin.zig+1
...@@ -460,6 +460,7 @@ pub const ExportOptions = struct {...@@ -460,6 +460,7 @@ pub const ExportOptions = struct {
460pub const TestFn = struct {460pub const TestFn = struct {
461 name: []const u8,461 name: []const u8,
462 func: fn () anyerror!void,462 func: fn () anyerror!void,
463 async_frame_size: ?usize,
463};464};
464465
465/// This function type is used by the Zig language code generation and466/// This function type is used by the Zig language code generation and
lib/std/c.zig+3
...@@ -119,6 +119,9 @@ pub extern "c" fn getrusage(who: c_int, usage: *rusage) c_int;...@@ -119,6 +119,9 @@ pub extern "c" fn getrusage(who: c_int, usage: *rusage) c_int;
119pub extern "c" fn sysctl(name: [*]const c_int, namelen: c_uint, oldp: ?*c_void, oldlenp: ?*usize, newp: ?*c_void, newlen: usize) c_int;119pub extern "c" fn sysctl(name: [*]const c_int, namelen: c_uint, oldp: ?*c_void, oldlenp: ?*usize, newp: ?*c_void, newlen: usize) c_int;
120pub extern "c" fn sysctlbyname(name: [*:0]const u8, oldp: ?*c_void, oldlenp: ?*usize, newp: ?*c_void, newlen: usize) c_int;120pub extern "c" fn sysctlbyname(name: [*:0]const u8, oldp: ?*c_void, oldlenp: ?*usize, newp: ?*c_void, newlen: usize) c_int;
121pub extern "c" fn sysctlnametomib(name: [*:0]const u8, mibp: ?*c_int, sizep: ?*usize) c_int;121pub extern "c" fn sysctlnametomib(name: [*:0]const u8, mibp: ?*c_int, sizep: ?*usize) c_int;
122pub extern "c" fn tcgetattr(fd: fd_t, termios_p: *termios) c_int;
123pub extern "c" fn tcsetattr(fd: fd_t, optional_action: TCSA, termios_p: *const termios) c_int;
124pub extern "c" fn fcntl(fd: fd_t, cmd: c_int, ...) c_int;
122125
123pub extern "c" fn gethostname(name: [*]u8, len: usize) c_int;126pub extern "c" fn gethostname(name: [*]u8, len: usize) c_int;
124pub extern "c" fn bind(socket: fd_t, address: ?*const sockaddr, address_len: socklen_t) c_int;127pub extern "c" fn bind(socket: fd_t, address: ?*const sockaddr, address_len: socklen_t) c_int;
lib/std/c/tokenizer.zig+4-1
...@@ -776,12 +776,14 @@ pub const Tokenizer = struct {...@@ -776,12 +776,14 @@ pub const Tokenizer = struct {
776 }776 }
777 },777 },
778 else => {778 else => {
779 self.index -= 1;
779 state = if (string) .StringLiteral else .CharLiteral;780 state = if (string) .StringLiteral else .CharLiteral;
780 },781 },
781 },782 },
782 .HexEscape => switch (c) {783 .HexEscape => switch (c) {
783 '0'...'9', 'a'...'f', 'A'...'F' => {},784 '0'...'9', 'a'...'f', 'A'...'F' => {},
784 else => {785 else => {
786 self.index -= 1;
785 state = if (string) .StringLiteral else .CharLiteral;787 state = if (string) .StringLiteral else .CharLiteral;
786 },788 },
787 },789 },
...@@ -797,6 +799,7 @@ pub const Tokenizer = struct {...@@ -797,6 +799,7 @@ pub const Tokenizer = struct {
797 result.id = .Invalid;799 result.id = .Invalid;
798 break;800 break;
799 }801 }
802 self.index -= 1;
800 state = if (string) .StringLiteral else .CharLiteral;803 state = if (string) .StringLiteral else .CharLiteral;
801 },804 },
802 },805 },
...@@ -1046,7 +1049,6 @@ pub const Tokenizer = struct {...@@ -1046,7 +1049,6 @@ pub const Tokenizer = struct {
1046 .LineComment => switch (c) {1049 .LineComment => switch (c) {
1047 '\n' => {1050 '\n' => {
1048 result.id = .LineComment;1051 result.id = .LineComment;
1049 self.index += 1;
1050 break;1052 break;
1051 },1053 },
1052 else => {},1054 else => {},
...@@ -1217,6 +1219,7 @@ pub const Tokenizer = struct {...@@ -1217,6 +1219,7 @@ pub const Tokenizer = struct {
1217 result.id = .Invalid;1219 result.id = .Invalid;
1218 break;1220 break;
1219 }1221 }
1222 self.index -= 1;
1220 state = .FloatSuffix;1223 state = .FloatSuffix;
1221 },1224 },
1222 },1225 },
lib/std/child_process.zig+44-15
...@@ -329,17 +329,18 @@ pub const ChildProcess = struct {...@@ -329,17 +329,18 @@ pub const ChildProcess = struct {
329 }329 }
330330
331 fn spawnPosix(self: *ChildProcess) SpawnError!void {331 fn spawnPosix(self: *ChildProcess) SpawnError!void {
332 const stdin_pipe = if (self.stdin_behavior == StdIo.Pipe) try os.pipe() else undefined;332 const pipe_flags = if (io.is_async) os.O_NONBLOCK else 0;
333 const stdin_pipe = if (self.stdin_behavior == StdIo.Pipe) try os.pipe2(pipe_flags) else undefined;
333 errdefer if (self.stdin_behavior == StdIo.Pipe) {334 errdefer if (self.stdin_behavior == StdIo.Pipe) {
334 destroyPipe(stdin_pipe);335 destroyPipe(stdin_pipe);
335 };336 };
336337
337 const stdout_pipe = if (self.stdout_behavior == StdIo.Pipe) try os.pipe() else undefined;338 const stdout_pipe = if (self.stdout_behavior == StdIo.Pipe) try os.pipe2(pipe_flags) else undefined;
338 errdefer if (self.stdout_behavior == StdIo.Pipe) {339 errdefer if (self.stdout_behavior == StdIo.Pipe) {
339 destroyPipe(stdout_pipe);340 destroyPipe(stdout_pipe);
340 };341 };
341342
342 const stderr_pipe = if (self.stderr_behavior == StdIo.Pipe) try os.pipe() else undefined;343 const stderr_pipe = if (self.stderr_behavior == StdIo.Pipe) try os.pipe2(pipe_flags) else undefined;
343 errdefer if (self.stderr_behavior == StdIo.Pipe) {344 errdefer if (self.stderr_behavior == StdIo.Pipe) {
344 destroyPipe(stderr_pipe);345 destroyPipe(stderr_pipe);
345 };346 };
...@@ -426,17 +427,26 @@ pub const ChildProcess = struct {...@@ -426,17 +427,26 @@ pub const ChildProcess = struct {
426 // we are the parent427 // we are the parent
427 const pid = @intCast(i32, pid_result);428 const pid = @intCast(i32, pid_result);
428 if (self.stdin_behavior == StdIo.Pipe) {429 if (self.stdin_behavior == StdIo.Pipe) {
429 self.stdin = File.openHandle(stdin_pipe[1]);430 self.stdin = File{
431 .handle = stdin_pipe[1],
432 .io_mode = std.io.mode,
433 };
430 } else {434 } else {
431 self.stdin = null;435 self.stdin = null;
432 }436 }
433 if (self.stdout_behavior == StdIo.Pipe) {437 if (self.stdout_behavior == StdIo.Pipe) {
434 self.stdout = File.openHandle(stdout_pipe[0]);438 self.stdout = File{
439 .handle = stdout_pipe[0],
440 .io_mode = std.io.mode,
441 };
435 } else {442 } else {
436 self.stdout = null;443 self.stdout = null;
437 }444 }
438 if (self.stderr_behavior == StdIo.Pipe) {445 if (self.stderr_behavior == StdIo.Pipe) {
439 self.stderr = File.openHandle(stderr_pipe[0]);446 self.stderr = File{
447 .handle = stderr_pipe[0],
448 .io_mode = std.io.mode,
449 };
440 } else {450 } else {
441 self.stderr = null;451 self.stderr = null;
442 }452 }
...@@ -661,17 +671,26 @@ pub const ChildProcess = struct {...@@ -661,17 +671,26 @@ pub const ChildProcess = struct {
661 };671 };
662672
663 if (g_hChildStd_IN_Wr) |h| {673 if (g_hChildStd_IN_Wr) |h| {
664 self.stdin = File.openHandle(h);674 self.stdin = File{
675 .handle = h,
676 .io_mode = io.mode,
677 };
665 } else {678 } else {
666 self.stdin = null;679 self.stdin = null;
667 }680 }
668 if (g_hChildStd_OUT_Rd) |h| {681 if (g_hChildStd_OUT_Rd) |h| {
669 self.stdout = File.openHandle(h);682 self.stdout = File{
683 .handle = h,
684 .io_mode = io.mode,
685 };
670 } else {686 } else {
671 self.stdout = null;687 self.stdout = null;
672 }688 }
673 if (g_hChildStd_ERR_Rd) |h| {689 if (g_hChildStd_ERR_Rd) |h| {
674 self.stderr = File.openHandle(h);690 self.stderr = File{
691 .handle = h,
692 .io_mode = io.mode,
693 };
675 } else {694 } else {
676 self.stderr = null;695 self.stderr = null;
677 }696 }
...@@ -693,10 +712,10 @@ pub const ChildProcess = struct {...@@ -693,10 +712,10 @@ pub const ChildProcess = struct {
693712
694 fn setUpChildIo(stdio: StdIo, pipe_fd: i32, std_fileno: i32, dev_null_fd: i32) !void {713 fn setUpChildIo(stdio: StdIo, pipe_fd: i32, std_fileno: i32, dev_null_fd: i32) !void {
695 switch (stdio) {714 switch (stdio) {
696 StdIo.Pipe => try os.dup2(pipe_fd, std_fileno),715 .Pipe => try os.dup2(pipe_fd, std_fileno),
697 StdIo.Close => os.close(std_fileno),716 .Close => os.close(std_fileno),
698 StdIo.Inherit => {},717 .Inherit => {},
699 StdIo.Ignore => try os.dup2(dev_null_fd, std_fileno),718 .Ignore => try os.dup2(dev_null_fd, std_fileno),
700 }719 }
701 }720 }
702};721};
...@@ -811,12 +830,22 @@ fn forkChildErrReport(fd: i32, err: ChildProcess.SpawnError) noreturn {...@@ -811,12 +830,22 @@ fn forkChildErrReport(fd: i32, err: ChildProcess.SpawnError) noreturn {
811const ErrInt = @IntType(false, @sizeOf(anyerror) * 8);830const ErrInt = @IntType(false, @sizeOf(anyerror) * 8);
812831
813fn writeIntFd(fd: i32, value: ErrInt) !void {832fn writeIntFd(fd: i32, value: ErrInt) !void {
814 const stream = &File.openHandle(fd).outStream().stream;833 const file = File{
834 .handle = fd,
835 .io_mode = .blocking,
836 .async_block_allowed = File.async_block_allowed_yes,
837 };
838 const stream = &file.outStream().stream;
815 stream.writeIntNative(u64, @intCast(u64, value)) catch return error.SystemResources;839 stream.writeIntNative(u64, @intCast(u64, value)) catch return error.SystemResources;
816}840}
817841
818fn readIntFd(fd: i32) !ErrInt {842fn readIntFd(fd: i32) !ErrInt {
819 const stream = &File.openHandle(fd).inStream().stream;843 const file = File{
844 .handle = fd,
845 .io_mode = .blocking,
846 .async_block_allowed = File.async_block_allowed_yes,
847 };
848 const stream = &file.inStream().stream;
820 return @intCast(ErrInt, stream.readIntNative(u64) catch return error.SystemResources);849 return @intCast(ErrInt, stream.readIntNative(u64) catch return error.SystemResources);
821}850}
822851
lib/std/crypto/benchmark.zig+1
...@@ -23,6 +23,7 @@ const hashes = [_]Crypto{...@@ -23,6 +23,7 @@ const hashes = [_]Crypto{
23 Crypto{ .ty = crypto.Sha512, .name = "sha512" },23 Crypto{ .ty = crypto.Sha512, .name = "sha512" },
24 Crypto{ .ty = crypto.Sha3_256, .name = "sha3-256" },24 Crypto{ .ty = crypto.Sha3_256, .name = "sha3-256" },
25 Crypto{ .ty = crypto.Sha3_512, .name = "sha3-512" },25 Crypto{ .ty = crypto.Sha3_512, .name = "sha3-512" },
26 Crypto{ .ty = crypto.gimli.Hash, .name = "gimli-hash" },
26 Crypto{ .ty = crypto.Blake2s256, .name = "blake2s" },27 Crypto{ .ty = crypto.Blake2s256, .name = "blake2s" },
27 Crypto{ .ty = crypto.Blake2b512, .name = "blake2b" },28 Crypto{ .ty = crypto.Blake2b512, .name = "blake2b" },
28 Crypto{ .ty = crypto.Blake3, .name = "blake3" },29 Crypto{ .ty = crypto.Blake3, .name = "blake3" },
lib/std/crypto/gimli.zig+221-1
...@@ -19,7 +19,6 @@ pub const State = struct {...@@ -19,7 +19,6 @@ pub const State = struct {
19 pub const BLOCKBYTES = 48;19 pub const BLOCKBYTES = 48;
20 pub const RATE = 16;20 pub const RATE = 16;
2121
22 // TODO: https://github.com/ziglang/zig/issues/2673#issuecomment-501763017
23 data: [BLOCKBYTES / 4]u32,22 data: [BLOCKBYTES / 4]u32,
2423
25 const Self = @This();24 const Self = @This();
...@@ -134,6 +133,8 @@ pub const Hash = struct {...@@ -134,6 +133,8 @@ pub const Hash = struct {
134 }133 }
135 }134 }
136135
136 pub const digest_length = 32;
137
137 /// Finish the current hashing operation, writing the hash to `out`138 /// Finish the current hashing operation, writing the hash to `out`
138 ///139 ///
139 /// From 4.9 "Application to hashing"140 /// From 4.9 "Application to hashing"
...@@ -166,3 +167,222 @@ test "hash" {...@@ -166,3 +167,222 @@ test "hash" {
166 hash(&md, &msg);167 hash(&md, &msg);
167 htest.assertEqual("1C9A03DC6A5DDC5444CFC6F4B154CFF5CF081633B2CEA4D7D0AE7CCFED5AAA44", &md);168 htest.assertEqual("1C9A03DC6A5DDC5444CFC6F4B154CFF5CF081633B2CEA4D7D0AE7CCFED5AAA44", &md);
168}169}
170
171pub const Aead = struct {
172 /// ad: Associated Data
173 /// npub: public nonce
174 /// k: private key
175 fn init(ad: []const u8, npub: [16]u8, k: [32]u8) State {
176 var state = State{
177 .data = undefined,
178 };
179 const buf = state.toSlice();
180
181 // Gimli-Cipher initializes a 48-byte Gimli state to a 16-byte nonce
182 // followed by a 32-byte key.
183 assert(npub.len + k.len == State.BLOCKBYTES);
184 std.mem.copy(u8, buf[0..npub.len], &npub);
185 std.mem.copy(u8, buf[npub.len .. npub.len + k.len], &k);
186
187 // It then applies the Gimli permutation.
188 state.permute();
189
190 {
191 // Gimli-Cipher then handles each block of associated data, including
192 // exactly one final non-full block, in the same way as Gimli-Hash.
193 var data = ad;
194 while (data.len >= State.RATE) : (data = data[State.RATE..]) {
195 for (buf[0..State.RATE]) |*p, i| {
196 p.* ^= data[i];
197 }
198 state.permute();
199 }
200 for (buf[0..data.len]) |*p, i| {
201 p.* ^= data[i];
202 }
203
204 // XOR 1 into the next byte of the state
205 buf[data.len] ^= 1;
206 // XOR 1 into the last byte of the state, position 47.
207 buf[buf.len - 1] ^= 1;
208
209 state.permute();
210 }
211
212 return state;
213 }
214
215 /// c: ciphertext: output buffer should be of size m.len
216 /// at: authentication tag: output MAC
217 /// m: message
218 /// ad: Associated Data
219 /// npub: public nonce
220 /// k: private key
221 pub fn encrypt(c: []u8, at: *[State.RATE]u8, m: []const u8, ad: []const u8, npub: [16]u8, k: [32]u8) void {
222 assert(c.len == m.len);
223
224 var state = Aead.init(ad, npub, k);
225 const buf = state.toSlice();
226
227 // Gimli-Cipher then handles each block of plaintext, including
228 // exactly one final non-full block, in the same way as Gimli-Hash.
229 // Whenever a plaintext byte is XORed into a state byte, the new state
230 // byte is output as ciphertext.
231 var in = m;
232 var out = c;
233 while (in.len >= State.RATE) : ({
234 in = in[State.RATE..];
235 out = out[State.RATE..];
236 }) {
237 for (buf[0..State.RATE]) |*p, i| {
238 p.* ^= in[i];
239 out[i] = p.*;
240 }
241 state.permute();
242 }
243 for (buf[0..in.len]) |*p, i| {
244 p.* ^= in[i];
245 out[i] = p.*;
246 }
247
248 // XOR 1 into the next byte of the state
249 buf[in.len] ^= 1;
250 // XOR 1 into the last byte of the state, position 47.
251 buf[buf.len - 1] ^= 1;
252
253 state.permute();
254
255 // After the final non-full block of plaintext, the first 16 bytes
256 // of the state are output as an authentication tag.
257 std.mem.copy(u8, at, buf[0..State.RATE]);
258 }
259
260 /// m: message: output buffer should be of size c.len
261 /// c: ciphertext
262 /// at: authentication tag
263 /// ad: Associated Data
264 /// npub: public nonce
265 /// k: private key
266 /// NOTE: the check of the authentication tag is currently not done in constant time
267 pub fn decrypt(m: []u8, c: []const u8, at: [State.RATE]u8, ad: []u8, npub: [16]u8, k: [32]u8) !void {
268 assert(c.len == m.len);
269
270 var state = Aead.init(ad, npub, k);
271 const buf = state.toSlice();
272
273 var in = c;
274 var out = m;
275 while (in.len >= State.RATE) : ({
276 in = in[State.RATE..];
277 out = out[State.RATE..];
278 }) {
279 for (buf[0..State.RATE]) |*p, i| {
280 out[i] = p.* ^ in[i];
281 p.* = in[i];
282 }
283 state.permute();
284 }
285 for (buf[0..in.len]) |*p, i| {
286 out[i] = p.* ^ in[i];
287 p.* = in[i];
288 }
289
290 // XOR 1 into the next byte of the state
291 buf[in.len] ^= 1;
292 // XOR 1 into the last byte of the state, position 47.
293 buf[buf.len - 1] ^= 1;
294
295 state.permute();
296
297 // After the final non-full block of plaintext, the first 16 bytes
298 // of the state are the authentication tag.
299 // TODO: use a constant-time equality check here, see https://github.com/ziglang/zig/issues/1776
300 if (!mem.eql(u8, buf[0..State.RATE], &at)) {
301 @memset(m.ptr, undefined, m.len);
302 return error.InvalidMessage;
303 }
304 }
305};
306
307test "cipher" {
308 var key: [32]u8 = undefined;
309 try std.fmt.hexToBytes(&key, "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F");
310 var nonce: [16]u8 = undefined;
311 try std.fmt.hexToBytes(&nonce, "000102030405060708090A0B0C0D0E0F");
312 { // test vector (1) from NIST KAT submission.
313 const ad: [0]u8 = undefined;
314 const pt: [0]u8 = undefined;
315
316 var ct: [pt.len]u8 = undefined;
317 var at: [16]u8 = undefined;
318 Aead.encrypt(&ct, &at, &pt, &ad, nonce, key);
319 htest.assertEqual("", &ct);
320 htest.assertEqual("14DA9BB7120BF58B985A8E00FDEBA15B", &at);
321
322 var pt2: [pt.len]u8 = undefined;
323 try Aead.decrypt(&pt2, &ct, at, &ad, nonce, key);
324 testing.expectEqualSlices(u8, &pt, &pt2);
325 }
326 { // test vector (34) from NIST KAT submission.
327 const ad: [0]u8 = undefined;
328 var pt: [2 / 2]u8 = undefined;
329 try std.fmt.hexToBytes(&pt, "00");
330
331 var ct: [pt.len]u8 = undefined;
332 var at: [16]u8 = undefined;
333 Aead.encrypt(&ct, &at, &pt, &ad, nonce, key);
334 htest.assertEqual("7F", &ct);
335 htest.assertEqual("80492C317B1CD58A1EDC3A0D3E9876FC", &at);
336
337 var pt2: [pt.len]u8 = undefined;
338 try Aead.decrypt(&pt2, &ct, at, &ad, nonce, key);
339 testing.expectEqualSlices(u8, &pt, &pt2);
340 }
341 { // test vector (106) from NIST KAT submission.
342 var ad: [12 / 2]u8 = undefined;
343 try std.fmt.hexToBytes(&ad, "000102030405");
344 var pt: [6 / 2]u8 = undefined;
345 try std.fmt.hexToBytes(&pt, "000102");
346
347 var ct: [pt.len]u8 = undefined;
348 var at: [16]u8 = undefined;
349 Aead.encrypt(&ct, &at, &pt, &ad, nonce, key);
350 htest.assertEqual("484D35", &ct);
351 htest.assertEqual("030BBEA23B61C00CED60A923BDCF9147", &at);
352
353 var pt2: [pt.len]u8 = undefined;
354 try Aead.decrypt(&pt2, &ct, at, &ad, nonce, key);
355 testing.expectEqualSlices(u8, &pt, &pt2);
356 }
357 { // test vector (790) from NIST KAT submission.
358 var ad: [60 / 2]u8 = undefined;
359 try std.fmt.hexToBytes(&ad, "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D");
360 var pt: [46 / 2]u8 = undefined;
361 try std.fmt.hexToBytes(&pt, "000102030405060708090A0B0C0D0E0F10111213141516");
362
363 var ct: [pt.len]u8 = undefined;
364 var at: [16]u8 = undefined;
365 Aead.encrypt(&ct, &at, &pt, &ad, nonce, key);
366 htest.assertEqual("6815B4A0ECDAD01596EAD87D9E690697475D234C6A13D1", &ct);
367 htest.assertEqual("DFE23F1642508290D68245279558B2FB", &at);
368
369 var pt2: [pt.len]u8 = undefined;
370 try Aead.decrypt(&pt2, &ct, at, &ad, nonce, key);
371 testing.expectEqualSlices(u8, &pt, &pt2);
372 }
373 { // test vector (1057) from NIST KAT submission.
374 const ad: [0]u8 = undefined;
375 var pt: [64 / 2]u8 = undefined;
376 try std.fmt.hexToBytes(&pt, "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F");
377
378 var ct: [pt.len]u8 = undefined;
379 var at: [16]u8 = undefined;
380 Aead.encrypt(&ct, &at, &pt, &ad, nonce, key);
381 htest.assertEqual("7F8A2CF4F52AA4D6B2E74105C30A2777B9D0C8AEFDD555DE35861BD3011F652F", &ct);
382 htest.assertEqual("7256456FA935AC34BBF55AE135F33257", &at);
383
384 var pt2: [pt.len]u8 = undefined;
385 try Aead.decrypt(&pt2, &ct, at, &ad, nonce, key);
386 testing.expectEqualSlices(u8, &pt, &pt2);
387 }
388}
lib/std/debug.zig+74-54
...@@ -50,7 +50,7 @@ pub fn warn(comptime fmt: []const u8, args: var) void {...@@ -50,7 +50,7 @@ pub fn warn(comptime fmt: []const u8, args: var) void {
50 const held = stderr_mutex.acquire();50 const held = stderr_mutex.acquire();
51 defer held.release();51 defer held.release();
52 const stderr = getStderrStream();52 const stderr = getStderrStream();
53 stderr.print(fmt, args) catch return;53 noasync stderr.print(fmt, args) catch return;
54}54}
5555
56pub fn getStderrStream() *io.OutStream(File.WriteError) {56pub fn getStderrStream() *io.OutStream(File.WriteError) {
...@@ -102,15 +102,15 @@ pub fn detectTTYConfig() TTY.Config {...@@ -102,15 +102,15 @@ pub fn detectTTYConfig() TTY.Config {
102pub fn dumpCurrentStackTrace(start_addr: ?usize) void {102pub fn dumpCurrentStackTrace(start_addr: ?usize) void {
103 const stderr = getStderrStream();103 const stderr = getStderrStream();
104 if (builtin.strip_debug_info) {104 if (builtin.strip_debug_info) {
105 stderr.print("Unable to dump stack trace: debug info stripped\n", .{}) catch return;105 noasync stderr.print("Unable to dump stack trace: debug info stripped\n", .{}) catch return;
106 return;106 return;
107 }107 }
108 const debug_info = getSelfDebugInfo() catch |err| {108 const debug_info = getSelfDebugInfo() catch |err| {
109 stderr.print("Unable to dump stack trace: Unable to open debug info: {}\n", .{@errorName(err)}) catch return;109 noasync stderr.print("Unable to dump stack trace: Unable to open debug info: {}\n", .{@errorName(err)}) catch return;
110 return;110 return;
111 };111 };
112 writeCurrentStackTrace(stderr, debug_info, detectTTYConfig(), start_addr) catch |err| {112 writeCurrentStackTrace(stderr, debug_info, detectTTYConfig(), start_addr) catch |err| {
113 stderr.print("Unable to dump stack trace: {}\n", .{@errorName(err)}) catch return;113 noasync stderr.print("Unable to dump stack trace: {}\n", .{@errorName(err)}) catch return;
114 return;114 return;
115 };115 };
116}116}
...@@ -121,22 +121,16 @@ pub fn dumpCurrentStackTrace(start_addr: ?usize) void {...@@ -121,22 +121,16 @@ pub fn dumpCurrentStackTrace(start_addr: ?usize) void {
121pub fn dumpStackTraceFromBase(bp: usize, ip: usize) void {121pub fn dumpStackTraceFromBase(bp: usize, ip: usize) void {
122 const stderr = getStderrStream();122 const stderr = getStderrStream();
123 if (builtin.strip_debug_info) {123 if (builtin.strip_debug_info) {
124 stderr.print("Unable to dump stack trace: debug info stripped\n", .{}) catch return;124 noasync stderr.print("Unable to dump stack trace: debug info stripped\n", .{}) catch return;
125 return;125 return;
126 }126 }
127 const debug_info = getSelfDebugInfo() catch |err| {127 const debug_info = getSelfDebugInfo() catch |err| {
128 stderr.print("Unable to dump stack trace: Unable to open debug info: {}\n", .{@errorName(err)}) catch return;128 noasync stderr.print("Unable to dump stack trace: Unable to open debug info: {}\n", .{@errorName(err)}) catch return;
129 return;129 return;
130 };130 };
131 const tty_config = detectTTYConfig();131 const tty_config = detectTTYConfig();
132 printSourceAtAddress(debug_info, stderr, ip, tty_config) catch return;132 printSourceAtAddress(debug_info, stderr, ip, tty_config) catch return;
133 const first_return_address = @intToPtr(*const usize, bp + @sizeOf(usize)).*;133 var it = StackIterator.init(null, bp);
134 if (first_return_address == 0) return; // The whole call stack may be optimized out
135 printSourceAtAddress(debug_info, stderr, first_return_address - 1, tty_config) catch return;
136 var it = StackIterator{
137 .first_addr = null,
138 .fp = bp,
139 };
140 while (it.next()) |return_address| {134 while (it.next()) |return_address| {
141 printSourceAtAddress(debug_info, stderr, return_address - 1, tty_config) catch return;135 printSourceAtAddress(debug_info, stderr, return_address - 1, tty_config) catch return;
142 }136 }
...@@ -179,7 +173,7 @@ pub fn captureStackTrace(first_address: ?usize, stack_trace: *builtin.StackTrace...@@ -179,7 +173,7 @@ pub fn captureStackTrace(first_address: ?usize, stack_trace: *builtin.StackTrace
179 }173 }
180 stack_trace.index = slice.len;174 stack_trace.index = slice.len;
181 } else {175 } else {
182 var it = StackIterator.init(first_address);176 var it = StackIterator.init(first_address, null);
183 for (stack_trace.instruction_addresses) |*addr, i| {177 for (stack_trace.instruction_addresses) |*addr, i| {
184 addr.* = it.next() orelse {178 addr.* = it.next() orelse {
185 stack_trace.index = i;179 stack_trace.index = i;
...@@ -195,15 +189,15 @@ pub fn captureStackTrace(first_address: ?usize, stack_trace: *builtin.StackTrace...@@ -195,15 +189,15 @@ pub fn captureStackTrace(first_address: ?usize, stack_trace: *builtin.StackTrace
195pub fn dumpStackTrace(stack_trace: builtin.StackTrace) void {189pub fn dumpStackTrace(stack_trace: builtin.StackTrace) void {
196 const stderr = getStderrStream();190 const stderr = getStderrStream();
197 if (builtin.strip_debug_info) {191 if (builtin.strip_debug_info) {
198 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;
199 return;193 return;
200 }194 }
201 const debug_info = getSelfDebugInfo() catch |err| {195 const debug_info = getSelfDebugInfo() catch |err| {
202 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;
203 return;197 return;
204 };198 };
205 writeStackTrace(stack_trace, stderr, getDebugInfoAllocator(), debug_info, detectTTYConfig()) catch |err| {199 writeStackTrace(stack_trace, stderr, getDebugInfoAllocator(), debug_info, detectTTYConfig()) catch |err| {
206 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;
207 return;201 return;
208 };202 };
209}203}
...@@ -244,7 +238,7 @@ pub fn panicExtra(trace: ?*const builtin.StackTrace, first_trace_addr: ?usize, c...@@ -244,7 +238,7 @@ pub fn panicExtra(trace: ?*const builtin.StackTrace, first_trace_addr: ?usize, c
244 switch (@atomicRmw(u8, &panicking, .Add, 1, .SeqCst)) {238 switch (@atomicRmw(u8, &panicking, .Add, 1, .SeqCst)) {
245 0 => {239 0 => {
246 const stderr = getStderrStream();240 const stderr = getStderrStream();
247 stderr.print(format ++ "\n", args) catch os.abort();241 noasync stderr.print(format ++ "\n", args) catch os.abort();
248 if (trace) |t| {242 if (trace) |t| {
249 dumpStackTrace(t.*);243 dumpStackTrace(t.*);
250 }244 }
...@@ -291,13 +285,15 @@ pub fn writeStackTrace(...@@ -291,13 +285,15 @@ pub fn writeStackTrace(
291}285}
292286
293pub const StackIterator = struct {287pub const StackIterator = struct {
294 first_addr: ?usize,288 // Skip every frame before this address is found
289 first_address: ?usize,
290 // Last known value of the frame pointer register
295 fp: usize,291 fp: usize,
296292
297 pub fn init(first_addr: ?usize) StackIterator {293 pub fn init(first_address: ?usize, fp: ?usize) StackIterator {
298 return StackIterator{294 return StackIterator{
299 .first_addr = first_addr,295 .first_address = first_address,
300 .fp = @frameAddress(),296 .fp = fp orelse @frameAddress(),
301 };297 };
302 }298 }
303299
...@@ -305,29 +301,45 @@ pub const StackIterator = struct {...@@ -305,29 +301,45 @@ pub const StackIterator = struct {
305 // the previous fp is stored, while on some other architectures such as301 // the previous fp is stored, while on some other architectures such as
306 // RISC-V it points to the "top" of the frame, just above where the previous302 // RISC-V it points to the "top" of the frame, just above where the previous
307 // fp and the return address are stored.303 // fp and the return address are stored.
308 const fp_adjust_factor = if (builtin.arch == .riscv32 or builtin.arch == .riscv64)304 const fp_offset = if (builtin.arch.isRISCV())
309 2 * @sizeOf(usize)305 2 * @sizeOf(usize)
310 else306 else
311 0;307 0;
312308
313 fn next(self: *StackIterator) ?usize {309 fn next(self: *StackIterator) ?usize {
314 if (self.fp <= fp_adjust_factor) return null;310 var address = self.next_internal() orelse return null;
315 self.fp = @intToPtr(*const usize, self.fp - fp_adjust_factor).*;311
316 if (self.fp <= fp_adjust_factor) return null;312 if (self.first_address) |first_address| {
317313 while (address != first_address) {
318 if (self.first_addr) |addr| {314 address = self.next_internal() orelse return null;
319 while (self.fp > fp_adjust_factor) : (self.fp = @intToPtr(*const usize, self.fp - fp_adjust_factor).*) {
320 const return_address = @intToPtr(*const usize, self.fp - fp_adjust_factor + @sizeOf(usize)).*;
321 if (addr == return_address) {
322 self.first_addr = null;
323 return return_address;
324 }
325 }315 }
316 self.first_address = null;
326 }317 }
327318
328 const return_address = @intToPtr(*const usize, self.fp - fp_adjust_factor + @sizeOf(usize)).*;319 return address;
329 if (return_address == 0) return null;320 }
330 return return_address;321
322 fn next_internal(self: *StackIterator) ?usize {
323 const fp = math.sub(usize, self.fp, fp_offset) catch return null;
324
325 // Sanity check
326 if (fp == 0 or !mem.isAligned(fp, @alignOf(usize)))
327 return null;
328
329 const new_fp = @intToPtr(*const usize, fp).*;
330
331 // Sanity check: the stack grows down thus all the parent frames must be
332 // be at addresses that are greater (or equal) than the previous one.
333 // A zero frame pointer often signals this is the last frame, that case
334 // is gracefully handled by the next call to next_internal
335 if (new_fp != 0 and new_fp < self.fp)
336 return null;
337
338 const new_pc = @intToPtr(*const usize, fp + @sizeOf(usize)).*;
339
340 self.fp = new_fp;
341
342 return new_pc;
331 }343 }
332};344};
333345
...@@ -340,7 +352,7 @@ pub fn writeCurrentStackTrace(...@@ -340,7 +352,7 @@ pub fn writeCurrentStackTrace(
340 if (builtin.os == .windows) {352 if (builtin.os == .windows) {
341 return writeCurrentStackTraceWindows(out_stream, debug_info, tty_config, start_addr);353 return writeCurrentStackTraceWindows(out_stream, debug_info, tty_config, start_addr);
342 }354 }
343 var it = StackIterator.init(start_addr);355 var it = StackIterator.init(start_addr, null);
344 while (it.next()) |return_address| {356 while (it.next()) |return_address| {
345 try printSourceAtAddress(debug_info, out_stream, return_address - 1, tty_config);357 try printSourceAtAddress(debug_info, out_stream, return_address - 1, tty_config);
346 }358 }
...@@ -378,6 +390,7 @@ pub fn printSourceAtAddress(debug_info: *DebugInfo, out_stream: var, address: us...@@ -378,6 +390,7 @@ pub fn printSourceAtAddress(debug_info: *DebugInfo, out_stream: var, address: us
378 return noasync printSourceAtAddressPosix(debug_info, out_stream, address, tty_config);390 return noasync printSourceAtAddressPosix(debug_info, out_stream, address, tty_config);
379}391}
380392
393/// TODO resources https://github.com/ziglang/zig/issues/4353
381fn printSourceAtAddressWindows(394fn printSourceAtAddressWindows(
382 di: *DebugInfo,395 di: *DebugInfo,
383 out_stream: var,396 out_stream: var,
...@@ -555,12 +568,12 @@ pub const TTY = struct {...@@ -555,12 +568,12 @@ pub const TTY = struct {
555 switch (conf) {568 switch (conf) {
556 .no_color => return,569 .no_color => return,
557 .escape_codes => switch (color) {570 .escape_codes => switch (color) {
558 .Red => out_stream.write(RED) catch return,571 .Red => noasync out_stream.write(RED) catch return,
559 .Green => out_stream.write(GREEN) catch return,572 .Green => noasync out_stream.write(GREEN) catch return,
560 .Cyan => out_stream.write(CYAN) catch return,573 .Cyan => noasync out_stream.write(CYAN) catch return,
561 .White, .Bold => out_stream.write(WHITE) catch return,574 .White, .Bold => noasync out_stream.write(WHITE) catch return,
562 .Dim => out_stream.write(DIM) catch return,575 .Dim => noasync out_stream.write(DIM) catch return,
563 .Reset => out_stream.write(RESET) catch return,576 .Reset => noasync out_stream.write(RESET) catch return,
564 },577 },
565 .windows_api => if (builtin.os == .windows) {578 .windows_api => if (builtin.os == .windows) {
566 const S = struct {579 const S = struct {
...@@ -604,6 +617,7 @@ pub const TTY = struct {...@@ -604,6 +617,7 @@ pub const TTY = struct {
604 };617 };
605};618};
606619
620/// TODO resources https://github.com/ziglang/zig/issues/4353
607fn populateModule(di: *DebugInfo, mod: *Module) !void {621fn populateModule(di: *DebugInfo, mod: *Module) !void {
608 if (mod.populated)622 if (mod.populated)
609 return;623 return;
...@@ -715,17 +729,17 @@ fn printLineInfo(...@@ -715,17 +729,17 @@ fn printLineInfo(
715 tty_config.setColor(out_stream, .White);729 tty_config.setColor(out_stream, .White);
716730
717 if (line_info) |*li| {731 if (line_info) |*li| {
718 try out_stream.print("{}:{}:{}", .{ li.file_name, li.line, li.column });732 try noasync out_stream.print("{}:{}:{}", .{ li.file_name, li.line, li.column });
719 } else {733 } else {
720 try out_stream.print("???:?:?", .{});734 try noasync out_stream.write("???:?:?");
721 }735 }
722736
723 tty_config.setColor(out_stream, .Reset);737 tty_config.setColor(out_stream, .Reset);
724 try out_stream.write(": ");738 try noasync out_stream.write(": ");
725 tty_config.setColor(out_stream, .Dim);739 tty_config.setColor(out_stream, .Dim);
726 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 });
727 tty_config.setColor(out_stream, .Reset);741 tty_config.setColor(out_stream, .Reset);
728 try out_stream.write("\n");742 try noasync out_stream.write("\n");
729743
730 // Show the matching source code line if possible744 // Show the matching source code line if possible
731 if (line_info) |li| {745 if (line_info) |li| {
...@@ -734,12 +748,12 @@ fn printLineInfo(...@@ -734,12 +748,12 @@ fn printLineInfo(
734 // The caret already takes one char748 // The caret already takes one char
735 const space_needed = @intCast(usize, li.column - 1);749 const space_needed = @intCast(usize, li.column - 1);
736750
737 try out_stream.writeByteNTimes(' ', space_needed);751 try noasync out_stream.writeByteNTimes(' ', space_needed);
738 tty_config.setColor(out_stream, .Green);752 tty_config.setColor(out_stream, .Green);
739 try out_stream.write("^");753 try noasync out_stream.write("^");
740 tty_config.setColor(out_stream, .Reset);754 tty_config.setColor(out_stream, .Reset);
741 }755 }
742 try out_stream.write("\n");756 try noasync out_stream.write("\n");
743 } else |err| switch (err) {757 } else |err| switch (err) {
744 error.EndOfFile, error.FileNotFound => {},758 error.EndOfFile, error.FileNotFound => {},
745 error.BadPathName => {},759 error.BadPathName => {},
...@@ -755,6 +769,7 @@ pub const OpenSelfDebugInfoError = error{...@@ -755,6 +769,7 @@ pub const OpenSelfDebugInfoError = error{
755 UnsupportedOperatingSystem,769 UnsupportedOperatingSystem,
756};770};
757771
772/// TODO resources https://github.com/ziglang/zig/issues/4353
758/// TODO once https://github.com/ziglang/zig/issues/3157 is fully implemented,773/// TODO once https://github.com/ziglang/zig/issues/3157 is fully implemented,
759/// make this `noasync fn` and remove the individual noasync calls.774/// make this `noasync fn` and remove the individual noasync calls.
760pub fn openSelfDebugInfo(allocator: *mem.Allocator) !DebugInfo {775pub fn openSelfDebugInfo(allocator: *mem.Allocator) !DebugInfo {
...@@ -963,6 +978,7 @@ pub fn openDwarfDebugInfo(di: *DwarfInfo, allocator: *mem.Allocator) !void {...@@ -963,6 +978,7 @@ pub fn openDwarfDebugInfo(di: *DwarfInfo, allocator: *mem.Allocator) !void {
963 try di.scanAllCompileUnits();978 try di.scanAllCompileUnits();
964}979}
965980
981/// TODO resources https://github.com/ziglang/zig/issues/4353
966pub fn openElfDebugInfo(982pub fn openElfDebugInfo(
967 allocator: *mem.Allocator,983 allocator: *mem.Allocator,
968 data: []u8,984 data: []u8,
...@@ -997,12 +1013,11 @@ pub fn openElfDebugInfo(...@@ -997,12 +1013,11 @@ pub fn openElfDebugInfo(
997 null,1013 null,
998 };1014 };
9991015
1000 efile.close();
1001
1002 try openDwarfDebugInfo(&di, allocator);1016 try openDwarfDebugInfo(&di, allocator);
1003 return di;1017 return di;
1004}1018}
10051019
1020/// TODO resources https://github.com/ziglang/zig/issues/4353
1006fn openSelfDebugInfoPosix(allocator: *mem.Allocator) !DwarfInfo {1021fn openSelfDebugInfoPosix(allocator: *mem.Allocator) !DwarfInfo {
1007 var exe_file = try fs.openSelfExe();1022 var exe_file = try fs.openSelfExe();
1008 errdefer exe_file.close();1023 errdefer exe_file.close();
...@@ -1022,6 +1037,7 @@ fn openSelfDebugInfoPosix(allocator: *mem.Allocator) !DwarfInfo {...@@ -1022,6 +1037,7 @@ fn openSelfDebugInfoPosix(allocator: *mem.Allocator) !DwarfInfo {
1022 return openElfDebugInfo(allocator, exe_mmap);1037 return openElfDebugInfo(allocator, exe_mmap);
1023}1038}
10241039
1040/// TODO resources https://github.com/ziglang/zig/issues/4353
1025fn openSelfDebugInfoMacOs(allocator: *mem.Allocator) !DebugInfo {1041fn openSelfDebugInfoMacOs(allocator: *mem.Allocator) !DebugInfo {
1026 const hdr = &std.c._mh_execute_header;1042 const hdr = &std.c._mh_execute_header;
1027 assert(hdr.magic == std.macho.MH_MAGIC_64);1043 assert(hdr.magic == std.macho.MH_MAGIC_64);
...@@ -2074,6 +2090,7 @@ fn getAbbrevTableEntry(abbrev_table: *const AbbrevTable, abbrev_code: u64) ?*con...@@ -2074,6 +2090,7 @@ fn getAbbrevTableEntry(abbrev_table: *const AbbrevTable, abbrev_code: u64) ?*con
2074 return null;2090 return null;
2075}2091}
20762092
2093/// TODO resources https://github.com/ziglang/zig/issues/4353
2077fn getLineNumberInfoMacOs(di: *DebugInfo, symbol: MachoSymbol, address: usize) !LineInfo {2094fn getLineNumberInfoMacOs(di: *DebugInfo, symbol: MachoSymbol, address: usize) !LineInfo {
2078 const ofile = symbol.ofile orelse return error.MissingDebugInfo;2095 const ofile = symbol.ofile orelse return error.MissingDebugInfo;
2079 const gop = try di.ofiles.getOrPut(ofile);2096 const gop = try di.ofiles.getOrPut(ofile);
...@@ -2239,6 +2256,7 @@ pub fn attachSegfaultHandler() void {...@@ -2239,6 +2256,7 @@ pub fn attachSegfaultHandler() void {
22392256
2240 os.sigaction(os.SIGSEGV, &act, null);2257 os.sigaction(os.SIGSEGV, &act, null);
2241 os.sigaction(os.SIGILL, &act, null);2258 os.sigaction(os.SIGILL, &act, null);
2259 os.sigaction(os.SIGBUS, &act, null);
2242}2260}
22432261
2244fn resetSegfaultHandler() void {2262fn resetSegfaultHandler() void {
...@@ -2256,6 +2274,7 @@ fn resetSegfaultHandler() void {...@@ -2256,6 +2274,7 @@ fn resetSegfaultHandler() void {
2256 };2274 };
2257 os.sigaction(os.SIGSEGV, &act, null);2275 os.sigaction(os.SIGSEGV, &act, null);
2258 os.sigaction(os.SIGILL, &act, null);2276 os.sigaction(os.SIGILL, &act, null);
2277 os.sigaction(os.SIGBUS, &act, null);
2259}2278}
22602279
2261fn handleSegfaultLinux(sig: i32, info: *const os.siginfo_t, ctx_ptr: *const c_void) callconv(.C) noreturn {2280fn handleSegfaultLinux(sig: i32, info: *const os.siginfo_t, ctx_ptr: *const c_void) callconv(.C) noreturn {
...@@ -2268,6 +2287,7 @@ fn handleSegfaultLinux(sig: i32, info: *const os.siginfo_t, ctx_ptr: *const c_vo...@@ -2268,6 +2287,7 @@ fn handleSegfaultLinux(sig: i32, info: *const os.siginfo_t, ctx_ptr: *const c_vo
2268 switch (sig) {2287 switch (sig) {
2269 os.SIGSEGV => std.debug.warn("Segmentation fault at address 0x{x}\n", .{addr}),2288 os.SIGSEGV => std.debug.warn("Segmentation fault at address 0x{x}\n", .{addr}),
2270 os.SIGILL => std.debug.warn("Illegal instruction at address 0x{x}\n", .{addr}),2289 os.SIGILL => std.debug.warn("Illegal instruction at address 0x{x}\n", .{addr}),
2290 os.SIGBUS => std.debug.warn("Bus error at address 0x{x}\n", .{addr}),
2271 else => unreachable,2291 else => unreachable,
2272 }2292 }
2273 switch (builtin.arch) {2293 switch (builtin.arch) {
lib/std/event.zig-2
...@@ -6,11 +6,9 @@ pub const Locked = @import("event/locked.zig").Locked;...@@ -6,11 +6,9 @@ pub const Locked = @import("event/locked.zig").Locked;
6pub const RwLock = @import("event/rwlock.zig").RwLock;6pub const RwLock = @import("event/rwlock.zig").RwLock;
7pub const RwLocked = @import("event/rwlocked.zig").RwLocked;7pub const RwLocked = @import("event/rwlocked.zig").RwLocked;
8pub const Loop = @import("event/loop.zig").Loop;8pub const Loop = @import("event/loop.zig").Loop;
9pub const fs = @import("event/fs.zig");
109
11test "import event tests" {10test "import event tests" {
12 _ = @import("event/channel.zig");11 _ = @import("event/channel.zig");
13 _ = @import("event/fs.zig");
14 _ = @import("event/future.zig");12 _ = @import("event/future.zig");
15 _ = @import("event/group.zig");13 _ = @import("event/group.zig");
16 _ = @import("event/lock.zig");14 _ = @import("event/lock.zig");
lib/std/event/channel.zig+3-4
...@@ -267,17 +267,16 @@ pub fn Channel(comptime T: type) type {...@@ -267,17 +267,16 @@ pub fn Channel(comptime T: type) type {
267}267}
268268
269test "std.event.Channel" {269test "std.event.Channel" {
270 if (!std.io.is_async) return error.SkipZigTest;
271
270 // https://github.com/ziglang/zig/issues/1908272 // https://github.com/ziglang/zig/issues/1908
271 if (builtin.single_threaded) return error.SkipZigTest;273 if (builtin.single_threaded) return error.SkipZigTest;
272274
273 // https://github.com/ziglang/zig/issues/3251275 // https://github.com/ziglang/zig/issues/3251
274 if (builtin.os == .freebsd) return error.SkipZigTest;276 if (builtin.os == .freebsd) return error.SkipZigTest;
275277
276 // TODO provide a way to run tests in evented I/O mode
277 if (!std.io.is_async) return error.SkipZigTest;
278
279 var channel: Channel(i32) = undefined;278 var channel: Channel(i32) = undefined;
280 channel.init([0]i32{});279 channel.init(&[0]i32{});
281 defer channel.deinit();280 defer channel.deinit();
282281
283 var handle = async testChannelGetter(&channel);282 var handle = async testChannelGetter(&channel);
lib/std/event/fs.zig deleted-1418
...@@ -1,1418 +0,0 @@
1const builtin = @import("builtin");
2const std = @import("../std.zig");
3const event = std.event;
4const assert = std.debug.assert;
5const testing = std.testing;
6const os = std.os;
7const mem = std.mem;
8const windows = os.windows;
9const Loop = event.Loop;
10const fd_t = os.fd_t;
11const File = std.fs.File;
12const Allocator = mem.Allocator;
13
14//! TODO mege this with `std.fs`
15
16const global_event_loop = Loop.instance orelse
17 @compileError("std.event.fs currently only works with event-based I/O");
18
19pub const RequestNode = std.atomic.Queue(Request).Node;
20
21pub const Request = struct {
22 msg: Msg,
23 finish: Finish,
24
25 pub const Finish = union(enum) {
26 TickNode: Loop.NextTickNode,
27 DeallocCloseOperation: *CloseOperation,
28 NoAction,
29 };
30
31 pub const Msg = union(enum) {
32 WriteV: WriteV,
33 PWriteV: PWriteV,
34 PReadV: PReadV,
35 Open: Open,
36 Close: Close,
37 WriteFile: WriteFile,
38 End, // special - means the fs thread should exit
39
40 pub const WriteV = struct {
41 fd: fd_t,
42 iov: []const os.iovec_const,
43 result: Error!void,
44
45 pub const Error = os.WriteError;
46 };
47
48 pub const PWriteV = struct {
49 fd: fd_t,
50 iov: []const os.iovec_const,
51 offset: usize,
52 result: Error!void,
53
54 pub const Error = os.WriteError;
55 };
56
57 pub const PReadV = struct {
58 fd: fd_t,
59 iov: []const os.iovec,
60 offset: usize,
61 result: Error!usize,
62
63 pub const Error = os.ReadError;
64 };
65
66 pub const Open = struct {
67 path: [:0]const u8,
68 flags: u32,
69 mode: File.Mode,
70 result: Error!fd_t,
71
72 pub const Error = File.OpenError;
73 };
74
75 pub const WriteFile = struct {
76 path: [:0]const u8,
77 contents: []const u8,
78 mode: File.Mode,
79 result: Error!void,
80
81 pub const Error = File.OpenError || File.WriteError;
82 };
83
84 pub const Close = struct {
85 fd: fd_t,
86 };
87 };
88};
89
90pub const PWriteVError = error{OutOfMemory} || File.WriteError;
91
92/// data - just the inner references - must live until pwritev frame completes.
93pub fn pwritev(allocator: *Allocator, fd: fd_t, data: []const []const u8, offset: usize) PWriteVError!void {
94 switch (builtin.os) {
95 .macosx,
96 .linux,
97 .freebsd,
98 .netbsd,
99 .dragonfly,
100 => {
101 const iovecs = try allocator.alloc(os.iovec_const, data.len);
102 defer allocator.free(iovecs);
103
104 for (data) |buf, i| {
105 iovecs[i] = os.iovec_const{
106 .iov_base = buf.ptr,
107 .iov_len = buf.len,
108 };
109 }
110
111 return pwritevPosix(fd, iovecs, offset);
112 },
113 .windows => {
114 const data_copy = try std.mem.dupe(allocator, []const u8, data);
115 defer allocator.free(data_copy);
116 return pwritevWindows(fd, data, offset);
117 },
118 else => @compileError("Unsupported OS"),
119 }
120}
121
122/// data must outlive the returned frame
123pub fn pwritevWindows(fd: fd_t, data: []const []const u8, offset: usize) os.WindowsWriteError!void {
124 if (data.len == 0) return;
125 if (data.len == 1) return pwriteWindows(fd, data[0], offset);
126
127 // TODO do these in parallel
128 var off = offset;
129 for (data) |buf| {
130 try pwriteWindows(fd, buf, off);
131 off += buf.len;
132 }
133}
134
135pub fn pwriteWindows(fd: fd_t, data: []const u8, offset: u64) os.WindowsWriteError!void {
136 var resume_node = Loop.ResumeNode.Basic{
137 .base = Loop.ResumeNode{
138 .id = Loop.ResumeNode.Id.Basic,
139 .handle = @frame(),
140 .overlapped = windows.OVERLAPPED{
141 .Internal = 0,
142 .InternalHigh = 0,
143 .Offset = @truncate(u32, offset),
144 .OffsetHigh = @truncate(u32, offset >> 32),
145 .hEvent = null,
146 },
147 },
148 };
149 // TODO only call create io completion port once per fd
150 _ = windows.CreateIoCompletionPort(fd, global_event_loop.os_data.io_port, undefined, undefined);
151 global_event_loop.beginOneEvent();
152 errdefer global_event_loop.finishOneEvent();
153
154 errdefer {
155 _ = windows.kernel32.CancelIoEx(fd, &resume_node.base.overlapped);
156 }
157 suspend {
158 _ = windows.kernel32.WriteFile(fd, data.ptr, @intCast(windows.DWORD, data.len), null, &resume_node.base.overlapped);
159 }
160 var bytes_transferred: windows.DWORD = undefined;
161 if (windows.kernel32.GetOverlappedResult(fd, &resume_node.base.overlapped, &bytes_transferred, windows.FALSE) == 0) {
162 switch (windows.kernel32.GetLastError()) {
163 .IO_PENDING => unreachable,
164 .INVALID_USER_BUFFER => return error.SystemResources,
165 .NOT_ENOUGH_MEMORY => return error.SystemResources,
166 .OPERATION_ABORTED => return error.OperationAborted,
167 .NOT_ENOUGH_QUOTA => return error.SystemResources,
168 .BROKEN_PIPE => return error.BrokenPipe,
169 else => |err| return windows.unexpectedError(err),
170 }
171 }
172}
173
174/// iovecs must live until pwritev frame completes.
175pub fn pwritevPosix(fd: fd_t, iovecs: []const os.iovec_const, offset: usize) os.WriteError!void {
176 var req_node = RequestNode{
177 .prev = null,
178 .next = null,
179 .data = Request{
180 .msg = Request.Msg{
181 .PWriteV = Request.Msg.PWriteV{
182 .fd = fd,
183 .iov = iovecs,
184 .offset = offset,
185 .result = undefined,
186 },
187 },
188 .finish = Request.Finish{
189 .TickNode = Loop.NextTickNode{
190 .prev = null,
191 .next = null,
192 .data = @frame(),
193 },
194 },
195 },
196 };
197
198 errdefer global_event_loop.posixFsCancel(&req_node);
199
200 suspend {
201 global_event_loop.posixFsRequest(&req_node);
202 }
203
204 return req_node.data.msg.PWriteV.result;
205}
206
207/// iovecs must live until pwritev frame completes.
208pub fn writevPosix(fd: fd_t, iovecs: []const os.iovec_const) os.WriteError!void {
209 var req_node = RequestNode{
210 .prev = null,
211 .next = null,
212 .data = Request{
213 .msg = Request.Msg{
214 .WriteV = Request.Msg.WriteV{
215 .fd = fd,
216 .iov = iovecs,
217 .result = undefined,
218 },
219 },
220 .finish = Request.Finish{
221 .TickNode = Loop.NextTickNode{
222 .prev = null,
223 .next = null,
224 .data = @frame(),
225 },
226 },
227 },
228 };
229
230 suspend {
231 global_event_loop.posixFsRequest(&req_node);
232 }
233
234 return req_node.data.msg.WriteV.result;
235}
236
237pub const PReadVError = error{OutOfMemory} || File.ReadError;
238
239/// data - just the inner references - must live until preadv frame completes.
240pub fn preadv(allocator: *Allocator, fd: fd_t, data: []const []u8, offset: usize) PReadVError!usize {
241 assert(data.len != 0);
242 switch (builtin.os) {
243 .macosx,
244 .linux,
245 .freebsd,
246 .netbsd,
247 .dragonfly,
248 => {
249 const iovecs = try allocator.alloc(os.iovec, data.len);
250 defer allocator.free(iovecs);
251
252 for (data) |buf, i| {
253 iovecs[i] = os.iovec{
254 .iov_base = buf.ptr,
255 .iov_len = buf.len,
256 };
257 }
258
259 return preadvPosix(fd, iovecs, offset);
260 },
261 .windows => {
262 const data_copy = try std.mem.dupe(allocator, []u8, data);
263 defer allocator.free(data_copy);
264 return preadvWindows(fd, data_copy, offset);
265 },
266 else => @compileError("Unsupported OS"),
267 }
268}
269
270/// data must outlive the returned frame
271pub fn preadvWindows(fd: fd_t, data: []const []u8, offset: u64) !usize {
272 assert(data.len != 0);
273 if (data.len == 1) return preadWindows(fd, data[0], offset);
274
275 // TODO do these in parallel?
276 var off: usize = 0;
277 var iov_i: usize = 0;
278 var inner_off: usize = 0;
279 while (true) {
280 const v = data[iov_i];
281 const amt_read = try preadWindows(fd, v[inner_off .. v.len - inner_off], offset + off);
282 off += amt_read;
283 inner_off += amt_read;
284 if (inner_off == v.len) {
285 iov_i += 1;
286 inner_off = 0;
287 if (iov_i == data.len) {
288 return off;
289 }
290 }
291 if (amt_read == 0) return off; // EOF
292 }
293}
294
295pub fn preadWindows(fd: fd_t, data: []u8, offset: u64) !usize {
296 var resume_node = Loop.ResumeNode.Basic{
297 .base = Loop.ResumeNode{
298 .id = Loop.ResumeNode.Id.Basic,
299 .handle = @frame(),
300 .overlapped = windows.OVERLAPPED{
301 .Internal = 0,
302 .InternalHigh = 0,
303 .Offset = @truncate(u32, offset),
304 .OffsetHigh = @truncate(u32, offset >> 32),
305 .hEvent = null,
306 },
307 },
308 };
309 // TODO only call create io completion port once per fd
310 _ = windows.CreateIoCompletionPort(fd, global_event_loop.os_data.io_port, undefined, undefined) catch undefined;
311 global_event_loop.beginOneEvent();
312 errdefer global_event_loop.finishOneEvent();
313
314 errdefer {
315 _ = windows.kernel32.CancelIoEx(fd, &resume_node.base.overlapped);
316 }
317 suspend {
318 _ = windows.kernel32.ReadFile(fd, data.ptr, @intCast(windows.DWORD, data.len), null, &resume_node.base.overlapped);
319 }
320 var bytes_transferred: windows.DWORD = undefined;
321 if (windows.kernel32.GetOverlappedResult(fd, &resume_node.base.overlapped, &bytes_transferred, windows.FALSE) == 0) {
322 switch (windows.kernel32.GetLastError()) {
323 .IO_PENDING => unreachable,
324 .OPERATION_ABORTED => return error.OperationAborted,
325 .BROKEN_PIPE => return error.BrokenPipe,
326 .HANDLE_EOF => return @as(usize, bytes_transferred),
327 else => |err| return windows.unexpectedError(err),
328 }
329 }
330 return @as(usize, bytes_transferred);
331}
332
333/// iovecs must live until preadv frame completes
334pub fn preadvPosix(fd: fd_t, iovecs: []const os.iovec, offset: usize) os.ReadError!usize {
335 var req_node = RequestNode{
336 .prev = null,
337 .next = null,
338 .data = Request{
339 .msg = Request.Msg{
340 .PReadV = Request.Msg.PReadV{
341 .fd = fd,
342 .iov = iovecs,
343 .offset = offset,
344 .result = undefined,
345 },
346 },
347 .finish = Request.Finish{
348 .TickNode = Loop.NextTickNode{
349 .prev = null,
350 .next = null,
351 .data = @frame(),
352 },
353 },
354 },
355 };
356
357 errdefer global_event_loop.posixFsCancel(&req_node);
358
359 suspend {
360 global_event_loop.posixFsRequest(&req_node);
361 }
362
363 return req_node.data.msg.PReadV.result;
364}
365
366pub fn openPosix(path: []const u8, flags: u32, mode: File.Mode) File.OpenError!fd_t {
367 const path_c = try std.os.toPosixPath(path);
368
369 var req_node = RequestNode{
370 .prev = null,
371 .next = null,
372 .data = Request{
373 .msg = Request.Msg{
374 .Open = Request.Msg.Open{
375 .path = path_c[0..path.len],
376 .flags = flags,
377 .mode = mode,
378 .result = undefined,
379 },
380 },
381 .finish = Request.Finish{
382 .TickNode = Loop.NextTickNode{
383 .prev = null,
384 .next = null,
385 .data = @frame(),
386 },
387 },
388 },
389 };
390
391 errdefer global_event_loop.posixFsCancel(&req_node);
392
393 suspend {
394 global_event_loop.posixFsRequest(&req_node);
395 }
396
397 return req_node.data.msg.Open.result;
398}
399
400pub fn openRead(path: []const u8) File.OpenError!fd_t {
401 switch (builtin.os) {
402 .macosx, .linux, .freebsd, .netbsd, .dragonfly => {
403 const O_LARGEFILE = if (@hasDecl(os, "O_LARGEFILE")) os.O_LARGEFILE else 0;
404 const flags = O_LARGEFILE | os.O_RDONLY | os.O_CLOEXEC;
405 return openPosix(path, flags, File.default_mode);
406 },
407
408 .windows => return windows.CreateFile(
409 path,
410 windows.GENERIC_READ,
411 windows.FILE_SHARE_READ,
412 null,
413 windows.OPEN_EXISTING,
414 windows.FILE_ATTRIBUTE_NORMAL | windows.FILE_FLAG_OVERLAPPED,
415 null,
416 ),
417
418 else => @compileError("Unsupported OS"),
419 }
420}
421
422/// Creates if does not exist. Truncates the file if it exists.
423/// Uses the default mode.
424pub fn openWrite(path: []const u8) File.OpenError!fd_t {
425 return openWriteMode(path, File.default_mode);
426}
427
428/// Creates if does not exist. Truncates the file if it exists.
429pub fn openWriteMode(path: []const u8, mode: File.Mode) File.OpenError!fd_t {
430 switch (builtin.os) {
431 .macosx,
432 .linux,
433 .freebsd,
434 .netbsd,
435 .dragonfly,
436 => {
437 const O_LARGEFILE = if (@hasDecl(os, "O_LARGEFILE")) os.O_LARGEFILE else 0;
438 const flags = O_LARGEFILE | os.O_WRONLY | os.O_CREAT | os.O_CLOEXEC | os.O_TRUNC;
439 return openPosix(path, flags, File.default_mode);
440 },
441 .windows => return windows.CreateFile(
442 path,
443 windows.GENERIC_WRITE,
444 windows.FILE_SHARE_WRITE | windows.FILE_SHARE_READ | windows.FILE_SHARE_DELETE,
445 null,
446 windows.CREATE_ALWAYS,
447 windows.FILE_ATTRIBUTE_NORMAL | windows.FILE_FLAG_OVERLAPPED,
448 null,
449 ),
450 else => @compileError("Unsupported OS"),
451 }
452}
453
454/// Creates if does not exist. Does not truncate.
455pub fn openReadWrite(path: []const u8, mode: File.Mode) File.OpenError!fd_t {
456 switch (builtin.os) {
457 .macosx, .linux, .freebsd, .netbsd, .dragonfly => {
458 const O_LARGEFILE = if (@hasDecl(os, "O_LARGEFILE")) os.O_LARGEFILE else 0;
459 const flags = O_LARGEFILE | os.O_RDWR | os.O_CREAT | os.O_CLOEXEC;
460 return openPosix(path, flags, mode);
461 },
462
463 .windows => return windows.CreateFile(
464 path,
465 windows.GENERIC_WRITE | windows.GENERIC_READ,
466 windows.FILE_SHARE_WRITE | windows.FILE_SHARE_READ | windows.FILE_SHARE_DELETE,
467 null,
468 windows.OPEN_ALWAYS,
469 windows.FILE_ATTRIBUTE_NORMAL | windows.FILE_FLAG_OVERLAPPED,
470 null,
471 ),
472
473 else => @compileError("Unsupported OS"),
474 }
475}
476
477/// This abstraction helps to close file handles in defer expressions
478/// without the possibility of failure and without the use of suspend points.
479/// Start a `CloseOperation` before opening a file, so that you can defer
480/// `CloseOperation.finish`.
481/// If you call `setHandle` then finishing will close the fd; otherwise finishing
482/// will deallocate the `CloseOperation`.
483pub const CloseOperation = struct {
484 allocator: *Allocator,
485 os_data: OsData,
486
487 const OsData = switch (builtin.os) {
488 .linux, .macosx, .freebsd, .netbsd, .dragonfly => OsDataPosix,
489
490 .windows => struct {
491 handle: ?fd_t,
492 },
493
494 else => @compileError("Unsupported OS"),
495 };
496
497 const OsDataPosix = struct {
498 have_fd: bool,
499 close_req_node: RequestNode,
500 };
501
502 pub fn start(allocator: *Allocator) (error{OutOfMemory}!*CloseOperation) {
503 const self = try allocator.create(CloseOperation);
504 self.* = CloseOperation{
505 .allocator = allocator,
506 .os_data = switch (builtin.os) {
507 .linux, .macosx, .freebsd, .netbsd, .dragonfly => initOsDataPosix(self),
508 .windows => OsData{ .handle = null },
509 else => @compileError("Unsupported OS"),
510 },
511 };
512 return self;
513 }
514
515 fn initOsDataPosix(self: *CloseOperation) OsData {
516 return OsData{
517 .have_fd = false,
518 .close_req_node = RequestNode{
519 .prev = null,
520 .next = null,
521 .data = Request{
522 .msg = Request.Msg{
523 .Close = Request.Msg.Close{ .fd = undefined },
524 },
525 .finish = Request.Finish{ .DeallocCloseOperation = self },
526 },
527 },
528 };
529 }
530
531 /// Defer this after creating.
532 pub fn finish(self: *CloseOperation) void {
533 switch (builtin.os) {
534 .linux,
535 .macosx,
536 .freebsd,
537 .netbsd,
538 .dragonfly,
539 => {
540 if (self.os_data.have_fd) {
541 global_event_loop.posixFsRequest(&self.os_data.close_req_node);
542 } else {
543 self.allocator.destroy(self);
544 }
545 },
546 .windows => {
547 if (self.os_data.handle) |handle| {
548 os.close(handle);
549 }
550 self.allocator.destroy(self);
551 },
552 else => @compileError("Unsupported OS"),
553 }
554 }
555
556 pub fn setHandle(self: *CloseOperation, handle: fd_t) void {
557 switch (builtin.os) {
558 .linux,
559 .macosx,
560 .freebsd,
561 .netbsd,
562 .dragonfly,
563 => {
564 self.os_data.close_req_node.data.msg.Close.fd = handle;
565 self.os_data.have_fd = true;
566 },
567 .windows => {
568 self.os_data.handle = handle;
569 },
570 else => @compileError("Unsupported OS"),
571 }
572 }
573
574 /// Undo a `setHandle`.
575 pub fn clearHandle(self: *CloseOperation) void {
576 switch (builtin.os) {
577 .linux,
578 .macosx,
579 .freebsd,
580 .netbsd,
581 .dragonfly,
582 => {
583 self.os_data.have_fd = false;
584 },
585 .windows => {
586 self.os_data.handle = null;
587 },
588 else => @compileError("Unsupported OS"),
589 }
590 }
591
592 pub fn getHandle(self: *CloseOperation) fd_t {
593 switch (builtin.os) {
594 .linux,
595 .macosx,
596 .freebsd,
597 .netbsd,
598 .dragonfly,
599 => {
600 assert(self.os_data.have_fd);
601 return self.os_data.close_req_node.data.msg.Close.fd;
602 },
603 .windows => {
604 return self.os_data.handle.?;
605 },
606 else => @compileError("Unsupported OS"),
607 }
608 }
609};
610
611/// contents must remain alive until writeFile completes.
612/// TODO make this atomic or provide writeFileAtomic and rename this one to writeFileTruncate
613pub fn writeFile(allocator: *Allocator, path: []const u8, contents: []const u8) !void {
614 return writeFileMode(allocator, path, contents, File.default_mode);
615}
616
617/// contents must remain alive until writeFile completes.
618pub fn writeFileMode(allocator: *Allocator, path: []const u8, contents: []const u8, mode: File.Mode) !void {
619 switch (builtin.os) {
620 .linux,
621 .macosx,
622 .freebsd,
623 .netbsd,
624 .dragonfly,
625 => return writeFileModeThread(allocator, path, contents, mode),
626 .windows => return writeFileWindows(path, contents),
627 else => @compileError("Unsupported OS"),
628 }
629}
630
631fn writeFileWindows(path: []const u8, contents: []const u8) !void {
632 const handle = try windows.CreateFile(
633 path,
634 windows.GENERIC_WRITE,
635 windows.FILE_SHARE_WRITE | windows.FILE_SHARE_READ | windows.FILE_SHARE_DELETE,
636 null,
637 windows.CREATE_ALWAYS,
638 windows.FILE_ATTRIBUTE_NORMAL | windows.FILE_FLAG_OVERLAPPED,
639 null,
640 );
641 defer os.close(handle);
642
643 try pwriteWindows(handle, contents, 0);
644}
645
646fn writeFileModeThread(allocator: *Allocator, path: []const u8, contents: []const u8, mode: File.Mode) !void {
647 const path_with_null = try std.cstr.addNullByte(allocator, path);
648 defer allocator.free(path_with_null);
649
650 var req_node = RequestNode{
651 .prev = null,
652 .next = null,
653 .data = Request{
654 .msg = Request.Msg{
655 .WriteFile = Request.Msg.WriteFile{
656 .path = path_with_null[0..path.len],
657 .contents = contents,
658 .mode = mode,
659 .result = undefined,
660 },
661 },
662 .finish = Request.Finish{
663 .TickNode = Loop.NextTickNode{
664 .prev = null,
665 .next = null,
666 .data = @frame(),
667 },
668 },
669 },
670 };
671
672 errdefer global_event_loop.posixFsCancel(&req_node);
673
674 suspend {
675 global_event_loop.posixFsRequest(&req_node);
676 }
677
678 return req_node.data.msg.WriteFile.result;
679}
680
681/// The frame resumes when the last data has been confirmed written, but before the file handle
682/// is closed.
683/// Caller owns returned memory.
684pub fn readFile(allocator: *Allocator, file_path: []const u8, max_size: usize) ![]u8 {
685 var close_op = try CloseOperation.start(allocator);
686 defer close_op.finish();
687
688 const fd = try openRead(file_path);
689 close_op.setHandle(fd);
690
691 var list = std.ArrayList(u8).init(allocator);
692 defer list.deinit();
693
694 while (true) {
695 try list.ensureCapacity(list.len + mem.page_size);
696 const buf = list.items[list.len..];
697 const buf_array = [_][]u8{buf};
698 const amt = try preadv(allocator, fd, &buf_array, list.len);
699 list.len += amt;
700 if (list.len > max_size) {
701 return error.FileTooBig;
702 }
703 if (amt < buf.len) {
704 return list.toOwnedSlice();
705 }
706 }
707}
708
709pub const WatchEventId = enum {
710 CloseWrite,
711 Delete,
712};
713
714fn eqlString(a: []const u16, b: []const u16) bool {
715 if (a.len != b.len) return false;
716 if (a.ptr == b.ptr) return true;
717 return mem.compare(u16, a, b) == .Equal;
718}
719
720fn hashString(s: []const u16) u32 {
721 return @truncate(u32, std.hash.Wyhash.hash(0, @sliceToBytes(s)));
722}
723
724pub const WatchEventError = error{
725 UserResourceLimitReached,
726 SystemResources,
727 AccessDenied,
728 Unexpected, // TODO remove this possibility
729};
730
731pub fn Watch(comptime V: type) type {
732 return struct {
733 channel: *event.Channel(Event.Error!Event),
734 os_data: OsData,
735 allocator: *Allocator,
736
737 const OsData = switch (builtin.os) {
738 // TODO https://github.com/ziglang/zig/issues/3778
739 .macosx, .freebsd, .netbsd, .dragonfly => KqOsData,
740 .linux => LinuxOsData,
741 .windows => WindowsOsData,
742
743 else => @compileError("Unsupported OS"),
744 };
745
746 const KqOsData = struct {
747 file_table: FileTable,
748 table_lock: event.Lock,
749
750 const FileTable = std.StringHashMap(*Put);
751 const Put = struct {
752 putter_frame: @Frame(kqPutEvents),
753 cancelled: bool = false,
754 value: V,
755 };
756 };
757
758 const WindowsOsData = struct {
759 table_lock: event.Lock,
760 dir_table: DirTable,
761 all_putters: std.atomic.Queue(Put),
762 ref_count: std.atomic.Int(usize),
763
764 const Put = struct {
765 putter: anyframe,
766 cancelled: bool = false,
767 };
768
769 const DirTable = std.StringHashMap(*Dir);
770 const FileTable = std.HashMap([]const u16, V, hashString, eqlString);
771
772 const Dir = struct {
773 putter_frame: @Frame(windowsDirReader),
774 file_table: FileTable,
775 table_lock: event.Lock,
776 };
777 };
778
779 const LinuxOsData = struct {
780 putter_frame: @Frame(linuxEventPutter),
781 inotify_fd: i32,
782 wd_table: WdTable,
783 table_lock: event.Lock,
784 cancelled: bool = false,
785
786 const WdTable = std.AutoHashMap(i32, Dir);
787 const FileTable = std.StringHashMap(V);
788
789 const Dir = struct {
790 dirname: []const u8,
791 file_table: FileTable,
792 };
793 };
794
795 const Self = @This();
796
797 pub const Event = struct {
798 id: Id,
799 data: V,
800
801 pub const Id = WatchEventId;
802 pub const Error = WatchEventError;
803 };
804
805 pub fn init(allocator: *Allocator, event_buf_count: usize) !*Self {
806 const channel = try allocator.create(event.Channel(Event.Error!Event));
807 errdefer allocator.destroy(channel);
808 var buf = try allocator.alloc(Event.Error!Event, event_buf_count);
809 errdefer allocator.free(buf);
810 channel.init(buf);
811 errdefer channel.deinit();
812
813 const self = try allocator.create(Self);
814 errdefer allocator.destroy(self);
815
816 switch (builtin.os) {
817 .linux => {
818 const inotify_fd = try os.inotify_init1(os.linux.IN_NONBLOCK | os.linux.IN_CLOEXEC);
819 errdefer os.close(inotify_fd);
820
821 self.* = Self{
822 .allocator = allocator,
823 .channel = channel,
824 .os_data = OsData{
825 .putter_frame = undefined,
826 .inotify_fd = inotify_fd,
827 .wd_table = OsData.WdTable.init(allocator),
828 .table_lock = event.Lock.init(),
829 },
830 };
831
832 self.os_data.putter_frame = async self.linuxEventPutter();
833 return self;
834 },
835
836 .windows => {
837 self.* = Self{
838 .allocator = allocator,
839 .channel = channel,
840 .os_data = OsData{
841 .table_lock = event.Lock.init(),
842 .dir_table = OsData.DirTable.init(allocator),
843 .ref_count = std.atomic.Int(usize).init(1),
844 .all_putters = std.atomic.Queue(anyframe).init(),
845 },
846 };
847 return self;
848 },
849
850 .macosx, .freebsd, .netbsd, .dragonfly => {
851 self.* = Self{
852 .allocator = allocator,
853 .channel = channel,
854 .os_data = OsData{
855 .table_lock = event.Lock.init(),
856 .file_table = OsData.FileTable.init(allocator),
857 },
858 };
859 return self;
860 },
861 else => @compileError("Unsupported OS"),
862 }
863 }
864
865 /// All addFile calls and removeFile calls must have completed.
866 pub fn deinit(self: *Self) void {
867 switch (builtin.os) {
868 .macosx, .freebsd, .netbsd, .dragonfly => {
869 // TODO we need to cancel the frames before destroying the lock
870 self.os_data.table_lock.deinit();
871 var it = self.os_data.file_table.iterator();
872 while (it.next()) |entry| {
873 entry.cancelled = true;
874 await entry.value.putter;
875 self.allocator.free(entry.key);
876 self.allocator.free(entry.value);
877 }
878 self.channel.deinit();
879 self.allocator.destroy(self.channel.buffer_nodes);
880 self.allocator.destroy(self);
881 },
882 .linux => {
883 self.os_data.cancelled = true;
884 await self.os_data.putter_frame;
885 self.allocator.destroy(self);
886 },
887 .windows => {
888 while (self.os_data.all_putters.get()) |putter_node| {
889 putter_node.cancelled = true;
890 await putter_node.frame;
891 }
892 self.deref();
893 },
894 else => @compileError("Unsupported OS"),
895 }
896 }
897
898 fn ref(self: *Self) void {
899 _ = self.os_data.ref_count.incr();
900 }
901
902 fn deref(self: *Self) void {
903 if (self.os_data.ref_count.decr() == 1) {
904 self.os_data.table_lock.deinit();
905 var it = self.os_data.dir_table.iterator();
906 while (it.next()) |entry| {
907 self.allocator.free(entry.key);
908 self.allocator.destroy(entry.value);
909 }
910 self.os_data.dir_table.deinit();
911 self.channel.deinit();
912 self.allocator.destroy(self.channel.buffer_nodes);
913 self.allocator.destroy(self);
914 }
915 }
916
917 pub fn addFile(self: *Self, file_path: []const u8, value: V) !?V {
918 switch (builtin.os) {
919 .macosx, .freebsd, .netbsd, .dragonfly => return addFileKEvent(self, file_path, value),
920 .linux => return addFileLinux(self, file_path, value),
921 .windows => return addFileWindows(self, file_path, value),
922 else => @compileError("Unsupported OS"),
923 }
924 }
925
926 fn addFileKEvent(self: *Self, file_path: []const u8, value: V) !?V {
927 const resolved_path = try std.fs.path.resolve(self.allocator, [_][]const u8{file_path});
928 var resolved_path_consumed = false;
929 defer if (!resolved_path_consumed) self.allocator.free(resolved_path);
930
931 var close_op = try CloseOperation.start(self.allocator);
932 var close_op_consumed = false;
933 defer if (!close_op_consumed) close_op.finish();
934
935 const flags = if (comptime std.Target.current.isDarwin()) os.O_SYMLINK | os.O_EVTONLY else 0;
936 const mode = 0;
937 const fd = try openPosix(self.allocator, resolved_path, flags, mode);
938 close_op.setHandle(fd);
939
940 var put = try self.allocator.create(OsData.Put);
941 errdefer self.allocator.destroy(put);
942 put.* = OsData.Put{
943 .value = value,
944 .putter_frame = undefined,
945 };
946 put.putter_frame = async self.kqPutEvents(close_op, put);
947 close_op_consumed = true;
948 errdefer {
949 put.cancelled = true;
950 await put.putter_frame;
951 }
952
953 const result = blk: {
954 const held = self.os_data.table_lock.acquire();
955 defer held.release();
956
957 const gop = try self.os_data.file_table.getOrPut(resolved_path);
958 if (gop.found_existing) {
959 const prev_value = gop.kv.value.value;
960 await gop.kv.value.putter_frame;
961 gop.kv.value = put;
962 break :blk prev_value;
963 } else {
964 resolved_path_consumed = true;
965 gop.kv.value = put;
966 break :blk null;
967 }
968 };
969
970 return result;
971 }
972
973 fn kqPutEvents(self: *Self, close_op: *CloseOperation, put: *OsData.Put) void {
974 global_event_loop.beginOneEvent();
975
976 defer {
977 close_op.finish();
978 global_event_loop.finishOneEvent();
979 }
980
981 while (!put.cancelled) {
982 if (global_event_loop.bsdWaitKev(
983 @intCast(usize, close_op.getHandle()),
984 os.EVFILT_VNODE,
985 os.NOTE_WRITE | os.NOTE_DELETE,
986 )) |kev| {
987 // TODO handle EV_ERROR
988 if (kev.fflags & os.NOTE_DELETE != 0) {
989 self.channel.put(Self.Event{
990 .id = Event.Id.Delete,
991 .data = put.value,
992 });
993 } else if (kev.fflags & os.NOTE_WRITE != 0) {
994 self.channel.put(Self.Event{
995 .id = Event.Id.CloseWrite,
996 .data = put.value,
997 });
998 }
999 } else |err| switch (err) {
1000 error.EventNotFound => unreachable,
1001 error.ProcessNotFound => unreachable,
1002 error.Overflow => unreachable,
1003 error.AccessDenied, error.SystemResources => |casted_err| {
1004 self.channel.put(casted_err);
1005 },
1006 }
1007 }
1008 }
1009
1010 fn addFileLinux(self: *Self, file_path: []const u8, value: V) !?V {
1011 const dirname = std.fs.path.dirname(file_path) orelse ".";
1012 const dirname_with_null = try std.cstr.addNullByte(self.allocator, dirname);
1013 var dirname_with_null_consumed = false;
1014 defer if (!dirname_with_null_consumed) self.channel.free(dirname_with_null);
1015
1016 const basename = std.fs.path.basename(file_path);
1017 const basename_with_null = try std.cstr.addNullByte(self.allocator, basename);
1018 var basename_with_null_consumed = false;
1019 defer if (!basename_with_null_consumed) self.allocator.free(basename_with_null);
1020
1021 const wd = try os.inotify_add_watchC(
1022 self.os_data.inotify_fd,
1023 dirname_with_null.ptr,
1024 os.linux.IN_CLOSE_WRITE | os.linux.IN_ONLYDIR | os.linux.IN_EXCL_UNLINK,
1025 );
1026 // wd is either a newly created watch or an existing one.
1027
1028 const held = self.os_data.table_lock.acquire();
1029 defer held.release();
1030
1031 const gop = try self.os_data.wd_table.getOrPut(wd);
1032 if (!gop.found_existing) {
1033 gop.kv.value = OsData.Dir{
1034 .dirname = dirname_with_null,
1035 .file_table = OsData.FileTable.init(self.allocator),
1036 };
1037 dirname_with_null_consumed = true;
1038 }
1039 const dir = &gop.kv.value;
1040
1041 const file_table_gop = try dir.file_table.getOrPut(basename_with_null);
1042 if (file_table_gop.found_existing) {
1043 const prev_value = file_table_gop.kv.value;
1044 file_table_gop.kv.value = value;
1045 return prev_value;
1046 } else {
1047 file_table_gop.kv.value = value;
1048 basename_with_null_consumed = true;
1049 return null;
1050 }
1051 }
1052
1053 fn addFileWindows(self: *Self, file_path: []const u8, value: V) !?V {
1054 // TODO we might need to convert dirname and basename to canonical file paths ("short"?)
1055 const dirname = try std.mem.dupe(self.allocator, u8, std.fs.path.dirname(file_path) orelse ".");
1056 var dirname_consumed = false;
1057 defer if (!dirname_consumed) self.allocator.free(dirname);
1058
1059 const dirname_utf16le = try std.unicode.utf8ToUtf16LeWithNull(self.allocator, dirname);
1060 defer self.allocator.free(dirname_utf16le);
1061
1062 // TODO https://github.com/ziglang/zig/issues/265
1063 const basename = std.fs.path.basename(file_path);
1064 const basename_utf16le_null = try std.unicode.utf8ToUtf16LeWithNull(self.allocator, basename);
1065 var basename_utf16le_null_consumed = false;
1066 defer if (!basename_utf16le_null_consumed) self.allocator.free(basename_utf16le_null);
1067 const basename_utf16le_no_null = basename_utf16le_null[0 .. basename_utf16le_null.len - 1];
1068
1069 const dir_handle = try windows.CreateFileW(
1070 dirname_utf16le.ptr,
1071 windows.FILE_LIST_DIRECTORY,
1072 windows.FILE_SHARE_READ | windows.FILE_SHARE_DELETE | windows.FILE_SHARE_WRITE,
1073 null,
1074 windows.OPEN_EXISTING,
1075 windows.FILE_FLAG_BACKUP_SEMANTICS | windows.FILE_FLAG_OVERLAPPED,
1076 null,
1077 );
1078 var dir_handle_consumed = false;
1079 defer if (!dir_handle_consumed) windows.CloseHandle(dir_handle);
1080
1081 const held = self.os_data.table_lock.acquire();
1082 defer held.release();
1083
1084 const gop = try self.os_data.dir_table.getOrPut(dirname);
1085 if (gop.found_existing) {
1086 const dir = gop.kv.value;
1087 const held_dir_lock = dir.table_lock.acquire();
1088 defer held_dir_lock.release();
1089
1090 const file_gop = try dir.file_table.getOrPut(basename_utf16le_no_null);
1091 if (file_gop.found_existing) {
1092 const prev_value = file_gop.kv.value;
1093 file_gop.kv.value = value;
1094 return prev_value;
1095 } else {
1096 file_gop.kv.value = value;
1097 basename_utf16le_null_consumed = true;
1098 return null;
1099 }
1100 } else {
1101 errdefer _ = self.os_data.dir_table.remove(dirname);
1102 const dir = try self.allocator.create(OsData.Dir);
1103 errdefer self.allocator.destroy(dir);
1104
1105 dir.* = OsData.Dir{
1106 .file_table = OsData.FileTable.init(self.allocator),
1107 .table_lock = event.Lock.init(),
1108 .putter_frame = undefined,
1109 };
1110 gop.kv.value = dir;
1111 assert((try dir.file_table.put(basename_utf16le_no_null, value)) == null);
1112 basename_utf16le_null_consumed = true;
1113
1114 dir.putter_frame = async self.windowsDirReader(dir_handle, dir);
1115 dir_handle_consumed = true;
1116
1117 dirname_consumed = true;
1118
1119 return null;
1120 }
1121 }
1122
1123 fn windowsDirReader(self: *Self, dir_handle: windows.HANDLE, dir: *OsData.Dir) void {
1124 self.ref();
1125 defer self.deref();
1126
1127 defer os.close(dir_handle);
1128
1129 var putter_node = std.atomic.Queue(anyframe).Node{
1130 .data = .{ .putter = @frame() },
1131 .prev = null,
1132 .next = null,
1133 };
1134 self.os_data.all_putters.put(&putter_node);
1135 defer _ = self.os_data.all_putters.remove(&putter_node);
1136
1137 var resume_node = Loop.ResumeNode.Basic{
1138 .base = Loop.ResumeNode{
1139 .id = Loop.ResumeNode.Id.Basic,
1140 .handle = @frame(),
1141 .overlapped = windows.OVERLAPPED{
1142 .Internal = 0,
1143 .InternalHigh = 0,
1144 .Offset = 0,
1145 .OffsetHigh = 0,
1146 .hEvent = null,
1147 },
1148 },
1149 };
1150 var event_buf: [4096]u8 align(@alignOf(windows.FILE_NOTIFY_INFORMATION)) = undefined;
1151
1152 // TODO handle this error not in the channel but in the setup
1153 _ = windows.CreateIoCompletionPort(
1154 dir_handle,
1155 global_event_loop.os_data.io_port,
1156 undefined,
1157 undefined,
1158 ) catch |err| {
1159 self.channel.put(err);
1160 return;
1161 };
1162
1163 while (!putter_node.data.cancelled) {
1164 {
1165 // TODO only 1 beginOneEvent for the whole function
1166 global_event_loop.beginOneEvent();
1167 errdefer global_event_loop.finishOneEvent();
1168 errdefer {
1169 _ = windows.kernel32.CancelIoEx(dir_handle, &resume_node.base.overlapped);
1170 }
1171 suspend {
1172 _ = windows.kernel32.ReadDirectoryChangesW(
1173 dir_handle,
1174 &event_buf,
1175 @intCast(windows.DWORD, event_buf.len),
1176 windows.FALSE, // watch subtree
1177 windows.FILE_NOTIFY_CHANGE_FILE_NAME | windows.FILE_NOTIFY_CHANGE_DIR_NAME |
1178 windows.FILE_NOTIFY_CHANGE_ATTRIBUTES | windows.FILE_NOTIFY_CHANGE_SIZE |
1179 windows.FILE_NOTIFY_CHANGE_LAST_WRITE | windows.FILE_NOTIFY_CHANGE_LAST_ACCESS |
1180 windows.FILE_NOTIFY_CHANGE_CREATION | windows.FILE_NOTIFY_CHANGE_SECURITY,
1181 null, // number of bytes transferred (unused for async)
1182 &resume_node.base.overlapped,
1183 null, // completion routine - unused because we use IOCP
1184 );
1185 }
1186 }
1187 var bytes_transferred: windows.DWORD = undefined;
1188 if (windows.kernel32.GetOverlappedResult(dir_handle, &resume_node.base.overlapped, &bytes_transferred, windows.FALSE) == 0) {
1189 const err = switch (windows.kernel32.GetLastError()) {
1190 else => |err| windows.unexpectedError(err),
1191 };
1192 self.channel.put(err);
1193 } else {
1194 // can't use @bytesToSlice because of the special variable length name field
1195 var ptr = event_buf[0..].ptr;
1196 const end_ptr = ptr + bytes_transferred;
1197 var ev: *windows.FILE_NOTIFY_INFORMATION = undefined;
1198 while (@ptrToInt(ptr) < @ptrToInt(end_ptr)) : (ptr += ev.NextEntryOffset) {
1199 ev = @ptrCast(*windows.FILE_NOTIFY_INFORMATION, ptr);
1200 const emit = switch (ev.Action) {
1201 windows.FILE_ACTION_REMOVED => WatchEventId.Delete,
1202 windows.FILE_ACTION_MODIFIED => WatchEventId.CloseWrite,
1203 else => null,
1204 };
1205 if (emit) |id| {
1206 const basename_utf16le = ([*]u16)(&ev.FileName)[0 .. ev.FileNameLength / 2];
1207 const user_value = blk: {
1208 const held = dir.table_lock.acquire();
1209 defer held.release();
1210
1211 if (dir.file_table.get(basename_utf16le)) |entry| {
1212 break :blk entry.value;
1213 } else {
1214 break :blk null;
1215 }
1216 };
1217 if (user_value) |v| {
1218 self.channel.put(Event{
1219 .id = id,
1220 .data = v,
1221 });
1222 }
1223 }
1224 if (ev.NextEntryOffset == 0) break;
1225 }
1226 }
1227 }
1228 }
1229
1230 pub fn removeFile(self: *Self, file_path: []const u8) ?V {
1231 @panic("TODO");
1232 }
1233
1234 fn linuxEventPutter(self: *Self) void {
1235 global_event_loop.beginOneEvent();
1236
1237 defer {
1238 self.os_data.table_lock.deinit();
1239 var wd_it = self.os_data.wd_table.iterator();
1240 while (wd_it.next()) |wd_entry| {
1241 var file_it = wd_entry.value.file_table.iterator();
1242 while (file_it.next()) |file_entry| {
1243 self.allocator.free(file_entry.key);
1244 }
1245 self.allocator.free(wd_entry.value.dirname);
1246 wd_entry.value.file_table.deinit();
1247 }
1248 self.os_data.wd_table.deinit();
1249 global_event_loop.finishOneEvent();
1250 os.close(self.os_data.inotify_fd);
1251 self.channel.deinit();
1252 self.allocator.free(self.channel.buffer_nodes);
1253 }
1254
1255 var event_buf: [4096]u8 align(@alignOf(os.linux.inotify_event)) = undefined;
1256
1257 while (!self.os_data.cancelled) {
1258 const rc = os.linux.read(self.os_data.inotify_fd, &event_buf, event_buf.len);
1259 const errno = os.linux.getErrno(rc);
1260 switch (errno) {
1261 0 => {
1262 // can't use @bytesToSlice because of the special variable length name field
1263 var ptr = event_buf[0..].ptr;
1264 const end_ptr = ptr + event_buf.len;
1265 var ev: *os.linux.inotify_event = undefined;
1266 while (@ptrToInt(ptr) < @ptrToInt(end_ptr)) {
1267 ev = @ptrCast(*os.linux.inotify_event, ptr);
1268 if (ev.mask & os.linux.IN_CLOSE_WRITE == os.linux.IN_CLOSE_WRITE) {
1269 const basename_ptr = ptr + @sizeOf(os.linux.inotify_event);
1270 // `ev.len` counts all bytes in `ev.name` including terminating null byte.
1271 const basename_with_null = basename_ptr[0..ev.len];
1272 const user_value = blk: {
1273 const held = self.os_data.table_lock.acquire();
1274 defer held.release();
1275
1276 const dir = &self.os_data.wd_table.get(ev.wd).?.value;
1277 if (dir.file_table.get(basename_with_null)) |entry| {
1278 break :blk entry.value;
1279 } else {
1280 break :blk null;
1281 }
1282 };
1283 if (user_value) |v| {
1284 self.channel.put(Event{
1285 .id = WatchEventId.CloseWrite,
1286 .data = v,
1287 });
1288 }
1289 }
1290
1291 ptr = @alignCast(@alignOf(os.linux.inotify_event), ptr + @sizeOf(os.linux.inotify_event) + ev.len);
1292 }
1293 },
1294 os.linux.EINTR => continue,
1295 os.linux.EINVAL => unreachable,
1296 os.linux.EFAULT => unreachable,
1297 os.linux.EAGAIN => {
1298 global_event_loop.linuxWaitFd(self.os_data.inotify_fd, os.linux.EPOLLET | os.linux.EPOLLIN | os.EPOLLONESHOT);
1299 },
1300 else => unreachable,
1301 }
1302 }
1303 }
1304 };
1305}
1306
1307const test_tmp_dir = "std_event_fs_test";
1308
1309test "write a file, watch it, write it again" {
1310 // TODO provide a way to run tests in evented I/O mode
1311 if (!std.io.is_async) return error.SkipZigTest;
1312
1313 const allocator = std.heap.page_allocator;
1314
1315 // TODO move this into event loop too
1316 try os.makePath(allocator, test_tmp_dir);
1317 defer os.deleteTree(test_tmp_dir) catch {};
1318
1319 return testFsWatch(&allocator);
1320}
1321
1322fn testFsWatch(allocator: *Allocator) !void {
1323 const file_path = try std.fs.path.join(allocator, [_][]const u8{ test_tmp_dir, "file.txt" });
1324 defer allocator.free(file_path);
1325
1326 const contents =
1327 \\line 1
1328 \\line 2
1329 ;
1330 const line2_offset = 7;
1331
1332 // first just write then read the file
1333 try writeFile(allocator, file_path, contents);
1334
1335 const read_contents = try readFile(allocator, file_path, 1024 * 1024);
1336 testing.expectEqualSlices(u8, contents, read_contents);
1337
1338 // now watch the file
1339 var watch = try Watch(void).init(allocator, 0);
1340 defer watch.deinit();
1341
1342 testing.expect((try watch.addFile(file_path, {})) == null);
1343
1344 const ev = watch.channel.get();
1345 var ev_consumed = false;
1346 defer if (!ev_consumed) await ev;
1347
1348 // overwrite line 2
1349 const fd = try await openReadWrite(file_path, File.default_mode);
1350 {
1351 defer os.close(fd);
1352
1353 try pwritev(allocator, fd, []const []const u8{"lorem ipsum"}, line2_offset);
1354 }
1355
1356 ev_consumed = true;
1357 switch ((try await ev).id) {
1358 WatchEventId.CloseWrite => {},
1359 WatchEventId.Delete => @panic("wrong event"),
1360 }
1361 const contents_updated = try readFile(allocator, file_path, 1024 * 1024);
1362 testing.expectEqualSlices(u8,
1363 \\line 1
1364 \\lorem ipsum
1365 , contents_updated);
1366
1367 // TODO test deleting the file and then re-adding it. we should get events for both
1368}
1369
1370pub const OutStream = struct {
1371 fd: fd_t,
1372 stream: Stream,
1373 allocator: *Allocator,
1374 offset: usize,
1375
1376 pub const Error = File.WriteError;
1377 pub const Stream = event.io.OutStream(Error);
1378
1379 pub fn init(allocator: *Allocator, fd: fd_t, offset: usize) OutStream {
1380 return OutStream{
1381 .fd = fd,
1382 .offset = offset,
1383 .stream = Stream{ .writeFn = writeFn },
1384 };
1385 }
1386
1387 fn writeFn(out_stream: *Stream, bytes: []const u8) Error!void {
1388 const self = @fieldParentPtr(OutStream, "stream", out_stream);
1389 const offset = self.offset;
1390 self.offset += bytes.len;
1391 return pwritev(self.allocator, self.fd, [_][]const u8{bytes}, offset);
1392 }
1393};
1394
1395pub const InStream = struct {
1396 fd: fd_t,
1397 stream: Stream,
1398 allocator: *Allocator,
1399 offset: usize,
1400
1401 pub const Error = PReadVError; // TODO make this not have OutOfMemory
1402 pub const Stream = event.io.InStream(Error);
1403
1404 pub fn init(allocator: *Allocator, fd: fd_t, offset: usize) InStream {
1405 return InStream{
1406 .fd = fd,
1407 .offset = offset,
1408 .stream = Stream{ .readFn = readFn },
1409 };
1410 }
1411
1412 fn readFn(in_stream: *Stream, bytes: []u8) Error!usize {
1413 const self = @fieldParentPtr(InStream, "stream", in_stream);
1414 const amt = try preadv(self.allocator, self.fd, [_][]u8{bytes}, self.offset);
1415 self.offset += amt;
1416 return amt;
1417 }
1418};
lib/std/event/group.zig+1-1
...@@ -22,7 +22,7 @@ pub fn Group(comptime ReturnType: type) type {...@@ -22,7 +22,7 @@ pub fn Group(comptime ReturnType: type) type {
22 const AllocStack = std.atomic.Stack(Node);22 const AllocStack = std.atomic.Stack(Node);
2323
24 pub const Node = struct {24 pub const Node = struct {
25 bytes: []const u8 = [0]u8{},25 bytes: []const u8 = &[0]u8{},
26 handle: anyframe->ReturnType,26 handle: anyframe->ReturnType,
27 };27 };
2828
lib/std/event/lock.zig+4-4
...@@ -117,21 +117,21 @@ pub const Lock = struct {...@@ -117,21 +117,21 @@ pub const Lock = struct {
117};117};
118118
119test "std.event.Lock" {119test "std.event.Lock" {
120 if (!std.io.is_async) return error.SkipZigTest;
121
120 // TODO https://github.com/ziglang/zig/issues/1908122 // TODO https://github.com/ziglang/zig/issues/1908
121 if (builtin.single_threaded) return error.SkipZigTest;123 if (builtin.single_threaded) return error.SkipZigTest;
122124
123 // TODO https://github.com/ziglang/zig/issues/3251125 // TODO https://github.com/ziglang/zig/issues/3251
124 if (builtin.os == .freebsd) return error.SkipZigTest;126 if (builtin.os == .freebsd) return error.SkipZigTest;
125127
126 // TODO provide a way to run tests in evented I/O mode
127 if (!std.io.is_async) return error.SkipZigTest;
128
129 var lock = Lock.init();128 var lock = Lock.init();
130 defer lock.deinit();129 defer lock.deinit();
131130
132 _ = async testLock(&lock);131 _ = async testLock(&lock);
133132
134 testing.expectEqualSlices(i32, [1]i32{3 * @intCast(i32, shared_test_data.len)} ** shared_test_data.len, shared_test_data);133 const expected_result = [1]i32{3 * @intCast(i32, shared_test_data.len)} ** shared_test_data.len;
134 testing.expectEqualSlices(i32, &expected_result, &shared_test_data);
135}135}
136136
137async fn testLock(lock: *Lock) void {137async fn testLock(lock: *Lock) void {
lib/std/event/loop.zig+317-50
...@@ -6,7 +6,6 @@ const testing = std.testing;...@@ -6,7 +6,6 @@ const testing = std.testing;
6const mem = std.mem;6const mem = std.mem;
7const AtomicRmwOp = builtin.AtomicRmwOp;7const AtomicRmwOp = builtin.AtomicRmwOp;
8const AtomicOrder = builtin.AtomicOrder;8const AtomicOrder = builtin.AtomicOrder;
9const fs = std.event.fs;
10const os = std.os;9const os = std.os;
11const windows = os.windows;10const windows = os.windows;
12const maxInt = std.math.maxInt;11const maxInt = std.math.maxInt;
...@@ -174,21 +173,19 @@ pub const Loop = struct {...@@ -174,21 +173,19 @@ pub const Loop = struct {
174 fn initOsData(self: *Loop, extra_thread_count: usize) InitOsDataError!void {173 fn initOsData(self: *Loop, extra_thread_count: usize) InitOsDataError!void {
175 switch (builtin.os) {174 switch (builtin.os) {
176 .linux => {175 .linux => {
177 self.os_data.fs_queue = std.atomic.Queue(fs.Request).init();176 self.os_data.fs_queue = std.atomic.Queue(Request).init();
178 self.os_data.fs_queue_item = 0;177 self.os_data.fs_queue_item = 0;
179 // we need another thread for the file system because Linux does not have an async178 // we need another thread for the file system because Linux does not have an async
180 // file system I/O API.179 // file system I/O API.
181 self.os_data.fs_end_request = fs.RequestNode{180 self.os_data.fs_end_request = Request.Node{
182 .prev = undefined,181 .data = Request{
183 .next = undefined,182 .msg = .end,
184 .data = fs.Request{183 .finish = .NoAction,
185 .msg = fs.Request.Msg.End,
186 .finish = fs.Request.Finish.NoAction,
187 },184 },
188 };185 };
189186
190 errdefer {187 errdefer {
191 while (self.available_eventfd_resume_nodes.pop()) |node| os.close(node.data.eventfd);188 while (self.available_eventfd_resume_nodes.pop()) |node| noasync os.close(node.data.eventfd);
192 }189 }
193 for (self.eventfd_resume_nodes) |*eventfd_node| {190 for (self.eventfd_resume_nodes) |*eventfd_node| {
194 eventfd_node.* = std.atomic.Stack(ResumeNode.EventFd).Node{191 eventfd_node.* = std.atomic.Stack(ResumeNode.EventFd).Node{
...@@ -207,10 +204,10 @@ pub const Loop = struct {...@@ -207,10 +204,10 @@ pub const Loop = struct {
207 }204 }
208205
209 self.os_data.epollfd = try os.epoll_create1(os.EPOLL_CLOEXEC);206 self.os_data.epollfd = try os.epoll_create1(os.EPOLL_CLOEXEC);
210 errdefer os.close(self.os_data.epollfd);207 errdefer noasync os.close(self.os_data.epollfd);
211208
212 self.os_data.final_eventfd = try os.eventfd(0, os.EFD_CLOEXEC | os.EFD_NONBLOCK);209 self.os_data.final_eventfd = try os.eventfd(0, os.EFD_CLOEXEC | os.EFD_NONBLOCK);
213 errdefer os.close(self.os_data.final_eventfd);210 errdefer noasync os.close(self.os_data.final_eventfd);
214211
215 self.os_data.final_eventfd_event = os.epoll_event{212 self.os_data.final_eventfd_event = os.epoll_event{
216 .events = os.EPOLLIN,213 .events = os.EPOLLIN,
...@@ -237,7 +234,7 @@ pub const Loop = struct {...@@ -237,7 +234,7 @@ pub const Loop = struct {
237 var extra_thread_index: usize = 0;234 var extra_thread_index: usize = 0;
238 errdefer {235 errdefer {
239 // writing 8 bytes to an eventfd cannot fail236 // writing 8 bytes to an eventfd cannot fail
240 os.write(self.os_data.final_eventfd, &wakeup_bytes) catch unreachable;237 noasync os.write(self.os_data.final_eventfd, &wakeup_bytes) catch unreachable;
241 while (extra_thread_index != 0) {238 while (extra_thread_index != 0) {
242 extra_thread_index -= 1;239 extra_thread_index -= 1;
243 self.extra_threads[extra_thread_index].wait();240 self.extra_threads[extra_thread_index].wait();
...@@ -249,20 +246,20 @@ pub const Loop = struct {...@@ -249,20 +246,20 @@ pub const Loop = struct {
249 },246 },
250 .macosx, .freebsd, .netbsd, .dragonfly => {247 .macosx, .freebsd, .netbsd, .dragonfly => {
251 self.os_data.kqfd = try os.kqueue();248 self.os_data.kqfd = try os.kqueue();
252 errdefer os.close(self.os_data.kqfd);249 errdefer noasync os.close(self.os_data.kqfd);
253250
254 self.os_data.fs_kqfd = try os.kqueue();251 self.os_data.fs_kqfd = try os.kqueue();
255 errdefer os.close(self.os_data.fs_kqfd);252 errdefer noasync os.close(self.os_data.fs_kqfd);
256253
257 self.os_data.fs_queue = std.atomic.Queue(fs.Request).init();254 self.os_data.fs_queue = std.atomic.Queue(Request).init();
258 // we need another thread for the file system because Darwin does not have an async255 // we need another thread for the file system because Darwin does not have an async
259 // file system I/O API.256 // file system I/O API.
260 self.os_data.fs_end_request = fs.RequestNode{257 self.os_data.fs_end_request = Request.Node{
261 .prev = undefined,258 .prev = undefined,
262 .next = undefined,259 .next = undefined,
263 .data = fs.Request{260 .data = Request{
264 .msg = fs.Request.Msg.End,261 .msg = .end,
265 .finish = fs.Request.Finish.NoAction,262 .finish = .NoAction,
266 },263 },
267 };264 };
268265
...@@ -407,14 +404,14 @@ pub const Loop = struct {...@@ -407,14 +404,14 @@ pub const Loop = struct {
407 fn deinitOsData(self: *Loop) void {404 fn deinitOsData(self: *Loop) void {
408 switch (builtin.os) {405 switch (builtin.os) {
409 .linux => {406 .linux => {
410 os.close(self.os_data.final_eventfd);407 noasync os.close(self.os_data.final_eventfd);
411 while (self.available_eventfd_resume_nodes.pop()) |node| os.close(node.data.eventfd);408 while (self.available_eventfd_resume_nodes.pop()) |node| noasync os.close(node.data.eventfd);
412 os.close(self.os_data.epollfd);409 noasync os.close(self.os_data.epollfd);
413 self.allocator.free(self.eventfd_resume_nodes);410 self.allocator.free(self.eventfd_resume_nodes);
414 },411 },
415 .macosx, .freebsd, .netbsd, .dragonfly => {412 .macosx, .freebsd, .netbsd, .dragonfly => {
416 os.close(self.os_data.kqfd);413 noasync os.close(self.os_data.kqfd);
417 os.close(self.os_data.fs_kqfd);414 noasync os.close(self.os_data.fs_kqfd);
418 },415 },
419 .windows => {416 .windows => {
420 windows.CloseHandle(self.os_data.io_port);417 windows.CloseHandle(self.os_data.io_port);
...@@ -711,6 +708,190 @@ pub const Loop = struct {...@@ -711,6 +708,190 @@ pub const Loop = struct {
711 }708 }
712 }709 }
713710
711 /// Performs an async `os.open` using a separate thread.
712 pub fn openZ(self: *Loop, file_path: [*:0]const u8, flags: u32, mode: usize) os.OpenError!os.fd_t {
713 var req_node = Request.Node{
714 .data = .{
715 .msg = .{
716 .open = .{
717 .path = file_path,
718 .flags = flags,
719 .mode = mode,
720 .result = undefined,
721 },
722 },
723 .finish = .{ .TickNode = .{ .data = @frame() } },
724 },
725 };
726 suspend {
727 self.posixFsRequest(&req_node);
728 }
729 return req_node.data.msg.open.result;
730 }
731
732 /// Performs an async `os.opent` using a separate thread.
733 pub fn openatZ(self: *Loop, fd: os.fd_t, file_path: [*:0]const u8, flags: u32, mode: usize) os.OpenError!os.fd_t {
734 var req_node = Request.Node{
735 .data = .{
736 .msg = .{
737 .openat = .{
738 .fd = fd,
739 .path = file_path,
740 .flags = flags,
741 .mode = mode,
742 .result = undefined,
743 },
744 },
745 .finish = .{ .TickNode = .{ .data = @frame() } },
746 },
747 };
748 suspend {
749 self.posixFsRequest(&req_node);
750 }
751 return req_node.data.msg.openat.result;
752 }
753
754 /// Performs an async `os.close` using a separate thread.
755 pub fn close(self: *Loop, fd: os.fd_t) void {
756 var req_node = Request.Node{
757 .data = .{
758 .msg = .{ .close = .{ .fd = fd } },
759 .finish = .{ .TickNode = .{ .data = @frame() } },
760 },
761 };
762 suspend {
763 self.posixFsRequest(&req_node);
764 }
765 }
766
767 /// Performs an async `os.read` using a separate thread.
768 /// `fd` must block and not return EAGAIN.
769 pub fn read(self: *Loop, fd: os.fd_t, buf: []u8) os.ReadError!usize {
770 var req_node = Request.Node{
771 .data = .{
772 .msg = .{
773 .read = .{
774 .fd = fd,
775 .buf = buf,
776 .result = undefined,
777 },
778 },
779 .finish = .{ .TickNode = .{ .data = @frame() } },
780 },
781 };
782 suspend {
783 self.posixFsRequest(&req_node);
784 }
785 return req_node.data.msg.read.result;
786 }
787
788 /// Performs an async `os.readv` using a separate thread.
789 /// `fd` must block and not return EAGAIN.
790 pub fn readv(self: *Loop, fd: os.fd_t, iov: []const os.iovec) os.ReadError!usize {
791 var req_node = Request.Node{
792 .data = .{
793 .msg = .{
794 .readv = .{
795 .fd = fd,
796 .iov = iov,
797 .result = undefined,
798 },
799 },
800 .finish = .{ .TickNode = .{ .data = @frame() } },
801 },
802 };
803 suspend {
804 self.posixFsRequest(&req_node);
805 }
806 return req_node.data.msg.readv.result;
807 }
808
809 /// Performs an async `os.preadv` using a separate thread.
810 /// `fd` must block and not return EAGAIN.
811 pub fn preadv(self: *Loop, fd: os.fd_t, iov: []const os.iovec, offset: u64) os.ReadError!usize {
812 var req_node = Request.Node{
813 .data = .{
814 .msg = .{
815 .preadv = .{
816 .fd = fd,
817 .iov = iov,
818 .offset = offset,
819 .result = undefined,
820 },
821 },
822 .finish = .{ .TickNode = .{ .data = @frame() } },
823 },
824 };
825 suspend {
826 self.posixFsRequest(&req_node);
827 }
828 return req_node.data.msg.preadv.result;
829 }
830
831 /// Performs an async `os.write` using a separate thread.
832 /// `fd` must block and not return EAGAIN.
833 pub fn write(self: *Loop, fd: os.fd_t, bytes: []const u8) os.WriteError!void {
834 var req_node = Request.Node{
835 .data = .{
836 .msg = .{
837 .write = .{
838 .fd = fd,
839 .bytes = bytes,
840 .result = undefined,
841 },
842 },
843 .finish = .{ .TickNode = .{ .data = @frame() } },
844 },
845 };
846 suspend {
847 self.posixFsRequest(&req_node);
848 }
849 return req_node.data.msg.write.result;
850 }
851
852 /// Performs an async `os.writev` using a separate thread.
853 /// `fd` must block and not return EAGAIN.
854 pub fn writev(self: *Loop, fd: os.fd_t, iov: []const os.iovec_const) os.WriteError!void {
855 var req_node = Request.Node{
856 .data = .{
857 .msg = .{
858 .writev = .{
859 .fd = fd,
860 .iov = iov,
861 .result = undefined,
862 },
863 },
864 .finish = .{ .TickNode = .{ .data = @frame() } },
865 },
866 };
867 suspend {
868 self.posixFsRequest(&req_node);
869 }
870 return req_node.data.msg.writev.result;
871 }
872
873 /// Performs an async `os.pwritev` using a separate thread.
874 /// `fd` must block and not return EAGAIN.
875 pub fn pwritev(self: *Loop, fd: os.fd_t, iov: []const os.iovec_const, offset: u64) os.WriteError!void {
876 var req_node = Request.Node{
877 .data = .{
878 .msg = .{
879 .pwritev = .{
880 .fd = fd,
881 .iov = iov,
882 .offset = offset,
883 .result = undefined,
884 },
885 },
886 .finish = .{ .TickNode = .{ .data = @frame() } },
887 },
888 };
889 suspend {
890 self.posixFsRequest(&req_node);
891 }
892 return req_node.data.msg.pwritev.result;
893 }
894
714 fn workerRun(self: *Loop) void {895 fn workerRun(self: *Loop) void {
715 while (true) {896 while (true) {
716 while (true) {897 while (true) {
...@@ -804,7 +985,7 @@ pub const Loop = struct {...@@ -804,7 +985,7 @@ pub const Loop = struct {
804 }985 }
805 }986 }
806987
807 fn posixFsRequest(self: *Loop, request_node: *fs.RequestNode) void {988 fn posixFsRequest(self: *Loop, request_node: *Request.Node) void {
808 self.beginOneEvent(); // finished in posixFsRun after processing the msg989 self.beginOneEvent(); // finished in posixFsRun after processing the msg
809 self.os_data.fs_queue.put(request_node);990 self.os_data.fs_queue.put(request_node);
810 switch (builtin.os) {991 switch (builtin.os) {
...@@ -826,7 +1007,7 @@ pub const Loop = struct {...@@ -826,7 +1007,7 @@ pub const Loop = struct {
826 }1007 }
827 }1008 }
8281009
829 fn posixFsCancel(self: *Loop, request_node: *fs.RequestNode) void {1010 fn posixFsCancel(self: *Loop, request_node: *Request.Node) void {
830 if (self.os_data.fs_queue.remove(request_node)) {1011 if (self.os_data.fs_queue.remove(request_node)) {
831 self.finishOneEvent();1012 self.finishOneEvent();
832 }1013 }
...@@ -841,37 +1022,32 @@ pub const Loop = struct {...@@ -841,37 +1022,32 @@ pub const Loop = struct {
841 }1022 }
842 while (self.os_data.fs_queue.get()) |node| {1023 while (self.os_data.fs_queue.get()) |node| {
843 switch (node.data.msg) {1024 switch (node.data.msg) {
844 .End => return,1025 .end => return,
845 .WriteV => |*msg| {1026 .read => |*msg| {
1027 msg.result = noasync os.read(msg.fd, msg.buf);
1028 },
1029 .write => |*msg| {
1030 msg.result = noasync os.write(msg.fd, msg.bytes);
1031 },
1032 .writev => |*msg| {
846 msg.result = noasync os.writev(msg.fd, msg.iov);1033 msg.result = noasync os.writev(msg.fd, msg.iov);
847 },1034 },
848 .PWriteV => |*msg| {1035 .pwritev => |*msg| {
849 msg.result = noasync os.pwritev(msg.fd, msg.iov, msg.offset);1036 msg.result = noasync os.pwritev(msg.fd, msg.iov, msg.offset);
850 },1037 },
851 .PReadV => |*msg| {1038 .preadv => |*msg| {
852 msg.result = noasync os.preadv(msg.fd, msg.iov, msg.offset);1039 msg.result = noasync os.preadv(msg.fd, msg.iov, msg.offset);
853 },1040 },
854 .Open => |*msg| {1041 .open => |*msg| {
855 msg.result = noasync os.openC(msg.path.ptr, msg.flags, msg.mode);1042 msg.result = noasync os.openC(msg.path, msg.flags, msg.mode);
856 },1043 },
857 .Close => |*msg| noasync os.close(msg.fd),1044 .openat => |*msg| {
858 .WriteFile => |*msg| blk: {1045 msg.result = noasync os.openatC(msg.fd, msg.path, msg.flags, msg.mode);
859 const O_LARGEFILE = if (@hasDecl(os, "O_LARGEFILE")) os.O_LARGEFILE else 0;
860 const flags = O_LARGEFILE | os.O_WRONLY | os.O_CREAT |
861 os.O_CLOEXEC | os.O_TRUNC;
862 const fd = noasync os.openC(msg.path.ptr, flags, msg.mode) catch |err| {
863 msg.result = err;
864 break :blk;
865 };
866 defer noasync os.close(fd);
867 msg.result = noasync os.write(fd, msg.contents);
868 },1046 },
1047 .close => |*msg| noasync os.close(msg.fd),
869 }1048 }
870 switch (node.data.finish) {1049 switch (node.data.finish) {
871 .TickNode => |*tick_node| self.onNextTick(tick_node),1050 .TickNode => |*tick_node| self.onNextTick(tick_node),
872 .DeallocCloseOperation => |close_op| {
873 self.allocator.destroy(close_op);
874 },
875 .NoAction => {},1051 .NoAction => {},
876 }1052 }
877 self.finishOneEvent();1053 self.finishOneEvent();
...@@ -911,8 +1087,8 @@ pub const Loop = struct {...@@ -911,8 +1087,8 @@ pub const Loop = struct {
911 fs_kevent_wait: os.Kevent,1087 fs_kevent_wait: os.Kevent,
912 fs_thread: *Thread,1088 fs_thread: *Thread,
913 fs_kqfd: i32,1089 fs_kqfd: i32,
914 fs_queue: std.atomic.Queue(fs.Request),1090 fs_queue: std.atomic.Queue(Request),
915 fs_end_request: fs.RequestNode,1091 fs_end_request: Request.Node,
916 };1092 };
9171093
918 const LinuxOsData = struct {1094 const LinuxOsData = struct {
...@@ -921,8 +1097,99 @@ pub const Loop = struct {...@@ -921,8 +1097,99 @@ pub const Loop = struct {
921 final_eventfd_event: os.linux.epoll_event,1097 final_eventfd_event: os.linux.epoll_event,
922 fs_thread: *Thread,1098 fs_thread: *Thread,
923 fs_queue_item: i32,1099 fs_queue_item: i32,
924 fs_queue: std.atomic.Queue(fs.Request),1100 fs_queue: std.atomic.Queue(Request),
925 fs_end_request: fs.RequestNode,1101 fs_end_request: Request.Node,
1102 };
1103
1104 pub const Request = struct {
1105 msg: Msg,
1106 finish: Finish,
1107
1108 pub const Node = std.atomic.Queue(Request).Node;
1109
1110 pub const Finish = union(enum) {
1111 TickNode: Loop.NextTickNode,
1112 NoAction,
1113 };
1114
1115 pub const Msg = union(enum) {
1116 read: Read,
1117 write: Write,
1118 writev: WriteV,
1119 pwritev: PWriteV,
1120 preadv: PReadV,
1121 open: Open,
1122 openat: OpenAt,
1123 close: Close,
1124
1125 /// special - means the fs thread should exit
1126 end,
1127
1128 pub const Read = struct {
1129 fd: os.fd_t,
1130 buf: []u8,
1131 result: Error!usize,
1132
1133 pub const Error = os.ReadError;
1134 };
1135
1136 pub const Write = struct {
1137 fd: os.fd_t,
1138 bytes: []const u8,
1139 result: Error!void,
1140
1141 pub const Error = os.WriteError;
1142 };
1143
1144 pub const WriteV = struct {
1145 fd: os.fd_t,
1146 iov: []const os.iovec_const,
1147 result: Error!void,
1148
1149 pub const Error = os.WriteError;
1150 };
1151
1152 pub const PWriteV = struct {
1153 fd: os.fd_t,
1154 iov: []const os.iovec_const,
1155 offset: usize,
1156 result: Error!void,
1157
1158 pub const Error = os.WriteError;
1159 };
1160
1161 pub const PReadV = struct {
1162 fd: os.fd_t,
1163 iov: []const os.iovec,
1164 offset: usize,
1165 result: Error!usize,
1166
1167 pub const Error = os.ReadError;
1168 };
1169
1170 pub const Open = struct {
1171 path: [*:0]const u8,
1172 flags: u32,
1173 mode: os.mode_t,
1174 result: Error!os.fd_t,
1175
1176 pub const Error = os.OpenError;
1177 };
1178
1179 pub const OpenAt = struct {
1180 fd: os.fd_t,
1181 path: [*:0]const u8,
1182 flags: u32,
1183 mode: os.mode_t,
1184 result: Error!os.fd_t,
1185
1186 pub const Error = os.OpenError;
1187 };
1188
1189 pub const Close = struct {
1190 fd: os.fd_t,
1191 };
1192 };
926 };1193 };
927};1194};
9281195
lib/std/fmt.zig+16-16
...@@ -78,7 +78,7 @@ fn peekIsAlign(comptime fmt: []const u8) bool {...@@ -78,7 +78,7 @@ fn peekIsAlign(comptime fmt: []const u8) bool {
78pub fn format(78pub fn format(
79 context: var,79 context: var,
80 comptime Errors: type,80 comptime Errors: type,
81 output: fn (@TypeOf(context), []const u8) Errors!void,81 comptime output: fn (@TypeOf(context), []const u8) Errors!void,
82 comptime fmt: []const u8,82 comptime fmt: []const u8,
83 args: var,83 args: var,
84) Errors!void {84) Errors!void {
...@@ -326,7 +326,7 @@ pub fn formatType(...@@ -326,7 +326,7 @@ pub fn formatType(
326 options: FormatOptions,326 options: FormatOptions,
327 context: var,327 context: var,
328 comptime Errors: type,328 comptime Errors: type,
329 output: fn (@TypeOf(context), []const u8) Errors!void,329 comptime output: fn (@TypeOf(context), []const u8) Errors!void,
330 max_depth: usize,330 max_depth: usize,
331) Errors!void {331) Errors!void {
332 if (comptime std.mem.eql(u8, fmt, "*")) {332 if (comptime std.mem.eql(u8, fmt, "*")) {
...@@ -488,7 +488,7 @@ fn formatValue(...@@ -488,7 +488,7 @@ fn formatValue(
488 options: FormatOptions,488 options: FormatOptions,
489 context: var,489 context: var,
490 comptime Errors: type,490 comptime Errors: type,
491 output: fn (@TypeOf(context), []const u8) Errors!void,491 comptime output: fn (@TypeOf(context), []const u8) Errors!void,
492) Errors!void {492) Errors!void {
493 if (comptime std.mem.eql(u8, fmt, "B")) {493 if (comptime std.mem.eql(u8, fmt, "B")) {
494 return formatBytes(value, options, 1000, context, Errors, output);494 return formatBytes(value, options, 1000, context, Errors, output);
...@@ -510,7 +510,7 @@ pub fn formatIntValue(...@@ -510,7 +510,7 @@ pub fn formatIntValue(
510 options: FormatOptions,510 options: FormatOptions,
511 context: var,511 context: var,
512 comptime Errors: type,512 comptime Errors: type,
513 output: fn (@TypeOf(context), []const u8) Errors!void,513 comptime output: fn (@TypeOf(context), []const u8) Errors!void,
514) Errors!void {514) Errors!void {
515 comptime var radix = 10;515 comptime var radix = 10;
516 comptime var uppercase = false;516 comptime var uppercase = false;
...@@ -552,7 +552,7 @@ fn formatFloatValue(...@@ -552,7 +552,7 @@ fn formatFloatValue(
552 options: FormatOptions,552 options: FormatOptions,
553 context: var,553 context: var,
554 comptime Errors: type,554 comptime Errors: type,
555 output: fn (@TypeOf(context), []const u8) Errors!void,555 comptime output: fn (@TypeOf(context), []const u8) Errors!void,
556) Errors!void {556) Errors!void {
557 if (fmt.len == 0 or comptime std.mem.eql(u8, fmt, "e")) {557 if (fmt.len == 0 or comptime std.mem.eql(u8, fmt, "e")) {
558 return formatFloatScientific(value, options, context, Errors, output);558 return formatFloatScientific(value, options, context, Errors, output);
...@@ -569,7 +569,7 @@ pub fn formatText(...@@ -569,7 +569,7 @@ pub fn formatText(
569 options: FormatOptions,569 options: FormatOptions,
570 context: var,570 context: var,
571 comptime Errors: type,571 comptime Errors: type,
572 output: fn (@TypeOf(context), []const u8) Errors!void,572 comptime output: fn (@TypeOf(context), []const u8) Errors!void,
573) Errors!void {573) Errors!void {
574 if (fmt.len == 0) {574 if (fmt.len == 0) {
575 return output(context, bytes);575 return output(context, bytes);
...@@ -590,7 +590,7 @@ pub fn formatAsciiChar(...@@ -590,7 +590,7 @@ pub fn formatAsciiChar(
590 options: FormatOptions,590 options: FormatOptions,
591 context: var,591 context: var,
592 comptime Errors: type,592 comptime Errors: type,
593 output: fn (@TypeOf(context), []const u8) Errors!void,593 comptime output: fn (@TypeOf(context), []const u8) Errors!void,
594) Errors!void {594) Errors!void {
595 return output(context, @as(*const [1]u8, &c)[0..]);595 return output(context, @as(*const [1]u8, &c)[0..]);
596}596}
...@@ -600,7 +600,7 @@ pub fn formatBuf(...@@ -600,7 +600,7 @@ pub fn formatBuf(
600 options: FormatOptions,600 options: FormatOptions,
601 context: var,601 context: var,
602 comptime Errors: type,602 comptime Errors: type,
603 output: fn (@TypeOf(context), []const u8) Errors!void,603 comptime output: fn (@TypeOf(context), []const u8) Errors!void,
604) Errors!void {604) Errors!void {
605 try output(context, buf);605 try output(context, buf);
606606
...@@ -620,7 +620,7 @@ pub fn formatFloatScientific(...@@ -620,7 +620,7 @@ pub fn formatFloatScientific(
620 options: FormatOptions,620 options: FormatOptions,
621 context: var,621 context: var,
622 comptime Errors: type,622 comptime Errors: type,
623 output: fn (@TypeOf(context), []const u8) Errors!void,623 comptime output: fn (@TypeOf(context), []const u8) Errors!void,
624) Errors!void {624) Errors!void {
625 var x = @floatCast(f64, value);625 var x = @floatCast(f64, value);
626626
...@@ -715,7 +715,7 @@ pub fn formatFloatDecimal(...@@ -715,7 +715,7 @@ pub fn formatFloatDecimal(
715 options: FormatOptions,715 options: FormatOptions,
716 context: var,716 context: var,
717 comptime Errors: type,717 comptime Errors: type,
718 output: fn (@TypeOf(context), []const u8) Errors!void,718 comptime output: fn (@TypeOf(context), []const u8) Errors!void,
719) Errors!void {719) Errors!void {
720 var x = @as(f64, value);720 var x = @as(f64, value);
721721
...@@ -861,7 +861,7 @@ pub fn formatBytes(...@@ -861,7 +861,7 @@ pub fn formatBytes(
861 comptime radix: usize,861 comptime radix: usize,
862 context: var,862 context: var,
863 comptime Errors: type,863 comptime Errors: type,
864 output: fn (@TypeOf(context), []const u8) Errors!void,864 comptime output: fn (@TypeOf(context), []const u8) Errors!void,
865) Errors!void {865) Errors!void {
866 if (value == 0) {866 if (value == 0) {
867 return output(context, "0B");867 return output(context, "0B");
...@@ -902,7 +902,7 @@ pub fn formatInt(...@@ -902,7 +902,7 @@ pub fn formatInt(
902 options: FormatOptions,902 options: FormatOptions,
903 context: var,903 context: var,
904 comptime Errors: type,904 comptime Errors: type,
905 output: fn (@TypeOf(context), []const u8) Errors!void,905 comptime output: fn (@TypeOf(context), []const u8) Errors!void,
906) Errors!void {906) Errors!void {
907 const int_value = if (@TypeOf(value) == comptime_int) blk: {907 const int_value = if (@TypeOf(value) == comptime_int) blk: {
908 const Int = math.IntFittingRange(value, value);908 const Int = math.IntFittingRange(value, value);
...@@ -924,7 +924,7 @@ fn formatIntSigned(...@@ -924,7 +924,7 @@ fn formatIntSigned(
924 options: FormatOptions,924 options: FormatOptions,
925 context: var,925 context: var,
926 comptime Errors: type,926 comptime Errors: type,
927 output: fn (@TypeOf(context), []const u8) Errors!void,927 comptime output: fn (@TypeOf(context), []const u8) Errors!void,
928) Errors!void {928) Errors!void {
929 const new_options = FormatOptions{929 const new_options = FormatOptions{
930 .width = if (options.width) |w| (if (w == 0) 0 else w - 1) else null,930 .width = if (options.width) |w| (if (w == 0) 0 else w - 1) else null,
...@@ -955,7 +955,7 @@ fn formatIntUnsigned(...@@ -955,7 +955,7 @@ fn formatIntUnsigned(
955 options: FormatOptions,955 options: FormatOptions,
956 context: var,956 context: var,
957 comptime Errors: type,957 comptime Errors: type,
958 output: fn (@TypeOf(context), []const u8) Errors!void,958 comptime output: fn (@TypeOf(context), []const u8) Errors!void,
959) Errors!void {959) Errors!void {
960 assert(base >= 2);960 assert(base >= 2);
961 var buf: [math.max(@TypeOf(value).bit_count, 1)]u8 = undefined;961 var buf: [math.max(@TypeOf(value).bit_count, 1)]u8 = undefined;
...@@ -1419,7 +1419,7 @@ test "custom" {...@@ -1419,7 +1419,7 @@ test "custom" {
1419 options: FormatOptions,1419 options: FormatOptions,
1420 context: var,1420 context: var,
1421 comptime Errors: type,1421 comptime Errors: type,
1422 output: fn (@TypeOf(context), []const u8) Errors!void,1422 comptime output: fn (@TypeOf(context), []const u8) Errors!void,
1423 ) Errors!void {1423 ) Errors!void {
1424 if (fmt.len == 0 or comptime std.mem.eql(u8, fmt, "p")) {1424 if (fmt.len == 0 or comptime std.mem.eql(u8, fmt, "p")) {
1425 return std.fmt.format(context, Errors, output, "({d:.3},{d:.3})", .{ self.x, self.y });1425 return std.fmt.format(context, Errors, output, "({d:.3},{d:.3})", .{ self.x, self.y });
...@@ -1626,7 +1626,7 @@ test "formatType max_depth" {...@@ -1626,7 +1626,7 @@ test "formatType max_depth" {
1626 options: FormatOptions,1626 options: FormatOptions,
1627 context: var,1627 context: var,
1628 comptime Errors: type,1628 comptime Errors: type,
1629 output: fn (@TypeOf(context), []const u8) Errors!void,1629 comptime output: fn (@TypeOf(context), []const u8) Errors!void,
1630 ) Errors!void {1630 ) Errors!void {
1631 if (fmt.len == 0) {1631 if (fmt.len == 0) {
1632 return std.fmt.format(context, Errors, output, "({d:.3},{d:.3})", .{ self.x, self.y });1632 return std.fmt.format(context, Errors, output, "({d:.3},{d:.3})", .{ self.x, self.y });
lib/std/fs.zig+41-12
...@@ -23,6 +23,8 @@ pub const realpathW = os.realpathW;...@@ -23,6 +23,8 @@ pub const realpathW = os.realpathW;
23pub const getAppDataDir = @import("fs/get_app_data_dir.zig").getAppDataDir;23pub const getAppDataDir = @import("fs/get_app_data_dir.zig").getAppDataDir;
24pub const GetAppDataDirError = @import("fs/get_app_data_dir.zig").GetAppDataDirError;24pub const GetAppDataDirError = @import("fs/get_app_data_dir.zig").GetAppDataDirError;
2525
26pub const Watch = @import("fs/watch.zig").Watch;
27
26/// This represents the maximum size of a UTF-8 encoded file path.28/// This represents the maximum size of a UTF-8 encoded file path.
27/// All file system operations which return a path are guaranteed to29/// All file system operations which return a path are guaranteed to
28/// fit into a UTF-8 encoded array of this length.30/// fit into a UTF-8 encoded array of this length.
...@@ -43,6 +45,13 @@ pub const base64_encoder = base64.Base64Encoder.init(...@@ -43,6 +45,13 @@ pub const base64_encoder = base64.Base64Encoder.init(
43 base64.standard_pad_char,45 base64.standard_pad_char,
44);46);
4547
48/// Whether or not async file system syscalls need a dedicated thread because the operating
49/// system does not support non-blocking I/O on the file system.
50pub const need_async_thread = std.io.is_async and switch (builtin.os) {
51 .windows, .other => false,
52 else => true,
53};
54
46/// TODO remove the allocator requirement from this API55/// TODO remove the allocator requirement from this API
47pub fn atomicSymLink(allocator: *Allocator, existing_path: []const u8, new_path: []const u8) !void {56pub fn atomicSymLink(allocator: *Allocator, existing_path: []const u8, new_path: []const u8) !void {
48 if (symLink(existing_path, new_path)) {57 if (symLink(existing_path, new_path)) {
...@@ -688,11 +697,16 @@ pub const Dir = struct {...@@ -688,11 +697,16 @@ pub const Dir = struct {
688 }697 }
689698
690 pub fn close(self: *Dir) void {699 pub fn close(self: *Dir) void {
691 os.close(self.fd);700 if (need_async_thread) {
701 std.event.Loop.instance.?.close(self.fd);
702 } else {
703 os.close(self.fd);
704 }
692 self.* = undefined;705 self.* = undefined;
693 }706 }
694707
695 /// Opens a file for reading or writing, without attempting to create a new file.708 /// Opens a file for reading or writing, without attempting to create a new file.
709 /// To create a new file, see `createFile`.
696 /// Call `File.close` to release the resource.710 /// Call `File.close` to release the resource.
697 /// Asserts that the path parameter has no null bytes.711 /// Asserts that the path parameter has no null bytes.
698 pub fn openFile(self: Dir, sub_path: []const u8, flags: File.OpenFlags) File.OpenError!File {712 pub fn openFile(self: Dir, sub_path: []const u8, flags: File.OpenFlags) File.OpenError!File {
...@@ -718,8 +732,11 @@ pub const Dir = struct {...@@ -718,8 +732,11 @@ pub const Dir = struct {
718 @as(u32, os.O_WRONLY)732 @as(u32, os.O_WRONLY)
719 else733 else
720 @as(u32, os.O_RDONLY);734 @as(u32, os.O_RDONLY);
721 const fd = try os.openatC(self.fd, sub_path, os_flags, 0);735 const fd = if (need_async_thread)
722 return File{ .handle = fd };736 try std.event.Loop.instance.?.openatZ(self.fd, sub_path, os_flags, 0)
737 else
738 try os.openatC(self.fd, sub_path, os_flags, 0);
739 return File{ .handle = fd, .io_mode = .blocking };
723 }740 }
724741
725 /// Same as `openFile` but Windows-only and the path parameter is742 /// Same as `openFile` but Windows-only and the path parameter is
...@@ -756,8 +773,11 @@ pub const Dir = struct {...@@ -756,8 +773,11 @@ pub const Dir = struct {
756 (if (flags.truncate) @as(u32, os.O_TRUNC) else 0) |773 (if (flags.truncate) @as(u32, os.O_TRUNC) else 0) |
757 (if (flags.read) @as(u32, os.O_RDWR) else os.O_WRONLY) |774 (if (flags.read) @as(u32, os.O_RDWR) else os.O_WRONLY) |
758 (if (flags.exclusive) @as(u32, os.O_EXCL) else 0);775 (if (flags.exclusive) @as(u32, os.O_EXCL) else 0);
759 const fd = try os.openatC(self.fd, sub_path_c, os_flags, flags.mode);776 const fd = if (need_async_thread)
760 return File{ .handle = fd };777 try std.event.Loop.instance.?.openatZ(self.fd, sub_path_c, os_flags, flags.mode)
778 else
779 try os.openatC(self.fd, sub_path_c, os_flags, flags.mode);
780 return File{ .handle = fd, .io_mode = .blocking };
761 }781 }
762782
763 /// Same as `createFile` but Windows-only and the path parameter is783 /// Same as `createFile` but Windows-only and the path parameter is
...@@ -798,7 +818,10 @@ pub const Dir = struct {...@@ -798,7 +818,10 @@ pub const Dir = struct {
798 ) File.OpenError!File {818 ) File.OpenError!File {
799 const w = os.windows;819 const w = os.windows;
800820
801 var result = File{ .handle = undefined };821 var result = File{
822 .handle = undefined,
823 .io_mode = .blocking,
824 };
802825
803 const path_len_bytes = math.cast(u16, mem.toSliceConst(u16, sub_path_w).len * 2) catch |err| switch (err) {826 const path_len_bytes = math.cast(u16, mem.toSliceConst(u16, sub_path_w).len * 2) catch |err| switch (err) {
804 error.Overflow => return error.NameTooLong,827 error.Overflow => return error.NameTooLong,
...@@ -810,7 +833,7 @@ pub const Dir = struct {...@@ -810,7 +833,7 @@ pub const Dir = struct {
810 };833 };
811 var attr = w.OBJECT_ATTRIBUTES{834 var attr = w.OBJECT_ATTRIBUTES{
812 .Length = @sizeOf(w.OBJECT_ATTRIBUTES),835 .Length = @sizeOf(w.OBJECT_ATTRIBUTES),
813 .RootDirectory = if (path.isAbsoluteW(sub_path_w)) null else self.fd,836 .RootDirectory = if (path.isAbsoluteWindowsW(sub_path_w)) null else self.fd,
814 .Attributes = 0, // Note we do not use OBJ_CASE_INSENSITIVE here.837 .Attributes = 0, // Note we do not use OBJ_CASE_INSENSITIVE here.
815 .ObjectName = &nt_name,838 .ObjectName = &nt_name,
816 .SecurityDescriptor = null,839 .SecurityDescriptor = null,
...@@ -919,7 +942,12 @@ pub const Dir = struct {...@@ -919,7 +942,12 @@ pub const Dir = struct {
919 }942 }
920943
921 fn openDirFlagsC(self: Dir, sub_path_c: [*:0]const u8, flags: u32) OpenError!Dir {944 fn openDirFlagsC(self: Dir, sub_path_c: [*:0]const u8, flags: u32) OpenError!Dir {
922 const fd = os.openatC(self.fd, sub_path_c, flags | os.O_DIRECTORY, 0) catch |err| switch (err) {945 const os_flags = flags | os.O_DIRECTORY;
946 const result = if (need_async_thread)
947 std.event.Loop.instance.?.openatZ(self.fd, sub_path_c, os_flags, 0)
948 else
949 os.openatC(self.fd, sub_path_c, os_flags, 0);
950 const fd = result catch |err| switch (err) {
923 error.FileTooBig => unreachable, // can't happen for directories951 error.FileTooBig => unreachable, // can't happen for directories
924 error.IsDir => unreachable, // we're providing O_DIRECTORY952 error.IsDir => unreachable, // we're providing O_DIRECTORY
925 error.NoSpaceLeft => unreachable, // not providing O_CREAT953 error.NoSpaceLeft => unreachable, // not providing O_CREAT
...@@ -960,7 +988,7 @@ pub const Dir = struct {...@@ -960,7 +988,7 @@ pub const Dir = struct {
960 };988 };
961 var attr = w.OBJECT_ATTRIBUTES{989 var attr = w.OBJECT_ATTRIBUTES{
962 .Length = @sizeOf(w.OBJECT_ATTRIBUTES),990 .Length = @sizeOf(w.OBJECT_ATTRIBUTES),
963 .RootDirectory = if (path.isAbsoluteW(sub_path_w)) null else self.fd,991 .RootDirectory = if (path.isAbsoluteWindowsW(sub_path_w)) null else self.fd,
964 .Attributes = 0, // Note we do not use OBJ_CASE_INSENSITIVE here.992 .Attributes = 0, // Note we do not use OBJ_CASE_INSENSITIVE here.
965 .ObjectName = &nt_name,993 .ObjectName = &nt_name,
966 .SecurityDescriptor = null,994 .SecurityDescriptor = null,
...@@ -1327,7 +1355,7 @@ pub fn openFileAbsoluteC(absolute_path_c: [*:0]const u8, flags: File.OpenFlags)...@@ -1327,7 +1355,7 @@ pub fn openFileAbsoluteC(absolute_path_c: [*:0]const u8, flags: File.OpenFlags)
13271355
1328/// Same as `openFileAbsolute` but the path parameter is WTF-16 encoded.1356/// Same as `openFileAbsolute` but the path parameter is WTF-16 encoded.
1329pub fn openFileAbsoluteW(absolute_path_w: [*:0]const u16, flags: File.OpenFlags) File.OpenError!File {1357pub fn openFileAbsoluteW(absolute_path_w: [*:0]const u16, flags: File.OpenFlags) File.OpenError!File {
1330 assert(path.isAbsoluteW(absolute_path_w));1358 assert(path.isAbsoluteWindowsW(absolute_path_w));
1331 return cwd().openFileW(absolute_path_w, flags);1359 return cwd().openFileW(absolute_path_w, flags);
1332}1360}
13331361
...@@ -1350,7 +1378,7 @@ pub fn createFileAbsoluteC(absolute_path_c: [*:0]const u8, flags: File.CreateFla...@@ -1350,7 +1378,7 @@ pub fn createFileAbsoluteC(absolute_path_c: [*:0]const u8, flags: File.CreateFla
13501378
1351/// Same as `createFileAbsolute` but the path parameter is WTF-16 encoded.1379/// Same as `createFileAbsolute` but the path parameter is WTF-16 encoded.
1352pub fn createFileAbsoluteW(absolute_path_w: [*:0]const u16, flags: File.CreateFlags) File.OpenError!File {1380pub fn createFileAbsoluteW(absolute_path_w: [*:0]const u16, flags: File.CreateFlags) File.OpenError!File {
1353 assert(path.isAbsoluteW(absolute_path_w));1381 assert(path.isAbsoluteWindowsW(absolute_path_w));
1354 return cwd().createFileW(absolute_path_w, flags);1382 return cwd().createFileW(absolute_path_w, flags);
1355}1383}
13561384
...@@ -1371,7 +1399,7 @@ pub fn deleteFileAbsoluteC(absolute_path_c: [*:0]const u8) DeleteFileError!void...@@ -1371,7 +1399,7 @@ pub fn deleteFileAbsoluteC(absolute_path_c: [*:0]const u8) DeleteFileError!void
13711399
1372/// Same as `deleteFileAbsolute` except the parameter is WTF-16 encoded.1400/// Same as `deleteFileAbsolute` except the parameter is WTF-16 encoded.
1373pub fn deleteFileAbsoluteW(absolute_path_w: [*:0]const u16) DeleteFileError!void {1401pub fn deleteFileAbsoluteW(absolute_path_w: [*:0]const u16) DeleteFileError!void {
1374 assert(path.isAbsoluteW(absolute_path_w));1402 assert(path.isAbsoluteWindowsW(absolute_path_w));
1375 return cwd().deleteFileW(absolute_path_w);1403 return cwd().deleteFileW(absolute_path_w);
1376}1404}
13771405
...@@ -1588,4 +1616,5 @@ test "" {...@@ -1588,4 +1616,5 @@ test "" {
1588 _ = @import("fs/path.zig");1616 _ = @import("fs/path.zig");
1589 _ = @import("fs/file.zig");1617 _ = @import("fs/file.zig");
1590 _ = @import("fs/get_app_data_dir.zig");1618 _ = @import("fs/get_app_data_dir.zig");
1619 _ = @import("fs/watch.zig");
1591}1620}
lib/std/fs/file.zig+78-75
...@@ -8,18 +8,29 @@ const assert = std.debug.assert;...@@ -8,18 +8,29 @@ const assert = std.debug.assert;
8const windows = os.windows;8const windows = os.windows;
9const Os = builtin.Os;9const Os = builtin.Os;
10const maxInt = std.math.maxInt;10const maxInt = std.math.maxInt;
11const need_async_thread = std.fs.need_async_thread;
1112
12pub const File = struct {13pub const File = struct {
13 /// The OS-specific file descriptor or file handle.14 /// The OS-specific file descriptor or file handle.
14 handle: os.fd_t,15 handle: os.fd_t,
1516
16 pub const Mode = switch (builtin.os) {17 /// On some systems, such as Linux, file system file descriptors are incapable of non-blocking I/O.
17 Os.windows => void,18 /// This forces us to perform asynchronous I/O on a dedicated thread, to achieve non-blocking
18 else => u32,19 /// file-system I/O. To do this, `File` must be aware of whether it is a file system file descriptor,
19 };20 /// or, more specifically, whether the I/O is blocking.
21 io_mode: io.Mode,
22
23 /// Even when std.io.mode is async, it is still sometimes desirable to perform blocking I/O, although
24 /// not by default. For example, when printing a stack trace to stderr.
25 async_block_allowed: @TypeOf(async_block_allowed_no) = async_block_allowed_no,
26
27 pub const async_block_allowed_yes = if (io.is_async) true else {};
28 pub const async_block_allowed_no = if (io.is_async) false else {};
29
30 pub const Mode = os.mode_t;
2031
21 pub const default_mode = switch (builtin.os) {32 pub const default_mode = switch (builtin.os) {
22 Os.windows => {},33 .windows => 0,
23 else => 0o666,34 else => 0o666,
24 };35 };
2536
...@@ -49,87 +60,27 @@ pub const File = struct {...@@ -49,87 +60,27 @@ pub const File = struct {
49 mode: Mode = default_mode,60 mode: Mode = default_mode,
50 };61 };
5162
52 /// Deprecated; call `std.fs.Dir.openFile` directly.
53 pub fn openRead(path: []const u8) OpenError!File {
54 return std.fs.cwd().openFile(path, .{});
55 }
56
57 /// Deprecated; call `std.fs.Dir.openFileC` directly.
58 pub fn openReadC(path_c: [*:0]const u8) OpenError!File {
59 return std.fs.cwd().openFileC(path_c, .{});
60 }
61
62 /// Deprecated; call `std.fs.Dir.openFileW` directly.
63 pub fn openReadW(path_w: [*:0]const u16) OpenError!File {
64 return std.fs.cwd().openFileW(path_w, .{});
65 }
66
67 /// Deprecated; call `std.fs.Dir.createFile` directly.
68 pub fn openWrite(path: []const u8) OpenError!File {
69 return std.fs.cwd().createFile(path, .{});
70 }
71
72 /// Deprecated; call `std.fs.Dir.createFile` directly.
73 pub fn openWriteMode(path: []const u8, file_mode: Mode) OpenError!File {
74 return std.fs.cwd().createFile(path, .{ .mode = file_mode });
75 }
76
77 /// Deprecated; call `std.fs.Dir.createFileC` directly.
78 pub fn openWriteModeC(path_c: [*:0]const u8, file_mode: Mode) OpenError!File {
79 return std.fs.cwd().createFileC(path_c, .{ .mode = file_mode });
80 }
81
82 /// Deprecated; call `std.fs.Dir.createFileW` directly.
83 pub fn openWriteModeW(path_w: [*:0]const u16, file_mode: Mode) OpenError!File {
84 return std.fs.cwd().createFileW(path_w, .{ .mode = file_mode });
85 }
86
87 /// Deprecated; call `std.fs.Dir.createFile` directly.
88 pub fn openWriteNoClobber(path: []const u8, file_mode: Mode) OpenError!File {
89 return std.fs.cwd().createFile(path, .{
90 .mode = file_mode,
91 .exclusive = true,
92 });
93 }
94
95 /// Deprecated; call `std.fs.Dir.createFileC` directly.
96 pub fn openWriteNoClobberC(path_c: [*:0]const u8, file_mode: Mode) OpenError!File {
97 return std.fs.cwd().createFileC(path_c, .{
98 .mode = file_mode,
99 .exclusive = true,
100 });
101 }
102
103 /// Deprecated; call `std.fs.Dir.createFileW` directly.
104 pub fn openWriteNoClobberW(path_w: [*:0]const u16, file_mode: Mode) OpenError!File {
105 return std.fs.cwd().createFileW(path_w, .{
106 .mode = file_mode,
107 .exclusive = true,
108 });
109 }
110
111 pub fn openHandle(handle: os.fd_t) File {
112 return File{ .handle = handle };
113 }
114
115 /// Test for the existence of `path`.63 /// Test for the existence of `path`.
116 /// `path` is UTF8-encoded.64 /// `path` is UTF8-encoded.
117 /// In general it is recommended to avoid this function. For example,65 /// In general it is recommended to avoid this function. For example,
118 /// instead of testing if a file exists and then opening it, just66 /// instead of testing if a file exists and then opening it, just
119 /// open it and handle the error for file not found.67 /// open it and handle the error for file not found.
120 /// TODO: deprecate this and move it to `std.fs.Dir`.68 /// TODO: deprecate this and move it to `std.fs.Dir`.
69 /// TODO: integrate with async I/O
121 pub fn access(path: []const u8) !void {70 pub fn access(path: []const u8) !void {
122 return os.access(path, os.F_OK);71 return os.access(path, os.F_OK);
123 }72 }
12473
125 /// Same as `access` except the parameter is null-terminated.74 /// Same as `access` except the parameter is null-terminated.
126 /// TODO: deprecate this and move it to `std.fs.Dir`.75 /// TODO: deprecate this and move it to `std.fs.Dir`.
76 /// TODO: integrate with async I/O
127 pub fn accessC(path: [*:0]const u8) !void {77 pub fn accessC(path: [*:0]const u8) !void {
128 return os.accessC(path, os.F_OK);78 return os.accessC(path, os.F_OK);
129 }79 }
13080
131 /// Same as `access` except the parameter is null-terminated UTF16LE-encoded.81 /// Same as `access` except the parameter is null-terminated UTF16LE-encoded.
132 /// TODO: deprecate this and move it to `std.fs.Dir`.82 /// TODO: deprecate this and move it to `std.fs.Dir`.
83 /// TODO: integrate with async I/O
133 pub fn accessW(path: [*:0]const u16) !void {84 pub fn accessW(path: [*:0]const u16) !void {
134 return os.accessW(path, os.F_OK);85 return os.accessW(path, os.F_OK);
135 }86 }
...@@ -137,7 +88,11 @@ pub const File = struct {...@@ -137,7 +88,11 @@ pub const File = struct {
137 /// Upon success, the stream is in an uninitialized state. To continue using it,88 /// Upon success, the stream is in an uninitialized state. To continue using it,
138 /// you must use the open() function.89 /// you must use the open() function.
139 pub fn close(self: File) void {90 pub fn close(self: File) void {
140 return os.close(self.handle);91 if (need_async_thread and self.io_mode == .blocking and !self.async_block_allowed) {
92 std.event.Loop.instance.?.close(self.handle);
93 } else {
94 return os.close(self.handle);
95 }
141 }96 }
14297
143 /// Test whether the file refers to a terminal.98 /// Test whether the file refers to a terminal.
...@@ -167,26 +122,31 @@ pub const File = struct {...@@ -167,26 +122,31 @@ pub const File = struct {
167 pub const SeekError = os.SeekError;122 pub const SeekError = os.SeekError;
168123
169 /// Repositions read/write file offset relative to the current offset.124 /// Repositions read/write file offset relative to the current offset.
125 /// TODO: integrate with async I/O
170 pub fn seekBy(self: File, offset: i64) SeekError!void {126 pub fn seekBy(self: File, offset: i64) SeekError!void {
171 return os.lseek_CUR(self.handle, offset);127 return os.lseek_CUR(self.handle, offset);
172 }128 }
173129
174 /// Repositions read/write file offset relative to the end.130 /// Repositions read/write file offset relative to the end.
131 /// TODO: integrate with async I/O
175 pub fn seekFromEnd(self: File, offset: i64) SeekError!void {132 pub fn seekFromEnd(self: File, offset: i64) SeekError!void {
176 return os.lseek_END(self.handle, offset);133 return os.lseek_END(self.handle, offset);
177 }134 }
178135
179 /// Repositions read/write file offset relative to the beginning.136 /// Repositions read/write file offset relative to the beginning.
137 /// TODO: integrate with async I/O
180 pub fn seekTo(self: File, offset: u64) SeekError!void {138 pub fn seekTo(self: File, offset: u64) SeekError!void {
181 return os.lseek_SET(self.handle, offset);139 return os.lseek_SET(self.handle, offset);
182 }140 }
183141
184 pub const GetPosError = os.SeekError || os.FStatError;142 pub const GetPosError = os.SeekError || os.FStatError;
185143
144 /// TODO: integrate with async I/O
186 pub fn getPos(self: File) GetPosError!u64 {145 pub fn getPos(self: File) GetPosError!u64 {
187 return os.lseek_CUR_get(self.handle);146 return os.lseek_CUR_get(self.handle);
188 }147 }
189148
149 /// TODO: integrate with async I/O
190 pub fn getEndPos(self: File) GetPosError!u64 {150 pub fn getEndPos(self: File) GetPosError!u64 {
191 if (builtin.os == .windows) {151 if (builtin.os == .windows) {
192 return windows.GetFileSizeEx(self.handle);152 return windows.GetFileSizeEx(self.handle);
...@@ -196,6 +156,7 @@ pub const File = struct {...@@ -196,6 +156,7 @@ pub const File = struct {
196156
197 pub const ModeError = os.FStatError;157 pub const ModeError = os.FStatError;
198158
159 /// TODO: integrate with async I/O
199 pub fn mode(self: File) ModeError!Mode {160 pub fn mode(self: File) ModeError!Mode {
200 if (builtin.os == .windows) {161 if (builtin.os == .windows) {
201 return {};162 return {};
...@@ -219,6 +180,7 @@ pub const File = struct {...@@ -219,6 +180,7 @@ pub const File = struct {
219180
220 pub const StatError = os.FStatError;181 pub const StatError = os.FStatError;
221182
183 /// TODO: integrate with async I/O
222 pub fn stat(self: File) StatError!Stat {184 pub fn stat(self: File) StatError!Stat {
223 if (builtin.os == .windows) {185 if (builtin.os == .windows) {
224 var io_status_block: windows.IO_STATUS_BLOCK = undefined;186 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
...@@ -233,7 +195,7 @@ pub const File = struct {...@@ -233,7 +195,7 @@ pub const File = struct {
233 }195 }
234 return Stat{196 return Stat{
235 .size = @bitCast(u64, info.StandardInformation.EndOfFile),197 .size = @bitCast(u64, info.StandardInformation.EndOfFile),
236 .mode = {},198 .mode = 0,
237 .atime = windows.fromSysTime(info.BasicInformation.LastAccessTime),199 .atime = windows.fromSysTime(info.BasicInformation.LastAccessTime),
238 .mtime = windows.fromSysTime(info.BasicInformation.LastWriteTime),200 .mtime = windows.fromSysTime(info.BasicInformation.LastWriteTime),
239 .ctime = windows.fromSysTime(info.BasicInformation.CreationTime),201 .ctime = windows.fromSysTime(info.BasicInformation.CreationTime),
...@@ -259,6 +221,7 @@ pub const File = struct {...@@ -259,6 +221,7 @@ pub const File = struct {
259 /// and therefore this function cannot guarantee any precision will be stored.221 /// and therefore this function cannot guarantee any precision will be stored.
260 /// Further, the maximum value is limited by the system ABI. When a value is provided222 /// Further, the maximum value is limited by the system ABI. When a value is provided
261 /// that exceeds this range, the value is clamped to the maximum.223 /// that exceeds this range, the value is clamped to the maximum.
224 /// TODO: integrate with async I/O
262 pub fn updateTimes(225 pub fn updateTimes(
263 self: File,226 self: File,
264 /// access timestamp in nanoseconds227 /// access timestamp in nanoseconds
...@@ -287,21 +250,61 @@ pub const File = struct {...@@ -287,21 +250,61 @@ pub const File = struct {
287 pub const ReadError = os.ReadError;250 pub const ReadError = os.ReadError;
288251
289 pub fn read(self: File, buffer: []u8) ReadError!usize {252 pub fn read(self: File, buffer: []u8) ReadError!usize {
253 if (need_async_thread and self.io_mode == .blocking and !self.async_block_allowed) {
254 return std.event.Loop.instance.?.read(self.handle, buffer);
255 }
290 return os.read(self.handle, buffer);256 return os.read(self.handle, buffer);
291 }257 }
292258
259 pub fn pread(self: File, buffer: []u8, offset: u64) ReadError!usize {
260 if (need_async_thread and self.io_mode == .blocking and !self.async_block_allowed) {
261 return std.event.Loop.instance.?.pread(self.handle, buffer);
262 }
263 return os.pread(self.handle, buffer, offset);
264 }
265
266 pub fn readv(self: File, iovecs: []const os.iovec) ReadError!usize {
267 if (need_async_thread and self.io_mode == .blocking and !self.async_block_allowed) {
268 return std.event.Loop.instance.?.readv(self.handle, iovecs);
269 }
270 return os.readv(self.handle, iovecs);
271 }
272
273 pub fn preadv(self: File, iovecs: []const os.iovec, offset: u64) ReadError!usize {
274 if (need_async_thread and self.io_mode == .blocking and !self.async_block_allowed) {
275 return std.event.Loop.instance.?.preadv(self.handle, iovecs, offset);
276 }
277 return os.preadv(self.handle, iovecs, offset);
278 }
279
293 pub const WriteError = os.WriteError;280 pub const WriteError = os.WriteError;
294281
295 pub fn write(self: File, bytes: []const u8) WriteError!void {282 pub fn write(self: File, bytes: []const u8) WriteError!void {
283 if (need_async_thread and self.io_mode == .blocking and !self.async_block_allowed) {
284 return std.event.Loop.instance.?.write(self.handle, bytes);
285 }
296 return os.write(self.handle, bytes);286 return os.write(self.handle, bytes);
297 }287 }
298288
299 pub fn writev_iovec(self: File, iovecs: []const os.iovec_const) WriteError!void {289 pub fn pwrite(self: File, bytes: []const u8, offset: u64) WriteError!void {
300 if (std.event.Loop.instance) |loop| {290 if (need_async_thread and self.io_mode == .blocking and !self.async_block_allowed) {
301 return std.event.fs.writevPosix(loop, self.handle, iovecs);291 return std.event.Loop.instance.?.pwrite(self.handle, bytes, offset);
302 } else {292 }
303 return os.writev(self.handle, iovecs);293 return os.pwrite(self.handle, bytes, offset);
294 }
295
296 pub fn writev(self: File, iovecs: []const os.iovec_const) WriteError!void {
297 if (need_async_thread and self.io_mode == .blocking and !self.async_block_allowed) {
298 return std.event.Loop.instance.?.writev(self.handle, iovecs);
299 }
300 return os.writev(self.handle, iovecs);
301 }
302
303 pub fn pwritev(self: File, iovecs: []const os.iovec_const, offset: usize) WriteError!void {
304 if (need_async_thread and self.io_mode == .blocking and !self.async_block_allowed) {
305 return std.event.Loop.instance.?.pwritev(self.handle, iovecs);
304 }306 }
307 return os.pwritev(self.handle, iovecs);
305 }308 }
306309
307 pub fn inStream(file: File) InStream {310 pub fn inStream(file: File) InStream {
lib/std/fs/path.zig+20-40
...@@ -146,72 +146,51 @@ pub fn isAbsolute(path: []const u8) bool {...@@ -146,72 +146,51 @@ pub fn isAbsolute(path: []const u8) bool {
146 }146 }
147}147}
148148
149pub fn isAbsoluteW(path_w: [*:0]const u16) bool {149fn isAbsoluteWindowsImpl(comptime T: type, path: []const T) bool {
150 if (path_w[0] == '/')150 if (path.len < 1)
151 return true;
152
153 if (path_w[0] == '\\') {
154 return true;
155 }
156 if (path_w[0] == 0 or path_w[1] == 0 or path_w[2] == 0) {
157 return false;151 return false;
158 }
159 if (path_w[1] == ':') {
160 if (path_w[2] == '/')
161 return true;
162 if (path_w[2] == '\\')
163 return true;
164 }
165 return false;
166}
167152
168pub fn isAbsoluteWindows(path: []const u8) bool {
169 if (path[0] == '/')153 if (path[0] == '/')
170 return true;154 return true;
171155
172 if (path[0] == '\\') {156 if (path[0] == '\\')
173 return true;157 return true;
174 }158
175 if (path.len < 3) {159 if (path.len < 3)
176 return false;160 return false;
177 }161
178 if (path[1] == ':') {162 if (path[1] == ':') {
179 if (path[2] == '/')163 if (path[2] == '/')
180 return true;164 return true;
181 if (path[2] == '\\')165 if (path[2] == '\\')
182 return true;166 return true;
183 }167 }
168
184 return false;169 return false;
185}170}
186171
187pub fn isAbsoluteWindowsC(path_c: [*:0]const u8) bool {172pub fn isAbsoluteWindows(path: []const u8) bool {
188 if (path_c[0] == '/')173 return isAbsoluteWindowsImpl(u8, path);
189 return true;174}
190175
191 if (path_c[0] == '\\') {176pub fn isAbsoluteWindowsW(path_w: [*:0]const u16) bool {
192 return true;177 return isAbsoluteWindowsImpl(u16, mem.toSliceConst(u16, path_w));
193 }178}
194 if (path_c[0] == 0 or path_c[1] == 0 or path_c[2] == 0) {179
195 return false;180pub fn isAbsoluteWindowsC(path_c: [*:0]const u8) bool {
196 }181 return isAbsoluteWindowsImpl(u8, mem.toSliceConst(u8, path_c));
197 if (path_c[1] == ':') {
198 if (path_c[2] == '/')
199 return true;
200 if (path_c[2] == '\\')
201 return true;
202 }
203 return false;
204}182}
205183
206pub fn isAbsolutePosix(path: []const u8) bool {184pub fn isAbsolutePosix(path: []const u8) bool {
207 return path[0] == sep_posix;185 return path.len > 0 and path[0] == sep_posix;
208}186}
209187
210pub fn isAbsolutePosixC(path_c: [*:0]const u8) bool {188pub fn isAbsolutePosixC(path_c: [*:0]const u8) bool {
211 return path_c[0] == sep_posix;189 return isAbsolutePosix(mem.toSliceConst(u8, path_c));
212}190}
213191
214test "isAbsoluteWindows" {192test "isAbsoluteWindows" {
193 testIsAbsoluteWindows("", false);
215 testIsAbsoluteWindows("/", true);194 testIsAbsoluteWindows("/", true);
216 testIsAbsoluteWindows("//", true);195 testIsAbsoluteWindows("//", true);
217 testIsAbsoluteWindows("//server", true);196 testIsAbsoluteWindows("//server", true);
...@@ -234,6 +213,7 @@ test "isAbsoluteWindows" {...@@ -234,6 +213,7 @@ test "isAbsoluteWindows" {
234}213}
235214
236test "isAbsolutePosix" {215test "isAbsolutePosix" {
216 testIsAbsolutePosix("", false);
237 testIsAbsolutePosix("/home/foo", true);217 testIsAbsolutePosix("/home/foo", true);
238 testIsAbsolutePosix("/home/foo/..", true);218 testIsAbsolutePosix("/home/foo/..", true);
239 testIsAbsolutePosix("bar/", false);219 testIsAbsolutePosix("bar/", false);
lib/std/fs/watch.zig created+675
...@@ -0,0 +1,675 @@
1const std = @import("../std.zig");
2const builtin = @import("builtin");
3const event = std.event;
4const assert = std.debug.assert;
5const testing = std.testing;
6const os = std.os;
7const mem = std.mem;
8const windows = os.windows;
9const Loop = event.Loop;
10const fd_t = os.fd_t;
11const File = std.fs.File;
12const Allocator = mem.Allocator;
13
14const global_event_loop = Loop.instance orelse
15 @compileError("std.fs.Watch currently only works with event-based I/O");
16
17const WatchEventId = enum {
18 CloseWrite,
19 Delete,
20};
21
22fn eqlString(a: []const u16, b: []const u16) bool {
23 if (a.len != b.len) return false;
24 if (a.ptr == b.ptr) return true;
25 return mem.compare(u16, a, b) == .Equal;
26}
27
28fn hashString(s: []const u16) u32 {
29 return @truncate(u32, std.hash.Wyhash.hash(0, @sliceToBytes(s)));
30}
31
32const WatchEventError = error{
33 UserResourceLimitReached,
34 SystemResources,
35 AccessDenied,
36 Unexpected, // TODO remove this possibility
37};
38
39pub fn Watch(comptime V: type) type {
40 return struct {
41 channel: *event.Channel(Event.Error!Event),
42 os_data: OsData,
43 allocator: *Allocator,
44
45 const OsData = switch (builtin.os) {
46 // TODO https://github.com/ziglang/zig/issues/3778
47 .macosx, .freebsd, .netbsd, .dragonfly => KqOsData,
48 .linux => LinuxOsData,
49 .windows => WindowsOsData,
50
51 else => @compileError("Unsupported OS"),
52 };
53
54 const KqOsData = struct {
55 file_table: FileTable,
56 table_lock: event.Lock,
57
58 const FileTable = std.StringHashMap(*Put);
59 const Put = struct {
60 putter_frame: @Frame(kqPutEvents),
61 cancelled: bool = false,
62 value: V,
63 };
64 };
65
66 const WindowsOsData = struct {
67 table_lock: event.Lock,
68 dir_table: DirTable,
69 all_putters: std.atomic.Queue(Put),
70 ref_count: std.atomic.Int(usize),
71
72 const Put = struct {
73 putter: anyframe,
74 cancelled: bool = false,
75 };
76
77 const DirTable = std.StringHashMap(*Dir);
78 const FileTable = std.HashMap([]const u16, V, hashString, eqlString);
79
80 const Dir = struct {
81 putter_frame: @Frame(windowsDirReader),
82 file_table: FileTable,
83 table_lock: event.Lock,
84 };
85 };
86
87 const LinuxOsData = struct {
88 putter_frame: @Frame(linuxEventPutter),
89 inotify_fd: i32,
90 wd_table: WdTable,
91 table_lock: event.Lock,
92 cancelled: bool = false,
93
94 const WdTable = std.AutoHashMap(i32, Dir);
95 const FileTable = std.StringHashMap(V);
96
97 const Dir = struct {
98 dirname: []const u8,
99 file_table: FileTable,
100 };
101 };
102
103 const Self = @This();
104
105 pub const Event = struct {
106 id: Id,
107 data: V,
108
109 pub const Id = WatchEventId;
110 pub const Error = WatchEventError;
111 };
112
113 pub fn init(allocator: *Allocator, event_buf_count: usize) !*Self {
114 const channel = try allocator.create(event.Channel(Event.Error!Event));
115 errdefer allocator.destroy(channel);
116 var buf = try allocator.alloc(Event.Error!Event, event_buf_count);
117 errdefer allocator.free(buf);
118 channel.init(buf);
119 errdefer channel.deinit();
120
121 const self = try allocator.create(Self);
122 errdefer allocator.destroy(self);
123
124 switch (builtin.os) {
125 .linux => {
126 const inotify_fd = try os.inotify_init1(os.linux.IN_NONBLOCK | os.linux.IN_CLOEXEC);
127 errdefer os.close(inotify_fd);
128
129 self.* = Self{
130 .allocator = allocator,
131 .channel = channel,
132 .os_data = OsData{
133 .putter_frame = undefined,
134 .inotify_fd = inotify_fd,
135 .wd_table = OsData.WdTable.init(allocator),
136 .table_lock = event.Lock.init(),
137 },
138 };
139
140 self.os_data.putter_frame = async self.linuxEventPutter();
141 return self;
142 },
143
144 .windows => {
145 self.* = Self{
146 .allocator = allocator,
147 .channel = channel,
148 .os_data = OsData{
149 .table_lock = event.Lock.init(),
150 .dir_table = OsData.DirTable.init(allocator),
151 .ref_count = std.atomic.Int(usize).init(1),
152 .all_putters = std.atomic.Queue(anyframe).init(),
153 },
154 };
155 return self;
156 },
157
158 .macosx, .freebsd, .netbsd, .dragonfly => {
159 self.* = Self{
160 .allocator = allocator,
161 .channel = channel,
162 .os_data = OsData{
163 .table_lock = event.Lock.init(),
164 .file_table = OsData.FileTable.init(allocator),
165 },
166 };
167 return self;
168 },
169 else => @compileError("Unsupported OS"),
170 }
171 }
172
173 /// All addFile calls and removeFile calls must have completed.
174 pub fn deinit(self: *Self) void {
175 switch (builtin.os) {
176 .macosx, .freebsd, .netbsd, .dragonfly => {
177 // TODO we need to cancel the frames before destroying the lock
178 self.os_data.table_lock.deinit();
179 var it = self.os_data.file_table.iterator();
180 while (it.next()) |entry| {
181 entry.cancelled = true;
182 await entry.value.putter;
183 self.allocator.free(entry.key);
184 self.allocator.free(entry.value);
185 }
186 self.channel.deinit();
187 self.allocator.destroy(self.channel.buffer_nodes);
188 self.allocator.destroy(self);
189 },
190 .linux => {
191 self.os_data.cancelled = true;
192 await self.os_data.putter_frame;
193 self.allocator.destroy(self);
194 },
195 .windows => {
196 while (self.os_data.all_putters.get()) |putter_node| {
197 putter_node.cancelled = true;
198 await putter_node.frame;
199 }
200 self.deref();
201 },
202 else => @compileError("Unsupported OS"),
203 }
204 }
205
206 fn ref(self: *Self) void {
207 _ = self.os_data.ref_count.incr();
208 }
209
210 fn deref(self: *Self) void {
211 if (self.os_data.ref_count.decr() == 1) {
212 self.os_data.table_lock.deinit();
213 var it = self.os_data.dir_table.iterator();
214 while (it.next()) |entry| {
215 self.allocator.free(entry.key);
216 self.allocator.destroy(entry.value);
217 }
218 self.os_data.dir_table.deinit();
219 self.channel.deinit();
220 self.allocator.destroy(self.channel.buffer_nodes);
221 self.allocator.destroy(self);
222 }
223 }
224
225 pub fn addFile(self: *Self, file_path: []const u8, value: V) !?V {
226 switch (builtin.os) {
227 .macosx, .freebsd, .netbsd, .dragonfly => return addFileKEvent(self, file_path, value),
228 .linux => return addFileLinux(self, file_path, value),
229 .windows => return addFileWindows(self, file_path, value),
230 else => @compileError("Unsupported OS"),
231 }
232 }
233
234 fn addFileKEvent(self: *Self, file_path: []const u8, value: V) !?V {
235 const resolved_path = try std.fs.path.resolve(self.allocator, [_][]const u8{file_path});
236 var resolved_path_consumed = false;
237 defer if (!resolved_path_consumed) self.allocator.free(resolved_path);
238
239 var close_op = try CloseOperation.start(self.allocator);
240 var close_op_consumed = false;
241 defer if (!close_op_consumed) close_op.finish();
242
243 const flags = if (comptime std.Target.current.isDarwin()) os.O_SYMLINK | os.O_EVTONLY else 0;
244 const mode = 0;
245 const fd = try openPosix(self.allocator, resolved_path, flags, mode);
246 close_op.setHandle(fd);
247
248 var put = try self.allocator.create(OsData.Put);
249 errdefer self.allocator.destroy(put);
250 put.* = OsData.Put{
251 .value = value,
252 .putter_frame = undefined,
253 };
254 put.putter_frame = async self.kqPutEvents(close_op, put);
255 close_op_consumed = true;
256 errdefer {
257 put.cancelled = true;
258 await put.putter_frame;
259 }
260
261 const result = blk: {
262 const held = self.os_data.table_lock.acquire();
263 defer held.release();
264
265 const gop = try self.os_data.file_table.getOrPut(resolved_path);
266 if (gop.found_existing) {
267 const prev_value = gop.kv.value.value;
268 await gop.kv.value.putter_frame;
269 gop.kv.value = put;
270 break :blk prev_value;
271 } else {
272 resolved_path_consumed = true;
273 gop.kv.value = put;
274 break :blk null;
275 }
276 };
277
278 return result;
279 }
280
281 fn kqPutEvents(self: *Self, close_op: *CloseOperation, put: *OsData.Put) void {
282 global_event_loop.beginOneEvent();
283
284 defer {
285 close_op.finish();
286 global_event_loop.finishOneEvent();
287 }
288
289 while (!put.cancelled) {
290 if (global_event_loop.bsdWaitKev(
291 @intCast(usize, close_op.getHandle()),
292 os.EVFILT_VNODE,
293 os.NOTE_WRITE | os.NOTE_DELETE,
294 )) |kev| {
295 // TODO handle EV_ERROR
296 if (kev.fflags & os.NOTE_DELETE != 0) {
297 self.channel.put(Self.Event{
298 .id = Event.Id.Delete,
299 .data = put.value,
300 });
301 } else if (kev.fflags & os.NOTE_WRITE != 0) {
302 self.channel.put(Self.Event{
303 .id = Event.Id.CloseWrite,
304 .data = put.value,
305 });
306 }
307 } else |err| switch (err) {
308 error.EventNotFound => unreachable,
309 error.ProcessNotFound => unreachable,
310 error.Overflow => unreachable,
311 error.AccessDenied, error.SystemResources => |casted_err| {
312 self.channel.put(casted_err);
313 },
314 }
315 }
316 }
317
318 fn addFileLinux(self: *Self, file_path: []const u8, value: V) !?V {
319 const dirname = std.fs.path.dirname(file_path) orelse ".";
320 const dirname_with_null = try std.cstr.addNullByte(self.allocator, dirname);
321 var dirname_with_null_consumed = false;
322 defer if (!dirname_with_null_consumed) self.channel.free(dirname_with_null);
323
324 const basename = std.fs.path.basename(file_path);
325 const basename_with_null = try std.cstr.addNullByte(self.allocator, basename);
326 var basename_with_null_consumed = false;
327 defer if (!basename_with_null_consumed) self.allocator.free(basename_with_null);
328
329 const wd = try os.inotify_add_watchC(
330 self.os_data.inotify_fd,
331 dirname_with_null.ptr,
332 os.linux.IN_CLOSE_WRITE | os.linux.IN_ONLYDIR | os.linux.IN_EXCL_UNLINK,
333 );
334 // wd is either a newly created watch or an existing one.
335
336 const held = self.os_data.table_lock.acquire();
337 defer held.release();
338
339 const gop = try self.os_data.wd_table.getOrPut(wd);
340 if (!gop.found_existing) {
341 gop.kv.value = OsData.Dir{
342 .dirname = dirname_with_null,
343 .file_table = OsData.FileTable.init(self.allocator),
344 };
345 dirname_with_null_consumed = true;
346 }
347 const dir = &gop.kv.value;
348
349 const file_table_gop = try dir.file_table.getOrPut(basename_with_null);
350 if (file_table_gop.found_existing) {
351 const prev_value = file_table_gop.kv.value;
352 file_table_gop.kv.value = value;
353 return prev_value;
354 } else {
355 file_table_gop.kv.value = value;
356 basename_with_null_consumed = true;
357 return null;
358 }
359 }
360
361 fn addFileWindows(self: *Self, file_path: []const u8, value: V) !?V {
362 // TODO we might need to convert dirname and basename to canonical file paths ("short"?)
363 const dirname = try std.mem.dupe(self.allocator, u8, std.fs.path.dirname(file_path) orelse ".");
364 var dirname_consumed = false;
365 defer if (!dirname_consumed) self.allocator.free(dirname);
366
367 const dirname_utf16le = try std.unicode.utf8ToUtf16LeWithNull(self.allocator, dirname);
368 defer self.allocator.free(dirname_utf16le);
369
370 // TODO https://github.com/ziglang/zig/issues/265
371 const basename = std.fs.path.basename(file_path);
372 const basename_utf16le_null = try std.unicode.utf8ToUtf16LeWithNull(self.allocator, basename);
373 var basename_utf16le_null_consumed = false;
374 defer if (!basename_utf16le_null_consumed) self.allocator.free(basename_utf16le_null);
375 const basename_utf16le_no_null = basename_utf16le_null[0 .. basename_utf16le_null.len - 1];
376
377 const dir_handle = try windows.CreateFileW(
378 dirname_utf16le.ptr,
379 windows.FILE_LIST_DIRECTORY,
380 windows.FILE_SHARE_READ | windows.FILE_SHARE_DELETE | windows.FILE_SHARE_WRITE,
381 null,
382 windows.OPEN_EXISTING,
383 windows.FILE_FLAG_BACKUP_SEMANTICS | windows.FILE_FLAG_OVERLAPPED,
384 null,
385 );
386 var dir_handle_consumed = false;
387 defer if (!dir_handle_consumed) windows.CloseHandle(dir_handle);
388
389 const held = self.os_data.table_lock.acquire();
390 defer held.release();
391
392 const gop = try self.os_data.dir_table.getOrPut(dirname);
393 if (gop.found_existing) {
394 const dir = gop.kv.value;
395 const held_dir_lock = dir.table_lock.acquire();
396 defer held_dir_lock.release();
397
398 const file_gop = try dir.file_table.getOrPut(basename_utf16le_no_null);
399 if (file_gop.found_existing) {
400 const prev_value = file_gop.kv.value;
401 file_gop.kv.value = value;
402 return prev_value;
403 } else {
404 file_gop.kv.value = value;
405 basename_utf16le_null_consumed = true;
406 return null;
407 }
408 } else {
409 errdefer _ = self.os_data.dir_table.remove(dirname);
410 const dir = try self.allocator.create(OsData.Dir);
411 errdefer self.allocator.destroy(dir);
412
413 dir.* = OsData.Dir{
414 .file_table = OsData.FileTable.init(self.allocator),
415 .table_lock = event.Lock.init(),
416 .putter_frame = undefined,
417 };
418 gop.kv.value = dir;
419 assert((try dir.file_table.put(basename_utf16le_no_null, value)) == null);
420 basename_utf16le_null_consumed = true;
421
422 dir.putter_frame = async self.windowsDirReader(dir_handle, dir);
423 dir_handle_consumed = true;
424
425 dirname_consumed = true;
426
427 return null;
428 }
429 }
430
431 fn windowsDirReader(self: *Self, dir_handle: windows.HANDLE, dir: *OsData.Dir) void {
432 self.ref();
433 defer self.deref();
434
435 defer os.close(dir_handle);
436
437 var putter_node = std.atomic.Queue(anyframe).Node{
438 .data = .{ .putter = @frame() },
439 .prev = null,
440 .next = null,
441 };
442 self.os_data.all_putters.put(&putter_node);
443 defer _ = self.os_data.all_putters.remove(&putter_node);
444
445 var resume_node = Loop.ResumeNode.Basic{
446 .base = Loop.ResumeNode{
447 .id = Loop.ResumeNode.Id.Basic,
448 .handle = @frame(),
449 .overlapped = windows.OVERLAPPED{
450 .Internal = 0,
451 .InternalHigh = 0,
452 .Offset = 0,
453 .OffsetHigh = 0,
454 .hEvent = null,
455 },
456 },
457 };
458 var event_buf: [4096]u8 align(@alignOf(windows.FILE_NOTIFY_INFORMATION)) = undefined;
459
460 // TODO handle this error not in the channel but in the setup
461 _ = windows.CreateIoCompletionPort(
462 dir_handle,
463 global_event_loop.os_data.io_port,
464 undefined,
465 undefined,
466 ) catch |err| {
467 self.channel.put(err);
468 return;
469 };
470
471 while (!putter_node.data.cancelled) {
472 {
473 // TODO only 1 beginOneEvent for the whole function
474 global_event_loop.beginOneEvent();
475 errdefer global_event_loop.finishOneEvent();
476 errdefer {
477 _ = windows.kernel32.CancelIoEx(dir_handle, &resume_node.base.overlapped);
478 }
479 suspend {
480 _ = windows.kernel32.ReadDirectoryChangesW(
481 dir_handle,
482 &event_buf,
483 @intCast(windows.DWORD, event_buf.len),
484 windows.FALSE, // watch subtree
485 windows.FILE_NOTIFY_CHANGE_FILE_NAME | windows.FILE_NOTIFY_CHANGE_DIR_NAME |
486 windows.FILE_NOTIFY_CHANGE_ATTRIBUTES | windows.FILE_NOTIFY_CHANGE_SIZE |
487 windows.FILE_NOTIFY_CHANGE_LAST_WRITE | windows.FILE_NOTIFY_CHANGE_LAST_ACCESS |
488 windows.FILE_NOTIFY_CHANGE_CREATION | windows.FILE_NOTIFY_CHANGE_SECURITY,
489 null, // number of bytes transferred (unused for async)
490 &resume_node.base.overlapped,
491 null, // completion routine - unused because we use IOCP
492 );
493 }
494 }
495 var bytes_transferred: windows.DWORD = undefined;
496 if (windows.kernel32.GetOverlappedResult(dir_handle, &resume_node.base.overlapped, &bytes_transferred, windows.FALSE) == 0) {
497 const err = switch (windows.kernel32.GetLastError()) {
498 else => |err| windows.unexpectedError(err),
499 };
500 self.channel.put(err);
501 } else {
502 // can't use @bytesToSlice because of the special variable length name field
503 var ptr = event_buf[0..].ptr;
504 const end_ptr = ptr + bytes_transferred;
505 var ev: *windows.FILE_NOTIFY_INFORMATION = undefined;
506 while (@ptrToInt(ptr) < @ptrToInt(end_ptr)) : (ptr += ev.NextEntryOffset) {
507 ev = @ptrCast(*windows.FILE_NOTIFY_INFORMATION, ptr);
508 const emit = switch (ev.Action) {
509 windows.FILE_ACTION_REMOVED => WatchEventId.Delete,
510 windows.FILE_ACTION_MODIFIED => WatchEventId.CloseWrite,
511 else => null,
512 };
513 if (emit) |id| {
514 const basename_utf16le = ([*]u16)(&ev.FileName)[0 .. ev.FileNameLength / 2];
515 const user_value = blk: {
516 const held = dir.table_lock.acquire();
517 defer held.release();
518
519 if (dir.file_table.get(basename_utf16le)) |entry| {
520 break :blk entry.value;
521 } else {
522 break :blk null;
523 }
524 };
525 if (user_value) |v| {
526 self.channel.put(Event{
527 .id = id,
528 .data = v,
529 });
530 }
531 }
532 if (ev.NextEntryOffset == 0) break;
533 }
534 }
535 }
536 }
537
538 pub fn removeFile(self: *Self, file_path: []const u8) ?V {
539 @panic("TODO");
540 }
541
542 fn linuxEventPutter(self: *Self) void {
543 global_event_loop.beginOneEvent();
544
545 defer {
546 self.os_data.table_lock.deinit();
547 var wd_it = self.os_data.wd_table.iterator();
548 while (wd_it.next()) |wd_entry| {
549 var file_it = wd_entry.value.file_table.iterator();
550 while (file_it.next()) |file_entry| {
551 self.allocator.free(file_entry.key);
552 }
553 self.allocator.free(wd_entry.value.dirname);
554 wd_entry.value.file_table.deinit();
555 }
556 self.os_data.wd_table.deinit();
557 global_event_loop.finishOneEvent();
558 os.close(self.os_data.inotify_fd);
559 self.channel.deinit();
560 self.allocator.free(self.channel.buffer_nodes);
561 }
562
563 var event_buf: [4096]u8 align(@alignOf(os.linux.inotify_event)) = undefined;
564
565 while (!self.os_data.cancelled) {
566 const rc = os.linux.read(self.os_data.inotify_fd, &event_buf, event_buf.len);
567 const errno = os.linux.getErrno(rc);
568 switch (errno) {
569 0 => {
570 // can't use @bytesToSlice because of the special variable length name field
571 var ptr = event_buf[0..].ptr;
572 const end_ptr = ptr + event_buf.len;
573 var ev: *os.linux.inotify_event = undefined;
574 while (@ptrToInt(ptr) < @ptrToInt(end_ptr)) {
575 ev = @ptrCast(*os.linux.inotify_event, ptr);
576 if (ev.mask & os.linux.IN_CLOSE_WRITE == os.linux.IN_CLOSE_WRITE) {
577 const basename_ptr = ptr + @sizeOf(os.linux.inotify_event);
578 // `ev.len` counts all bytes in `ev.name` including terminating null byte.
579 const basename_with_null = basename_ptr[0..ev.len];
580 const user_value = blk: {
581 const held = self.os_data.table_lock.acquire();
582 defer held.release();
583
584 const dir = &self.os_data.wd_table.get(ev.wd).?.value;
585 if (dir.file_table.get(basename_with_null)) |entry| {
586 break :blk entry.value;
587 } else {
588 break :blk null;
589 }
590 };
591 if (user_value) |v| {
592 self.channel.put(Event{
593 .id = WatchEventId.CloseWrite,
594 .data = v,
595 });
596 }
597 }
598
599 ptr = @alignCast(@alignOf(os.linux.inotify_event), ptr + @sizeOf(os.linux.inotify_event) + ev.len);
600 }
601 },
602 os.linux.EINTR => continue,
603 os.linux.EINVAL => unreachable,
604 os.linux.EFAULT => unreachable,
605 os.linux.EAGAIN => {
606 global_event_loop.linuxWaitFd(self.os_data.inotify_fd, os.linux.EPOLLET | os.linux.EPOLLIN | os.EPOLLONESHOT);
607 },
608 else => unreachable,
609 }
610 }
611 }
612 };
613}
614
615const test_tmp_dir = "std_event_fs_test";
616
617test "write a file, watch it, write it again" {
618 // TODO re-enable this test
619 if (true) return error.SkipZigTest;
620
621 const allocator = std.heap.page_allocator;
622
623 try os.makePath(allocator, test_tmp_dir);
624 defer os.deleteTree(test_tmp_dir) catch {};
625
626 return testFsWatch(&allocator);
627}
628
629fn testFsWatch(allocator: *Allocator) !void {
630 const file_path = try std.fs.path.join(allocator, [_][]const u8{ test_tmp_dir, "file.txt" });
631 defer allocator.free(file_path);
632
633 const contents =
634 \\line 1
635 \\line 2
636 ;
637 const line2_offset = 7;
638
639 // first just write then read the file
640 try writeFile(allocator, file_path, contents);
641
642 const read_contents = try readFile(allocator, file_path, 1024 * 1024);
643 testing.expectEqualSlices(u8, contents, read_contents);
644
645 // now watch the file
646 var watch = try Watch(void).init(allocator, 0);
647 defer watch.deinit();
648
649 testing.expect((try watch.addFile(file_path, {})) == null);
650
651 const ev = watch.channel.get();
652 var ev_consumed = false;
653 defer if (!ev_consumed) await ev;
654
655 // overwrite line 2
656 const fd = try await openReadWrite(file_path, File.default_mode);
657 {
658 defer os.close(fd);
659
660 try pwritev(allocator, fd, []const []const u8{"lorem ipsum"}, line2_offset);
661 }
662
663 ev_consumed = true;
664 switch ((try await ev).id) {
665 WatchEventId.CloseWrite => {},
666 WatchEventId.Delete => @panic("wrong event"),
667 }
668 const contents_updated = try readFile(allocator, file_path, 1024 * 1024);
669 testing.expectEqualSlices(u8,
670 \\line 1
671 \\lorem ipsum
672 , contents_updated);
673
674 // TODO test deleting the file and then re-adding it. we should get events for both
675}
lib/std/io.zig+13-3
...@@ -47,7 +47,10 @@ fn getStdOutHandle() os.fd_t {...@@ -47,7 +47,10 @@ fn getStdOutHandle() os.fd_t {
47}47}
4848
49pub fn getStdOut() File {49pub fn getStdOut() File {
50 return File.openHandle(getStdOutHandle());50 return File{
51 .handle = getStdOutHandle(),
52 .io_mode = .blocking,
53 };
51}54}
5255
53fn getStdErrHandle() os.fd_t {56fn getStdErrHandle() os.fd_t {
...@@ -63,7 +66,11 @@ fn getStdErrHandle() os.fd_t {...@@ -63,7 +66,11 @@ fn getStdErrHandle() os.fd_t {
63}66}
6467
65pub fn getStdErr() File {68pub fn getStdErr() File {
66 return File.openHandle(getStdErrHandle());69 return File{
70 .handle = getStdErrHandle(),
71 .io_mode = .blocking,
72 .async_block_allowed = File.async_block_allowed_yes,
73 };
67}74}
6875
69fn getStdInHandle() os.fd_t {76fn getStdInHandle() os.fd_t {
...@@ -79,7 +86,10 @@ fn getStdInHandle() os.fd_t {...@@ -79,7 +86,10 @@ fn getStdInHandle() os.fd_t {
79}86}
8087
81pub fn getStdIn() File {88pub fn getStdIn() File {
82 return File.openHandle(getStdInHandle());89 return File{
90 .handle = getStdInHandle(),
91 .io_mode = .blocking,
92 };
83}93}
8494
85pub const SeekableStream = @import("io/seekable_stream.zig").SeekableStream;95pub const SeekableStream = @import("io/seekable_stream.zig").SeekableStream;
lib/std/io/out_stream.zig+11-15
...@@ -9,14 +9,11 @@ pub const stack_size: usize = if (@hasDecl(root, "stack_size_std_io_OutStream"))...@@ -9,14 +9,11 @@ pub const stack_size: usize = if (@hasDecl(root, "stack_size_std_io_OutStream"))
9else9else
10 default_stack_size;10 default_stack_size;
1111
12/// TODO this is not integrated with evented I/O yet.
13/// https://github.com/ziglang/zig/issues/3557
14pub fn OutStream(comptime WriteError: type) type {12pub fn OutStream(comptime WriteError: type) type {
15 return struct {13 return struct {
16 const Self = @This();14 const Self = @This();
17 pub const Error = WriteError;15 pub const Error = WriteError;
18 // TODO https://github.com/ziglang/zig/issues/355716 pub const WriteFn = if (std.io.is_async)
19 pub const WriteFn = if (std.io.is_async and false)
20 async fn (self: *Self, bytes: []const u8) Error!void17 async fn (self: *Self, bytes: []const u8) Error!void
21 else18 else
22 fn (self: *Self, bytes: []const u8) Error!void;19 fn (self: *Self, bytes: []const u8) Error!void;
...@@ -24,8 +21,7 @@ pub fn OutStream(comptime WriteError: type) type {...@@ -24,8 +21,7 @@ pub fn OutStream(comptime WriteError: type) type {
24 writeFn: WriteFn,21 writeFn: WriteFn,
2522
26 pub fn write(self: *Self, bytes: []const u8) Error!void {23 pub fn write(self: *Self, bytes: []const u8) Error!void {
27 // TODO https://github.com/ziglang/zig/issues/355724 if (std.io.is_async) {
28 if (std.io.is_async and false) {
29 // Let's not be writing 0xaa in safe modes for upwards of 4 MiB for every stream write.25 // Let's not be writing 0xaa in safe modes for upwards of 4 MiB for every stream write.
30 @setRuntimeSafety(false);26 @setRuntimeSafety(false);
31 var stack_frame: [stack_size]u8 align(std.Target.stack_align) = undefined;27 var stack_frame: [stack_size]u8 align(std.Target.stack_align) = undefined;
...@@ -36,12 +32,12 @@ pub fn OutStream(comptime WriteError: type) type {...@@ -36,12 +32,12 @@ pub fn OutStream(comptime WriteError: type) type {
36 }32 }
3733
38 pub fn print(self: *Self, comptime format: []const u8, args: var) Error!void {34 pub fn print(self: *Self, comptime format: []const u8, args: var) Error!void {
39 return std.fmt.format(self, Error, self.writeFn, format, args);35 return std.fmt.format(self, Error, write, format, args);
40 }36 }
4137
42 pub fn writeByte(self: *Self, byte: u8) Error!void {38 pub fn writeByte(self: *Self, byte: u8) Error!void {
43 const slice = @as(*const [1]u8, &byte)[0..];39 const array = [1]u8{byte};
44 return self.writeFn(self, slice);40 return self.write(&array);
45 }41 }
4642
47 pub fn writeByteNTimes(self: *Self, byte: u8, n: usize) Error!void {43 pub fn writeByteNTimes(self: *Self, byte: u8, n: usize) Error!void {
...@@ -51,7 +47,7 @@ pub fn OutStream(comptime WriteError: type) type {...@@ -51,7 +47,7 @@ pub fn OutStream(comptime WriteError: type) type {
51 var remaining: usize = n;47 var remaining: usize = n;
52 while (remaining > 0) {48 while (remaining > 0) {
53 const to_write = std.math.min(remaining, bytes.len);49 const to_write = std.math.min(remaining, bytes.len);
54 try self.writeFn(self, bytes[0..to_write]);50 try self.write(bytes[0..to_write]);
55 remaining -= to_write;51 remaining -= to_write;
56 }52 }
57 }53 }
...@@ -60,32 +56,32 @@ pub fn OutStream(comptime WriteError: type) type {...@@ -60,32 +56,32 @@ pub fn OutStream(comptime WriteError: type) type {
60 pub fn writeIntNative(self: *Self, comptime T: type, value: T) Error!void {56 pub fn writeIntNative(self: *Self, comptime T: type, value: T) Error!void {
61 var bytes: [(T.bit_count + 7) / 8]u8 = undefined;57 var bytes: [(T.bit_count + 7) / 8]u8 = undefined;
62 mem.writeIntNative(T, &bytes, value);58 mem.writeIntNative(T, &bytes, value);
63 return self.writeFn(self, &bytes);59 return self.write(&bytes);
64 }60 }
6561
66 /// Write a foreign-endian integer.62 /// Write a foreign-endian integer.
67 pub fn writeIntForeign(self: *Self, comptime T: type, value: T) Error!void {63 pub fn writeIntForeign(self: *Self, comptime T: type, value: T) Error!void {
68 var bytes: [(T.bit_count + 7) / 8]u8 = undefined;64 var bytes: [(T.bit_count + 7) / 8]u8 = undefined;
69 mem.writeIntForeign(T, &bytes, value);65 mem.writeIntForeign(T, &bytes, value);
70 return self.writeFn(self, &bytes);66 return self.write(&bytes);
71 }67 }
7268
73 pub fn writeIntLittle(self: *Self, comptime T: type, value: T) Error!void {69 pub fn writeIntLittle(self: *Self, comptime T: type, value: T) Error!void {
74 var bytes: [(T.bit_count + 7) / 8]u8 = undefined;70 var bytes: [(T.bit_count + 7) / 8]u8 = undefined;
75 mem.writeIntLittle(T, &bytes, value);71 mem.writeIntLittle(T, &bytes, value);
76 return self.writeFn(self, &bytes);72 return self.write(&bytes);
77 }73 }
7874
79 pub fn writeIntBig(self: *Self, comptime T: type, value: T) Error!void {75 pub fn writeIntBig(self: *Self, comptime T: type, value: T) Error!void {
80 var bytes: [(T.bit_count + 7) / 8]u8 = undefined;76 var bytes: [(T.bit_count + 7) / 8]u8 = undefined;
81 mem.writeIntBig(T, &bytes, value);77 mem.writeIntBig(T, &bytes, value);
82 return self.writeFn(self, &bytes);78 return self.write(&bytes);
83 }79 }
8480
85 pub fn writeInt(self: *Self, comptime T: type, value: T, endian: builtin.Endian) Error!void {81 pub fn writeInt(self: *Self, comptime T: type, value: T, endian: builtin.Endian) Error!void {
86 var bytes: [(T.bit_count + 7) / 8]u8 = undefined;82 var bytes: [(T.bit_count + 7) / 8]u8 = undefined;
87 mem.writeInt(T, &bytes, value, endian);83 mem.writeInt(T, &bytes, value, endian);
88 return self.writeFn(self, &bytes);84 return self.write(&bytes);
89 }85 }
90 };86 };
91}87}
lib/std/linked_list.zig+3-6
...@@ -18,12 +18,11 @@ pub fn SinglyLinkedList(comptime T: type) type {...@@ -18,12 +18,11 @@ pub fn SinglyLinkedList(comptime T: type) type {
1818
19 /// Node inside the linked list wrapping the actual data.19 /// Node inside the linked list wrapping the actual data.
20 pub const Node = struct {20 pub const Node = struct {
21 next: ?*Node,21 next: ?*Node = null,
22 data: T,22 data: T,
2323
24 pub fn init(data: T) Node {24 pub fn init(data: T) Node {
25 return Node{25 return Node{
26 .next = null,
27 .data = data,26 .data = data,
28 };27 };
29 }28 }
...@@ -196,14 +195,12 @@ pub fn TailQueue(comptime T: type) type {...@@ -196,14 +195,12 @@ pub fn TailQueue(comptime T: type) type {
196195
197 /// Node inside the linked list wrapping the actual data.196 /// Node inside the linked list wrapping the actual data.
198 pub const Node = struct {197 pub const Node = struct {
199 prev: ?*Node,198 prev: ?*Node = null,
200 next: ?*Node,199 next: ?*Node = null,
201 data: T,200 data: T,
202201
203 pub fn init(data: T) Node {202 pub fn init(data: T) Node {
204 return Node{203 return Node{
205 .prev = null,
206 .next = null,
207 .data = data,204 .data = data,
208 };205 };
209 }206 }
lib/std/mem.zig+1-1
...@@ -233,7 +233,7 @@ pub const Allocator = struct {...@@ -233,7 +233,7 @@ pub const Allocator = struct {
233 pub fn free(self: *Allocator, memory: var) void {233 pub fn free(self: *Allocator, memory: var) void {
234 const Slice = @typeInfo(@TypeOf(memory)).Pointer;234 const Slice = @typeInfo(@TypeOf(memory)).Pointer;
235 const bytes = @sliceToBytes(memory);235 const bytes = @sliceToBytes(memory);
236 const bytes_len = bytes.len + @boolToInt(Slice.sentinel != null);236 const bytes_len = bytes.len + if (Slice.sentinel != null) @sizeOf(Slice.child) else 0;
237 if (bytes_len == 0) return;237 if (bytes_len == 0) return;
238 const non_const_ptr = @intToPtr([*]u8, @ptrToInt(bytes.ptr));238 const non_const_ptr = @intToPtr([*]u8, @ptrToInt(bytes.ptr));
239 @memset(non_const_ptr, undefined, bytes_len);239 @memset(non_const_ptr, undefined, bytes_len);
lib/std/net.zig+11-5
...@@ -271,7 +271,7 @@ pub const Address = extern union {...@@ -271,7 +271,7 @@ pub const Address = extern union {
271 options: std.fmt.FormatOptions,271 options: std.fmt.FormatOptions,
272 context: var,272 context: var,
273 comptime Errors: type,273 comptime Errors: type,
274 output: fn (@TypeOf(context), []const u8) Errors!void,274 comptime output: fn (@TypeOf(context), []const u8) Errors!void,
275 ) !void {275 ) !void {
276 switch (self.any.family) {276 switch (self.any.family) {
277 os.AF_INET => {277 os.AF_INET => {
...@@ -361,7 +361,7 @@ pub const Address = extern union {...@@ -361,7 +361,7 @@ pub const Address = extern union {
361};361};
362362
363pub fn connectUnixSocket(path: []const u8) !fs.File {363pub fn connectUnixSocket(path: []const u8) !fs.File {
364 const opt_non_block = if (std.io.mode == .evented) os.SOCK_NONBLOCK else 0;364 const opt_non_block = if (std.io.is_async) os.SOCK_NONBLOCK else 0;
365 const sockfd = try os.socket(365 const sockfd = try os.socket(
366 os.AF_UNIX,366 os.AF_UNIX,
367 os.SOCK_STREAM | os.SOCK_CLOEXEC | opt_non_block,367 os.SOCK_STREAM | os.SOCK_CLOEXEC | opt_non_block,
...@@ -377,7 +377,10 @@ pub fn connectUnixSocket(path: []const u8) !fs.File {...@@ -377,7 +377,10 @@ pub fn connectUnixSocket(path: []const u8) !fs.File {
377 addr.getOsSockLen(),377 addr.getOsSockLen(),
378 );378 );
379379
380 return fs.File.openHandle(sockfd);380 return fs.File{
381 .handle = sockfd,
382 .io_mode = std.io.mode,
383 };
381}384}
382385
383pub const AddressList = struct {386pub const AddressList = struct {
...@@ -412,7 +415,7 @@ pub fn tcpConnectToAddress(address: Address) !fs.File {...@@ -412,7 +415,7 @@ pub fn tcpConnectToAddress(address: Address) !fs.File {
412 errdefer os.close(sockfd);415 errdefer os.close(sockfd);
413 try os.connect(sockfd, &address.any, address.getOsSockLen());416 try os.connect(sockfd, &address.any, address.getOsSockLen());
414417
415 return fs.File{ .handle = sockfd };418 return fs.File{ .handle = sockfd, .io_mode = std.io.mode };
416}419}
417420
418/// Call `AddressList.deinit` on the result.421/// Call `AddressList.deinit` on the result.
...@@ -1379,7 +1382,10 @@ pub const StreamServer = struct {...@@ -1379,7 +1382,10 @@ pub const StreamServer = struct {
1379 var adr_len: os.socklen_t = @sizeOf(Address);1382 var adr_len: os.socklen_t = @sizeOf(Address);
1380 if (os.accept4(self.sockfd.?, &accepted_addr.any, &adr_len, accept_flags)) |fd| {1383 if (os.accept4(self.sockfd.?, &accepted_addr.any, &adr_len, accept_flags)) |fd| {
1381 return Connection{1384 return Connection{
1382 .file = fs.File.openHandle(fd),1385 .file = fs.File{
1386 .handle = fd,
1387 .io_mode = std.io.mode,
1388 },
1383 .address = accepted_addr,1389 .address = accepted_addr,
1384 };1390 };
1385 } else |err| switch (err) {1391 } else |err| switch (err) {
lib/std/net/test.zig+3-5
...@@ -81,17 +81,15 @@ test "resolve DNS" {...@@ -81,17 +81,15 @@ test "resolve DNS" {
81}81}
8282
83test "listen on a port, send bytes, receive bytes" {83test "listen on a port, send bytes, receive bytes" {
84 if (!std.io.is_async) return error.SkipZigTest;
85
84 if (std.builtin.os != .linux) {86 if (std.builtin.os != .linux) {
85 // TODO build abstractions for other operating systems87 // TODO build abstractions for other operating systems
86 return error.SkipZigTest;88 return error.SkipZigTest;
87 }89 }
88 if (std.io.mode != .evented) {
89 // TODO add ability to run tests in non-blocking I/O mode
90 return error.SkipZigTest;
91 }
9290
93 // TODO doing this at comptime crashed the compiler91 // TODO doing this at comptime crashed the compiler
94 const localhost = net.Address.parseIp("127.0.0.1", 0);92 const localhost = try net.Address.parseIp("127.0.0.1", 0);
9593
96 var server = net.StreamServer.init(net.StreamServer.Options{});94 var server = net.StreamServer.init(net.StreamServer.Options{});
97 defer server.deinit();95 defer server.deinit();
lib/std/os.zig+222-21
...@@ -169,7 +169,12 @@ fn getRandomBytesDevURandom(buf: []u8) !void {...@@ -169,7 +169,12 @@ fn getRandomBytesDevURandom(buf: []u8) !void {
169 return error.NoDevice;169 return error.NoDevice;
170 }170 }
171171
172 const stream = &std.fs.File.openHandle(fd).inStream().stream;172 const file = std.fs.File{
173 .handle = fd,
174 .io_mode = .blocking,
175 .async_block_allowed = std.fs.File.async_block_allowed_yes,
176 };
177 const stream = &file.inStream().stream;
173 stream.readNoEof(buf) catch return error.Unexpected;178 stream.readNoEof(buf) catch return error.Unexpected;
174}179}
175180
...@@ -293,7 +298,7 @@ pub const ReadError = error{...@@ -293,7 +298,7 @@ pub const ReadError = error{
293/// via the event loop. Otherwise EAGAIN results in error.WouldBlock.298/// via the event loop. Otherwise EAGAIN results in error.WouldBlock.
294pub fn read(fd: fd_t, buf: []u8) ReadError!usize {299pub fn read(fd: fd_t, buf: []u8) ReadError!usize {
295 if (builtin.os == .windows) {300 if (builtin.os == .windows) {
296 return windows.ReadFile(fd, buf);301 return windows.ReadFile(fd, buf, null);
297 }302 }
298303
299 if (builtin.os == .wasi and !builtin.link_libc) {304 if (builtin.os == .wasi and !builtin.link_libc) {
...@@ -335,9 +340,37 @@ pub fn read(fd: fd_t, buf: []u8) ReadError!usize {...@@ -335,9 +340,37 @@ pub fn read(fd: fd_t, buf: []u8) ReadError!usize {
335}340}
336341
337/// Number of bytes read is returned. Upon reading end-of-file, zero is returned.342/// Number of bytes read is returned. Upon reading end-of-file, zero is returned.
338/// If the application has a global event loop enabled, EAGAIN is handled343///
339/// via the event loop. Otherwise EAGAIN results in error.WouldBlock.344/// For POSIX systems, if the application has a global event loop enabled, EAGAIN is handled
345/// via the event loop. Otherwise EAGAIN results in `error.WouldBlock`.
346/// On Windows, if the application has a global event loop enabled, I/O Completion Ports are
347/// used to perform the I/O. `error.WouldBlock` is not possible on Windows.
348///
349/// This operation is non-atomic on the following systems:
350/// * Windows
351/// On these systems, the read races with concurrent writes to the same file descriptor.
340pub fn readv(fd: fd_t, iov: []const iovec) ReadError!usize {352pub fn readv(fd: fd_t, iov: []const iovec) ReadError!usize {
353 if (builtin.os == .windows) {
354 // TODO batch these into parallel requests
355 var off: usize = 0;
356 var iov_i: usize = 0;
357 var inner_off: usize = 0;
358 while (true) {
359 const v = iov[iov_i];
360 const amt_read = try read(fd, v.iov_base[inner_off .. v.iov_len - inner_off]);
361 off += amt_read;
362 inner_off += amt_read;
363 if (inner_off == v.len) {
364 iov_i += 1;
365 inner_off = 0;
366 if (iov_i == iov.len) {
367 return off;
368 }
369 }
370 if (amt_read == 0) return off; // EOF
371 } else unreachable; // TODO https://github.com/ziglang/zig/issues/707
372 }
373
341 while (true) {374 while (true) {
342 // TODO handle the case when iov_len is too large and get rid of this @intCast375 // TODO handle the case when iov_len is too large and get rid of this @intCast
343 const rc = system.readv(fd, iov.ptr, @intCast(u32, iov.len));376 const rc = system.readv(fd, iov.ptr, @intCast(u32, iov.len));
...@@ -363,8 +396,56 @@ pub fn readv(fd: fd_t, iov: []const iovec) ReadError!usize {...@@ -363,8 +396,56 @@ pub fn readv(fd: fd_t, iov: []const iovec) ReadError!usize {
363}396}
364397
365/// Number of bytes read is returned. Upon reading end-of-file, zero is returned.398/// Number of bytes read is returned. Upon reading end-of-file, zero is returned.
366/// If the application has a global event loop enabled, EAGAIN is handled399///
367/// via the event loop. Otherwise EAGAIN results in error.WouldBlock.400/// Retries when interrupted by a signal.
401///
402/// For POSIX systems, if the application has a global event loop enabled, EAGAIN is handled
403/// via the event loop. Otherwise EAGAIN results in `error.WouldBlock`.
404/// On Windows, if the application has a global event loop enabled, I/O Completion Ports are
405/// used to perform the I/O. `error.WouldBlock` is not possible on Windows.
406pub fn pread(fd: fd_t, buf: []u8, offset: u64) ReadError!usize {
407 if (builtin.os == .windows) {
408 return windows.ReadFile(fd, buf, offset);
409 }
410
411 while (true) {
412 const rc = system.pread(fd, buf.ptr, buf.len, offset);
413 switch (errno(rc)) {
414 0 => return @intCast(usize, rc),
415 EINTR => continue,
416 EINVAL => unreachable,
417 EFAULT => unreachable,
418 EAGAIN => if (std.event.Loop.instance) |loop| {
419 loop.waitUntilFdReadable(fd);
420 continue;
421 } else {
422 return error.WouldBlock;
423 },
424 EBADF => unreachable, // Always a race condition.
425 EIO => return error.InputOutput,
426 EISDIR => return error.IsDir,
427 ENOBUFS => return error.SystemResources,
428 ENOMEM => return error.SystemResources,
429 ECONNRESET => return error.ConnectionResetByPeer,
430 else => |err| return unexpectedErrno(err),
431 }
432 }
433 return index;
434}
435
436/// Number of bytes read is returned. Upon reading end-of-file, zero is returned.
437///
438/// Retries when interrupted by a signal.
439///
440/// For POSIX systems, if the application has a global event loop enabled, EAGAIN is handled
441/// via the event loop. Otherwise EAGAIN results in `error.WouldBlock`.
442/// On Windows, if the application has a global event loop enabled, I/O Completion Ports are
443/// used to perform the I/O. `error.WouldBlock` is not possible on Windows.
444///
445/// This operation is non-atomic on the following systems:
446/// * Darwin
447/// * Windows
448/// On these systems, the read races with concurrent writes to the same file descriptor.
368pub fn preadv(fd: fd_t, iov: []const iovec, offset: u64) ReadError!usize {449pub fn preadv(fd: fd_t, iov: []const iovec, offset: u64) ReadError!usize {
369 if (comptime std.Target.current.isDarwin()) {450 if (comptime std.Target.current.isDarwin()) {
370 // Darwin does not have preadv but it does have pread.451 // Darwin does not have preadv but it does have pread.
...@@ -409,6 +490,28 @@ pub fn preadv(fd: fd_t, iov: []const iovec, offset: u64) ReadError!usize {...@@ -409,6 +490,28 @@ pub fn preadv(fd: fd_t, iov: []const iovec, offset: u64) ReadError!usize {
409 }490 }
410 }491 }
411 }492 }
493
494 if (builtin.os == .windows) {
495 // TODO batch these into parallel requests
496 var off: usize = 0;
497 var iov_i: usize = 0;
498 var inner_off: usize = 0;
499 while (true) {
500 const v = iov[iov_i];
501 const amt_read = try pread(fd, v.iov_base[inner_off .. v.iov_len - inner_off], offset + off);
502 off += amt_read;
503 inner_off += amt_read;
504 if (inner_off == v.len) {
505 iov_i += 1;
506 inner_off = 0;
507 if (iov_i == iov.len) {
508 return off;
509 }
510 }
511 if (amt_read == 0) return off; // EOF
512 } else unreachable; // TODO https://github.com/ziglang/zig/issues/707
513 }
514
412 while (true) {515 while (true) {
413 // TODO handle the case when iov_len is too large and get rid of this @intCast516 // TODO handle the case when iov_len is too large and get rid of this @intCast
414 const rc = system.preadv(fd, iov.ptr, @intCast(u32, iov.len), offset);517 const rc = system.preadv(fd, iov.ptr, @intCast(u32, iov.len), offset);
...@@ -451,11 +554,9 @@ pub const WriteError = error{...@@ -451,11 +554,9 @@ pub const WriteError = error{
451/// Write to a file descriptor. Keeps trying if it gets interrupted.554/// Write to a file descriptor. Keeps trying if it gets interrupted.
452/// If the application has a global event loop enabled, EAGAIN is handled555/// If the application has a global event loop enabled, EAGAIN is handled
453/// via the event loop. Otherwise EAGAIN results in error.WouldBlock.556/// via the event loop. Otherwise EAGAIN results in error.WouldBlock.
454/// TODO evented I/O integration is disabled until
455/// https://github.com/ziglang/zig/issues/3557 is solved.
456pub fn write(fd: fd_t, bytes: []const u8) WriteError!void {557pub fn write(fd: fd_t, bytes: []const u8) WriteError!void {
457 if (builtin.os == .windows) {558 if (builtin.os == .windows) {
458 return windows.WriteFile(fd, bytes);559 return windows.WriteFile(fd, bytes, null);
459 }560 }
460561
461 if (builtin.os == .wasi and !builtin.link_libc) {562 if (builtin.os == .wasi and !builtin.link_libc) {
...@@ -488,14 +589,12 @@ pub fn write(fd: fd_t, bytes: []const u8) WriteError!void {...@@ -488,14 +589,12 @@ pub fn write(fd: fd_t, bytes: []const u8) WriteError!void {
488 EINTR => continue,589 EINTR => continue,
489 EINVAL => unreachable,590 EINVAL => unreachable,
490 EFAULT => unreachable,591 EFAULT => unreachable,
491 // TODO https://github.com/ziglang/zig/issues/3557592 EAGAIN => if (std.event.Loop.instance) |loop| {
492 EAGAIN => return error.WouldBlock,593 loop.waitUntilFdWritable(fd);
493 //EAGAIN => if (std.event.Loop.instance) |loop| {594 continue;
494 // loop.waitUntilFdWritable(fd);595 } else {
495 // continue;596 return error.WouldBlock;
496 //} else {597 },
497 // return error.WouldBlock;
498 //},
499 EBADF => unreachable, // Always a race condition.598 EBADF => unreachable, // Always a race condition.
500 EDESTADDRREQ => unreachable, // `connect` was never called.599 EDESTADDRREQ => unreachable, // `connect` was never called.
501 EDQUOT => return error.DiskQuota,600 EDQUOT => return error.DiskQuota,
...@@ -540,8 +639,57 @@ pub fn writev(fd: fd_t, iov: []const iovec_const) WriteError!void {...@@ -540,8 +639,57 @@ pub fn writev(fd: fd_t, iov: []const iovec_const) WriteError!void {
540 }639 }
541}640}
542641
642/// Write to a file descriptor, with a position offset.
643///
644/// Retries when interrupted by a signal.
645///
646/// For POSIX systems, if the application has a global event loop enabled, EAGAIN is handled
647/// via the event loop. Otherwise EAGAIN results in `error.WouldBlock`.
648/// On Windows, if the application has a global event loop enabled, I/O Completion Ports are
649/// used to perform the I/O. `error.WouldBlock` is not possible on Windows.
650pub fn pwrite(fd: fd_t, bytes: []const u8, offset: u64) WriteError!void {
651 if (comptime std.Target.current.isWindows()) {
652 return windows.WriteFile(fd, bytes, offset);
653 }
654
655 while (true) {
656 const rc = system.pwrite(fd, bytes.ptr, bytes.len, offset);
657 switch (errno(rc)) {
658 0 => return,
659 EINTR => continue,
660 EINVAL => unreachable,
661 EFAULT => unreachable,
662 EAGAIN => if (std.event.Loop.instance) |loop| {
663 loop.waitUntilFdWritable(fd);
664 continue;
665 } else {
666 return error.WouldBlock;
667 },
668 EBADF => unreachable, // Always a race condition.
669 EDESTADDRREQ => unreachable, // `connect` was never called.
670 EDQUOT => return error.DiskQuota,
671 EFBIG => return error.FileTooBig,
672 EIO => return error.InputOutput,
673 ENOSPC => return error.NoSpaceLeft,
674 EPERM => return error.AccessDenied,
675 EPIPE => return error.BrokenPipe,
676 else => |err| return unexpectedErrno(err),
677 }
678 }
679}
680
543/// Write multiple buffers to a file descriptor, with a position offset.681/// Write multiple buffers to a file descriptor, with a position offset.
544/// Keeps trying if it gets interrupted.682///
683/// Retries when interrupted by a signal.
684///
685/// If the application has a global event loop enabled, EAGAIN is handled
686/// via the event loop. Otherwise EAGAIN results in `error.WouldBlock`.
687///
688/// This operation is non-atomic on the following systems:
689/// * Darwin
690/// * Windows
691/// On these systems, the write races with concurrent writes to the same file descriptor, and
692/// the file can be in a partially written state when an error occurs.
545pub fn pwritev(fd: fd_t, iov: []const iovec_const, offset: u64) WriteError!void {693pub fn pwritev(fd: fd_t, iov: []const iovec_const, offset: u64) WriteError!void {
546 if (comptime std.Target.current.isDarwin()) {694 if (comptime std.Target.current.isDarwin()) {
547 // Darwin does not have pwritev but it does have pwrite.695 // Darwin does not have pwritev but it does have pwrite.
...@@ -589,6 +737,15 @@ pub fn pwritev(fd: fd_t, iov: []const iovec_const, offset: u64) WriteError!void...@@ -589,6 +737,15 @@ pub fn pwritev(fd: fd_t, iov: []const iovec_const, offset: u64) WriteError!void
589 }737 }
590 }738 }
591739
740 if (comptime std.Target.current.isWindows()) {
741 var off = offset;
742 for (iov) |item| {
743 try pwrite(fd, item.iov_base[0..item.iov_len], off);
744 off += buf.len;
745 }
746 return;
747 }
748
592 while (true) {749 while (true) {
593 // TODO handle the case when iov_len is too large and get rid of this @intCast750 // TODO handle the case when iov_len is too large and get rid of this @intCast
594 const rc = system.pwritev(fd, iov.ptr, @intCast(u32, iov.len), offset);751 const rc = system.pwritev(fd, iov.ptr, @intCast(u32, iov.len), offset);
...@@ -694,7 +851,7 @@ pub fn openC(file_path: [*:0]const u8, flags: u32, perm: usize) OpenError!fd_t {...@@ -694,7 +851,7 @@ pub fn openC(file_path: [*:0]const u8, flags: u32, perm: usize) OpenError!fd_t {
694/// Open and possibly create a file. Keeps trying if it gets interrupted.851/// Open and possibly create a file. Keeps trying if it gets interrupted.
695/// `file_path` is relative to the open directory handle `dir_fd`.852/// `file_path` is relative to the open directory handle `dir_fd`.
696/// See also `openatC`.853/// See also `openatC`.
697pub fn openat(dir_fd: fd_t, file_path: []const u8, flags: u32, mode: usize) OpenError!fd_t {854pub fn openat(dir_fd: fd_t, file_path: []const u8, flags: u32, mode: mode_t) OpenError!fd_t {
698 const file_path_c = try toPosixPath(file_path);855 const file_path_c = try toPosixPath(file_path);
699 return openatC(dir_fd, &file_path_c, flags, mode);856 return openatC(dir_fd, &file_path_c, flags, mode);
700}857}
...@@ -702,7 +859,7 @@ pub fn openat(dir_fd: fd_t, file_path: []const u8, flags: u32, mode: usize) Open...@@ -702,7 +859,7 @@ pub fn openat(dir_fd: fd_t, file_path: []const u8, flags: u32, mode: usize) Open
702/// Open and possibly create a file. Keeps trying if it gets interrupted.859/// Open and possibly create a file. Keeps trying if it gets interrupted.
703/// `file_path` is relative to the open directory handle `dir_fd`.860/// `file_path` is relative to the open directory handle `dir_fd`.
704/// See also `openat`.861/// See also `openat`.
705pub fn openatC(dir_fd: fd_t, file_path: [*:0]const u8, flags: u32, mode: usize) OpenError!fd_t {862pub fn openatC(dir_fd: fd_t, file_path: [*:0]const u8, flags: u32, mode: mode_t) OpenError!fd_t {
706 while (true) {863 while (true) {
707 const rc = system.openat(dir_fd, file_path, flags, mode);864 const rc = system.openat(dir_fd, file_path, flags, mode);
708 switch (errno(rc)) {865 switch (errno(rc)) {
...@@ -2237,7 +2394,7 @@ pub const MMapError = error{...@@ -2237,7 +2394,7 @@ pub const MMapError = error{
2237} || UnexpectedError;2394} || UnexpectedError;
22382395
2239/// Map files or devices into memory.2396/// Map files or devices into memory.
2240/// `length` must be aligned to `mem.page_size`.2397/// `length` does not need to be aligned.
2241/// Use of a mapped region can result in these signals:2398/// Use of a mapped region can result in these signals:
2242/// * SIGSEGV - Attempted write into a region mapped as read-only.2399/// * SIGSEGV - Attempted write into a region mapped as read-only.
2243/// * SIGBUS - Attempted access to a portion of the buffer that does not correspond to the file2400/// * SIGBUS - Attempted access to a portion of the buffer that does not correspond to the file
...@@ -2372,6 +2529,22 @@ pub fn pipe() PipeError![2]fd_t {...@@ -2372,6 +2529,22 @@ pub fn pipe() PipeError![2]fd_t {
2372}2529}
23732530
2374pub fn pipe2(flags: u32) PipeError![2]fd_t {2531pub fn pipe2(flags: u32) PipeError![2]fd_t {
2532 if (comptime std.Target.current.isDarwin()) {
2533 var fds: [2]fd_t = try pipe();
2534 if (flags == 0) return fds;
2535 errdefer {
2536 close(fds[0]);
2537 close(fds[1]);
2538 }
2539 for (fds) |fd| switch (errno(system.fcntl(fd, F_SETFL, flags))) {
2540 0 => {},
2541 EINVAL => unreachable, // Invalid flags
2542 EBADF => unreachable, // Always a race condition
2543 else => |err| return unexpectedErrno(err),
2544 };
2545 return fds;
2546 }
2547
2375 var fds: [2]fd_t = undefined;2548 var fds: [2]fd_t = undefined;
2376 switch (errno(system.pipe2(&fds, flags))) {2549 switch (errno(system.pipe2(&fds, flags))) {
2377 0 => return fds,2550 0 => return fds,
...@@ -3328,3 +3501,31 @@ pub fn getrusage(who: i32) rusage {...@@ -3328,3 +3501,31 @@ pub fn getrusage(who: i32) rusage {
3328 else => unreachable,3501 else => unreachable,
3329 }3502 }
3330}3503}
3504
3505pub const TermiosGetError = error{NotATerminal} || UnexpectedError;
3506
3507pub fn tcgetattr(handle: fd_t) TermiosGetError!termios {
3508 var term: termios = undefined;
3509 switch (errno(system.tcgetattr(handle, &term))) {
3510 0 => return term,
3511 EBADF => unreachable,
3512 ENOTTY => return error.NotATerminal,
3513 else => |err| return unexpectedErrno(err),
3514 }
3515}
3516
3517pub const TermiosSetError = TermiosGetError || error{ProcessOrphaned};
3518
3519pub fn tcsetattr(handle: fd_t, optional_action: TCSA, termios_p: termios) TermiosSetError!void {
3520 while (true) {
3521 switch (errno(system.tcsetattr(handle, optional_action, &termios_p))) {
3522 0 => return,
3523 EBADF => unreachable,
3524 EINTR => continue,
3525 EINVAL => unreachable,
3526 ENOTTY => return error.NotATerminal,
3527 EIO => return error.ProcessOrphaned,
3528 else => |err| return unexpectedErrno(err),
3529 }
3530 }
3531}
lib/std/os/bits/darwin.zig+159
...@@ -4,6 +4,7 @@ const maxInt = std.math.maxInt;...@@ -4,6 +4,7 @@ const maxInt = std.math.maxInt;
44
5pub const fd_t = c_int;5pub const fd_t = c_int;
6pub const pid_t = c_int;6pub const pid_t = c_int;
7pub const mode_t = c_uint;
78
8pub const in_port_t = u16;9pub const in_port_t = u16;
9pub const sa_family_t = u8;10pub const sa_family_t = u8;
...@@ -1223,3 +1224,161 @@ pub const RTLD_NEXT = @intToPtr(*c_void, ~maxInt(usize));...@@ -1223,3 +1224,161 @@ pub const RTLD_NEXT = @intToPtr(*c_void, ~maxInt(usize));
1223pub const RTLD_DEFAULT = @intToPtr(*c_void, ~maxInt(usize) - 1);1224pub const RTLD_DEFAULT = @intToPtr(*c_void, ~maxInt(usize) - 1);
1224pub const RTLD_SELF = @intToPtr(*c_void, ~maxInt(usize) - 2);1225pub const RTLD_SELF = @intToPtr(*c_void, ~maxInt(usize) - 2);
1225pub const RTLD_MAIN_ONLY = @intToPtr(*c_void, ~maxInt(usize) - 4);1226pub const RTLD_MAIN_ONLY = @intToPtr(*c_void, ~maxInt(usize) - 4);
1227
1228/// duplicate file descriptor
1229pub const F_DUPFD = 0;
1230
1231/// get file descriptor flags
1232pub const F_GETFD = 1;
1233
1234/// set file descriptor flags
1235pub const F_SETFD = 2;
1236
1237/// get file status flags
1238pub const F_GETFL = 3;
1239
1240/// set file status flags
1241pub const F_SETFL = 4;
1242
1243/// get SIGIO/SIGURG proc/pgrp
1244pub const F_GETOWN = 5;
1245
1246/// set SIGIO/SIGURG proc/pgrp
1247pub const F_SETOWN = 6;
1248
1249/// get record locking information
1250pub const F_GETLK = 7;
1251
1252/// set record locking information
1253pub const F_SETLK = 8;
1254
1255/// F_SETLK; wait if blocked
1256pub const F_SETLKW = 9;
1257
1258/// F_SETLK; wait if blocked, return on timeout
1259pub const F_SETLKWTIMEOUT = 10;
1260pub const F_FLUSH_DATA = 40;
1261
1262/// Used for regression test
1263pub const F_CHKCLEAN = 41;
1264
1265/// Preallocate storage
1266pub const F_PREALLOCATE = 42;
1267
1268/// Truncate a file without zeroing space
1269pub const F_SETSIZE = 43;
1270
1271/// Issue an advisory read async with no copy to user
1272pub const F_RDADVISE = 44;
1273
1274/// turn read ahead off/on for this fd
1275pub const F_RDAHEAD = 45;
1276
1277/// turn data caching off/on for this fd
1278pub const F_NOCACHE = 48;
1279
1280/// file offset to device offset
1281pub const F_LOG2PHYS = 49;
1282
1283/// return the full path of the fd
1284pub const F_GETPATH = 50;
1285
1286/// fsync + ask the drive to flush to the media
1287pub const F_FULLFSYNC = 51;
1288
1289/// find which component (if any) is a package
1290pub const F_PATHPKG_CHECK = 52;
1291
1292/// "freeze" all fs operations
1293pub const F_FREEZE_FS = 53;
1294
1295/// "thaw" all fs operations
1296pub const F_THAW_FS = 54;
1297
1298/// turn data caching off/on (globally) for this file
1299pub const F_GLOBAL_NOCACHE = 55;
1300
1301/// add detached signatures
1302pub const F_ADDSIGS = 59;
1303
1304/// add signature from same file (used by dyld for shared libs)
1305pub const F_ADDFILESIGS = 61;
1306
1307/// used in conjunction with F_NOCACHE to indicate that DIRECT, synchonous writes
1308/// should not be used (i.e. its ok to temporaily create cached pages)
1309pub const F_NODIRECT = 62;
1310
1311///Get the protection class of a file from the EA, returns int
1312pub const F_GETPROTECTIONCLASS = 63;
1313
1314///Set the protection class of a file for the EA, requires int
1315pub const F_SETPROTECTIONCLASS = 64;
1316
1317///file offset to device offset, extended
1318pub const F_LOG2PHYS_EXT = 65;
1319
1320///get record locking information, per-process
1321pub const F_GETLKPID = 66;
1322
1323///Mark the file as being the backing store for another filesystem
1324pub const F_SETBACKINGSTORE = 70;
1325
1326///return the full path of the FD, but error in specific mtmd circumstances
1327pub const F_GETPATH_MTMINFO = 71;
1328
1329///Returns the code directory, with associated hashes, to the caller
1330pub const F_GETCODEDIR = 72;
1331
1332///No SIGPIPE generated on EPIPE
1333pub const F_SETNOSIGPIPE = 73;
1334
1335///Status of SIGPIPE for this fd
1336pub const F_GETNOSIGPIPE = 74;
1337
1338///For some cases, we need to rewrap the key for AKS/MKB
1339pub const F_TRANSCODEKEY = 75;
1340
1341///file being written to a by single writer... if throttling enabled, writes
1342///may be broken into smaller chunks with throttling in between
1343pub const F_SINGLE_WRITER = 76;
1344
1345///Get the protection version number for this filesystem
1346pub const F_GETPROTECTIONLEVEL = 77;
1347
1348///Add detached code signatures (used by dyld for shared libs)
1349pub const F_FINDSIGS = 78;
1350
1351///Add signature from same file, only if it is signed by Apple (used by dyld for simulator)
1352pub const F_ADDFILESIGS_FOR_DYLD_SIM = 83;
1353
1354///fsync + issue barrier to drive
1355pub const F_BARRIERFSYNC = 85;
1356
1357///Add signature from same file, return end offset in structure on success
1358pub const F_ADDFILESIGS_RETURN = 97;
1359
1360///Check if Library Validation allows this Mach-O file to be mapped into the calling process
1361pub const F_CHECK_LV = 98;
1362
1363///Deallocate a range of the file
1364pub const F_PUNCHHOLE = 99;
1365
1366///Trim an active file
1367pub const F_TRIM_ACTIVE_FILE = 100;
1368
1369pub const FCNTL_FS_SPECIFIC_BASE = 0x00010000;
1370
1371///mark the dup with FD_CLOEXEC
1372pub const F_DUPFD_CLOEXEC = 67;
1373
1374///close-on-exec flag
1375pub const FD_CLOEXEC = 1;
1376
1377/// shared or read lock
1378pub const F_RDLCK = 1;
1379
1380/// unlock
1381pub const F_UNLCK = 2;
1382
1383/// exclusive or write lock
1384pub const F_WRLCK = 3;
lib/std/os/bits/dragonfly.zig+1
...@@ -7,6 +7,7 @@ pub fn S_ISCHR(m: u32) bool {...@@ -7,6 +7,7 @@ pub fn S_ISCHR(m: u32) bool {
7pub const fd_t = c_int;7pub const fd_t = c_int;
8pub const pid_t = c_int;8pub const pid_t = c_int;
9pub const off_t = c_long;9pub const off_t = c_long;
10pub const mode_t = c_uint;
1011
11pub const ENOTSUP = EOPNOTSUPP;12pub const ENOTSUP = EOPNOTSUPP;
12pub const EWOULDBLOCK = EAGAIN;13pub const EWOULDBLOCK = EAGAIN;
lib/std/os/bits/freebsd.zig+1
...@@ -3,6 +3,7 @@ const maxInt = std.math.maxInt;...@@ -3,6 +3,7 @@ const maxInt = std.math.maxInt;
33
4pub const fd_t = c_int;4pub const fd_t = c_int;
5pub const pid_t = c_int;5pub const pid_t = c_int;
6pub const mode_t = c_uint;
67
7pub const socklen_t = u32;8pub const socklen_t = u32;
89
lib/std/os/bits/linux.zig+74
...@@ -1515,3 +1515,77 @@ pub const rusage = extern struct {...@@ -1515,3 +1515,77 @@ pub const rusage = extern struct {
1515 nivcsw: isize,1515 nivcsw: isize,
1516 __reserved: [16]isize = [1]isize{0} ** 16,1516 __reserved: [16]isize = [1]isize{0} ** 16,
1517};1517};
1518
1519pub const cc_t = u8;
1520pub const speed_t = u32;
1521pub const tcflag_t = u32;
1522
1523pub const NCCS = 32;
1524
1525pub const IGNBRK = 1;
1526pub const BRKINT = 2;
1527pub const IGNPAR = 4;
1528pub const PARMRK = 8;
1529pub const INPCK = 16;
1530pub const ISTRIP = 32;
1531pub const INLCR = 64;
1532pub const IGNCR = 128;
1533pub const ICRNL = 256;
1534pub const IUCLC = 512;
1535pub const IXON = 1024;
1536pub const IXANY = 2048;
1537pub const IXOFF = 4096;
1538pub const IMAXBEL = 8192;
1539pub const IUTF8 = 16384;
1540
1541pub const OPOST = 1;
1542pub const OLCUC = 2;
1543pub const ONLCR = 4;
1544pub const OCRNL = 8;
1545pub const ONOCR = 16;
1546pub const ONLRET = 32;
1547pub const OFILL = 64;
1548pub const OFDEL = 128;
1549pub const VTDLY = 16384;
1550pub const VT0 = 0;
1551pub const VT1 = 16384;
1552
1553pub const CSIZE = 48;
1554pub const CS5 = 0;
1555pub const CS6 = 16;
1556pub const CS7 = 32;
1557pub const CS8 = 48;
1558pub const CSTOPB = 64;
1559pub const CREAD = 128;
1560pub const PARENB = 256;
1561pub const PARODD = 512;
1562pub const HUPCL = 1024;
1563pub const CLOCAL = 2048;
1564
1565pub const ISIG = 1;
1566pub const ICANON = 2;
1567pub const ECHO = 8;
1568pub const ECHOE = 16;
1569pub const ECHOK = 32;
1570pub const ECHONL = 64;
1571pub const NOFLSH = 128;
1572pub const TOSTOP = 256;
1573pub const IEXTEN = 32768;
1574
1575pub const TCSA = extern enum(c_uint) {
1576 NOW,
1577 DRAIN,
1578 FLUSH,
1579 _,
1580};
1581
1582pub const termios = extern struct {
1583 iflag: tcflag_t,
1584 oflag: tcflag_t,
1585 cflag: tcflag_t,
1586 lflag: tcflag_t,
1587 line: cc_t,
1588 cc: [NCCS]cc_t,
1589 ispeed: speed_t,
1590 ospeed: speed_t,
1591};
lib/std/os/bits/linux/x86_64.zig+2
...@@ -12,6 +12,8 @@ const socklen_t = linux.socklen_t;...@@ -12,6 +12,8 @@ const socklen_t = linux.socklen_t;
12const iovec = linux.iovec;12const iovec = linux.iovec;
13const iovec_const = linux.iovec_const;13const iovec_const = linux.iovec_const;
1414
15pub const mode_t = usize;
16
15pub const SYS_read = 0;17pub const SYS_read = 0;
16pub const SYS_write = 1;18pub const SYS_write = 1;
17pub const SYS_open = 2;19pub const SYS_open = 2;
lib/std/os/bits/netbsd.zig+1
...@@ -3,6 +3,7 @@ const maxInt = std.math.maxInt;...@@ -3,6 +3,7 @@ const maxInt = std.math.maxInt;
33
4pub const fd_t = c_int;4pub const fd_t = c_int;
5pub const pid_t = c_int;5pub const pid_t = c_int;
6pub const mode_t = c_uint;
67
7/// Renamed from `kevent` to `Kevent` to avoid conflict with function name.8/// Renamed from `kevent` to `Kevent` to avoid conflict with function name.
8pub const Kevent = extern struct {9pub const Kevent = extern struct {
lib/std/os/bits/wasi.zig+1
...@@ -130,6 +130,7 @@ pub const EVENTTYPE_FD_WRITE: eventtype_t = 2;...@@ -130,6 +130,7 @@ pub const EVENTTYPE_FD_WRITE: eventtype_t = 2;
130pub const exitcode_t = u32;130pub const exitcode_t = u32;
131131
132pub const fd_t = u32;132pub const fd_t = u32;
133pub const mode_t = u32;
133134
134pub const fdflags_t = u16;135pub const fdflags_t = u16;
135pub const FDFLAG_APPEND: fdflags_t = 0x0001;136pub const FDFLAG_APPEND: fdflags_t = 0x0001;
lib/std/os/bits/windows.zig+1
...@@ -5,6 +5,7 @@ const ws2_32 = @import("../windows/ws2_32.zig");...@@ -5,6 +5,7 @@ const ws2_32 = @import("../windows/ws2_32.zig");
55
6pub const fd_t = HANDLE;6pub const fd_t = HANDLE;
7pub const pid_t = HANDLE;7pub const pid_t = HANDLE;
8pub const mode_t = u0;
89
9pub const PATH_MAX = 260;10pub const PATH_MAX = 260;
1011
lib/std/os/linux.zig+8
...@@ -1061,6 +1061,14 @@ pub fn getrusage(who: i32, usage: *rusage) usize {...@@ -1061,6 +1061,14 @@ pub fn getrusage(who: i32, usage: *rusage) usize {
1061 return syscall2(SYS_getrusage, @bitCast(usize, @as(isize, who)), @ptrToInt(usage));1061 return syscall2(SYS_getrusage, @bitCast(usize, @as(isize, who)), @ptrToInt(usage));
1062}1062}
10631063
1064pub fn tcgetattr(fd: fd_t, termios_p: *termios) usize {
1065 return syscall3(SYS_ioctl, @bitCast(usize, @as(isize, fd)), TCGETS, @ptrToInt(termios_p));
1066}
1067
1068pub fn tcsetattr(fd: fd_t, optional_action: TCSA, termios_p: *const termios) usize {
1069 return syscall3(SYS_ioctl, @bitCast(usize, @as(isize, fd)), TCSETS + @enumToInt(optional_action), @ptrToInt(termios_p));
1070}
1071
1064test "" {1072test "" {
1065 if (builtin.os == .linux) {1073 if (builtin.os == .linux) {
1066 _ = @import("linux/test.zig");1074 _ = @import("linux/test.zig");
lib/std/os/linux/i386.zig+10-4
...@@ -72,11 +72,17 @@ pub fn syscall6(...@@ -72,11 +72,17 @@ pub fn syscall6(
72 arg5: usize,72 arg5: usize,
73 arg6: usize,73 arg6: usize,
74) usize {74) usize {
75 // The 6th argument is passed via memory as we're out of registers if ebp is
76 // used as frame pointer. We push arg6 value on the stack before changing
77 // ebp or esp as the compiler may reference it as an offset relative to one
78 // of those two registers.
75 return asm volatile (79 return asm volatile (
76 \\ push %%ebp80 \\ push %[arg6]
77 \\ mov %[arg6], %%ebp81 \\ push %%ebp
78 \\ int $0x8082 \\ mov 4(%%esp), %%ebp
79 \\ pop %%ebp83 \\ int $0x80
84 \\ pop %%ebp
85 \\ add $4, %%esp
80 : [ret] "={eax}" (-> usize)86 : [ret] "={eax}" (-> usize)
81 : [number] "{eax}" (number),87 : [number] "{eax}" (number),
82 [arg1] "{ebx}" (arg1),88 [arg1] "{ebx}" (arg1),
lib/std/os/test.zig+98
...@@ -256,3 +256,101 @@ test "memfd_create" {...@@ -256,3 +256,101 @@ test "memfd_create" {
256 expect(bytes_read == 4);256 expect(bytes_read == 4);
257 expect(mem.eql(u8, buf[0..4], "test"));257 expect(mem.eql(u8, buf[0..4], "test"));
258}258}
259
260test "mmap" {
261 if (builtin.os == .windows)
262 return error.SkipZigTest;
263
264 // Simple mmap() call with non page-aligned size
265 {
266 const data = try os.mmap(
267 null,
268 1234,
269 os.PROT_READ | os.PROT_WRITE,
270 os.MAP_ANONYMOUS | os.MAP_PRIVATE,
271 -1,
272 0,
273 );
274 defer os.munmap(data);
275
276 testing.expectEqual(@as(usize, 1234), data.len);
277
278 // By definition the data returned by mmap is zero-filled
279 std.mem.set(u8, data[0 .. data.len - 1], 0x55);
280 testing.expect(mem.indexOfScalar(u8, data, 0).? == 1234 - 1);
281 }
282
283 const test_out_file = "os_tmp_test";
284 // Must be a multiple of 4096 so that the test works with mmap2
285 const alloc_size = 8 * 4096;
286
287 // Create a file used for testing mmap() calls with a file descriptor
288 {
289 const file = try fs.cwd().createFile(test_out_file, .{});
290 defer file.close();
291
292 var out_stream = file.outStream();
293 const stream = &out_stream.stream;
294
295 var i: u32 = 0;
296 while (i < alloc_size / @sizeOf(u32)) : (i += 1) {
297 try stream.writeIntNative(u32, i);
298 }
299 }
300
301 // Map the whole file
302 {
303 const file = try fs.cwd().createFile(test_out_file, .{
304 .read = true,
305 .truncate = false,
306 });
307 defer file.close();
308
309 const data = try os.mmap(
310 null,
311 alloc_size,
312 os.PROT_READ,
313 os.MAP_PRIVATE,
314 file.handle,
315 0,
316 );
317 defer os.munmap(data);
318
319 var mem_stream = io.SliceInStream.init(data);
320 const stream = &mem_stream.stream;
321
322 var i: u32 = 0;
323 while (i < alloc_size / @sizeOf(u32)) : (i += 1) {
324 testing.expectEqual(i, try stream.readIntNative(u32));
325 }
326 }
327
328 // Map the upper half of the file
329 {
330 const file = try fs.cwd().createFile(test_out_file, .{
331 .read = true,
332 .truncate = false,
333 });
334 defer file.close();
335
336 const data = try os.mmap(
337 null,
338 alloc_size,
339 os.PROT_READ,
340 os.MAP_PRIVATE,
341 file.handle,
342 alloc_size / 2,
343 );
344 defer os.munmap(data);
345
346 var mem_stream = io.SliceInStream.init(data);
347 const stream = &mem_stream.stream;
348
349 var i: u32 = alloc_size / 2 / @sizeOf(u32);
350 while (i < alloc_size / @sizeOf(u32)) : (i += 1) {
351 testing.expectEqual(i, try stream.readIntNative(u32));
352 }
353 }
354
355 try fs.cwd().deleteFile(test_out_file);
356}
lib/std/os/windows.zig+128-29
...@@ -344,24 +344,77 @@ pub fn FindClose(hFindFile: HANDLE) void {...@@ -344,24 +344,77 @@ pub fn FindClose(hFindFile: HANDLE) void {
344 assert(kernel32.FindClose(hFindFile) != 0);344 assert(kernel32.FindClose(hFindFile) != 0);
345}345}
346346
347pub const ReadFileError = error{Unexpected};347pub const ReadFileError = error{
348348 OperationAborted,
349pub fn ReadFile(in_hFile: HANDLE, buffer: []u8) ReadFileError!usize {349 BrokenPipe,
350 var index: usize = 0;350 Unexpected,
351 while (index < buffer.len) {351};
352 const want_read_count = @intCast(DWORD, math.min(@as(DWORD, maxInt(DWORD)), buffer.len - index));352
353 var amt_read: DWORD = undefined;353/// If buffer's length exceeds what a Windows DWORD integer can hold, it will be broken into
354 if (kernel32.ReadFile(in_hFile, buffer.ptr + index, want_read_count, &amt_read, null) == 0) {354/// multiple non-atomic reads.
355 switch (kernel32.GetLastError()) {355pub fn ReadFile(in_hFile: HANDLE, buffer: []u8, offset: ?u64) ReadFileError!usize {
356 .OPERATION_ABORTED => continue,356 if (std.event.Loop.instance) |loop| {
357 .BROKEN_PIPE => return index,357 // TODO support async ReadFile with no offset
358 else => |err| return unexpectedError(err),358 const off = offset.?;
359 var resume_node = std.event.Loop.ResumeNode.Basic{
360 .base = .{
361 .id = .Basic,
362 .handle = @frame(),
363 .overlapped = OVERLAPPED{
364 .Internal = 0,
365 .InternalHigh = 0,
366 .Offset = @truncate(u32, off),
367 .OffsetHigh = @truncate(u32, off >> 32),
368 .hEvent = null,
369 },
370 },
371 };
372 // TODO only call create io completion port once per fd
373 _ = windows.CreateIoCompletionPort(fd, loop.os_data.io_port, undefined, undefined) catch undefined;
374 loop.beginOneEvent();
375 suspend {
376 // TODO handle buffer bigger than DWORD can hold
377 _ = windows.kernel32.ReadFile(fd, buffer.ptr, @intCast(windows.DWORD, buffer.len), null, &resume_node.base.overlapped);
378 }
379 var bytes_transferred: windows.DWORD = undefined;
380 if (windows.kernel32.GetOverlappedResult(fd, &resume_node.base.overlapped, &bytes_transferred, windows.FALSE) == 0) {
381 switch (windows.kernel32.GetLastError()) {
382 .IO_PENDING => unreachable,
383 .OPERATION_ABORTED => return error.OperationAborted,
384 .BROKEN_PIPE => return error.BrokenPipe,
385 .HANDLE_EOF => return @as(usize, bytes_transferred),
386 else => |err| return windows.unexpectedError(err),
387 }
388 }
389 return @as(usize, bytes_transferred);
390 } else {
391 var index: usize = 0;
392 while (index < buffer.len) {
393 const want_read_count = @intCast(DWORD, math.min(@as(DWORD, maxInt(DWORD)), buffer.len - index));
394 var amt_read: DWORD = undefined;
395 var overlapped_data: OVERLAPPED = undefined;
396 const overlapped: ?*OVERLAPPED = if (offset) |off| blk: {
397 overlapped_data = .{
398 .Internal = 0,
399 .InternalHigh = 0,
400 .Offset = @truncate(u32, off + index),
401 .OffsetHigh = @truncate(u32, (off + index) >> 32),
402 .hEvent = null,
403 };
404 break :blk &overlapped_data;
405 } else null;
406 if (kernel32.ReadFile(in_hFile, buffer.ptr + index, want_read_count, &amt_read, overlapped) == 0) {
407 switch (kernel32.GetLastError()) {
408 .OPERATION_ABORTED => continue,
409 .BROKEN_PIPE => return index,
410 else => |err| return unexpectedError(err),
411 }
359 }412 }
413 if (amt_read == 0) return index;
414 index += amt_read;
360 }415 }
361 if (amt_read == 0) return index;416 return index;
362 index += amt_read;
363 }417 }
364 return index;
365}418}
366419
367pub const WriteFileError = error{420pub const WriteFileError = error{
...@@ -371,20 +424,66 @@ pub const WriteFileError = error{...@@ -371,20 +424,66 @@ pub const WriteFileError = error{
371 Unexpected,424 Unexpected,
372};425};
373426
374/// This function is for blocking file descriptors only. For non-blocking, see427pub fn WriteFile(handle: HANDLE, bytes: []const u8, offset: ?u64) WriteFileError!void {
375/// `WriteFileAsync`.428 if (std.event.Loop.instance) |loop| {
376pub fn WriteFile(handle: HANDLE, bytes: []const u8) WriteFileError!void {429 // TODO support async WriteFile with no offset
377 var bytes_written: DWORD = undefined;430 const off = offset.?;
378 // TODO replace this @intCast with a loop that writes all the bytes431 var resume_node = std.event.Loop.ResumeNode.Basic{
379 if (kernel32.WriteFile(handle, bytes.ptr, @intCast(u32, bytes.len), &bytes_written, null) == 0) {432 .base = .{
380 switch (kernel32.GetLastError()) {433 .id = .Basic,
381 .INVALID_USER_BUFFER => return error.SystemResources,434 .handle = @frame(),
382 .NOT_ENOUGH_MEMORY => return error.SystemResources,435 .overlapped = OVERLAPPED{
383 .OPERATION_ABORTED => return error.OperationAborted,436 .Internal = 0,
384 .NOT_ENOUGH_QUOTA => return error.SystemResources,437 .InternalHigh = 0,
385 .IO_PENDING => unreachable, // this function is for blocking files only438 .Offset = @truncate(u32, off),
386 .BROKEN_PIPE => return error.BrokenPipe,439 .OffsetHigh = @truncate(u32, off >> 32),
387 else => |err| return unexpectedError(err),440 .hEvent = null,
441 },
442 },
443 };
444 // TODO only call create io completion port once per fd
445 _ = CreateIoCompletionPort(fd, loop.os_data.io_port, undefined, undefined);
446 loop.beginOneEvent();
447 suspend {
448 // TODO replace this @intCast with a loop that writes all the bytes
449 _ = kernel32.WriteFile(fd, bytes.ptr, @intCast(windows.DWORD, bytes.len), null, &resume_node.base.overlapped);
450 }
451 var bytes_transferred: windows.DWORD = undefined;
452 if (kernel32.GetOverlappedResult(fd, &resume_node.base.overlapped, &bytes_transferred, FALSE) == 0) {
453 switch (kernel32.GetLastError()) {
454 .IO_PENDING => unreachable,
455 .INVALID_USER_BUFFER => return error.SystemResources,
456 .NOT_ENOUGH_MEMORY => return error.SystemResources,
457 .OPERATION_ABORTED => return error.OperationAborted,
458 .NOT_ENOUGH_QUOTA => return error.SystemResources,
459 .BROKEN_PIPE => return error.BrokenPipe,
460 else => |err| return windows.unexpectedError(err),
461 }
462 }
463 } else {
464 var bytes_written: DWORD = undefined;
465 var overlapped_data: OVERLAPPED = undefined;
466 const overlapped: ?*OVERLAPPED = if (offset) |off| blk: {
467 overlapped_data = .{
468 .Internal = 0,
469 .InternalHigh = 0,
470 .Offset = @truncate(u32, off),
471 .OffsetHigh = @truncate(u32, off >> 32),
472 .hEvent = null,
473 };
474 break :blk &overlapped_data;
475 } else null;
476 // TODO replace this @intCast with a loop that writes all the bytes
477 if (kernel32.WriteFile(handle, bytes.ptr, @intCast(u32, bytes.len), &bytes_written, overlapped) == 0) {
478 switch (kernel32.GetLastError()) {
479 .INVALID_USER_BUFFER => return error.SystemResources,
480 .NOT_ENOUGH_MEMORY => return error.SystemResources,
481 .OPERATION_ABORTED => return error.OperationAborted,
482 .NOT_ENOUGH_QUOTA => return error.SystemResources,
483 .IO_PENDING => unreachable, // this function is for blocking files only
484 .BROKEN_PIPE => return error.BrokenPipe,
485 else => |err| return unexpectedError(err),
486 }
388 }487 }
389 }488 }
390}489}
lib/std/rand.zig+26
...@@ -733,6 +733,32 @@ test "xoroshiro sequence" {...@@ -733,6 +733,32 @@ test "xoroshiro sequence" {
733 }733 }
734}734}
735735
736// Gimli
737//
738// CSPRNG
739pub const Gimli = struct {
740 random: Random,
741 state: std.crypto.gimli.State,
742
743 pub fn init(init_s: u64) Gimli {
744 var self = Gimli{
745 .random = Random{ .fillFn = fill },
746 .state = std.crypto.gimli.State{
747 .data = [_]u32{0} ** (std.crypto.gimli.State.BLOCKBYTES / 4),
748 },
749 };
750 self.state.data[0] = @truncate(u32, init_s >> 32);
751 self.state.data[1] = @truncate(u32, init_s);
752 return self;
753 }
754
755 fn fill(r: *Random, buf: []u8) void {
756 const self = @fieldParentPtr(Gimli, "random", r);
757
758 self.state.squeeze(buf);
759 }
760};
761
736// ISAAC64 - http://www.burtleburtle.net/bob/rand/isaacafa.html762// ISAAC64 - http://www.burtleburtle.net/bob/rand/isaacafa.html
737//763//
738// CSPRNG764// CSPRNG
lib/std/special/compiler_rt/clzsi2.zig+2-1
...@@ -45,8 +45,9 @@ fn __clzsi2_thumb1() callconv(.Naked) void {...@@ -45,8 +45,9 @@ fn __clzsi2_thumb1() callconv(.Naked) void {
45 \\ subs r0, r1, r045 \\ subs r0, r1, r0
46 \\ bx lr46 \\ bx lr
47 \\ .p2align 247 \\ .p2align 2
48 \\ // Number of bits set in the 0-15 range
48 \\ LUT:49 \\ LUT:
49 \\ .byte 4,3,2,2,1,1,1,1,0,0,0,0,0,0,0,050 \\ .byte 0,1,2,2,3,3,3,3,4,4,4,4,4,4,4,4
50 );51 );
5152
52 unreachable;53 unreachable;
lib/std/special/test_runner.zig+25-1
...@@ -2,6 +2,8 @@ const std = @import("std");...@@ -2,6 +2,8 @@ const std = @import("std");
2const io = std.io;2const io = std.io;
3const builtin = @import("builtin");3const builtin = @import("builtin");
44
5pub const io_mode: io.Mode = builtin.test_io_mode;
6
5pub fn main() anyerror!void {7pub fn main() anyerror!void {
6 const test_fn_list = builtin.test_functions;8 const test_fn_list = builtin.test_functions;
7 var ok_count: usize = 0;9 var ok_count: usize = 0;
...@@ -12,6 +14,11 @@ pub fn main() anyerror!void {...@@ -12,6 +14,11 @@ pub fn main() anyerror!void {
12 error.TimerUnsupported => @panic("timer unsupported"),14 error.TimerUnsupported => @panic("timer unsupported"),
13 };15 };
1416
17 var async_frame_buffer: []align(std.Target.stack_align) u8 = undefined;
18 // TODO this is on the next line (using `undefined` above) because otherwise zig incorrectly
19 // ignores the alignment of the slice.
20 async_frame_buffer = &[_]u8{};
21
15 for (test_fn_list) |test_fn, i| {22 for (test_fn_list) |test_fn, i| {
16 std.testing.base_allocator_instance.reset();23 std.testing.base_allocator_instance.reset();
1724
...@@ -21,7 +28,24 @@ pub fn main() anyerror!void {...@@ -21,7 +28,24 @@ pub fn main() anyerror!void {
21 if (progress.terminal == null) {28 if (progress.terminal == null) {
22 std.debug.warn("{}/{} {}...", .{ i + 1, test_fn_list.len, test_fn.name });29 std.debug.warn("{}/{} {}...", .{ i + 1, test_fn_list.len, test_fn.name });
23 }30 }
24 if (test_fn.func()) |_| {31 const result = if (test_fn.async_frame_size) |size| switch (io_mode) {
32 .evented => blk: {
33 if (async_frame_buffer.len < size) {
34 std.heap.page_allocator.free(async_frame_buffer);
35 async_frame_buffer = try std.heap.page_allocator.alignedAlloc(u8, std.Target.stack_align, size);
36 }
37 const casted_fn = @ptrCast(async fn () anyerror!void, test_fn.func);
38 break :blk await @asyncCall(async_frame_buffer, {}, casted_fn);
39 },
40 .blocking => {
41 skip_count += 1;
42 test_node.end();
43 progress.log("{}...SKIP (async test)\n", .{test_fn.name});
44 if (progress.terminal == null) std.debug.warn("SKIP (async test)\n", .{});
45 continue;
46 },
47 } else test_fn.func();
48 if (result) |_| {
25 ok_count += 1;49 ok_count += 1;
26 test_node.end();50 test_node.end();
27 std.testing.allocator_instance.validate() catch |err| switch (err) {51 std.testing.allocator_instance.validate() catch |err| switch (err) {
lib/std/start.zig+1-1
...@@ -21,7 +21,7 @@ comptime {...@@ -21,7 +21,7 @@ comptime {
21 @export(main, .{ .name = "main", .linkage = .Weak });21 @export(main, .{ .name = "main", .linkage = .Weak });
22 }22 }
23 } else if (builtin.os == .windows) {23 } else if (builtin.os == .windows) {
24 if (!@hasDecl(root, "WinMain") and !@hasDecl(root, "WinMainCRTStartup")) {24 if (!@hasDecl(root, "WinMain") and !@hasDecl(root, "WinMainCRTStartup") and !@hasDecl(root, "wWinMain") and !@hasDecl(root, "wWinMainCRTStartup")) {
25 @export(WinMainCRTStartup, .{ .name = "WinMainCRTStartup" });25 @export(WinMainCRTStartup, .{ .name = "WinMainCRTStartup" });
26 }26 }
27 } else if (builtin.os == .uefi) {27 } else if (builtin.os == .uefi) {
lib/std/target.zig+9
...@@ -242,6 +242,13 @@ pub const Target = union(enum) {...@@ -242,6 +242,13 @@ pub const Target = union(enum) {
242 };242 };
243 }243 }
244244
245 pub fn isRISCV(arch: Arch) bool {
246 return switch (arch) {
247 .riscv32, .riscv64 => true,
248 else => false,
249 };
250 }
251
245 pub fn isMIPS(arch: Arch) bool {252 pub fn isMIPS(arch: Arch) bool {
246 return switch (arch) {253 return switch (arch) {
247 .mips, .mipsel, .mips64, .mips64el => true,254 .mips, .mipsel, .mips64, .mips64el => true,
...@@ -598,6 +605,8 @@ pub const Target = union(enum) {...@@ -598,6 +605,8 @@ pub const Target = union(enum) {
598 }605 }
599606
600 pub fn populateDependencies(set: *Set, all_features_list: []const Cpu.Feature) void {607 pub fn populateDependencies(set: *Set, all_features_list: []const Cpu.Feature) void {
608 @setEvalBranchQuota(1000000);
609
601 var old = set.ints;610 var old = set.ints;
602 while (true) {611 while (true) {
603 for (all_features_list) |feature, index_usize| {612 for (all_features_list) |feature, index_usize| {
lib/std/unicode.zig+6-3
...@@ -571,8 +571,9 @@ pub fn utf8ToUtf16LeWithNull(allocator: *mem.Allocator, utf8: []const u8) ![:0]u...@@ -571,8 +571,9 @@ pub fn utf8ToUtf16LeWithNull(allocator: *mem.Allocator, utf8: []const u8) ![:0]u
571 }571 }
572 }572 }
573573
574 const len = result.len;
574 try result.append(0);575 try result.append(0);
575 return result.toOwnedSlice()[0..:0];576 return result.toOwnedSlice()[0..len :0];
576}577}
577578
578/// Returns index of next character. If exact fit, returned index equals output slice length.579/// Returns index of next character. If exact fit, returned index equals output slice length.
...@@ -619,12 +620,14 @@ test "utf8ToUtf16LeWithNull" {...@@ -619,12 +620,14 @@ test "utf8ToUtf16LeWithNull" {
619 var bytes: [128]u8 = undefined;620 var bytes: [128]u8 = undefined;
620 const allocator = &std.heap.FixedBufferAllocator.init(bytes[0..]).allocator;621 const allocator = &std.heap.FixedBufferAllocator.init(bytes[0..]).allocator;
621 const utf16 = try utf8ToUtf16LeWithNull(allocator, "𐐷");622 const utf16 = try utf8ToUtf16LeWithNull(allocator, "𐐷");
622 testing.expectEqualSlices(u8, "\x01\xd8\x37\xdc\x00\x00", @sliceToBytes(utf16[0..]));623 testing.expectEqualSlices(u8, "\x01\xd8\x37\xdc", @sliceToBytes(utf16[0..]));
624 testing.expect(utf16[2] == 0);
623 }625 }
624 {626 {
625 var bytes: [128]u8 = undefined;627 var bytes: [128]u8 = undefined;
626 const allocator = &std.heap.FixedBufferAllocator.init(bytes[0..]).allocator;628 const allocator = &std.heap.FixedBufferAllocator.init(bytes[0..]).allocator;
627 const utf16 = try utf8ToUtf16LeWithNull(allocator, "\u{10FFFF}");629 const utf16 = try utf8ToUtf16LeWithNull(allocator, "\u{10FFFF}");
628 testing.expectEqualSlices(u8, "\xff\xdb\xff\xdf\x00\x00", @sliceToBytes(utf16[0..]));630 testing.expectEqualSlices(u8, "\xff\xdb\xff\xdf", @sliceToBytes(utf16[0..]));
631 testing.expect(utf16[2] == 0);
629 }632 }
630}633}
src-self-hosted/c_tokenizer.zig deleted-977
...@@ -1,977 +0,0 @@
1const std = @import("std");
2const expect = std.testing.expect;
3const ZigClangSourceLocation = @import("clang.zig").ZigClangSourceLocation;
4const Context = @import("translate_c.zig").Context;
5const failDecl = @import("translate_c.zig").failDecl;
6
7pub const TokenList = std.SegmentedList(CToken, 32);
8
9pub const CToken = struct {
10 id: Id,
11 bytes: []const u8 = "",
12 num_lit_suffix: NumLitSuffix = .None,
13
14 pub const Id = enum {
15 CharLit,
16 StrLit,
17 NumLitInt,
18 NumLitFloat,
19 Identifier,
20 Plus,
21 Minus,
22 Slash,
23 LParen,
24 RParen,
25 Eof,
26 Dot,
27 Asterisk, // *
28 Ampersand, // &
29 And, // &&
30 Assign, // =
31 Or, // ||
32 Bang, // !
33 Tilde, // ~
34 Shl, // <<
35 Shr, // >>
36 Lt, // <
37 Lte, // <=
38 Gt, // >
39 Gte, // >=
40 Eq, // ==
41 Ne, // !=
42 Increment, // ++
43 Decrement, // --
44 Comma,
45 Fn,
46 Arrow, // ->
47 LBrace,
48 RBrace,
49 Pipe,
50 QuestionMark,
51 Colon,
52 };
53
54 pub const NumLitSuffix = enum {
55 None,
56 F,
57 L,
58 U,
59 LU,
60 LL,
61 LLU,
62 };
63};
64
65pub fn tokenizeCMacro(ctx: *Context, loc: ZigClangSourceLocation, name: []const u8, tl: *TokenList, chars: [*:0]const u8) !void {
66 var index: usize = 0;
67 var first = true;
68 while (true) {
69 const tok = try next(ctx, loc, name, chars, &index);
70 if (tok.id == .StrLit or tok.id == .CharLit)
71 try tl.push(try zigifyEscapeSequences(ctx, loc, name, tl.allocator, tok))
72 else
73 try tl.push(tok);
74 if (tok.id == .Eof)
75 return;
76 if (first) {
77 // distinguish NAME (EXPR) from NAME(ARGS)
78 first = false;
79 if (chars[index] == '(') {
80 try tl.push(.{
81 .id = .Fn,
82 .bytes = "",
83 });
84 }
85 }
86 }
87}
88
89fn zigifyEscapeSequences(ctx: *Context, loc: ZigClangSourceLocation, name: []const u8, allocator: *std.mem.Allocator, tok: CToken) !CToken {
90 for (tok.bytes) |c| {
91 if (c == '\\') {
92 break;
93 }
94 } else return tok;
95 var bytes = try allocator.alloc(u8, tok.bytes.len * 2);
96 var state: enum {
97 Start,
98 Escape,
99 Hex,
100 Octal,
101 } = .Start;
102 var i: usize = 0;
103 var count: u8 = 0;
104 var num: u8 = 0;
105 for (tok.bytes) |c| {
106 switch (state) {
107 .Escape => {
108 switch (c) {
109 'n', 'r', 't', '\\', '\'', '\"' => {
110 bytes[i] = c;
111 },
112 '0'...'7' => {
113 count += 1;
114 num += c - '0';
115 state = .Octal;
116 bytes[i] = 'x';
117 },
118 'x' => {
119 state = .Hex;
120 bytes[i] = 'x';
121 },
122 'a' => {
123 bytes[i] = 'x';
124 i += 1;
125 bytes[i] = '0';
126 i += 1;
127 bytes[i] = '7';
128 },
129 'b' => {
130 bytes[i] = 'x';
131 i += 1;
132 bytes[i] = '0';
133 i += 1;
134 bytes[i] = '8';
135 },
136 'f' => {
137 bytes[i] = 'x';
138 i += 1;
139 bytes[i] = '0';
140 i += 1;
141 bytes[i] = 'C';
142 },
143 'v' => {
144 bytes[i] = 'x';
145 i += 1;
146 bytes[i] = '0';
147 i += 1;
148 bytes[i] = 'B';
149 },
150 '?' => {
151 i -= 1;
152 bytes[i] = '?';
153 },
154 'u', 'U' => {
155 try failDecl(ctx, loc, name, "macro tokenizing failed: TODO unicode escape sequences", .{});
156 return error.TokenizingFailed;
157 },
158 else => {
159 try failDecl(ctx, loc, name, "macro tokenizing failed: unknown escape sequence", .{});
160 return error.TokenizingFailed;
161 },
162 }
163 i += 1;
164 if (state == .Escape)
165 state = .Start;
166 },
167 .Start => {
168 if (c == '\\') {
169 state = .Escape;
170 }
171 bytes[i] = c;
172 i += 1;
173 },
174 .Hex => {
175 switch (c) {
176 '0'...'9' => {
177 num = std.math.mul(u8, num, 16) catch {
178 try failDecl(ctx, loc, name, "macro tokenizing failed: hex literal overflowed", .{});
179 return error.TokenizingFailed;
180 };
181 num += c - '0';
182 },
183 'a'...'f' => {
184 num = std.math.mul(u8, num, 16) catch {
185 try failDecl(ctx, loc, name, "macro tokenizing failed: hex literal overflowed", .{});
186 return error.TokenizingFailed;
187 };
188 num += c - 'a' + 10;
189 },
190 'A'...'F' => {
191 num = std.math.mul(u8, num, 16) catch {
192 try failDecl(ctx, loc, name, "macro tokenizing failed: hex literal overflowed", .{});
193 return error.TokenizingFailed;
194 };
195 num += c - 'A' + 10;
196 },
197 else => {
198 i += std.fmt.formatIntBuf(bytes[i..], num, 16, false, std.fmt.FormatOptions{ .fill = '0', .width = 2 });
199 num = 0;
200 if (c == '\\')
201 state = .Escape
202 else
203 state = .Start;
204 bytes[i] = c;
205 i += 1;
206 },
207 }
208 },
209 .Octal => {
210 const accept_digit = switch (c) {
211 // The maximum length of a octal literal is 3 digits
212 '0'...'7' => count < 3,
213 else => false,
214 };
215
216 if (accept_digit) {
217 count += 1;
218 num = std.math.mul(u8, num, 8) catch {
219 try failDecl(ctx, loc, name, "macro tokenizing failed: octal literal overflowed", .{});
220 return error.TokenizingFailed;
221 };
222 num += c - '0';
223 } else {
224 i += std.fmt.formatIntBuf(bytes[i..], num, 16, false, std.fmt.FormatOptions{ .fill = '0', .width = 2 });
225 num = 0;
226 count = 0;
227 if (c == '\\')
228 state = .Escape
229 else
230 state = .Start;
231 bytes[i] = c;
232 i += 1;
233 }
234 },
235 }
236 }
237 if (state == .Hex or state == .Octal)
238 i += std.fmt.formatIntBuf(bytes[i..], num, 16, false, std.fmt.FormatOptions{ .fill = '0', .width = 2 });
239 return CToken{
240 .id = tok.id,
241 .bytes = bytes[0..i],
242 };
243}
244
245fn next(ctx: *Context, loc: ZigClangSourceLocation, name: []const u8, chars: [*:0]const u8, i: *usize) !CToken {
246 var state: enum {
247 Start,
248 SawLt,
249 SawGt,
250 SawPlus,
251 SawMinus,
252 SawAmpersand,
253 SawPipe,
254 SawBang,
255 SawEq,
256 CharLit,
257 OpenComment,
258 Comment,
259 CommentStar,
260 Backslash,
261 String,
262 Identifier,
263 Decimal,
264 Octal,
265 SawZero,
266 Hex,
267 Bin,
268 Float,
269 ExpSign,
270 FloatExp,
271 FloatExpFirst,
272 NumLitIntSuffixU,
273 NumLitIntSuffixL,
274 NumLitIntSuffixLL,
275 NumLitIntSuffixUL,
276 Done,
277 } = .Start;
278
279 var result = CToken{
280 .bytes = "",
281 .id = .Eof,
282 };
283 var begin_index: usize = 0;
284 var digits: u8 = 0;
285 var pre_escape = state;
286
287 while (true) {
288 const c = chars[i.*];
289 if (c == 0) {
290 switch (state) {
291 .Identifier,
292 .Decimal,
293 .Hex,
294 .Bin,
295 .Octal,
296 .SawZero,
297 .Float,
298 .FloatExp,
299 => {
300 result.bytes = chars[begin_index..i.*];
301 return result;
302 },
303 .Start,
304 .SawMinus,
305 .Done,
306 .NumLitIntSuffixU,
307 .NumLitIntSuffixL,
308 .NumLitIntSuffixUL,
309 .NumLitIntSuffixLL,
310 .SawLt,
311 .SawGt,
312 .SawPlus,
313 .SawAmpersand,
314 .SawPipe,
315 .SawBang,
316 .SawEq,
317 => {
318 return result;
319 },
320 .CharLit,
321 .OpenComment,
322 .Comment,
323 .CommentStar,
324 .Backslash,
325 .String,
326 .ExpSign,
327 .FloatExpFirst,
328 => {
329 try failDecl(ctx, loc, name, "macro tokenizing failed: unexpected EOF", .{});
330 return error.TokenizingFailed;
331 },
332 }
333 }
334 switch (state) {
335 .Start => {
336 switch (c) {
337 ' ', '\t', '\x0B', '\x0C' => {},
338 '\'' => {
339 state = .CharLit;
340 result.id = .CharLit;
341 begin_index = i.*;
342 },
343 '\"' => {
344 state = .String;
345 result.id = .StrLit;
346 begin_index = i.*;
347 },
348 '/' => {
349 state = .OpenComment;
350 },
351 '\\' => {
352 state = .Backslash;
353 },
354 '\n', '\r' => {
355 return result;
356 },
357 'a'...'z', 'A'...'Z', '_' => {
358 state = .Identifier;
359 result.id = .Identifier;
360 begin_index = i.*;
361 },
362 '1'...'9' => {
363 state = .Decimal;
364 result.id = .NumLitInt;
365 begin_index = i.*;
366 },
367 '0' => {
368 state = .SawZero;
369 result.id = .NumLitInt;
370 begin_index = i.*;
371 },
372 '.' => {
373 result.id = .Dot;
374 state = .Done;
375 },
376 '<' => {
377 result.id = .Lt;
378 state = .SawLt;
379 },
380 '>' => {
381 result.id = .Gt;
382 state = .SawGt;
383 },
384 '(' => {
385 result.id = .LParen;
386 state = .Done;
387 },
388 ')' => {
389 result.id = .RParen;
390 state = .Done;
391 },
392 '*' => {
393 result.id = .Asterisk;
394 state = .Done;
395 },
396 '+' => {
397 result.id = .Plus;
398 state = .SawPlus;
399 },
400 '-' => {
401 result.id = .Minus;
402 state = .SawMinus;
403 },
404 '!' => {
405 result.id = .Bang;
406 state = .SawBang;
407 },
408 '~' => {
409 result.id = .Tilde;
410 state = .Done;
411 },
412 '=' => {
413 result.id = .Assign;
414 state = .SawEq;
415 },
416 ',' => {
417 result.id = .Comma;
418 state = .Done;
419 },
420 '[' => {
421 result.id = .LBrace;
422 state = .Done;
423 },
424 ']' => {
425 result.id = .RBrace;
426 state = .Done;
427 },
428 '|' => {
429 result.id = .Pipe;
430 state = .SawPipe;
431 },
432 '&' => {
433 result.id = .Ampersand;
434 state = .SawAmpersand;
435 },
436 '?' => {
437 result.id = .QuestionMark;
438 state = .Done;
439 },
440 ':' => {
441 result.id = .Colon;
442 state = .Done;
443 },
444 else => {
445 try failDecl(ctx, loc, name, "macro tokenizing failed: unexpected character '{c}'", .{c});
446 return error.TokenizingFailed;
447 },
448 }
449 },
450 .Done => return result,
451 .SawMinus => {
452 switch (c) {
453 '>' => {
454 result.id = .Arrow;
455 state = .Done;
456 },
457 '-' => {
458 result.id = .Decrement;
459 state = .Done;
460 },
461 else => return result,
462 }
463 },
464 .SawPlus => {
465 switch (c) {
466 '+' => {
467 result.id = .Increment;
468 state = .Done;
469 },
470 else => return result,
471 }
472 },
473 .SawLt => {
474 switch (c) {
475 '<' => {
476 result.id = .Shl;
477 state = .Done;
478 },
479 '=' => {
480 result.id = .Lte;
481 state = .Done;
482 },
483 else => return result,
484 }
485 },
486 .SawGt => {
487 switch (c) {
488 '>' => {
489 result.id = .Shr;
490 state = .Done;
491 },
492 '=' => {
493 result.id = .Gte;
494 state = .Done;
495 },
496 else => return result,
497 }
498 },
499 .SawPipe => {
500 switch (c) {
501 '|' => {
502 result.id = .Or;
503 state = .Done;
504 },
505 else => return result,
506 }
507 },
508 .SawAmpersand => {
509 switch (c) {
510 '&' => {
511 result.id = .And;
512 state = .Done;
513 },
514 else => return result,
515 }
516 },
517 .SawBang => {
518 switch (c) {
519 '=' => {
520 result.id = .Ne;
521 state = .Done;
522 },
523 else => return result,
524 }
525 },
526 .SawEq => {
527 switch (c) {
528 '=' => {
529 result.id = .Eq;
530 state = .Done;
531 },
532 else => return result,
533 }
534 },
535 .Float => {
536 switch (c) {
537 '.', '0'...'9' => {},
538 'e', 'E' => {
539 state = .ExpSign;
540 },
541 'f',
542 'F',
543 => {
544 result.num_lit_suffix = .F;
545 result.bytes = chars[begin_index..i.*];
546 state = .Done;
547 },
548 'l', 'L' => {
549 result.num_lit_suffix = .L;
550 result.bytes = chars[begin_index..i.*];
551 state = .Done;
552 },
553 else => {
554 result.bytes = chars[begin_index..i.*];
555 return result;
556 },
557 }
558 },
559 .ExpSign => {
560 switch (c) {
561 '+', '-' => {
562 state = .FloatExpFirst;
563 },
564 '0'...'9' => {
565 state = .FloatExp;
566 },
567 else => {
568 try failDecl(ctx, loc, name, "macro tokenizing failed: expected a digit or '+' or '-'", .{});
569 return error.TokenizingFailed;
570 },
571 }
572 },
573 .FloatExpFirst => {
574 switch (c) {
575 '0'...'9' => {
576 state = .FloatExp;
577 },
578 else => {
579 try failDecl(ctx, loc, name, "macro tokenizing failed: expected a digit", .{});
580 return error.TokenizingFailed;
581 },
582 }
583 },
584 .FloatExp => {
585 switch (c) {
586 '0'...'9' => {},
587 'f', 'F' => {
588 result.num_lit_suffix = .F;
589 result.bytes = chars[begin_index..i.*];
590 state = .Done;
591 },
592 'l', 'L' => {
593 result.num_lit_suffix = .L;
594 result.bytes = chars[begin_index..i.*];
595 state = .Done;
596 },
597 else => {
598 result.bytes = chars[begin_index..i.*];
599 return result;
600 },
601 }
602 },
603 .Decimal => {
604 switch (c) {
605 '0'...'9' => {},
606 '\'' => {},
607 'u', 'U' => {
608 state = .NumLitIntSuffixU;
609 result.num_lit_suffix = .U;
610 result.bytes = chars[begin_index..i.*];
611 },
612 'l', 'L' => {
613 state = .NumLitIntSuffixL;
614 result.num_lit_suffix = .L;
615 result.bytes = chars[begin_index..i.*];
616 },
617 '.' => {
618 result.id = .NumLitFloat;
619 state = .Float;
620 },
621 else => {
622 result.bytes = chars[begin_index..i.*];
623 return result;
624 },
625 }
626 },
627 .SawZero => {
628 switch (c) {
629 'x', 'X' => {
630 state = .Hex;
631 },
632 'b', 'B' => {
633 state = .Bin;
634 },
635 '.' => {
636 state = .Float;
637 result.id = .NumLitFloat;
638 },
639 'u', 'U' => {
640 state = .NumLitIntSuffixU;
641 result.num_lit_suffix = .U;
642 result.bytes = chars[begin_index..i.*];
643 },
644 'l', 'L' => {
645 state = .NumLitIntSuffixL;
646 result.num_lit_suffix = .L;
647 result.bytes = chars[begin_index..i.*];
648 },
649 else => {
650 i.* -= 1;
651 state = .Octal;
652 },
653 }
654 },
655 .Octal => {
656 switch (c) {
657 '0'...'7' => {},
658 '8', '9' => {
659 try failDecl(ctx, loc, name, "macro tokenizing failed: invalid digit '{c}' in octal number", .{c});
660 return error.TokenizingFailed;
661 },
662 'u', 'U' => {
663 state = .NumLitIntSuffixU;
664 result.num_lit_suffix = .U;
665 result.bytes = chars[begin_index..i.*];
666 },
667 'l', 'L' => {
668 state = .NumLitIntSuffixL;
669 result.num_lit_suffix = .L;
670 result.bytes = chars[begin_index..i.*];
671 },
672 else => {
673 result.bytes = chars[begin_index..i.*];
674 return result;
675 },
676 }
677 },
678 .Hex => {
679 switch (c) {
680 '0'...'9', 'a'...'f', 'A'...'F' => {},
681 'u', 'U' => {
682 // marks the number literal as unsigned
683 state = .NumLitIntSuffixU;
684 result.num_lit_suffix = .U;
685 result.bytes = chars[begin_index..i.*];
686 },
687 'l', 'L' => {
688 // marks the number literal as long
689 state = .NumLitIntSuffixL;
690 result.num_lit_suffix = .L;
691 result.bytes = chars[begin_index..i.*];
692 },
693 else => {
694 result.bytes = chars[begin_index..i.*];
695 return result;
696 },
697 }
698 },
699 .Bin => {
700 switch (c) {
701 '0'...'1' => {},
702 '2'...'9' => {
703 try failDecl(ctx, loc, name, "macro tokenizing failed: invalid digit '{c}' in binary number", .{c});
704 return error.TokenizingFailed;
705 },
706 'u', 'U' => {
707 // marks the number literal as unsigned
708 state = .NumLitIntSuffixU;
709 result.num_lit_suffix = .U;
710 result.bytes = chars[begin_index..i.*];
711 },
712 'l', 'L' => {
713 // marks the number literal as long
714 state = .NumLitIntSuffixL;
715 result.num_lit_suffix = .L;
716 result.bytes = chars[begin_index..i.*];
717 },
718 else => {
719 result.bytes = chars[begin_index..i.*];
720 return result;
721 },
722 }
723 },
724 .NumLitIntSuffixU => {
725 switch (c) {
726 'l', 'L' => {
727 result.num_lit_suffix = .LU;
728 state = .NumLitIntSuffixUL;
729 },
730 else => {
731 return result;
732 },
733 }
734 },
735 .NumLitIntSuffixL => {
736 switch (c) {
737 'l', 'L' => {
738 result.num_lit_suffix = .LL;
739 state = .NumLitIntSuffixLL;
740 },
741 'u', 'U' => {
742 result.num_lit_suffix = .LU;
743 state = .Done;
744 },
745 else => {
746 return result;
747 },
748 }
749 },
750 .NumLitIntSuffixLL => {
751 switch (c) {
752 'u', 'U' => {
753 result.num_lit_suffix = .LLU;
754 state = .Done;
755 },
756 else => {
757 return result;
758 },
759 }
760 },
761 .NumLitIntSuffixUL => {
762 switch (c) {
763 'l', 'L' => {
764 result.num_lit_suffix = .LLU;
765 state = .Done;
766 },
767 else => {
768 return result;
769 },
770 }
771 },
772 .Identifier => {
773 switch (c) {
774 '_', 'a'...'z', 'A'...'Z', '0'...'9' => {},
775 else => {
776 result.bytes = chars[begin_index..i.*];
777 return result;
778 },
779 }
780 },
781 .String => {
782 switch (c) {
783 '\"' => {
784 result.bytes = chars[begin_index .. i.* + 1];
785 state = .Done;
786 },
787 else => {},
788 }
789 },
790 .CharLit => {
791 switch (c) {
792 '\'' => {
793 result.bytes = chars[begin_index .. i.* + 1];
794 state = .Done;
795 },
796 else => {},
797 }
798 },
799 .OpenComment => {
800 switch (c) {
801 '/' => {
802 return result;
803 },
804 '*' => {
805 state = .Comment;
806 },
807 else => {
808 result.id = .Slash;
809 state = .Done;
810 },
811 }
812 },
813 .Comment => {
814 switch (c) {
815 '*' => {
816 state = .CommentStar;
817 },
818 else => {},
819 }
820 },
821 .CommentStar => {
822 switch (c) {
823 '/' => {
824 state = .Start;
825 },
826 else => {
827 state = .Comment;
828 },
829 }
830 },
831 .Backslash => {
832 switch (c) {
833 ' ', '\t', '\x0B', '\x0C' => {},
834 '\n', '\r' => {
835 state = .Start;
836 },
837 else => {
838 try failDecl(ctx, loc, name, "macro tokenizing failed: expected whitespace", .{});
839 return error.TokenizingFailed;
840 },
841 }
842 },
843 }
844 i.* += 1;
845 }
846 unreachable;
847}
848
849fn expectTokens(tl: *TokenList, src: [*:0]const u8, expected: []CToken) void {
850 // these can be undefined since they are only used for error reporting
851 tokenizeCMacro(undefined, undefined, undefined, tl, src) catch unreachable;
852 var it = tl.iterator(0);
853 for (expected) |t| {
854 var tok = it.next().?;
855 std.testing.expectEqual(t.id, tok.id);
856 if (t.bytes.len > 0) {
857 //std.debug.warn(" {} = {}\n", .{tok.bytes, t.bytes});
858 std.testing.expectEqualSlices(u8, tok.bytes, t.bytes);
859 }
860 if (t.num_lit_suffix != .None) {
861 std.testing.expectEqual(t.num_lit_suffix, tok.num_lit_suffix);
862 }
863 }
864 std.testing.expect(it.next() == null);
865 tl.shrink(0);
866}
867
868test "tokenize macro" {
869 var tl = TokenList.init(std.testing.allocator);
870 defer tl.deinit();
871
872 expectTokens(&tl, "TEST(0\n", &[_]CToken{
873 .{ .id = .Identifier, .bytes = "TEST" },
874 .{ .id = .Fn },
875 .{ .id = .LParen },
876 .{ .id = .NumLitInt, .bytes = "0" },
877 .{ .id = .Eof },
878 });
879
880 expectTokens(&tl, "__FLT_MIN_10_EXP__ -37\n", &[_]CToken{
881 .{ .id = .Identifier, .bytes = "__FLT_MIN_10_EXP__" },
882 .{ .id = .Minus },
883 .{ .id = .NumLitInt, .bytes = "37" },
884 .{ .id = .Eof },
885 });
886
887 expectTokens(&tl, "__llvm__ 1\n#define", &[_]CToken{
888 .{ .id = .Identifier, .bytes = "__llvm__" },
889 .{ .id = .NumLitInt, .bytes = "1" },
890 .{ .id = .Eof },
891 });
892
893 expectTokens(&tl, "TEST 2", &[_]CToken{
894 .{ .id = .Identifier, .bytes = "TEST" },
895 .{ .id = .NumLitInt, .bytes = "2" },
896 .{ .id = .Eof },
897 });
898
899 expectTokens(&tl, "FOO 0ull", &[_]CToken{
900 .{ .id = .Identifier, .bytes = "FOO" },
901 .{ .id = .NumLitInt, .bytes = "0", .num_lit_suffix = .LLU },
902 .{ .id = .Eof },
903 });
904}
905
906test "tokenize macro ops" {
907 var tl = TokenList.init(std.testing.allocator);
908 defer tl.deinit();
909
910 expectTokens(&tl, "ADD A + B", &[_]CToken{
911 .{ .id = .Identifier, .bytes = "ADD" },
912 .{ .id = .Identifier, .bytes = "A" },
913 .{ .id = .Plus },
914 .{ .id = .Identifier, .bytes = "B" },
915 .{ .id = .Eof },
916 });
917
918 expectTokens(&tl, "ADD (A) + B", &[_]CToken{
919 .{ .id = .Identifier, .bytes = "ADD" },
920 .{ .id = .LParen },
921 .{ .id = .Identifier, .bytes = "A" },
922 .{ .id = .RParen },
923 .{ .id = .Plus },
924 .{ .id = .Identifier, .bytes = "B" },
925 .{ .id = .Eof },
926 });
927
928 expectTokens(&tl, "ADD (A) + B", &[_]CToken{
929 .{ .id = .Identifier, .bytes = "ADD" },
930 .{ .id = .LParen },
931 .{ .id = .Identifier, .bytes = "A" },
932 .{ .id = .RParen },
933 .{ .id = .Plus },
934 .{ .id = .Identifier, .bytes = "B" },
935 .{ .id = .Eof },
936 });
937}
938
939test "escape sequences" {
940 var buf: [1024]u8 = undefined;
941 var alloc = std.heap.FixedBufferAllocator.init(buf[0..]);
942 const a = &alloc.allocator;
943 // these can be undefined since they are only used for error reporting
944 expect(std.mem.eql(u8, (try zigifyEscapeSequences(undefined, undefined, undefined, a, .{
945 .id = .StrLit,
946 .bytes = "\\x0077",
947 })).bytes, "\\x77"));
948 expect(std.mem.eql(u8, (try zigifyEscapeSequences(undefined, undefined, undefined, a, .{
949 .id = .StrLit,
950 .bytes = "\\24500",
951 })).bytes, "\\xa500"));
952 expect(std.mem.eql(u8, (try zigifyEscapeSequences(undefined, undefined, undefined, a, .{
953 .id = .StrLit,
954 .bytes = "\\x0077 abc",
955 })).bytes, "\\x77 abc"));
956 expect(std.mem.eql(u8, (try zigifyEscapeSequences(undefined, undefined, undefined, a, .{
957 .id = .StrLit,
958 .bytes = "\\045abc",
959 })).bytes, "\\x25abc"));
960
961 expect(std.mem.eql(u8, (try zigifyEscapeSequences(undefined, undefined, undefined, a, .{
962 .id = .CharLit,
963 .bytes = "\\0",
964 })).bytes, "\\x00"));
965 expect(std.mem.eql(u8, (try zigifyEscapeSequences(undefined, undefined, undefined, a, .{
966 .id = .CharLit,
967 .bytes = "\\00",
968 })).bytes, "\\x00"));
969 expect(std.mem.eql(u8, (try zigifyEscapeSequences(undefined, undefined, undefined, a, .{
970 .id = .CharLit,
971 .bytes = "\\000\\001",
972 })).bytes, "\\x00\\x01"));
973 expect(std.mem.eql(u8, (try zigifyEscapeSequences(undefined, undefined, undefined, a, .{
974 .id = .CharLit,
975 .bytes = "\\000abc",
976 })).bytes, "\\x00abc"));
977}
src-self-hosted/compilation.zig+12-12
...@@ -29,7 +29,7 @@ const Package = @import("package.zig").Package;...@@ -29,7 +29,7 @@ const Package = @import("package.zig").Package;
29const link = @import("link.zig").link;29const link = @import("link.zig").link;
30const LibCInstallation = @import("libc_installation.zig").LibCInstallation;30const LibCInstallation = @import("libc_installation.zig").LibCInstallation;
31const CInt = @import("c_int.zig").CInt;31const CInt = @import("c_int.zig").CInt;
32const fs = event.fs;32const fs = std.fs;
33const util = @import("util.zig");33const util = @import("util.zig");
3434
35const max_src_size = 2 * 1024 * 1024 * 1024; // 2 GiB35const max_src_size = 2 * 1024 * 1024 * 1024; // 2 GiB
...@@ -442,7 +442,7 @@ pub const Compilation = struct {...@@ -442,7 +442,7 @@ pub const Compilation = struct {
442 comp.name = try Buffer.init(comp.arena(), name);442 comp.name = try Buffer.init(comp.arena(), name);
443 comp.llvm_triple = try util.getTriple(comp.arena(), target);443 comp.llvm_triple = try util.getTriple(comp.arena(), target);
444 comp.llvm_target = try util.llvmTargetFromTriple(comp.llvm_triple);444 comp.llvm_target = try util.llvmTargetFromTriple(comp.llvm_triple);
445 comp.zig_std_dir = try std.fs.path.join(comp.arena(), &[_][]const u8{ zig_lib_dir, "std" });445 comp.zig_std_dir = try fs.path.join(comp.arena(), &[_][]const u8{ zig_lib_dir, "std" });
446446
447 const opt_level = switch (build_mode) {447 const opt_level = switch (build_mode) {
448 .Debug => llvm.CodeGenLevelNone,448 .Debug => llvm.CodeGenLevelNone,
...@@ -485,8 +485,8 @@ pub const Compilation = struct {...@@ -485,8 +485,8 @@ pub const Compilation = struct {
485 defer comp.events.deinit();485 defer comp.events.deinit();
486486
487 if (root_src_path) |root_src| {487 if (root_src_path) |root_src| {
488 const dirname = std.fs.path.dirname(root_src) orelse ".";488 const dirname = fs.path.dirname(root_src) orelse ".";
489 const basename = std.fs.path.basename(root_src);489 const basename = fs.path.basename(root_src);
490490
491 comp.root_package = try Package.create(comp.arena(), dirname, basename);491 comp.root_package = try Package.create(comp.arena(), dirname, basename);
492 comp.std_package = try Package.create(comp.arena(), comp.zig_std_dir, "std.zig");492 comp.std_package = try Package.create(comp.arena(), comp.zig_std_dir, "std.zig");
...@@ -518,7 +518,7 @@ pub const Compilation = struct {...@@ -518,7 +518,7 @@ pub const Compilation = struct {
518 if (comp.tmp_dir.getOrNull()) |tmp_dir_result|518 if (comp.tmp_dir.getOrNull()) |tmp_dir_result|
519 if (tmp_dir_result.*) |tmp_dir| {519 if (tmp_dir_result.*) |tmp_dir| {
520 // TODO evented I/O?520 // TODO evented I/O?
521 std.fs.deleteTree(tmp_dir) catch {};521 fs.deleteTree(tmp_dir) catch {};
522 } else |_| {};522 } else |_| {};
523 }523 }
524524
...@@ -794,7 +794,7 @@ pub const Compilation = struct {...@@ -794,7 +794,7 @@ pub const Compilation = struct {
794794
795 async fn rebuildFile(self: *Compilation, root_scope: *Scope.Root) BuildError!void {795 async fn rebuildFile(self: *Compilation, root_scope: *Scope.Root) BuildError!void {
796 const tree_scope = blk: {796 const tree_scope = blk: {
797 const source_code = fs.readFile(797 const source_code = fs.cwd().readFileAlloc(
798 self.gpa(),798 self.gpa(),
799 root_scope.realpath,799 root_scope.realpath,
800 max_src_size,800 max_src_size,
...@@ -932,8 +932,8 @@ pub const Compilation = struct {...@@ -932,8 +932,8 @@ pub const Compilation = struct {
932 fn initialCompile(self: *Compilation) !void {932 fn initialCompile(self: *Compilation) !void {
933 if (self.root_src_path) |root_src_path| {933 if (self.root_src_path) |root_src_path| {
934 const root_scope = blk: {934 const root_scope = blk: {
935 // TODO async/await std.fs.realpath935 // TODO async/await fs.realpath
936 const root_src_real_path = std.fs.realpathAlloc(self.gpa(), root_src_path) catch |err| {936 const root_src_real_path = fs.realpathAlloc(self.gpa(), root_src_path) catch |err| {
937 try self.addCompileErrorCli(root_src_path, "unable to open: {}", .{@errorName(err)});937 try self.addCompileErrorCli(root_src_path, "unable to open: {}", .{@errorName(err)});
938 return;938 return;
939 };939 };
...@@ -1154,7 +1154,7 @@ pub const Compilation = struct {...@@ -1154,7 +1154,7 @@ pub const Compilation = struct {
1154 const file_name = try std.fmt.allocPrint(self.gpa(), "{}{}", .{ file_prefix[0..], suffix });1154 const file_name = try std.fmt.allocPrint(self.gpa(), "{}{}", .{ file_prefix[0..], suffix });
1155 defer self.gpa().free(file_name);1155 defer self.gpa().free(file_name);
11561156
1157 const full_path = try std.fs.path.join(self.gpa(), &[_][]const u8{ tmp_dir, file_name[0..] });1157 const full_path = try fs.path.join(self.gpa(), &[_][]const u8{ tmp_dir, file_name[0..] });
1158 errdefer self.gpa().free(full_path);1158 errdefer self.gpa().free(full_path);
11591159
1160 return Buffer.fromOwnedSlice(self.gpa(), full_path);1160 return Buffer.fromOwnedSlice(self.gpa(), full_path);
...@@ -1175,8 +1175,8 @@ pub const Compilation = struct {...@@ -1175,8 +1175,8 @@ pub const Compilation = struct {
1175 const zig_dir_path = try getZigDir(self.gpa());1175 const zig_dir_path = try getZigDir(self.gpa());
1176 defer self.gpa().free(zig_dir_path);1176 defer self.gpa().free(zig_dir_path);
11771177
1178 const tmp_dir = try std.fs.path.join(self.arena(), &[_][]const u8{ zig_dir_path, comp_dir_name[0..] });1178 const tmp_dir = try fs.path.join(self.arena(), &[_][]const u8{ zig_dir_path, comp_dir_name[0..] });
1179 try std.fs.makePath(self.gpa(), tmp_dir);1179 try fs.makePath(self.gpa(), tmp_dir);
1180 return tmp_dir;1180 return tmp_dir;
1181 }1181 }
11821182
...@@ -1348,7 +1348,7 @@ async fn addFnToLinkSet(comp: *Compilation, fn_val: *Value.Fn) Compilation.Build...@@ -1348,7 +1348,7 @@ async fn addFnToLinkSet(comp: *Compilation, fn_val: *Value.Fn) Compilation.Build
1348}1348}
13491349
1350fn getZigDir(allocator: *mem.Allocator) ![]u8 {1350fn getZigDir(allocator: *mem.Allocator) ![]u8 {
1351 return std.fs.getAppDataDir(allocator, "zig");1351 return fs.getAppDataDir(allocator, "zig");
1352}1352}
13531353
1354fn analyzeFnType(1354fn analyzeFnType(
src-self-hosted/dep_tokenizer.zig+10-23
...@@ -998,7 +998,8 @@ fn printCharValues(out: var, bytes: []const u8) !void {...@@ -998,7 +998,8 @@ fn printCharValues(out: var, bytes: []const u8) !void {
998998
999fn printUnderstandableChar(out: var, char: u8) !void {999fn printUnderstandableChar(out: var, char: u8) !void {
1000 if (!std.ascii.isPrint(char) or char == ' ') {1000 if (!std.ascii.isPrint(char) or char == ' ') {
1001 std.fmt.format(out.context, anyerror, out.output, "\\x{X:2}", .{char}) catch {};1001 const output = @typeInfo(@TypeOf(out)).Pointer.child.output;
1002 std.fmt.format(out.context, anyerror, output, "\\x{X:2}", .{char}) catch {};
1002 } else {1003 } else {
1003 try out.write("'");1004 try out.write("'");
1004 try out.write(&[_]u8{printable_char_tab[char]});1005 try out.write(&[_]u8{printable_char_tab[char]});
...@@ -1021,34 +1022,20 @@ comptime {...@@ -1021,34 +1022,20 @@ comptime {
1021// output: must be a function that takes a `self` idiom parameter1022// output: must be a function that takes a `self` idiom parameter
1022// and a bytes parameter1023// and a bytes parameter
1023// context: must be that self1024// context: must be that self
1024fn makeOutput(output: var, context: var) Output(@TypeOf(output)) {1025fn makeOutput(comptime output: var, context: var) Output(output, @TypeOf(context)) {
1025 return Output(@TypeOf(output)){1026 return Output(output, @TypeOf(context)){
1026 .output = output,
1027 .context = context,1027 .context = context,
1028 };1028 };
1029}1029}
10301030
1031fn Output(comptime T: type) type {1031fn Output(comptime output_func: var, comptime Context: type) type {
1032 const args = switch (@typeInfo(T)) {
1033 .Fn => |f| f.args,
1034 else => @compileError("output parameter is not a function"),
1035 };
1036 if (args.len != 2) {
1037 @compileError("output function must take 2 arguments");
1038 }
1039 const at0 = args[0].arg_type orelse @compileError("output arg[0] does not have a type");
1040 const at1 = args[1].arg_type orelse @compileError("output arg[1] does not have a type");
1041 const arg1p = switch (@typeInfo(at1)) {
1042 .Pointer => |p| p,
1043 else => @compileError("output arg[1] is not a slice"),
1044 };
1045 if (arg1p.child != u8) @compileError("output arg[1] is not a u8 slice");
1046 return struct {1032 return struct {
1047 output: T,1033 context: Context,
1048 context: at0,1034
1035 pub const output = output_func;
10491036
1050 fn write(self: *@This(), bytes: []const u8) !void {1037 fn write(self: @This(), bytes: []const u8) !void {
1051 try self.output(self.context, bytes);1038 try output_func(self.context, bytes);
1052 }1039 }
1053 };1040 };
1054}1041}
src-self-hosted/introspect.zig+1-1
...@@ -14,7 +14,7 @@ pub fn testZigInstallPrefix(allocator: *mem.Allocator, test_path: []const u8) ![...@@ -14,7 +14,7 @@ pub fn testZigInstallPrefix(allocator: *mem.Allocator, test_path: []const u8) ![
14 const test_index_file = try fs.path.join(allocator, &[_][]const u8{ test_zig_dir, "std", "std.zig" });14 const test_index_file = try fs.path.join(allocator, &[_][]const u8{ test_zig_dir, "std", "std.zig" });
15 defer allocator.free(test_index_file);15 defer allocator.free(test_index_file);
1616
17 var file = try fs.File.openRead(test_index_file);17 var file = try fs.cwd().openRead(test_index_file);
18 file.close();18 file.close();
1919
20 return test_zig_dir;20 return test_zig_dir;
src-self-hosted/main.zig+1-1
...@@ -724,7 +724,7 @@ async fn fmtPath(fmt: *Fmt, file_path_ref: []const u8, check_mode: bool) FmtErro...@@ -724,7 +724,7 @@ async fn fmtPath(fmt: *Fmt, file_path_ref: []const u8, check_mode: bool) FmtErro
724 if (try held.value.put(file_path, {})) |_| return;724 if (try held.value.put(file_path, {})) |_| return;
725 }725 }
726726
727 const source_code = event.fs.readFile(727 const source_code = fs.cwd().readFileAlloc(
728 fmt.allocator,728 fmt.allocator,
729 file_path,729 file_path,
730 max_src_size,730 max_src_size,
src-self-hosted/translate_c.zig+320-98
...@@ -6,8 +6,9 @@ const assert = std.debug.assert;...@@ -6,8 +6,9 @@ const assert = std.debug.assert;
6const ast = std.zig.ast;6const ast = std.zig.ast;
7const Token = std.zig.Token;7const Token = std.zig.Token;
8usingnamespace @import("clang.zig");8usingnamespace @import("clang.zig");
9const ctok = @import("c_tokenizer.zig");9const ctok = std.c.tokenizer;
10const CToken = ctok.CToken;10const CToken = std.c.Token;
11const CTokenList = std.c.tokenizer.Source.TokenList;
11const mem = std.mem;12const mem = std.mem;
12const math = std.math;13const math = std.math;
1314
...@@ -4811,6 +4812,15 @@ fn transCreateNodeIdentifier(c: *Context, name: []const u8) !*ast.Node {...@@ -4811,6 +4812,15 @@ fn transCreateNodeIdentifier(c: *Context, name: []const u8) !*ast.Node {
4811 return &identifier.base;4812 return &identifier.base;
4812}4813}
48134814
4815fn transCreateNodeTypeIdentifier(c: *Context, name: []const u8) !*ast.Node {
4816 const token_index = try appendTokenFmt(c, .Identifier, "{}", .{name});
4817 const identifier = try c.a().create(ast.Node.Identifier);
4818 identifier.* = .{
4819 .token = token_index,
4820 };
4821 return &identifier.base;
4822}
4823
4814pub fn freeErrors(errors: []ClangErrMsg) void {4824pub fn freeErrors(errors: []ClangErrMsg) void {
4815 ZigClangErrorMsg_delete(errors.ptr, errors.len);4825 ZigClangErrorMsg_delete(errors.ptr, errors.len);
4816}4826}
...@@ -4819,7 +4829,7 @@ fn transPreprocessorEntities(c: *Context, unit: *ZigClangASTUnit) Error!void {...@@ -4819,7 +4829,7 @@ fn transPreprocessorEntities(c: *Context, unit: *ZigClangASTUnit) Error!void {
4819 // TODO if we see #undef, delete it from the table4829 // TODO if we see #undef, delete it from the table
4820 var it = ZigClangASTUnit_getLocalPreprocessingEntities_begin(unit);4830 var it = ZigClangASTUnit_getLocalPreprocessingEntities_begin(unit);
4821 const it_end = ZigClangASTUnit_getLocalPreprocessingEntities_end(unit);4831 const it_end = ZigClangASTUnit_getLocalPreprocessingEntities_end(unit);
4822 var tok_list = ctok.TokenList.init(c.a());4832 var tok_list = CTokenList.init(c.a());
4823 const scope = c.global_scope;4833 const scope = c.global_scope;
48244834
4825 while (it.I != it_end.I) : (it.I += 1) {4835 while (it.I != it_end.I) : (it.I += 1) {
...@@ -4840,42 +4850,59 @@ fn transPreprocessorEntities(c: *Context, unit: *ZigClangASTUnit) Error!void {...@@ -4840,42 +4850,59 @@ fn transPreprocessorEntities(c: *Context, unit: *ZigClangASTUnit) Error!void {
4840 }4850 }
48414851
4842 const begin_c = ZigClangSourceManager_getCharacterData(c.source_manager, begin_loc);4852 const begin_c = ZigClangSourceManager_getCharacterData(c.source_manager, begin_loc);
4843 ctok.tokenizeCMacro(c, begin_loc, mangled_name, &tok_list, begin_c) catch |err| switch (err) {4853 const slice = begin_c[0..mem.len(u8, begin_c)];
4844 error.OutOfMemory => |e| return e,4854
4845 else => {4855 tok_list.shrink(0);
4846 continue;4856 var tokenizer = std.c.Tokenizer{
4857 .source = &std.c.tokenizer.Source{
4858 .buffer = slice,
4859 .file_name = undefined,
4860 .tokens = undefined,
4847 },4861 },
4848 };4862 };
4863 while (true) {
4864 const tok = tokenizer.next();
4865 switch (tok.id) {
4866 .Nl, .Eof => {
4867 try tok_list.push(tok);
4868 break;
4869 },
4870 .LineComment, .MultiLineComment => continue,
4871 else => {},
4872 }
4873 try tok_list.push(tok);
4874 }
48494875
4850 var tok_it = tok_list.iterator(0);4876 var tok_it = tok_list.iterator(0);
4851 const first_tok = tok_it.next().?;4877 const first_tok = tok_it.next().?;
4852 assert(first_tok.id == .Identifier and mem.eql(u8, first_tok.bytes, name));4878 assert(first_tok.id == .Identifier and mem.eql(u8, slice[first_tok.start..first_tok.end], name));
4879
4880 var macro_fn = false;
4853 const next = tok_it.peek().?;4881 const next = tok_it.peek().?;
4854 switch (next.id) {4882 switch (next.id) {
4855 .Identifier => {4883 .Identifier => {
4856 // if it equals itself, ignore. for example, from stdio.h:4884 // if it equals itself, ignore. for example, from stdio.h:
4857 // #define stdin stdin4885 // #define stdin stdin
4858 if (mem.eql(u8, name, next.bytes)) {4886 if (mem.eql(u8, name, slice[next.start..next.end])) {
4859 continue;4887 continue;
4860 }4888 }
4861 },4889 },
4862 .Eof => {4890 .Nl, .Eof => {
4863 // this means it is a macro without a value4891 // this means it is a macro without a value
4864 // we don't care about such things4892 // we don't care about such things
4865 continue;4893 continue;
4866 },4894 },
4895 .LParen => {
4896 // if the name is immediately followed by a '(' then it is a function
4897 macro_fn = first_tok.end == next.start;
4898 },
4867 else => {},4899 else => {},
4868 }4900 }
48694901
4870 const macro_fn = if (tok_it.peek().?.id == .Fn) blk: {
4871 _ = tok_it.next();
4872 break :blk true;
4873 } else false;
4874
4875 (if (macro_fn)4902 (if (macro_fn)
4876 transMacroFnDefine(c, &tok_it, mangled_name, begin_loc)4903 transMacroFnDefine(c, &tok_it, slice, mangled_name, begin_loc)
4877 else4904 else
4878 transMacroDefine(c, &tok_it, mangled_name, begin_loc)) catch |err| switch (err) {4905 transMacroDefine(c, &tok_it, slice, mangled_name, begin_loc)) catch |err| switch (err) {
4879 error.ParseError => continue,4906 error.ParseError => continue,
4880 error.OutOfMemory => |e| return e,4907 error.OutOfMemory => |e| return e,
4881 };4908 };
...@@ -4885,15 +4912,15 @@ fn transPreprocessorEntities(c: *Context, unit: *ZigClangASTUnit) Error!void {...@@ -4885,15 +4912,15 @@ fn transPreprocessorEntities(c: *Context, unit: *ZigClangASTUnit) Error!void {
4885 }4912 }
4886}4913}
48874914
4888fn transMacroDefine(c: *Context, it: *ctok.TokenList.Iterator, name: []const u8, source_loc: ZigClangSourceLocation) ParseError!void {4915fn transMacroDefine(c: *Context, it: *CTokenList.Iterator, source: []const u8, name: []const u8, source_loc: ZigClangSourceLocation) ParseError!void {
4889 const scope = &c.global_scope.base;4916 const scope = &c.global_scope.base;
48904917
4891 const node = try transCreateNodeVarDecl(c, true, true, name);4918 const node = try transCreateNodeVarDecl(c, true, true, name);
4892 node.eq_token = try appendToken(c, .Equal, "=");4919 node.eq_token = try appendToken(c, .Equal, "=");
48934920
4894 node.init_node = try parseCExpr(c, it, source_loc, scope);4921 node.init_node = try parseCExpr(c, it, source, source_loc, scope);
4895 const last = it.next().?;4922 const last = it.next().?;
4896 if (last.id != .Eof)4923 if (last.id != .Eof and last.id != .Nl)
4897 return failDecl(4924 return failDecl(
4898 c,4925 c,
4899 source_loc,4926 source_loc,
...@@ -4906,7 +4933,7 @@ fn transMacroDefine(c: *Context, it: *ctok.TokenList.Iterator, name: []const u8,...@@ -4906,7 +4933,7 @@ fn transMacroDefine(c: *Context, it: *ctok.TokenList.Iterator, name: []const u8,
4906 _ = try c.global_scope.macro_table.put(name, &node.base);4933 _ = try c.global_scope.macro_table.put(name, &node.base);
4907}4934}
49084935
4909fn transMacroFnDefine(c: *Context, it: *ctok.TokenList.Iterator, name: []const u8, source_loc: ZigClangSourceLocation) ParseError!void {4936fn transMacroFnDefine(c: *Context, it: *CTokenList.Iterator, source: []const u8, name: []const u8, source_loc: ZigClangSourceLocation) ParseError!void {
4910 const block_scope = try Scope.Block.init(c, &c.global_scope.base, null);4937 const block_scope = try Scope.Block.init(c, &c.global_scope.base, null);
4911 const scope = &block_scope.base;4938 const scope = &block_scope.base;
49124939
...@@ -4938,7 +4965,7 @@ fn transMacroFnDefine(c: *Context, it: *ctok.TokenList.Iterator, name: []const u...@@ -4938,7 +4965,7 @@ fn transMacroFnDefine(c: *Context, it: *ctok.TokenList.Iterator, name: []const u
4938 );4965 );
4939 }4966 }
49404967
4941 const mangled_name = try block_scope.makeMangledName(c, param_tok.bytes);4968 const mangled_name = try block_scope.makeMangledName(c, source[param_tok.start..param_tok.end]);
4942 const param_name_tok = try appendIdentifier(c, mangled_name);4969 const param_name_tok = try appendIdentifier(c, mangled_name);
4943 _ = try appendToken(c, .Colon, ":");4970 _ = try appendToken(c, .Colon, ":");
49444971
...@@ -5001,9 +5028,9 @@ fn transMacroFnDefine(c: *Context, it: *ctok.TokenList.Iterator, name: []const u...@@ -5001,9 +5028,9 @@ fn transMacroFnDefine(c: *Context, it: *ctok.TokenList.Iterator, name: []const u
5001 const block = try transCreateNodeBlock(c, null);5028 const block = try transCreateNodeBlock(c, null);
50025029
5003 const return_expr = try transCreateNodeReturnExpr(c);5030 const return_expr = try transCreateNodeReturnExpr(c);
5004 const expr = try parseCExpr(c, it, source_loc, scope);5031 const expr = try parseCExpr(c, it, source, source_loc, scope);
5005 const last = it.next().?;5032 const last = it.next().?;
5006 if (last.id != .Eof)5033 if (last.id != .Eof and last.id != .Nl)
5007 return failDecl(5034 return failDecl(
5008 c,5035 c,
5009 source_loc,5036 source_loc,
...@@ -5023,27 +5050,28 @@ fn transMacroFnDefine(c: *Context, it: *ctok.TokenList.Iterator, name: []const u...@@ -5023,27 +5050,28 @@ fn transMacroFnDefine(c: *Context, it: *ctok.TokenList.Iterator, name: []const u
50235050
5024const ParseError = Error || error{ParseError};5051const ParseError = Error || error{ParseError};
50255052
5026fn parseCExpr(c: *Context, it: *ctok.TokenList.Iterator, source_loc: ZigClangSourceLocation, scope: *Scope) ParseError!*ast.Node {5053fn parseCExpr(c: *Context, it: *CTokenList.Iterator, source: []const u8, source_loc: ZigClangSourceLocation, scope: *Scope) ParseError!*ast.Node {
5027 const node = try parseCPrefixOpExpr(c, it, source_loc, scope);5054 const node = try parseCPrefixOpExpr(c, it, source, source_loc, scope);
5028 switch (it.next().?.id) {5055 switch (it.next().?.id) {
5029 .QuestionMark => {5056 .QuestionMark => {
5030 // must come immediately after expr5057 // must come immediately after expr
5031 _ = try appendToken(c, .RParen, ")");5058 _ = try appendToken(c, .RParen, ")");
5032 const if_node = try transCreateNodeIf(c);5059 const if_node = try transCreateNodeIf(c);
5033 if_node.condition = node;5060 if_node.condition = node;
5034 if_node.body = try parseCPrimaryExpr(c, it, source_loc, scope);5061 if_node.body = try parseCPrimaryExpr(c, it, source, source_loc, scope);
5035 if (it.next().?.id != .Colon) {5062 if (it.next().?.id != .Colon) {
5063 const first_tok = it.list.at(0);
5036 try failDecl(5064 try failDecl(
5037 c,5065 c,
5038 source_loc,5066 source_loc,
5039 it.list.at(0).*.bytes,5067 source[first_tok.start..first_tok.end],
5040 "unable to translate C expr: expected ':'",5068 "unable to translate C expr: expected ':'",
5041 .{},5069 .{},
5042 );5070 );
5043 return error.ParseError;5071 return error.ParseError;
5044 }5072 }
5045 if_node.@"else" = try transCreateNodeElse(c);5073 if_node.@"else" = try transCreateNodeElse(c);
5046 if_node.@"else".?.body = try parseCPrimaryExpr(c, it, source_loc, scope);5074 if_node.@"else".?.body = try parseCPrimaryExpr(c, it, source, source_loc, scope);
5047 return &if_node.base;5075 return &if_node.base;
5048 },5076 },
5049 else => {5077 else => {
...@@ -5053,30 +5081,30 @@ fn parseCExpr(c: *Context, it: *ctok.TokenList.Iterator, source_loc: ZigClangSou...@@ -5053,30 +5081,30 @@ fn parseCExpr(c: *Context, it: *ctok.TokenList.Iterator, source_loc: ZigClangSou
5053 }5081 }
5054}5082}
50555083
5056fn parseCNumLit(c: *Context, tok: *CToken, source_loc: ZigClangSourceLocation) ParseError!*ast.Node {5084fn parseCNumLit(c: *Context, tok: *CToken, source: []const u8, source_loc: ZigClangSourceLocation) ParseError!*ast.Node {
5057 if (tok.id == .NumLitInt) {5085 var lit_bytes = source[tok.start..tok.end];
5058 var lit_bytes = tok.bytes;
50595086
5060 if (tok.bytes.len > 2 and tok.bytes[0] == '0') {5087 if (tok.id == .IntegerLiteral) {
5061 switch (tok.bytes[1]) {5088 if (lit_bytes.len > 2 and lit_bytes[0] == '0') {
5089 switch (lit_bytes[1]) {
5062 '0'...'7' => {5090 '0'...'7' => {
5063 // Octal5091 // Octal
5064 lit_bytes = try std.fmt.allocPrint(c.a(), "0o{}", .{tok.bytes});5092 lit_bytes = try std.fmt.allocPrint(c.a(), "0o{}", .{lit_bytes});
5065 },5093 },
5066 'X' => {5094 'X' => {
5067 // Hexadecimal with capital X, valid in C but not in Zig5095 // Hexadecimal with capital X, valid in C but not in Zig
5068 lit_bytes = try std.fmt.allocPrint(c.a(), "0x{}", .{tok.bytes[2..]});5096 lit_bytes = try std.fmt.allocPrint(c.a(), "0x{}", .{lit_bytes[2..]});
5069 },5097 },
5070 else => {},5098 else => {},
5071 }5099 }
5072 }5100 }
50735101
5074 if (tok.num_lit_suffix == .None) {5102 if (tok.id.IntegerLiteral == .None) {
5075 return transCreateNodeInt(c, lit_bytes);5103 return transCreateNodeInt(c, lit_bytes);
5076 }5104 }
50775105
5078 const cast_node = try transCreateNodeBuiltinFnCall(c, "@as");5106 const cast_node = try transCreateNodeBuiltinFnCall(c, "@as");
5079 try cast_node.params.push(try transCreateNodeIdentifier(c, switch (tok.num_lit_suffix) {5107 try cast_node.params.push(try transCreateNodeIdentifier(c, switch (tok.id.IntegerLiteral) {
5080 .U => "c_uint",5108 .U => "c_uint",
5081 .L => "c_long",5109 .L => "c_long",
5082 .LU => "c_ulong",5110 .LU => "c_ulong",
...@@ -5084,55 +5112,233 @@ fn parseCNumLit(c: *Context, tok: *CToken, source_loc: ZigClangSourceLocation) P...@@ -5084,55 +5112,233 @@ fn parseCNumLit(c: *Context, tok: *CToken, source_loc: ZigClangSourceLocation) P
5084 .LLU => "c_ulonglong",5112 .LLU => "c_ulonglong",
5085 else => unreachable,5113 else => unreachable,
5086 }));5114 }));
5115 lit_bytes = lit_bytes[0 .. lit_bytes.len - switch (tok.id.IntegerLiteral) {
5116 .U, .L => @as(u8, 1),
5117 .LU, .LL => 2,
5118 .LLU => 3,
5119 else => unreachable,
5120 }];
5087 _ = try appendToken(c, .Comma, ",");5121 _ = try appendToken(c, .Comma, ",");
5088 try cast_node.params.push(try transCreateNodeInt(c, lit_bytes));5122 try cast_node.params.push(try transCreateNodeInt(c, lit_bytes));
5089 cast_node.rparen_token = try appendToken(c, .RParen, ")");5123 cast_node.rparen_token = try appendToken(c, .RParen, ")");
5090 return &cast_node.base;5124 return &cast_node.base;
5091 } else if (tok.id == .NumLitFloat) {5125 } else if (tok.id == .FloatLiteral) {
5092 if (tok.num_lit_suffix == .None) {5126 if (tok.id.FloatLiteral == .None) {
5093 return transCreateNodeFloat(c, tok.bytes);5127 return transCreateNodeFloat(c, lit_bytes);
5094 }5128 }
5095 const cast_node = try transCreateNodeBuiltinFnCall(c, "@as");5129 const cast_node = try transCreateNodeBuiltinFnCall(c, "@as");
5096 try cast_node.params.push(try transCreateNodeIdentifier(c, switch (tok.num_lit_suffix) {5130 try cast_node.params.push(try transCreateNodeIdentifier(c, switch (tok.id.FloatLiteral) {
5097 .F => "f32",5131 .F => "f32",
5098 .L => "f64",5132 .L => "c_longdouble",
5099 else => unreachable,5133 else => unreachable,
5100 }));5134 }));
5101 _ = try appendToken(c, .Comma, ",");5135 _ = try appendToken(c, .Comma, ",");
5102 try cast_node.params.push(try transCreateNodeFloat(c, tok.bytes));5136 try cast_node.params.push(try transCreateNodeFloat(c, lit_bytes[0 .. lit_bytes.len - 1]));
5103 cast_node.rparen_token = try appendToken(c, .RParen, ")");5137 cast_node.rparen_token = try appendToken(c, .RParen, ")");
5104 return &cast_node.base;5138 return &cast_node.base;
5105 } else unreachable;5139 } else unreachable;
5106}5140}
51075141
5108fn parseCPrimaryExpr(c: *Context, it: *ctok.TokenList.Iterator, source_loc: ZigClangSourceLocation, scope: *Scope) ParseError!*ast.Node {5142fn zigifyEscapeSequences(ctx: *Context, source_bytes: []const u8, name: []const u8, source_loc: ZigClangSourceLocation) ![]const u8 {
5143 var source = source_bytes;
5144 for (source) |c, i| {
5145 if (c == '\"' or c == '\'') {
5146 source = source[i..];
5147 break;
5148 }
5149 }
5150 for (source) |c| {
5151 if (c == '\\') {
5152 break;
5153 }
5154 } else return source;
5155 var bytes = try ctx.a().alloc(u8, source.len * 2);
5156 var state: enum {
5157 Start,
5158 Escape,
5159 Hex,
5160 Octal,
5161 } = .Start;
5162 var i: usize = 0;
5163 var count: u8 = 0;
5164 var num: u8 = 0;
5165 for (source) |c| {
5166 switch (state) {
5167 .Escape => {
5168 switch (c) {
5169 'n', 'r', 't', '\\', '\'', '\"' => {
5170 bytes[i] = c;
5171 },
5172 '0'...'7' => {
5173 count += 1;
5174 num += c - '0';
5175 state = .Octal;
5176 bytes[i] = 'x';
5177 },
5178 'x' => {
5179 state = .Hex;
5180 bytes[i] = 'x';
5181 },
5182 'a' => {
5183 bytes[i] = 'x';
5184 i += 1;
5185 bytes[i] = '0';
5186 i += 1;
5187 bytes[i] = '7';
5188 },
5189 'b' => {
5190 bytes[i] = 'x';
5191 i += 1;
5192 bytes[i] = '0';
5193 i += 1;
5194 bytes[i] = '8';
5195 },
5196 'f' => {
5197 bytes[i] = 'x';
5198 i += 1;
5199 bytes[i] = '0';
5200 i += 1;
5201 bytes[i] = 'C';
5202 },
5203 'v' => {
5204 bytes[i] = 'x';
5205 i += 1;
5206 bytes[i] = '0';
5207 i += 1;
5208 bytes[i] = 'B';
5209 },
5210 '?' => {
5211 i -= 1;
5212 bytes[i] = '?';
5213 },
5214 'u', 'U' => {
5215 try failDecl(ctx, source_loc, name, "macro tokenizing failed: TODO unicode escape sequences", .{});
5216 return error.ParseError;
5217 },
5218 else => {
5219 try failDecl(ctx, source_loc, name, "macro tokenizing failed: unknown escape sequence", .{});
5220 return error.ParseError;
5221 },
5222 }
5223 i += 1;
5224 if (state == .Escape)
5225 state = .Start;
5226 },
5227 .Start => {
5228 if (c == '\\') {
5229 state = .Escape;
5230 }
5231 bytes[i] = c;
5232 i += 1;
5233 },
5234 .Hex => {
5235 switch (c) {
5236 '0'...'9' => {
5237 num = std.math.mul(u8, num, 16) catch {
5238 try failDecl(ctx, source_loc, name, "macro tokenizing failed: hex literal overflowed", .{});
5239 return error.ParseError;
5240 };
5241 num += c - '0';
5242 },
5243 'a'...'f' => {
5244 num = std.math.mul(u8, num, 16) catch {
5245 try failDecl(ctx, source_loc, name, "macro tokenizing failed: hex literal overflowed", .{});
5246 return error.ParseError;
5247 };
5248 num += c - 'a' + 10;
5249 },
5250 'A'...'F' => {
5251 num = std.math.mul(u8, num, 16) catch {
5252 try failDecl(ctx, source_loc, name, "macro tokenizing failed: hex literal overflowed", .{});
5253 return error.ParseError;
5254 };
5255 num += c - 'A' + 10;
5256 },
5257 else => {
5258 i += std.fmt.formatIntBuf(bytes[i..], num, 16, false, std.fmt.FormatOptions{ .fill = '0', .width = 2 });
5259 num = 0;
5260 if (c == '\\')
5261 state = .Escape
5262 else
5263 state = .Start;
5264 bytes[i] = c;
5265 i += 1;
5266 },
5267 }
5268 },
5269 .Octal => {
5270 const accept_digit = switch (c) {
5271 // The maximum length of a octal literal is 3 digits
5272 '0'...'7' => count < 3,
5273 else => false,
5274 };
5275
5276 if (accept_digit) {
5277 count += 1;
5278 num = std.math.mul(u8, num, 8) catch {
5279 try failDecl(ctx, source_loc, name, "macro tokenizing failed: octal literal overflowed", .{});
5280 return error.ParseError;
5281 };
5282 num += c - '0';
5283 } else {
5284 i += std.fmt.formatIntBuf(bytes[i..], num, 16, false, std.fmt.FormatOptions{ .fill = '0', .width = 2 });
5285 num = 0;
5286 count = 0;
5287 if (c == '\\')
5288 state = .Escape
5289 else
5290 state = .Start;
5291 bytes[i] = c;
5292 i += 1;
5293 }
5294 },
5295 }
5296 }
5297 if (state == .Hex or state == .Octal)
5298 i += std.fmt.formatIntBuf(bytes[i..], num, 16, false, std.fmt.FormatOptions{ .fill = '0', .width = 2 });
5299 return bytes[0..i];
5300}
5301
5302fn parseCPrimaryExpr(c: *Context, it: *CTokenList.Iterator, source: []const u8, source_loc: ZigClangSourceLocation, scope: *Scope) ParseError!*ast.Node {
5109 const tok = it.next().?;5303 const tok = it.next().?;
5110 switch (tok.id) {5304 switch (tok.id) {
5111 .CharLit => {5305 .CharLiteral => {
5112 const token = try appendToken(c, .CharLiteral, tok.bytes);5306 const first_tok = it.list.at(0);
5307 const token = try appendToken(c, .CharLiteral, try zigifyEscapeSequences(c, source[tok.start..tok.end], source[first_tok.start..first_tok.end], source_loc));
5113 const node = try c.a().create(ast.Node.CharLiteral);5308 const node = try c.a().create(ast.Node.CharLiteral);
5114 node.* = ast.Node.CharLiteral{5309 node.* = ast.Node.CharLiteral{
5115 .token = token,5310 .token = token,
5116 };5311 };
5117 return &node.base;5312 return &node.base;
5118 },5313 },
5119 .StrLit => {5314 .StringLiteral => {
5120 const token = try appendToken(c, .StringLiteral, tok.bytes);5315 const first_tok = it.list.at(0);
5316 const token = try appendToken(c, .StringLiteral, try zigifyEscapeSequences(c, source[tok.start..tok.end], source[first_tok.start..first_tok.end], source_loc));
5121 const node = try c.a().create(ast.Node.StringLiteral);5317 const node = try c.a().create(ast.Node.StringLiteral);
5122 node.* = ast.Node.StringLiteral{5318 node.* = ast.Node.StringLiteral{
5123 .token = token,5319 .token = token,
5124 };5320 };
5125 return &node.base;5321 return &node.base;
5126 },5322 },
5127 .NumLitInt, .NumLitFloat => {5323 .IntegerLiteral, .FloatLiteral => {
5128 return parseCNumLit(c, tok, source_loc);5324 return parseCNumLit(c, tok, source, source_loc);
5129 },5325 },
5326 // eventually this will be replaced by std.c.parse which will handle these correctly
5327 .Keyword_void => return transCreateNodeTypeIdentifier(c, "c_void"),
5328 .Keyword_bool => return transCreateNodeTypeIdentifier(c, "bool"),
5329 .Keyword_double => return transCreateNodeTypeIdentifier(c, "f64"),
5330 .Keyword_long => return transCreateNodeTypeIdentifier(c, "c_long"),
5331 .Keyword_int => return transCreateNodeTypeIdentifier(c, "c_int"),
5332 .Keyword_float => return transCreateNodeTypeIdentifier(c, "f32"),
5333 .Keyword_short => return transCreateNodeTypeIdentifier(c, "c_short"),
5334 .Keyword_char => return transCreateNodeTypeIdentifier(c, "c_char"),
5335 .Keyword_unsigned => return transCreateNodeTypeIdentifier(c, "c_uint"),
5130 .Identifier => {5336 .Identifier => {
5131 const mangled_name = scope.getAlias(tok.bytes);5337 const mangled_name = scope.getAlias(source[tok.start..tok.end]);
5132 return transCreateNodeIdentifier(c, mangled_name);5338 return transCreateNodeIdentifier(c, mangled_name);
5133 },5339 },
5134 .LParen => {5340 .LParen => {
5135 const inner_node = try parseCExpr(c, it, source_loc, scope);5341 const inner_node = try parseCExpr(c, it, source, source_loc, scope);
51365342
5137 if (it.peek().?.id == .RParen) {5343 if (it.peek().?.id == .RParen) {
5138 _ = it.next();5344 _ = it.next();
...@@ -5145,13 +5351,14 @@ fn parseCPrimaryExpr(c: *Context, it: *ctok.TokenList.Iterator, source_loc: ZigC...@@ -5145,13 +5351,14 @@ fn parseCPrimaryExpr(c: *Context, it: *ctok.TokenList.Iterator, source_loc: ZigC
5145 // hack to get zig fmt to render a comma in builtin calls5351 // hack to get zig fmt to render a comma in builtin calls
5146 _ = try appendToken(c, .Comma, ",");5352 _ = try appendToken(c, .Comma, ",");
51475353
5148 const node_to_cast = try parseCExpr(c, it, source_loc, scope);5354 const node_to_cast = try parseCExpr(c, it, source, source_loc, scope);
51495355
5150 if (it.next().?.id != .RParen) {5356 if (it.next().?.id != .RParen) {
5357 const first_tok = it.list.at(0);
5151 try failDecl(5358 try failDecl(
5152 c,5359 c,
5153 source_loc,5360 source_loc,
5154 it.list.at(0).*.bytes,5361 source[first_tok.start..first_tok.end],
5155 "unable to translate C expr: expected ')''",5362 "unable to translate C expr: expected ')''",
5156 .{},5363 .{},
5157 );5364 );
...@@ -5229,10 +5436,11 @@ fn parseCPrimaryExpr(c: *Context, it: *ctok.TokenList.Iterator, source_loc: ZigC...@@ -5229,10 +5436,11 @@ fn parseCPrimaryExpr(c: *Context, it: *ctok.TokenList.Iterator, source_loc: ZigC
5229 return &if_1.base;5436 return &if_1.base;
5230 },5437 },
5231 else => {5438 else => {
5439 const first_tok = it.list.at(0);
5232 try failDecl(5440 try failDecl(
5233 c,5441 c,
5234 source_loc,5442 source_loc,
5235 it.list.at(0).*.bytes,5443 source[first_tok.start..first_tok.end],
5236 "unable to translate C expr: unexpected token {}",5444 "unable to translate C expr: unexpected token {}",
5237 .{tok.id},5445 .{tok.id},
5238 );5446 );
...@@ -5241,33 +5449,35 @@ fn parseCPrimaryExpr(c: *Context, it: *ctok.TokenList.Iterator, source_loc: ZigC...@@ -5241,33 +5449,35 @@ fn parseCPrimaryExpr(c: *Context, it: *ctok.TokenList.Iterator, source_loc: ZigC
5241 }5449 }
5242}5450}
52435451
5244fn parseCSuffixOpExpr(c: *Context, it: *ctok.TokenList.Iterator, source_loc: ZigClangSourceLocation, scope: *Scope) ParseError!*ast.Node {5452fn parseCSuffixOpExpr(c: *Context, it: *CTokenList.Iterator, source: []const u8, source_loc: ZigClangSourceLocation, scope: *Scope) ParseError!*ast.Node {
5245 var node = try parseCPrimaryExpr(c, it, source_loc, scope);5453 var node = try parseCPrimaryExpr(c, it, source, source_loc, scope);
5246 while (true) {5454 while (true) {
5247 const tok = it.next().?;5455 const tok = it.next().?;
5248 switch (tok.id) {5456 switch (tok.id) {
5249 .Dot => {5457 .Period => {
5250 const name_tok = it.next().?;5458 const name_tok = it.next().?;
5251 if (name_tok.id != .Identifier) {5459 if (name_tok.id != .Identifier) {
5460 const first_tok = it.list.at(0);
5252 try failDecl(5461 try failDecl(
5253 c,5462 c,
5254 source_loc,5463 source_loc,
5255 it.list.at(0).*.bytes,5464 source[first_tok.start..first_tok.end],
5256 "unable to translate C expr: expected identifier",5465 "unable to translate C expr: expected identifier",
5257 .{},5466 .{},
5258 );5467 );
5259 return error.ParseError;5468 return error.ParseError;
5260 }5469 }
52615470
5262 node = try transCreateNodeFieldAccess(c, node, name_tok.bytes);5471 node = try transCreateNodeFieldAccess(c, node, source[name_tok.start..name_tok.end]);
5263 },5472 },
5264 .Arrow => {5473 .Arrow => {
5265 const name_tok = it.next().?;5474 const name_tok = it.next().?;
5266 if (name_tok.id != .Identifier) {5475 if (name_tok.id != .Identifier) {
5476 const first_tok = it.list.at(0);
5267 try failDecl(5477 try failDecl(
5268 c,5478 c,
5269 source_loc,5479 source_loc,
5270 it.list.at(0).*.bytes,5480 source[first_tok.start..first_tok.end],
5271 "unable to translate C expr: expected identifier",5481 "unable to translate C expr: expected identifier",
5272 .{},5482 .{},
5273 );5483 );
...@@ -5275,7 +5485,7 @@ fn parseCSuffixOpExpr(c: *Context, it: *ctok.TokenList.Iterator, source_loc: Zig...@@ -5275,7 +5485,7 @@ fn parseCSuffixOpExpr(c: *Context, it: *ctok.TokenList.Iterator, source_loc: Zig
5275 }5485 }
52765486
5277 const deref = try transCreateNodePtrDeref(c, node);5487 const deref = try transCreateNodePtrDeref(c, node);
5278 node = try transCreateNodeFieldAccess(c, deref, name_tok.bytes);5488 node = try transCreateNodeFieldAccess(c, deref, source[name_tok.start..name_tok.end]);
5279 },5489 },
5280 .Asterisk => {5490 .Asterisk => {
5281 if (it.peek().?.id == .RParen) {5491 if (it.peek().?.id == .RParen) {
...@@ -5284,13 +5494,23 @@ fn parseCSuffixOpExpr(c: *Context, it: *ctok.TokenList.Iterator, source_loc: Zig...@@ -5284,13 +5494,23 @@ fn parseCSuffixOpExpr(c: *Context, it: *ctok.TokenList.Iterator, source_loc: Zig
5284 // hack to get zig fmt to render a comma in builtin calls5494 // hack to get zig fmt to render a comma in builtin calls
5285 _ = try appendToken(c, .Comma, ",");5495 _ = try appendToken(c, .Comma, ",");
52865496
5287 const ptr = try transCreateNodePtrType(c, false, false, .Identifier);5497 const ptr_kind = blk:{
5498 // * token
5499 _ = it.prev();
5500 // last token of `node`
5501 const prev_id = it.prev().?.id;
5502 _ = it.next();
5503 _ = it.next();
5504 break :blk if (prev_id == .Keyword_void) .Asterisk else Token.Id.Identifier;
5505 };
5506
5507 const ptr = try transCreateNodePtrType(c, false, false, ptr_kind);
5288 ptr.rhs = node;5508 ptr.rhs = node;
5289 return &ptr.base;5509 return &ptr.base;
5290 } else {5510 } else {
5291 // expr * expr5511 // expr * expr
5292 const op_token = try appendToken(c, .Asterisk, "*");5512 const op_token = try appendToken(c, .Asterisk, "*");
5293 const rhs = try parseCPrimaryExpr(c, it, source_loc, scope);5513 const rhs = try parseCPrimaryExpr(c, it, source, source_loc, scope);
5294 const mul_node = try c.a().create(ast.Node.InfixOp);5514 const mul_node = try c.a().create(ast.Node.InfixOp);
5295 mul_node.* = .{5515 mul_node.* = .{
5296 .op_token = op_token,5516 .op_token = op_token,
...@@ -5301,9 +5521,9 @@ fn parseCSuffixOpExpr(c: *Context, it: *ctok.TokenList.Iterator, source_loc: Zig...@@ -5301,9 +5521,9 @@ fn parseCSuffixOpExpr(c: *Context, it: *ctok.TokenList.Iterator, source_loc: Zig
5301 node = &mul_node.base;5521 node = &mul_node.base;
5302 }5522 }
5303 },5523 },
5304 .Shl => {5524 .AngleBracketAngleBracketLeft => {
5305 const op_token = try appendToken(c, .AngleBracketAngleBracketLeft, "<<");5525 const op_token = try appendToken(c, .AngleBracketAngleBracketLeft, "<<");
5306 const rhs = try parseCPrefixOpExpr(c, it, source_loc, scope);5526 const rhs = try parseCPrefixOpExpr(c, it, source, source_loc, scope);
5307 const bitshift_node = try c.a().create(ast.Node.InfixOp);5527 const bitshift_node = try c.a().create(ast.Node.InfixOp);
5308 bitshift_node.* = .{5528 bitshift_node.* = .{
5309 .op_token = op_token,5529 .op_token = op_token,
...@@ -5313,9 +5533,9 @@ fn parseCSuffixOpExpr(c: *Context, it: *ctok.TokenList.Iterator, source_loc: Zig...@@ -5313,9 +5533,9 @@ fn parseCSuffixOpExpr(c: *Context, it: *ctok.TokenList.Iterator, source_loc: Zig
5313 };5533 };
5314 node = &bitshift_node.base;5534 node = &bitshift_node.base;
5315 },5535 },
5316 .Shr => {5536 .AngleBracketAngleBracketRight => {
5317 const op_token = try appendToken(c, .AngleBracketAngleBracketRight, ">>");5537 const op_token = try appendToken(c, .AngleBracketAngleBracketRight, ">>");
5318 const rhs = try parseCPrefixOpExpr(c, it, source_loc, scope);5538 const rhs = try parseCPrefixOpExpr(c, it, source, source_loc, scope);
5319 const bitshift_node = try c.a().create(ast.Node.InfixOp);5539 const bitshift_node = try c.a().create(ast.Node.InfixOp);
5320 bitshift_node.* = .{5540 bitshift_node.* = .{
5321 .op_token = op_token,5541 .op_token = op_token,
...@@ -5327,7 +5547,7 @@ fn parseCSuffixOpExpr(c: *Context, it: *ctok.TokenList.Iterator, source_loc: Zig...@@ -5327,7 +5547,7 @@ fn parseCSuffixOpExpr(c: *Context, it: *ctok.TokenList.Iterator, source_loc: Zig
5327 },5547 },
5328 .Pipe => {5548 .Pipe => {
5329 const op_token = try appendToken(c, .Pipe, "|");5549 const op_token = try appendToken(c, .Pipe, "|");
5330 const rhs = try parseCPrefixOpExpr(c, it, source_loc, scope);5550 const rhs = try parseCPrefixOpExpr(c, it, source, source_loc, scope);
5331 const or_node = try c.a().create(ast.Node.InfixOp);5551 const or_node = try c.a().create(ast.Node.InfixOp);
5332 or_node.* = .{5552 or_node.* = .{
5333 .op_token = op_token,5553 .op_token = op_token,
...@@ -5339,7 +5559,7 @@ fn parseCSuffixOpExpr(c: *Context, it: *ctok.TokenList.Iterator, source_loc: Zig...@@ -5339,7 +5559,7 @@ fn parseCSuffixOpExpr(c: *Context, it: *ctok.TokenList.Iterator, source_loc: Zig
5339 },5559 },
5340 .Ampersand => {5560 .Ampersand => {
5341 const op_token = try appendToken(c, .Ampersand, "&");5561 const op_token = try appendToken(c, .Ampersand, "&");
5342 const rhs = try parseCPrefixOpExpr(c, it, source_loc, scope);5562 const rhs = try parseCPrefixOpExpr(c, it, source, source_loc, scope);
5343 const bitand_node = try c.a().create(ast.Node.InfixOp);5563 const bitand_node = try c.a().create(ast.Node.InfixOp);
5344 bitand_node.* = .{5564 bitand_node.* = .{
5345 .op_token = op_token,5565 .op_token = op_token,
...@@ -5351,7 +5571,7 @@ fn parseCSuffixOpExpr(c: *Context, it: *ctok.TokenList.Iterator, source_loc: Zig...@@ -5351,7 +5571,7 @@ fn parseCSuffixOpExpr(c: *Context, it: *ctok.TokenList.Iterator, source_loc: Zig
5351 },5571 },
5352 .Plus => {5572 .Plus => {
5353 const op_token = try appendToken(c, .Plus, "+");5573 const op_token = try appendToken(c, .Plus, "+");
5354 const rhs = try parseCPrefixOpExpr(c, it, source_loc, scope);5574 const rhs = try parseCPrefixOpExpr(c, it, source, source_loc, scope);
5355 const add_node = try c.a().create(ast.Node.InfixOp);5575 const add_node = try c.a().create(ast.Node.InfixOp);
5356 add_node.* = .{5576 add_node.* = .{
5357 .op_token = op_token,5577 .op_token = op_token,
...@@ -5363,7 +5583,7 @@ fn parseCSuffixOpExpr(c: *Context, it: *ctok.TokenList.Iterator, source_loc: Zig...@@ -5363,7 +5583,7 @@ fn parseCSuffixOpExpr(c: *Context, it: *ctok.TokenList.Iterator, source_loc: Zig
5363 },5583 },
5364 .Minus => {5584 .Minus => {
5365 const op_token = try appendToken(c, .Minus, "-");5585 const op_token = try appendToken(c, .Minus, "-");
5366 const rhs = try parseCPrefixOpExpr(c, it, source_loc, scope);5586 const rhs = try parseCPrefixOpExpr(c, it, source, source_loc, scope);
5367 const sub_node = try c.a().create(ast.Node.InfixOp);5587 const sub_node = try c.a().create(ast.Node.InfixOp);
5368 sub_node.* = .{5588 sub_node.* = .{
5369 .op_token = op_token,5589 .op_token = op_token,
...@@ -5373,9 +5593,9 @@ fn parseCSuffixOpExpr(c: *Context, it: *ctok.TokenList.Iterator, source_loc: Zig...@@ -5373,9 +5593,9 @@ fn parseCSuffixOpExpr(c: *Context, it: *ctok.TokenList.Iterator, source_loc: Zig
5373 };5593 };
5374 node = &sub_node.base;5594 node = &sub_node.base;
5375 },5595 },
5376 .And => {5596 .AmpersandAmpersand => {
5377 const op_token = try appendToken(c, .Keyword_and, "and");5597 const op_token = try appendToken(c, .Keyword_and, "and");
5378 const rhs = try parseCPrefixOpExpr(c, it, source_loc, scope);5598 const rhs = try parseCPrefixOpExpr(c, it, source, source_loc, scope);
5379 const and_node = try c.a().create(ast.Node.InfixOp);5599 const and_node = try c.a().create(ast.Node.InfixOp);
5380 and_node.* = .{5600 and_node.* = .{
5381 .op_token = op_token,5601 .op_token = op_token,
...@@ -5385,9 +5605,9 @@ fn parseCSuffixOpExpr(c: *Context, it: *ctok.TokenList.Iterator, source_loc: Zig...@@ -5385,9 +5605,9 @@ fn parseCSuffixOpExpr(c: *Context, it: *ctok.TokenList.Iterator, source_loc: Zig
5385 };5605 };
5386 node = &and_node.base;5606 node = &and_node.base;
5387 },5607 },
5388 .Or => {5608 .PipePipe => {
5389 const op_token = try appendToken(c, .Keyword_or, "or");5609 const op_token = try appendToken(c, .Keyword_or, "or");
5390 const rhs = try parseCPrefixOpExpr(c, it, source_loc, scope);5610 const rhs = try parseCPrefixOpExpr(c, it, source, source_loc, scope);
5391 const or_node = try c.a().create(ast.Node.InfixOp);5611 const or_node = try c.a().create(ast.Node.InfixOp);
5392 or_node.* = .{5612 or_node.* = .{
5393 .op_token = op_token,5613 .op_token = op_token,
...@@ -5397,9 +5617,9 @@ fn parseCSuffixOpExpr(c: *Context, it: *ctok.TokenList.Iterator, source_loc: Zig...@@ -5397,9 +5617,9 @@ fn parseCSuffixOpExpr(c: *Context, it: *ctok.TokenList.Iterator, source_loc: Zig
5397 };5617 };
5398 node = &or_node.base;5618 node = &or_node.base;
5399 },5619 },
5400 .Gt => {5620 .AngleBracketRight => {
5401 const op_token = try appendToken(c, .AngleBracketRight, ">");5621 const op_token = try appendToken(c, .AngleBracketRight, ">");
5402 const rhs = try parseCPrefixOpExpr(c, it, source_loc, scope);5622 const rhs = try parseCPrefixOpExpr(c, it, source, source_loc, scope);
5403 const and_node = try c.a().create(ast.Node.InfixOp);5623 const and_node = try c.a().create(ast.Node.InfixOp);
5404 and_node.* = .{5624 and_node.* = .{
5405 .op_token = op_token,5625 .op_token = op_token,
...@@ -5409,9 +5629,9 @@ fn parseCSuffixOpExpr(c: *Context, it: *ctok.TokenList.Iterator, source_loc: Zig...@@ -5409,9 +5629,9 @@ fn parseCSuffixOpExpr(c: *Context, it: *ctok.TokenList.Iterator, source_loc: Zig
5409 };5629 };
5410 node = &and_node.base;5630 node = &and_node.base;
5411 },5631 },
5412 .Gte => {5632 .AngleBracketRightEqual => {
5413 const op_token = try appendToken(c, .AngleBracketRightEqual, ">=");5633 const op_token = try appendToken(c, .AngleBracketRightEqual, ">=");
5414 const rhs = try parseCPrefixOpExpr(c, it, source_loc, scope);5634 const rhs = try parseCPrefixOpExpr(c, it, source, source_loc, scope);
5415 const and_node = try c.a().create(ast.Node.InfixOp);5635 const and_node = try c.a().create(ast.Node.InfixOp);
5416 and_node.* = .{5636 and_node.* = .{
5417 .op_token = op_token,5637 .op_token = op_token,
...@@ -5421,9 +5641,9 @@ fn parseCSuffixOpExpr(c: *Context, it: *ctok.TokenList.Iterator, source_loc: Zig...@@ -5421,9 +5641,9 @@ fn parseCSuffixOpExpr(c: *Context, it: *ctok.TokenList.Iterator, source_loc: Zig
5421 };5641 };
5422 node = &and_node.base;5642 node = &and_node.base;
5423 },5643 },
5424 .Lt => {5644 .AngleBracketLeft => {
5425 const op_token = try appendToken(c, .AngleBracketLeft, "<");5645 const op_token = try appendToken(c, .AngleBracketLeft, "<");
5426 const rhs = try parseCPrefixOpExpr(c, it, source_loc, scope);5646 const rhs = try parseCPrefixOpExpr(c, it, source, source_loc, scope);
5427 const and_node = try c.a().create(ast.Node.InfixOp);5647 const and_node = try c.a().create(ast.Node.InfixOp);
5428 and_node.* = .{5648 and_node.* = .{
5429 .op_token = op_token,5649 .op_token = op_token,
...@@ -5433,9 +5653,9 @@ fn parseCSuffixOpExpr(c: *Context, it: *ctok.TokenList.Iterator, source_loc: Zig...@@ -5433,9 +5653,9 @@ fn parseCSuffixOpExpr(c: *Context, it: *ctok.TokenList.Iterator, source_loc: Zig
5433 };5653 };
5434 node = &and_node.base;5654 node = &and_node.base;
5435 },5655 },
5436 .Lte => {5656 .AngleBracketLeftEqual => {
5437 const op_token = try appendToken(c, .AngleBracketLeftEqual, "<=");5657 const op_token = try appendToken(c, .AngleBracketLeftEqual, "<=");
5438 const rhs = try parseCPrefixOpExpr(c, it, source_loc, scope);5658 const rhs = try parseCPrefixOpExpr(c, it, source, source_loc, scope);
5439 const and_node = try c.a().create(ast.Node.InfixOp);5659 const and_node = try c.a().create(ast.Node.InfixOp);
5440 and_node.* = .{5660 and_node.* = .{
5441 .op_token = op_token,5661 .op_token = op_token,
...@@ -5445,16 +5665,17 @@ fn parseCSuffixOpExpr(c: *Context, it: *ctok.TokenList.Iterator, source_loc: Zig...@@ -5445,16 +5665,17 @@ fn parseCSuffixOpExpr(c: *Context, it: *ctok.TokenList.Iterator, source_loc: Zig
5445 };5665 };
5446 node = &and_node.base;5666 node = &and_node.base;
5447 },5667 },
5448 .LBrace => {5668 .LBracket => {
5449 const arr_node = try transCreateNodeArrayAccess(c, node);5669 const arr_node = try transCreateNodeArrayAccess(c, node);
5450 arr_node.op.ArrayAccess = try parseCPrefixOpExpr(c, it, source_loc, scope);5670 arr_node.op.ArrayAccess = try parseCPrefixOpExpr(c, it, source, source_loc, scope);
5451 arr_node.rtoken = try appendToken(c, .RBrace, "]");5671 arr_node.rtoken = try appendToken(c, .RBracket, "]");
5452 node = &arr_node.base;5672 node = &arr_node.base;
5453 if (it.next().?.id != .RBrace) {5673 if (it.next().?.id != .RBracket) {
5674 const first_tok = it.list.at(0);
5454 try failDecl(5675 try failDecl(
5455 c,5676 c,
5456 source_loc,5677 source_loc,
5457 it.list.at(0).*.bytes,5678 source[first_tok.start..first_tok.end],
5458 "unable to translate C expr: expected ']'",5679 "unable to translate C expr: expected ']'",
5459 .{},5680 .{},
5460 );5681 );
...@@ -5464,7 +5685,7 @@ fn parseCSuffixOpExpr(c: *Context, it: *ctok.TokenList.Iterator, source_loc: Zig...@@ -5464,7 +5685,7 @@ fn parseCSuffixOpExpr(c: *Context, it: *ctok.TokenList.Iterator, source_loc: Zig
5464 .LParen => {5685 .LParen => {
5465 const call_node = try transCreateNodeFnCall(c, node);5686 const call_node = try transCreateNodeFnCall(c, node);
5466 while (true) {5687 while (true) {
5467 const arg = try parseCPrefixOpExpr(c, it, source_loc, scope);5688 const arg = try parseCPrefixOpExpr(c, it, source, source_loc, scope);
5468 try call_node.op.Call.params.push(arg);5689 try call_node.op.Call.params.push(arg);
5469 const next = it.next().?;5690 const next = it.next().?;
5470 if (next.id == .Comma)5691 if (next.id == .Comma)
...@@ -5472,10 +5693,11 @@ fn parseCSuffixOpExpr(c: *Context, it: *ctok.TokenList.Iterator, source_loc: Zig...@@ -5472,10 +5693,11 @@ fn parseCSuffixOpExpr(c: *Context, it: *ctok.TokenList.Iterator, source_loc: Zig
5472 else if (next.id == .RParen)5693 else if (next.id == .RParen)
5473 break5694 break
5474 else {5695 else {
5696 const first_tok = it.list.at(0);
5475 try failDecl(5697 try failDecl(
5476 c,5698 c,
5477 source_loc,5699 source_loc,
5478 it.list.at(0).*.bytes,5700 source[first_tok.start..first_tok.end],
5479 "unable to translate C expr: expected ',' or ')'",5701 "unable to translate C expr: expected ',' or ')'",
5480 .{},5702 .{},
5481 );5703 );
...@@ -5493,32 +5715,32 @@ fn parseCSuffixOpExpr(c: *Context, it: *ctok.TokenList.Iterator, source_loc: Zig...@@ -5493,32 +5715,32 @@ fn parseCSuffixOpExpr(c: *Context, it: *ctok.TokenList.Iterator, source_loc: Zig
5493 }5715 }
5494}5716}
54955717
5496fn parseCPrefixOpExpr(c: *Context, it: *ctok.TokenList.Iterator, source_loc: ZigClangSourceLocation, scope: *Scope) ParseError!*ast.Node {5718fn parseCPrefixOpExpr(c: *Context, it: *CTokenList.Iterator, source: []const u8, source_loc: ZigClangSourceLocation, scope: *Scope) ParseError!*ast.Node {
5497 const op_tok = it.next().?;5719 const op_tok = it.next().?;
54985720
5499 switch (op_tok.id) {5721 switch (op_tok.id) {
5500 .Bang => {5722 .Bang => {
5501 const node = try transCreateNodePrefixOp(c, .BoolNot, .Bang, "!");5723 const node = try transCreateNodePrefixOp(c, .BoolNot, .Bang, "!");
5502 node.rhs = try parseCPrefixOpExpr(c, it, source_loc, scope);5724 node.rhs = try parseCPrefixOpExpr(c, it, source, source_loc, scope);
5503 return &node.base;5725 return &node.base;
5504 },5726 },
5505 .Minus => {5727 .Minus => {
5506 const node = try transCreateNodePrefixOp(c, .Negation, .Minus, "-");5728 const node = try transCreateNodePrefixOp(c, .Negation, .Minus, "-");
5507 node.rhs = try parseCPrefixOpExpr(c, it, source_loc, scope);5729 node.rhs = try parseCPrefixOpExpr(c, it, source, source_loc, scope);
5508 return &node.base;5730 return &node.base;
5509 },5731 },
5510 .Tilde => {5732 .Tilde => {
5511 const node = try transCreateNodePrefixOp(c, .BitNot, .Tilde, "~");5733 const node = try transCreateNodePrefixOp(c, .BitNot, .Tilde, "~");
5512 node.rhs = try parseCPrefixOpExpr(c, it, source_loc, scope);5734 node.rhs = try parseCPrefixOpExpr(c, it, source, source_loc, scope);
5513 return &node.base;5735 return &node.base;
5514 },5736 },
5515 .Asterisk => {5737 .Asterisk => {
5516 const prefix_op_expr = try parseCPrefixOpExpr(c, it, source_loc, scope);5738 const prefix_op_expr = try parseCPrefixOpExpr(c, it, source, source_loc, scope);
5517 return try transCreateNodePtrDeref(c, prefix_op_expr);5739 return try transCreateNodePtrDeref(c, prefix_op_expr);
5518 },5740 },
5519 else => {5741 else => {
5520 _ = it.prev();5742 _ = it.prev();
5521 return try parseCSuffixOpExpr(c, it, source_loc, scope);5743 return try parseCSuffixOpExpr(c, it, source, source_loc, scope);
5522 },5744 },
5523 }5745 }
5524}5746}
src/all_types.hpp+6
...@@ -2174,7 +2174,9 @@ struct CodeGen {...@@ -2174,7 +2174,9 @@ struct CodeGen {
2174 bool is_big_endian;2174 bool is_big_endian;
2175 bool have_c_main;2175 bool have_c_main;
2176 bool have_winmain;2176 bool have_winmain;
2177 bool have_wwinmain;
2177 bool have_winmain_crt_startup;2178 bool have_winmain_crt_startup;
2179 bool have_wwinmain_crt_startup;
2178 bool have_dllmain_crt_startup;2180 bool have_dllmain_crt_startup;
2179 bool have_err_ret_tracing;2181 bool have_err_ret_tracing;
2180 bool link_eh_frame_hdr;2182 bool link_eh_frame_hdr;
...@@ -2243,6 +2245,7 @@ struct CodeGen {...@@ -2243,6 +2245,7 @@ struct CodeGen {
2243 bool enable_dump_analysis;2245 bool enable_dump_analysis;
2244 bool enable_doc_generation;2246 bool enable_doc_generation;
2245 bool disable_bin_generation;2247 bool disable_bin_generation;
2248 bool test_is_evented;
2246 CodeModel code_model;2249 CodeModel code_model;
22472250
2248 Buf *mmacosx_version_min;2251 Buf *mmacosx_version_min;
...@@ -2488,6 +2491,9 @@ struct ScopeExpr {...@@ -2488,6 +2491,9 @@ struct ScopeExpr {
2488 size_t children_len;2491 size_t children_len;
24892492
2490 MemoizedBool need_spill;2493 MemoizedBool need_spill;
2494 // This is a hack. I apologize for this, I need this to work so that I
2495 // can make progress on other fronts. I'll pay off this tech debt eventually.
2496 bool spill_harder;
2491};2497};
24922498
2493// synchronized with code in define_builtin_compile_vars2499// synchronized with code in define_builtin_compile_vars
src/analyze.cpp+33-6
...@@ -3419,8 +3419,12 @@ void add_fn_export(CodeGen *g, ZigFn *fn_table_entry, const char *symbol_name, G...@@ -3419,8 +3419,12 @@ void add_fn_export(CodeGen *g, ZigFn *fn_table_entry, const char *symbol_name, G
3419 } else if (cc == CallingConventionStdcall && g->zig_target->os == OsWindows) {3419 } else if (cc == CallingConventionStdcall && g->zig_target->os == OsWindows) {
3420 if (strcmp(symbol_name, "WinMain") == 0) {3420 if (strcmp(symbol_name, "WinMain") == 0) {
3421 g->have_winmain = true;3421 g->have_winmain = true;
3422 } else if (strcmp(symbol_name, "wWinMain") == 0) {
3423 g->have_wwinmain = true;
3422 } else if (strcmp(symbol_name, "WinMainCRTStartup") == 0) {3424 } else if (strcmp(symbol_name, "WinMainCRTStartup") == 0) {
3423 g->have_winmain_crt_startup = true;3425 g->have_winmain_crt_startup = true;
3426 } else if (strcmp(symbol_name, "wWinMainCRTStartup") == 0) {
3427 g->have_wwinmain_crt_startup = true;
3424 } else if (strcmp(symbol_name, "DllMainCRTStartup") == 0) {3428 } else if (strcmp(symbol_name, "DllMainCRTStartup") == 0) {
3425 g->have_dllmain_crt_startup = true;3429 g->have_dllmain_crt_startup = true;
3426 }3430 }
...@@ -6104,11 +6108,14 @@ static void mark_suspension_point(Scope *scope) {...@@ -6104,11 +6108,14 @@ static void mark_suspension_point(Scope *scope) {
6104 continue;6108 continue;
6105 }6109 }
6106 case ScopeIdExpr: {6110 case ScopeIdExpr: {
6111 ScopeExpr *parent_expr_scope = reinterpret_cast<ScopeExpr *>(scope);
6107 if (!looking_for_exprs) {6112 if (!looking_for_exprs) {
6113 if (parent_expr_scope->spill_harder) {
6114 parent_expr_scope->need_spill = MemoizedBoolTrue;
6115 }
6108 // Now we're only looking for a block, to see if it's in a loop (see the case ScopeIdBlock)6116 // Now we're only looking for a block, to see if it's in a loop (see the case ScopeIdBlock)
6109 continue;6117 continue;
6110 }6118 }
6111 ScopeExpr *parent_expr_scope = reinterpret_cast<ScopeExpr *>(scope);
6112 if (child_expr_scope != nullptr) {6119 if (child_expr_scope != nullptr) {
6113 for (size_t i = 0; parent_expr_scope->children_ptr[i] != child_expr_scope; i += 1) {6120 for (size_t i = 0; parent_expr_scope->children_ptr[i] != child_expr_scope; i += 1) {
6114 assert(i < parent_expr_scope->children_len);6121 assert(i < parent_expr_scope->children_len);
...@@ -6144,6 +6151,15 @@ static bool scope_needs_spill(Scope *scope) {...@@ -6144,6 +6151,15 @@ static bool scope_needs_spill(Scope *scope) {
6144 zig_unreachable();6151 zig_unreachable();
6145}6152}
61466153
6154static ZigType *resolve_type_isf(ZigType *ty) {
6155 if (ty->id != ZigTypeIdPointer) return ty;
6156 InferredStructField *isf = ty->data.pointer.inferred_struct_field;
6157 if (isf == nullptr) return ty;
6158 TypeStructField *field = find_struct_type_field(isf->inferred_struct_type, isf->field_name);
6159 assert(field != nullptr);
6160 return field->type_entry;
6161}
6162
6147static Error resolve_async_frame(CodeGen *g, ZigType *frame_type) {6163static Error resolve_async_frame(CodeGen *g, ZigType *frame_type) {
6148 Error err;6164 Error err;
61496165
...@@ -6245,6 +6261,9 @@ static Error resolve_async_frame(CodeGen *g, ZigType *frame_type) {...@@ -6245,6 +6261,9 @@ static Error resolve_async_frame(CodeGen *g, ZigType *frame_type) {
6245 }6261 }
6246 ZigFn *callee = call->fn_entry;6262 ZigFn *callee = call->fn_entry;
6247 if (callee == nullptr) {6263 if (callee == nullptr) {
6264 if (call->fn_ref->value->type->data.fn.fn_type_id.cc != CallingConventionAsync) {
6265 continue;
6266 }
6248 add_node_error(g, call->base.base.source_node,6267 add_node_error(g, call->base.base.source_node,
6249 buf_sprintf("function is not comptime-known; @asyncCall required"));6268 buf_sprintf("function is not comptime-known; @asyncCall required"));
6250 return ErrorSemanticAnalyzeFail;6269 return ErrorSemanticAnalyzeFail;
...@@ -6352,11 +6371,19 @@ static Error resolve_async_frame(CodeGen *g, ZigType *frame_type) {...@@ -6352,11 +6371,19 @@ static Error resolve_async_frame(CodeGen *g, ZigType *frame_type) {
6352 IrInstGen *instruction = block->instruction_list.at(instr_i);6371 IrInstGen *instruction = block->instruction_list.at(instr_i);
6353 if (instruction->id == IrInstGenIdAwait ||6372 if (instruction->id == IrInstGenIdAwait ||
6354 instruction->id == IrInstGenIdVarPtr ||6373 instruction->id == IrInstGenIdVarPtr ||
6355 instruction->id == IrInstGenIdAlloca)6374 instruction->id == IrInstGenIdAlloca ||
6375 instruction->id == IrInstGenIdSpillBegin ||
6376 instruction->id == IrInstGenIdSpillEnd)
6356 {6377 {
6357 // This instruction does its own spilling specially, or otherwise doesn't need it.6378 // This instruction does its own spilling specially, or otherwise doesn't need it.
6358 continue;6379 continue;
6359 }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 }
6360 if (instruction->value->special != ConstValSpecialRuntime)6387 if (instruction->value->special != ConstValSpecialRuntime)
6361 continue;6388 continue;
6362 if (instruction->base.ref_count == 0)6389 if (instruction->base.ref_count == 0)
...@@ -6402,7 +6429,7 @@ static Error resolve_async_frame(CodeGen *g, ZigType *frame_type) {...@@ -6402,7 +6429,7 @@ static Error resolve_async_frame(CodeGen *g, ZigType *frame_type) {
6402 } else {6429 } else {
6403 param_name = buf_sprintf("@arg%" ZIG_PRI_usize, arg_i);6430 param_name = buf_sprintf("@arg%" ZIG_PRI_usize, arg_i);
6404 }6431 }
6405 ZigType *param_type = param_info->type;6432 ZigType *param_type = resolve_type_isf(param_info->type);
6406 if ((err = type_resolve(g, param_type, ResolveStatusSizeKnown))) {6433 if ((err = type_resolve(g, param_type, ResolveStatusSizeKnown))) {
6407 return err;6434 return err;
6408 }6435 }
...@@ -6421,7 +6448,7 @@ static Error resolve_async_frame(CodeGen *g, ZigType *frame_type) {...@@ -6421,7 +6448,7 @@ static Error resolve_async_frame(CodeGen *g, ZigType *frame_type) {
6421 instruction->field_index = SIZE_MAX;6448 instruction->field_index = SIZE_MAX;
6422 ZigType *ptr_type = instruction->base.value->type;6449 ZigType *ptr_type = instruction->base.value->type;
6423 assert(ptr_type->id == ZigTypeIdPointer);6450 assert(ptr_type->id == ZigTypeIdPointer);
6424 ZigType *child_type = ptr_type->data.pointer.child_type;6451 ZigType *child_type = resolve_type_isf(ptr_type->data.pointer.child_type);
6425 if (!type_has_bits(child_type))6452 if (!type_has_bits(child_type))
6426 continue;6453 continue;
6427 if (instruction->base.base.ref_count == 0)6454 if (instruction->base.base.ref_count == 0)
...@@ -6448,8 +6475,6 @@ static Error resolve_async_frame(CodeGen *g, ZigType *frame_type) {...@@ -6448,8 +6475,6 @@ static Error resolve_async_frame(CodeGen *g, ZigType *frame_type) {
6448 }6475 }
6449 instruction->field_index = fields.length;6476 instruction->field_index = fields.length;
64506477
6451 src_assert(child_type->id != ZigTypeIdPointer || child_type->data.pointer.inferred_struct_field == nullptr,
6452 instruction->base.base.source_node);
6453 fields.append({name, child_type, instruction->align});6478 fields.append({name, child_type, instruction->align});
6454 }6479 }
64556480
...@@ -8251,6 +8276,8 @@ static void resolve_llvm_types_struct(CodeGen *g, ZigType *struct_type, ResolveS...@@ -8251,6 +8276,8 @@ static void resolve_llvm_types_struct(CodeGen *g, ZigType *struct_type, ResolveS
8251 size_t debug_field_index = 0;8276 size_t debug_field_index = 0;
8252 for (size_t i = 0; i < field_count; i += 1) {8277 for (size_t i = 0; i < field_count; i += 1) {
8253 TypeStructField *field = struct_type->data.structure.fields[i];8278 TypeStructField *field = struct_type->data.structure.fields[i];
8279 //fprintf(stderr, "%s at gen index %zu\n", buf_ptr(field->name), field->gen_index);
8280
8254 size_t gen_field_index = field->gen_index;8281 size_t gen_field_index = field->gen_index;
8255 if (gen_field_index == SIZE_MAX) {8282 if (gen_field_index == SIZE_MAX) {
8256 continue;8283 continue;
src/codegen.cpp+149-56
...@@ -343,33 +343,67 @@ static LLVMLinkage to_llvm_linkage(GlobalLinkageId id) {...@@ -343,33 +343,67 @@ static LLVMLinkage to_llvm_linkage(GlobalLinkageId id) {
343 zig_unreachable();343 zig_unreachable();
344}344}
345345
346struct CalcLLVMFieldIndex {
347 uint32_t offset;
348 uint32_t field_index;
349};
350
351static void calc_llvm_field_index_add(CodeGen *g, CalcLLVMFieldIndex *calc, ZigType *ty) {
352 if (!type_has_bits(ty)) return;
353 uint32_t ty_align = get_abi_alignment(g, ty);
354 if (calc->offset % ty_align != 0) {
355 uint32_t llvm_align = LLVMABIAlignmentOfType(g->target_data_ref, get_llvm_type(g, ty));
356 if (llvm_align >= ty_align) {
357 ty_align = llvm_align; // llvm's padding is sufficient
358 } else if (calc->offset) {
359 calc->field_index += 1; // zig will insert an extra padding field here
360 }
361 calc->offset += ty_align - (calc->offset % ty_align); // padding bytes
362 }
363 calc->offset += ty->abi_size;
364 calc->field_index += 1;
365}
366
346// label (grep this): [fn_frame_struct_layout]367// label (grep this): [fn_frame_struct_layout]
368static void frame_index_trace_arg_calc(CodeGen *g, CalcLLVMFieldIndex *calc, ZigType *return_type) {
369 calc_llvm_field_index_add(g, calc, g->builtin_types.entry_usize); // function pointer
370 calc_llvm_field_index_add(g, calc, g->builtin_types.entry_usize); // resume index
371 calc_llvm_field_index_add(g, calc, g->builtin_types.entry_usize); // awaiter index
372
373 if (type_has_bits(return_type)) {
374 calc_llvm_field_index_add(g, calc, g->builtin_types.entry_usize); // *ReturnType (callee's)
375 calc_llvm_field_index_add(g, calc, g->builtin_types.entry_usize); // *ReturnType (awaiter's)
376 calc_llvm_field_index_add(g, calc, return_type); // ReturnType
377 }
378}
379
347static uint32_t frame_index_trace_arg(CodeGen *g, ZigType *return_type) {380static uint32_t frame_index_trace_arg(CodeGen *g, ZigType *return_type) {
348 // [0] *ReturnType (callee's)381 CalcLLVMFieldIndex calc = {0};
349 // [1] *ReturnType (awaiter's)382 frame_index_trace_arg_calc(g, &calc, return_type);
350 // [2] ReturnType383 return calc.field_index;
351 uint32_t return_field_count = type_has_bits(return_type) ? 3 : 0;
352 return frame_ret_start + return_field_count;
353}384}
354385
355// label (grep this): [fn_frame_struct_layout]386// label (grep this): [fn_frame_struct_layout]
356static uint32_t frame_index_arg(CodeGen *g, ZigType *return_type) {387static void frame_index_arg_calc(CodeGen *g, CalcLLVMFieldIndex *calc, ZigType *return_type) {
357 bool have_stack_trace = codegen_fn_has_err_ret_tracing_arg(g, return_type);388 frame_index_trace_arg_calc(g, calc, return_type);
358 // [0] *StackTrace (callee's)389
359 // [1] *StackTrace (awaiter's)390 if (codegen_fn_has_err_ret_tracing_arg(g, return_type)) {
360 uint32_t trace_field_count = have_stack_trace ? 2 : 0;391 calc_llvm_field_index_add(g, calc, g->builtin_types.entry_usize); // *StackTrace (callee's)
361 return frame_index_trace_arg(g, return_type) + trace_field_count;392 calc_llvm_field_index_add(g, calc, g->builtin_types.entry_usize); // *StackTrace (awaiter's)
393 }
362}394}
363395
364// label (grep this): [fn_frame_struct_layout]396// label (grep this): [fn_frame_struct_layout]
365static uint32_t frame_index_trace_stack(CodeGen *g, FnTypeId *fn_type_id) {397static uint32_t frame_index_trace_stack(CodeGen *g, ZigFn *fn) {
366 uint32_t result = frame_index_arg(g, fn_type_id->return_type);398 size_t field_index = 6;
367 for (size_t i = 0; i < fn_type_id->param_count; i += 1) {399 bool have_stack_trace = codegen_fn_has_err_ret_tracing_arg(g, fn->type_entry->data.fn.fn_type_id.return_type);
368 if (type_has_bits(fn_type_id->param_info->type)) {400 if (have_stack_trace) {
369 result += 1;401 field_index += 2;
370 }
371 }402 }
372 return result;403 field_index += fn->type_entry->data.fn.fn_type_id.param_count;
404 ZigType *locals_struct = fn->frame_type->data.frame.locals_struct;
405 TypeStructField *field = locals_struct->data.structure.fields[field_index];
406 return field->gen_index;
373}407}
374408
375409
...@@ -2523,7 +2557,12 @@ static LLVMValueRef ir_render_return(CodeGen *g, IrExecutableGen *executable, Ir...@@ -2523,7 +2557,12 @@ static LLVMValueRef ir_render_return(CodeGen *g, IrExecutableGen *executable, Ir
2523 LLVMBuildRet(g->builder, by_val_value);2557 LLVMBuildRet(g->builder, by_val_value);
2524 }2558 }
2525 } else if (instruction->operand == nullptr) {2559 } else if (instruction->operand == nullptr) {
2526 LLVMBuildRetVoid(g->builder);2560 if (g->cur_ret_ptr == nullptr) {
2561 LLVMBuildRetVoid(g->builder);
2562 } else {
2563 LLVMValueRef by_val_value = gen_load_untyped(g, g->cur_ret_ptr, 0, false, "");
2564 LLVMBuildRet(g->builder, by_val_value);
2565 }
2527 } else {2566 } else {
2528 LLVMValueRef value = ir_llvm_value(g, instruction->operand);2567 LLVMValueRef value = ir_llvm_value(g, instruction->operand);
2529 LLVMBuildRet(g->builder, value);2568 LLVMBuildRet(g->builder, value);
...@@ -3916,7 +3955,9 @@ static void set_call_instr_sret(CodeGen *g, LLVMValueRef call_instr) {...@@ -3916,7 +3955,9 @@ static void set_call_instr_sret(CodeGen *g, LLVMValueRef call_instr) {
3916static void render_async_spills(CodeGen *g) {3955static void render_async_spills(CodeGen *g) {
3917 ZigType *fn_type = g->cur_fn->type_entry;3956 ZigType *fn_type = g->cur_fn->type_entry;
3918 ZigType *import = get_scope_import(&g->cur_fn->fndef_scope->base);3957 ZigType *import = get_scope_import(&g->cur_fn->fndef_scope->base);
3919 uint32_t async_var_index = frame_index_arg(g, fn_type->data.fn.fn_type_id.return_type);3958
3959 CalcLLVMFieldIndex arg_calc = {0};
3960 frame_index_arg_calc(g, &arg_calc, fn_type->data.fn.fn_type_id.return_type);
3920 for (size_t var_i = 0; var_i < g->cur_fn->variable_list.length; var_i += 1) {3961 for (size_t var_i = 0; var_i < g->cur_fn->variable_list.length; var_i += 1) {
3921 ZigVar *var = g->cur_fn->variable_list.at(var_i);3962 ZigVar *var = g->cur_fn->variable_list.at(var_i);
39223963
...@@ -3937,8 +3978,8 @@ static void render_async_spills(CodeGen *g) {...@@ -3937,8 +3978,8 @@ static void render_async_spills(CodeGen *g) {
3937 continue;3978 continue;
3938 }3979 }
39393980
3940 var->value_ref = LLVMBuildStructGEP(g->builder, g->cur_frame_ptr, async_var_index, var->name);3981 calc_llvm_field_index_add(g, &arg_calc, var->var_type);
3941 async_var_index += 1;3982 var->value_ref = LLVMBuildStructGEP(g->builder, g->cur_frame_ptr, arg_calc.field_index - 1, var->name);
3942 if (var->decl_node) {3983 if (var->decl_node) {
3943 var->di_loc_var = ZigLLVMCreateAutoVariable(g->dbuilder, get_di_scope(g, var->parent_scope),3984 var->di_loc_var = ZigLLVMCreateAutoVariable(g->dbuilder, get_di_scope(g, var->parent_scope),
3944 var->name, import->data.structure.root_struct->di_file,3985 var->name, import->data.structure.root_struct->di_file,
...@@ -4019,6 +4060,8 @@ static void gen_init_stack_trace(CodeGen *g, LLVMValueRef trace_field_ptr, LLVMV...@@ -4019,6 +4060,8 @@ static void gen_init_stack_trace(CodeGen *g, LLVMValueRef trace_field_ptr, LLVMV
4019}4060}
40204061
4021static LLVMValueRef ir_render_call(CodeGen *g, IrExecutableGen *executable, IrInstGenCall *instruction) {4062static LLVMValueRef ir_render_call(CodeGen *g, IrExecutableGen *executable, IrInstGenCall *instruction) {
4063 Error err;
4064
4022 LLVMTypeRef usize_type_ref = g->builtin_types.entry_usize->llvm_type;4065 LLVMTypeRef usize_type_ref = g->builtin_types.entry_usize->llvm_type;
40234066
4024 LLVMValueRef fn_val;4067 LLVMValueRef fn_val;
...@@ -4049,6 +4092,8 @@ static LLVMValueRef ir_render_call(CodeGen *g, IrExecutableGen *executable, IrIn...@@ -4049,6 +4092,8 @@ static LLVMValueRef ir_render_call(CodeGen *g, IrExecutableGen *executable, IrIn
4049 ZigList<ZigType *> gen_param_types = {};4092 ZigList<ZigType *> gen_param_types = {};
4050 LLVMValueRef result_loc = instruction->result_loc ? ir_llvm_value(g, instruction->result_loc) : nullptr;4093 LLVMValueRef result_loc = instruction->result_loc ? ir_llvm_value(g, instruction->result_loc) : nullptr;
4051 LLVMValueRef zero = LLVMConstNull(usize_type_ref);4094 LLVMValueRef zero = LLVMConstNull(usize_type_ref);
4095 bool need_frame_ptr_ptr_spill = false;
4096 ZigType *anyframe_type = nullptr;
4052 LLVMValueRef frame_result_loc_uncasted = nullptr;4097 LLVMValueRef frame_result_loc_uncasted = nullptr;
4053 LLVMValueRef frame_result_loc;4098 LLVMValueRef frame_result_loc;
4054 LLVMValueRef awaiter_init_val;4099 LLVMValueRef awaiter_init_val;
...@@ -4087,14 +4132,17 @@ static LLVMValueRef ir_render_call(CodeGen *g, IrExecutableGen *executable, IrIn...@@ -4087,14 +4132,17 @@ static LLVMValueRef ir_render_call(CodeGen *g, IrExecutableGen *executable, IrIn
40874132
4088 LLVMPositionBuilderAtEnd(g->builder, ok_block);4133 LLVMPositionBuilderAtEnd(g->builder, ok_block);
4089 }4134 }
4135 need_frame_ptr_ptr_spill = true;
4090 LLVMValueRef frame_ptr_ptr = LLVMBuildStructGEP(g->builder, frame_slice_ptr, slice_ptr_index, "");4136 LLVMValueRef frame_ptr_ptr = LLVMBuildStructGEP(g->builder, frame_slice_ptr, slice_ptr_index, "");
4091 LLVMValueRef frame_ptr = LLVMBuildLoad(g->builder, frame_ptr_ptr, "");4137 LLVMValueRef frame_ptr = LLVMBuildLoad(g->builder, frame_ptr_ptr, "");
4092 if (instruction->fn_entry == nullptr) {4138 if (instruction->fn_entry == nullptr) {
4093 ZigType *anyframe_type = get_any_frame_type(g, src_return_type);4139 anyframe_type = get_any_frame_type(g, src_return_type);
4094 frame_result_loc = LLVMBuildBitCast(g->builder, frame_ptr, get_llvm_type(g, anyframe_type), "");4140 frame_result_loc = LLVMBuildBitCast(g->builder, frame_ptr, get_llvm_type(g, anyframe_type), "");
4095 } else {4141 } else {
4096 ZigType *ptr_frame_type = get_pointer_to_type(g,4142 ZigType *frame_type = get_fn_frame_type(g, instruction->fn_entry);
4097 get_fn_frame_type(g, instruction->fn_entry), false);4143 if ((err = type_resolve(g, frame_type, ResolveStatusLLVMFull)))
4144 codegen_report_errors_and_exit(g);
4145 ZigType *ptr_frame_type = get_pointer_to_type(g, frame_type, false);
4098 frame_result_loc = LLVMBuildBitCast(g->builder, frame_ptr,4146 frame_result_loc = LLVMBuildBitCast(g->builder, frame_ptr,
4099 get_llvm_type(g, ptr_frame_type), "");4147 get_llvm_type(g, ptr_frame_type), "");
4100 }4148 }
...@@ -4261,17 +4309,35 @@ static LLVMValueRef ir_render_call(CodeGen *g, IrExecutableGen *executable, IrIn...@@ -4261,17 +4309,35 @@ static LLVMValueRef ir_render_call(CodeGen *g, IrExecutableGen *executable, IrIn
4261 LLVMValueRef result;4309 LLVMValueRef result;
42624310
4263 if (callee_is_async) {4311 if (callee_is_async) {
4264 uint32_t arg_start_i = frame_index_arg(g, fn_type->data.fn.fn_type_id.return_type);4312 CalcLLVMFieldIndex arg_calc_start = {0};
4313 frame_index_arg_calc(g, &arg_calc_start, fn_type->data.fn.fn_type_id.return_type);
42654314
4266 LLVMValueRef casted_frame;4315 LLVMValueRef casted_frame;
4267 if (instruction->new_stack != nullptr && instruction->fn_entry == nullptr) {4316 if (instruction->new_stack != nullptr && instruction->fn_entry == nullptr) {
4268 // We need the frame type to be a pointer to a struct that includes the args4317 // We need the frame type to be a pointer to a struct that includes the args
4269 size_t field_count = arg_start_i + gen_param_values.length;4318
4319 // Count ahead to determine how many llvm struct fields we need.
4320 CalcLLVMFieldIndex arg_calc = arg_calc_start;
4321 for (size_t i = 0; i < gen_param_types.length; i += 1) {
4322 calc_llvm_field_index_add(g, &arg_calc, gen_param_types.at(i));
4323 }
4324 size_t field_count = arg_calc.field_index;
4325
4270 LLVMTypeRef *field_types = allocate_nonzero<LLVMTypeRef>(field_count);4326 LLVMTypeRef *field_types = allocate_nonzero<LLVMTypeRef>(field_count);
4271 LLVMGetStructElementTypes(LLVMGetElementType(LLVMTypeOf(frame_result_loc)), field_types);4327 LLVMGetStructElementTypes(LLVMGetElementType(LLVMTypeOf(frame_result_loc)), field_types);
4272 assert(LLVMCountStructElementTypes(LLVMGetElementType(LLVMTypeOf(frame_result_loc))) == arg_start_i);4328 assert(LLVMCountStructElementTypes(LLVMGetElementType(LLVMTypeOf(frame_result_loc))) == arg_calc_start.field_index);
4329
4330 arg_calc = arg_calc_start;
4273 for (size_t arg_i = 0; arg_i < gen_param_values.length; arg_i += 1) {4331 for (size_t arg_i = 0; arg_i < gen_param_values.length; arg_i += 1) {
4274 field_types[arg_start_i + arg_i] = LLVMTypeOf(gen_param_values.at(arg_i));4332 CalcLLVMFieldIndex prev = arg_calc;
4333 calc_llvm_field_index_add(g, &arg_calc, gen_param_types.at(arg_i));
4334 field_types[arg_calc.field_index - 1] = LLVMTypeOf(gen_param_values.at(arg_i));
4335 if (arg_calc.field_index - prev.field_index > 1) {
4336 // Padding field
4337 uint32_t pad_bytes = arg_calc.offset - prev.offset - gen_param_types.at(arg_i)->abi_size;
4338 LLVMTypeRef pad_llvm_type = LLVMArrayType(LLVMInt8Type(), pad_bytes);
4339 field_types[arg_calc.field_index - 2] = pad_llvm_type;
4340 }
4275 }4341 }
4276 LLVMTypeRef frame_with_args_type = LLVMStructType(field_types, field_count, false);4342 LLVMTypeRef frame_with_args_type = LLVMStructType(field_types, field_count, false);
4277 LLVMTypeRef ptr_frame_with_args_type = LLVMPointerType(frame_with_args_type, 0);4343 LLVMTypeRef ptr_frame_with_args_type = LLVMPointerType(frame_with_args_type, 0);
...@@ -4281,8 +4347,10 @@ static LLVMValueRef ir_render_call(CodeGen *g, IrExecutableGen *executable, IrIn...@@ -4281,8 +4347,10 @@ static LLVMValueRef ir_render_call(CodeGen *g, IrExecutableGen *executable, IrIn
4281 casted_frame = frame_result_loc;4347 casted_frame = frame_result_loc;
4282 }4348 }
42834349
4350 CalcLLVMFieldIndex arg_calc = arg_calc_start;
4284 for (size_t arg_i = 0; arg_i < gen_param_values.length; arg_i += 1) {4351 for (size_t arg_i = 0; arg_i < gen_param_values.length; arg_i += 1) {
4285 LLVMValueRef arg_ptr = LLVMBuildStructGEP(g->builder, casted_frame, arg_start_i + arg_i, "");4352 calc_llvm_field_index_add(g, &arg_calc, gen_param_types.at(arg_i));
4353 LLVMValueRef arg_ptr = LLVMBuildStructGEP(g->builder, casted_frame, arg_calc.field_index - 1, "");
4286 gen_assign_raw(g, arg_ptr, get_pointer_to_type(g, gen_param_types.at(arg_i), true),4354 gen_assign_raw(g, arg_ptr, get_pointer_to_type(g, gen_param_types.at(arg_i), true),
4287 gen_param_values.at(arg_i));4355 gen_param_values.at(arg_i));
4288 }4356 }
...@@ -4345,11 +4413,19 @@ static LLVMValueRef ir_render_call(CodeGen *g, IrExecutableGen *executable, IrIn...@@ -4345,11 +4413,19 @@ static LLVMValueRef ir_render_call(CodeGen *g, IrExecutableGen *executable, IrIn
4345 }4413 }
4346 }4414 }
43474415
4348 if (frame_result_loc_uncasted != nullptr && instruction->fn_entry != nullptr) {4416 if (need_frame_ptr_ptr_spill) {
4349 // Instead of a spill, we do the bitcast again. The uncasted LLVM IR instruction will4417 LLVMValueRef frame_slice_ptr = ir_llvm_value(g, instruction->new_stack);
4350 // be an Alloca from the entry block, so it does not need to be spilled.4418 LLVMValueRef frame_ptr_ptr = LLVMBuildStructGEP(g->builder, frame_slice_ptr, slice_ptr_index, "");
4351 frame_result_loc = LLVMBuildBitCast(g->builder, frame_result_loc_uncasted,4419 frame_result_loc_uncasted = LLVMBuildLoad(g->builder, frame_ptr_ptr, "");
4352 LLVMPointerType(get_llvm_type(g, instruction->fn_entry->frame_type), 0), "");4420 }
4421 if (frame_result_loc_uncasted != nullptr) {
4422 if (instruction->fn_entry != nullptr) {
4423 frame_result_loc = LLVMBuildBitCast(g->builder, frame_result_loc_uncasted,
4424 LLVMPointerType(get_llvm_type(g, instruction->fn_entry->frame_type), 0), "");
4425 } else {
4426 frame_result_loc = LLVMBuildBitCast(g->builder, frame_result_loc_uncasted,
4427 get_llvm_type(g, anyframe_type), "");
4428 }
4353 }4429 }
43544430
4355 LLVMValueRef result_ptr = LLVMBuildStructGEP(g->builder, frame_result_loc, frame_ret_start + 2, "");4431 LLVMValueRef result_ptr = LLVMBuildStructGEP(g->builder, frame_result_loc, frame_ret_start + 2, "");
...@@ -5639,18 +5715,24 @@ static LLVMValueRef ir_render_unwrap_err_payload(CodeGen *g, IrExecutableGen *ex...@@ -5639,18 +5715,24 @@ static LLVMValueRef ir_render_unwrap_err_payload(CodeGen *g, IrExecutableGen *ex
5639 bool want_safety = instruction->safety_check_on && ir_want_runtime_safety(g, &instruction->base) &&5715 bool want_safety = instruction->safety_check_on && ir_want_runtime_safety(g, &instruction->base) &&
5640 g->errors_by_index.length > 1;5716 g->errors_by_index.length > 1;
56415717
5642 bool value_has_bits;
5643 if ((err = type_has_bits2(g, instruction->base.value->type, &value_has_bits)))
5644 codegen_report_errors_and_exit(g);
5645
5646 if (!want_safety && !value_has_bits)
5647 return nullptr;
5648
5649 ZigType *ptr_type = instruction->value->value->type;5718 ZigType *ptr_type = instruction->value->value->type;
5650 assert(ptr_type->id == ZigTypeIdPointer);5719 assert(ptr_type->id == ZigTypeIdPointer);
5651 ZigType *err_union_type = ptr_type->data.pointer.child_type;5720 ZigType *err_union_type = ptr_type->data.pointer.child_type;
5652 ZigType *payload_type = err_union_type->data.error_union.payload_type;5721 ZigType *payload_type = err_union_type->data.error_union.payload_type;
5653 LLVMValueRef err_union_ptr = ir_llvm_value(g, instruction->value);5722 LLVMValueRef err_union_ptr = ir_llvm_value(g, instruction->value);
5723
5724 LLVMValueRef zero = LLVMConstNull(get_llvm_type(g, g->err_tag_type));
5725 bool value_has_bits;
5726 if ((err = type_has_bits2(g, instruction->base.value->type, &value_has_bits)))
5727 codegen_report_errors_and_exit(g);
5728 if (!want_safety && !value_has_bits) {
5729 if (instruction->initializing) {
5730 gen_store_untyped(g, zero, err_union_ptr, 0, false);
5731 }
5732 return nullptr;
5733 }
5734
5735
5654 LLVMValueRef err_union_handle = get_handle_value(g, err_union_ptr, err_union_type, ptr_type);5736 LLVMValueRef err_union_handle = get_handle_value(g, err_union_ptr, err_union_type, ptr_type);
56555737
5656 if (!type_has_bits(err_union_type->data.error_union.err_set_type)) {5738 if (!type_has_bits(err_union_type->data.error_union.err_set_type)) {
...@@ -5665,7 +5747,6 @@ static LLVMValueRef ir_render_unwrap_err_payload(CodeGen *g, IrExecutableGen *ex...@@ -5665,7 +5747,6 @@ static LLVMValueRef ir_render_unwrap_err_payload(CodeGen *g, IrExecutableGen *ex
5665 } else {5747 } else {
5666 err_val = err_union_handle;5748 err_val = err_union_handle;
5667 }5749 }
5668 LLVMValueRef zero = LLVMConstNull(get_llvm_type(g, g->err_tag_type));
5669 LLVMValueRef cond_val = LLVMBuildICmp(g->builder, LLVMIntEQ, err_val, zero, "");5750 LLVMValueRef cond_val = LLVMBuildICmp(g->builder, LLVMIntEQ, err_val, zero, "");
5670 LLVMBasicBlockRef err_block = LLVMAppendBasicBlock(g->cur_fn_val, "UnwrapErrError");5751 LLVMBasicBlockRef err_block = LLVMAppendBasicBlock(g->cur_fn_val, "UnwrapErrError");
5671 LLVMBasicBlockRef ok_block = LLVMAppendBasicBlock(g->cur_fn_val, "UnwrapErrOk");5752 LLVMBasicBlockRef ok_block = LLVMAppendBasicBlock(g->cur_fn_val, "UnwrapErrOk");
...@@ -5685,6 +5766,9 @@ static LLVMValueRef ir_render_unwrap_err_payload(CodeGen *g, IrExecutableGen *ex...@@ -5685,6 +5766,9 @@ static LLVMValueRef ir_render_unwrap_err_payload(CodeGen *g, IrExecutableGen *ex
5685 }5766 }
5686 return LLVMBuildStructGEP(g->builder, err_union_handle, err_union_payload_index, "");5767 return LLVMBuildStructGEP(g->builder, err_union_handle, err_union_payload_index, "");
5687 } else {5768 } else {
5769 if (instruction->initializing) {
5770 gen_store_untyped(g, zero, err_union_ptr, 0, false);
5771 }
5688 return nullptr;5772 return nullptr;
5689 }5773 }
5690}5774}
...@@ -7737,7 +7821,7 @@ static void do_code_gen(CodeGen *g) {...@@ -7737,7 +7821,7 @@ static void do_code_gen(CodeGen *g) {
7737 }7821 }
7738 uint32_t trace_field_index_stack = UINT32_MAX;7822 uint32_t trace_field_index_stack = UINT32_MAX;
7739 if (codegen_fn_has_err_ret_tracing_stack(g, fn_table_entry, true)) {7823 if (codegen_fn_has_err_ret_tracing_stack(g, fn_table_entry, true)) {
7740 trace_field_index_stack = frame_index_trace_stack(g, fn_type_id);7824 trace_field_index_stack = frame_index_trace_stack(g, fn_table_entry);
7741 g->cur_err_ret_trace_val_stack = LLVMBuildStructGEP(g->builder, g->cur_frame_ptr,7825 g->cur_err_ret_trace_val_stack = LLVMBuildStructGEP(g->builder, g->cur_frame_ptr,
7742 trace_field_index_stack, "");7826 trace_field_index_stack, "");
7743 }7827 }
...@@ -8334,9 +8418,9 @@ TargetSubsystem detect_subsystem(CodeGen *g) {...@@ -8334,9 +8418,9 @@ TargetSubsystem detect_subsystem(CodeGen *g) {
8334 if (g->zig_target->os == OsWindows) {8418 if (g->zig_target->os == OsWindows) {
8335 if (g->have_dllmain_crt_startup || (g->out_type == OutTypeLib && g->is_dynamic))8419 if (g->have_dllmain_crt_startup || (g->out_type == OutTypeLib && g->is_dynamic))
8336 return TargetSubsystemAuto;8420 return TargetSubsystemAuto;
8337 if (g->have_c_main || g->is_test_build || g->have_winmain_crt_startup)8421 if (g->have_c_main || g->is_test_build || g->have_winmain_crt_startup || g->have_wwinmain_crt_startup)
8338 return TargetSubsystemConsole;8422 return TargetSubsystemConsole;
8339 if (g->have_winmain)8423 if (g->have_winmain || g->have_wwinmain)
8340 return TargetSubsystemWindows;8424 return TargetSubsystemWindows;
8341 } else if (g->zig_target->os == OsUefi) {8425 } else if (g->zig_target->os == OsUefi) {
8342 return TargetSubsystemEfiApplication;8426 return TargetSubsystemEfiApplication;
...@@ -8596,6 +8680,9 @@ Buf *codegen_generate_builtin_source(CodeGen *g) {...@@ -8596,6 +8680,9 @@ Buf *codegen_generate_builtin_source(CodeGen *g) {
8596 buf_appendf(contents,8680 buf_appendf(contents,
8597 "pub var test_functions: []TestFn = undefined; // overwritten later\n"8681 "pub var test_functions: []TestFn = undefined; // overwritten later\n"
8598 );8682 );
8683
8684 buf_appendf(contents, "pub const test_io_mode = %s;\n",
8685 g->test_is_evented ? ".evented" : ".blocking");
8599 }8686 }
86008687
8601 return contents;8688 return contents;
...@@ -8629,6 +8716,7 @@ static Error define_builtin_compile_vars(CodeGen *g) {...@@ -8629,6 +8716,7 @@ static Error define_builtin_compile_vars(CodeGen *g) {
8629 cache_bool(&cache_hash, g->is_dynamic);8716 cache_bool(&cache_hash, g->is_dynamic);
8630 cache_bool(&cache_hash, g->is_test_build);8717 cache_bool(&cache_hash, g->is_test_build);
8631 cache_bool(&cache_hash, g->is_single_threaded);8718 cache_bool(&cache_hash, g->is_single_threaded);
8719 cache_bool(&cache_hash, g->test_is_evented);
8632 cache_int(&cache_hash, g->code_model);8720 cache_int(&cache_hash, g->code_model);
8633 cache_int(&cache_hash, g->zig_target->is_native);8721 cache_int(&cache_hash, g->zig_target->is_native);
8634 cache_int(&cache_hash, g->zig_target->arch);8722 cache_int(&cache_hash, g->zig_target->arch);
...@@ -9386,22 +9474,13 @@ static void update_test_functions_builtin_decl(CodeGen *g) {...@@ -9386,22 +9474,13 @@ static void update_test_functions_builtin_decl(CodeGen *g) {
9386 for (size_t i = 0; i < g->test_fns.length; i += 1) {9474 for (size_t i = 0; i < g->test_fns.length; i += 1) {
9387 ZigFn *test_fn_entry = g->test_fns.at(i);9475 ZigFn *test_fn_entry = g->test_fns.at(i);
93889476
9389 if (fn_is_async(test_fn_entry)) {
9390 ErrorMsg *msg = add_node_error(g, test_fn_entry->proto_node,
9391 buf_create_from_str("test functions cannot be async"));
9392 add_error_note(g, msg, test_fn_entry->proto_node,
9393 buf_sprintf("this restriction may be lifted in the future. See https://github.com/ziglang/zig/issues/3117 for more details"));
9394 add_async_error_notes(g, msg, test_fn_entry);
9395 continue;
9396 }
9397
9398 ZigValue *this_val = &test_fn_array->data.x_array.data.s_none.elements[i];9477 ZigValue *this_val = &test_fn_array->data.x_array.data.s_none.elements[i];
9399 this_val->special = ConstValSpecialStatic;9478 this_val->special = ConstValSpecialStatic;
9400 this_val->type = struct_type;9479 this_val->type = struct_type;
9401 this_val->parent.id = ConstParentIdArray;9480 this_val->parent.id = ConstParentIdArray;
9402 this_val->parent.data.p_array.array_val = test_fn_array;9481 this_val->parent.data.p_array.array_val = test_fn_array;
9403 this_val->parent.data.p_array.elem_index = i;9482 this_val->parent.data.p_array.elem_index = i;
9404 this_val->data.x_struct.fields = alloc_const_vals_ptrs(2);9483 this_val->data.x_struct.fields = alloc_const_vals_ptrs(3);
94059484
9406 ZigValue *name_field = this_val->data.x_struct.fields[0];9485 ZigValue *name_field = this_val->data.x_struct.fields[0];
9407 ZigValue *name_array_val = create_const_str_lit(g, &test_fn_entry->symbol_name)->data.x_ptr.data.ref.pointee;9486 ZigValue *name_array_val = create_const_str_lit(g, &test_fn_entry->symbol_name)->data.x_ptr.data.ref.pointee;
...@@ -9413,6 +9492,19 @@ static void update_test_functions_builtin_decl(CodeGen *g) {...@@ -9413,6 +9492,19 @@ static void update_test_functions_builtin_decl(CodeGen *g) {
9413 fn_field->data.x_ptr.special = ConstPtrSpecialFunction;9492 fn_field->data.x_ptr.special = ConstPtrSpecialFunction;
9414 fn_field->data.x_ptr.mut = ConstPtrMutComptimeConst;9493 fn_field->data.x_ptr.mut = ConstPtrMutComptimeConst;
9415 fn_field->data.x_ptr.data.fn.fn_entry = test_fn_entry;9494 fn_field->data.x_ptr.data.fn.fn_entry = test_fn_entry;
9495
9496 ZigValue *frame_size_field = this_val->data.x_struct.fields[2];
9497 frame_size_field->type = get_optional_type(g, g->builtin_types.entry_usize);
9498 frame_size_field->special = ConstValSpecialStatic;
9499 frame_size_field->data.x_optional = nullptr;
9500
9501 if (fn_is_async(test_fn_entry)) {
9502 frame_size_field->data.x_optional = create_const_vals(1);
9503 frame_size_field->data.x_optional->special = ConstValSpecialStatic;
9504 frame_size_field->data.x_optional->type = g->builtin_types.entry_usize;
9505 bigint_init_unsigned(&frame_size_field->data.x_optional->data.x_bigint,
9506 test_fn_entry->frame_type->abi_size);
9507 }
9416 }9508 }
9417 report_errors_and_maybe_exit(g);9509 report_errors_and_maybe_exit(g);
94189510
...@@ -10344,6 +10436,7 @@ static Error check_cache(CodeGen *g, Buf *manifest_dir, Buf *digest) {...@@ -10344,6 +10436,7 @@ static Error check_cache(CodeGen *g, Buf *manifest_dir, Buf *digest) {
10344 if (g->is_test_build) {10436 if (g->is_test_build) {
10345 cache_buf_opt(ch, g->test_filter);10437 cache_buf_opt(ch, g->test_filter);
10346 cache_buf_opt(ch, g->test_name_prefix);10438 cache_buf_opt(ch, g->test_name_prefix);
10439 cache_bool(ch, g->test_is_evented);
10347 }10440 }
10348 cache_bool(ch, g->link_eh_frame_hdr);10441 cache_bool(ch, g->link_eh_frame_hdr);
10349 cache_bool(ch, g->is_single_threaded);10442 cache_bool(ch, g->is_single_threaded);
src/ir.cpp+47-21
...@@ -5252,6 +5252,7 @@ static IrInstSrc *ir_gen_return(IrBuilderSrc *irb, Scope *scope, AstNode *node,...@@ -5252,6 +5252,7 @@ static IrInstSrc *ir_gen_return(IrBuilderSrc *irb, Scope *scope, AstNode *node,
5252 return irb->codegen->invalid_inst_src;5252 return irb->codegen->invalid_inst_src;
5253 } else {5253 } else {
5254 return_value = ir_build_const_void(irb, scope, node);5254 return_value = ir_build_const_void(irb, scope, node);
5255 ir_build_end_expr(irb, scope, node, return_value, &result_loc_ret->base);
5255 }5256 }
52565257
5257 ir_mark_gen(ir_build_add_implicit_return_type(irb, scope, node, return_value, result_loc_ret));5258 ir_mark_gen(ir_build_add_implicit_return_type(irb, scope, node, return_value, result_loc_ret));
...@@ -5262,7 +5263,7 @@ static IrInstSrc *ir_gen_return(IrBuilderSrc *irb, Scope *scope, AstNode *node,...@@ -5262,7 +5263,7 @@ static IrInstSrc *ir_gen_return(IrBuilderSrc *irb, Scope *scope, AstNode *node,
5262 if (!have_err_defers && !irb->codegen->have_err_ret_tracing) {5263 if (!have_err_defers && !irb->codegen->have_err_ret_tracing) {
5263 // only generate unconditional defers5264 // only generate unconditional defers
5264 ir_gen_defers_for_block(irb, scope, outer_scope, false);5265 ir_gen_defers_for_block(irb, scope, outer_scope, false);
5265 IrInstSrc *result = ir_build_return_src(irb, scope, node, return_value);5266 IrInstSrc *result = ir_build_return_src(irb, scope, node, nullptr);
5266 result_loc_ret->base.source_instruction = result;5267 result_loc_ret->base.source_instruction = result;
5267 return result;5268 return result;
5268 }5269 }
...@@ -5271,10 +5272,6 @@ static IrInstSrc *ir_gen_return(IrBuilderSrc *irb, Scope *scope, AstNode *node,...@@ -5271,10 +5272,6 @@ static IrInstSrc *ir_gen_return(IrBuilderSrc *irb, Scope *scope, AstNode *node,
5271 IrBasicBlockSrc *err_block = ir_create_basic_block(irb, scope, "ErrRetErr");5272 IrBasicBlockSrc *err_block = ir_create_basic_block(irb, scope, "ErrRetErr");
5272 IrBasicBlockSrc *ok_block = ir_create_basic_block(irb, scope, "ErrRetOk");5273 IrBasicBlockSrc *ok_block = ir_create_basic_block(irb, scope, "ErrRetOk");
52735274
5274 if (!have_err_defers) {
5275 ir_gen_defers_for_block(irb, scope, outer_scope, false);
5276 }
5277
5278 IrInstSrc *is_err = ir_build_test_err_src(irb, scope, node, return_value, false, true);5275 IrInstSrc *is_err = ir_build_test_err_src(irb, scope, node, return_value, false, true);
52795276
5280 IrInstSrc *is_comptime;5277 IrInstSrc *is_comptime;
...@@ -5288,22 +5285,18 @@ static IrInstSrc *ir_gen_return(IrBuilderSrc *irb, Scope *scope, AstNode *node,...@@ -5288,22 +5285,18 @@ static IrInstSrc *ir_gen_return(IrBuilderSrc *irb, Scope *scope, AstNode *node,
5288 IrBasicBlockSrc *ret_stmt_block = ir_create_basic_block(irb, scope, "RetStmt");5285 IrBasicBlockSrc *ret_stmt_block = ir_create_basic_block(irb, scope, "RetStmt");
52895286
5290 ir_set_cursor_at_end_and_append_block(irb, err_block);5287 ir_set_cursor_at_end_and_append_block(irb, err_block);
5291 if (have_err_defers) {5288 ir_gen_defers_for_block(irb, scope, outer_scope, true);
5292 ir_gen_defers_for_block(irb, scope, outer_scope, true);
5293 }
5294 if (irb->codegen->have_err_ret_tracing && !should_inline) {5289 if (irb->codegen->have_err_ret_tracing && !should_inline) {
5295 ir_build_save_err_ret_addr_src(irb, scope, node);5290 ir_build_save_err_ret_addr_src(irb, scope, node);
5296 }5291 }
5297 ir_build_br(irb, scope, node, ret_stmt_block, is_comptime);5292 ir_build_br(irb, scope, node, ret_stmt_block, is_comptime);
52985293
5299 ir_set_cursor_at_end_and_append_block(irb, ok_block);5294 ir_set_cursor_at_end_and_append_block(irb, ok_block);
5300 if (have_err_defers) {5295 ir_gen_defers_for_block(irb, scope, outer_scope, false);
5301 ir_gen_defers_for_block(irb, scope, outer_scope, false);
5302 }
5303 ir_build_br(irb, scope, node, ret_stmt_block, is_comptime);5296 ir_build_br(irb, scope, node, ret_stmt_block, is_comptime);
53045297
5305 ir_set_cursor_at_end_and_append_block(irb, ret_stmt_block);5298 ir_set_cursor_at_end_and_append_block(irb, ret_stmt_block);
5306 IrInstSrc *result = ir_build_return_src(irb, scope, node, return_value);5299 IrInstSrc *result = ir_build_return_src(irb, scope, node, nullptr);
5307 result_loc_ret->base.source_instruction = result;5300 result_loc_ret->base.source_instruction = result;
5308 return result;5301 return result;
5309 }5302 }
...@@ -8841,7 +8834,10 @@ static IrInstSrc *ir_gen_if_optional_expr(IrBuilderSrc *irb, Scope *scope, AstNo...@@ -8841,7 +8834,10 @@ static IrInstSrc *ir_gen_if_optional_expr(IrBuilderSrc *irb, Scope *scope, AstNo
8841 AstNode *else_node = node->data.test_expr.else_node;8834 AstNode *else_node = node->data.test_expr.else_node;
8842 bool var_is_ptr = node->data.test_expr.var_is_ptr;8835 bool var_is_ptr = node->data.test_expr.var_is_ptr;
88438836
8844 IrInstSrc *maybe_val_ptr = ir_gen_node_extra(irb, expr_node, scope, LValPtr, nullptr);8837 ScopeExpr *spill_scope = create_expr_scope(irb->codegen, expr_node, scope);
8838 spill_scope->spill_harder = true;
8839
8840 IrInstSrc *maybe_val_ptr = ir_gen_node_extra(irb, expr_node, &spill_scope->base, LValPtr, nullptr);
8845 if (maybe_val_ptr == irb->codegen->invalid_inst_src)8841 if (maybe_val_ptr == irb->codegen->invalid_inst_src)
8846 return maybe_val_ptr;8842 return maybe_val_ptr;
88478843
...@@ -8866,7 +8862,7 @@ static IrInstSrc *ir_gen_if_optional_expr(IrBuilderSrc *irb, Scope *scope, AstNo...@@ -8866,7 +8862,7 @@ static IrInstSrc *ir_gen_if_optional_expr(IrBuilderSrc *irb, Scope *scope, AstNo
88668862
8867 ir_set_cursor_at_end_and_append_block(irb, then_block);8863 ir_set_cursor_at_end_and_append_block(irb, then_block);
88688864
8869 Scope *subexpr_scope = create_runtime_scope(irb->codegen, node, scope, is_comptime);8865 Scope *subexpr_scope = create_runtime_scope(irb->codegen, node, &spill_scope->base, is_comptime);
8870 Scope *var_scope;8866 Scope *var_scope;
8871 if (var_symbol) {8867 if (var_symbol) {
8872 bool is_shadowable = false;8868 bool is_shadowable = false;
...@@ -9586,7 +9582,10 @@ static IrInstSrc *ir_gen_catch(IrBuilderSrc *irb, Scope *parent_scope, AstNode *...@@ -9586,7 +9582,10 @@ static IrInstSrc *ir_gen_catch(IrBuilderSrc *irb, Scope *parent_scope, AstNode *
9586 }9582 }
95879583
95889584
9589 IrInstSrc *err_union_ptr = ir_gen_node_extra(irb, op1_node, parent_scope, LValPtr, nullptr);9585 ScopeExpr *spill_scope = create_expr_scope(irb->codegen, op1_node, parent_scope);
9586 spill_scope->spill_harder = true;
9587
9588 IrInstSrc *err_union_ptr = ir_gen_node_extra(irb, op1_node, &spill_scope->base, LValPtr, nullptr);
9590 if (err_union_ptr == irb->codegen->invalid_inst_src)9589 if (err_union_ptr == irb->codegen->invalid_inst_src)
9591 return irb->codegen->invalid_inst_src;9590 return irb->codegen->invalid_inst_src;
95929591
...@@ -9608,7 +9607,7 @@ static IrInstSrc *ir_gen_catch(IrBuilderSrc *irb, Scope *parent_scope, AstNode *...@@ -9608,7 +9607,7 @@ static IrInstSrc *ir_gen_catch(IrBuilderSrc *irb, Scope *parent_scope, AstNode *
9608 is_comptime);9607 is_comptime);
96099608
9610 ir_set_cursor_at_end_and_append_block(irb, err_block);9609 ir_set_cursor_at_end_and_append_block(irb, err_block);
9611 Scope *subexpr_scope = create_runtime_scope(irb->codegen, node, parent_scope, is_comptime);9610 Scope *subexpr_scope = create_runtime_scope(irb->codegen, node, &spill_scope->base, is_comptime);
9612 Scope *err_scope;9611 Scope *err_scope;
9613 if (var_node) {9612 if (var_node) {
9614 assert(var_node->type == NodeTypeSymbol);9613 assert(var_node->type == NodeTypeSymbol);
...@@ -11831,7 +11830,7 @@ static ConstCastOnly types_match_const_cast_only(IrAnalyze *ira, ZigType *wanted...@@ -11831,7 +11830,7 @@ static ConstCastOnly types_match_const_cast_only(IrAnalyze *ira, ZigType *wanted
11831 }11830 }
11832 assert(wanted_type->data.fn.is_generic ||11831 assert(wanted_type->data.fn.is_generic ||
11833 wanted_type->data.fn.fn_type_id.next_param_index == wanted_type->data.fn.fn_type_id.param_count);11832 wanted_type->data.fn.fn_type_id.next_param_index == wanted_type->data.fn.fn_type_id.param_count);
11834 for (size_t i = 0; i < wanted_type->data.fn.fn_type_id.next_param_index; i += 1) {11833 for (size_t i = 0; i < wanted_type->data.fn.fn_type_id.param_count; i += 1) {
11835 // note it's reversed for parameters11834 // note it's reversed for parameters
11836 FnTypeParamInfo *actual_param_info = &actual_type->data.fn.fn_type_id.param_info[i];11835 FnTypeParamInfo *actual_param_info = &actual_type->data.fn.fn_type_id.param_info[i];
11837 FnTypeParamInfo *expected_param_info = &wanted_type->data.fn.fn_type_id.param_info[i];11836 FnTypeParamInfo *expected_param_info = &wanted_type->data.fn.fn_type_id.param_info[i];
...@@ -15461,6 +15460,12 @@ static IrInstGen *ir_analyze_instruction_add_implicit_return_type(IrAnalyze *ira...@@ -15461,6 +15460,12 @@ static IrInstGen *ir_analyze_instruction_add_implicit_return_type(IrAnalyze *ira
15461}15460}
1546215461
15463static IrInstGen *ir_analyze_instruction_return(IrAnalyze *ira, IrInstSrcReturn *instruction) {15462static IrInstGen *ir_analyze_instruction_return(IrAnalyze *ira, IrInstSrcReturn *instruction) {
15463 if (instruction->operand == nullptr) {
15464 // result location mechanism took care of it.
15465 IrInstGen *result = ir_build_return_gen(ira, &instruction->base.base, nullptr);
15466 return ir_finish_anal(ira, result);
15467 }
15468
15464 IrInstGen *operand = instruction->operand->child;15469 IrInstGen *operand = instruction->operand->child;
15465 if (type_is_invalid(operand->value->type))15470 if (type_is_invalid(operand->value->type))
15466 return ir_unreach_error(ira);15471 return ir_unreach_error(ira);
...@@ -19553,6 +19558,12 @@ static IrInstGen *ir_analyze_fn_call(IrAnalyze *ira, IrInst* source_instr,...@@ -19553,6 +19558,12 @@ static IrInstGen *ir_analyze_fn_call(IrAnalyze *ira, IrInst* source_instr,
19553 if (type_is_invalid(result_loc->value->type) || result_loc->value->type->id == ZigTypeIdUnreachable) {19558 if (type_is_invalid(result_loc->value->type) || result_loc->value->type->id == ZigTypeIdUnreachable) {
19554 return result_loc;19559 return result_loc;
19555 }19560 }
19561 IrInstGen *dummy_value = ir_const(ira, source_instr, impl_fn_type_id->return_type);
19562 dummy_value->value->special = ConstValSpecialRuntime;
19563 IrInstGen *dummy_result = ir_implicit_cast2(ira, source_instr,
19564 dummy_value, result_loc->value->type->data.pointer.child_type);
19565 if (type_is_invalid(dummy_result->value->type))
19566 return ira->codegen->invalid_inst_gen;
19556 ZigType *res_child_type = result_loc->value->type->data.pointer.child_type;19567 ZigType *res_child_type = result_loc->value->type->data.pointer.child_type;
19557 if (res_child_type == ira->codegen->builtin_types.entry_var) {19568 if (res_child_type == ira->codegen->builtin_types.entry_var) {
19558 res_child_type = impl_fn_type_id->return_type;19569 res_child_type = impl_fn_type_id->return_type;
...@@ -19685,6 +19696,12 @@ static IrInstGen *ir_analyze_fn_call(IrAnalyze *ira, IrInst* source_instr,...@@ -19685,6 +19696,12 @@ static IrInstGen *ir_analyze_fn_call(IrAnalyze *ira, IrInst* source_instr,
19685 if (type_is_invalid(result_loc->value->type) || result_loc->value->type->id == ZigTypeIdUnreachable) {19696 if (type_is_invalid(result_loc->value->type) || result_loc->value->type->id == ZigTypeIdUnreachable) {
19686 return result_loc;19697 return result_loc;
19687 }19698 }
19699 IrInstGen *dummy_value = ir_const(ira, source_instr, return_type);
19700 dummy_value->value->special = ConstValSpecialRuntime;
19701 IrInstGen *dummy_result = ir_implicit_cast2(ira, source_instr,
19702 dummy_value, result_loc->value->type->data.pointer.child_type);
19703 if (type_is_invalid(dummy_result->value->type))
19704 return ira->codegen->invalid_inst_gen;
19688 ZigType *res_child_type = result_loc->value->type->data.pointer.child_type;19705 ZigType *res_child_type = result_loc->value->type->data.pointer.child_type;
19689 if (res_child_type == ira->codegen->builtin_types.entry_var) {19706 if (res_child_type == ira->codegen->builtin_types.entry_var) {
19690 res_child_type = return_type;19707 res_child_type = return_type;
...@@ -29515,8 +29532,13 @@ static IrInstGen *ir_analyze_instruction_spill_begin(IrAnalyze *ira, IrInstSrcSp...@@ -29515,8 +29532,13 @@ static IrInstGen *ir_analyze_instruction_spill_begin(IrAnalyze *ira, IrInstSrcSp
29515 if (!type_has_bits(operand->value->type))29532 if (!type_has_bits(operand->value->type))
29516 return ir_const_void(ira, &instruction->base.base);29533 return ir_const_void(ira, &instruction->base.base);
2951729534
29518 ir_assert(instruction->spill_id == SpillIdRetErrCode, &instruction->base.base);29535 switch (instruction->spill_id) {
29519 ira->new_irb.exec->need_err_code_spill = true;29536 case SpillIdInvalid:
29537 zig_unreachable();
29538 case SpillIdRetErrCode:
29539 ira->new_irb.exec->need_err_code_spill = true;
29540 break;
29541 }
2952029542
29521 return ir_build_spill_begin_gen(ira, &instruction->base.base, operand, instruction->spill_id);29543 return ir_build_spill_begin_gen(ira, &instruction->base.base, operand, instruction->spill_id);
29522}29544}
...@@ -29526,8 +29548,12 @@ static IrInstGen *ir_analyze_instruction_spill_end(IrAnalyze *ira, IrInstSrcSpil...@@ -29526,8 +29548,12 @@ static IrInstGen *ir_analyze_instruction_spill_end(IrAnalyze *ira, IrInstSrcSpil
29526 if (type_is_invalid(operand->value->type))29548 if (type_is_invalid(operand->value->type))
29527 return ira->codegen->invalid_inst_gen;29549 return ira->codegen->invalid_inst_gen;
2952829550
29529 if (ir_should_inline(ira->old_irb.exec, instruction->base.base.scope) || !type_has_bits(operand->value->type))29551 if (ir_should_inline(ira->old_irb.exec, instruction->base.base.scope) ||
29552 !type_has_bits(operand->value->type) ||
29553 instr_is_comptime(operand))
29554 {
29530 return operand;29555 return operand;
29556 }
2953129557
29532 ir_assert(instruction->begin->base.child->id == IrInstGenIdSpillBegin, &instruction->base.base);29558 ir_assert(instruction->begin->base.child->id == IrInstGenIdSpillBegin, &instruction->base.base);
29533 IrInstGenSpillBegin *begin = reinterpret_cast<IrInstGenSpillBegin *>(instruction->begin->base.child);29559 IrInstGenSpillBegin *begin = reinterpret_cast<IrInstGenSpillBegin *>(instruction->begin->base.child);
...@@ -30252,7 +30278,7 @@ static ZigType *ir_resolve_lazy_fn_type(IrAnalyze *ira, AstNode *source_node, La...@@ -30252,7 +30278,7 @@ static ZigType *ir_resolve_lazy_fn_type(IrAnalyze *ira, AstNode *source_node, La
30252 if (param_is_var_args) {30278 if (param_is_var_args) {
30253 if (fn_type_id.cc == CallingConventionC) {30279 if (fn_type_id.cc == CallingConventionC) {
30254 fn_type_id.param_count = fn_type_id.next_param_index;30280 fn_type_id.param_count = fn_type_id.next_param_index;
30255 continue;30281 break;
30256 } else if (fn_type_id.cc == CallingConventionUnspecified) {30282 } else if (fn_type_id.cc == CallingConventionUnspecified) {
30257 return get_generic_fn_type(ira->codegen, &fn_type_id);30283 return get_generic_fn_type(ira->codegen, &fn_type_id);
30258 } else {30284 } else {
src/link.cpp+4
...@@ -2210,6 +2210,10 @@ static void add_win_link_args(LinkJob *lj, bool is_library, bool *have_windows_d...@@ -2210,6 +2210,10 @@ static void add_win_link_args(LinkJob *lj, bool is_library, bool *have_windows_d
2210 if (!is_library) {2210 if (!is_library) {
2211 if (lj->codegen->have_winmain) {2211 if (lj->codegen->have_winmain) {
2212 lj->args.append("-ENTRY:WinMain");2212 lj->args.append("-ENTRY:WinMain");
2213 } else if (lj->codegen->have_wwinmain) {
2214 lj->args.append("-ENTRY:wWinMain");
2215 } else if (lj->codegen->have_wwinmain_crt_startup) {
2216 lj->args.append("-ENTRY:wWinMainCRTStartup");
2213 } else {2217 } else {
2214 lj->args.append("-ENTRY:WinMainCRTStartup");2218 lj->args.append("-ENTRY:WinMainCRTStartup");
2215 }2219 }
src/main.cpp+6
...@@ -135,6 +135,7 @@ static int print_full_usage(const char *arg0, FILE *file, int return_code) {...@@ -135,6 +135,7 @@ static int print_full_usage(const char *arg0, FILE *file, int return_code) {
135 " --test-name-prefix [text] add prefix to all tests\n"135 " --test-name-prefix [text] add prefix to all tests\n"
136 " --test-cmd [arg] specify test execution command one arg at a time\n"136 " --test-cmd [arg] specify test execution command one arg at a time\n"
137 " --test-cmd-bin appends test binary path to test cmd args\n"137 " --test-cmd-bin appends test binary path to test cmd args\n"
138 " --test-evented-io runs the test in evented I/O mode\n"
138 , arg0);139 , arg0);
139 return return_code;140 return return_code;
140}141}
...@@ -429,6 +430,7 @@ int main(int argc, char **argv) {...@@ -429,6 +430,7 @@ int main(int argc, char **argv) {
429 ZigList<CFile *> c_source_files = {0};430 ZigList<CFile *> c_source_files = {0};
430 const char *test_filter = nullptr;431 const char *test_filter = nullptr;
431 const char *test_name_prefix = nullptr;432 const char *test_name_prefix = nullptr;
433 bool test_evented_io = false;
432 size_t ver_major = 0;434 size_t ver_major = 0;
433 size_t ver_minor = 0;435 size_t ver_minor = 0;
434 size_t ver_patch = 0;436 size_t ver_patch = 0;
...@@ -710,6 +712,8 @@ int main(int argc, char **argv) {...@@ -710,6 +712,8 @@ int main(int argc, char **argv) {
710 cur_pkg = cur_pkg->parent;712 cur_pkg = cur_pkg->parent;
711 } else if (strcmp(arg, "-ffunction-sections") == 0) {713 } else if (strcmp(arg, "-ffunction-sections") == 0) {
712 function_sections = true;714 function_sections = true;
715 } else if (strcmp(arg, "--test-evented-io") == 0) {
716 test_evented_io = true;
713 } else if (i + 1 >= argc) {717 } else if (i + 1 >= argc) {
714 fprintf(stderr, "Expected another argument after %s\n", arg);718 fprintf(stderr, "Expected another argument after %s\n", arg);
715 return print_error_usage(arg0);719 return print_error_usage(arg0);
...@@ -1060,6 +1064,7 @@ int main(int argc, char **argv) {...@@ -1060,6 +1064,7 @@ int main(int argc, char **argv) {
1060 g->want_stack_check = want_stack_check;1064 g->want_stack_check = want_stack_check;
1061 g->want_sanitize_c = want_sanitize_c;1065 g->want_sanitize_c = want_sanitize_c;
1062 g->want_single_threaded = want_single_threaded;1066 g->want_single_threaded = want_single_threaded;
1067 g->test_is_evented = test_evented_io;
1063 Buf *builtin_source = codegen_generate_builtin_source(g);1068 Buf *builtin_source = codegen_generate_builtin_source(g);
1064 if (fwrite(buf_ptr(builtin_source), 1, buf_len(builtin_source), stdout) != buf_len(builtin_source)) {1069 if (fwrite(buf_ptr(builtin_source), 1, buf_len(builtin_source), stdout) != buf_len(builtin_source)) {
1065 fprintf(stderr, "unable to write to stdout: %s\n", strerror(ferror(stdout)));1070 fprintf(stderr, "unable to write to stdout: %s\n", strerror(ferror(stdout)));
...@@ -1233,6 +1238,7 @@ int main(int argc, char **argv) {...@@ -1233,6 +1238,7 @@ int main(int argc, char **argv) {
1233 if (test_filter) {1238 if (test_filter) {
1234 codegen_set_test_filter(g, buf_create_from_str(test_filter));1239 codegen_set_test_filter(g, buf_create_from_str(test_filter));
1235 }1240 }
1241 g->test_is_evented = test_evented_io;
12361242
1237 if (test_name_prefix) {1243 if (test_name_prefix) {
1238 codegen_set_test_name_prefix(g, buf_create_from_str(test_name_prefix));1244 codegen_set_test_name_prefix(g, buf_create_from_str(test_name_prefix));
src/parser.cpp+1-1
...@@ -806,7 +806,7 @@ static AstNode *ast_parse_fn_proto(ParseContext *pc) {...@@ -806,7 +806,7 @@ static AstNode *ast_parse_fn_proto(ParseContext *pc) {
806 if (param_decl->data.param_decl.is_var_args)806 if (param_decl->data.param_decl.is_var_args)
807 res->data.fn_proto.is_var_args = true;807 res->data.fn_proto.is_var_args = true;
808 if (i != params.length - 1 && res->data.fn_proto.is_var_args)808 if (i != params.length - 1 && res->data.fn_proto.is_var_args)
809 ast_error(pc, first, "Function prototype have varargs as a none last paramter.");809 ast_error(pc, first, "Function prototype have varargs as a none last parameter.");
810 }810 }
811 return res;811 return res;
812}812}
test/compile_errors.zig+35-19
...@@ -3,12 +3,47 @@ const builtin = @import("builtin");...@@ -3,12 +3,47 @@ const builtin = @import("builtin");
3const Target = @import("std").Target;3const Target = @import("std").Target;
44
5pub fn addCases(cases: *tests.CompileErrorContext) void {5pub fn addCases(cases: *tests.CompileErrorContext) void {
6 cases.addTest("type mismatch in C prototype with varargs",
7 \\const fn_ty = ?fn ([*c]u8, ...) callconv(.C) void;
8 \\extern fn fn_decl(fmt: [*:0]u8, ...) void;
9 \\
10 \\export fn main() void {
11 \\ const x: fn_ty = fn_decl;
12 \\}
13 , &[_][]const u8{
14 "tmp.zig:5:22: error: expected type 'fn([*c]u8, ...) callconv(.C) void', found 'fn([*:0]u8, ...) callconv(.C) void'",
15 });
16
6 cases.addTest("dependency loop in top-level decl with @TypeInfo",17 cases.addTest("dependency loop in top-level decl with @TypeInfo",
7 \\export const foo = @typeInfo(@This());18 \\export const foo = @typeInfo(@This());
8 , &[_][]const u8{19 , &[_][]const u8{
9 "tmp.zig:1:20: error: dependency loop detected",20 "tmp.zig:1:20: error: dependency loop detected",
10 });21 });
1122
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
12 cases.addTest("non-exhaustive enums",47 cases.addTest("non-exhaustive enums",
13 \\const A = enum {48 \\const A = enum {
14 \\ a,49 \\ a,
...@@ -5268,25 +5303,6 @@ pub fn addCases(cases: *tests.CompileErrorContext) void {...@@ -5268,25 +5303,6 @@ pub fn addCases(cases: *tests.CompileErrorContext) void {
5268 "tmp.zig:2:30: error: cannot set section of local variable 'foo'",5303 "tmp.zig:2:30: error: cannot set section of local variable 'foo'",
5269 });5304 });
52705305
5271 cases.add("returning address of local variable - simple",
5272 \\export fn foo() *i32 {
5273 \\ var a: i32 = undefined;
5274 \\ return &a;
5275 \\}
5276 , &[_][]const u8{
5277 "tmp.zig:3:13: error: function returns address of local variable",
5278 });
5279
5280 cases.add("returning address of local variable - phi",
5281 \\export fn foo(c: bool) *i32 {
5282 \\ var a: i32 = undefined;
5283 \\ var b: i32 = undefined;
5284 \\ return if (c) &a else &b;
5285 \\}
5286 , &[_][]const u8{
5287 "tmp.zig:4:12: error: function returns address of local variable",
5288 });
5289
5290 cases.add("inner struct member shadowing outer struct member",5306 cases.add("inner struct member shadowing outer struct member",
5291 \\fn A() type {5307 \\fn A() type {
5292 \\ return struct {5308 \\ return struct {
test/stage1/behavior/async_fn.zig+152
...@@ -2,6 +2,7 @@ const std = @import("std");...@@ -2,6 +2,7 @@ const std = @import("std");
2const builtin = @import("builtin");2const builtin = @import("builtin");
3const expect = std.testing.expect;3const expect = std.testing.expect;
4const expectEqual = std.testing.expectEqual;4const expectEqual = std.testing.expectEqual;
5const expectError = std.testing.expectError;
56
6var global_x: i32 = 1;7var global_x: i32 = 1;
78
...@@ -1329,3 +1330,154 @@ test "async call with @call" {...@@ -1329,3 +1330,154 @@ test "async call with @call" {
1329 };1330 };
1330 S.doTheTest();1331 S.doTheTest();
1331}1332}
1333
1334test "async function passed 0-bit arg after non-0-bit arg" {
1335 const S = struct {
1336 var global_frame: anyframe = undefined;
1337 var global_int: i32 = 0;
1338
1339 fn foo() void {
1340 bar(1, .{}) catch unreachable;
1341 }
1342
1343 fn bar(x: i32, args: var) anyerror!void {
1344 global_frame = @frame();
1345 suspend;
1346 global_int = x;
1347 }
1348 };
1349 _ = async S.foo();
1350 resume S.global_frame;
1351 expect(S.global_int == 1);
1352}
1353
1354test "async function passed align(16) arg after align(8) arg" {
1355 const S = struct {
1356 var global_frame: anyframe = undefined;
1357 var global_int: u128 = 0;
1358
1359 fn foo() void {
1360 var a: u128 = 99;
1361 bar(10, .{a}) catch unreachable;
1362 }
1363
1364 fn bar(x: u64, args: var) anyerror!void {
1365 expect(x == 10);
1366 global_frame = @frame();
1367 suspend;
1368 global_int = args[0];
1369 }
1370 };
1371 _ = async S.foo();
1372 resume S.global_frame;
1373 expect(S.global_int == 99);
1374}
1375
1376test "async function call resolves target fn frame, comptime func" {
1377 const S = struct {
1378 var global_frame: anyframe = undefined;
1379 var global_int: i32 = 9;
1380
1381 fn foo() anyerror!void {
1382 const stack_size = 1000;
1383 var stack_frame: [stack_size]u8 align(std.Target.stack_align) = undefined;
1384 return await @asyncCall(&stack_frame, {}, bar);
1385 }
1386
1387 fn bar() anyerror!void {
1388 global_frame = @frame();
1389 suspend;
1390 global_int += 1;
1391 }
1392 };
1393 _ = async S.foo();
1394 resume S.global_frame;
1395 expect(S.global_int == 10);
1396}
1397
1398test "async function call resolves target fn frame, runtime func" {
1399 const S = struct {
1400 var global_frame: anyframe = undefined;
1401 var global_int: i32 = 9;
1402
1403 fn foo() anyerror!void {
1404 const stack_size = 1000;
1405 var stack_frame: [stack_size]u8 align(std.Target.stack_align) = undefined;
1406 var func: async fn () anyerror!void = bar;
1407 return await @asyncCall(&stack_frame, {}, func);
1408 }
1409
1410 fn bar() anyerror!void {
1411 global_frame = @frame();
1412 suspend;
1413 global_int += 1;
1414 }
1415 };
1416 _ = async S.foo();
1417 resume S.global_frame;
1418 expect(S.global_int == 10);
1419}
1420
1421test "properly spill optional payload capture value" {
1422 const S = struct {
1423 var global_frame: anyframe = undefined;
1424 var global_int: usize = 2;
1425
1426 fn foo() void {
1427 var opt: ?usize = 1234;
1428 if (opt) |x| {
1429 bar();
1430 global_int += x;
1431 }
1432 }
1433
1434 fn bar() void {
1435 global_frame = @frame();
1436 suspend;
1437 global_int += 1;
1438 }
1439 };
1440 _ = async S.foo();
1441 resume S.global_frame;
1442 expect(S.global_int == 1237);
1443}
1444
1445test "handle defer interfering with return value spill" {
1446 const S = struct {
1447 var global_frame1: anyframe = undefined;
1448 var global_frame2: anyframe = undefined;
1449 var finished = false;
1450 var baz_happened = false;
1451
1452 fn doTheTest() void {
1453 _ = async testFoo();
1454 resume global_frame1;
1455 resume global_frame2;
1456 expect(baz_happened);
1457 expect(finished);
1458 }
1459
1460 fn testFoo() void {
1461 expectError(error.Bad, foo());
1462 finished = true;
1463 }
1464
1465 fn foo() anyerror!void {
1466 defer baz();
1467 return bar() catch |err| return err;
1468 }
1469
1470 fn bar() anyerror!void {
1471 global_frame1 = @frame();
1472 suspend;
1473 return error.Bad;
1474 }
1475
1476 fn baz() void {
1477 global_frame2 = @frame();
1478 suspend;
1479 baz_happened = true;
1480 }
1481 };
1482 S.doTheTest();
1483}
test/translate_c.zig+24-2
...@@ -618,6 +618,28 @@ pub fn addCases(cases: *tests.TranslateCContext) void {...@@ -618,6 +618,28 @@ pub fn addCases(cases: *tests.TranslateCContext) void {
618 },618 },
619 );619 );
620620
621 cases.add("float suffixes",
622 \\#define foo 3.14f
623 \\#define bar 16.e-2l
624 , &[_][]const u8{
625 "pub const foo = @as(f32, 3.14);",
626 "pub const bar = @as(c_longdouble, 16.e-2);",
627 });
628
629 cases.add("comments",
630 \\#define foo 1 //foo
631 \\#define bar /* bar */ 2
632 , &[_][]const u8{
633 "pub const foo = 1;",
634 "pub const bar = 2;",
635 });
636
637 cases.add("string prefix",
638 \\#define foo L"hello"
639 , &[_][]const u8{
640 "pub const foo = \"hello\";",
641 });
642
621 cases.add("null statements",643 cases.add("null statements",
622 \\void foo(void) {644 \\void foo(void) {
623 \\ ;;;;;645 \\ ;;;;;
...@@ -2508,8 +2530,8 @@ pub fn addCases(cases: *tests.TranslateCContext) void {...@@ -2508,8 +2530,8 @@ pub fn addCases(cases: *tests.TranslateCContext) void {
2508 cases.add("macro cast",2530 cases.add("macro cast",
2509 \\#define FOO(bar) baz((void *)(baz))2531 \\#define FOO(bar) baz((void *)(baz))
2510 , &[_][]const u8{2532 , &[_][]const u8{
2511 \\pub inline fn FOO(bar: var) @TypeOf(baz(if (@typeId(@TypeOf(baz)) == .Pointer) @ptrCast([*c]void, baz) else if (@typeId(@TypeOf(baz)) == .Int) @intToPtr([*c]void, baz) else @as([*c]void, baz))) {2533 \\pub inline fn FOO(bar: var) @TypeOf(baz(if (@typeId(@TypeOf(baz)) == .Pointer) @ptrCast(*c_void, baz) else if (@typeId(@TypeOf(baz)) == .Int) @intToPtr(*c_void, baz) else @as(*c_void, baz))) {
2512 \\ return baz(if (@typeId(@TypeOf(baz)) == .Pointer) @ptrCast([*c]void, baz) else if (@typeId(@TypeOf(baz)) == .Int) @intToPtr([*c]void, baz) else @as([*c]void, baz));2534 \\ return baz(if (@typeId(@TypeOf(baz)) == .Pointer) @ptrCast(*c_void, baz) else if (@typeId(@TypeOf(baz)) == .Int) @intToPtr(*c_void, baz) else @as(*c_void, baz));
2513 \\}2535 \\}
2514 });2536 });
25152537