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

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


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

CMakeLists.txt+13-1
......@@ -2,7 +2,19 @@ cmake_minimum_required(VERSION 2.8.5)
22
33if(NOT CMAKE_BUILD_TYPE)
44 set(CMAKE_BUILD_TYPE "Debug" CACHE STRING
5 "Choose the type of build, options are: Debug Release RelWithDebInfo MinSizeRel." FORCE)
5 "Choose the type of build, options are: None Debug Release RelWithDebInfo MinSizeRel." FORCE)
6endif()
7
8set(_list "None;Debug;Release;RelWithDebInfo;MinSizeRel")
9list(FIND _list ${CMAKE_BUILD_TYPE} _index)
10if(${_index} EQUAL -1)
11 string(REPLACE ";" ", " _list_pretty "${_list}")
12 message("::")
13 message(":: ERROR: Invalid build type: ${CMAKE_BUILD_TYPE}")
14 message("::")
15 message(":: valid types: { ${_list_pretty} }")
16 message("::")
17 message(FATAL_ERROR)
618endif()
719
820if(NOT CMAKE_INSTALL_PREFIX)
build.zig+3-4
......@@ -73,14 +73,13 @@ pub fn build(b: *Builder) !void {
7373 const skip_release_safe = b.option(bool, "skip-release-safe", "Main test suite skips release-safe builds") orelse skip_release;
7474 const skip_non_native = b.option(bool, "skip-non-native", "Main test suite skips non-native builds") orelse false;
7575 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) {
7978 test_step.dependOn(&exe.step);
8079 }
8180
8281 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) {
8483 b.default_step.dependOn(&exe.step);
8584 exe.install();
8685 }
doc/docgen.zig+2-2
......@@ -34,10 +34,10 @@ pub fn main() !void {
3434 const out_file_name = try (args_it.next(allocator) orelse @panic("expected output arg"));
3535 defer allocator.free(out_file_name);
3636
37 var in_file = try fs.File.openRead(in_file_name);
37 var in_file = try fs.cwd().openFile(in_file_name, .{ .read = true });
3838 defer in_file.close();
3939
40 var out_file = try fs.File.openWrite(out_file_name);
40 var out_file = try fs.cwd().createFile(out_file_name, .{});
4141 defer out_file.close();
4242
4343 var file_in_stream = in_file.inStream();
lib/std/atomic/queue.zig+13-4
......@@ -113,11 +113,20 @@ pub fn Queue(comptime T: type) type {
113113
114114 pub fn dumpToStream(self: *Self, comptime Error: type, stream: *std.io.OutStream(Error)) Error!void {
115115 const S = struct {
116 fn dumpRecursive(s: *std.io.OutStream(Error), optional_node: ?*Node, indent: usize) Error!void {
116 fn dumpRecursive(
117 s: *std.io.OutStream(Error),
118 optional_node: ?*Node,
119 indent: usize,
120 comptime depth: comptime_int,
121 ) Error!void {
117122 try s.writeByteNTimes(' ', indent);
118123 if (optional_node) |node| {
119124 try s.print("0x{x}={}\n", .{ @ptrToInt(node), node.data });
120 try dumpRecursive(s, node.next, indent + 1);
125 if (depth == 0) {
126 try s.print("(max depth)\n", .{});
127 return;
128 }
129 try dumpRecursive(s, node.next, indent + 1, depth - 1);
121130 } else {
122131 try s.print("(null)\n", .{});
123132 }
......@@ -127,9 +136,9 @@ pub fn Queue(comptime T: type) type {
127136 defer held.release();
128137
129138 try stream.print("head: ", .{});
130 try S.dumpRecursive(stream, self.head, 0);
139 try S.dumpRecursive(stream, self.head, 0, 4);
131140 try stream.print("tail: ", .{});
132 try S.dumpRecursive(stream, self.tail, 0);
141 try S.dumpRecursive(stream, self.tail, 0, 4);
133142 }
134143 };
135144}
lib/std/build.zig+6-2
......@@ -495,12 +495,16 @@ pub const Builder = struct {
495495
496496 self.addNativeSystemIncludeDir("/usr/local/include");
497497 self.addNativeSystemLibPath("/usr/local/lib");
498 self.addNativeSystemLibPath("/usr/local/lib64");
498499
499500 self.addNativeSystemIncludeDir(self.fmt("/usr/include/{}", .{triple}));
500501 self.addNativeSystemLibPath(self.fmt("/usr/lib/{}", .{triple}));
501502
502503 self.addNativeSystemIncludeDir("/usr/include");
504 self.addNativeSystemLibPath("/lib");
505 self.addNativeSystemLibPath("/lib64");
503506 self.addNativeSystemLibPath("/usr/lib");
507 self.addNativeSystemLibPath("/usr/lib64");
504508
505509 // example: on a 64-bit debian-based linux distro, with zlib installed from apt:
506510 // zlib.h is in /usr/include (added above)
......@@ -1416,7 +1420,7 @@ pub const LibExeObjStep = struct {
14161420 self.builder.installArtifact(self);
14171421 }
14181422
1419 pub fn installRaw(self: *LibExeObjStep, dest_filename: [] const u8) void {
1423 pub fn installRaw(self: *LibExeObjStep, dest_filename: []const u8) void {
14201424 self.builder.installRaw(self, dest_filename);
14211425 }
14221426
......@@ -2135,7 +2139,7 @@ pub const LibExeObjStep = struct {
21352139 try zig_args.append("-isystem");
21362140 try zig_args.append(self.builder.pathFromRoot(include_path));
21372141 },
2138 .OtherStep => |other| {
2142 .OtherStep => |other| if (!other.disable_gen_h) {
21392143 const h_path = other.getOutputHPath();
21402144 try zig_args.append("-isystem");
21412145 try zig_args.append(fs.path.dirname(h_path).?);
lib/std/builtin.zig+1
......@@ -460,6 +460,7 @@ pub const ExportOptions = struct {
460460pub const TestFn = struct {
461461 name: []const u8,
462462 func: fn () anyerror!void,
463 async_frame_size: ?usize,
463464};
464465
465466/// 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;
119119pub extern "c" fn sysctl(name: [*]const c_int, namelen: c_uint, oldp: ?*c_void, oldlenp: ?*usize, newp: ?*c_void, newlen: usize) c_int;
120120pub extern "c" fn sysctlbyname(name: [*:0]const u8, oldp: ?*c_void, oldlenp: ?*usize, newp: ?*c_void, newlen: usize) c_int;
121121pub extern "c" fn sysctlnametomib(name: [*:0]const u8, mibp: ?*c_int, sizep: ?*usize) c_int;
122pub extern "c" fn tcgetattr(fd: fd_t, termios_p: *termios) c_int;
123pub extern "c" fn tcsetattr(fd: fd_t, optional_action: TCSA, termios_p: *const termios) c_int;
124pub extern "c" fn fcntl(fd: fd_t, cmd: c_int, ...) c_int;
122125
123126pub extern "c" fn gethostname(name: [*]u8, len: usize) c_int;
124127pub 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 {
776776 }
777777 },
778778 else => {
779 self.index -= 1;
779780 state = if (string) .StringLiteral else .CharLiteral;
780781 },
781782 },
782783 .HexEscape => switch (c) {
783784 '0'...'9', 'a'...'f', 'A'...'F' => {},
784785 else => {
786 self.index -= 1;
785787 state = if (string) .StringLiteral else .CharLiteral;
786788 },
787789 },
......@@ -797,6 +799,7 @@ pub const Tokenizer = struct {
797799 result.id = .Invalid;
798800 break;
799801 }
802 self.index -= 1;
800803 state = if (string) .StringLiteral else .CharLiteral;
801804 },
802805 },
......@@ -1046,7 +1049,6 @@ pub const Tokenizer = struct {
10461049 .LineComment => switch (c) {
10471050 '\n' => {
10481051 result.id = .LineComment;
1049 self.index += 1;
10501052 break;
10511053 },
10521054 else => {},
......@@ -1217,6 +1219,7 @@ pub const Tokenizer = struct {
12171219 result.id = .Invalid;
12181220 break;
12191221 }
1222 self.index -= 1;
12201223 state = .FloatSuffix;
12211224 },
12221225 },
lib/std/child_process.zig+44-15
......@@ -329,17 +329,18 @@ pub const ChildProcess = struct {
329329 }
330330
331331 fn spawnPosix(self: *ChildProcess) SpawnError!void {
332 const stdin_pipe = if (self.stdin_behavior == StdIo.Pipe) try os.pipe() else undefined;
332 const pipe_flags = if (io.is_async) os.O_NONBLOCK else 0;
333 const stdin_pipe = if (self.stdin_behavior == StdIo.Pipe) try os.pipe2(pipe_flags) else undefined;
333334 errdefer if (self.stdin_behavior == StdIo.Pipe) {
334335 destroyPipe(stdin_pipe);
335336 };
336337
337 const stdout_pipe = if (self.stdout_behavior == StdIo.Pipe) try os.pipe() else undefined;
338 const stdout_pipe = if (self.stdout_behavior == StdIo.Pipe) try os.pipe2(pipe_flags) else undefined;
338339 errdefer if (self.stdout_behavior == StdIo.Pipe) {
339340 destroyPipe(stdout_pipe);
340341 };
341342
342 const stderr_pipe = if (self.stderr_behavior == StdIo.Pipe) try os.pipe() else undefined;
343 const stderr_pipe = if (self.stderr_behavior == StdIo.Pipe) try os.pipe2(pipe_flags) else undefined;
343344 errdefer if (self.stderr_behavior == StdIo.Pipe) {
344345 destroyPipe(stderr_pipe);
345346 };
......@@ -426,17 +427,26 @@ pub const ChildProcess = struct {
426427 // we are the parent
427428 const pid = @intCast(i32, pid_result);
428429 if (self.stdin_behavior == StdIo.Pipe) {
429 self.stdin = File.openHandle(stdin_pipe[1]);
430 self.stdin = File{
431 .handle = stdin_pipe[1],
432 .io_mode = std.io.mode,
433 };
430434 } else {
431435 self.stdin = null;
432436 }
433437 if (self.stdout_behavior == StdIo.Pipe) {
434 self.stdout = File.openHandle(stdout_pipe[0]);
438 self.stdout = File{
439 .handle = stdout_pipe[0],
440 .io_mode = std.io.mode,
441 };
435442 } else {
436443 self.stdout = null;
437444 }
438445 if (self.stderr_behavior == StdIo.Pipe) {
439 self.stderr = File.openHandle(stderr_pipe[0]);
446 self.stderr = File{
447 .handle = stderr_pipe[0],
448 .io_mode = std.io.mode,
449 };
440450 } else {
441451 self.stderr = null;
442452 }
......@@ -661,17 +671,26 @@ pub const ChildProcess = struct {
661671 };
662672
663673 if (g_hChildStd_IN_Wr) |h| {
664 self.stdin = File.openHandle(h);
674 self.stdin = File{
675 .handle = h,
676 .io_mode = io.mode,
677 };
665678 } else {
666679 self.stdin = null;
667680 }
668681 if (g_hChildStd_OUT_Rd) |h| {
669 self.stdout = File.openHandle(h);
682 self.stdout = File{
683 .handle = h,
684 .io_mode = io.mode,
685 };
670686 } else {
671687 self.stdout = null;
672688 }
673689 if (g_hChildStd_ERR_Rd) |h| {
674 self.stderr = File.openHandle(h);
690 self.stderr = File{
691 .handle = h,
692 .io_mode = io.mode,
693 };
675694 } else {
676695 self.stderr = null;
677696 }
......@@ -693,10 +712,10 @@ pub const ChildProcess = struct {
693712
694713 fn setUpChildIo(stdio: StdIo, pipe_fd: i32, std_fileno: i32, dev_null_fd: i32) !void {
695714 switch (stdio) {
696 StdIo.Pipe => try os.dup2(pipe_fd, std_fileno),
697 StdIo.Close => os.close(std_fileno),
698 StdIo.Inherit => {},
699 StdIo.Ignore => try os.dup2(dev_null_fd, std_fileno),
715 .Pipe => try os.dup2(pipe_fd, std_fileno),
716 .Close => os.close(std_fileno),
717 .Inherit => {},
718 .Ignore => try os.dup2(dev_null_fd, std_fileno),
700719 }
701720 }
702721};
......@@ -811,12 +830,22 @@ fn forkChildErrReport(fd: i32, err: ChildProcess.SpawnError) noreturn {
811830const ErrInt = @IntType(false, @sizeOf(anyerror) * 8);
812831
813832fn writeIntFd(fd: i32, value: ErrInt) !void {
814 const stream = &File.openHandle(fd).outStream().stream;
833 const file = File{
834 .handle = fd,
835 .io_mode = .blocking,
836 .async_block_allowed = File.async_block_allowed_yes,
837 };
838 const stream = &file.outStream().stream;
815839 stream.writeIntNative(u64, @intCast(u64, value)) catch return error.SystemResources;
816840}
817841
818842fn readIntFd(fd: i32) !ErrInt {
819 const stream = &File.openHandle(fd).inStream().stream;
843 const file = File{
844 .handle = fd,
845 .io_mode = .blocking,
846 .async_block_allowed = File.async_block_allowed_yes,
847 };
848 const stream = &file.inStream().stream;
820849 return @intCast(ErrInt, stream.readIntNative(u64) catch return error.SystemResources);
821850}
822851
lib/std/crypto/benchmark.zig+1
......@@ -23,6 +23,7 @@ const hashes = [_]Crypto{
2323 Crypto{ .ty = crypto.Sha512, .name = "sha512" },
2424 Crypto{ .ty = crypto.Sha3_256, .name = "sha3-256" },
2525 Crypto{ .ty = crypto.Sha3_512, .name = "sha3-512" },
26 Crypto{ .ty = crypto.gimli.Hash, .name = "gimli-hash" },
2627 Crypto{ .ty = crypto.Blake2s256, .name = "blake2s" },
2728 Crypto{ .ty = crypto.Blake2b512, .name = "blake2b" },
2829 Crypto{ .ty = crypto.Blake3, .name = "blake3" },
lib/std/crypto/gimli.zig+221-1
......@@ -19,7 +19,6 @@ pub const State = struct {
1919 pub const BLOCKBYTES = 48;
2020 pub const RATE = 16;
2121
22 // TODO: https://github.com/ziglang/zig/issues/2673#issuecomment-501763017
2322 data: [BLOCKBYTES / 4]u32,
2423
2524 const Self = @This();
......@@ -134,6 +133,8 @@ pub const Hash = struct {
134133 }
135134 }
136135
136 pub const digest_length = 32;
137
137138 /// Finish the current hashing operation, writing the hash to `out`
138139 ///
139140 /// From 4.9 "Application to hashing"
......@@ -166,3 +167,222 @@ test "hash" {
166167 hash(&md, &msg);
167168 htest.assertEqual("1C9A03DC6A5DDC5444CFC6F4B154CFF5CF081633B2CEA4D7D0AE7CCFED5AAA44", &md);
168169}
170
171pub const Aead = struct {
172 /// ad: Associated Data
173 /// npub: public nonce
174 /// k: private key
175 fn init(ad: []const u8, npub: [16]u8, k: [32]u8) State {
176 var state = State{
177 .data = undefined,
178 };
179 const buf = state.toSlice();
180
181 // Gimli-Cipher initializes a 48-byte Gimli state to a 16-byte nonce
182 // followed by a 32-byte key.
183 assert(npub.len + k.len == State.BLOCKBYTES);
184 std.mem.copy(u8, buf[0..npub.len], &npub);
185 std.mem.copy(u8, buf[npub.len .. npub.len + k.len], &k);
186
187 // It then applies the Gimli permutation.
188 state.permute();
189
190 {
191 // Gimli-Cipher then handles each block of associated data, including
192 // exactly one final non-full block, in the same way as Gimli-Hash.
193 var data = ad;
194 while (data.len >= State.RATE) : (data = data[State.RATE..]) {
195 for (buf[0..State.RATE]) |*p, i| {
196 p.* ^= data[i];
197 }
198 state.permute();
199 }
200 for (buf[0..data.len]) |*p, i| {
201 p.* ^= data[i];
202 }
203
204 // XOR 1 into the next byte of the state
205 buf[data.len] ^= 1;
206 // XOR 1 into the last byte of the state, position 47.
207 buf[buf.len - 1] ^= 1;
208
209 state.permute();
210 }
211
212 return state;
213 }
214
215 /// c: ciphertext: output buffer should be of size m.len
216 /// at: authentication tag: output MAC
217 /// m: message
218 /// ad: Associated Data
219 /// npub: public nonce
220 /// k: private key
221 pub fn encrypt(c: []u8, at: *[State.RATE]u8, m: []const u8, ad: []const u8, npub: [16]u8, k: [32]u8) void {
222 assert(c.len == m.len);
223
224 var state = Aead.init(ad, npub, k);
225 const buf = state.toSlice();
226
227 // Gimli-Cipher then handles each block of plaintext, including
228 // exactly one final non-full block, in the same way as Gimli-Hash.
229 // Whenever a plaintext byte is XORed into a state byte, the new state
230 // byte is output as ciphertext.
231 var in = m;
232 var out = c;
233 while (in.len >= State.RATE) : ({
234 in = in[State.RATE..];
235 out = out[State.RATE..];
236 }) {
237 for (buf[0..State.RATE]) |*p, i| {
238 p.* ^= in[i];
239 out[i] = p.*;
240 }
241 state.permute();
242 }
243 for (buf[0..in.len]) |*p, i| {
244 p.* ^= in[i];
245 out[i] = p.*;
246 }
247
248 // XOR 1 into the next byte of the state
249 buf[in.len] ^= 1;
250 // XOR 1 into the last byte of the state, position 47.
251 buf[buf.len - 1] ^= 1;
252
253 state.permute();
254
255 // After the final non-full block of plaintext, the first 16 bytes
256 // of the state are output as an authentication tag.
257 std.mem.copy(u8, at, buf[0..State.RATE]);
258 }
259
260 /// m: message: output buffer should be of size c.len
261 /// c: ciphertext
262 /// at: authentication tag
263 /// ad: Associated Data
264 /// npub: public nonce
265 /// k: private key
266 /// NOTE: the check of the authentication tag is currently not done in constant time
267 pub fn decrypt(m: []u8, c: []const u8, at: [State.RATE]u8, ad: []u8, npub: [16]u8, k: [32]u8) !void {
268 assert(c.len == m.len);
269
270 var state = Aead.init(ad, npub, k);
271 const buf = state.toSlice();
272
273 var in = c;
274 var out = m;
275 while (in.len >= State.RATE) : ({
276 in = in[State.RATE..];
277 out = out[State.RATE..];
278 }) {
279 for (buf[0..State.RATE]) |*p, i| {
280 out[i] = p.* ^ in[i];
281 p.* = in[i];
282 }
283 state.permute();
284 }
285 for (buf[0..in.len]) |*p, i| {
286 out[i] = p.* ^ in[i];
287 p.* = in[i];
288 }
289
290 // XOR 1 into the next byte of the state
291 buf[in.len] ^= 1;
292 // XOR 1 into the last byte of the state, position 47.
293 buf[buf.len - 1] ^= 1;
294
295 state.permute();
296
297 // After the final non-full block of plaintext, the first 16 bytes
298 // of the state are the authentication tag.
299 // TODO: use a constant-time equality check here, see https://github.com/ziglang/zig/issues/1776
300 if (!mem.eql(u8, buf[0..State.RATE], &at)) {
301 @memset(m.ptr, undefined, m.len);
302 return error.InvalidMessage;
303 }
304 }
305};
306
307test "cipher" {
308 var key: [32]u8 = undefined;
309 try std.fmt.hexToBytes(&key, "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F");
310 var nonce: [16]u8 = undefined;
311 try std.fmt.hexToBytes(&nonce, "000102030405060708090A0B0C0D0E0F");
312 { // test vector (1) from NIST KAT submission.
313 const ad: [0]u8 = undefined;
314 const pt: [0]u8 = undefined;
315
316 var ct: [pt.len]u8 = undefined;
317 var at: [16]u8 = undefined;
318 Aead.encrypt(&ct, &at, &pt, &ad, nonce, key);
319 htest.assertEqual("", &ct);
320 htest.assertEqual("14DA9BB7120BF58B985A8E00FDEBA15B", &at);
321
322 var pt2: [pt.len]u8 = undefined;
323 try Aead.decrypt(&pt2, &ct, at, &ad, nonce, key);
324 testing.expectEqualSlices(u8, &pt, &pt2);
325 }
326 { // test vector (34) from NIST KAT submission.
327 const ad: [0]u8 = undefined;
328 var pt: [2 / 2]u8 = undefined;
329 try std.fmt.hexToBytes(&pt, "00");
330
331 var ct: [pt.len]u8 = undefined;
332 var at: [16]u8 = undefined;
333 Aead.encrypt(&ct, &at, &pt, &ad, nonce, key);
334 htest.assertEqual("7F", &ct);
335 htest.assertEqual("80492C317B1CD58A1EDC3A0D3E9876FC", &at);
336
337 var pt2: [pt.len]u8 = undefined;
338 try Aead.decrypt(&pt2, &ct, at, &ad, nonce, key);
339 testing.expectEqualSlices(u8, &pt, &pt2);
340 }
341 { // test vector (106) from NIST KAT submission.
342 var ad: [12 / 2]u8 = undefined;
343 try std.fmt.hexToBytes(&ad, "000102030405");
344 var pt: [6 / 2]u8 = undefined;
345 try std.fmt.hexToBytes(&pt, "000102");
346
347 var ct: [pt.len]u8 = undefined;
348 var at: [16]u8 = undefined;
349 Aead.encrypt(&ct, &at, &pt, &ad, nonce, key);
350 htest.assertEqual("484D35", &ct);
351 htest.assertEqual("030BBEA23B61C00CED60A923BDCF9147", &at);
352
353 var pt2: [pt.len]u8 = undefined;
354 try Aead.decrypt(&pt2, &ct, at, &ad, nonce, key);
355 testing.expectEqualSlices(u8, &pt, &pt2);
356 }
357 { // test vector (790) from NIST KAT submission.
358 var ad: [60 / 2]u8 = undefined;
359 try std.fmt.hexToBytes(&ad, "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D");
360 var pt: [46 / 2]u8 = undefined;
361 try std.fmt.hexToBytes(&pt, "000102030405060708090A0B0C0D0E0F10111213141516");
362
363 var ct: [pt.len]u8 = undefined;
364 var at: [16]u8 = undefined;
365 Aead.encrypt(&ct, &at, &pt, &ad, nonce, key);
366 htest.assertEqual("6815B4A0ECDAD01596EAD87D9E690697475D234C6A13D1", &ct);
367 htest.assertEqual("DFE23F1642508290D68245279558B2FB", &at);
368
369 var pt2: [pt.len]u8 = undefined;
370 try Aead.decrypt(&pt2, &ct, at, &ad, nonce, key);
371 testing.expectEqualSlices(u8, &pt, &pt2);
372 }
373 { // test vector (1057) from NIST KAT submission.
374 const ad: [0]u8 = undefined;
375 var pt: [64 / 2]u8 = undefined;
376 try std.fmt.hexToBytes(&pt, "000102030405060708090A0B0C0D0E0F101112131415161718191A1B1C1D1E1F");
377
378 var ct: [pt.len]u8 = undefined;
379 var at: [16]u8 = undefined;
380 Aead.encrypt(&ct, &at, &pt, &ad, nonce, key);
381 htest.assertEqual("7F8A2CF4F52AA4D6B2E74105C30A2777B9D0C8AEFDD555DE35861BD3011F652F", &ct);
382 htest.assertEqual("7256456FA935AC34BBF55AE135F33257", &at);
383
384 var pt2: [pt.len]u8 = undefined;
385 try Aead.decrypt(&pt2, &ct, at, &ad, nonce, key);
386 testing.expectEqualSlices(u8, &pt, &pt2);
387 }
388}
lib/std/debug.zig+74-54
......@@ -50,7 +50,7 @@ pub fn warn(comptime fmt: []const u8, args: var) void {
5050 const held = stderr_mutex.acquire();
5151 defer held.release();
5252 const stderr = getStderrStream();
53 stderr.print(fmt, args) catch return;
53 noasync stderr.print(fmt, args) catch return;
5454}
5555
5656pub fn getStderrStream() *io.OutStream(File.WriteError) {
......@@ -102,15 +102,15 @@ pub fn detectTTYConfig() TTY.Config {
102102pub fn dumpCurrentStackTrace(start_addr: ?usize) void {
103103 const stderr = getStderrStream();
104104 if (builtin.strip_debug_info) {
105 stderr.print("Unable to dump stack trace: debug info stripped\n", .{}) catch return;
105 noasync stderr.print("Unable to dump stack trace: debug info stripped\n", .{}) catch return;
106106 return;
107107 }
108108 const debug_info = getSelfDebugInfo() catch |err| {
109 stderr.print("Unable to dump stack trace: Unable to open debug info: {}\n", .{@errorName(err)}) catch return;
109 noasync stderr.print("Unable to dump stack trace: Unable to open debug info: {}\n", .{@errorName(err)}) catch return;
110110 return;
111111 };
112112 writeCurrentStackTrace(stderr, debug_info, detectTTYConfig(), start_addr) catch |err| {
113 stderr.print("Unable to dump stack trace: {}\n", .{@errorName(err)}) catch return;
113 noasync stderr.print("Unable to dump stack trace: {}\n", .{@errorName(err)}) catch return;
114114 return;
115115 };
116116}
......@@ -121,22 +121,16 @@ pub fn dumpCurrentStackTrace(start_addr: ?usize) void {
121121pub fn dumpStackTraceFromBase(bp: usize, ip: usize) void {
122122 const stderr = getStderrStream();
123123 if (builtin.strip_debug_info) {
124 stderr.print("Unable to dump stack trace: debug info stripped\n", .{}) catch return;
124 noasync stderr.print("Unable to dump stack trace: debug info stripped\n", .{}) catch return;
125125 return;
126126 }
127127 const debug_info = getSelfDebugInfo() catch |err| {
128 stderr.print("Unable to dump stack trace: Unable to open debug info: {}\n", .{@errorName(err)}) catch return;
128 noasync stderr.print("Unable to dump stack trace: Unable to open debug info: {}\n", .{@errorName(err)}) catch return;
129129 return;
130130 };
131131 const tty_config = detectTTYConfig();
132132 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);
140134 while (it.next()) |return_address| {
141135 printSourceAtAddress(debug_info, stderr, return_address - 1, tty_config) catch return;
142136 }
......@@ -179,7 +173,7 @@ pub fn captureStackTrace(first_address: ?usize, stack_trace: *builtin.StackTrace
179173 }
180174 stack_trace.index = slice.len;
181175 } else {
182 var it = StackIterator.init(first_address);
176 var it = StackIterator.init(first_address, null);
183177 for (stack_trace.instruction_addresses) |*addr, i| {
184178 addr.* = it.next() orelse {
185179 stack_trace.index = i;
......@@ -195,15 +189,15 @@ pub fn captureStackTrace(first_address: ?usize, stack_trace: *builtin.StackTrace
195189pub fn dumpStackTrace(stack_trace: builtin.StackTrace) void {
196190 const stderr = getStderrStream();
197191 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;
199193 return;
200194 }
201195 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;
203197 return;
204198 };
205199 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;
207201 return;
208202 };
209203}
......@@ -244,7 +238,7 @@ pub fn panicExtra(trace: ?*const builtin.StackTrace, first_trace_addr: ?usize, c
244238 switch (@atomicRmw(u8, &panicking, .Add, 1, .SeqCst)) {
245239 0 => {
246240 const stderr = getStderrStream();
247 stderr.print(format ++ "\n", args) catch os.abort();
241 noasync stderr.print(format ++ "\n", args) catch os.abort();
248242 if (trace) |t| {
249243 dumpStackTrace(t.*);
250244 }
......@@ -291,13 +285,15 @@ pub fn writeStackTrace(
291285}
292286
293287pub 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
295291 fp: usize,
296292
297 pub fn init(first_addr: ?usize) StackIterator {
293 pub fn init(first_address: ?usize, fp: ?usize) StackIterator {
298294 return StackIterator{
299 .first_addr = first_addr,
300 .fp = @frameAddress(),
295 .first_address = first_address,
296 .fp = fp orelse @frameAddress(),
301297 };
302298 }
303299
......@@ -305,29 +301,45 @@ pub const StackIterator = struct {
305301 // the previous fp is stored, while on some other architectures such as
306302 // RISC-V it points to the "top" of the frame, just above where the previous
307303 // 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())
309305 2 * @sizeOf(usize)
310306 else
311307 0;
312308
313309 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;
325315 }
316 self.first_address = null;
326317 }
327318
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;
331343 }
332344};
333345
......@@ -340,7 +352,7 @@ pub fn writeCurrentStackTrace(
340352 if (builtin.os == .windows) {
341353 return writeCurrentStackTraceWindows(out_stream, debug_info, tty_config, start_addr);
342354 }
343 var it = StackIterator.init(start_addr);
355 var it = StackIterator.init(start_addr, null);
344356 while (it.next()) |return_address| {
345357 try printSourceAtAddress(debug_info, out_stream, return_address - 1, tty_config);
346358 }
......@@ -378,6 +390,7 @@ pub fn printSourceAtAddress(debug_info: *DebugInfo, out_stream: var, address: us
378390 return noasync printSourceAtAddressPosix(debug_info, out_stream, address, tty_config);
379391}
380392
393/// TODO resources https://github.com/ziglang/zig/issues/4353
381394fn printSourceAtAddressWindows(
382395 di: *DebugInfo,
383396 out_stream: var,
......@@ -555,12 +568,12 @@ pub const TTY = struct {
555568 switch (conf) {
556569 .no_color => return,
557570 .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,
564577 },
565578 .windows_api => if (builtin.os == .windows) {
566579 const S = struct {
......@@ -604,6 +617,7 @@ pub const TTY = struct {
604617 };
605618};
606619
620/// TODO resources https://github.com/ziglang/zig/issues/4353
607621fn populateModule(di: *DebugInfo, mod: *Module) !void {
608622 if (mod.populated)
609623 return;
......@@ -715,17 +729,17 @@ fn printLineInfo(
715729 tty_config.setColor(out_stream, .White);
716730
717731 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 });
719733 } else {
720 try out_stream.print("???:?:?", .{});
734 try noasync out_stream.write("???:?:?");
721735 }
722736
723737 tty_config.setColor(out_stream, .Reset);
724 try out_stream.write(": ");
738 try noasync out_stream.write(": ");
725739 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 });
727741 tty_config.setColor(out_stream, .Reset);
728 try out_stream.write("\n");
742 try noasync out_stream.write("\n");
729743
730744 // Show the matching source code line if possible
731745 if (line_info) |li| {
......@@ -734,12 +748,12 @@ fn printLineInfo(
734748 // The caret already takes one char
735749 const space_needed = @intCast(usize, li.column - 1);
736750
737 try out_stream.writeByteNTimes(' ', space_needed);
751 try noasync out_stream.writeByteNTimes(' ', space_needed);
738752 tty_config.setColor(out_stream, .Green);
739 try out_stream.write("^");
753 try noasync out_stream.write("^");
740754 tty_config.setColor(out_stream, .Reset);
741755 }
742 try out_stream.write("\n");
756 try noasync out_stream.write("\n");
743757 } else |err| switch (err) {
744758 error.EndOfFile, error.FileNotFound => {},
745759 error.BadPathName => {},
......@@ -755,6 +769,7 @@ pub const OpenSelfDebugInfoError = error{
755769 UnsupportedOperatingSystem,
756770};
757771
772/// TODO resources https://github.com/ziglang/zig/issues/4353
758773/// TODO once https://github.com/ziglang/zig/issues/3157 is fully implemented,
759774/// make this `noasync fn` and remove the individual noasync calls.
760775pub fn openSelfDebugInfo(allocator: *mem.Allocator) !DebugInfo {
......@@ -963,6 +978,7 @@ pub fn openDwarfDebugInfo(di: *DwarfInfo, allocator: *mem.Allocator) !void {
963978 try di.scanAllCompileUnits();
964979}
965980
981/// TODO resources https://github.com/ziglang/zig/issues/4353
966982pub fn openElfDebugInfo(
967983 allocator: *mem.Allocator,
968984 data: []u8,
......@@ -997,12 +1013,11 @@ pub fn openElfDebugInfo(
9971013 null,
9981014 };
9991015
1000 efile.close();
1001
10021016 try openDwarfDebugInfo(&di, allocator);
10031017 return di;
10041018}
10051019
1020/// TODO resources https://github.com/ziglang/zig/issues/4353
10061021fn openSelfDebugInfoPosix(allocator: *mem.Allocator) !DwarfInfo {
10071022 var exe_file = try fs.openSelfExe();
10081023 errdefer exe_file.close();
......@@ -1022,6 +1037,7 @@ fn openSelfDebugInfoPosix(allocator: *mem.Allocator) !DwarfInfo {
10221037 return openElfDebugInfo(allocator, exe_mmap);
10231038}
10241039
1040/// TODO resources https://github.com/ziglang/zig/issues/4353
10251041fn openSelfDebugInfoMacOs(allocator: *mem.Allocator) !DebugInfo {
10261042 const hdr = &std.c._mh_execute_header;
10271043 assert(hdr.magic == std.macho.MH_MAGIC_64);
......@@ -2074,6 +2090,7 @@ fn getAbbrevTableEntry(abbrev_table: *const AbbrevTable, abbrev_code: u64) ?*con
20742090 return null;
20752091}
20762092
2093/// TODO resources https://github.com/ziglang/zig/issues/4353
20772094fn getLineNumberInfoMacOs(di: *DebugInfo, symbol: MachoSymbol, address: usize) !LineInfo {
20782095 const ofile = symbol.ofile orelse return error.MissingDebugInfo;
20792096 const gop = try di.ofiles.getOrPut(ofile);
......@@ -2239,6 +2256,7 @@ pub fn attachSegfaultHandler() void {
22392256
22402257 os.sigaction(os.SIGSEGV, &act, null);
22412258 os.sigaction(os.SIGILL, &act, null);
2259 os.sigaction(os.SIGBUS, &act, null);
22422260}
22432261
22442262fn resetSegfaultHandler() void {
......@@ -2256,6 +2274,7 @@ fn resetSegfaultHandler() void {
22562274 };
22572275 os.sigaction(os.SIGSEGV, &act, null);
22582276 os.sigaction(os.SIGILL, &act, null);
2277 os.sigaction(os.SIGBUS, &act, null);
22592278}
22602279
22612280fn 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
22682287 switch (sig) {
22692288 os.SIGSEGV => std.debug.warn("Segmentation fault at address 0x{x}\n", .{addr}),
22702289 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}),
22712291 else => unreachable,
22722292 }
22732293 switch (builtin.arch) {
lib/std/event.zig-2
......@@ -6,11 +6,9 @@ pub const Locked = @import("event/locked.zig").Locked;
66pub const RwLock = @import("event/rwlock.zig").RwLock;
77pub const RwLocked = @import("event/rwlocked.zig").RwLocked;
88pub const Loop = @import("event/loop.zig").Loop;
9pub const fs = @import("event/fs.zig");
109
1110test "import event tests" {
1211 _ = @import("event/channel.zig");
13 _ = @import("event/fs.zig");
1412 _ = @import("event/future.zig");
1513 _ = @import("event/group.zig");
1614 _ = @import("event/lock.zig");
lib/std/event/channel.zig+3-4
......@@ -267,17 +267,16 @@ pub fn Channel(comptime T: type) type {
267267}
268268
269269test "std.event.Channel" {
270 if (!std.io.is_async) return error.SkipZigTest;
271
270272 // https://github.com/ziglang/zig/issues/1908
271273 if (builtin.single_threaded) return error.SkipZigTest;
272274
273275 // https://github.com/ziglang/zig/issues/3251
274276 if (builtin.os == .freebsd) return error.SkipZigTest;
275277
276 // TODO provide a way to run tests in evented I/O mode
277 if (!std.io.is_async) return error.SkipZigTest;
278
279278 var channel: Channel(i32) = undefined;
280 channel.init([0]i32{});
279 channel.init(&[0]i32{});
281280 defer channel.deinit();
282281
283282 var handle = async testChannelGetter(&channel);
lib/std/event/fs.zig deleted-1418
......@@ -1,1418 +0,0 @@
1const builtin = @import("builtin");
2const std = @import("../std.zig");
3const event = std.event;
4const assert = std.debug.assert;
5const testing = std.testing;
6const os = std.os;
7const mem = std.mem;
8const windows = os.windows;
9const Loop = event.Loop;
10const fd_t = os.fd_t;
11const File = std.fs.File;
12const Allocator = mem.Allocator;
13
14//! TODO mege this with `std.fs`
15
16const global_event_loop = Loop.instance orelse
17 @compileError("std.event.fs currently only works with event-based I/O");
18
19pub const RequestNode = std.atomic.Queue(Request).Node;
20
21pub const Request = struct {
22 msg: Msg,
23 finish: Finish,
24
25 pub const Finish = union(enum) {
26 TickNode: Loop.NextTickNode,
27 DeallocCloseOperation: *CloseOperation,
28 NoAction,
29 };
30
31 pub const Msg = union(enum) {
32 WriteV: WriteV,
33 PWriteV: PWriteV,
34 PReadV: PReadV,
35 Open: Open,
36 Close: Close,
37 WriteFile: WriteFile,
38 End, // special - means the fs thread should exit
39
40 pub const WriteV = struct {
41 fd: fd_t,
42 iov: []const os.iovec_const,
43 result: Error!void,
44
45 pub const Error = os.WriteError;
46 };
47
48 pub const PWriteV = struct {
49 fd: fd_t,
50 iov: []const os.iovec_const,
51 offset: usize,
52 result: Error!void,
53
54 pub const Error = os.WriteError;
55 };
56
57 pub const PReadV = struct {
58 fd: fd_t,
59 iov: []const os.iovec,
60 offset: usize,
61 result: Error!usize,
62
63 pub const Error = os.ReadError;
64 };
65
66 pub const Open = struct {
67 path: [:0]const u8,
68 flags: u32,
69 mode: File.Mode,
70 result: Error!fd_t,
71
72 pub const Error = File.OpenError;
73 };
74
75 pub const WriteFile = struct {
76 path: [:0]const u8,
77 contents: []const u8,
78 mode: File.Mode,
79 result: Error!void,
80
81 pub const Error = File.OpenError || File.WriteError;
82 };
83
84 pub const Close = struct {
85 fd: fd_t,
86 };
87 };
88};
89
90pub const PWriteVError = error{OutOfMemory} || File.WriteError;
91
92/// data - just the inner references - must live until pwritev frame completes.
93pub fn pwritev(allocator: *Allocator, fd: fd_t, data: []const []const u8, offset: usize) PWriteVError!void {
94 switch (builtin.os) {
95 .macosx,
96 .linux,
97 .freebsd,
98 .netbsd,
99 .dragonfly,
100 => {
101 const iovecs = try allocator.alloc(os.iovec_const, data.len);
102 defer allocator.free(iovecs);
103
104 for (data) |buf, i| {
105 iovecs[i] = os.iovec_const{
106 .iov_base = buf.ptr,
107 .iov_len = buf.len,
108 };
109 }
110
111 return pwritevPosix(fd, iovecs, offset);
112 },
113 .windows => {
114 const data_copy = try std.mem.dupe(allocator, []const u8, data);
115 defer allocator.free(data_copy);
116 return pwritevWindows(fd, data, offset);
117 },
118 else => @compileError("Unsupported OS"),
119 }
120}
121
122/// data must outlive the returned frame
123pub fn pwritevWindows(fd: fd_t, data: []const []const u8, offset: usize) os.WindowsWriteError!void {
124 if (data.len == 0) return;
125 if (data.len == 1) return pwriteWindows(fd, data[0], offset);
126
127 // TODO do these in parallel
128 var off = offset;
129 for (data) |buf| {
130 try pwriteWindows(fd, buf, off);
131 off += buf.len;
132 }
133}
134
135pub fn pwriteWindows(fd: fd_t, data: []const u8, offset: u64) os.WindowsWriteError!void {
136 var resume_node = Loop.ResumeNode.Basic{
137 .base = Loop.ResumeNode{
138 .id = Loop.ResumeNode.Id.Basic,
139 .handle = @frame(),
140 .overlapped = windows.OVERLAPPED{
141 .Internal = 0,
142 .InternalHigh = 0,
143 .Offset = @truncate(u32, offset),
144 .OffsetHigh = @truncate(u32, offset >> 32),
145 .hEvent = null,
146 },
147 },
148 };
149 // TODO only call create io completion port once per fd
150 _ = windows.CreateIoCompletionPort(fd, global_event_loop.os_data.io_port, undefined, undefined);
151 global_event_loop.beginOneEvent();
152 errdefer global_event_loop.finishOneEvent();
153
154 errdefer {
155 _ = windows.kernel32.CancelIoEx(fd, &resume_node.base.overlapped);
156 }
157 suspend {
158 _ = windows.kernel32.WriteFile(fd, data.ptr, @intCast(windows.DWORD, data.len), null, &resume_node.base.overlapped);
159 }
160 var bytes_transferred: windows.DWORD = undefined;
161 if (windows.kernel32.GetOverlappedResult(fd, &resume_node.base.overlapped, &bytes_transferred, windows.FALSE) == 0) {
162 switch (windows.kernel32.GetLastError()) {
163 .IO_PENDING => unreachable,
164 .INVALID_USER_BUFFER => return error.SystemResources,
165 .NOT_ENOUGH_MEMORY => return error.SystemResources,
166 .OPERATION_ABORTED => return error.OperationAborted,
167 .NOT_ENOUGH_QUOTA => return error.SystemResources,
168 .BROKEN_PIPE => return error.BrokenPipe,
169 else => |err| return windows.unexpectedError(err),
170 }
171 }
172}
173
174/// iovecs must live until pwritev frame completes.
175pub fn pwritevPosix(fd: fd_t, iovecs: []const os.iovec_const, offset: usize) os.WriteError!void {
176 var req_node = RequestNode{
177 .prev = null,
178 .next = null,
179 .data = Request{
180 .msg = Request.Msg{
181 .PWriteV = Request.Msg.PWriteV{
182 .fd = fd,
183 .iov = iovecs,
184 .offset = offset,
185 .result = undefined,
186 },
187 },
188 .finish = Request.Finish{
189 .TickNode = Loop.NextTickNode{
190 .prev = null,
191 .next = null,
192 .data = @frame(),
193 },
194 },
195 },
196 };
197
198 errdefer global_event_loop.posixFsCancel(&req_node);
199
200 suspend {
201 global_event_loop.posixFsRequest(&req_node);
202 }
203
204 return req_node.data.msg.PWriteV.result;
205}
206
207/// iovecs must live until pwritev frame completes.
208pub fn writevPosix(fd: fd_t, iovecs: []const os.iovec_const) os.WriteError!void {
209 var req_node = RequestNode{
210 .prev = null,
211 .next = null,
212 .data = Request{
213 .msg = Request.Msg{
214 .WriteV = Request.Msg.WriteV{
215 .fd = fd,
216 .iov = iovecs,
217 .result = undefined,
218 },
219 },
220 .finish = Request.Finish{
221 .TickNode = Loop.NextTickNode{
222 .prev = null,
223 .next = null,
224 .data = @frame(),
225 },
226 },
227 },
228 };
229
230 suspend {
231 global_event_loop.posixFsRequest(&req_node);
232 }
233
234 return req_node.data.msg.WriteV.result;
235}
236
237pub const PReadVError = error{OutOfMemory} || File.ReadError;
238
239/// data - just the inner references - must live until preadv frame completes.
240pub fn preadv(allocator: *Allocator, fd: fd_t, data: []const []u8, offset: usize) PReadVError!usize {
241 assert(data.len != 0);
242 switch (builtin.os) {
243 .macosx,
244 .linux,
245 .freebsd,
246 .netbsd,
247 .dragonfly,
248 => {
249 const iovecs = try allocator.alloc(os.iovec, data.len);
250 defer allocator.free(iovecs);
251
252 for (data) |buf, i| {
253 iovecs[i] = os.iovec{
254 .iov_base = buf.ptr,
255 .iov_len = buf.len,
256 };
257 }
258
259 return preadvPosix(fd, iovecs, offset);
260 },
261 .windows => {
262 const data_copy = try std.mem.dupe(allocator, []u8, data);
263 defer allocator.free(data_copy);
264 return preadvWindows(fd, data_copy, offset);
265 },
266 else => @compileError("Unsupported OS"),
267 }
268}
269
270/// data must outlive the returned frame
271pub fn preadvWindows(fd: fd_t, data: []const []u8, offset: u64) !usize {
272 assert(data.len != 0);
273 if (data.len == 1) return preadWindows(fd, data[0], offset);
274
275 // TODO do these in parallel?
276 var off: usize = 0;
277 var iov_i: usize = 0;
278 var inner_off: usize = 0;
279 while (true) {
280 const v = data[iov_i];
281 const amt_read = try preadWindows(fd, v[inner_off .. v.len - inner_off], offset + off);
282 off += amt_read;
283 inner_off += amt_read;
284 if (inner_off == v.len) {
285 iov_i += 1;
286 inner_off = 0;
287 if (iov_i == data.len) {
288 return off;
289 }
290 }
291 if (amt_read == 0) return off; // EOF
292 }
293}
294
295pub fn preadWindows(fd: fd_t, data: []u8, offset: u64) !usize {
296 var resume_node = Loop.ResumeNode.Basic{
297 .base = Loop.ResumeNode{
298 .id = Loop.ResumeNode.Id.Basic,
299 .handle = @frame(),
300 .overlapped = windows.OVERLAPPED{
301 .Internal = 0,
302 .InternalHigh = 0,
303 .Offset = @truncate(u32, offset),
304 .OffsetHigh = @truncate(u32, offset >> 32),
305 .hEvent = null,
306 },
307 },
308 };
309 // TODO only call create io completion port once per fd
310 _ = windows.CreateIoCompletionPort(fd, global_event_loop.os_data.io_port, undefined, undefined) catch undefined;
311 global_event_loop.beginOneEvent();
312 errdefer global_event_loop.finishOneEvent();
313
314 errdefer {
315 _ = windows.kernel32.CancelIoEx(fd, &resume_node.base.overlapped);
316 }
317 suspend {
318 _ = windows.kernel32.ReadFile(fd, data.ptr, @intCast(windows.DWORD, data.len), null, &resume_node.base.overlapped);
319 }
320 var bytes_transferred: windows.DWORD = undefined;
321 if (windows.kernel32.GetOverlappedResult(fd, &resume_node.base.overlapped, &bytes_transferred, windows.FALSE) == 0) {
322 switch (windows.kernel32.GetLastError()) {
323 .IO_PENDING => unreachable,
324 .OPERATION_ABORTED => return error.OperationAborted,
325 .BROKEN_PIPE => return error.BrokenPipe,
326 .HANDLE_EOF => return @as(usize, bytes_transferred),
327 else => |err| return windows.unexpectedError(err),
328 }
329 }
330 return @as(usize, bytes_transferred);
331}
332
333/// iovecs must live until preadv frame completes
334pub fn preadvPosix(fd: fd_t, iovecs: []const os.iovec, offset: usize) os.ReadError!usize {
335 var req_node = RequestNode{
336 .prev = null,
337 .next = null,
338 .data = Request{
339 .msg = Request.Msg{
340 .PReadV = Request.Msg.PReadV{
341 .fd = fd,
342 .iov = iovecs,
343 .offset = offset,
344 .result = undefined,
345 },
346 },
347 .finish = Request.Finish{
348 .TickNode = Loop.NextTickNode{
349 .prev = null,
350 .next = null,
351 .data = @frame(),
352 },
353 },
354 },
355 };
356
357 errdefer global_event_loop.posixFsCancel(&req_node);
358
359 suspend {
360 global_event_loop.posixFsRequest(&req_node);
361 }
362
363 return req_node.data.msg.PReadV.result;
364}
365
366pub fn openPosix(path: []const u8, flags: u32, mode: File.Mode) File.OpenError!fd_t {
367 const path_c = try std.os.toPosixPath(path);
368
369 var req_node = RequestNode{
370 .prev = null,
371 .next = null,
372 .data = Request{
373 .msg = Request.Msg{
374 .Open = Request.Msg.Open{
375 .path = path_c[0..path.len],
376 .flags = flags,
377 .mode = mode,
378 .result = undefined,
379 },
380 },
381 .finish = Request.Finish{
382 .TickNode = Loop.NextTickNode{
383 .prev = null,
384 .next = null,
385 .data = @frame(),
386 },
387 },
388 },
389 };
390
391 errdefer global_event_loop.posixFsCancel(&req_node);
392
393 suspend {
394 global_event_loop.posixFsRequest(&req_node);
395 }
396
397 return req_node.data.msg.Open.result;
398}
399
400pub fn openRead(path: []const u8) File.OpenError!fd_t {
401 switch (builtin.os) {
402 .macosx, .linux, .freebsd, .netbsd, .dragonfly => {
403 const O_LARGEFILE = if (@hasDecl(os, "O_LARGEFILE")) os.O_LARGEFILE else 0;
404 const flags = O_LARGEFILE | os.O_RDONLY | os.O_CLOEXEC;
405 return openPosix(path, flags, File.default_mode);
406 },
407
408 .windows => return windows.CreateFile(
409 path,
410 windows.GENERIC_READ,
411 windows.FILE_SHARE_READ,
412 null,
413 windows.OPEN_EXISTING,
414 windows.FILE_ATTRIBUTE_NORMAL | windows.FILE_FLAG_OVERLAPPED,
415 null,
416 ),
417
418 else => @compileError("Unsupported OS"),
419 }
420}
421
422/// Creates if does not exist. Truncates the file if it exists.
423/// Uses the default mode.
424pub fn openWrite(path: []const u8) File.OpenError!fd_t {
425 return openWriteMode(path, File.default_mode);
426}
427
428/// Creates if does not exist. Truncates the file if it exists.
429pub fn openWriteMode(path: []const u8, mode: File.Mode) File.OpenError!fd_t {
430 switch (builtin.os) {
431 .macosx,
432 .linux,
433 .freebsd,
434 .netbsd,
435 .dragonfly,
436 => {
437 const O_LARGEFILE = if (@hasDecl(os, "O_LARGEFILE")) os.O_LARGEFILE else 0;
438 const flags = O_LARGEFILE | os.O_WRONLY | os.O_CREAT | os.O_CLOEXEC | os.O_TRUNC;
439 return openPosix(path, flags, File.default_mode);
440 },
441 .windows => return windows.CreateFile(
442 path,
443 windows.GENERIC_WRITE,
444 windows.FILE_SHARE_WRITE | windows.FILE_SHARE_READ | windows.FILE_SHARE_DELETE,
445 null,
446 windows.CREATE_ALWAYS,
447 windows.FILE_ATTRIBUTE_NORMAL | windows.FILE_FLAG_OVERLAPPED,
448 null,
449 ),
450 else => @compileError("Unsupported OS"),
451 }
452}
453
454/// Creates if does not exist. Does not truncate.
455pub fn openReadWrite(path: []const u8, mode: File.Mode) File.OpenError!fd_t {
456 switch (builtin.os) {
457 .macosx, .linux, .freebsd, .netbsd, .dragonfly => {
458 const O_LARGEFILE = if (@hasDecl(os, "O_LARGEFILE")) os.O_LARGEFILE else 0;
459 const flags = O_LARGEFILE | os.O_RDWR | os.O_CREAT | os.O_CLOEXEC;
460 return openPosix(path, flags, mode);
461 },
462
463 .windows => return windows.CreateFile(
464 path,
465 windows.GENERIC_WRITE | windows.GENERIC_READ,
466 windows.FILE_SHARE_WRITE | windows.FILE_SHARE_READ | windows.FILE_SHARE_DELETE,
467 null,
468 windows.OPEN_ALWAYS,
469 windows.FILE_ATTRIBUTE_NORMAL | windows.FILE_FLAG_OVERLAPPED,
470 null,
471 ),
472
473 else => @compileError("Unsupported OS"),
474 }
475}
476
477/// This abstraction helps to close file handles in defer expressions
478/// without the possibility of failure and without the use of suspend points.
479/// Start a `CloseOperation` before opening a file, so that you can defer
480/// `CloseOperation.finish`.
481/// If you call `setHandle` then finishing will close the fd; otherwise finishing
482/// will deallocate the `CloseOperation`.
483pub const CloseOperation = struct {
484 allocator: *Allocator,
485 os_data: OsData,
486
487 const OsData = switch (builtin.os) {
488 .linux, .macosx, .freebsd, .netbsd, .dragonfly => OsDataPosix,
489
490 .windows => struct {
491 handle: ?fd_t,
492 },
493
494 else => @compileError("Unsupported OS"),
495 };
496
497 const OsDataPosix = struct {
498 have_fd: bool,
499 close_req_node: RequestNode,
500 };
501
502 pub fn start(allocator: *Allocator) (error{OutOfMemory}!*CloseOperation) {
503 const self = try allocator.create(CloseOperation);
504 self.* = CloseOperation{
505 .allocator = allocator,
506 .os_data = switch (builtin.os) {
507 .linux, .macosx, .freebsd, .netbsd, .dragonfly => initOsDataPosix(self),
508 .windows => OsData{ .handle = null },
509 else => @compileError("Unsupported OS"),
510 },
511 };
512 return self;
513 }
514
515 fn initOsDataPosix(self: *CloseOperation) OsData {
516 return OsData{
517 .have_fd = false,
518 .close_req_node = RequestNode{
519 .prev = null,
520 .next = null,
521 .data = Request{
522 .msg = Request.Msg{
523 .Close = Request.Msg.Close{ .fd = undefined },
524 },
525 .finish = Request.Finish{ .DeallocCloseOperation = self },
526 },
527 },
528 };
529 }
530
531 /// Defer this after creating.
532 pub fn finish(self: *CloseOperation) void {
533 switch (builtin.os) {
534 .linux,
535 .macosx,
536 .freebsd,
537 .netbsd,
538 .dragonfly,
539 => {
540 if (self.os_data.have_fd) {
541 global_event_loop.posixFsRequest(&self.os_data.close_req_node);
542 } else {
543 self.allocator.destroy(self);
544 }
545 },
546 .windows => {
547 if (self.os_data.handle) |handle| {
548 os.close(handle);
549 }
550 self.allocator.destroy(self);
551 },
552 else => @compileError("Unsupported OS"),
553 }
554 }
555
556 pub fn setHandle(self: *CloseOperation, handle: fd_t) void {
557 switch (builtin.os) {
558 .linux,
559 .macosx,
560 .freebsd,
561 .netbsd,
562 .dragonfly,
563 => {
564 self.os_data.close_req_node.data.msg.Close.fd = handle;
565 self.os_data.have_fd = true;
566 },
567 .windows => {
568 self.os_data.handle = handle;
569 },
570 else => @compileError("Unsupported OS"),
571 }
572 }
573
574 /// Undo a `setHandle`.
575 pub fn clearHandle(self: *CloseOperation) void {
576 switch (builtin.os) {
577 .linux,
578 .macosx,
579 .freebsd,
580 .netbsd,
581 .dragonfly,
582 => {
583 self.os_data.have_fd = false;
584 },
585 .windows => {
586 self.os_data.handle = null;
587 },
588 else => @compileError("Unsupported OS"),
589 }
590 }
591
592 pub fn getHandle(self: *CloseOperation) fd_t {
593 switch (builtin.os) {
594 .linux,
595 .macosx,
596 .freebsd,
597 .netbsd,
598 .dragonfly,
599 => {
600 assert(self.os_data.have_fd);
601 return self.os_data.close_req_node.data.msg.Close.fd;
602 },
603 .windows => {
604 return self.os_data.handle.?;
605 },
606 else => @compileError("Unsupported OS"),
607 }
608 }
609};
610
611/// contents must remain alive until writeFile completes.
612/// TODO make this atomic or provide writeFileAtomic and rename this one to writeFileTruncate
613pub fn writeFile(allocator: *Allocator, path: []const u8, contents: []const u8) !void {
614 return writeFileMode(allocator, path, contents, File.default_mode);
615}
616
617/// contents must remain alive until writeFile completes.
618pub fn writeFileMode(allocator: *Allocator, path: []const u8, contents: []const u8, mode: File.Mode) !void {
619 switch (builtin.os) {
620 .linux,
621 .macosx,
622 .freebsd,
623 .netbsd,
624 .dragonfly,
625 => return writeFileModeThread(allocator, path, contents, mode),
626 .windows => return writeFileWindows(path, contents),
627 else => @compileError("Unsupported OS"),
628 }
629}
630
631fn writeFileWindows(path: []const u8, contents: []const u8) !void {
632 const handle = try windows.CreateFile(
633 path,
634 windows.GENERIC_WRITE,
635 windows.FILE_SHARE_WRITE | windows.FILE_SHARE_READ | windows.FILE_SHARE_DELETE,
636 null,
637 windows.CREATE_ALWAYS,
638 windows.FILE_ATTRIBUTE_NORMAL | windows.FILE_FLAG_OVERLAPPED,
639 null,
640 );
641 defer os.close(handle);
642
643 try pwriteWindows(handle, contents, 0);
644}
645
646fn writeFileModeThread(allocator: *Allocator, path: []const u8, contents: []const u8, mode: File.Mode) !void {
647 const path_with_null = try std.cstr.addNullByte(allocator, path);
648 defer allocator.free(path_with_null);
649
650 var req_node = RequestNode{
651 .prev = null,
652 .next = null,
653 .data = Request{
654 .msg = Request.Msg{
655 .WriteFile = Request.Msg.WriteFile{
656 .path = path_with_null[0..path.len],
657 .contents = contents,
658 .mode = mode,
659 .result = undefined,
660 },
661 },
662 .finish = Request.Finish{
663 .TickNode = Loop.NextTickNode{
664 .prev = null,
665 .next = null,
666 .data = @frame(),
667 },
668 },
669 },
670 };
671
672 errdefer global_event_loop.posixFsCancel(&req_node);
673
674 suspend {
675 global_event_loop.posixFsRequest(&req_node);
676 }
677
678 return req_node.data.msg.WriteFile.result;
679}
680
681/// The frame resumes when the last data has been confirmed written, but before the file handle
682/// is closed.
683/// Caller owns returned memory.
684pub fn readFile(allocator: *Allocator, file_path: []const u8, max_size: usize) ![]u8 {
685 var close_op = try CloseOperation.start(allocator);
686 defer close_op.finish();
687
688 const fd = try openRead(file_path);
689 close_op.setHandle(fd);
690
691 var list = std.ArrayList(u8).init(allocator);
692 defer list.deinit();
693
694 while (true) {
695 try list.ensureCapacity(list.len + mem.page_size);
696 const buf = list.items[list.len..];
697 const buf_array = [_][]u8{buf};
698 const amt = try preadv(allocator, fd, &buf_array, list.len);
699 list.len += amt;
700 if (list.len > max_size) {
701 return error.FileTooBig;
702 }
703 if (amt < buf.len) {
704 return list.toOwnedSlice();
705 }
706 }
707}
708
709pub const WatchEventId = enum {
710 CloseWrite,
711 Delete,
712};
713
714fn eqlString(a: []const u16, b: []const u16) bool {
715 if (a.len != b.len) return false;
716 if (a.ptr == b.ptr) return true;
717 return mem.compare(u16, a, b) == .Equal;
718}
719
720fn hashString(s: []const u16) u32 {
721 return @truncate(u32, std.hash.Wyhash.hash(0, @sliceToBytes(s)));
722}
723
724pub const WatchEventError = error{
725 UserResourceLimitReached,
726 SystemResources,
727 AccessDenied,
728 Unexpected, // TODO remove this possibility
729};
730
731pub fn Watch(comptime V: type) type {
732 return struct {
733 channel: *event.Channel(Event.Error!Event),
734 os_data: OsData,
735 allocator: *Allocator,
736
737 const OsData = switch (builtin.os) {
738 // TODO https://github.com/ziglang/zig/issues/3778
739 .macosx, .freebsd, .netbsd, .dragonfly => KqOsData,
740 .linux => LinuxOsData,
741 .windows => WindowsOsData,
742
743 else => @compileError("Unsupported OS"),
744 };
745
746 const KqOsData = struct {
747 file_table: FileTable,
748 table_lock: event.Lock,
749
750 const FileTable = std.StringHashMap(*Put);
751 const Put = struct {
752 putter_frame: @Frame(kqPutEvents),
753 cancelled: bool = false,
754 value: V,
755 };
756 };
757
758 const WindowsOsData = struct {
759 table_lock: event.Lock,
760 dir_table: DirTable,
761 all_putters: std.atomic.Queue(Put),
762 ref_count: std.atomic.Int(usize),
763
764 const Put = struct {
765 putter: anyframe,
766 cancelled: bool = false,
767 };
768
769 const DirTable = std.StringHashMap(*Dir);
770 const FileTable = std.HashMap([]const u16, V, hashString, eqlString);
771
772 const Dir = struct {
773 putter_frame: @Frame(windowsDirReader),
774 file_table: FileTable,
775 table_lock: event.Lock,
776 };
777 };
778
779 const LinuxOsData = struct {
780 putter_frame: @Frame(linuxEventPutter),
781 inotify_fd: i32,
782 wd_table: WdTable,
783 table_lock: event.Lock,
784 cancelled: bool = false,
785
786 const WdTable = std.AutoHashMap(i32, Dir);
787 const FileTable = std.StringHashMap(V);
788
789 const Dir = struct {
790 dirname: []const u8,
791 file_table: FileTable,
792 };
793 };
794
795 const Self = @This();
796
797 pub const Event = struct {
798 id: Id,
799 data: V,
800
801 pub const Id = WatchEventId;
802 pub const Error = WatchEventError;
803 };
804
805 pub fn init(allocator: *Allocator, event_buf_count: usize) !*Self {
806 const channel = try allocator.create(event.Channel(Event.Error!Event));
807 errdefer allocator.destroy(channel);
808 var buf = try allocator.alloc(Event.Error!Event, event_buf_count);
809 errdefer allocator.free(buf);
810 channel.init(buf);
811 errdefer channel.deinit();
812
813 const self = try allocator.create(Self);
814 errdefer allocator.destroy(self);
815
816 switch (builtin.os) {
817 .linux => {
818 const inotify_fd = try os.inotify_init1(os.linux.IN_NONBLOCK | os.linux.IN_CLOEXEC);
819 errdefer os.close(inotify_fd);
820
821 self.* = Self{
822 .allocator = allocator,
823 .channel = channel,
824 .os_data = OsData{
825 .putter_frame = undefined,
826 .inotify_fd = inotify_fd,
827 .wd_table = OsData.WdTable.init(allocator),
828 .table_lock = event.Lock.init(),
829 },
830 };
831
832 self.os_data.putter_frame = async self.linuxEventPutter();
833 return self;
834 },
835
836 .windows => {
837 self.* = Self{
838 .allocator = allocator,
839 .channel = channel,
840 .os_data = OsData{
841 .table_lock = event.Lock.init(),
842 .dir_table = OsData.DirTable.init(allocator),
843 .ref_count = std.atomic.Int(usize).init(1),
844 .all_putters = std.atomic.Queue(anyframe).init(),
845 },
846 };
847 return self;
848 },
849
850 .macosx, .freebsd, .netbsd, .dragonfly => {
851 self.* = Self{
852 .allocator = allocator,
853 .channel = channel,
854 .os_data = OsData{
855 .table_lock = event.Lock.init(),
856 .file_table = OsData.FileTable.init(allocator),
857 },
858 };
859 return self;
860 },
861 else => @compileError("Unsupported OS"),
862 }
863 }
864
865 /// All addFile calls and removeFile calls must have completed.
866 pub fn deinit(self: *Self) void {
867 switch (builtin.os) {
868 .macosx, .freebsd, .netbsd, .dragonfly => {
869 // TODO we need to cancel the frames before destroying the lock
870 self.os_data.table_lock.deinit();
871 var it = self.os_data.file_table.iterator();
872 while (it.next()) |entry| {
873 entry.cancelled = true;
874 await entry.value.putter;
875 self.allocator.free(entry.key);
876 self.allocator.free(entry.value);
877 }
878 self.channel.deinit();
879 self.allocator.destroy(self.channel.buffer_nodes);
880 self.allocator.destroy(self);
881 },
882 .linux => {
883 self.os_data.cancelled = true;
884 await self.os_data.putter_frame;
885 self.allocator.destroy(self);
886 },
887 .windows => {
888 while (self.os_data.all_putters.get()) |putter_node| {
889 putter_node.cancelled = true;
890 await putter_node.frame;
891 }
892 self.deref();
893 },
894 else => @compileError("Unsupported OS"),
895 }
896 }
897
898 fn ref(self: *Self) void {
899 _ = self.os_data.ref_count.incr();
900 }
901
902 fn deref(self: *Self) void {
903 if (self.os_data.ref_count.decr() == 1) {
904 self.os_data.table_lock.deinit();
905 var it = self.os_data.dir_table.iterator();
906 while (it.next()) |entry| {
907 self.allocator.free(entry.key);
908 self.allocator.destroy(entry.value);
909 }
910 self.os_data.dir_table.deinit();
911 self.channel.deinit();
912 self.allocator.destroy(self.channel.buffer_nodes);
913 self.allocator.destroy(self);
914 }
915 }
916
917 pub fn addFile(self: *Self, file_path: []const u8, value: V) !?V {
918 switch (builtin.os) {
919 .macosx, .freebsd, .netbsd, .dragonfly => return addFileKEvent(self, file_path, value),
920 .linux => return addFileLinux(self, file_path, value),
921 .windows => return addFileWindows(self, file_path, value),
922 else => @compileError("Unsupported OS"),
923 }
924 }
925
926 fn addFileKEvent(self: *Self, file_path: []const u8, value: V) !?V {
927 const resolved_path = try std.fs.path.resolve(self.allocator, [_][]const u8{file_path});
928 var resolved_path_consumed = false;
929 defer if (!resolved_path_consumed) self.allocator.free(resolved_path);
930
931 var close_op = try CloseOperation.start(self.allocator);
932 var close_op_consumed = false;
933 defer if (!close_op_consumed) close_op.finish();
934
935 const flags = if (comptime std.Target.current.isDarwin()) os.O_SYMLINK | os.O_EVTONLY else 0;
936 const mode = 0;
937 const fd = try openPosix(self.allocator, resolved_path, flags, mode);
938 close_op.setHandle(fd);
939
940 var put = try self.allocator.create(OsData.Put);
941 errdefer self.allocator.destroy(put);
942 put.* = OsData.Put{
943 .value = value,
944 .putter_frame = undefined,
945 };
946 put.putter_frame = async self.kqPutEvents(close_op, put);
947 close_op_consumed = true;
948 errdefer {
949 put.cancelled = true;
950 await put.putter_frame;
951 }
952
953 const result = blk: {
954 const held = self.os_data.table_lock.acquire();
955 defer held.release();
956
957 const gop = try self.os_data.file_table.getOrPut(resolved_path);
958 if (gop.found_existing) {
959 const prev_value = gop.kv.value.value;
960 await gop.kv.value.putter_frame;
961 gop.kv.value = put;
962 break :blk prev_value;
963 } else {
964 resolved_path_consumed = true;
965 gop.kv.value = put;
966 break :blk null;
967 }
968 };
969
970 return result;
971 }
972
973 fn kqPutEvents(self: *Self, close_op: *CloseOperation, put: *OsData.Put) void {
974 global_event_loop.beginOneEvent();
975
976 defer {
977 close_op.finish();
978 global_event_loop.finishOneEvent();
979 }
980
981 while (!put.cancelled) {
982 if (global_event_loop.bsdWaitKev(
983 @intCast(usize, close_op.getHandle()),
984 os.EVFILT_VNODE,
985 os.NOTE_WRITE | os.NOTE_DELETE,
986 )) |kev| {
987 // TODO handle EV_ERROR
988 if (kev.fflags & os.NOTE_DELETE != 0) {
989 self.channel.put(Self.Event{
990 .id = Event.Id.Delete,
991 .data = put.value,
992 });
993 } else if (kev.fflags & os.NOTE_WRITE != 0) {
994 self.channel.put(Self.Event{
995 .id = Event.Id.CloseWrite,
996 .data = put.value,
997 });
998 }
999 } else |err| switch (err) {
1000 error.EventNotFound => unreachable,
1001 error.ProcessNotFound => unreachable,
1002 error.Overflow => unreachable,
1003 error.AccessDenied, error.SystemResources => |casted_err| {
1004 self.channel.put(casted_err);
1005 },
1006 }
1007 }
1008 }
1009
1010 fn addFileLinux(self: *Self, file_path: []const u8, value: V) !?V {
1011 const dirname = std.fs.path.dirname(file_path) orelse ".";
1012 const dirname_with_null = try std.cstr.addNullByte(self.allocator, dirname);
1013 var dirname_with_null_consumed = false;
1014 defer if (!dirname_with_null_consumed) self.channel.free(dirname_with_null);
1015
1016 const basename = std.fs.path.basename(file_path);
1017 const basename_with_null = try std.cstr.addNullByte(self.allocator, basename);
1018 var basename_with_null_consumed = false;
1019 defer if (!basename_with_null_consumed) self.allocator.free(basename_with_null);
1020
1021 const wd = try os.inotify_add_watchC(
1022 self.os_data.inotify_fd,
1023 dirname_with_null.ptr,
1024 os.linux.IN_CLOSE_WRITE | os.linux.IN_ONLYDIR | os.linux.IN_EXCL_UNLINK,
1025 );
1026 // wd is either a newly created watch or an existing one.
1027
1028 const held = self.os_data.table_lock.acquire();
1029 defer held.release();
1030
1031 const gop = try self.os_data.wd_table.getOrPut(wd);
1032 if (!gop.found_existing) {
1033 gop.kv.value = OsData.Dir{
1034 .dirname = dirname_with_null,
1035 .file_table = OsData.FileTable.init(self.allocator),
1036 };
1037 dirname_with_null_consumed = true;
1038 }
1039 const dir = &gop.kv.value;
1040
1041 const file_table_gop = try dir.file_table.getOrPut(basename_with_null);
1042 if (file_table_gop.found_existing) {
1043 const prev_value = file_table_gop.kv.value;
1044 file_table_gop.kv.value = value;
1045 return prev_value;
1046 } else {
1047 file_table_gop.kv.value = value;
1048 basename_with_null_consumed = true;
1049 return null;
1050 }
1051 }
1052
1053 fn addFileWindows(self: *Self, file_path: []const u8, value: V) !?V {
1054 // TODO we might need to convert dirname and basename to canonical file paths ("short"?)
1055 const dirname = try std.mem.dupe(self.allocator, u8, std.fs.path.dirname(file_path) orelse ".");
1056 var dirname_consumed = false;
1057 defer if (!dirname_consumed) self.allocator.free(dirname);
1058
1059 const dirname_utf16le = try std.unicode.utf8ToUtf16LeWithNull(self.allocator, dirname);
1060 defer self.allocator.free(dirname_utf16le);
1061
1062 // TODO https://github.com/ziglang/zig/issues/265
1063 const basename = std.fs.path.basename(file_path);
1064 const basename_utf16le_null = try std.unicode.utf8ToUtf16LeWithNull(self.allocator, basename);
1065 var basename_utf16le_null_consumed = false;
1066 defer if (!basename_utf16le_null_consumed) self.allocator.free(basename_utf16le_null);
1067 const basename_utf16le_no_null = basename_utf16le_null[0 .. basename_utf16le_null.len - 1];
1068
1069 const dir_handle = try windows.CreateFileW(
1070 dirname_utf16le.ptr,
1071 windows.FILE_LIST_DIRECTORY,
1072 windows.FILE_SHARE_READ | windows.FILE_SHARE_DELETE | windows.FILE_SHARE_WRITE,
1073 null,
1074 windows.OPEN_EXISTING,
1075 windows.FILE_FLAG_BACKUP_SEMANTICS | windows.FILE_FLAG_OVERLAPPED,
1076 null,
1077 );
1078 var dir_handle_consumed = false;
1079 defer if (!dir_handle_consumed) windows.CloseHandle(dir_handle);
1080
1081 const held = self.os_data.table_lock.acquire();
1082 defer held.release();
1083
1084 const gop = try self.os_data.dir_table.getOrPut(dirname);
1085 if (gop.found_existing) {
1086 const dir = gop.kv.value;
1087 const held_dir_lock = dir.table_lock.acquire();
1088 defer held_dir_lock.release();
1089
1090 const file_gop = try dir.file_table.getOrPut(basename_utf16le_no_null);
1091 if (file_gop.found_existing) {
1092 const prev_value = file_gop.kv.value;
1093 file_gop.kv.value = value;
1094 return prev_value;
1095 } else {
1096 file_gop.kv.value = value;
1097 basename_utf16le_null_consumed = true;
1098 return null;
1099 }
1100 } else {
1101 errdefer _ = self.os_data.dir_table.remove(dirname);
1102 const dir = try self.allocator.create(OsData.Dir);
1103 errdefer self.allocator.destroy(dir);
1104
1105 dir.* = OsData.Dir{
1106 .file_table = OsData.FileTable.init(self.allocator),
1107 .table_lock = event.Lock.init(),
1108 .putter_frame = undefined,
1109 };
1110 gop.kv.value = dir;
1111 assert((try dir.file_table.put(basename_utf16le_no_null, value)) == null);
1112 basename_utf16le_null_consumed = true;
1113
1114 dir.putter_frame = async self.windowsDirReader(dir_handle, dir);
1115 dir_handle_consumed = true;
1116
1117 dirname_consumed = true;
1118
1119 return null;
1120 }
1121 }
1122
1123 fn windowsDirReader(self: *Self, dir_handle: windows.HANDLE, dir: *OsData.Dir) void {
1124 self.ref();
1125 defer self.deref();
1126
1127 defer os.close(dir_handle);
1128
1129 var putter_node = std.atomic.Queue(anyframe).Node{
1130 .data = .{ .putter = @frame() },
1131 .prev = null,
1132 .next = null,
1133 };
1134 self.os_data.all_putters.put(&putter_node);
1135 defer _ = self.os_data.all_putters.remove(&putter_node);
1136
1137 var resume_node = Loop.ResumeNode.Basic{
1138 .base = Loop.ResumeNode{
1139 .id = Loop.ResumeNode.Id.Basic,
1140 .handle = @frame(),
1141 .overlapped = windows.OVERLAPPED{
1142 .Internal = 0,
1143 .InternalHigh = 0,
1144 .Offset = 0,
1145 .OffsetHigh = 0,
1146 .hEvent = null,
1147 },
1148 },
1149 };
1150 var event_buf: [4096]u8 align(@alignOf(windows.FILE_NOTIFY_INFORMATION)) = undefined;
1151
1152 // TODO handle this error not in the channel but in the setup
1153 _ = windows.CreateIoCompletionPort(
1154 dir_handle,
1155 global_event_loop.os_data.io_port,
1156 undefined,
1157 undefined,
1158 ) catch |err| {
1159 self.channel.put(err);
1160 return;
1161 };
1162
1163 while (!putter_node.data.cancelled) {
1164 {
1165 // TODO only 1 beginOneEvent for the whole function
1166 global_event_loop.beginOneEvent();
1167 errdefer global_event_loop.finishOneEvent();
1168 errdefer {
1169 _ = windows.kernel32.CancelIoEx(dir_handle, &resume_node.base.overlapped);
1170 }
1171 suspend {
1172 _ = windows.kernel32.ReadDirectoryChangesW(
1173 dir_handle,
1174 &event_buf,
1175 @intCast(windows.DWORD, event_buf.len),
1176 windows.FALSE, // watch subtree
1177 windows.FILE_NOTIFY_CHANGE_FILE_NAME | windows.FILE_NOTIFY_CHANGE_DIR_NAME |
1178 windows.FILE_NOTIFY_CHANGE_ATTRIBUTES | windows.FILE_NOTIFY_CHANGE_SIZE |
1179 windows.FILE_NOTIFY_CHANGE_LAST_WRITE | windows.FILE_NOTIFY_CHANGE_LAST_ACCESS |
1180 windows.FILE_NOTIFY_CHANGE_CREATION | windows.FILE_NOTIFY_CHANGE_SECURITY,
1181 null, // number of bytes transferred (unused for async)
1182 &resume_node.base.overlapped,
1183 null, // completion routine - unused because we use IOCP
1184 );
1185 }
1186 }
1187 var bytes_transferred: windows.DWORD = undefined;
1188 if (windows.kernel32.GetOverlappedResult(dir_handle, &resume_node.base.overlapped, &bytes_transferred, windows.FALSE) == 0) {
1189 const err = switch (windows.kernel32.GetLastError()) {
1190 else => |err| windows.unexpectedError(err),
1191 };
1192 self.channel.put(err);
1193 } else {
1194 // can't use @bytesToSlice because of the special variable length name field
1195 var ptr = event_buf[0..].ptr;
1196 const end_ptr = ptr + bytes_transferred;
1197 var ev: *windows.FILE_NOTIFY_INFORMATION = undefined;
1198 while (@ptrToInt(ptr) < @ptrToInt(end_ptr)) : (ptr += ev.NextEntryOffset) {
1199 ev = @ptrCast(*windows.FILE_NOTIFY_INFORMATION, ptr);
1200 const emit = switch (ev.Action) {
1201 windows.FILE_ACTION_REMOVED => WatchEventId.Delete,
1202 windows.FILE_ACTION_MODIFIED => WatchEventId.CloseWrite,
1203 else => null,
1204 };
1205 if (emit) |id| {
1206 const basename_utf16le = ([*]u16)(&ev.FileName)[0 .. ev.FileNameLength / 2];
1207 const user_value = blk: {
1208 const held = dir.table_lock.acquire();
1209 defer held.release();
1210
1211 if (dir.file_table.get(basename_utf16le)) |entry| {
1212 break :blk entry.value;
1213 } else {
1214 break :blk null;
1215 }
1216 };
1217 if (user_value) |v| {
1218 self.channel.put(Event{
1219 .id = id,
1220 .data = v,
1221 });
1222 }
1223 }
1224 if (ev.NextEntryOffset == 0) break;
1225 }
1226 }
1227 }
1228 }
1229
1230 pub fn removeFile(self: *Self, file_path: []const u8) ?V {
1231 @panic("TODO");
1232 }
1233
1234 fn linuxEventPutter(self: *Self) void {
1235 global_event_loop.beginOneEvent();
1236
1237 defer {
1238 self.os_data.table_lock.deinit();
1239 var wd_it = self.os_data.wd_table.iterator();
1240 while (wd_it.next()) |wd_entry| {
1241 var file_it = wd_entry.value.file_table.iterator();
1242 while (file_it.next()) |file_entry| {
1243 self.allocator.free(file_entry.key);
1244 }
1245 self.allocator.free(wd_entry.value.dirname);
1246 wd_entry.value.file_table.deinit();
1247 }
1248 self.os_data.wd_table.deinit();
1249 global_event_loop.finishOneEvent();
1250 os.close(self.os_data.inotify_fd);
1251 self.channel.deinit();
1252 self.allocator.free(self.channel.buffer_nodes);
1253 }
1254
1255 var event_buf: [4096]u8 align(@alignOf(os.linux.inotify_event)) = undefined;
1256
1257 while (!self.os_data.cancelled) {
1258 const rc = os.linux.read(self.os_data.inotify_fd, &event_buf, event_buf.len);
1259 const errno = os.linux.getErrno(rc);
1260 switch (errno) {
1261 0 => {
1262 // can't use @bytesToSlice because of the special variable length name field
1263 var ptr = event_buf[0..].ptr;
1264 const end_ptr = ptr + event_buf.len;
1265 var ev: *os.linux.inotify_event = undefined;
1266 while (@ptrToInt(ptr) < @ptrToInt(end_ptr)) {
1267 ev = @ptrCast(*os.linux.inotify_event, ptr);
1268 if (ev.mask & os.linux.IN_CLOSE_WRITE == os.linux.IN_CLOSE_WRITE) {
1269 const basename_ptr = ptr + @sizeOf(os.linux.inotify_event);
1270 // `ev.len` counts all bytes in `ev.name` including terminating null byte.
1271 const basename_with_null = basename_ptr[0..ev.len];
1272 const user_value = blk: {
1273 const held = self.os_data.table_lock.acquire();
1274 defer held.release();
1275
1276 const dir = &self.os_data.wd_table.get(ev.wd).?.value;
1277 if (dir.file_table.get(basename_with_null)) |entry| {
1278 break :blk entry.value;
1279 } else {
1280 break :blk null;
1281 }
1282 };
1283 if (user_value) |v| {
1284 self.channel.put(Event{
1285 .id = WatchEventId.CloseWrite,
1286 .data = v,
1287 });
1288 }
1289 }
1290
1291 ptr = @alignCast(@alignOf(os.linux.inotify_event), ptr + @sizeOf(os.linux.inotify_event) + ev.len);
1292 }
1293 },
1294 os.linux.EINTR => continue,
1295 os.linux.EINVAL => unreachable,
1296 os.linux.EFAULT => unreachable,
1297 os.linux.EAGAIN => {
1298 global_event_loop.linuxWaitFd(self.os_data.inotify_fd, os.linux.EPOLLET | os.linux.EPOLLIN | os.EPOLLONESHOT);
1299 },
1300 else => unreachable,
1301 }
1302 }
1303 }
1304 };
1305}
1306
1307const test_tmp_dir = "std_event_fs_test";
1308
1309test "write a file, watch it, write it again" {
1310 // TODO provide a way to run tests in evented I/O mode
1311 if (!std.io.is_async) return error.SkipZigTest;
1312
1313 const allocator = std.heap.page_allocator;
1314
1315 // TODO move this into event loop too
1316 try os.makePath(allocator, test_tmp_dir);
1317 defer os.deleteTree(test_tmp_dir) catch {};
1318
1319 return testFsWatch(&allocator);
1320}
1321
1322fn testFsWatch(allocator: *Allocator) !void {
1323 const file_path = try std.fs.path.join(allocator, [_][]const u8{ test_tmp_dir, "file.txt" });
1324 defer allocator.free(file_path);
1325
1326 const contents =
1327 \\line 1
1328 \\line 2
1329 ;
1330 const line2_offset = 7;
1331
1332 // first just write then read the file
1333 try writeFile(allocator, file_path, contents);
1334
1335 const read_contents = try readFile(allocator, file_path, 1024 * 1024);
1336 testing.expectEqualSlices(u8, contents, read_contents);
1337
1338 // now watch the file
1339 var watch = try Watch(void).init(allocator, 0);
1340 defer watch.deinit();
1341
1342 testing.expect((try watch.addFile(file_path, {})) == null);
1343
1344 const ev = watch.channel.get();
1345 var ev_consumed = false;
1346 defer if (!ev_consumed) await ev;
1347
1348 // overwrite line 2
1349 const fd = try await openReadWrite(file_path, File.default_mode);
1350 {
1351 defer os.close(fd);
1352
1353 try pwritev(allocator, fd, []const []const u8{"lorem ipsum"}, line2_offset);
1354 }
1355
1356 ev_consumed = true;
1357 switch ((try await ev).id) {
1358 WatchEventId.CloseWrite => {},
1359 WatchEventId.Delete => @panic("wrong event"),
1360 }
1361 const contents_updated = try readFile(allocator, file_path, 1024 * 1024);
1362 testing.expectEqualSlices(u8,
1363 \\line 1
1364 \\lorem ipsum
1365 , contents_updated);
1366
1367 // TODO test deleting the file and then re-adding it. we should get events for both
1368}
1369
1370pub const OutStream = struct {
1371 fd: fd_t,
1372 stream: Stream,
1373 allocator: *Allocator,
1374 offset: usize,
1375
1376 pub const Error = File.WriteError;
1377 pub const Stream = event.io.OutStream(Error);
1378
1379 pub fn init(allocator: *Allocator, fd: fd_t, offset: usize) OutStream {
1380 return OutStream{
1381 .fd = fd,
1382 .offset = offset,
1383 .stream = Stream{ .writeFn = writeFn },
1384 };
1385 }
1386
1387 fn writeFn(out_stream: *Stream, bytes: []const u8) Error!void {
1388 const self = @fieldParentPtr(OutStream, "stream", out_stream);
1389 const offset = self.offset;
1390 self.offset += bytes.len;
1391 return pwritev(self.allocator, self.fd, [_][]const u8{bytes}, offset);
1392 }
1393};
1394
1395pub const InStream = struct {
1396 fd: fd_t,
1397 stream: Stream,
1398 allocator: *Allocator,
1399 offset: usize,
1400
1401 pub const Error = PReadVError; // TODO make this not have OutOfMemory
1402 pub const Stream = event.io.InStream(Error);
1403
1404 pub fn init(allocator: *Allocator, fd: fd_t, offset: usize) InStream {
1405 return InStream{
1406 .fd = fd,
1407 .offset = offset,
1408 .stream = Stream{ .readFn = readFn },
1409 };
1410 }
1411
1412 fn readFn(in_stream: *Stream, bytes: []u8) Error!usize {
1413 const self = @fieldParentPtr(InStream, "stream", in_stream);
1414 const amt = try preadv(self.allocator, self.fd, [_][]u8{bytes}, self.offset);
1415 self.offset += amt;
1416 return amt;
1417 }
1418};
lib/std/event/group.zig+1-1
......@@ -22,7 +22,7 @@ pub fn Group(comptime ReturnType: type) type {
2222 const AllocStack = std.atomic.Stack(Node);
2323
2424 pub const Node = struct {
25 bytes: []const u8 = [0]u8{},
25 bytes: []const u8 = &[0]u8{},
2626 handle: anyframe->ReturnType,
2727 };
2828
lib/std/event/lock.zig+4-4
......@@ -117,21 +117,21 @@ pub const Lock = struct {
117117};
118118
119119test "std.event.Lock" {
120 if (!std.io.is_async) return error.SkipZigTest;
121
120122 // TODO https://github.com/ziglang/zig/issues/1908
121123 if (builtin.single_threaded) return error.SkipZigTest;
122124
123125 // TODO https://github.com/ziglang/zig/issues/3251
124126 if (builtin.os == .freebsd) return error.SkipZigTest;
125127
126 // TODO provide a way to run tests in evented I/O mode
127 if (!std.io.is_async) return error.SkipZigTest;
128
129128 var lock = Lock.init();
130129 defer lock.deinit();
131130
132131 _ = async testLock(&lock);
133132
134 testing.expectEqualSlices(i32, [1]i32{3 * @intCast(i32, shared_test_data.len)} ** shared_test_data.len, shared_test_data);
133 const expected_result = [1]i32{3 * @intCast(i32, shared_test_data.len)} ** shared_test_data.len;
134 testing.expectEqualSlices(i32, &expected_result, &shared_test_data);
135135}
136136
137137async fn testLock(lock: *Lock) void {
lib/std/event/loop.zig+317-50
......@@ -6,7 +6,6 @@ const testing = std.testing;
66const mem = std.mem;
77const AtomicRmwOp = builtin.AtomicRmwOp;
88const AtomicOrder = builtin.AtomicOrder;
9const fs = std.event.fs;
109const os = std.os;
1110const windows = os.windows;
1211const maxInt = std.math.maxInt;
......@@ -174,21 +173,19 @@ pub const Loop = struct {
174173 fn initOsData(self: *Loop, extra_thread_count: usize) InitOsDataError!void {
175174 switch (builtin.os) {
176175 .linux => {
177 self.os_data.fs_queue = std.atomic.Queue(fs.Request).init();
176 self.os_data.fs_queue = std.atomic.Queue(Request).init();
178177 self.os_data.fs_queue_item = 0;
179178 // we need another thread for the file system because Linux does not have an async
180179 // file system I/O API.
181 self.os_data.fs_end_request = fs.RequestNode{
182 .prev = undefined,
183 .next = undefined,
184 .data = fs.Request{
185 .msg = fs.Request.Msg.End,
186 .finish = fs.Request.Finish.NoAction,
180 self.os_data.fs_end_request = Request.Node{
181 .data = Request{
182 .msg = .end,
183 .finish = .NoAction,
187184 },
188185 };
189186
190187 errdefer {
191 while (self.available_eventfd_resume_nodes.pop()) |node| os.close(node.data.eventfd);
188 while (self.available_eventfd_resume_nodes.pop()) |node| noasync os.close(node.data.eventfd);
192189 }
193190 for (self.eventfd_resume_nodes) |*eventfd_node| {
194191 eventfd_node.* = std.atomic.Stack(ResumeNode.EventFd).Node{
......@@ -207,10 +204,10 @@ pub const Loop = struct {
207204 }
208205
209206 self.os_data.epollfd = try os.epoll_create1(os.EPOLL_CLOEXEC);
210 errdefer os.close(self.os_data.epollfd);
207 errdefer noasync os.close(self.os_data.epollfd);
211208
212209 self.os_data.final_eventfd = try os.eventfd(0, os.EFD_CLOEXEC | os.EFD_NONBLOCK);
213 errdefer os.close(self.os_data.final_eventfd);
210 errdefer noasync os.close(self.os_data.final_eventfd);
214211
215212 self.os_data.final_eventfd_event = os.epoll_event{
216213 .events = os.EPOLLIN,
......@@ -237,7 +234,7 @@ pub const Loop = struct {
237234 var extra_thread_index: usize = 0;
238235 errdefer {
239236 // writing 8 bytes to an eventfd cannot fail
240 os.write(self.os_data.final_eventfd, &wakeup_bytes) catch unreachable;
237 noasync os.write(self.os_data.final_eventfd, &wakeup_bytes) catch unreachable;
241238 while (extra_thread_index != 0) {
242239 extra_thread_index -= 1;
243240 self.extra_threads[extra_thread_index].wait();
......@@ -249,20 +246,20 @@ pub const Loop = struct {
249246 },
250247 .macosx, .freebsd, .netbsd, .dragonfly => {
251248 self.os_data.kqfd = try os.kqueue();
252 errdefer os.close(self.os_data.kqfd);
249 errdefer noasync os.close(self.os_data.kqfd);
253250
254251 self.os_data.fs_kqfd = try os.kqueue();
255 errdefer os.close(self.os_data.fs_kqfd);
252 errdefer noasync os.close(self.os_data.fs_kqfd);
256253
257 self.os_data.fs_queue = std.atomic.Queue(fs.Request).init();
254 self.os_data.fs_queue = std.atomic.Queue(Request).init();
258255 // we need another thread for the file system because Darwin does not have an async
259256 // file system I/O API.
260 self.os_data.fs_end_request = fs.RequestNode{
257 self.os_data.fs_end_request = Request.Node{
261258 .prev = undefined,
262259 .next = undefined,
263 .data = fs.Request{
264 .msg = fs.Request.Msg.End,
265 .finish = fs.Request.Finish.NoAction,
260 .data = Request{
261 .msg = .end,
262 .finish = .NoAction,
266263 },
267264 };
268265
......@@ -407,14 +404,14 @@ pub const Loop = struct {
407404 fn deinitOsData(self: *Loop) void {
408405 switch (builtin.os) {
409406 .linux => {
410 os.close(self.os_data.final_eventfd);
411 while (self.available_eventfd_resume_nodes.pop()) |node| os.close(node.data.eventfd);
412 os.close(self.os_data.epollfd);
407 noasync os.close(self.os_data.final_eventfd);
408 while (self.available_eventfd_resume_nodes.pop()) |node| noasync os.close(node.data.eventfd);
409 noasync os.close(self.os_data.epollfd);
413410 self.allocator.free(self.eventfd_resume_nodes);
414411 },
415412 .macosx, .freebsd, .netbsd, .dragonfly => {
416 os.close(self.os_data.kqfd);
417 os.close(self.os_data.fs_kqfd);
413 noasync os.close(self.os_data.kqfd);
414 noasync os.close(self.os_data.fs_kqfd);
418415 },
419416 .windows => {
420417 windows.CloseHandle(self.os_data.io_port);
......@@ -711,6 +708,190 @@ pub const Loop = struct {
711708 }
712709 }
713710
711 /// Performs an async `os.open` using a separate thread.
712 pub fn openZ(self: *Loop, file_path: [*:0]const u8, flags: u32, mode: usize) os.OpenError!os.fd_t {
713 var req_node = Request.Node{
714 .data = .{
715 .msg = .{
716 .open = .{
717 .path = file_path,
718 .flags = flags,
719 .mode = mode,
720 .result = undefined,
721 },
722 },
723 .finish = .{ .TickNode = .{ .data = @frame() } },
724 },
725 };
726 suspend {
727 self.posixFsRequest(&req_node);
728 }
729 return req_node.data.msg.open.result;
730 }
731
732 /// Performs an async `os.opent` using a separate thread.
733 pub fn openatZ(self: *Loop, fd: os.fd_t, file_path: [*:0]const u8, flags: u32, mode: usize) os.OpenError!os.fd_t {
734 var req_node = Request.Node{
735 .data = .{
736 .msg = .{
737 .openat = .{
738 .fd = fd,
739 .path = file_path,
740 .flags = flags,
741 .mode = mode,
742 .result = undefined,
743 },
744 },
745 .finish = .{ .TickNode = .{ .data = @frame() } },
746 },
747 };
748 suspend {
749 self.posixFsRequest(&req_node);
750 }
751 return req_node.data.msg.openat.result;
752 }
753
754 /// Performs an async `os.close` using a separate thread.
755 pub fn close(self: *Loop, fd: os.fd_t) void {
756 var req_node = Request.Node{
757 .data = .{
758 .msg = .{ .close = .{ .fd = fd } },
759 .finish = .{ .TickNode = .{ .data = @frame() } },
760 },
761 };
762 suspend {
763 self.posixFsRequest(&req_node);
764 }
765 }
766
767 /// Performs an async `os.read` using a separate thread.
768 /// `fd` must block and not return EAGAIN.
769 pub fn read(self: *Loop, fd: os.fd_t, buf: []u8) os.ReadError!usize {
770 var req_node = Request.Node{
771 .data = .{
772 .msg = .{
773 .read = .{
774 .fd = fd,
775 .buf = buf,
776 .result = undefined,
777 },
778 },
779 .finish = .{ .TickNode = .{ .data = @frame() } },
780 },
781 };
782 suspend {
783 self.posixFsRequest(&req_node);
784 }
785 return req_node.data.msg.read.result;
786 }
787
788 /// Performs an async `os.readv` using a separate thread.
789 /// `fd` must block and not return EAGAIN.
790 pub fn readv(self: *Loop, fd: os.fd_t, iov: []const os.iovec) os.ReadError!usize {
791 var req_node = Request.Node{
792 .data = .{
793 .msg = .{
794 .readv = .{
795 .fd = fd,
796 .iov = iov,
797 .result = undefined,
798 },
799 },
800 .finish = .{ .TickNode = .{ .data = @frame() } },
801 },
802 };
803 suspend {
804 self.posixFsRequest(&req_node);
805 }
806 return req_node.data.msg.readv.result;
807 }
808
809 /// Performs an async `os.preadv` using a separate thread.
810 /// `fd` must block and not return EAGAIN.
811 pub fn preadv(self: *Loop, fd: os.fd_t, iov: []const os.iovec, offset: u64) os.ReadError!usize {
812 var req_node = Request.Node{
813 .data = .{
814 .msg = .{
815 .preadv = .{
816 .fd = fd,
817 .iov = iov,
818 .offset = offset,
819 .result = undefined,
820 },
821 },
822 .finish = .{ .TickNode = .{ .data = @frame() } },
823 },
824 };
825 suspend {
826 self.posixFsRequest(&req_node);
827 }
828 return req_node.data.msg.preadv.result;
829 }
830
831 /// Performs an async `os.write` using a separate thread.
832 /// `fd` must block and not return EAGAIN.
833 pub fn write(self: *Loop, fd: os.fd_t, bytes: []const u8) os.WriteError!void {
834 var req_node = Request.Node{
835 .data = .{
836 .msg = .{
837 .write = .{
838 .fd = fd,
839 .bytes = bytes,
840 .result = undefined,
841 },
842 },
843 .finish = .{ .TickNode = .{ .data = @frame() } },
844 },
845 };
846 suspend {
847 self.posixFsRequest(&req_node);
848 }
849 return req_node.data.msg.write.result;
850 }
851
852 /// Performs an async `os.writev` using a separate thread.
853 /// `fd` must block and not return EAGAIN.
854 pub fn writev(self: *Loop, fd: os.fd_t, iov: []const os.iovec_const) os.WriteError!void {
855 var req_node = Request.Node{
856 .data = .{
857 .msg = .{
858 .writev = .{
859 .fd = fd,
860 .iov = iov,
861 .result = undefined,
862 },
863 },
864 .finish = .{ .TickNode = .{ .data = @frame() } },
865 },
866 };
867 suspend {
868 self.posixFsRequest(&req_node);
869 }
870 return req_node.data.msg.writev.result;
871 }
872
873 /// Performs an async `os.pwritev` using a separate thread.
874 /// `fd` must block and not return EAGAIN.
875 pub fn pwritev(self: *Loop, fd: os.fd_t, iov: []const os.iovec_const, offset: u64) os.WriteError!void {
876 var req_node = Request.Node{
877 .data = .{
878 .msg = .{
879 .pwritev = .{
880 .fd = fd,
881 .iov = iov,
882 .offset = offset,
883 .result = undefined,
884 },
885 },
886 .finish = .{ .TickNode = .{ .data = @frame() } },
887 },
888 };
889 suspend {
890 self.posixFsRequest(&req_node);
891 }
892 return req_node.data.msg.pwritev.result;
893 }
894
714895 fn workerRun(self: *Loop) void {
715896 while (true) {
716897 while (true) {
......@@ -804,7 +985,7 @@ pub const Loop = struct {
804985 }
805986 }
806987
807 fn posixFsRequest(self: *Loop, request_node: *fs.RequestNode) void {
988 fn posixFsRequest(self: *Loop, request_node: *Request.Node) void {
808989 self.beginOneEvent(); // finished in posixFsRun after processing the msg
809990 self.os_data.fs_queue.put(request_node);
810991 switch (builtin.os) {
......@@ -826,7 +1007,7 @@ pub const Loop = struct {
8261007 }
8271008 }
8281009
829 fn posixFsCancel(self: *Loop, request_node: *fs.RequestNode) void {
1010 fn posixFsCancel(self: *Loop, request_node: *Request.Node) void {
8301011 if (self.os_data.fs_queue.remove(request_node)) {
8311012 self.finishOneEvent();
8321013 }
......@@ -841,37 +1022,32 @@ pub const Loop = struct {
8411022 }
8421023 while (self.os_data.fs_queue.get()) |node| {
8431024 switch (node.data.msg) {
844 .End => return,
845 .WriteV => |*msg| {
1025 .end => return,
1026 .read => |*msg| {
1027 msg.result = noasync os.read(msg.fd, msg.buf);
1028 },
1029 .write => |*msg| {
1030 msg.result = noasync os.write(msg.fd, msg.bytes);
1031 },
1032 .writev => |*msg| {
8461033 msg.result = noasync os.writev(msg.fd, msg.iov);
8471034 },
848 .PWriteV => |*msg| {
1035 .pwritev => |*msg| {
8491036 msg.result = noasync os.pwritev(msg.fd, msg.iov, msg.offset);
8501037 },
851 .PReadV => |*msg| {
1038 .preadv => |*msg| {
8521039 msg.result = noasync os.preadv(msg.fd, msg.iov, msg.offset);
8531040 },
854 .Open => |*msg| {
855 msg.result = noasync os.openC(msg.path.ptr, msg.flags, msg.mode);
1041 .open => |*msg| {
1042 msg.result = noasync os.openC(msg.path, msg.flags, msg.mode);
8561043 },
857 .Close => |*msg| noasync os.close(msg.fd),
858 .WriteFile => |*msg| blk: {
859 const O_LARGEFILE = if (@hasDecl(os, "O_LARGEFILE")) os.O_LARGEFILE else 0;
860 const flags = O_LARGEFILE | os.O_WRONLY | os.O_CREAT |
861 os.O_CLOEXEC | os.O_TRUNC;
862 const fd = noasync os.openC(msg.path.ptr, flags, msg.mode) catch |err| {
863 msg.result = err;
864 break :blk;
865 };
866 defer noasync os.close(fd);
867 msg.result = noasync os.write(fd, msg.contents);
1044 .openat => |*msg| {
1045 msg.result = noasync os.openatC(msg.fd, msg.path, msg.flags, msg.mode);
8681046 },
1047 .close => |*msg| noasync os.close(msg.fd),
8691048 }
8701049 switch (node.data.finish) {
8711050 .TickNode => |*tick_node| self.onNextTick(tick_node),
872 .DeallocCloseOperation => |close_op| {
873 self.allocator.destroy(close_op);
874 },
8751051 .NoAction => {},
8761052 }
8771053 self.finishOneEvent();
......@@ -911,8 +1087,8 @@ pub const Loop = struct {
9111087 fs_kevent_wait: os.Kevent,
9121088 fs_thread: *Thread,
9131089 fs_kqfd: i32,
914 fs_queue: std.atomic.Queue(fs.Request),
915 fs_end_request: fs.RequestNode,
1090 fs_queue: std.atomic.Queue(Request),
1091 fs_end_request: Request.Node,
9161092 };
9171093
9181094 const LinuxOsData = struct {
......@@ -921,8 +1097,99 @@ pub const Loop = struct {
9211097 final_eventfd_event: os.linux.epoll_event,
9221098 fs_thread: *Thread,
9231099 fs_queue_item: i32,
924 fs_queue: std.atomic.Queue(fs.Request),
925 fs_end_request: fs.RequestNode,
1100 fs_queue: std.atomic.Queue(Request),
1101 fs_end_request: Request.Node,
1102 };
1103
1104 pub const Request = struct {
1105 msg: Msg,
1106 finish: Finish,
1107
1108 pub const Node = std.atomic.Queue(Request).Node;
1109
1110 pub const Finish = union(enum) {
1111 TickNode: Loop.NextTickNode,
1112 NoAction,
1113 };
1114
1115 pub const Msg = union(enum) {
1116 read: Read,
1117 write: Write,
1118 writev: WriteV,
1119 pwritev: PWriteV,
1120 preadv: PReadV,
1121 open: Open,
1122 openat: OpenAt,
1123 close: Close,
1124
1125 /// special - means the fs thread should exit
1126 end,
1127
1128 pub const Read = struct {
1129 fd: os.fd_t,
1130 buf: []u8,
1131 result: Error!usize,
1132
1133 pub const Error = os.ReadError;
1134 };
1135
1136 pub const Write = struct {
1137 fd: os.fd_t,
1138 bytes: []const u8,
1139 result: Error!void,
1140
1141 pub const Error = os.WriteError;
1142 };
1143
1144 pub const WriteV = struct {
1145 fd: os.fd_t,
1146 iov: []const os.iovec_const,
1147 result: Error!void,
1148
1149 pub const Error = os.WriteError;
1150 };
1151
1152 pub const PWriteV = struct {
1153 fd: os.fd_t,
1154 iov: []const os.iovec_const,
1155 offset: usize,
1156 result: Error!void,
1157
1158 pub const Error = os.WriteError;
1159 };
1160
1161 pub const PReadV = struct {
1162 fd: os.fd_t,
1163 iov: []const os.iovec,
1164 offset: usize,
1165 result: Error!usize,
1166
1167 pub const Error = os.ReadError;
1168 };
1169
1170 pub const Open = struct {
1171 path: [*:0]const u8,
1172 flags: u32,
1173 mode: os.mode_t,
1174 result: Error!os.fd_t,
1175
1176 pub const Error = os.OpenError;
1177 };
1178
1179 pub const OpenAt = struct {
1180 fd: os.fd_t,
1181 path: [*:0]const u8,
1182 flags: u32,
1183 mode: os.mode_t,
1184 result: Error!os.fd_t,
1185
1186 pub const Error = os.OpenError;
1187 };
1188
1189 pub const Close = struct {
1190 fd: os.fd_t,
1191 };
1192 };
9261193 };
9271194};
9281195
lib/std/fmt.zig+16-16
......@@ -78,7 +78,7 @@ fn peekIsAlign(comptime fmt: []const u8) bool {
7878pub fn format(
7979 context: var,
8080 comptime Errors: type,
81 output: fn (@TypeOf(context), []const u8) Errors!void,
81 comptime output: fn (@TypeOf(context), []const u8) Errors!void,
8282 comptime fmt: []const u8,
8383 args: var,
8484) Errors!void {
......@@ -326,7 +326,7 @@ pub fn formatType(
326326 options: FormatOptions,
327327 context: var,
328328 comptime Errors: type,
329 output: fn (@TypeOf(context), []const u8) Errors!void,
329 comptime output: fn (@TypeOf(context), []const u8) Errors!void,
330330 max_depth: usize,
331331) Errors!void {
332332 if (comptime std.mem.eql(u8, fmt, "*")) {
......@@ -488,7 +488,7 @@ fn formatValue(
488488 options: FormatOptions,
489489 context: var,
490490 comptime Errors: type,
491 output: fn (@TypeOf(context), []const u8) Errors!void,
491 comptime output: fn (@TypeOf(context), []const u8) Errors!void,
492492) Errors!void {
493493 if (comptime std.mem.eql(u8, fmt, "B")) {
494494 return formatBytes(value, options, 1000, context, Errors, output);
......@@ -510,7 +510,7 @@ pub fn formatIntValue(
510510 options: FormatOptions,
511511 context: var,
512512 comptime Errors: type,
513 output: fn (@TypeOf(context), []const u8) Errors!void,
513 comptime output: fn (@TypeOf(context), []const u8) Errors!void,
514514) Errors!void {
515515 comptime var radix = 10;
516516 comptime var uppercase = false;
......@@ -552,7 +552,7 @@ fn formatFloatValue(
552552 options: FormatOptions,
553553 context: var,
554554 comptime Errors: type,
555 output: fn (@TypeOf(context), []const u8) Errors!void,
555 comptime output: fn (@TypeOf(context), []const u8) Errors!void,
556556) Errors!void {
557557 if (fmt.len == 0 or comptime std.mem.eql(u8, fmt, "e")) {
558558 return formatFloatScientific(value, options, context, Errors, output);
......@@ -569,7 +569,7 @@ pub fn formatText(
569569 options: FormatOptions,
570570 context: var,
571571 comptime Errors: type,
572 output: fn (@TypeOf(context), []const u8) Errors!void,
572 comptime output: fn (@TypeOf(context), []const u8) Errors!void,
573573) Errors!void {
574574 if (fmt.len == 0) {
575575 return output(context, bytes);
......@@ -590,7 +590,7 @@ pub fn formatAsciiChar(
590590 options: FormatOptions,
591591 context: var,
592592 comptime Errors: type,
593 output: fn (@TypeOf(context), []const u8) Errors!void,
593 comptime output: fn (@TypeOf(context), []const u8) Errors!void,
594594) Errors!void {
595595 return output(context, @as(*const [1]u8, &c)[0..]);
596596}
......@@ -600,7 +600,7 @@ pub fn formatBuf(
600600 options: FormatOptions,
601601 context: var,
602602 comptime Errors: type,
603 output: fn (@TypeOf(context), []const u8) Errors!void,
603 comptime output: fn (@TypeOf(context), []const u8) Errors!void,
604604) Errors!void {
605605 try output(context, buf);
606606
......@@ -620,7 +620,7 @@ pub fn formatFloatScientific(
620620 options: FormatOptions,
621621 context: var,
622622 comptime Errors: type,
623 output: fn (@TypeOf(context), []const u8) Errors!void,
623 comptime output: fn (@TypeOf(context), []const u8) Errors!void,
624624) Errors!void {
625625 var x = @floatCast(f64, value);
626626
......@@ -715,7 +715,7 @@ pub fn formatFloatDecimal(
715715 options: FormatOptions,
716716 context: var,
717717 comptime Errors: type,
718 output: fn (@TypeOf(context), []const u8) Errors!void,
718 comptime output: fn (@TypeOf(context), []const u8) Errors!void,
719719) Errors!void {
720720 var x = @as(f64, value);
721721
......@@ -861,7 +861,7 @@ pub fn formatBytes(
861861 comptime radix: usize,
862862 context: var,
863863 comptime Errors: type,
864 output: fn (@TypeOf(context), []const u8) Errors!void,
864 comptime output: fn (@TypeOf(context), []const u8) Errors!void,
865865) Errors!void {
866866 if (value == 0) {
867867 return output(context, "0B");
......@@ -902,7 +902,7 @@ pub fn formatInt(
902902 options: FormatOptions,
903903 context: var,
904904 comptime Errors: type,
905 output: fn (@TypeOf(context), []const u8) Errors!void,
905 comptime output: fn (@TypeOf(context), []const u8) Errors!void,
906906) Errors!void {
907907 const int_value = if (@TypeOf(value) == comptime_int) blk: {
908908 const Int = math.IntFittingRange(value, value);
......@@ -924,7 +924,7 @@ fn formatIntSigned(
924924 options: FormatOptions,
925925 context: var,
926926 comptime Errors: type,
927 output: fn (@TypeOf(context), []const u8) Errors!void,
927 comptime output: fn (@TypeOf(context), []const u8) Errors!void,
928928) Errors!void {
929929 const new_options = FormatOptions{
930930 .width = if (options.width) |w| (if (w == 0) 0 else w - 1) else null,
......@@ -955,7 +955,7 @@ fn formatIntUnsigned(
955955 options: FormatOptions,
956956 context: var,
957957 comptime Errors: type,
958 output: fn (@TypeOf(context), []const u8) Errors!void,
958 comptime output: fn (@TypeOf(context), []const u8) Errors!void,
959959) Errors!void {
960960 assert(base >= 2);
961961 var buf: [math.max(@TypeOf(value).bit_count, 1)]u8 = undefined;
......@@ -1419,7 +1419,7 @@ test "custom" {
14191419 options: FormatOptions,
14201420 context: var,
14211421 comptime Errors: type,
1422 output: fn (@TypeOf(context), []const u8) Errors!void,
1422 comptime output: fn (@TypeOf(context), []const u8) Errors!void,
14231423 ) Errors!void {
14241424 if (fmt.len == 0 or comptime std.mem.eql(u8, fmt, "p")) {
14251425 return std.fmt.format(context, Errors, output, "({d:.3},{d:.3})", .{ self.x, self.y });
......@@ -1626,7 +1626,7 @@ test "formatType max_depth" {
16261626 options: FormatOptions,
16271627 context: var,
16281628 comptime Errors: type,
1629 output: fn (@TypeOf(context), []const u8) Errors!void,
1629 comptime output: fn (@TypeOf(context), []const u8) Errors!void,
16301630 ) Errors!void {
16311631 if (fmt.len == 0) {
16321632 return std.fmt.format(context, Errors, output, "({d:.3},{d:.3})", .{ self.x, self.y });
lib/std/fs.zig+41-12
......@@ -23,6 +23,8 @@ pub const realpathW = os.realpathW;
2323pub const getAppDataDir = @import("fs/get_app_data_dir.zig").getAppDataDir;
2424pub const GetAppDataDirError = @import("fs/get_app_data_dir.zig").GetAppDataDirError;
2525
26pub const Watch = @import("fs/watch.zig").Watch;
27
2628/// This represents the maximum size of a UTF-8 encoded file path.
2729/// All file system operations which return a path are guaranteed to
2830/// fit into a UTF-8 encoded array of this length.
......@@ -43,6 +45,13 @@ pub const base64_encoder = base64.Base64Encoder.init(
4345 base64.standard_pad_char,
4446);
4547
48/// Whether or not async file system syscalls need a dedicated thread because the operating
49/// system does not support non-blocking I/O on the file system.
50pub const need_async_thread = std.io.is_async and switch (builtin.os) {
51 .windows, .other => false,
52 else => true,
53};
54
4655/// TODO remove the allocator requirement from this API
4756pub fn atomicSymLink(allocator: *Allocator, existing_path: []const u8, new_path: []const u8) !void {
4857 if (symLink(existing_path, new_path)) {
......@@ -688,11 +697,16 @@ pub const Dir = struct {
688697 }
689698
690699 pub fn close(self: *Dir) void {
691 os.close(self.fd);
700 if (need_async_thread) {
701 std.event.Loop.instance.?.close(self.fd);
702 } else {
703 os.close(self.fd);
704 }
692705 self.* = undefined;
693706 }
694707
695708 /// Opens a file for reading or writing, without attempting to create a new file.
709 /// To create a new file, see `createFile`.
696710 /// Call `File.close` to release the resource.
697711 /// Asserts that the path parameter has no null bytes.
698712 pub fn openFile(self: Dir, sub_path: []const u8, flags: File.OpenFlags) File.OpenError!File {
......@@ -718,8 +732,11 @@ pub const Dir = struct {
718732 @as(u32, os.O_WRONLY)
719733 else
720734 @as(u32, os.O_RDONLY);
721 const fd = try os.openatC(self.fd, sub_path, os_flags, 0);
722 return File{ .handle = fd };
735 const fd = if (need_async_thread)
736 try std.event.Loop.instance.?.openatZ(self.fd, sub_path, os_flags, 0)
737 else
738 try os.openatC(self.fd, sub_path, os_flags, 0);
739 return File{ .handle = fd, .io_mode = .blocking };
723740 }
724741
725742 /// Same as `openFile` but Windows-only and the path parameter is
......@@ -756,8 +773,11 @@ pub const Dir = struct {
756773 (if (flags.truncate) @as(u32, os.O_TRUNC) else 0) |
757774 (if (flags.read) @as(u32, os.O_RDWR) else os.O_WRONLY) |
758775 (if (flags.exclusive) @as(u32, os.O_EXCL) else 0);
759 const fd = try os.openatC(self.fd, sub_path_c, os_flags, flags.mode);
760 return File{ .handle = fd };
776 const fd = if (need_async_thread)
777 try std.event.Loop.instance.?.openatZ(self.fd, sub_path_c, os_flags, flags.mode)
778 else
779 try os.openatC(self.fd, sub_path_c, os_flags, flags.mode);
780 return File{ .handle = fd, .io_mode = .blocking };
761781 }
762782
763783 /// Same as `createFile` but Windows-only and the path parameter is
......@@ -798,7 +818,10 @@ pub const Dir = struct {
798818 ) File.OpenError!File {
799819 const w = os.windows;
800820
801 var result = File{ .handle = undefined };
821 var result = File{
822 .handle = undefined,
823 .io_mode = .blocking,
824 };
802825
803826 const path_len_bytes = math.cast(u16, mem.toSliceConst(u16, sub_path_w).len * 2) catch |err| switch (err) {
804827 error.Overflow => return error.NameTooLong,
......@@ -810,7 +833,7 @@ pub const Dir = struct {
810833 };
811834 var attr = w.OBJECT_ATTRIBUTES{
812835 .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,
814837 .Attributes = 0, // Note we do not use OBJ_CASE_INSENSITIVE here.
815838 .ObjectName = &nt_name,
816839 .SecurityDescriptor = null,
......@@ -919,7 +942,12 @@ pub const Dir = struct {
919942 }
920943
921944 fn openDirFlagsC(self: Dir, sub_path_c: [*:0]const u8, flags: u32) OpenError!Dir {
922 const fd = os.openatC(self.fd, sub_path_c, flags | os.O_DIRECTORY, 0) catch |err| switch (err) {
945 const os_flags = flags | os.O_DIRECTORY;
946 const result = if (need_async_thread)
947 std.event.Loop.instance.?.openatZ(self.fd, sub_path_c, os_flags, 0)
948 else
949 os.openatC(self.fd, sub_path_c, os_flags, 0);
950 const fd = result catch |err| switch (err) {
923951 error.FileTooBig => unreachable, // can't happen for directories
924952 error.IsDir => unreachable, // we're providing O_DIRECTORY
925953 error.NoSpaceLeft => unreachable, // not providing O_CREAT
......@@ -960,7 +988,7 @@ pub const Dir = struct {
960988 };
961989 var attr = w.OBJECT_ATTRIBUTES{
962990 .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,
964992 .Attributes = 0, // Note we do not use OBJ_CASE_INSENSITIVE here.
965993 .ObjectName = &nt_name,
966994 .SecurityDescriptor = null,
......@@ -1327,7 +1355,7 @@ pub fn openFileAbsoluteC(absolute_path_c: [*:0]const u8, flags: File.OpenFlags)
13271355
13281356/// Same as `openFileAbsolute` but the path parameter is WTF-16 encoded.
13291357pub 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));
13311359 return cwd().openFileW(absolute_path_w, flags);
13321360}
13331361
......@@ -1350,7 +1378,7 @@ pub fn createFileAbsoluteC(absolute_path_c: [*:0]const u8, flags: File.CreateFla
13501378
13511379/// Same as `createFileAbsolute` but the path parameter is WTF-16 encoded.
13521380pub 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));
13541382 return cwd().createFileW(absolute_path_w, flags);
13551383}
13561384
......@@ -1371,7 +1399,7 @@ pub fn deleteFileAbsoluteC(absolute_path_c: [*:0]const u8) DeleteFileError!void
13711399
13721400/// Same as `deleteFileAbsolute` except the parameter is WTF-16 encoded.
13731401pub fn deleteFileAbsoluteW(absolute_path_w: [*:0]const u16) DeleteFileError!void {
1374 assert(path.isAbsoluteW(absolute_path_w));
1402 assert(path.isAbsoluteWindowsW(absolute_path_w));
13751403 return cwd().deleteFileW(absolute_path_w);
13761404}
13771405
......@@ -1588,4 +1616,5 @@ test "" {
15881616 _ = @import("fs/path.zig");
15891617 _ = @import("fs/file.zig");
15901618 _ = @import("fs/get_app_data_dir.zig");
1619 _ = @import("fs/watch.zig");
15911620}
lib/std/fs/file.zig+78-75
......@@ -8,18 +8,29 @@ const assert = std.debug.assert;
88const windows = os.windows;
99const Os = builtin.Os;
1010const maxInt = std.math.maxInt;
11const need_async_thread = std.fs.need_async_thread;
1112
1213pub const File = struct {
1314 /// The OS-specific file descriptor or file handle.
1415 handle: os.fd_t,
1516
16 pub const Mode = switch (builtin.os) {
17 Os.windows => void,
18 else => u32,
19 };
17 /// On some systems, such as Linux, file system file descriptors are incapable of non-blocking I/O.
18 /// This forces us to perform asynchronous I/O on a dedicated thread, to achieve non-blocking
19 /// file-system I/O. To do this, `File` must be aware of whether it is a file system file descriptor,
20 /// or, more specifically, whether the I/O is blocking.
21 io_mode: io.Mode,
22
23 /// Even when std.io.mode is async, it is still sometimes desirable to perform blocking I/O, although
24 /// not by default. For example, when printing a stack trace to stderr.
25 async_block_allowed: @TypeOf(async_block_allowed_no) = async_block_allowed_no,
26
27 pub const async_block_allowed_yes = if (io.is_async) true else {};
28 pub const async_block_allowed_no = if (io.is_async) false else {};
29
30 pub const Mode = os.mode_t;
2031
2132 pub const default_mode = switch (builtin.os) {
22 Os.windows => {},
33 .windows => 0,
2334 else => 0o666,
2435 };
2536
......@@ -49,87 +60,27 @@ pub const File = struct {
4960 mode: Mode = default_mode,
5061 };
5162
52 /// Deprecated; call `std.fs.Dir.openFile` directly.
53 pub fn openRead(path: []const u8) OpenError!File {
54 return std.fs.cwd().openFile(path, .{});
55 }
56
57 /// Deprecated; call `std.fs.Dir.openFileC` directly.
58 pub fn openReadC(path_c: [*:0]const u8) OpenError!File {
59 return std.fs.cwd().openFileC(path_c, .{});
60 }
61
62 /// Deprecated; call `std.fs.Dir.openFileW` directly.
63 pub fn openReadW(path_w: [*:0]const u16) OpenError!File {
64 return std.fs.cwd().openFileW(path_w, .{});
65 }
66
67 /// Deprecated; call `std.fs.Dir.createFile` directly.
68 pub fn openWrite(path: []const u8) OpenError!File {
69 return std.fs.cwd().createFile(path, .{});
70 }
71
72 /// Deprecated; call `std.fs.Dir.createFile` directly.
73 pub fn openWriteMode(path: []const u8, file_mode: Mode) OpenError!File {
74 return std.fs.cwd().createFile(path, .{ .mode = file_mode });
75 }
76
77 /// Deprecated; call `std.fs.Dir.createFileC` directly.
78 pub fn openWriteModeC(path_c: [*:0]const u8, file_mode: Mode) OpenError!File {
79 return std.fs.cwd().createFileC(path_c, .{ .mode = file_mode });
80 }
81
82 /// Deprecated; call `std.fs.Dir.createFileW` directly.
83 pub fn openWriteModeW(path_w: [*:0]const u16, file_mode: Mode) OpenError!File {
84 return std.fs.cwd().createFileW(path_w, .{ .mode = file_mode });
85 }
86
87 /// Deprecated; call `std.fs.Dir.createFile` directly.
88 pub fn openWriteNoClobber(path: []const u8, file_mode: Mode) OpenError!File {
89 return std.fs.cwd().createFile(path, .{
90 .mode = file_mode,
91 .exclusive = true,
92 });
93 }
94
95 /// Deprecated; call `std.fs.Dir.createFileC` directly.
96 pub fn openWriteNoClobberC(path_c: [*:0]const u8, file_mode: Mode) OpenError!File {
97 return std.fs.cwd().createFileC(path_c, .{
98 .mode = file_mode,
99 .exclusive = true,
100 });
101 }
102
103 /// Deprecated; call `std.fs.Dir.createFileW` directly.
104 pub fn openWriteNoClobberW(path_w: [*:0]const u16, file_mode: Mode) OpenError!File {
105 return std.fs.cwd().createFileW(path_w, .{
106 .mode = file_mode,
107 .exclusive = true,
108 });
109 }
110
111 pub fn openHandle(handle: os.fd_t) File {
112 return File{ .handle = handle };
113 }
114
11563 /// Test for the existence of `path`.
11664 /// `path` is UTF8-encoded.
11765 /// In general it is recommended to avoid this function. For example,
11866 /// instead of testing if a file exists and then opening it, just
11967 /// open it and handle the error for file not found.
12068 /// TODO: deprecate this and move it to `std.fs.Dir`.
69 /// TODO: integrate with async I/O
12170 pub fn access(path: []const u8) !void {
12271 return os.access(path, os.F_OK);
12372 }
12473
12574 /// Same as `access` except the parameter is null-terminated.
12675 /// TODO: deprecate this and move it to `std.fs.Dir`.
76 /// TODO: integrate with async I/O
12777 pub fn accessC(path: [*:0]const u8) !void {
12878 return os.accessC(path, os.F_OK);
12979 }
13080
13181 /// Same as `access` except the parameter is null-terminated UTF16LE-encoded.
13282 /// TODO: deprecate this and move it to `std.fs.Dir`.
83 /// TODO: integrate with async I/O
13384 pub fn accessW(path: [*:0]const u16) !void {
13485 return os.accessW(path, os.F_OK);
13586 }
......@@ -137,7 +88,11 @@ pub const File = struct {
13788 /// Upon success, the stream is in an uninitialized state. To continue using it,
13889 /// you must use the open() function.
13990 pub fn close(self: File) void {
140 return os.close(self.handle);
91 if (need_async_thread and self.io_mode == .blocking and !self.async_block_allowed) {
92 std.event.Loop.instance.?.close(self.handle);
93 } else {
94 return os.close(self.handle);
95 }
14196 }
14297
14398 /// Test whether the file refers to a terminal.
......@@ -167,26 +122,31 @@ pub const File = struct {
167122 pub const SeekError = os.SeekError;
168123
169124 /// Repositions read/write file offset relative to the current offset.
125 /// TODO: integrate with async I/O
170126 pub fn seekBy(self: File, offset: i64) SeekError!void {
171127 return os.lseek_CUR(self.handle, offset);
172128 }
173129
174130 /// Repositions read/write file offset relative to the end.
131 /// TODO: integrate with async I/O
175132 pub fn seekFromEnd(self: File, offset: i64) SeekError!void {
176133 return os.lseek_END(self.handle, offset);
177134 }
178135
179136 /// Repositions read/write file offset relative to the beginning.
137 /// TODO: integrate with async I/O
180138 pub fn seekTo(self: File, offset: u64) SeekError!void {
181139 return os.lseek_SET(self.handle, offset);
182140 }
183141
184142 pub const GetPosError = os.SeekError || os.FStatError;
185143
144 /// TODO: integrate with async I/O
186145 pub fn getPos(self: File) GetPosError!u64 {
187146 return os.lseek_CUR_get(self.handle);
188147 }
189148
149 /// TODO: integrate with async I/O
190150 pub fn getEndPos(self: File) GetPosError!u64 {
191151 if (builtin.os == .windows) {
192152 return windows.GetFileSizeEx(self.handle);
......@@ -196,6 +156,7 @@ pub const File = struct {
196156
197157 pub const ModeError = os.FStatError;
198158
159 /// TODO: integrate with async I/O
199160 pub fn mode(self: File) ModeError!Mode {
200161 if (builtin.os == .windows) {
201162 return {};
......@@ -219,6 +180,7 @@ pub const File = struct {
219180
220181 pub const StatError = os.FStatError;
221182
183 /// TODO: integrate with async I/O
222184 pub fn stat(self: File) StatError!Stat {
223185 if (builtin.os == .windows) {
224186 var io_status_block: windows.IO_STATUS_BLOCK = undefined;
......@@ -233,7 +195,7 @@ pub const File = struct {
233195 }
234196 return Stat{
235197 .size = @bitCast(u64, info.StandardInformation.EndOfFile),
236 .mode = {},
198 .mode = 0,
237199 .atime = windows.fromSysTime(info.BasicInformation.LastAccessTime),
238200 .mtime = windows.fromSysTime(info.BasicInformation.LastWriteTime),
239201 .ctime = windows.fromSysTime(info.BasicInformation.CreationTime),
......@@ -259,6 +221,7 @@ pub const File = struct {
259221 /// and therefore this function cannot guarantee any precision will be stored.
260222 /// Further, the maximum value is limited by the system ABI. When a value is provided
261223 /// that exceeds this range, the value is clamped to the maximum.
224 /// TODO: integrate with async I/O
262225 pub fn updateTimes(
263226 self: File,
264227 /// access timestamp in nanoseconds
......@@ -287,21 +250,61 @@ pub const File = struct {
287250 pub const ReadError = os.ReadError;
288251
289252 pub fn read(self: File, buffer: []u8) ReadError!usize {
253 if (need_async_thread and self.io_mode == .blocking and !self.async_block_allowed) {
254 return std.event.Loop.instance.?.read(self.handle, buffer);
255 }
290256 return os.read(self.handle, buffer);
291257 }
292258
259 pub fn pread(self: File, buffer: []u8, offset: u64) ReadError!usize {
260 if (need_async_thread and self.io_mode == .blocking and !self.async_block_allowed) {
261 return std.event.Loop.instance.?.pread(self.handle, buffer);
262 }
263 return os.pread(self.handle, buffer, offset);
264 }
265
266 pub fn readv(self: File, iovecs: []const os.iovec) ReadError!usize {
267 if (need_async_thread and self.io_mode == .blocking and !self.async_block_allowed) {
268 return std.event.Loop.instance.?.readv(self.handle, iovecs);
269 }
270 return os.readv(self.handle, iovecs);
271 }
272
273 pub fn preadv(self: File, iovecs: []const os.iovec, offset: u64) ReadError!usize {
274 if (need_async_thread and self.io_mode == .blocking and !self.async_block_allowed) {
275 return std.event.Loop.instance.?.preadv(self.handle, iovecs, offset);
276 }
277 return os.preadv(self.handle, iovecs, offset);
278 }
279
293280 pub const WriteError = os.WriteError;
294281
295282 pub fn write(self: File, bytes: []const u8) WriteError!void {
283 if (need_async_thread and self.io_mode == .blocking and !self.async_block_allowed) {
284 return std.event.Loop.instance.?.write(self.handle, bytes);
285 }
296286 return os.write(self.handle, bytes);
297287 }
298288
299 pub fn writev_iovec(self: File, iovecs: []const os.iovec_const) WriteError!void {
300 if (std.event.Loop.instance) |loop| {
301 return std.event.fs.writevPosix(loop, self.handle, iovecs);
302 } else {
303 return os.writev(self.handle, iovecs);
289 pub fn pwrite(self: File, bytes: []const u8, offset: u64) WriteError!void {
290 if (need_async_thread and self.io_mode == .blocking and !self.async_block_allowed) {
291 return std.event.Loop.instance.?.pwrite(self.handle, bytes, offset);
292 }
293 return os.pwrite(self.handle, bytes, offset);
294 }
295
296 pub fn writev(self: File, iovecs: []const os.iovec_const) WriteError!void {
297 if (need_async_thread and self.io_mode == .blocking and !self.async_block_allowed) {
298 return std.event.Loop.instance.?.writev(self.handle, iovecs);
299 }
300 return os.writev(self.handle, iovecs);
301 }
302
303 pub fn pwritev(self: File, iovecs: []const os.iovec_const, offset: usize) WriteError!void {
304 if (need_async_thread and self.io_mode == .blocking and !self.async_block_allowed) {
305 return std.event.Loop.instance.?.pwritev(self.handle, iovecs);
304306 }
307 return os.pwritev(self.handle, iovecs);
305308 }
306309
307310 pub fn inStream(file: File) InStream {
lib/std/fs/path.zig+20-40
......@@ -146,72 +146,51 @@ pub fn isAbsolute(path: []const u8) bool {
146146 }
147147}
148148
149pub 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) {
149fn isAbsoluteWindowsImpl(comptime T: type, path: []const T) bool {
150 if (path.len < 1)
157151 return false;
158 }
159 if (path_w[1] == ':') {
160 if (path_w[2] == '/')
161 return true;
162 if (path_w[2] == '\\')
163 return true;
164 }
165 return false;
166}
167152
168pub fn isAbsoluteWindows(path: []const u8) bool {
169153 if (path[0] == '/')
170154 return true;
171155
172 if (path[0] == '\\') {
156 if (path[0] == '\\')
173157 return true;
174 }
175 if (path.len < 3) {
158
159 if (path.len < 3)
176160 return false;
177 }
161
178162 if (path[1] == ':') {
179163 if (path[2] == '/')
180164 return true;
181165 if (path[2] == '\\')
182166 return true;
183167 }
168
184169 return false;
185170}
186171
187pub fn isAbsoluteWindowsC(path_c: [*:0]const u8) bool {
188 if (path_c[0] == '/')
189 return true;
172pub fn isAbsoluteWindows(path: []const u8) bool {
173 return isAbsoluteWindowsImpl(u8, path);
174}
190175
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;
176pub fn isAbsoluteWindowsW(path_w: [*:0]const u16) bool {
177 return isAbsoluteWindowsImpl(u16, mem.toSliceConst(u16, path_w));
178}
179
180pub fn isAbsoluteWindowsC(path_c: [*:0]const u8) bool {
181 return isAbsoluteWindowsImpl(u8, mem.toSliceConst(u8, path_c));
204182}
205183
206184pub fn isAbsolutePosix(path: []const u8) bool {
207 return path[0] == sep_posix;
185 return path.len > 0 and path[0] == sep_posix;
208186}
209187
210188pub fn isAbsolutePosixC(path_c: [*:0]const u8) bool {
211 return path_c[0] == sep_posix;
189 return isAbsolutePosix(mem.toSliceConst(u8, path_c));
212190}
213191
214192test "isAbsoluteWindows" {
193 testIsAbsoluteWindows("", false);
215194 testIsAbsoluteWindows("/", true);
216195 testIsAbsoluteWindows("//", true);
217196 testIsAbsoluteWindows("//server", true);
......@@ -234,6 +213,7 @@ test "isAbsoluteWindows" {
234213}
235214
236215test "isAbsolutePosix" {
216 testIsAbsolutePosix("", false);
237217 testIsAbsolutePosix("/home/foo", true);
238218 testIsAbsolutePosix("/home/foo/..", true);
239219 testIsAbsolutePosix("bar/", false);
lib/std/fs/watch.zig created+675
......@@ -0,0 +1,675 @@
1const std = @import("../std.zig");
2const builtin = @import("builtin");
3const event = std.event;
4const assert = std.debug.assert;
5const testing = std.testing;
6const os = std.os;
7const mem = std.mem;
8const windows = os.windows;
9const Loop = event.Loop;
10const fd_t = os.fd_t;
11const File = std.fs.File;
12const Allocator = mem.Allocator;
13
14const global_event_loop = Loop.instance orelse
15 @compileError("std.fs.Watch currently only works with event-based I/O");
16
17const WatchEventId = enum {
18 CloseWrite,
19 Delete,
20};
21
22fn eqlString(a: []const u16, b: []const u16) bool {
23 if (a.len != b.len) return false;
24 if (a.ptr == b.ptr) return true;
25 return mem.compare(u16, a, b) == .Equal;
26}
27
28fn hashString(s: []const u16) u32 {
29 return @truncate(u32, std.hash.Wyhash.hash(0, @sliceToBytes(s)));
30}
31
32const WatchEventError = error{
33 UserResourceLimitReached,
34 SystemResources,
35 AccessDenied,
36 Unexpected, // TODO remove this possibility
37};
38
39pub fn Watch(comptime V: type) type {
40 return struct {
41 channel: *event.Channel(Event.Error!Event),
42 os_data: OsData,
43 allocator: *Allocator,
44
45 const OsData = switch (builtin.os) {
46 // TODO https://github.com/ziglang/zig/issues/3778
47 .macosx, .freebsd, .netbsd, .dragonfly => KqOsData,
48 .linux => LinuxOsData,
49 .windows => WindowsOsData,
50
51 else => @compileError("Unsupported OS"),
52 };
53
54 const KqOsData = struct {
55 file_table: FileTable,
56 table_lock: event.Lock,
57
58 const FileTable = std.StringHashMap(*Put);
59 const Put = struct {
60 putter_frame: @Frame(kqPutEvents),
61 cancelled: bool = false,
62 value: V,
63 };
64 };
65
66 const WindowsOsData = struct {
67 table_lock: event.Lock,
68 dir_table: DirTable,
69 all_putters: std.atomic.Queue(Put),
70 ref_count: std.atomic.Int(usize),
71
72 const Put = struct {
73 putter: anyframe,
74 cancelled: bool = false,
75 };
76
77 const DirTable = std.StringHashMap(*Dir);
78 const FileTable = std.HashMap([]const u16, V, hashString, eqlString);
79
80 const Dir = struct {
81 putter_frame: @Frame(windowsDirReader),
82 file_table: FileTable,
83 table_lock: event.Lock,
84 };
85 };
86
87 const LinuxOsData = struct {
88 putter_frame: @Frame(linuxEventPutter),
89 inotify_fd: i32,
90 wd_table: WdTable,
91 table_lock: event.Lock,
92 cancelled: bool = false,
93
94 const WdTable = std.AutoHashMap(i32, Dir);
95 const FileTable = std.StringHashMap(V);
96
97 const Dir = struct {
98 dirname: []const u8,
99 file_table: FileTable,
100 };
101 };
102
103 const Self = @This();
104
105 pub const Event = struct {
106 id: Id,
107 data: V,
108
109 pub const Id = WatchEventId;
110 pub const Error = WatchEventError;
111 };
112
113 pub fn init(allocator: *Allocator, event_buf_count: usize) !*Self {
114 const channel = try allocator.create(event.Channel(Event.Error!Event));
115 errdefer allocator.destroy(channel);
116 var buf = try allocator.alloc(Event.Error!Event, event_buf_count);
117 errdefer allocator.free(buf);
118 channel.init(buf);
119 errdefer channel.deinit();
120
121 const self = try allocator.create(Self);
122 errdefer allocator.destroy(self);
123
124 switch (builtin.os) {
125 .linux => {
126 const inotify_fd = try os.inotify_init1(os.linux.IN_NONBLOCK | os.linux.IN_CLOEXEC);
127 errdefer os.close(inotify_fd);
128
129 self.* = Self{
130 .allocator = allocator,
131 .channel = channel,
132 .os_data = OsData{
133 .putter_frame = undefined,
134 .inotify_fd = inotify_fd,
135 .wd_table = OsData.WdTable.init(allocator),
136 .table_lock = event.Lock.init(),
137 },
138 };
139
140 self.os_data.putter_frame = async self.linuxEventPutter();
141 return self;
142 },
143
144 .windows => {
145 self.* = Self{
146 .allocator = allocator,
147 .channel = channel,
148 .os_data = OsData{
149 .table_lock = event.Lock.init(),
150 .dir_table = OsData.DirTable.init(allocator),
151 .ref_count = std.atomic.Int(usize).init(1),
152 .all_putters = std.atomic.Queue(anyframe).init(),
153 },
154 };
155 return self;
156 },
157
158 .macosx, .freebsd, .netbsd, .dragonfly => {
159 self.* = Self{
160 .allocator = allocator,
161 .channel = channel,
162 .os_data = OsData{
163 .table_lock = event.Lock.init(),
164 .file_table = OsData.FileTable.init(allocator),
165 },
166 };
167 return self;
168 },
169 else => @compileError("Unsupported OS"),
170 }
171 }
172
173 /// All addFile calls and removeFile calls must have completed.
174 pub fn deinit(self: *Self) void {
175 switch (builtin.os) {
176 .macosx, .freebsd, .netbsd, .dragonfly => {
177 // TODO we need to cancel the frames before destroying the lock
178 self.os_data.table_lock.deinit();
179 var it = self.os_data.file_table.iterator();
180 while (it.next()) |entry| {
181 entry.cancelled = true;
182 await entry.value.putter;
183 self.allocator.free(entry.key);
184 self.allocator.free(entry.value);
185 }
186 self.channel.deinit();
187 self.allocator.destroy(self.channel.buffer_nodes);
188 self.allocator.destroy(self);
189 },
190 .linux => {
191 self.os_data.cancelled = true;
192 await self.os_data.putter_frame;
193 self.allocator.destroy(self);
194 },
195 .windows => {
196 while (self.os_data.all_putters.get()) |putter_node| {
197 putter_node.cancelled = true;
198 await putter_node.frame;
199 }
200 self.deref();
201 },
202 else => @compileError("Unsupported OS"),
203 }
204 }
205
206 fn ref(self: *Self) void {
207 _ = self.os_data.ref_count.incr();
208 }
209
210 fn deref(self: *Self) void {
211 if (self.os_data.ref_count.decr() == 1) {
212 self.os_data.table_lock.deinit();
213 var it = self.os_data.dir_table.iterator();
214 while (it.next()) |entry| {
215 self.allocator.free(entry.key);
216 self.allocator.destroy(entry.value);
217 }
218 self.os_data.dir_table.deinit();
219 self.channel.deinit();
220 self.allocator.destroy(self.channel.buffer_nodes);
221 self.allocator.destroy(self);
222 }
223 }
224
225 pub fn addFile(self: *Self, file_path: []const u8, value: V) !?V {
226 switch (builtin.os) {
227 .macosx, .freebsd, .netbsd, .dragonfly => return addFileKEvent(self, file_path, value),
228 .linux => return addFileLinux(self, file_path, value),
229 .windows => return addFileWindows(self, file_path, value),
230 else => @compileError("Unsupported OS"),
231 }
232 }
233
234 fn addFileKEvent(self: *Self, file_path: []const u8, value: V) !?V {
235 const resolved_path = try std.fs.path.resolve(self.allocator, [_][]const u8{file_path});
236 var resolved_path_consumed = false;
237 defer if (!resolved_path_consumed) self.allocator.free(resolved_path);
238
239 var close_op = try CloseOperation.start(self.allocator);
240 var close_op_consumed = false;
241 defer if (!close_op_consumed) close_op.finish();
242
243 const flags = if (comptime std.Target.current.isDarwin()) os.O_SYMLINK | os.O_EVTONLY else 0;
244 const mode = 0;
245 const fd = try openPosix(self.allocator, resolved_path, flags, mode);
246 close_op.setHandle(fd);
247
248 var put = try self.allocator.create(OsData.Put);
249 errdefer self.allocator.destroy(put);
250 put.* = OsData.Put{
251 .value = value,
252 .putter_frame = undefined,
253 };
254 put.putter_frame = async self.kqPutEvents(close_op, put);
255 close_op_consumed = true;
256 errdefer {
257 put.cancelled = true;
258 await put.putter_frame;
259 }
260
261 const result = blk: {
262 const held = self.os_data.table_lock.acquire();
263 defer held.release();
264
265 const gop = try self.os_data.file_table.getOrPut(resolved_path);
266 if (gop.found_existing) {
267 const prev_value = gop.kv.value.value;
268 await gop.kv.value.putter_frame;
269 gop.kv.value = put;
270 break :blk prev_value;
271 } else {
272 resolved_path_consumed = true;
273 gop.kv.value = put;
274 break :blk null;
275 }
276 };
277
278 return result;
279 }
280
281 fn kqPutEvents(self: *Self, close_op: *CloseOperation, put: *OsData.Put) void {
282 global_event_loop.beginOneEvent();
283
284 defer {
285 close_op.finish();
286 global_event_loop.finishOneEvent();
287 }
288
289 while (!put.cancelled) {
290 if (global_event_loop.bsdWaitKev(
291 @intCast(usize, close_op.getHandle()),
292 os.EVFILT_VNODE,
293 os.NOTE_WRITE | os.NOTE_DELETE,
294 )) |kev| {
295 // TODO handle EV_ERROR
296 if (kev.fflags & os.NOTE_DELETE != 0) {
297 self.channel.put(Self.Event{
298 .id = Event.Id.Delete,
299 .data = put.value,
300 });
301 } else if (kev.fflags & os.NOTE_WRITE != 0) {
302 self.channel.put(Self.Event{
303 .id = Event.Id.CloseWrite,
304 .data = put.value,
305 });
306 }
307 } else |err| switch (err) {
308 error.EventNotFound => unreachable,
309 error.ProcessNotFound => unreachable,
310 error.Overflow => unreachable,
311 error.AccessDenied, error.SystemResources => |casted_err| {
312 self.channel.put(casted_err);
313 },
314 }
315 }
316 }
317
318 fn addFileLinux(self: *Self, file_path: []const u8, value: V) !?V {
319 const dirname = std.fs.path.dirname(file_path) orelse ".";
320 const dirname_with_null = try std.cstr.addNullByte(self.allocator, dirname);
321 var dirname_with_null_consumed = false;
322 defer if (!dirname_with_null_consumed) self.channel.free(dirname_with_null);
323
324 const basename = std.fs.path.basename(file_path);
325 const basename_with_null = try std.cstr.addNullByte(self.allocator, basename);
326 var basename_with_null_consumed = false;
327 defer if (!basename_with_null_consumed) self.allocator.free(basename_with_null);
328
329 const wd = try os.inotify_add_watchC(
330 self.os_data.inotify_fd,
331 dirname_with_null.ptr,
332 os.linux.IN_CLOSE_WRITE | os.linux.IN_ONLYDIR | os.linux.IN_EXCL_UNLINK,
333 );
334 // wd is either a newly created watch or an existing one.
335
336 const held = self.os_data.table_lock.acquire();
337 defer held.release();
338
339 const gop = try self.os_data.wd_table.getOrPut(wd);
340 if (!gop.found_existing) {
341 gop.kv.value = OsData.Dir{
342 .dirname = dirname_with_null,
343 .file_table = OsData.FileTable.init(self.allocator),
344 };
345 dirname_with_null_consumed = true;
346 }
347 const dir = &gop.kv.value;
348
349 const file_table_gop = try dir.file_table.getOrPut(basename_with_null);
350 if (file_table_gop.found_existing) {
351 const prev_value = file_table_gop.kv.value;
352 file_table_gop.kv.value = value;
353 return prev_value;
354 } else {
355 file_table_gop.kv.value = value;
356 basename_with_null_consumed = true;
357 return null;
358 }
359 }
360
361 fn addFileWindows(self: *Self, file_path: []const u8, value: V) !?V {
362 // TODO we might need to convert dirname and basename to canonical file paths ("short"?)
363 const dirname = try std.mem.dupe(self.allocator, u8, std.fs.path.dirname(file_path) orelse ".");
364 var dirname_consumed = false;
365 defer if (!dirname_consumed) self.allocator.free(dirname);
366
367 const dirname_utf16le = try std.unicode.utf8ToUtf16LeWithNull(self.allocator, dirname);
368 defer self.allocator.free(dirname_utf16le);
369
370 // TODO https://github.com/ziglang/zig/issues/265
371 const basename = std.fs.path.basename(file_path);
372 const basename_utf16le_null = try std.unicode.utf8ToUtf16LeWithNull(self.allocator, basename);
373 var basename_utf16le_null_consumed = false;
374 defer if (!basename_utf16le_null_consumed) self.allocator.free(basename_utf16le_null);
375 const basename_utf16le_no_null = basename_utf16le_null[0 .. basename_utf16le_null.len - 1];
376
377 const dir_handle = try windows.CreateFileW(
378 dirname_utf16le.ptr,
379 windows.FILE_LIST_DIRECTORY,
380 windows.FILE_SHARE_READ | windows.FILE_SHARE_DELETE | windows.FILE_SHARE_WRITE,
381 null,
382 windows.OPEN_EXISTING,
383 windows.FILE_FLAG_BACKUP_SEMANTICS | windows.FILE_FLAG_OVERLAPPED,
384 null,
385 );
386 var dir_handle_consumed = false;
387 defer if (!dir_handle_consumed) windows.CloseHandle(dir_handle);
388
389 const held = self.os_data.table_lock.acquire();
390 defer held.release();
391
392 const gop = try self.os_data.dir_table.getOrPut(dirname);
393 if (gop.found_existing) {
394 const dir = gop.kv.value;
395 const held_dir_lock = dir.table_lock.acquire();
396 defer held_dir_lock.release();
397
398 const file_gop = try dir.file_table.getOrPut(basename_utf16le_no_null);
399 if (file_gop.found_existing) {
400 const prev_value = file_gop.kv.value;
401 file_gop.kv.value = value;
402 return prev_value;
403 } else {
404 file_gop.kv.value = value;
405 basename_utf16le_null_consumed = true;
406 return null;
407 }
408 } else {
409 errdefer _ = self.os_data.dir_table.remove(dirname);
410 const dir = try self.allocator.create(OsData.Dir);
411 errdefer self.allocator.destroy(dir);
412
413 dir.* = OsData.Dir{
414 .file_table = OsData.FileTable.init(self.allocator),
415 .table_lock = event.Lock.init(),
416 .putter_frame = undefined,
417 };
418 gop.kv.value = dir;
419 assert((try dir.file_table.put(basename_utf16le_no_null, value)) == null);
420 basename_utf16le_null_consumed = true;
421
422 dir.putter_frame = async self.windowsDirReader(dir_handle, dir);
423 dir_handle_consumed = true;
424
425 dirname_consumed = true;
426
427 return null;
428 }
429 }
430
431 fn windowsDirReader(self: *Self, dir_handle: windows.HANDLE, dir: *OsData.Dir) void {
432 self.ref();
433 defer self.deref();
434
435 defer os.close(dir_handle);
436
437 var putter_node = std.atomic.Queue(anyframe).Node{
438 .data = .{ .putter = @frame() },
439 .prev = null,
440 .next = null,
441 };
442 self.os_data.all_putters.put(&putter_node);
443 defer _ = self.os_data.all_putters.remove(&putter_node);
444
445 var resume_node = Loop.ResumeNode.Basic{
446 .base = Loop.ResumeNode{
447 .id = Loop.ResumeNode.Id.Basic,
448 .handle = @frame(),
449 .overlapped = windows.OVERLAPPED{
450 .Internal = 0,
451 .InternalHigh = 0,
452 .Offset = 0,
453 .OffsetHigh = 0,
454 .hEvent = null,
455 },
456 },
457 };
458 var event_buf: [4096]u8 align(@alignOf(windows.FILE_NOTIFY_INFORMATION)) = undefined;
459
460 // TODO handle this error not in the channel but in the setup
461 _ = windows.CreateIoCompletionPort(
462 dir_handle,
463 global_event_loop.os_data.io_port,
464 undefined,
465 undefined,
466 ) catch |err| {
467 self.channel.put(err);
468 return;
469 };
470
471 while (!putter_node.data.cancelled) {
472 {
473 // TODO only 1 beginOneEvent for the whole function
474 global_event_loop.beginOneEvent();
475 errdefer global_event_loop.finishOneEvent();
476 errdefer {
477 _ = windows.kernel32.CancelIoEx(dir_handle, &resume_node.base.overlapped);
478 }
479 suspend {
480 _ = windows.kernel32.ReadDirectoryChangesW(
481 dir_handle,
482 &event_buf,
483 @intCast(windows.DWORD, event_buf.len),
484 windows.FALSE, // watch subtree
485 windows.FILE_NOTIFY_CHANGE_FILE_NAME | windows.FILE_NOTIFY_CHANGE_DIR_NAME |
486 windows.FILE_NOTIFY_CHANGE_ATTRIBUTES | windows.FILE_NOTIFY_CHANGE_SIZE |
487 windows.FILE_NOTIFY_CHANGE_LAST_WRITE | windows.FILE_NOTIFY_CHANGE_LAST_ACCESS |
488 windows.FILE_NOTIFY_CHANGE_CREATION | windows.FILE_NOTIFY_CHANGE_SECURITY,
489 null, // number of bytes transferred (unused for async)
490 &resume_node.base.overlapped,
491 null, // completion routine - unused because we use IOCP
492 );
493 }
494 }
495 var bytes_transferred: windows.DWORD = undefined;
496 if (windows.kernel32.GetOverlappedResult(dir_handle, &resume_node.base.overlapped, &bytes_transferred, windows.FALSE) == 0) {
497 const err = switch (windows.kernel32.GetLastError()) {
498 else => |err| windows.unexpectedError(err),
499 };
500 self.channel.put(err);
501 } else {
502 // can't use @bytesToSlice because of the special variable length name field
503 var ptr = event_buf[0..].ptr;
504 const end_ptr = ptr + bytes_transferred;
505 var ev: *windows.FILE_NOTIFY_INFORMATION = undefined;
506 while (@ptrToInt(ptr) < @ptrToInt(end_ptr)) : (ptr += ev.NextEntryOffset) {
507 ev = @ptrCast(*windows.FILE_NOTIFY_INFORMATION, ptr);
508 const emit = switch (ev.Action) {
509 windows.FILE_ACTION_REMOVED => WatchEventId.Delete,
510 windows.FILE_ACTION_MODIFIED => WatchEventId.CloseWrite,
511 else => null,
512 };
513 if (emit) |id| {
514 const basename_utf16le = ([*]u16)(&ev.FileName)[0 .. ev.FileNameLength / 2];
515 const user_value = blk: {
516 const held = dir.table_lock.acquire();
517 defer held.release();
518
519 if (dir.file_table.get(basename_utf16le)) |entry| {
520 break :blk entry.value;
521 } else {
522 break :blk null;
523 }
524 };
525 if (user_value) |v| {
526 self.channel.put(Event{
527 .id = id,
528 .data = v,
529 });
530 }
531 }
532 if (ev.NextEntryOffset == 0) break;
533 }
534 }
535 }
536 }
537
538 pub fn removeFile(self: *Self, file_path: []const u8) ?V {
539 @panic("TODO");
540 }
541
542 fn linuxEventPutter(self: *Self) void {
543 global_event_loop.beginOneEvent();
544
545 defer {
546 self.os_data.table_lock.deinit();
547 var wd_it = self.os_data.wd_table.iterator();
548 while (wd_it.next()) |wd_entry| {
549 var file_it = wd_entry.value.file_table.iterator();
550 while (file_it.next()) |file_entry| {
551 self.allocator.free(file_entry.key);
552 }
553 self.allocator.free(wd_entry.value.dirname);
554 wd_entry.value.file_table.deinit();
555 }
556 self.os_data.wd_table.deinit();
557 global_event_loop.finishOneEvent();
558 os.close(self.os_data.inotify_fd);
559 self.channel.deinit();
560 self.allocator.free(self.channel.buffer_nodes);
561 }
562
563 var event_buf: [4096]u8 align(@alignOf(os.linux.inotify_event)) = undefined;
564
565 while (!self.os_data.cancelled) {
566 const rc = os.linux.read(self.os_data.inotify_fd, &event_buf, event_buf.len);
567 const errno = os.linux.getErrno(rc);
568 switch (errno) {
569 0 => {
570 // can't use @bytesToSlice because of the special variable length name field
571 var ptr = event_buf[0..].ptr;
572 const end_ptr = ptr + event_buf.len;
573 var ev: *os.linux.inotify_event = undefined;
574 while (@ptrToInt(ptr) < @ptrToInt(end_ptr)) {
575 ev = @ptrCast(*os.linux.inotify_event, ptr);
576 if (ev.mask & os.linux.IN_CLOSE_WRITE == os.linux.IN_CLOSE_WRITE) {
577 const basename_ptr = ptr + @sizeOf(os.linux.inotify_event);
578 // `ev.len` counts all bytes in `ev.name` including terminating null byte.
579 const basename_with_null = basename_ptr[0..ev.len];
580 const user_value = blk: {
581 const held = self.os_data.table_lock.acquire();
582 defer held.release();
583
584 const dir = &self.os_data.wd_table.get(ev.wd).?.value;
585 if (dir.file_table.get(basename_with_null)) |entry| {
586 break :blk entry.value;
587 } else {
588 break :blk null;
589 }
590 };
591 if (user_value) |v| {
592 self.channel.put(Event{
593 .id = WatchEventId.CloseWrite,
594 .data = v,
595 });
596 }
597 }
598
599 ptr = @alignCast(@alignOf(os.linux.inotify_event), ptr + @sizeOf(os.linux.inotify_event) + ev.len);
600 }
601 },
602 os.linux.EINTR => continue,
603 os.linux.EINVAL => unreachable,
604 os.linux.EFAULT => unreachable,
605 os.linux.EAGAIN => {
606 global_event_loop.linuxWaitFd(self.os_data.inotify_fd, os.linux.EPOLLET | os.linux.EPOLLIN | os.EPOLLONESHOT);
607 },
608 else => unreachable,
609 }
610 }
611 }
612 };
613}
614
615const test_tmp_dir = "std_event_fs_test";
616
617test "write a file, watch it, write it again" {
618 // TODO re-enable this test
619 if (true) return error.SkipZigTest;
620
621 const allocator = std.heap.page_allocator;
622
623 try os.makePath(allocator, test_tmp_dir);
624 defer os.deleteTree(test_tmp_dir) catch {};
625
626 return testFsWatch(&allocator);
627}
628
629fn testFsWatch(allocator: *Allocator) !void {
630 const file_path = try std.fs.path.join(allocator, [_][]const u8{ test_tmp_dir, "file.txt" });
631 defer allocator.free(file_path);
632
633 const contents =
634 \\line 1
635 \\line 2
636 ;
637 const line2_offset = 7;
638
639 // first just write then read the file
640 try writeFile(allocator, file_path, contents);
641
642 const read_contents = try readFile(allocator, file_path, 1024 * 1024);
643 testing.expectEqualSlices(u8, contents, read_contents);
644
645 // now watch the file
646 var watch = try Watch(void).init(allocator, 0);
647 defer watch.deinit();
648
649 testing.expect((try watch.addFile(file_path, {})) == null);
650
651 const ev = watch.channel.get();
652 var ev_consumed = false;
653 defer if (!ev_consumed) await ev;
654
655 // overwrite line 2
656 const fd = try await openReadWrite(file_path, File.default_mode);
657 {
658 defer os.close(fd);
659
660 try pwritev(allocator, fd, []const []const u8{"lorem ipsum"}, line2_offset);
661 }
662
663 ev_consumed = true;
664 switch ((try await ev).id) {
665 WatchEventId.CloseWrite => {},
666 WatchEventId.Delete => @panic("wrong event"),
667 }
668 const contents_updated = try readFile(allocator, file_path, 1024 * 1024);
669 testing.expectEqualSlices(u8,
670 \\line 1
671 \\lorem ipsum
672 , contents_updated);
673
674 // TODO test deleting the file and then re-adding it. we should get events for both
675}
lib/std/io.zig+13-3
......@@ -47,7 +47,10 @@ fn getStdOutHandle() os.fd_t {
4747}
4848
4949pub fn getStdOut() File {
50 return File.openHandle(getStdOutHandle());
50 return File{
51 .handle = getStdOutHandle(),
52 .io_mode = .blocking,
53 };
5154}
5255
5356fn getStdErrHandle() os.fd_t {
......@@ -63,7 +66,11 @@ fn getStdErrHandle() os.fd_t {
6366}
6467
6568pub fn getStdErr() File {
66 return File.openHandle(getStdErrHandle());
69 return File{
70 .handle = getStdErrHandle(),
71 .io_mode = .blocking,
72 .async_block_allowed = File.async_block_allowed_yes,
73 };
6774}
6875
6976fn getStdInHandle() os.fd_t {
......@@ -79,7 +86,10 @@ fn getStdInHandle() os.fd_t {
7986}
8087
8188pub fn getStdIn() File {
82 return File.openHandle(getStdInHandle());
89 return File{
90 .handle = getStdInHandle(),
91 .io_mode = .blocking,
92 };
8393}
8494
8595pub const SeekableStream = @import("io/seekable_stream.zig").SeekableStream;
lib/std/io/out_stream.zig+11-15
......@@ -9,14 +9,11 @@ pub const stack_size: usize = if (@hasDecl(root, "stack_size_std_io_OutStream"))
99else
1010 default_stack_size;
1111
12/// TODO this is not integrated with evented I/O yet.
13/// https://github.com/ziglang/zig/issues/3557
1412pub fn OutStream(comptime WriteError: type) type {
1513 return struct {
1614 const Self = @This();
1715 pub const Error = WriteError;
18 // TODO https://github.com/ziglang/zig/issues/3557
19 pub const WriteFn = if (std.io.is_async and false)
16 pub const WriteFn = if (std.io.is_async)
2017 async fn (self: *Self, bytes: []const u8) Error!void
2118 else
2219 fn (self: *Self, bytes: []const u8) Error!void;
......@@ -24,8 +21,7 @@ pub fn OutStream(comptime WriteError: type) type {
2421 writeFn: WriteFn,
2522
2623 pub fn write(self: *Self, bytes: []const u8) Error!void {
27 // TODO https://github.com/ziglang/zig/issues/3557
28 if (std.io.is_async and false) {
24 if (std.io.is_async) {
2925 // Let's not be writing 0xaa in safe modes for upwards of 4 MiB for every stream write.
3026 @setRuntimeSafety(false);
3127 var stack_frame: [stack_size]u8 align(std.Target.stack_align) = undefined;
......@@ -36,12 +32,12 @@ pub fn OutStream(comptime WriteError: type) type {
3632 }
3733
3834 pub fn print(self: *Self, comptime format: []const u8, args: var) Error!void {
39 return std.fmt.format(self, Error, self.writeFn, format, args);
35 return std.fmt.format(self, Error, write, format, args);
4036 }
4137
4238 pub fn writeByte(self: *Self, byte: u8) Error!void {
43 const slice = @as(*const [1]u8, &byte)[0..];
44 return self.writeFn(self, slice);
39 const array = [1]u8{byte};
40 return self.write(&array);
4541 }
4642
4743 pub fn writeByteNTimes(self: *Self, byte: u8, n: usize) Error!void {
......@@ -51,7 +47,7 @@ pub fn OutStream(comptime WriteError: type) type {
5147 var remaining: usize = n;
5248 while (remaining > 0) {
5349 const to_write = std.math.min(remaining, bytes.len);
54 try self.writeFn(self, bytes[0..to_write]);
50 try self.write(bytes[0..to_write]);
5551 remaining -= to_write;
5652 }
5753 }
......@@ -60,32 +56,32 @@ pub fn OutStream(comptime WriteError: type) type {
6056 pub fn writeIntNative(self: *Self, comptime T: type, value: T) Error!void {
6157 var bytes: [(T.bit_count + 7) / 8]u8 = undefined;
6258 mem.writeIntNative(T, &bytes, value);
63 return self.writeFn(self, &bytes);
59 return self.write(&bytes);
6460 }
6561
6662 /// Write a foreign-endian integer.
6763 pub fn writeIntForeign(self: *Self, comptime T: type, value: T) Error!void {
6864 var bytes: [(T.bit_count + 7) / 8]u8 = undefined;
6965 mem.writeIntForeign(T, &bytes, value);
70 return self.writeFn(self, &bytes);
66 return self.write(&bytes);
7167 }
7268
7369 pub fn writeIntLittle(self: *Self, comptime T: type, value: T) Error!void {
7470 var bytes: [(T.bit_count + 7) / 8]u8 = undefined;
7571 mem.writeIntLittle(T, &bytes, value);
76 return self.writeFn(self, &bytes);
72 return self.write(&bytes);
7773 }
7874
7975 pub fn writeIntBig(self: *Self, comptime T: type, value: T) Error!void {
8076 var bytes: [(T.bit_count + 7) / 8]u8 = undefined;
8177 mem.writeIntBig(T, &bytes, value);
82 return self.writeFn(self, &bytes);
78 return self.write(&bytes);
8379 }
8480
8581 pub fn writeInt(self: *Self, comptime T: type, value: T, endian: builtin.Endian) Error!void {
8682 var bytes: [(T.bit_count + 7) / 8]u8 = undefined;
8783 mem.writeInt(T, &bytes, value, endian);
88 return self.writeFn(self, &bytes);
84 return self.write(&bytes);
8985 }
9086 };
9187}
lib/std/linked_list.zig+3-6
......@@ -18,12 +18,11 @@ pub fn SinglyLinkedList(comptime T: type) type {
1818
1919 /// Node inside the linked list wrapping the actual data.
2020 pub const Node = struct {
21 next: ?*Node,
21 next: ?*Node = null,
2222 data: T,
2323
2424 pub fn init(data: T) Node {
2525 return Node{
26 .next = null,
2726 .data = data,
2827 };
2928 }
......@@ -196,14 +195,12 @@ pub fn TailQueue(comptime T: type) type {
196195
197196 /// Node inside the linked list wrapping the actual data.
198197 pub const Node = struct {
199 prev: ?*Node,
200 next: ?*Node,
198 prev: ?*Node = null,
199 next: ?*Node = null,
201200 data: T,
202201
203202 pub fn init(data: T) Node {
204203 return Node{
205 .prev = null,
206 .next = null,
207204 .data = data,
208205 };
209206 }
lib/std/mem.zig+1-1
......@@ -233,7 +233,7 @@ pub const Allocator = struct {
233233 pub fn free(self: *Allocator, memory: var) void {
234234 const Slice = @typeInfo(@TypeOf(memory)).Pointer;
235235 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;
237237 if (bytes_len == 0) return;
238238 const non_const_ptr = @intToPtr([*]u8, @ptrToInt(bytes.ptr));
239239 @memset(non_const_ptr, undefined, bytes_len);
lib/std/net.zig+11-5
......@@ -271,7 +271,7 @@ pub const Address = extern union {
271271 options: std.fmt.FormatOptions,
272272 context: var,
273273 comptime Errors: type,
274 output: fn (@TypeOf(context), []const u8) Errors!void,
274 comptime output: fn (@TypeOf(context), []const u8) Errors!void,
275275 ) !void {
276276 switch (self.any.family) {
277277 os.AF_INET => {
......@@ -361,7 +361,7 @@ pub const Address = extern union {
361361};
362362
363363pub fn connectUnixSocket(path: []const u8) !fs.File {
364 const opt_non_block = if (std.io.mode == .evented) os.SOCK_NONBLOCK else 0;
364 const opt_non_block = if (std.io.is_async) os.SOCK_NONBLOCK else 0;
365365 const sockfd = try os.socket(
366366 os.AF_UNIX,
367367 os.SOCK_STREAM | os.SOCK_CLOEXEC | opt_non_block,
......@@ -377,7 +377,10 @@ pub fn connectUnixSocket(path: []const u8) !fs.File {
377377 addr.getOsSockLen(),
378378 );
379379
380 return fs.File.openHandle(sockfd);
380 return fs.File{
381 .handle = sockfd,
382 .io_mode = std.io.mode,
383 };
381384}
382385
383386pub const AddressList = struct {
......@@ -412,7 +415,7 @@ pub fn tcpConnectToAddress(address: Address) !fs.File {
412415 errdefer os.close(sockfd);
413416 try os.connect(sockfd, &address.any, address.getOsSockLen());
414417
415 return fs.File{ .handle = sockfd };
418 return fs.File{ .handle = sockfd, .io_mode = std.io.mode };
416419}
417420
418421/// Call `AddressList.deinit` on the result.
......@@ -1379,7 +1382,10 @@ pub const StreamServer = struct {
13791382 var adr_len: os.socklen_t = @sizeOf(Address);
13801383 if (os.accept4(self.sockfd.?, &accepted_addr.any, &adr_len, accept_flags)) |fd| {
13811384 return Connection{
1382 .file = fs.File.openHandle(fd),
1385 .file = fs.File{
1386 .handle = fd,
1387 .io_mode = std.io.mode,
1388 },
13831389 .address = accepted_addr,
13841390 };
13851391 } else |err| switch (err) {
lib/std/net/test.zig+3-5
......@@ -81,17 +81,15 @@ test "resolve DNS" {
8181}
8282
8383test "listen on a port, send bytes, receive bytes" {
84 if (!std.io.is_async) return error.SkipZigTest;
85
8486 if (std.builtin.os != .linux) {
8587 // TODO build abstractions for other operating systems
8688 return error.SkipZigTest;
8789 }
88 if (std.io.mode != .evented) {
89 // TODO add ability to run tests in non-blocking I/O mode
90 return error.SkipZigTest;
91 }
9290
9391 // TODO doing this at comptime crashed the compiler
94 const localhost = net.Address.parseIp("127.0.0.1", 0);
92 const localhost = try net.Address.parseIp("127.0.0.1", 0);
9593
9694 var server = net.StreamServer.init(net.StreamServer.Options{});
9795 defer server.deinit();
lib/std/os.zig+222-21
......@@ -169,7 +169,12 @@ fn getRandomBytesDevURandom(buf: []u8) !void {
169169 return error.NoDevice;
170170 }
171171
172 const stream = &std.fs.File.openHandle(fd).inStream().stream;
172 const file = std.fs.File{
173 .handle = fd,
174 .io_mode = .blocking,
175 .async_block_allowed = std.fs.File.async_block_allowed_yes,
176 };
177 const stream = &file.inStream().stream;
173178 stream.readNoEof(buf) catch return error.Unexpected;
174179}
175180
......@@ -293,7 +298,7 @@ pub const ReadError = error{
293298/// via the event loop. Otherwise EAGAIN results in error.WouldBlock.
294299pub fn read(fd: fd_t, buf: []u8) ReadError!usize {
295300 if (builtin.os == .windows) {
296 return windows.ReadFile(fd, buf);
301 return windows.ReadFile(fd, buf, null);
297302 }
298303
299304 if (builtin.os == .wasi and !builtin.link_libc) {
......@@ -335,9 +340,37 @@ pub fn read(fd: fd_t, buf: []u8) ReadError!usize {
335340}
336341
337342/// Number of bytes read is returned. Upon reading end-of-file, zero is returned.
338/// If the application has a global event loop enabled, EAGAIN is handled
339/// via the event loop. Otherwise EAGAIN results in error.WouldBlock.
343///
344/// For POSIX systems, if the application has a global event loop enabled, EAGAIN is handled
345/// via the event loop. Otherwise EAGAIN results in `error.WouldBlock`.
346/// On Windows, if the application has a global event loop enabled, I/O Completion Ports are
347/// used to perform the I/O. `error.WouldBlock` is not possible on Windows.
348///
349/// This operation is non-atomic on the following systems:
350/// * Windows
351/// On these systems, the read races with concurrent writes to the same file descriptor.
340352pub fn readv(fd: fd_t, iov: []const iovec) ReadError!usize {
353 if (builtin.os == .windows) {
354 // TODO batch these into parallel requests
355 var off: usize = 0;
356 var iov_i: usize = 0;
357 var inner_off: usize = 0;
358 while (true) {
359 const v = iov[iov_i];
360 const amt_read = try read(fd, v.iov_base[inner_off .. v.iov_len - inner_off]);
361 off += amt_read;
362 inner_off += amt_read;
363 if (inner_off == v.len) {
364 iov_i += 1;
365 inner_off = 0;
366 if (iov_i == iov.len) {
367 return off;
368 }
369 }
370 if (amt_read == 0) return off; // EOF
371 } else unreachable; // TODO https://github.com/ziglang/zig/issues/707
372 }
373
341374 while (true) {
342375 // TODO handle the case when iov_len is too large and get rid of this @intCast
343376 const rc = system.readv(fd, iov.ptr, @intCast(u32, iov.len));
......@@ -363,8 +396,56 @@ pub fn readv(fd: fd_t, iov: []const iovec) ReadError!usize {
363396}
364397
365398/// Number of bytes read is returned. Upon reading end-of-file, zero is returned.
366/// If the application has a global event loop enabled, EAGAIN is handled
367/// via the event loop. Otherwise EAGAIN results in error.WouldBlock.
399///
400/// Retries when interrupted by a signal.
401///
402/// For POSIX systems, if the application has a global event loop enabled, EAGAIN is handled
403/// via the event loop. Otherwise EAGAIN results in `error.WouldBlock`.
404/// On Windows, if the application has a global event loop enabled, I/O Completion Ports are
405/// used to perform the I/O. `error.WouldBlock` is not possible on Windows.
406pub fn pread(fd: fd_t, buf: []u8, offset: u64) ReadError!usize {
407 if (builtin.os == .windows) {
408 return windows.ReadFile(fd, buf, offset);
409 }
410
411 while (true) {
412 const rc = system.pread(fd, buf.ptr, buf.len, offset);
413 switch (errno(rc)) {
414 0 => return @intCast(usize, rc),
415 EINTR => continue,
416 EINVAL => unreachable,
417 EFAULT => unreachable,
418 EAGAIN => if (std.event.Loop.instance) |loop| {
419 loop.waitUntilFdReadable(fd);
420 continue;
421 } else {
422 return error.WouldBlock;
423 },
424 EBADF => unreachable, // Always a race condition.
425 EIO => return error.InputOutput,
426 EISDIR => return error.IsDir,
427 ENOBUFS => return error.SystemResources,
428 ENOMEM => return error.SystemResources,
429 ECONNRESET => return error.ConnectionResetByPeer,
430 else => |err| return unexpectedErrno(err),
431 }
432 }
433 return index;
434}
435
436/// Number of bytes read is returned. Upon reading end-of-file, zero is returned.
437///
438/// Retries when interrupted by a signal.
439///
440/// For POSIX systems, if the application has a global event loop enabled, EAGAIN is handled
441/// via the event loop. Otherwise EAGAIN results in `error.WouldBlock`.
442/// On Windows, if the application has a global event loop enabled, I/O Completion Ports are
443/// used to perform the I/O. `error.WouldBlock` is not possible on Windows.
444///
445/// This operation is non-atomic on the following systems:
446/// * Darwin
447/// * Windows
448/// On these systems, the read races with concurrent writes to the same file descriptor.
368449pub fn preadv(fd: fd_t, iov: []const iovec, offset: u64) ReadError!usize {
369450 if (comptime std.Target.current.isDarwin()) {
370451 // Darwin does not have preadv but it does have pread.
......@@ -409,6 +490,28 @@ pub fn preadv(fd: fd_t, iov: []const iovec, offset: u64) ReadError!usize {
409490 }
410491 }
411492 }
493
494 if (builtin.os == .windows) {
495 // TODO batch these into parallel requests
496 var off: usize = 0;
497 var iov_i: usize = 0;
498 var inner_off: usize = 0;
499 while (true) {
500 const v = iov[iov_i];
501 const amt_read = try pread(fd, v.iov_base[inner_off .. v.iov_len - inner_off], offset + off);
502 off += amt_read;
503 inner_off += amt_read;
504 if (inner_off == v.len) {
505 iov_i += 1;
506 inner_off = 0;
507 if (iov_i == iov.len) {
508 return off;
509 }
510 }
511 if (amt_read == 0) return off; // EOF
512 } else unreachable; // TODO https://github.com/ziglang/zig/issues/707
513 }
514
412515 while (true) {
413516 // TODO handle the case when iov_len is too large and get rid of this @intCast
414517 const rc = system.preadv(fd, iov.ptr, @intCast(u32, iov.len), offset);
......@@ -451,11 +554,9 @@ pub const WriteError = error{
451554/// Write to a file descriptor. Keeps trying if it gets interrupted.
452555/// If the application has a global event loop enabled, EAGAIN is handled
453556/// via the event loop. Otherwise EAGAIN results in error.WouldBlock.
454/// TODO evented I/O integration is disabled until
455/// https://github.com/ziglang/zig/issues/3557 is solved.
456557pub fn write(fd: fd_t, bytes: []const u8) WriteError!void {
457558 if (builtin.os == .windows) {
458 return windows.WriteFile(fd, bytes);
559 return windows.WriteFile(fd, bytes, null);
459560 }
460561
461562 if (builtin.os == .wasi and !builtin.link_libc) {
......@@ -488,14 +589,12 @@ pub fn write(fd: fd_t, bytes: []const u8) WriteError!void {
488589 EINTR => continue,
489590 EINVAL => unreachable,
490591 EFAULT => unreachable,
491 // TODO https://github.com/ziglang/zig/issues/3557
492 EAGAIN => return error.WouldBlock,
493 //EAGAIN => if (std.event.Loop.instance) |loop| {
494 // loop.waitUntilFdWritable(fd);
495 // continue;
496 //} else {
497 // return error.WouldBlock;
498 //},
592 EAGAIN => if (std.event.Loop.instance) |loop| {
593 loop.waitUntilFdWritable(fd);
594 continue;
595 } else {
596 return error.WouldBlock;
597 },
499598 EBADF => unreachable, // Always a race condition.
500599 EDESTADDRREQ => unreachable, // `connect` was never called.
501600 EDQUOT => return error.DiskQuota,
......@@ -540,8 +639,57 @@ pub fn writev(fd: fd_t, iov: []const iovec_const) WriteError!void {
540639 }
541640}
542641
642/// Write to a file descriptor, with a position offset.
643///
644/// Retries when interrupted by a signal.
645///
646/// For POSIX systems, if the application has a global event loop enabled, EAGAIN is handled
647/// via the event loop. Otherwise EAGAIN results in `error.WouldBlock`.
648/// On Windows, if the application has a global event loop enabled, I/O Completion Ports are
649/// used to perform the I/O. `error.WouldBlock` is not possible on Windows.
650pub fn pwrite(fd: fd_t, bytes: []const u8, offset: u64) WriteError!void {
651 if (comptime std.Target.current.isWindows()) {
652 return windows.WriteFile(fd, bytes, offset);
653 }
654
655 while (true) {
656 const rc = system.pwrite(fd, bytes.ptr, bytes.len, offset);
657 switch (errno(rc)) {
658 0 => return,
659 EINTR => continue,
660 EINVAL => unreachable,
661 EFAULT => unreachable,
662 EAGAIN => if (std.event.Loop.instance) |loop| {
663 loop.waitUntilFdWritable(fd);
664 continue;
665 } else {
666 return error.WouldBlock;
667 },
668 EBADF => unreachable, // Always a race condition.
669 EDESTADDRREQ => unreachable, // `connect` was never called.
670 EDQUOT => return error.DiskQuota,
671 EFBIG => return error.FileTooBig,
672 EIO => return error.InputOutput,
673 ENOSPC => return error.NoSpaceLeft,
674 EPERM => return error.AccessDenied,
675 EPIPE => return error.BrokenPipe,
676 else => |err| return unexpectedErrno(err),
677 }
678 }
679}
680
543681/// Write multiple buffers to a file descriptor, with a position offset.
544/// Keeps trying if it gets interrupted.
682///
683/// Retries when interrupted by a signal.
684///
685/// If the application has a global event loop enabled, EAGAIN is handled
686/// via the event loop. Otherwise EAGAIN results in `error.WouldBlock`.
687///
688/// This operation is non-atomic on the following systems:
689/// * Darwin
690/// * Windows
691/// On these systems, the write races with concurrent writes to the same file descriptor, and
692/// the file can be in a partially written state when an error occurs.
545693pub fn pwritev(fd: fd_t, iov: []const iovec_const, offset: u64) WriteError!void {
546694 if (comptime std.Target.current.isDarwin()) {
547695 // Darwin does not have pwritev but it does have pwrite.
......@@ -589,6 +737,15 @@ pub fn pwritev(fd: fd_t, iov: []const iovec_const, offset: u64) WriteError!void
589737 }
590738 }
591739
740 if (comptime std.Target.current.isWindows()) {
741 var off = offset;
742 for (iov) |item| {
743 try pwrite(fd, item.iov_base[0..item.iov_len], off);
744 off += buf.len;
745 }
746 return;
747 }
748
592749 while (true) {
593750 // TODO handle the case when iov_len is too large and get rid of this @intCast
594751 const rc = system.pwritev(fd, iov.ptr, @intCast(u32, iov.len), offset);
......@@ -694,7 +851,7 @@ pub fn openC(file_path: [*:0]const u8, flags: u32, perm: usize) OpenError!fd_t {
694851/// Open and possibly create a file. Keeps trying if it gets interrupted.
695852/// `file_path` is relative to the open directory handle `dir_fd`.
696853/// See also `openatC`.
697pub fn openat(dir_fd: fd_t, file_path: []const u8, flags: u32, mode: usize) OpenError!fd_t {
854pub fn openat(dir_fd: fd_t, file_path: []const u8, flags: u32, mode: mode_t) OpenError!fd_t {
698855 const file_path_c = try toPosixPath(file_path);
699856 return openatC(dir_fd, &file_path_c, flags, mode);
700857}
......@@ -702,7 +859,7 @@ pub fn openat(dir_fd: fd_t, file_path: []const u8, flags: u32, mode: usize) Open
702859/// Open and possibly create a file. Keeps trying if it gets interrupted.
703860/// `file_path` is relative to the open directory handle `dir_fd`.
704861/// See also `openat`.
705pub fn openatC(dir_fd: fd_t, file_path: [*:0]const u8, flags: u32, mode: usize) OpenError!fd_t {
862pub fn openatC(dir_fd: fd_t, file_path: [*:0]const u8, flags: u32, mode: mode_t) OpenError!fd_t {
706863 while (true) {
707864 const rc = system.openat(dir_fd, file_path, flags, mode);
708865 switch (errno(rc)) {
......@@ -2237,7 +2394,7 @@ pub const MMapError = error{
22372394} || UnexpectedError;
22382395
22392396/// Map files or devices into memory.
2240/// `length` must be aligned to `mem.page_size`.
2397/// `length` does not need to be aligned.
22412398/// Use of a mapped region can result in these signals:
22422399/// * SIGSEGV - Attempted write into a region mapped as read-only.
22432400/// * 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 {
23722529}
23732530
23742531pub fn pipe2(flags: u32) PipeError![2]fd_t {
2532 if (comptime std.Target.current.isDarwin()) {
2533 var fds: [2]fd_t = try pipe();
2534 if (flags == 0) return fds;
2535 errdefer {
2536 close(fds[0]);
2537 close(fds[1]);
2538 }
2539 for (fds) |fd| switch (errno(system.fcntl(fd, F_SETFL, flags))) {
2540 0 => {},
2541 EINVAL => unreachable, // Invalid flags
2542 EBADF => unreachable, // Always a race condition
2543 else => |err| return unexpectedErrno(err),
2544 };
2545 return fds;
2546 }
2547
23752548 var fds: [2]fd_t = undefined;
23762549 switch (errno(system.pipe2(&fds, flags))) {
23772550 0 => return fds,
......@@ -3328,3 +3501,31 @@ pub fn getrusage(who: i32) rusage {
33283501 else => unreachable,
33293502 }
33303503}
3504
3505pub const TermiosGetError = error{NotATerminal} || UnexpectedError;
3506
3507pub fn tcgetattr(handle: fd_t) TermiosGetError!termios {
3508 var term: termios = undefined;
3509 switch (errno(system.tcgetattr(handle, &term))) {
3510 0 => return term,
3511 EBADF => unreachable,
3512 ENOTTY => return error.NotATerminal,
3513 else => |err| return unexpectedErrno(err),
3514 }
3515}
3516
3517pub const TermiosSetError = TermiosGetError || error{ProcessOrphaned};
3518
3519pub fn tcsetattr(handle: fd_t, optional_action: TCSA, termios_p: termios) TermiosSetError!void {
3520 while (true) {
3521 switch (errno(system.tcsetattr(handle, optional_action, &termios_p))) {
3522 0 => return,
3523 EBADF => unreachable,
3524 EINTR => continue,
3525 EINVAL => unreachable,
3526 ENOTTY => return error.NotATerminal,
3527 EIO => return error.ProcessOrphaned,
3528 else => |err| return unexpectedErrno(err),
3529 }
3530 }
3531}
lib/std/os/bits/darwin.zig+159
......@@ -4,6 +4,7 @@ const maxInt = std.math.maxInt;
44
55pub const fd_t = c_int;
66pub const pid_t = c_int;
7pub const mode_t = c_uint;
78
89pub const in_port_t = u16;
910pub const sa_family_t = u8;
......@@ -1223,3 +1224,161 @@ pub const RTLD_NEXT = @intToPtr(*c_void, ~maxInt(usize));
12231224pub const RTLD_DEFAULT = @intToPtr(*c_void, ~maxInt(usize) - 1);
12241225pub const RTLD_SELF = @intToPtr(*c_void, ~maxInt(usize) - 2);
12251226pub const RTLD_MAIN_ONLY = @intToPtr(*c_void, ~maxInt(usize) - 4);
1227
1228/// duplicate file descriptor
1229pub const F_DUPFD = 0;
1230
1231/// get file descriptor flags
1232pub const F_GETFD = 1;
1233
1234/// set file descriptor flags
1235pub const F_SETFD = 2;
1236
1237/// get file status flags
1238pub const F_GETFL = 3;
1239
1240/// set file status flags
1241pub const F_SETFL = 4;
1242
1243/// get SIGIO/SIGURG proc/pgrp
1244pub const F_GETOWN = 5;
1245
1246/// set SIGIO/SIGURG proc/pgrp
1247pub const F_SETOWN = 6;
1248
1249/// get record locking information
1250pub const F_GETLK = 7;
1251
1252/// set record locking information
1253pub const F_SETLK = 8;
1254
1255/// F_SETLK; wait if blocked
1256pub const F_SETLKW = 9;
1257
1258/// F_SETLK; wait if blocked, return on timeout
1259pub const F_SETLKWTIMEOUT = 10;
1260pub const F_FLUSH_DATA = 40;
1261
1262/// Used for regression test
1263pub const F_CHKCLEAN = 41;
1264
1265/// Preallocate storage
1266pub const F_PREALLOCATE = 42;
1267
1268/// Truncate a file without zeroing space
1269pub const F_SETSIZE = 43;
1270
1271/// Issue an advisory read async with no copy to user
1272pub const F_RDADVISE = 44;
1273
1274/// turn read ahead off/on for this fd
1275pub const F_RDAHEAD = 45;
1276
1277/// turn data caching off/on for this fd
1278pub const F_NOCACHE = 48;
1279
1280/// file offset to device offset
1281pub const F_LOG2PHYS = 49;
1282
1283/// return the full path of the fd
1284pub const F_GETPATH = 50;
1285
1286/// fsync + ask the drive to flush to the media
1287pub const F_FULLFSYNC = 51;
1288
1289/// find which component (if any) is a package
1290pub const F_PATHPKG_CHECK = 52;
1291
1292/// "freeze" all fs operations
1293pub const F_FREEZE_FS = 53;
1294
1295/// "thaw" all fs operations
1296pub const F_THAW_FS = 54;
1297
1298/// turn data caching off/on (globally) for this file
1299pub const F_GLOBAL_NOCACHE = 55;
1300
1301/// add detached signatures
1302pub const F_ADDSIGS = 59;
1303
1304/// add signature from same file (used by dyld for shared libs)
1305pub const F_ADDFILESIGS = 61;
1306
1307/// used in conjunction with F_NOCACHE to indicate that DIRECT, synchonous writes
1308/// should not be used (i.e. its ok to temporaily create cached pages)
1309pub const F_NODIRECT = 62;
1310
1311///Get the protection class of a file from the EA, returns int
1312pub const F_GETPROTECTIONCLASS = 63;
1313
1314///Set the protection class of a file for the EA, requires int
1315pub const F_SETPROTECTIONCLASS = 64;
1316
1317///file offset to device offset, extended
1318pub const F_LOG2PHYS_EXT = 65;
1319
1320///get record locking information, per-process
1321pub const F_GETLKPID = 66;
1322
1323///Mark the file as being the backing store for another filesystem
1324pub const F_SETBACKINGSTORE = 70;
1325
1326///return the full path of the FD, but error in specific mtmd circumstances
1327pub const F_GETPATH_MTMINFO = 71;
1328
1329///Returns the code directory, with associated hashes, to the caller
1330pub const F_GETCODEDIR = 72;
1331
1332///No SIGPIPE generated on EPIPE
1333pub const F_SETNOSIGPIPE = 73;
1334
1335///Status of SIGPIPE for this fd
1336pub const F_GETNOSIGPIPE = 74;
1337
1338///For some cases, we need to rewrap the key for AKS/MKB
1339pub const F_TRANSCODEKEY = 75;
1340
1341///file being written to a by single writer... if throttling enabled, writes
1342///may be broken into smaller chunks with throttling in between
1343pub const F_SINGLE_WRITER = 76;
1344
1345///Get the protection version number for this filesystem
1346pub const F_GETPROTECTIONLEVEL = 77;
1347
1348///Add detached code signatures (used by dyld for shared libs)
1349pub const F_FINDSIGS = 78;
1350
1351///Add signature from same file, only if it is signed by Apple (used by dyld for simulator)
1352pub const F_ADDFILESIGS_FOR_DYLD_SIM = 83;
1353
1354///fsync + issue barrier to drive
1355pub const F_BARRIERFSYNC = 85;
1356
1357///Add signature from same file, return end offset in structure on success
1358pub const F_ADDFILESIGS_RETURN = 97;
1359
1360///Check if Library Validation allows this Mach-O file to be mapped into the calling process
1361pub const F_CHECK_LV = 98;
1362
1363///Deallocate a range of the file
1364pub const F_PUNCHHOLE = 99;
1365
1366///Trim an active file
1367pub const F_TRIM_ACTIVE_FILE = 100;
1368
1369pub const FCNTL_FS_SPECIFIC_BASE = 0x00010000;
1370
1371///mark the dup with FD_CLOEXEC
1372pub const F_DUPFD_CLOEXEC = 67;
1373
1374///close-on-exec flag
1375pub const FD_CLOEXEC = 1;
1376
1377/// shared or read lock
1378pub const F_RDLCK = 1;
1379
1380/// unlock
1381pub const F_UNLCK = 2;
1382
1383/// exclusive or write lock
1384pub const F_WRLCK = 3;
lib/std/os/bits/dragonfly.zig+1
......@@ -7,6 +7,7 @@ pub fn S_ISCHR(m: u32) bool {
77pub const fd_t = c_int;
88pub const pid_t = c_int;
99pub const off_t = c_long;
10pub const mode_t = c_uint;
1011
1112pub const ENOTSUP = EOPNOTSUPP;
1213pub const EWOULDBLOCK = EAGAIN;
lib/std/os/bits/freebsd.zig+1
......@@ -3,6 +3,7 @@ const maxInt = std.math.maxInt;
33
44pub const fd_t = c_int;
55pub const pid_t = c_int;
6pub const mode_t = c_uint;
67
78pub const socklen_t = u32;
89
lib/std/os/bits/linux.zig+74
......@@ -1515,3 +1515,77 @@ pub const rusage = extern struct {
15151515 nivcsw: isize,
15161516 __reserved: [16]isize = [1]isize{0} ** 16,
15171517};
1518
1519pub const cc_t = u8;
1520pub const speed_t = u32;
1521pub const tcflag_t = u32;
1522
1523pub const NCCS = 32;
1524
1525pub const IGNBRK = 1;
1526pub const BRKINT = 2;
1527pub const IGNPAR = 4;
1528pub const PARMRK = 8;
1529pub const INPCK = 16;
1530pub const ISTRIP = 32;
1531pub const INLCR = 64;
1532pub const IGNCR = 128;
1533pub const ICRNL = 256;
1534pub const IUCLC = 512;
1535pub const IXON = 1024;
1536pub const IXANY = 2048;
1537pub const IXOFF = 4096;
1538pub const IMAXBEL = 8192;
1539pub const IUTF8 = 16384;
1540
1541pub const OPOST = 1;
1542pub const OLCUC = 2;
1543pub const ONLCR = 4;
1544pub const OCRNL = 8;
1545pub const ONOCR = 16;
1546pub const ONLRET = 32;
1547pub const OFILL = 64;
1548pub const OFDEL = 128;
1549pub const VTDLY = 16384;
1550pub const VT0 = 0;
1551pub const VT1 = 16384;
1552
1553pub const CSIZE = 48;
1554pub const CS5 = 0;
1555pub const CS6 = 16;
1556pub const CS7 = 32;
1557pub const CS8 = 48;
1558pub const CSTOPB = 64;
1559pub const CREAD = 128;
1560pub const PARENB = 256;
1561pub const PARODD = 512;
1562pub const HUPCL = 1024;
1563pub const CLOCAL = 2048;
1564
1565pub const ISIG = 1;
1566pub const ICANON = 2;
1567pub const ECHO = 8;
1568pub const ECHOE = 16;
1569pub const ECHOK = 32;
1570pub const ECHONL = 64;
1571pub const NOFLSH = 128;
1572pub const TOSTOP = 256;
1573pub const IEXTEN = 32768;
1574
1575pub const TCSA = extern enum(c_uint) {
1576 NOW,
1577 DRAIN,
1578 FLUSH,
1579 _,
1580};
1581
1582pub const termios = extern struct {
1583 iflag: tcflag_t,
1584 oflag: tcflag_t,
1585 cflag: tcflag_t,
1586 lflag: tcflag_t,
1587 line: cc_t,
1588 cc: [NCCS]cc_t,
1589 ispeed: speed_t,
1590 ospeed: speed_t,
1591};
lib/std/os/bits/linux/x86_64.zig+2
......@@ -12,6 +12,8 @@ const socklen_t = linux.socklen_t;
1212const iovec = linux.iovec;
1313const iovec_const = linux.iovec_const;
1414
15pub const mode_t = usize;
16
1517pub const SYS_read = 0;
1618pub const SYS_write = 1;
1719pub const SYS_open = 2;
lib/std/os/bits/netbsd.zig+1
......@@ -3,6 +3,7 @@ const maxInt = std.math.maxInt;
33
44pub const fd_t = c_int;
55pub const pid_t = c_int;
6pub const mode_t = c_uint;
67
78/// Renamed from `kevent` to `Kevent` to avoid conflict with function name.
89pub const Kevent = extern struct {
lib/std/os/bits/wasi.zig+1
......@@ -130,6 +130,7 @@ pub const EVENTTYPE_FD_WRITE: eventtype_t = 2;
130130pub const exitcode_t = u32;
131131
132132pub const fd_t = u32;
133pub const mode_t = u32;
133134
134135pub const fdflags_t = u16;
135136pub const FDFLAG_APPEND: fdflags_t = 0x0001;
lib/std/os/bits/windows.zig+1
......@@ -5,6 +5,7 @@ const ws2_32 = @import("../windows/ws2_32.zig");
55
66pub const fd_t = HANDLE;
77pub const pid_t = HANDLE;
8pub const mode_t = u0;
89
910pub const PATH_MAX = 260;
1011
lib/std/os/linux.zig+8
......@@ -1061,6 +1061,14 @@ pub fn getrusage(who: i32, usage: *rusage) usize {
10611061 return syscall2(SYS_getrusage, @bitCast(usize, @as(isize, who)), @ptrToInt(usage));
10621062}
10631063
1064pub fn tcgetattr(fd: fd_t, termios_p: *termios) usize {
1065 return syscall3(SYS_ioctl, @bitCast(usize, @as(isize, fd)), TCGETS, @ptrToInt(termios_p));
1066}
1067
1068pub fn tcsetattr(fd: fd_t, optional_action: TCSA, termios_p: *const termios) usize {
1069 return syscall3(SYS_ioctl, @bitCast(usize, @as(isize, fd)), TCSETS + @enumToInt(optional_action), @ptrToInt(termios_p));
1070}
1071
10641072test "" {
10651073 if (builtin.os == .linux) {
10661074 _ = @import("linux/test.zig");
lib/std/os/linux/i386.zig+10-4
......@@ -72,11 +72,17 @@ pub fn syscall6(
7272 arg5: usize,
7373 arg6: usize,
7474) 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.
7579 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
8086 : [ret] "={eax}" (-> usize)
8187 : [number] "{eax}" (number),
8288 [arg1] "{ebx}" (arg1),
lib/std/os/test.zig+98
......@@ -256,3 +256,101 @@ test "memfd_create" {
256256 expect(bytes_read == 4);
257257 expect(mem.eql(u8, buf[0..4], "test"));
258258}
259
260test "mmap" {
261 if (builtin.os == .windows)
262 return error.SkipZigTest;
263
264 // Simple mmap() call with non page-aligned size
265 {
266 const data = try os.mmap(
267 null,
268 1234,
269 os.PROT_READ | os.PROT_WRITE,
270 os.MAP_ANONYMOUS | os.MAP_PRIVATE,
271 -1,
272 0,
273 );
274 defer os.munmap(data);
275
276 testing.expectEqual(@as(usize, 1234), data.len);
277
278 // By definition the data returned by mmap is zero-filled
279 std.mem.set(u8, data[0 .. data.len - 1], 0x55);
280 testing.expect(mem.indexOfScalar(u8, data, 0).? == 1234 - 1);
281 }
282
283 const test_out_file = "os_tmp_test";
284 // Must be a multiple of 4096 so that the test works with mmap2
285 const alloc_size = 8 * 4096;
286
287 // Create a file used for testing mmap() calls with a file descriptor
288 {
289 const file = try fs.cwd().createFile(test_out_file, .{});
290 defer file.close();
291
292 var out_stream = file.outStream();
293 const stream = &out_stream.stream;
294
295 var i: u32 = 0;
296 while (i < alloc_size / @sizeOf(u32)) : (i += 1) {
297 try stream.writeIntNative(u32, i);
298 }
299 }
300
301 // Map the whole file
302 {
303 const file = try fs.cwd().createFile(test_out_file, .{
304 .read = true,
305 .truncate = false,
306 });
307 defer file.close();
308
309 const data = try os.mmap(
310 null,
311 alloc_size,
312 os.PROT_READ,
313 os.MAP_PRIVATE,
314 file.handle,
315 0,
316 );
317 defer os.munmap(data);
318
319 var mem_stream = io.SliceInStream.init(data);
320 const stream = &mem_stream.stream;
321
322 var i: u32 = 0;
323 while (i < alloc_size / @sizeOf(u32)) : (i += 1) {
324 testing.expectEqual(i, try stream.readIntNative(u32));
325 }
326 }
327
328 // Map the upper half of the file
329 {
330 const file = try fs.cwd().createFile(test_out_file, .{
331 .read = true,
332 .truncate = false,
333 });
334 defer file.close();
335
336 const data = try os.mmap(
337 null,
338 alloc_size,
339 os.PROT_READ,
340 os.MAP_PRIVATE,
341 file.handle,
342 alloc_size / 2,
343 );
344 defer os.munmap(data);
345
346 var mem_stream = io.SliceInStream.init(data);
347 const stream = &mem_stream.stream;
348
349 var i: u32 = alloc_size / 2 / @sizeOf(u32);
350 while (i < alloc_size / @sizeOf(u32)) : (i += 1) {
351 testing.expectEqual(i, try stream.readIntNative(u32));
352 }
353 }
354
355 try fs.cwd().deleteFile(test_out_file);
356}
lib/std/os/windows.zig+128-29
......@@ -344,24 +344,77 @@ pub fn FindClose(hFindFile: HANDLE) void {
344344 assert(kernel32.FindClose(hFindFile) != 0);
345345}
346346
347pub const ReadFileError = error{Unexpected};
348
349pub fn ReadFile(in_hFile: HANDLE, buffer: []u8) ReadFileError!usize {
350 var index: usize = 0;
351 while (index < buffer.len) {
352 const want_read_count = @intCast(DWORD, math.min(@as(DWORD, maxInt(DWORD)), buffer.len - index));
353 var amt_read: DWORD = undefined;
354 if (kernel32.ReadFile(in_hFile, buffer.ptr + index, want_read_count, &amt_read, null) == 0) {
355 switch (kernel32.GetLastError()) {
356 .OPERATION_ABORTED => continue,
357 .BROKEN_PIPE => return index,
358 else => |err| return unexpectedError(err),
347pub const ReadFileError = error{
348 OperationAborted,
349 BrokenPipe,
350 Unexpected,
351};
352
353/// If buffer's length exceeds what a Windows DWORD integer can hold, it will be broken into
354/// multiple non-atomic reads.
355pub fn ReadFile(in_hFile: HANDLE, buffer: []u8, offset: ?u64) ReadFileError!usize {
356 if (std.event.Loop.instance) |loop| {
357 // TODO support async ReadFile with no offset
358 const off = offset.?;
359 var resume_node = std.event.Loop.ResumeNode.Basic{
360 .base = .{
361 .id = .Basic,
362 .handle = @frame(),
363 .overlapped = OVERLAPPED{
364 .Internal = 0,
365 .InternalHigh = 0,
366 .Offset = @truncate(u32, off),
367 .OffsetHigh = @truncate(u32, off >> 32),
368 .hEvent = null,
369 },
370 },
371 };
372 // TODO only call create io completion port once per fd
373 _ = windows.CreateIoCompletionPort(fd, loop.os_data.io_port, undefined, undefined) catch undefined;
374 loop.beginOneEvent();
375 suspend {
376 // TODO handle buffer bigger than DWORD can hold
377 _ = windows.kernel32.ReadFile(fd, buffer.ptr, @intCast(windows.DWORD, buffer.len), null, &resume_node.base.overlapped);
378 }
379 var bytes_transferred: windows.DWORD = undefined;
380 if (windows.kernel32.GetOverlappedResult(fd, &resume_node.base.overlapped, &bytes_transferred, windows.FALSE) == 0) {
381 switch (windows.kernel32.GetLastError()) {
382 .IO_PENDING => unreachable,
383 .OPERATION_ABORTED => return error.OperationAborted,
384 .BROKEN_PIPE => return error.BrokenPipe,
385 .HANDLE_EOF => return @as(usize, bytes_transferred),
386 else => |err| return windows.unexpectedError(err),
387 }
388 }
389 return @as(usize, bytes_transferred);
390 } else {
391 var index: usize = 0;
392 while (index < buffer.len) {
393 const want_read_count = @intCast(DWORD, math.min(@as(DWORD, maxInt(DWORD)), buffer.len - index));
394 var amt_read: DWORD = undefined;
395 var overlapped_data: OVERLAPPED = undefined;
396 const overlapped: ?*OVERLAPPED = if (offset) |off| blk: {
397 overlapped_data = .{
398 .Internal = 0,
399 .InternalHigh = 0,
400 .Offset = @truncate(u32, off + index),
401 .OffsetHigh = @truncate(u32, (off + index) >> 32),
402 .hEvent = null,
403 };
404 break :blk &overlapped_data;
405 } else null;
406 if (kernel32.ReadFile(in_hFile, buffer.ptr + index, want_read_count, &amt_read, overlapped) == 0) {
407 switch (kernel32.GetLastError()) {
408 .OPERATION_ABORTED => continue,
409 .BROKEN_PIPE => return index,
410 else => |err| return unexpectedError(err),
411 }
359412 }
413 if (amt_read == 0) return index;
414 index += amt_read;
360415 }
361 if (amt_read == 0) return index;
362 index += amt_read;
416 return index;
363417 }
364 return index;
365418}
366419
367420pub const WriteFileError = error{
......@@ -371,20 +424,66 @@ pub const WriteFileError = error{
371424 Unexpected,
372425};
373426
374/// This function is for blocking file descriptors only. For non-blocking, see
375/// `WriteFileAsync`.
376pub fn WriteFile(handle: HANDLE, bytes: []const u8) WriteFileError!void {
377 var bytes_written: DWORD = undefined;
378 // TODO replace this @intCast with a loop that writes all the bytes
379 if (kernel32.WriteFile(handle, bytes.ptr, @intCast(u32, bytes.len), &bytes_written, null) == 0) {
380 switch (kernel32.GetLastError()) {
381 .INVALID_USER_BUFFER => return error.SystemResources,
382 .NOT_ENOUGH_MEMORY => return error.SystemResources,
383 .OPERATION_ABORTED => return error.OperationAborted,
384 .NOT_ENOUGH_QUOTA => return error.SystemResources,
385 .IO_PENDING => unreachable, // this function is for blocking files only
386 .BROKEN_PIPE => return error.BrokenPipe,
387 else => |err| return unexpectedError(err),
427pub fn WriteFile(handle: HANDLE, bytes: []const u8, offset: ?u64) WriteFileError!void {
428 if (std.event.Loop.instance) |loop| {
429 // TODO support async WriteFile with no offset
430 const off = offset.?;
431 var resume_node = std.event.Loop.ResumeNode.Basic{
432 .base = .{
433 .id = .Basic,
434 .handle = @frame(),
435 .overlapped = OVERLAPPED{
436 .Internal = 0,
437 .InternalHigh = 0,
438 .Offset = @truncate(u32, off),
439 .OffsetHigh = @truncate(u32, off >> 32),
440 .hEvent = null,
441 },
442 },
443 };
444 // TODO only call create io completion port once per fd
445 _ = CreateIoCompletionPort(fd, loop.os_data.io_port, undefined, undefined);
446 loop.beginOneEvent();
447 suspend {
448 // TODO replace this @intCast with a loop that writes all the bytes
449 _ = kernel32.WriteFile(fd, bytes.ptr, @intCast(windows.DWORD, bytes.len), null, &resume_node.base.overlapped);
450 }
451 var bytes_transferred: windows.DWORD = undefined;
452 if (kernel32.GetOverlappedResult(fd, &resume_node.base.overlapped, &bytes_transferred, FALSE) == 0) {
453 switch (kernel32.GetLastError()) {
454 .IO_PENDING => unreachable,
455 .INVALID_USER_BUFFER => return error.SystemResources,
456 .NOT_ENOUGH_MEMORY => return error.SystemResources,
457 .OPERATION_ABORTED => return error.OperationAborted,
458 .NOT_ENOUGH_QUOTA => return error.SystemResources,
459 .BROKEN_PIPE => return error.BrokenPipe,
460 else => |err| return windows.unexpectedError(err),
461 }
462 }
463 } else {
464 var bytes_written: DWORD = undefined;
465 var overlapped_data: OVERLAPPED = undefined;
466 const overlapped: ?*OVERLAPPED = if (offset) |off| blk: {
467 overlapped_data = .{
468 .Internal = 0,
469 .InternalHigh = 0,
470 .Offset = @truncate(u32, off),
471 .OffsetHigh = @truncate(u32, off >> 32),
472 .hEvent = null,
473 };
474 break :blk &overlapped_data;
475 } else null;
476 // TODO replace this @intCast with a loop that writes all the bytes
477 if (kernel32.WriteFile(handle, bytes.ptr, @intCast(u32, bytes.len), &bytes_written, overlapped) == 0) {
478 switch (kernel32.GetLastError()) {
479 .INVALID_USER_BUFFER => return error.SystemResources,
480 .NOT_ENOUGH_MEMORY => return error.SystemResources,
481 .OPERATION_ABORTED => return error.OperationAborted,
482 .NOT_ENOUGH_QUOTA => return error.SystemResources,
483 .IO_PENDING => unreachable, // this function is for blocking files only
484 .BROKEN_PIPE => return error.BrokenPipe,
485 else => |err| return unexpectedError(err),
486 }
388487 }
389488 }
390489}
lib/std/rand.zig+26
......@@ -733,6 +733,32 @@ test "xoroshiro sequence" {
733733 }
734734}
735735
736// Gimli
737//
738// CSPRNG
739pub const Gimli = struct {
740 random: Random,
741 state: std.crypto.gimli.State,
742
743 pub fn init(init_s: u64) Gimli {
744 var self = Gimli{
745 .random = Random{ .fillFn = fill },
746 .state = std.crypto.gimli.State{
747 .data = [_]u32{0} ** (std.crypto.gimli.State.BLOCKBYTES / 4),
748 },
749 };
750 self.state.data[0] = @truncate(u32, init_s >> 32);
751 self.state.data[1] = @truncate(u32, init_s);
752 return self;
753 }
754
755 fn fill(r: *Random, buf: []u8) void {
756 const self = @fieldParentPtr(Gimli, "random", r);
757
758 self.state.squeeze(buf);
759 }
760};
761
736762// ISAAC64 - http://www.burtleburtle.net/bob/rand/isaacafa.html
737763//
738764// CSPRNG
lib/std/special/compiler_rt/clzsi2.zig+2-1
......@@ -45,8 +45,9 @@ fn __clzsi2_thumb1() callconv(.Naked) void {
4545 \\ subs r0, r1, r0
4646 \\ bx lr
4747 \\ .p2align 2
48 \\ // Number of bits set in the 0-15 range
4849 \\ 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
5051 );
5152
5253 unreachable;
lib/std/special/test_runner.zig+25-1
......@@ -2,6 +2,8 @@ const std = @import("std");
22const io = std.io;
33const builtin = @import("builtin");
44
5pub const io_mode: io.Mode = builtin.test_io_mode;
6
57pub fn main() anyerror!void {
68 const test_fn_list = builtin.test_functions;
79 var ok_count: usize = 0;
......@@ -12,6 +14,11 @@ pub fn main() anyerror!void {
1214 error.TimerUnsupported => @panic("timer unsupported"),
1315 };
1416
17 var async_frame_buffer: []align(std.Target.stack_align) u8 = undefined;
18 // TODO this is on the next line (using `undefined` above) because otherwise zig incorrectly
19 // ignores the alignment of the slice.
20 async_frame_buffer = &[_]u8{};
21
1522 for (test_fn_list) |test_fn, i| {
1623 std.testing.base_allocator_instance.reset();
1724
......@@ -21,7 +28,24 @@ pub fn main() anyerror!void {
2128 if (progress.terminal == null) {
2229 std.debug.warn("{}/{} {}...", .{ i + 1, test_fn_list.len, test_fn.name });
2330 }
24 if (test_fn.func()) |_| {
31 const result = if (test_fn.async_frame_size) |size| switch (io_mode) {
32 .evented => blk: {
33 if (async_frame_buffer.len < size) {
34 std.heap.page_allocator.free(async_frame_buffer);
35 async_frame_buffer = try std.heap.page_allocator.alignedAlloc(u8, std.Target.stack_align, size);
36 }
37 const casted_fn = @ptrCast(async fn () anyerror!void, test_fn.func);
38 break :blk await @asyncCall(async_frame_buffer, {}, casted_fn);
39 },
40 .blocking => {
41 skip_count += 1;
42 test_node.end();
43 progress.log("{}...SKIP (async test)\n", .{test_fn.name});
44 if (progress.terminal == null) std.debug.warn("SKIP (async test)\n", .{});
45 continue;
46 },
47 } else test_fn.func();
48 if (result) |_| {
2549 ok_count += 1;
2650 test_node.end();
2751 std.testing.allocator_instance.validate() catch |err| switch (err) {
lib/std/start.zig+1-1
......@@ -21,7 +21,7 @@ comptime {
2121 @export(main, .{ .name = "main", .linkage = .Weak });
2222 }
2323 } 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")) {
2525 @export(WinMainCRTStartup, .{ .name = "WinMainCRTStartup" });
2626 }
2727 } else if (builtin.os == .uefi) {
lib/std/target.zig+9
......@@ -242,6 +242,13 @@ pub const Target = union(enum) {
242242 };
243243 }
244244
245 pub fn isRISCV(arch: Arch) bool {
246 return switch (arch) {
247 .riscv32, .riscv64 => true,
248 else => false,
249 };
250 }
251
245252 pub fn isMIPS(arch: Arch) bool {
246253 return switch (arch) {
247254 .mips, .mipsel, .mips64, .mips64el => true,
......@@ -598,6 +605,8 @@ pub const Target = union(enum) {
598605 }
599606
600607 pub fn populateDependencies(set: *Set, all_features_list: []const Cpu.Feature) void {
608 @setEvalBranchQuota(1000000);
609
601610 var old = set.ints;
602611 while (true) {
603612 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
571571 }
572572 }
573573
574 const len = result.len;
574575 try result.append(0);
575 return result.toOwnedSlice()[0..:0];
576 return result.toOwnedSlice()[0..len :0];
576577}
577578
578579/// Returns index of next character. If exact fit, returned index equals output slice length.
......@@ -619,12 +620,14 @@ test "utf8ToUtf16LeWithNull" {
619620 var bytes: [128]u8 = undefined;
620621 const allocator = &std.heap.FixedBufferAllocator.init(bytes[0..]).allocator;
621622 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);
623625 }
624626 {
625627 var bytes: [128]u8 = undefined;
626628 const allocator = &std.heap.FixedBufferAllocator.init(bytes[0..]).allocator;
627629 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);
629632 }
630633}
src-self-hosted/c_tokenizer.zig deleted-977
......@@ -1,977 +0,0 @@
1const std = @import("std");
2const expect = std.testing.expect;
3const ZigClangSourceLocation = @import("clang.zig").ZigClangSourceLocation;
4const Context = @import("translate_c.zig").Context;
5const failDecl = @import("translate_c.zig").failDecl;
6
7pub const TokenList = std.SegmentedList(CToken, 32);
8
9pub const CToken = struct {
10 id: Id,
11 bytes: []const u8 = "",
12 num_lit_suffix: NumLitSuffix = .None,
13
14 pub const Id = enum {
15 CharLit,
16 StrLit,
17 NumLitInt,
18 NumLitFloat,
19 Identifier,
20 Plus,
21 Minus,
22 Slash,
23 LParen,
24 RParen,
25 Eof,
26 Dot,
27 Asterisk, // *
28 Ampersand, // &
29 And, // &&
30 Assign, // =
31 Or, // ||
32 Bang, // !
33 Tilde, // ~
34 Shl, // <<
35 Shr, // >>
36 Lt, // <
37 Lte, // <=
38 Gt, // >
39 Gte, // >=
40 Eq, // ==
41 Ne, // !=
42 Increment, // ++
43 Decrement, // --
44 Comma,
45 Fn,
46 Arrow, // ->
47 LBrace,
48 RBrace,
49 Pipe,
50 QuestionMark,
51 Colon,
52 };
53
54 pub const NumLitSuffix = enum {
55 None,
56 F,
57 L,
58 U,
59 LU,
60 LL,
61 LLU,
62 };
63};
64
65pub fn tokenizeCMacro(ctx: *Context, loc: ZigClangSourceLocation, name: []const u8, tl: *TokenList, chars: [*:0]const u8) !void {
66 var index: usize = 0;
67 var first = true;
68 while (true) {
69 const tok = try next(ctx, loc, name, chars, &index);
70 if (tok.id == .StrLit or tok.id == .CharLit)
71 try tl.push(try zigifyEscapeSequences(ctx, loc, name, tl.allocator, tok))
72 else
73 try tl.push(tok);
74 if (tok.id == .Eof)
75 return;
76 if (first) {
77 // distinguish NAME (EXPR) from NAME(ARGS)
78 first = false;
79 if (chars[index] == '(') {
80 try tl.push(.{
81 .id = .Fn,
82 .bytes = "",
83 });
84 }
85 }
86 }
87}
88
89fn zigifyEscapeSequences(ctx: *Context, loc: ZigClangSourceLocation, name: []const u8, allocator: *std.mem.Allocator, tok: CToken) !CToken {
90 for (tok.bytes) |c| {
91 if (c == '\\') {
92 break;
93 }
94 } else return tok;
95 var bytes = try allocator.alloc(u8, tok.bytes.len * 2);
96 var state: enum {
97 Start,
98 Escape,
99 Hex,
100 Octal,
101 } = .Start;
102 var i: usize = 0;
103 var count: u8 = 0;
104 var num: u8 = 0;
105 for (tok.bytes) |c| {
106 switch (state) {
107 .Escape => {
108 switch (c) {
109 'n', 'r', 't', '\\', '\'', '\"' => {
110 bytes[i] = c;
111 },
112 '0'...'7' => {
113 count += 1;
114 num += c - '0';
115 state = .Octal;
116 bytes[i] = 'x';
117 },
118 'x' => {
119 state = .Hex;
120 bytes[i] = 'x';
121 },
122 'a' => {
123 bytes[i] = 'x';
124 i += 1;
125 bytes[i] = '0';
126 i += 1;
127 bytes[i] = '7';
128 },
129 'b' => {
130 bytes[i] = 'x';
131 i += 1;
132 bytes[i] = '0';
133 i += 1;
134 bytes[i] = '8';
135 },
136 'f' => {
137 bytes[i] = 'x';
138 i += 1;
139 bytes[i] = '0';
140 i += 1;
141 bytes[i] = 'C';
142 },
143 'v' => {
144 bytes[i] = 'x';
145 i += 1;
146 bytes[i] = '0';
147 i += 1;
148 bytes[i] = 'B';
149 },
150 '?' => {
151 i -= 1;
152 bytes[i] = '?';
153 },
154 'u', 'U' => {
155 try failDecl(ctx, loc, name, "macro tokenizing failed: TODO unicode escape sequences", .{});
156 return error.TokenizingFailed;
157 },
158 else => {
159 try failDecl(ctx, loc, name, "macro tokenizing failed: unknown escape sequence", .{});
160 return error.TokenizingFailed;
161 },
162 }
163 i += 1;
164 if (state == .Escape)
165 state = .Start;
166 },
167 .Start => {
168 if (c == '\\') {
169 state = .Escape;
170 }
171 bytes[i] = c;
172 i += 1;
173 },
174 .Hex => {
175 switch (c) {
176 '0'...'9' => {
177 num = std.math.mul(u8, num, 16) catch {
178 try failDecl(ctx, loc, name, "macro tokenizing failed: hex literal overflowed", .{});
179 return error.TokenizingFailed;
180 };
181 num += c - '0';
182 },
183 'a'...'f' => {
184 num = std.math.mul(u8, num, 16) catch {
185 try failDecl(ctx, loc, name, "macro tokenizing failed: hex literal overflowed", .{});
186 return error.TokenizingFailed;
187 };
188 num += c - 'a' + 10;
189 },
190 'A'...'F' => {
191 num = std.math.mul(u8, num, 16) catch {
192 try failDecl(ctx, loc, name, "macro tokenizing failed: hex literal overflowed", .{});
193 return error.TokenizingFailed;
194 };
195 num += c - 'A' + 10;
196 },
197 else => {
198 i += std.fmt.formatIntBuf(bytes[i..], num, 16, false, std.fmt.FormatOptions{ .fill = '0', .width = 2 });
199 num = 0;
200 if (c == '\\')
201 state = .Escape
202 else
203 state = .Start;
204 bytes[i] = c;
205 i += 1;
206 },
207 }
208 },
209 .Octal => {
210 const accept_digit = switch (c) {
211 // The maximum length of a octal literal is 3 digits
212 '0'...'7' => count < 3,
213 else => false,
214 };
215
216 if (accept_digit) {
217 count += 1;
218 num = std.math.mul(u8, num, 8) catch {
219 try failDecl(ctx, loc, name, "macro tokenizing failed: octal literal overflowed", .{});
220 return error.TokenizingFailed;
221 };
222 num += c - '0';
223 } else {
224 i += std.fmt.formatIntBuf(bytes[i..], num, 16, false, std.fmt.FormatOptions{ .fill = '0', .width = 2 });
225 num = 0;
226 count = 0;
227 if (c == '\\')
228 state = .Escape
229 else
230 state = .Start;
231 bytes[i] = c;
232 i += 1;
233 }
234 },
235 }
236 }
237 if (state == .Hex or state == .Octal)
238 i += std.fmt.formatIntBuf(bytes[i..], num, 16, false, std.fmt.FormatOptions{ .fill = '0', .width = 2 });
239 return CToken{
240 .id = tok.id,
241 .bytes = bytes[0..i],
242 };
243}
244
245fn next(ctx: *Context, loc: ZigClangSourceLocation, name: []const u8, chars: [*:0]const u8, i: *usize) !CToken {
246 var state: enum {
247 Start,
248 SawLt,
249 SawGt,
250 SawPlus,
251 SawMinus,
252 SawAmpersand,
253 SawPipe,
254 SawBang,
255 SawEq,
256 CharLit,
257 OpenComment,
258 Comment,
259 CommentStar,
260 Backslash,
261 String,
262 Identifier,
263 Decimal,
264 Octal,
265 SawZero,
266 Hex,
267 Bin,
268 Float,
269 ExpSign,
270 FloatExp,
271 FloatExpFirst,
272 NumLitIntSuffixU,
273 NumLitIntSuffixL,
274 NumLitIntSuffixLL,
275 NumLitIntSuffixUL,
276 Done,
277 } = .Start;
278
279 var result = CToken{
280 .bytes = "",
281 .id = .Eof,
282 };
283 var begin_index: usize = 0;
284 var digits: u8 = 0;
285 var pre_escape = state;
286
287 while (true) {
288 const c = chars[i.*];
289 if (c == 0) {
290 switch (state) {
291 .Identifier,
292 .Decimal,
293 .Hex,
294 .Bin,
295 .Octal,
296 .SawZero,
297 .Float,
298 .FloatExp,
299 => {
300 result.bytes = chars[begin_index..i.*];
301 return result;
302 },
303 .Start,
304 .SawMinus,
305 .Done,
306 .NumLitIntSuffixU,
307 .NumLitIntSuffixL,
308 .NumLitIntSuffixUL,
309 .NumLitIntSuffixLL,
310 .SawLt,
311 .SawGt,
312 .SawPlus,
313 .SawAmpersand,
314 .SawPipe,
315 .SawBang,
316 .SawEq,
317 => {
318 return result;
319 },
320 .CharLit,
321 .OpenComment,
322 .Comment,
323 .CommentStar,
324 .Backslash,
325 .String,
326 .ExpSign,
327 .FloatExpFirst,
328 => {
329 try failDecl(ctx, loc, name, "macro tokenizing failed: unexpected EOF", .{});
330 return error.TokenizingFailed;
331 },
332 }
333 }
334 switch (state) {
335 .Start => {
336 switch (c) {
337 ' ', '\t', '\x0B', '\x0C' => {},
338 '\'' => {
339 state = .CharLit;
340 result.id = .CharLit;
341 begin_index = i.*;
342 },
343 '\"' => {
344 state = .String;
345 result.id = .StrLit;
346 begin_index = i.*;
347 },
348 '/' => {
349 state = .OpenComment;
350 },
351 '\\' => {
352 state = .Backslash;
353 },
354 '\n', '\r' => {
355 return result;
356 },
357 'a'...'z', 'A'...'Z', '_' => {
358 state = .Identifier;
359 result.id = .Identifier;
360 begin_index = i.*;
361 },
362 '1'...'9' => {
363 state = .Decimal;
364 result.id = .NumLitInt;
365 begin_index = i.*;
366 },
367 '0' => {
368 state = .SawZero;
369 result.id = .NumLitInt;
370 begin_index = i.*;
371 },
372 '.' => {
373 result.id = .Dot;
374 state = .Done;
375 },
376 '<' => {
377 result.id = .Lt;
378 state = .SawLt;
379 },
380 '>' => {
381 result.id = .Gt;
382 state = .SawGt;
383 },
384 '(' => {
385 result.id = .LParen;
386 state = .Done;
387 },
388 ')' => {
389 result.id = .RParen;
390 state = .Done;
391 },
392 '*' => {
393 result.id = .Asterisk;
394 state = .Done;
395 },
396 '+' => {
397 result.id = .Plus;
398 state = .SawPlus;
399 },
400 '-' => {
401 result.id = .Minus;
402 state = .SawMinus;
403 },
404 '!' => {
405 result.id = .Bang;
406 state = .SawBang;
407 },
408 '~' => {
409 result.id = .Tilde;
410 state = .Done;
411 },
412 '=' => {
413 result.id = .Assign;
414 state = .SawEq;
415 },
416 ',' => {
417 result.id = .Comma;
418 state = .Done;
419 },
420 '[' => {
421 result.id = .LBrace;
422 state = .Done;
423 },
424 ']' => {
425 result.id = .RBrace;
426 state = .Done;
427 },
428 '|' => {
429 result.id = .Pipe;
430 state = .SawPipe;
431 },
432 '&' => {
433 result.id = .Ampersand;
434 state = .SawAmpersand;
435 },
436 '?' => {
437 result.id = .QuestionMark;
438 state = .Done;
439 },
440 ':' => {
441 result.id = .Colon;
442 state = .Done;
443 },
444 else => {
445 try failDecl(ctx, loc, name, "macro tokenizing failed: unexpected character '{c}'", .{c});
446 return error.TokenizingFailed;
447 },
448 }
449 },
450 .Done => return result,
451 .SawMinus => {
452 switch (c) {
453 '>' => {
454 result.id = .Arrow;
455 state = .Done;
456 },
457 '-' => {
458 result.id = .Decrement;
459 state = .Done;
460 },
461 else => return result,
462 }
463 },
464 .SawPlus => {
465 switch (c) {
466 '+' => {
467 result.id = .Increment;
468 state = .Done;
469 },
470 else => return result,
471 }
472 },
473 .SawLt => {
474 switch (c) {
475 '<' => {
476 result.id = .Shl;
477 state = .Done;
478 },
479 '=' => {
480 result.id = .Lte;
481 state = .Done;
482 },
483 else => return result,
484 }
485 },
486 .SawGt => {
487 switch (c) {
488 '>' => {
489 result.id = .Shr;
490 state = .Done;
491 },
492 '=' => {
493 result.id = .Gte;
494 state = .Done;
495 },
496 else => return result,
497 }
498 },
499 .SawPipe => {
500 switch (c) {
501 '|' => {
502 result.id = .Or;
503 state = .Done;
504 },
505 else => return result,
506 }
507 },
508 .SawAmpersand => {
509 switch (c) {
510 '&' => {
511 result.id = .And;
512 state = .Done;
513 },
514 else => return result,
515 }
516 },
517 .SawBang => {
518 switch (c) {
519 '=' => {
520 result.id = .Ne;
521 state = .Done;
522 },
523 else => return result,
524 }
525 },
526 .SawEq => {
527 switch (c) {
528 '=' => {
529 result.id = .Eq;
530 state = .Done;
531 },
532 else => return result,
533 }
534 },
535 .Float => {
536 switch (c) {
537 '.', '0'...'9' => {},
538 'e', 'E' => {
539 state = .ExpSign;
540 },
541 'f',
542 'F',
543 => {
544 result.num_lit_suffix = .F;
545 result.bytes = chars[begin_index..i.*];
546 state = .Done;
547 },
548 'l', 'L' => {
549 result.num_lit_suffix = .L;
550 result.bytes = chars[begin_index..i.*];
551 state = .Done;
552 },
553 else => {
554 result.bytes = chars[begin_index..i.*];
555 return result;
556 },
557 }
558 },
559 .ExpSign => {
560 switch (c) {
561 '+', '-' => {
562 state = .FloatExpFirst;
563 },
564 '0'...'9' => {
565 state = .FloatExp;
566 },
567 else => {
568 try failDecl(ctx, loc, name, "macro tokenizing failed: expected a digit or '+' or '-'", .{});
569 return error.TokenizingFailed;
570 },
571 }
572 },
573 .FloatExpFirst => {
574 switch (c) {
575 '0'...'9' => {
576 state = .FloatExp;
577 },
578 else => {
579 try failDecl(ctx, loc, name, "macro tokenizing failed: expected a digit", .{});
580 return error.TokenizingFailed;
581 },
582 }
583 },
584 .FloatExp => {
585 switch (c) {
586 '0'...'9' => {},
587 'f', 'F' => {
588 result.num_lit_suffix = .F;
589 result.bytes = chars[begin_index..i.*];
590 state = .Done;
591 },
592 'l', 'L' => {
593 result.num_lit_suffix = .L;
594 result.bytes = chars[begin_index..i.*];
595 state = .Done;
596 },
597 else => {
598 result.bytes = chars[begin_index..i.*];
599 return result;
600 },
601 }
602 },
603 .Decimal => {
604 switch (c) {
605 '0'...'9' => {},
606 '\'' => {},
607 'u', 'U' => {
608 state = .NumLitIntSuffixU;
609 result.num_lit_suffix = .U;
610 result.bytes = chars[begin_index..i.*];
611 },
612 'l', 'L' => {
613 state = .NumLitIntSuffixL;
614 result.num_lit_suffix = .L;
615 result.bytes = chars[begin_index..i.*];
616 },
617 '.' => {
618 result.id = .NumLitFloat;
619 state = .Float;
620 },
621 else => {
622 result.bytes = chars[begin_index..i.*];
623 return result;
624 },
625 }
626 },
627 .SawZero => {
628 switch (c) {
629 'x', 'X' => {
630 state = .Hex;
631 },
632 'b', 'B' => {
633 state = .Bin;
634 },
635 '.' => {
636 state = .Float;
637 result.id = .NumLitFloat;
638 },
639 'u', 'U' => {
640 state = .NumLitIntSuffixU;
641 result.num_lit_suffix = .U;
642 result.bytes = chars[begin_index..i.*];
643 },
644 'l', 'L' => {
645 state = .NumLitIntSuffixL;
646 result.num_lit_suffix = .L;
647 result.bytes = chars[begin_index..i.*];
648 },
649 else => {
650 i.* -= 1;
651 state = .Octal;
652 },
653 }
654 },
655 .Octal => {
656 switch (c) {
657 '0'...'7' => {},
658 '8', '9' => {
659 try failDecl(ctx, loc, name, "macro tokenizing failed: invalid digit '{c}' in octal number", .{c});
660 return error.TokenizingFailed;
661 },
662 'u', 'U' => {
663 state = .NumLitIntSuffixU;
664 result.num_lit_suffix = .U;
665 result.bytes = chars[begin_index..i.*];
666 },
667 'l', 'L' => {
668 state = .NumLitIntSuffixL;
669 result.num_lit_suffix = .L;
670 result.bytes = chars[begin_index..i.*];
671 },
672 else => {
673 result.bytes = chars[begin_index..i.*];
674 return result;
675 },
676 }
677 },
678 .Hex => {
679 switch (c) {
680 '0'...'9', 'a'...'f', 'A'...'F' => {},
681 'u', 'U' => {
682 // marks the number literal as unsigned
683 state = .NumLitIntSuffixU;
684 result.num_lit_suffix = .U;
685 result.bytes = chars[begin_index..i.*];
686 },
687 'l', 'L' => {
688 // marks the number literal as long
689 state = .NumLitIntSuffixL;
690 result.num_lit_suffix = .L;
691 result.bytes = chars[begin_index..i.*];
692 },
693 else => {
694 result.bytes = chars[begin_index..i.*];
695 return result;
696 },
697 }
698 },
699 .Bin => {
700 switch (c) {
701 '0'...'1' => {},
702 '2'...'9' => {
703 try failDecl(ctx, loc, name, "macro tokenizing failed: invalid digit '{c}' in binary number", .{c});
704 return error.TokenizingFailed;
705 },
706 'u', 'U' => {
707 // marks the number literal as unsigned
708 state = .NumLitIntSuffixU;
709 result.num_lit_suffix = .U;
710 result.bytes = chars[begin_index..i.*];
711 },
712 'l', 'L' => {
713 // marks the number literal as long
714 state = .NumLitIntSuffixL;
715 result.num_lit_suffix = .L;
716 result.bytes = chars[begin_index..i.*];
717 },
718 else => {
719 result.bytes = chars[begin_index..i.*];
720 return result;
721 },
722 }
723 },
724 .NumLitIntSuffixU => {
725 switch (c) {
726 'l', 'L' => {
727 result.num_lit_suffix = .LU;
728 state = .NumLitIntSuffixUL;
729 },
730 else => {
731 return result;
732 },
733 }
734 },
735 .NumLitIntSuffixL => {
736 switch (c) {
737 'l', 'L' => {
738 result.num_lit_suffix = .LL;
739 state = .NumLitIntSuffixLL;
740 },
741 'u', 'U' => {
742 result.num_lit_suffix = .LU;
743 state = .Done;
744 },
745 else => {
746 return result;
747 },
748 }
749 },
750 .NumLitIntSuffixLL => {
751 switch (c) {
752 'u', 'U' => {
753 result.num_lit_suffix = .LLU;
754 state = .Done;
755 },
756 else => {
757 return result;
758 },
759 }
760 },
761 .NumLitIntSuffixUL => {
762 switch (c) {
763 'l', 'L' => {
764 result.num_lit_suffix = .LLU;
765 state = .Done;
766 },
767 else => {
768 return result;
769 },
770 }
771 },
772 .Identifier => {
773 switch (c) {
774 '_', 'a'...'z', 'A'...'Z', '0'...'9' => {},
775 else => {
776 result.bytes = chars[begin_index..i.*];
777 return result;
778 },
779 }
780 },
781 .String => {
782 switch (c) {
783 '\"' => {
784 result.bytes = chars[begin_index .. i.* + 1];
785 state = .Done;
786 },
787 else => {},
788 }
789 },
790 .CharLit => {
791 switch (c) {
792 '\'' => {
793 result.bytes = chars[begin_index .. i.* + 1];
794 state = .Done;
795 },
796 else => {},
797 }
798 },
799 .OpenComment => {
800 switch (c) {
801 '/' => {
802 return result;
803 },
804 '*' => {
805 state = .Comment;
806 },
807 else => {
808 result.id = .Slash;
809 state = .Done;
810 },
811 }
812 },
813 .Comment => {
814 switch (c) {
815 '*' => {
816 state = .CommentStar;
817 },
818 else => {},
819 }
820 },
821 .CommentStar => {
822 switch (c) {
823 '/' => {
824 state = .Start;
825 },
826 else => {
827 state = .Comment;
828 },
829 }
830 },
831 .Backslash => {
832 switch (c) {
833 ' ', '\t', '\x0B', '\x0C' => {},
834 '\n', '\r' => {
835 state = .Start;
836 },
837 else => {
838 try failDecl(ctx, loc, name, "macro tokenizing failed: expected whitespace", .{});
839 return error.TokenizingFailed;
840 },
841 }
842 },
843 }
844 i.* += 1;
845 }
846 unreachable;
847}
848
849fn expectTokens(tl: *TokenList, src: [*:0]const u8, expected: []CToken) void {
850 // these can be undefined since they are only used for error reporting
851 tokenizeCMacro(undefined, undefined, undefined, tl, src) catch unreachable;
852 var it = tl.iterator(0);
853 for (expected) |t| {
854 var tok = it.next().?;
855 std.testing.expectEqual(t.id, tok.id);
856 if (t.bytes.len > 0) {
857 //std.debug.warn(" {} = {}\n", .{tok.bytes, t.bytes});
858 std.testing.expectEqualSlices(u8, tok.bytes, t.bytes);
859 }
860 if (t.num_lit_suffix != .None) {
861 std.testing.expectEqual(t.num_lit_suffix, tok.num_lit_suffix);
862 }
863 }
864 std.testing.expect(it.next() == null);
865 tl.shrink(0);
866}
867
868test "tokenize macro" {
869 var tl = TokenList.init(std.testing.allocator);
870 defer tl.deinit();
871
872 expectTokens(&tl, "TEST(0\n", &[_]CToken{
873 .{ .id = .Identifier, .bytes = "TEST" },
874 .{ .id = .Fn },
875 .{ .id = .LParen },
876 .{ .id = .NumLitInt, .bytes = "0" },
877 .{ .id = .Eof },
878 });
879
880 expectTokens(&tl, "__FLT_MIN_10_EXP__ -37\n", &[_]CToken{
881 .{ .id = .Identifier, .bytes = "__FLT_MIN_10_EXP__" },
882 .{ .id = .Minus },
883 .{ .id = .NumLitInt, .bytes = "37" },
884 .{ .id = .Eof },
885 });
886
887 expectTokens(&tl, "__llvm__ 1\n#define", &[_]CToken{
888 .{ .id = .Identifier, .bytes = "__llvm__" },
889 .{ .id = .NumLitInt, .bytes = "1" },
890 .{ .id = .Eof },
891 });
892
893 expectTokens(&tl, "TEST 2", &[_]CToken{
894 .{ .id = .Identifier, .bytes = "TEST" },
895 .{ .id = .NumLitInt, .bytes = "2" },
896 .{ .id = .Eof },
897 });
898
899 expectTokens(&tl, "FOO 0ull", &[_]CToken{
900 .{ .id = .Identifier, .bytes = "FOO" },
901 .{ .id = .NumLitInt, .bytes = "0", .num_lit_suffix = .LLU },
902 .{ .id = .Eof },
903 });
904}
905
906test "tokenize macro ops" {
907 var tl = TokenList.init(std.testing.allocator);
908 defer tl.deinit();
909
910 expectTokens(&tl, "ADD A + B", &[_]CToken{
911 .{ .id = .Identifier, .bytes = "ADD" },
912 .{ .id = .Identifier, .bytes = "A" },
913 .{ .id = .Plus },
914 .{ .id = .Identifier, .bytes = "B" },
915 .{ .id = .Eof },
916 });
917
918 expectTokens(&tl, "ADD (A) + B", &[_]CToken{
919 .{ .id = .Identifier, .bytes = "ADD" },
920 .{ .id = .LParen },
921 .{ .id = .Identifier, .bytes = "A" },
922 .{ .id = .RParen },
923 .{ .id = .Plus },
924 .{ .id = .Identifier, .bytes = "B" },
925 .{ .id = .Eof },
926 });
927
928 expectTokens(&tl, "ADD (A) + B", &[_]CToken{
929 .{ .id = .Identifier, .bytes = "ADD" },
930 .{ .id = .LParen },
931 .{ .id = .Identifier, .bytes = "A" },
932 .{ .id = .RParen },
933 .{ .id = .Plus },
934 .{ .id = .Identifier, .bytes = "B" },
935 .{ .id = .Eof },
936 });
937}
938
939test "escape sequences" {
940 var buf: [1024]u8 = undefined;
941 var alloc = std.heap.FixedBufferAllocator.init(buf[0..]);
942 const a = &alloc.allocator;
943 // these can be undefined since they are only used for error reporting
944 expect(std.mem.eql(u8, (try zigifyEscapeSequences(undefined, undefined, undefined, a, .{
945 .id = .StrLit,
946 .bytes = "\\x0077",
947 })).bytes, "\\x77"));
948 expect(std.mem.eql(u8, (try zigifyEscapeSequences(undefined, undefined, undefined, a, .{
949 .id = .StrLit,
950 .bytes = "\\24500",
951 })).bytes, "\\xa500"));
952 expect(std.mem.eql(u8, (try zigifyEscapeSequences(undefined, undefined, undefined, a, .{
953 .id = .StrLit,
954 .bytes = "\\x0077 abc",
955 })).bytes, "\\x77 abc"));
956 expect(std.mem.eql(u8, (try zigifyEscapeSequences(undefined, undefined, undefined, a, .{
957 .id = .StrLit,
958 .bytes = "\\045abc",
959 })).bytes, "\\x25abc"));
960
961 expect(std.mem.eql(u8, (try zigifyEscapeSequences(undefined, undefined, undefined, a, .{
962 .id = .CharLit,
963 .bytes = "\\0",
964 })).bytes, "\\x00"));
965 expect(std.mem.eql(u8, (try zigifyEscapeSequences(undefined, undefined, undefined, a, .{
966 .id = .CharLit,
967 .bytes = "\\00",
968 })).bytes, "\\x00"));
969 expect(std.mem.eql(u8, (try zigifyEscapeSequences(undefined, undefined, undefined, a, .{
970 .id = .CharLit,
971 .bytes = "\\000\\001",
972 })).bytes, "\\x00\\x01"));
973 expect(std.mem.eql(u8, (try zigifyEscapeSequences(undefined, undefined, undefined, a, .{
974 .id = .CharLit,
975 .bytes = "\\000abc",
976 })).bytes, "\\x00abc"));
977}
src-self-hosted/compilation.zig+12-12
......@@ -29,7 +29,7 @@ const Package = @import("package.zig").Package;
2929const link = @import("link.zig").link;
3030const LibCInstallation = @import("libc_installation.zig").LibCInstallation;
3131const CInt = @import("c_int.zig").CInt;
32const fs = event.fs;
32const fs = std.fs;
3333const util = @import("util.zig");
3434
3535const max_src_size = 2 * 1024 * 1024 * 1024; // 2 GiB
......@@ -442,7 +442,7 @@ pub const Compilation = struct {
442442 comp.name = try Buffer.init(comp.arena(), name);
443443 comp.llvm_triple = try util.getTriple(comp.arena(), target);
444444 comp.llvm_target = try util.llvmTargetFromTriple(comp.llvm_triple);
445 comp.zig_std_dir = try std.fs.path.join(comp.arena(), &[_][]const u8{ zig_lib_dir, "std" });
445 comp.zig_std_dir = try fs.path.join(comp.arena(), &[_][]const u8{ zig_lib_dir, "std" });
446446
447447 const opt_level = switch (build_mode) {
448448 .Debug => llvm.CodeGenLevelNone,
......@@ -485,8 +485,8 @@ pub const Compilation = struct {
485485 defer comp.events.deinit();
486486
487487 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);
490490
491491 comp.root_package = try Package.create(comp.arena(), dirname, basename);
492492 comp.std_package = try Package.create(comp.arena(), comp.zig_std_dir, "std.zig");
......@@ -518,7 +518,7 @@ pub const Compilation = struct {
518518 if (comp.tmp_dir.getOrNull()) |tmp_dir_result|
519519 if (tmp_dir_result.*) |tmp_dir| {
520520 // TODO evented I/O?
521 std.fs.deleteTree(tmp_dir) catch {};
521 fs.deleteTree(tmp_dir) catch {};
522522 } else |_| {};
523523 }
524524
......@@ -794,7 +794,7 @@ pub const Compilation = struct {
794794
795795 async fn rebuildFile(self: *Compilation, root_scope: *Scope.Root) BuildError!void {
796796 const tree_scope = blk: {
797 const source_code = fs.readFile(
797 const source_code = fs.cwd().readFileAlloc(
798798 self.gpa(),
799799 root_scope.realpath,
800800 max_src_size,
......@@ -932,8 +932,8 @@ pub const Compilation = struct {
932932 fn initialCompile(self: *Compilation) !void {
933933 if (self.root_src_path) |root_src_path| {
934934 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| {
937937 try self.addCompileErrorCli(root_src_path, "unable to open: {}", .{@errorName(err)});
938938 return;
939939 };
......@@ -1154,7 +1154,7 @@ pub const Compilation = struct {
11541154 const file_name = try std.fmt.allocPrint(self.gpa(), "{}{}", .{ file_prefix[0..], suffix });
11551155 defer self.gpa().free(file_name);
11561156
1157 const full_path = try std.fs.path.join(self.gpa(), &[_][]const u8{ tmp_dir, file_name[0..] });
1157 const full_path = try fs.path.join(self.gpa(), &[_][]const u8{ tmp_dir, file_name[0..] });
11581158 errdefer self.gpa().free(full_path);
11591159
11601160 return Buffer.fromOwnedSlice(self.gpa(), full_path);
......@@ -1175,8 +1175,8 @@ pub const Compilation = struct {
11751175 const zig_dir_path = try getZigDir(self.gpa());
11761176 defer self.gpa().free(zig_dir_path);
11771177
1178 const tmp_dir = try std.fs.path.join(self.arena(), &[_][]const u8{ zig_dir_path, comp_dir_name[0..] });
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);
11801180 return tmp_dir;
11811181 }
11821182
......@@ -1348,7 +1348,7 @@ async fn addFnToLinkSet(comp: *Compilation, fn_val: *Value.Fn) Compilation.Build
13481348}
13491349
13501350fn getZigDir(allocator: *mem.Allocator) ![]u8 {
1351 return std.fs.getAppDataDir(allocator, "zig");
1351 return fs.getAppDataDir(allocator, "zig");
13521352}
13531353
13541354fn analyzeFnType(
src-self-hosted/dep_tokenizer.zig+10-23
......@@ -998,7 +998,8 @@ fn printCharValues(out: var, bytes: []const u8) !void {
998998
999999fn printUnderstandableChar(out: var, char: u8) !void {
10001000 if (!std.ascii.isPrint(char) or char == ' ') {
1001 std.fmt.format(out.context, anyerror, out.output, "\\x{X:2}", .{char}) catch {};
1001 const output = @typeInfo(@TypeOf(out)).Pointer.child.output;
1002 std.fmt.format(out.context, anyerror, output, "\\x{X:2}", .{char}) catch {};
10021003 } else {
10031004 try out.write("'");
10041005 try out.write(&[_]u8{printable_char_tab[char]});
......@@ -1021,34 +1022,20 @@ comptime {
10211022// output: must be a function that takes a `self` idiom parameter
10221023// and a bytes parameter
10231024// context: must be that self
1024fn makeOutput(output: var, context: var) Output(@TypeOf(output)) {
1025 return Output(@TypeOf(output)){
1026 .output = output,
1025fn makeOutput(comptime output: var, context: var) Output(output, @TypeOf(context)) {
1026 return Output(output, @TypeOf(context)){
10271027 .context = context,
10281028 };
10291029}
10301030
1031fn Output(comptime T: type) type {
1032 const args = switch (@typeInfo(T)) {
1033 .Fn => |f| f.args,
1034 else => @compileError("output parameter is not a function"),
1035 };
1036 if (args.len != 2) {
1037 @compileError("output function must take 2 arguments");
1038 }
1039 const at0 = args[0].arg_type orelse @compileError("output arg[0] does not have a type");
1040 const at1 = args[1].arg_type orelse @compileError("output arg[1] does not have a type");
1041 const arg1p = switch (@typeInfo(at1)) {
1042 .Pointer => |p| p,
1043 else => @compileError("output arg[1] is not a slice"),
1044 };
1045 if (arg1p.child != u8) @compileError("output arg[1] is not a u8 slice");
1031fn Output(comptime output_func: var, comptime Context: type) type {
10461032 return struct {
1047 output: T,
1048 context: at0,
1033 context: Context,
1034
1035 pub const output = output_func;
10491036
1050 fn write(self: *@This(), bytes: []const u8) !void {
1051 try self.output(self.context, bytes);
1037 fn write(self: @This(), bytes: []const u8) !void {
1038 try output_func(self.context, bytes);
10521039 }
10531040 };
10541041}
src-self-hosted/introspect.zig+1-1
......@@ -14,7 +14,7 @@ pub fn testZigInstallPrefix(allocator: *mem.Allocator, test_path: []const u8) ![
1414 const test_index_file = try fs.path.join(allocator, &[_][]const u8{ test_zig_dir, "std", "std.zig" });
1515 defer allocator.free(test_index_file);
1616
17 var file = try fs.File.openRead(test_index_file);
17 var file = try fs.cwd().openRead(test_index_file);
1818 file.close();
1919
2020 return test_zig_dir;
src-self-hosted/main.zig+1-1
......@@ -724,7 +724,7 @@ async fn fmtPath(fmt: *Fmt, file_path_ref: []const u8, check_mode: bool) FmtErro
724724 if (try held.value.put(file_path, {})) |_| return;
725725 }
726726
727 const source_code = event.fs.readFile(
727 const source_code = fs.cwd().readFileAlloc(
728728 fmt.allocator,
729729 file_path,
730730 max_src_size,
src-self-hosted/translate_c.zig+320-98
......@@ -6,8 +6,9 @@ const assert = std.debug.assert;
66const ast = std.zig.ast;
77const Token = std.zig.Token;
88usingnamespace @import("clang.zig");
9const ctok = @import("c_tokenizer.zig");
10const CToken = ctok.CToken;
9const ctok = std.c.tokenizer;
10const CToken = std.c.Token;
11const CTokenList = std.c.tokenizer.Source.TokenList;
1112const mem = std.mem;
1213const math = std.math;
1314
......@@ -4811,6 +4812,15 @@ fn transCreateNodeIdentifier(c: *Context, name: []const u8) !*ast.Node {
48114812 return &identifier.base;
48124813}
48134814
4815fn transCreateNodeTypeIdentifier(c: *Context, name: []const u8) !*ast.Node {
4816 const token_index = try appendTokenFmt(c, .Identifier, "{}", .{name});
4817 const identifier = try c.a().create(ast.Node.Identifier);
4818 identifier.* = .{
4819 .token = token_index,
4820 };
4821 return &identifier.base;
4822}
4823
48144824pub fn freeErrors(errors: []ClangErrMsg) void {
48154825 ZigClangErrorMsg_delete(errors.ptr, errors.len);
48164826}
......@@ -4819,7 +4829,7 @@ fn transPreprocessorEntities(c: *Context, unit: *ZigClangASTUnit) Error!void {
48194829 // TODO if we see #undef, delete it from the table
48204830 var it = ZigClangASTUnit_getLocalPreprocessingEntities_begin(unit);
48214831 const it_end = ZigClangASTUnit_getLocalPreprocessingEntities_end(unit);
4822 var tok_list = ctok.TokenList.init(c.a());
4832 var tok_list = CTokenList.init(c.a());
48234833 const scope = c.global_scope;
48244834
48254835 while (it.I != it_end.I) : (it.I += 1) {
......@@ -4840,42 +4850,59 @@ fn transPreprocessorEntities(c: *Context, unit: *ZigClangASTUnit) Error!void {
48404850 }
48414851
48424852 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,
48474861 },
48484862 };
4863 while (true) {
4864 const tok = tokenizer.next();
4865 switch (tok.id) {
4866 .Nl, .Eof => {
4867 try tok_list.push(tok);
4868 break;
4869 },
4870 .LineComment, .MultiLineComment => continue,
4871 else => {},
4872 }
4873 try tok_list.push(tok);
4874 }
48494875
48504876 var tok_it = tok_list.iterator(0);
48514877 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;
48534881 const next = tok_it.peek().?;
48544882 switch (next.id) {
48554883 .Identifier => {
48564884 // if it equals itself, ignore. for example, from stdio.h:
48574885 // #define stdin stdin
4858 if (mem.eql(u8, name, next.bytes)) {
4886 if (mem.eql(u8, name, slice[next.start..next.end])) {
48594887 continue;
48604888 }
48614889 },
4862 .Eof => {
4890 .Nl, .Eof => {
48634891 // this means it is a macro without a value
48644892 // we don't care about such things
48654893 continue;
48664894 },
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 },
48674899 else => {},
48684900 }
48694901
4870 const macro_fn = if (tok_it.peek().?.id == .Fn) blk: {
4871 _ = tok_it.next();
4872 break :blk true;
4873 } else false;
4874
48754902 (if (macro_fn)
4876 transMacroFnDefine(c, &tok_it, mangled_name, begin_loc)
4903 transMacroFnDefine(c, &tok_it, slice, mangled_name, begin_loc)
48774904 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) {
48794906 error.ParseError => continue,
48804907 error.OutOfMemory => |e| return e,
48814908 };
......@@ -4885,15 +4912,15 @@ fn transPreprocessorEntities(c: *Context, unit: *ZigClangASTUnit) Error!void {
48854912 }
48864913}
48874914
4888fn transMacroDefine(c: *Context, it: *ctok.TokenList.Iterator, name: []const u8, source_loc: ZigClangSourceLocation) ParseError!void {
4915fn transMacroDefine(c: *Context, it: *CTokenList.Iterator, source: []const u8, name: []const u8, source_loc: ZigClangSourceLocation) ParseError!void {
48894916 const scope = &c.global_scope.base;
48904917
48914918 const node = try transCreateNodeVarDecl(c, true, true, name);
48924919 node.eq_token = try appendToken(c, .Equal, "=");
48934920
4894 node.init_node = try parseCExpr(c, it, source_loc, scope);
4921 node.init_node = try parseCExpr(c, it, source, source_loc, scope);
48954922 const last = it.next().?;
4896 if (last.id != .Eof)
4923 if (last.id != .Eof and last.id != .Nl)
48974924 return failDecl(
48984925 c,
48994926 source_loc,
......@@ -4906,7 +4933,7 @@ fn transMacroDefine(c: *Context, it: *ctok.TokenList.Iterator, name: []const u8,
49064933 _ = try c.global_scope.macro_table.put(name, &node.base);
49074934}
49084935
4909fn transMacroFnDefine(c: *Context, it: *ctok.TokenList.Iterator, name: []const u8, source_loc: ZigClangSourceLocation) ParseError!void {
4936fn transMacroFnDefine(c: *Context, it: *CTokenList.Iterator, source: []const u8, name: []const u8, source_loc: ZigClangSourceLocation) ParseError!void {
49104937 const block_scope = try Scope.Block.init(c, &c.global_scope.base, null);
49114938 const scope = &block_scope.base;
49124939
......@@ -4938,7 +4965,7 @@ fn transMacroFnDefine(c: *Context, it: *ctok.TokenList.Iterator, name: []const u
49384965 );
49394966 }
49404967
4941 const mangled_name = try block_scope.makeMangledName(c, param_tok.bytes);
4968 const mangled_name = try block_scope.makeMangledName(c, source[param_tok.start..param_tok.end]);
49424969 const param_name_tok = try appendIdentifier(c, mangled_name);
49434970 _ = try appendToken(c, .Colon, ":");
49444971
......@@ -5001,9 +5028,9 @@ fn transMacroFnDefine(c: *Context, it: *ctok.TokenList.Iterator, name: []const u
50015028 const block = try transCreateNodeBlock(c, null);
50025029
50035030 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);
50055032 const last = it.next().?;
5006 if (last.id != .Eof)
5033 if (last.id != .Eof and last.id != .Nl)
50075034 return failDecl(
50085035 c,
50095036 source_loc,
......@@ -5023,27 +5050,28 @@ fn transMacroFnDefine(c: *Context, it: *ctok.TokenList.Iterator, name: []const u
50235050
50245051const ParseError = Error || error{ParseError};
50255052
5026fn 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);
5053fn 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);
50285055 switch (it.next().?.id) {
50295056 .QuestionMark => {
50305057 // must come immediately after expr
50315058 _ = try appendToken(c, .RParen, ")");
50325059 const if_node = try transCreateNodeIf(c);
50335060 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);
50355062 if (it.next().?.id != .Colon) {
5063 const first_tok = it.list.at(0);
50365064 try failDecl(
50375065 c,
50385066 source_loc,
5039 it.list.at(0).*.bytes,
5067 source[first_tok.start..first_tok.end],
50405068 "unable to translate C expr: expected ':'",
50415069 .{},
50425070 );
50435071 return error.ParseError;
50445072 }
50455073 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);
50475075 return &if_node.base;
50485076 },
50495077 else => {
......@@ -5053,30 +5081,30 @@ fn parseCExpr(c: *Context, it: *ctok.TokenList.Iterator, source_loc: ZigClangSou
50535081 }
50545082}
50555083
5056fn parseCNumLit(c: *Context, tok: *CToken, source_loc: ZigClangSourceLocation) ParseError!*ast.Node {
5057 if (tok.id == .NumLitInt) {
5058 var lit_bytes = tok.bytes;
5084fn parseCNumLit(c: *Context, tok: *CToken, source: []const u8, source_loc: ZigClangSourceLocation) ParseError!*ast.Node {
5085 var lit_bytes = source[tok.start..tok.end];
50595086
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]) {
50625090 '0'...'7' => {
50635091 // 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});
50655093 },
50665094 'X' => {
50675095 // 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..]});
50695097 },
50705098 else => {},
50715099 }
50725100 }
50735101
5074 if (tok.num_lit_suffix == .None) {
5102 if (tok.id.IntegerLiteral == .None) {
50755103 return transCreateNodeInt(c, lit_bytes);
50765104 }
50775105
50785106 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) {
50805108 .U => "c_uint",
50815109 .L => "c_long",
50825110 .LU => "c_ulong",
......@@ -5084,55 +5112,233 @@ fn parseCNumLit(c: *Context, tok: *CToken, source_loc: ZigClangSourceLocation) P
50845112 .LLU => "c_ulonglong",
50855113 else => unreachable,
50865114 }));
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 }];
50875121 _ = try appendToken(c, .Comma, ",");
50885122 try cast_node.params.push(try transCreateNodeInt(c, lit_bytes));
50895123 cast_node.rparen_token = try appendToken(c, .RParen, ")");
50905124 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);
50945128 }
50955129 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) {
50975131 .F => "f32",
5098 .L => "f64",
5132 .L => "c_longdouble",
50995133 else => unreachable,
51005134 }));
51015135 _ = 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]));
51035137 cast_node.rparen_token = try appendToken(c, .RParen, ")");
51045138 return &cast_node.base;
51055139 } else unreachable;
51065140}
51075141
5108fn parseCPrimaryExpr(c: *Context, it: *ctok.TokenList.Iterator, source_loc: ZigClangSourceLocation, scope: *Scope) ParseError!*ast.Node {
5142fn zigifyEscapeSequences(ctx: *Context, source_bytes: []const u8, name: []const u8, source_loc: ZigClangSourceLocation) ![]const u8 {
5143 var source = source_bytes;
5144 for (source) |c, i| {
5145 if (c == '\"' or c == '\'') {
5146 source = source[i..];
5147 break;
5148 }
5149 }
5150 for (source) |c| {
5151 if (c == '\\') {
5152 break;
5153 }
5154 } else return source;
5155 var bytes = try ctx.a().alloc(u8, source.len * 2);
5156 var state: enum {
5157 Start,
5158 Escape,
5159 Hex,
5160 Octal,
5161 } = .Start;
5162 var i: usize = 0;
5163 var count: u8 = 0;
5164 var num: u8 = 0;
5165 for (source) |c| {
5166 switch (state) {
5167 .Escape => {
5168 switch (c) {
5169 'n', 'r', 't', '\\', '\'', '\"' => {
5170 bytes[i] = c;
5171 },
5172 '0'...'7' => {
5173 count += 1;
5174 num += c - '0';
5175 state = .Octal;
5176 bytes[i] = 'x';
5177 },
5178 'x' => {
5179 state = .Hex;
5180 bytes[i] = 'x';
5181 },
5182 'a' => {
5183 bytes[i] = 'x';
5184 i += 1;
5185 bytes[i] = '0';
5186 i += 1;
5187 bytes[i] = '7';
5188 },
5189 'b' => {
5190 bytes[i] = 'x';
5191 i += 1;
5192 bytes[i] = '0';
5193 i += 1;
5194 bytes[i] = '8';
5195 },
5196 'f' => {
5197 bytes[i] = 'x';
5198 i += 1;
5199 bytes[i] = '0';
5200 i += 1;
5201 bytes[i] = 'C';
5202 },
5203 'v' => {
5204 bytes[i] = 'x';
5205 i += 1;
5206 bytes[i] = '0';
5207 i += 1;
5208 bytes[i] = 'B';
5209 },
5210 '?' => {
5211 i -= 1;
5212 bytes[i] = '?';
5213 },
5214 'u', 'U' => {
5215 try failDecl(ctx, source_loc, name, "macro tokenizing failed: TODO unicode escape sequences", .{});
5216 return error.ParseError;
5217 },
5218 else => {
5219 try failDecl(ctx, source_loc, name, "macro tokenizing failed: unknown escape sequence", .{});
5220 return error.ParseError;
5221 },
5222 }
5223 i += 1;
5224 if (state == .Escape)
5225 state = .Start;
5226 },
5227 .Start => {
5228 if (c == '\\') {
5229 state = .Escape;
5230 }
5231 bytes[i] = c;
5232 i += 1;
5233 },
5234 .Hex => {
5235 switch (c) {
5236 '0'...'9' => {
5237 num = std.math.mul(u8, num, 16) catch {
5238 try failDecl(ctx, source_loc, name, "macro tokenizing failed: hex literal overflowed", .{});
5239 return error.ParseError;
5240 };
5241 num += c - '0';
5242 },
5243 'a'...'f' => {
5244 num = std.math.mul(u8, num, 16) catch {
5245 try failDecl(ctx, source_loc, name, "macro tokenizing failed: hex literal overflowed", .{});
5246 return error.ParseError;
5247 };
5248 num += c - 'a' + 10;
5249 },
5250 'A'...'F' => {
5251 num = std.math.mul(u8, num, 16) catch {
5252 try failDecl(ctx, source_loc, name, "macro tokenizing failed: hex literal overflowed", .{});
5253 return error.ParseError;
5254 };
5255 num += c - 'A' + 10;
5256 },
5257 else => {
5258 i += std.fmt.formatIntBuf(bytes[i..], num, 16, false, std.fmt.FormatOptions{ .fill = '0', .width = 2 });
5259 num = 0;
5260 if (c == '\\')
5261 state = .Escape
5262 else
5263 state = .Start;
5264 bytes[i] = c;
5265 i += 1;
5266 },
5267 }
5268 },
5269 .Octal => {
5270 const accept_digit = switch (c) {
5271 // The maximum length of a octal literal is 3 digits
5272 '0'...'7' => count < 3,
5273 else => false,
5274 };
5275
5276 if (accept_digit) {
5277 count += 1;
5278 num = std.math.mul(u8, num, 8) catch {
5279 try failDecl(ctx, source_loc, name, "macro tokenizing failed: octal literal overflowed", .{});
5280 return error.ParseError;
5281 };
5282 num += c - '0';
5283 } else {
5284 i += std.fmt.formatIntBuf(bytes[i..], num, 16, false, std.fmt.FormatOptions{ .fill = '0', .width = 2 });
5285 num = 0;
5286 count = 0;
5287 if (c == '\\')
5288 state = .Escape
5289 else
5290 state = .Start;
5291 bytes[i] = c;
5292 i += 1;
5293 }
5294 },
5295 }
5296 }
5297 if (state == .Hex or state == .Octal)
5298 i += std.fmt.formatIntBuf(bytes[i..], num, 16, false, std.fmt.FormatOptions{ .fill = '0', .width = 2 });
5299 return bytes[0..i];
5300}
5301
5302fn parseCPrimaryExpr(c: *Context, it: *CTokenList.Iterator, source: []const u8, source_loc: ZigClangSourceLocation, scope: *Scope) ParseError!*ast.Node {
51095303 const tok = it.next().?;
51105304 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));
51135308 const node = try c.a().create(ast.Node.CharLiteral);
51145309 node.* = ast.Node.CharLiteral{
51155310 .token = token,
51165311 };
51175312 return &node.base;
51185313 },
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));
51215317 const node = try c.a().create(ast.Node.StringLiteral);
51225318 node.* = ast.Node.StringLiteral{
51235319 .token = token,
51245320 };
51255321 return &node.base;
51265322 },
5127 .NumLitInt, .NumLitFloat => {
5128 return parseCNumLit(c, tok, source_loc);
5323 .IntegerLiteral, .FloatLiteral => {
5324 return parseCNumLit(c, tok, source, source_loc);
51295325 },
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"),
51305336 .Identifier => {
5131 const mangled_name = scope.getAlias(tok.bytes);
5337 const mangled_name = scope.getAlias(source[tok.start..tok.end]);
51325338 return transCreateNodeIdentifier(c, mangled_name);
51335339 },
51345340 .LParen => {
5135 const inner_node = try parseCExpr(c, it, source_loc, scope);
5341 const inner_node = try parseCExpr(c, it, source, source_loc, scope);
51365342
51375343 if (it.peek().?.id == .RParen) {
51385344 _ = it.next();
......@@ -5145,13 +5351,14 @@ fn parseCPrimaryExpr(c: *Context, it: *ctok.TokenList.Iterator, source_loc: ZigC
51455351 // hack to get zig fmt to render a comma in builtin calls
51465352 _ = try appendToken(c, .Comma, ",");
51475353
5148 const node_to_cast = try parseCExpr(c, it, source_loc, scope);
5354 const node_to_cast = try parseCExpr(c, it, source, source_loc, scope);
51495355
51505356 if (it.next().?.id != .RParen) {
5357 const first_tok = it.list.at(0);
51515358 try failDecl(
51525359 c,
51535360 source_loc,
5154 it.list.at(0).*.bytes,
5361 source[first_tok.start..first_tok.end],
51555362 "unable to translate C expr: expected ')''",
51565363 .{},
51575364 );
......@@ -5229,10 +5436,11 @@ fn parseCPrimaryExpr(c: *Context, it: *ctok.TokenList.Iterator, source_loc: ZigC
52295436 return &if_1.base;
52305437 },
52315438 else => {
5439 const first_tok = it.list.at(0);
52325440 try failDecl(
52335441 c,
52345442 source_loc,
5235 it.list.at(0).*.bytes,
5443 source[first_tok.start..first_tok.end],
52365444 "unable to translate C expr: unexpected token {}",
52375445 .{tok.id},
52385446 );
......@@ -5241,33 +5449,35 @@ fn parseCPrimaryExpr(c: *Context, it: *ctok.TokenList.Iterator, source_loc: ZigC
52415449 }
52425450}
52435451
5244fn 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);
5452fn 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);
52465454 while (true) {
52475455 const tok = it.next().?;
52485456 switch (tok.id) {
5249 .Dot => {
5457 .Period => {
52505458 const name_tok = it.next().?;
52515459 if (name_tok.id != .Identifier) {
5460 const first_tok = it.list.at(0);
52525461 try failDecl(
52535462 c,
52545463 source_loc,
5255 it.list.at(0).*.bytes,
5464 source[first_tok.start..first_tok.end],
52565465 "unable to translate C expr: expected identifier",
52575466 .{},
52585467 );
52595468 return error.ParseError;
52605469 }
52615470
5262 node = try transCreateNodeFieldAccess(c, node, name_tok.bytes);
5471 node = try transCreateNodeFieldAccess(c, node, source[name_tok.start..name_tok.end]);
52635472 },
52645473 .Arrow => {
52655474 const name_tok = it.next().?;
52665475 if (name_tok.id != .Identifier) {
5476 const first_tok = it.list.at(0);
52675477 try failDecl(
52685478 c,
52695479 source_loc,
5270 it.list.at(0).*.bytes,
5480 source[first_tok.start..first_tok.end],
52715481 "unable to translate C expr: expected identifier",
52725482 .{},
52735483 );
......@@ -5275,7 +5485,7 @@ fn parseCSuffixOpExpr(c: *Context, it: *ctok.TokenList.Iterator, source_loc: Zig
52755485 }
52765486
52775487 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]);
52795489 },
52805490 .Asterisk => {
52815491 if (it.peek().?.id == .RParen) {
......@@ -5284,13 +5494,23 @@ fn parseCSuffixOpExpr(c: *Context, it: *ctok.TokenList.Iterator, source_loc: Zig
52845494 // hack to get zig fmt to render a comma in builtin calls
52855495 _ = try appendToken(c, .Comma, ",");
52865496
5287 const ptr = try transCreateNodePtrType(c, false, false, .Identifier);
5497 const ptr_kind = blk:{
5498 // * token
5499 _ = it.prev();
5500 // last token of `node`
5501 const prev_id = it.prev().?.id;
5502 _ = it.next();
5503 _ = it.next();
5504 break :blk if (prev_id == .Keyword_void) .Asterisk else Token.Id.Identifier;
5505 };
5506
5507 const ptr = try transCreateNodePtrType(c, false, false, ptr_kind);
52885508 ptr.rhs = node;
52895509 return &ptr.base;
52905510 } else {
52915511 // expr * expr
52925512 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);
52945514 const mul_node = try c.a().create(ast.Node.InfixOp);
52955515 mul_node.* = .{
52965516 .op_token = op_token,
......@@ -5301,9 +5521,9 @@ fn parseCSuffixOpExpr(c: *Context, it: *ctok.TokenList.Iterator, source_loc: Zig
53015521 node = &mul_node.base;
53025522 }
53035523 },
5304 .Shl => {
5524 .AngleBracketAngleBracketLeft => {
53055525 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);
53075527 const bitshift_node = try c.a().create(ast.Node.InfixOp);
53085528 bitshift_node.* = .{
53095529 .op_token = op_token,
......@@ -5313,9 +5533,9 @@ fn parseCSuffixOpExpr(c: *Context, it: *ctok.TokenList.Iterator, source_loc: Zig
53135533 };
53145534 node = &bitshift_node.base;
53155535 },
5316 .Shr => {
5536 .AngleBracketAngleBracketRight => {
53175537 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);
53195539 const bitshift_node = try c.a().create(ast.Node.InfixOp);
53205540 bitshift_node.* = .{
53215541 .op_token = op_token,
......@@ -5327,7 +5547,7 @@ fn parseCSuffixOpExpr(c: *Context, it: *ctok.TokenList.Iterator, source_loc: Zig
53275547 },
53285548 .Pipe => {
53295549 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);
53315551 const or_node = try c.a().create(ast.Node.InfixOp);
53325552 or_node.* = .{
53335553 .op_token = op_token,
......@@ -5339,7 +5559,7 @@ fn parseCSuffixOpExpr(c: *Context, it: *ctok.TokenList.Iterator, source_loc: Zig
53395559 },
53405560 .Ampersand => {
53415561 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);
53435563 const bitand_node = try c.a().create(ast.Node.InfixOp);
53445564 bitand_node.* = .{
53455565 .op_token = op_token,
......@@ -5351,7 +5571,7 @@ fn parseCSuffixOpExpr(c: *Context, it: *ctok.TokenList.Iterator, source_loc: Zig
53515571 },
53525572 .Plus => {
53535573 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);
53555575 const add_node = try c.a().create(ast.Node.InfixOp);
53565576 add_node.* = .{
53575577 .op_token = op_token,
......@@ -5363,7 +5583,7 @@ fn parseCSuffixOpExpr(c: *Context, it: *ctok.TokenList.Iterator, source_loc: Zig
53635583 },
53645584 .Minus => {
53655585 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);
53675587 const sub_node = try c.a().create(ast.Node.InfixOp);
53685588 sub_node.* = .{
53695589 .op_token = op_token,
......@@ -5373,9 +5593,9 @@ fn parseCSuffixOpExpr(c: *Context, it: *ctok.TokenList.Iterator, source_loc: Zig
53735593 };
53745594 node = &sub_node.base;
53755595 },
5376 .And => {
5596 .AmpersandAmpersand => {
53775597 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);
53795599 const and_node = try c.a().create(ast.Node.InfixOp);
53805600 and_node.* = .{
53815601 .op_token = op_token,
......@@ -5385,9 +5605,9 @@ fn parseCSuffixOpExpr(c: *Context, it: *ctok.TokenList.Iterator, source_loc: Zig
53855605 };
53865606 node = &and_node.base;
53875607 },
5388 .Or => {
5608 .PipePipe => {
53895609 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);
53915611 const or_node = try c.a().create(ast.Node.InfixOp);
53925612 or_node.* = .{
53935613 .op_token = op_token,
......@@ -5397,9 +5617,9 @@ fn parseCSuffixOpExpr(c: *Context, it: *ctok.TokenList.Iterator, source_loc: Zig
53975617 };
53985618 node = &or_node.base;
53995619 },
5400 .Gt => {
5620 .AngleBracketRight => {
54015621 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);
54035623 const and_node = try c.a().create(ast.Node.InfixOp);
54045624 and_node.* = .{
54055625 .op_token = op_token,
......@@ -5409,9 +5629,9 @@ fn parseCSuffixOpExpr(c: *Context, it: *ctok.TokenList.Iterator, source_loc: Zig
54095629 };
54105630 node = &and_node.base;
54115631 },
5412 .Gte => {
5632 .AngleBracketRightEqual => {
54135633 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);
54155635 const and_node = try c.a().create(ast.Node.InfixOp);
54165636 and_node.* = .{
54175637 .op_token = op_token,
......@@ -5421,9 +5641,9 @@ fn parseCSuffixOpExpr(c: *Context, it: *ctok.TokenList.Iterator, source_loc: Zig
54215641 };
54225642 node = &and_node.base;
54235643 },
5424 .Lt => {
5644 .AngleBracketLeft => {
54255645 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);
54275647 const and_node = try c.a().create(ast.Node.InfixOp);
54285648 and_node.* = .{
54295649 .op_token = op_token,
......@@ -5433,9 +5653,9 @@ fn parseCSuffixOpExpr(c: *Context, it: *ctok.TokenList.Iterator, source_loc: Zig
54335653 };
54345654 node = &and_node.base;
54355655 },
5436 .Lte => {
5656 .AngleBracketLeftEqual => {
54375657 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);
54395659 const and_node = try c.a().create(ast.Node.InfixOp);
54405660 and_node.* = .{
54415661 .op_token = op_token,
......@@ -5445,16 +5665,17 @@ fn parseCSuffixOpExpr(c: *Context, it: *ctok.TokenList.Iterator, source_loc: Zig
54455665 };
54465666 node = &and_node.base;
54475667 },
5448 .LBrace => {
5668 .LBracket => {
54495669 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, "]");
54525672 node = &arr_node.base;
5453 if (it.next().?.id != .RBrace) {
5673 if (it.next().?.id != .RBracket) {
5674 const first_tok = it.list.at(0);
54545675 try failDecl(
54555676 c,
54565677 source_loc,
5457 it.list.at(0).*.bytes,
5678 source[first_tok.start..first_tok.end],
54585679 "unable to translate C expr: expected ']'",
54595680 .{},
54605681 );
......@@ -5464,7 +5685,7 @@ fn parseCSuffixOpExpr(c: *Context, it: *ctok.TokenList.Iterator, source_loc: Zig
54645685 .LParen => {
54655686 const call_node = try transCreateNodeFnCall(c, node);
54665687 while (true) {
5467 const arg = try parseCPrefixOpExpr(c, it, source_loc, scope);
5688 const arg = try parseCPrefixOpExpr(c, it, source, source_loc, scope);
54685689 try call_node.op.Call.params.push(arg);
54695690 const next = it.next().?;
54705691 if (next.id == .Comma)
......@@ -5472,10 +5693,11 @@ fn parseCSuffixOpExpr(c: *Context, it: *ctok.TokenList.Iterator, source_loc: Zig
54725693 else if (next.id == .RParen)
54735694 break
54745695 else {
5696 const first_tok = it.list.at(0);
54755697 try failDecl(
54765698 c,
54775699 source_loc,
5478 it.list.at(0).*.bytes,
5700 source[first_tok.start..first_tok.end],
54795701 "unable to translate C expr: expected ',' or ')'",
54805702 .{},
54815703 );
......@@ -5493,32 +5715,32 @@ fn parseCSuffixOpExpr(c: *Context, it: *ctok.TokenList.Iterator, source_loc: Zig
54935715 }
54945716}
54955717
5496fn parseCPrefixOpExpr(c: *Context, it: *ctok.TokenList.Iterator, source_loc: ZigClangSourceLocation, scope: *Scope) ParseError!*ast.Node {
5718fn parseCPrefixOpExpr(c: *Context, it: *CTokenList.Iterator, source: []const u8, source_loc: ZigClangSourceLocation, scope: *Scope) ParseError!*ast.Node {
54975719 const op_tok = it.next().?;
54985720
54995721 switch (op_tok.id) {
55005722 .Bang => {
55015723 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);
55035725 return &node.base;
55045726 },
55055727 .Minus => {
55065728 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);
55085730 return &node.base;
55095731 },
55105732 .Tilde => {
55115733 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);
55135735 return &node.base;
55145736 },
55155737 .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);
55175739 return try transCreateNodePtrDeref(c, prefix_op_expr);
55185740 },
55195741 else => {
55205742 _ = it.prev();
5521 return try parseCSuffixOpExpr(c, it, source_loc, scope);
5743 return try parseCSuffixOpExpr(c, it, source, source_loc, scope);
55225744 },
55235745 }
55245746}
src/all_types.hpp+6
......@@ -2174,7 +2174,9 @@ struct CodeGen {
21742174 bool is_big_endian;
21752175 bool have_c_main;
21762176 bool have_winmain;
2177 bool have_wwinmain;
21772178 bool have_winmain_crt_startup;
2179 bool have_wwinmain_crt_startup;
21782180 bool have_dllmain_crt_startup;
21792181 bool have_err_ret_tracing;
21802182 bool link_eh_frame_hdr;
......@@ -2243,6 +2245,7 @@ struct CodeGen {
22432245 bool enable_dump_analysis;
22442246 bool enable_doc_generation;
22452247 bool disable_bin_generation;
2248 bool test_is_evented;
22462249 CodeModel code_model;
22472250
22482251 Buf *mmacosx_version_min;
......@@ -2488,6 +2491,9 @@ struct ScopeExpr {
24882491 size_t children_len;
24892492
24902493 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;
24912497};
24922498
24932499// 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
34193419 } else if (cc == CallingConventionStdcall && g->zig_target->os == OsWindows) {
34203420 if (strcmp(symbol_name, "WinMain") == 0) {
34213421 g->have_winmain = true;
3422 } else if (strcmp(symbol_name, "wWinMain") == 0) {
3423 g->have_wwinmain = true;
34223424 } else if (strcmp(symbol_name, "WinMainCRTStartup") == 0) {
34233425 g->have_winmain_crt_startup = true;
3426 } else if (strcmp(symbol_name, "wWinMainCRTStartup") == 0) {
3427 g->have_wwinmain_crt_startup = true;
34243428 } else if (strcmp(symbol_name, "DllMainCRTStartup") == 0) {
34253429 g->have_dllmain_crt_startup = true;
34263430 }
......@@ -6104,11 +6108,14 @@ static void mark_suspension_point(Scope *scope) {
61046108 continue;
61056109 }
61066110 case ScopeIdExpr: {
6111 ScopeExpr *parent_expr_scope = reinterpret_cast<ScopeExpr *>(scope);
61076112 if (!looking_for_exprs) {
6113 if (parent_expr_scope->spill_harder) {
6114 parent_expr_scope->need_spill = MemoizedBoolTrue;
6115 }
61086116 // Now we're only looking for a block, to see if it's in a loop (see the case ScopeIdBlock)
61096117 continue;
61106118 }
6111 ScopeExpr *parent_expr_scope = reinterpret_cast<ScopeExpr *>(scope);
61126119 if (child_expr_scope != nullptr) {
61136120 for (size_t i = 0; parent_expr_scope->children_ptr[i] != child_expr_scope; i += 1) {
61146121 assert(i < parent_expr_scope->children_len);
......@@ -6144,6 +6151,15 @@ static bool scope_needs_spill(Scope *scope) {
61446151 zig_unreachable();
61456152}
61466153
6154static ZigType *resolve_type_isf(ZigType *ty) {
6155 if (ty->id != ZigTypeIdPointer) return ty;
6156 InferredStructField *isf = ty->data.pointer.inferred_struct_field;
6157 if (isf == nullptr) return ty;
6158 TypeStructField *field = find_struct_type_field(isf->inferred_struct_type, isf->field_name);
6159 assert(field != nullptr);
6160 return field->type_entry;
6161}
6162
61476163static Error resolve_async_frame(CodeGen *g, ZigType *frame_type) {
61486164 Error err;
61496165
......@@ -6245,6 +6261,9 @@ static Error resolve_async_frame(CodeGen *g, ZigType *frame_type) {
62456261 }
62466262 ZigFn *callee = call->fn_entry;
62476263 if (callee == nullptr) {
6264 if (call->fn_ref->value->type->data.fn.fn_type_id.cc != CallingConventionAsync) {
6265 continue;
6266 }
62486267 add_node_error(g, call->base.base.source_node,
62496268 buf_sprintf("function is not comptime-known; @asyncCall required"));
62506269 return ErrorSemanticAnalyzeFail;
......@@ -6352,11 +6371,19 @@ static Error resolve_async_frame(CodeGen *g, ZigType *frame_type) {
63526371 IrInstGen *instruction = block->instruction_list.at(instr_i);
63536372 if (instruction->id == IrInstGenIdAwait ||
63546373 instruction->id == IrInstGenIdVarPtr ||
6355 instruction->id == IrInstGenIdAlloca)
6374 instruction->id == IrInstGenIdAlloca ||
6375 instruction->id == IrInstGenIdSpillBegin ||
6376 instruction->id == IrInstGenIdSpillEnd)
63566377 {
63576378 // This instruction does its own spilling specially, or otherwise doesn't need it.
63586379 continue;
63596380 }
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 }
63606387 if (instruction->value->special != ConstValSpecialRuntime)
63616388 continue;
63626389 if (instruction->base.ref_count == 0)
......@@ -6402,7 +6429,7 @@ static Error resolve_async_frame(CodeGen *g, ZigType *frame_type) {
64026429 } else {
64036430 param_name = buf_sprintf("@arg%" ZIG_PRI_usize, arg_i);
64046431 }
6405 ZigType *param_type = param_info->type;
6432 ZigType *param_type = resolve_type_isf(param_info->type);
64066433 if ((err = type_resolve(g, param_type, ResolveStatusSizeKnown))) {
64076434 return err;
64086435 }
......@@ -6421,7 +6448,7 @@ static Error resolve_async_frame(CodeGen *g, ZigType *frame_type) {
64216448 instruction->field_index = SIZE_MAX;
64226449 ZigType *ptr_type = instruction->base.value->type;
64236450 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);
64256452 if (!type_has_bits(child_type))
64266453 continue;
64276454 if (instruction->base.base.ref_count == 0)
......@@ -6448,8 +6475,6 @@ static Error resolve_async_frame(CodeGen *g, ZigType *frame_type) {
64486475 }
64496476 instruction->field_index = fields.length;
64506477
6451 src_assert(child_type->id != ZigTypeIdPointer || child_type->data.pointer.inferred_struct_field == nullptr,
6452 instruction->base.base.source_node);
64536478 fields.append({name, child_type, instruction->align});
64546479 }
64556480
......@@ -8251,6 +8276,8 @@ static void resolve_llvm_types_struct(CodeGen *g, ZigType *struct_type, ResolveS
82518276 size_t debug_field_index = 0;
82528277 for (size_t i = 0; i < field_count; i += 1) {
82538278 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
82548281 size_t gen_field_index = field->gen_index;
82558282 if (gen_field_index == SIZE_MAX) {
82568283 continue;
src/codegen.cpp+149-56
......@@ -343,33 +343,67 @@ static LLVMLinkage to_llvm_linkage(GlobalLinkageId id) {
343343 zig_unreachable();
344344}
345345
346struct CalcLLVMFieldIndex {
347 uint32_t offset;
348 uint32_t field_index;
349};
350
351static void calc_llvm_field_index_add(CodeGen *g, CalcLLVMFieldIndex *calc, ZigType *ty) {
352 if (!type_has_bits(ty)) return;
353 uint32_t ty_align = get_abi_alignment(g, ty);
354 if (calc->offset % ty_align != 0) {
355 uint32_t llvm_align = LLVMABIAlignmentOfType(g->target_data_ref, get_llvm_type(g, ty));
356 if (llvm_align >= ty_align) {
357 ty_align = llvm_align; // llvm's padding is sufficient
358 } else if (calc->offset) {
359 calc->field_index += 1; // zig will insert an extra padding field here
360 }
361 calc->offset += ty_align - (calc->offset % ty_align); // padding bytes
362 }
363 calc->offset += ty->abi_size;
364 calc->field_index += 1;
365}
366
346367// label (grep this): [fn_frame_struct_layout]
368static void frame_index_trace_arg_calc(CodeGen *g, CalcLLVMFieldIndex *calc, ZigType *return_type) {
369 calc_llvm_field_index_add(g, calc, g->builtin_types.entry_usize); // function pointer
370 calc_llvm_field_index_add(g, calc, g->builtin_types.entry_usize); // resume index
371 calc_llvm_field_index_add(g, calc, g->builtin_types.entry_usize); // awaiter index
372
373 if (type_has_bits(return_type)) {
374 calc_llvm_field_index_add(g, calc, g->builtin_types.entry_usize); // *ReturnType (callee's)
375 calc_llvm_field_index_add(g, calc, g->builtin_types.entry_usize); // *ReturnType (awaiter's)
376 calc_llvm_field_index_add(g, calc, return_type); // ReturnType
377 }
378}
379
347380static uint32_t frame_index_trace_arg(CodeGen *g, ZigType *return_type) {
348 // [0] *ReturnType (callee's)
349 // [1] *ReturnType (awaiter's)
350 // [2] ReturnType
351 uint32_t return_field_count = type_has_bits(return_type) ? 3 : 0;
352 return frame_ret_start + return_field_count;
381 CalcLLVMFieldIndex calc = {0};
382 frame_index_trace_arg_calc(g, &calc, return_type);
383 return calc.field_index;
353384}
354385
355386// label (grep this): [fn_frame_struct_layout]
356static uint32_t frame_index_arg(CodeGen *g, ZigType *return_type) {
357 bool have_stack_trace = codegen_fn_has_err_ret_tracing_arg(g, return_type);
358 // [0] *StackTrace (callee's)
359 // [1] *StackTrace (awaiter's)
360 uint32_t trace_field_count = have_stack_trace ? 2 : 0;
361 return frame_index_trace_arg(g, return_type) + trace_field_count;
387static void frame_index_arg_calc(CodeGen *g, CalcLLVMFieldIndex *calc, ZigType *return_type) {
388 frame_index_trace_arg_calc(g, calc, return_type);
389
390 if (codegen_fn_has_err_ret_tracing_arg(g, return_type)) {
391 calc_llvm_field_index_add(g, calc, g->builtin_types.entry_usize); // *StackTrace (callee's)
392 calc_llvm_field_index_add(g, calc, g->builtin_types.entry_usize); // *StackTrace (awaiter's)
393 }
362394}
363395
364396// label (grep this): [fn_frame_struct_layout]
365static uint32_t frame_index_trace_stack(CodeGen *g, FnTypeId *fn_type_id) {
366 uint32_t result = frame_index_arg(g, fn_type_id->return_type);
367 for (size_t i = 0; i < fn_type_id->param_count; i += 1) {
368 if (type_has_bits(fn_type_id->param_info->type)) {
369 result += 1;
370 }
397static uint32_t frame_index_trace_stack(CodeGen *g, ZigFn *fn) {
398 size_t field_index = 6;
399 bool have_stack_trace = codegen_fn_has_err_ret_tracing_arg(g, fn->type_entry->data.fn.fn_type_id.return_type);
400 if (have_stack_trace) {
401 field_index += 2;
371402 }
372 return result;
403 field_index += fn->type_entry->data.fn.fn_type_id.param_count;
404 ZigType *locals_struct = fn->frame_type->data.frame.locals_struct;
405 TypeStructField *field = locals_struct->data.structure.fields[field_index];
406 return field->gen_index;
373407}
374408
375409
......@@ -2523,7 +2557,12 @@ static LLVMValueRef ir_render_return(CodeGen *g, IrExecutableGen *executable, Ir
25232557 LLVMBuildRet(g->builder, by_val_value);
25242558 }
25252559 } 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 }
25272566 } else {
25282567 LLVMValueRef value = ir_llvm_value(g, instruction->operand);
25292568 LLVMBuildRet(g->builder, value);
......@@ -3916,7 +3955,9 @@ static void set_call_instr_sret(CodeGen *g, LLVMValueRef call_instr) {
39163955static void render_async_spills(CodeGen *g) {
39173956 ZigType *fn_type = g->cur_fn->type_entry;
39183957 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);
39203961 for (size_t var_i = 0; var_i < g->cur_fn->variable_list.length; var_i += 1) {
39213962 ZigVar *var = g->cur_fn->variable_list.at(var_i);
39223963
......@@ -3937,8 +3978,8 @@ static void render_async_spills(CodeGen *g) {
39373978 continue;
39383979 }
39393980
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);
39423983 if (var->decl_node) {
39433984 var->di_loc_var = ZigLLVMCreateAutoVariable(g->dbuilder, get_di_scope(g, var->parent_scope),
39443985 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
40194060}
40204061
40214062static LLVMValueRef ir_render_call(CodeGen *g, IrExecutableGen *executable, IrInstGenCall *instruction) {
4063 Error err;
4064
40224065 LLVMTypeRef usize_type_ref = g->builtin_types.entry_usize->llvm_type;
40234066
40244067 LLVMValueRef fn_val;
......@@ -4049,6 +4092,8 @@ static LLVMValueRef ir_render_call(CodeGen *g, IrExecutableGen *executable, IrIn
40494092 ZigList<ZigType *> gen_param_types = {};
40504093 LLVMValueRef result_loc = instruction->result_loc ? ir_llvm_value(g, instruction->result_loc) : nullptr;
40514094 LLVMValueRef zero = LLVMConstNull(usize_type_ref);
4095 bool need_frame_ptr_ptr_spill = false;
4096 ZigType *anyframe_type = nullptr;
40524097 LLVMValueRef frame_result_loc_uncasted = nullptr;
40534098 LLVMValueRef frame_result_loc;
40544099 LLVMValueRef awaiter_init_val;
......@@ -4087,14 +4132,17 @@ static LLVMValueRef ir_render_call(CodeGen *g, IrExecutableGen *executable, IrIn
40874132
40884133 LLVMPositionBuilderAtEnd(g->builder, ok_block);
40894134 }
4135 need_frame_ptr_ptr_spill = true;
40904136 LLVMValueRef frame_ptr_ptr = LLVMBuildStructGEP(g->builder, frame_slice_ptr, slice_ptr_index, "");
40914137 LLVMValueRef frame_ptr = LLVMBuildLoad(g->builder, frame_ptr_ptr, "");
40924138 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);
40944140 frame_result_loc = LLVMBuildBitCast(g->builder, frame_ptr, get_llvm_type(g, anyframe_type), "");
40954141 } 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);
40984146 frame_result_loc = LLVMBuildBitCast(g->builder, frame_ptr,
40994147 get_llvm_type(g, ptr_frame_type), "");
41004148 }
......@@ -4261,17 +4309,35 @@ static LLVMValueRef ir_render_call(CodeGen *g, IrExecutableGen *executable, IrIn
42614309 LLVMValueRef result;
42624310
42634311 if (callee_is_async) {
4264 uint32_t arg_start_i = frame_index_arg(g, fn_type->data.fn.fn_type_id.return_type);
4312 CalcLLVMFieldIndex arg_calc_start = {0};
4313 frame_index_arg_calc(g, &arg_calc_start, fn_type->data.fn.fn_type_id.return_type);
42654314
42664315 LLVMValueRef casted_frame;
42674316 if (instruction->new_stack != nullptr && instruction->fn_entry == nullptr) {
42684317 // 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
42704326 LLVMTypeRef *field_types = allocate_nonzero<LLVMTypeRef>(field_count);
42714327 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;
42734331 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 }
42754341 }
42764342 LLVMTypeRef frame_with_args_type = LLVMStructType(field_types, field_count, false);
42774343 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
42814347 casted_frame = frame_result_loc;
42824348 }
42834349
4350 CalcLLVMFieldIndex arg_calc = arg_calc_start;
42844351 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, "");
42864354 gen_assign_raw(g, arg_ptr, get_pointer_to_type(g, gen_param_types.at(arg_i), true),
42874355 gen_param_values.at(arg_i));
42884356 }
......@@ -4345,11 +4413,19 @@ static LLVMValueRef ir_render_call(CodeGen *g, IrExecutableGen *executable, IrIn
43454413 }
43464414 }
43474415
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 }
43534429 }
43544430
43554431 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
56395715 bool want_safety = instruction->safety_check_on && ir_want_runtime_safety(g, &instruction->base) &&
56405716 g->errors_by_index.length > 1;
56415717
5642 bool value_has_bits;
5643 if ((err = type_has_bits2(g, instruction->base.value->type, &value_has_bits)))
5644 codegen_report_errors_and_exit(g);
5645
5646 if (!want_safety && !value_has_bits)
5647 return nullptr;
5648
56495718 ZigType *ptr_type = instruction->value->value->type;
56505719 assert(ptr_type->id == ZigTypeIdPointer);
56515720 ZigType *err_union_type = ptr_type->data.pointer.child_type;
56525721 ZigType *payload_type = err_union_type->data.error_union.payload_type;
56535722 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
56545736 LLVMValueRef err_union_handle = get_handle_value(g, err_union_ptr, err_union_type, ptr_type);
56555737
56565738 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
56655747 } else {
56665748 err_val = err_union_handle;
56675749 }
5668 LLVMValueRef zero = LLVMConstNull(get_llvm_type(g, g->err_tag_type));
56695750 LLVMValueRef cond_val = LLVMBuildICmp(g->builder, LLVMIntEQ, err_val, zero, "");
56705751 LLVMBasicBlockRef err_block = LLVMAppendBasicBlock(g->cur_fn_val, "UnwrapErrError");
56715752 LLVMBasicBlockRef ok_block = LLVMAppendBasicBlock(g->cur_fn_val, "UnwrapErrOk");
......@@ -5685,6 +5766,9 @@ static LLVMValueRef ir_render_unwrap_err_payload(CodeGen *g, IrExecutableGen *ex
56855766 }
56865767 return LLVMBuildStructGEP(g->builder, err_union_handle, err_union_payload_index, "");
56875768 } else {
5769 if (instruction->initializing) {
5770 gen_store_untyped(g, zero, err_union_ptr, 0, false);
5771 }
56885772 return nullptr;
56895773 }
56905774}
......@@ -7737,7 +7821,7 @@ static void do_code_gen(CodeGen *g) {
77377821 }
77387822 uint32_t trace_field_index_stack = UINT32_MAX;
77397823 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);
77417825 g->cur_err_ret_trace_val_stack = LLVMBuildStructGEP(g->builder, g->cur_frame_ptr,
77427826 trace_field_index_stack, "");
77437827 }
......@@ -8334,9 +8418,9 @@ TargetSubsystem detect_subsystem(CodeGen *g) {
83348418 if (g->zig_target->os == OsWindows) {
83358419 if (g->have_dllmain_crt_startup || (g->out_type == OutTypeLib && g->is_dynamic))
83368420 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)
83388422 return TargetSubsystemConsole;
8339 if (g->have_winmain)
8423 if (g->have_winmain || g->have_wwinmain)
83408424 return TargetSubsystemWindows;
83418425 } else if (g->zig_target->os == OsUefi) {
83428426 return TargetSubsystemEfiApplication;
......@@ -8596,6 +8680,9 @@ Buf *codegen_generate_builtin_source(CodeGen *g) {
85968680 buf_appendf(contents,
85978681 "pub var test_functions: []TestFn = undefined; // overwritten later\n"
85988682 );
8683
8684 buf_appendf(contents, "pub const test_io_mode = %s;\n",
8685 g->test_is_evented ? ".evented" : ".blocking");
85998686 }
86008687
86018688 return contents;
......@@ -8629,6 +8716,7 @@ static Error define_builtin_compile_vars(CodeGen *g) {
86298716 cache_bool(&cache_hash, g->is_dynamic);
86308717 cache_bool(&cache_hash, g->is_test_build);
86318718 cache_bool(&cache_hash, g->is_single_threaded);
8719 cache_bool(&cache_hash, g->test_is_evented);
86328720 cache_int(&cache_hash, g->code_model);
86338721 cache_int(&cache_hash, g->zig_target->is_native);
86348722 cache_int(&cache_hash, g->zig_target->arch);
......@@ -9386,22 +9474,13 @@ static void update_test_functions_builtin_decl(CodeGen *g) {
93869474 for (size_t i = 0; i < g->test_fns.length; i += 1) {
93879475 ZigFn *test_fn_entry = g->test_fns.at(i);
93889476
9389 if (fn_is_async(test_fn_entry)) {
9390 ErrorMsg *msg = add_node_error(g, test_fn_entry->proto_node,
9391 buf_create_from_str("test functions cannot be async"));
9392 add_error_note(g, msg, test_fn_entry->proto_node,
9393 buf_sprintf("this restriction may be lifted in the future. See https://github.com/ziglang/zig/issues/3117 for more details"));
9394 add_async_error_notes(g, msg, test_fn_entry);
9395 continue;
9396 }
9397
93989477 ZigValue *this_val = &test_fn_array->data.x_array.data.s_none.elements[i];
93999478 this_val->special = ConstValSpecialStatic;
94009479 this_val->type = struct_type;
94019480 this_val->parent.id = ConstParentIdArray;
94029481 this_val->parent.data.p_array.array_val = test_fn_array;
94039482 this_val->parent.data.p_array.elem_index = i;
9404 this_val->data.x_struct.fields = alloc_const_vals_ptrs(2);
9483 this_val->data.x_struct.fields = alloc_const_vals_ptrs(3);
94059484
94069485 ZigValue *name_field = this_val->data.x_struct.fields[0];
94079486 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) {
94139492 fn_field->data.x_ptr.special = ConstPtrSpecialFunction;
94149493 fn_field->data.x_ptr.mut = ConstPtrMutComptimeConst;
94159494 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 }
94169508 }
94179509 report_errors_and_maybe_exit(g);
94189510
......@@ -10344,6 +10436,7 @@ static Error check_cache(CodeGen *g, Buf *manifest_dir, Buf *digest) {
1034410436 if (g->is_test_build) {
1034510437 cache_buf_opt(ch, g->test_filter);
1034610438 cache_buf_opt(ch, g->test_name_prefix);
10439 cache_bool(ch, g->test_is_evented);
1034710440 }
1034810441 cache_bool(ch, g->link_eh_frame_hdr);
1034910442 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,
52525252 return irb->codegen->invalid_inst_src;
52535253 } else {
52545254 return_value = ir_build_const_void(irb, scope, node);
5255 ir_build_end_expr(irb, scope, node, return_value, &result_loc_ret->base);
52555256 }
52565257
52575258 ir_mark_gen(ir_build_add_implicit_return_type(irb, scope, node, return_value, result_loc_ret));
......@@ -5262,7 +5263,7 @@ static IrInstSrc *ir_gen_return(IrBuilderSrc *irb, Scope *scope, AstNode *node,
52625263 if (!have_err_defers && !irb->codegen->have_err_ret_tracing) {
52635264 // only generate unconditional defers
52645265 ir_gen_defers_for_block(irb, scope, outer_scope, false);
5265 IrInstSrc *result = ir_build_return_src(irb, scope, node, return_value);
5266 IrInstSrc *result = ir_build_return_src(irb, scope, node, nullptr);
52665267 result_loc_ret->base.source_instruction = result;
52675268 return result;
52685269 }
......@@ -5271,10 +5272,6 @@ static IrInstSrc *ir_gen_return(IrBuilderSrc *irb, Scope *scope, AstNode *node,
52715272 IrBasicBlockSrc *err_block = ir_create_basic_block(irb, scope, "ErrRetErr");
52725273 IrBasicBlockSrc *ok_block = ir_create_basic_block(irb, scope, "ErrRetOk");
52735274
5274 if (!have_err_defers) {
5275 ir_gen_defers_for_block(irb, scope, outer_scope, false);
5276 }
5277
52785275 IrInstSrc *is_err = ir_build_test_err_src(irb, scope, node, return_value, false, true);
52795276
52805277 IrInstSrc *is_comptime;
......@@ -5288,22 +5285,18 @@ static IrInstSrc *ir_gen_return(IrBuilderSrc *irb, Scope *scope, AstNode *node,
52885285 IrBasicBlockSrc *ret_stmt_block = ir_create_basic_block(irb, scope, "RetStmt");
52895286
52905287 ir_set_cursor_at_end_and_append_block(irb, err_block);
5291 if (have_err_defers) {
5292 ir_gen_defers_for_block(irb, scope, outer_scope, true);
5293 }
5288 ir_gen_defers_for_block(irb, scope, outer_scope, true);
52945289 if (irb->codegen->have_err_ret_tracing && !should_inline) {
52955290 ir_build_save_err_ret_addr_src(irb, scope, node);
52965291 }
52975292 ir_build_br(irb, scope, node, ret_stmt_block, is_comptime);
52985293
52995294 ir_set_cursor_at_end_and_append_block(irb, ok_block);
5300 if (have_err_defers) {
5301 ir_gen_defers_for_block(irb, scope, outer_scope, false);
5302 }
5295 ir_gen_defers_for_block(irb, scope, outer_scope, false);
53035296 ir_build_br(irb, scope, node, ret_stmt_block, is_comptime);
53045297
53055298 ir_set_cursor_at_end_and_append_block(irb, ret_stmt_block);
5306 IrInstSrc *result = ir_build_return_src(irb, scope, node, return_value);
5299 IrInstSrc *result = ir_build_return_src(irb, scope, node, nullptr);
53075300 result_loc_ret->base.source_instruction = result;
53085301 return result;
53095302 }
......@@ -8841,7 +8834,10 @@ static IrInstSrc *ir_gen_if_optional_expr(IrBuilderSrc *irb, Scope *scope, AstNo
88418834 AstNode *else_node = node->data.test_expr.else_node;
88428835 bool var_is_ptr = node->data.test_expr.var_is_ptr;
88438836
8844 IrInstSrc *maybe_val_ptr = ir_gen_node_extra(irb, expr_node, scope, LValPtr, nullptr);
8837 ScopeExpr *spill_scope = create_expr_scope(irb->codegen, expr_node, scope);
8838 spill_scope->spill_harder = true;
8839
8840 IrInstSrc *maybe_val_ptr = ir_gen_node_extra(irb, expr_node, &spill_scope->base, LValPtr, nullptr);
88458841 if (maybe_val_ptr == irb->codegen->invalid_inst_src)
88468842 return maybe_val_ptr;
88478843
......@@ -8866,7 +8862,7 @@ static IrInstSrc *ir_gen_if_optional_expr(IrBuilderSrc *irb, Scope *scope, AstNo
88668862
88678863 ir_set_cursor_at_end_and_append_block(irb, then_block);
88688864
8869 Scope *subexpr_scope = create_runtime_scope(irb->codegen, node, scope, is_comptime);
8865 Scope *subexpr_scope = create_runtime_scope(irb->codegen, node, &spill_scope->base, is_comptime);
88708866 Scope *var_scope;
88718867 if (var_symbol) {
88728868 bool is_shadowable = false;
......@@ -9586,7 +9582,10 @@ static IrInstSrc *ir_gen_catch(IrBuilderSrc *irb, Scope *parent_scope, AstNode *
95869582 }
95879583
95889584
9589 IrInstSrc *err_union_ptr = ir_gen_node_extra(irb, op1_node, parent_scope, LValPtr, nullptr);
9585 ScopeExpr *spill_scope = create_expr_scope(irb->codegen, op1_node, parent_scope);
9586 spill_scope->spill_harder = true;
9587
9588 IrInstSrc *err_union_ptr = ir_gen_node_extra(irb, op1_node, &spill_scope->base, LValPtr, nullptr);
95909589 if (err_union_ptr == irb->codegen->invalid_inst_src)
95919590 return irb->codegen->invalid_inst_src;
95929591
......@@ -9608,7 +9607,7 @@ static IrInstSrc *ir_gen_catch(IrBuilderSrc *irb, Scope *parent_scope, AstNode *
96089607 is_comptime);
96099608
96109609 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);
96129611 Scope *err_scope;
96139612 if (var_node) {
96149613 assert(var_node->type == NodeTypeSymbol);
......@@ -11831,7 +11830,7 @@ static ConstCastOnly types_match_const_cast_only(IrAnalyze *ira, ZigType *wanted
1183111830 }
1183211831 assert(wanted_type->data.fn.is_generic ||
1183311832 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) {
1183511834 // note it's reversed for parameters
1183611835 FnTypeParamInfo *actual_param_info = &actual_type->data.fn.fn_type_id.param_info[i];
1183711836 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
1546115460}
1546215461
1546315462static 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
1546415469 IrInstGen *operand = instruction->operand->child;
1546515470 if (type_is_invalid(operand->value->type))
1546615471 return ir_unreach_error(ira);
......@@ -19553,6 +19558,12 @@ static IrInstGen *ir_analyze_fn_call(IrAnalyze *ira, IrInst* source_instr,
1955319558 if (type_is_invalid(result_loc->value->type) || result_loc->value->type->id == ZigTypeIdUnreachable) {
1955419559 return result_loc;
1955519560 }
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;
1955619567 ZigType *res_child_type = result_loc->value->type->data.pointer.child_type;
1955719568 if (res_child_type == ira->codegen->builtin_types.entry_var) {
1955819569 res_child_type = impl_fn_type_id->return_type;
......@@ -19685,6 +19696,12 @@ static IrInstGen *ir_analyze_fn_call(IrAnalyze *ira, IrInst* source_instr,
1968519696 if (type_is_invalid(result_loc->value->type) || result_loc->value->type->id == ZigTypeIdUnreachable) {
1968619697 return result_loc;
1968719698 }
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;
1968819705 ZigType *res_child_type = result_loc->value->type->data.pointer.child_type;
1968919706 if (res_child_type == ira->codegen->builtin_types.entry_var) {
1969019707 res_child_type = return_type;
......@@ -29515,8 +29532,13 @@ static IrInstGen *ir_analyze_instruction_spill_begin(IrAnalyze *ira, IrInstSrcSp
2951529532 if (!type_has_bits(operand->value->type))
2951629533 return ir_const_void(ira, &instruction->base.base);
2951729534
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 }
2952029542
2952129543 return ir_build_spill_begin_gen(ira, &instruction->base.base, operand, instruction->spill_id);
2952229544}
......@@ -29526,8 +29548,12 @@ static IrInstGen *ir_analyze_instruction_spill_end(IrAnalyze *ira, IrInstSrcSpil
2952629548 if (type_is_invalid(operand->value->type))
2952729549 return ira->codegen->invalid_inst_gen;
2952829550
29529 if (ir_should_inline(ira->old_irb.exec, instruction->base.base.scope) || !type_has_bits(operand->value->type))
29551 if (ir_should_inline(ira->old_irb.exec, instruction->base.base.scope) ||
29552 !type_has_bits(operand->value->type) ||
29553 instr_is_comptime(operand))
29554 {
2953029555 return operand;
29556 }
2953129557
2953229558 ir_assert(instruction->begin->base.child->id == IrInstGenIdSpillBegin, &instruction->base.base);
2953329559 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
3025230278 if (param_is_var_args) {
3025330279 if (fn_type_id.cc == CallingConventionC) {
3025430280 fn_type_id.param_count = fn_type_id.next_param_index;
30255 continue;
30281 break;
3025630282 } else if (fn_type_id.cc == CallingConventionUnspecified) {
3025730283 return get_generic_fn_type(ira->codegen, &fn_type_id);
3025830284 } else {
src/link.cpp+4
......@@ -2210,6 +2210,10 @@ static void add_win_link_args(LinkJob *lj, bool is_library, bool *have_windows_d
22102210 if (!is_library) {
22112211 if (lj->codegen->have_winmain) {
22122212 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");
22132217 } else {
22142218 lj->args.append("-ENTRY:WinMainCRTStartup");
22152219 }
src/main.cpp+6
......@@ -135,6 +135,7 @@ static int print_full_usage(const char *arg0, FILE *file, int return_code) {
135135 " --test-name-prefix [text] add prefix to all tests\n"
136136 " --test-cmd [arg] specify test execution command one arg at a time\n"
137137 " --test-cmd-bin appends test binary path to test cmd args\n"
138 " --test-evented-io runs the test in evented I/O mode\n"
138139 , arg0);
139140 return return_code;
140141}
......@@ -429,6 +430,7 @@ int main(int argc, char **argv) {
429430 ZigList<CFile *> c_source_files = {0};
430431 const char *test_filter = nullptr;
431432 const char *test_name_prefix = nullptr;
433 bool test_evented_io = false;
432434 size_t ver_major = 0;
433435 size_t ver_minor = 0;
434436 size_t ver_patch = 0;
......@@ -710,6 +712,8 @@ int main(int argc, char **argv) {
710712 cur_pkg = cur_pkg->parent;
711713 } else if (strcmp(arg, "-ffunction-sections") == 0) {
712714 function_sections = true;
715 } else if (strcmp(arg, "--test-evented-io") == 0) {
716 test_evented_io = true;
713717 } else if (i + 1 >= argc) {
714718 fprintf(stderr, "Expected another argument after %s\n", arg);
715719 return print_error_usage(arg0);
......@@ -1060,6 +1064,7 @@ int main(int argc, char **argv) {
10601064 g->want_stack_check = want_stack_check;
10611065 g->want_sanitize_c = want_sanitize_c;
10621066 g->want_single_threaded = want_single_threaded;
1067 g->test_is_evented = test_evented_io;
10631068 Buf *builtin_source = codegen_generate_builtin_source(g);
10641069 if (fwrite(buf_ptr(builtin_source), 1, buf_len(builtin_source), stdout) != buf_len(builtin_source)) {
10651070 fprintf(stderr, "unable to write to stdout: %s\n", strerror(ferror(stdout)));
......@@ -1233,6 +1238,7 @@ int main(int argc, char **argv) {
12331238 if (test_filter) {
12341239 codegen_set_test_filter(g, buf_create_from_str(test_filter));
12351240 }
1241 g->test_is_evented = test_evented_io;
12361242
12371243 if (test_name_prefix) {
12381244 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) {
806806 if (param_decl->data.param_decl.is_var_args)
807807 res->data.fn_proto.is_var_args = true;
808808 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.");
810810 }
811811 return res;
812812}
test/compile_errors.zig+35-19
......@@ -3,12 +3,47 @@ const builtin = @import("builtin");
33const Target = @import("std").Target;
44
55pub 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
617 cases.addTest("dependency loop in top-level decl with @TypeInfo",
718 \\export const foo = @typeInfo(@This());
819 , &[_][]const u8{
920 "tmp.zig:1:20: error: dependency loop detected",
1021 });
1122
23 cases.add("function call assigned to incorrect type",
24 \\export fn entry() void {
25 \\ var arr: [4]f32 = undefined;
26 \\ arr = concat();
27 \\}
28 \\fn concat() [16]f32 {
29 \\ return [1]f32{0}**16;
30 \\}
31 , &[_][]const u8{
32 "tmp.zig:3:17: error: expected type '[4]f32', found '[16]f32'",
33 });
34
35 cases.add("generic function call assigned to incorrect type",
36 \\pub export fn entry() void {
37 \\ var res: []i32 = undefined;
38 \\ res = myAlloc(i32);
39 \\}
40 \\fn myAlloc(comptime arg: type) anyerror!arg{
41 \\ unreachable;
42 \\}
43 , &[_][]const u8{
44 "tmp.zig:3:18: error: expected type '[]i32', found 'anyerror!i32",
45 });
46
1247 cases.addTest("non-exhaustive enums",
1348 \\const A = enum {
1449 \\ a,
......@@ -5268,25 +5303,6 @@ pub fn addCases(cases: *tests.CompileErrorContext) void {
52685303 "tmp.zig:2:30: error: cannot set section of local variable 'foo'",
52695304 });
52705305
5271 cases.add("returning address of local variable - simple",
5272 \\export fn foo() *i32 {
5273 \\ var a: i32 = undefined;
5274 \\ return &a;
5275 \\}
5276 , &[_][]const u8{
5277 "tmp.zig:3:13: error: function returns address of local variable",
5278 });
5279
5280 cases.add("returning address of local variable - phi",
5281 \\export fn foo(c: bool) *i32 {
5282 \\ var a: i32 = undefined;
5283 \\ var b: i32 = undefined;
5284 \\ return if (c) &a else &b;
5285 \\}
5286 , &[_][]const u8{
5287 "tmp.zig:4:12: error: function returns address of local variable",
5288 });
5289
52905306 cases.add("inner struct member shadowing outer struct member",
52915307 \\fn A() type {
52925308 \\ return struct {
test/stage1/behavior/async_fn.zig+152
......@@ -2,6 +2,7 @@ const std = @import("std");
22const builtin = @import("builtin");
33const expect = std.testing.expect;
44const expectEqual = std.testing.expectEqual;
5const expectError = std.testing.expectError;
56
67var global_x: i32 = 1;
78
......@@ -1329,3 +1330,154 @@ test "async call with @call" {
13291330 };
13301331 S.doTheTest();
13311332}
1333
1334test "async function passed 0-bit arg after non-0-bit arg" {
1335 const S = struct {
1336 var global_frame: anyframe = undefined;
1337 var global_int: i32 = 0;
1338
1339 fn foo() void {
1340 bar(1, .{}) catch unreachable;
1341 }
1342
1343 fn bar(x: i32, args: var) anyerror!void {
1344 global_frame = @frame();
1345 suspend;
1346 global_int = x;
1347 }
1348 };
1349 _ = async S.foo();
1350 resume S.global_frame;
1351 expect(S.global_int == 1);
1352}
1353
1354test "async function passed align(16) arg after align(8) arg" {
1355 const S = struct {
1356 var global_frame: anyframe = undefined;
1357 var global_int: u128 = 0;
1358
1359 fn foo() void {
1360 var a: u128 = 99;
1361 bar(10, .{a}) catch unreachable;
1362 }
1363
1364 fn bar(x: u64, args: var) anyerror!void {
1365 expect(x == 10);
1366 global_frame = @frame();
1367 suspend;
1368 global_int = args[0];
1369 }
1370 };
1371 _ = async S.foo();
1372 resume S.global_frame;
1373 expect(S.global_int == 99);
1374}
1375
1376test "async function call resolves target fn frame, comptime func" {
1377 const S = struct {
1378 var global_frame: anyframe = undefined;
1379 var global_int: i32 = 9;
1380
1381 fn foo() anyerror!void {
1382 const stack_size = 1000;
1383 var stack_frame: [stack_size]u8 align(std.Target.stack_align) = undefined;
1384 return await @asyncCall(&stack_frame, {}, bar);
1385 }
1386
1387 fn bar() anyerror!void {
1388 global_frame = @frame();
1389 suspend;
1390 global_int += 1;
1391 }
1392 };
1393 _ = async S.foo();
1394 resume S.global_frame;
1395 expect(S.global_int == 10);
1396}
1397
1398test "async function call resolves target fn frame, runtime func" {
1399 const S = struct {
1400 var global_frame: anyframe = undefined;
1401 var global_int: i32 = 9;
1402
1403 fn foo() anyerror!void {
1404 const stack_size = 1000;
1405 var stack_frame: [stack_size]u8 align(std.Target.stack_align) = undefined;
1406 var func: async fn () anyerror!void = bar;
1407 return await @asyncCall(&stack_frame, {}, func);
1408 }
1409
1410 fn bar() anyerror!void {
1411 global_frame = @frame();
1412 suspend;
1413 global_int += 1;
1414 }
1415 };
1416 _ = async S.foo();
1417 resume S.global_frame;
1418 expect(S.global_int == 10);
1419}
1420
1421test "properly spill optional payload capture value" {
1422 const S = struct {
1423 var global_frame: anyframe = undefined;
1424 var global_int: usize = 2;
1425
1426 fn foo() void {
1427 var opt: ?usize = 1234;
1428 if (opt) |x| {
1429 bar();
1430 global_int += x;
1431 }
1432 }
1433
1434 fn bar() void {
1435 global_frame = @frame();
1436 suspend;
1437 global_int += 1;
1438 }
1439 };
1440 _ = async S.foo();
1441 resume S.global_frame;
1442 expect(S.global_int == 1237);
1443}
1444
1445test "handle defer interfering with return value spill" {
1446 const S = struct {
1447 var global_frame1: anyframe = undefined;
1448 var global_frame2: anyframe = undefined;
1449 var finished = false;
1450 var baz_happened = false;
1451
1452 fn doTheTest() void {
1453 _ = async testFoo();
1454 resume global_frame1;
1455 resume global_frame2;
1456 expect(baz_happened);
1457 expect(finished);
1458 }
1459
1460 fn testFoo() void {
1461 expectError(error.Bad, foo());
1462 finished = true;
1463 }
1464
1465 fn foo() anyerror!void {
1466 defer baz();
1467 return bar() catch |err| return err;
1468 }
1469
1470 fn bar() anyerror!void {
1471 global_frame1 = @frame();
1472 suspend;
1473 return error.Bad;
1474 }
1475
1476 fn baz() void {
1477 global_frame2 = @frame();
1478 suspend;
1479 baz_happened = true;
1480 }
1481 };
1482 S.doTheTest();
1483}
test/translate_c.zig+24-2
......@@ -618,6 +618,28 @@ pub fn addCases(cases: *tests.TranslateCContext) void {
618618 },
619619 );
620620
621 cases.add("float suffixes",
622 \\#define foo 3.14f
623 \\#define bar 16.e-2l
624 , &[_][]const u8{
625 "pub const foo = @as(f32, 3.14);",
626 "pub const bar = @as(c_longdouble, 16.e-2);",
627 });
628
629 cases.add("comments",
630 \\#define foo 1 //foo
631 \\#define bar /* bar */ 2
632 , &[_][]const u8{
633 "pub const foo = 1;",
634 "pub const bar = 2;",
635 });
636
637 cases.add("string prefix",
638 \\#define foo L"hello"
639 , &[_][]const u8{
640 "pub const foo = \"hello\";",
641 });
642
621643 cases.add("null statements",
622644 \\void foo(void) {
623645 \\ ;;;;;
......@@ -2508,8 +2530,8 @@ pub fn addCases(cases: *tests.TranslateCContext) void {
25082530 cases.add("macro cast",
25092531 \\#define FOO(bar) baz((void *)(baz))
25102532 , &[_][]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));
25132535 \\}
25142536 });
25152537