From 3dce41b61a20c23c494a485b1d3092e7c67dd97f Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 16 Aug 2019 21:29:29 -0400 Subject: [PATCH 01/25] improvements to std lib for event-based I/O --- std/c.zig | 2 +- std/event/fs.zig | 135 ++++++++++++++++++++++++++++-------------- std/event/loop.zig | 6 ++ std/fs.zig | 13 ++++ std/fs/file.zig | 8 +++ std/net.zig | 30 ++++++++++ std/os.zig | 74 ++++++++++++++++++++++- std/os/bits/linux.zig | 1 + 8 files changed, 219 insertions(+), 50 deletions(-) diff --git a/std/c.zig b/std/c.zig index 4c02f45c11..3cf295ca39 100644 --- a/std/c.zig +++ b/std/c.zig @@ -64,6 +64,7 @@ pub extern "c" fn fstat(fd: fd_t, buf: *Stat) c_int; pub extern "c" fn @"fstat$INODE64"(fd: fd_t, buf: *Stat) c_int; pub extern "c" fn lseek(fd: fd_t, offset: isize, whence: c_int) isize; pub extern "c" fn open(path: [*]const u8, oflag: c_uint, ...) c_int; +pub extern "c" fn openat(fd: c_int, path: [*]const u8, oflag: c_uint, ...) c_int; pub extern "c" fn raise(sig: c_int) c_int; pub extern "c" fn read(fd: fd_t, buf: [*]u8, nbyte: usize) isize; pub extern "c" fn pread(fd: fd_t, buf: [*]u8, nbyte: usize, offset: u64) isize; @@ -112,7 +113,6 @@ pub extern "c" fn accept4(sockfd: fd_t, addr: *sockaddr, addrlen: *socklen_t, fl pub extern "c" fn getsockopt(sockfd: fd_t, level: c_int, optname: c_int, optval: *c_void, optlen: *socklen_t) c_int; pub extern "c" fn kill(pid: pid_t, sig: c_int) c_int; pub extern "c" fn getdirentries(fd: fd_t, buf_ptr: [*]u8, nbytes: usize, basep: *i64) isize; -pub extern "c" fn openat(fd: c_int, path: [*]const u8, flags: c_int) c_int; pub extern "c" fn setgid(ruid: c_uint, euid: c_uint) c_int; pub extern "c" fn setuid(uid: c_uint) c_int; pub extern "c" fn clock_gettime(clk_id: c_int, tp: *timespec) c_int; diff --git a/std/event/fs.zig b/std/event/fs.zig index d6d8f2faef..1e979d55b3 100644 --- a/std/event/fs.zig +++ b/std/event/fs.zig @@ -23,6 +23,7 @@ pub const Request = struct { }; pub const Msg = union(enum) { + WriteV: WriteV, PWriteV: PWriteV, PReadV: PReadV, Open: Open, @@ -30,6 +31,14 @@ pub const Request = struct { WriteFile: WriteFile, End, // special - means the fs thread should exit + pub const WriteV = struct { + fd: fd_t, + iov: []const os.iovec_const, + result: Error!void, + + pub const Error = os.WriteError; + }; + pub const PWriteV = struct { fd: fd_t, iov: []const os.iovec_const, @@ -77,7 +86,7 @@ pub const Request = struct { pub const PWriteVError = error{OutOfMemory} || File.WriteError; /// data - just the inner references - must live until pwritev frame completes. -pub async fn pwritev(loop: *Loop, fd: fd_t, data: []const []const u8, offset: usize) PWriteVError!void { +pub fn pwritev(loop: *Loop, fd: fd_t, data: []const []const u8, offset: usize) PWriteVError!void { switch (builtin.os) { .macosx, .linux, @@ -94,31 +103,31 @@ pub async fn pwritev(loop: *Loop, fd: fd_t, data: []const []const u8, offset: us }; } - return await (async pwritevPosix(loop, fd, iovecs, offset) catch unreachable); + return pwritevPosix(loop, fd, iovecs, offset); }, .windows => { const data_copy = try std.mem.dupe(loop.allocator, []const u8, data); defer loop.allocator.free(data_copy); - return await (async pwritevWindows(loop, fd, data, offset) catch unreachable); + return pwritevWindows(loop, fd, data, offset); }, else => @compileError("Unsupported OS"), } } /// data must outlive the returned frame -pub async fn pwritevWindows(loop: *Loop, fd: fd_t, data: []const []const u8, offset: usize) os.WindowsWriteError!void { +pub fn pwritevWindows(loop: *Loop, fd: fd_t, data: []const []const u8, offset: usize) os.WindowsWriteError!void { if (data.len == 0) return; - if (data.len == 1) return await (async pwriteWindows(loop, fd, data[0], offset) catch unreachable); + if (data.len == 1) return pwriteWindows(loop, fd, data[0], offset); // TODO do these in parallel var off = offset; for (data) |buf| { - try await (async pwriteWindows(loop, fd, buf, off) catch unreachable); + try pwriteWindows(loop, fd, buf, off); off += buf.len; } } -pub async fn pwriteWindows(loop: *Loop, fd: fd_t, data: []const u8, offset: u64) os.WindowsWriteError!void { +pub fn pwriteWindows(loop: *Loop, fd: fd_t, data: []const u8, offset: u64) os.WindowsWriteError!void { var resume_node = Loop.ResumeNode.Basic{ .base = Loop.ResumeNode{ .id = Loop.ResumeNode.Id.Basic, @@ -158,7 +167,7 @@ pub async fn pwriteWindows(loop: *Loop, fd: fd_t, data: []const u8, offset: u64) } /// iovecs must live until pwritev frame completes. -pub async fn pwritevPosix( +pub fn pwritevPosix( loop: *Loop, fd: fd_t, iovecs: []const os.iovec_const, @@ -195,10 +204,44 @@ pub async fn pwritevPosix( return req_node.data.msg.PWriteV.result; } +/// iovecs must live until pwritev frame completes. +pub fn writevPosix( + loop: *Loop, + fd: fd_t, + iovecs: []const os.iovec_const, +) os.WriteError!void { + var req_node = RequestNode{ + .prev = null, + .next = null, + .data = Request{ + .msg = Request.Msg{ + .WriteV = Request.Msg.WriteV{ + .fd = fd, + .iov = iovecs, + .result = undefined, + }, + }, + .finish = Request.Finish{ + .TickNode = Loop.NextTickNode{ + .prev = null, + .next = null, + .data = @frame(), + }, + }, + }, + }; + + suspend { + loop.posixFsRequest(&req_node); + } + + return req_node.data.msg.WriteV.result; +} + pub const PReadVError = error{OutOfMemory} || File.ReadError; /// data - just the inner references - must live until preadv frame completes. -pub async fn preadv(loop: *Loop, fd: fd_t, data: []const []u8, offset: usize) PReadVError!usize { +pub fn preadv(loop: *Loop, fd: fd_t, data: []const []u8, offset: usize) PReadVError!usize { assert(data.len != 0); switch (builtin.os) { .macosx, @@ -216,21 +259,21 @@ pub async fn preadv(loop: *Loop, fd: fd_t, data: []const []u8, offset: usize) PR }; } - return await (async preadvPosix(loop, fd, iovecs, offset) catch unreachable); + return preadvPosix(loop, fd, iovecs, offset); }, .windows => { const data_copy = try std.mem.dupe(loop.allocator, []u8, data); defer loop.allocator.free(data_copy); - return await (async preadvWindows(loop, fd, data_copy, offset) catch unreachable); + return preadvWindows(loop, fd, data_copy, offset); }, else => @compileError("Unsupported OS"), } } /// data must outlive the returned frame -pub async fn preadvWindows(loop: *Loop, fd: fd_t, data: []const []u8, offset: u64) !usize { +pub fn preadvWindows(loop: *Loop, fd: fd_t, data: []const []u8, offset: u64) !usize { assert(data.len != 0); - if (data.len == 1) return await (async preadWindows(loop, fd, data[0], offset) catch unreachable); + if (data.len == 1) return preadWindows(loop, fd, data[0], offset); // TODO do these in parallel? var off: usize = 0; @@ -238,7 +281,7 @@ pub async fn preadvWindows(loop: *Loop, fd: fd_t, data: []const []u8, offset: u6 var inner_off: usize = 0; while (true) { const v = data[iov_i]; - const amt_read = try await (async preadWindows(loop, fd, v[inner_off .. v.len - inner_off], offset + off) catch unreachable); + const amt_read = try preadWindows(loop, fd, v[inner_off .. v.len - inner_off], offset + off); off += amt_read; inner_off += amt_read; if (inner_off == v.len) { @@ -252,7 +295,7 @@ pub async fn preadvWindows(loop: *Loop, fd: fd_t, data: []const []u8, offset: u6 } } -pub async fn preadWindows(loop: *Loop, fd: fd_t, data: []u8, offset: u64) !usize { +pub fn preadWindows(loop: *Loop, fd: fd_t, data: []u8, offset: u64) !usize { var resume_node = Loop.ResumeNode.Basic{ .base = Loop.ResumeNode{ .id = Loop.ResumeNode.Id.Basic, @@ -291,7 +334,7 @@ pub async fn preadWindows(loop: *Loop, fd: fd_t, data: []u8, offset: u64) !usize } /// iovecs must live until preadv frame completes -pub async fn preadvPosix( +pub fn preadvPosix( loop: *Loop, fd: fd_t, iovecs: []const os.iovec, @@ -328,7 +371,7 @@ pub async fn preadvPosix( return req_node.data.msg.PReadV.result; } -pub async fn openPosix( +pub fn openPosix( loop: *Loop, path: []const u8, flags: u32, @@ -367,11 +410,11 @@ pub async fn openPosix( return req_node.data.msg.Open.result; } -pub async fn openRead(loop: *Loop, path: []const u8) File.OpenError!fd_t { +pub fn openRead(loop: *Loop, path: []const u8) File.OpenError!fd_t { switch (builtin.os) { .macosx, .linux, .freebsd, .netbsd => { const flags = os.O_LARGEFILE | os.O_RDONLY | os.O_CLOEXEC; - return await (async openPosix(loop, path, flags, File.default_mode) catch unreachable); + return openPosix(loop, path, flags, File.default_mode); }, .windows => return windows.CreateFile( @@ -390,12 +433,12 @@ pub async fn openRead(loop: *Loop, path: []const u8) File.OpenError!fd_t { /// Creates if does not exist. Truncates the file if it exists. /// Uses the default mode. -pub async fn openWrite(loop: *Loop, path: []const u8) File.OpenError!fd_t { - return await (async openWriteMode(loop, path, File.default_mode) catch unreachable); +pub fn openWrite(loop: *Loop, path: []const u8) File.OpenError!fd_t { + return openWriteMode(loop, path, File.default_mode); } /// Creates if does not exist. Truncates the file if it exists. -pub async fn openWriteMode(loop: *Loop, path: []const u8, mode: File.Mode) File.OpenError!fd_t { +pub fn openWriteMode(loop: *Loop, path: []const u8, mode: File.Mode) File.OpenError!fd_t { switch (builtin.os) { .macosx, .linux, @@ -403,7 +446,7 @@ pub async fn openWriteMode(loop: *Loop, path: []const u8, mode: File.Mode) File. .netbsd, => { const flags = os.O_LARGEFILE | os.O_WRONLY | os.O_CREAT | os.O_CLOEXEC | os.O_TRUNC; - return await (async openPosix(loop, path, flags, File.default_mode) catch unreachable); + return openPosix(loop, path, flags, File.default_mode); }, .windows => return windows.CreateFile( path, @@ -419,7 +462,7 @@ pub async fn openWriteMode(loop: *Loop, path: []const u8, mode: File.Mode) File. } /// Creates if does not exist. Does not truncate. -pub async fn openReadWrite( +pub fn openReadWrite( loop: *Loop, path: []const u8, mode: File.Mode, @@ -427,7 +470,7 @@ pub async fn openReadWrite( switch (builtin.os) { .macosx, .linux, .freebsd, .netbsd => { const flags = os.O_LARGEFILE | os.O_RDWR | os.O_CREAT | os.O_CLOEXEC; - return await (async openPosix(loop, path, flags, mode) catch unreachable); + return openPosix(loop, path, flags, mode); }, .windows => return windows.CreateFile( @@ -576,24 +619,24 @@ pub const CloseOperation = struct { /// contents must remain alive until writeFile completes. /// TODO make this atomic or provide writeFileAtomic and rename this one to writeFileTruncate -pub async fn writeFile(loop: *Loop, path: []const u8, contents: []const u8) !void { - return await (async writeFileMode(loop, path, contents, File.default_mode) catch unreachable); +pub fn writeFile(loop: *Loop, path: []const u8, contents: []const u8) !void { + return writeFileMode(loop, path, contents, File.default_mode); } /// contents must remain alive until writeFile completes. -pub async fn writeFileMode(loop: *Loop, path: []const u8, contents: []const u8, mode: File.Mode) !void { +pub fn writeFileMode(loop: *Loop, path: []const u8, contents: []const u8, mode: File.Mode) !void { switch (builtin.os) { .linux, .macosx, .freebsd, .netbsd, - => return await (async writeFileModeThread(loop, path, contents, mode) catch unreachable), - .windows => return await (async writeFileWindows(loop, path, contents) catch unreachable), + => return writeFileModeThread(loop, path, contents, mode), + .windows => return writeFileWindows(loop, path, contents), else => @compileError("Unsupported OS"), } } -async fn writeFileWindows(loop: *Loop, path: []const u8, contents: []const u8) !void { +fn writeFileWindows(loop: *Loop, path: []const u8, contents: []const u8) !void { const handle = try windows.CreateFile( path, windows.GENERIC_WRITE, @@ -605,10 +648,10 @@ async fn writeFileWindows(loop: *Loop, path: []const u8, contents: []const u8) ! ); defer os.close(handle); - try await (async pwriteWindows(loop, handle, contents, 0) catch unreachable); + try pwriteWindows(loop, handle, contents, 0); } -async fn writeFileModeThread(loop: *Loop, path: []const u8, contents: []const u8, mode: File.Mode) !void { +fn writeFileModeThread(loop: *Loop, path: []const u8, contents: []const u8, mode: File.Mode) !void { const path_with_null = try std.cstr.addNullByte(loop.allocator, path); defer loop.allocator.free(path_with_null); @@ -646,11 +689,11 @@ async fn writeFileModeThread(loop: *Loop, path: []const u8, contents: []const u8 /// The frame resumes when the last data has been confirmed written, but before the file handle /// is closed. /// Caller owns returned memory. -pub async fn readFile(loop: *Loop, file_path: []const u8, max_size: usize) ![]u8 { +pub fn readFile(loop: *Loop, file_path: []const u8, max_size: usize) ![]u8 { var close_op = try CloseOperation.start(loop); defer close_op.finish(); - const fd = try await (async openRead(loop, file_path) catch unreachable); + const fd = try openRead(loop, file_path); close_op.setHandle(fd); var list = std.ArrayList(u8).init(loop.allocator); @@ -660,7 +703,7 @@ pub async fn readFile(loop: *Loop, file_path: []const u8, max_size: usize) ![]u8 try list.ensureCapacity(list.len + mem.page_size); const buf = list.items[list.len..]; const buf_array = [_][]u8{buf}; - const amt = try await (async preadv(loop, fd, buf_array, list.len) catch unreachable); + const amt = try preadv(loop, fd, buf_array, list.len); list.len += amt; if (list.len > max_size) { return error.FileTooBig; @@ -1273,11 +1316,11 @@ const test_tmp_dir = "std_event_fs_test"; // return result; //} -async fn testFsWatchCantFail(loop: *Loop, result: *(anyerror!void)) void { - result.* = await (async testFsWatch(loop) catch unreachable); +fn testFsWatchCantFail(loop: *Loop, result: *(anyerror!void)) void { + result.* = testFsWatch(loop); } -async fn testFsWatch(loop: *Loop) !void { +fn testFsWatch(loop: *Loop) !void { const file_path = try std.fs.path.join(loop.allocator, [][]const u8{ test_tmp_dir, "file.txt" }); defer loop.allocator.free(file_path); @@ -1288,27 +1331,27 @@ async fn testFsWatch(loop: *Loop) !void { const line2_offset = 7; // first just write then read the file - try await try async writeFile(loop, file_path, contents); + try writeFile(loop, file_path, contents); - const read_contents = try await try async readFile(loop, file_path, 1024 * 1024); + const read_contents = try readFile(loop, file_path, 1024 * 1024); testing.expectEqualSlices(u8, contents, read_contents); // now watch the file var watch = try Watch(void).create(loop, 0); defer watch.destroy(); - testing.expect((try await try async watch.addFile(file_path, {})) == null); + testing.expect((try watch.addFile(file_path, {})) == null); - const ev = try async watch.channel.get(); + const ev = async watch.channel.get(); var ev_consumed = false; defer if (!ev_consumed) await ev; // overwrite line 2 - const fd = try await try async openReadWrite(loop, file_path, File.default_mode); + const fd = try await openReadWrite(loop, file_path, File.default_mode); { defer os.close(fd); - try await try async pwritev(loop, fd, []const []const u8{"lorem ipsum"}, line2_offset); + try pwritev(loop, fd, []const []const u8{"lorem ipsum"}, line2_offset); } ev_consumed = true; @@ -1316,7 +1359,7 @@ async fn testFsWatch(loop: *Loop) !void { WatchEventId.CloseWrite => {}, WatchEventId.Delete => @panic("wrong event"), } - const contents_updated = try await try async readFile(loop, file_path, 1024 * 1024); + const contents_updated = try readFile(loop, file_path, 1024 * 1024); testing.expectEqualSlices(u8, \\line 1 \\lorem ipsum diff --git a/std/event/loop.zig b/std/event/loop.zig index a4605c8928..f3170d2a68 100644 --- a/std/event/loop.zig +++ b/std/event/loop.zig @@ -89,12 +89,15 @@ pub const Loop = struct { pub const IoMode = enum { blocking, evented, + mixed, }; pub const io_mode: IoMode = if (@hasDecl(root, "io_mode")) root.io_mode else IoMode.blocking; var global_instance_state: Loop = undefined; + threadlocal var per_thread_instance: ?*Loop = null; const default_instance: ?*Loop = switch (io_mode) { .blocking => null, .evented => &global_instance_state, + .mixed => per_thread_instance, }; pub const instance: ?*Loop = if (@hasDecl(root, "event_loop")) root.event_loop else default_instance; @@ -796,6 +799,9 @@ pub const Loop = struct { while (self.os_data.fs_queue.get()) |node| { switch (node.data.msg) { .End => return, + .WriteV => |*msg| { + msg.result = os.writev(msg.fd, msg.iov); + }, .PWriteV => |*msg| { msg.result = os.pwritev(msg.fd, msg.iov, msg.offset); }, diff --git a/std/fs.zig b/std/fs.zig index a638a95fc5..6301c8a26c 100644 --- a/std/fs.zig +++ b/std/fs.zig @@ -442,6 +442,8 @@ pub fn deleteTree(allocator: *Allocator, full_path: []const u8) DeleteTreeError! } } +/// TODO: separate this API into the one that opens directory handles to then subsequently open +/// files, and into the one that reads files from an open directory handle. pub const Dir = struct { handle: Handle, allocator: *Allocator, @@ -564,6 +566,17 @@ pub const Dir = struct { } } + pub fn openRead(self: Dir, file_path: []const u8) os.OpenError!File { + const path_c = try os.toPosixPath(file_path); + return self.openReadC(&path_c); + } + + pub fn openReadC(self: Dir, file_path: [*]const u8) OpenError!File { + const flags = os.O_LARGEFILE | os.O_RDONLY; + const fd = try os.openatC(self.handle.fd, file_path, flags, 0); + return File.openHandle(fd); + } + fn nextDarwin(self: *Dir) !?Entry { start_over: while (true) { if (self.handle.index >= self.handle.end_index) { diff --git a/std/fs/file.zig b/std/fs/file.zig index df4067b661..0014693d40 100644 --- a/std/fs/file.zig +++ b/std/fs/file.zig @@ -302,6 +302,14 @@ pub const File = struct { return os.write(self.handle, bytes); } + pub fn writev_iovec(self: File, iovecs: []const os.iovec_const) WriteError!void { + if (std.event.Loop.instance) |loop| { + return std.event.fs.writevPosix(loop, self.handle, iovecs); + } else { + return os.writev(self.handle, iovecs); + } + } + pub fn inStream(file: File) InStream { return InStream{ .file = file, diff --git a/std/net.zig b/std/net.zig index efcbf7000d..be9d18056c 100644 --- a/std/net.zig +++ b/std/net.zig @@ -215,3 +215,33 @@ test "std.net.parseIp6" { assert(addr.addr[1] == 0x01); assert(addr.addr[2] == 0x00); } + +pub fn connectUnixSocket(path: []const u8) !std.fs.File { + const opt_non_block = if (std.event.Loop.instance != null) os.SOCK_NONBLOCK else 0; + const sockfd = try os.socket( + os.AF_UNIX, + os.SOCK_STREAM | os.SOCK_CLOEXEC | opt_non_block, + 0, + ); + errdefer os.close(sockfd); + + var sock_addr = os.sockaddr{ + .un = os.sockaddr_un{ + .family = os.AF_UNIX, + .path = undefined, + }, + }; + + if (path.len > @typeOf(sock_addr.un.path).len) return error.NameTooLong; + mem.copy(u8, sock_addr.un.path[0..], path); + const size = @intCast(u32, @sizeOf(os.sa_family_t) + path.len); + if (std.event.Loop.instance) |loop| { + try os.connect_async(sockfd, &sock_addr, size); + try loop.linuxWaitFd(sockfd, os.EPOLLIN | os.EPOLLOUT | os.EPOLLET); + try os.getsockoptError(sockfd); + } else { + try os.connect(sockfd, &sock_addr, size); + } + + return std.fs.File.openHandle(sockfd); +} diff --git a/std/os.zig b/std/os.zig index 805f7f1645..0a826c26c2 100644 --- a/std/os.zig +++ b/std/os.zig @@ -440,6 +440,33 @@ pub fn write(fd: fd_t, bytes: []const u8) WriteError!void { /// Write multiple buffers to a file descriptor. Keeps trying if it gets interrupted. /// This function is for blocking file descriptors only. For non-blocking, see +/// `writevAsync`. +pub fn writev(fd: fd_t, iov: []const iovec_const) WriteError!void { + while (true) { + // TODO handle the case when iov_len is too large and get rid of this @intCast + const rc = system.writev(fd, iov.ptr, @intCast(u32, iov.len)); + switch (errno(rc)) { + 0 => return, + EINTR => continue, + EINVAL => unreachable, + EFAULT => unreachable, + EAGAIN => unreachable, // This function is for blocking writes. + EBADF => unreachable, // Always a race condition. + EDESTADDRREQ => unreachable, // `connect` was never called. + EDQUOT => return error.DiskQuota, + EFBIG => return error.FileTooBig, + EIO => return error.InputOutput, + ENOSPC => return error.NoSpaceLeft, + EPERM => return error.AccessDenied, + EPIPE => return error.BrokenPipe, + else => |err| return unexpectedErrno(err), + } + } +} + +/// Write multiple buffers to a file descriptor, with a position offset. +/// Keeps trying if it gets interrupted. +/// This function is for blocking file descriptors only. For non-blocking, see /// `pwritevAsync`. pub fn pwritev(fd: fd_t, iov: []const iovec_const, offset: u64) WriteError!void { if (darwin.is_the_target) { @@ -524,7 +551,6 @@ pub const OpenError = error{ }; /// Open and possibly create a file. Keeps trying if it gets interrupted. -/// `file_path` needs to be copied in memory to add a null terminating byte. /// See also `openC`. pub fn open(file_path: []const u8, flags: u32, perm: usize) OpenError!fd_t { const file_path_c = try toPosixPath(file_path); @@ -564,6 +590,47 @@ pub fn openC(file_path: [*]const u8, flags: u32, perm: usize) OpenError!fd_t { } } +/// Open and possibly create a file. Keeps trying if it gets interrupted. +/// `file_path` is relative to the open directory handle `dir_fd`. +/// See also `openatC`. +pub fn openat(dir_fd: fd_t, file_path: []const u8, flags: u32, mode: usize) OpenError!fd_t { + const file_path_c = try toPosixPath(file_path); + return openatC(dir_fd, &file_path_c, flags, mode); +} + +/// Open and possibly create a file. Keeps trying if it gets interrupted. +/// `file_path` is relative to the open directory handle `dir_fd`. +/// See also `openat`. +pub fn openatC(dir_fd: fd_t, file_path: [*]const u8, flags: u32, mode: usize) OpenError!fd_t { + while (true) { + const rc = system.openat(dir_fd, file_path, flags, mode); + switch (errno(rc)) { + 0 => return @intCast(fd_t, rc), + EINTR => continue, + + EFAULT => unreachable, + EINVAL => unreachable, + EACCES => return error.AccessDenied, + EFBIG => return error.FileTooBig, + EOVERFLOW => return error.FileTooBig, + EISDIR => return error.IsDir, + ELOOP => return error.SymLinkLoop, + EMFILE => return error.ProcessFdQuotaExceeded, + ENAMETOOLONG => return error.NameTooLong, + ENFILE => return error.SystemFdQuotaExceeded, + ENODEV => return error.NoDevice, + ENOENT => return error.FileNotFound, + ENOMEM => return error.SystemResources, + ENOSPC => return error.NoSpaceLeft, + ENOTDIR => return error.NotDir, + EPERM => return error.AccessDenied, + EEXIST => return error.PathAlreadyExists, + EBUSY => return error.DeviceBusy, + else => |err| return unexpectedErrno(err), + } + } +} + pub fn dup2(old_fd: fd_t, new_fd: fd_t) !void { while (true) { switch (errno(system.dup2(old_fd, new_fd))) { @@ -1655,7 +1722,7 @@ pub const ConnectError = error{ /// For non-blocking, see `connect_async`. pub fn connect(sockfd: i32, sock_addr: *sockaddr, len: socklen_t) ConnectError!void { while (true) { - switch (errno(system.connect(sockfd, sock_addr, @sizeOf(sockaddr)))) { + switch (errno(system.connect(sockfd, sock_addr, len))) { 0 => return, EACCES => return error.PermissionDenied, EPERM => return error.PermissionDenied, @@ -1683,7 +1750,8 @@ pub fn connect(sockfd: i32, sock_addr: *sockaddr, len: socklen_t) ConnectError!v /// It expects to receive EINPROGRESS`. pub fn connect_async(sockfd: i32, sock_addr: *sockaddr, len: socklen_t) ConnectError!void { while (true) { - switch (errno(system.connect(sockfd, sock_addr, @sizeOf(sockaddr)))) { + switch (errno(system.connect(sockfd, sock_addr, len))) { + EINVAL => unreachable, EINTR => continue, 0, EINPROGRESS => return, EACCES => return error.PermissionDenied, diff --git a/std/os/bits/linux.zig b/std/os/bits/linux.zig index 8430a05bf0..c7d8cc2ad2 100644 --- a/std/os/bits/linux.zig +++ b/std/os/bits/linux.zig @@ -783,6 +783,7 @@ pub const socklen_t = u32; pub const sockaddr = extern union { in: sockaddr_in, in6: sockaddr_in6, + un: sockaddr_un, }; pub const sockaddr_in = extern struct { From e24cc2e77b741fe49e738acc497fbedf2998008c Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 16 Aug 2019 23:19:13 -0400 Subject: [PATCH 02/25] std.event.Loop: fix not waking up after file system I/O for single threaded event loops --- std/c.zig | 1 + std/event/loop.zig | 19 ++++++++++--------- 2 files changed, 11 insertions(+), 9 deletions(-) diff --git a/std/c.zig b/std/c.zig index 3cf295ca39..7ba606c18b 100644 --- a/std/c.zig +++ b/std/c.zig @@ -69,6 +69,7 @@ pub extern "c" fn raise(sig: c_int) c_int; pub extern "c" fn read(fd: fd_t, buf: [*]u8, nbyte: usize) isize; pub extern "c" fn pread(fd: fd_t, buf: [*]u8, nbyte: usize, offset: u64) isize; pub extern "c" fn preadv(fd: c_int, iov: [*]const iovec, iovcnt: c_uint, offset: usize) isize; +pub extern "c" fn writev(fd: c_int, iov: [*]const iovec_const, iovcnt: c_uint) isize; pub extern "c" fn pwritev(fd: c_int, iov: [*]const iovec_const, iovcnt: c_uint, offset: usize) isize; pub extern "c" fn stat(noalias path: [*]const u8, noalias buf: *Stat) c_int; pub extern "c" fn write(fd: fd_t, buf: [*]const u8, nbyte: usize) isize; diff --git a/std/event/loop.zig b/std/event/loop.zig index f3170d2a68..6e61abf8d7 100644 --- a/std/event/loop.zig +++ b/std/event/loop.zig @@ -149,13 +149,14 @@ pub const Loop = struct { .overlapped = ResumeNode.overlapped_init, }, }; - const extra_thread_count = thread_count - 1; + // We need an extra one of these in case the fs thread wants to use onNextTick self.eventfd_resume_nodes = try self.allocator.alloc( std.atomic.Stack(ResumeNode.EventFd).Node, - extra_thread_count, + thread_count, ); errdefer self.allocator.free(self.eventfd_resume_nodes); + const extra_thread_count = thread_count - 1; self.extra_threads = try self.allocator.alloc(*Thread, extra_thread_count); errdefer self.allocator.free(self.extra_threads); @@ -197,7 +198,7 @@ pub const Loop = struct { eventfd_node.* = std.atomic.Stack(ResumeNode.EventFd).Node{ .data = ResumeNode.EventFd{ .base = ResumeNode{ - .id = ResumeNode.Id.EventFd, + .id = .EventFd, .handle = undefined, .overlapped = ResumeNode.overlapped_init, }, @@ -454,12 +455,12 @@ pub const Loop = struct { self.finishOneEvent(); } - pub async fn linuxWaitFd(self: *Loop, fd: i32, flags: u32) !void { + pub fn linuxWaitFd(self: *Loop, fd: i32, flags: u32) !void { defer self.linuxRemoveFd(fd); suspend { var resume_node = ResumeNode.Basic{ .base = ResumeNode{ - .id = ResumeNode.Id.Basic, + .id = .Basic, .handle = @frame(), .overlapped = ResumeNode.overlapped_init, }, @@ -793,8 +794,8 @@ pub const Loop = struct { fn posixFsRun(self: *Loop) void { while (true) { - if (builtin.os == builtin.Os.linux) { - _ = @atomicRmw(i32, &self.os_data.fs_queue_item, AtomicRmwOp.Xchg, 0, AtomicOrder.SeqCst); + if (builtin.os == .linux) { + _ = @atomicRmw(i32, &self.os_data.fs_queue_item, .Xchg, 0, .SeqCst); } while (self.os_data.fs_queue.get()) |node| { switch (node.data.msg) { @@ -833,14 +834,14 @@ pub const Loop = struct { self.finishOneEvent(); } switch (builtin.os) { - builtin.Os.linux => { + .linux => { const rc = os.linux.futex_wait(&self.os_data.fs_queue_item, os.linux.FUTEX_WAIT, 0, null); switch (os.linux.getErrno(rc)) { 0, os.EINTR, os.EAGAIN => continue, else => unreachable, } }, - builtin.Os.macosx, builtin.Os.freebsd, builtin.Os.netbsd => { + .macosx, .freebsd, .netbsd => { const fs_kevs = (*const [1]os.Kevent)(&self.os_data.fs_kevent_wait); var out_kevs: [1]os.Kevent = undefined; _ = os.kevent(self.os_data.fs_kqfd, fs_kevs, out_kevs[0..], null) catch unreachable; From 456a244d62df62940f4d860cd9f57b40d563ca96 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sat, 17 Aug 2019 11:40:48 -0400 Subject: [PATCH 03/25] fix event loop regression on macos --- std/event/future.zig | 11 +++++------ std/event/loop.zig | 7 ++++--- 2 files changed, 9 insertions(+), 9 deletions(-) diff --git a/std/event/future.zig b/std/event/future.zig index 70e20819be..117765a525 100644 --- a/std/event/future.zig +++ b/std/event/future.zig @@ -97,28 +97,27 @@ test "std.event.Future" { loop.run(); } -async fn testFuture(loop: *Loop) void { +fn testFuture(loop: *Loop) void { var future = Future(i32).init(loop); var a = async waitOnFuture(&future); var b = async waitOnFuture(&future); - var c = async resolveFuture(&future); + resolveFuture(&future); - // TODO make this work: + // TODO https://github.com/ziglang/zig/issues/3077 //const result = (await a) + (await b); const a_result = await a; const b_result = await b; const result = a_result + b_result; - await c; testing.expect(result == 12); } -async fn waitOnFuture(future: *Future(i32)) i32 { +fn waitOnFuture(future: *Future(i32)) i32 { return future.get().*; } -async fn resolveFuture(future: *Future(i32)) void { +fn resolveFuture(future: *Future(i32)) void { future.data = 6; future.resolve(); } diff --git a/std/event/loop.zig b/std/event/loop.zig index 6e61abf8d7..242452237e 100644 --- a/std/event/loop.zig +++ b/std/event/loop.zig @@ -149,14 +149,15 @@ pub const Loop = struct { .overlapped = ResumeNode.overlapped_init, }, }; - // We need an extra one of these in case the fs thread wants to use onNextTick + // We need at least one of these in case the fs thread wants to use onNextTick + const extra_thread_count = thread_count - 1; + const resume_node_count = std.math.max(extra_thread_count, 1); self.eventfd_resume_nodes = try self.allocator.alloc( std.atomic.Stack(ResumeNode.EventFd).Node, - thread_count, + resume_node_count, ); errdefer self.allocator.free(self.eventfd_resume_nodes); - const extra_thread_count = thread_count - 1; self.extra_threads = try self.allocator.alloc(*Thread, extra_thread_count); errdefer self.allocator.free(self.extra_threads); From 4d8a6f6fea1b6922e7904b33c5b575249213fe53 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sat, 17 Aug 2019 12:48:48 -0400 Subject: [PATCH 04/25] fix compiler not checking alignment of function frames closes #3086 --- doc/langref.html.in | 13 +++++++++---- src/ir.cpp | 23 +++++++++++++++++++++-- test/compile_errors.zig | 24 +++++++++++++++++++++++- test/runtime_safety.zig | 2 +- test/stage1/behavior/async_fn.zig | 4 ++-- test/stage1/behavior/new_stack_call.zig | 2 +- 6 files changed, 57 insertions(+), 11 deletions(-) diff --git a/doc/langref.html.in b/doc/langref.html.in index 2e56bc6557..b2f313b234 100644 --- a/doc/langref.html.in +++ b/doc/langref.html.in @@ -6379,7 +6379,7 @@ comptime { {#header_close#} {#header_open|@asyncCall#} -
{#syntax#}@asyncCall(frame_buffer: []u8, result_ptr, function_ptr, args: ...) anyframe->T{#endsyntax#}
+
{#syntax#}@asyncCall(frame_buffer: []align(@alignOf(@Frame(anyAsyncFunction))) u8, result_ptr, function_ptr, args: ...) anyframe->T{#endsyntax#}

{#syntax#}@asyncCall{#endsyntax#} performs an {#syntax#}async{#endsyntax#} call on a function pointer, which may or may not be an {#link|async function|Async Functions#}. @@ -6405,7 +6405,7 @@ test "async fn pointer in a struct field" { bar: async fn (*i32) void, }; var foo = Foo{ .bar = func }; - var bytes: [64]u8 = undefined; + var bytes: [64]u8 align(@alignOf(@Frame(func))) = undefined; const f = @asyncCall(&bytes, {}, foo.bar, &data); assert(data == 2); resume f; @@ -7322,17 +7322,22 @@ mem.set(u8, dest, c);{#endsyntax#} {#header_close#} {#header_open|@newStackCall#} -

{#syntax#}@newStackCall(new_stack: []u8, function: var, args: ...) var{#endsyntax#}
+
{#syntax#}@newStackCall(new_stack: []align(target_stack_align) u8, function: var, args: ...) var{#endsyntax#}

This calls a function, in the same way that invoking an expression with parentheses does. However, instead of using the same stack as the caller, the function uses the stack provided in the {#syntax#}new_stack{#endsyntax#} parameter.

+

+ The new stack must be aligned to {#syntax#}target_stack_align{#endsyntax#} bytes. This is a target-specific + number. A safe value that will work on all targets is {#syntax#}16{#endsyntax#}. This value can + also be obtained by using {#link|@sizeOf#} on the {#link|@Frame#} type of {#link|Async Functions#}. +

{#code_begin|test#} const std = @import("std"); const assert = std.debug.assert; -var new_stack_bytes: [1024]u8 = undefined; +var new_stack_bytes: [1024]u8 align(16) = undefined; test "calling a function with a new stack" { const arg = 1234; diff --git a/src/ir.cpp b/src/ir.cpp index 0129081e22..15c570ddd9 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -12110,7 +12110,26 @@ static IrInstruction *ir_analyze_cast(IrAnalyze *ira, IrInstruction *source_inst array_type->data.array.child_type, source_node, !slice_ptr_type->data.pointer.is_const).id == ConstCastResultIdOk) { - return ir_resolve_ptr_of_array_to_slice(ira, source_instr, value, wanted_type, result_loc); + // If the pointers both have ABI align, it works. + bool ok_align = slice_ptr_type->data.pointer.explicit_alignment == 0 && + actual_type->data.pointer.explicit_alignment == 0; + if (!ok_align) { + // If either one has non ABI align, we have to resolve them both + if ((err = type_resolve(ira->codegen, actual_type->data.pointer.child_type, + ResolveStatusAlignmentKnown))) + { + return ira->codegen->invalid_instruction; + } + if ((err = type_resolve(ira->codegen, slice_ptr_type->data.pointer.child_type, + ResolveStatusAlignmentKnown))) + { + return ira->codegen->invalid_instruction; + } + ok_align = get_ptr_align(ira->codegen, actual_type) >= get_ptr_align(ira->codegen, slice_ptr_type); + } + if (ok_align) { + return ir_resolve_ptr_of_array_to_slice(ira, source_instr, value, wanted_type, result_loc); + } } } @@ -15421,7 +15440,7 @@ static IrInstruction *ir_analyze_fn_call(IrAnalyze *ira, IrInstructionCallSrc *c IrInstruction *casted_new_stack = nullptr; if (call_instruction->new_stack != nullptr) { ZigType *u8_ptr = get_pointer_to_type_extra(ira->codegen, ira->codegen->builtin_types.entry_u8, - false, false, PtrLenUnknown, 0, 0, 0, false); + false, false, PtrLenUnknown, target_fn_align(ira->codegen->zig_target), 0, 0, false); ZigType *u8_slice = get_slice_type(ira->codegen, u8_ptr); IrInstruction *new_stack = call_instruction->new_stack->child; if (type_is_invalid(new_stack->value.type)) diff --git a/test/compile_errors.zig b/test/compile_errors.zig index 5eec78fa7f..2088b32d98 100644 --- a/test/compile_errors.zig +++ b/test/compile_errors.zig @@ -2,6 +2,28 @@ const tests = @import("tests.zig"); const builtin = @import("builtin"); pub fn addCases(cases: *tests.CompileErrorContext) void { + cases.add( + "bad alignment in @asyncCall", + \\export fn entry() void { + \\ var ptr: async fn () void = func; + \\ var bytes: [64]u8 = undefined; + \\ _ = @asyncCall(&bytes, {}, ptr); + \\} + \\async fn func() void {} + , + "tmp.zig:4:21: error: expected type '[]align(16) u8', found '*[64]u8'", + ); + + cases.add( + "bad alignment in implicit cast from array pointer to slice", + \\export fn a() void { + \\ var x: [10]u8 = undefined; + \\ var y: []align(16) u8 = &x; + \\} + , + "tmp.zig:3:30: error: expected type '[]align(16) u8', found '*[10]u8'", + ); + cases.add( "result location incompatibility mismatching handle_is_ptr (generic call)", \\export fn entry() void { @@ -164,7 +186,7 @@ pub fn addCases(cases: *tests.CompileErrorContext) void { "non async function pointer passed to @asyncCall", \\export fn entry() void { \\ var ptr = afunc; - \\ var bytes: [100]u8 = undefined; + \\ var bytes: [100]u8 align(16) = undefined; \\ _ = @asyncCall(&bytes, {}, ptr); \\} \\fn afunc() void { } diff --git a/test/runtime_safety.zig b/test/runtime_safety.zig index 0fb593c0e2..07a8c3910a 100644 --- a/test/runtime_safety.zig +++ b/test/runtime_safety.zig @@ -30,7 +30,7 @@ pub fn addCases(cases: *tests.CompareOutputContext) void { \\ @import("std").os.exit(126); \\} \\pub fn main() void { - \\ var bytes: [1]u8 = undefined; + \\ var bytes: [1]u8 align(16) = undefined; \\ var ptr = other; \\ var frame = @asyncCall(&bytes, {}, ptr); \\} diff --git a/test/stage1/behavior/async_fn.zig b/test/stage1/behavior/async_fn.zig index e1b173292b..cb9bf2c1ea 100644 --- a/test/stage1/behavior/async_fn.zig +++ b/test/stage1/behavior/async_fn.zig @@ -280,7 +280,7 @@ test "async fn pointer in a struct field" { bar: async fn (*i32) void, }; var foo = Foo{ .bar = simpleAsyncFn2 }; - var bytes: [64]u8 = undefined; + var bytes: [64]u8 align(16) = undefined; const f = @asyncCall(&bytes, {}, foo.bar, &data); comptime expect(@typeOf(f) == anyframe->void); expect(data == 2); @@ -317,7 +317,7 @@ test "@asyncCall with return type" { } }; var foo = Foo{ .bar = Foo.middle }; - var bytes: [150]u8 = undefined; + var bytes: [150]u8 align(16) = undefined; var aresult: i32 = 0; _ = @asyncCall(&bytes, &aresult, foo.bar); expect(aresult == 0); diff --git a/test/stage1/behavior/new_stack_call.zig b/test/stage1/behavior/new_stack_call.zig index 1e01a5a8a2..cbda9a2435 100644 --- a/test/stage1/behavior/new_stack_call.zig +++ b/test/stage1/behavior/new_stack_call.zig @@ -1,7 +1,7 @@ const std = @import("std"); const expect = std.testing.expect; -var new_stack_bytes: [1024]u8 = undefined; +var new_stack_bytes: [1024]u8 align(16) = undefined; test "calling a function with a new stack" { const arg = 1234; From 0ff396c34f93b60a000e1ee50e881a8c25122b79 Mon Sep 17 00:00:00 2001 From: Vexu <15308111+Vexu@users.noreply.github.com> Date: Sat, 17 Aug 2019 22:51:25 +0300 Subject: [PATCH 05/25] add compile error for incorrect atomic ordering in fence #3082 --- src/ir.cpp | 6 ++++++ test/compile_errors.zig | 9 +++++++++ 2 files changed, 15 insertions(+) diff --git a/src/ir.cpp b/src/ir.cpp index 15c570ddd9..51f849ce19 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -20860,6 +20860,12 @@ static IrInstruction *ir_analyze_instruction_fence(IrAnalyze *ira, IrInstruction if (!ir_resolve_atomic_order(ira, order_value, &order)) return ira->codegen->invalid_instruction; + if (order < AtomicOrderAcquire) { + ir_add_error(ira, order_value, + buf_sprintf("atomic ordering must be Acquire or stricter")); + return ira->codegen->invalid_instruction; + } + IrInstruction *result = ir_build_fence(&ira->new_irb, instruction->base.scope, instruction->base.source_node, order_value, order); result->value.type = ira->codegen->builtin_types.entry_void; diff --git a/test/compile_errors.zig b/test/compile_errors.zig index 2088b32d98..d0c12e7cf6 100644 --- a/test/compile_errors.zig +++ b/test/compile_errors.zig @@ -14,6 +14,15 @@ pub fn addCases(cases: *tests.CompileErrorContext) void { "tmp.zig:4:21: error: expected type '[]align(16) u8', found '*[64]u8'", ); + cases.add( + "atomic orderings of fence Acquire or stricter", + \\export fn entry() void { + \\ @fence(.Monotonic); + \\} + , + "tmp.zig:2:12: error: atomic ordering must be Acquire or stricter", + ); + cases.add( "bad alignment in implicit cast from array pointer to slice", \\export fn a() void { From 66a490c27c01c958d8d20dbc289c6b2b934a724e Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sat, 17 Aug 2019 16:49:23 -0400 Subject: [PATCH 06/25] detect non-async function pointer of inferred async function closes #3075 --- src/all_types.hpp | 1 + src/analyze.cpp | 11 +++++++++-- src/ir.cpp | 18 ++++++++++++++++++ test/compile_errors.zig | 15 +++++++++++++++ 4 files changed, 43 insertions(+), 2 deletions(-) diff --git a/src/all_types.hpp b/src/all_types.hpp index f1c699ba10..f075dd7c58 100644 --- a/src/all_types.hpp +++ b/src/all_types.hpp @@ -1396,6 +1396,7 @@ struct ZigFn { AstNode *set_cold_node; const AstNode *inferred_async_node; ZigFn *inferred_async_fn; + AstNode *non_async_node; ZigList export_list; ZigList call_list; diff --git a/src/analyze.cpp b/src/analyze.cpp index 4aff6da8e9..16e66a5906 100644 --- a/src/analyze.cpp +++ b/src/analyze.cpp @@ -4144,8 +4144,15 @@ void semantic_analyze(CodeGen *g) { // second pass over functions for detecting async for (g->fn_defs_index = 0; g->fn_defs_index < g->fn_defs.length; g->fn_defs_index += 1) { - ZigFn *fn_entry = g->fn_defs.at(g->fn_defs_index); - analyze_fn_async(g, fn_entry, true); + ZigFn *fn = g->fn_defs.at(g->fn_defs_index); + analyze_fn_async(g, fn, true); + if (fn_is_async(fn) && fn->non_async_node != nullptr) { + ErrorMsg *msg = add_node_error(g, fn->proto_node, + buf_sprintf("'%s' cannot be async", buf_ptr(&fn->symbol_name))); + add_error_note(g, msg, fn->non_async_node, + buf_sprintf("required to be non-async here")); + add_async_error_notes(g, msg, fn); + } } } diff --git a/src/ir.cpp b/src/ir.cpp index 51f849ce19..c37fac2f52 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -15160,6 +15160,20 @@ no_mem_slot: return var_ptr_instruction; } +// This function is called when a comptime value becomes accessible at runtime. +static void mark_comptime_value_escape(IrAnalyze *ira, IrInstruction *source_instr, ConstExprValue *val) { + ir_assert(value_is_comptime(val), source_instr); + if (val->special == ConstValSpecialUndef) + return; + + if (val->type->id == ZigTypeIdFn && val->type->data.fn.fn_type_id.cc == CallingConventionUnspecified) { + ir_assert(val->data.x_ptr.special == ConstPtrSpecialFunction, source_instr); + if (val->data.x_ptr.data.fn.fn_entry->non_async_node == nullptr) { + val->data.x_ptr.data.fn.fn_entry->non_async_node = source_instr->source_node; + } + } +} + static IrInstruction *ir_analyze_store_ptr(IrAnalyze *ira, IrInstruction *source_instr, IrInstruction *ptr, IrInstruction *uncasted_value, bool allow_write_through_const) { @@ -15256,6 +15270,10 @@ static IrInstruction *ir_analyze_store_ptr(IrAnalyze *ira, IrInstruction *source break; } + if (instr_is_comptime(value)) { + mark_comptime_value_escape(ira, source_instr, &value->value); + } + IrInstructionStorePtr *store_ptr = ir_build_store_ptr(&ira->new_irb, source_instr->scope, source_instr->source_node, ptr, value); return &store_ptr->base; diff --git a/test/compile_errors.zig b/test/compile_errors.zig index d0c12e7cf6..654171f553 100644 --- a/test/compile_errors.zig +++ b/test/compile_errors.zig @@ -2,6 +2,21 @@ const tests = @import("tests.zig"); const builtin = @import("builtin"); pub fn addCases(cases: *tests.CompileErrorContext) void { + cases.add( + "non-async function pointer eventually is inferred to become async", + \\export fn a() void { + \\ var non_async_fn: fn () void = undefined; + \\ non_async_fn = func; + \\} + \\fn func() void { + \\ suspend; + \\} + , + "tmp.zig:5:1: error: 'func' cannot be async", + "tmp.zig:3:20: note: required to be non-async here", + "tmp.zig:6:5: note: suspends here", + ); + cases.add( "bad alignment in @asyncCall", \\export fn entry() void { From 57b90d2d98154e382c58f1b385de2bcef132f7d9 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sat, 17 Aug 2019 17:22:20 -0400 Subject: [PATCH 07/25] allow implicit cast of fn to async fn it forces the fn to be async. closes #3079 --- src/ir.cpp | 29 ++++++++++++++++++++++++----- test/stage1/behavior/async_fn.zig | 27 +++++++++++++++++++++++++++ 2 files changed, 51 insertions(+), 5 deletions(-) diff --git a/src/ir.cpp b/src/ir.cpp index c37fac2f52..31b354be3a 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -9485,10 +9485,6 @@ static ConstCastOnly types_match_const_cast_only(IrAnalyze *ira, ZigType *wanted result.id = ConstCastResultIdFnAlign; return result; } - if (wanted_type->data.fn.fn_type_id.cc != actual_type->data.fn.fn_type_id.cc) { - result.id = ConstCastResultIdFnCC; - return result; - } if (wanted_type->data.fn.fn_type_id.is_var_args != actual_type->data.fn.fn_type_id.is_var_args) { result.id = ConstCastResultIdFnVarArgs; return result; @@ -9546,6 +9542,11 @@ static ConstCastOnly types_match_const_cast_only(IrAnalyze *ira, ZigType *wanted return result; } } + if (wanted_type->data.fn.fn_type_id.cc != actual_type->data.fn.fn_type_id.cc) { + // ConstCastResultIdFnCC is guaranteed to be the last one reported, meaning everything else is ok. + result.id = ConstCastResultIdFnCC; + return result; + } return result; } @@ -11780,8 +11781,11 @@ static void report_recursive_error(IrAnalyze *ira, AstNode *source_node, ConstCa add_error_note(ira->codegen, parent_msg, source_node, buf_sprintf("only one of the functions is generic")); break; + case ConstCastResultIdFnCC: + add_error_note(ira->codegen, parent_msg, source_node, + buf_sprintf("calling convention mismatch")); + break; case ConstCastResultIdFnAlign: // TODO - case ConstCastResultIdFnCC: // TODO case ConstCastResultIdFnVarArgs: // TODO case ConstCastResultIdFnReturnType: // TODO case ConstCastResultIdFnArgCount: // TODO @@ -11891,6 +11895,21 @@ static IrInstruction *ir_analyze_cast(IrAnalyze *ira, IrInstruction *source_inst return ir_resolve_cast(ira, source_instr, value, wanted_type, CastOpNoop); } + if (const_cast_result.id == ConstCastResultIdFnCC) { + ir_assert(value->value.type->id == ZigTypeIdFn, source_instr); + // ConstCastResultIdFnCC is guaranteed to be the last one reported, meaning everything else is ok. + if (wanted_type->data.fn.fn_type_id.cc == CallingConventionAsync && + actual_type->data.fn.fn_type_id.cc == CallingConventionUnspecified) + { + ir_assert(value->value.data.x_ptr.special == ConstPtrSpecialFunction, source_instr); + ZigFn *fn = value->value.data.x_ptr.data.fn.fn_entry; + if (fn->inferred_async_node == nullptr) { + fn->inferred_async_node = source_instr->source_node; + } + return ir_resolve_cast(ira, source_instr, value, wanted_type, CastOpNoop); + } + } + // cast from T to ?T // note that the *T to ?*T case is handled via the "ConstCastOnly" mechanism if (wanted_type->id == ZigTypeIdOptional) { diff --git a/test/stage1/behavior/async_fn.zig b/test/stage1/behavior/async_fn.zig index cb9bf2c1ea..ccfc4d1ea6 100644 --- a/test/stage1/behavior/async_fn.zig +++ b/test/stage1/behavior/async_fn.zig @@ -817,3 +817,30 @@ test "struct parameter to async function is copied to the frame" { }; S.doTheTest(); } + +test "cast fn to async fn when it is inferred to be async" { + const S = struct { + var frame: anyframe = undefined; + var ok = false; + + fn doTheTest() void { + var ptr: async fn () i32 = undefined; + ptr = func; + var buf: [100]u8 align(16) = undefined; + var result: i32 = undefined; + _ = await @asyncCall(&buf, &result, ptr); + expect(result == 1234); + ok = true; + } + + fn func() i32 { + suspend { + frame = @frame(); + } + return 1234; + } + }; + _ = async S.doTheTest(); + resume S.frame; + expect(S.ok); +} From 6844dafeca42daaf904bc4df0347c6fd625dbcaf Mon Sep 17 00:00:00 2001 From: Marc Tiehuis Date: Mon, 5 Aug 2019 19:07:31 +1200 Subject: [PATCH 08/25] std/fmt: Move pointer parsing out of main state machine This allows us to format a pointer with alignment/padding as we would with any other format specifier. e.g. {*:5} --- std/fmt.zig | 24 +++++++----------------- 1 file changed, 7 insertions(+), 17 deletions(-) diff --git a/std/fmt.zig b/std/fmt.zig index 8c4995cb91..b67848bddb 100644 --- a/std/fmt.zig +++ b/std/fmt.zig @@ -69,7 +69,6 @@ pub fn format( FormatFillAndAlign, FormatWidth, FormatPrecision, - Pointer, }; comptime var start_index = 0; @@ -109,9 +108,6 @@ pub fn format( state = .Start; start_index = i; }, - '*' => { - state = .Pointer; - }, ':' => { state = if (comptime peekIsAlign(fmt[i..])) State.FormatFillAndAlign else State.FormatWidth; specifier_end = i; @@ -256,19 +252,6 @@ pub fn format( @compileError("Unexpected character in precision value: " ++ [_]u8{c}); }, }, - .Pointer => switch (c) { - '}' => { - const arg_to_print = comptime nextArg(&used_pos_args, maybe_pos_arg, &next_arg); - - try output(context, @typeName(@typeOf(args[arg_to_print]).Child)); - try output(context, "@"); - try formatInt(@ptrToInt(args[arg_to_print]), 16, false, 0, context, Errors, output); - - state = .Start; - start_index = i + 1; - }, - else => @compileError("Unexpected format character after '*'"), - }, } } comptime { @@ -299,6 +282,13 @@ pub fn formatType( output: fn (@typeOf(context), []const u8) Errors!void, max_depth: usize, ) Errors!void { + if (comptime std.mem.eql(u8, fmt, "*")) { + try output(context, @typeName(@typeOf(value).Child)); + try output(context, "@"); + try formatInt(@ptrToInt(value), 16, false, 0, context, Errors, output); + return; + } + const T = @typeOf(value); switch (@typeInfo(T)) { .ComptimeInt, .Int, .Float => { From ea1734773ba9913e32318aba963cdcb9f51128d4 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sat, 17 Aug 2019 19:47:49 -0400 Subject: [PATCH 09/25] add compile error for async frames depending on themselves --- src/all_types.hpp | 6 ++++++ src/analyze.cpp | 45 +++++++++++++++++++++++++++++++++++++++-- test/compile_errors.zig | 36 +++++++++++++++++++++++++++++++++ 3 files changed, 85 insertions(+), 2 deletions(-) diff --git a/src/all_types.hpp b/src/all_types.hpp index f075dd7c58..559ebe8cda 100644 --- a/src/all_types.hpp +++ b/src/all_types.hpp @@ -1279,6 +1279,12 @@ struct ZigTypeOpaque { struct ZigTypeFnFrame { ZigFn *fn; ZigType *locals_struct; + + // This is set to the type that resolving the frame currently depends on, null if none. + // It's for generating a helpful error message. + ZigType *resolve_loop_type; + AstNode *resolve_loop_src_node; + bool reported_loop_err; }; struct ZigTypeAnyFrame { diff --git a/src/analyze.cpp b/src/analyze.cpp index 16e66a5906..ab994d07e8 100644 --- a/src/analyze.cpp +++ b/src/analyze.cpp @@ -5197,6 +5197,27 @@ static ZigType *get_async_fn_type(CodeGen *g, ZigType *orig_fn_type) { return fn_type; } +static void emit_error_notes_for_type_loop(CodeGen *g, ErrorMsg *msg, ZigType *stop_type, + ZigType *ty, AstNode *src_node) +{ + ErrorMsg *note = add_error_note(g, msg, src_node, + buf_sprintf("when analyzing type '%s' here", buf_ptr(&ty->name))); + if (ty == stop_type) + return; + switch (ty->id) { + case ZigTypeIdFnFrame: { + ty->data.frame.reported_loop_err = true; + ZigType *depending_type = ty->data.frame.resolve_loop_type; + if (depending_type == nullptr) + return; + emit_error_notes_for_type_loop(g, note, stop_type, + depending_type, ty->data.frame.resolve_loop_src_node); + } + default: + return; + } +} + static Error resolve_async_frame(CodeGen *g, ZigType *frame_type) { Error err; @@ -5206,6 +5227,20 @@ static Error resolve_async_frame(CodeGen *g, ZigType *frame_type) { ZigFn *fn = frame_type->data.frame.fn; assert(!fn->type_entry->data.fn.is_generic); + if (frame_type->data.frame.resolve_loop_type != nullptr) { + if (!frame_type->data.frame.reported_loop_err) { + frame_type->data.frame.reported_loop_err = true; + ErrorMsg *msg = add_node_error(g, fn->proto_node, + buf_sprintf("'%s' depends on itself", buf_ptr(&frame_type->name))); + emit_error_notes_for_type_loop(g, msg, + frame_type, + frame_type->data.frame.resolve_loop_type, + frame_type->data.frame.resolve_loop_src_node); + emit_error_notes_for_ref_stack(g, msg); + } + return ErrorSemanticAnalyzeFail; + } + switch (fn->anal_state) { case FnAnalStateInvalid: return ErrorSemanticAnalyzeFail; @@ -5299,6 +5334,10 @@ static Error resolve_async_frame(CodeGen *g, ZigType *frame_type) { return ErrorSemanticAnalyzeFail; } + ZigType *callee_frame_type = get_fn_frame_type(g, callee); + frame_type->data.frame.resolve_loop_type = callee_frame_type; + frame_type->data.frame.resolve_loop_src_node = call->base.source_node; + analyze_fn_body(g, callee); if (callee->anal_state == FnAnalStateInvalid) { frame_type->data.frame.locals_struct = g->builtin_types.entry_invalid; @@ -5308,8 +5347,6 @@ static Error resolve_async_frame(CodeGen *g, ZigType *frame_type) { if (!fn_is_async(callee)) continue; - ZigType *callee_frame_type = get_fn_frame_type(g, callee); - IrInstructionAllocaGen *alloca_gen = allocate(1); alloca_gen->base.id = IrInstructionIdAllocaGen; alloca_gen->base.source_node = call->base.source_node; @@ -5378,9 +5415,13 @@ static Error resolve_async_frame(CodeGen *g, ZigType *frame_type) { continue; } } + + frame_type->data.frame.resolve_loop_type = child_type; + frame_type->data.frame.resolve_loop_src_node = instruction->base.source_node; if ((err = type_resolve(g, child_type, ResolveStatusSizeKnown))) { return err; } + const char *name; if (*instruction->name_hint == 0) { name = buf_ptr(buf_sprintf("@local%" ZIG_PRI_usize, alloca_i)); diff --git a/test/compile_errors.zig b/test/compile_errors.zig index 654171f553..cecb37620c 100644 --- a/test/compile_errors.zig +++ b/test/compile_errors.zig @@ -2,6 +2,42 @@ const tests = @import("tests.zig"); const builtin = @import("builtin"); pub fn addCases(cases: *tests.CompileErrorContext) void { + cases.add( + "indirect recursion of async functions detected", + \\var frame: ?anyframe = null; + \\ + \\export fn a() void { + \\ _ = async rangeSum(10); + \\ while (frame) |f| resume f; + \\} + \\ + \\fn rangeSum(x: i32) i32 { + \\ suspend { + \\ frame = @frame(); + \\ } + \\ frame = null; + \\ + \\ if (x == 0) return 0; + \\ var child = rangeSumIndirect(x - 1); + \\ return child + 1; + \\} + \\ + \\fn rangeSumIndirect(x: i32) i32 { + \\ suspend { + \\ frame = @frame(); + \\ } + \\ frame = null; + \\ + \\ if (x == 0) return 0; + \\ var child = rangeSum(x - 1); + \\ return child + 1; + \\} + , + "tmp.zig:8:1: error: '@Frame(rangeSum)' depends on itself", + "tmp.zig:15:33: note: when analyzing type '@Frame(rangeSumIndirect)' here", + "tmp.zig:26:25: note: when analyzing type '@Frame(rangeSum)' here", + ); + cases.add( "non-async function pointer eventually is inferred to become async", \\export fn a() void { From 5a0275c247730040af91666518a6aa3f518e6905 Mon Sep 17 00:00:00 2001 From: Nick Erdmann Date: Mon, 19 Aug 2019 00:20:07 +0200 Subject: [PATCH 10/25] fix error message when dependency requires position independent code --- src/ir.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/ir.cpp b/src/ir.cpp index 31b354be3a..65a66c8416 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -17179,7 +17179,7 @@ static void add_link_lib_symbol(IrAnalyze *ira, Buf *lib_name, Buf *symbol_name, buf_sprintf("dependency on dynamic library '%s' requires enabling Position Independent Code", buf_ptr(lib_name))); add_error_note(ira->codegen, msg, source_node, - buf_sprintf("fixed by `--library %s` or `--enable-pic`", buf_ptr(lib_name))); + buf_sprintf("fixed by `--library %s` or `-fPIC`", buf_ptr(lib_name))); ira->codegen->reported_bad_link_libc_error = true; } From 98859c885e8e66518c1081c56d203cdd77c51ce8 Mon Sep 17 00:00:00 2001 From: Marc Tiehuis Date: Sun, 18 Aug 2019 11:17:16 +1200 Subject: [PATCH 11/25] std/fmt.zig: Pass full options struct to all internal functions The fill specifier is now handled in some cases. The default fill of '0' is now ' ' for integers and non-byte sequences. --- std/fmt.zig | 111 +++++++++++---------- test/compare_output.zig | 2 +- test/stage1/behavior/enum_with_members.zig | 4 +- 3 files changed, 62 insertions(+), 55 deletions(-) diff --git a/std/fmt.zig b/std/fmt.zig index b67848bddb..56ae268194 100644 --- a/std/fmt.zig +++ b/std/fmt.zig @@ -285,7 +285,7 @@ pub fn formatType( if (comptime std.mem.eql(u8, fmt, "*")) { try output(context, @typeName(@typeOf(value).Child)); try output(context, "@"); - try formatInt(@ptrToInt(value), 16, false, 0, context, Errors, output); + try formatInt(@ptrToInt(value), 16, false, FormatOptions{}, context, Errors, output); return; } @@ -434,9 +434,9 @@ fn formatValue( output: fn (@typeOf(context), []const u8) Errors!void, ) Errors!void { if (comptime std.mem.eql(u8, fmt, "B")) { - return formatBytes(value, options.width, 1000, context, Errors, output); + return formatBytes(value, options, 1000, context, Errors, output); } else if (comptime std.mem.eql(u8, fmt, "Bi")) { - return formatBytes(value, options.width, 1024, context, Errors, output); + return formatBytes(value, options, 1024, context, Errors, output); } const T = @typeOf(value); @@ -469,7 +469,7 @@ pub fn formatIntValue( uppercase = false; } else if (comptime std.mem.eql(u8, fmt, "c")) { if (@typeOf(int_value).bit_count <= 8) { - return formatAsciiChar(u8(int_value), context, Errors, output); + return formatAsciiChar(u8(int_value), options, context, Errors, output); } else { @compileError("Cannot print integer that is larger than 8 bits as a ascii"); } @@ -486,7 +486,7 @@ pub fn formatIntValue( @compileError("Unknown format string: '" ++ fmt ++ "'"); } - return formatInt(int_value, radix, uppercase, options.width orelse 0, context, Errors, output); + return formatInt(int_value, radix, uppercase, options, context, Errors, output); } fn formatFloatValue( @@ -498,9 +498,9 @@ fn formatFloatValue( output: fn (@typeOf(context), []const u8) Errors!void, ) Errors!void { if (fmt.len == 0 or comptime std.mem.eql(u8, fmt, "e")) { - return formatFloatScientific(value, options.precision, context, Errors, output); + return formatFloatScientific(value, options, context, Errors, output); } else if (comptime std.mem.eql(u8, fmt, "d")) { - return formatFloatDecimal(value, options.precision, context, Errors, output); + return formatFloatDecimal(value, options, context, Errors, output); } else { @compileError("Unknown format string: '" ++ fmt ++ "'"); } @@ -517,11 +517,10 @@ pub fn formatText( if (fmt.len == 0) { return output(context, bytes); } else if (comptime std.mem.eql(u8, fmt, "s")) { - if (options.width) |w| return formatBuf(bytes, w, context, Errors, output); - return formatBuf(bytes, 0, context, Errors, output); + return formatBuf(bytes, options, context, Errors, output); } else if (comptime (std.mem.eql(u8, fmt, "x") or std.mem.eql(u8, fmt, "X"))) { for (bytes) |c| { - try formatInt(c, 16, fmt[0] == 'X', 2, context, Errors, output); + try formatInt(c, 16, fmt[0] == 'X', FormatOptions{ .width = 2, .fill = '0' }, context, Errors, output); } return; } else { @@ -531,6 +530,7 @@ pub fn formatText( pub fn formatAsciiChar( c: u8, + comptime options: FormatOptions, context: var, comptime Errors: type, output: fn (@typeOf(context), []const u8) Errors!void, @@ -540,15 +540,16 @@ pub fn formatAsciiChar( pub fn formatBuf( buf: []const u8, - width: usize, + comptime options: FormatOptions, context: var, comptime Errors: type, output: fn (@typeOf(context), []const u8) Errors!void, ) Errors!void { try output(context, buf); + const width = options.width orelse 0; var leftover_padding = if (width > buf.len) (width - buf.len) else return; - const pad_byte: u8 = ' '; + const pad_byte: u8 = options.fill; while (leftover_padding > 0) : (leftover_padding -= 1) { try output(context, (*const [1]u8)(&pad_byte)[0..1]); } @@ -559,7 +560,7 @@ pub fn formatBuf( // same type unambiguously. pub fn formatFloatScientific( value: var, - maybe_precision: ?usize, + comptime options: FormatOptions, context: var, comptime Errors: type, output: fn (@typeOf(context), []const u8) Errors!void, @@ -581,7 +582,7 @@ pub fn formatFloatScientific( if (x == 0.0) { try output(context, "0"); - if (maybe_precision) |precision| { + if (options.precision) |precision| { if (precision != 0) { try output(context, "."); var i: usize = 0; @@ -600,7 +601,7 @@ pub fn formatFloatScientific( var buffer: [32]u8 = undefined; var float_decimal = errol.errol3(x, buffer[0..]); - if (maybe_precision) |precision| { + if (options.precision) |precision| { errol.roundToPrecision(&float_decimal, precision, errol.RoundMode.Scientific); try output(context, float_decimal.digits[0..1]); @@ -640,13 +641,13 @@ pub fn formatFloatScientific( if (exp > -10 and exp < 10) { try output(context, "0"); } - try formatInt(exp, 10, false, 0, context, Errors, output); + try formatInt(exp, 10, false, FormatOptions{ .width = 0 }, context, Errors, output); } else { try output(context, "-"); if (exp > -10 and exp < 10) { try output(context, "0"); } - try formatInt(-exp, 10, false, 0, context, Errors, output); + try formatInt(-exp, 10, false, FormatOptions{ .width = 0 }, context, Errors, output); } } @@ -654,7 +655,7 @@ pub fn formatFloatScientific( // By default floats are printed at full precision (no rounding). pub fn formatFloatDecimal( value: var, - maybe_precision: ?usize, + comptime options: FormatOptions, context: var, comptime Errors: type, output: fn (@typeOf(context), []const u8) Errors!void, @@ -676,7 +677,7 @@ pub fn formatFloatDecimal( if (x == 0.0) { try output(context, "0"); - if (maybe_precision) |precision| { + if (options.precision) |precision| { if (precision != 0) { try output(context, "."); var i: usize = 0; @@ -697,7 +698,7 @@ pub fn formatFloatDecimal( var buffer: [32]u8 = undefined; var float_decimal = errol.errol3(x, buffer[0..]); - if (maybe_precision) |precision| { + if (options.precision) |precision| { errol.roundToPrecision(&float_decimal, precision, errol.RoundMode.Decimal); // exp < 0 means the leading is always 0 as errol result is normalized. @@ -799,7 +800,7 @@ pub fn formatFloatDecimal( pub fn formatBytes( value: var, - width: ?usize, + comptime options: FormatOptions, comptime radix: usize, context: var, comptime Errors: type, @@ -823,7 +824,7 @@ pub fn formatBytes( else => unreachable, }; - try formatFloatDecimal(new_value, width, context, Errors, output); + try formatFloatDecimal(new_value, options, context, Errors, output); if (suffix == ' ') { return output(context, "B"); @@ -841,7 +842,7 @@ pub fn formatInt( value: var, base: u8, uppercase: bool, - width: usize, + comptime options: FormatOptions, context: var, comptime Errors: type, output: fn (@typeOf(context), []const u8) Errors!void, @@ -853,9 +854,9 @@ pub fn formatInt( value; if (@typeOf(int_value).is_signed) { - return formatIntSigned(int_value, base, uppercase, width, context, Errors, output); + return formatIntSigned(int_value, base, uppercase, options, context, Errors, output); } else { - return formatIntUnsigned(int_value, base, uppercase, width, context, Errors, output); + return formatIntUnsigned(int_value, base, uppercase, options, context, Errors, output); } } @@ -863,26 +864,30 @@ fn formatIntSigned( value: var, base: u8, uppercase: bool, - width: usize, + comptime options: FormatOptions, context: var, comptime Errors: type, output: fn (@typeOf(context), []const u8) Errors!void, ) Errors!void { + const new_options = FormatOptions{ + .width = if (options.width) |w| (if (w == 0) 0 else w - 1) else null, + .precision = options.precision, + .fill = options.fill, + }; + const uint = @IntType(false, @typeOf(value).bit_count); if (value < 0) { const minus_sign: u8 = '-'; try output(context, (*const [1]u8)(&minus_sign)[0..]); const new_value = @intCast(uint, -(value + 1)) + 1; - const new_width = if (width == 0) 0 else (width - 1); - return formatIntUnsigned(new_value, base, uppercase, new_width, context, Errors, output); - } else if (width == 0) { - return formatIntUnsigned(@intCast(uint, value), base, uppercase, width, context, Errors, output); + return formatIntUnsigned(new_value, base, uppercase, new_options, context, Errors, output); + } else if (options.width == null or options.width.? == 0) { + return formatIntUnsigned(@intCast(uint, value), base, uppercase, options, context, Errors, output); } else { const plus_sign: u8 = '+'; try output(context, (*const [1]u8)(&plus_sign)[0..]); const new_value = @intCast(uint, value); - const new_width = if (width == 0) 0 else (width - 1); - return formatIntUnsigned(new_value, base, uppercase, new_width, context, Errors, output); + return formatIntUnsigned(new_value, base, uppercase, new_options, context, Errors, output); } } @@ -890,7 +895,7 @@ fn formatIntUnsigned( value: var, base: u8, uppercase: bool, - width: usize, + comptime options: FormatOptions, context: var, comptime Errors: type, output: fn (@typeOf(context), []const u8) Errors!void, @@ -911,31 +916,32 @@ fn formatIntUnsigned( } const digits_buf = buf[index..]; + const width = options.width orelse 0; const padding = if (width > digits_buf.len) (width - digits_buf.len) else 0; if (padding > index) { - const zero_byte: u8 = '0'; + const zero_byte: u8 = options.fill; var leftover_padding = padding - index; while (true) { try output(context, (*const [1]u8)(&zero_byte)[0..]); leftover_padding -= 1; if (leftover_padding == 0) break; } - mem.set(u8, buf[0..index], '0'); + mem.set(u8, buf[0..index], options.fill); return output(context, buf); } else { const padded_buf = buf[index - padding ..]; - mem.set(u8, padded_buf[0..padding], '0'); + mem.set(u8, padded_buf[0..padding], options.fill); return output(context, padded_buf); } } -pub fn formatIntBuf(out_buf: []u8, value: var, base: u8, uppercase: bool, width: usize) usize { +pub fn formatIntBuf(out_buf: []u8, value: var, base: u8, uppercase: bool, comptime options: FormatOptions) usize { var context = FormatIntBuf{ .out_buf = out_buf, .index = 0, }; - formatInt(value, base, uppercase, width, &context, error{}, formatIntCallback) catch unreachable; + formatInt(value, base, uppercase, options, &context, error{}, formatIntCallback) catch unreachable; return context.index; } const FormatIntBuf = struct { @@ -1078,23 +1084,23 @@ fn countSize(size: *usize, bytes: []const u8) (error{}!void) { test "bufPrintInt" { var buffer: [100]u8 = undefined; const buf = buffer[0..]; - testing.expect(mem.eql(u8, bufPrintIntToSlice(buf, i32(-12345678), 2, false, 0), "-101111000110000101001110")); - testing.expect(mem.eql(u8, bufPrintIntToSlice(buf, i32(-12345678), 10, false, 0), "-12345678")); - testing.expect(mem.eql(u8, bufPrintIntToSlice(buf, i32(-12345678), 16, false, 0), "-bc614e")); - testing.expect(mem.eql(u8, bufPrintIntToSlice(buf, i32(-12345678), 16, true, 0), "-BC614E")); + testing.expect(mem.eql(u8, bufPrintIntToSlice(buf, i32(-12345678), 2, false, FormatOptions{}), "-101111000110000101001110")); + testing.expect(mem.eql(u8, bufPrintIntToSlice(buf, i32(-12345678), 10, false, FormatOptions{}), "-12345678")); + testing.expect(mem.eql(u8, bufPrintIntToSlice(buf, i32(-12345678), 16, false, FormatOptions{}), "-bc614e")); + testing.expect(mem.eql(u8, bufPrintIntToSlice(buf, i32(-12345678), 16, true, FormatOptions{}), "-BC614E")); - testing.expect(mem.eql(u8, bufPrintIntToSlice(buf, u32(12345678), 10, true, 0), "12345678")); + testing.expect(mem.eql(u8, bufPrintIntToSlice(buf, u32(12345678), 10, true, FormatOptions{}), "12345678")); - testing.expect(mem.eql(u8, bufPrintIntToSlice(buf, u32(666), 10, false, 6), "000666")); - testing.expect(mem.eql(u8, bufPrintIntToSlice(buf, u32(0x1234), 16, false, 6), "001234")); - testing.expect(mem.eql(u8, bufPrintIntToSlice(buf, u32(0x1234), 16, false, 1), "1234")); + testing.expect(mem.eql(u8, bufPrintIntToSlice(buf, u32(666), 10, false, FormatOptions{ .width = 6 }), " 666")); + testing.expect(mem.eql(u8, bufPrintIntToSlice(buf, u32(0x1234), 16, false, FormatOptions{ .width = 6 }), " 1234")); + testing.expect(mem.eql(u8, bufPrintIntToSlice(buf, u32(0x1234), 16, false, FormatOptions{ .width = 1 }), "1234")); - testing.expect(mem.eql(u8, bufPrintIntToSlice(buf, i32(42), 10, false, 3), "+42")); - testing.expect(mem.eql(u8, bufPrintIntToSlice(buf, i32(-42), 10, false, 3), "-42")); + testing.expect(mem.eql(u8, bufPrintIntToSlice(buf, i32(42), 10, false, FormatOptions{ .width = 3 }), "+42")); + testing.expect(mem.eql(u8, bufPrintIntToSlice(buf, i32(-42), 10, false, FormatOptions{ .width = 3 }), "-42")); } -fn bufPrintIntToSlice(buf: []u8, value: var, base: u8, uppercase: bool, width: usize) []u8 { - return buf[0..formatIntBuf(buf, value, base, uppercase, width)]; +fn bufPrintIntToSlice(buf: []u8, value: var, base: u8, uppercase: bool, comptime options: FormatOptions) []u8 { + return buf[0..formatIntBuf(buf, value, base, uppercase, options)]; } test "parse u64 digit too big" { @@ -1152,7 +1158,8 @@ test "int.specifier" { } test "int.padded" { - try testFmt("u8: '0001'", "u8: '{:4}'", u8(1)); + try testFmt("u8: ' 1'", "u8: '{:4}'", u8(1)); + try testFmt("u8: 'xxx1'", "u8: '{:x<4}'", u8(1)); } test "buffer" { @@ -1227,7 +1234,7 @@ test "cstr" { test "filesize" { try testFmt("file size: 63MiB\n", "file size: {Bi}\n", usize(63 * 1024 * 1024)); - try testFmt("file size: 66.06MB\n", "file size: {B:2}\n", usize(63 * 1024 * 1024)); + try testFmt("file size: 66.06MB\n", "file size: {B:.2}\n", usize(63 * 1024 * 1024)); } test "struct" { diff --git a/test/compare_output.zig b/test/compare_output.zig index 79057f3c54..cbc74c8be2 100644 --- a/test/compare_output.zig +++ b/test/compare_output.zig @@ -124,7 +124,7 @@ pub fn addCases(cases: *tests.CompareOutputContext) void { \\ const stdout = &(io.getStdOut() catch unreachable).outStream().stream; \\ stdout.print("Hello, world!\n{d:4} {x:3} {c}\n", u32(12), u16(0x12), u8('a')) catch unreachable; \\} - , "Hello, world!\n0012 012 a\n"); + , "Hello, world!\n 12 12 a\n"); cases.addC("number literals", \\const builtin = @import("builtin"); diff --git a/test/stage1/behavior/enum_with_members.zig b/test/stage1/behavior/enum_with_members.zig index 2e022a3427..08b195494b 100644 --- a/test/stage1/behavior/enum_with_members.zig +++ b/test/stage1/behavior/enum_with_members.zig @@ -8,8 +8,8 @@ const ET = union(enum) { pub fn print(a: *const ET, buf: []u8) anyerror!usize { return switch (a.*) { - ET.SINT => |x| fmt.formatIntBuf(buf, x, 10, false, 0), - ET.UINT => |x| fmt.formatIntBuf(buf, x, 10, false, 0), + ET.SINT => |x| fmt.formatIntBuf(buf, x, 10, false, fmt.FormatOptions{}), + ET.UINT => |x| fmt.formatIntBuf(buf, x, 10, false, fmt.FormatOptions{}), }; } }; From e0447c6ddd71d400fc501bf33c4cb33dde4e3300 Mon Sep 17 00:00:00 2001 From: Marc Tiehuis Date: Mon, 19 Aug 2019 22:28:13 +1200 Subject: [PATCH 12/25] std/fmt: Make FormatOptions arguments non-comptime No need and should help avoid exessive function monomorphizaton. --- std/fmt.zig | 34 +++++++++++++++++----------------- std/math/big/int.zig | 2 +- 2 files changed, 18 insertions(+), 18 deletions(-) diff --git a/std/fmt.zig b/std/fmt.zig index 56ae268194..984d46272c 100644 --- a/std/fmt.zig +++ b/std/fmt.zig @@ -276,7 +276,7 @@ pub fn format( pub fn formatType( value: var, comptime fmt: []const u8, - comptime options: FormatOptions, + options: FormatOptions, context: var, comptime Errors: type, output: fn (@typeOf(context), []const u8) Errors!void, @@ -428,7 +428,7 @@ pub fn formatType( fn formatValue( value: var, comptime fmt: []const u8, - comptime options: FormatOptions, + options: FormatOptions, context: var, comptime Errors: type, output: fn (@typeOf(context), []const u8) Errors!void, @@ -450,7 +450,7 @@ fn formatValue( pub fn formatIntValue( value: var, comptime fmt: []const u8, - comptime options: FormatOptions, + options: FormatOptions, context: var, comptime Errors: type, output: fn (@typeOf(context), []const u8) Errors!void, @@ -492,7 +492,7 @@ pub fn formatIntValue( fn formatFloatValue( value: var, comptime fmt: []const u8, - comptime options: FormatOptions, + options: FormatOptions, context: var, comptime Errors: type, output: fn (@typeOf(context), []const u8) Errors!void, @@ -509,7 +509,7 @@ fn formatFloatValue( pub fn formatText( bytes: []const u8, comptime fmt: []const u8, - comptime options: FormatOptions, + options: FormatOptions, context: var, comptime Errors: type, output: fn (@typeOf(context), []const u8) Errors!void, @@ -530,7 +530,7 @@ pub fn formatText( pub fn formatAsciiChar( c: u8, - comptime options: FormatOptions, + options: FormatOptions, context: var, comptime Errors: type, output: fn (@typeOf(context), []const u8) Errors!void, @@ -540,7 +540,7 @@ pub fn formatAsciiChar( pub fn formatBuf( buf: []const u8, - comptime options: FormatOptions, + options: FormatOptions, context: var, comptime Errors: type, output: fn (@typeOf(context), []const u8) Errors!void, @@ -560,7 +560,7 @@ pub fn formatBuf( // same type unambiguously. pub fn formatFloatScientific( value: var, - comptime options: FormatOptions, + options: FormatOptions, context: var, comptime Errors: type, output: fn (@typeOf(context), []const u8) Errors!void, @@ -655,7 +655,7 @@ pub fn formatFloatScientific( // By default floats are printed at full precision (no rounding). pub fn formatFloatDecimal( value: var, - comptime options: FormatOptions, + options: FormatOptions, context: var, comptime Errors: type, output: fn (@typeOf(context), []const u8) Errors!void, @@ -800,7 +800,7 @@ pub fn formatFloatDecimal( pub fn formatBytes( value: var, - comptime options: FormatOptions, + options: FormatOptions, comptime radix: usize, context: var, comptime Errors: type, @@ -842,7 +842,7 @@ pub fn formatInt( value: var, base: u8, uppercase: bool, - comptime options: FormatOptions, + options: FormatOptions, context: var, comptime Errors: type, output: fn (@typeOf(context), []const u8) Errors!void, @@ -864,7 +864,7 @@ fn formatIntSigned( value: var, base: u8, uppercase: bool, - comptime options: FormatOptions, + options: FormatOptions, context: var, comptime Errors: type, output: fn (@typeOf(context), []const u8) Errors!void, @@ -895,7 +895,7 @@ fn formatIntUnsigned( value: var, base: u8, uppercase: bool, - comptime options: FormatOptions, + options: FormatOptions, context: var, comptime Errors: type, output: fn (@typeOf(context), []const u8) Errors!void, @@ -936,7 +936,7 @@ fn formatIntUnsigned( } } -pub fn formatIntBuf(out_buf: []u8, value: var, base: u8, uppercase: bool, comptime options: FormatOptions) usize { +pub fn formatIntBuf(out_buf: []u8, value: var, base: u8, uppercase: bool, options: FormatOptions) usize { var context = FormatIntBuf{ .out_buf = out_buf, .index = 0, @@ -1099,7 +1099,7 @@ test "bufPrintInt" { testing.expect(mem.eql(u8, bufPrintIntToSlice(buf, i32(-42), 10, false, FormatOptions{ .width = 3 }), "-42")); } -fn bufPrintIntToSlice(buf: []u8, value: var, base: u8, uppercase: bool, comptime options: FormatOptions) []u8 { +fn bufPrintIntToSlice(buf: []u8, value: var, base: u8, uppercase: bool, options: FormatOptions) []u8 { return buf[0..formatIntBuf(buf, value, base, uppercase, options)]; } @@ -1339,7 +1339,7 @@ test "custom" { pub fn format( self: SelfType, comptime fmt: []const u8, - comptime options: FormatOptions, + options: FormatOptions, context: var, comptime Errors: type, output: fn (@typeOf(context), []const u8) Errors!void, @@ -1545,7 +1545,7 @@ test "formatType max_depth" { pub fn format( self: SelfType, comptime fmt: []const u8, - comptime options: FormatOptions, + options: FormatOptions, context: var, comptime Errors: type, output: fn (@typeOf(context), []const u8) Errors!void, diff --git a/std/math/big/int.zig b/std/math/big/int.zig index 4ad5c92b3f..8a6f6c1f75 100644 --- a/std/math/big/int.zig +++ b/std/math/big/int.zig @@ -519,7 +519,7 @@ pub const Int = struct { pub fn format( self: Int, comptime fmt: []const u8, - comptime options: std.fmt.FormatOptions, + options: std.fmt.FormatOptions, context: var, comptime FmtError: type, output: fn (@typeOf(context), []const u8) FmtError!void, From 3f7f52003690ccee8a2a33e91258428e22761492 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 19 Aug 2019 12:32:30 -0400 Subject: [PATCH 13/25] don't put libc on the elf linker line for objects closes #3093 --- src/link.cpp | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/link.cpp b/src/link.cpp index 44b3abaa90..8a1f889234 100644 --- a/src/link.cpp +++ b/src/link.cpp @@ -1753,7 +1753,7 @@ static void construct_linker_job_elf(LinkJob *lj) { // libc dep - if (g->libc_link_lib != nullptr) { + if (g->libc_link_lib != nullptr && g->out_type != OutTypeObj) { if (g->libc != nullptr) { if (!g->have_dynamic_link) { lj->args.append("--start-group"); From 44fb5275c1babed97a38b4b3c97e59740a2a5cc5 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 19 Aug 2019 14:46:12 -0400 Subject: [PATCH 14/25] fix array multiplication not setting parent value info closes #3095 --- src/ir.cpp | 7 +++++-- test/stage1/behavior/array.zig | 22 ++++++++++++++++++++-- 2 files changed, 25 insertions(+), 4 deletions(-) diff --git a/src/ir.cpp b/src/ir.cpp index 65a66c8416..3b855b374f 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -13963,8 +13963,11 @@ static IrInstruction *ir_analyze_array_mult(IrAnalyze *ira, IrInstructionBinOp * uint64_t i = 0; for (uint64_t x = 0; x < mult_amt; x += 1) { for (uint64_t y = 0; y < old_array_len; y += 1) { - copy_const_val(&out_val->data.x_array.data.s_none.elements[i], - &array_val->data.x_array.data.s_none.elements[y], false); + ConstExprValue *elem_dest_val = &out_val->data.x_array.data.s_none.elements[i]; + copy_const_val(elem_dest_val, &array_val->data.x_array.data.s_none.elements[y], false); + elem_dest_val->parent.id = ConstParentIdArray; + elem_dest_val->parent.data.p_array.array_val = out_val; + elem_dest_val->parent.data.p_array.elem_index = i; i += 1; } } diff --git a/test/stage1/behavior/array.zig b/test/stage1/behavior/array.zig index 49f9885702..9349af573c 100644 --- a/test/stage1/behavior/array.zig +++ b/test/stage1/behavior/array.zig @@ -1,5 +1,6 @@ -const expect = @import("std").testing.expect; -const mem = @import("std").mem; +const std = @import("std"); +const expect = std.testing.expect; +const mem = std.mem; test "arrays" { var array: [5]u32 = undefined; @@ -274,3 +275,20 @@ test "double nested array to const slice cast in array literal" { S.entry(2); comptime S.entry(2); } + +test "read/write through global variable array of struct fields initialized via array mult" { + const S = struct { + fn doTheTest() void { + expect(storage[0].term == 1); + storage[0] = MyStruct{ .term = 123 }; + expect(storage[0].term == 123); + } + + pub const MyStruct = struct { + term: usize, + }; + + var storage: [1]MyStruct = [_]MyStruct{MyStruct{ .term = 1 }} ** 1; + }; + S.doTheTest(); +} From d067a037cce906d600851e9b88df251451f9a93e Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 19 Aug 2019 15:58:31 -0400 Subject: [PATCH 15/25] fix void array as a local variable initializer Related: #1767 --- src/ir.cpp | 12 ++++++++++-- test/stage1/behavior/void.zig | 5 +++++ 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/src/ir.cpp b/src/ir.cpp index 3b855b374f..ccdd34f893 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -13940,8 +13940,7 @@ static IrInstruction *ir_analyze_array_mult(IrAnalyze *ira, IrInstructionBinOp * uint64_t old_array_len = array_type->data.array.len; uint64_t new_array_len; - if (mul_u64_overflow(old_array_len, mult_amt, &new_array_len)) - { + if (mul_u64_overflow(old_array_len, mult_amt, &new_array_len)) { ir_add_error(ira, &instruction->base, buf_sprintf("operation results in overflow")); return ira->codegen->invalid_instruction; } @@ -13956,6 +13955,15 @@ static IrInstruction *ir_analyze_array_mult(IrAnalyze *ira, IrInstructionBinOp * return result; } + switch (type_has_one_possible_value(ira->codegen, result->value.type)) { + case OnePossibleValueInvalid: + return ira->codegen->invalid_instruction; + case OnePossibleValueYes: + return result; + case OnePossibleValueNo: + break; + } + // TODO optimize the buf case expand_undef_array(ira->codegen, array_val); out_val->data.x_array.data.s_none.elements = create_const_vals(new_array_len); diff --git a/test/stage1/behavior/void.zig b/test/stage1/behavior/void.zig index 9722791946..19e879d157 100644 --- a/test/stage1/behavior/void.zig +++ b/test/stage1/behavior/void.zig @@ -33,3 +33,8 @@ test "void optional" { var x: ?void = {}; expect(x != null); } + +test "void array as a local variable initializer" { + var x = [_]void{{}} ** 1004; + var y = x[0]; +} From efc2237e5a545fac94ecc9c9757d202bda322abd Mon Sep 17 00:00:00 2001 From: Aaron Klapatch Date: Mon, 19 Aug 2019 15:47:16 -0500 Subject: [PATCH 16/25] added documentation for field access to C pointers (#3088) --- doc/langref.html.in | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/doc/langref.html.in b/doc/langref.html.in index b2f313b234..c1fe08ddb6 100644 --- a/doc/langref.html.in +++ b/doc/langref.html.in @@ -9322,6 +9322,13 @@ const c = @cImport({
  • Does not support Zig-only pointer attributes such as alignment. Use normal {#link|Pointers#} please!
  • +

    When a C pointer is pointing to a single struct (not an array), deference the C pointer to + access to the struct's fields or member data. That syntax looks like + this:

    +

    {#syntax#}ptr_to_struct.*.struct_member{#endsyntax#}

    +

    This is comparable to doing {#syntax#}->{#endsyntax#} in C.

    +

    When a C pointer is pointing to an array of structs, the syntax reverts to this:

    +

    {#syntax#}ptr_to_struct_array[index].struct_member{#endsyntax#}

    {#header_close#} {#header_open|Exporting a C Library#} From 24deb1a7fe955202335ed7540fa20a43ae6eca36 Mon Sep 17 00:00:00 2001 From: Michael Dusan Date: Tue, 6 Aug 2019 12:24:52 -0400 Subject: [PATCH 17/25] fix @bitCast segfault with literal array param closes #3010 --- src/ir.cpp | 2 +- test/stage1/behavior/bitcast.zig | 5 +++++ 2 files changed, 6 insertions(+), 1 deletion(-) diff --git a/src/ir.cpp b/src/ir.cpp index ccdd34f893..a018477e0d 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -24561,7 +24561,7 @@ static IrInstruction *ir_analyze_instruction_bit_cast_src(IrAnalyze *ira, IrInst IrInstruction *result_loc = ir_resolve_result(ira, &instruction->base, &instruction->result_loc_bit_cast->base, operand->value.type, operand, false, false, true); - if (result_loc != nullptr && (type_is_invalid(result_loc->value.type) || instr_is_unreachable(result_loc))) + if (result_loc != nullptr && !(type_is_invalid(result_loc->value.type) || instr_is_unreachable(result_loc))) return result_loc; return instruction->result_loc_bit_cast->parent->gen_instruction; diff --git a/test/stage1/behavior/bitcast.zig b/test/stage1/behavior/bitcast.zig index 394ade1a21..a8d90b6ae6 100644 --- a/test/stage1/behavior/bitcast.zig +++ b/test/stage1/behavior/bitcast.zig @@ -125,3 +125,8 @@ test "implicit cast to error union by returning" { S.entry(); comptime S.entry(); } + +// issue #3010: compiler segfault +test "bitcast literal [4]u8 param to u32" { + const ip = @bitCast(u32, [_]u8{ 255, 255, 255, 255 }); +} From 6bc520ab957e25f9ae6d0f4d8a8ad4b96e145ac3 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 19 Aug 2019 17:35:19 -0400 Subject: [PATCH 18/25] solve it a slightly different way the error handling of result locations is a bit awkward but it should basically be the same everywhere --- src/ir.cpp | 8 ++++++-- test/stage1/behavior/bitcast.zig | 1 + 2 files changed, 7 insertions(+), 2 deletions(-) diff --git a/src/ir.cpp b/src/ir.cpp index a018477e0d..e98f028e31 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -24561,10 +24561,14 @@ static IrInstruction *ir_analyze_instruction_bit_cast_src(IrAnalyze *ira, IrInst IrInstruction *result_loc = ir_resolve_result(ira, &instruction->base, &instruction->result_loc_bit_cast->base, operand->value.type, operand, false, false, true); - if (result_loc != nullptr && !(type_is_invalid(result_loc->value.type) || instr_is_unreachable(result_loc))) + if (result_loc != nullptr && (type_is_invalid(result_loc->value.type) || instr_is_unreachable(result_loc))) return result_loc; - return instruction->result_loc_bit_cast->parent->gen_instruction; + if (instruction->result_loc_bit_cast->parent->gen_instruction != nullptr) { + return instruction->result_loc_bit_cast->parent->gen_instruction; + } + + return result_loc; } static IrInstruction *ir_analyze_instruction_union_init_named_field(IrAnalyze *ira, diff --git a/test/stage1/behavior/bitcast.zig b/test/stage1/behavior/bitcast.zig index a8d90b6ae6..92777e3d12 100644 --- a/test/stage1/behavior/bitcast.zig +++ b/test/stage1/behavior/bitcast.zig @@ -129,4 +129,5 @@ test "implicit cast to error union by returning" { // issue #3010: compiler segfault test "bitcast literal [4]u8 param to u32" { const ip = @bitCast(u32, [_]u8{ 255, 255, 255, 255 }); + expect(ip == maxInt(u32)); } From 3dbed54294bc6769f64fc8bd23b98605d009677c Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 19 Aug 2019 17:50:37 -0400 Subject: [PATCH 19/25] fix @bitCast of packed struct literal closes #3042 --- src/ir.cpp | 3 +++ test/stage1/behavior/bitcast.zig | 8 ++++++++ 2 files changed, 11 insertions(+) diff --git a/src/ir.cpp b/src/ir.cpp index e98f028e31..8f27c9ea2c 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -14802,6 +14802,9 @@ static IrInstruction *ir_resolve_result_raw(IrAnalyze *ira, IrInstruction *suspe return ira->codegen->invalid_instruction; } uint64_t parent_ptr_align = get_ptr_align(ira->codegen, parent_ptr_type); + if ((err = type_resolve(ira->codegen, value_type, ResolveStatusAlignmentKnown))) { + return ira->codegen->invalid_instruction; + } ZigType *ptr_type = get_pointer_to_type_extra(ira->codegen, value_type, parent_ptr_type->data.pointer.is_const, parent_ptr_type->data.pointer.is_volatile, PtrLenSingle, parent_ptr_align, 0, 0, parent_ptr_type->data.pointer.allow_zero); diff --git a/test/stage1/behavior/bitcast.zig b/test/stage1/behavior/bitcast.zig index 92777e3d12..125e4cce54 100644 --- a/test/stage1/behavior/bitcast.zig +++ b/test/stage1/behavior/bitcast.zig @@ -131,3 +131,11 @@ test "bitcast literal [4]u8 param to u32" { const ip = @bitCast(u32, [_]u8{ 255, 255, 255, 255 }); expect(ip == maxInt(u32)); } + +test "bitcast packed struct literal to byte" { + const Foo = packed struct { + value: u8, + }; + const casted = @bitCast(u8, Foo{ .value = 0xF }); + expect(casted == 0xf); +} From 2addec8ea1f35241e9399c5da6855c427481985f Mon Sep 17 00:00:00 2001 From: Timon Kruiper Date: Sat, 17 Aug 2019 14:16:51 +0200 Subject: [PATCH 20/25] compiler error when variable in asm template cannot be found --- src/ir.cpp | 35 +++++++++++++++++++++++++++++++++++ test/compile_errors.zig | 12 ++++++++++++ 2 files changed, 47 insertions(+) diff --git a/src/ir.cpp b/src/ir.cpp index 8f27c9ea2c..cdddce1b18 100644 --- a/src/ir.cpp +++ b/src/ir.cpp @@ -6743,6 +6743,25 @@ static Error parse_asm_template(IrBuilder *irb, AstNode *source_node, Buf *asm_t return ErrorNone; } +static size_t find_asm_index(CodeGen *g, AstNode *node, AsmToken *tok, Buf *src_template) { + const char *ptr = buf_ptr(src_template) + tok->start + 2; + size_t len = tok->end - tok->start - 2; + size_t result = 0; + for (size_t i = 0; i < node->data.asm_expr.output_list.length; i += 1, result += 1) { + AsmOutput *asm_output = node->data.asm_expr.output_list.at(i); + if (buf_eql_mem(asm_output->asm_symbolic_name, ptr, len)) { + return result; + } + } + for (size_t i = 0; i < node->data.asm_expr.input_list.length; i += 1, result += 1) { + AsmInput *asm_input = node->data.asm_expr.input_list.at(i); + if (buf_eql_mem(asm_input->asm_symbolic_name, ptr, len)) { + return result; + } + } + return SIZE_MAX; +} + static IrInstruction *ir_gen_asm_expr(IrBuilder *irb, Scope *scope, AstNode *node) { Error err; assert(node->type == NodeTypeAsmExpr); @@ -6830,6 +6849,22 @@ static IrInstruction *ir_gen_asm_expr(IrBuilder *irb, Scope *scope, AstNode *nod input_list[i] = input_value; } + for (size_t token_i = 0; token_i < tok_list.length; token_i += 1) { + AsmToken asm_token = tok_list.at(token_i); + if (asm_token.id == AsmTokenIdVar) { + size_t index = find_asm_index(irb->codegen, node, &asm_token, template_buf); + if (index == SIZE_MAX) { + const char *ptr = buf_ptr(template_buf) + asm_token.start + 2; + uint32_t len = asm_token.end - asm_token.start - 2; + + add_node_error(irb->codegen, node, + buf_sprintf("could not find '%.*s' in the inputs or outputs.", + len, ptr)); + return irb->codegen->invalid_instruction; + } + } + } + return ir_build_asm(irb, scope, node, template_buf, tok_list.items, tok_list.length, input_list, output_types, output_vars, return_count, is_volatile); } diff --git a/test/compile_errors.zig b/test/compile_errors.zig index cecb37620c..690db2c4eb 100644 --- a/test/compile_errors.zig +++ b/test/compile_errors.zig @@ -2,6 +2,18 @@ const tests = @import("tests.zig"); const builtin = @import("builtin"); pub fn addCases(cases: *tests.CompileErrorContext) void { + cases.add( + "variable in inline assembly template cannot be found", + \\export fn entry() void { + \\ var sp = asm volatile ( + \\ "mov %[foo], sp" + \\ : [bar] "=r" (-> usize) + \\ ); + \\} + , + "tmp.zig:2:14: error: could not find 'foo' in the inputs or outputs." + ); + cases.add( "indirect recursion of async functions detected", \\var frame: ?anyframe = null; From 276eb4402b3c54adef10033d1ec54bf2053cc563 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 20 Aug 2019 14:40:57 -0400 Subject: [PATCH 21/25] specify the target for the newest test case --- test/compile_errors.zig | 29 ++++++++++++++++++----------- test/tests.zig | 25 +++++++++++++++---------- 2 files changed, 33 insertions(+), 21 deletions(-) diff --git a/test/compile_errors.zig b/test/compile_errors.zig index 690db2c4eb..a6a1d0219b 100644 --- a/test/compile_errors.zig +++ b/test/compile_errors.zig @@ -2,17 +2,24 @@ const tests = @import("tests.zig"); const builtin = @import("builtin"); pub fn addCases(cases: *tests.CompileErrorContext) void { - cases.add( - "variable in inline assembly template cannot be found", - \\export fn entry() void { - \\ var sp = asm volatile ( - \\ "mov %[foo], sp" - \\ : [bar] "=r" (-> usize) - \\ ); - \\} - , - "tmp.zig:2:14: error: could not find 'foo' in the inputs or outputs." - ); + cases.addCase(x: { + var tc = cases.create("variable in inline assembly template cannot be found", + \\export fn entry() void { + \\ var sp = asm volatile ( + \\ "mov %[foo], sp" + \\ : [bar] "=r" (-> usize) + \\ ); + \\} + , "tmp.zig:2:14: error: could not find 'foo' in the inputs or outputs."); + tc.target = tests.Target{ + .Cross = tests.CrossTarget{ + .arch = .x86_64, + .os = .linux, + .abi = .gnu, + }, + }; + break :x tc; + }); cases.add( "indirect recursion of async functions detected", diff --git a/test/tests.zig b/test/tests.zig index c8aea33591..f7ef05d3cd 100644 --- a/test/tests.zig +++ b/test/tests.zig @@ -2,6 +2,8 @@ const std = @import("std"); const debug = std.debug; const warn = debug.warn; const build = std.build; +pub const Target = build.Target; +pub const CrossTarget = build.CrossTarget; const Buffer = std.Buffer; const io = std.io; const fs = std.fs; @@ -20,24 +22,18 @@ const runtime_safety = @import("runtime_safety.zig"); const translate_c = @import("translate_c.zig"); const gen_h = @import("gen_h.zig"); -const TestTarget = struct { - os: builtin.Os, - arch: builtin.Arch, - abi: builtin.Abi, -}; - -const test_targets = [_]TestTarget{ - TestTarget{ +const test_targets = [_]CrossTarget{ + CrossTarget{ .os = .linux, .arch = .x86_64, .abi = .gnu, }, - TestTarget{ + CrossTarget{ .os = .macosx, .arch = .x86_64, .abi = .gnu, }, - TestTarget{ + CrossTarget{ .os = .windows, .arch = .x86_64, .abi = .msvc, @@ -568,6 +564,7 @@ pub const CompileErrorContext = struct { link_libc: bool, is_exe: bool, is_test: bool, + target: Target = .Native, const SourceFile = struct { filename: []const u8, @@ -655,6 +652,14 @@ pub const CompileErrorContext = struct { zig_args.append("--output-dir") catch unreachable; zig_args.append(b.pathFromRoot(b.cache_root)) catch unreachable; + switch (self.case.target) { + .Native => {}, + .Cross => { + try zig_args.append("-target"); + try zig_args.append(try self.case.target.zigTriple(b.allocator)); + }, + } + switch (self.build_mode) { Mode.Debug => {}, Mode.ReleaseSafe => zig_args.append("--release-safe") catch unreachable, From c175e53564e206f90dd8bd224b2db2fb7eca7c27 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 20 Aug 2019 14:42:39 -0400 Subject: [PATCH 22/25] add std.c.printf --- std/c.zig | 1 + 1 file changed, 1 insertion(+) diff --git a/std/c.zig b/std/c.zig index 7ba606c18b..e7b98107ab 100644 --- a/std/c.zig +++ b/std/c.zig @@ -55,6 +55,7 @@ pub extern "c" fn fclose(stream: *FILE) c_int; pub extern "c" fn fwrite(ptr: [*]const u8, size_of_type: usize, item_count: usize, stream: *FILE) usize; pub extern "c" fn fread(ptr: [*]u8, size_of_type: usize, item_count: usize, stream: *FILE) usize; +pub extern "c" fn printf(format: [*]const u8, ...) c_int; pub extern "c" fn abort() noreturn; pub extern "c" fn exit(code: c_int) noreturn; pub extern "c" fn isatty(fd: fd_t) c_int; From 8c32c09807ed5ae28afce3e9cf3145f350f8c4e3 Mon Sep 17 00:00:00 2001 From: Euan Torano Date: Mon, 5 Aug 2019 19:18:19 +0100 Subject: [PATCH 23/25] Fix InStream.readNoEof --- std/io.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/std/io.zig b/std/io.zig index 3a8da3ed3e..dcf2ec5f61 100644 --- a/std/io.zig +++ b/std/io.zig @@ -146,7 +146,7 @@ pub fn InStream(comptime ReadError: type) type { /// Same as `readFull` but end of stream returns `error.EndOfStream`. pub fn readNoEof(self: *Self, buf: []u8) !void { - const amt_read = try self.read(buf); + const amt_read = try self.readFull(buf); if (amt_read < buf.len) return error.EndOfStream; } From 79354243e32b0e64f4dc93bb581ff1b3061e3861 Mon Sep 17 00:00:00 2001 From: Euan Torano Date: Mon, 5 Aug 2019 19:39:04 +0100 Subject: [PATCH 24/25] fix os.getrandom logic to fill the entire buffer --- std/os.zig | 60 +++++++++++++++++++++++++++++++----------------------- 1 file changed, 34 insertions(+), 26 deletions(-) diff --git a/std/os.zig b/std/os.zig index 0a826c26c2..58ee569f0c 100644 --- a/std/os.zig +++ b/std/os.zig @@ -103,33 +103,41 @@ pub fn getrandom(buf: []u8) GetRandomError!void { if (windows.is_the_target) { return windows.RtlGenRandom(buf); } - if (linux.is_the_target) { - while (true) { - const err = if (std.c.versionCheck(builtin.Version{ .major = 2, .minor = 25, .patch = 0 }).ok) blk: { - break :blk errno(std.c.getrandom(buf.ptr, buf.len, 0)); - } else blk: { - break :blk linux.getErrno(linux.getrandom(buf.ptr, buf.len, 0)); - }; - switch (err) { - 0 => return, - EINVAL => unreachable, - EFAULT => unreachable, - EINTR => continue, - ENOSYS => return getRandomBytesDevURandom(buf), - else => return unexpectedErrno(err), - } - } - } - if (freebsd.is_the_target) { - while (true) { - const err = std.c.getErrno(std.c.getrandom(buf.ptr, buf.len, 0)); + if (linux.is_the_target or freebsd.is_the_target) { + var buf_slice: []u8 = buf[0..]; + var total_read: usize = 0; + const use_c = (!linux.is_the_target) or std.c.versionCheck(builtin.Version{ .major = 2, .minor = 25, .patch = 0 }).ok; - switch (err) { - 0 => return, - EINVAL => unreachable, - EFAULT => unreachable, - EINTR => continue, - else => return unexpectedErrno(err), + while (total_read < buf.len) { + var err: u16 = 0; + + const num_read: usize = if (use_c) blk: { + const res: c_int = std.c.getrandom(buf_slice.ptr, buf_slice.len, 0); + + if (res == -1) { + err = @intCast(u16, std.c._errno().*); + break :blk 0; + } else { + break :blk @intCast(usize, res); + } + } else blk: { + const res: usize = linux.getrandom(buf_slice.ptr, buf_slice.len, 0); + + err = @intCast(u16, linux.getErrno(res)); + break :blk res; + }; + + if (err != 0) { + switch (err) { + EINVAL => unreachable, + EFAULT => unreachable, + EINTR => continue, + ENOSYS => return getRandomBytesDevURandom(buf), + else => return unexpectedErrno(err), + } + } else { + total_read += num_read; + buf_slice = buf_slice[num_read..]; } } } From 9bf283c0851b39d6c54d1f5dfb5c2c55cd0a1ab0 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 20 Aug 2019 15:25:30 -0400 Subject: [PATCH 25/25] fixups * getrandom libc prototypes had the wrong return type * `total_read` local variable was unnecessary since the sub-slice buffer has a length * I was able to get rid of all the integer casts * the err == 0 check can be a switch case * add missing `return` statement --- std/c/freebsd.zig | 2 +- std/c/linux.zig | 2 +- std/os.zig | 57 ++++++++++++++++++++--------------------------- 3 files changed, 26 insertions(+), 35 deletions(-) diff --git a/std/c/freebsd.zig b/std/c/freebsd.zig index 3d5736d37b..71dd4705f8 100644 --- a/std/c/freebsd.zig +++ b/std/c/freebsd.zig @@ -6,4 +6,4 @@ pub const _errno = __error; pub extern "c" fn getdents(fd: c_int, buf_ptr: [*]u8, nbytes: usize) usize; pub extern "c" fn sigaltstack(ss: ?*stack_t, old_ss: ?*stack_t) c_int; -pub extern "c" fn getrandom(buf_ptr: [*]u8, buf_len: usize, flags: c_uint) c_int; +pub extern "c" fn getrandom(buf_ptr: [*]u8, buf_len: usize, flags: c_uint) isize; diff --git a/std/c/linux.zig b/std/c/linux.zig index a69f3c18b2..b3b3cbdde7 100644 --- a/std/c/linux.zig +++ b/std/c/linux.zig @@ -7,7 +7,7 @@ pub const _errno = __errno_location; pub const MAP_FAILED = @intToPtr(*c_void, maxInt(usize)); -pub extern "c" fn getrandom(buf_ptr: [*]u8, buf_len: usize, flags: c_uint) c_int; +pub extern "c" fn getrandom(buf_ptr: [*]u8, buf_len: usize, flags: c_uint) isize; pub extern "c" fn sched_getaffinity(pid: c_int, size: usize, set: *cpu_set_t) c_int; pub extern "c" fn eventfd(initval: c_uint, flags: c_uint) c_int; pub extern "c" fn epoll_ctl(epfd: fd_t, op: c_uint, fd: fd_t, event: ?*epoll_event) c_int; diff --git a/std/os.zig b/std/os.zig index 58ee569f0c..0291282a77 100644 --- a/std/os.zig +++ b/std/os.zig @@ -99,55 +99,46 @@ pub const GetRandomError = OpenError; /// When linking against libc, this calls the /// appropriate OS-specific library call. Otherwise it uses the zig standard /// library implementation. -pub fn getrandom(buf: []u8) GetRandomError!void { +pub fn getrandom(buffer: []u8) GetRandomError!void { if (windows.is_the_target) { - return windows.RtlGenRandom(buf); + return windows.RtlGenRandom(buffer); } if (linux.is_the_target or freebsd.is_the_target) { - var buf_slice: []u8 = buf[0..]; - var total_read: usize = 0; - const use_c = (!linux.is_the_target) or std.c.versionCheck(builtin.Version{ .major = 2, .minor = 25, .patch = 0 }).ok; + var buf = buffer; + const use_c = !linux.is_the_target or + std.c.versionCheck(builtin.Version{ .major = 2, .minor = 25, .patch = 0 }).ok; - while (total_read < buf.len) { - var err: u16 = 0; + while (buf.len != 0) { + var err: u16 = undefined; - const num_read: usize = if (use_c) blk: { - const res: c_int = std.c.getrandom(buf_slice.ptr, buf_slice.len, 0); - - if (res == -1) { - err = @intCast(u16, std.c._errno().*); - break :blk 0; - } else { - break :blk @intCast(usize, res); - } + const num_read = if (use_c) blk: { + const rc = std.c.getrandom(buf.ptr, buf.len, 0); + err = std.c.getErrno(rc); + break :blk @bitCast(usize, rc); } else blk: { - const res: usize = linux.getrandom(buf_slice.ptr, buf_slice.len, 0); - - err = @intCast(u16, linux.getErrno(res)); - break :blk res; + const rc = linux.getrandom(buf.ptr, buf.len, 0); + err = linux.getErrno(rc); + break :blk rc; }; - if (err != 0) { - switch (err) { - EINVAL => unreachable, - EFAULT => unreachable, - EINTR => continue, - ENOSYS => return getRandomBytesDevURandom(buf), - else => return unexpectedErrno(err), - } - } else { - total_read += num_read; - buf_slice = buf_slice[num_read..]; + switch (err) { + 0 => buf = buf[num_read..], + EINVAL => unreachable, + EFAULT => unreachable, + EINTR => continue, + ENOSYS => return getRandomBytesDevURandom(buf), + else => return unexpectedErrno(err), } } + return; } if (wasi.is_the_target) { - switch (wasi.random_get(buf.ptr, buf.len)) { + switch (wasi.random_get(buffer.ptr, buffer.len)) { 0 => return, else => |err| return unexpectedErrno(err), } } - return getRandomBytesDevURandom(buf); + return getRandomBytesDevURandom(buffer); } fn getRandomBytesDevURandom(buf: []u8) !void {