diff --git a/CODE_OF_CONDUCT.md b/CODE_OF_CONDUCT.md new file mode 100644 index 0000000000..f0bded8356 --- /dev/null +++ b/CODE_OF_CONDUCT.md @@ -0,0 +1,75 @@ +# Code of Conduct + +Hello, and welcome! 👋 + +The Zig community is decentralized. Anyone is free to start and maintain their +own space for people to gather, and edit +[the Community wiki page](https://github.com/ziglang/zig/wiki/Community) to add +a link. There is no concept of "official" or "unofficial", however, each +gathering place has its own moderators and rules. + +This is Andrew Kelley speaking. At least for now, I'm the moderator of the +ziglang organization GitHub repositories and the #zig IRC channel on Freenode. +**This document contains the rules that govern these two spaces only**. + +The rules here are strict. This space is for focused, on topic, technical work +on the Zig project only. It is everyone's responsibility to maintain a positive +environment, especially when disagreements occur. + +## Our Standards + +Examples of behavior that contribute to creating a positive environment include: + + * Using welcoming and inclusive language. + * Being respectful of differing viewpoints and experiences. + * Gracefully accepting constructive criticism. + * Helping another person accomplish their own goals. + * Showing empathy towards others. + * Showing appreciation for others' work. + * Validating someone else's experience, skills, insight, and use cases. + +Examples of unacceptable behavior by participants include: + + * Unwelcome sexual attention or advances, or use of sexualized language or + imagery that causes discomfort. + * Trolling, insulting/derogatory comments, and personal attacks. Anything + antagonistic towards someone else. + * Off-topic discussion of any kind - especially offensive or sensitive issues. + * Publishing others' private information, such as a physical or electronic + address, without explicit permission. + * Discussing this Code of Conduct or publicly accusing someone of violating it. + * Making someone else feel like an outsider or implying a lack of technical + abilities. + * Destructive behavior. Anything that harms Zig or another open-source project. + +## Enforcement + +If you need to report an issue you can contact me or Loris Cro, who are both +paid by the Zig Software Foundation, and so moderation of this space is part of +our job. We will swiftly remove anyone who is antagonizing others or being +generally destructive. + +This includes Private Harassment. If person A is directly harassed or +antagonized by person B, person B will be blocked from participating in this +space even if the harassment didn't take place on one of the mediums directly +under rule of this Code of Conduct. + +As noted, discussing this Code of Conduct should not take place on GitHub or IRC +because these spaces are for directly working on code, not for meta-discussion. +If you have any issues with it, you can contact me directly, or you can join one +of the community spaces that has different rules. + + * Andrew Kelley + * Loris Cro + +## Conclusion + +Thanks for reading the rules. Together, we can make this space welcoming and +inclusive for everyone, regardless of age, body size, disability, ethnicity, +sex characteristics, gender identity and expression, level of experience, +education, socio-economic status, nationality, personal appearance, race, +religion, or sexual identity and orientation. + +Sincerely, + +Andrew ✌️ diff --git a/README.md b/README.md index c416893d32..fbfc2f95b2 100644 --- a/README.md +++ b/README.md @@ -7,8 +7,10 @@ A general-purpose programming language and toolchain for maintaining * [Introduction](https://ziglang.org/#Introduction) * [Download & Documentation](https://ziglang.org/download) + * [Chapter 0 - Getting Started | ZigLearn.org](https://ziglearn.org/) * [Community](https://github.com/ziglang/zig/wiki/Community) * [Contributing](https://github.com/ziglang/zig/blob/master/CONTRIBUTING.md) + * [Code of Conduct](https://github.com/ziglang/zig/blob/master/CODE_OF_CONDUCT.md) * [Frequently Asked Questions](https://github.com/ziglang/zig/wiki/FAQ) * [Community Projects](https://github.com/ziglang/zig/wiki/Community-Projects) diff --git a/doc/langref.html.in b/doc/langref.html.in index 021fc76289..7caae4afff 100644 --- a/doc/langref.html.in +++ b/doc/langref.html.in @@ -9905,7 +9905,10 @@ const std = @import("std"); const PreopenList = std.fs.wasi.PreopenList; pub fn main() !void { - var preopens = PreopenList.init(std.heap.page_allocator); + var general_purpose_allocator = std.heap.GeneralPurposeAllocator(.{}){}; + const gpa = &general_purpose_allocator.allocator; + + var preopens = PreopenList.init(gpa); defer preopens.deinit(); try preopens.populate(); diff --git a/lib/std/c.zig b/lib/std/c.zig index a75fcaa84b..1fd3c593cc 100644 --- a/lib/std/c.zig +++ b/lib/std/c.zig @@ -22,7 +22,7 @@ pub usingnamespace @import("os/bits.zig"); pub usingnamespace switch (std.Target.current.os.tag) { .linux => @import("c/linux.zig"), .windows => @import("c/windows.zig"), - .macosx, .ios, .tvos, .watchos => @import("c/darwin.zig"), + .macos, .ios, .tvos, .watchos => @import("c/darwin.zig"), .freebsd, .kfreebsd => @import("c/freebsd.zig"), .netbsd => @import("c/netbsd.zig"), .dragonfly => @import("c/dragonfly.zig"), @@ -122,7 +122,7 @@ pub extern "c" fn readlink(noalias path: [*:0]const u8, noalias buf: [*]u8, bufs pub extern "c" fn readlinkat(dirfd: fd_t, noalias path: [*:0]const u8, noalias buf: [*]u8, bufsize: usize) isize; pub usingnamespace switch (builtin.os.tag) { - .macosx, .ios, .watchos, .tvos => struct { + .macos, .ios, .watchos, .tvos => struct { pub const realpath = @"realpath$DARWIN_EXTSN"; pub const fstatat = @"fstatat$INODE64"; }, @@ -189,7 +189,7 @@ pub usingnamespace switch (builtin.os.tag) { pub const sigprocmask = __sigprocmask14; pub const stat = __stat50; }, - .macosx, .ios, .watchos, .tvos => struct { + .macos, .ios, .watchos, .tvos => struct { // XXX: close -> close$NOCANCEL // XXX: getdirentries -> _getdirentries64 pub extern "c" fn clock_getres(clk_id: c_int, tp: *timespec) c_int; @@ -252,7 +252,7 @@ pub usingnamespace switch (builtin.os.tag) { .linux, .freebsd, .kfreebsd, .netbsd, .openbsd => struct { pub extern "c" fn malloc_usable_size(?*const c_void) usize; }, - .macosx, .ios, .watchos, .tvos => struct { + .macos, .ios, .watchos, .tvos => struct { pub extern "c" fn malloc_size(?*const c_void) usize; }, else => struct {}, diff --git a/lib/std/c/darwin.zig b/lib/std/c/darwin.zig index 976690d6b7..cc7e6d0463 100644 --- a/lib/std/c/darwin.zig +++ b/lib/std/c/darwin.zig @@ -18,6 +18,14 @@ pub extern "c" fn _dyld_get_image_header(image_index: u32) ?*mach_header; pub extern "c" fn _dyld_get_image_vmaddr_slide(image_index: u32) usize; pub extern "c" fn _dyld_get_image_name(image_index: u32) [*:0]const u8; +pub const COPYFILE_ACL = 1 << 0; +pub const COPYFILE_STAT = 1 << 1; +pub const COPYFILE_XATTR = 1 << 2; +pub const COPYFILE_DATA = 1 << 3; + +pub const copyfile_state_t = *opaque {}; +pub extern "c" fn fcopyfile(from: fd_t, to: fd_t, state: ?copyfile_state_t, flags: u32) c_int; + pub extern "c" fn @"realpath$DARWIN_EXTSN"(noalias file_name: [*:0]const u8, noalias resolved_name: [*]u8) ?[*:0]u8; pub extern "c" fn __getdirentries64(fd: c_int, buf_ptr: [*]u8, buf_len: usize, basep: *i64) isize; diff --git a/lib/std/debug.zig b/lib/std/debug.zig index be57c0b2fd..8dc2938894 100644 --- a/lib/std/debug.zig +++ b/lib/std/debug.zig @@ -654,7 +654,7 @@ pub fn openSelfDebugInfo(allocator: *mem.Allocator) anyerror!DebugInfo { .freebsd, .netbsd, .dragonfly, - .macosx, + .macos, .windows, => return DebugInfo.init(allocator), else => @compileError("openSelfDebugInfo unsupported for this platform"), @@ -1320,7 +1320,7 @@ const SymbolInfo = struct { }; pub const ModuleDebugInfo = switch (builtin.os.tag) { - .macosx, .ios, .watchos, .tvos => struct { + .macos, .ios, .watchos, .tvos => struct { base_address: usize, mapped_memory: []const u8, symbols: []const MachoSymbol, diff --git a/lib/std/dynamic_library.zig b/lib/std/dynamic_library.zig index 238854f07f..07be46a8e1 100644 --- a/lib/std/dynamic_library.zig +++ b/lib/std/dynamic_library.zig @@ -19,7 +19,7 @@ const max = std.math.max; pub const DynLib = switch (builtin.os.tag) { .linux => if (builtin.link_libc) DlDynlib else ElfDynLib, .windows => WindowsDynLib, - .macosx, .tvos, .watchos, .ios, .freebsd => DlDynlib, + .macos, .tvos, .watchos, .ios, .freebsd => DlDynlib, else => void, }; @@ -404,7 +404,7 @@ test "dynamic_library" { const libname = switch (builtin.os.tag) { .linux, .freebsd => "invalid_so.so", .windows => "invalid_dll.dll", - .macosx, .tvos, .watchos, .ios => "invalid_dylib.dylib", + .macos, .tvos, .watchos, .ios => "invalid_dylib.dylib", else => return error.SkipZigTest, }; diff --git a/lib/std/event/loop.zig b/lib/std/event/loop.zig index a064f711e2..5b10335535 100644 --- a/lib/std/event/loop.zig +++ b/lib/std/event/loop.zig @@ -66,7 +66,7 @@ pub const Loop = struct { }; pub const EventFd = switch (builtin.os.tag) { - .macosx, .freebsd, .netbsd, .dragonfly => KEventFd, + .macos, .freebsd, .netbsd, .dragonfly => KEventFd, .linux => struct { base: ResumeNode, epoll_op: u32, @@ -85,7 +85,7 @@ pub const Loop = struct { }; pub const Basic = switch (builtin.os.tag) { - .macosx, .freebsd, .netbsd, .dragonfly => KEventBasic, + .macos, .freebsd, .netbsd, .dragonfly => KEventBasic, .linux => struct { base: ResumeNode, }, @@ -259,7 +259,7 @@ pub const Loop = struct { self.extra_threads[extra_thread_index] = try Thread.spawn(self, workerRun); } }, - .macosx, .freebsd, .netbsd, .dragonfly => { + .macos, .freebsd, .netbsd, .dragonfly => { self.os_data.kqfd = try os.kqueue(); errdefer os.close(self.os_data.kqfd); @@ -384,7 +384,7 @@ pub const Loop = struct { while (self.available_eventfd_resume_nodes.pop()) |node| os.close(node.data.eventfd); os.close(self.os_data.epollfd); }, - .macosx, .freebsd, .netbsd, .dragonfly => { + .macos, .freebsd, .netbsd, .dragonfly => { os.close(self.os_data.kqfd); }, .windows => { @@ -478,7 +478,7 @@ pub const Loop = struct { .linux => { self.linuxWaitFd(fd, os.EPOLLET | os.EPOLLONESHOT | os.EPOLLIN); }, - .macosx, .freebsd, .netbsd, .dragonfly => { + .macos, .freebsd, .netbsd, .dragonfly => { self.bsdWaitKev(@intCast(usize, fd), os.EVFILT_READ, os.EV_ONESHOT); }, else => @compileError("Unsupported OS"), @@ -490,7 +490,7 @@ pub const Loop = struct { .linux => { self.linuxWaitFd(fd, os.EPOLLET | os.EPOLLONESHOT | os.EPOLLOUT); }, - .macosx, .freebsd, .netbsd, .dragonfly => { + .macos, .freebsd, .netbsd, .dragonfly => { self.bsdWaitKev(@intCast(usize, fd), os.EVFILT_WRITE, os.EV_ONESHOT); }, else => @compileError("Unsupported OS"), @@ -502,7 +502,7 @@ pub const Loop = struct { .linux => { self.linuxWaitFd(fd, os.EPOLLET | os.EPOLLONESHOT | os.EPOLLOUT | os.EPOLLIN); }, - .macosx, .freebsd, .netbsd, .dragonfly => { + .macos, .freebsd, .netbsd, .dragonfly => { self.bsdWaitKev(@intCast(usize, fd), os.EVFILT_READ, os.EV_ONESHOT); self.bsdWaitKev(@intCast(usize, fd), os.EVFILT_WRITE, os.EV_ONESHOT); }, @@ -571,7 +571,7 @@ pub const Loop = struct { const eventfd_node = &resume_stack_node.data; eventfd_node.base.handle = next_tick_node.data; switch (builtin.os.tag) { - .macosx, .freebsd, .netbsd, .dragonfly => { + .macos, .freebsd, .netbsd, .dragonfly => { const kevent_array = @as(*const [1]os.Kevent, &eventfd_node.kevent); const empty_kevs = &[0]os.Kevent{}; _ = os.kevent(self.os_data.kqfd, kevent_array, empty_kevs, null) catch { @@ -633,7 +633,7 @@ pub const Loop = struct { if (!builtin.single_threaded) { switch (builtin.os.tag) { .linux, - .macosx, + .macos, .freebsd, .netbsd, .dragonfly, @@ -725,7 +725,7 @@ pub const Loop = struct { } return; }, - .macosx, .freebsd, .netbsd, .dragonfly => { + .macos, .freebsd, .netbsd, .dragonfly => { const final_kevent = @as(*const [1]os.Kevent, &self.os_data.final_kevent); const empty_kevs = &[0]os.Kevent{}; // cannot fail because we already added it and this just enables it @@ -1218,7 +1218,7 @@ pub const Loop = struct { } } }, - .macosx, .freebsd, .netbsd, .dragonfly => { + .macos, .freebsd, .netbsd, .dragonfly => { var eventlist: [1]os.Kevent = undefined; const empty_kevs = &[0]os.Kevent{}; const count = os.kevent(self.os_data.kqfd, empty_kevs, eventlist[0..], null) catch unreachable; @@ -1344,7 +1344,7 @@ pub const Loop = struct { const OsData = switch (builtin.os.tag) { .linux => LinuxOsData, - .macosx, .freebsd, .netbsd, .dragonfly => KEventData, + .macos, .freebsd, .netbsd, .dragonfly => KEventData, .windows => struct { io_port: windows.HANDLE, extra_thread_count: usize, diff --git a/lib/std/fs.zig b/lib/std/fs.zig index 22d243612a..6fd188e1f9 100644 --- a/lib/std/fs.zig +++ b/lib/std/fs.zig @@ -39,7 +39,7 @@ pub const Watch = @import("fs/watch.zig").Watch; /// fit into a UTF-8 encoded array of this length. /// The byte count includes room for a null sentinel byte. pub const MAX_PATH_BYTES = switch (builtin.os.tag) { - .linux, .macosx, .ios, .freebsd, .netbsd, .dragonfly => os.PATH_MAX, + .linux, .macos, .ios, .freebsd, .netbsd, .dragonfly => os.PATH_MAX, // Each UTF-16LE character may be expanded to 3 UTF-8 bytes. // If it would require 4 UTF-8 bytes, then there would be a surrogate // pair in the UTF-16LE, and we (over)account 3 bytes for it that way. @@ -303,7 +303,7 @@ pub const Dir = struct { const IteratorError = error{AccessDenied} || os.UnexpectedError; pub const Iterator = switch (builtin.os.tag) { - .macosx, .ios, .freebsd, .netbsd, .dragonfly => struct { + .macos, .ios, .freebsd, .netbsd, .dragonfly => struct { dir: Dir, seek: i64, buf: [8192]u8, // TODO align(@alignOf(os.dirent)), @@ -318,7 +318,7 @@ pub const Dir = struct { /// with subsequent calls to `next`, as well as when this `Dir` is deinitialized. pub fn next(self: *Self) Error!?Entry { switch (builtin.os.tag) { - .macosx, .ios => return self.nextDarwin(), + .macos, .ios => return self.nextDarwin(), .freebsd, .netbsd, .dragonfly => return self.nextBsd(), else => @compileError("unimplemented"), } @@ -615,7 +615,7 @@ pub const Dir = struct { pub fn iterate(self: Dir) Iterator { switch (builtin.os.tag) { - .macosx, .ios, .freebsd, .netbsd, .dragonfly => return Iterator{ + .macos, .ios, .freebsd, .netbsd, .dragonfly => return Iterator{ .dir = self, .seek = 0, .index = 0, @@ -1302,7 +1302,7 @@ pub const Dir = struct { error.AccessDenied => |e| switch (builtin.os.tag) { // non-Linux POSIX systems return EPERM when trying to delete a directory, so // we need to handle that case specifically and translate the error - .macosx, .ios, .freebsd, .netbsd, .dragonfly => { + .macos, .ios, .freebsd, .netbsd, .dragonfly => { // Don't follow symlinks to match unlinkat (which acts on symlinks rather than follows them) const fstat = os.fstatatZ(self.fd, sub_path_c, os.AT_SYMLINK_NOFOLLOW) catch return e; const is_dir = fstat.mode & os.S_IFMT == os.S_IFDIR; @@ -1490,6 +1490,19 @@ pub const Dir = struct { return os.windows.ReadLink(self.fd, sub_path_w, buffer); } + /// Read all of file contents using a preallocated buffer. + /// The returned slice has the same pointer as `buffer`. If the length matches `buffer.len` + /// the situation is ambiguous. It could either mean that the entire file was read, and + /// it exactly fits the buffer, or it could mean the buffer was not big enough for the + /// entire file. + pub fn readFile(self: Dir, file_path: []const u8, buffer: []u8) ![]u8 { + var file = try self.openFile(file_path, .{}); + defer file.close(); + + const end_index = try file.readAll(buffer); + return buffer[0..end_index]; + } + /// On success, caller owns returned buffer. /// If the file is larger than `max_bytes`, returns `error.FileTooBig`. pub fn readFileAlloc(self: Dir, allocator: *mem.Allocator, file_path: []const u8, max_bytes: usize) ![]u8 { @@ -1823,7 +1836,7 @@ pub const Dir = struct { var atomic_file = try dest_dir.atomicFile(dest_path, .{ .mode = mode }); defer atomic_file.deinit(); - try atomic_file.file.writeFileAll(in_file, .{ .in_len = size }); + try copy_file(in_file.handle, atomic_file.file.handle); return atomic_file.finish(); } @@ -2271,6 +2284,53 @@ pub fn realpathAlloc(allocator: *Allocator, pathname: []const u8) ![]u8 { return allocator.dupe(u8, try os.realpath(pathname, &buf)); } +const CopyFileError = error{SystemResources} || os.CopyFileRangeError || os.SendFileError; + +// Transfer all the data between two file descriptors in the most efficient way. +// The copy starts at offset 0, the initial offsets are preserved. +// No metadata is transferred over. +fn copy_file(fd_in: os.fd_t, fd_out: os.fd_t) CopyFileError!void { + if (comptime std.Target.current.isDarwin()) { + const rc = os.system.fcopyfile(fd_in, fd_out, null, os.system.COPYFILE_DATA); + switch (os.errno(rc)) { + 0 => return, + os.EINVAL => unreachable, + os.ENOMEM => return error.SystemResources, + // The source file is not a directory, symbolic link, or regular file. + // Try with the fallback path before giving up. + os.ENOTSUP => {}, + else => |err| return os.unexpectedErrno(err), + } + } + + if (std.Target.current.os.tag == .linux) { + // Try copy_file_range first as that works at the FS level and is the + // most efficient method (if available). + var offset: u64 = 0; + cfr_loop: while (true) { + // The kernel checks the u64 value `offset+count` for overflow, use + // a 32 bit value so that the syscall won't return EINVAL except for + // impossibly large files (> 2^64-1 - 2^32-1). + const amt = try os.copy_file_range(fd_in, offset, fd_out, offset, math.maxInt(u32), 0); + // Terminate when no data was copied + if (amt == 0) break :cfr_loop; + offset += amt; + } + return; + } + + // Sendfile is a zero-copy mechanism iff the OS supports it, otherwise the + // fallback code will copy the contents chunk by chunk. + const empty_iovec = [0]os.iovec_const{}; + var offset: u64 = 0; + sendfile_loop: while (true) { + const amt = try os.sendfile(fd_out, fd_in, offset, 0, &empty_iovec, &empty_iovec, 0); + // Terminate when no data was copied + if (amt == 0) break :sendfile_loop; + offset += amt; + } +} + test "" { if (builtin.os.tag != .wasi) { _ = makeDirAbsolute; diff --git a/lib/std/fs/get_app_data_dir.zig b/lib/std/fs/get_app_data_dir.zig index 17d365f684..6f8dae07c5 100644 --- a/lib/std/fs/get_app_data_dir.zig +++ b/lib/std/fs/get_app_data_dir.zig @@ -42,7 +42,7 @@ pub fn getAppDataDir(allocator: *mem.Allocator, appname: []const u8) GetAppDataD else => return error.AppDataDirUnavailable, } }, - .macosx => { + .macos => { const home_dir = os.getenv("HOME") orelse { // TODO look in /etc/passwd return error.AppDataDirUnavailable; diff --git a/lib/std/fs/test.zig b/lib/std/fs/test.zig index 60cd380444..860da8ef16 100644 --- a/lib/std/fs/test.zig +++ b/lib/std/fs/test.zig @@ -143,7 +143,7 @@ fn contains(entries: *const std.ArrayList(Dir.Entry), el: Dir.Entry) bool { test "Dir.realpath smoke test" { switch (builtin.os.tag) { - .linux, .windows, .macosx, .ios, .watchos, .tvos => {}, + .linux, .windows, .macos, .ios, .watchos, .tvos => {}, else => return error.SkipZigTest, } diff --git a/lib/std/fs/watch.zig b/lib/std/fs/watch.zig index 269cdec6d8..8b709c2d4b 100644 --- a/lib/std/fs/watch.zig +++ b/lib/std/fs/watch.zig @@ -49,7 +49,7 @@ pub fn Watch(comptime V: type) type { const OsData = switch (builtin.os.tag) { // TODO https://github.com/ziglang/zig/issues/3778 - .macosx, .freebsd, .netbsd, .dragonfly => KqOsData, + .macos, .freebsd, .netbsd, .dragonfly => KqOsData, .linux => LinuxOsData, .windows => WindowsOsData, @@ -160,7 +160,7 @@ pub fn Watch(comptime V: type) type { return self; }, - .macosx, .freebsd, .netbsd, .dragonfly => { + .macos, .freebsd, .netbsd, .dragonfly => { self.* = Self{ .allocator = allocator, .channel = channel, @@ -178,7 +178,7 @@ pub fn Watch(comptime V: type) type { /// All addFile calls and removeFile calls must have completed. pub fn deinit(self: *Self) void { switch (builtin.os.tag) { - .macosx, .freebsd, .netbsd, .dragonfly => { + .macos, .freebsd, .netbsd, .dragonfly => { // TODO we need to cancel the frames before destroying the lock self.os_data.table_lock.deinit(); var it = self.os_data.file_table.iterator(); @@ -229,7 +229,7 @@ pub fn Watch(comptime V: type) type { pub fn addFile(self: *Self, file_path: []const u8, value: V) !?V { switch (builtin.os.tag) { - .macosx, .freebsd, .netbsd, .dragonfly => return addFileKEvent(self, file_path, value), + .macos, .freebsd, .netbsd, .dragonfly => return addFileKEvent(self, file_path, value), .linux => return addFileLinux(self, file_path, value), .windows => return addFileWindows(self, file_path, value), else => @compileError("Unsupported OS"), diff --git a/lib/std/math/big/int.zig b/lib/std/math/big/int.zig index 54ad2f55d0..25cafda9ac 100644 --- a/lib/std/math/big/int.zig +++ b/lib/std/math/big/int.zig @@ -446,6 +446,26 @@ pub const Mutable = struct { rma.positive = (a.positive == b.positive); } + /// rma = a * a + /// + /// `rma` may not alias with `a`. + /// + /// Asserts the result fits in `rma`. An upper bound on the number of limbs needed by + /// rma is given by `2 * a.limbs.len + 1`. + /// + /// If `allocator` is provided, it will be used for temporary storage to improve + /// multiplication performance. `error.OutOfMemory` is handled with a fallback algorithm. + pub fn sqrNoAlias(rma: *Mutable, a: Const, opt_allocator: ?*Allocator) void { + assert(rma.limbs.ptr != a.limbs.ptr); // illegal aliasing + + mem.set(Limb, rma.limbs, 0); + + llsquare_basecase(rma.limbs, a.limbs); + + rma.normalize(2 * a.limbs.len + 1); + rma.positive = true; + } + /// q = a / b (rem r) /// /// a / b are floored (rounded towards 0). @@ -1827,7 +1847,28 @@ pub const Managed = struct { rma.setMetadata(m.positive, m.len); } - pub fn pow(rma: *Managed, a: Managed, b: u32) !void { + /// r = a * a + pub fn sqr(rma: *Managed, a: Const) !void { + const needed_limbs = 2 * a.limbs.len + 1; + + if (rma.limbs.ptr == a.limbs.ptr) { + var m = try Managed.initCapacity(rma.allocator, needed_limbs); + errdefer m.deinit(); + var m_mut = m.toMutable(); + m_mut.sqrNoAlias(a, rma.allocator); + m.setMetadata(m_mut.positive, m_mut.len); + + rma.deinit(); + rma.swap(&m); + } else { + try rma.ensureCapacity(needed_limbs); + var rma_mut = rma.toMutable(); + rma_mut.sqrNoAlias(a, rma.allocator); + rma.setMetadata(rma_mut.positive, rma_mut.len); + } + } + + pub fn pow(rma: *Managed, a: Const, b: u32) !void { const needed_limbs = calcPowLimbsBufferLen(a.bitCountAbs(), b); const limbs_buffer = try rma.allocator.alloc(Limb, needed_limbs); @@ -1837,7 +1878,7 @@ pub const Managed = struct { var m = try Managed.initCapacity(rma.allocator, needed_limbs); errdefer m.deinit(); var m_mut = m.toMutable(); - try m_mut.pow(a.toConst(), b, limbs_buffer); + try m_mut.pow(a, b, limbs_buffer); m.setMetadata(m_mut.positive, m_mut.len); rma.deinit(); @@ -1845,7 +1886,7 @@ pub const Managed = struct { } else { try rma.ensureCapacity(needed_limbs); var rma_mut = rma.toMutable(); - try rma_mut.pow(a.toConst(), b, limbs_buffer); + try rma_mut.pow(a, b, limbs_buffer); rma.setMetadata(rma_mut.positive, rma_mut.len); } } @@ -1869,11 +1910,14 @@ fn llmulacc(opt_allocator: ?*Allocator, r: []Limb, a: []const Limb, b: []const L assert(r.len >= x.len + y.len + 1); // 48 is a pretty abitrary size chosen based on performance of a factorial program. - if (x.len > 48) { - if (opt_allocator) |allocator| { - llmulacc_karatsuba(allocator, r, x, y) catch |err| switch (err) { - error.OutOfMemory => {}, // handled below - }; + k_mul: { + if (x.len > 48) { + if (opt_allocator) |allocator| { + llmulacc_karatsuba(allocator, r, x, y) catch |err| switch (err) { + error.OutOfMemory => break :k_mul, // handled below + }; + return; + } } } @@ -2203,6 +2247,42 @@ fn llxor(r: []Limb, a: []const Limb, b: []const Limb) void { } } +/// r MUST NOT alias x. +fn llsquare_basecase(r: []Limb, x: []const Limb) void { + @setRuntimeSafety(debug_safety); + + const x_norm = x; + assert(r.len >= 2 * x_norm.len + 1); + + // Compute the square of a N-limb bigint with only (N^2 + N)/2 + // multiplications by exploting the symmetry of the coefficients around the + // diagonal: + // + // a b c * + // a b c = + // ------------------- + // ca cb cc + + // ba bb bc + + // aa ab ac + // + // Note that: + // - Each mixed-product term appears twice for each column, + // - Squares are always in the 2k (0 <= k < N) column + + for (x_norm) |v, i| { + // Accumulate all the x[i]*x[j] (with x!=j) products + llmulDigit(r[2 * i + 1 ..], x_norm[i + 1 ..], v); + } + + // Each product appears twice, multiply by 2 + llshl(r, r[0 .. 2 * x_norm.len], 1); + + for (x_norm) |v, i| { + // Compute and add the squares + llmulDigit(r[2 * i ..], x[i .. i + 1], v); + } +} + /// Knuth 4.6.3 fn llpow(r: []Limb, a: []const Limb, b: u32, tmp_limbs: []Limb) void { var tmp1: []Limb = undefined; @@ -2212,9 +2292,9 @@ fn llpow(r: []Limb, a: []const Limb, b: u32, tmp_limbs: []Limb) void { // variable, use the output limbs and another temporary set to overcome this // limitation. // The initial assignment makes the result end in `r` so an extra memory - // copy is saved, each 1 flips the index twice so it's a no-op so count the - // 0. - const b_leading_zeros = @intCast(u5, @clz(u32, b)); + // copy is saved, each 1 flips the index twice so it's only the zeros that + // matter. + const b_leading_zeros = @clz(u32, b); const exp_zeros = @popCount(u32, ~b) - b_leading_zeros; if (exp_zeros & 1 != 0) { tmp1 = tmp_limbs; @@ -2224,32 +2304,28 @@ fn llpow(r: []Limb, a: []const Limb, b: u32, tmp_limbs: []Limb) void { tmp2 = tmp_limbs; } - const a_norm = a[0..llnormalize(a)]; - - mem.copy(Limb, tmp1, a_norm); - mem.set(Limb, tmp1[a_norm.len..], 0); + mem.copy(Limb, tmp1, a); + mem.set(Limb, tmp1[a.len..], 0); // Scan the exponent as a binary number, from left to right, dropping the // most significant bit set. - const exp_bits = @intCast(u5, 31 - b_leading_zeros); - var exp = @bitReverse(u32, b) >> 1 + b_leading_zeros; + // Square the result if the current bit is zero, square and multiply by a if + // it is one. + var exp_bits = 32 - 1 - b_leading_zeros; + var exp = b << @intCast(u5, 1 + b_leading_zeros); - var i: u5 = 0; + var i: usize = 0; while (i < exp_bits) : (i += 1) { // Square - { - mem.set(Limb, tmp2, 0); - const op = tmp1[0..llnormalize(tmp1)]; - llmulacc(null, tmp2, op, op); - mem.swap([]Limb, &tmp1, &tmp2); - } + mem.set(Limb, tmp2, 0); + llsquare_basecase(tmp2, tmp1[0..llnormalize(tmp1)]); + mem.swap([]Limb, &tmp1, &tmp2); // Multiply by a - if (exp & 1 != 0) { + if (@shlWithOverflow(u32, exp, 1, &exp)) { mem.set(Limb, tmp2, 0); - llmulacc(null, tmp2, tmp1[0..llnormalize(tmp1)], a_norm); + llmulacc(null, tmp2, tmp1[0..llnormalize(tmp1)], a); mem.swap([]Limb, &tmp1, &tmp2); } - exp >>= 1; } } diff --git a/lib/std/math/big/int_test.zig b/lib/std/math/big/int_test.zig index 5d07bee9b5..1f4bd65974 100644 --- a/lib/std/math/big/int_test.zig +++ b/lib/std/math/big/int_test.zig @@ -720,6 +720,27 @@ test "big.int mul 0*0" { testing.expect((try c.to(u32)) == 0); } +test "big.int mul large" { + var a = try Managed.initCapacity(testing.allocator, 50); + defer a.deinit(); + var b = try Managed.initCapacity(testing.allocator, 100); + defer b.deinit(); + var c = try Managed.initCapacity(testing.allocator, 100); + defer c.deinit(); + + // Generate a number that's large enough to cross the thresholds for the use + // of subquadratic algorithms + for (a.limbs) |*p| { + p.* = std.math.maxInt(Limb); + } + a.setMetadata(true, 50); + + try b.mul(a.toConst(), a.toConst()); + try c.sqr(a.toConst()); + + testing.expect(b.eq(c)); +} + test "big.int div single-single no rem" { var a = try Managed.initSet(testing.allocator, 50); defer a.deinit(); @@ -1483,11 +1504,14 @@ test "big.int const to managed" { test "big.int pow" { { - var a = try Managed.initSet(testing.allocator, 10); + var a = try Managed.initSet(testing.allocator, -3); defer a.deinit(); - try a.pow(a, 8); - testing.expectEqual(@as(u32, 100000000), try a.to(u32)); + try a.pow(a.toConst(), 3); + testing.expectEqual(@as(i32, -27), try a.to(i32)); + + try a.pow(a.toConst(), 4); + testing.expectEqual(@as(i32, 531441), try a.to(i32)); } { var a = try Managed.initSet(testing.allocator, 10); @@ -1497,9 +1521,9 @@ test "big.int pow" { defer y.deinit(); // y and a are not aliased - try y.pow(a, 123); + try y.pow(a.toConst(), 123); // y and a are aliased - try a.pow(a, 123); + try a.pow(a.toConst(), 123); testing.expect(a.eq(y)); @@ -1517,18 +1541,18 @@ test "big.int pow" { var a = try Managed.initSet(testing.allocator, 0); defer a.deinit(); - try a.pow(a, 100); + try a.pow(a.toConst(), 100); testing.expectEqual(@as(i32, 0), try a.to(i32)); try a.set(1); - try a.pow(a, 0); + try a.pow(a.toConst(), 0); testing.expectEqual(@as(i32, 1), try a.to(i32)); - try a.pow(a, 100); + try a.pow(a.toConst(), 100); testing.expectEqual(@as(i32, 1), try a.to(i32)); try a.set(-1); - try a.pow(a, 15); + try a.pow(a.toConst(), 15); testing.expectEqual(@as(i32, -1), try a.to(i32)); - try a.pow(a, 16); + try a.pow(a.toConst(), 16); testing.expectEqual(@as(i32, 1), try a.to(i32)); } } diff --git a/lib/std/os.zig b/lib/std/os.zig index c89f122c63..bc7dae031c 100644 --- a/lib/std/os.zig +++ b/lib/std/os.zig @@ -62,7 +62,7 @@ pub const system = if (@hasDecl(root, "os") and root.os != @This()) else if (builtin.link_libc) std.c else switch (builtin.os.tag) { - .macosx, .ios, .watchos, .tvos => darwin, + .macos, .ios, .watchos, .tvos => darwin, .freebsd => freebsd, .linux => linux, .netbsd => netbsd, @@ -354,7 +354,7 @@ pub fn read(fd: fd_t, buf: []u8) ReadError!usize { // Prevents EINVAL. const max_count = switch (std.Target.current.os.tag) { .linux => 0x7ffff000, - .macosx, .ios, .watchos, .tvos => math.maxInt(i32), + .macos, .ios, .watchos, .tvos => math.maxInt(i32), else => math.maxInt(isize), }; const adjusted_len = math.min(max_count, buf.len); @@ -582,7 +582,7 @@ pub fn ftruncate(fd: fd_t, length: u64) TruncateError!void { /// On these systems, the read races with concurrent writes to the same file descriptor. pub fn preadv(fd: fd_t, iov: []const iovec, offset: u64) PReadError!usize { const have_pread_but_not_preadv = switch (std.Target.current.os.tag) { - .windows, .macosx, .ios, .watchos, .tvos => true, + .windows, .macos, .ios, .watchos, .tvos => true, else => false, }; if (have_pread_but_not_preadv) { @@ -709,7 +709,7 @@ pub fn write(fd: fd_t, bytes: []const u8) WriteError!usize { const max_count = switch (std.Target.current.os.tag) { .linux => 0x7ffff000, - .macosx, .ios, .watchos, .tvos => math.maxInt(i32), + .macos, .ios, .watchos, .tvos => math.maxInt(i32), else => math.maxInt(isize), }; const adjusted_len = math.min(max_count, bytes.len); @@ -863,7 +863,7 @@ pub fn pwrite(fd: fd_t, bytes: []const u8, offset: u64) PWriteError!usize { // Prevent EINVAL. const max_count = switch (std.Target.current.os.tag) { .linux => 0x7ffff000, - .macosx, .ios, .watchos, .tvos => math.maxInt(i32), + .macos, .ios, .watchos, .tvos => math.maxInt(i32), else => math.maxInt(isize), }; const adjusted_len = math.min(max_count, bytes.len); @@ -915,7 +915,7 @@ pub fn pwrite(fd: fd_t, bytes: []const u8, offset: u64) PWriteError!usize { /// If `iov.len` is larger than will fit in a `u31`, a partial write will occur. pub fn pwritev(fd: fd_t, iov: []const iovec_const, offset: u64) PWriteError!usize { const have_pwrite_but_not_pwritev = switch (std.Target.current.os.tag) { - .windows, .macosx, .ios, .watchos, .tvos => true, + .windows, .macos, .ios, .watchos, .tvos => true, else => false, }; @@ -4091,7 +4091,7 @@ pub fn getFdPath(fd: fd_t, out_buffer: *[MAX_PATH_BYTES]u8) RealPathError![]u8 { const end_index = std.unicode.utf16leToUtf8(out_buffer, wide_slice) catch unreachable; return out_buffer[0..end_index]; }, - .macosx, .ios, .watchos, .tvos => { + .macos, .ios, .watchos, .tvos => { // On macOS, we can use F_GETPATH fcntl command to query the OS for // the path to the file descriptor. @memset(out_buffer, 0, MAX_PATH_BYTES); @@ -4688,7 +4688,7 @@ pub fn sendfile( }); const max_count = switch (std.Target.current.os.tag) { .linux => 0x7ffff000, - .macosx, .ios, .watchos, .tvos => math.maxInt(i32), + .macos, .ios, .watchos, .tvos => math.maxInt(i32), else => math.maxInt(size_t), }; @@ -4846,7 +4846,7 @@ pub fn sendfile( } } }, - .macosx, .ios, .tvos, .watchos => sf: { + .macos, .ios, .tvos, .watchos => sf: { var hdtr_data: std.c.sf_hdtr = undefined; var hdtr: ?*std.c.sf_hdtr = null; if (headers.len != 0 or trailers.len != 0) { @@ -4945,6 +4945,9 @@ pub fn sendfile( pub const CopyFileRangeError = error{ FileTooBig, InputOutput, + /// `fd_in` is not open for reading; or `fd_out` is not open for writing; + /// or the `O_APPEND` flag is set for `fd_out`. + FilesOpenedWithWrongFlags, IsDir, OutOfMemory, NoSpaceLeft, @@ -4953,6 +4956,11 @@ pub const CopyFileRangeError = error{ FileBusy, } || PReadError || PWriteError || UnexpectedError; +var has_copy_file_range_syscall = init: { + const kernel_has_syscall = std.Target.current.os.isAtLeast(.linux, .{ .major = 4, .minor = 5 }) orelse true; + break :init std.atomic.Int(bool).init(kernel_has_syscall); +}; + /// Transfer data between file descriptors at specified offsets. /// Returns the number of bytes written, which can less than requested. /// @@ -4981,22 +4989,18 @@ pub const CopyFileRangeError = error{ pub fn copy_file_range(fd_in: fd_t, off_in: u64, fd_out: fd_t, off_out: u64, len: usize, flags: u32) CopyFileRangeError!usize { const use_c = std.c.versionCheck(.{ .major = 2, .minor = 27, .patch = 0 }).ok; - // TODO support for other systems than linux - const try_syscall = comptime std.Target.current.os.isAtLeast(.linux, .{ .major = 4, .minor = 5 }) != false; - - if (use_c or try_syscall) { + if (std.Target.current.os.tag == .linux and + (use_c or has_copy_file_range_syscall.get())) + { const sys = if (use_c) std.c else linux; var off_in_copy = @bitCast(i64, off_in); var off_out_copy = @bitCast(i64, off_out); const rc = sys.copy_file_range(fd_in, &off_in_copy, fd_out, &off_out_copy, len, flags); - - // TODO avoid wasting a syscall every time if kernel is too old and returns ENOSYS https://github.com/ziglang/zig/issues/1018 - switch (sys.getErrno(rc)) { 0 => return @intCast(usize, rc), - EBADF => unreachable, + EBADF => return error.FilesOpenedWithWrongFlags, EFBIG => return error.FileTooBig, EIO => return error.InputOutput, EISDIR => return error.IsDir, @@ -5005,9 +5009,14 @@ pub fn copy_file_range(fd_in: fd_t, off_in: u64, fd_out: fd_t, off_out: u64, len EOVERFLOW => return error.Unseekable, EPERM => return error.PermissionDenied, ETXTBSY => return error.FileBusy, - EINVAL => {}, // these may not be regular files, try fallback - EXDEV => {}, // support for cross-filesystem copy added in Linux 5.3, use fallback - ENOSYS => {}, // syscall added in Linux 4.5, use fallback + // these may not be regular files, try fallback + EINVAL => {}, + // support for cross-filesystem copy added in Linux 5.3, use fallback + EXDEV => {}, + // syscall added in Linux 4.5, use fallback + ENOSYS => { + has_copy_file_range_syscall.set(false); + }, else => |err| return unexpectedErrno(err), } } diff --git a/lib/std/os/bits.zig b/lib/std/os/bits.zig index 177b7daad2..3647f67f2a 100644 --- a/lib/std/os/bits.zig +++ b/lib/std/os/bits.zig @@ -12,7 +12,7 @@ const std = @import("std"); const root = @import("root"); pub usingnamespace switch (std.Target.current.os.tag) { - .macosx, .ios, .tvos, .watchos => @import("bits/darwin.zig"), + .macos, .ios, .tvos, .watchos => @import("bits/darwin.zig"), .dragonfly => @import("bits/dragonfly.zig"), .freebsd => @import("bits/freebsd.zig"), .linux => @import("bits/linux.zig"), diff --git a/lib/std/os/test.zig b/lib/std/os/test.zig index aee1b9549a..58c2b311b1 100644 --- a/lib/std/os/test.zig +++ b/lib/std/os/test.zig @@ -360,7 +360,7 @@ fn iter_fn(info: *dl_phdr_info, size: usize, counter: *usize) IterFnError!void { } test "dl_iterate_phdr" { - if (builtin.os.tag == .windows or builtin.os.tag == .wasi or builtin.os.tag == .macosx) + if (builtin.os.tag == .windows or builtin.os.tag == .wasi or builtin.os.tag == .macos) return error.SkipZigTest; var counter: usize = 0; diff --git a/lib/std/packed_int_array.zig b/lib/std/packed_int_array.zig index f25ff0b1b8..2f2b671692 100644 --- a/lib/std/packed_int_array.zig +++ b/lib/std/packed_int_array.zig @@ -618,7 +618,7 @@ test "PackedIntArray at end of available memory" { if (we_are_testing_this_with_stage1_which_leaks_comptime_memory) return error.SkipZigTest; switch (builtin.os.tag) { - .linux, .macosx, .ios, .freebsd, .netbsd, .windows => {}, + .linux, .macos, .ios, .freebsd, .netbsd, .windows => {}, else => return, } const PackedArray = PackedIntArray(u3, 8); @@ -639,7 +639,7 @@ test "PackedIntSlice at end of available memory" { if (we_are_testing_this_with_stage1_which_leaks_comptime_memory) return error.SkipZigTest; switch (builtin.os.tag) { - .linux, .macosx, .ios, .freebsd, .netbsd, .windows => {}, + .linux, .macos, .ios, .freebsd, .netbsd, .windows => {}, else => return, } const PackedSlice = PackedIntSlice(u11); diff --git a/lib/std/process.zig b/lib/std/process.zig index 2813d8cbab..3a499f3e24 100644 --- a/lib/std/process.zig +++ b/lib/std/process.zig @@ -585,7 +585,7 @@ pub const UserInfo = struct { /// POSIX function which gets a uid from username. pub fn getUserInfo(name: []const u8) !UserInfo { return switch (builtin.os.tag) { - .linux, .macosx, .watchos, .tvos, .ios, .freebsd, .netbsd => posixGetUserInfo(name), + .linux, .macos, .watchos, .tvos, .ios, .freebsd, .netbsd => posixGetUserInfo(name), else => @compileError("Unsupported OS"), }; } @@ -688,7 +688,7 @@ pub fn getBaseAddress() usize { const phdr = os.system.getauxval(std.elf.AT_PHDR); return phdr - @sizeOf(std.elf.Ehdr); }, - .macosx, .freebsd, .netbsd => { + .macos, .freebsd, .netbsd => { return @ptrToInt(&std.c._mh_execute_header); }, .windows => return @ptrToInt(os.windows.kernel32.GetModuleHandleW(null)), @@ -733,7 +733,7 @@ pub fn getSelfExeSharedLibPaths(allocator: *Allocator) error{OutOfMemory}![][:0] }.callback); return paths.toOwnedSlice(); }, - .macosx, .ios, .watchos, .tvos => { + .macos, .ios, .watchos, .tvos => { var paths = List.init(allocator); errdefer { const slice = paths.toOwnedSlice(); diff --git a/lib/std/special/compiler_rt/clear_cache.zig b/lib/std/special/compiler_rt/clear_cache.zig index d862f739d3..4b00721868 100644 --- a/lib/std/special/compiler_rt/clear_cache.zig +++ b/lib/std/special/compiler_rt/clear_cache.zig @@ -44,7 +44,7 @@ pub fn clear_cache(start: usize, end: usize) callconv(.C) void { else => false, }; const apple = switch (os) { - .ios, .macosx, .watchos, .tvos => true, + .ios, .macos, .watchos, .tvos => true, else => false, }; if (x86) { diff --git a/lib/std/target.zig b/lib/std/target.zig index d39b3874c4..86aabf4282 100644 --- a/lib/std/target.zig +++ b/lib/std/target.zig @@ -31,7 +31,7 @@ pub const Target = struct { kfreebsd, linux, lv2, - macosx, + macos, netbsd, openbsd, solaris, @@ -61,7 +61,7 @@ pub const Target = struct { pub fn isDarwin(tag: Tag) bool { return switch (tag) { - .ios, .macosx, .watchos, .tvos => true, + .ios, .macos, .watchos, .tvos => true, else => false, }; } @@ -234,7 +234,7 @@ pub const Target = struct { .max = .{ .major = 12, .minor = 1 }, }, }, - .macosx => return .{ + .macos => return .{ .semver = .{ .min = .{ .major = 10, .minor = 13 }, .max = .{ .major = 10, .minor = 15, .patch = 3 }, @@ -312,7 +312,7 @@ pub const Target = struct { .windows => return TaggedVersionRange{ .windows = self.version_range.windows }, .freebsd, - .macosx, + .macos, .ios, .tvos, .watchos, @@ -341,7 +341,7 @@ pub const Target = struct { return switch (os.tag) { .freebsd, .netbsd, - .macosx, + .macos, .ios, .tvos, .watchos, @@ -450,7 +450,7 @@ pub const Target = struct { .other, => return .eabi, .openbsd, - .macosx, + .macos, .freebsd, .ios, .tvos, @@ -1277,7 +1277,7 @@ pub const Target = struct { .ios, .tvos, .watchos, - .macosx, + .macos, .uefi, .windows, .emscripten, @@ -1450,7 +1450,7 @@ pub const Target = struct { .ios, .tvos, .watchos, - .macosx, + .macos, .uefi, .windows, .emscripten, diff --git a/lib/std/time.zig b/lib/std/time.zig index ae128f6b0c..344f192784 100644 --- a/lib/std/time.zig +++ b/lib/std/time.zig @@ -143,7 +143,7 @@ pub const Timer = struct { /// be less precise frequency: switch (builtin.os.tag) { .windows => u64, - .macosx, .ios, .tvos, .watchos => os.darwin.mach_timebase_info_data, + .macos, .ios, .tvos, .watchos => os.darwin.mach_timebase_info_data, else => void, }, resolution: u64, diff --git a/lib/std/zig/cross_target.zig b/lib/std/zig/cross_target.zig index f1ae3457a5..3d39c8338b 100644 --- a/lib/std/zig/cross_target.zig +++ b/lib/std/zig/cross_target.zig @@ -137,7 +137,7 @@ pub const CrossTarget = struct { }, .freebsd, - .macosx, + .macos, .ios, .tvos, .watchos, @@ -578,7 +578,7 @@ pub const CrossTarget = struct { const os = switch (self.getOsTag()) { .windows => "windows", .linux => "linux", - .macosx => "macos", + .macos => "macos", else => return error.UnsupportedVcpkgOperatingSystem, }; @@ -718,7 +718,7 @@ pub const CrossTarget = struct { => return error.InvalidOperatingSystemVersion, .freebsd, - .macosx, + .macos, .ios, .tvos, .watchos, diff --git a/lib/std/zig/system.zig b/lib/std/zig/system.zig index b1947058cc..33ef137eb6 100644 --- a/lib/std/zig/system.zig +++ b/lib/std/zig/system.zig @@ -267,7 +267,7 @@ pub const NativeTargetInfo = struct { os.version_range.windows.max = @intToEnum(Target.Os.WindowsVersion, version); os.version_range.windows.min = @intToEnum(Target.Os.WindowsVersion, version); }, - .macosx => { + .macos => { var scbuf: [32]u8 = undefined; var size: usize = undefined; diff --git a/src/Cache.zig b/src/Cache.zig index dff6f7e38e..1adf72d16f 100644 --- a/src/Cache.zig +++ b/src/Cache.zig @@ -576,6 +576,30 @@ pub const Manifest = struct { } }; +/// On operating systems that support symlinks, does a readlink. On other operating systems, +/// uses the file contents. Windows supports symlinks but only with elevated privileges, so +/// it is treated as not supporting symlinks. +pub fn readSmallFile(dir: fs.Dir, sub_path: []const u8, buffer: []u8) ![]u8 { + if (std.Target.current.os.tag == .windows) { + return dir.readFile(sub_path, buffer); + } else { + return dir.readLink(sub_path, buffer); + } +} + +/// On operating systems that support symlinks, does a symlink. On other operating systems, +/// uses the file contents. Windows supports symlinks but only with elevated privileges, so +/// it is treated as not supporting symlinks. +/// `data` must be a valid UTF-8 encoded file path and 255 bytes or fewer. +pub fn writeSmallFile(dir: fs.Dir, sub_path: []const u8, data: []const u8) !void { + assert(data.len <= 255); + if (std.Target.current.os.tag == .windows) { + return dir.writeFile(sub_path, data); + } else { + return dir.symLink(data, sub_path, .{}); + } +} + fn hashFile(file: fs.File, bin_digest: []u8) !void { var buf: [1024]u8 = undefined; diff --git a/src/Compilation.zig b/src/Compilation.zig index 3e74df35af..1fddda42f9 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -922,7 +922,10 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation { try comp.work_queue.writeItem(.libcxx); try comp.work_queue.writeItem(.libcxxabi); } - if (is_exe_or_dyn_lib and build_options.is_stage1) { + + const needs_compiler_rt_and_c = is_exe_or_dyn_lib or + (comp.getTarget().isWasm() and comp.bin_file.options.output_mode != .Obj); + if (needs_compiler_rt_and_c and build_options.is_stage1) { try comp.work_queue.writeItem(.{ .libcompiler_rt = {} }); if (!comp.bin_file.options.link_libc) { try comp.work_queue.writeItem(.{ .zig_libc = {} }); @@ -2602,8 +2605,12 @@ fn updateStage1Module(comp: *Compilation, main_progress_node: *std.Progress.Node // We use an extra hex-encoded byte here to store some flags. var prev_digest_buf: [digest.len + 2]u8 = undefined; - const prev_digest: []u8 = directory.handle.readLink(id_symlink_basename, &prev_digest_buf) catch |err| blk: { - log.debug("stage1 {} new_digest={} readlink error: {}", .{ mod.root_pkg.root_src_path, digest, @errorName(err) }); + const prev_digest: []u8 = Cache.readSmallFile( + directory.handle, + id_symlink_basename, + &prev_digest_buf, + ) catch |err| blk: { + log.debug("stage1 {} new_digest={} error: {}", .{ mod.root_pkg.root_src_path, digest, @errorName(err) }); // Handle this as a cache miss. break :blk prev_digest_buf[0..0]; }; @@ -2774,7 +2781,7 @@ fn updateStage1Module(comp: *Compilation, main_progress_node: *std.Progress.Node const digest = man.final(); - // Update the dangling symlink with the digest. If it fails we can continue; it only + // Update the small file with the digest. If it fails we can continue; it only // means that the next invocation will have an unnecessary cache miss. const stage1_flags_byte = @bitCast(u8, mod.stage1_flags); log.debug("stage1 {} final digest={} flags={x}", .{ @@ -2789,10 +2796,10 @@ fn updateStage1Module(comp: *Compilation, main_progress_node: *std.Progress.Node log.debug("saved digest + flags: '{s}' (byte = {}) have_winmain_crt_startup={}", .{ digest_plus_flags, stage1_flags_byte, mod.stage1_flags.have_winmain_crt_startup, }); - directory.handle.symLink(&digest_plus_flags, id_symlink_basename, .{}) catch |err| { - log.warn("failed to save stage1 hash digest symlink: {}", .{@errorName(err)}); + Cache.writeSmallFile(directory.handle, id_symlink_basename, &digest_plus_flags) catch |err| { + log.warn("failed to save stage1 hash digest file: {}", .{@errorName(err)}); }; - // Again failure here only means an unnecessary cache miss. + // Failure here only means an unnecessary cache miss. man.writeManifest() catch |err| { log.warn("failed to write cache manifest when linking: {}", .{@errorName(err)}); }; diff --git a/src/Module.zig b/src/Module.zig index 75b6afffcd..0c2a38be28 100644 --- a/src/Module.zig +++ b/src/Module.zig @@ -93,7 +93,7 @@ pub const Export = struct { /// Byte offset into the file that contains the export directive. src: usize, /// Represents the position of the export, if any, in the output file. - link: link.File.Elf.Export, + link: link.File.Export, /// The Decl that performs the export. Note that this is *not* the Decl being exported. owner_decl: *Decl, /// The Decl being exported. Note this is *not* the Decl performing the export. @@ -1712,7 +1712,10 @@ fn deleteDeclExports(self: *Module, decl: *Decl) void { } } if (self.comp.bin_file.cast(link.File.Elf)) |elf| { - elf.deleteExport(exp.link); + elf.deleteExport(exp.link.elf); + } + if (self.comp.bin_file.cast(link.File.MachO)) |macho| { + macho.deleteExport(exp.link.macho); } if (self.failed_exports.remove(exp)) |entry| { entry.value.destroy(self.gpa); @@ -1875,7 +1878,13 @@ pub fn analyzeExport(self: *Module, scope: *Scope, src: usize, borrowed_symbol_n new_export.* = .{ .options = .{ .name = symbol_name }, .src = src, - .link = .{}, + .link = switch (self.comp.bin_file.tag) { + .coff => .{ .coff = {} }, + .elf => .{ .elf = link.File.Elf.Export{} }, + .macho => .{ .macho = link.File.MachO.Export{} }, + .c => .{ .c = {} }, + .wasm => .{ .wasm = {} }, + }, .owner_decl = owner_decl, .exported_decl = exported_decl, .status = .in_progress, diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 01fa0baf02..f87a7a940c 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -69,7 +69,7 @@ pub fn targetTriple(allocator: *Allocator, target: std.Target) ![]u8 { .kfreebsd => "kfreebsd", .linux => "linux", .lv2 => "lv2", - .macosx => "macosx", + .macos => "macosx", .netbsd => "netbsd", .openbsd => "openbsd", .solaris => "solaris", diff --git a/src/link.zig b/src/link.zig index 139977b3e2..b7b891b5b1 100644 --- a/src/link.zig +++ b/src/link.zig @@ -133,6 +133,14 @@ pub const File = struct { wasm: ?Wasm.FnData, }; + pub const Export = union { + elf: Elf.Export, + coff: void, + macho: MachO.Export, + c: void, + wasm: void, + }; + /// For DWARF .debug_info. pub const DbgInfoTypeRelocsTable = std.HashMapUnmanaged(Type, DbgInfoTypeReloc, Type.hash, Type.eql, std.hash_map.DefaultMaxLoadPercentage); @@ -458,8 +466,12 @@ pub const File = struct { const digest = ch.final(); var prev_digest_buf: [digest.len]u8 = undefined; - const prev_digest: []u8 = directory.handle.readLink(id_symlink_basename, &prev_digest_buf) catch |err| b: { - log.debug("archive new_digest={} readlink error: {}", .{ digest, @errorName(err) }); + const prev_digest: []u8 = Cache.readSmallFile( + directory.handle, + id_symlink_basename, + &prev_digest_buf, + ) catch |err| b: { + log.debug("archive new_digest={} readFile error: {}", .{ digest, @errorName(err) }); break :b prev_digest_buf[0..0]; }; if (mem.eql(u8, prev_digest, &digest)) { @@ -504,8 +516,8 @@ pub const File = struct { const bad = llvm.WriteArchive(full_out_path_z, object_files.items.ptr, object_files.items.len, os_type); if (bad) return error.UnableToWriteArchive; - directory.handle.symLink(&digest, id_symlink_basename, .{}) catch |err| { - std.log.warn("failed to save archive hash digest symlink: {}", .{@errorName(err)}); + Cache.writeSmallFile(directory.handle, id_symlink_basename, &digest) catch |err| { + std.log.warn("failed to save archive hash digest file: {}", .{@errorName(err)}); }; ch.writeManifest() catch |err| { diff --git a/src/link/Coff.zig b/src/link/Coff.zig index 24c3833e0a..492dbfc8eb 100644 --- a/src/link/Coff.zig +++ b/src/link/Coff.zig @@ -854,8 +854,12 @@ fn linkWithLLD(self: *Coff, comp: *Compilation) !void { _ = try man.hit(); digest = man.final(); var prev_digest_buf: [digest.len]u8 = undefined; - const prev_digest: []u8 = directory.handle.readLink(id_symlink_basename, &prev_digest_buf) catch |err| blk: { - log.debug("COFF LLD new_digest={} readlink error: {}", .{ digest, @errorName(err) }); + const prev_digest: []u8 = Cache.readSmallFile( + directory.handle, + id_symlink_basename, + &prev_digest_buf, + ) catch |err| blk: { + log.debug("COFF LLD new_digest={} error: {}", .{ digest, @errorName(err) }); // Handle this as a cache miss. break :blk prev_digest_buf[0..0]; }; @@ -1180,10 +1184,10 @@ fn linkWithLLD(self: *Coff, comp: *Compilation) !void { } if (!self.base.options.disable_lld_caching) { - // Update the dangling symlink with the digest. If it fails we can continue; it only + // Update the file with the digest. If it fails we can continue; it only // means that the next invocation will have an unnecessary cache miss. - directory.handle.symLink(&digest, id_symlink_basename, .{}) catch |err| { - std.log.warn("failed to save linking hash digest symlink: {}", .{@errorName(err)}); + Cache.writeSmallFile(directory.handle, id_symlink_basename, &digest) catch |err| { + std.log.warn("failed to save linking hash digest file: {}", .{@errorName(err)}); }; // Again failure here only means an unnecessary cache miss. man.writeManifest() catch |err| { diff --git a/src/link/Elf.zig b/src/link/Elf.zig index c62bb29f78..9c4029b3cd 100644 --- a/src/link/Elf.zig +++ b/src/link/Elf.zig @@ -1326,8 +1326,12 @@ fn linkWithLLD(self: *Elf, comp: *Compilation) !void { digest = man.final(); var prev_digest_buf: [digest.len]u8 = undefined; - const prev_digest: []u8 = directory.handle.readLink(id_symlink_basename, &prev_digest_buf) catch |err| blk: { - log.debug("ELF LLD new_digest={} readlink error: {}", .{ digest, @errorName(err) }); + const prev_digest: []u8 = Cache.readSmallFile( + directory.handle, + id_symlink_basename, + &prev_digest_buf, + ) catch |err| blk: { + log.debug("ELF LLD new_digest={} error: {}", .{ digest, @errorName(err) }); // Handle this as a cache miss. break :blk prev_digest_buf[0..0]; }; @@ -1647,10 +1651,10 @@ fn linkWithLLD(self: *Elf, comp: *Compilation) !void { } if (!self.base.options.disable_lld_caching) { - // Update the dangling symlink with the digest. If it fails we can continue; it only + // Update the file with the digest. If it fails we can continue; it only // means that the next invocation will have an unnecessary cache miss. - directory.handle.symLink(&digest, id_symlink_basename, .{}) catch |err| { - std.log.warn("failed to save linking hash digest symlink: {}", .{@errorName(err)}); + Cache.writeSmallFile(directory.handle, id_symlink_basename, &digest) catch |err| { + std.log.warn("failed to save linking hash digest file: {}", .{@errorName(err)}); }; // Again failure here only means an unnecessary cache miss. man.writeManifest() catch |err| { @@ -2588,7 +2592,7 @@ pub fn updateDeclExports( }, }; const stt_bits: u8 = @truncate(u4, decl_sym.st_info); - if (exp.link.sym_index) |i| { + if (exp.link.elf.sym_index) |i| { const sym = &self.global_symbols.items[i]; sym.* = .{ .st_name = try self.updateString(sym.st_name, exp.options.name), @@ -2613,7 +2617,7 @@ pub fn updateDeclExports( .st_size = decl_sym.st_size, }; - exp.link.sym_index = @intCast(u32, i); + exp.link.elf.sym_index = @intCast(u32, i); } } } diff --git a/src/link/MachO.zig b/src/link/MachO.zig index a1b9484e13..0d3de89d58 100644 --- a/src/link/MachO.zig +++ b/src/link/MachO.zig @@ -20,6 +20,8 @@ const File = link.File; const Cache = @import("../Cache.zig"); const target_util = @import("../target.zig"); +const Trie = @import("MachO/Trie.zig"); + pub const base_tag: File.Tag = File.Tag.macho; const LoadCommand = union(enum) { @@ -113,6 +115,9 @@ local_symbols: std.ArrayListUnmanaged(macho.nlist_64) = .{}, global_symbols: std.ArrayListUnmanaged(macho.nlist_64) = .{}, /// Table of all undefined symbols undef_symbols: std.ArrayListUnmanaged(macho.nlist_64) = .{}, + +global_symbol_free_list: std.ArrayListUnmanaged(u32) = .{}, + dyld_stub_binder_index: ?u16 = null, /// Table of symbol names aka the string table. @@ -176,6 +181,10 @@ pub const TextBlock = struct { }; }; +pub const Export = struct { + sym_index: ?u32 = null, +}; + pub const SrcFn = struct { pub const empty = SrcFn{}; }; @@ -256,10 +265,10 @@ pub fn flushModule(self: *MachO, comp: *Compilation) !void { switch (self.base.options.output_mode) { .Exe => { - if (self.entry_addr) |addr| { - // Write export trie. - try self.writeExportTrie(); + // Write export trie. + try self.writeExportTrie(); + if (self.entry_addr) |addr| { // Update LC_MAIN with entry offset const text_segment = self.load_commands.items[self.text_segment_cmd_index.?].Segment; const main_cmd = &self.load_commands.items[self.main_cmd_index.?].EntryPoint; @@ -410,8 +419,12 @@ fn linkWithLLD(self: *MachO, comp: *Compilation) !void { digest = man.final(); var prev_digest_buf: [digest.len]u8 = undefined; - const prev_digest: []u8 = directory.handle.readLink(id_symlink_basename, &prev_digest_buf) catch |err| blk: { - log.debug("MachO LLD new_digest={} readlink error: {}", .{ digest, @errorName(err) }); + const prev_digest: []u8 = Cache.readSmallFile( + directory.handle, + id_symlink_basename, + &prev_digest_buf, + ) catch |err| blk: { + log.debug("MachO LLD new_digest={} error: {}", .{ digest, @errorName(err) }); // Handle this as a cache miss. break :blk prev_digest_buf[0..0]; }; @@ -512,7 +525,7 @@ fn linkWithLLD(self: *MachO, comp: *Compilation) !void { try argv.append(darwinArchString(target.cpu.arch)); switch (target.os.tag) { - .macosx => { + .macos => { try argv.append("-macosx_version_min"); }, .ios, .tvos, .watchos => switch (target.cpu.arch) { @@ -665,10 +678,10 @@ fn linkWithLLD(self: *MachO, comp: *Compilation) !void { } if (!self.base.options.disable_lld_caching) { - // Update the dangling symlink with the digest. If it fails we can continue; it only + // Update the file with the digest. If it fails we can continue; it only // means that the next invocation will have an unnecessary cache miss. - directory.handle.symLink(&digest, id_symlink_basename, .{}) catch |err| { - std.log.warn("failed to save linking hash digest symlink: {}", .{@errorName(err)}); + Cache.writeSmallFile(directory.handle, id_symlink_basename, &digest) catch |err| { + std.log.warn("failed to save linking hash digest file: {}", .{@errorName(err)}); }; // Again failure here only means an unnecessary cache miss. man.writeManifest() catch |err| { @@ -711,6 +724,7 @@ pub fn deinit(self: *MachO) void { self.string_table.deinit(self.base.allocator); self.undef_symbols.deinit(self.base.allocator); self.global_symbols.deinit(self.base.allocator); + self.global_symbol_free_list.deinit(self.base.allocator); self.local_symbols.deinit(self.base.allocator); self.sections.deinit(self.base.allocator); self.load_commands.deinit(self.base.allocator); @@ -835,7 +849,7 @@ pub fn updateDeclExports( }, }; const n_type = decl_sym.n_type | macho.N_EXT; - if (exp.link.sym_index) |i| { + if (exp.link.macho.sym_index) |i| { const sym = &self.global_symbols.items[i]; sym.* = .{ .n_strx = try self.updateString(sym.n_strx, exp.options.name), @@ -846,8 +860,10 @@ pub fn updateDeclExports( }; } else { const name_str_index = try self.makeString(exp.options.name); - _ = self.global_symbols.addOneAssumeCapacity(); - const i = self.global_symbols.items.len - 1; + const i = if (self.global_symbol_free_list.popOrNull()) |i| i else blk: { + _ = self.global_symbols.addOneAssumeCapacity(); + break :blk self.global_symbols.items.len - 1; + }; self.global_symbols.items[i] = .{ .n_strx = name_str_index, .n_type = n_type, @@ -856,11 +872,17 @@ pub fn updateDeclExports( .n_value = decl_sym.n_value, }; - exp.link.sym_index = @intCast(u32, i); + exp.link.macho.sym_index = @intCast(u32, i); } } } +pub fn deleteExport(self: *MachO, exp: Export) void { + const sym_index = exp.sym_index orelse return; + self.global_symbol_free_list.append(self.base.allocator, sym_index) catch {}; + self.global_symbols.items[sym_index].n_type = 0; +} + pub fn freeDecl(self: *MachO, decl: *Module.Decl) void {} pub fn getDeclVAddr(self: *MachO, decl: *const Module.Decl) u64 { @@ -1383,25 +1405,30 @@ fn writeAllUndefSymbols(self: *MachO) !void { } fn writeExportTrie(self: *MachO) !void { - assert(self.entry_addr != null); + if (self.global_symbols.items.len == 0) return; // No exports, nothing to do. - // TODO implement mechanism for generating a prefix tree of the exported symbols - // single branch export trie - var buf = [_]u8{0} ** 24; - buf[0] = 0; // root node - buf[1] = 1; // 1 branch from root - mem.copy(u8, buf[2..], "_start"); - buf[8] = 0; - buf[9] = 9 + 1; + var trie: Trie = .{}; + defer trie.deinit(self.base.allocator); const text_segment = self.load_commands.items[self.text_segment_cmd_index.?].Segment; - const addr = self.entry_addr.? - text_segment.vmaddr; - const written = try std.debug.leb.writeULEB128Mem(buf[12..], addr); - buf[10] = @intCast(u8, written) + 1; - buf[11] = 0; + for (self.global_symbols.items) |symbol| { + // TODO figure out if we should put all global symbols into the export trie + const name = self.getString(symbol.n_strx); + assert(symbol.n_value >= text_segment.vmaddr); + try trie.put(self.base.allocator, .{ + .name = name, + .vmaddr_offset = symbol.n_value - text_segment.vmaddr, + .export_flags = 0, // TODO workout creation of export flags + }); + } + + var buffer: std.ArrayListUnmanaged(u8) = .{}; + defer buffer.deinit(self.base.allocator); + + try trie.writeULEB128Mem(self.base.allocator, &buffer); const dyld_info = &self.load_commands.items[self.dyld_info_cmd_index.?].DyldInfo; - try self.base.file.?.pwriteAll(buf[0..], dyld_info.export_off); + try self.base.file.?.pwriteAll(buffer.items, dyld_info.export_off); } fn writeStringTable(self: *MachO) !void { diff --git a/src/link/MachO/Trie.zig b/src/link/MachO/Trie.zig new file mode 100644 index 0000000000..e077df101d --- /dev/null +++ b/src/link/MachO/Trie.zig @@ -0,0 +1,436 @@ +//! Represents export trie used in MachO executables and dynamic libraries. +//! The purpose of an export trie is to encode as compactly as possible all +//! export symbols for the loader `dyld`. +//! The export trie encodes offset and other information using ULEB128 +//! encoding, and is part of the __LINKEDIT segment. +//! +//! Description from loader.h: +//! +//! The symbols exported by a dylib are encoded in a trie. This is a compact +//! representation that factors out common prefixes. It also reduces LINKEDIT pages +//! in RAM because it encodes all information (name, address, flags) in one small, +//! contiguous range. The export area is a stream of nodes. The first node sequentially +//! is the start node for the trie. +//! +//! Nodes for a symbol start with a uleb128 that is the length of the exported symbol +//! information for the string so far. If there is no exported symbol, the node starts +//! with a zero byte. If there is exported info, it follows the length. +//! +//! First is a uleb128 containing flags. Normally, it is followed by a uleb128 encoded +//! offset which is location of the content named by the symbol from the mach_header +//! for the image. If the flags is EXPORT_SYMBOL_FLAGS_REEXPORT, then following the flags +//! is a uleb128 encoded library ordinal, then a zero terminated UTF8 string. If the string +//! is zero length, then the symbol is re-export from the specified dylib with the same name. +//! If the flags is EXPORT_SYMBOL_FLAGS_STUB_AND_RESOLVER, then following the flags is two +//! uleb128s: the stub offset and the resolver offset. The stub is used by non-lazy pointers. +//! The resolver is used by lazy pointers and must be called to get the actual address to use. +//! +//! After the optional exported symbol information is a byte of how many edges (0-255) that +//! this node has leaving it, followed by each edge. Each edge is a zero terminated UTF8 of +//! the addition chars in the symbol, followed by a uleb128 offset for the node that edge points to. +const Trie = @This(); + +const std = @import("std"); +const mem = std.mem; +const leb = std.debug.leb; +const log = std.log.scoped(.link); +const testing = std.testing; +const assert = std.debug.assert; +const Allocator = mem.Allocator; + +pub const Symbol = struct { + name: []const u8, + vmaddr_offset: u64, + export_flags: u64, +}; + +const Edge = struct { + from: *Node, + to: *Node, + label: []const u8, + + fn deinit(self: *Edge, alloc: *Allocator) void { + self.to.deinit(alloc); + alloc.destroy(self.to); + self.from = undefined; + self.to = undefined; + } +}; + +const Node = struct { + /// Export flags associated with this exported symbol (if any). + export_flags: ?u64 = null, + /// VM address offset wrt to the section this symbol is defined against (if any). + vmaddr_offset: ?u64 = null, + /// Offset of this node in the trie output byte stream. + trie_offset: ?usize = null, + /// List of all edges originating from this node. + edges: std.ArrayListUnmanaged(Edge) = .{}, + + fn deinit(self: *Node, alloc: *Allocator) void { + for (self.edges.items) |*edge| { + edge.deinit(alloc); + } + self.edges.deinit(alloc); + } + + const PutResult = struct { + /// Node reached at this stage of `put` op. + node: *Node, + /// Count of newly inserted nodes at this stage of `put` op. + node_count: usize, + }; + + /// Inserts a new node starting from `self`. + fn put(self: *Node, alloc: *Allocator, label: []const u8, node_count: usize) !PutResult { + var curr_node_count = node_count; + // Check for match with edges from this node. + for (self.edges.items) |*edge| { + const match = mem.indexOfDiff(u8, edge.label, label) orelse return PutResult{ + .node = edge.to, + .node_count = curr_node_count, + }; + if (match == 0) continue; + if (match == edge.label.len) return edge.to.put(alloc, label[match..], curr_node_count); + + // Found a match, need to splice up nodes. + // From: A -> B + // To: A -> C -> B + const mid = try alloc.create(Node); + mid.* = .{}; + const to_label = edge.label; + const to_node = edge.to; + edge.to = mid; + edge.label = label[0..match]; + curr_node_count += 1; + + try mid.edges.append(alloc, .{ + .from = mid, + .to = to_node, + .label = to_label[match..], + }); + + if (match == label.len) { + return PutResult{ .node = to_node, .node_count = curr_node_count }; + } else { + return mid.put(alloc, label[match..], curr_node_count); + } + } + + // Add a new node. + const node = try alloc.create(Node); + node.* = .{}; + curr_node_count += 1; + + try self.edges.append(alloc, .{ + .from = self, + .to = node, + .label = label, + }); + + return PutResult{ .node = node, .node_count = curr_node_count }; + } + + /// This method should only be called *after* updateOffset has been called! + /// In case this is not upheld, this method will panic. + fn writeULEB128Mem(self: Node, buffer: *std.ArrayListUnmanaged(u8)) !void { + assert(self.trie_offset != null); // You need to call updateOffset first. + if (self.vmaddr_offset) |offset| { + // Terminal node info: encode export flags and vmaddr offset of this symbol. + var info_buf_len: usize = 0; + var info_buf: [@sizeOf(u64) * 2]u8 = undefined; + info_buf_len += try leb.writeULEB128Mem(info_buf[0..], self.export_flags.?); + info_buf_len += try leb.writeULEB128Mem(info_buf[info_buf_len..], offset); + + // Encode the size of the terminal node info. + var size_buf: [@sizeOf(u64)]u8 = undefined; + const size_buf_len = try leb.writeULEB128Mem(size_buf[0..], info_buf_len); + + // Now, write them to the output buffer. + buffer.appendSliceAssumeCapacity(size_buf[0..size_buf_len]); + buffer.appendSliceAssumeCapacity(info_buf[0..info_buf_len]); + } else { + // Non-terminal node is delimited by 0 byte. + buffer.appendAssumeCapacity(0); + } + // Write number of edges (max legal number of edges is 256). + buffer.appendAssumeCapacity(@intCast(u8, self.edges.items.len)); + + for (self.edges.items) |edge| { + // Write edges labels. + buffer.appendSliceAssumeCapacity(edge.label); + buffer.appendAssumeCapacity(0); + + var buf: [@sizeOf(u64)]u8 = undefined; + const buf_len = try leb.writeULEB128Mem(buf[0..], edge.to.trie_offset.?); + buffer.appendSliceAssumeCapacity(buf[0..buf_len]); + } + } + + const UpdateResult = struct { + /// Current size of this node in bytes. + node_size: usize, + /// True if the trie offset of this node in the output byte stream + /// would need updating; false otherwise. + updated: bool, + }; + + /// Updates offset of this node in the output byte stream. + fn updateOffset(self: *Node, offset: usize) UpdateResult { + var node_size: usize = 0; + if (self.vmaddr_offset) |vmaddr| { + node_size += sizeULEB128Mem(self.export_flags.?); + node_size += sizeULEB128Mem(vmaddr); + node_size += sizeULEB128Mem(node_size); + } else { + node_size += 1; // 0x0 for non-terminal nodes + } + node_size += 1; // 1 byte for edge count + + for (self.edges.items) |edge| { + const next_node_offset = edge.to.trie_offset orelse 0; + node_size += edge.label.len + 1 + sizeULEB128Mem(next_node_offset); + } + + const trie_offset = self.trie_offset orelse 0; + const updated = offset != trie_offset; + self.trie_offset = offset; + + return .{ .node_size = node_size, .updated = updated }; + } + + /// Calculates number of bytes in ULEB128 encoding of value. + fn sizeULEB128Mem(value: u64) usize { + var res: usize = 0; + var v = value; + while (true) { + v = v >> 7; + res += 1; + if (v == 0) break; + } + return res; + } +}; + +/// Count of nodes in the trie. +/// The count is updated at every `put` call. +/// The trie always consists of at least a root node, hence +/// the count always starts at 1. +node_count: usize = 1, +/// The root node of the trie. +root: Node = .{}, + +/// Insert a symbol into the trie, updating the prefixes in the process. +/// This operation may change the layout of the trie by splicing edges in +/// certain circumstances. +pub fn put(self: *Trie, alloc: *Allocator, symbol: Symbol) !void { + const res = try self.root.put(alloc, symbol.name, 0); + self.node_count += res.node_count; + res.node.vmaddr_offset = symbol.vmaddr_offset; + res.node.export_flags = symbol.export_flags; +} + +/// Write the trie to a buffer ULEB128 encoded. +pub fn writeULEB128Mem(self: *Trie, alloc: *Allocator, buffer: *std.ArrayListUnmanaged(u8)) !void { + var ordered_nodes: std.ArrayListUnmanaged(*Node) = .{}; + defer ordered_nodes.deinit(alloc); + + try ordered_nodes.ensureCapacity(alloc, self.node_count); + walkInOrder(&self.root, &ordered_nodes); + + var offset: usize = 0; + var more: bool = true; + while (more) { + offset = 0; + more = false; + for (ordered_nodes.items) |node| { + const res = node.updateOffset(offset); + offset += res.node_size; + if (res.updated) more = true; + } + } + + try buffer.ensureCapacity(alloc, buffer.items.len + offset); + for (ordered_nodes.items) |node| { + try node.writeULEB128Mem(buffer); + } +} + +/// Walks the trie in DFS order gathering all nodes into a linear stream of nodes. +fn walkInOrder(node: *Node, list: *std.ArrayListUnmanaged(*Node)) void { + list.appendAssumeCapacity(node); + for (node.edges.items) |*edge| { + walkInOrder(edge.to, list); + } +} + +pub fn deinit(self: *Trie, alloc: *Allocator) void { + self.root.deinit(alloc); +} + +test "Trie node count" { + var gpa = testing.allocator; + var trie: Trie = .{}; + defer trie.deinit(gpa); + + testing.expectEqual(trie.node_count, 1); + + try trie.put(gpa, .{ + .name = "_main", + .vmaddr_offset = 0, + .export_flags = 0, + }); + testing.expectEqual(trie.node_count, 2); + + // Inserting the same node shouldn't update the trie. + try trie.put(gpa, .{ + .name = "_main", + .vmaddr_offset = 0, + .export_flags = 0, + }); + testing.expectEqual(trie.node_count, 2); + + try trie.put(gpa, .{ + .name = "__mh_execute_header", + .vmaddr_offset = 0x1000, + .export_flags = 0, + }); + testing.expectEqual(trie.node_count, 4); + + // Inserting the same node shouldn't update the trie. + try trie.put(gpa, .{ + .name = "__mh_execute_header", + .vmaddr_offset = 0x1000, + .export_flags = 0, + }); + testing.expectEqual(trie.node_count, 4); + try trie.put(gpa, .{ + .name = "_main", + .vmaddr_offset = 0, + .export_flags = 0, + }); + testing.expectEqual(trie.node_count, 4); +} + +test "Trie basic" { + var gpa = testing.allocator; + var trie: Trie = .{}; + defer trie.deinit(gpa); + + // root + testing.expect(trie.root.edges.items.len == 0); + + // root --- _st ---> node + try trie.put(gpa, .{ + .name = "_st", + .vmaddr_offset = 0, + .export_flags = 0, + }); + testing.expect(trie.root.edges.items.len == 1); + testing.expect(mem.eql(u8, trie.root.edges.items[0].label, "_st")); + + { + // root --- _st ---> node --- art ---> node + try trie.put(gpa, .{ + .name = "_start", + .vmaddr_offset = 0, + .export_flags = 0, + }); + testing.expect(trie.root.edges.items.len == 1); + + const nextEdge = &trie.root.edges.items[0]; + testing.expect(mem.eql(u8, nextEdge.label, "_st")); + testing.expect(nextEdge.to.edges.items.len == 1); + testing.expect(mem.eql(u8, nextEdge.to.edges.items[0].label, "art")); + } + { + // root --- _ ---> node --- st ---> node --- art ---> node + // | + // | --- main ---> node + try trie.put(gpa, .{ + .name = "_main", + .vmaddr_offset = 0, + .export_flags = 0, + }); + testing.expect(trie.root.edges.items.len == 1); + + const nextEdge = &trie.root.edges.items[0]; + testing.expect(mem.eql(u8, nextEdge.label, "_")); + testing.expect(nextEdge.to.edges.items.len == 2); + testing.expect(mem.eql(u8, nextEdge.to.edges.items[0].label, "st")); + testing.expect(mem.eql(u8, nextEdge.to.edges.items[1].label, "main")); + + const nextNextEdge = &nextEdge.to.edges.items[0]; + testing.expect(mem.eql(u8, nextNextEdge.to.edges.items[0].label, "art")); + } +} + +test "Trie.writeULEB128Mem" { + var gpa = testing.allocator; + var trie: Trie = .{}; + defer trie.deinit(gpa); + + try trie.put(gpa, .{ + .name = "__mh_execute_header", + .vmaddr_offset = 0, + .export_flags = 0, + }); + try trie.put(gpa, .{ + .name = "_main", + .vmaddr_offset = 0x1000, + .export_flags = 0, + }); + + var buffer: std.ArrayListUnmanaged(u8) = .{}; + defer buffer.deinit(gpa); + + try trie.writeULEB128Mem(gpa, &buffer); + + const exp_buffer = [_]u8{ + 0x0, + 0x1, + 0x5f, + 0x0, + 0x5, + 0x0, + 0x2, + 0x5f, + 0x6d, + 0x68, + 0x5f, + 0x65, + 0x78, + 0x65, + 0x63, + 0x75, + 0x74, + 0x65, + 0x5f, + 0x68, + 0x65, + 0x61, + 0x64, + 0x65, + 0x72, + 0x0, + 0x21, + 0x6d, + 0x61, + 0x69, + 0x6e, + 0x0, + 0x25, + 0x2, + 0x0, + 0x0, + 0x0, + 0x3, + 0x0, + 0x80, + 0x20, + 0x0, + }; + + testing.expect(buffer.items.len == exp_buffer.len); + testing.expect(mem.eql(u8, buffer.items, exp_buffer[0..])); +} diff --git a/src/link/Wasm.zig b/src/link/Wasm.zig index 3f879a3b32..2ab757461c 100644 --- a/src/link/Wasm.zig +++ b/src/link/Wasm.zig @@ -310,8 +310,12 @@ fn linkWithLLD(self: *Wasm, comp: *Compilation) !void { digest = man.final(); var prev_digest_buf: [digest.len]u8 = undefined; - const prev_digest: []u8 = directory.handle.readLink(id_symlink_basename, &prev_digest_buf) catch |err| blk: { - log.debug("WASM LLD new_digest={} readlink error: {}", .{ digest, @errorName(err) }); + const prev_digest: []u8 = Cache.readSmallFile( + directory.handle, + id_symlink_basename, + &prev_digest_buf, + ) catch |err| blk: { + log.debug("WASM LLD new_digest={} error: {}", .{ digest, @errorName(err) }); // Handle this as a cache miss. break :blk prev_digest_buf[0..0]; }; @@ -374,7 +378,7 @@ fn linkWithLLD(self: *Wasm, comp: *Compilation) !void { try argv.append(p); } - if (self.base.options.output_mode == .Exe and !self.base.options.is_compiler_rt_or_libc) { + if (self.base.options.output_mode != .Obj and !self.base.options.is_compiler_rt_or_libc) { if (!self.base.options.link_libc) { try argv.append(comp.libc_static_lib.?.full_object_path); } @@ -424,9 +428,9 @@ fn linkWithLLD(self: *Wasm, comp: *Compilation) !void { } if (!self.base.options.disable_lld_caching) { - // Update the dangling symlink with the digest. If it fails we can continue; it only + // Update the file with the digest. If it fails we can continue; it only // means that the next invocation will have an unnecessary cache miss. - directory.handle.symLink(&digest, id_symlink_basename, .{}) catch |err| { + Cache.writeSmallFile(directory.handle, id_symlink_basename, &digest) catch |err| { std.log.warn("failed to save linking hash digest symlink: {}", .{@errorName(err)}); }; // Again failure here only means an unnecessary cache miss. diff --git a/src/target.zig b/src/target.zig index d009cf8556..ce3a200d2b 100644 --- a/src/target.zig +++ b/src/target.zig @@ -123,14 +123,14 @@ pub fn cannotDynamicLink(target: std.Target) bool { /// since this is the stable syscall interface. pub fn osRequiresLibC(target: std.Target) bool { return switch (target.os.tag) { - .freebsd, .netbsd, .dragonfly, .macosx, .ios, .watchos, .tvos => true, + .freebsd, .netbsd, .dragonfly, .macos, .ios, .watchos, .tvos => true, else => false, }; } pub fn libcNeedsLibUnwind(target: std.Target) bool { return switch (target.os.tag) { - .macosx, + .macos, .ios, .watchos, .tvos, @@ -197,7 +197,7 @@ pub fn osToLLVM(os_tag: std.Target.Os.Tag) llvm.OSType { .kfreebsd => .KFreeBSD, .linux => .Linux, .lv2 => .Lv2, - .macosx => .MacOSX, + .macos => .MacOSX, .netbsd => .NetBSD, .openbsd => .OpenBSD, .solaris => .Solaris, diff --git a/src/type.zig b/src/type.zig index c22edaa561..f07df290fc 100644 --- a/src/type.zig +++ b/src/type.zig @@ -3104,7 +3104,7 @@ pub const CType = enum { }, .linux, - .macosx, + .macos, .freebsd, .netbsd, .dragonfly, diff --git a/src/zig_llvm.cpp b/src/zig_llvm.cpp index e08366b5b6..ac45932e0a 100644 --- a/src/zig_llvm.cpp +++ b/src/zig_llvm.cpp @@ -820,7 +820,9 @@ const char *ZigLLVMGetVendorTypeName(ZigLLVM_VendorType vendor) { } const char *ZigLLVMGetOSTypeName(ZigLLVM_OSType os) { - return (const char*)Triple::getOSTypeName((Triple::OSType)os).bytes_begin(); + const char* name = (const char*)Triple::getOSTypeName((Triple::OSType)os).bytes_begin(); + if (strcmp(name, "macosx") == 0) return "macos"; + return name; } const char *ZigLLVMGetEnvironmentTypeName(ZigLLVM_EnvironmentType env_type) { diff --git a/test/stack_traces.zig b/test/stack_traces.zig index 496be05138..640cc0904c 100644 --- a/test/stack_traces.zig +++ b/test/stack_traces.zig @@ -308,7 +308,7 @@ pub fn addCases(cases: *tests.StackTracesContext) void { }, ); }, - .macosx => { + .macos => { cases.addCase( "return", source_return, diff --git a/test/stage2/test.zig b/test/stage2/test.zig index 2e3b570570..5f5fe76d17 100644 --- a/test/stage2/test.zig +++ b/test/stage2/test.zig @@ -13,7 +13,7 @@ const linux_x64 = std.zig.CrossTarget{ const macosx_x64 = std.zig.CrossTarget{ .cpu_arch = .x86_64, - .os_tag = .macosx, + .os_tag = .macos, }; const linux_riscv64 = std.zig.CrossTarget{ diff --git a/test/tests.zig b/test/tests.zig index 58b2b50094..09001b02a1 100644 --- a/test/tests.zig +++ b/test/tests.zig @@ -234,7 +234,7 @@ const test_targets = blk: { TestTarget{ .target = .{ .cpu_arch = .x86_64, - .os_tag = .macosx, + .os_tag = .macos, .abi = .gnu, }, // https://github.com/ziglang/zig/issues/3295