| author | |
| committer | |
| log | cdc5070f216a924d24588b8d0fe06400e036e6bf |
| tree | c1943e1831725e41810ea4db4eb1785a130e18e1 |
| parent | 9e5b2489913f72764ded2089bccd7e612a3cc347 |
| parent | 014f66e6de4aaf81f32c796b12f981326a479397 |
| signature |
64 files changed, 3158 insertions(+), 3018 deletions(-)
CMakeLists.txt+13-1| ... | ... | @@ -2,7 +2,19 @@ cmake_minimum_required(VERSION 2.8.5) |
| 2 | 2 | |
| 3 | 3 | if(NOT CMAKE_BUILD_TYPE) |
| 4 | 4 | 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) | |
| 6 | endif() | |
| 7 | ||
| 8 | set(_list "None;Debug;Release;RelWithDebInfo;MinSizeRel") | |
| 9 | list(FIND _list ${CMAKE_BUILD_TYPE} _index) | |
| 10 | if(${_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) | |
| 6 | 18 | endif() |
| 7 | 19 | |
| 8 | 20 | if(NOT CMAKE_INSTALL_PREFIX) |
build.zig+3-4| ... | ... | @@ -73,14 +73,13 @@ pub fn build(b: *Builder) !void { |
| 73 | 73 | const skip_release_safe = b.option(bool, "skip-release-safe", "Main test suite skips release-safe builds") orelse skip_release; |
| 74 | 74 | const skip_non_native = b.option(bool, "skip-non-native", "Main test suite skips non-native builds") orelse false; |
| 75 | 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; | |
| 77 | if (!skip_self_hosted and builtin.os == .linux) { | |
| 78 | // TODO evented I/O other OS's | |
| 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) { | |
| 79 | 78 | test_step.dependOn(&exe.step); |
| 80 | 79 | } |
| 81 | 80 | |
| 82 | 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 | 83 | b.default_step.dependOn(&exe.step); |
| 85 | 84 | exe.install(); |
| 86 | 85 | } |
doc/docgen.zig+2-2| ... | ... | @@ -34,10 +34,10 @@ pub fn main() !void { |
| 34 | 34 | const out_file_name = try (args_it.next(allocator) orelse @panic("expected output arg")); |
| 35 | 35 | defer allocator.free(out_file_name); |
| 36 | 36 | |
| 37 | var in_file = try fs.File.openRead(in_file_name); | |
| 37 | var in_file = try fs.cwd().openFile(in_file_name, .{ .read = true }); | |
| 38 | 38 | defer in_file.close(); |
| 39 | 39 | |
| 40 | var out_file = try fs.File.openWrite(out_file_name); | |
| 40 | var out_file = try fs.cwd().createFile(out_file_name, .{}); | |
| 41 | 41 | defer out_file.close(); |
| 42 | 42 | |
| 43 | 43 | var file_in_stream = in_file.inStream(); |
lib/std/atomic/queue.zig+13-4| ... | ... | @@ -113,11 +113,20 @@ pub fn Queue(comptime T: type) type { |
| 113 | 113 | |
| 114 | 114 | pub fn dumpToStream(self: *Self, comptime Error: type, stream: *std.io.OutStream(Error)) Error!void { |
| 115 | 115 | const S = struct { |
| 116 | fn dumpRecursive(s: *std.io.OutStream(Error), optional_node: ?*Node, indent: usize) Error!void { | |
| 116 | fn dumpRecursive( | |
| 117 | s: *std.io.OutStream(Error), | |
| 118 | optional_node: ?*Node, | |
| 119 | indent: usize, | |
| 120 | comptime depth: comptime_int, | |
| 121 | ) Error!void { | |
| 117 | 122 | try s.writeByteNTimes(' ', indent); |
| 118 | 123 | if (optional_node) |node| { |
| 119 | 124 | try s.print("0x{x}={}\n", .{ @ptrToInt(node), node.data }); |
| 120 | try dumpRecursive(s, node.next, indent + 1); | |
| 125 | if (depth == 0) { | |
| 126 | try s.print("(max depth)\n", .{}); | |
| 127 | return; | |
| 128 | } | |
| 129 | try dumpRecursive(s, node.next, indent + 1, depth - 1); | |
| 121 | 130 | } else { |
| 122 | 131 | try s.print("(null)\n", .{}); |
| 123 | 132 | } |
| ... | ... | @@ -127,9 +136,9 @@ pub fn Queue(comptime T: type) type { |
| 127 | 136 | defer held.release(); |
| 128 | 137 | |
| 129 | 138 | try stream.print("head: ", .{}); |
| 130 | try S.dumpRecursive(stream, self.head, 0); | |
| 139 | try S.dumpRecursive(stream, self.head, 0, 4); | |
| 131 | 140 | try stream.print("tail: ", .{}); |
| 132 | try S.dumpRecursive(stream, self.tail, 0); | |
| 141 | try S.dumpRecursive(stream, self.tail, 0, 4); | |
| 133 | 142 | } |
| 134 | 143 | }; |
| 135 | 144 | } |
lib/std/build.zig+6-2| ... | ... | @@ -495,12 +495,16 @@ pub const Builder = struct { |
| 495 | 495 | |
| 496 | 496 | self.addNativeSystemIncludeDir("/usr/local/include"); |
| 497 | 497 | self.addNativeSystemLibPath("/usr/local/lib"); |
| 498 | self.addNativeSystemLibPath("/usr/local/lib64"); | |
| 498 | 499 | |
| 499 | 500 | self.addNativeSystemIncludeDir(self.fmt("/usr/include/{}", .{triple})); |
| 500 | 501 | self.addNativeSystemLibPath(self.fmt("/usr/lib/{}", .{triple})); |
| 501 | 502 | |
| 502 | 503 | self.addNativeSystemIncludeDir("/usr/include"); |
| 504 | self.addNativeSystemLibPath("/lib"); | |
| 505 | self.addNativeSystemLibPath("/lib64"); | |
| 503 | 506 | self.addNativeSystemLibPath("/usr/lib"); |
| 507 | self.addNativeSystemLibPath("/usr/lib64"); | |
| 504 | 508 | |
| 505 | 509 | // example: on a 64-bit debian-based linux distro, with zlib installed from apt: |
| 506 | 510 | // zlib.h is in /usr/include (added above) |
| ... | ... | @@ -1416,7 +1420,7 @@ pub const LibExeObjStep = struct { |
| 1416 | 1420 | self.builder.installArtifact(self); |
| 1417 | 1421 | } |
| 1418 | 1422 | |
| 1419 | pub fn installRaw(self: *LibExeObjStep, dest_filename: [] const u8) void { | |
| 1423 | pub fn installRaw(self: *LibExeObjStep, dest_filename: []const u8) void { | |
| 1420 | 1424 | self.builder.installRaw(self, dest_filename); |
| 1421 | 1425 | } |
| 1422 | 1426 | |
| ... | ... | @@ -2135,7 +2139,7 @@ pub const LibExeObjStep = struct { |
| 2135 | 2139 | try zig_args.append("-isystem"); |
| 2136 | 2140 | try zig_args.append(self.builder.pathFromRoot(include_path)); |
| 2137 | 2141 | }, |
| 2138 | .OtherStep => |other| { | |
| 2142 | .OtherStep => |other| if (!other.disable_gen_h) { | |
| 2139 | 2143 | const h_path = other.getOutputHPath(); |
| 2140 | 2144 | try zig_args.append("-isystem"); |
| 2141 | 2145 | try zig_args.append(fs.path.dirname(h_path).?); |
lib/std/builtin.zig+1| ... | ... | @@ -460,6 +460,7 @@ pub const ExportOptions = struct { |
| 460 | 460 | pub const TestFn = struct { |
| 461 | 461 | name: []const u8, |
| 462 | 462 | func: fn () anyerror!void, |
| 463 | async_frame_size: ?usize, | |
| 463 | 464 | }; |
| 464 | 465 | |
| 465 | 466 | /// 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 | 119 | pub extern "c" fn sysctl(name: [*]const c_int, namelen: c_uint, oldp: ?*c_void, oldlenp: ?*usize, newp: ?*c_void, newlen: usize) c_int; |
| 120 | 120 | pub extern "c" fn sysctlbyname(name: [*:0]const u8, oldp: ?*c_void, oldlenp: ?*usize, newp: ?*c_void, newlen: usize) c_int; |
| 121 | 121 | pub extern "c" fn sysctlnametomib(name: [*:0]const u8, mibp: ?*c_int, sizep: ?*usize) c_int; |
| 122 | pub extern "c" fn tcgetattr(fd: fd_t, termios_p: *termios) c_int; | |
| 123 | pub extern "c" fn tcsetattr(fd: fd_t, optional_action: TCSA, termios_p: *const termios) c_int; | |
| 124 | pub extern "c" fn fcntl(fd: fd_t, cmd: c_int, ...) c_int; | |
| 122 | 125 | |
| 123 | 126 | pub extern "c" fn gethostname(name: [*]u8, len: usize) c_int; |
| 124 | 127 | pub 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 | 776 | } |
| 777 | 777 | }, |
| 778 | 778 | else => { |
| 779 | self.index -= 1; | |
| 779 | 780 | state = if (string) .StringLiteral else .CharLiteral; |
| 780 | 781 | }, |
| 781 | 782 | }, |
| 782 | 783 | .HexEscape => switch (c) { |
| 783 | 784 | '0'...'9', 'a'...'f', 'A'...'F' => {}, |
| 784 | 785 | else => { |
| 786 | self.index -= 1; | |
| 785 | 787 | state = if (string) .StringLiteral else .CharLiteral; |
| 786 | 788 | }, |
| 787 | 789 | }, |
| ... | ... | @@ -797,6 +799,7 @@ pub const Tokenizer = struct { |
| 797 | 799 | result.id = .Invalid; |
| 798 | 800 | break; |
| 799 | 801 | } |
| 802 | self.index -= 1; | |
| 800 | 803 | state = if (string) .StringLiteral else .CharLiteral; |
| 801 | 804 | }, |
| 802 | 805 | }, |
| ... | ... | @@ -1046,7 +1049,6 @@ pub const Tokenizer = struct { |
| 1046 | 1049 | .LineComment => switch (c) { |
| 1047 | 1050 | '\n' => { |
| 1048 | 1051 | result.id = .LineComment; |
| 1049 | self.index += 1; | |
| 1050 | 1052 | break; |
| 1051 | 1053 | }, |
| 1052 | 1054 | else => {}, |
| ... | ... | @@ -1217,6 +1219,7 @@ pub const Tokenizer = struct { |
| 1217 | 1219 | result.id = .Invalid; |
| 1218 | 1220 | break; |
| 1219 | 1221 | } |
| 1222 | self.index -= 1; | |
| 1220 | 1223 | state = .FloatSuffix; |
| 1221 | 1224 | }, |
| 1222 | 1225 | }, |
lib/std/child_process.zig+44-15| ... | ... | @@ -329,17 +329,18 @@ pub const ChildProcess = struct { |
| 329 | 329 | } |
| 330 | 330 | |
| 331 | 331 | fn spawnPosix(self: *ChildProcess) SpawnError!void { |
| 332 | const stdin_pipe = if (self.stdin_behavior == StdIo.Pipe) try os.pipe() else undefined; | |
| 332 | const pipe_flags = if (io.is_async) os.O_NONBLOCK else 0; | |
| 333 | const stdin_pipe = if (self.stdin_behavior == StdIo.Pipe) try os.pipe2(pipe_flags) else undefined; | |
| 333 | 334 | errdefer if (self.stdin_behavior == StdIo.Pipe) { |
| 334 | 335 | destroyPipe(stdin_pipe); |
| 335 | 336 | }; |
| 336 | 337 | |
| 337 | const stdout_pipe = if (self.stdout_behavior == StdIo.Pipe) try os.pipe() else undefined; | |
| 338 | const stdout_pipe = if (self.stdout_behavior == StdIo.Pipe) try os.pipe2(pipe_flags) else undefined; | |
| 338 | 339 | errdefer if (self.stdout_behavior == StdIo.Pipe) { |
| 339 | 340 | destroyPipe(stdout_pipe); |
| 340 | 341 | }; |
| 341 | 342 | |
| 342 | const stderr_pipe = if (self.stderr_behavior == StdIo.Pipe) try os.pipe() else undefined; | |
| 343 | const stderr_pipe = if (self.stderr_behavior == StdIo.Pipe) try os.pipe2(pipe_flags) else undefined; | |
| 343 | 344 | errdefer if (self.stderr_behavior == StdIo.Pipe) { |
| 344 | 345 | destroyPipe(stderr_pipe); |
| 345 | 346 | }; |
| ... | ... | @@ -426,17 +427,26 @@ pub const ChildProcess = struct { |
| 426 | 427 | // we are the parent |
| 427 | 428 | const pid = @intCast(i32, pid_result); |
| 428 | 429 | if (self.stdin_behavior == StdIo.Pipe) { |
| 429 | self.stdin = File.openHandle(stdin_pipe[1]); | |
| 430 | self.stdin = File{ | |
| 431 | .handle = stdin_pipe[1], | |
| 432 | .io_mode = std.io.mode, | |
| 433 | }; | |
| 430 | 434 | } else { |
| 431 | 435 | self.stdin = null; |
| 432 | 436 | } |
| 433 | 437 | if (self.stdout_behavior == StdIo.Pipe) { |
| 434 | self.stdout = File.openHandle(stdout_pipe[0]); | |
| 438 | self.stdout = File{ | |
| 439 | .handle = stdout_pipe[0], | |
| 440 | .io_mode = std.io.mode, | |
| 441 | }; | |
| 435 | 442 | } else { |
| 436 | 443 | self.stdout = null; |
| 437 | 444 | } |
| 438 | 445 | if (self.stderr_behavior == StdIo.Pipe) { |
| 439 | self.stderr = File.openHandle(stderr_pipe[0]); | |
| 446 | self.stderr = File{ | |
| 447 | .handle = stderr_pipe[0], | |
| 448 | .io_mode = std.io.mode, | |
| 449 | }; | |
| 440 | 450 | } else { |
| 441 | 451 | self.stderr = null; |
| 442 | 452 | } |
| ... | ... | @@ -661,17 +671,26 @@ pub const ChildProcess = struct { |
| 661 | 671 | }; |
| 662 | 672 | |
| 663 | 673 | if (g_hChildStd_IN_Wr) |h| { |
| 664 | self.stdin = File.openHandle(h); | |
| 674 | self.stdin = File{ | |
| 675 | .handle = h, | |
| 676 | .io_mode = io.mode, | |
| 677 | }; | |
| 665 | 678 | } else { |
| 666 | 679 | self.stdin = null; |
| 667 | 680 | } |
| 668 | 681 | if (g_hChildStd_OUT_Rd) |h| { |
| 669 | self.stdout = File.openHandle(h); | |
| 682 | self.stdout = File{ | |
| 683 | .handle = h, | |
| 684 | .io_mode = io.mode, | |
| 685 | }; | |
| 670 | 686 | } else { |
| 671 | 687 | self.stdout = null; |
| 672 | 688 | } |
| 673 | 689 | if (g_hChildStd_ERR_Rd) |h| { |
| 674 | self.stderr = File.openHandle(h); | |
| 690 | self.stderr = File{ | |
| 691 | .handle = h, | |
| 692 | .io_mode = io.mode, | |
| 693 | }; | |
| 675 | 694 | } else { |
| 676 | 695 | self.stderr = null; |
| 677 | 696 | } |
| ... | ... | @@ -693,10 +712,10 @@ pub const ChildProcess = struct { |
| 693 | 712 | |
| 694 | 713 | fn setUpChildIo(stdio: StdIo, pipe_fd: i32, std_fileno: i32, dev_null_fd: i32) !void { |
| 695 | 714 | switch (stdio) { |
| 696 | StdIo.Pipe => try os.dup2(pipe_fd, std_fileno), | |
| 697 | StdIo.Close => os.close(std_fileno), | |
| 698 | StdIo.Inherit => {}, | |
| 699 | StdIo.Ignore => try os.dup2(dev_null_fd, std_fileno), | |
| 715 | .Pipe => try os.dup2(pipe_fd, std_fileno), | |
| 716 | .Close => os.close(std_fileno), | |
| 717 | .Inherit => {}, | |
| 718 | .Ignore => try os.dup2(dev_null_fd, std_fileno), | |
| 700 | 719 | } |
| 701 | 720 | } |
| 702 | 721 | }; |
| ... | ... | @@ -811,12 +830,22 @@ fn forkChildErrReport(fd: i32, err: ChildProcess.SpawnError) noreturn { |
| 811 | 830 | const ErrInt = @IntType(false, @sizeOf(anyerror) * 8); |
| 812 | 831 | |
| 813 | 832 | fn writeIntFd(fd: i32, value: ErrInt) !void { |
| 814 | const stream = &File.openHandle(fd).outStream().stream; | |
| 833 | const file = File{ | |
| 834 | .handle = fd, | |
| 835 | .io_mode = .blocking, | |
| 836 | .async_block_allowed = File.async_block_allowed_yes, | |
| 837 | }; | |
| 838 | const stream = &file.outStream().stream; | |
| 815 | 839 | stream.writeIntNative(u64, @intCast(u64, value)) catch return error.SystemResources; |
| 816 | 840 | } |
| 817 | 841 | |
| 818 | 842 | fn readIntFd(fd: i32) !ErrInt { |
| 819 | const stream = &File.openHandle(fd).inStream().stream; | |
| 843 | const file = File{ | |
| 844 | .handle = fd, | |
| 845 | .io_mode = .blocking, | |
| 846 | .async_block_allowed = File.async_block_allowed_yes, | |
| 847 | }; | |
| 848 | const stream = &file.inStream().stream; | |
| 820 | 849 | return @intCast(ErrInt, stream.readIntNative(u64) catch return error.SystemResources); |
| 821 | 850 | } |
| 822 | 851 |
lib/std/crypto/benchmark.zig+1| ... | ... | @@ -23,6 +23,7 @@ const hashes = [_]Crypto{ |
| 23 | 23 | Crypto{ .ty = crypto.Sha512, .name = "sha512" }, |
| 24 | 24 | Crypto{ .ty = crypto.Sha3_256, .name = "sha3-256" }, |
| 25 | 25 | Crypto{ .ty = crypto.Sha3_512, .name = "sha3-512" }, |
| 26 | Crypto{ .ty = crypto.gimli.Hash, .name = "gimli-hash" }, | |
| 26 | 27 | Crypto{ .ty = crypto.Blake2s256, .name = "blake2s" }, |
| 27 | 28 | Crypto{ .ty = crypto.Blake2b512, .name = "blake2b" }, |
| 28 | 29 | Crypto{ .ty = crypto.Blake3, .name = "blake3" }, |
lib/std/crypto/gimli.zig+221-1| ... | ... | @@ -19,7 +19,6 @@ pub const State = struct { |
| 19 | 19 | pub const BLOCKBYTES = 48; |
| 20 | 20 | pub const RATE = 16; |
| 21 | 21 | |
| 22 | // TODO: https://github.com/ziglang/zig/issues/2673#issuecomment-501763017 | |
| 23 | 22 | data: [BLOCKBYTES / 4]u32, |
| 24 | 23 | |
| 25 | 24 | const Self = @This(); |
| ... | ... | @@ -134,6 +133,8 @@ pub const Hash = struct { |
| 134 | 133 | } |
| 135 | 134 | } |
| 136 | 135 | |
| 136 | pub const digest_length = 32; | |
| 137 | ||
| 137 | 138 | /// Finish the current hashing operation, writing the hash to `out` |
| 138 | 139 | /// |
| 139 | 140 | /// From 4.9 "Application to hashing" |
| ... | ... | @@ -166,3 +167,222 @@ test "hash" { |
| 166 | 167 | hash(&md, &msg); |
| 167 | 168 | htest.assertEqual("1C9A03DC6A5DDC5444CFC6F4B154CFF5CF081633B2CEA4D7D0AE7CCFED5AAA44", &md); |
| 168 | 169 | } |
| 170 | ||
| 171 | pub 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 | ||
| 307 | test "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 | 50 | const held = stderr_mutex.acquire(); |
| 51 | 51 | defer held.release(); |
| 52 | 52 | const stderr = getStderrStream(); |
| 53 | stderr.print(fmt, args) catch return; | |
| 53 | noasync stderr.print(fmt, args) catch return; | |
| 54 | 54 | } |
| 55 | 55 | |
| 56 | 56 | pub fn getStderrStream() *io.OutStream(File.WriteError) { |
| ... | ... | @@ -102,15 +102,15 @@ pub fn detectTTYConfig() TTY.Config { |
| 102 | 102 | pub fn dumpCurrentStackTrace(start_addr: ?usize) void { |
| 103 | 103 | const stderr = getStderrStream(); |
| 104 | 104 | if (builtin.strip_debug_info) { |
| 105 | stderr.print("Unable to dump stack trace: debug info stripped\n", .{}) catch return; | |
| 105 | noasync stderr.print("Unable to dump stack trace: debug info stripped\n", .{}) catch return; | |
| 106 | 106 | return; |
| 107 | 107 | } |
| 108 | 108 | const debug_info = getSelfDebugInfo() catch |err| { |
| 109 | stderr.print("Unable to dump stack trace: Unable to open debug info: {}\n", .{@errorName(err)}) catch return; | |
| 109 | noasync stderr.print("Unable to dump stack trace: Unable to open debug info: {}\n", .{@errorName(err)}) catch return; | |
| 110 | 110 | return; |
| 111 | 111 | }; |
| 112 | 112 | writeCurrentStackTrace(stderr, debug_info, detectTTYConfig(), start_addr) catch |err| { |
| 113 | stderr.print("Unable to dump stack trace: {}\n", .{@errorName(err)}) catch return; | |
| 113 | noasync stderr.print("Unable to dump stack trace: {}\n", .{@errorName(err)}) catch return; | |
| 114 | 114 | return; |
| 115 | 115 | }; |
| 116 | 116 | } |
| ... | ... | @@ -121,22 +121,16 @@ pub fn dumpCurrentStackTrace(start_addr: ?usize) void { |
| 121 | 121 | pub fn dumpStackTraceFromBase(bp: usize, ip: usize) void { |
| 122 | 122 | const stderr = getStderrStream(); |
| 123 | 123 | if (builtin.strip_debug_info) { |
| 124 | stderr.print("Unable to dump stack trace: debug info stripped\n", .{}) catch return; | |
| 124 | noasync stderr.print("Unable to dump stack trace: debug info stripped\n", .{}) catch return; | |
| 125 | 125 | return; |
| 126 | 126 | } |
| 127 | 127 | const debug_info = getSelfDebugInfo() catch |err| { |
| 128 | stderr.print("Unable to dump stack trace: Unable to open debug info: {}\n", .{@errorName(err)}) catch return; | |
| 128 | noasync stderr.print("Unable to dump stack trace: Unable to open debug info: {}\n", .{@errorName(err)}) catch return; | |
| 129 | 129 | return; |
| 130 | 130 | }; |
| 131 | 131 | const tty_config = detectTTYConfig(); |
| 132 | 132 | printSourceAtAddress(debug_info, stderr, ip, tty_config) catch return; |
| 133 | const first_return_address = @intToPtr(*const usize, bp + @sizeOf(usize)).*; | |
| 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 | }; | |
| 133 | var it = StackIterator.init(null, bp); | |
| 140 | 134 | while (it.next()) |return_address| { |
| 141 | 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 | 173 | } |
| 180 | 174 | stack_trace.index = slice.len; |
| 181 | 175 | } else { |
| 182 | var it = StackIterator.init(first_address); | |
| 176 | var it = StackIterator.init(first_address, null); | |
| 183 | 177 | for (stack_trace.instruction_addresses) |*addr, i| { |
| 184 | 178 | addr.* = it.next() orelse { |
| 185 | 179 | stack_trace.index = i; |
| ... | ... | @@ -195,15 +189,15 @@ pub fn captureStackTrace(first_address: ?usize, stack_trace: *builtin.StackTrace |
| 195 | 189 | pub fn dumpStackTrace(stack_trace: builtin.StackTrace) void { |
| 196 | 190 | const stderr = getStderrStream(); |
| 197 | 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 | 193 | return; |
| 200 | 194 | } |
| 201 | 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 | 197 | return; |
| 204 | 198 | }; |
| 205 | 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 | 201 | return; |
| 208 | 202 | }; |
| 209 | 203 | } |
| ... | ... | @@ -244,7 +238,7 @@ pub fn panicExtra(trace: ?*const builtin.StackTrace, first_trace_addr: ?usize, c |
| 244 | 238 | switch (@atomicRmw(u8, &panicking, .Add, 1, .SeqCst)) { |
| 245 | 239 | 0 => { |
| 246 | 240 | const stderr = getStderrStream(); |
| 247 | stderr.print(format ++ "\n", args) catch os.abort(); | |
| 241 | noasync stderr.print(format ++ "\n", args) catch os.abort(); | |
| 248 | 242 | if (trace) |t| { |
| 249 | 243 | dumpStackTrace(t.*); |
| 250 | 244 | } |
| ... | ... | @@ -291,13 +285,15 @@ pub fn writeStackTrace( |
| 291 | 285 | } |
| 292 | 286 | |
| 293 | 287 | pub 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 | 291 | fp: usize, |
| 296 | 292 | |
| 297 | pub fn init(first_addr: ?usize) StackIterator { | |
| 293 | pub fn init(first_address: ?usize, fp: ?usize) StackIterator { | |
| 298 | 294 | return StackIterator{ |
| 299 | .first_addr = first_addr, | |
| 300 | .fp = @frameAddress(), | |
| 295 | .first_address = first_address, | |
| 296 | .fp = fp orelse @frameAddress(), | |
| 301 | 297 | }; |
| 302 | 298 | } |
| 303 | 299 | |
| ... | ... | @@ -305,29 +301,45 @@ pub const StackIterator = struct { |
| 305 | 301 | // the previous fp is stored, while on some other architectures such as |
| 306 | 302 | // RISC-V it points to the "top" of the frame, just above where the previous |
| 307 | 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 | 305 | 2 * @sizeOf(usize) |
| 310 | 306 | else |
| 311 | 307 | 0; |
| 312 | 308 | |
| 313 | 309 | fn next(self: *StackIterator) ?usize { |
| 314 | if (self.fp <= fp_adjust_factor) return null; | |
| 315 | self.fp = @intToPtr(*const usize, self.fp - fp_adjust_factor).*; | |
| 316 | if (self.fp <= fp_adjust_factor) return null; | |
| 317 | ||
| 318 | if (self.first_addr) |addr| { | |
| 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 | } | |
| 310 | var address = self.next_internal() orelse return null; | |
| 311 | ||
| 312 | if (self.first_address) |first_address| { | |
| 313 | while (address != first_address) { | |
| 314 | address = self.next_internal() orelse return null; | |
| 325 | 315 | } |
| 316 | self.first_address = null; | |
| 326 | 317 | } |
| 327 | 318 | |
| 328 | const return_address = @intToPtr(*const usize, self.fp - fp_adjust_factor + @sizeOf(usize)).*; | |
| 329 | if (return_address == 0) return null; | |
| 330 | return return_address; | |
| 319 | return address; | |
| 320 | } | |
| 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 | }; |
| 333 | 345 | |
| ... | ... | @@ -340,7 +352,7 @@ pub fn writeCurrentStackTrace( |
| 340 | 352 | if (builtin.os == .windows) { |
| 341 | 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 | 356 | while (it.next()) |return_address| { |
| 345 | 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 | 390 | return noasync printSourceAtAddressPosix(debug_info, out_stream, address, tty_config); |
| 379 | 391 | } |
| 380 | 392 | |
| 393 | /// TODO resources https://github.com/ziglang/zig/issues/4353 | |
| 381 | 394 | fn printSourceAtAddressWindows( |
| 382 | 395 | di: *DebugInfo, |
| 383 | 396 | out_stream: var, |
| ... | ... | @@ -555,12 +568,12 @@ pub const TTY = struct { |
| 555 | 568 | switch (conf) { |
| 556 | 569 | .no_color => return, |
| 557 | 570 | .escape_codes => switch (color) { |
| 558 | .Red => out_stream.write(RED) catch return, | |
| 559 | .Green => out_stream.write(GREEN) catch return, | |
| 560 | .Cyan => out_stream.write(CYAN) catch return, | |
| 561 | .White, .Bold => out_stream.write(WHITE) catch return, | |
| 562 | .Dim => out_stream.write(DIM) catch return, | |
| 563 | .Reset => out_stream.write(RESET) catch return, | |
| 571 | .Red => noasync out_stream.write(RED) catch return, | |
| 572 | .Green => noasync out_stream.write(GREEN) catch return, | |
| 573 | .Cyan => noasync out_stream.write(CYAN) catch return, | |
| 574 | .White, .Bold => noasync out_stream.write(WHITE) catch return, | |
| 575 | .Dim => noasync out_stream.write(DIM) catch return, | |
| 576 | .Reset => noasync out_stream.write(RESET) catch return, | |
| 564 | 577 | }, |
| 565 | 578 | .windows_api => if (builtin.os == .windows) { |
| 566 | 579 | const S = struct { |
| ... | ... | @@ -604,6 +617,7 @@ pub const TTY = struct { |
| 604 | 617 | }; |
| 605 | 618 | }; |
| 606 | 619 | |
| 620 | /// TODO resources https://github.com/ziglang/zig/issues/4353 | |
| 607 | 621 | fn populateModule(di: *DebugInfo, mod: *Module) !void { |
| 608 | 622 | if (mod.populated) |
| 609 | 623 | return; |
| ... | ... | @@ -715,17 +729,17 @@ fn printLineInfo( |
| 715 | 729 | tty_config.setColor(out_stream, .White); |
| 716 | 730 | |
| 717 | 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 | 733 | } else { |
| 720 | try out_stream.print("???:?:?", .{}); | |
| 734 | try noasync out_stream.write("???:?:?"); | |
| 721 | 735 | } |
| 722 | 736 | |
| 723 | 737 | tty_config.setColor(out_stream, .Reset); |
| 724 | try out_stream.write(": "); | |
| 738 | try noasync out_stream.write(": "); | |
| 725 | 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 | 741 | tty_config.setColor(out_stream, .Reset); |
| 728 | try out_stream.write("\n"); | |
| 742 | try noasync out_stream.write("\n"); | |
| 729 | 743 | |
| 730 | 744 | // Show the matching source code line if possible |
| 731 | 745 | if (line_info) |li| { |
| ... | ... | @@ -734,12 +748,12 @@ fn printLineInfo( |
| 734 | 748 | // The caret already takes one char |
| 735 | 749 | const space_needed = @intCast(usize, li.column - 1); |
| 736 | 750 | |
| 737 | try out_stream.writeByteNTimes(' ', space_needed); | |
| 751 | try noasync out_stream.writeByteNTimes(' ', space_needed); | |
| 738 | 752 | tty_config.setColor(out_stream, .Green); |
| 739 | try out_stream.write("^"); | |
| 753 | try noasync out_stream.write("^"); | |
| 740 | 754 | tty_config.setColor(out_stream, .Reset); |
| 741 | 755 | } |
| 742 | try out_stream.write("\n"); | |
| 756 | try noasync out_stream.write("\n"); | |
| 743 | 757 | } else |err| switch (err) { |
| 744 | 758 | error.EndOfFile, error.FileNotFound => {}, |
| 745 | 759 | error.BadPathName => {}, |
| ... | ... | @@ -755,6 +769,7 @@ pub const OpenSelfDebugInfoError = error{ |
| 755 | 769 | UnsupportedOperatingSystem, |
| 756 | 770 | }; |
| 757 | 771 | |
| 772 | /// TODO resources https://github.com/ziglang/zig/issues/4353 | |
| 758 | 773 | /// TODO once https://github.com/ziglang/zig/issues/3157 is fully implemented, |
| 759 | 774 | /// make this `noasync fn` and remove the individual noasync calls. |
| 760 | 775 | pub fn openSelfDebugInfo(allocator: *mem.Allocator) !DebugInfo { |
| ... | ... | @@ -963,6 +978,7 @@ pub fn openDwarfDebugInfo(di: *DwarfInfo, allocator: *mem.Allocator) !void { |
| 963 | 978 | try di.scanAllCompileUnits(); |
| 964 | 979 | } |
| 965 | 980 | |
| 981 | /// TODO resources https://github.com/ziglang/zig/issues/4353 | |
| 966 | 982 | pub fn openElfDebugInfo( |
| 967 | 983 | allocator: *mem.Allocator, |
| 968 | 984 | data: []u8, |
| ... | ... | @@ -997,12 +1013,11 @@ pub fn openElfDebugInfo( |
| 997 | 1013 | null, |
| 998 | 1014 | }; |
| 999 | 1015 | |
| 1000 | efile.close(); | |
| 1001 | ||
| 1002 | 1016 | try openDwarfDebugInfo(&di, allocator); |
| 1003 | 1017 | return di; |
| 1004 | 1018 | } |
| 1005 | 1019 | |
| 1020 | /// TODO resources https://github.com/ziglang/zig/issues/4353 | |
| 1006 | 1021 | fn openSelfDebugInfoPosix(allocator: *mem.Allocator) !DwarfInfo { |
| 1007 | 1022 | var exe_file = try fs.openSelfExe(); |
| 1008 | 1023 | errdefer exe_file.close(); |
| ... | ... | @@ -1022,6 +1037,7 @@ fn openSelfDebugInfoPosix(allocator: *mem.Allocator) !DwarfInfo { |
| 1022 | 1037 | return openElfDebugInfo(allocator, exe_mmap); |
| 1023 | 1038 | } |
| 1024 | 1039 | |
| 1040 | /// TODO resources https://github.com/ziglang/zig/issues/4353 | |
| 1025 | 1041 | fn openSelfDebugInfoMacOs(allocator: *mem.Allocator) !DebugInfo { |
| 1026 | 1042 | const hdr = &std.c._mh_execute_header; |
| 1027 | 1043 | assert(hdr.magic == std.macho.MH_MAGIC_64); |
| ... | ... | @@ -2074,6 +2090,7 @@ fn getAbbrevTableEntry(abbrev_table: *const AbbrevTable, abbrev_code: u64) ?*con |
| 2074 | 2090 | return null; |
| 2075 | 2091 | } |
| 2076 | 2092 | |
| 2093 | /// TODO resources https://github.com/ziglang/zig/issues/4353 | |
| 2077 | 2094 | fn getLineNumberInfoMacOs(di: *DebugInfo, symbol: MachoSymbol, address: usize) !LineInfo { |
| 2078 | 2095 | const ofile = symbol.ofile orelse return error.MissingDebugInfo; |
| 2079 | 2096 | const gop = try di.ofiles.getOrPut(ofile); |
| ... | ... | @@ -2239,6 +2256,7 @@ pub fn attachSegfaultHandler() void { |
| 2239 | 2256 | |
| 2240 | 2257 | os.sigaction(os.SIGSEGV, &act, null); |
| 2241 | 2258 | os.sigaction(os.SIGILL, &act, null); |
| 2259 | os.sigaction(os.SIGBUS, &act, null); | |
| 2242 | 2260 | } |
| 2243 | 2261 | |
| 2244 | 2262 | fn resetSegfaultHandler() void { |
| ... | ... | @@ -2256,6 +2274,7 @@ fn resetSegfaultHandler() void { |
| 2256 | 2274 | }; |
| 2257 | 2275 | os.sigaction(os.SIGSEGV, &act, null); |
| 2258 | 2276 | os.sigaction(os.SIGILL, &act, null); |
| 2277 | os.sigaction(os.SIGBUS, &act, null); | |
| 2259 | 2278 | } |
| 2260 | 2279 | |
| 2261 | 2280 | fn 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 | 2287 | switch (sig) { |
| 2269 | 2288 | os.SIGSEGV => std.debug.warn("Segmentation fault at address 0x{x}\n", .{addr}), |
| 2270 | 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 | 2291 | else => unreachable, |
| 2272 | 2292 | } |
| 2273 | 2293 | switch (builtin.arch) { |
lib/std/event.zig-2| ... | ... | @@ -6,11 +6,9 @@ pub const Locked = @import("event/locked.zig").Locked; |
| 6 | 6 | pub const RwLock = @import("event/rwlock.zig").RwLock; |
| 7 | 7 | pub const RwLocked = @import("event/rwlocked.zig").RwLocked; |
| 8 | 8 | pub const Loop = @import("event/loop.zig").Loop; |
| 9 | pub const fs = @import("event/fs.zig"); | |
| 10 | 9 | |
| 11 | 10 | test "import event tests" { |
| 12 | 11 | _ = @import("event/channel.zig"); |
| 13 | _ = @import("event/fs.zig"); | |
| 14 | 12 | _ = @import("event/future.zig"); |
| 15 | 13 | _ = @import("event/group.zig"); |
| 16 | 14 | _ = @import("event/lock.zig"); |
lib/std/event/channel.zig+3-4| ... | ... | @@ -267,17 +267,16 @@ pub fn Channel(comptime T: type) type { |
| 267 | 267 | } |
| 268 | 268 | |
| 269 | 269 | test "std.event.Channel" { |
| 270 | if (!std.io.is_async) return error.SkipZigTest; | |
| 271 | ||
| 270 | 272 | // https://github.com/ziglang/zig/issues/1908 |
| 271 | 273 | if (builtin.single_threaded) return error.SkipZigTest; |
| 272 | 274 | |
| 273 | 275 | // https://github.com/ziglang/zig/issues/3251 |
| 274 | 276 | if (builtin.os == .freebsd) return error.SkipZigTest; |
| 275 | 277 | |
| 276 | // TODO provide a way to run tests in evented I/O mode | |
| 277 | if (!std.io.is_async) return error.SkipZigTest; | |
| 278 | ||
| 279 | 278 | var channel: Channel(i32) = undefined; |
| 280 | channel.init([0]i32{}); | |
| 279 | channel.init(&[0]i32{}); | |
| 281 | 280 | defer channel.deinit(); |
| 282 | 281 | |
| 283 | 282 | var handle = async testChannelGetter(&channel); |
lib/std/event/fs.zig deleted-1418| ... | ... | @@ -1,1418 +0,0 @@ |
| 1 | const builtin = @import("builtin"); | |
| 2 | const std = @import("../std.zig"); | |
| 3 | const event = std.event; | |
| 4 | const assert = std.debug.assert; | |
| 5 | const testing = std.testing; | |
| 6 | const os = std.os; | |
| 7 | const mem = std.mem; | |
| 8 | const windows = os.windows; | |
| 9 | const Loop = event.Loop; | |
| 10 | const fd_t = os.fd_t; | |
| 11 | const File = std.fs.File; | |
| 12 | const Allocator = mem.Allocator; | |
| 13 | ||
| 14 | //! TODO mege this with `std.fs` | |
| 15 | ||
| 16 | const global_event_loop = Loop.instance orelse | |
| 17 | @compileError("std.event.fs currently only works with event-based I/O"); | |
| 18 | ||
| 19 | pub const RequestNode = std.atomic.Queue(Request).Node; | |
| 20 | ||
| 21 | pub const Request = struct { | |
| 22 | msg: Msg, | |
| 23 | finish: Finish, | |
| 24 | ||
| 25 | pub const Finish = union(enum) { | |
| 26 | TickNode: Loop.NextTickNode, | |
| 27 | DeallocCloseOperation: *CloseOperation, | |
| 28 | NoAction, | |
| 29 | }; | |
| 30 | ||
| 31 | pub const Msg = union(enum) { | |
| 32 | WriteV: WriteV, | |
| 33 | PWriteV: PWriteV, | |
| 34 | PReadV: PReadV, | |
| 35 | Open: Open, | |
| 36 | Close: Close, | |
| 37 | WriteFile: WriteFile, | |
| 38 | End, // special - means the fs thread should exit | |
| 39 | ||
| 40 | pub const WriteV = struct { | |
| 41 | fd: fd_t, | |
| 42 | iov: []const os.iovec_const, | |
| 43 | result: Error!void, | |
| 44 | ||
| 45 | pub const Error = os.WriteError; | |
| 46 | }; | |
| 47 | ||
| 48 | pub const PWriteV = struct { | |
| 49 | fd: fd_t, | |
| 50 | iov: []const os.iovec_const, | |
| 51 | offset: usize, | |
| 52 | result: Error!void, | |
| 53 | ||
| 54 | pub const Error = os.WriteError; | |
| 55 | }; | |
| 56 | ||
| 57 | pub const PReadV = struct { | |
| 58 | fd: fd_t, | |
| 59 | iov: []const os.iovec, | |
| 60 | offset: usize, | |
| 61 | result: Error!usize, | |
| 62 | ||
| 63 | pub const Error = os.ReadError; | |
| 64 | }; | |
| 65 | ||
| 66 | pub const Open = struct { | |
| 67 | path: [:0]const u8, | |
| 68 | flags: u32, | |
| 69 | mode: File.Mode, | |
| 70 | result: Error!fd_t, | |
| 71 | ||
| 72 | pub const Error = File.OpenError; | |
| 73 | }; | |
| 74 | ||
| 75 | pub const WriteFile = struct { | |
| 76 | path: [:0]const u8, | |
| 77 | contents: []const u8, | |
| 78 | mode: File.Mode, | |
| 79 | result: Error!void, | |
| 80 | ||
| 81 | pub const Error = File.OpenError || File.WriteError; | |
| 82 | }; | |
| 83 | ||
| 84 | pub const Close = struct { | |
| 85 | fd: fd_t, | |
| 86 | }; | |
| 87 | }; | |
| 88 | }; | |
| 89 | ||
| 90 | pub const PWriteVError = error{OutOfMemory} || File.WriteError; | |
| 91 | ||
| 92 | /// data - just the inner references - must live until pwritev frame completes. | |
| 93 | pub fn pwritev(allocator: *Allocator, fd: fd_t, data: []const []const u8, offset: usize) PWriteVError!void { | |
| 94 | switch (builtin.os) { | |
| 95 | .macosx, | |
| 96 | .linux, | |
| 97 | .freebsd, | |
| 98 | .netbsd, | |
| 99 | .dragonfly, | |
| 100 | => { | |
| 101 | const iovecs = try allocator.alloc(os.iovec_const, data.len); | |
| 102 | defer allocator.free(iovecs); | |
| 103 | ||
| 104 | for (data) |buf, i| { | |
| 105 | iovecs[i] = os.iovec_const{ | |
| 106 | .iov_base = buf.ptr, | |
| 107 | .iov_len = buf.len, | |
| 108 | }; | |
| 109 | } | |
| 110 | ||
| 111 | return pwritevPosix(fd, iovecs, offset); | |
| 112 | }, | |
| 113 | .windows => { | |
| 114 | const data_copy = try std.mem.dupe(allocator, []const u8, data); | |
| 115 | defer allocator.free(data_copy); | |
| 116 | return pwritevWindows(fd, data, offset); | |
| 117 | }, | |
| 118 | else => @compileError("Unsupported OS"), | |
| 119 | } | |
| 120 | } | |
| 121 | ||
| 122 | /// data must outlive the returned frame | |
| 123 | pub fn pwritevWindows(fd: fd_t, data: []const []const u8, offset: usize) os.WindowsWriteError!void { | |
| 124 | if (data.len == 0) return; | |
| 125 | if (data.len == 1) return pwriteWindows(fd, data[0], offset); | |
| 126 | ||
| 127 | // TODO do these in parallel | |
| 128 | var off = offset; | |
| 129 | for (data) |buf| { | |
| 130 | try pwriteWindows(fd, buf, off); | |
| 131 | off += buf.len; | |
| 132 | } | |
| 133 | } | |
| 134 | ||
| 135 | pub fn pwriteWindows(fd: fd_t, data: []const u8, offset: u64) os.WindowsWriteError!void { | |
| 136 | var resume_node = Loop.ResumeNode.Basic{ | |
| 137 | .base = Loop.ResumeNode{ | |
| 138 | .id = Loop.ResumeNode.Id.Basic, | |
| 139 | .handle = @frame(), | |
| 140 | .overlapped = windows.OVERLAPPED{ | |
| 141 | .Internal = 0, | |
| 142 | .InternalHigh = 0, | |
| 143 | .Offset = @truncate(u32, offset), | |
| 144 | .OffsetHigh = @truncate(u32, offset >> 32), | |
| 145 | .hEvent = null, | |
| 146 | }, | |
| 147 | }, | |
| 148 | }; | |
| 149 | // TODO only call create io completion port once per fd | |
| 150 | _ = windows.CreateIoCompletionPort(fd, global_event_loop.os_data.io_port, undefined, undefined); | |
| 151 | global_event_loop.beginOneEvent(); | |
| 152 | errdefer global_event_loop.finishOneEvent(); | |
| 153 | ||
| 154 | errdefer { | |
| 155 | _ = windows.kernel32.CancelIoEx(fd, &resume_node.base.overlapped); | |
| 156 | } | |
| 157 | suspend { | |
| 158 | _ = windows.kernel32.WriteFile(fd, data.ptr, @intCast(windows.DWORD, data.len), null, &resume_node.base.overlapped); | |
| 159 | } | |
| 160 | var bytes_transferred: windows.DWORD = undefined; | |
| 161 | if (windows.kernel32.GetOverlappedResult(fd, &resume_node.base.overlapped, &bytes_transferred, windows.FALSE) == 0) { | |
| 162 | switch (windows.kernel32.GetLastError()) { | |
| 163 | .IO_PENDING => unreachable, | |
| 164 | .INVALID_USER_BUFFER => return error.SystemResources, | |
| 165 | .NOT_ENOUGH_MEMORY => return error.SystemResources, | |
| 166 | .OPERATION_ABORTED => return error.OperationAborted, | |
| 167 | .NOT_ENOUGH_QUOTA => return error.SystemResources, | |
| 168 | .BROKEN_PIPE => return error.BrokenPipe, | |
| 169 | else => |err| return windows.unexpectedError(err), | |
| 170 | } | |
| 171 | } | |
| 172 | } | |
| 173 | ||
| 174 | /// iovecs must live until pwritev frame completes. | |
| 175 | pub fn pwritevPosix(fd: fd_t, iovecs: []const os.iovec_const, offset: usize) os.WriteError!void { | |
| 176 | var req_node = RequestNode{ | |
| 177 | .prev = null, | |
| 178 | .next = null, | |
| 179 | .data = Request{ | |
| 180 | .msg = Request.Msg{ | |
| 181 | .PWriteV = Request.Msg.PWriteV{ | |
| 182 | .fd = fd, | |
| 183 | .iov = iovecs, | |
| 184 | .offset = offset, | |
| 185 | .result = undefined, | |
| 186 | }, | |
| 187 | }, | |
| 188 | .finish = Request.Finish{ | |
| 189 | .TickNode = Loop.NextTickNode{ | |
| 190 | .prev = null, | |
| 191 | .next = null, | |
| 192 | .data = @frame(), | |
| 193 | }, | |
| 194 | }, | |
| 195 | }, | |
| 196 | }; | |
| 197 | ||
| 198 | errdefer global_event_loop.posixFsCancel(&req_node); | |
| 199 | ||
| 200 | suspend { | |
| 201 | global_event_loop.posixFsRequest(&req_node); | |
| 202 | } | |
| 203 | ||
| 204 | return req_node.data.msg.PWriteV.result; | |
| 205 | } | |
| 206 | ||
| 207 | /// iovecs must live until pwritev frame completes. | |
| 208 | pub fn writevPosix(fd: fd_t, iovecs: []const os.iovec_const) os.WriteError!void { | |
| 209 | var req_node = RequestNode{ | |
| 210 | .prev = null, | |
| 211 | .next = null, | |
| 212 | .data = Request{ | |
| 213 | .msg = Request.Msg{ | |
| 214 | .WriteV = Request.Msg.WriteV{ | |
| 215 | .fd = fd, | |
| 216 | .iov = iovecs, | |
| 217 | .result = undefined, | |
| 218 | }, | |
| 219 | }, | |
| 220 | .finish = Request.Finish{ | |
| 221 | .TickNode = Loop.NextTickNode{ | |
| 222 | .prev = null, | |
| 223 | .next = null, | |
| 224 | .data = @frame(), | |
| 225 | }, | |
| 226 | }, | |
| 227 | }, | |
| 228 | }; | |
| 229 | ||
| 230 | suspend { | |
| 231 | global_event_loop.posixFsRequest(&req_node); | |
| 232 | } | |
| 233 | ||
| 234 | return req_node.data.msg.WriteV.result; | |
| 235 | } | |
| 236 | ||
| 237 | pub const PReadVError = error{OutOfMemory} || File.ReadError; | |
| 238 | ||
| 239 | /// data - just the inner references - must live until preadv frame completes. | |
| 240 | pub fn preadv(allocator: *Allocator, fd: fd_t, data: []const []u8, offset: usize) PReadVError!usize { | |
| 241 | assert(data.len != 0); | |
| 242 | switch (builtin.os) { | |
| 243 | .macosx, | |
| 244 | .linux, | |
| 245 | .freebsd, | |
| 246 | .netbsd, | |
| 247 | .dragonfly, | |
| 248 | => { | |
| 249 | const iovecs = try allocator.alloc(os.iovec, data.len); | |
| 250 | defer allocator.free(iovecs); | |
| 251 | ||
| 252 | for (data) |buf, i| { | |
| 253 | iovecs[i] = os.iovec{ | |
| 254 | .iov_base = buf.ptr, | |
| 255 | .iov_len = buf.len, | |
| 256 | }; | |
| 257 | } | |
| 258 | ||
| 259 | return preadvPosix(fd, iovecs, offset); | |
| 260 | }, | |
| 261 | .windows => { | |
| 262 | const data_copy = try std.mem.dupe(allocator, []u8, data); | |
| 263 | defer allocator.free(data_copy); | |
| 264 | return preadvWindows(fd, data_copy, offset); | |
| 265 | }, | |
| 266 | else => @compileError("Unsupported OS"), | |
| 267 | } | |
| 268 | } | |
| 269 | ||
| 270 | /// data must outlive the returned frame | |
| 271 | pub fn preadvWindows(fd: fd_t, data: []const []u8, offset: u64) !usize { | |
| 272 | assert(data.len != 0); | |
| 273 | if (data.len == 1) return preadWindows(fd, data[0], offset); | |
| 274 | ||
| 275 | // TODO do these in parallel? | |
| 276 | var off: usize = 0; | |
| 277 | var iov_i: usize = 0; | |
| 278 | var inner_off: usize = 0; | |
| 279 | while (true) { | |
| 280 | const v = data[iov_i]; | |
| 281 | const amt_read = try preadWindows(fd, v[inner_off .. v.len - inner_off], offset + off); | |
| 282 | off += amt_read; | |
| 283 | inner_off += amt_read; | |
| 284 | if (inner_off == v.len) { | |
| 285 | iov_i += 1; | |
| 286 | inner_off = 0; | |
| 287 | if (iov_i == data.len) { | |
| 288 | return off; | |
| 289 | } | |
| 290 | } | |
| 291 | if (amt_read == 0) return off; // EOF | |
| 292 | } | |
| 293 | } | |
| 294 | ||
| 295 | pub fn preadWindows(fd: fd_t, data: []u8, offset: u64) !usize { | |
| 296 | var resume_node = Loop.ResumeNode.Basic{ | |
| 297 | .base = Loop.ResumeNode{ | |
| 298 | .id = Loop.ResumeNode.Id.Basic, | |
| 299 | .handle = @frame(), | |
| 300 | .overlapped = windows.OVERLAPPED{ | |
| 301 | .Internal = 0, | |
| 302 | .InternalHigh = 0, | |
| 303 | .Offset = @truncate(u32, offset), | |
| 304 | .OffsetHigh = @truncate(u32, offset >> 32), | |
| 305 | .hEvent = null, | |
| 306 | }, | |
| 307 | }, | |
| 308 | }; | |
| 309 | // TODO only call create io completion port once per fd | |
| 310 | _ = windows.CreateIoCompletionPort(fd, global_event_loop.os_data.io_port, undefined, undefined) catch undefined; | |
| 311 | global_event_loop.beginOneEvent(); | |
| 312 | errdefer global_event_loop.finishOneEvent(); | |
| 313 | ||
| 314 | errdefer { | |
| 315 | _ = windows.kernel32.CancelIoEx(fd, &resume_node.base.overlapped); | |
| 316 | } | |
| 317 | suspend { | |
| 318 | _ = windows.kernel32.ReadFile(fd, data.ptr, @intCast(windows.DWORD, data.len), null, &resume_node.base.overlapped); | |
| 319 | } | |
| 320 | var bytes_transferred: windows.DWORD = undefined; | |
| 321 | if (windows.kernel32.GetOverlappedResult(fd, &resume_node.base.overlapped, &bytes_transferred, windows.FALSE) == 0) { | |
| 322 | switch (windows.kernel32.GetLastError()) { | |
| 323 | .IO_PENDING => unreachable, | |
| 324 | .OPERATION_ABORTED => return error.OperationAborted, | |
| 325 | .BROKEN_PIPE => return error.BrokenPipe, | |
| 326 | .HANDLE_EOF => return @as(usize, bytes_transferred), | |
| 327 | else => |err| return windows.unexpectedError(err), | |
| 328 | } | |
| 329 | } | |
| 330 | return @as(usize, bytes_transferred); | |
| 331 | } | |
| 332 | ||
| 333 | /// iovecs must live until preadv frame completes | |
| 334 | pub fn preadvPosix(fd: fd_t, iovecs: []const os.iovec, offset: usize) os.ReadError!usize { | |
| 335 | var req_node = RequestNode{ | |
| 336 | .prev = null, | |
| 337 | .next = null, | |
| 338 | .data = Request{ | |
| 339 | .msg = Request.Msg{ | |
| 340 | .PReadV = Request.Msg.PReadV{ | |
| 341 | .fd = fd, | |
| 342 | .iov = iovecs, | |
| 343 | .offset = offset, | |
| 344 | .result = undefined, | |
| 345 | }, | |
| 346 | }, | |
| 347 | .finish = Request.Finish{ | |
| 348 | .TickNode = Loop.NextTickNode{ | |
| 349 | .prev = null, | |
| 350 | .next = null, | |
| 351 | .data = @frame(), | |
| 352 | }, | |
| 353 | }, | |
| 354 | }, | |
| 355 | }; | |
| 356 | ||
| 357 | errdefer global_event_loop.posixFsCancel(&req_node); | |
| 358 | ||
| 359 | suspend { | |
| 360 | global_event_loop.posixFsRequest(&req_node); | |
| 361 | } | |
| 362 | ||
| 363 | return req_node.data.msg.PReadV.result; | |
| 364 | } | |
| 365 | ||
| 366 | pub fn openPosix(path: []const u8, flags: u32, mode: File.Mode) File.OpenError!fd_t { | |
| 367 | const path_c = try std.os.toPosixPath(path); | |
| 368 | ||
| 369 | var req_node = RequestNode{ | |
| 370 | .prev = null, | |
| 371 | .next = null, | |
| 372 | .data = Request{ | |
| 373 | .msg = Request.Msg{ | |
| 374 | .Open = Request.Msg.Open{ | |
| 375 | .path = path_c[0..path.len], | |
| 376 | .flags = flags, | |
| 377 | .mode = mode, | |
| 378 | .result = undefined, | |
| 379 | }, | |
| 380 | }, | |
| 381 | .finish = Request.Finish{ | |
| 382 | .TickNode = Loop.NextTickNode{ | |
| 383 | .prev = null, | |
| 384 | .next = null, | |
| 385 | .data = @frame(), | |
| 386 | }, | |
| 387 | }, | |
| 388 | }, | |
| 389 | }; | |
| 390 | ||
| 391 | errdefer global_event_loop.posixFsCancel(&req_node); | |
| 392 | ||
| 393 | suspend { | |
| 394 | global_event_loop.posixFsRequest(&req_node); | |
| 395 | } | |
| 396 | ||
| 397 | return req_node.data.msg.Open.result; | |
| 398 | } | |
| 399 | ||
| 400 | pub fn openRead(path: []const u8) File.OpenError!fd_t { | |
| 401 | switch (builtin.os) { | |
| 402 | .macosx, .linux, .freebsd, .netbsd, .dragonfly => { | |
| 403 | const O_LARGEFILE = if (@hasDecl(os, "O_LARGEFILE")) os.O_LARGEFILE else 0; | |
| 404 | const flags = O_LARGEFILE | os.O_RDONLY | os.O_CLOEXEC; | |
| 405 | return openPosix(path, flags, File.default_mode); | |
| 406 | }, | |
| 407 | ||
| 408 | .windows => return windows.CreateFile( | |
| 409 | path, | |
| 410 | windows.GENERIC_READ, | |
| 411 | windows.FILE_SHARE_READ, | |
| 412 | null, | |
| 413 | windows.OPEN_EXISTING, | |
| 414 | windows.FILE_ATTRIBUTE_NORMAL | windows.FILE_FLAG_OVERLAPPED, | |
| 415 | null, | |
| 416 | ), | |
| 417 | ||
| 418 | else => @compileError("Unsupported OS"), | |
| 419 | } | |
| 420 | } | |
| 421 | ||
| 422 | /// Creates if does not exist. Truncates the file if it exists. | |
| 423 | /// Uses the default mode. | |
| 424 | pub fn openWrite(path: []const u8) File.OpenError!fd_t { | |
| 425 | return openWriteMode(path, File.default_mode); | |
| 426 | } | |
| 427 | ||
| 428 | /// Creates if does not exist. Truncates the file if it exists. | |
| 429 | pub fn openWriteMode(path: []const u8, mode: File.Mode) File.OpenError!fd_t { | |
| 430 | switch (builtin.os) { | |
| 431 | .macosx, | |
| 432 | .linux, | |
| 433 | .freebsd, | |
| 434 | .netbsd, | |
| 435 | .dragonfly, | |
| 436 | => { | |
| 437 | const O_LARGEFILE = if (@hasDecl(os, "O_LARGEFILE")) os.O_LARGEFILE else 0; | |
| 438 | const flags = O_LARGEFILE | os.O_WRONLY | os.O_CREAT | os.O_CLOEXEC | os.O_TRUNC; | |
| 439 | return openPosix(path, flags, File.default_mode); | |
| 440 | }, | |
| 441 | .windows => return windows.CreateFile( | |
| 442 | path, | |
| 443 | windows.GENERIC_WRITE, | |
| 444 | windows.FILE_SHARE_WRITE | windows.FILE_SHARE_READ | windows.FILE_SHARE_DELETE, | |
| 445 | null, | |
| 446 | windows.CREATE_ALWAYS, | |
| 447 | windows.FILE_ATTRIBUTE_NORMAL | windows.FILE_FLAG_OVERLAPPED, | |
| 448 | null, | |
| 449 | ), | |
| 450 | else => @compileError("Unsupported OS"), | |
| 451 | } | |
| 452 | } | |
| 453 | ||
| 454 | /// Creates if does not exist. Does not truncate. | |
| 455 | pub fn openReadWrite(path: []const u8, mode: File.Mode) File.OpenError!fd_t { | |
| 456 | switch (builtin.os) { | |
| 457 | .macosx, .linux, .freebsd, .netbsd, .dragonfly => { | |
| 458 | const O_LARGEFILE = if (@hasDecl(os, "O_LARGEFILE")) os.O_LARGEFILE else 0; | |
| 459 | const flags = O_LARGEFILE | os.O_RDWR | os.O_CREAT | os.O_CLOEXEC; | |
| 460 | return openPosix(path, flags, mode); | |
| 461 | }, | |
| 462 | ||
| 463 | .windows => return windows.CreateFile( | |
| 464 | path, | |
| 465 | windows.GENERIC_WRITE | windows.GENERIC_READ, | |
| 466 | windows.FILE_SHARE_WRITE | windows.FILE_SHARE_READ | windows.FILE_SHARE_DELETE, | |
| 467 | null, | |
| 468 | windows.OPEN_ALWAYS, | |
| 469 | windows.FILE_ATTRIBUTE_NORMAL | windows.FILE_FLAG_OVERLAPPED, | |
| 470 | null, | |
| 471 | ), | |
| 472 | ||
| 473 | else => @compileError("Unsupported OS"), | |
| 474 | } | |
| 475 | } | |
| 476 | ||
| 477 | /// This abstraction helps to close file handles in defer expressions | |
| 478 | /// without the possibility of failure and without the use of suspend points. | |
| 479 | /// Start a `CloseOperation` before opening a file, so that you can defer | |
| 480 | /// `CloseOperation.finish`. | |
| 481 | /// If you call `setHandle` then finishing will close the fd; otherwise finishing | |
| 482 | /// will deallocate the `CloseOperation`. | |
| 483 | pub const CloseOperation = struct { | |
| 484 | allocator: *Allocator, | |
| 485 | os_data: OsData, | |
| 486 | ||
| 487 | const OsData = switch (builtin.os) { | |
| 488 | .linux, .macosx, .freebsd, .netbsd, .dragonfly => OsDataPosix, | |
| 489 | ||
| 490 | .windows => struct { | |
| 491 | handle: ?fd_t, | |
| 492 | }, | |
| 493 | ||
| 494 | else => @compileError("Unsupported OS"), | |
| 495 | }; | |
| 496 | ||
| 497 | const OsDataPosix = struct { | |
| 498 | have_fd: bool, | |
| 499 | close_req_node: RequestNode, | |
| 500 | }; | |
| 501 | ||
| 502 | pub fn start(allocator: *Allocator) (error{OutOfMemory}!*CloseOperation) { | |
| 503 | const self = try allocator.create(CloseOperation); | |
| 504 | self.* = CloseOperation{ | |
| 505 | .allocator = allocator, | |
| 506 | .os_data = switch (builtin.os) { | |
| 507 | .linux, .macosx, .freebsd, .netbsd, .dragonfly => initOsDataPosix(self), | |
| 508 | .windows => OsData{ .handle = null }, | |
| 509 | else => @compileError("Unsupported OS"), | |
| 510 | }, | |
| 511 | }; | |
| 512 | return self; | |
| 513 | } | |
| 514 | ||
| 515 | fn initOsDataPosix(self: *CloseOperation) OsData { | |
| 516 | return OsData{ | |
| 517 | .have_fd = false, | |
| 518 | .close_req_node = RequestNode{ | |
| 519 | .prev = null, | |
| 520 | .next = null, | |
| 521 | .data = Request{ | |
| 522 | .msg = Request.Msg{ | |
| 523 | .Close = Request.Msg.Close{ .fd = undefined }, | |
| 524 | }, | |
| 525 | .finish = Request.Finish{ .DeallocCloseOperation = self }, | |
| 526 | }, | |
| 527 | }, | |
| 528 | }; | |
| 529 | } | |
| 530 | ||
| 531 | /// Defer this after creating. | |
| 532 | pub fn finish(self: *CloseOperation) void { | |
| 533 | switch (builtin.os) { | |
| 534 | .linux, | |
| 535 | .macosx, | |
| 536 | .freebsd, | |
| 537 | .netbsd, | |
| 538 | .dragonfly, | |
| 539 | => { | |
| 540 | if (self.os_data.have_fd) { | |
| 541 | global_event_loop.posixFsRequest(&self.os_data.close_req_node); | |
| 542 | } else { | |
| 543 | self.allocator.destroy(self); | |
| 544 | } | |
| 545 | }, | |
| 546 | .windows => { | |
| 547 | if (self.os_data.handle) |handle| { | |
| 548 | os.close(handle); | |
| 549 | } | |
| 550 | self.allocator.destroy(self); | |
| 551 | }, | |
| 552 | else => @compileError("Unsupported OS"), | |
| 553 | } | |
| 554 | } | |
| 555 | ||
| 556 | pub fn setHandle(self: *CloseOperation, handle: fd_t) void { | |
| 557 | switch (builtin.os) { | |
| 558 | .linux, | |
| 559 | .macosx, | |
| 560 | .freebsd, | |
| 561 | .netbsd, | |
| 562 | .dragonfly, | |
| 563 | => { | |
| 564 | self.os_data.close_req_node.data.msg.Close.fd = handle; | |
| 565 | self.os_data.have_fd = true; | |
| 566 | }, | |
| 567 | .windows => { | |
| 568 | self.os_data.handle = handle; | |
| 569 | }, | |
| 570 | else => @compileError("Unsupported OS"), | |
| 571 | } | |
| 572 | } | |
| 573 | ||
| 574 | /// Undo a `setHandle`. | |
| 575 | pub fn clearHandle(self: *CloseOperation) void { | |
| 576 | switch (builtin.os) { | |
| 577 | .linux, | |
| 578 | .macosx, | |
| 579 | .freebsd, | |
| 580 | .netbsd, | |
| 581 | .dragonfly, | |
| 582 | => { | |
| 583 | self.os_data.have_fd = false; | |
| 584 | }, | |
| 585 | .windows => { | |
| 586 | self.os_data.handle = null; | |
| 587 | }, | |
| 588 | else => @compileError("Unsupported OS"), | |
| 589 | } | |
| 590 | } | |
| 591 | ||
| 592 | pub fn getHandle(self: *CloseOperation) fd_t { | |
| 593 | switch (builtin.os) { | |
| 594 | .linux, | |
| 595 | .macosx, | |
| 596 | .freebsd, | |
| 597 | .netbsd, | |
| 598 | .dragonfly, | |
| 599 | => { | |
| 600 | assert(self.os_data.have_fd); | |
| 601 | return self.os_data.close_req_node.data.msg.Close.fd; | |
| 602 | }, | |
| 603 | .windows => { | |
| 604 | return self.os_data.handle.?; | |
| 605 | }, | |
| 606 | else => @compileError("Unsupported OS"), | |
| 607 | } | |
| 608 | } | |
| 609 | }; | |
| 610 | ||
| 611 | /// contents must remain alive until writeFile completes. | |
| 612 | /// TODO make this atomic or provide writeFileAtomic and rename this one to writeFileTruncate | |
| 613 | pub fn writeFile(allocator: *Allocator, path: []const u8, contents: []const u8) !void { | |
| 614 | return writeFileMode(allocator, path, contents, File.default_mode); | |
| 615 | } | |
| 616 | ||
| 617 | /// contents must remain alive until writeFile completes. | |
| 618 | pub fn writeFileMode(allocator: *Allocator, path: []const u8, contents: []const u8, mode: File.Mode) !void { | |
| 619 | switch (builtin.os) { | |
| 620 | .linux, | |
| 621 | .macosx, | |
| 622 | .freebsd, | |
| 623 | .netbsd, | |
| 624 | .dragonfly, | |
| 625 | => return writeFileModeThread(allocator, path, contents, mode), | |
| 626 | .windows => return writeFileWindows(path, contents), | |
| 627 | else => @compileError("Unsupported OS"), | |
| 628 | } | |
| 629 | } | |
| 630 | ||
| 631 | fn writeFileWindows(path: []const u8, contents: []const u8) !void { | |
| 632 | const handle = try windows.CreateFile( | |
| 633 | path, | |
| 634 | windows.GENERIC_WRITE, | |
| 635 | windows.FILE_SHARE_WRITE | windows.FILE_SHARE_READ | windows.FILE_SHARE_DELETE, | |
| 636 | null, | |
| 637 | windows.CREATE_ALWAYS, | |
| 638 | windows.FILE_ATTRIBUTE_NORMAL | windows.FILE_FLAG_OVERLAPPED, | |
| 639 | null, | |
| 640 | ); | |
| 641 | defer os.close(handle); | |
| 642 | ||
| 643 | try pwriteWindows(handle, contents, 0); | |
| 644 | } | |
| 645 | ||
| 646 | fn writeFileModeThread(allocator: *Allocator, path: []const u8, contents: []const u8, mode: File.Mode) !void { | |
| 647 | const path_with_null = try std.cstr.addNullByte(allocator, path); | |
| 648 | defer allocator.free(path_with_null); | |
| 649 | ||
| 650 | var req_node = RequestNode{ | |
| 651 | .prev = null, | |
| 652 | .next = null, | |
| 653 | .data = Request{ | |
| 654 | .msg = Request.Msg{ | |
| 655 | .WriteFile = Request.Msg.WriteFile{ | |
| 656 | .path = path_with_null[0..path.len], | |
| 657 | .contents = contents, | |
| 658 | .mode = mode, | |
| 659 | .result = undefined, | |
| 660 | }, | |
| 661 | }, | |
| 662 | .finish = Request.Finish{ | |
| 663 | .TickNode = Loop.NextTickNode{ | |
| 664 | .prev = null, | |
| 665 | .next = null, | |
| 666 | .data = @frame(), | |
| 667 | }, | |
| 668 | }, | |
| 669 | }, | |
| 670 | }; | |
| 671 | ||
| 672 | errdefer global_event_loop.posixFsCancel(&req_node); | |
| 673 | ||
| 674 | suspend { | |
| 675 | global_event_loop.posixFsRequest(&req_node); | |
| 676 | } | |
| 677 | ||
| 678 | return req_node.data.msg.WriteFile.result; | |
| 679 | } | |
| 680 | ||
| 681 | /// The frame resumes when the last data has been confirmed written, but before the file handle | |
| 682 | /// is closed. | |
| 683 | /// Caller owns returned memory. | |
| 684 | pub fn readFile(allocator: *Allocator, file_path: []const u8, max_size: usize) ![]u8 { | |
| 685 | var close_op = try CloseOperation.start(allocator); | |
| 686 | defer close_op.finish(); | |
| 687 | ||
| 688 | const fd = try openRead(file_path); | |
| 689 | close_op.setHandle(fd); | |
| 690 | ||
| 691 | var list = std.ArrayList(u8).init(allocator); | |
| 692 | defer list.deinit(); | |
| 693 | ||
| 694 | while (true) { | |
| 695 | try list.ensureCapacity(list.len + mem.page_size); | |
| 696 | const buf = list.items[list.len..]; | |
| 697 | const buf_array = [_][]u8{buf}; | |
| 698 | const amt = try preadv(allocator, fd, &buf_array, list.len); | |
| 699 | list.len += amt; | |
| 700 | if (list.len > max_size) { | |
| 701 | return error.FileTooBig; | |
| 702 | } | |
| 703 | if (amt < buf.len) { | |
| 704 | return list.toOwnedSlice(); | |
| 705 | } | |
| 706 | } | |
| 707 | } | |
| 708 | ||
| 709 | pub const WatchEventId = enum { | |
| 710 | CloseWrite, | |
| 711 | Delete, | |
| 712 | }; | |
| 713 | ||
| 714 | fn eqlString(a: []const u16, b: []const u16) bool { | |
| 715 | if (a.len != b.len) return false; | |
| 716 | if (a.ptr == b.ptr) return true; | |
| 717 | return mem.compare(u16, a, b) == .Equal; | |
| 718 | } | |
| 719 | ||
| 720 | fn hashString(s: []const u16) u32 { | |
| 721 | return @truncate(u32, std.hash.Wyhash.hash(0, @sliceToBytes(s))); | |
| 722 | } | |
| 723 | ||
| 724 | pub const WatchEventError = error{ | |
| 725 | UserResourceLimitReached, | |
| 726 | SystemResources, | |
| 727 | AccessDenied, | |
| 728 | Unexpected, // TODO remove this possibility | |
| 729 | }; | |
| 730 | ||
| 731 | pub fn Watch(comptime V: type) type { | |
| 732 | return struct { | |
| 733 | channel: *event.Channel(Event.Error!Event), | |
| 734 | os_data: OsData, | |
| 735 | allocator: *Allocator, | |
| 736 | ||
| 737 | const OsData = switch (builtin.os) { | |
| 738 | // TODO https://github.com/ziglang/zig/issues/3778 | |
| 739 | .macosx, .freebsd, .netbsd, .dragonfly => KqOsData, | |
| 740 | .linux => LinuxOsData, | |
| 741 | .windows => WindowsOsData, | |
| 742 | ||
| 743 | else => @compileError("Unsupported OS"), | |
| 744 | }; | |
| 745 | ||
| 746 | const KqOsData = struct { | |
| 747 | file_table: FileTable, | |
| 748 | table_lock: event.Lock, | |
| 749 | ||
| 750 | const FileTable = std.StringHashMap(*Put); | |
| 751 | const Put = struct { | |
| 752 | putter_frame: @Frame(kqPutEvents), | |
| 753 | cancelled: bool = false, | |
| 754 | value: V, | |
| 755 | }; | |
| 756 | }; | |
| 757 | ||
| 758 | const WindowsOsData = struct { | |
| 759 | table_lock: event.Lock, | |
| 760 | dir_table: DirTable, | |
| 761 | all_putters: std.atomic.Queue(Put), | |
| 762 | ref_count: std.atomic.Int(usize), | |
| 763 | ||
| 764 | const Put = struct { | |
| 765 | putter: anyframe, | |
| 766 | cancelled: bool = false, | |
| 767 | }; | |
| 768 | ||
| 769 | const DirTable = std.StringHashMap(*Dir); | |
| 770 | const FileTable = std.HashMap([]const u16, V, hashString, eqlString); | |
| 771 | ||
| 772 | const Dir = struct { | |
| 773 | putter_frame: @Frame(windowsDirReader), | |
| 774 | file_table: FileTable, | |
| 775 | table_lock: event.Lock, | |
| 776 | }; | |
| 777 | }; | |
| 778 | ||
| 779 | const LinuxOsData = struct { | |
| 780 | putter_frame: @Frame(linuxEventPutter), | |
| 781 | inotify_fd: i32, | |
| 782 | wd_table: WdTable, | |
| 783 | table_lock: event.Lock, | |
| 784 | cancelled: bool = false, | |
| 785 | ||
| 786 | const WdTable = std.AutoHashMap(i32, Dir); | |
| 787 | const FileTable = std.StringHashMap(V); | |
| 788 | ||
| 789 | const Dir = struct { | |
| 790 | dirname: []const u8, | |
| 791 | file_table: FileTable, | |
| 792 | }; | |
| 793 | }; | |
| 794 | ||
| 795 | const Self = @This(); | |
| 796 | ||
| 797 | pub const Event = struct { | |
| 798 | id: Id, | |
| 799 | data: V, | |
| 800 | ||
| 801 | pub const Id = WatchEventId; | |
| 802 | pub const Error = WatchEventError; | |
| 803 | }; | |
| 804 | ||
| 805 | pub fn init(allocator: *Allocator, event_buf_count: usize) !*Self { | |
| 806 | const channel = try allocator.create(event.Channel(Event.Error!Event)); | |
| 807 | errdefer allocator.destroy(channel); | |
| 808 | var buf = try allocator.alloc(Event.Error!Event, event_buf_count); | |
| 809 | errdefer allocator.free(buf); | |
| 810 | channel.init(buf); | |
| 811 | errdefer channel.deinit(); | |
| 812 | ||
| 813 | const self = try allocator.create(Self); | |
| 814 | errdefer allocator.destroy(self); | |
| 815 | ||
| 816 | switch (builtin.os) { | |
| 817 | .linux => { | |
| 818 | const inotify_fd = try os.inotify_init1(os.linux.IN_NONBLOCK | os.linux.IN_CLOEXEC); | |
| 819 | errdefer os.close(inotify_fd); | |
| 820 | ||
| 821 | self.* = Self{ | |
| 822 | .allocator = allocator, | |
| 823 | .channel = channel, | |
| 824 | .os_data = OsData{ | |
| 825 | .putter_frame = undefined, | |
| 826 | .inotify_fd = inotify_fd, | |
| 827 | .wd_table = OsData.WdTable.init(allocator), | |
| 828 | .table_lock = event.Lock.init(), | |
| 829 | }, | |
| 830 | }; | |
| 831 | ||
| 832 | self.os_data.putter_frame = async self.linuxEventPutter(); | |
| 833 | return self; | |
| 834 | }, | |
| 835 | ||
| 836 | .windows => { | |
| 837 | self.* = Self{ | |
| 838 | .allocator = allocator, | |
| 839 | .channel = channel, | |
| 840 | .os_data = OsData{ | |
| 841 | .table_lock = event.Lock.init(), | |
| 842 | .dir_table = OsData.DirTable.init(allocator), | |
| 843 | .ref_count = std.atomic.Int(usize).init(1), | |
| 844 | .all_putters = std.atomic.Queue(anyframe).init(), | |
| 845 | }, | |
| 846 | }; | |
| 847 | return self; | |
| 848 | }, | |
| 849 | ||
| 850 | .macosx, .freebsd, .netbsd, .dragonfly => { | |
| 851 | self.* = Self{ | |
| 852 | .allocator = allocator, | |
| 853 | .channel = channel, | |
| 854 | .os_data = OsData{ | |
| 855 | .table_lock = event.Lock.init(), | |
| 856 | .file_table = OsData.FileTable.init(allocator), | |
| 857 | }, | |
| 858 | }; | |
| 859 | return self; | |
| 860 | }, | |
| 861 | else => @compileError("Unsupported OS"), | |
| 862 | } | |
| 863 | } | |
| 864 | ||
| 865 | /// All addFile calls and removeFile calls must have completed. | |
| 866 | pub fn deinit(self: *Self) void { | |
| 867 | switch (builtin.os) { | |
| 868 | .macosx, .freebsd, .netbsd, .dragonfly => { | |
| 869 | // TODO we need to cancel the frames before destroying the lock | |
| 870 | self.os_data.table_lock.deinit(); | |
| 871 | var it = self.os_data.file_table.iterator(); | |
| 872 | while (it.next()) |entry| { | |
| 873 | entry.cancelled = true; | |
| 874 | await entry.value.putter; | |
| 875 | self.allocator.free(entry.key); | |
| 876 | self.allocator.free(entry.value); | |
| 877 | } | |
| 878 | self.channel.deinit(); | |
| 879 | self.allocator.destroy(self.channel.buffer_nodes); | |
| 880 | self.allocator.destroy(self); | |
| 881 | }, | |
| 882 | .linux => { | |
| 883 | self.os_data.cancelled = true; | |
| 884 | await self.os_data.putter_frame; | |
| 885 | self.allocator.destroy(self); | |
| 886 | }, | |
| 887 | .windows => { | |
| 888 | while (self.os_data.all_putters.get()) |putter_node| { | |
| 889 | putter_node.cancelled = true; | |
| 890 | await putter_node.frame; | |
| 891 | } | |
| 892 | self.deref(); | |
| 893 | }, | |
| 894 | else => @compileError("Unsupported OS"), | |
| 895 | } | |
| 896 | } | |
| 897 | ||
| 898 | fn ref(self: *Self) void { | |
| 899 | _ = self.os_data.ref_count.incr(); | |
| 900 | } | |
| 901 | ||
| 902 | fn deref(self: *Self) void { | |
| 903 | if (self.os_data.ref_count.decr() == 1) { | |
| 904 | self.os_data.table_lock.deinit(); | |
| 905 | var it = self.os_data.dir_table.iterator(); | |
| 906 | while (it.next()) |entry| { | |
| 907 | self.allocator.free(entry.key); | |
| 908 | self.allocator.destroy(entry.value); | |
| 909 | } | |
| 910 | self.os_data.dir_table.deinit(); | |
| 911 | self.channel.deinit(); | |
| 912 | self.allocator.destroy(self.channel.buffer_nodes); | |
| 913 | self.allocator.destroy(self); | |
| 914 | } | |
| 915 | } | |
| 916 | ||
| 917 | pub fn addFile(self: *Self, file_path: []const u8, value: V) !?V { | |
| 918 | switch (builtin.os) { | |
| 919 | .macosx, .freebsd, .netbsd, .dragonfly => return addFileKEvent(self, file_path, value), | |
| 920 | .linux => return addFileLinux(self, file_path, value), | |
| 921 | .windows => return addFileWindows(self, file_path, value), | |
| 922 | else => @compileError("Unsupported OS"), | |
| 923 | } | |
| 924 | } | |
| 925 | ||
| 926 | fn addFileKEvent(self: *Self, file_path: []const u8, value: V) !?V { | |
| 927 | const resolved_path = try std.fs.path.resolve(self.allocator, [_][]const u8{file_path}); | |
| 928 | var resolved_path_consumed = false; | |
| 929 | defer if (!resolved_path_consumed) self.allocator.free(resolved_path); | |
| 930 | ||
| 931 | var close_op = try CloseOperation.start(self.allocator); | |
| 932 | var close_op_consumed = false; | |
| 933 | defer if (!close_op_consumed) close_op.finish(); | |
| 934 | ||
| 935 | const flags = if (comptime std.Target.current.isDarwin()) os.O_SYMLINK | os.O_EVTONLY else 0; | |
| 936 | const mode = 0; | |
| 937 | const fd = try openPosix(self.allocator, resolved_path, flags, mode); | |
| 938 | close_op.setHandle(fd); | |
| 939 | ||
| 940 | var put = try self.allocator.create(OsData.Put); | |
| 941 | errdefer self.allocator.destroy(put); | |
| 942 | put.* = OsData.Put{ | |
| 943 | .value = value, | |
| 944 | .putter_frame = undefined, | |
| 945 | }; | |
| 946 | put.putter_frame = async self.kqPutEvents(close_op, put); | |
| 947 | close_op_consumed = true; | |
| 948 | errdefer { | |
| 949 | put.cancelled = true; | |
| 950 | await put.putter_frame; | |
| 951 | } | |
| 952 | ||
| 953 | const result = blk: { | |
| 954 | const held = self.os_data.table_lock.acquire(); | |
| 955 | defer held.release(); | |
| 956 | ||
| 957 | const gop = try self.os_data.file_table.getOrPut(resolved_path); | |
| 958 | if (gop.found_existing) { | |
| 959 | const prev_value = gop.kv.value.value; | |
| 960 | await gop.kv.value.putter_frame; | |
| 961 | gop.kv.value = put; | |
| 962 | break :blk prev_value; | |
| 963 | } else { | |
| 964 | resolved_path_consumed = true; | |
| 965 | gop.kv.value = put; | |
| 966 | break :blk null; | |
| 967 | } | |
| 968 | }; | |
| 969 | ||
| 970 | return result; | |
| 971 | } | |
| 972 | ||
| 973 | fn kqPutEvents(self: *Self, close_op: *CloseOperation, put: *OsData.Put) void { | |
| 974 | global_event_loop.beginOneEvent(); | |
| 975 | ||
| 976 | defer { | |
| 977 | close_op.finish(); | |
| 978 | global_event_loop.finishOneEvent(); | |
| 979 | } | |
| 980 | ||
| 981 | while (!put.cancelled) { | |
| 982 | if (global_event_loop.bsdWaitKev( | |
| 983 | @intCast(usize, close_op.getHandle()), | |
| 984 | os.EVFILT_VNODE, | |
| 985 | os.NOTE_WRITE | os.NOTE_DELETE, | |
| 986 | )) |kev| { | |
| 987 | // TODO handle EV_ERROR | |
| 988 | if (kev.fflags & os.NOTE_DELETE != 0) { | |
| 989 | self.channel.put(Self.Event{ | |
| 990 | .id = Event.Id.Delete, | |
| 991 | .data = put.value, | |
| 992 | }); | |
| 993 | } else if (kev.fflags & os.NOTE_WRITE != 0) { | |
| 994 | self.channel.put(Self.Event{ | |
| 995 | .id = Event.Id.CloseWrite, | |
| 996 | .data = put.value, | |
| 997 | }); | |
| 998 | } | |
| 999 | } else |err| switch (err) { | |
| 1000 | error.EventNotFound => unreachable, | |
| 1001 | error.ProcessNotFound => unreachable, | |
| 1002 | error.Overflow => unreachable, | |
| 1003 | error.AccessDenied, error.SystemResources => |casted_err| { | |
| 1004 | self.channel.put(casted_err); | |
| 1005 | }, | |
| 1006 | } | |
| 1007 | } | |
| 1008 | } | |
| 1009 | ||
| 1010 | fn addFileLinux(self: *Self, file_path: []const u8, value: V) !?V { | |
| 1011 | const dirname = std.fs.path.dirname(file_path) orelse "."; | |
| 1012 | const dirname_with_null = try std.cstr.addNullByte(self.allocator, dirname); | |
| 1013 | var dirname_with_null_consumed = false; | |
| 1014 | defer if (!dirname_with_null_consumed) self.channel.free(dirname_with_null); | |
| 1015 | ||
| 1016 | const basename = std.fs.path.basename(file_path); | |
| 1017 | const basename_with_null = try std.cstr.addNullByte(self.allocator, basename); | |
| 1018 | var basename_with_null_consumed = false; | |
| 1019 | defer if (!basename_with_null_consumed) self.allocator.free(basename_with_null); | |
| 1020 | ||
| 1021 | const wd = try os.inotify_add_watchC( | |
| 1022 | self.os_data.inotify_fd, | |
| 1023 | dirname_with_null.ptr, | |
| 1024 | os.linux.IN_CLOSE_WRITE | os.linux.IN_ONLYDIR | os.linux.IN_EXCL_UNLINK, | |
| 1025 | ); | |
| 1026 | // wd is either a newly created watch or an existing one. | |
| 1027 | ||
| 1028 | const held = self.os_data.table_lock.acquire(); | |
| 1029 | defer held.release(); | |
| 1030 | ||
| 1031 | const gop = try self.os_data.wd_table.getOrPut(wd); | |
| 1032 | if (!gop.found_existing) { | |
| 1033 | gop.kv.value = OsData.Dir{ | |
| 1034 | .dirname = dirname_with_null, | |
| 1035 | .file_table = OsData.FileTable.init(self.allocator), | |
| 1036 | }; | |
| 1037 | dirname_with_null_consumed = true; | |
| 1038 | } | |
| 1039 | const dir = &gop.kv.value; | |
| 1040 | ||
| 1041 | const file_table_gop = try dir.file_table.getOrPut(basename_with_null); | |
| 1042 | if (file_table_gop.found_existing) { | |
| 1043 | const prev_value = file_table_gop.kv.value; | |
| 1044 | file_table_gop.kv.value = value; | |
| 1045 | return prev_value; | |
| 1046 | } else { | |
| 1047 | file_table_gop.kv.value = value; | |
| 1048 | basename_with_null_consumed = true; | |
| 1049 | return null; | |
| 1050 | } | |
| 1051 | } | |
| 1052 | ||
| 1053 | fn addFileWindows(self: *Self, file_path: []const u8, value: V) !?V { | |
| 1054 | // TODO we might need to convert dirname and basename to canonical file paths ("short"?) | |
| 1055 | const dirname = try std.mem.dupe(self.allocator, u8, std.fs.path.dirname(file_path) orelse "."); | |
| 1056 | var dirname_consumed = false; | |
| 1057 | defer if (!dirname_consumed) self.allocator.free(dirname); | |
| 1058 | ||
| 1059 | const dirname_utf16le = try std.unicode.utf8ToUtf16LeWithNull(self.allocator, dirname); | |
| 1060 | defer self.allocator.free(dirname_utf16le); | |
| 1061 | ||
| 1062 | // TODO https://github.com/ziglang/zig/issues/265 | |
| 1063 | const basename = std.fs.path.basename(file_path); | |
| 1064 | const basename_utf16le_null = try std.unicode.utf8ToUtf16LeWithNull(self.allocator, basename); | |
| 1065 | var basename_utf16le_null_consumed = false; | |
| 1066 | defer if (!basename_utf16le_null_consumed) self.allocator.free(basename_utf16le_null); | |
| 1067 | const basename_utf16le_no_null = basename_utf16le_null[0 .. basename_utf16le_null.len - 1]; | |
| 1068 | ||
| 1069 | const dir_handle = try windows.CreateFileW( | |
| 1070 | dirname_utf16le.ptr, | |
| 1071 | windows.FILE_LIST_DIRECTORY, | |
| 1072 | windows.FILE_SHARE_READ | windows.FILE_SHARE_DELETE | windows.FILE_SHARE_WRITE, | |
| 1073 | null, | |
| 1074 | windows.OPEN_EXISTING, | |
| 1075 | windows.FILE_FLAG_BACKUP_SEMANTICS | windows.FILE_FLAG_OVERLAPPED, | |
| 1076 | null, | |
| 1077 | ); | |
| 1078 | var dir_handle_consumed = false; | |
| 1079 | defer if (!dir_handle_consumed) windows.CloseHandle(dir_handle); | |
| 1080 | ||
| 1081 | const held = self.os_data.table_lock.acquire(); | |
| 1082 | defer held.release(); | |
| 1083 | ||
| 1084 | const gop = try self.os_data.dir_table.getOrPut(dirname); | |
| 1085 | if (gop.found_existing) { | |
| 1086 | const dir = gop.kv.value; | |
| 1087 | const held_dir_lock = dir.table_lock.acquire(); | |
| 1088 | defer held_dir_lock.release(); | |
| 1089 | ||
| 1090 | const file_gop = try dir.file_table.getOrPut(basename_utf16le_no_null); | |
| 1091 | if (file_gop.found_existing) { | |
| 1092 | const prev_value = file_gop.kv.value; | |
| 1093 | file_gop.kv.value = value; | |
| 1094 | return prev_value; | |
| 1095 | } else { | |
| 1096 | file_gop.kv.value = value; | |
| 1097 | basename_utf16le_null_consumed = true; | |
| 1098 | return null; | |
| 1099 | } | |
| 1100 | } else { | |
| 1101 | errdefer _ = self.os_data.dir_table.remove(dirname); | |
| 1102 | const dir = try self.allocator.create(OsData.Dir); | |
| 1103 | errdefer self.allocator.destroy(dir); | |
| 1104 | ||
| 1105 | dir.* = OsData.Dir{ | |
| 1106 | .file_table = OsData.FileTable.init(self.allocator), | |
| 1107 | .table_lock = event.Lock.init(), | |
| 1108 | .putter_frame = undefined, | |
| 1109 | }; | |
| 1110 | gop.kv.value = dir; | |
| 1111 | assert((try dir.file_table.put(basename_utf16le_no_null, value)) == null); | |
| 1112 | basename_utf16le_null_consumed = true; | |
| 1113 | ||
| 1114 | dir.putter_frame = async self.windowsDirReader(dir_handle, dir); | |
| 1115 | dir_handle_consumed = true; | |
| 1116 | ||
| 1117 | dirname_consumed = true; | |
| 1118 | ||
| 1119 | return null; | |
| 1120 | } | |
| 1121 | } | |
| 1122 | ||
| 1123 | fn windowsDirReader(self: *Self, dir_handle: windows.HANDLE, dir: *OsData.Dir) void { | |
| 1124 | self.ref(); | |
| 1125 | defer self.deref(); | |
| 1126 | ||
| 1127 | defer os.close(dir_handle); | |
| 1128 | ||
| 1129 | var putter_node = std.atomic.Queue(anyframe).Node{ | |
| 1130 | .data = .{ .putter = @frame() }, | |
| 1131 | .prev = null, | |
| 1132 | .next = null, | |
| 1133 | }; | |
| 1134 | self.os_data.all_putters.put(&putter_node); | |
| 1135 | defer _ = self.os_data.all_putters.remove(&putter_node); | |
| 1136 | ||
| 1137 | var resume_node = Loop.ResumeNode.Basic{ | |
| 1138 | .base = Loop.ResumeNode{ | |
| 1139 | .id = Loop.ResumeNode.Id.Basic, | |
| 1140 | .handle = @frame(), | |
| 1141 | .overlapped = windows.OVERLAPPED{ | |
| 1142 | .Internal = 0, | |
| 1143 | .InternalHigh = 0, | |
| 1144 | .Offset = 0, | |
| 1145 | .OffsetHigh = 0, | |
| 1146 | .hEvent = null, | |
| 1147 | }, | |
| 1148 | }, | |
| 1149 | }; | |
| 1150 | var event_buf: [4096]u8 align(@alignOf(windows.FILE_NOTIFY_INFORMATION)) = undefined; | |
| 1151 | ||
| 1152 | // TODO handle this error not in the channel but in the setup | |
| 1153 | _ = windows.CreateIoCompletionPort( | |
| 1154 | dir_handle, | |
| 1155 | global_event_loop.os_data.io_port, | |
| 1156 | undefined, | |
| 1157 | undefined, | |
| 1158 | ) catch |err| { | |
| 1159 | self.channel.put(err); | |
| 1160 | return; | |
| 1161 | }; | |
| 1162 | ||
| 1163 | while (!putter_node.data.cancelled) { | |
| 1164 | { | |
| 1165 | // TODO only 1 beginOneEvent for the whole function | |
| 1166 | global_event_loop.beginOneEvent(); | |
| 1167 | errdefer global_event_loop.finishOneEvent(); | |
| 1168 | errdefer { | |
| 1169 | _ = windows.kernel32.CancelIoEx(dir_handle, &resume_node.base.overlapped); | |
| 1170 | } | |
| 1171 | suspend { | |
| 1172 | _ = windows.kernel32.ReadDirectoryChangesW( | |
| 1173 | dir_handle, | |
| 1174 | &event_buf, | |
| 1175 | @intCast(windows.DWORD, event_buf.len), | |
| 1176 | windows.FALSE, // watch subtree | |
| 1177 | windows.FILE_NOTIFY_CHANGE_FILE_NAME | windows.FILE_NOTIFY_CHANGE_DIR_NAME | | |
| 1178 | windows.FILE_NOTIFY_CHANGE_ATTRIBUTES | windows.FILE_NOTIFY_CHANGE_SIZE | | |
| 1179 | windows.FILE_NOTIFY_CHANGE_LAST_WRITE | windows.FILE_NOTIFY_CHANGE_LAST_ACCESS | | |
| 1180 | windows.FILE_NOTIFY_CHANGE_CREATION | windows.FILE_NOTIFY_CHANGE_SECURITY, | |
| 1181 | null, // number of bytes transferred (unused for async) | |
| 1182 | &resume_node.base.overlapped, | |
| 1183 | null, // completion routine - unused because we use IOCP | |
| 1184 | ); | |
| 1185 | } | |
| 1186 | } | |
| 1187 | var bytes_transferred: windows.DWORD = undefined; | |
| 1188 | if (windows.kernel32.GetOverlappedResult(dir_handle, &resume_node.base.overlapped, &bytes_transferred, windows.FALSE) == 0) { | |
| 1189 | const err = switch (windows.kernel32.GetLastError()) { | |
| 1190 | else => |err| windows.unexpectedError(err), | |
| 1191 | }; | |
| 1192 | self.channel.put(err); | |
| 1193 | } else { | |
| 1194 | // can't use @bytesToSlice because of the special variable length name field | |
| 1195 | var ptr = event_buf[0..].ptr; | |
| 1196 | const end_ptr = ptr + bytes_transferred; | |
| 1197 | var ev: *windows.FILE_NOTIFY_INFORMATION = undefined; | |
| 1198 | while (@ptrToInt(ptr) < @ptrToInt(end_ptr)) : (ptr += ev.NextEntryOffset) { | |
| 1199 | ev = @ptrCast(*windows.FILE_NOTIFY_INFORMATION, ptr); | |
| 1200 | const emit = switch (ev.Action) { | |
| 1201 | windows.FILE_ACTION_REMOVED => WatchEventId.Delete, | |
| 1202 | windows.FILE_ACTION_MODIFIED => WatchEventId.CloseWrite, | |
| 1203 | else => null, | |
| 1204 | }; | |
| 1205 | if (emit) |id| { | |
| 1206 | const basename_utf16le = ([*]u16)(&ev.FileName)[0 .. ev.FileNameLength / 2]; | |
| 1207 | const user_value = blk: { | |
| 1208 | const held = dir.table_lock.acquire(); | |
| 1209 | defer held.release(); | |
| 1210 | ||
| 1211 | if (dir.file_table.get(basename_utf16le)) |entry| { | |
| 1212 | break :blk entry.value; | |
| 1213 | } else { | |
| 1214 | break :blk null; | |
| 1215 | } | |
| 1216 | }; | |
| 1217 | if (user_value) |v| { | |
| 1218 | self.channel.put(Event{ | |
| 1219 | .id = id, | |
| 1220 | .data = v, | |
| 1221 | }); | |
| 1222 | } | |
| 1223 | } | |
| 1224 | if (ev.NextEntryOffset == 0) break; | |
| 1225 | } | |
| 1226 | } | |
| 1227 | } | |
| 1228 | } | |
| 1229 | ||
| 1230 | pub fn removeFile(self: *Self, file_path: []const u8) ?V { | |
| 1231 | @panic("TODO"); | |
| 1232 | } | |
| 1233 | ||
| 1234 | fn linuxEventPutter(self: *Self) void { | |
| 1235 | global_event_loop.beginOneEvent(); | |
| 1236 | ||
| 1237 | defer { | |
| 1238 | self.os_data.table_lock.deinit(); | |
| 1239 | var wd_it = self.os_data.wd_table.iterator(); | |
| 1240 | while (wd_it.next()) |wd_entry| { | |
| 1241 | var file_it = wd_entry.value.file_table.iterator(); | |
| 1242 | while (file_it.next()) |file_entry| { | |
| 1243 | self.allocator.free(file_entry.key); | |
| 1244 | } | |
| 1245 | self.allocator.free(wd_entry.value.dirname); | |
| 1246 | wd_entry.value.file_table.deinit(); | |
| 1247 | } | |
| 1248 | self.os_data.wd_table.deinit(); | |
| 1249 | global_event_loop.finishOneEvent(); | |
| 1250 | os.close(self.os_data.inotify_fd); | |
| 1251 | self.channel.deinit(); | |
| 1252 | self.allocator.free(self.channel.buffer_nodes); | |
| 1253 | } | |
| 1254 | ||
| 1255 | var event_buf: [4096]u8 align(@alignOf(os.linux.inotify_event)) = undefined; | |
| 1256 | ||
| 1257 | while (!self.os_data.cancelled) { | |
| 1258 | const rc = os.linux.read(self.os_data.inotify_fd, &event_buf, event_buf.len); | |
| 1259 | const errno = os.linux.getErrno(rc); | |
| 1260 | switch (errno) { | |
| 1261 | 0 => { | |
| 1262 | // can't use @bytesToSlice because of the special variable length name field | |
| 1263 | var ptr = event_buf[0..].ptr; | |
| 1264 | const end_ptr = ptr + event_buf.len; | |
| 1265 | var ev: *os.linux.inotify_event = undefined; | |
| 1266 | while (@ptrToInt(ptr) < @ptrToInt(end_ptr)) { | |
| 1267 | ev = @ptrCast(*os.linux.inotify_event, ptr); | |
| 1268 | if (ev.mask & os.linux.IN_CLOSE_WRITE == os.linux.IN_CLOSE_WRITE) { | |
| 1269 | const basename_ptr = ptr + @sizeOf(os.linux.inotify_event); | |
| 1270 | // `ev.len` counts all bytes in `ev.name` including terminating null byte. | |
| 1271 | const basename_with_null = basename_ptr[0..ev.len]; | |
| 1272 | const user_value = blk: { | |
| 1273 | const held = self.os_data.table_lock.acquire(); | |
| 1274 | defer held.release(); | |
| 1275 | ||
| 1276 | const dir = &self.os_data.wd_table.get(ev.wd).?.value; | |
| 1277 | if (dir.file_table.get(basename_with_null)) |entry| { | |
| 1278 | break :blk entry.value; | |
| 1279 | } else { | |
| 1280 | break :blk null; | |
| 1281 | } | |
| 1282 | }; | |
| 1283 | if (user_value) |v| { | |
| 1284 | self.channel.put(Event{ | |
| 1285 | .id = WatchEventId.CloseWrite, | |
| 1286 | .data = v, | |
| 1287 | }); | |
| 1288 | } | |
| 1289 | } | |
| 1290 | ||
| 1291 | ptr = @alignCast(@alignOf(os.linux.inotify_event), ptr + @sizeOf(os.linux.inotify_event) + ev.len); | |
| 1292 | } | |
| 1293 | }, | |
| 1294 | os.linux.EINTR => continue, | |
| 1295 | os.linux.EINVAL => unreachable, | |
| 1296 | os.linux.EFAULT => unreachable, | |
| 1297 | os.linux.EAGAIN => { | |
| 1298 | global_event_loop.linuxWaitFd(self.os_data.inotify_fd, os.linux.EPOLLET | os.linux.EPOLLIN | os.EPOLLONESHOT); | |
| 1299 | }, | |
| 1300 | else => unreachable, | |
| 1301 | } | |
| 1302 | } | |
| 1303 | } | |
| 1304 | }; | |
| 1305 | } | |
| 1306 | ||
| 1307 | const test_tmp_dir = "std_event_fs_test"; | |
| 1308 | ||
| 1309 | test "write a file, watch it, write it again" { | |
| 1310 | // TODO provide a way to run tests in evented I/O mode | |
| 1311 | if (!std.io.is_async) return error.SkipZigTest; | |
| 1312 | ||
| 1313 | const allocator = std.heap.page_allocator; | |
| 1314 | ||
| 1315 | // TODO move this into event loop too | |
| 1316 | try os.makePath(allocator, test_tmp_dir); | |
| 1317 | defer os.deleteTree(test_tmp_dir) catch {}; | |
| 1318 | ||
| 1319 | return testFsWatch(&allocator); | |
| 1320 | } | |
| 1321 | ||
| 1322 | fn testFsWatch(allocator: *Allocator) !void { | |
| 1323 | const file_path = try std.fs.path.join(allocator, [_][]const u8{ test_tmp_dir, "file.txt" }); | |
| 1324 | defer allocator.free(file_path); | |
| 1325 | ||
| 1326 | const contents = | |
| 1327 | \\line 1 | |
| 1328 | \\line 2 | |
| 1329 | ; | |
| 1330 | const line2_offset = 7; | |
| 1331 | ||
| 1332 | // first just write then read the file | |
| 1333 | try writeFile(allocator, file_path, contents); | |
| 1334 | ||
| 1335 | const read_contents = try readFile(allocator, file_path, 1024 * 1024); | |
| 1336 | testing.expectEqualSlices(u8, contents, read_contents); | |
| 1337 | ||
| 1338 | // now watch the file | |
| 1339 | var watch = try Watch(void).init(allocator, 0); | |
| 1340 | defer watch.deinit(); | |
| 1341 | ||
| 1342 | testing.expect((try watch.addFile(file_path, {})) == null); | |
| 1343 | ||
| 1344 | const ev = watch.channel.get(); | |
| 1345 | var ev_consumed = false; | |
| 1346 | defer if (!ev_consumed) await ev; | |
| 1347 | ||
| 1348 | // overwrite line 2 | |
| 1349 | const fd = try await openReadWrite(file_path, File.default_mode); | |
| 1350 | { | |
| 1351 | defer os.close(fd); | |
| 1352 | ||
| 1353 | try pwritev(allocator, fd, []const []const u8{"lorem ipsum"}, line2_offset); | |
| 1354 | } | |
| 1355 | ||
| 1356 | ev_consumed = true; | |
| 1357 | switch ((try await ev).id) { | |
| 1358 | WatchEventId.CloseWrite => {}, | |
| 1359 | WatchEventId.Delete => @panic("wrong event"), | |
| 1360 | } | |
| 1361 | const contents_updated = try readFile(allocator, file_path, 1024 * 1024); | |
| 1362 | testing.expectEqualSlices(u8, | |
| 1363 | \\line 1 | |
| 1364 | \\lorem ipsum | |
| 1365 | , contents_updated); | |
| 1366 | ||
| 1367 | // TODO test deleting the file and then re-adding it. we should get events for both | |
| 1368 | } | |
| 1369 | ||
| 1370 | pub const OutStream = struct { | |
| 1371 | fd: fd_t, | |
| 1372 | stream: Stream, | |
| 1373 | allocator: *Allocator, | |
| 1374 | offset: usize, | |
| 1375 | ||
| 1376 | pub const Error = File.WriteError; | |
| 1377 | pub const Stream = event.io.OutStream(Error); | |
| 1378 | ||
| 1379 | pub fn init(allocator: *Allocator, fd: fd_t, offset: usize) OutStream { | |
| 1380 | return OutStream{ | |
| 1381 | .fd = fd, | |
| 1382 | .offset = offset, | |
| 1383 | .stream = Stream{ .writeFn = writeFn }, | |
| 1384 | }; | |
| 1385 | } | |
| 1386 | ||
| 1387 | fn writeFn(out_stream: *Stream, bytes: []const u8) Error!void { | |
| 1388 | const self = @fieldParentPtr(OutStream, "stream", out_stream); | |
| 1389 | const offset = self.offset; | |
| 1390 | self.offset += bytes.len; | |
| 1391 | return pwritev(self.allocator, self.fd, [_][]const u8{bytes}, offset); | |
| 1392 | } | |
| 1393 | }; | |
| 1394 | ||
| 1395 | pub const InStream = struct { | |
| 1396 | fd: fd_t, | |
| 1397 | stream: Stream, | |
| 1398 | allocator: *Allocator, | |
| 1399 | offset: usize, | |
| 1400 | ||
| 1401 | pub const Error = PReadVError; // TODO make this not have OutOfMemory | |
| 1402 | pub const Stream = event.io.InStream(Error); | |
| 1403 | ||
| 1404 | pub fn init(allocator: *Allocator, fd: fd_t, offset: usize) InStream { | |
| 1405 | return InStream{ | |
| 1406 | .fd = fd, | |
| 1407 | .offset = offset, | |
| 1408 | .stream = Stream{ .readFn = readFn }, | |
| 1409 | }; | |
| 1410 | } | |
| 1411 | ||
| 1412 | fn readFn(in_stream: *Stream, bytes: []u8) Error!usize { | |
| 1413 | const self = @fieldParentPtr(InStream, "stream", in_stream); | |
| 1414 | const amt = try preadv(self.allocator, self.fd, [_][]u8{bytes}, self.offset); | |
| 1415 | self.offset += amt; | |
| 1416 | return amt; | |
| 1417 | } | |
| 1418 | }; |
lib/std/event/group.zig+1-1| ... | ... | @@ -22,7 +22,7 @@ pub fn Group(comptime ReturnType: type) type { |
| 22 | 22 | const AllocStack = std.atomic.Stack(Node); |
| 23 | 23 | |
| 24 | 24 | pub const Node = struct { |
| 25 | bytes: []const u8 = [0]u8{}, | |
| 25 | bytes: []const u8 = &[0]u8{}, | |
| 26 | 26 | handle: anyframe->ReturnType, |
| 27 | 27 | }; |
| 28 | 28 |
lib/std/event/lock.zig+4-4| ... | ... | @@ -117,21 +117,21 @@ pub const Lock = struct { |
| 117 | 117 | }; |
| 118 | 118 | |
| 119 | 119 | test "std.event.Lock" { |
| 120 | if (!std.io.is_async) return error.SkipZigTest; | |
| 121 | ||
| 120 | 122 | // TODO https://github.com/ziglang/zig/issues/1908 |
| 121 | 123 | if (builtin.single_threaded) return error.SkipZigTest; |
| 122 | 124 | |
| 123 | 125 | // TODO https://github.com/ziglang/zig/issues/3251 |
| 124 | 126 | if (builtin.os == .freebsd) return error.SkipZigTest; |
| 125 | 127 | |
| 126 | // TODO provide a way to run tests in evented I/O mode | |
| 127 | if (!std.io.is_async) return error.SkipZigTest; | |
| 128 | ||
| 129 | 128 | var lock = Lock.init(); |
| 130 | 129 | defer lock.deinit(); |
| 131 | 130 | |
| 132 | 131 | _ = async testLock(&lock); |
| 133 | 132 | |
| 134 | testing.expectEqualSlices(i32, [1]i32{3 * @intCast(i32, shared_test_data.len)} ** shared_test_data.len, shared_test_data); | |
| 133 | const expected_result = [1]i32{3 * @intCast(i32, shared_test_data.len)} ** shared_test_data.len; | |
| 134 | testing.expectEqualSlices(i32, &expected_result, &shared_test_data); | |
| 135 | 135 | } |
| 136 | 136 | |
| 137 | 137 | async fn testLock(lock: *Lock) void { |
lib/std/event/loop.zig+317-50| ... | ... | @@ -6,7 +6,6 @@ const testing = std.testing; |
| 6 | 6 | const mem = std.mem; |
| 7 | 7 | const AtomicRmwOp = builtin.AtomicRmwOp; |
| 8 | 8 | const AtomicOrder = builtin.AtomicOrder; |
| 9 | const fs = std.event.fs; | |
| 10 | 9 | const os = std.os; |
| 11 | 10 | const windows = os.windows; |
| 12 | 11 | const maxInt = std.math.maxInt; |
| ... | ... | @@ -174,21 +173,19 @@ pub const Loop = struct { |
| 174 | 173 | fn initOsData(self: *Loop, extra_thread_count: usize) InitOsDataError!void { |
| 175 | 174 | switch (builtin.os) { |
| 176 | 175 | .linux => { |
| 177 | self.os_data.fs_queue = std.atomic.Queue(fs.Request).init(); | |
| 176 | self.os_data.fs_queue = std.atomic.Queue(Request).init(); | |
| 178 | 177 | self.os_data.fs_queue_item = 0; |
| 179 | 178 | // we need another thread for the file system because Linux does not have an async |
| 180 | 179 | // file system I/O API. |
| 181 | self.os_data.fs_end_request = fs.RequestNode{ | |
| 182 | .prev = undefined, | |
| 183 | .next = undefined, | |
| 184 | .data = fs.Request{ | |
| 185 | .msg = fs.Request.Msg.End, | |
| 186 | .finish = fs.Request.Finish.NoAction, | |
| 180 | self.os_data.fs_end_request = Request.Node{ | |
| 181 | .data = Request{ | |
| 182 | .msg = .end, | |
| 183 | .finish = .NoAction, | |
| 187 | 184 | }, |
| 188 | 185 | }; |
| 189 | 186 | |
| 190 | 187 | errdefer { |
| 191 | while (self.available_eventfd_resume_nodes.pop()) |node| os.close(node.data.eventfd); | |
| 188 | while (self.available_eventfd_resume_nodes.pop()) |node| noasync os.close(node.data.eventfd); | |
| 192 | 189 | } |
| 193 | 190 | for (self.eventfd_resume_nodes) |*eventfd_node| { |
| 194 | 191 | eventfd_node.* = std.atomic.Stack(ResumeNode.EventFd).Node{ |
| ... | ... | @@ -207,10 +204,10 @@ pub const Loop = struct { |
| 207 | 204 | } |
| 208 | 205 | |
| 209 | 206 | self.os_data.epollfd = try os.epoll_create1(os.EPOLL_CLOEXEC); |
| 210 | errdefer os.close(self.os_data.epollfd); | |
| 207 | errdefer noasync os.close(self.os_data.epollfd); | |
| 211 | 208 | |
| 212 | 209 | self.os_data.final_eventfd = try os.eventfd(0, os.EFD_CLOEXEC | os.EFD_NONBLOCK); |
| 213 | errdefer os.close(self.os_data.final_eventfd); | |
| 210 | errdefer noasync os.close(self.os_data.final_eventfd); | |
| 214 | 211 | |
| 215 | 212 | self.os_data.final_eventfd_event = os.epoll_event{ |
| 216 | 213 | .events = os.EPOLLIN, |
| ... | ... | @@ -237,7 +234,7 @@ pub const Loop = struct { |
| 237 | 234 | var extra_thread_index: usize = 0; |
| 238 | 235 | errdefer { |
| 239 | 236 | // writing 8 bytes to an eventfd cannot fail |
| 240 | os.write(self.os_data.final_eventfd, &wakeup_bytes) catch unreachable; | |
| 237 | noasync os.write(self.os_data.final_eventfd, &wakeup_bytes) catch unreachable; | |
| 241 | 238 | while (extra_thread_index != 0) { |
| 242 | 239 | extra_thread_index -= 1; |
| 243 | 240 | self.extra_threads[extra_thread_index].wait(); |
| ... | ... | @@ -249,20 +246,20 @@ pub const Loop = struct { |
| 249 | 246 | }, |
| 250 | 247 | .macosx, .freebsd, .netbsd, .dragonfly => { |
| 251 | 248 | self.os_data.kqfd = try os.kqueue(); |
| 252 | errdefer os.close(self.os_data.kqfd); | |
| 249 | errdefer noasync os.close(self.os_data.kqfd); | |
| 253 | 250 | |
| 254 | 251 | self.os_data.fs_kqfd = try os.kqueue(); |
| 255 | errdefer os.close(self.os_data.fs_kqfd); | |
| 252 | errdefer noasync os.close(self.os_data.fs_kqfd); | |
| 256 | 253 | |
| 257 | self.os_data.fs_queue = std.atomic.Queue(fs.Request).init(); | |
| 254 | self.os_data.fs_queue = std.atomic.Queue(Request).init(); | |
| 258 | 255 | // we need another thread for the file system because Darwin does not have an async |
| 259 | 256 | // file system I/O API. |
| 260 | self.os_data.fs_end_request = fs.RequestNode{ | |
| 257 | self.os_data.fs_end_request = Request.Node{ | |
| 261 | 258 | .prev = undefined, |
| 262 | 259 | .next = undefined, |
| 263 | .data = fs.Request{ | |
| 264 | .msg = fs.Request.Msg.End, | |
| 265 | .finish = fs.Request.Finish.NoAction, | |
| 260 | .data = Request{ | |
| 261 | .msg = .end, | |
| 262 | .finish = .NoAction, | |
| 266 | 263 | }, |
| 267 | 264 | }; |
| 268 | 265 | |
| ... | ... | @@ -407,14 +404,14 @@ pub const Loop = struct { |
| 407 | 404 | fn deinitOsData(self: *Loop) void { |
| 408 | 405 | switch (builtin.os) { |
| 409 | 406 | .linux => { |
| 410 | os.close(self.os_data.final_eventfd); | |
| 411 | while (self.available_eventfd_resume_nodes.pop()) |node| os.close(node.data.eventfd); | |
| 412 | os.close(self.os_data.epollfd); | |
| 407 | noasync os.close(self.os_data.final_eventfd); | |
| 408 | while (self.available_eventfd_resume_nodes.pop()) |node| noasync os.close(node.data.eventfd); | |
| 409 | noasync os.close(self.os_data.epollfd); | |
| 413 | 410 | self.allocator.free(self.eventfd_resume_nodes); |
| 414 | 411 | }, |
| 415 | 412 | .macosx, .freebsd, .netbsd, .dragonfly => { |
| 416 | os.close(self.os_data.kqfd); | |
| 417 | os.close(self.os_data.fs_kqfd); | |
| 413 | noasync os.close(self.os_data.kqfd); | |
| 414 | noasync os.close(self.os_data.fs_kqfd); | |
| 418 | 415 | }, |
| 419 | 416 | .windows => { |
| 420 | 417 | windows.CloseHandle(self.os_data.io_port); |
| ... | ... | @@ -711,6 +708,190 @@ pub const Loop = struct { |
| 711 | 708 | } |
| 712 | 709 | } |
| 713 | 710 | |
| 711 | /// Performs an async `os.open` using a separate thread. | |
| 712 | pub fn openZ(self: *Loop, file_path: [*:0]const u8, flags: u32, mode: usize) os.OpenError!os.fd_t { | |
| 713 | var req_node = Request.Node{ | |
| 714 | .data = .{ | |
| 715 | .msg = .{ | |
| 716 | .open = .{ | |
| 717 | .path = file_path, | |
| 718 | .flags = flags, | |
| 719 | .mode = mode, | |
| 720 | .result = undefined, | |
| 721 | }, | |
| 722 | }, | |
| 723 | .finish = .{ .TickNode = .{ .data = @frame() } }, | |
| 724 | }, | |
| 725 | }; | |
| 726 | suspend { | |
| 727 | self.posixFsRequest(&req_node); | |
| 728 | } | |
| 729 | return req_node.data.msg.open.result; | |
| 730 | } | |
| 731 | ||
| 732 | /// Performs an async `os.opent` using a separate thread. | |
| 733 | pub fn openatZ(self: *Loop, fd: os.fd_t, file_path: [*:0]const u8, flags: u32, mode: usize) os.OpenError!os.fd_t { | |
| 734 | var req_node = Request.Node{ | |
| 735 | .data = .{ | |
| 736 | .msg = .{ | |
| 737 | .openat = .{ | |
| 738 | .fd = fd, | |
| 739 | .path = file_path, | |
| 740 | .flags = flags, | |
| 741 | .mode = mode, | |
| 742 | .result = undefined, | |
| 743 | }, | |
| 744 | }, | |
| 745 | .finish = .{ .TickNode = .{ .data = @frame() } }, | |
| 746 | }, | |
| 747 | }; | |
| 748 | suspend { | |
| 749 | self.posixFsRequest(&req_node); | |
| 750 | } | |
| 751 | return req_node.data.msg.openat.result; | |
| 752 | } | |
| 753 | ||
| 754 | /// Performs an async `os.close` using a separate thread. | |
| 755 | pub fn close(self: *Loop, fd: os.fd_t) void { | |
| 756 | var req_node = Request.Node{ | |
| 757 | .data = .{ | |
| 758 | .msg = .{ .close = .{ .fd = fd } }, | |
| 759 | .finish = .{ .TickNode = .{ .data = @frame() } }, | |
| 760 | }, | |
| 761 | }; | |
| 762 | suspend { | |
| 763 | self.posixFsRequest(&req_node); | |
| 764 | } | |
| 765 | } | |
| 766 | ||
| 767 | /// Performs an async `os.read` using a separate thread. | |
| 768 | /// `fd` must block and not return EAGAIN. | |
| 769 | pub fn read(self: *Loop, fd: os.fd_t, buf: []u8) os.ReadError!usize { | |
| 770 | var req_node = Request.Node{ | |
| 771 | .data = .{ | |
| 772 | .msg = .{ | |
| 773 | .read = .{ | |
| 774 | .fd = fd, | |
| 775 | .buf = buf, | |
| 776 | .result = undefined, | |
| 777 | }, | |
| 778 | }, | |
| 779 | .finish = .{ .TickNode = .{ .data = @frame() } }, | |
| 780 | }, | |
| 781 | }; | |
| 782 | suspend { | |
| 783 | self.posixFsRequest(&req_node); | |
| 784 | } | |
| 785 | return req_node.data.msg.read.result; | |
| 786 | } | |
| 787 | ||
| 788 | /// Performs an async `os.readv` using a separate thread. | |
| 789 | /// `fd` must block and not return EAGAIN. | |
| 790 | pub fn readv(self: *Loop, fd: os.fd_t, iov: []const os.iovec) os.ReadError!usize { | |
| 791 | var req_node = Request.Node{ | |
| 792 | .data = .{ | |
| 793 | .msg = .{ | |
| 794 | .readv = .{ | |
| 795 | .fd = fd, | |
| 796 | .iov = iov, | |
| 797 | .result = undefined, | |
| 798 | }, | |
| 799 | }, | |
| 800 | .finish = .{ .TickNode = .{ .data = @frame() } }, | |
| 801 | }, | |
| 802 | }; | |
| 803 | suspend { | |
| 804 | self.posixFsRequest(&req_node); | |
| 805 | } | |
| 806 | return req_node.data.msg.readv.result; | |
| 807 | } | |
| 808 | ||
| 809 | /// Performs an async `os.preadv` using a separate thread. | |
| 810 | /// `fd` must block and not return EAGAIN. | |
| 811 | pub fn preadv(self: *Loop, fd: os.fd_t, iov: []const os.iovec, offset: u64) os.ReadError!usize { | |
| 812 | var req_node = Request.Node{ | |
| 813 | .data = .{ | |
| 814 | .msg = .{ | |
| 815 | .preadv = .{ | |
| 816 | .fd = fd, | |
| 817 | .iov = iov, | |
| 818 | .offset = offset, | |
| 819 | .result = undefined, | |
| 820 | }, | |
| 821 | }, | |
| 822 | .finish = .{ .TickNode = .{ .data = @frame() } }, | |
| 823 | }, | |
| 824 | }; | |
| 825 | suspend { | |
| 826 | self.posixFsRequest(&req_node); | |
| 827 | } | |
| 828 | return req_node.data.msg.preadv.result; | |
| 829 | } | |
| 830 | ||
| 831 | /// Performs an async `os.write` using a separate thread. | |
| 832 | /// `fd` must block and not return EAGAIN. | |
| 833 | pub fn write(self: *Loop, fd: os.fd_t, bytes: []const u8) os.WriteError!void { | |
| 834 | var req_node = Request.Node{ | |
| 835 | .data = .{ | |
| 836 | .msg = .{ | |
| 837 | .write = .{ | |
| 838 | .fd = fd, | |
| 839 | .bytes = bytes, | |
| 840 | .result = undefined, | |
| 841 | }, | |
| 842 | }, | |
| 843 | .finish = .{ .TickNode = .{ .data = @frame() } }, | |
| 844 | }, | |
| 845 | }; | |
| 846 | suspend { | |
| 847 | self.posixFsRequest(&req_node); | |
| 848 | } | |
| 849 | return req_node.data.msg.write.result; | |
| 850 | } | |
| 851 | ||
| 852 | /// Performs an async `os.writev` using a separate thread. | |
| 853 | /// `fd` must block and not return EAGAIN. | |
| 854 | pub fn writev(self: *Loop, fd: os.fd_t, iov: []const os.iovec_const) os.WriteError!void { | |
| 855 | var req_node = Request.Node{ | |
| 856 | .data = .{ | |
| 857 | .msg = .{ | |
| 858 | .writev = .{ | |
| 859 | .fd = fd, | |
| 860 | .iov = iov, | |
| 861 | .result = undefined, | |
| 862 | }, | |
| 863 | }, | |
| 864 | .finish = .{ .TickNode = .{ .data = @frame() } }, | |
| 865 | }, | |
| 866 | }; | |
| 867 | suspend { | |
| 868 | self.posixFsRequest(&req_node); | |
| 869 | } | |
| 870 | return req_node.data.msg.writev.result; | |
| 871 | } | |
| 872 | ||
| 873 | /// Performs an async `os.pwritev` using a separate thread. | |
| 874 | /// `fd` must block and not return EAGAIN. | |
| 875 | pub fn pwritev(self: *Loop, fd: os.fd_t, iov: []const os.iovec_const, offset: u64) os.WriteError!void { | |
| 876 | var req_node = Request.Node{ | |
| 877 | .data = .{ | |
| 878 | .msg = .{ | |
| 879 | .pwritev = .{ | |
| 880 | .fd = fd, | |
| 881 | .iov = iov, | |
| 882 | .offset = offset, | |
| 883 | .result = undefined, | |
| 884 | }, | |
| 885 | }, | |
| 886 | .finish = .{ .TickNode = .{ .data = @frame() } }, | |
| 887 | }, | |
| 888 | }; | |
| 889 | suspend { | |
| 890 | self.posixFsRequest(&req_node); | |
| 891 | } | |
| 892 | return req_node.data.msg.pwritev.result; | |
| 893 | } | |
| 894 | ||
| 714 | 895 | fn workerRun(self: *Loop) void { |
| 715 | 896 | while (true) { |
| 716 | 897 | while (true) { |
| ... | ... | @@ -804,7 +985,7 @@ pub const Loop = struct { |
| 804 | 985 | } |
| 805 | 986 | } |
| 806 | 987 | |
| 807 | fn posixFsRequest(self: *Loop, request_node: *fs.RequestNode) void { | |
| 988 | fn posixFsRequest(self: *Loop, request_node: *Request.Node) void { | |
| 808 | 989 | self.beginOneEvent(); // finished in posixFsRun after processing the msg |
| 809 | 990 | self.os_data.fs_queue.put(request_node); |
| 810 | 991 | switch (builtin.os) { |
| ... | ... | @@ -826,7 +1007,7 @@ pub const Loop = struct { |
| 826 | 1007 | } |
| 827 | 1008 | } |
| 828 | 1009 | |
| 829 | fn posixFsCancel(self: *Loop, request_node: *fs.RequestNode) void { | |
| 1010 | fn posixFsCancel(self: *Loop, request_node: *Request.Node) void { | |
| 830 | 1011 | if (self.os_data.fs_queue.remove(request_node)) { |
| 831 | 1012 | self.finishOneEvent(); |
| 832 | 1013 | } |
| ... | ... | @@ -841,37 +1022,32 @@ pub const Loop = struct { |
| 841 | 1022 | } |
| 842 | 1023 | while (self.os_data.fs_queue.get()) |node| { |
| 843 | 1024 | switch (node.data.msg) { |
| 844 | .End => return, | |
| 845 | .WriteV => |*msg| { | |
| 1025 | .end => return, | |
| 1026 | .read => |*msg| { | |
| 1027 | msg.result = noasync os.read(msg.fd, msg.buf); | |
| 1028 | }, | |
| 1029 | .write => |*msg| { | |
| 1030 | msg.result = noasync os.write(msg.fd, msg.bytes); | |
| 1031 | }, | |
| 1032 | .writev => |*msg| { | |
| 846 | 1033 | msg.result = noasync os.writev(msg.fd, msg.iov); |
| 847 | 1034 | }, |
| 848 | .PWriteV => |*msg| { | |
| 1035 | .pwritev => |*msg| { | |
| 849 | 1036 | msg.result = noasync os.pwritev(msg.fd, msg.iov, msg.offset); |
| 850 | 1037 | }, |
| 851 | .PReadV => |*msg| { | |
| 1038 | .preadv => |*msg| { | |
| 852 | 1039 | msg.result = noasync os.preadv(msg.fd, msg.iov, msg.offset); |
| 853 | 1040 | }, |
| 854 | .Open => |*msg| { | |
| 855 | msg.result = noasync os.openC(msg.path.ptr, msg.flags, msg.mode); | |
| 1041 | .open => |*msg| { | |
| 1042 | msg.result = noasync os.openC(msg.path, msg.flags, msg.mode); | |
| 856 | 1043 | }, |
| 857 | .Close => |*msg| noasync os.close(msg.fd), | |
| 858 | .WriteFile => |*msg| blk: { | |
| 859 | const O_LARGEFILE = if (@hasDecl(os, "O_LARGEFILE")) os.O_LARGEFILE else 0; | |
| 860 | const flags = O_LARGEFILE | os.O_WRONLY | os.O_CREAT | | |
| 861 | os.O_CLOEXEC | os.O_TRUNC; | |
| 862 | const fd = noasync os.openC(msg.path.ptr, flags, msg.mode) catch |err| { | |
| 863 | msg.result = err; | |
| 864 | break :blk; | |
| 865 | }; | |
| 866 | defer noasync os.close(fd); | |
| 867 | msg.result = noasync os.write(fd, msg.contents); | |
| 1044 | .openat => |*msg| { | |
| 1045 | msg.result = noasync os.openatC(msg.fd, msg.path, msg.flags, msg.mode); | |
| 868 | 1046 | }, |
| 1047 | .close => |*msg| noasync os.close(msg.fd), | |
| 869 | 1048 | } |
| 870 | 1049 | switch (node.data.finish) { |
| 871 | 1050 | .TickNode => |*tick_node| self.onNextTick(tick_node), |
| 872 | .DeallocCloseOperation => |close_op| { | |
| 873 | self.allocator.destroy(close_op); | |
| 874 | }, | |
| 875 | 1051 | .NoAction => {}, |
| 876 | 1052 | } |
| 877 | 1053 | self.finishOneEvent(); |
| ... | ... | @@ -911,8 +1087,8 @@ pub const Loop = struct { |
| 911 | 1087 | fs_kevent_wait: os.Kevent, |
| 912 | 1088 | fs_thread: *Thread, |
| 913 | 1089 | fs_kqfd: i32, |
| 914 | fs_queue: std.atomic.Queue(fs.Request), | |
| 915 | fs_end_request: fs.RequestNode, | |
| 1090 | fs_queue: std.atomic.Queue(Request), | |
| 1091 | fs_end_request: Request.Node, | |
| 916 | 1092 | }; |
| 917 | 1093 | |
| 918 | 1094 | const LinuxOsData = struct { |
| ... | ... | @@ -921,8 +1097,99 @@ pub const Loop = struct { |
| 921 | 1097 | final_eventfd_event: os.linux.epoll_event, |
| 922 | 1098 | fs_thread: *Thread, |
| 923 | 1099 | fs_queue_item: i32, |
| 924 | fs_queue: std.atomic.Queue(fs.Request), | |
| 925 | fs_end_request: fs.RequestNode, | |
| 1100 | fs_queue: std.atomic.Queue(Request), | |
| 1101 | fs_end_request: Request.Node, | |
| 1102 | }; | |
| 1103 | ||
| 1104 | pub const Request = struct { | |
| 1105 | msg: Msg, | |
| 1106 | finish: Finish, | |
| 1107 | ||
| 1108 | pub const Node = std.atomic.Queue(Request).Node; | |
| 1109 | ||
| 1110 | pub const Finish = union(enum) { | |
| 1111 | TickNode: Loop.NextTickNode, | |
| 1112 | NoAction, | |
| 1113 | }; | |
| 1114 | ||
| 1115 | pub const Msg = union(enum) { | |
| 1116 | read: Read, | |
| 1117 | write: Write, | |
| 1118 | writev: WriteV, | |
| 1119 | pwritev: PWriteV, | |
| 1120 | preadv: PReadV, | |
| 1121 | open: Open, | |
| 1122 | openat: OpenAt, | |
| 1123 | close: Close, | |
| 1124 | ||
| 1125 | /// special - means the fs thread should exit | |
| 1126 | end, | |
| 1127 | ||
| 1128 | pub const Read = struct { | |
| 1129 | fd: os.fd_t, | |
| 1130 | buf: []u8, | |
| 1131 | result: Error!usize, | |
| 1132 | ||
| 1133 | pub const Error = os.ReadError; | |
| 1134 | }; | |
| 1135 | ||
| 1136 | pub const Write = struct { | |
| 1137 | fd: os.fd_t, | |
| 1138 | bytes: []const u8, | |
| 1139 | result: Error!void, | |
| 1140 | ||
| 1141 | pub const Error = os.WriteError; | |
| 1142 | }; | |
| 1143 | ||
| 1144 | pub const WriteV = struct { | |
| 1145 | fd: os.fd_t, | |
| 1146 | iov: []const os.iovec_const, | |
| 1147 | result: Error!void, | |
| 1148 | ||
| 1149 | pub const Error = os.WriteError; | |
| 1150 | }; | |
| 1151 | ||
| 1152 | pub const PWriteV = struct { | |
| 1153 | fd: os.fd_t, | |
| 1154 | iov: []const os.iovec_const, | |
| 1155 | offset: usize, | |
| 1156 | result: Error!void, | |
| 1157 | ||
| 1158 | pub const Error = os.WriteError; | |
| 1159 | }; | |
| 1160 | ||
| 1161 | pub const PReadV = struct { | |
| 1162 | fd: os.fd_t, | |
| 1163 | iov: []const os.iovec, | |
| 1164 | offset: usize, | |
| 1165 | result: Error!usize, | |
| 1166 | ||
| 1167 | pub const Error = os.ReadError; | |
| 1168 | }; | |
| 1169 | ||
| 1170 | pub const Open = struct { | |
| 1171 | path: [*:0]const u8, | |
| 1172 | flags: u32, | |
| 1173 | mode: os.mode_t, | |
| 1174 | result: Error!os.fd_t, | |
| 1175 | ||
| 1176 | pub const Error = os.OpenError; | |
| 1177 | }; | |
| 1178 | ||
| 1179 | pub const OpenAt = struct { | |
| 1180 | fd: os.fd_t, | |
| 1181 | path: [*:0]const u8, | |
| 1182 | flags: u32, | |
| 1183 | mode: os.mode_t, | |
| 1184 | result: Error!os.fd_t, | |
| 1185 | ||
| 1186 | pub const Error = os.OpenError; | |
| 1187 | }; | |
| 1188 | ||
| 1189 | pub const Close = struct { | |
| 1190 | fd: os.fd_t, | |
| 1191 | }; | |
| 1192 | }; | |
| 926 | 1193 | }; |
| 927 | 1194 | }; |
| 928 | 1195 |
lib/std/fmt.zig+16-16| ... | ... | @@ -78,7 +78,7 @@ fn peekIsAlign(comptime fmt: []const u8) bool { |
| 78 | 78 | pub fn format( |
| 79 | 79 | context: var, |
| 80 | 80 | comptime Errors: type, |
| 81 | output: fn (@TypeOf(context), []const u8) Errors!void, | |
| 81 | comptime output: fn (@TypeOf(context), []const u8) Errors!void, | |
| 82 | 82 | comptime fmt: []const u8, |
| 83 | 83 | args: var, |
| 84 | 84 | ) Errors!void { |
| ... | ... | @@ -326,7 +326,7 @@ pub fn formatType( |
| 326 | 326 | options: FormatOptions, |
| 327 | 327 | context: var, |
| 328 | 328 | comptime Errors: type, |
| 329 | output: fn (@TypeOf(context), []const u8) Errors!void, | |
| 329 | comptime output: fn (@TypeOf(context), []const u8) Errors!void, | |
| 330 | 330 | max_depth: usize, |
| 331 | 331 | ) Errors!void { |
| 332 | 332 | if (comptime std.mem.eql(u8, fmt, "*")) { |
| ... | ... | @@ -488,7 +488,7 @@ fn formatValue( |
| 488 | 488 | options: FormatOptions, |
| 489 | 489 | context: var, |
| 490 | 490 | comptime Errors: type, |
| 491 | output: fn (@TypeOf(context), []const u8) Errors!void, | |
| 491 | comptime output: fn (@TypeOf(context), []const u8) Errors!void, | |
| 492 | 492 | ) Errors!void { |
| 493 | 493 | if (comptime std.mem.eql(u8, fmt, "B")) { |
| 494 | 494 | return formatBytes(value, options, 1000, context, Errors, output); |
| ... | ... | @@ -510,7 +510,7 @@ pub fn formatIntValue( |
| 510 | 510 | options: FormatOptions, |
| 511 | 511 | context: var, |
| 512 | 512 | comptime Errors: type, |
| 513 | output: fn (@TypeOf(context), []const u8) Errors!void, | |
| 513 | comptime output: fn (@TypeOf(context), []const u8) Errors!void, | |
| 514 | 514 | ) Errors!void { |
| 515 | 515 | comptime var radix = 10; |
| 516 | 516 | comptime var uppercase = false; |
| ... | ... | @@ -552,7 +552,7 @@ fn formatFloatValue( |
| 552 | 552 | options: FormatOptions, |
| 553 | 553 | context: var, |
| 554 | 554 | comptime Errors: type, |
| 555 | output: fn (@TypeOf(context), []const u8) Errors!void, | |
| 555 | comptime output: fn (@TypeOf(context), []const u8) Errors!void, | |
| 556 | 556 | ) Errors!void { |
| 557 | 557 | if (fmt.len == 0 or comptime std.mem.eql(u8, fmt, "e")) { |
| 558 | 558 | return formatFloatScientific(value, options, context, Errors, output); |
| ... | ... | @@ -569,7 +569,7 @@ pub fn formatText( |
| 569 | 569 | options: FormatOptions, |
| 570 | 570 | context: var, |
| 571 | 571 | comptime Errors: type, |
| 572 | output: fn (@TypeOf(context), []const u8) Errors!void, | |
| 572 | comptime output: fn (@TypeOf(context), []const u8) Errors!void, | |
| 573 | 573 | ) Errors!void { |
| 574 | 574 | if (fmt.len == 0) { |
| 575 | 575 | return output(context, bytes); |
| ... | ... | @@ -590,7 +590,7 @@ pub fn formatAsciiChar( |
| 590 | 590 | options: FormatOptions, |
| 591 | 591 | context: var, |
| 592 | 592 | comptime Errors: type, |
| 593 | output: fn (@TypeOf(context), []const u8) Errors!void, | |
| 593 | comptime output: fn (@TypeOf(context), []const u8) Errors!void, | |
| 594 | 594 | ) Errors!void { |
| 595 | 595 | return output(context, @as(*const [1]u8, &c)[0..]); |
| 596 | 596 | } |
| ... | ... | @@ -600,7 +600,7 @@ pub fn formatBuf( |
| 600 | 600 | options: FormatOptions, |
| 601 | 601 | context: var, |
| 602 | 602 | comptime Errors: type, |
| 603 | output: fn (@TypeOf(context), []const u8) Errors!void, | |
| 603 | comptime output: fn (@TypeOf(context), []const u8) Errors!void, | |
| 604 | 604 | ) Errors!void { |
| 605 | 605 | try output(context, buf); |
| 606 | 606 | |
| ... | ... | @@ -620,7 +620,7 @@ pub fn formatFloatScientific( |
| 620 | 620 | options: FormatOptions, |
| 621 | 621 | context: var, |
| 622 | 622 | comptime Errors: type, |
| 623 | output: fn (@TypeOf(context), []const u8) Errors!void, | |
| 623 | comptime output: fn (@TypeOf(context), []const u8) Errors!void, | |
| 624 | 624 | ) Errors!void { |
| 625 | 625 | var x = @floatCast(f64, value); |
| 626 | 626 | |
| ... | ... | @@ -715,7 +715,7 @@ pub fn formatFloatDecimal( |
| 715 | 715 | options: FormatOptions, |
| 716 | 716 | context: var, |
| 717 | 717 | comptime Errors: type, |
| 718 | output: fn (@TypeOf(context), []const u8) Errors!void, | |
| 718 | comptime output: fn (@TypeOf(context), []const u8) Errors!void, | |
| 719 | 719 | ) Errors!void { |
| 720 | 720 | var x = @as(f64, value); |
| 721 | 721 | |
| ... | ... | @@ -861,7 +861,7 @@ pub fn formatBytes( |
| 861 | 861 | comptime radix: usize, |
| 862 | 862 | context: var, |
| 863 | 863 | comptime Errors: type, |
| 864 | output: fn (@TypeOf(context), []const u8) Errors!void, | |
| 864 | comptime output: fn (@TypeOf(context), []const u8) Errors!void, | |
| 865 | 865 | ) Errors!void { |
| 866 | 866 | if (value == 0) { |
| 867 | 867 | return output(context, "0B"); |
| ... | ... | @@ -902,7 +902,7 @@ pub fn formatInt( |
| 902 | 902 | options: FormatOptions, |
| 903 | 903 | context: var, |
| 904 | 904 | comptime Errors: type, |
| 905 | output: fn (@TypeOf(context), []const u8) Errors!void, | |
| 905 | comptime output: fn (@TypeOf(context), []const u8) Errors!void, | |
| 906 | 906 | ) Errors!void { |
| 907 | 907 | const int_value = if (@TypeOf(value) == comptime_int) blk: { |
| 908 | 908 | const Int = math.IntFittingRange(value, value); |
| ... | ... | @@ -924,7 +924,7 @@ fn formatIntSigned( |
| 924 | 924 | options: FormatOptions, |
| 925 | 925 | context: var, |
| 926 | 926 | comptime Errors: type, |
| 927 | output: fn (@TypeOf(context), []const u8) Errors!void, | |
| 927 | comptime output: fn (@TypeOf(context), []const u8) Errors!void, | |
| 928 | 928 | ) Errors!void { |
| 929 | 929 | const new_options = FormatOptions{ |
| 930 | 930 | .width = if (options.width) |w| (if (w == 0) 0 else w - 1) else null, |
| ... | ... | @@ -955,7 +955,7 @@ fn formatIntUnsigned( |
| 955 | 955 | options: FormatOptions, |
| 956 | 956 | context: var, |
| 957 | 957 | comptime Errors: type, |
| 958 | output: fn (@TypeOf(context), []const u8) Errors!void, | |
| 958 | comptime output: fn (@TypeOf(context), []const u8) Errors!void, | |
| 959 | 959 | ) Errors!void { |
| 960 | 960 | assert(base >= 2); |
| 961 | 961 | var buf: [math.max(@TypeOf(value).bit_count, 1)]u8 = undefined; |
| ... | ... | @@ -1419,7 +1419,7 @@ test "custom" { |
| 1419 | 1419 | options: FormatOptions, |
| 1420 | 1420 | context: var, |
| 1421 | 1421 | comptime Errors: type, |
| 1422 | output: fn (@TypeOf(context), []const u8) Errors!void, | |
| 1422 | comptime output: fn (@TypeOf(context), []const u8) Errors!void, | |
| 1423 | 1423 | ) Errors!void { |
| 1424 | 1424 | if (fmt.len == 0 or comptime std.mem.eql(u8, fmt, "p")) { |
| 1425 | 1425 | return std.fmt.format(context, Errors, output, "({d:.3},{d:.3})", .{ self.x, self.y }); |
| ... | ... | @@ -1626,7 +1626,7 @@ test "formatType max_depth" { |
| 1626 | 1626 | options: FormatOptions, |
| 1627 | 1627 | context: var, |
| 1628 | 1628 | comptime Errors: type, |
| 1629 | output: fn (@TypeOf(context), []const u8) Errors!void, | |
| 1629 | comptime output: fn (@TypeOf(context), []const u8) Errors!void, | |
| 1630 | 1630 | ) Errors!void { |
| 1631 | 1631 | if (fmt.len == 0) { |
| 1632 | 1632 | return std.fmt.format(context, Errors, output, "({d:.3},{d:.3})", .{ self.x, self.y }); |
lib/std/fs.zig+41-12| ... | ... | @@ -23,6 +23,8 @@ pub const realpathW = os.realpathW; |
| 23 | 23 | pub const getAppDataDir = @import("fs/get_app_data_dir.zig").getAppDataDir; |
| 24 | 24 | pub const GetAppDataDirError = @import("fs/get_app_data_dir.zig").GetAppDataDirError; |
| 25 | 25 | |
| 26 | pub const Watch = @import("fs/watch.zig").Watch; | |
| 27 | ||
| 26 | 28 | /// This represents the maximum size of a UTF-8 encoded file path. |
| 27 | 29 | /// All file system operations which return a path are guaranteed to |
| 28 | 30 | /// fit into a UTF-8 encoded array of this length. |
| ... | ... | @@ -43,6 +45,13 @@ pub const base64_encoder = base64.Base64Encoder.init( |
| 43 | 45 | base64.standard_pad_char, |
| 44 | 46 | ); |
| 45 | 47 | |
| 48 | /// Whether or not async file system syscalls need a dedicated thread because the operating | |
| 49 | /// system does not support non-blocking I/O on the file system. | |
| 50 | pub const need_async_thread = std.io.is_async and switch (builtin.os) { | |
| 51 | .windows, .other => false, | |
| 52 | else => true, | |
| 53 | }; | |
| 54 | ||
| 46 | 55 | /// TODO remove the allocator requirement from this API |
| 47 | 56 | pub fn atomicSymLink(allocator: *Allocator, existing_path: []const u8, new_path: []const u8) !void { |
| 48 | 57 | if (symLink(existing_path, new_path)) { |
| ... | ... | @@ -688,11 +697,16 @@ pub const Dir = struct { |
| 688 | 697 | } |
| 689 | 698 | |
| 690 | 699 | pub fn close(self: *Dir) void { |
| 691 | os.close(self.fd); | |
| 700 | if (need_async_thread) { | |
| 701 | std.event.Loop.instance.?.close(self.fd); | |
| 702 | } else { | |
| 703 | os.close(self.fd); | |
| 704 | } | |
| 692 | 705 | self.* = undefined; |
| 693 | 706 | } |
| 694 | 707 | |
| 695 | 708 | /// Opens a file for reading or writing, without attempting to create a new file. |
| 709 | /// To create a new file, see `createFile`. | |
| 696 | 710 | /// Call `File.close` to release the resource. |
| 697 | 711 | /// Asserts that the path parameter has no null bytes. |
| 698 | 712 | pub fn openFile(self: Dir, sub_path: []const u8, flags: File.OpenFlags) File.OpenError!File { |
| ... | ... | @@ -718,8 +732,11 @@ pub const Dir = struct { |
| 718 | 732 | @as(u32, os.O_WRONLY) |
| 719 | 733 | else |
| 720 | 734 | @as(u32, os.O_RDONLY); |
| 721 | const fd = try os.openatC(self.fd, sub_path, os_flags, 0); | |
| 722 | return File{ .handle = fd }; | |
| 735 | const fd = if (need_async_thread) | |
| 736 | try std.event.Loop.instance.?.openatZ(self.fd, sub_path, os_flags, 0) | |
| 737 | else | |
| 738 | try os.openatC(self.fd, sub_path, os_flags, 0); | |
| 739 | return File{ .handle = fd, .io_mode = .blocking }; | |
| 723 | 740 | } |
| 724 | 741 | |
| 725 | 742 | /// Same as `openFile` but Windows-only and the path parameter is |
| ... | ... | @@ -756,8 +773,11 @@ pub const Dir = struct { |
| 756 | 773 | (if (flags.truncate) @as(u32, os.O_TRUNC) else 0) | |
| 757 | 774 | (if (flags.read) @as(u32, os.O_RDWR) else os.O_WRONLY) | |
| 758 | 775 | (if (flags.exclusive) @as(u32, os.O_EXCL) else 0); |
| 759 | const fd = try os.openatC(self.fd, sub_path_c, os_flags, flags.mode); | |
| 760 | return File{ .handle = fd }; | |
| 776 | const fd = if (need_async_thread) | |
| 777 | try std.event.Loop.instance.?.openatZ(self.fd, sub_path_c, os_flags, flags.mode) | |
| 778 | else | |
| 779 | try os.openatC(self.fd, sub_path_c, os_flags, flags.mode); | |
| 780 | return File{ .handle = fd, .io_mode = .blocking }; | |
| 761 | 781 | } |
| 762 | 782 | |
| 763 | 783 | /// Same as `createFile` but Windows-only and the path parameter is |
| ... | ... | @@ -798,7 +818,10 @@ pub const Dir = struct { |
| 798 | 818 | ) File.OpenError!File { |
| 799 | 819 | const w = os.windows; |
| 800 | 820 | |
| 801 | var result = File{ .handle = undefined }; | |
| 821 | var result = File{ | |
| 822 | .handle = undefined, | |
| 823 | .io_mode = .blocking, | |
| 824 | }; | |
| 802 | 825 | |
| 803 | 826 | const path_len_bytes = math.cast(u16, mem.toSliceConst(u16, sub_path_w).len * 2) catch |err| switch (err) { |
| 804 | 827 | error.Overflow => return error.NameTooLong, |
| ... | ... | @@ -810,7 +833,7 @@ pub const Dir = struct { |
| 810 | 833 | }; |
| 811 | 834 | var attr = w.OBJECT_ATTRIBUTES{ |
| 812 | 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 | 837 | .Attributes = 0, // Note we do not use OBJ_CASE_INSENSITIVE here. |
| 815 | 838 | .ObjectName = &nt_name, |
| 816 | 839 | .SecurityDescriptor = null, |
| ... | ... | @@ -919,7 +942,12 @@ pub const Dir = struct { |
| 919 | 942 | } |
| 920 | 943 | |
| 921 | 944 | fn openDirFlagsC(self: Dir, sub_path_c: [*:0]const u8, flags: u32) OpenError!Dir { |
| 922 | const fd = os.openatC(self.fd, sub_path_c, flags | os.O_DIRECTORY, 0) catch |err| switch (err) { | |
| 945 | const os_flags = flags | os.O_DIRECTORY; | |
| 946 | const result = if (need_async_thread) | |
| 947 | std.event.Loop.instance.?.openatZ(self.fd, sub_path_c, os_flags, 0) | |
| 948 | else | |
| 949 | os.openatC(self.fd, sub_path_c, os_flags, 0); | |
| 950 | const fd = result catch |err| switch (err) { | |
| 923 | 951 | error.FileTooBig => unreachable, // can't happen for directories |
| 924 | 952 | error.IsDir => unreachable, // we're providing O_DIRECTORY |
| 925 | 953 | error.NoSpaceLeft => unreachable, // not providing O_CREAT |
| ... | ... | @@ -960,7 +988,7 @@ pub const Dir = struct { |
| 960 | 988 | }; |
| 961 | 989 | var attr = w.OBJECT_ATTRIBUTES{ |
| 962 | 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 | 992 | .Attributes = 0, // Note we do not use OBJ_CASE_INSENSITIVE here. |
| 965 | 993 | .ObjectName = &nt_name, |
| 966 | 994 | .SecurityDescriptor = null, |
| ... | ... | @@ -1327,7 +1355,7 @@ pub fn openFileAbsoluteC(absolute_path_c: [*:0]const u8, flags: File.OpenFlags) |
| 1327 | 1355 | |
| 1328 | 1356 | /// Same as `openFileAbsolute` but the path parameter is WTF-16 encoded. |
| 1329 | 1357 | pub 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 | 1359 | return cwd().openFileW(absolute_path_w, flags); |
| 1332 | 1360 | } |
| 1333 | 1361 | |
| ... | ... | @@ -1350,7 +1378,7 @@ pub fn createFileAbsoluteC(absolute_path_c: [*:0]const u8, flags: File.CreateFla |
| 1350 | 1378 | |
| 1351 | 1379 | /// Same as `createFileAbsolute` but the path parameter is WTF-16 encoded. |
| 1352 | 1380 | pub 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 | 1382 | return cwd().createFileW(absolute_path_w, flags); |
| 1355 | 1383 | } |
| 1356 | 1384 | |
| ... | ... | @@ -1371,7 +1399,7 @@ pub fn deleteFileAbsoluteC(absolute_path_c: [*:0]const u8) DeleteFileError!void |
| 1371 | 1399 | |
| 1372 | 1400 | /// Same as `deleteFileAbsolute` except the parameter is WTF-16 encoded. |
| 1373 | 1401 | pub 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 | 1403 | return cwd().deleteFileW(absolute_path_w); |
| 1376 | 1404 | } |
| 1377 | 1405 | |
| ... | ... | @@ -1588,4 +1616,5 @@ test "" { |
| 1588 | 1616 | _ = @import("fs/path.zig"); |
| 1589 | 1617 | _ = @import("fs/file.zig"); |
| 1590 | 1618 | _ = @import("fs/get_app_data_dir.zig"); |
| 1619 | _ = @import("fs/watch.zig"); | |
| 1591 | 1620 | } |
lib/std/fs/file.zig+78-75| ... | ... | @@ -8,18 +8,29 @@ const assert = std.debug.assert; |
| 8 | 8 | const windows = os.windows; |
| 9 | 9 | const Os = builtin.Os; |
| 10 | 10 | const maxInt = std.math.maxInt; |
| 11 | const need_async_thread = std.fs.need_async_thread; | |
| 11 | 12 | |
| 12 | 13 | pub const File = struct { |
| 13 | 14 | /// The OS-specific file descriptor or file handle. |
| 14 | 15 | handle: os.fd_t, |
| 15 | 16 | |
| 16 | pub const Mode = switch (builtin.os) { | |
| 17 | Os.windows => void, | |
| 18 | else => u32, | |
| 19 | }; | |
| 17 | /// On some systems, such as Linux, file system file descriptors are incapable of non-blocking I/O. | |
| 18 | /// This forces us to perform asynchronous I/O on a dedicated thread, to achieve non-blocking | |
| 19 | /// file-system I/O. To do this, `File` must be aware of whether it is a file system file descriptor, | |
| 20 | /// or, more specifically, whether the I/O is blocking. | |
| 21 | io_mode: io.Mode, | |
| 22 | ||
| 23 | /// Even when std.io.mode is async, it is still sometimes desirable to perform blocking I/O, although | |
| 24 | /// not by default. For example, when printing a stack trace to stderr. | |
| 25 | async_block_allowed: @TypeOf(async_block_allowed_no) = async_block_allowed_no, | |
| 26 | ||
| 27 | pub const async_block_allowed_yes = if (io.is_async) true else {}; | |
| 28 | pub const async_block_allowed_no = if (io.is_async) false else {}; | |
| 29 | ||
| 30 | pub const Mode = os.mode_t; | |
| 20 | 31 | |
| 21 | 32 | pub const default_mode = switch (builtin.os) { |
| 22 | Os.windows => {}, | |
| 33 | .windows => 0, | |
| 23 | 34 | else => 0o666, |
| 24 | 35 | }; |
| 25 | 36 | |
| ... | ... | @@ -49,87 +60,27 @@ pub const File = struct { |
| 49 | 60 | mode: Mode = default_mode, |
| 50 | 61 | }; |
| 51 | 62 | |
| 52 | /// Deprecated; call `std.fs.Dir.openFile` directly. | |
| 53 | pub fn openRead(path: []const u8) OpenError!File { | |
| 54 | return std.fs.cwd().openFile(path, .{}); | |
| 55 | } | |
| 56 | ||
| 57 | /// Deprecated; call `std.fs.Dir.openFileC` directly. | |
| 58 | pub fn openReadC(path_c: [*:0]const u8) OpenError!File { | |
| 59 | return std.fs.cwd().openFileC(path_c, .{}); | |
| 60 | } | |
| 61 | ||
| 62 | /// Deprecated; call `std.fs.Dir.openFileW` directly. | |
| 63 | pub fn openReadW(path_w: [*:0]const u16) OpenError!File { | |
| 64 | return std.fs.cwd().openFileW(path_w, .{}); | |
| 65 | } | |
| 66 | ||
| 67 | /// Deprecated; call `std.fs.Dir.createFile` directly. | |
| 68 | pub fn openWrite(path: []const u8) OpenError!File { | |
| 69 | return std.fs.cwd().createFile(path, .{}); | |
| 70 | } | |
| 71 | ||
| 72 | /// Deprecated; call `std.fs.Dir.createFile` directly. | |
| 73 | pub fn openWriteMode(path: []const u8, file_mode: Mode) OpenError!File { | |
| 74 | return std.fs.cwd().createFile(path, .{ .mode = file_mode }); | |
| 75 | } | |
| 76 | ||
| 77 | /// Deprecated; call `std.fs.Dir.createFileC` directly. | |
| 78 | pub fn openWriteModeC(path_c: [*:0]const u8, file_mode: Mode) OpenError!File { | |
| 79 | return std.fs.cwd().createFileC(path_c, .{ .mode = file_mode }); | |
| 80 | } | |
| 81 | ||
| 82 | /// Deprecated; call `std.fs.Dir.createFileW` directly. | |
| 83 | pub fn openWriteModeW(path_w: [*:0]const u16, file_mode: Mode) OpenError!File { | |
| 84 | return std.fs.cwd().createFileW(path_w, .{ .mode = file_mode }); | |
| 85 | } | |
| 86 | ||
| 87 | /// Deprecated; call `std.fs.Dir.createFile` directly. | |
| 88 | pub fn openWriteNoClobber(path: []const u8, file_mode: Mode) OpenError!File { | |
| 89 | return std.fs.cwd().createFile(path, .{ | |
| 90 | .mode = file_mode, | |
| 91 | .exclusive = true, | |
| 92 | }); | |
| 93 | } | |
| 94 | ||
| 95 | /// Deprecated; call `std.fs.Dir.createFileC` directly. | |
| 96 | pub fn openWriteNoClobberC(path_c: [*:0]const u8, file_mode: Mode) OpenError!File { | |
| 97 | return std.fs.cwd().createFileC(path_c, .{ | |
| 98 | .mode = file_mode, | |
| 99 | .exclusive = true, | |
| 100 | }); | |
| 101 | } | |
| 102 | ||
| 103 | /// Deprecated; call `std.fs.Dir.createFileW` directly. | |
| 104 | pub fn openWriteNoClobberW(path_w: [*:0]const u16, file_mode: Mode) OpenError!File { | |
| 105 | return std.fs.cwd().createFileW(path_w, .{ | |
| 106 | .mode = file_mode, | |
| 107 | .exclusive = true, | |
| 108 | }); | |
| 109 | } | |
| 110 | ||
| 111 | pub fn openHandle(handle: os.fd_t) File { | |
| 112 | return File{ .handle = handle }; | |
| 113 | } | |
| 114 | ||
| 115 | 63 | /// Test for the existence of `path`. |
| 116 | 64 | /// `path` is UTF8-encoded. |
| 117 | 65 | /// In general it is recommended to avoid this function. For example, |
| 118 | 66 | /// instead of testing if a file exists and then opening it, just |
| 119 | 67 | /// open it and handle the error for file not found. |
| 120 | 68 | /// TODO: deprecate this and move it to `std.fs.Dir`. |
| 69 | /// TODO: integrate with async I/O | |
| 121 | 70 | pub fn access(path: []const u8) !void { |
| 122 | 71 | return os.access(path, os.F_OK); |
| 123 | 72 | } |
| 124 | 73 | |
| 125 | 74 | /// Same as `access` except the parameter is null-terminated. |
| 126 | 75 | /// TODO: deprecate this and move it to `std.fs.Dir`. |
| 76 | /// TODO: integrate with async I/O | |
| 127 | 77 | pub fn accessC(path: [*:0]const u8) !void { |
| 128 | 78 | return os.accessC(path, os.F_OK); |
| 129 | 79 | } |
| 130 | 80 | |
| 131 | 81 | /// Same as `access` except the parameter is null-terminated UTF16LE-encoded. |
| 132 | 82 | /// TODO: deprecate this and move it to `std.fs.Dir`. |
| 83 | /// TODO: integrate with async I/O | |
| 133 | 84 | pub fn accessW(path: [*:0]const u16) !void { |
| 134 | 85 | return os.accessW(path, os.F_OK); |
| 135 | 86 | } |
| ... | ... | @@ -137,7 +88,11 @@ pub const File = struct { |
| 137 | 88 | /// Upon success, the stream is in an uninitialized state. To continue using it, |
| 138 | 89 | /// you must use the open() function. |
| 139 | 90 | pub fn close(self: File) void { |
| 140 | return os.close(self.handle); | |
| 91 | if (need_async_thread and self.io_mode == .blocking and !self.async_block_allowed) { | |
| 92 | std.event.Loop.instance.?.close(self.handle); | |
| 93 | } else { | |
| 94 | return os.close(self.handle); | |
| 95 | } | |
| 141 | 96 | } |
| 142 | 97 | |
| 143 | 98 | /// Test whether the file refers to a terminal. |
| ... | ... | @@ -167,26 +122,31 @@ pub const File = struct { |
| 167 | 122 | pub const SeekError = os.SeekError; |
| 168 | 123 | |
| 169 | 124 | /// Repositions read/write file offset relative to the current offset. |
| 125 | /// TODO: integrate with async I/O | |
| 170 | 126 | pub fn seekBy(self: File, offset: i64) SeekError!void { |
| 171 | 127 | return os.lseek_CUR(self.handle, offset); |
| 172 | 128 | } |
| 173 | 129 | |
| 174 | 130 | /// Repositions read/write file offset relative to the end. |
| 131 | /// TODO: integrate with async I/O | |
| 175 | 132 | pub fn seekFromEnd(self: File, offset: i64) SeekError!void { |
| 176 | 133 | return os.lseek_END(self.handle, offset); |
| 177 | 134 | } |
| 178 | 135 | |
| 179 | 136 | /// Repositions read/write file offset relative to the beginning. |
| 137 | /// TODO: integrate with async I/O | |
| 180 | 138 | pub fn seekTo(self: File, offset: u64) SeekError!void { |
| 181 | 139 | return os.lseek_SET(self.handle, offset); |
| 182 | 140 | } |
| 183 | 141 | |
| 184 | 142 | pub const GetPosError = os.SeekError || os.FStatError; |
| 185 | 143 | |
| 144 | /// TODO: integrate with async I/O | |
| 186 | 145 | pub fn getPos(self: File) GetPosError!u64 { |
| 187 | 146 | return os.lseek_CUR_get(self.handle); |
| 188 | 147 | } |
| 189 | 148 | |
| 149 | /// TODO: integrate with async I/O | |
| 190 | 150 | pub fn getEndPos(self: File) GetPosError!u64 { |
| 191 | 151 | if (builtin.os == .windows) { |
| 192 | 152 | return windows.GetFileSizeEx(self.handle); |
| ... | ... | @@ -196,6 +156,7 @@ pub const File = struct { |
| 196 | 156 | |
| 197 | 157 | pub const ModeError = os.FStatError; |
| 198 | 158 | |
| 159 | /// TODO: integrate with async I/O | |
| 199 | 160 | pub fn mode(self: File) ModeError!Mode { |
| 200 | 161 | if (builtin.os == .windows) { |
| 201 | 162 | return {}; |
| ... | ... | @@ -219,6 +180,7 @@ pub const File = struct { |
| 219 | 180 | |
| 220 | 181 | pub const StatError = os.FStatError; |
| 221 | 182 | |
| 183 | /// TODO: integrate with async I/O | |
| 222 | 184 | pub fn stat(self: File) StatError!Stat { |
| 223 | 185 | if (builtin.os == .windows) { |
| 224 | 186 | var io_status_block: windows.IO_STATUS_BLOCK = undefined; |
| ... | ... | @@ -233,7 +195,7 @@ pub const File = struct { |
| 233 | 195 | } |
| 234 | 196 | return Stat{ |
| 235 | 197 | .size = @bitCast(u64, info.StandardInformation.EndOfFile), |
| 236 | .mode = {}, | |
| 198 | .mode = 0, | |
| 237 | 199 | .atime = windows.fromSysTime(info.BasicInformation.LastAccessTime), |
| 238 | 200 | .mtime = windows.fromSysTime(info.BasicInformation.LastWriteTime), |
| 239 | 201 | .ctime = windows.fromSysTime(info.BasicInformation.CreationTime), |
| ... | ... | @@ -259,6 +221,7 @@ pub const File = struct { |
| 259 | 221 | /// and therefore this function cannot guarantee any precision will be stored. |
| 260 | 222 | /// Further, the maximum value is limited by the system ABI. When a value is provided |
| 261 | 223 | /// that exceeds this range, the value is clamped to the maximum. |
| 224 | /// TODO: integrate with async I/O | |
| 262 | 225 | pub fn updateTimes( |
| 263 | 226 | self: File, |
| 264 | 227 | /// access timestamp in nanoseconds |
| ... | ... | @@ -287,21 +250,61 @@ pub const File = struct { |
| 287 | 250 | pub const ReadError = os.ReadError; |
| 288 | 251 | |
| 289 | 252 | pub fn read(self: File, buffer: []u8) ReadError!usize { |
| 253 | if (need_async_thread and self.io_mode == .blocking and !self.async_block_allowed) { | |
| 254 | return std.event.Loop.instance.?.read(self.handle, buffer); | |
| 255 | } | |
| 290 | 256 | return os.read(self.handle, buffer); |
| 291 | 257 | } |
| 292 | 258 | |
| 259 | pub fn pread(self: File, buffer: []u8, offset: u64) ReadError!usize { | |
| 260 | if (need_async_thread and self.io_mode == .blocking and !self.async_block_allowed) { | |
| 261 | return std.event.Loop.instance.?.pread(self.handle, buffer); | |
| 262 | } | |
| 263 | return os.pread(self.handle, buffer, offset); | |
| 264 | } | |
| 265 | ||
| 266 | pub fn readv(self: File, iovecs: []const os.iovec) ReadError!usize { | |
| 267 | if (need_async_thread and self.io_mode == .blocking and !self.async_block_allowed) { | |
| 268 | return std.event.Loop.instance.?.readv(self.handle, iovecs); | |
| 269 | } | |
| 270 | return os.readv(self.handle, iovecs); | |
| 271 | } | |
| 272 | ||
| 273 | pub fn preadv(self: File, iovecs: []const os.iovec, offset: u64) ReadError!usize { | |
| 274 | if (need_async_thread and self.io_mode == .blocking and !self.async_block_allowed) { | |
| 275 | return std.event.Loop.instance.?.preadv(self.handle, iovecs, offset); | |
| 276 | } | |
| 277 | return os.preadv(self.handle, iovecs, offset); | |
| 278 | } | |
| 279 | ||
| 293 | 280 | pub const WriteError = os.WriteError; |
| 294 | 281 | |
| 295 | 282 | pub fn write(self: File, bytes: []const u8) WriteError!void { |
| 283 | if (need_async_thread and self.io_mode == .blocking and !self.async_block_allowed) { | |
| 284 | return std.event.Loop.instance.?.write(self.handle, bytes); | |
| 285 | } | |
| 296 | 286 | return os.write(self.handle, bytes); |
| 297 | 287 | } |
| 298 | 288 | |
| 299 | pub fn writev_iovec(self: File, iovecs: []const os.iovec_const) WriteError!void { | |
| 300 | if (std.event.Loop.instance) |loop| { | |
| 301 | return std.event.fs.writevPosix(loop, self.handle, iovecs); | |
| 302 | } else { | |
| 303 | return os.writev(self.handle, iovecs); | |
| 289 | pub fn pwrite(self: File, bytes: []const u8, offset: u64) WriteError!void { | |
| 290 | if (need_async_thread and self.io_mode == .blocking and !self.async_block_allowed) { | |
| 291 | return std.event.Loop.instance.?.pwrite(self.handle, bytes, offset); | |
| 292 | } | |
| 293 | return os.pwrite(self.handle, bytes, offset); | |
| 294 | } | |
| 295 | ||
| 296 | pub fn writev(self: File, iovecs: []const os.iovec_const) WriteError!void { | |
| 297 | if (need_async_thread and self.io_mode == .blocking and !self.async_block_allowed) { | |
| 298 | return std.event.Loop.instance.?.writev(self.handle, iovecs); | |
| 299 | } | |
| 300 | return os.writev(self.handle, iovecs); | |
| 301 | } | |
| 302 | ||
| 303 | pub fn pwritev(self: File, iovecs: []const os.iovec_const, offset: usize) WriteError!void { | |
| 304 | if (need_async_thread and self.io_mode == .blocking and !self.async_block_allowed) { | |
| 305 | return std.event.Loop.instance.?.pwritev(self.handle, iovecs); | |
| 304 | 306 | } |
| 307 | return os.pwritev(self.handle, iovecs); | |
| 305 | 308 | } |
| 306 | 309 | |
| 307 | 310 | pub fn inStream(file: File) InStream { |
lib/std/fs/path.zig+20-40| ... | ... | @@ -146,72 +146,51 @@ pub fn isAbsolute(path: []const u8) bool { |
| 146 | 146 | } |
| 147 | 147 | } |
| 148 | 148 | |
| 149 | pub fn isAbsoluteW(path_w: [*:0]const u16) bool { | |
| 150 | if (path_w[0] == '/') | |
| 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) { | |
| 149 | fn isAbsoluteWindowsImpl(comptime T: type, path: []const T) bool { | |
| 150 | if (path.len < 1) | |
| 157 | 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 | } | |
| 167 | 152 | |
| 168 | pub fn isAbsoluteWindows(path: []const u8) bool { | |
| 169 | 153 | if (path[0] == '/') |
| 170 | 154 | return true; |
| 171 | 155 | |
| 172 | if (path[0] == '\\') { | |
| 156 | if (path[0] == '\\') | |
| 173 | 157 | return true; |
| 174 | } | |
| 175 | if (path.len < 3) { | |
| 158 | ||
| 159 | if (path.len < 3) | |
| 176 | 160 | return false; |
| 177 | } | |
| 161 | ||
| 178 | 162 | if (path[1] == ':') { |
| 179 | 163 | if (path[2] == '/') |
| 180 | 164 | return true; |
| 181 | 165 | if (path[2] == '\\') |
| 182 | 166 | return true; |
| 183 | 167 | } |
| 168 | ||
| 184 | 169 | return false; |
| 185 | 170 | } |
| 186 | 171 | |
| 187 | pub fn isAbsoluteWindowsC(path_c: [*:0]const u8) bool { | |
| 188 | if (path_c[0] == '/') | |
| 189 | return true; | |
| 172 | pub fn isAbsoluteWindows(path: []const u8) bool { | |
| 173 | return isAbsoluteWindowsImpl(u8, path); | |
| 174 | } | |
| 190 | 175 | |
| 191 | if (path_c[0] == '\\') { | |
| 192 | return true; | |
| 193 | } | |
| 194 | if (path_c[0] == 0 or path_c[1] == 0 or path_c[2] == 0) { | |
| 195 | return false; | |
| 196 | } | |
| 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; | |
| 176 | pub fn isAbsoluteWindowsW(path_w: [*:0]const u16) bool { | |
| 177 | return isAbsoluteWindowsImpl(u16, mem.toSliceConst(u16, path_w)); | |
| 178 | } | |
| 179 | ||
| 180 | pub fn isAbsoluteWindowsC(path_c: [*:0]const u8) bool { | |
| 181 | return isAbsoluteWindowsImpl(u8, mem.toSliceConst(u8, path_c)); | |
| 204 | 182 | } |
| 205 | 183 | |
| 206 | 184 | pub fn isAbsolutePosix(path: []const u8) bool { |
| 207 | return path[0] == sep_posix; | |
| 185 | return path.len > 0 and path[0] == sep_posix; | |
| 208 | 186 | } |
| 209 | 187 | |
| 210 | 188 | pub 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 | } |
| 213 | 191 | |
| 214 | 192 | test "isAbsoluteWindows" { |
| 193 | testIsAbsoluteWindows("", false); | |
| 215 | 194 | testIsAbsoluteWindows("/", true); |
| 216 | 195 | testIsAbsoluteWindows("//", true); |
| 217 | 196 | testIsAbsoluteWindows("//server", true); |
| ... | ... | @@ -234,6 +213,7 @@ test "isAbsoluteWindows" { |
| 234 | 213 | } |
| 235 | 214 | |
| 236 | 215 | test "isAbsolutePosix" { |
| 216 | testIsAbsolutePosix("", false); | |
| 237 | 217 | testIsAbsolutePosix("/home/foo", true); |
| 238 | 218 | testIsAbsolutePosix("/home/foo/..", true); |
| 239 | 219 | testIsAbsolutePosix("bar/", false); |
lib/std/fs/watch.zig created+675| ... | ... | @@ -0,0 +1,675 @@ |
| 1 | const std = @import("../std.zig"); | |
| 2 | const builtin = @import("builtin"); | |
| 3 | const event = std.event; | |
| 4 | const assert = std.debug.assert; | |
| 5 | const testing = std.testing; | |
| 6 | const os = std.os; | |
| 7 | const mem = std.mem; | |
| 8 | const windows = os.windows; | |
| 9 | const Loop = event.Loop; | |
| 10 | const fd_t = os.fd_t; | |
| 11 | const File = std.fs.File; | |
| 12 | const Allocator = mem.Allocator; | |
| 13 | ||
| 14 | const global_event_loop = Loop.instance orelse | |
| 15 | @compileError("std.fs.Watch currently only works with event-based I/O"); | |
| 16 | ||
| 17 | const WatchEventId = enum { | |
| 18 | CloseWrite, | |
| 19 | Delete, | |
| 20 | }; | |
| 21 | ||
| 22 | fn eqlString(a: []const u16, b: []const u16) bool { | |
| 23 | if (a.len != b.len) return false; | |
| 24 | if (a.ptr == b.ptr) return true; | |
| 25 | return mem.compare(u16, a, b) == .Equal; | |
| 26 | } | |
| 27 | ||
| 28 | fn hashString(s: []const u16) u32 { | |
| 29 | return @truncate(u32, std.hash.Wyhash.hash(0, @sliceToBytes(s))); | |
| 30 | } | |
| 31 | ||
| 32 | const WatchEventError = error{ | |
| 33 | UserResourceLimitReached, | |
| 34 | SystemResources, | |
| 35 | AccessDenied, | |
| 36 | Unexpected, // TODO remove this possibility | |
| 37 | }; | |
| 38 | ||
| 39 | pub fn Watch(comptime V: type) type { | |
| 40 | return struct { | |
| 41 | channel: *event.Channel(Event.Error!Event), | |
| 42 | os_data: OsData, | |
| 43 | allocator: *Allocator, | |
| 44 | ||
| 45 | const OsData = switch (builtin.os) { | |
| 46 | // TODO https://github.com/ziglang/zig/issues/3778 | |
| 47 | .macosx, .freebsd, .netbsd, .dragonfly => KqOsData, | |
| 48 | .linux => LinuxOsData, | |
| 49 | .windows => WindowsOsData, | |
| 50 | ||
| 51 | else => @compileError("Unsupported OS"), | |
| 52 | }; | |
| 53 | ||
| 54 | const KqOsData = struct { | |
| 55 | file_table: FileTable, | |
| 56 | table_lock: event.Lock, | |
| 57 | ||
| 58 | const FileTable = std.StringHashMap(*Put); | |
| 59 | const Put = struct { | |
| 60 | putter_frame: @Frame(kqPutEvents), | |
| 61 | cancelled: bool = false, | |
| 62 | value: V, | |
| 63 | }; | |
| 64 | }; | |
| 65 | ||
| 66 | const WindowsOsData = struct { | |
| 67 | table_lock: event.Lock, | |
| 68 | dir_table: DirTable, | |
| 69 | all_putters: std.atomic.Queue(Put), | |
| 70 | ref_count: std.atomic.Int(usize), | |
| 71 | ||
| 72 | const Put = struct { | |
| 73 | putter: anyframe, | |
| 74 | cancelled: bool = false, | |
| 75 | }; | |
| 76 | ||
| 77 | const DirTable = std.StringHashMap(*Dir); | |
| 78 | const FileTable = std.HashMap([]const u16, V, hashString, eqlString); | |
| 79 | ||
| 80 | const Dir = struct { | |
| 81 | putter_frame: @Frame(windowsDirReader), | |
| 82 | file_table: FileTable, | |
| 83 | table_lock: event.Lock, | |
| 84 | }; | |
| 85 | }; | |
| 86 | ||
| 87 | const LinuxOsData = struct { | |
| 88 | putter_frame: @Frame(linuxEventPutter), | |
| 89 | inotify_fd: i32, | |
| 90 | wd_table: WdTable, | |
| 91 | table_lock: event.Lock, | |
| 92 | cancelled: bool = false, | |
| 93 | ||
| 94 | const WdTable = std.AutoHashMap(i32, Dir); | |
| 95 | const FileTable = std.StringHashMap(V); | |
| 96 | ||
| 97 | const Dir = struct { | |
| 98 | dirname: []const u8, | |
| 99 | file_table: FileTable, | |
| 100 | }; | |
| 101 | }; | |
| 102 | ||
| 103 | const Self = @This(); | |
| 104 | ||
| 105 | pub const Event = struct { | |
| 106 | id: Id, | |
| 107 | data: V, | |
| 108 | ||
| 109 | pub const Id = WatchEventId; | |
| 110 | pub const Error = WatchEventError; | |
| 111 | }; | |
| 112 | ||
| 113 | pub fn init(allocator: *Allocator, event_buf_count: usize) !*Self { | |
| 114 | const channel = try allocator.create(event.Channel(Event.Error!Event)); | |
| 115 | errdefer allocator.destroy(channel); | |
| 116 | var buf = try allocator.alloc(Event.Error!Event, event_buf_count); | |
| 117 | errdefer allocator.free(buf); | |
| 118 | channel.init(buf); | |
| 119 | errdefer channel.deinit(); | |
| 120 | ||
| 121 | const self = try allocator.create(Self); | |
| 122 | errdefer allocator.destroy(self); | |
| 123 | ||
| 124 | switch (builtin.os) { | |
| 125 | .linux => { | |
| 126 | const inotify_fd = try os.inotify_init1(os.linux.IN_NONBLOCK | os.linux.IN_CLOEXEC); | |
| 127 | errdefer os.close(inotify_fd); | |
| 128 | ||
| 129 | self.* = Self{ | |
| 130 | .allocator = allocator, | |
| 131 | .channel = channel, | |
| 132 | .os_data = OsData{ | |
| 133 | .putter_frame = undefined, | |
| 134 | .inotify_fd = inotify_fd, | |
| 135 | .wd_table = OsData.WdTable.init(allocator), | |
| 136 | .table_lock = event.Lock.init(), | |
| 137 | }, | |
| 138 | }; | |
| 139 | ||
| 140 | self.os_data.putter_frame = async self.linuxEventPutter(); | |
| 141 | return self; | |
| 142 | }, | |
| 143 | ||
| 144 | .windows => { | |
| 145 | self.* = Self{ | |
| 146 | .allocator = allocator, | |
| 147 | .channel = channel, | |
| 148 | .os_data = OsData{ | |
| 149 | .table_lock = event.Lock.init(), | |
| 150 | .dir_table = OsData.DirTable.init(allocator), | |
| 151 | .ref_count = std.atomic.Int(usize).init(1), | |
| 152 | .all_putters = std.atomic.Queue(anyframe).init(), | |
| 153 | }, | |
| 154 | }; | |
| 155 | return self; | |
| 156 | }, | |
| 157 | ||
| 158 | .macosx, .freebsd, .netbsd, .dragonfly => { | |
| 159 | self.* = Self{ | |
| 160 | .allocator = allocator, | |
| 161 | .channel = channel, | |
| 162 | .os_data = OsData{ | |
| 163 | .table_lock = event.Lock.init(), | |
| 164 | .file_table = OsData.FileTable.init(allocator), | |
| 165 | }, | |
| 166 | }; | |
| 167 | return self; | |
| 168 | }, | |
| 169 | else => @compileError("Unsupported OS"), | |
| 170 | } | |
| 171 | } | |
| 172 | ||
| 173 | /// All addFile calls and removeFile calls must have completed. | |
| 174 | pub fn deinit(self: *Self) void { | |
| 175 | switch (builtin.os) { | |
| 176 | .macosx, .freebsd, .netbsd, .dragonfly => { | |
| 177 | // TODO we need to cancel the frames before destroying the lock | |
| 178 | self.os_data.table_lock.deinit(); | |
| 179 | var it = self.os_data.file_table.iterator(); | |
| 180 | while (it.next()) |entry| { | |
| 181 | entry.cancelled = true; | |
| 182 | await entry.value.putter; | |
| 183 | self.allocator.free(entry.key); | |
| 184 | self.allocator.free(entry.value); | |
| 185 | } | |
| 186 | self.channel.deinit(); | |
| 187 | self.allocator.destroy(self.channel.buffer_nodes); | |
| 188 | self.allocator.destroy(self); | |
| 189 | }, | |
| 190 | .linux => { | |
| 191 | self.os_data.cancelled = true; | |
| 192 | await self.os_data.putter_frame; | |
| 193 | self.allocator.destroy(self); | |
| 194 | }, | |
| 195 | .windows => { | |
| 196 | while (self.os_data.all_putters.get()) |putter_node| { | |
| 197 | putter_node.cancelled = true; | |
| 198 | await putter_node.frame; | |
| 199 | } | |
| 200 | self.deref(); | |
| 201 | }, | |
| 202 | else => @compileError("Unsupported OS"), | |
| 203 | } | |
| 204 | } | |
| 205 | ||
| 206 | fn ref(self: *Self) void { | |
| 207 | _ = self.os_data.ref_count.incr(); | |
| 208 | } | |
| 209 | ||
| 210 | fn deref(self: *Self) void { | |
| 211 | if (self.os_data.ref_count.decr() == 1) { | |
| 212 | self.os_data.table_lock.deinit(); | |
| 213 | var it = self.os_data.dir_table.iterator(); | |
| 214 | while (it.next()) |entry| { | |
| 215 | self.allocator.free(entry.key); | |
| 216 | self.allocator.destroy(entry.value); | |
| 217 | } | |
| 218 | self.os_data.dir_table.deinit(); | |
| 219 | self.channel.deinit(); | |
| 220 | self.allocator.destroy(self.channel.buffer_nodes); | |
| 221 | self.allocator.destroy(self); | |
| 222 | } | |
| 223 | } | |
| 224 | ||
| 225 | pub fn addFile(self: *Self, file_path: []const u8, value: V) !?V { | |
| 226 | switch (builtin.os) { | |
| 227 | .macosx, .freebsd, .netbsd, .dragonfly => return addFileKEvent(self, file_path, value), | |
| 228 | .linux => return addFileLinux(self, file_path, value), | |
| 229 | .windows => return addFileWindows(self, file_path, value), | |
| 230 | else => @compileError("Unsupported OS"), | |
| 231 | } | |
| 232 | } | |
| 233 | ||
| 234 | fn addFileKEvent(self: *Self, file_path: []const u8, value: V) !?V { | |
| 235 | const resolved_path = try std.fs.path.resolve(self.allocator, [_][]const u8{file_path}); | |
| 236 | var resolved_path_consumed = false; | |
| 237 | defer if (!resolved_path_consumed) self.allocator.free(resolved_path); | |
| 238 | ||
| 239 | var close_op = try CloseOperation.start(self.allocator); | |
| 240 | var close_op_consumed = false; | |
| 241 | defer if (!close_op_consumed) close_op.finish(); | |
| 242 | ||
| 243 | const flags = if (comptime std.Target.current.isDarwin()) os.O_SYMLINK | os.O_EVTONLY else 0; | |
| 244 | const mode = 0; | |
| 245 | const fd = try openPosix(self.allocator, resolved_path, flags, mode); | |
| 246 | close_op.setHandle(fd); | |
| 247 | ||
| 248 | var put = try self.allocator.create(OsData.Put); | |
| 249 | errdefer self.allocator.destroy(put); | |
| 250 | put.* = OsData.Put{ | |
| 251 | .value = value, | |
| 252 | .putter_frame = undefined, | |
| 253 | }; | |
| 254 | put.putter_frame = async self.kqPutEvents(close_op, put); | |
| 255 | close_op_consumed = true; | |
| 256 | errdefer { | |
| 257 | put.cancelled = true; | |
| 258 | await put.putter_frame; | |
| 259 | } | |
| 260 | ||
| 261 | const result = blk: { | |
| 262 | const held = self.os_data.table_lock.acquire(); | |
| 263 | defer held.release(); | |
| 264 | ||
| 265 | const gop = try self.os_data.file_table.getOrPut(resolved_path); | |
| 266 | if (gop.found_existing) { | |
| 267 | const prev_value = gop.kv.value.value; | |
| 268 | await gop.kv.value.putter_frame; | |
| 269 | gop.kv.value = put; | |
| 270 | break :blk prev_value; | |
| 271 | } else { | |
| 272 | resolved_path_consumed = true; | |
| 273 | gop.kv.value = put; | |
| 274 | break :blk null; | |
| 275 | } | |
| 276 | }; | |
| 277 | ||
| 278 | return result; | |
| 279 | } | |
| 280 | ||
| 281 | fn kqPutEvents(self: *Self, close_op: *CloseOperation, put: *OsData.Put) void { | |
| 282 | global_event_loop.beginOneEvent(); | |
| 283 | ||
| 284 | defer { | |
| 285 | close_op.finish(); | |
| 286 | global_event_loop.finishOneEvent(); | |
| 287 | } | |
| 288 | ||
| 289 | while (!put.cancelled) { | |
| 290 | if (global_event_loop.bsdWaitKev( | |
| 291 | @intCast(usize, close_op.getHandle()), | |
| 292 | os.EVFILT_VNODE, | |
| 293 | os.NOTE_WRITE | os.NOTE_DELETE, | |
| 294 | )) |kev| { | |
| 295 | // TODO handle EV_ERROR | |
| 296 | if (kev.fflags & os.NOTE_DELETE != 0) { | |
| 297 | self.channel.put(Self.Event{ | |
| 298 | .id = Event.Id.Delete, | |
| 299 | .data = put.value, | |
| 300 | }); | |
| 301 | } else if (kev.fflags & os.NOTE_WRITE != 0) { | |
| 302 | self.channel.put(Self.Event{ | |
| 303 | .id = Event.Id.CloseWrite, | |
| 304 | .data = put.value, | |
| 305 | }); | |
| 306 | } | |
| 307 | } else |err| switch (err) { | |
| 308 | error.EventNotFound => unreachable, | |
| 309 | error.ProcessNotFound => unreachable, | |
| 310 | error.Overflow => unreachable, | |
| 311 | error.AccessDenied, error.SystemResources => |casted_err| { | |
| 312 | self.channel.put(casted_err); | |
| 313 | }, | |
| 314 | } | |
| 315 | } | |
| 316 | } | |
| 317 | ||
| 318 | fn addFileLinux(self: *Self, file_path: []const u8, value: V) !?V { | |
| 319 | const dirname = std.fs.path.dirname(file_path) orelse "."; | |
| 320 | const dirname_with_null = try std.cstr.addNullByte(self.allocator, dirname); | |
| 321 | var dirname_with_null_consumed = false; | |
| 322 | defer if (!dirname_with_null_consumed) self.channel.free(dirname_with_null); | |
| 323 | ||
| 324 | const basename = std.fs.path.basename(file_path); | |
| 325 | const basename_with_null = try std.cstr.addNullByte(self.allocator, basename); | |
| 326 | var basename_with_null_consumed = false; | |
| 327 | defer if (!basename_with_null_consumed) self.allocator.free(basename_with_null); | |
| 328 | ||
| 329 | const wd = try os.inotify_add_watchC( | |
| 330 | self.os_data.inotify_fd, | |
| 331 | dirname_with_null.ptr, | |
| 332 | os.linux.IN_CLOSE_WRITE | os.linux.IN_ONLYDIR | os.linux.IN_EXCL_UNLINK, | |
| 333 | ); | |
| 334 | // wd is either a newly created watch or an existing one. | |
| 335 | ||
| 336 | const held = self.os_data.table_lock.acquire(); | |
| 337 | defer held.release(); | |
| 338 | ||
| 339 | const gop = try self.os_data.wd_table.getOrPut(wd); | |
| 340 | if (!gop.found_existing) { | |
| 341 | gop.kv.value = OsData.Dir{ | |
| 342 | .dirname = dirname_with_null, | |
| 343 | .file_table = OsData.FileTable.init(self.allocator), | |
| 344 | }; | |
| 345 | dirname_with_null_consumed = true; | |
| 346 | } | |
| 347 | const dir = &gop.kv.value; | |
| 348 | ||
| 349 | const file_table_gop = try dir.file_table.getOrPut(basename_with_null); | |
| 350 | if (file_table_gop.found_existing) { | |
| 351 | const prev_value = file_table_gop.kv.value; | |
| 352 | file_table_gop.kv.value = value; | |
| 353 | return prev_value; | |
| 354 | } else { | |
| 355 | file_table_gop.kv.value = value; | |
| 356 | basename_with_null_consumed = true; | |
| 357 | return null; | |
| 358 | } | |
| 359 | } | |
| 360 | ||
| 361 | fn addFileWindows(self: *Self, file_path: []const u8, value: V) !?V { | |
| 362 | // TODO we might need to convert dirname and basename to canonical file paths ("short"?) | |
| 363 | const dirname = try std.mem.dupe(self.allocator, u8, std.fs.path.dirname(file_path) orelse "."); | |
| 364 | var dirname_consumed = false; | |
| 365 | defer if (!dirname_consumed) self.allocator.free(dirname); | |
| 366 | ||
| 367 | const dirname_utf16le = try std.unicode.utf8ToUtf16LeWithNull(self.allocator, dirname); | |
| 368 | defer self.allocator.free(dirname_utf16le); | |
| 369 | ||
| 370 | // TODO https://github.com/ziglang/zig/issues/265 | |
| 371 | const basename = std.fs.path.basename(file_path); | |
| 372 | const basename_utf16le_null = try std.unicode.utf8ToUtf16LeWithNull(self.allocator, basename); | |
| 373 | var basename_utf16le_null_consumed = false; | |
| 374 | defer if (!basename_utf16le_null_consumed) self.allocator.free(basename_utf16le_null); | |
| 375 | const basename_utf16le_no_null = basename_utf16le_null[0 .. basename_utf16le_null.len - 1]; | |
| 376 | ||
| 377 | const dir_handle = try windows.CreateFileW( | |
| 378 | dirname_utf16le.ptr, | |
| 379 | windows.FILE_LIST_DIRECTORY, | |
| 380 | windows.FILE_SHARE_READ | windows.FILE_SHARE_DELETE | windows.FILE_SHARE_WRITE, | |
| 381 | null, | |
| 382 | windows.OPEN_EXISTING, | |
| 383 | windows.FILE_FLAG_BACKUP_SEMANTICS | windows.FILE_FLAG_OVERLAPPED, | |
| 384 | null, | |
| 385 | ); | |
| 386 | var dir_handle_consumed = false; | |
| 387 | defer if (!dir_handle_consumed) windows.CloseHandle(dir_handle); | |
| 388 | ||
| 389 | const held = self.os_data.table_lock.acquire(); | |
| 390 | defer held.release(); | |
| 391 | ||
| 392 | const gop = try self.os_data.dir_table.getOrPut(dirname); | |
| 393 | if (gop.found_existing) { | |
| 394 | const dir = gop.kv.value; | |
| 395 | const held_dir_lock = dir.table_lock.acquire(); | |
| 396 | defer held_dir_lock.release(); | |
| 397 | ||
| 398 | const file_gop = try dir.file_table.getOrPut(basename_utf16le_no_null); | |
| 399 | if (file_gop.found_existing) { | |
| 400 | const prev_value = file_gop.kv.value; | |
| 401 | file_gop.kv.value = value; | |
| 402 | return prev_value; | |
| 403 | } else { | |
| 404 | file_gop.kv.value = value; | |
| 405 | basename_utf16le_null_consumed = true; | |
| 406 | return null; | |
| 407 | } | |
| 408 | } else { | |
| 409 | errdefer _ = self.os_data.dir_table.remove(dirname); | |
| 410 | const dir = try self.allocator.create(OsData.Dir); | |
| 411 | errdefer self.allocator.destroy(dir); | |
| 412 | ||
| 413 | dir.* = OsData.Dir{ | |
| 414 | .file_table = OsData.FileTable.init(self.allocator), | |
| 415 | .table_lock = event.Lock.init(), | |
| 416 | .putter_frame = undefined, | |
| 417 | }; | |
| 418 | gop.kv.value = dir; | |
| 419 | assert((try dir.file_table.put(basename_utf16le_no_null, value)) == null); | |
| 420 | basename_utf16le_null_consumed = true; | |
| 421 | ||
| 422 | dir.putter_frame = async self.windowsDirReader(dir_handle, dir); | |
| 423 | dir_handle_consumed = true; | |
| 424 | ||
| 425 | dirname_consumed = true; | |
| 426 | ||
| 427 | return null; | |
| 428 | } | |
| 429 | } | |
| 430 | ||
| 431 | fn windowsDirReader(self: *Self, dir_handle: windows.HANDLE, dir: *OsData.Dir) void { | |
| 432 | self.ref(); | |
| 433 | defer self.deref(); | |
| 434 | ||
| 435 | defer os.close(dir_handle); | |
| 436 | ||
| 437 | var putter_node = std.atomic.Queue(anyframe).Node{ | |
| 438 | .data = .{ .putter = @frame() }, | |
| 439 | .prev = null, | |
| 440 | .next = null, | |
| 441 | }; | |
| 442 | self.os_data.all_putters.put(&putter_node); | |
| 443 | defer _ = self.os_data.all_putters.remove(&putter_node); | |
| 444 | ||
| 445 | var resume_node = Loop.ResumeNode.Basic{ | |
| 446 | .base = Loop.ResumeNode{ | |
| 447 | .id = Loop.ResumeNode.Id.Basic, | |
| 448 | .handle = @frame(), | |
| 449 | .overlapped = windows.OVERLAPPED{ | |
| 450 | .Internal = 0, | |
| 451 | .InternalHigh = 0, | |
| 452 | .Offset = 0, | |
| 453 | .OffsetHigh = 0, | |
| 454 | .hEvent = null, | |
| 455 | }, | |
| 456 | }, | |
| 457 | }; | |
| 458 | var event_buf: [4096]u8 align(@alignOf(windows.FILE_NOTIFY_INFORMATION)) = undefined; | |
| 459 | ||
| 460 | // TODO handle this error not in the channel but in the setup | |
| 461 | _ = windows.CreateIoCompletionPort( | |
| 462 | dir_handle, | |
| 463 | global_event_loop.os_data.io_port, | |
| 464 | undefined, | |
| 465 | undefined, | |
| 466 | ) catch |err| { | |
| 467 | self.channel.put(err); | |
| 468 | return; | |
| 469 | }; | |
| 470 | ||
| 471 | while (!putter_node.data.cancelled) { | |
| 472 | { | |
| 473 | // TODO only 1 beginOneEvent for the whole function | |
| 474 | global_event_loop.beginOneEvent(); | |
| 475 | errdefer global_event_loop.finishOneEvent(); | |
| 476 | errdefer { | |
| 477 | _ = windows.kernel32.CancelIoEx(dir_handle, &resume_node.base.overlapped); | |
| 478 | } | |
| 479 | suspend { | |
| 480 | _ = windows.kernel32.ReadDirectoryChangesW( | |
| 481 | dir_handle, | |
| 482 | &event_buf, | |
| 483 | @intCast(windows.DWORD, event_buf.len), | |
| 484 | windows.FALSE, // watch subtree | |
| 485 | windows.FILE_NOTIFY_CHANGE_FILE_NAME | windows.FILE_NOTIFY_CHANGE_DIR_NAME | | |
| 486 | windows.FILE_NOTIFY_CHANGE_ATTRIBUTES | windows.FILE_NOTIFY_CHANGE_SIZE | | |
| 487 | windows.FILE_NOTIFY_CHANGE_LAST_WRITE | windows.FILE_NOTIFY_CHANGE_LAST_ACCESS | | |
| 488 | windows.FILE_NOTIFY_CHANGE_CREATION | windows.FILE_NOTIFY_CHANGE_SECURITY, | |
| 489 | null, // number of bytes transferred (unused for async) | |
| 490 | &resume_node.base.overlapped, | |
| 491 | null, // completion routine - unused because we use IOCP | |
| 492 | ); | |
| 493 | } | |
| 494 | } | |
| 495 | var bytes_transferred: windows.DWORD = undefined; | |
| 496 | if (windows.kernel32.GetOverlappedResult(dir_handle, &resume_node.base.overlapped, &bytes_transferred, windows.FALSE) == 0) { | |
| 497 | const err = switch (windows.kernel32.GetLastError()) { | |
| 498 | else => |err| windows.unexpectedError(err), | |
| 499 | }; | |
| 500 | self.channel.put(err); | |
| 501 | } else { | |
| 502 | // can't use @bytesToSlice because of the special variable length name field | |
| 503 | var ptr = event_buf[0..].ptr; | |
| 504 | const end_ptr = ptr + bytes_transferred; | |
| 505 | var ev: *windows.FILE_NOTIFY_INFORMATION = undefined; | |
| 506 | while (@ptrToInt(ptr) < @ptrToInt(end_ptr)) : (ptr += ev.NextEntryOffset) { | |
| 507 | ev = @ptrCast(*windows.FILE_NOTIFY_INFORMATION, ptr); | |
| 508 | const emit = switch (ev.Action) { | |
| 509 | windows.FILE_ACTION_REMOVED => WatchEventId.Delete, | |
| 510 | windows.FILE_ACTION_MODIFIED => WatchEventId.CloseWrite, | |
| 511 | else => null, | |
| 512 | }; | |
| 513 | if (emit) |id| { | |
| 514 | const basename_utf16le = ([*]u16)(&ev.FileName)[0 .. ev.FileNameLength / 2]; | |
| 515 | const user_value = blk: { | |
| 516 | const held = dir.table_lock.acquire(); | |
| 517 | defer held.release(); | |
| 518 | ||
| 519 | if (dir.file_table.get(basename_utf16le)) |entry| { | |
| 520 | break :blk entry.value; | |
| 521 | } else { | |
| 522 | break :blk null; | |
| 523 | } | |
| 524 | }; | |
| 525 | if (user_value) |v| { | |
| 526 | self.channel.put(Event{ | |
| 527 | .id = id, | |
| 528 | .data = v, | |
| 529 | }); | |
| 530 | } | |
| 531 | } | |
| 532 | if (ev.NextEntryOffset == 0) break; | |
| 533 | } | |
| 534 | } | |
| 535 | } | |
| 536 | } | |
| 537 | ||
| 538 | pub fn removeFile(self: *Self, file_path: []const u8) ?V { | |
| 539 | @panic("TODO"); | |
| 540 | } | |
| 541 | ||
| 542 | fn linuxEventPutter(self: *Self) void { | |
| 543 | global_event_loop.beginOneEvent(); | |
| 544 | ||
| 545 | defer { | |
| 546 | self.os_data.table_lock.deinit(); | |
| 547 | var wd_it = self.os_data.wd_table.iterator(); | |
| 548 | while (wd_it.next()) |wd_entry| { | |
| 549 | var file_it = wd_entry.value.file_table.iterator(); | |
| 550 | while (file_it.next()) |file_entry| { | |
| 551 | self.allocator.free(file_entry.key); | |
| 552 | } | |
| 553 | self.allocator.free(wd_entry.value.dirname); | |
| 554 | wd_entry.value.file_table.deinit(); | |
| 555 | } | |
| 556 | self.os_data.wd_table.deinit(); | |
| 557 | global_event_loop.finishOneEvent(); | |
| 558 | os.close(self.os_data.inotify_fd); | |
| 559 | self.channel.deinit(); | |
| 560 | self.allocator.free(self.channel.buffer_nodes); | |
| 561 | } | |
| 562 | ||
| 563 | var event_buf: [4096]u8 align(@alignOf(os.linux.inotify_event)) = undefined; | |
| 564 | ||
| 565 | while (!self.os_data.cancelled) { | |
| 566 | const rc = os.linux.read(self.os_data.inotify_fd, &event_buf, event_buf.len); | |
| 567 | const errno = os.linux.getErrno(rc); | |
| 568 | switch (errno) { | |
| 569 | 0 => { | |
| 570 | // can't use @bytesToSlice because of the special variable length name field | |
| 571 | var ptr = event_buf[0..].ptr; | |
| 572 | const end_ptr = ptr + event_buf.len; | |
| 573 | var ev: *os.linux.inotify_event = undefined; | |
| 574 | while (@ptrToInt(ptr) < @ptrToInt(end_ptr)) { | |
| 575 | ev = @ptrCast(*os.linux.inotify_event, ptr); | |
| 576 | if (ev.mask & os.linux.IN_CLOSE_WRITE == os.linux.IN_CLOSE_WRITE) { | |
| 577 | const basename_ptr = ptr + @sizeOf(os.linux.inotify_event); | |
| 578 | // `ev.len` counts all bytes in `ev.name` including terminating null byte. | |
| 579 | const basename_with_null = basename_ptr[0..ev.len]; | |
| 580 | const user_value = blk: { | |
| 581 | const held = self.os_data.table_lock.acquire(); | |
| 582 | defer held.release(); | |
| 583 | ||
| 584 | const dir = &self.os_data.wd_table.get(ev.wd).?.value; | |
| 585 | if (dir.file_table.get(basename_with_null)) |entry| { | |
| 586 | break :blk entry.value; | |
| 587 | } else { | |
| 588 | break :blk null; | |
| 589 | } | |
| 590 | }; | |
| 591 | if (user_value) |v| { | |
| 592 | self.channel.put(Event{ | |
| 593 | .id = WatchEventId.CloseWrite, | |
| 594 | .data = v, | |
| 595 | }); | |
| 596 | } | |
| 597 | } | |
| 598 | ||
| 599 | ptr = @alignCast(@alignOf(os.linux.inotify_event), ptr + @sizeOf(os.linux.inotify_event) + ev.len); | |
| 600 | } | |
| 601 | }, | |
| 602 | os.linux.EINTR => continue, | |
| 603 | os.linux.EINVAL => unreachable, | |
| 604 | os.linux.EFAULT => unreachable, | |
| 605 | os.linux.EAGAIN => { | |
| 606 | global_event_loop.linuxWaitFd(self.os_data.inotify_fd, os.linux.EPOLLET | os.linux.EPOLLIN | os.EPOLLONESHOT); | |
| 607 | }, | |
| 608 | else => unreachable, | |
| 609 | } | |
| 610 | } | |
| 611 | } | |
| 612 | }; | |
| 613 | } | |
| 614 | ||
| 615 | const test_tmp_dir = "std_event_fs_test"; | |
| 616 | ||
| 617 | test "write a file, watch it, write it again" { | |
| 618 | // TODO re-enable this test | |
| 619 | if (true) return error.SkipZigTest; | |
| 620 | ||
| 621 | const allocator = std.heap.page_allocator; | |
| 622 | ||
| 623 | try os.makePath(allocator, test_tmp_dir); | |
| 624 | defer os.deleteTree(test_tmp_dir) catch {}; | |
| 625 | ||
| 626 | return testFsWatch(&allocator); | |
| 627 | } | |
| 628 | ||
| 629 | fn testFsWatch(allocator: *Allocator) !void { | |
| 630 | const file_path = try std.fs.path.join(allocator, [_][]const u8{ test_tmp_dir, "file.txt" }); | |
| 631 | defer allocator.free(file_path); | |
| 632 | ||
| 633 | const contents = | |
| 634 | \\line 1 | |
| 635 | \\line 2 | |
| 636 | ; | |
| 637 | const line2_offset = 7; | |
| 638 | ||
| 639 | // first just write then read the file | |
| 640 | try writeFile(allocator, file_path, contents); | |
| 641 | ||
| 642 | const read_contents = try readFile(allocator, file_path, 1024 * 1024); | |
| 643 | testing.expectEqualSlices(u8, contents, read_contents); | |
| 644 | ||
| 645 | // now watch the file | |
| 646 | var watch = try Watch(void).init(allocator, 0); | |
| 647 | defer watch.deinit(); | |
| 648 | ||
| 649 | testing.expect((try watch.addFile(file_path, {})) == null); | |
| 650 | ||
| 651 | const ev = watch.channel.get(); | |
| 652 | var ev_consumed = false; | |
| 653 | defer if (!ev_consumed) await ev; | |
| 654 | ||
| 655 | // overwrite line 2 | |
| 656 | const fd = try await openReadWrite(file_path, File.default_mode); | |
| 657 | { | |
| 658 | defer os.close(fd); | |
| 659 | ||
| 660 | try pwritev(allocator, fd, []const []const u8{"lorem ipsum"}, line2_offset); | |
| 661 | } | |
| 662 | ||
| 663 | ev_consumed = true; | |
| 664 | switch ((try await ev).id) { | |
| 665 | WatchEventId.CloseWrite => {}, | |
| 666 | WatchEventId.Delete => @panic("wrong event"), | |
| 667 | } | |
| 668 | const contents_updated = try readFile(allocator, file_path, 1024 * 1024); | |
| 669 | testing.expectEqualSlices(u8, | |
| 670 | \\line 1 | |
| 671 | \\lorem ipsum | |
| 672 | , contents_updated); | |
| 673 | ||
| 674 | // TODO test deleting the file and then re-adding it. we should get events for both | |
| 675 | } |
lib/std/io.zig+13-3| ... | ... | @@ -47,7 +47,10 @@ fn getStdOutHandle() os.fd_t { |
| 47 | 47 | } |
| 48 | 48 | |
| 49 | 49 | pub fn getStdOut() File { |
| 50 | return File.openHandle(getStdOutHandle()); | |
| 50 | return File{ | |
| 51 | .handle = getStdOutHandle(), | |
| 52 | .io_mode = .blocking, | |
| 53 | }; | |
| 51 | 54 | } |
| 52 | 55 | |
| 53 | 56 | fn getStdErrHandle() os.fd_t { |
| ... | ... | @@ -63,7 +66,11 @@ fn getStdErrHandle() os.fd_t { |
| 63 | 66 | } |
| 64 | 67 | |
| 65 | 68 | pub fn getStdErr() File { |
| 66 | return File.openHandle(getStdErrHandle()); | |
| 69 | return File{ | |
| 70 | .handle = getStdErrHandle(), | |
| 71 | .io_mode = .blocking, | |
| 72 | .async_block_allowed = File.async_block_allowed_yes, | |
| 73 | }; | |
| 67 | 74 | } |
| 68 | 75 | |
| 69 | 76 | fn getStdInHandle() os.fd_t { |
| ... | ... | @@ -79,7 +86,10 @@ fn getStdInHandle() os.fd_t { |
| 79 | 86 | } |
| 80 | 87 | |
| 81 | 88 | pub fn getStdIn() File { |
| 82 | return File.openHandle(getStdInHandle()); | |
| 89 | return File{ | |
| 90 | .handle = getStdInHandle(), | |
| 91 | .io_mode = .blocking, | |
| 92 | }; | |
| 83 | 93 | } |
| 84 | 94 | |
| 85 | 95 | pub const SeekableStream = @import("io/seekable_stream.zig").SeekableStream; |
lib/std/io/out_stream.zig+11-15| ... | ... | @@ -9,14 +9,11 @@ pub const stack_size: usize = if (@hasDecl(root, "stack_size_std_io_OutStream")) |
| 9 | 9 | else |
| 10 | 10 | default_stack_size; |
| 11 | 11 | |
| 12 | /// TODO this is not integrated with evented I/O yet. | |
| 13 | /// https://github.com/ziglang/zig/issues/3557 | |
| 14 | 12 | pub fn OutStream(comptime WriteError: type) type { |
| 15 | 13 | return struct { |
| 16 | 14 | const Self = @This(); |
| 17 | 15 | pub const Error = WriteError; |
| 18 | // TODO https://github.com/ziglang/zig/issues/3557 | |
| 19 | pub const WriteFn = if (std.io.is_async and false) | |
| 16 | pub const WriteFn = if (std.io.is_async) | |
| 20 | 17 | async fn (self: *Self, bytes: []const u8) Error!void |
| 21 | 18 | else |
| 22 | 19 | fn (self: *Self, bytes: []const u8) Error!void; |
| ... | ... | @@ -24,8 +21,7 @@ pub fn OutStream(comptime WriteError: type) type { |
| 24 | 21 | writeFn: WriteFn, |
| 25 | 22 | |
| 26 | 23 | pub fn write(self: *Self, bytes: []const u8) Error!void { |
| 27 | // TODO https://github.com/ziglang/zig/issues/3557 | |
| 28 | if (std.io.is_async and false) { | |
| 24 | if (std.io.is_async) { | |
| 29 | 25 | // Let's not be writing 0xaa in safe modes for upwards of 4 MiB for every stream write. |
| 30 | 26 | @setRuntimeSafety(false); |
| 31 | 27 | var stack_frame: [stack_size]u8 align(std.Target.stack_align) = undefined; |
| ... | ... | @@ -36,12 +32,12 @@ pub fn OutStream(comptime WriteError: type) type { |
| 36 | 32 | } |
| 37 | 33 | |
| 38 | 34 | pub fn print(self: *Self, comptime format: []const u8, args: var) Error!void { |
| 39 | return std.fmt.format(self, Error, self.writeFn, format, args); | |
| 35 | return std.fmt.format(self, Error, write, format, args); | |
| 40 | 36 | } |
| 41 | 37 | |
| 42 | 38 | pub fn writeByte(self: *Self, byte: u8) Error!void { |
| 43 | const slice = @as(*const [1]u8, &byte)[0..]; | |
| 44 | return self.writeFn(self, slice); | |
| 39 | const array = [1]u8{byte}; | |
| 40 | return self.write(&array); | |
| 45 | 41 | } |
| 46 | 42 | |
| 47 | 43 | pub fn writeByteNTimes(self: *Self, byte: u8, n: usize) Error!void { |
| ... | ... | @@ -51,7 +47,7 @@ pub fn OutStream(comptime WriteError: type) type { |
| 51 | 47 | var remaining: usize = n; |
| 52 | 48 | while (remaining > 0) { |
| 53 | 49 | const to_write = std.math.min(remaining, bytes.len); |
| 54 | try self.writeFn(self, bytes[0..to_write]); | |
| 50 | try self.write(bytes[0..to_write]); | |
| 55 | 51 | remaining -= to_write; |
| 56 | 52 | } |
| 57 | 53 | } |
| ... | ... | @@ -60,32 +56,32 @@ pub fn OutStream(comptime WriteError: type) type { |
| 60 | 56 | pub fn writeIntNative(self: *Self, comptime T: type, value: T) Error!void { |
| 61 | 57 | var bytes: [(T.bit_count + 7) / 8]u8 = undefined; |
| 62 | 58 | mem.writeIntNative(T, &bytes, value); |
| 63 | return self.writeFn(self, &bytes); | |
| 59 | return self.write(&bytes); | |
| 64 | 60 | } |
| 65 | 61 | |
| 66 | 62 | /// Write a foreign-endian integer. |
| 67 | 63 | pub fn writeIntForeign(self: *Self, comptime T: type, value: T) Error!void { |
| 68 | 64 | var bytes: [(T.bit_count + 7) / 8]u8 = undefined; |
| 69 | 65 | mem.writeIntForeign(T, &bytes, value); |
| 70 | return self.writeFn(self, &bytes); | |
| 66 | return self.write(&bytes); | |
| 71 | 67 | } |
| 72 | 68 | |
| 73 | 69 | pub fn writeIntLittle(self: *Self, comptime T: type, value: T) Error!void { |
| 74 | 70 | var bytes: [(T.bit_count + 7) / 8]u8 = undefined; |
| 75 | 71 | mem.writeIntLittle(T, &bytes, value); |
| 76 | return self.writeFn(self, &bytes); | |
| 72 | return self.write(&bytes); | |
| 77 | 73 | } |
| 78 | 74 | |
| 79 | 75 | pub fn writeIntBig(self: *Self, comptime T: type, value: T) Error!void { |
| 80 | 76 | var bytes: [(T.bit_count + 7) / 8]u8 = undefined; |
| 81 | 77 | mem.writeIntBig(T, &bytes, value); |
| 82 | return self.writeFn(self, &bytes); | |
| 78 | return self.write(&bytes); | |
| 83 | 79 | } |
| 84 | 80 | |
| 85 | 81 | pub fn writeInt(self: *Self, comptime T: type, value: T, endian: builtin.Endian) Error!void { |
| 86 | 82 | var bytes: [(T.bit_count + 7) / 8]u8 = undefined; |
| 87 | 83 | mem.writeInt(T, &bytes, value, endian); |
| 88 | return self.writeFn(self, &bytes); | |
| 84 | return self.write(&bytes); | |
| 89 | 85 | } |
| 90 | 86 | }; |
| 91 | 87 | } |
lib/std/linked_list.zig+3-6| ... | ... | @@ -18,12 +18,11 @@ pub fn SinglyLinkedList(comptime T: type) type { |
| 18 | 18 | |
| 19 | 19 | /// Node inside the linked list wrapping the actual data. |
| 20 | 20 | pub const Node = struct { |
| 21 | next: ?*Node, | |
| 21 | next: ?*Node = null, | |
| 22 | 22 | data: T, |
| 23 | 23 | |
| 24 | 24 | pub fn init(data: T) Node { |
| 25 | 25 | return Node{ |
| 26 | .next = null, | |
| 27 | 26 | .data = data, |
| 28 | 27 | }; |
| 29 | 28 | } |
| ... | ... | @@ -196,14 +195,12 @@ pub fn TailQueue(comptime T: type) type { |
| 196 | 195 | |
| 197 | 196 | /// Node inside the linked list wrapping the actual data. |
| 198 | 197 | pub const Node = struct { |
| 199 | prev: ?*Node, | |
| 200 | next: ?*Node, | |
| 198 | prev: ?*Node = null, | |
| 199 | next: ?*Node = null, | |
| 201 | 200 | data: T, |
| 202 | 201 | |
| 203 | 202 | pub fn init(data: T) Node { |
| 204 | 203 | return Node{ |
| 205 | .prev = null, | |
| 206 | .next = null, | |
| 207 | 204 | .data = data, |
| 208 | 205 | }; |
| 209 | 206 | } |
lib/std/mem.zig+1-1| ... | ... | @@ -233,7 +233,7 @@ pub const Allocator = struct { |
| 233 | 233 | pub fn free(self: *Allocator, memory: var) void { |
| 234 | 234 | const Slice = @typeInfo(@TypeOf(memory)).Pointer; |
| 235 | 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 | 237 | if (bytes_len == 0) return; |
| 238 | 238 | const non_const_ptr = @intToPtr([*]u8, @ptrToInt(bytes.ptr)); |
| 239 | 239 | @memset(non_const_ptr, undefined, bytes_len); |
lib/std/net.zig+11-5| ... | ... | @@ -271,7 +271,7 @@ pub const Address = extern union { |
| 271 | 271 | options: std.fmt.FormatOptions, |
| 272 | 272 | context: var, |
| 273 | 273 | comptime Errors: type, |
| 274 | output: fn (@TypeOf(context), []const u8) Errors!void, | |
| 274 | comptime output: fn (@TypeOf(context), []const u8) Errors!void, | |
| 275 | 275 | ) !void { |
| 276 | 276 | switch (self.any.family) { |
| 277 | 277 | os.AF_INET => { |
| ... | ... | @@ -361,7 +361,7 @@ pub const Address = extern union { |
| 361 | 361 | }; |
| 362 | 362 | |
| 363 | 363 | pub fn connectUnixSocket(path: []const u8) !fs.File { |
| 364 | const opt_non_block = if (std.io.mode == .evented) os.SOCK_NONBLOCK else 0; | |
| 364 | const opt_non_block = if (std.io.is_async) os.SOCK_NONBLOCK else 0; | |
| 365 | 365 | const sockfd = try os.socket( |
| 366 | 366 | os.AF_UNIX, |
| 367 | 367 | os.SOCK_STREAM | os.SOCK_CLOEXEC | opt_non_block, |
| ... | ... | @@ -377,7 +377,10 @@ pub fn connectUnixSocket(path: []const u8) !fs.File { |
| 377 | 377 | addr.getOsSockLen(), |
| 378 | 378 | ); |
| 379 | 379 | |
| 380 | return fs.File.openHandle(sockfd); | |
| 380 | return fs.File{ | |
| 381 | .handle = sockfd, | |
| 382 | .io_mode = std.io.mode, | |
| 383 | }; | |
| 381 | 384 | } |
| 382 | 385 | |
| 383 | 386 | pub const AddressList = struct { |
| ... | ... | @@ -412,7 +415,7 @@ pub fn tcpConnectToAddress(address: Address) !fs.File { |
| 412 | 415 | errdefer os.close(sockfd); |
| 413 | 416 | try os.connect(sockfd, &address.any, address.getOsSockLen()); |
| 414 | 417 | |
| 415 | return fs.File{ .handle = sockfd }; | |
| 418 | return fs.File{ .handle = sockfd, .io_mode = std.io.mode }; | |
| 416 | 419 | } |
| 417 | 420 | |
| 418 | 421 | /// Call `AddressList.deinit` on the result. |
| ... | ... | @@ -1379,7 +1382,10 @@ pub const StreamServer = struct { |
| 1379 | 1382 | var adr_len: os.socklen_t = @sizeOf(Address); |
| 1380 | 1383 | if (os.accept4(self.sockfd.?, &accepted_addr.any, &adr_len, accept_flags)) |fd| { |
| 1381 | 1384 | return Connection{ |
| 1382 | .file = fs.File.openHandle(fd), | |
| 1385 | .file = fs.File{ | |
| 1386 | .handle = fd, | |
| 1387 | .io_mode = std.io.mode, | |
| 1388 | }, | |
| 1383 | 1389 | .address = accepted_addr, |
| 1384 | 1390 | }; |
| 1385 | 1391 | } else |err| switch (err) { |
lib/std/net/test.zig+3-5| ... | ... | @@ -81,17 +81,15 @@ test "resolve DNS" { |
| 81 | 81 | } |
| 82 | 82 | |
| 83 | 83 | test "listen on a port, send bytes, receive bytes" { |
| 84 | if (!std.io.is_async) return error.SkipZigTest; | |
| 85 | ||
| 84 | 86 | if (std.builtin.os != .linux) { |
| 85 | 87 | // TODO build abstractions for other operating systems |
| 86 | 88 | return error.SkipZigTest; |
| 87 | 89 | } |
| 88 | if (std.io.mode != .evented) { | |
| 89 | // TODO add ability to run tests in non-blocking I/O mode | |
| 90 | return error.SkipZigTest; | |
| 91 | } | |
| 92 | 90 | |
| 93 | 91 | // TODO doing this at comptime crashed the compiler |
| 94 | const localhost = net.Address.parseIp("127.0.0.1", 0); | |
| 92 | const localhost = try net.Address.parseIp("127.0.0.1", 0); | |
| 95 | 93 | |
| 96 | 94 | var server = net.StreamServer.init(net.StreamServer.Options{}); |
| 97 | 95 | defer server.deinit(); |
lib/std/os.zig+222-21| ... | ... | @@ -169,7 +169,12 @@ fn getRandomBytesDevURandom(buf: []u8) !void { |
| 169 | 169 | return error.NoDevice; |
| 170 | 170 | } |
| 171 | 171 | |
| 172 | const stream = &std.fs.File.openHandle(fd).inStream().stream; | |
| 172 | const file = std.fs.File{ | |
| 173 | .handle = fd, | |
| 174 | .io_mode = .blocking, | |
| 175 | .async_block_allowed = std.fs.File.async_block_allowed_yes, | |
| 176 | }; | |
| 177 | const stream = &file.inStream().stream; | |
| 173 | 178 | stream.readNoEof(buf) catch return error.Unexpected; |
| 174 | 179 | } |
| 175 | 180 | |
| ... | ... | @@ -293,7 +298,7 @@ pub const ReadError = error{ |
| 293 | 298 | /// via the event loop. Otherwise EAGAIN results in error.WouldBlock. |
| 294 | 299 | pub fn read(fd: fd_t, buf: []u8) ReadError!usize { |
| 295 | 300 | if (builtin.os == .windows) { |
| 296 | return windows.ReadFile(fd, buf); | |
| 301 | return windows.ReadFile(fd, buf, null); | |
| 297 | 302 | } |
| 298 | 303 | |
| 299 | 304 | if (builtin.os == .wasi and !builtin.link_libc) { |
| ... | ... | @@ -335,9 +340,37 @@ pub fn read(fd: fd_t, buf: []u8) ReadError!usize { |
| 335 | 340 | } |
| 336 | 341 | |
| 337 | 342 | /// Number of bytes read is returned. Upon reading end-of-file, zero is returned. |
| 338 | /// If the application has a global event loop enabled, EAGAIN is handled | |
| 339 | /// via the event loop. Otherwise EAGAIN results in error.WouldBlock. | |
| 343 | /// | |
| 344 | /// For POSIX systems, if the application has a global event loop enabled, EAGAIN is handled | |
| 345 | /// via the event loop. Otherwise EAGAIN results in `error.WouldBlock`. | |
| 346 | /// On Windows, if the application has a global event loop enabled, I/O Completion Ports are | |
| 347 | /// used to perform the I/O. `error.WouldBlock` is not possible on Windows. | |
| 348 | /// | |
| 349 | /// This operation is non-atomic on the following systems: | |
| 350 | /// * Windows | |
| 351 | /// On these systems, the read races with concurrent writes to the same file descriptor. | |
| 340 | 352 | pub fn readv(fd: fd_t, iov: []const iovec) ReadError!usize { |
| 353 | if (builtin.os == .windows) { | |
| 354 | // TODO batch these into parallel requests | |
| 355 | var off: usize = 0; | |
| 356 | var iov_i: usize = 0; | |
| 357 | var inner_off: usize = 0; | |
| 358 | while (true) { | |
| 359 | const v = iov[iov_i]; | |
| 360 | const amt_read = try read(fd, v.iov_base[inner_off .. v.iov_len - inner_off]); | |
| 361 | off += amt_read; | |
| 362 | inner_off += amt_read; | |
| 363 | if (inner_off == v.len) { | |
| 364 | iov_i += 1; | |
| 365 | inner_off = 0; | |
| 366 | if (iov_i == iov.len) { | |
| 367 | return off; | |
| 368 | } | |
| 369 | } | |
| 370 | if (amt_read == 0) return off; // EOF | |
| 371 | } else unreachable; // TODO https://github.com/ziglang/zig/issues/707 | |
| 372 | } | |
| 373 | ||
| 341 | 374 | while (true) { |
| 342 | 375 | // TODO handle the case when iov_len is too large and get rid of this @intCast |
| 343 | 376 | const rc = system.readv(fd, iov.ptr, @intCast(u32, iov.len)); |
| ... | ... | @@ -363,8 +396,56 @@ pub fn readv(fd: fd_t, iov: []const iovec) ReadError!usize { |
| 363 | 396 | } |
| 364 | 397 | |
| 365 | 398 | /// Number of bytes read is returned. Upon reading end-of-file, zero is returned. |
| 366 | /// If the application has a global event loop enabled, EAGAIN is handled | |
| 367 | /// via the event loop. Otherwise EAGAIN results in error.WouldBlock. | |
| 399 | /// | |
| 400 | /// Retries when interrupted by a signal. | |
| 401 | /// | |
| 402 | /// For POSIX systems, if the application has a global event loop enabled, EAGAIN is handled | |
| 403 | /// via the event loop. Otherwise EAGAIN results in `error.WouldBlock`. | |
| 404 | /// On Windows, if the application has a global event loop enabled, I/O Completion Ports are | |
| 405 | /// used to perform the I/O. `error.WouldBlock` is not possible on Windows. | |
| 406 | pub fn pread(fd: fd_t, buf: []u8, offset: u64) ReadError!usize { | |
| 407 | if (builtin.os == .windows) { | |
| 408 | return windows.ReadFile(fd, buf, offset); | |
| 409 | } | |
| 410 | ||
| 411 | while (true) { | |
| 412 | const rc = system.pread(fd, buf.ptr, buf.len, offset); | |
| 413 | switch (errno(rc)) { | |
| 414 | 0 => return @intCast(usize, rc), | |
| 415 | EINTR => continue, | |
| 416 | EINVAL => unreachable, | |
| 417 | EFAULT => unreachable, | |
| 418 | EAGAIN => if (std.event.Loop.instance) |loop| { | |
| 419 | loop.waitUntilFdReadable(fd); | |
| 420 | continue; | |
| 421 | } else { | |
| 422 | return error.WouldBlock; | |
| 423 | }, | |
| 424 | EBADF => unreachable, // Always a race condition. | |
| 425 | EIO => return error.InputOutput, | |
| 426 | EISDIR => return error.IsDir, | |
| 427 | ENOBUFS => return error.SystemResources, | |
| 428 | ENOMEM => return error.SystemResources, | |
| 429 | ECONNRESET => return error.ConnectionResetByPeer, | |
| 430 | else => |err| return unexpectedErrno(err), | |
| 431 | } | |
| 432 | } | |
| 433 | return index; | |
| 434 | } | |
| 435 | ||
| 436 | /// Number of bytes read is returned. Upon reading end-of-file, zero is returned. | |
| 437 | /// | |
| 438 | /// Retries when interrupted by a signal. | |
| 439 | /// | |
| 440 | /// For POSIX systems, if the application has a global event loop enabled, EAGAIN is handled | |
| 441 | /// via the event loop. Otherwise EAGAIN results in `error.WouldBlock`. | |
| 442 | /// On Windows, if the application has a global event loop enabled, I/O Completion Ports are | |
| 443 | /// used to perform the I/O. `error.WouldBlock` is not possible on Windows. | |
| 444 | /// | |
| 445 | /// This operation is non-atomic on the following systems: | |
| 446 | /// * Darwin | |
| 447 | /// * Windows | |
| 448 | /// On these systems, the read races with concurrent writes to the same file descriptor. | |
| 368 | 449 | pub fn preadv(fd: fd_t, iov: []const iovec, offset: u64) ReadError!usize { |
| 369 | 450 | if (comptime std.Target.current.isDarwin()) { |
| 370 | 451 | // Darwin does not have preadv but it does have pread. |
| ... | ... | @@ -409,6 +490,28 @@ pub fn preadv(fd: fd_t, iov: []const iovec, offset: u64) ReadError!usize { |
| 409 | 490 | } |
| 410 | 491 | } |
| 411 | 492 | } |
| 493 | ||
| 494 | if (builtin.os == .windows) { | |
| 495 | // TODO batch these into parallel requests | |
| 496 | var off: usize = 0; | |
| 497 | var iov_i: usize = 0; | |
| 498 | var inner_off: usize = 0; | |
| 499 | while (true) { | |
| 500 | const v = iov[iov_i]; | |
| 501 | const amt_read = try pread(fd, v.iov_base[inner_off .. v.iov_len - inner_off], offset + off); | |
| 502 | off += amt_read; | |
| 503 | inner_off += amt_read; | |
| 504 | if (inner_off == v.len) { | |
| 505 | iov_i += 1; | |
| 506 | inner_off = 0; | |
| 507 | if (iov_i == iov.len) { | |
| 508 | return off; | |
| 509 | } | |
| 510 | } | |
| 511 | if (amt_read == 0) return off; // EOF | |
| 512 | } else unreachable; // TODO https://github.com/ziglang/zig/issues/707 | |
| 513 | } | |
| 514 | ||
| 412 | 515 | while (true) { |
| 413 | 516 | // TODO handle the case when iov_len is too large and get rid of this @intCast |
| 414 | 517 | const rc = system.preadv(fd, iov.ptr, @intCast(u32, iov.len), offset); |
| ... | ... | @@ -451,11 +554,9 @@ pub const WriteError = error{ |
| 451 | 554 | /// Write to a file descriptor. Keeps trying if it gets interrupted. |
| 452 | 555 | /// If the application has a global event loop enabled, EAGAIN is handled |
| 453 | 556 | /// via the event loop. Otherwise EAGAIN results in error.WouldBlock. |
| 454 | /// TODO evented I/O integration is disabled until | |
| 455 | /// https://github.com/ziglang/zig/issues/3557 is solved. | |
| 456 | 557 | pub fn write(fd: fd_t, bytes: []const u8) WriteError!void { |
| 457 | 558 | if (builtin.os == .windows) { |
| 458 | return windows.WriteFile(fd, bytes); | |
| 559 | return windows.WriteFile(fd, bytes, null); | |
| 459 | 560 | } |
| 460 | 561 | |
| 461 | 562 | if (builtin.os == .wasi and !builtin.link_libc) { |
| ... | ... | @@ -488,14 +589,12 @@ pub fn write(fd: fd_t, bytes: []const u8) WriteError!void { |
| 488 | 589 | EINTR => continue, |
| 489 | 590 | EINVAL => unreachable, |
| 490 | 591 | EFAULT => unreachable, |
| 491 | // TODO https://github.com/ziglang/zig/issues/3557 | |
| 492 | EAGAIN => return error.WouldBlock, | |
| 493 | //EAGAIN => if (std.event.Loop.instance) |loop| { | |
| 494 | // loop.waitUntilFdWritable(fd); | |
| 495 | // continue; | |
| 496 | //} else { | |
| 497 | // return error.WouldBlock; | |
| 498 | //}, | |
| 592 | EAGAIN => if (std.event.Loop.instance) |loop| { | |
| 593 | loop.waitUntilFdWritable(fd); | |
| 594 | continue; | |
| 595 | } else { | |
| 596 | return error.WouldBlock; | |
| 597 | }, | |
| 499 | 598 | EBADF => unreachable, // Always a race condition. |
| 500 | 599 | EDESTADDRREQ => unreachable, // `connect` was never called. |
| 501 | 600 | EDQUOT => return error.DiskQuota, |
| ... | ... | @@ -540,8 +639,57 @@ pub fn writev(fd: fd_t, iov: []const iovec_const) WriteError!void { |
| 540 | 639 | } |
| 541 | 640 | } |
| 542 | 641 | |
| 642 | /// Write to a file descriptor, with a position offset. | |
| 643 | /// | |
| 644 | /// Retries when interrupted by a signal. | |
| 645 | /// | |
| 646 | /// For POSIX systems, if the application has a global event loop enabled, EAGAIN is handled | |
| 647 | /// via the event loop. Otherwise EAGAIN results in `error.WouldBlock`. | |
| 648 | /// On Windows, if the application has a global event loop enabled, I/O Completion Ports are | |
| 649 | /// used to perform the I/O. `error.WouldBlock` is not possible on Windows. | |
| 650 | pub fn pwrite(fd: fd_t, bytes: []const u8, offset: u64) WriteError!void { | |
| 651 | if (comptime std.Target.current.isWindows()) { | |
| 652 | return windows.WriteFile(fd, bytes, offset); | |
| 653 | } | |
| 654 | ||
| 655 | while (true) { | |
| 656 | const rc = system.pwrite(fd, bytes.ptr, bytes.len, offset); | |
| 657 | switch (errno(rc)) { | |
| 658 | 0 => return, | |
| 659 | EINTR => continue, | |
| 660 | EINVAL => unreachable, | |
| 661 | EFAULT => unreachable, | |
| 662 | EAGAIN => if (std.event.Loop.instance) |loop| { | |
| 663 | loop.waitUntilFdWritable(fd); | |
| 664 | continue; | |
| 665 | } else { | |
| 666 | return error.WouldBlock; | |
| 667 | }, | |
| 668 | EBADF => unreachable, // Always a race condition. | |
| 669 | EDESTADDRREQ => unreachable, // `connect` was never called. | |
| 670 | EDQUOT => return error.DiskQuota, | |
| 671 | EFBIG => return error.FileTooBig, | |
| 672 | EIO => return error.InputOutput, | |
| 673 | ENOSPC => return error.NoSpaceLeft, | |
| 674 | EPERM => return error.AccessDenied, | |
| 675 | EPIPE => return error.BrokenPipe, | |
| 676 | else => |err| return unexpectedErrno(err), | |
| 677 | } | |
| 678 | } | |
| 679 | } | |
| 680 | ||
| 543 | 681 | /// Write multiple buffers to a file descriptor, with a position offset. |
| 544 | /// Keeps trying if it gets interrupted. | |
| 682 | /// | |
| 683 | /// Retries when interrupted by a signal. | |
| 684 | /// | |
| 685 | /// If the application has a global event loop enabled, EAGAIN is handled | |
| 686 | /// via the event loop. Otherwise EAGAIN results in `error.WouldBlock`. | |
| 687 | /// | |
| 688 | /// This operation is non-atomic on the following systems: | |
| 689 | /// * Darwin | |
| 690 | /// * Windows | |
| 691 | /// On these systems, the write races with concurrent writes to the same file descriptor, and | |
| 692 | /// the file can be in a partially written state when an error occurs. | |
| 545 | 693 | pub fn pwritev(fd: fd_t, iov: []const iovec_const, offset: u64) WriteError!void { |
| 546 | 694 | if (comptime std.Target.current.isDarwin()) { |
| 547 | 695 | // Darwin does not have pwritev but it does have pwrite. |
| ... | ... | @@ -589,6 +737,15 @@ pub fn pwritev(fd: fd_t, iov: []const iovec_const, offset: u64) WriteError!void |
| 589 | 737 | } |
| 590 | 738 | } |
| 591 | 739 | |
| 740 | if (comptime std.Target.current.isWindows()) { | |
| 741 | var off = offset; | |
| 742 | for (iov) |item| { | |
| 743 | try pwrite(fd, item.iov_base[0..item.iov_len], off); | |
| 744 | off += buf.len; | |
| 745 | } | |
| 746 | return; | |
| 747 | } | |
| 748 | ||
| 592 | 749 | while (true) { |
| 593 | 750 | // TODO handle the case when iov_len is too large and get rid of this @intCast |
| 594 | 751 | const rc = system.pwritev(fd, iov.ptr, @intCast(u32, iov.len), offset); |
| ... | ... | @@ -694,7 +851,7 @@ pub fn openC(file_path: [*:0]const u8, flags: u32, perm: usize) OpenError!fd_t { |
| 694 | 851 | /// Open and possibly create a file. Keeps trying if it gets interrupted. |
| 695 | 852 | /// `file_path` is relative to the open directory handle `dir_fd`. |
| 696 | 853 | /// See also `openatC`. |
| 697 | pub fn openat(dir_fd: fd_t, file_path: []const u8, flags: u32, mode: usize) OpenError!fd_t { | |
| 854 | pub fn openat(dir_fd: fd_t, file_path: []const u8, flags: u32, mode: mode_t) OpenError!fd_t { | |
| 698 | 855 | const file_path_c = try toPosixPath(file_path); |
| 699 | 856 | return openatC(dir_fd, &file_path_c, flags, mode); |
| 700 | 857 | } |
| ... | ... | @@ -702,7 +859,7 @@ pub fn openat(dir_fd: fd_t, file_path: []const u8, flags: u32, mode: usize) Open |
| 702 | 859 | /// Open and possibly create a file. Keeps trying if it gets interrupted. |
| 703 | 860 | /// `file_path` is relative to the open directory handle `dir_fd`. |
| 704 | 861 | /// See also `openat`. |
| 705 | pub fn openatC(dir_fd: fd_t, file_path: [*:0]const u8, flags: u32, mode: usize) OpenError!fd_t { | |
| 862 | pub fn openatC(dir_fd: fd_t, file_path: [*:0]const u8, flags: u32, mode: mode_t) OpenError!fd_t { | |
| 706 | 863 | while (true) { |
| 707 | 864 | const rc = system.openat(dir_fd, file_path, flags, mode); |
| 708 | 865 | switch (errno(rc)) { |
| ... | ... | @@ -2237,7 +2394,7 @@ pub const MMapError = error{ |
| 2237 | 2394 | } || UnexpectedError; |
| 2238 | 2395 | |
| 2239 | 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 | 2398 | /// Use of a mapped region can result in these signals: |
| 2242 | 2399 | /// * SIGSEGV - Attempted write into a region mapped as read-only. |
| 2243 | 2400 | /// * 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 | 2529 | } |
| 2373 | 2530 | |
| 2374 | 2531 | pub fn pipe2(flags: u32) PipeError![2]fd_t { |
| 2532 | if (comptime std.Target.current.isDarwin()) { | |
| 2533 | var fds: [2]fd_t = try pipe(); | |
| 2534 | if (flags == 0) return fds; | |
| 2535 | errdefer { | |
| 2536 | close(fds[0]); | |
| 2537 | close(fds[1]); | |
| 2538 | } | |
| 2539 | for (fds) |fd| switch (errno(system.fcntl(fd, F_SETFL, flags))) { | |
| 2540 | 0 => {}, | |
| 2541 | EINVAL => unreachable, // Invalid flags | |
| 2542 | EBADF => unreachable, // Always a race condition | |
| 2543 | else => |err| return unexpectedErrno(err), | |
| 2544 | }; | |
| 2545 | return fds; | |
| 2546 | } | |
| 2547 | ||
| 2375 | 2548 | var fds: [2]fd_t = undefined; |
| 2376 | 2549 | switch (errno(system.pipe2(&fds, flags))) { |
| 2377 | 2550 | 0 => return fds, |
| ... | ... | @@ -3328,3 +3501,31 @@ pub fn getrusage(who: i32) rusage { |
| 3328 | 3501 | else => unreachable, |
| 3329 | 3502 | } |
| 3330 | 3503 | } |
| 3504 | ||
| 3505 | pub const TermiosGetError = error{NotATerminal} || UnexpectedError; | |
| 3506 | ||
| 3507 | pub 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 | ||
| 3517 | pub const TermiosSetError = TermiosGetError || error{ProcessOrphaned}; | |
| 3518 | ||
| 3519 | pub 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 | 4 | |
| 5 | 5 | pub const fd_t = c_int; |
| 6 | 6 | pub const pid_t = c_int; |
| 7 | pub const mode_t = c_uint; | |
| 7 | 8 | |
| 8 | 9 | pub const in_port_t = u16; |
| 9 | 10 | pub const sa_family_t = u8; |
| ... | ... | @@ -1223,3 +1224,161 @@ pub const RTLD_NEXT = @intToPtr(*c_void, ~maxInt(usize)); |
| 1223 | 1224 | pub const RTLD_DEFAULT = @intToPtr(*c_void, ~maxInt(usize) - 1); |
| 1224 | 1225 | pub const RTLD_SELF = @intToPtr(*c_void, ~maxInt(usize) - 2); |
| 1225 | 1226 | pub const RTLD_MAIN_ONLY = @intToPtr(*c_void, ~maxInt(usize) - 4); |
| 1227 | ||
| 1228 | /// duplicate file descriptor | |
| 1229 | pub const F_DUPFD = 0; | |
| 1230 | ||
| 1231 | /// get file descriptor flags | |
| 1232 | pub const F_GETFD = 1; | |
| 1233 | ||
| 1234 | /// set file descriptor flags | |
| 1235 | pub const F_SETFD = 2; | |
| 1236 | ||
| 1237 | /// get file status flags | |
| 1238 | pub const F_GETFL = 3; | |
| 1239 | ||
| 1240 | /// set file status flags | |
| 1241 | pub const F_SETFL = 4; | |
| 1242 | ||
| 1243 | /// get SIGIO/SIGURG proc/pgrp | |
| 1244 | pub const F_GETOWN = 5; | |
| 1245 | ||
| 1246 | /// set SIGIO/SIGURG proc/pgrp | |
| 1247 | pub const F_SETOWN = 6; | |
| 1248 | ||
| 1249 | /// get record locking information | |
| 1250 | pub const F_GETLK = 7; | |
| 1251 | ||
| 1252 | /// set record locking information | |
| 1253 | pub const F_SETLK = 8; | |
| 1254 | ||
| 1255 | /// F_SETLK; wait if blocked | |
| 1256 | pub const F_SETLKW = 9; | |
| 1257 | ||
| 1258 | /// F_SETLK; wait if blocked, return on timeout | |
| 1259 | pub const F_SETLKWTIMEOUT = 10; | |
| 1260 | pub const F_FLUSH_DATA = 40; | |
| 1261 | ||
| 1262 | /// Used for regression test | |
| 1263 | pub const F_CHKCLEAN = 41; | |
| 1264 | ||
| 1265 | /// Preallocate storage | |
| 1266 | pub const F_PREALLOCATE = 42; | |
| 1267 | ||
| 1268 | /// Truncate a file without zeroing space | |
| 1269 | pub const F_SETSIZE = 43; | |
| 1270 | ||
| 1271 | /// Issue an advisory read async with no copy to user | |
| 1272 | pub const F_RDADVISE = 44; | |
| 1273 | ||
| 1274 | /// turn read ahead off/on for this fd | |
| 1275 | pub const F_RDAHEAD = 45; | |
| 1276 | ||
| 1277 | /// turn data caching off/on for this fd | |
| 1278 | pub const F_NOCACHE = 48; | |
| 1279 | ||
| 1280 | /// file offset to device offset | |
| 1281 | pub const F_LOG2PHYS = 49; | |
| 1282 | ||
| 1283 | /// return the full path of the fd | |
| 1284 | pub const F_GETPATH = 50; | |
| 1285 | ||
| 1286 | /// fsync + ask the drive to flush to the media | |
| 1287 | pub const F_FULLFSYNC = 51; | |
| 1288 | ||
| 1289 | /// find which component (if any) is a package | |
| 1290 | pub const F_PATHPKG_CHECK = 52; | |
| 1291 | ||
| 1292 | /// "freeze" all fs operations | |
| 1293 | pub const F_FREEZE_FS = 53; | |
| 1294 | ||
| 1295 | /// "thaw" all fs operations | |
| 1296 | pub const F_THAW_FS = 54; | |
| 1297 | ||
| 1298 | /// turn data caching off/on (globally) for this file | |
| 1299 | pub const F_GLOBAL_NOCACHE = 55; | |
| 1300 | ||
| 1301 | /// add detached signatures | |
| 1302 | pub const F_ADDSIGS = 59; | |
| 1303 | ||
| 1304 | /// add signature from same file (used by dyld for shared libs) | |
| 1305 | pub const F_ADDFILESIGS = 61; | |
| 1306 | ||
| 1307 | /// used in conjunction with F_NOCACHE to indicate that DIRECT, synchonous writes | |
| 1308 | /// should not be used (i.e. its ok to temporaily create cached pages) | |
| 1309 | pub const F_NODIRECT = 62; | |
| 1310 | ||
| 1311 | ///Get the protection class of a file from the EA, returns int | |
| 1312 | pub const F_GETPROTECTIONCLASS = 63; | |
| 1313 | ||
| 1314 | ///Set the protection class of a file for the EA, requires int | |
| 1315 | pub const F_SETPROTECTIONCLASS = 64; | |
| 1316 | ||
| 1317 | ///file offset to device offset, extended | |
| 1318 | pub const F_LOG2PHYS_EXT = 65; | |
| 1319 | ||
| 1320 | ///get record locking information, per-process | |
| 1321 | pub const F_GETLKPID = 66; | |
| 1322 | ||
| 1323 | ///Mark the file as being the backing store for another filesystem | |
| 1324 | pub const F_SETBACKINGSTORE = 70; | |
| 1325 | ||
| 1326 | ///return the full path of the FD, but error in specific mtmd circumstances | |
| 1327 | pub const F_GETPATH_MTMINFO = 71; | |
| 1328 | ||
| 1329 | ///Returns the code directory, with associated hashes, to the caller | |
| 1330 | pub const F_GETCODEDIR = 72; | |
| 1331 | ||
| 1332 | ///No SIGPIPE generated on EPIPE | |
| 1333 | pub const F_SETNOSIGPIPE = 73; | |
| 1334 | ||
| 1335 | ///Status of SIGPIPE for this fd | |
| 1336 | pub const F_GETNOSIGPIPE = 74; | |
| 1337 | ||
| 1338 | ///For some cases, we need to rewrap the key for AKS/MKB | |
| 1339 | pub const F_TRANSCODEKEY = 75; | |
| 1340 | ||
| 1341 | ///file being written to a by single writer... if throttling enabled, writes | |
| 1342 | ///may be broken into smaller chunks with throttling in between | |
| 1343 | pub const F_SINGLE_WRITER = 76; | |
| 1344 | ||
| 1345 | ///Get the protection version number for this filesystem | |
| 1346 | pub const F_GETPROTECTIONLEVEL = 77; | |
| 1347 | ||
| 1348 | ///Add detached code signatures (used by dyld for shared libs) | |
| 1349 | pub const F_FINDSIGS = 78; | |
| 1350 | ||
| 1351 | ///Add signature from same file, only if it is signed by Apple (used by dyld for simulator) | |
| 1352 | pub const F_ADDFILESIGS_FOR_DYLD_SIM = 83; | |
| 1353 | ||
| 1354 | ///fsync + issue barrier to drive | |
| 1355 | pub const F_BARRIERFSYNC = 85; | |
| 1356 | ||
| 1357 | ///Add signature from same file, return end offset in structure on success | |
| 1358 | pub const F_ADDFILESIGS_RETURN = 97; | |
| 1359 | ||
| 1360 | ///Check if Library Validation allows this Mach-O file to be mapped into the calling process | |
| 1361 | pub const F_CHECK_LV = 98; | |
| 1362 | ||
| 1363 | ///Deallocate a range of the file | |
| 1364 | pub const F_PUNCHHOLE = 99; | |
| 1365 | ||
| 1366 | ///Trim an active file | |
| 1367 | pub const F_TRIM_ACTIVE_FILE = 100; | |
| 1368 | ||
| 1369 | pub const FCNTL_FS_SPECIFIC_BASE = 0x00010000; | |
| 1370 | ||
| 1371 | ///mark the dup with FD_CLOEXEC | |
| 1372 | pub const F_DUPFD_CLOEXEC = 67; | |
| 1373 | ||
| 1374 | ///close-on-exec flag | |
| 1375 | pub const FD_CLOEXEC = 1; | |
| 1376 | ||
| 1377 | /// shared or read lock | |
| 1378 | pub const F_RDLCK = 1; | |
| 1379 | ||
| 1380 | /// unlock | |
| 1381 | pub const F_UNLCK = 2; | |
| 1382 | ||
| 1383 | /// exclusive or write lock | |
| 1384 | pub const F_WRLCK = 3; |
lib/std/os/bits/dragonfly.zig+1| ... | ... | @@ -7,6 +7,7 @@ pub fn S_ISCHR(m: u32) bool { |
| 7 | 7 | pub const fd_t = c_int; |
| 8 | 8 | pub const pid_t = c_int; |
| 9 | 9 | pub const off_t = c_long; |
| 10 | pub const mode_t = c_uint; | |
| 10 | 11 | |
| 11 | 12 | pub const ENOTSUP = EOPNOTSUPP; |
| 12 | 13 | pub const EWOULDBLOCK = EAGAIN; |
lib/std/os/bits/freebsd.zig+1| ... | ... | @@ -3,6 +3,7 @@ const maxInt = std.math.maxInt; |
| 3 | 3 | |
| 4 | 4 | pub const fd_t = c_int; |
| 5 | 5 | pub const pid_t = c_int; |
| 6 | pub const mode_t = c_uint; | |
| 6 | 7 | |
| 7 | 8 | pub const socklen_t = u32; |
| 8 | 9 |
lib/std/os/bits/linux.zig+74| ... | ... | @@ -1515,3 +1515,77 @@ pub const rusage = extern struct { |
| 1515 | 1515 | nivcsw: isize, |
| 1516 | 1516 | __reserved: [16]isize = [1]isize{0} ** 16, |
| 1517 | 1517 | }; |
| 1518 | ||
| 1519 | pub const cc_t = u8; | |
| 1520 | pub const speed_t = u32; | |
| 1521 | pub const tcflag_t = u32; | |
| 1522 | ||
| 1523 | pub const NCCS = 32; | |
| 1524 | ||
| 1525 | pub const IGNBRK = 1; | |
| 1526 | pub const BRKINT = 2; | |
| 1527 | pub const IGNPAR = 4; | |
| 1528 | pub const PARMRK = 8; | |
| 1529 | pub const INPCK = 16; | |
| 1530 | pub const ISTRIP = 32; | |
| 1531 | pub const INLCR = 64; | |
| 1532 | pub const IGNCR = 128; | |
| 1533 | pub const ICRNL = 256; | |
| 1534 | pub const IUCLC = 512; | |
| 1535 | pub const IXON = 1024; | |
| 1536 | pub const IXANY = 2048; | |
| 1537 | pub const IXOFF = 4096; | |
| 1538 | pub const IMAXBEL = 8192; | |
| 1539 | pub const IUTF8 = 16384; | |
| 1540 | ||
| 1541 | pub const OPOST = 1; | |
| 1542 | pub const OLCUC = 2; | |
| 1543 | pub const ONLCR = 4; | |
| 1544 | pub const OCRNL = 8; | |
| 1545 | pub const ONOCR = 16; | |
| 1546 | pub const ONLRET = 32; | |
| 1547 | pub const OFILL = 64; | |
| 1548 | pub const OFDEL = 128; | |
| 1549 | pub const VTDLY = 16384; | |
| 1550 | pub const VT0 = 0; | |
| 1551 | pub const VT1 = 16384; | |
| 1552 | ||
| 1553 | pub const CSIZE = 48; | |
| 1554 | pub const CS5 = 0; | |
| 1555 | pub const CS6 = 16; | |
| 1556 | pub const CS7 = 32; | |
| 1557 | pub const CS8 = 48; | |
| 1558 | pub const CSTOPB = 64; | |
| 1559 | pub const CREAD = 128; | |
| 1560 | pub const PARENB = 256; | |
| 1561 | pub const PARODD = 512; | |
| 1562 | pub const HUPCL = 1024; | |
| 1563 | pub const CLOCAL = 2048; | |
| 1564 | ||
| 1565 | pub const ISIG = 1; | |
| 1566 | pub const ICANON = 2; | |
| 1567 | pub const ECHO = 8; | |
| 1568 | pub const ECHOE = 16; | |
| 1569 | pub const ECHOK = 32; | |
| 1570 | pub const ECHONL = 64; | |
| 1571 | pub const NOFLSH = 128; | |
| 1572 | pub const TOSTOP = 256; | |
| 1573 | pub const IEXTEN = 32768; | |
| 1574 | ||
| 1575 | pub const TCSA = extern enum(c_uint) { | |
| 1576 | NOW, | |
| 1577 | DRAIN, | |
| 1578 | FLUSH, | |
| 1579 | _, | |
| 1580 | }; | |
| 1581 | ||
| 1582 | pub 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 | 12 | const iovec = linux.iovec; |
| 13 | 13 | const iovec_const = linux.iovec_const; |
| 14 | 14 | |
| 15 | pub const mode_t = usize; | |
| 16 | ||
| 15 | 17 | pub const SYS_read = 0; |
| 16 | 18 | pub const SYS_write = 1; |
| 17 | 19 | pub const SYS_open = 2; |
lib/std/os/bits/netbsd.zig+1| ... | ... | @@ -3,6 +3,7 @@ const maxInt = std.math.maxInt; |
| 3 | 3 | |
| 4 | 4 | pub const fd_t = c_int; |
| 5 | 5 | pub const pid_t = c_int; |
| 6 | pub const mode_t = c_uint; | |
| 6 | 7 | |
| 7 | 8 | /// Renamed from `kevent` to `Kevent` to avoid conflict with function name. |
| 8 | 9 | pub const Kevent = extern struct { |
lib/std/os/bits/wasi.zig+1| ... | ... | @@ -130,6 +130,7 @@ pub const EVENTTYPE_FD_WRITE: eventtype_t = 2; |
| 130 | 130 | pub const exitcode_t = u32; |
| 131 | 131 | |
| 132 | 132 | pub const fd_t = u32; |
| 133 | pub const mode_t = u32; | |
| 133 | 134 | |
| 134 | 135 | pub const fdflags_t = u16; |
| 135 | 136 | pub const FDFLAG_APPEND: fdflags_t = 0x0001; |
lib/std/os/bits/windows.zig+1| ... | ... | @@ -5,6 +5,7 @@ const ws2_32 = @import("../windows/ws2_32.zig"); |
| 5 | 5 | |
| 6 | 6 | pub const fd_t = HANDLE; |
| 7 | 7 | pub const pid_t = HANDLE; |
| 8 | pub const mode_t = u0; | |
| 8 | 9 | |
| 9 | 10 | pub const PATH_MAX = 260; |
| 10 | 11 |
lib/std/os/linux.zig+8| ... | ... | @@ -1061,6 +1061,14 @@ pub fn getrusage(who: i32, usage: *rusage) usize { |
| 1061 | 1061 | return syscall2(SYS_getrusage, @bitCast(usize, @as(isize, who)), @ptrToInt(usage)); |
| 1062 | 1062 | } |
| 1063 | 1063 | |
| 1064 | pub 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 | ||
| 1068 | pub 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 | ||
| 1064 | 1072 | test "" { |
| 1065 | 1073 | if (builtin.os == .linux) { |
| 1066 | 1074 | _ = @import("linux/test.zig"); |
lib/std/os/linux/i386.zig+10-4| ... | ... | @@ -72,11 +72,17 @@ pub fn syscall6( |
| 72 | 72 | arg5: usize, |
| 73 | 73 | arg6: usize, |
| 74 | 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 | 79 | return asm volatile ( |
| 76 | \\ push %%ebp | |
| 77 | \\ mov %[arg6], %%ebp | |
| 78 | \\ int $0x80 | |
| 79 | \\ pop %%ebp | |
| 80 | \\ push %[arg6] | |
| 81 | \\ push %%ebp | |
| 82 | \\ mov 4(%%esp), %%ebp | |
| 83 | \\ int $0x80 | |
| 84 | \\ pop %%ebp | |
| 85 | \\ add $4, %%esp | |
| 80 | 86 | : [ret] "={eax}" (-> usize) |
| 81 | 87 | : [number] "{eax}" (number), |
| 82 | 88 | [arg1] "{ebx}" (arg1), |
lib/std/os/test.zig+98| ... | ... | @@ -256,3 +256,101 @@ test "memfd_create" { |
| 256 | 256 | expect(bytes_read == 4); |
| 257 | 257 | expect(mem.eql(u8, buf[0..4], "test")); |
| 258 | 258 | } |
| 259 | ||
| 260 | test "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 | 344 | assert(kernel32.FindClose(hFindFile) != 0); |
| 345 | 345 | } |
| 346 | 346 | |
| 347 | pub const ReadFileError = error{Unexpected}; | |
| 348 | ||
| 349 | pub fn ReadFile(in_hFile: HANDLE, buffer: []u8) ReadFileError!usize { | |
| 350 | var index: usize = 0; | |
| 351 | while (index < buffer.len) { | |
| 352 | const want_read_count = @intCast(DWORD, math.min(@as(DWORD, maxInt(DWORD)), buffer.len - index)); | |
| 353 | var amt_read: DWORD = undefined; | |
| 354 | if (kernel32.ReadFile(in_hFile, buffer.ptr + index, want_read_count, &amt_read, null) == 0) { | |
| 355 | switch (kernel32.GetLastError()) { | |
| 356 | .OPERATION_ABORTED => continue, | |
| 357 | .BROKEN_PIPE => return index, | |
| 358 | else => |err| return unexpectedError(err), | |
| 347 | pub const ReadFileError = error{ | |
| 348 | OperationAborted, | |
| 349 | BrokenPipe, | |
| 350 | Unexpected, | |
| 351 | }; | |
| 352 | ||
| 353 | /// If buffer's length exceeds what a Windows DWORD integer can hold, it will be broken into | |
| 354 | /// multiple non-atomic reads. | |
| 355 | pub fn ReadFile(in_hFile: HANDLE, buffer: []u8, offset: ?u64) ReadFileError!usize { | |
| 356 | if (std.event.Loop.instance) |loop| { | |
| 357 | // TODO support async ReadFile with no offset | |
| 358 | const off = offset.?; | |
| 359 | var resume_node = std.event.Loop.ResumeNode.Basic{ | |
| 360 | .base = .{ | |
| 361 | .id = .Basic, | |
| 362 | .handle = @frame(), | |
| 363 | .overlapped = OVERLAPPED{ | |
| 364 | .Internal = 0, | |
| 365 | .InternalHigh = 0, | |
| 366 | .Offset = @truncate(u32, off), | |
| 367 | .OffsetHigh = @truncate(u32, off >> 32), | |
| 368 | .hEvent = null, | |
| 369 | }, | |
| 370 | }, | |
| 371 | }; | |
| 372 | // TODO only call create io completion port once per fd | |
| 373 | _ = windows.CreateIoCompletionPort(fd, loop.os_data.io_port, undefined, undefined) catch undefined; | |
| 374 | loop.beginOneEvent(); | |
| 375 | suspend { | |
| 376 | // TODO handle buffer bigger than DWORD can hold | |
| 377 | _ = windows.kernel32.ReadFile(fd, buffer.ptr, @intCast(windows.DWORD, buffer.len), null, &resume_node.base.overlapped); | |
| 378 | } | |
| 379 | var bytes_transferred: windows.DWORD = undefined; | |
| 380 | if (windows.kernel32.GetOverlappedResult(fd, &resume_node.base.overlapped, &bytes_transferred, windows.FALSE) == 0) { | |
| 381 | switch (windows.kernel32.GetLastError()) { | |
| 382 | .IO_PENDING => unreachable, | |
| 383 | .OPERATION_ABORTED => return error.OperationAborted, | |
| 384 | .BROKEN_PIPE => return error.BrokenPipe, | |
| 385 | .HANDLE_EOF => return @as(usize, bytes_transferred), | |
| 386 | else => |err| return windows.unexpectedError(err), | |
| 387 | } | |
| 388 | } | |
| 389 | return @as(usize, bytes_transferred); | |
| 390 | } else { | |
| 391 | var index: usize = 0; | |
| 392 | while (index < buffer.len) { | |
| 393 | const want_read_count = @intCast(DWORD, math.min(@as(DWORD, maxInt(DWORD)), buffer.len - index)); | |
| 394 | var amt_read: DWORD = undefined; | |
| 395 | var overlapped_data: OVERLAPPED = undefined; | |
| 396 | const overlapped: ?*OVERLAPPED = if (offset) |off| blk: { | |
| 397 | overlapped_data = .{ | |
| 398 | .Internal = 0, | |
| 399 | .InternalHigh = 0, | |
| 400 | .Offset = @truncate(u32, off + index), | |
| 401 | .OffsetHigh = @truncate(u32, (off + index) >> 32), | |
| 402 | .hEvent = null, | |
| 403 | }; | |
| 404 | break :blk &overlapped_data; | |
| 405 | } else null; | |
| 406 | if (kernel32.ReadFile(in_hFile, buffer.ptr + index, want_read_count, &amt_read, overlapped) == 0) { | |
| 407 | switch (kernel32.GetLastError()) { | |
| 408 | .OPERATION_ABORTED => continue, | |
| 409 | .BROKEN_PIPE => return index, | |
| 410 | else => |err| return unexpectedError(err), | |
| 411 | } | |
| 359 | 412 | } |
| 413 | if (amt_read == 0) return index; | |
| 414 | index += amt_read; | |
| 360 | 415 | } |
| 361 | if (amt_read == 0) return index; | |
| 362 | index += amt_read; | |
| 416 | return index; | |
| 363 | 417 | } |
| 364 | return index; | |
| 365 | 418 | } |
| 366 | 419 | |
| 367 | 420 | pub const WriteFileError = error{ |
| ... | ... | @@ -371,20 +424,66 @@ pub const WriteFileError = error{ |
| 371 | 424 | Unexpected, |
| 372 | 425 | }; |
| 373 | 426 | |
| 374 | /// This function is for blocking file descriptors only. For non-blocking, see | |
| 375 | /// `WriteFileAsync`. | |
| 376 | pub fn WriteFile(handle: HANDLE, bytes: []const u8) WriteFileError!void { | |
| 377 | var bytes_written: DWORD = undefined; | |
| 378 | // TODO replace this @intCast with a loop that writes all the bytes | |
| 379 | if (kernel32.WriteFile(handle, bytes.ptr, @intCast(u32, bytes.len), &bytes_written, null) == 0) { | |
| 380 | switch (kernel32.GetLastError()) { | |
| 381 | .INVALID_USER_BUFFER => return error.SystemResources, | |
| 382 | .NOT_ENOUGH_MEMORY => return error.SystemResources, | |
| 383 | .OPERATION_ABORTED => return error.OperationAborted, | |
| 384 | .NOT_ENOUGH_QUOTA => return error.SystemResources, | |
| 385 | .IO_PENDING => unreachable, // this function is for blocking files only | |
| 386 | .BROKEN_PIPE => return error.BrokenPipe, | |
| 387 | else => |err| return unexpectedError(err), | |
| 427 | pub fn WriteFile(handle: HANDLE, bytes: []const u8, offset: ?u64) WriteFileError!void { | |
| 428 | if (std.event.Loop.instance) |loop| { | |
| 429 | // TODO support async WriteFile with no offset | |
| 430 | const off = offset.?; | |
| 431 | var resume_node = std.event.Loop.ResumeNode.Basic{ | |
| 432 | .base = .{ | |
| 433 | .id = .Basic, | |
| 434 | .handle = @frame(), | |
| 435 | .overlapped = OVERLAPPED{ | |
| 436 | .Internal = 0, | |
| 437 | .InternalHigh = 0, | |
| 438 | .Offset = @truncate(u32, off), | |
| 439 | .OffsetHigh = @truncate(u32, off >> 32), | |
| 440 | .hEvent = null, | |
| 441 | }, | |
| 442 | }, | |
| 443 | }; | |
| 444 | // TODO only call create io completion port once per fd | |
| 445 | _ = CreateIoCompletionPort(fd, loop.os_data.io_port, undefined, undefined); | |
| 446 | loop.beginOneEvent(); | |
| 447 | suspend { | |
| 448 | // TODO replace this @intCast with a loop that writes all the bytes | |
| 449 | _ = kernel32.WriteFile(fd, bytes.ptr, @intCast(windows.DWORD, bytes.len), null, &resume_node.base.overlapped); | |
| 450 | } | |
| 451 | var bytes_transferred: windows.DWORD = undefined; | |
| 452 | if (kernel32.GetOverlappedResult(fd, &resume_node.base.overlapped, &bytes_transferred, FALSE) == 0) { | |
| 453 | switch (kernel32.GetLastError()) { | |
| 454 | .IO_PENDING => unreachable, | |
| 455 | .INVALID_USER_BUFFER => return error.SystemResources, | |
| 456 | .NOT_ENOUGH_MEMORY => return error.SystemResources, | |
| 457 | .OPERATION_ABORTED => return error.OperationAborted, | |
| 458 | .NOT_ENOUGH_QUOTA => return error.SystemResources, | |
| 459 | .BROKEN_PIPE => return error.BrokenPipe, | |
| 460 | else => |err| return windows.unexpectedError(err), | |
| 461 | } | |
| 462 | } | |
| 463 | } else { | |
| 464 | var bytes_written: DWORD = undefined; | |
| 465 | var overlapped_data: OVERLAPPED = undefined; | |
| 466 | const overlapped: ?*OVERLAPPED = if (offset) |off| blk: { | |
| 467 | overlapped_data = .{ | |
| 468 | .Internal = 0, | |
| 469 | .InternalHigh = 0, | |
| 470 | .Offset = @truncate(u32, off), | |
| 471 | .OffsetHigh = @truncate(u32, off >> 32), | |
| 472 | .hEvent = null, | |
| 473 | }; | |
| 474 | break :blk &overlapped_data; | |
| 475 | } else null; | |
| 476 | // TODO replace this @intCast with a loop that writes all the bytes | |
| 477 | if (kernel32.WriteFile(handle, bytes.ptr, @intCast(u32, bytes.len), &bytes_written, overlapped) == 0) { | |
| 478 | switch (kernel32.GetLastError()) { | |
| 479 | .INVALID_USER_BUFFER => return error.SystemResources, | |
| 480 | .NOT_ENOUGH_MEMORY => return error.SystemResources, | |
| 481 | .OPERATION_ABORTED => return error.OperationAborted, | |
| 482 | .NOT_ENOUGH_QUOTA => return error.SystemResources, | |
| 483 | .IO_PENDING => unreachable, // this function is for blocking files only | |
| 484 | .BROKEN_PIPE => return error.BrokenPipe, | |
| 485 | else => |err| return unexpectedError(err), | |
| 486 | } | |
| 388 | 487 | } |
| 389 | 488 | } |
| 390 | 489 | } |
lib/std/rand.zig+26| ... | ... | @@ -733,6 +733,32 @@ test "xoroshiro sequence" { |
| 733 | 733 | } |
| 734 | 734 | } |
| 735 | 735 | |
| 736 | // Gimli | |
| 737 | // | |
| 738 | // CSPRNG | |
| 739 | pub 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 | 762 | // ISAAC64 - http://www.burtleburtle.net/bob/rand/isaacafa.html |
| 737 | 763 | // |
| 738 | 764 | // CSPRNG |
lib/std/special/compiler_rt/clzsi2.zig+2-1| ... | ... | @@ -45,8 +45,9 @@ fn __clzsi2_thumb1() callconv(.Naked) void { |
| 45 | 45 | \\ subs r0, r1, r0 |
| 46 | 46 | \\ bx lr |
| 47 | 47 | \\ .p2align 2 |
| 48 | \\ // Number of bits set in the 0-15 range | |
| 48 | 49 | \\ LUT: |
| 49 | \\ .byte 4,3,2,2,1,1,1,1,0,0,0,0,0,0,0,0 | |
| 50 | \\ .byte 0,1,2,2,3,3,3,3,4,4,4,4,4,4,4,4 | |
| 50 | 51 | ); |
| 51 | 52 | |
| 52 | 53 | unreachable; |
lib/std/special/test_runner.zig+25-1| ... | ... | @@ -2,6 +2,8 @@ const std = @import("std"); |
| 2 | 2 | const io = std.io; |
| 3 | 3 | const builtin = @import("builtin"); |
| 4 | 4 | |
| 5 | pub const io_mode: io.Mode = builtin.test_io_mode; | |
| 6 | ||
| 5 | 7 | pub fn main() anyerror!void { |
| 6 | 8 | const test_fn_list = builtin.test_functions; |
| 7 | 9 | var ok_count: usize = 0; |
| ... | ... | @@ -12,6 +14,11 @@ pub fn main() anyerror!void { |
| 12 | 14 | error.TimerUnsupported => @panic("timer unsupported"), |
| 13 | 15 | }; |
| 14 | 16 | |
| 17 | var async_frame_buffer: []align(std.Target.stack_align) u8 = undefined; | |
| 18 | // TODO this is on the next line (using `undefined` above) because otherwise zig incorrectly | |
| 19 | // ignores the alignment of the slice. | |
| 20 | async_frame_buffer = &[_]u8{}; | |
| 21 | ||
| 15 | 22 | for (test_fn_list) |test_fn, i| { |
| 16 | 23 | std.testing.base_allocator_instance.reset(); |
| 17 | 24 | |
| ... | ... | @@ -21,7 +28,24 @@ pub fn main() anyerror!void { |
| 21 | 28 | if (progress.terminal == null) { |
| 22 | 29 | std.debug.warn("{}/{} {}...", .{ i + 1, test_fn_list.len, test_fn.name }); |
| 23 | 30 | } |
| 24 | if (test_fn.func()) |_| { | |
| 31 | const result = if (test_fn.async_frame_size) |size| switch (io_mode) { | |
| 32 | .evented => blk: { | |
| 33 | if (async_frame_buffer.len < size) { | |
| 34 | std.heap.page_allocator.free(async_frame_buffer); | |
| 35 | async_frame_buffer = try std.heap.page_allocator.alignedAlloc(u8, std.Target.stack_align, size); | |
| 36 | } | |
| 37 | const casted_fn = @ptrCast(async fn () anyerror!void, test_fn.func); | |
| 38 | break :blk await @asyncCall(async_frame_buffer, {}, casted_fn); | |
| 39 | }, | |
| 40 | .blocking => { | |
| 41 | skip_count += 1; | |
| 42 | test_node.end(); | |
| 43 | progress.log("{}...SKIP (async test)\n", .{test_fn.name}); | |
| 44 | if (progress.terminal == null) std.debug.warn("SKIP (async test)\n", .{}); | |
| 45 | continue; | |
| 46 | }, | |
| 47 | } else test_fn.func(); | |
| 48 | if (result) |_| { | |
| 25 | 49 | ok_count += 1; |
| 26 | 50 | test_node.end(); |
| 27 | 51 | std.testing.allocator_instance.validate() catch |err| switch (err) { |
lib/std/start.zig+1-1| ... | ... | @@ -21,7 +21,7 @@ comptime { |
| 21 | 21 | @export(main, .{ .name = "main", .linkage = .Weak }); |
| 22 | 22 | } |
| 23 | 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 | 25 | @export(WinMainCRTStartup, .{ .name = "WinMainCRTStartup" }); |
| 26 | 26 | } |
| 27 | 27 | } else if (builtin.os == .uefi) { |
lib/std/target.zig+9| ... | ... | @@ -242,6 +242,13 @@ pub const Target = union(enum) { |
| 242 | 242 | }; |
| 243 | 243 | } |
| 244 | 244 | |
| 245 | pub fn isRISCV(arch: Arch) bool { | |
| 246 | return switch (arch) { | |
| 247 | .riscv32, .riscv64 => true, | |
| 248 | else => false, | |
| 249 | }; | |
| 250 | } | |
| 251 | ||
| 245 | 252 | pub fn isMIPS(arch: Arch) bool { |
| 246 | 253 | return switch (arch) { |
| 247 | 254 | .mips, .mipsel, .mips64, .mips64el => true, |
| ... | ... | @@ -598,6 +605,8 @@ pub const Target = union(enum) { |
| 598 | 605 | } |
| 599 | 606 | |
| 600 | 607 | pub fn populateDependencies(set: *Set, all_features_list: []const Cpu.Feature) void { |
| 608 | @setEvalBranchQuota(1000000); | |
| 609 | ||
| 601 | 610 | var old = set.ints; |
| 602 | 611 | while (true) { |
| 603 | 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 | 571 | } |
| 572 | 572 | } |
| 573 | 573 | |
| 574 | const len = result.len; | |
| 574 | 575 | try result.append(0); |
| 575 | return result.toOwnedSlice()[0..:0]; | |
| 576 | return result.toOwnedSlice()[0..len :0]; | |
| 576 | 577 | } |
| 577 | 578 | |
| 578 | 579 | /// Returns index of next character. If exact fit, returned index equals output slice length. |
| ... | ... | @@ -619,12 +620,14 @@ test "utf8ToUtf16LeWithNull" { |
| 619 | 620 | var bytes: [128]u8 = undefined; |
| 620 | 621 | const allocator = &std.heap.FixedBufferAllocator.init(bytes[0..]).allocator; |
| 621 | 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 | 627 | var bytes: [128]u8 = undefined; |
| 626 | 628 | const allocator = &std.heap.FixedBufferAllocator.init(bytes[0..]).allocator; |
| 627 | 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 @@ |
| 1 | const std = @import("std"); | |
| 2 | const expect = std.testing.expect; | |
| 3 | const ZigClangSourceLocation = @import("clang.zig").ZigClangSourceLocation; | |
| 4 | const Context = @import("translate_c.zig").Context; | |
| 5 | const failDecl = @import("translate_c.zig").failDecl; | |
| 6 | ||
| 7 | pub const TokenList = std.SegmentedList(CToken, 32); | |
| 8 | ||
| 9 | pub 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 | ||
| 65 | pub 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 | ||
| 89 | fn 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 | ||
| 245 | fn 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 | ||
| 849 | fn 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 | ||
| 868 | test "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 | ||
| 906 | test "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 | ||
| 939 | test "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 | 29 | const link = @import("link.zig").link; |
| 30 | 30 | const LibCInstallation = @import("libc_installation.zig").LibCInstallation; |
| 31 | 31 | const CInt = @import("c_int.zig").CInt; |
| 32 | const fs = event.fs; | |
| 32 | const fs = std.fs; | |
| 33 | 33 | const util = @import("util.zig"); |
| 34 | 34 | |
| 35 | 35 | const max_src_size = 2 * 1024 * 1024 * 1024; // 2 GiB |
| ... | ... | @@ -442,7 +442,7 @@ pub const Compilation = struct { |
| 442 | 442 | comp.name = try Buffer.init(comp.arena(), name); |
| 443 | 443 | comp.llvm_triple = try util.getTriple(comp.arena(), target); |
| 444 | 444 | comp.llvm_target = try util.llvmTargetFromTriple(comp.llvm_triple); |
| 445 | comp.zig_std_dir = try std.fs.path.join(comp.arena(), &[_][]const u8{ zig_lib_dir, "std" }); | |
| 445 | comp.zig_std_dir = try fs.path.join(comp.arena(), &[_][]const u8{ zig_lib_dir, "std" }); | |
| 446 | 446 | |
| 447 | 447 | const opt_level = switch (build_mode) { |
| 448 | 448 | .Debug => llvm.CodeGenLevelNone, |
| ... | ... | @@ -485,8 +485,8 @@ pub const Compilation = struct { |
| 485 | 485 | defer comp.events.deinit(); |
| 486 | 486 | |
| 487 | 487 | if (root_src_path) |root_src| { |
| 488 | const dirname = std.fs.path.dirname(root_src) orelse "."; | |
| 489 | const basename = std.fs.path.basename(root_src); | |
| 488 | const dirname = fs.path.dirname(root_src) orelse "."; | |
| 489 | const basename = fs.path.basename(root_src); | |
| 490 | 490 | |
| 491 | 491 | comp.root_package = try Package.create(comp.arena(), dirname, basename); |
| 492 | 492 | comp.std_package = try Package.create(comp.arena(), comp.zig_std_dir, "std.zig"); |
| ... | ... | @@ -518,7 +518,7 @@ pub const Compilation = struct { |
| 518 | 518 | if (comp.tmp_dir.getOrNull()) |tmp_dir_result| |
| 519 | 519 | if (tmp_dir_result.*) |tmp_dir| { |
| 520 | 520 | // TODO evented I/O? |
| 521 | std.fs.deleteTree(tmp_dir) catch {}; | |
| 521 | fs.deleteTree(tmp_dir) catch {}; | |
| 522 | 522 | } else |_| {}; |
| 523 | 523 | } |
| 524 | 524 | |
| ... | ... | @@ -794,7 +794,7 @@ pub const Compilation = struct { |
| 794 | 794 | |
| 795 | 795 | async fn rebuildFile(self: *Compilation, root_scope: *Scope.Root) BuildError!void { |
| 796 | 796 | const tree_scope = blk: { |
| 797 | const source_code = fs.readFile( | |
| 797 | const source_code = fs.cwd().readFileAlloc( | |
| 798 | 798 | self.gpa(), |
| 799 | 799 | root_scope.realpath, |
| 800 | 800 | max_src_size, |
| ... | ... | @@ -932,8 +932,8 @@ pub const Compilation = struct { |
| 932 | 932 | fn initialCompile(self: *Compilation) !void { |
| 933 | 933 | if (self.root_src_path) |root_src_path| { |
| 934 | 934 | const root_scope = blk: { |
| 935 | // TODO async/await std.fs.realpath | |
| 936 | const root_src_real_path = std.fs.realpathAlloc(self.gpa(), root_src_path) catch |err| { | |
| 935 | // TODO async/await fs.realpath | |
| 936 | const root_src_real_path = fs.realpathAlloc(self.gpa(), root_src_path) catch |err| { | |
| 937 | 937 | try self.addCompileErrorCli(root_src_path, "unable to open: {}", .{@errorName(err)}); |
| 938 | 938 | return; |
| 939 | 939 | }; |
| ... | ... | @@ -1154,7 +1154,7 @@ pub const Compilation = struct { |
| 1154 | 1154 | const file_name = try std.fmt.allocPrint(self.gpa(), "{}{}", .{ file_prefix[0..], suffix }); |
| 1155 | 1155 | defer self.gpa().free(file_name); |
| 1156 | 1156 | |
| 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 | 1158 | errdefer self.gpa().free(full_path); |
| 1159 | 1159 | |
| 1160 | 1160 | return Buffer.fromOwnedSlice(self.gpa(), full_path); |
| ... | ... | @@ -1175,8 +1175,8 @@ pub const Compilation = struct { |
| 1175 | 1175 | const zig_dir_path = try getZigDir(self.gpa()); |
| 1176 | 1176 | defer self.gpa().free(zig_dir_path); |
| 1177 | 1177 | |
| 1178 | const tmp_dir = try std.fs.path.join(self.arena(), &[_][]const u8{ zig_dir_path, comp_dir_name[0..] }); | |
| 1179 | try std.fs.makePath(self.gpa(), tmp_dir); | |
| 1178 | const tmp_dir = try fs.path.join(self.arena(), &[_][]const u8{ zig_dir_path, comp_dir_name[0..] }); | |
| 1179 | try fs.makePath(self.gpa(), tmp_dir); | |
| 1180 | 1180 | return tmp_dir; |
| 1181 | 1181 | } |
| 1182 | 1182 | |
| ... | ... | @@ -1348,7 +1348,7 @@ async fn addFnToLinkSet(comp: *Compilation, fn_val: *Value.Fn) Compilation.Build |
| 1348 | 1348 | } |
| 1349 | 1349 | |
| 1350 | 1350 | fn getZigDir(allocator: *mem.Allocator) ![]u8 { |
| 1351 | return std.fs.getAppDataDir(allocator, "zig"); | |
| 1351 | return fs.getAppDataDir(allocator, "zig"); | |
| 1352 | 1352 | } |
| 1353 | 1353 | |
| 1354 | 1354 | fn analyzeFnType( |
src-self-hosted/dep_tokenizer.zig+10-23| ... | ... | @@ -998,7 +998,8 @@ fn printCharValues(out: var, bytes: []const u8) !void { |
| 998 | 998 | |
| 999 | 999 | fn printUnderstandableChar(out: var, char: u8) !void { |
| 1000 | 1000 | if (!std.ascii.isPrint(char) or char == ' ') { |
| 1001 | std.fmt.format(out.context, anyerror, out.output, "\\x{X:2}", .{char}) catch {}; | |
| 1001 | const output = @typeInfo(@TypeOf(out)).Pointer.child.output; | |
| 1002 | std.fmt.format(out.context, anyerror, output, "\\x{X:2}", .{char}) catch {}; | |
| 1002 | 1003 | } else { |
| 1003 | 1004 | try out.write("'"); |
| 1004 | 1005 | try out.write(&[_]u8{printable_char_tab[char]}); |
| ... | ... | @@ -1021,34 +1022,20 @@ comptime { |
| 1021 | 1022 | // output: must be a function that takes a `self` idiom parameter |
| 1022 | 1023 | // and a bytes parameter |
| 1023 | 1024 | // context: must be that self |
| 1024 | fn makeOutput(output: var, context: var) Output(@TypeOf(output)) { | |
| 1025 | return Output(@TypeOf(output)){ | |
| 1026 | .output = output, | |
| 1025 | fn makeOutput(comptime output: var, context: var) Output(output, @TypeOf(context)) { | |
| 1026 | return Output(output, @TypeOf(context)){ | |
| 1027 | 1027 | .context = context, |
| 1028 | 1028 | }; |
| 1029 | 1029 | } |
| 1030 | 1030 | |
| 1031 | fn Output(comptime T: type) type { | |
| 1032 | const args = switch (@typeInfo(T)) { | |
| 1033 | .Fn => |f| f.args, | |
| 1034 | else => @compileError("output parameter is not a function"), | |
| 1035 | }; | |
| 1036 | if (args.len != 2) { | |
| 1037 | @compileError("output function must take 2 arguments"); | |
| 1038 | } | |
| 1039 | const at0 = args[0].arg_type orelse @compileError("output arg[0] does not have a type"); | |
| 1040 | const at1 = args[1].arg_type orelse @compileError("output arg[1] does not have a type"); | |
| 1041 | const arg1p = switch (@typeInfo(at1)) { | |
| 1042 | .Pointer => |p| p, | |
| 1043 | else => @compileError("output arg[1] is not a slice"), | |
| 1044 | }; | |
| 1045 | if (arg1p.child != u8) @compileError("output arg[1] is not a u8 slice"); | |
| 1031 | fn Output(comptime output_func: var, comptime Context: type) type { | |
| 1046 | 1032 | return struct { |
| 1047 | output: T, | |
| 1048 | context: at0, | |
| 1033 | context: Context, | |
| 1034 | ||
| 1035 | pub const output = output_func; | |
| 1049 | 1036 | |
| 1050 | fn write(self: *@This(), bytes: []const u8) !void { | |
| 1051 | try self.output(self.context, bytes); | |
| 1037 | fn write(self: @This(), bytes: []const u8) !void { | |
| 1038 | try output_func(self.context, bytes); | |
| 1052 | 1039 | } |
| 1053 | 1040 | }; |
| 1054 | 1041 | } |
src-self-hosted/introspect.zig+1-1| ... | ... | @@ -14,7 +14,7 @@ pub fn testZigInstallPrefix(allocator: *mem.Allocator, test_path: []const u8) ![ |
| 14 | 14 | const test_index_file = try fs.path.join(allocator, &[_][]const u8{ test_zig_dir, "std", "std.zig" }); |
| 15 | 15 | defer allocator.free(test_index_file); |
| 16 | 16 | |
| 17 | var file = try fs.File.openRead(test_index_file); | |
| 17 | var file = try fs.cwd().openRead(test_index_file); | |
| 18 | 18 | file.close(); |
| 19 | 19 | |
| 20 | 20 | return test_zig_dir; |
src-self-hosted/main.zig+1-1| ... | ... | @@ -724,7 +724,7 @@ async fn fmtPath(fmt: *Fmt, file_path_ref: []const u8, check_mode: bool) FmtErro |
| 724 | 724 | if (try held.value.put(file_path, {})) |_| return; |
| 725 | 725 | } |
| 726 | 726 | |
| 727 | const source_code = event.fs.readFile( | |
| 727 | const source_code = fs.cwd().readFileAlloc( | |
| 728 | 728 | fmt.allocator, |
| 729 | 729 | file_path, |
| 730 | 730 | max_src_size, |
src-self-hosted/translate_c.zig+320-98| ... | ... | @@ -6,8 +6,9 @@ const assert = std.debug.assert; |
| 6 | 6 | const ast = std.zig.ast; |
| 7 | 7 | const Token = std.zig.Token; |
| 8 | 8 | usingnamespace @import("clang.zig"); |
| 9 | const ctok = @import("c_tokenizer.zig"); | |
| 10 | const CToken = ctok.CToken; | |
| 9 | const ctok = std.c.tokenizer; | |
| 10 | const CToken = std.c.Token; | |
| 11 | const CTokenList = std.c.tokenizer.Source.TokenList; | |
| 11 | 12 | const mem = std.mem; |
| 12 | 13 | const math = std.math; |
| 13 | 14 | |
| ... | ... | @@ -4811,6 +4812,15 @@ fn transCreateNodeIdentifier(c: *Context, name: []const u8) !*ast.Node { |
| 4811 | 4812 | return &identifier.base; |
| 4812 | 4813 | } |
| 4813 | 4814 | |
| 4815 | fn 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 | ||
| 4814 | 4824 | pub fn freeErrors(errors: []ClangErrMsg) void { |
| 4815 | 4825 | ZigClangErrorMsg_delete(errors.ptr, errors.len); |
| 4816 | 4826 | } |
| ... | ... | @@ -4819,7 +4829,7 @@ fn transPreprocessorEntities(c: *Context, unit: *ZigClangASTUnit) Error!void { |
| 4819 | 4829 | // TODO if we see #undef, delete it from the table |
| 4820 | 4830 | var it = ZigClangASTUnit_getLocalPreprocessingEntities_begin(unit); |
| 4821 | 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 | 4833 | const scope = c.global_scope; |
| 4824 | 4834 | |
| 4825 | 4835 | while (it.I != it_end.I) : (it.I += 1) { |
| ... | ... | @@ -4840,42 +4850,59 @@ fn transPreprocessorEntities(c: *Context, unit: *ZigClangASTUnit) Error!void { |
| 4840 | 4850 | } |
| 4841 | 4851 | |
| 4842 | 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) { | |
| 4844 | error.OutOfMemory => |e| return e, | |
| 4845 | else => { | |
| 4846 | continue; | |
| 4853 | const slice = begin_c[0..mem.len(u8, begin_c)]; | |
| 4854 | ||
| 4855 | tok_list.shrink(0); | |
| 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 | } | |
| 4849 | 4875 | |
| 4850 | 4876 | var tok_it = tok_list.iterator(0); |
| 4851 | 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 | 4881 | const next = tok_it.peek().?; |
| 4854 | 4882 | switch (next.id) { |
| 4855 | 4883 | .Identifier => { |
| 4856 | 4884 | // if it equals itself, ignore. for example, from stdio.h: |
| 4857 | 4885 | // #define stdin stdin |
| 4858 | if (mem.eql(u8, name, next.bytes)) { | |
| 4886 | if (mem.eql(u8, name, slice[next.start..next.end])) { | |
| 4859 | 4887 | continue; |
| 4860 | 4888 | } |
| 4861 | 4889 | }, |
| 4862 | .Eof => { | |
| 4890 | .Nl, .Eof => { | |
| 4863 | 4891 | // this means it is a macro without a value |
| 4864 | 4892 | // we don't care about such things |
| 4865 | 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 | 4899 | else => {}, |
| 4868 | 4900 | } |
| 4869 | 4901 | |
| 4870 | const macro_fn = if (tok_it.peek().?.id == .Fn) blk: { | |
| 4871 | _ = tok_it.next(); | |
| 4872 | break :blk true; | |
| 4873 | } else false; | |
| 4874 | ||
| 4875 | 4902 | (if (macro_fn) |
| 4876 | transMacroFnDefine(c, &tok_it, mangled_name, begin_loc) | |
| 4903 | transMacroFnDefine(c, &tok_it, slice, mangled_name, begin_loc) | |
| 4877 | 4904 | 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 | 4906 | error.ParseError => continue, |
| 4880 | 4907 | error.OutOfMemory => |e| return e, |
| 4881 | 4908 | }; |
| ... | ... | @@ -4885,15 +4912,15 @@ fn transPreprocessorEntities(c: *Context, unit: *ZigClangASTUnit) Error!void { |
| 4885 | 4912 | } |
| 4886 | 4913 | } |
| 4887 | 4914 | |
| 4888 | fn transMacroDefine(c: *Context, it: *ctok.TokenList.Iterator, name: []const u8, source_loc: ZigClangSourceLocation) ParseError!void { | |
| 4915 | fn transMacroDefine(c: *Context, it: *CTokenList.Iterator, source: []const u8, name: []const u8, source_loc: ZigClangSourceLocation) ParseError!void { | |
| 4889 | 4916 | const scope = &c.global_scope.base; |
| 4890 | 4917 | |
| 4891 | 4918 | const node = try transCreateNodeVarDecl(c, true, true, name); |
| 4892 | 4919 | node.eq_token = try appendToken(c, .Equal, "="); |
| 4893 | 4920 | |
| 4894 | node.init_node = try parseCExpr(c, it, source_loc, scope); | |
| 4921 | node.init_node = try parseCExpr(c, it, source, source_loc, scope); | |
| 4895 | 4922 | const last = it.next().?; |
| 4896 | if (last.id != .Eof) | |
| 4923 | if (last.id != .Eof and last.id != .Nl) | |
| 4897 | 4924 | return failDecl( |
| 4898 | 4925 | c, |
| 4899 | 4926 | source_loc, |
| ... | ... | @@ -4906,7 +4933,7 @@ fn transMacroDefine(c: *Context, it: *ctok.TokenList.Iterator, name: []const u8, |
| 4906 | 4933 | _ = try c.global_scope.macro_table.put(name, &node.base); |
| 4907 | 4934 | } |
| 4908 | 4935 | |
| 4909 | fn transMacroFnDefine(c: *Context, it: *ctok.TokenList.Iterator, name: []const u8, source_loc: ZigClangSourceLocation) ParseError!void { | |
| 4936 | fn transMacroFnDefine(c: *Context, it: *CTokenList.Iterator, source: []const u8, name: []const u8, source_loc: ZigClangSourceLocation) ParseError!void { | |
| 4910 | 4937 | const block_scope = try Scope.Block.init(c, &c.global_scope.base, null); |
| 4911 | 4938 | const scope = &block_scope.base; |
| 4912 | 4939 | |
| ... | ... | @@ -4938,7 +4965,7 @@ fn transMacroFnDefine(c: *Context, it: *ctok.TokenList.Iterator, name: []const u |
| 4938 | 4965 | ); |
| 4939 | 4966 | } |
| 4940 | 4967 | |
| 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 | 4969 | const param_name_tok = try appendIdentifier(c, mangled_name); |
| 4943 | 4970 | _ = try appendToken(c, .Colon, ":"); |
| 4944 | 4971 | |
| ... | ... | @@ -5001,9 +5028,9 @@ fn transMacroFnDefine(c: *Context, it: *ctok.TokenList.Iterator, name: []const u |
| 5001 | 5028 | const block = try transCreateNodeBlock(c, null); |
| 5002 | 5029 | |
| 5003 | 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 | 5032 | const last = it.next().?; |
| 5006 | if (last.id != .Eof) | |
| 5033 | if (last.id != .Eof and last.id != .Nl) | |
| 5007 | 5034 | return failDecl( |
| 5008 | 5035 | c, |
| 5009 | 5036 | source_loc, |
| ... | ... | @@ -5023,27 +5050,28 @@ fn transMacroFnDefine(c: *Context, it: *ctok.TokenList.Iterator, name: []const u |
| 5023 | 5050 | |
| 5024 | 5051 | const ParseError = Error || error{ParseError}; |
| 5025 | 5052 | |
| 5026 | fn parseCExpr(c: *Context, it: *ctok.TokenList.Iterator, source_loc: ZigClangSourceLocation, scope: *Scope) ParseError!*ast.Node { | |
| 5027 | const node = try parseCPrefixOpExpr(c, it, source_loc, scope); | |
| 5053 | fn parseCExpr(c: *Context, it: *CTokenList.Iterator, source: []const u8, source_loc: ZigClangSourceLocation, scope: *Scope) ParseError!*ast.Node { | |
| 5054 | const node = try parseCPrefixOpExpr(c, it, source, source_loc, scope); | |
| 5028 | 5055 | switch (it.next().?.id) { |
| 5029 | 5056 | .QuestionMark => { |
| 5030 | 5057 | // must come immediately after expr |
| 5031 | 5058 | _ = try appendToken(c, .RParen, ")"); |
| 5032 | 5059 | const if_node = try transCreateNodeIf(c); |
| 5033 | 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 | 5062 | if (it.next().?.id != .Colon) { |
| 5063 | const first_tok = it.list.at(0); | |
| 5036 | 5064 | try failDecl( |
| 5037 | 5065 | c, |
| 5038 | 5066 | source_loc, |
| 5039 | it.list.at(0).*.bytes, | |
| 5067 | source[first_tok.start..first_tok.end], | |
| 5040 | 5068 | "unable to translate C expr: expected ':'", |
| 5041 | 5069 | .{}, |
| 5042 | 5070 | ); |
| 5043 | 5071 | return error.ParseError; |
| 5044 | 5072 | } |
| 5045 | 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 | 5075 | return &if_node.base; |
| 5048 | 5076 | }, |
| 5049 | 5077 | else => { |
| ... | ... | @@ -5053,30 +5081,30 @@ fn parseCExpr(c: *Context, it: *ctok.TokenList.Iterator, source_loc: ZigClangSou |
| 5053 | 5081 | } |
| 5054 | 5082 | } |
| 5055 | 5083 | |
| 5056 | fn parseCNumLit(c: *Context, tok: *CToken, source_loc: ZigClangSourceLocation) ParseError!*ast.Node { | |
| 5057 | if (tok.id == .NumLitInt) { | |
| 5058 | var lit_bytes = tok.bytes; | |
| 5084 | fn parseCNumLit(c: *Context, tok: *CToken, source: []const u8, source_loc: ZigClangSourceLocation) ParseError!*ast.Node { | |
| 5085 | var lit_bytes = source[tok.start..tok.end]; | |
| 5059 | 5086 | |
| 5060 | if (tok.bytes.len > 2 and tok.bytes[0] == '0') { | |
| 5061 | switch (tok.bytes[1]) { | |
| 5087 | if (tok.id == .IntegerLiteral) { | |
| 5088 | if (lit_bytes.len > 2 and lit_bytes[0] == '0') { | |
| 5089 | switch (lit_bytes[1]) { | |
| 5062 | 5090 | '0'...'7' => { |
| 5063 | 5091 | // 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 | 5094 | 'X' => { |
| 5067 | 5095 | // 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 | 5098 | else => {}, |
| 5071 | 5099 | } |
| 5072 | 5100 | } |
| 5073 | 5101 | |
| 5074 | if (tok.num_lit_suffix == .None) { | |
| 5102 | if (tok.id.IntegerLiteral == .None) { | |
| 5075 | 5103 | return transCreateNodeInt(c, lit_bytes); |
| 5076 | 5104 | } |
| 5077 | 5105 | |
| 5078 | 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 | 5108 | .U => "c_uint", |
| 5081 | 5109 | .L => "c_long", |
| 5082 | 5110 | .LU => "c_ulong", |
| ... | ... | @@ -5084,55 +5112,233 @@ fn parseCNumLit(c: *Context, tok: *CToken, source_loc: ZigClangSourceLocation) P |
| 5084 | 5112 | .LLU => "c_ulonglong", |
| 5085 | 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 | 5121 | _ = try appendToken(c, .Comma, ","); |
| 5088 | 5122 | try cast_node.params.push(try transCreateNodeInt(c, lit_bytes)); |
| 5089 | 5123 | cast_node.rparen_token = try appendToken(c, .RParen, ")"); |
| 5090 | 5124 | return &cast_node.base; |
| 5091 | } else if (tok.id == .NumLitFloat) { | |
| 5092 | if (tok.num_lit_suffix == .None) { | |
| 5093 | return transCreateNodeFloat(c, tok.bytes); | |
| 5125 | } else if (tok.id == .FloatLiteral) { | |
| 5126 | if (tok.id.FloatLiteral == .None) { | |
| 5127 | return transCreateNodeFloat(c, lit_bytes); | |
| 5094 | 5128 | } |
| 5095 | 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 | 5131 | .F => "f32", |
| 5098 | .L => "f64", | |
| 5132 | .L => "c_longdouble", | |
| 5099 | 5133 | else => unreachable, |
| 5100 | 5134 | })); |
| 5101 | 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 | 5137 | cast_node.rparen_token = try appendToken(c, .RParen, ")"); |
| 5104 | 5138 | return &cast_node.base; |
| 5105 | 5139 | } else unreachable; |
| 5106 | 5140 | } |
| 5107 | 5141 | |
| 5108 | fn parseCPrimaryExpr(c: *Context, it: *ctok.TokenList.Iterator, source_loc: ZigClangSourceLocation, scope: *Scope) ParseError!*ast.Node { | |
| 5142 | fn 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 | ||
| 5302 | fn parseCPrimaryExpr(c: *Context, it: *CTokenList.Iterator, source: []const u8, source_loc: ZigClangSourceLocation, scope: *Scope) ParseError!*ast.Node { | |
| 5109 | 5303 | const tok = it.next().?; |
| 5110 | 5304 | switch (tok.id) { |
| 5111 | .CharLit => { | |
| 5112 | const token = try appendToken(c, .CharLiteral, tok.bytes); | |
| 5305 | .CharLiteral => { | |
| 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 | 5308 | const node = try c.a().create(ast.Node.CharLiteral); |
| 5114 | 5309 | node.* = ast.Node.CharLiteral{ |
| 5115 | 5310 | .token = token, |
| 5116 | 5311 | }; |
| 5117 | 5312 | return &node.base; |
| 5118 | 5313 | }, |
| 5119 | .StrLit => { | |
| 5120 | const token = try appendToken(c, .StringLiteral, tok.bytes); | |
| 5314 | .StringLiteral => { | |
| 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 | 5317 | const node = try c.a().create(ast.Node.StringLiteral); |
| 5122 | 5318 | node.* = ast.Node.StringLiteral{ |
| 5123 | 5319 | .token = token, |
| 5124 | 5320 | }; |
| 5125 | 5321 | return &node.base; |
| 5126 | 5322 | }, |
| 5127 | .NumLitInt, .NumLitFloat => { | |
| 5128 | return parseCNumLit(c, tok, source_loc); | |
| 5323 | .IntegerLiteral, .FloatLiteral => { | |
| 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 | 5336 | .Identifier => { |
| 5131 | const mangled_name = scope.getAlias(tok.bytes); | |
| 5337 | const mangled_name = scope.getAlias(source[tok.start..tok.end]); | |
| 5132 | 5338 | return transCreateNodeIdentifier(c, mangled_name); |
| 5133 | 5339 | }, |
| 5134 | 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); | |
| 5136 | 5342 | |
| 5137 | 5343 | if (it.peek().?.id == .RParen) { |
| 5138 | 5344 | _ = it.next(); |
| ... | ... | @@ -5145,13 +5351,14 @@ fn parseCPrimaryExpr(c: *Context, it: *ctok.TokenList.Iterator, source_loc: ZigC |
| 5145 | 5351 | // hack to get zig fmt to render a comma in builtin calls |
| 5146 | 5352 | _ = try appendToken(c, .Comma, ","); |
| 5147 | 5353 | |
| 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); | |
| 5149 | 5355 | |
| 5150 | 5356 | if (it.next().?.id != .RParen) { |
| 5357 | const first_tok = it.list.at(0); | |
| 5151 | 5358 | try failDecl( |
| 5152 | 5359 | c, |
| 5153 | 5360 | source_loc, |
| 5154 | it.list.at(0).*.bytes, | |
| 5361 | source[first_tok.start..first_tok.end], | |
| 5155 | 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 | 5436 | return &if_1.base; |
| 5230 | 5437 | }, |
| 5231 | 5438 | else => { |
| 5439 | const first_tok = it.list.at(0); | |
| 5232 | 5440 | try failDecl( |
| 5233 | 5441 | c, |
| 5234 | 5442 | source_loc, |
| 5235 | it.list.at(0).*.bytes, | |
| 5443 | source[first_tok.start..first_tok.end], | |
| 5236 | 5444 | "unable to translate C expr: unexpected token {}", |
| 5237 | 5445 | .{tok.id}, |
| 5238 | 5446 | ); |
| ... | ... | @@ -5241,33 +5449,35 @@ fn parseCPrimaryExpr(c: *Context, it: *ctok.TokenList.Iterator, source_loc: ZigC |
| 5241 | 5449 | } |
| 5242 | 5450 | } |
| 5243 | 5451 | |
| 5244 | fn parseCSuffixOpExpr(c: *Context, it: *ctok.TokenList.Iterator, source_loc: ZigClangSourceLocation, scope: *Scope) ParseError!*ast.Node { | |
| 5245 | var node = try parseCPrimaryExpr(c, it, source_loc, scope); | |
| 5452 | fn parseCSuffixOpExpr(c: *Context, it: *CTokenList.Iterator, source: []const u8, source_loc: ZigClangSourceLocation, scope: *Scope) ParseError!*ast.Node { | |
| 5453 | var node = try parseCPrimaryExpr(c, it, source, source_loc, scope); | |
| 5246 | 5454 | while (true) { |
| 5247 | 5455 | const tok = it.next().?; |
| 5248 | 5456 | switch (tok.id) { |
| 5249 | .Dot => { | |
| 5457 | .Period => { | |
| 5250 | 5458 | const name_tok = it.next().?; |
| 5251 | 5459 | if (name_tok.id != .Identifier) { |
| 5460 | const first_tok = it.list.at(0); | |
| 5252 | 5461 | try failDecl( |
| 5253 | 5462 | c, |
| 5254 | 5463 | source_loc, |
| 5255 | it.list.at(0).*.bytes, | |
| 5464 | source[first_tok.start..first_tok.end], | |
| 5256 | 5465 | "unable to translate C expr: expected identifier", |
| 5257 | 5466 | .{}, |
| 5258 | 5467 | ); |
| 5259 | 5468 | return error.ParseError; |
| 5260 | 5469 | } |
| 5261 | 5470 | |
| 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 | 5473 | .Arrow => { |
| 5265 | 5474 | const name_tok = it.next().?; |
| 5266 | 5475 | if (name_tok.id != .Identifier) { |
| 5476 | const first_tok = it.list.at(0); | |
| 5267 | 5477 | try failDecl( |
| 5268 | 5478 | c, |
| 5269 | 5479 | source_loc, |
| 5270 | it.list.at(0).*.bytes, | |
| 5480 | source[first_tok.start..first_tok.end], | |
| 5271 | 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 | 5485 | } |
| 5276 | 5486 | |
| 5277 | 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 | 5490 | .Asterisk => { |
| 5281 | 5491 | if (it.peek().?.id == .RParen) { |
| ... | ... | @@ -5284,13 +5494,23 @@ fn parseCSuffixOpExpr(c: *Context, it: *ctok.TokenList.Iterator, source_loc: Zig |
| 5284 | 5494 | // hack to get zig fmt to render a comma in builtin calls |
| 5285 | 5495 | _ = try appendToken(c, .Comma, ","); |
| 5286 | 5496 | |
| 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 | 5508 | ptr.rhs = node; |
| 5289 | 5509 | return &ptr.base; |
| 5290 | 5510 | } else { |
| 5291 | 5511 | // expr * expr |
| 5292 | 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 | 5514 | const mul_node = try c.a().create(ast.Node.InfixOp); |
| 5295 | 5515 | mul_node.* = .{ |
| 5296 | 5516 | .op_token = op_token, |
| ... | ... | @@ -5301,9 +5521,9 @@ fn parseCSuffixOpExpr(c: *Context, it: *ctok.TokenList.Iterator, source_loc: Zig |
| 5301 | 5521 | node = &mul_node.base; |
| 5302 | 5522 | } |
| 5303 | 5523 | }, |
| 5304 | .Shl => { | |
| 5524 | .AngleBracketAngleBracketLeft => { | |
| 5305 | 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 | 5527 | const bitshift_node = try c.a().create(ast.Node.InfixOp); |
| 5308 | 5528 | bitshift_node.* = .{ |
| 5309 | 5529 | .op_token = op_token, |
| ... | ... | @@ -5313,9 +5533,9 @@ fn parseCSuffixOpExpr(c: *Context, it: *ctok.TokenList.Iterator, source_loc: Zig |
| 5313 | 5533 | }; |
| 5314 | 5534 | node = &bitshift_node.base; |
| 5315 | 5535 | }, |
| 5316 | .Shr => { | |
| 5536 | .AngleBracketAngleBracketRight => { | |
| 5317 | 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 | 5539 | const bitshift_node = try c.a().create(ast.Node.InfixOp); |
| 5320 | 5540 | bitshift_node.* = .{ |
| 5321 | 5541 | .op_token = op_token, |
| ... | ... | @@ -5327,7 +5547,7 @@ fn parseCSuffixOpExpr(c: *Context, it: *ctok.TokenList.Iterator, source_loc: Zig |
| 5327 | 5547 | }, |
| 5328 | 5548 | .Pipe => { |
| 5329 | 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 | 5551 | const or_node = try c.a().create(ast.Node.InfixOp); |
| 5332 | 5552 | or_node.* = .{ |
| 5333 | 5553 | .op_token = op_token, |
| ... | ... | @@ -5339,7 +5559,7 @@ fn parseCSuffixOpExpr(c: *Context, it: *ctok.TokenList.Iterator, source_loc: Zig |
| 5339 | 5559 | }, |
| 5340 | 5560 | .Ampersand => { |
| 5341 | 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 | 5563 | const bitand_node = try c.a().create(ast.Node.InfixOp); |
| 5344 | 5564 | bitand_node.* = .{ |
| 5345 | 5565 | .op_token = op_token, |
| ... | ... | @@ -5351,7 +5571,7 @@ fn parseCSuffixOpExpr(c: *Context, it: *ctok.TokenList.Iterator, source_loc: Zig |
| 5351 | 5571 | }, |
| 5352 | 5572 | .Plus => { |
| 5353 | 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 | 5575 | const add_node = try c.a().create(ast.Node.InfixOp); |
| 5356 | 5576 | add_node.* = .{ |
| 5357 | 5577 | .op_token = op_token, |
| ... | ... | @@ -5363,7 +5583,7 @@ fn parseCSuffixOpExpr(c: *Context, it: *ctok.TokenList.Iterator, source_loc: Zig |
| 5363 | 5583 | }, |
| 5364 | 5584 | .Minus => { |
| 5365 | 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 | 5587 | const sub_node = try c.a().create(ast.Node.InfixOp); |
| 5368 | 5588 | sub_node.* = .{ |
| 5369 | 5589 | .op_token = op_token, |
| ... | ... | @@ -5373,9 +5593,9 @@ fn parseCSuffixOpExpr(c: *Context, it: *ctok.TokenList.Iterator, source_loc: Zig |
| 5373 | 5593 | }; |
| 5374 | 5594 | node = &sub_node.base; |
| 5375 | 5595 | }, |
| 5376 | .And => { | |
| 5596 | .AmpersandAmpersand => { | |
| 5377 | 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 | 5599 | const and_node = try c.a().create(ast.Node.InfixOp); |
| 5380 | 5600 | and_node.* = .{ |
| 5381 | 5601 | .op_token = op_token, |
| ... | ... | @@ -5385,9 +5605,9 @@ fn parseCSuffixOpExpr(c: *Context, it: *ctok.TokenList.Iterator, source_loc: Zig |
| 5385 | 5605 | }; |
| 5386 | 5606 | node = &and_node.base; |
| 5387 | 5607 | }, |
| 5388 | .Or => { | |
| 5608 | .PipePipe => { | |
| 5389 | 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 | 5611 | const or_node = try c.a().create(ast.Node.InfixOp); |
| 5392 | 5612 | or_node.* = .{ |
| 5393 | 5613 | .op_token = op_token, |
| ... | ... | @@ -5397,9 +5617,9 @@ fn parseCSuffixOpExpr(c: *Context, it: *ctok.TokenList.Iterator, source_loc: Zig |
| 5397 | 5617 | }; |
| 5398 | 5618 | node = &or_node.base; |
| 5399 | 5619 | }, |
| 5400 | .Gt => { | |
| 5620 | .AngleBracketRight => { | |
| 5401 | 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 | 5623 | const and_node = try c.a().create(ast.Node.InfixOp); |
| 5404 | 5624 | and_node.* = .{ |
| 5405 | 5625 | .op_token = op_token, |
| ... | ... | @@ -5409,9 +5629,9 @@ fn parseCSuffixOpExpr(c: *Context, it: *ctok.TokenList.Iterator, source_loc: Zig |
| 5409 | 5629 | }; |
| 5410 | 5630 | node = &and_node.base; |
| 5411 | 5631 | }, |
| 5412 | .Gte => { | |
| 5632 | .AngleBracketRightEqual => { | |
| 5413 | 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 | 5635 | const and_node = try c.a().create(ast.Node.InfixOp); |
| 5416 | 5636 | and_node.* = .{ |
| 5417 | 5637 | .op_token = op_token, |
| ... | ... | @@ -5421,9 +5641,9 @@ fn parseCSuffixOpExpr(c: *Context, it: *ctok.TokenList.Iterator, source_loc: Zig |
| 5421 | 5641 | }; |
| 5422 | 5642 | node = &and_node.base; |
| 5423 | 5643 | }, |
| 5424 | .Lt => { | |
| 5644 | .AngleBracketLeft => { | |
| 5425 | 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 | 5647 | const and_node = try c.a().create(ast.Node.InfixOp); |
| 5428 | 5648 | and_node.* = .{ |
| 5429 | 5649 | .op_token = op_token, |
| ... | ... | @@ -5433,9 +5653,9 @@ fn parseCSuffixOpExpr(c: *Context, it: *ctok.TokenList.Iterator, source_loc: Zig |
| 5433 | 5653 | }; |
| 5434 | 5654 | node = &and_node.base; |
| 5435 | 5655 | }, |
| 5436 | .Lte => { | |
| 5656 | .AngleBracketLeftEqual => { | |
| 5437 | 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 | 5659 | const and_node = try c.a().create(ast.Node.InfixOp); |
| 5440 | 5660 | and_node.* = .{ |
| 5441 | 5661 | .op_token = op_token, |
| ... | ... | @@ -5445,16 +5665,17 @@ fn parseCSuffixOpExpr(c: *Context, it: *ctok.TokenList.Iterator, source_loc: Zig |
| 5445 | 5665 | }; |
| 5446 | 5666 | node = &and_node.base; |
| 5447 | 5667 | }, |
| 5448 | .LBrace => { | |
| 5668 | .LBracket => { | |
| 5449 | 5669 | const arr_node = try transCreateNodeArrayAccess(c, node); |
| 5450 | arr_node.op.ArrayAccess = try parseCPrefixOpExpr(c, it, source_loc, scope); | |
| 5451 | arr_node.rtoken = try appendToken(c, .RBrace, "]"); | |
| 5670 | arr_node.op.ArrayAccess = try parseCPrefixOpExpr(c, it, source, source_loc, scope); | |
| 5671 | arr_node.rtoken = try appendToken(c, .RBracket, "]"); | |
| 5452 | 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 | 5675 | try failDecl( |
| 5455 | 5676 | c, |
| 5456 | 5677 | source_loc, |
| 5457 | it.list.at(0).*.bytes, | |
| 5678 | source[first_tok.start..first_tok.end], | |
| 5458 | 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 | 5685 | .LParen => { |
| 5465 | 5686 | const call_node = try transCreateNodeFnCall(c, node); |
| 5466 | 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 | 5689 | try call_node.op.Call.params.push(arg); |
| 5469 | 5690 | const next = it.next().?; |
| 5470 | 5691 | if (next.id == .Comma) |
| ... | ... | @@ -5472,10 +5693,11 @@ fn parseCSuffixOpExpr(c: *Context, it: *ctok.TokenList.Iterator, source_loc: Zig |
| 5472 | 5693 | else if (next.id == .RParen) |
| 5473 | 5694 | break |
| 5474 | 5695 | else { |
| 5696 | const first_tok = it.list.at(0); | |
| 5475 | 5697 | try failDecl( |
| 5476 | 5698 | c, |
| 5477 | 5699 | source_loc, |
| 5478 | it.list.at(0).*.bytes, | |
| 5700 | source[first_tok.start..first_tok.end], | |
| 5479 | 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 | 5715 | } |
| 5494 | 5716 | } |
| 5495 | 5717 | |
| 5496 | fn parseCPrefixOpExpr(c: *Context, it: *ctok.TokenList.Iterator, source_loc: ZigClangSourceLocation, scope: *Scope) ParseError!*ast.Node { | |
| 5718 | fn parseCPrefixOpExpr(c: *Context, it: *CTokenList.Iterator, source: []const u8, source_loc: ZigClangSourceLocation, scope: *Scope) ParseError!*ast.Node { | |
| 5497 | 5719 | const op_tok = it.next().?; |
| 5498 | 5720 | |
| 5499 | 5721 | switch (op_tok.id) { |
| 5500 | 5722 | .Bang => { |
| 5501 | 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 | 5725 | return &node.base; |
| 5504 | 5726 | }, |
| 5505 | 5727 | .Minus => { |
| 5506 | 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 | 5730 | return &node.base; |
| 5509 | 5731 | }, |
| 5510 | 5732 | .Tilde => { |
| 5511 | 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 | 5735 | return &node.base; |
| 5514 | 5736 | }, |
| 5515 | 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 | 5739 | return try transCreateNodePtrDeref(c, prefix_op_expr); |
| 5518 | 5740 | }, |
| 5519 | 5741 | else => { |
| 5520 | 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 | 2174 | bool is_big_endian; |
| 2175 | 2175 | bool have_c_main; |
| 2176 | 2176 | bool have_winmain; |
| 2177 | bool have_wwinmain; | |
| 2177 | 2178 | bool have_winmain_crt_startup; |
| 2179 | bool have_wwinmain_crt_startup; | |
| 2178 | 2180 | bool have_dllmain_crt_startup; |
| 2179 | 2181 | bool have_err_ret_tracing; |
| 2180 | 2182 | bool link_eh_frame_hdr; |
| ... | ... | @@ -2243,6 +2245,7 @@ struct CodeGen { |
| 2243 | 2245 | bool enable_dump_analysis; |
| 2244 | 2246 | bool enable_doc_generation; |
| 2245 | 2247 | bool disable_bin_generation; |
| 2248 | bool test_is_evented; | |
| 2246 | 2249 | CodeModel code_model; |
| 2247 | 2250 | |
| 2248 | 2251 | Buf *mmacosx_version_min; |
| ... | ... | @@ -2488,6 +2491,9 @@ struct ScopeExpr { |
| 2488 | 2491 | size_t children_len; |
| 2489 | 2492 | |
| 2490 | 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 | }; |
| 2492 | 2498 | |
| 2493 | 2499 | // 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 | 3419 | } else if (cc == CallingConventionStdcall && g->zig_target->os == OsWindows) { |
| 3420 | 3420 | if (strcmp(symbol_name, "WinMain") == 0) { |
| 3421 | 3421 | g->have_winmain = true; |
| 3422 | } else if (strcmp(symbol_name, "wWinMain") == 0) { | |
| 3423 | g->have_wwinmain = true; | |
| 3422 | 3424 | } else if (strcmp(symbol_name, "WinMainCRTStartup") == 0) { |
| 3423 | 3425 | g->have_winmain_crt_startup = true; |
| 3426 | } else if (strcmp(symbol_name, "wWinMainCRTStartup") == 0) { | |
| 3427 | g->have_wwinmain_crt_startup = true; | |
| 3424 | 3428 | } else if (strcmp(symbol_name, "DllMainCRTStartup") == 0) { |
| 3425 | 3429 | g->have_dllmain_crt_startup = true; |
| 3426 | 3430 | } |
| ... | ... | @@ -6104,11 +6108,14 @@ static void mark_suspension_point(Scope *scope) { |
| 6104 | 6108 | continue; |
| 6105 | 6109 | } |
| 6106 | 6110 | case ScopeIdExpr: { |
| 6111 | ScopeExpr *parent_expr_scope = reinterpret_cast<ScopeExpr *>(scope); | |
| 6107 | 6112 | if (!looking_for_exprs) { |
| 6113 | if (parent_expr_scope->spill_harder) { | |
| 6114 | parent_expr_scope->need_spill = MemoizedBoolTrue; | |
| 6115 | } | |
| 6108 | 6116 | // Now we're only looking for a block, to see if it's in a loop (see the case ScopeIdBlock) |
| 6109 | 6117 | continue; |
| 6110 | 6118 | } |
| 6111 | ScopeExpr *parent_expr_scope = reinterpret_cast<ScopeExpr *>(scope); | |
| 6112 | 6119 | if (child_expr_scope != nullptr) { |
| 6113 | 6120 | for (size_t i = 0; parent_expr_scope->children_ptr[i] != child_expr_scope; i += 1) { |
| 6114 | 6121 | assert(i < parent_expr_scope->children_len); |
| ... | ... | @@ -6144,6 +6151,15 @@ static bool scope_needs_spill(Scope *scope) { |
| 6144 | 6151 | zig_unreachable(); |
| 6145 | 6152 | } |
| 6146 | 6153 | |
| 6154 | static ZigType *resolve_type_isf(ZigType *ty) { | |
| 6155 | if (ty->id != ZigTypeIdPointer) return ty; | |
| 6156 | InferredStructField *isf = ty->data.pointer.inferred_struct_field; | |
| 6157 | if (isf == nullptr) return ty; | |
| 6158 | TypeStructField *field = find_struct_type_field(isf->inferred_struct_type, isf->field_name); | |
| 6159 | assert(field != nullptr); | |
| 6160 | return field->type_entry; | |
| 6161 | } | |
| 6162 | ||
| 6147 | 6163 | static Error resolve_async_frame(CodeGen *g, ZigType *frame_type) { |
| 6148 | 6164 | Error err; |
| 6149 | 6165 | |
| ... | ... | @@ -6245,6 +6261,9 @@ static Error resolve_async_frame(CodeGen *g, ZigType *frame_type) { |
| 6245 | 6261 | } |
| 6246 | 6262 | ZigFn *callee = call->fn_entry; |
| 6247 | 6263 | if (callee == nullptr) { |
| 6264 | if (call->fn_ref->value->type->data.fn.fn_type_id.cc != CallingConventionAsync) { | |
| 6265 | continue; | |
| 6266 | } | |
| 6248 | 6267 | add_node_error(g, call->base.base.source_node, |
| 6249 | 6268 | buf_sprintf("function is not comptime-known; @asyncCall required")); |
| 6250 | 6269 | return ErrorSemanticAnalyzeFail; |
| ... | ... | @@ -6352,11 +6371,19 @@ static Error resolve_async_frame(CodeGen *g, ZigType *frame_type) { |
| 6352 | 6371 | IrInstGen *instruction = block->instruction_list.at(instr_i); |
| 6353 | 6372 | if (instruction->id == IrInstGenIdAwait || |
| 6354 | 6373 | instruction->id == IrInstGenIdVarPtr || |
| 6355 | instruction->id == IrInstGenIdAlloca) | |
| 6374 | instruction->id == IrInstGenIdAlloca || | |
| 6375 | instruction->id == IrInstGenIdSpillBegin || | |
| 6376 | instruction->id == IrInstGenIdSpillEnd) | |
| 6356 | 6377 | { |
| 6357 | 6378 | // This instruction does its own spilling specially, or otherwise doesn't need it. |
| 6358 | 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 | 6387 | if (instruction->value->special != ConstValSpecialRuntime) |
| 6361 | 6388 | continue; |
| 6362 | 6389 | if (instruction->base.ref_count == 0) |
| ... | ... | @@ -6402,7 +6429,7 @@ static Error resolve_async_frame(CodeGen *g, ZigType *frame_type) { |
| 6402 | 6429 | } else { |
| 6403 | 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 | 6433 | if ((err = type_resolve(g, param_type, ResolveStatusSizeKnown))) { |
| 6407 | 6434 | return err; |
| 6408 | 6435 | } |
| ... | ... | @@ -6421,7 +6448,7 @@ static Error resolve_async_frame(CodeGen *g, ZigType *frame_type) { |
| 6421 | 6448 | instruction->field_index = SIZE_MAX; |
| 6422 | 6449 | ZigType *ptr_type = instruction->base.value->type; |
| 6423 | 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 | 6452 | if (!type_has_bits(child_type)) |
| 6426 | 6453 | continue; |
| 6427 | 6454 | if (instruction->base.base.ref_count == 0) |
| ... | ... | @@ -6448,8 +6475,6 @@ static Error resolve_async_frame(CodeGen *g, ZigType *frame_type) { |
| 6448 | 6475 | } |
| 6449 | 6476 | instruction->field_index = fields.length; |
| 6450 | 6477 | |
| 6451 | src_assert(child_type->id != ZigTypeIdPointer || child_type->data.pointer.inferred_struct_field == nullptr, | |
| 6452 | instruction->base.base.source_node); | |
| 6453 | 6478 | fields.append({name, child_type, instruction->align}); |
| 6454 | 6479 | } |
| 6455 | 6480 | |
| ... | ... | @@ -8251,6 +8276,8 @@ static void resolve_llvm_types_struct(CodeGen *g, ZigType *struct_type, ResolveS |
| 8251 | 8276 | size_t debug_field_index = 0; |
| 8252 | 8277 | for (size_t i = 0; i < field_count; i += 1) { |
| 8253 | 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 | 8281 | size_t gen_field_index = field->gen_index; |
| 8255 | 8282 | if (gen_field_index == SIZE_MAX) { |
| 8256 | 8283 | continue; |
src/codegen.cpp+149-56| ... | ... | @@ -343,33 +343,67 @@ static LLVMLinkage to_llvm_linkage(GlobalLinkageId id) { |
| 343 | 343 | zig_unreachable(); |
| 344 | 344 | } |
| 345 | 345 | |
| 346 | struct CalcLLVMFieldIndex { | |
| 347 | uint32_t offset; | |
| 348 | uint32_t field_index; | |
| 349 | }; | |
| 350 | ||
| 351 | static void calc_llvm_field_index_add(CodeGen *g, CalcLLVMFieldIndex *calc, ZigType *ty) { | |
| 352 | if (!type_has_bits(ty)) return; | |
| 353 | uint32_t ty_align = get_abi_alignment(g, ty); | |
| 354 | if (calc->offset % ty_align != 0) { | |
| 355 | uint32_t llvm_align = LLVMABIAlignmentOfType(g->target_data_ref, get_llvm_type(g, ty)); | |
| 356 | if (llvm_align >= ty_align) { | |
| 357 | ty_align = llvm_align; // llvm's padding is sufficient | |
| 358 | } else if (calc->offset) { | |
| 359 | calc->field_index += 1; // zig will insert an extra padding field here | |
| 360 | } | |
| 361 | calc->offset += ty_align - (calc->offset % ty_align); // padding bytes | |
| 362 | } | |
| 363 | calc->offset += ty->abi_size; | |
| 364 | calc->field_index += 1; | |
| 365 | } | |
| 366 | ||
| 346 | 367 | // label (grep this): [fn_frame_struct_layout] |
| 368 | static void frame_index_trace_arg_calc(CodeGen *g, CalcLLVMFieldIndex *calc, ZigType *return_type) { | |
| 369 | calc_llvm_field_index_add(g, calc, g->builtin_types.entry_usize); // function pointer | |
| 370 | calc_llvm_field_index_add(g, calc, g->builtin_types.entry_usize); // resume index | |
| 371 | calc_llvm_field_index_add(g, calc, g->builtin_types.entry_usize); // awaiter index | |
| 372 | ||
| 373 | if (type_has_bits(return_type)) { | |
| 374 | calc_llvm_field_index_add(g, calc, g->builtin_types.entry_usize); // *ReturnType (callee's) | |
| 375 | calc_llvm_field_index_add(g, calc, g->builtin_types.entry_usize); // *ReturnType (awaiter's) | |
| 376 | calc_llvm_field_index_add(g, calc, return_type); // ReturnType | |
| 377 | } | |
| 378 | } | |
| 379 | ||
| 347 | 380 | static uint32_t frame_index_trace_arg(CodeGen *g, ZigType *return_type) { |
| 348 | // [0] *ReturnType (callee's) | |
| 349 | // [1] *ReturnType (awaiter's) | |
| 350 | // [2] ReturnType | |
| 351 | uint32_t return_field_count = type_has_bits(return_type) ? 3 : 0; | |
| 352 | return frame_ret_start + return_field_count; | |
| 381 | CalcLLVMFieldIndex calc = {0}; | |
| 382 | frame_index_trace_arg_calc(g, &calc, return_type); | |
| 383 | return calc.field_index; | |
| 353 | 384 | } |
| 354 | 385 | |
| 355 | 386 | // label (grep this): [fn_frame_struct_layout] |
| 356 | static uint32_t frame_index_arg(CodeGen *g, ZigType *return_type) { | |
| 357 | bool have_stack_trace = codegen_fn_has_err_ret_tracing_arg(g, return_type); | |
| 358 | // [0] *StackTrace (callee's) | |
| 359 | // [1] *StackTrace (awaiter's) | |
| 360 | uint32_t trace_field_count = have_stack_trace ? 2 : 0; | |
| 361 | return frame_index_trace_arg(g, return_type) + trace_field_count; | |
| 387 | static void frame_index_arg_calc(CodeGen *g, CalcLLVMFieldIndex *calc, ZigType *return_type) { | |
| 388 | frame_index_trace_arg_calc(g, calc, return_type); | |
| 389 | ||
| 390 | if (codegen_fn_has_err_ret_tracing_arg(g, return_type)) { | |
| 391 | calc_llvm_field_index_add(g, calc, g->builtin_types.entry_usize); // *StackTrace (callee's) | |
| 392 | calc_llvm_field_index_add(g, calc, g->builtin_types.entry_usize); // *StackTrace (awaiter's) | |
| 393 | } | |
| 362 | 394 | } |
| 363 | 395 | |
| 364 | 396 | // label (grep this): [fn_frame_struct_layout] |
| 365 | static uint32_t frame_index_trace_stack(CodeGen *g, FnTypeId *fn_type_id) { | |
| 366 | uint32_t result = frame_index_arg(g, fn_type_id->return_type); | |
| 367 | for (size_t i = 0; i < fn_type_id->param_count; i += 1) { | |
| 368 | if (type_has_bits(fn_type_id->param_info->type)) { | |
| 369 | result += 1; | |
| 370 | } | |
| 397 | static uint32_t frame_index_trace_stack(CodeGen *g, ZigFn *fn) { | |
| 398 | size_t field_index = 6; | |
| 399 | bool have_stack_trace = codegen_fn_has_err_ret_tracing_arg(g, fn->type_entry->data.fn.fn_type_id.return_type); | |
| 400 | if (have_stack_trace) { | |
| 401 | field_index += 2; | |
| 371 | 402 | } |
| 372 | return result; | |
| 403 | field_index += fn->type_entry->data.fn.fn_type_id.param_count; | |
| 404 | ZigType *locals_struct = fn->frame_type->data.frame.locals_struct; | |
| 405 | TypeStructField *field = locals_struct->data.structure.fields[field_index]; | |
| 406 | return field->gen_index; | |
| 373 | 407 | } |
| 374 | 408 | |
| 375 | 409 | |
| ... | ... | @@ -2523,7 +2557,12 @@ static LLVMValueRef ir_render_return(CodeGen *g, IrExecutableGen *executable, Ir |
| 2523 | 2557 | LLVMBuildRet(g->builder, by_val_value); |
| 2524 | 2558 | } |
| 2525 | 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 | 2566 | } else { |
| 2528 | 2567 | LLVMValueRef value = ir_llvm_value(g, instruction->operand); |
| 2529 | 2568 | LLVMBuildRet(g->builder, value); |
| ... | ... | @@ -3916,7 +3955,9 @@ static void set_call_instr_sret(CodeGen *g, LLVMValueRef call_instr) { |
| 3916 | 3955 | static void render_async_spills(CodeGen *g) { |
| 3917 | 3956 | ZigType *fn_type = g->cur_fn->type_entry; |
| 3918 | 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 | 3961 | for (size_t var_i = 0; var_i < g->cur_fn->variable_list.length; var_i += 1) { |
| 3921 | 3962 | ZigVar *var = g->cur_fn->variable_list.at(var_i); |
| 3922 | 3963 | |
| ... | ... | @@ -3937,8 +3978,8 @@ static void render_async_spills(CodeGen *g) { |
| 3937 | 3978 | continue; |
| 3938 | 3979 | } |
| 3939 | 3980 | |
| 3940 | var->value_ref = LLVMBuildStructGEP(g->builder, g->cur_frame_ptr, async_var_index, var->name); | |
| 3941 | async_var_index += 1; | |
| 3981 | calc_llvm_field_index_add(g, &arg_calc, var->var_type); | |
| 3982 | var->value_ref = LLVMBuildStructGEP(g->builder, g->cur_frame_ptr, arg_calc.field_index - 1, var->name); | |
| 3942 | 3983 | if (var->decl_node) { |
| 3943 | 3984 | var->di_loc_var = ZigLLVMCreateAutoVariable(g->dbuilder, get_di_scope(g, var->parent_scope), |
| 3944 | 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 | 4060 | } |
| 4020 | 4061 | |
| 4021 | 4062 | static LLVMValueRef ir_render_call(CodeGen *g, IrExecutableGen *executable, IrInstGenCall *instruction) { |
| 4063 | Error err; | |
| 4064 | ||
| 4022 | 4065 | LLVMTypeRef usize_type_ref = g->builtin_types.entry_usize->llvm_type; |
| 4023 | 4066 | |
| 4024 | 4067 | LLVMValueRef fn_val; |
| ... | ... | @@ -4049,6 +4092,8 @@ static LLVMValueRef ir_render_call(CodeGen *g, IrExecutableGen *executable, IrIn |
| 4049 | 4092 | ZigList<ZigType *> gen_param_types = {}; |
| 4050 | 4093 | LLVMValueRef result_loc = instruction->result_loc ? ir_llvm_value(g, instruction->result_loc) : nullptr; |
| 4051 | 4094 | LLVMValueRef zero = LLVMConstNull(usize_type_ref); |
| 4095 | bool need_frame_ptr_ptr_spill = false; | |
| 4096 | ZigType *anyframe_type = nullptr; | |
| 4052 | 4097 | LLVMValueRef frame_result_loc_uncasted = nullptr; |
| 4053 | 4098 | LLVMValueRef frame_result_loc; |
| 4054 | 4099 | LLVMValueRef awaiter_init_val; |
| ... | ... | @@ -4087,14 +4132,17 @@ static LLVMValueRef ir_render_call(CodeGen *g, IrExecutableGen *executable, IrIn |
| 4087 | 4132 | |
| 4088 | 4133 | LLVMPositionBuilderAtEnd(g->builder, ok_block); |
| 4089 | 4134 | } |
| 4135 | need_frame_ptr_ptr_spill = true; | |
| 4090 | 4136 | LLVMValueRef frame_ptr_ptr = LLVMBuildStructGEP(g->builder, frame_slice_ptr, slice_ptr_index, ""); |
| 4091 | 4137 | LLVMValueRef frame_ptr = LLVMBuildLoad(g->builder, frame_ptr_ptr, ""); |
| 4092 | 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 | 4140 | frame_result_loc = LLVMBuildBitCast(g->builder, frame_ptr, get_llvm_type(g, anyframe_type), ""); |
| 4095 | 4141 | } else { |
| 4096 | ZigType *ptr_frame_type = get_pointer_to_type(g, | |
| 4097 | get_fn_frame_type(g, instruction->fn_entry), false); | |
| 4142 | ZigType *frame_type = get_fn_frame_type(g, instruction->fn_entry); | |
| 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 | 4146 | frame_result_loc = LLVMBuildBitCast(g->builder, frame_ptr, |
| 4099 | 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 | 4309 | LLVMValueRef result; |
| 4262 | 4310 | |
| 4263 | 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); | |
| 4265 | 4314 | |
| 4266 | 4315 | LLVMValueRef casted_frame; |
| 4267 | 4316 | if (instruction->new_stack != nullptr && instruction->fn_entry == nullptr) { |
| 4268 | 4317 | // 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 | 4326 | LLVMTypeRef *field_types = allocate_nonzero<LLVMTypeRef>(field_count); |
| 4271 | 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 | 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 | 4342 | LLVMTypeRef frame_with_args_type = LLVMStructType(field_types, field_count, false); |
| 4277 | 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 | 4347 | casted_frame = frame_result_loc; |
| 4282 | 4348 | } |
| 4283 | 4349 | |
| 4350 | CalcLLVMFieldIndex arg_calc = arg_calc_start; | |
| 4284 | 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 | 4354 | gen_assign_raw(g, arg_ptr, get_pointer_to_type(g, gen_param_types.at(arg_i), true), |
| 4287 | 4355 | gen_param_values.at(arg_i)); |
| 4288 | 4356 | } |
| ... | ... | @@ -4345,11 +4413,19 @@ static LLVMValueRef ir_render_call(CodeGen *g, IrExecutableGen *executable, IrIn |
| 4345 | 4413 | } |
| 4346 | 4414 | } |
| 4347 | 4415 | |
| 4348 | if (frame_result_loc_uncasted != nullptr && instruction->fn_entry != nullptr) { | |
| 4349 | // Instead of a spill, we do the bitcast again. The uncasted LLVM IR instruction will | |
| 4350 | // be an Alloca from the entry block, so it does not need to be spilled. | |
| 4351 | frame_result_loc = LLVMBuildBitCast(g->builder, frame_result_loc_uncasted, | |
| 4352 | LLVMPointerType(get_llvm_type(g, instruction->fn_entry->frame_type), 0), ""); | |
| 4416 | if (need_frame_ptr_ptr_spill) { | |
| 4417 | LLVMValueRef frame_slice_ptr = ir_llvm_value(g, instruction->new_stack); | |
| 4418 | LLVMValueRef frame_ptr_ptr = LLVMBuildStructGEP(g->builder, frame_slice_ptr, slice_ptr_index, ""); | |
| 4419 | frame_result_loc_uncasted = LLVMBuildLoad(g->builder, frame_ptr_ptr, ""); | |
| 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 | } |
| 4354 | 4430 | |
| 4355 | 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 | 5715 | bool want_safety = instruction->safety_check_on && ir_want_runtime_safety(g, &instruction->base) && |
| 5640 | 5716 | g->errors_by_index.length > 1; |
| 5641 | 5717 | |
| 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 | 5718 | ZigType *ptr_type = instruction->value->value->type; |
| 5650 | 5719 | assert(ptr_type->id == ZigTypeIdPointer); |
| 5651 | 5720 | ZigType *err_union_type = ptr_type->data.pointer.child_type; |
| 5652 | 5721 | ZigType *payload_type = err_union_type->data.error_union.payload_type; |
| 5653 | 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 | 5736 | LLVMValueRef err_union_handle = get_handle_value(g, err_union_ptr, err_union_type, ptr_type); |
| 5655 | 5737 | |
| 5656 | 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 | 5747 | } else { |
| 5666 | 5748 | err_val = err_union_handle; |
| 5667 | 5749 | } |
| 5668 | LLVMValueRef zero = LLVMConstNull(get_llvm_type(g, g->err_tag_type)); | |
| 5669 | 5750 | LLVMValueRef cond_val = LLVMBuildICmp(g->builder, LLVMIntEQ, err_val, zero, ""); |
| 5670 | 5751 | LLVMBasicBlockRef err_block = LLVMAppendBasicBlock(g->cur_fn_val, "UnwrapErrError"); |
| 5671 | 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 | 5766 | } |
| 5686 | 5767 | return LLVMBuildStructGEP(g->builder, err_union_handle, err_union_payload_index, ""); |
| 5687 | 5768 | } else { |
| 5769 | if (instruction->initializing) { | |
| 5770 | gen_store_untyped(g, zero, err_union_ptr, 0, false); | |
| 5771 | } | |
| 5688 | 5772 | return nullptr; |
| 5689 | 5773 | } |
| 5690 | 5774 | } |
| ... | ... | @@ -7737,7 +7821,7 @@ static void do_code_gen(CodeGen *g) { |
| 7737 | 7821 | } |
| 7738 | 7822 | uint32_t trace_field_index_stack = UINT32_MAX; |
| 7739 | 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 | 7825 | g->cur_err_ret_trace_val_stack = LLVMBuildStructGEP(g->builder, g->cur_frame_ptr, |
| 7742 | 7826 | trace_field_index_stack, ""); |
| 7743 | 7827 | } |
| ... | ... | @@ -8334,9 +8418,9 @@ TargetSubsystem detect_subsystem(CodeGen *g) { |
| 8334 | 8418 | if (g->zig_target->os == OsWindows) { |
| 8335 | 8419 | if (g->have_dllmain_crt_startup || (g->out_type == OutTypeLib && g->is_dynamic)) |
| 8336 | 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 | 8422 | return TargetSubsystemConsole; |
| 8339 | if (g->have_winmain) | |
| 8423 | if (g->have_winmain || g->have_wwinmain) | |
| 8340 | 8424 | return TargetSubsystemWindows; |
| 8341 | 8425 | } else if (g->zig_target->os == OsUefi) { |
| 8342 | 8426 | return TargetSubsystemEfiApplication; |
| ... | ... | @@ -8596,6 +8680,9 @@ Buf *codegen_generate_builtin_source(CodeGen *g) { |
| 8596 | 8680 | buf_appendf(contents, |
| 8597 | 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 | } |
| 8600 | 8687 | |
| 8601 | 8688 | return contents; |
| ... | ... | @@ -8629,6 +8716,7 @@ static Error define_builtin_compile_vars(CodeGen *g) { |
| 8629 | 8716 | cache_bool(&cache_hash, g->is_dynamic); |
| 8630 | 8717 | cache_bool(&cache_hash, g->is_test_build); |
| 8631 | 8718 | cache_bool(&cache_hash, g->is_single_threaded); |
| 8719 | cache_bool(&cache_hash, g->test_is_evented); | |
| 8632 | 8720 | cache_int(&cache_hash, g->code_model); |
| 8633 | 8721 | cache_int(&cache_hash, g->zig_target->is_native); |
| 8634 | 8722 | cache_int(&cache_hash, g->zig_target->arch); |
| ... | ... | @@ -9386,22 +9474,13 @@ static void update_test_functions_builtin_decl(CodeGen *g) { |
| 9386 | 9474 | for (size_t i = 0; i < g->test_fns.length; i += 1) { |
| 9387 | 9475 | ZigFn *test_fn_entry = g->test_fns.at(i); |
| 9388 | 9476 | |
| 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 | 9477 | ZigValue *this_val = &test_fn_array->data.x_array.data.s_none.elements[i]; |
| 9399 | 9478 | this_val->special = ConstValSpecialStatic; |
| 9400 | 9479 | this_val->type = struct_type; |
| 9401 | 9480 | this_val->parent.id = ConstParentIdArray; |
| 9402 | 9481 | this_val->parent.data.p_array.array_val = test_fn_array; |
| 9403 | 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); | |
| 9405 | 9484 | |
| 9406 | 9485 | ZigValue *name_field = this_val->data.x_struct.fields[0]; |
| 9407 | 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 | 9492 | fn_field->data.x_ptr.special = ConstPtrSpecialFunction; |
| 9414 | 9493 | fn_field->data.x_ptr.mut = ConstPtrMutComptimeConst; |
| 9415 | 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 | 9509 | report_errors_and_maybe_exit(g); |
| 9418 | 9510 | |
| ... | ... | @@ -10344,6 +10436,7 @@ static Error check_cache(CodeGen *g, Buf *manifest_dir, Buf *digest) { |
| 10344 | 10436 | if (g->is_test_build) { |
| 10345 | 10437 | cache_buf_opt(ch, g->test_filter); |
| 10346 | 10438 | cache_buf_opt(ch, g->test_name_prefix); |
| 10439 | cache_bool(ch, g->test_is_evented); | |
| 10347 | 10440 | } |
| 10348 | 10441 | cache_bool(ch, g->link_eh_frame_hdr); |
| 10349 | 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 | 5252 | return irb->codegen->invalid_inst_src; |
| 5253 | 5253 | } else { |
| 5254 | 5254 | return_value = ir_build_const_void(irb, scope, node); |
| 5255 | ir_build_end_expr(irb, scope, node, return_value, &result_loc_ret->base); | |
| 5255 | 5256 | } |
| 5256 | 5257 | |
| 5257 | 5258 | ir_mark_gen(ir_build_add_implicit_return_type(irb, scope, node, return_value, result_loc_ret)); |
| ... | ... | @@ -5262,7 +5263,7 @@ static IrInstSrc *ir_gen_return(IrBuilderSrc *irb, Scope *scope, AstNode *node, |
| 5262 | 5263 | if (!have_err_defers && !irb->codegen->have_err_ret_tracing) { |
| 5263 | 5264 | // only generate unconditional defers |
| 5264 | 5265 | ir_gen_defers_for_block(irb, scope, outer_scope, false); |
| 5265 | IrInstSrc *result = ir_build_return_src(irb, scope, node, return_value); | |
| 5266 | IrInstSrc *result = ir_build_return_src(irb, scope, node, nullptr); | |
| 5266 | 5267 | result_loc_ret->base.source_instruction = result; |
| 5267 | 5268 | return result; |
| 5268 | 5269 | } |
| ... | ... | @@ -5271,10 +5272,6 @@ static IrInstSrc *ir_gen_return(IrBuilderSrc *irb, Scope *scope, AstNode *node, |
| 5271 | 5272 | IrBasicBlockSrc *err_block = ir_create_basic_block(irb, scope, "ErrRetErr"); |
| 5272 | 5273 | IrBasicBlockSrc *ok_block = ir_create_basic_block(irb, scope, "ErrRetOk"); |
| 5273 | 5274 | |
| 5274 | if (!have_err_defers) { | |
| 5275 | ir_gen_defers_for_block(irb, scope, outer_scope, false); | |
| 5276 | } | |
| 5277 | ||
| 5278 | 5275 | IrInstSrc *is_err = ir_build_test_err_src(irb, scope, node, return_value, false, true); |
| 5279 | 5276 | |
| 5280 | 5277 | IrInstSrc *is_comptime; |
| ... | ... | @@ -5288,22 +5285,18 @@ static IrInstSrc *ir_gen_return(IrBuilderSrc *irb, Scope *scope, AstNode *node, |
| 5288 | 5285 | IrBasicBlockSrc *ret_stmt_block = ir_create_basic_block(irb, scope, "RetStmt"); |
| 5289 | 5286 | |
| 5290 | 5287 | ir_set_cursor_at_end_and_append_block(irb, err_block); |
| 5291 | if (have_err_defers) { | |
| 5292 | ir_gen_defers_for_block(irb, scope, outer_scope, true); | |
| 5293 | } | |
| 5288 | ir_gen_defers_for_block(irb, scope, outer_scope, true); | |
| 5294 | 5289 | if (irb->codegen->have_err_ret_tracing && !should_inline) { |
| 5295 | 5290 | ir_build_save_err_ret_addr_src(irb, scope, node); |
| 5296 | 5291 | } |
| 5297 | 5292 | ir_build_br(irb, scope, node, ret_stmt_block, is_comptime); |
| 5298 | 5293 | |
| 5299 | 5294 | ir_set_cursor_at_end_and_append_block(irb, ok_block); |
| 5300 | if (have_err_defers) { | |
| 5301 | ir_gen_defers_for_block(irb, scope, outer_scope, false); | |
| 5302 | } | |
| 5295 | ir_gen_defers_for_block(irb, scope, outer_scope, false); | |
| 5303 | 5296 | ir_build_br(irb, scope, node, ret_stmt_block, is_comptime); |
| 5304 | 5297 | |
| 5305 | 5298 | ir_set_cursor_at_end_and_append_block(irb, ret_stmt_block); |
| 5306 | IrInstSrc *result = ir_build_return_src(irb, scope, node, return_value); | |
| 5299 | IrInstSrc *result = ir_build_return_src(irb, scope, node, nullptr); | |
| 5307 | 5300 | result_loc_ret->base.source_instruction = result; |
| 5308 | 5301 | return result; |
| 5309 | 5302 | } |
| ... | ... | @@ -8841,7 +8834,10 @@ static IrInstSrc *ir_gen_if_optional_expr(IrBuilderSrc *irb, Scope *scope, AstNo |
| 8841 | 8834 | AstNode *else_node = node->data.test_expr.else_node; |
| 8842 | 8835 | bool var_is_ptr = node->data.test_expr.var_is_ptr; |
| 8843 | 8836 | |
| 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 | 8841 | if (maybe_val_ptr == irb->codegen->invalid_inst_src) |
| 8846 | 8842 | return maybe_val_ptr; |
| 8847 | 8843 | |
| ... | ... | @@ -8866,7 +8862,7 @@ static IrInstSrc *ir_gen_if_optional_expr(IrBuilderSrc *irb, Scope *scope, AstNo |
| 8866 | 8862 | |
| 8867 | 8863 | ir_set_cursor_at_end_and_append_block(irb, then_block); |
| 8868 | 8864 | |
| 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 | 8866 | Scope *var_scope; |
| 8871 | 8867 | if (var_symbol) { |
| 8872 | 8868 | bool is_shadowable = false; |
| ... | ... | @@ -9586,7 +9582,10 @@ static IrInstSrc *ir_gen_catch(IrBuilderSrc *irb, Scope *parent_scope, AstNode * |
| 9586 | 9582 | } |
| 9587 | 9583 | |
| 9588 | 9584 | |
| 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 | 9589 | if (err_union_ptr == irb->codegen->invalid_inst_src) |
| 9591 | 9590 | return irb->codegen->invalid_inst_src; |
| 9592 | 9591 | |
| ... | ... | @@ -9608,7 +9607,7 @@ static IrInstSrc *ir_gen_catch(IrBuilderSrc *irb, Scope *parent_scope, AstNode * |
| 9608 | 9607 | is_comptime); |
| 9609 | 9608 | |
| 9610 | 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 | 9611 | Scope *err_scope; |
| 9613 | 9612 | if (var_node) { |
| 9614 | 9613 | assert(var_node->type == NodeTypeSymbol); |
| ... | ... | @@ -11831,7 +11830,7 @@ static ConstCastOnly types_match_const_cast_only(IrAnalyze *ira, ZigType *wanted |
| 11831 | 11830 | } |
| 11832 | 11831 | assert(wanted_type->data.fn.is_generic || |
| 11833 | 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 | 11834 | // note it's reversed for parameters |
| 11836 | 11835 | FnTypeParamInfo *actual_param_info = &actual_type->data.fn.fn_type_id.param_info[i]; |
| 11837 | 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 | 15460 | } |
| 15462 | 15461 | |
| 15463 | 15462 | static 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 | 15469 | IrInstGen *operand = instruction->operand->child; |
| 15465 | 15470 | if (type_is_invalid(operand->value->type)) |
| 15466 | 15471 | return ir_unreach_error(ira); |
| ... | ... | @@ -19553,6 +19558,12 @@ static IrInstGen *ir_analyze_fn_call(IrAnalyze *ira, IrInst* source_instr, |
| 19553 | 19558 | if (type_is_invalid(result_loc->value->type) || result_loc->value->type->id == ZigTypeIdUnreachable) { |
| 19554 | 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 | 19567 | ZigType *res_child_type = result_loc->value->type->data.pointer.child_type; |
| 19557 | 19568 | if (res_child_type == ira->codegen->builtin_types.entry_var) { |
| 19558 | 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 | 19696 | if (type_is_invalid(result_loc->value->type) || result_loc->value->type->id == ZigTypeIdUnreachable) { |
| 19686 | 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 | 19705 | ZigType *res_child_type = result_loc->value->type->data.pointer.child_type; |
| 19689 | 19706 | if (res_child_type == ira->codegen->builtin_types.entry_var) { |
| 19690 | 19707 | res_child_type = return_type; |
| ... | ... | @@ -29515,8 +29532,13 @@ static IrInstGen *ir_analyze_instruction_spill_begin(IrAnalyze *ira, IrInstSrcSp |
| 29515 | 29532 | if (!type_has_bits(operand->value->type)) |
| 29516 | 29533 | return ir_const_void(ira, &instruction->base.base); |
| 29517 | 29534 | |
| 29518 | ir_assert(instruction->spill_id == SpillIdRetErrCode, &instruction->base.base); | |
| 29519 | ira->new_irb.exec->need_err_code_spill = true; | |
| 29535 | switch (instruction->spill_id) { | |
| 29536 | case SpillIdInvalid: | |
| 29537 | zig_unreachable(); | |
| 29538 | case SpillIdRetErrCode: | |
| 29539 | ira->new_irb.exec->need_err_code_spill = true; | |
| 29540 | break; | |
| 29541 | } | |
| 29520 | 29542 | |
| 29521 | 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 | 29548 | if (type_is_invalid(operand->value->type)) |
| 29527 | 29549 | return ira->codegen->invalid_inst_gen; |
| 29528 | 29550 | |
| 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 | 29555 | return operand; |
| 29556 | } | |
| 29531 | 29557 | |
| 29532 | 29558 | ir_assert(instruction->begin->base.child->id == IrInstGenIdSpillBegin, &instruction->base.base); |
| 29533 | 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 | 30278 | if (param_is_var_args) { |
| 30253 | 30279 | if (fn_type_id.cc == CallingConventionC) { |
| 30254 | 30280 | fn_type_id.param_count = fn_type_id.next_param_index; |
| 30255 | continue; | |
| 30281 | break; | |
| 30256 | 30282 | } else if (fn_type_id.cc == CallingConventionUnspecified) { |
| 30257 | 30283 | return get_generic_fn_type(ira->codegen, &fn_type_id); |
| 30258 | 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 | 2210 | if (!is_library) { |
| 2211 | 2211 | if (lj->codegen->have_winmain) { |
| 2212 | 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 | 2217 | } else { |
| 2214 | 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 | 135 | " --test-name-prefix [text] add prefix to all tests\n" |
| 136 | 136 | " --test-cmd [arg] specify test execution command one arg at a time\n" |
| 137 | 137 | " --test-cmd-bin appends test binary path to test cmd args\n" |
| 138 | " --test-evented-io runs the test in evented I/O mode\n" | |
| 138 | 139 | , arg0); |
| 139 | 140 | return return_code; |
| 140 | 141 | } |
| ... | ... | @@ -429,6 +430,7 @@ int main(int argc, char **argv) { |
| 429 | 430 | ZigList<CFile *> c_source_files = {0}; |
| 430 | 431 | const char *test_filter = nullptr; |
| 431 | 432 | const char *test_name_prefix = nullptr; |
| 433 | bool test_evented_io = false; | |
| 432 | 434 | size_t ver_major = 0; |
| 433 | 435 | size_t ver_minor = 0; |
| 434 | 436 | size_t ver_patch = 0; |
| ... | ... | @@ -710,6 +712,8 @@ int main(int argc, char **argv) { |
| 710 | 712 | cur_pkg = cur_pkg->parent; |
| 711 | 713 | } else if (strcmp(arg, "-ffunction-sections") == 0) { |
| 712 | 714 | function_sections = true; |
| 715 | } else if (strcmp(arg, "--test-evented-io") == 0) { | |
| 716 | test_evented_io = true; | |
| 713 | 717 | } else if (i + 1 >= argc) { |
| 714 | 718 | fprintf(stderr, "Expected another argument after %s\n", arg); |
| 715 | 719 | return print_error_usage(arg0); |
| ... | ... | @@ -1060,6 +1064,7 @@ int main(int argc, char **argv) { |
| 1060 | 1064 | g->want_stack_check = want_stack_check; |
| 1061 | 1065 | g->want_sanitize_c = want_sanitize_c; |
| 1062 | 1066 | g->want_single_threaded = want_single_threaded; |
| 1067 | g->test_is_evented = test_evented_io; | |
| 1063 | 1068 | Buf *builtin_source = codegen_generate_builtin_source(g); |
| 1064 | 1069 | if (fwrite(buf_ptr(builtin_source), 1, buf_len(builtin_source), stdout) != buf_len(builtin_source)) { |
| 1065 | 1070 | fprintf(stderr, "unable to write to stdout: %s\n", strerror(ferror(stdout))); |
| ... | ... | @@ -1233,6 +1238,7 @@ int main(int argc, char **argv) { |
| 1233 | 1238 | if (test_filter) { |
| 1234 | 1239 | codegen_set_test_filter(g, buf_create_from_str(test_filter)); |
| 1235 | 1240 | } |
| 1241 | g->test_is_evented = test_evented_io; | |
| 1236 | 1242 | |
| 1237 | 1243 | if (test_name_prefix) { |
| 1238 | 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 | 806 | if (param_decl->data.param_decl.is_var_args) |
| 807 | 807 | res->data.fn_proto.is_var_args = true; |
| 808 | 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 | 811 | return res; |
| 812 | 812 | } |
test/compile_errors.zig+35-19| ... | ... | @@ -3,12 +3,47 @@ const builtin = @import("builtin"); |
| 3 | 3 | const Target = @import("std").Target; |
| 4 | 4 | |
| 5 | 5 | pub 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 | 17 | cases.addTest("dependency loop in top-level decl with @TypeInfo", |
| 7 | 18 | \\export const foo = @typeInfo(@This()); |
| 8 | 19 | , &[_][]const u8{ |
| 9 | 20 | "tmp.zig:1:20: error: dependency loop detected", |
| 10 | 21 | }); |
| 11 | 22 | |
| 23 | cases.add("function call assigned to incorrect type", | |
| 24 | \\export fn entry() void { | |
| 25 | \\ var arr: [4]f32 = undefined; | |
| 26 | \\ arr = concat(); | |
| 27 | \\} | |
| 28 | \\fn concat() [16]f32 { | |
| 29 | \\ return [1]f32{0}**16; | |
| 30 | \\} | |
| 31 | , &[_][]const u8{ | |
| 32 | "tmp.zig:3:17: error: expected type '[4]f32', found '[16]f32'", | |
| 33 | }); | |
| 34 | ||
| 35 | cases.add("generic function call assigned to incorrect type", | |
| 36 | \\pub export fn entry() void { | |
| 37 | \\ var res: []i32 = undefined; | |
| 38 | \\ res = myAlloc(i32); | |
| 39 | \\} | |
| 40 | \\fn myAlloc(comptime arg: type) anyerror!arg{ | |
| 41 | \\ unreachable; | |
| 42 | \\} | |
| 43 | , &[_][]const u8{ | |
| 44 | "tmp.zig:3:18: error: expected type '[]i32', found 'anyerror!i32", | |
| 45 | }); | |
| 46 | ||
| 12 | 47 | cases.addTest("non-exhaustive enums", |
| 13 | 48 | \\const A = enum { |
| 14 | 49 | \\ a, |
| ... | ... | @@ -5268,25 +5303,6 @@ pub fn addCases(cases: *tests.CompileErrorContext) void { |
| 5268 | 5303 | "tmp.zig:2:30: error: cannot set section of local variable 'foo'", |
| 5269 | 5304 | }); |
| 5270 | 5305 | |
| 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 | 5306 | cases.add("inner struct member shadowing outer struct member", |
| 5291 | 5307 | \\fn A() type { |
| 5292 | 5308 | \\ return struct { |
test/stage1/behavior/async_fn.zig+152| ... | ... | @@ -2,6 +2,7 @@ const std = @import("std"); |
| 2 | 2 | const builtin = @import("builtin"); |
| 3 | 3 | const expect = std.testing.expect; |
| 4 | 4 | const expectEqual = std.testing.expectEqual; |
| 5 | const expectError = std.testing.expectError; | |
| 5 | 6 | |
| 6 | 7 | var global_x: i32 = 1; |
| 7 | 8 | |
| ... | ... | @@ -1329,3 +1330,154 @@ test "async call with @call" { |
| 1329 | 1330 | }; |
| 1330 | 1331 | S.doTheTest(); |
| 1331 | 1332 | } |
| 1333 | ||
| 1334 | test "async function passed 0-bit arg after non-0-bit arg" { | |
| 1335 | const S = struct { | |
| 1336 | var global_frame: anyframe = undefined; | |
| 1337 | var global_int: i32 = 0; | |
| 1338 | ||
| 1339 | fn foo() void { | |
| 1340 | bar(1, .{}) catch unreachable; | |
| 1341 | } | |
| 1342 | ||
| 1343 | fn bar(x: i32, args: var) anyerror!void { | |
| 1344 | global_frame = @frame(); | |
| 1345 | suspend; | |
| 1346 | global_int = x; | |
| 1347 | } | |
| 1348 | }; | |
| 1349 | _ = async S.foo(); | |
| 1350 | resume S.global_frame; | |
| 1351 | expect(S.global_int == 1); | |
| 1352 | } | |
| 1353 | ||
| 1354 | test "async function passed align(16) arg after align(8) arg" { | |
| 1355 | const S = struct { | |
| 1356 | var global_frame: anyframe = undefined; | |
| 1357 | var global_int: u128 = 0; | |
| 1358 | ||
| 1359 | fn foo() void { | |
| 1360 | var a: u128 = 99; | |
| 1361 | bar(10, .{a}) catch unreachable; | |
| 1362 | } | |
| 1363 | ||
| 1364 | fn bar(x: u64, args: var) anyerror!void { | |
| 1365 | expect(x == 10); | |
| 1366 | global_frame = @frame(); | |
| 1367 | suspend; | |
| 1368 | global_int = args[0]; | |
| 1369 | } | |
| 1370 | }; | |
| 1371 | _ = async S.foo(); | |
| 1372 | resume S.global_frame; | |
| 1373 | expect(S.global_int == 99); | |
| 1374 | } | |
| 1375 | ||
| 1376 | test "async function call resolves target fn frame, comptime func" { | |
| 1377 | const S = struct { | |
| 1378 | var global_frame: anyframe = undefined; | |
| 1379 | var global_int: i32 = 9; | |
| 1380 | ||
| 1381 | fn foo() anyerror!void { | |
| 1382 | const stack_size = 1000; | |
| 1383 | var stack_frame: [stack_size]u8 align(std.Target.stack_align) = undefined; | |
| 1384 | return await @asyncCall(&stack_frame, {}, bar); | |
| 1385 | } | |
| 1386 | ||
| 1387 | fn bar() anyerror!void { | |
| 1388 | global_frame = @frame(); | |
| 1389 | suspend; | |
| 1390 | global_int += 1; | |
| 1391 | } | |
| 1392 | }; | |
| 1393 | _ = async S.foo(); | |
| 1394 | resume S.global_frame; | |
| 1395 | expect(S.global_int == 10); | |
| 1396 | } | |
| 1397 | ||
| 1398 | test "async function call resolves target fn frame, runtime func" { | |
| 1399 | const S = struct { | |
| 1400 | var global_frame: anyframe = undefined; | |
| 1401 | var global_int: i32 = 9; | |
| 1402 | ||
| 1403 | fn foo() anyerror!void { | |
| 1404 | const stack_size = 1000; | |
| 1405 | var stack_frame: [stack_size]u8 align(std.Target.stack_align) = undefined; | |
| 1406 | var func: async fn () anyerror!void = bar; | |
| 1407 | return await @asyncCall(&stack_frame, {}, func); | |
| 1408 | } | |
| 1409 | ||
| 1410 | fn bar() anyerror!void { | |
| 1411 | global_frame = @frame(); | |
| 1412 | suspend; | |
| 1413 | global_int += 1; | |
| 1414 | } | |
| 1415 | }; | |
| 1416 | _ = async S.foo(); | |
| 1417 | resume S.global_frame; | |
| 1418 | expect(S.global_int == 10); | |
| 1419 | } | |
| 1420 | ||
| 1421 | test "properly spill optional payload capture value" { | |
| 1422 | const S = struct { | |
| 1423 | var global_frame: anyframe = undefined; | |
| 1424 | var global_int: usize = 2; | |
| 1425 | ||
| 1426 | fn foo() void { | |
| 1427 | var opt: ?usize = 1234; | |
| 1428 | if (opt) |x| { | |
| 1429 | bar(); | |
| 1430 | global_int += x; | |
| 1431 | } | |
| 1432 | } | |
| 1433 | ||
| 1434 | fn bar() void { | |
| 1435 | global_frame = @frame(); | |
| 1436 | suspend; | |
| 1437 | global_int += 1; | |
| 1438 | } | |
| 1439 | }; | |
| 1440 | _ = async S.foo(); | |
| 1441 | resume S.global_frame; | |
| 1442 | expect(S.global_int == 1237); | |
| 1443 | } | |
| 1444 | ||
| 1445 | test "handle defer interfering with return value spill" { | |
| 1446 | const S = struct { | |
| 1447 | var global_frame1: anyframe = undefined; | |
| 1448 | var global_frame2: anyframe = undefined; | |
| 1449 | var finished = false; | |
| 1450 | var baz_happened = false; | |
| 1451 | ||
| 1452 | fn doTheTest() void { | |
| 1453 | _ = async testFoo(); | |
| 1454 | resume global_frame1; | |
| 1455 | resume global_frame2; | |
| 1456 | expect(baz_happened); | |
| 1457 | expect(finished); | |
| 1458 | } | |
| 1459 | ||
| 1460 | fn testFoo() void { | |
| 1461 | expectError(error.Bad, foo()); | |
| 1462 | finished = true; | |
| 1463 | } | |
| 1464 | ||
| 1465 | fn foo() anyerror!void { | |
| 1466 | defer baz(); | |
| 1467 | return bar() catch |err| return err; | |
| 1468 | } | |
| 1469 | ||
| 1470 | fn bar() anyerror!void { | |
| 1471 | global_frame1 = @frame(); | |
| 1472 | suspend; | |
| 1473 | return error.Bad; | |
| 1474 | } | |
| 1475 | ||
| 1476 | fn baz() void { | |
| 1477 | global_frame2 = @frame(); | |
| 1478 | suspend; | |
| 1479 | baz_happened = true; | |
| 1480 | } | |
| 1481 | }; | |
| 1482 | S.doTheTest(); | |
| 1483 | } |
test/translate_c.zig+24-2| ... | ... | @@ -618,6 +618,28 @@ pub fn addCases(cases: *tests.TranslateCContext) void { |
| 618 | 618 | }, |
| 619 | 619 | ); |
| 620 | 620 | |
| 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 | 643 | cases.add("null statements", |
| 622 | 644 | \\void foo(void) { |
| 623 | 645 | \\ ;;;;; |
| ... | ... | @@ -2508,8 +2530,8 @@ pub fn addCases(cases: *tests.TranslateCContext) void { |
| 2508 | 2530 | cases.add("macro cast", |
| 2509 | 2531 | \\#define FOO(bar) baz((void *)(baz)) |
| 2510 | 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))) { | |
| 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)); | |
| 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))) { | |
| 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 | }); |
| 2515 | 2537 |