diff options
Diffstat (limited to 'src/Compilation.zig')
| -rw-r--r-- | src/Compilation.zig | 1407 |
1 files changed, 830 insertions, 577 deletions
diff --git a/src/Compilation.zig b/src/Compilation.zig index d3cf9e8f38..f2cb7a8729 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -2,6 +2,7 @@ const Compilation = @This(); const std = @import("std"); const builtin = @import("builtin"); +const fs = std.fs; const mem = std.mem; const Allocator = std.mem.Allocator; const assert = std.debug.assert; @@ -10,12 +11,13 @@ const Target = std.Target; const ThreadPool = std.Thread.Pool; const WaitGroup = std.Thread.WaitGroup; const ErrorBundle = std.zig.ErrorBundle; -const Path = Cache.Path; +const fatal = std.process.fatal; const Value = @import("Value.zig"); const Type = @import("Type.zig"); const target_util = @import("target.zig"); const Package = @import("Package.zig"); +const introspect = @import("introspect.zig"); const link = @import("link.zig"); const tracy = @import("tracy.zig"); const trace = tracy.trace; @@ -28,7 +30,6 @@ const mingw = @import("libs/mingw.zig"); const libunwind = @import("libs/libunwind.zig"); const libcxx = @import("libs/libcxx.zig"); const wasi_libc = @import("libs/wasi_libc.zig"); -const fatal = @import("main.zig").fatal; const clangMain = @import("main.zig").clangMain; const Zcu = @import("Zcu.zig"); const Sema = @import("Sema.zig"); @@ -43,7 +44,6 @@ const LlvmObject = @import("codegen/llvm.zig").Object; const dev = @import("dev.zig"); const ThreadSafeQueue = @import("ThreadSafeQueue.zig").ThreadSafeQueue; -pub const Directory = Cache.Directory; pub const Config = @import("Compilation/Config.zig"); /// General-purpose allocator. Used for both temporary and long-term storage. @@ -75,9 +75,9 @@ bin_file: ?*link.File, /// The root path for the dynamic linker and system libraries (as well as frameworks on Darwin) sysroot: ?[]const u8, /// This is `null` when not building a Windows DLL, or when `-fno-emit-implib` is used. -implib_emit: ?Path, +implib_emit: ?Cache.Path, /// This is non-null when `-femit-docs` is provided. -docs_emit: ?Path, +docs_emit: ?Cache.Path, root_name: [:0]const u8, compiler_rt_strat: RtStrat, ubsan_rt_strat: RtStrat, @@ -152,11 +152,6 @@ win32_resource_work_queue: if (dev.env.supports(.win32_resource)) std.fifo.Linea pub fn deinit(_: @This()) void {} }, -/// These jobs are to tokenize, parse, and astgen files, which may be outdated -/// since the last compilation, as well as scan for `@import` and queue up -/// additional jobs corresponding to those new files. -astgen_work_queue: std.fifo.LinearFifo(Zcu.File.Index, .Dynamic), - /// The ErrorMsg memory is owned by the `CObject`, using Compilation's general purpose allocator. /// This data is accessed by multiple threads and is protected by `mutex`. failed_c_objects: std.AutoArrayHashMapUnmanaged(*CObject, *CObject.Diag.Bundle) = .empty, @@ -207,9 +202,8 @@ cache_parent: *Cache, parent_whole_cache: ?ParentWholeCache, /// Path to own executable for invoking `zig clang`. self_exe_path: ?[]const u8, -zig_lib_directory: Directory, -local_cache_directory: Directory, -global_cache_directory: Directory, +/// Owned by the caller of `Compilation.create`. +dirs: Directories, libc_include_dir_list: []const []const u8, libc_framework_dir_list: []const []const u8, rc_includes: RcIncludes, @@ -293,7 +287,6 @@ const QueuedJobs = struct { ubsan_rt_lib: bool = false, ubsan_rt_obj: bool = false, fuzzer_lib: bool = false, - update_builtin_zig: bool, musl_crt_file: [@typeInfo(musl.CrtFile).@"enum".fields.len]bool = @splat(false), glibc_crt_file: [@typeInfo(glibc.CrtFile).@"enum".fields.len]bool = @splat(false), freebsd_crt_file: [@typeInfo(freebsd.CrtFile).@"enum".fields.len]bool = @splat(false), @@ -312,12 +305,466 @@ const QueuedJobs = struct { zigc_lib: bool = false, }; +/// A filesystem path, represented relative to one of a few specific directories where possible. +/// Every path (considering symlinks as distinct paths) has a canonical representation in this form. +/// This abstraction allows us to: +/// * always open files relative to a consistent root on the filesystem +/// * detect when two paths correspond to the same file, e.g. for deduplicating `@import`s +pub const Path = struct { + root: Root, + /// This path is always in a normalized form, where: + /// * All components are separated by `fs.path.sep` + /// * There are no repeated separators (like "foo//bar") + /// * There are no "." or ".." components + /// * There is no trailing path separator + /// + /// There is a leading separator iff `root` is `.none` *and* `builtin.target.os.tag != .wasi`. + /// + /// If this `Path` exactly represents a `Root`, the sub path is "", not ".". + sub_path: []u8, + + const Root = enum { + /// `sub_path` is relative to the Zig lib directory on `Compilation`. + zig_lib, + /// `sub_path` is relative to the global cache directory on `Compilation`. + global_cache, + /// `sub_path` is relative to the local cache directory on `Compilation`. + local_cache, + /// `sub_path` is not relative to any of the roots listed above. + /// It is resolved starting with `Directories.cwd`; so it is an absolute path on most + /// targets, but cwd-relative on WASI. We do not make it cwd-relative on other targets + /// so that `Path.digest` gives hashes which can be stored in the Zig cache (as they + /// don't depend on a specific compiler instance). + none, + }; + + /// In general, we can only construct canonical `Path`s at runtime, because weird nesting might + /// mean that e.g. a sub path inside zig/lib/ is actually in the global cache. However, because + /// `Directories` guarantees that `zig_lib` is a distinct path from both cache directories, it's + /// okay for us to construct this path, and only this path, as a comptime constant. + pub const zig_lib_root: Path = .{ .root = .zig_lib, .sub_path = "" }; + + pub fn deinit(p: Path, gpa: Allocator) void { + gpa.free(p.sub_path); + } + + /// The returned digest is relocatable across any compiler process using the same lib and cache + /// directories; it does not depend on cwd. + pub fn digest(p: Path) Cache.BinDigest { + var h = Cache.hasher_init; + h.update(&.{@intFromEnum(p.root)}); + h.update(p.sub_path); + return h.finalResult(); + } + + /// Given a `Path`, returns the directory handle and sub path to be used to open the path. + pub fn openInfo(p: Path, dirs: Directories) struct { fs.Dir, []const u8 } { + const dir = switch (p.root) { + .none => { + const cwd_sub_path = absToCwdRelative(p.sub_path, dirs.cwd); + return .{ fs.cwd(), cwd_sub_path }; + }, + .zig_lib => dirs.zig_lib.handle, + .global_cache => dirs.global_cache.handle, + .local_cache => dirs.local_cache.handle, + }; + if (p.sub_path.len == 0) return .{ dir, "." }; + assert(!fs.path.isAbsolute(p.sub_path)); + return .{ dir, p.sub_path }; + } + + pub const format = unreachable; // do not format direcetly + pub fn fmt(p: Path, comp: *Compilation) Formatter { + return .{ .p = p, .comp = comp }; + } + const Formatter = struct { + p: Path, + comp: *Compilation, + pub fn format(f: Formatter, comptime unused_fmt: []const u8, options: std.fmt.FormatOptions, w: anytype) !void { + comptime assert(unused_fmt.len == 0); + _ = options; + const root_path: []const u8 = switch (f.p.root) { + .zig_lib => f.comp.dirs.zig_lib.path orelse ".", + .global_cache => f.comp.dirs.global_cache.path orelse ".", + .local_cache => f.comp.dirs.local_cache.path orelse ".", + .none => { + const cwd_sub_path = absToCwdRelative(f.p.sub_path, f.comp.dirs.cwd); + try w.writeAll(cwd_sub_path); + return; + }, + }; + assert(root_path.len != 0); + try w.writeAll(root_path); + if (f.p.sub_path.len > 0) { + try w.writeByte(fs.path.sep); + try w.writeAll(f.p.sub_path); + } + } + }; + + /// Given the `sub_path` of a `Path` with `Path.root == .none`, attempts to convert + /// the (absolute) path to a cwd-relative path. Otherwise, returns the absolute path + /// unmodified. The returned string is never empty: "" is converted to ".". + fn absToCwdRelative(sub_path: []const u8, cwd_path: []const u8) []const u8 { + if (builtin.target.os.tag == .wasi) { + if (sub_path.len == 0) return "."; + assert(!fs.path.isAbsolute(sub_path)); + return sub_path; + } + assert(fs.path.isAbsolute(sub_path)); + if (!std.mem.startsWith(u8, sub_path, cwd_path)) return sub_path; + if (sub_path.len == cwd_path.len) return "."; // the strings are equal + if (sub_path[cwd_path.len] != fs.path.sep) return sub_path; // last component before cwd differs + return sub_path[cwd_path.len + 1 ..]; // remove '/path/to/cwd/' prefix + } + + /// From an unresolved path (which can be made of multiple not-yet-joined strings), construct a + /// canonical `Path`. + pub fn fromUnresolved(gpa: Allocator, dirs: Compilation.Directories, unresolved_parts: []const []const u8) Allocator.Error!Path { + const resolved = try introspect.resolvePath(gpa, dirs.cwd, unresolved_parts); + errdefer gpa.free(resolved); + + // If, for instance, `dirs.local_cache.path` is within the lib dir, it must take priority, + // so that we prefer `.root = .local_cache` over `.root = .zig_lib`. The easiest way to do + // this is simply to prioritize the longest root path. + const PathAndRoot = struct { ?[]const u8, Root }; + var roots: [3]PathAndRoot = .{ + .{ dirs.zig_lib.path, .zig_lib }, + .{ dirs.global_cache.path, .global_cache }, + .{ dirs.local_cache.path, .local_cache }, + }; + // This must be a stable sort, because the global and local cache directories may be the same, in + // which case we need to make a consistent choice. + std.mem.sort(PathAndRoot, &roots, {}, struct { + fn lessThan(_: void, lhs: PathAndRoot, rhs: PathAndRoot) bool { + const lhs_path_len = if (lhs[0]) |p| p.len else 0; + const rhs_path_len = if (rhs[0]) |p| p.len else 0; + return lhs_path_len > rhs_path_len; // '>' instead of '<' to sort descending + } + }.lessThan); + + for (roots) |path_and_root| { + const opt_root_path, const root = path_and_root; + const root_path = opt_root_path orelse { + // This root is the cwd. + if (!fs.path.isAbsolute(resolved)) { + return .{ + .root = root, + .sub_path = resolved, + }; + } + continue; + }; + if (!mem.startsWith(u8, resolved, root_path)) continue; + const sub: []const u8 = if (resolved.len != root_path.len) sub: { + // Check the trailing slash, so that we don't match e.g. `/foo/bar` with `/foo/barren` + if (resolved[root_path.len] != fs.path.sep) continue; + break :sub resolved[root_path.len + 1 ..]; + } else ""; + const duped = try gpa.dupe(u8, sub); + gpa.free(resolved); + return .{ .root = root, .sub_path = duped }; + } + + // We're not relative to any root, so we will use an absolute path (on targets where they are available). + + if (builtin.target.os.tag == .wasi or fs.path.isAbsolute(resolved)) { + // `resolved` is already absolute (or we're on WASI, where absolute paths don't really exist). + return .{ .root = .none, .sub_path = resolved }; + } + + if (resolved.len == 0) { + // We just need the cwd path, no trailing separator. Note that `gpa.free(resolved)` would be a nop. + return .{ .root = .none, .sub_path = try gpa.dupe(u8, dirs.cwd) }; + } + + // We need to make an absolute path. Because `resolved` came from `introspect.resolvePath`, we can just + // join the paths with a simple format string. + const abs_path = try std.fmt.allocPrint(gpa, "{s}{c}{s}", .{ dirs.cwd, fs.path.sep, resolved }); + gpa.free(resolved); + return .{ .root = .none, .sub_path = abs_path }; + } + + /// Constructs a canonical `Path` representing `sub_path` relative to `root`. + /// + /// If `sub_path` is resolved, this is almost like directly constructing a `Path`, but this + /// function also canonicalizes the result, which matters because `sub_path` may move us into + /// a different root. + /// + /// For instance, if the Zig lib directory is inside the global cache, passing `root` as + /// `.global_cache` could still end up returning a `Path` with `Path.root == .zig_lib`. + pub fn fromRoot( + gpa: Allocator, + dirs: Compilation.Directories, + root: Path.Root, + sub_path: []const u8, + ) Allocator.Error!Path { + // Currently, this just wraps `fromUnresolved` for simplicity. A more efficient impl is + // probably possible if this function ever ends up impacting performance somehow. + return .fromUnresolved(gpa, dirs, &.{ + switch (root) { + .zig_lib => dirs.zig_lib.path orelse "", + .global_cache => dirs.global_cache.path orelse "", + .local_cache => dirs.local_cache.path orelse "", + .none => "", + }, + sub_path, + }); + } + + /// Given a `Path` and an (unresolved) sub path relative to it, construct a `Path` representing + /// the joined path `p/sub_path`. Note that, like with `fromRoot`, the `sub_path` might cause us + /// to move into a different `Path.Root`. + pub fn join( + p: Path, + gpa: Allocator, + dirs: Compilation.Directories, + sub_path: []const u8, + ) Allocator.Error!Path { + // Currently, this just wraps `fromUnresolved` for simplicity. A more efficient impl is + // probably possible if this function ever ends up impacting performance somehow. + return .fromUnresolved(gpa, dirs, &.{ + switch (p.root) { + .zig_lib => dirs.zig_lib.path orelse "", + .global_cache => dirs.global_cache.path orelse "", + .local_cache => dirs.local_cache.path orelse "", + .none => "", + }, + p.sub_path, + sub_path, + }); + } + + /// Like `join`, but `sub_path` is relative to the dirname of `p` instead of `p` itself. + pub fn upJoin( + p: Path, + gpa: Allocator, + dirs: Compilation.Directories, + sub_path: []const u8, + ) Allocator.Error!Path { + return .fromUnresolved(gpa, dirs, &.{ + switch (p.root) { + .zig_lib => dirs.zig_lib.path orelse "", + .global_cache => dirs.global_cache.path orelse "", + .local_cache => dirs.local_cache.path orelse "", + .none => "", + }, + p.sub_path, + "..", + sub_path, + }); + } + + pub fn toCachePath(p: Path, dirs: Directories) Cache.Path { + const root_dir: Cache.Directory = switch (p.root) { + .zig_lib => dirs.zig_lib, + .global_cache => dirs.global_cache, + .local_cache => dirs.local_cache, + else => { + const cwd_sub_path = absToCwdRelative(p.sub_path, dirs.cwd); + return .{ + .root_dir = .cwd(), + .sub_path = cwd_sub_path, + }; + }, + }; + assert(!fs.path.isAbsolute(p.sub_path)); + return .{ + .root_dir = root_dir, + .sub_path = p.sub_path, + }; + } + + /// This should not be used for most of the compiler pipeline, but is useful when emitting + /// paths from the compilation (e.g. in debug info), because they will not depend on the cwd. + /// The returned path is owned by the caller and allocated into `gpa`. + pub fn toAbsolute(p: Path, dirs: Directories, gpa: Allocator) Allocator.Error![]u8 { + const root_path: []const u8 = switch (p.root) { + .zig_lib => dirs.zig_lib.path orelse "", + .global_cache => dirs.global_cache.path orelse "", + .local_cache => dirs.local_cache.path orelse "", + .none => "", + }; + return fs.path.resolve(gpa, &.{ + dirs.cwd, + root_path, + p.sub_path, + }); + } + + pub fn isNested(inner: Path, outer: Path) union(enum) { + /// Value is the sub path, which is a sub-slice of `inner.sub_path`. + yes: []const u8, + no, + different_roots, + } { + if (inner.root != outer.root) return .different_roots; + if (!mem.startsWith(u8, inner.sub_path, outer.sub_path)) return .no; + if (inner.sub_path.len == outer.sub_path.len) return .no; + if (outer.sub_path.len == 0) return .{ .yes = inner.sub_path }; + if (inner.sub_path[outer.sub_path.len] != fs.path.sep) return .no; + return .{ .yes = inner.sub_path[outer.sub_path.len + 1 ..] }; + } + + /// Returns whether this `Path` is illegal to have as a user-imported `Zcu.File` (including + /// as the root of a module). Such paths exist in directories which the Zig compiler treats + /// specially, like 'global_cache/b/', which stores 'builtin.zig' files. + pub fn isIllegalZigImport(p: Path, gpa: Allocator, dirs: Directories) Allocator.Error!bool { + const zig_builtin_dir: Path = try .fromRoot(gpa, dirs, .global_cache, "b"); + defer zig_builtin_dir.deinit(gpa); + return switch (p.isNested(zig_builtin_dir)) { + .yes => true, + .no, .different_roots => false, + }; + } +}; + +pub const Directories = struct { + /// The string returned by `introspect.getResolvedCwd`. This is typically an absolute path, + /// but on WASI is the empty string "" instead, because WASI does not have absolute paths. + cwd: []const u8, + /// The Zig 'lib' directory. + /// `zig_lib.path` is resolved (`introspect.resolvePath`) or `null` for cwd. + /// Guaranteed to be a different path from `global_cache` and `local_cache`. + zig_lib: Cache.Directory, + /// The global Zig cache directory. + /// `global_cache.path` is resolved (`introspect.resolvePath`) or `null` for cwd. + global_cache: Cache.Directory, + /// The local Zig cache directory. + /// `local_cache.path` is resolved (`introspect.resolvePath`) or `null` for cwd. + /// This may be the same as `global_cache`. + local_cache: Cache.Directory, + + pub fn deinit(dirs: *Directories) void { + // The local and global caches could be the same. + const close_local = dirs.local_cache.handle.fd != dirs.global_cache.handle.fd; + + dirs.global_cache.handle.close(); + if (close_local) dirs.local_cache.handle.close(); + dirs.zig_lib.handle.close(); + } + + /// Returns a `Directories` where `local_cache` is replaced with `global_cache`, intended for + /// use by sub-compilations (e.g. compiler_rt). Do not `deinit` the returned `Directories`; it + /// shares handles with `dirs`. + pub fn withoutLocalCache(dirs: Directories) Directories { + return .{ + .cwd = dirs.cwd, + .zig_lib = dirs.zig_lib, + .global_cache = dirs.global_cache, + .local_cache = dirs.global_cache, + }; + } + + /// Uses `std.process.fatal` on error conditions. + pub fn init( + arena: Allocator, + override_zig_lib: ?[]const u8, + override_global_cache: ?[]const u8, + local_cache_strat: union(enum) { + override: []const u8, + search, + global, + }, + wasi_preopens: switch (builtin.target.os.tag) { + .wasi => std.fs.wasi.Preopens, + else => void, + }, + self_exe_path: switch (builtin.target.os.tag) { + .wasi => void, + else => []const u8, + }, + ) Directories { + const wasi = builtin.target.os.tag == .wasi; + + const cwd = introspect.getResolvedCwd(arena) catch |err| { + fatal("unable to get cwd: {s}", .{@errorName(err)}); + }; + + const zig_lib: Cache.Directory = d: { + if (override_zig_lib) |path| break :d openUnresolved(arena, cwd, path, .@"zig lib"); + if (wasi) break :d openWasiPreopen(wasi_preopens, "/lib"); + break :d introspect.findZigLibDirFromSelfExe(arena, cwd, self_exe_path) catch |err| { + fatal("unable to find zig installation directory '{s}': {s}", .{ self_exe_path, @errorName(err) }); + }; + }; + + const global_cache: Cache.Directory = d: { + if (override_global_cache) |path| break :d openUnresolved(arena, cwd, path, .@"global cache"); + if (wasi) break :d openWasiPreopen(wasi_preopens, "/cache"); + const path = introspect.resolveGlobalCacheDir(arena) catch |err| { + fatal("unable to resolve zig cache directory: {s}", .{@errorName(err)}); + }; + break :d openUnresolved(arena, cwd, path, .@"global cache"); + }; + + const local_cache: Cache.Directory = switch (local_cache_strat) { + .override => |path| openUnresolved(arena, cwd, path, .@"local cache"), + .search => d: { + const maybe_path = introspect.resolveSuitableLocalCacheDir(arena, cwd) catch |err| { + fatal("unable to resolve zig cache directory: {s}", .{@errorName(err)}); + }; + const path = maybe_path orelse break :d global_cache; + break :d openUnresolved(arena, cwd, path, .@"local cache"); + }, + .global => global_cache, + }; + + if (std.mem.eql(u8, zig_lib.path orelse "", global_cache.path orelse "")) { + fatal("zig lib directory '{}' cannot be equal to global cache directory '{}'", .{ zig_lib, global_cache }); + } + if (std.mem.eql(u8, zig_lib.path orelse "", local_cache.path orelse "")) { + fatal("zig lib directory '{}' cannot be equal to local cache directory '{}'", .{ zig_lib, local_cache }); + } + + return .{ + .cwd = cwd, + .zig_lib = zig_lib, + .global_cache = global_cache, + .local_cache = local_cache, + }; + } + fn openWasiPreopen(preopens: std.fs.wasi.Preopens, name: []const u8) Cache.Directory { + return .{ + .path = if (std.mem.eql(u8, name, ".")) null else name, + .handle = .{ + .fd = preopens.find(name) orelse fatal("WASI preopen not found: '{s}'", .{name}), + }, + }; + } + fn openUnresolved(arena: Allocator, cwd: []const u8, unresolved_path: []const u8, thing: enum { @"zig lib", @"global cache", @"local cache" }) Cache.Directory { + const path = introspect.resolvePath(arena, cwd, &.{unresolved_path}) catch |err| { + fatal("unable to resolve {s} directory: {s}", .{ @tagName(thing), @errorName(err) }); + }; + const nonempty_path = if (path.len == 0) "." else path; + const handle_or_err = switch (thing) { + .@"zig lib" => std.fs.cwd().openDir(nonempty_path, .{}), + .@"global cache", .@"local cache" => std.fs.cwd().makeOpenPath(nonempty_path, .{}), + }; + return .{ + .path = if (path.len == 0) null else path, + .handle = handle_or_err catch |err| { + const extra_str: []const u8 = e: { + if (thing == .@"global cache") switch (err) { + error.AccessDenied, error.ReadOnlyFileSystem => break :e "\n" ++ + "If this location is not writable then consider specifying an alternative with " ++ + "the ZIG_GLOBAL_CACHE_DIR environment variable or the --global-cache-dir option.", + else => {}, + }; + break :e ""; + }; + fatal("unable to open {s} directory '{s}': {s}{s}", .{ @tagName(thing), nonempty_path, @errorName(err), extra_str }); + }, + }; + } +}; + pub const default_stack_protector_buffer_size = target_util.default_stack_protector_buffer_size; pub const SemaError = Zcu.SemaError; pub const CrtFile = struct { lock: Cache.Lock, - full_object_path: Path, + full_object_path: Cache.Path, pub fn isObject(cf: CrtFile) bool { return switch (classifyFileExt(cf.full_object_path.sub_path)) { @@ -430,7 +877,7 @@ pub const CObject = struct { new, success: struct { /// The outputted result. `sub_path` owned by gpa. - object_path: Path, + object_path: Cache.Path, /// This is a file system lock on the cache hash manifest representing this /// object. It prevents other invocations of the Zig compiler from interfering /// with this object until released. @@ -854,7 +1301,7 @@ pub const MiscError = struct { pub const EmitLoc = struct { /// If this is `null` it means the file will be output to the cache directory. /// When provided, both the open file handle and the path name must outlive the `Compilation`. - directory: ?Compilation.Directory, + directory: ?Cache.Directory, /// This may not have sub-directories in it. basename: []const u8, }; @@ -977,7 +1424,7 @@ const CacheUse = union(CacheMode) { implib_sub_path: ?[]u8, docs_sub_path: ?[]u8, lf_open_opts: link.File.OpenOptions, - tmp_artifact_directory: ?Directory, + tmp_artifact_directory: ?Cache.Directory, /// Prevents other processes from clobbering files in the output directory. lock: ?Cache.Lock, @@ -997,7 +1444,7 @@ const CacheUse = union(CacheMode) { const Incremental = struct { /// Where build artifacts and incremental compilation metadata serialization go. - artifact_directory: Compilation.Directory, + artifact_directory: Cache.Directory, }; fn deinit(cu: CacheUse) void { @@ -1013,9 +1460,7 @@ const CacheUse = union(CacheMode) { }; pub const CreateOptions = struct { - zig_lib_directory: Directory, - local_cache_directory: Directory, - global_cache_directory: Directory, + dirs: Directories, thread_pool: *ThreadPool, self_exe_path: ?[]const u8 = null, @@ -1059,7 +1504,7 @@ pub const CreateOptions = struct { /// This field is intended to be removed. /// The ELF implementation no longer uses this data, however the MachO and COFF /// implementations still do. - lib_directories: []const Directory = &.{}, + lib_directories: []const Cache.Directory = &.{}, rpath_list: []const []const u8 = &[0][]const u8{}, symbol_wrap_set: std.StringArrayHashMapUnmanaged(void) = .empty, c_source_files: []const CSourceFile = &.{}, @@ -1195,68 +1640,35 @@ pub const CreateOptions = struct { }; fn addModuleTableToCacheHash( - gpa: Allocator, + zcu: *Zcu, arena: Allocator, hash: *Cache.HashHelper, - root_mod: *Package.Module, - main_mod: *Package.Module, hash_type: union(enum) { path_bytes, files: *Cache.Manifest }, -) (error{OutOfMemory} || std.process.GetCwdError)!void { - var seen_table: std.AutoArrayHashMapUnmanaged(*Package.Module, void) = .empty; - defer seen_table.deinit(gpa); - - // root_mod and main_mod may be the same pointer. In fact they usually are. - // However in the case of `zig test` or `zig build` they will be different, - // and it's possible for one to not reference the other via the import table. - try seen_table.put(gpa, root_mod, {}); - try seen_table.put(gpa, main_mod, {}); - - const SortByName = struct { - has_builtin: bool, - names: []const []const u8, - - pub fn lessThan(ctx: @This(), lhs: usize, rhs: usize) bool { - return if (ctx.has_builtin and (lhs == 0 or rhs == 0)) - lhs < rhs - else - mem.lessThan(u8, ctx.names[lhs], ctx.names[rhs]); +) error{ + OutOfMemory, + Unexpected, + CurrentWorkingDirectoryUnlinked, +}!void { + assert(zcu.module_roots.count() != 0); // module_roots is populated + + for (zcu.module_roots.keys(), zcu.module_roots.values()) |mod, opt_mod_root_file| { + if (mod == zcu.std_mod) continue; // redundant + if (opt_mod_root_file.unwrap()) |mod_root_file| { + if (zcu.fileByIndex(mod_root_file).is_builtin) continue; // redundant } - }; - - var i: usize = 0; - while (i < seen_table.count()) : (i += 1) { - const mod = seen_table.keys()[i]; - if (mod.isBuiltin()) { - // Skip builtin.zig; it is useless as an input, and we don't want to - // have to write it before checking for a cache hit. - continue; - } - cache_helpers.addModule(hash, mod); - switch (hash_type) { .path_bytes => { - hash.addBytes(mod.root_src_path); - hash.addOptionalBytes(mod.root.root_dir.path); + hash.add(mod.root.root); hash.addBytes(mod.root.sub_path); + hash.addBytes(mod.root_src_path); }, .files => |man| if (mod.root_src_path.len != 0) { - const pkg_zig_file = try mod.root.joinString(arena, mod.root_src_path); - _ = try man.addFile(pkg_zig_file, null); + const root_src_path = try mod.root.toCachePath(zcu.comp.dirs).join(arena, mod.root_src_path); + _ = try man.addFilePath(root_src_path, null); }, } - - mod.deps.sortUnstable(SortByName{ - .has_builtin = mod.deps.count() >= 1 and - mod.deps.values()[0].isBuiltin(), - .names = mod.deps.keys(), - }); - hash.addListOfBytes(mod.deps.keys()); - - const deps = mod.deps.values(); - try seen_table.ensureUnusedCapacity(gpa, deps.len); - for (deps) |dep| seen_table.putAssumeCapacity(dep, {}); } } @@ -1310,7 +1722,7 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil const libc_dirs = try std.zig.LibCDirs.detect( arena, - options.zig_lib_directory.path.?, + options.dirs.zig_lib.path.?, options.root_mod.resolved_target.result, options.root_mod.resolved_target.is_native_abi, link_libc, @@ -1332,11 +1744,8 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil // For objects, this mechanism relies on essentially `_ = @import("compiler-rt");` // injected into the object. const compiler_rt_mod = try Package.Module.create(arena, .{ - .global_cache_directory = options.global_cache_directory, .paths = .{ - .root = .{ - .root_dir = options.zig_lib_directory, - }, + .root = .zig_lib_root, .root_src_path = "compiler_rt.zig", }, .fully_qualified_name = "compiler_rt", @@ -1348,8 +1757,6 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil }, .global = options.config, .parent = options.root_mod, - .builtin_mod = options.root_mod.getBuiltinDependency(), - .builtin_modules = null, // `builtin_mod` is set }); try options.root_mod.deps.putNoClobber(arena, "compiler_rt", compiler_rt_mod); } @@ -1369,11 +1776,8 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil if (ubsan_rt_strat == .zcu) { const ubsan_rt_mod = try Package.Module.create(arena, .{ - .global_cache_directory = options.global_cache_directory, .paths = .{ - .root = .{ - .root_dir = options.zig_lib_directory, - }, + .root = .zig_lib_root, .root_src_path = "ubsan_rt.zig", }, .fully_qualified_name = "ubsan_rt", @@ -1381,8 +1785,6 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil .inherited = .{}, .global = options.config, .parent = options.root_mod, - .builtin_mod = options.root_mod.getBuiltinDependency(), - .builtin_modules = null, // `builtin_mod` is set }); try options.root_mod.deps.putNoClobber(arena, "ubsan_rt", ubsan_rt_mod); } @@ -1415,13 +1817,13 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil const cache = try arena.create(Cache); cache.* = .{ .gpa = gpa, - .manifest_dir = try options.local_cache_directory.handle.makeOpenPath("h", .{}), + .manifest_dir = try options.dirs.local_cache.handle.makeOpenPath("h", .{}), }; // These correspond to std.zig.Server.Message.PathPrefix. cache.addPrefix(.{ .path = null, .handle = std.fs.cwd() }); - cache.addPrefix(options.zig_lib_directory); - cache.addPrefix(options.local_cache_directory); - cache.addPrefix(options.global_cache_directory); + cache.addPrefix(options.dirs.zig_lib); + cache.addPrefix(options.dirs.local_cache); + cache.addPrefix(options.dirs.global_cache); errdefer cache.manifest_dir.close(); // This is shared hasher state common to zig source and all C source files. @@ -1458,26 +1860,22 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil // to redundantly happen for each AstGen operation. const zir_sub_dir = "z"; - var local_zir_dir = try options.local_cache_directory.handle.makeOpenPath(zir_sub_dir, .{}); + var local_zir_dir = try options.dirs.local_cache.handle.makeOpenPath(zir_sub_dir, .{}); errdefer local_zir_dir.close(); - const local_zir_cache: Directory = .{ + const local_zir_cache: Cache.Directory = .{ .handle = local_zir_dir, - .path = try options.local_cache_directory.join(arena, &[_][]const u8{zir_sub_dir}), + .path = try options.dirs.local_cache.join(arena, &.{zir_sub_dir}), }; - var global_zir_dir = try options.global_cache_directory.handle.makeOpenPath(zir_sub_dir, .{}); + var global_zir_dir = try options.dirs.global_cache.handle.makeOpenPath(zir_sub_dir, .{}); errdefer global_zir_dir.close(); - const global_zir_cache: Directory = .{ + const global_zir_cache: Cache.Directory = .{ .handle = global_zir_dir, - .path = try options.global_cache_directory.join(arena, &[_][]const u8{zir_sub_dir}), + .path = try options.dirs.global_cache.join(arena, &.{zir_sub_dir}), }; const std_mod = options.std_mod orelse try Package.Module.create(arena, .{ - .global_cache_directory = options.global_cache_directory, .paths = .{ - .root = .{ - .root_dir = options.zig_lib_directory, - .sub_path = "std", - }, + .root = try .fromRoot(arena, options.dirs, .zig_lib, "std"), .root_src_path = "std.zig", }, .fully_qualified_name = "std", @@ -1485,8 +1883,6 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil .inherited = .{}, .global = options.config, .parent = options.root_mod, - .builtin_mod = options.root_mod.getBuiltinDependency(), - .builtin_modules = null, // `builtin_mod` is set }); const zcu = try arena.create(Zcu); @@ -1522,16 +1918,13 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil .docs_emit = null, // handled below .root_mod = options.root_mod, .config = options.config, - .zig_lib_directory = options.zig_lib_directory, - .local_cache_directory = options.local_cache_directory, - .global_cache_directory = options.global_cache_directory, + .dirs = options.dirs, .emit_asm = options.emit_asm, .emit_llvm_ir = options.emit_llvm_ir, .emit_llvm_bc = options.emit_llvm_bc, .work_queues = @splat(.init(gpa)), .c_object_work_queue = std.fifo.LinearFifo(*CObject, .Dynamic).init(gpa), .win32_resource_work_queue = if (dev.env.supports(.win32_resource)) std.fifo.LinearFifo(*Win32Resource, .Dynamic).init(gpa) else .{}, - .astgen_work_queue = std.fifo.LinearFifo(Zcu.File.Index, .Dynamic).init(gpa), .c_source_files = options.c_source_files, .rc_source_files = options.rc_source_files, .cache_parent = cache, @@ -1572,9 +1965,7 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil .framework_dirs = options.framework_dirs, .llvm_opt_bisect_limit = options.llvm_opt_bisect_limit, .skip_linker_dependencies = options.skip_linker_dependencies, - .queued_jobs = .{ - .update_builtin_zig = have_zcu, - }, + .queued_jobs = .{}, .function_sections = options.function_sections, .data_sections = options.data_sections, .native_system_include_paths = options.native_system_include_paths, @@ -1596,6 +1987,13 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil comp.config.any_sanitize_c = any_sanitize_c; comp.config.any_fuzz = any_fuzz; + if (opt_zcu) |zcu| { + // Populate `zcu.module_roots`. + const pt: Zcu.PerThread = .activate(zcu, .main); + defer pt.deactivate(); + try pt.populateModuleRootTable(); + } + const lf_open_opts: link.File.OpenOptions = .{ .linker_script = options.linker_script, .z_nodelete = options.linker_z_nodelete, @@ -1686,7 +2084,11 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil // do want to namespace different source file names because they are // likely different compilations and therefore this would be likely to // cause cache hits. - try addModuleTableToCacheHash(gpa, arena, &hash, options.root_mod, main_mod, .path_bytes); + if (comp.zcu) |zcu| { + try addModuleTableToCacheHash(zcu, arena, &hash, .path_bytes); + } else { + cache_helpers.addModule(&hash, options.root_mod); + } // In the case of incremental cache mode, this `artifact_directory` // is computed based on a hash of non-linker inputs, and it is where all @@ -1695,11 +2097,11 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil const digest = hash.final(); const artifact_sub_dir = "o" ++ std.fs.path.sep_str ++ digest; - var artifact_dir = try options.local_cache_directory.handle.makeOpenPath(artifact_sub_dir, .{}); + var artifact_dir = try options.dirs.local_cache.handle.makeOpenPath(artifact_sub_dir, .{}); errdefer artifact_dir.close(); - const artifact_directory: Directory = .{ + const artifact_directory: Cache.Directory = .{ .handle = artifact_dir, - .path = try options.local_cache_directory.join(arena, &[_][]const u8{artifact_sub_dir}), + .path = try options.dirs.local_cache.join(arena, &.{artifact_sub_dir}), }; const incremental = try arena.create(CacheUse.Incremental); @@ -1709,7 +2111,7 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil comp.cache_use = .{ .incremental = incremental }; if (options.emit_bin) |emit_bin| { - const emit: Path = .{ + const emit: Cache.Path = .{ .root_dir = emit_bin.directory orelse artifact_directory, .sub_path = emit_bin.basename, }; @@ -1998,10 +2400,10 @@ pub fn destroy(comp: *Compilation) void { if (comp.bin_file) |lf| lf.destroy(); if (comp.zcu) |zcu| zcu.deinit(); comp.cache_use.deinit(); + for (comp.work_queues) |work_queue| work_queue.deinit(); comp.c_object_work_queue.deinit(); comp.win32_resource_work_queue.deinit(); - comp.astgen_work_queue.deinit(); comp.windows_libs.deinit(gpa); @@ -2207,15 +2609,15 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) !void { log.debug("CacheMode.whole cache miss for {s}", .{comp.root_name}); // Compile the artifacts to a temporary directory. - const tmp_artifact_directory: Directory = d: { + const tmp_artifact_directory: Cache.Directory = d: { const s = std.fs.path.sep_str; tmp_dir_rand_int = std.crypto.random.int(u64); const tmp_dir_sub_path = "tmp" ++ s ++ std.fmt.hex(tmp_dir_rand_int); - const path = try comp.local_cache_directory.join(gpa, &.{tmp_dir_sub_path}); + const path = try comp.dirs.local_cache.join(gpa, &.{tmp_dir_sub_path}); errdefer gpa.free(path); - const handle = try comp.local_cache_directory.handle.makeOpenPath(tmp_dir_sub_path, .{}); + const handle = try comp.dirs.local_cache.handle.makeOpenPath(tmp_dir_sub_path, .{}); errdefer handle.close(); break :d .{ @@ -2243,7 +2645,7 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) !void { } if (whole.bin_sub_path) |sub_path| { - const emit: Path = .{ + const emit: Cache.Path = .{ .root_dir = tmp_artifact_directory, .sub_path = std.fs.path.basename(sub_path), }; @@ -2265,26 +2667,22 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) !void { // For compiling C objects, we rely on the cache hash system to avoid duplicating work. // Add a Job for each C object. try comp.c_object_work_queue.ensureUnusedCapacity(comp.c_object_table.count()); - for (comp.c_object_table.keys()) |key| { - comp.c_object_work_queue.writeItemAssumeCapacity(key); - } - if (comp.file_system_inputs) |fsi| { - for (comp.c_object_table.keys()) |c_object| { - try comp.appendFileSystemInput(fsi, Cache.Path.cwd(), c_object.src.src_path); - } + for (comp.c_object_table.keys()) |c_object| { + comp.c_object_work_queue.writeItemAssumeCapacity(c_object); + try comp.appendFileSystemInput(try .fromUnresolved(arena, comp.dirs, &.{c_object.src.src_path})); } // For compiling Win32 resources, we rely on the cache hash system to avoid duplicating work. // Add a Job for each Win32 resource file. try comp.win32_resource_work_queue.ensureUnusedCapacity(comp.win32_resource_table.count()); - for (comp.win32_resource_table.keys()) |key| { - comp.win32_resource_work_queue.writeItemAssumeCapacity(key); - } - if (comp.file_system_inputs) |fsi| { - for (comp.win32_resource_table.keys()) |win32_resource| switch (win32_resource.src) { - .rc => |f| try comp.appendFileSystemInput(fsi, Cache.Path.cwd(), f.src_path), - .manifest => continue, - }; + for (comp.win32_resource_table.keys()) |win32_resource| { + comp.win32_resource_work_queue.writeItemAssumeCapacity(win32_resource); + switch (win32_resource.src) { + .rc => |f| { + try comp.appendFileSystemInput(try .fromUnresolved(arena, comp.dirs, &.{f.src_path})); + }, + .manifest => {}, + } } if (comp.zcu) |zcu| { @@ -2293,69 +2691,26 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) !void { zcu.skip_analysis_this_update = false; - // Make sure std.zig is inside the import_table. We unconditionally need - // it for start.zig. - const std_mod = zcu.std_mod; - _ = try pt.importPkg(std_mod); - - // Normally we rely on importing std to in turn import the root source file - // in the start code, but when using the stage1 backend that won't happen, - // so in order to run AstGen on the root source file we put it into the - // import_table here. - // Likewise, in the case of `zig test`, the test runner is the root source file, - // and so there is nothing to import the main file. - if (comp.config.is_test) { - _ = try pt.importPkg(zcu.main_mod); - } - - if (zcu.root_mod.deps.get("ubsan_rt")) |ubsan_rt_mod| { - _ = try pt.importPkg(ubsan_rt_mod); - } - - if (zcu.root_mod.deps.get("compiler_rt")) |compiler_rt_mod| { - _ = try pt.importPkg(compiler_rt_mod); - } - - // Put a work item in for every known source file to detect if - // it changed, and, if so, re-compute ZIR and then queue the job - // to update it. - try comp.astgen_work_queue.ensureUnusedCapacity(zcu.import_table.count()); - for (zcu.import_table.values()) |file_index| { - if (zcu.fileByIndex(file_index).mod.isBuiltin()) continue; - comp.astgen_work_queue.writeItemAssumeCapacity(file_index); - } - if (comp.file_system_inputs) |fsi| { - for (zcu.import_table.values()) |file_index| { - const file = zcu.fileByIndex(file_index); - try comp.appendFileSystemInput(fsi, file.mod.root, file.sub_file_path); - } - } - - if (comp.file_system_inputs) |fsi| { - const ip = &zcu.intern_pool; - for (zcu.embed_table.values()) |embed_file| { - const sub_file_path = embed_file.sub_file_path.toSlice(ip); - try comp.appendFileSystemInput(fsi, embed_file.owner.root, sub_file_path); - } + // TODO: doing this in `resolveReferences` later could avoid adding inputs for dead embedfiles. Investigate! + for (zcu.embed_table.keys()) |embed_file| { + try comp.appendFileSystemInput(embed_file.path); } zcu.analysis_roots.clear(); - try comp.queueJob(.{ .analyze_mod = std_mod }); - zcu.analysis_roots.appendAssumeCapacity(std_mod); + zcu.analysis_roots.appendAssumeCapacity(zcu.std_mod); - if (comp.config.is_test and zcu.main_mod != std_mod) { - try comp.queueJob(.{ .analyze_mod = zcu.main_mod }); + // Normally we rely on importing std to in turn import the root source file in the start code. + // However, the main module is distinct from the root module in tests, so that won't happen there. + if (comp.config.is_test and zcu.main_mod != zcu.std_mod) { zcu.analysis_roots.appendAssumeCapacity(zcu.main_mod); } if (zcu.root_mod.deps.get("compiler_rt")) |compiler_rt_mod| { - try comp.queueJob(.{ .analyze_mod = compiler_rt_mod }); zcu.analysis_roots.appendAssumeCapacity(compiler_rt_mod); } if (zcu.root_mod.deps.get("ubsan_rt")) |ubsan_rt_mod| { - try comp.queueJob(.{ .analyze_mod = ubsan_rt_mod }); zcu.analysis_roots.appendAssumeCapacity(ubsan_rt_mod); } } @@ -2451,13 +2806,13 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) !void { break :w .no; }; - renameTmpIntoCache(comp.local_cache_directory, tmp_dir_sub_path, o_sub_path) catch |err| { + renameTmpIntoCache(comp.dirs.local_cache, tmp_dir_sub_path, o_sub_path) catch |err| { return comp.setMiscFailure( .rename_results, "failed to rename compilation results ('{}{s}') into local cache ('{}{s}'): {s}", .{ - comp.local_cache_directory, tmp_dir_sub_path, - comp.local_cache_directory, o_sub_path, + comp.dirs.local_cache, tmp_dir_sub_path, + comp.dirs.local_cache, o_sub_path, @errorName(err), }, ); @@ -2470,7 +2825,7 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) !void { // references object file paths. if (comp.bin_file) |lf| { lf.emit = .{ - .root_dir = comp.local_cache_directory, + .root_dir = comp.dirs.local_cache, .sub_path = whole.bin_sub_path.?, }; @@ -2486,7 +2841,7 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) !void { } try flush(comp, arena, .{ - .root_dir = comp.local_cache_directory, + .root_dir = comp.dirs.local_cache, .sub_path = o_sub_path, }, .main, main_progress_node); @@ -2515,34 +2870,36 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) !void { } } -pub fn appendFileSystemInput( - comp: *Compilation, - file_system_inputs: *std.ArrayListUnmanaged(u8), - root: Cache.Path, - sub_file_path: []const u8, -) Allocator.Error!void { +pub fn appendFileSystemInput(comp: *Compilation, path: Compilation.Path) Allocator.Error!void { const gpa = comp.gpa; + const fsi = comp.file_system_inputs orelse return; const prefixes = comp.cache_parent.prefixes(); - try file_system_inputs.ensureUnusedCapacity(gpa, root.sub_path.len + sub_file_path.len + 3); - if (file_system_inputs.items.len > 0) file_system_inputs.appendAssumeCapacity(0); - for (prefixes, 1..) |prefix_directory, i| { - if (prefix_directory.eql(root.root_dir)) { - file_system_inputs.appendAssumeCapacity(@intCast(i)); - if (root.sub_path.len > 0) { - file_system_inputs.appendSliceAssumeCapacity(root.sub_path); - file_system_inputs.appendAssumeCapacity(std.fs.path.sep); - } - file_system_inputs.appendSliceAssumeCapacity(sub_file_path); - return; + + const want_prefix_dir: Cache.Directory = switch (path.root) { + .zig_lib => comp.dirs.zig_lib, + .global_cache => comp.dirs.global_cache, + .local_cache => comp.dirs.local_cache, + .none => .cwd(), + }; + const prefix: u8 = for (prefixes, 1..) |prefix_dir, i| { + if (prefix_dir.eql(want_prefix_dir)) { + break @intCast(i); } - } - std.debug.panic("missing prefix directory: {}, {s}", .{ root, sub_file_path }); + } else std.debug.panic( + "missing prefix directory '{s}' ('{}') for '{s}'", + .{ @tagName(path.root), want_prefix_dir, path.sub_path }, + ); + + try fsi.ensureUnusedCapacity(gpa, path.sub_path.len + 3); + if (fsi.items.len > 0) fsi.appendAssumeCapacity(0); + fsi.appendAssumeCapacity(prefix); + fsi.appendSliceAssumeCapacity(path.sub_path); } fn flush( comp: *Compilation, arena: Allocator, - default_artifact_directory: Path, + default_artifact_directory: Cache.Path, tid: Zcu.PerThread.Id, prog_node: std.Progress.Node, ) !void { @@ -2574,7 +2931,7 @@ fn flush( /// implementation at the bottom of this function. /// This function is only called when CacheMode is `whole`. fn renameTmpIntoCache( - cache_directory: Compilation.Directory, + cache_directory: Cache.Directory, tmp_dir_sub_path: []const u8, o_sub_path: []const u8, ) !void { @@ -2627,7 +2984,7 @@ fn wholeCacheModeSetBinFilePath( @memcpy(sub_path[digest_start..][0..digest.len], digest); comp.implib_emit = .{ - .root_dir = comp.local_cache_directory, + .root_dir = comp.dirs.local_cache, .sub_path = sub_path, }; } @@ -2636,7 +2993,7 @@ fn wholeCacheModeSetBinFilePath( @memcpy(sub_path[digest_start..][0..digest.len], digest); comp.docs_emit = .{ - .root_dir = comp.local_cache_directory, + .root_dir = comp.dirs.local_cache, .sub_path = sub_path, }; } @@ -2661,19 +3018,17 @@ fn addNonIncrementalStuffToCacheManifest( arena: Allocator, man: *Cache.Manifest, ) !void { - const gpa = comp.gpa; - comptime assert(link_hash_implementation_version == 14); - if (comp.zcu) |mod| { - try addModuleTableToCacheHash(gpa, arena, &man.hash, mod.root_mod, mod.main_mod, .{ .files = man }); + if (comp.zcu) |zcu| { + try addModuleTableToCacheHash(zcu, arena, &man.hash, .{ .files = man }); // Synchronize with other matching comments: ZigOnlyHashStuff man.hash.addListOfBytes(comp.test_filters); man.hash.addOptionalBytes(comp.test_name_prefix); man.hash.add(comp.skip_linker_dependencies); - //man.hash.add(mod.emit_h != null); - man.hash.add(mod.error_limit); + //man.hash.add(zcu.emit_h != null); + man.hash.add(zcu.error_limit); } else { cache_helpers.addModule(&man.hash, comp.root_mod); } @@ -2839,7 +3194,7 @@ fn emitOthers(comp: *Compilation) void { pub fn emitLlvmObject( comp: *Compilation, arena: Allocator, - default_artifact_directory: Path, + default_artifact_directory: Cache.Path, bin_emit_loc: ?EmitLoc, llvm_object: LlvmObject.Ptr, prog_node: std.Progress.Node, @@ -2866,7 +3221,7 @@ pub fn emitLlvmObject( fn resolveEmitLoc( arena: Allocator, - default_artifact_directory: Path, + default_artifact_directory: Cache.Path, opt_loc: ?EmitLoc, ) Allocator.Error!?[*:0]const u8 { const loc = opt_loc orelse return null; @@ -2877,132 +3232,6 @@ fn resolveEmitLoc( return slice.ptr; } -fn reportMultiModuleErrors(pt: Zcu.PerThread) !void { - const zcu = pt.zcu; - const gpa = zcu.gpa; - const ip = &zcu.intern_pool; - // Some cases can give you a whole bunch of multi-module errors, which it's not helpful to - // print all of, so we'll cap the number of these to emit. - var num_errors: u32 = 0; - const max_errors = 5; - // Attach the "some omitted" note to the final error message - var last_err: ?*Zcu.ErrorMsg = null; - - for (zcu.import_table.values()) |file_index| { - const file = zcu.fileByIndex(file_index); - if (!file.multi_pkg) continue; - - num_errors += 1; - if (num_errors > max_errors) continue; - - const err = err_blk: { - // Like with errors, let's cap the number of notes to prevent a huge error spew. - const max_notes = 5; - const omitted = file.references.items.len -| max_notes; - const num_notes = file.references.items.len - omitted; - - const notes = try gpa.alloc(Zcu.ErrorMsg, if (omitted > 0) num_notes + 1 else num_notes); - errdefer gpa.free(notes); - - for (notes[0..num_notes], file.references.items[0..num_notes], 0..) |*note, ref, i| { - errdefer for (notes[0..i]) |*n| n.deinit(gpa); - note.* = switch (ref) { - .import => |import| try Zcu.ErrorMsg.init( - gpa, - .{ - .base_node_inst = try ip.trackZir(gpa, pt.tid, .{ - .file = import.file, - .inst = .main_struct_inst, - }), - .offset = .{ .token_abs = import.token }, - }, - "imported from module {s}", - .{zcu.fileByIndex(import.file).mod.fully_qualified_name}, - ), - .root => |pkg| try Zcu.ErrorMsg.init( - gpa, - .{ - .base_node_inst = try ip.trackZir(gpa, pt.tid, .{ - .file = file_index, - .inst = .main_struct_inst, - }), - .offset = .entire_file, - }, - "root of module {s}", - .{pkg.fully_qualified_name}, - ), - }; - } - errdefer for (notes[0..num_notes]) |*n| n.deinit(gpa); - - if (omitted > 0) { - notes[num_notes] = try Zcu.ErrorMsg.init( - gpa, - .{ - .base_node_inst = try ip.trackZir(gpa, pt.tid, .{ - .file = file_index, - .inst = .main_struct_inst, - }), - .offset = .entire_file, - }, - "{} more references omitted", - .{omitted}, - ); - } - errdefer if (omitted > 0) notes[num_notes].deinit(gpa); - - const err = try Zcu.ErrorMsg.create( - gpa, - .{ - .base_node_inst = try ip.trackZir(gpa, pt.tid, .{ - .file = file_index, - .inst = .main_struct_inst, - }), - .offset = .entire_file, - }, - "file exists in multiple modules", - .{}, - ); - err.notes = notes; - break :err_blk err; - }; - errdefer err.destroy(gpa); - try zcu.failed_files.putNoClobber(gpa, file, err); - last_err = err; - } - - // If we omitted any errors, add a note saying that - if (num_errors > max_errors) { - const err = last_err.?; - - // There isn't really any meaningful place to put this note, so just attach it to the - // last failed file - var note = try Zcu.ErrorMsg.init( - gpa, - err.src_loc, - "{} more errors omitted", - .{num_errors - max_errors}, - ); - errdefer note.deinit(gpa); - - const i = err.notes.len; - err.notes = try gpa.realloc(err.notes, i + 1); - err.notes[i] = note; - } - - // Now that we've reported the errors, we need to deal with - // dependencies. Any file referenced by a multi_pkg file should also be - // marked multi_pkg and have its status set to astgen_failure, as it's - // ambiguous which package they should be analyzed as a part of. We need - // to add this flag after reporting the errors however, as otherwise - // we'd get an error for every single downstream file, which wouldn't be - // very useful. - for (zcu.import_table.values()) |file_index| { - const file = zcu.fileByIndex(file_index); - if (file.multi_pkg) file.recursiveMarkMultiPkg(pt); - } -} - /// Having the file open for writing is problematic as far as executing the /// binary is concerned. This will remove the write flag, or close the file, /// or whatever is needed so that it can be executed. @@ -3326,16 +3555,77 @@ pub fn getAllErrorsAlloc(comp: *Compilation) !ErrorBundle { } if (comp.zcu) |zcu| zcu_errors: { - for (zcu.failed_files.keys(), zcu.failed_files.values()) |file, error_msg| { + if (zcu.multi_module_err != null) { + try zcu.addFileInMultipleModulesError(&bundle); + break :zcu_errors; + } + for (zcu.failed_imports.items) |failed| { + assert(zcu.alive_files.contains(failed.file_index)); // otherwise it wouldn't have been added + const file = zcu.fileByIndex(failed.file_index); + const source = try file.getSource(zcu); + const tree = try file.getTree(zcu); + const start = tree.tokenStart(failed.import_token); + const end = start + tree.tokenSlice(failed.import_token).len; + const loc = std.zig.findLineColumn(source.bytes, start); + try bundle.addRootErrorMessage(.{ + .msg = switch (failed.kind) { + .file_outside_module_root => try bundle.addString("import of file outside module path"), + .illegal_zig_import => try bundle.addString("this compiler implementation does not allow importing files from this directory"), + }, + .src_loc = try bundle.addSourceLocation(.{ + .src_path = try bundle.printString("{}", .{file.path.fmt(comp)}), + .span_start = start, + .span_main = start, + .span_end = @intCast(end), + .line = @intCast(loc.line), + .column = @intCast(loc.column), + .source_line = try bundle.addString(loc.source_line), + }), + .notes_len = 0, + }); + } + + // Before iterating `failed_files`, we need to sort it into a consistent order so that error + // messages appear consistently despite different ordering from the AstGen worker pool. File + // paths are a great key for this sort! We are using sorting the `ArrayHashMap` itself to + // make sure it reindexes; that's important because these entries need to be retained for + // future updates. + const FileSortCtx = struct { + zcu: *Zcu, + failed_files_keys: []const Zcu.File.Index, + pub fn lessThan(ctx: @This(), lhs_index: usize, rhs_index: usize) bool { + const lhs_path = ctx.zcu.fileByIndex(ctx.failed_files_keys[lhs_index]).path; + const rhs_path = ctx.zcu.fileByIndex(ctx.failed_files_keys[rhs_index]).path; + if (lhs_path.root != rhs_path.root) return @intFromEnum(lhs_path.root) < @intFromEnum(rhs_path.root); + return std.mem.order(u8, lhs_path.sub_path, rhs_path.sub_path).compare(.lt); + } + }; + zcu.failed_files.sort(@as(FileSortCtx, .{ + .zcu = zcu, + .failed_files_keys = zcu.failed_files.keys(), + })); + + for (zcu.failed_files.keys(), zcu.failed_files.values()) |file_index, error_msg| { + if (!zcu.alive_files.contains(file_index)) continue; + const file = zcu.fileByIndex(file_index); + const is_retryable = switch (file.status) { + .retryable_failure => true, + .success, .astgen_failure => false, + .never_loaded => unreachable, + }; if (error_msg) |msg| { - try addModuleErrorMsg(zcu, &bundle, msg.*, false); + assert(is_retryable); + try addWholeFileError(zcu, &bundle, file_index, msg); } else { - // Must be ZIR or Zoir errors. Note that this may include AST errors. - _ = try file.getTree(gpa); // Tree must be loaded. + assert(!is_retryable); + // AstGen/ZoirGen succeeded with errors. Note that this may include AST errors. + _ = try file.getTree(zcu); // Tree must be loaded. + const path = try std.fmt.allocPrint(gpa, "{}", .{file.path.fmt(comp)}); + defer gpa.free(path); if (file.zir != null) { - try addZirErrorMessages(&bundle, file); + try bundle.addZirErrorMessages(file.zir.?, file.tree.?, file.source.?, path); } else if (file.zoir != null) { - try addZoirErrorMessages(&bundle, file); + try bundle.addZoirErrorMessages(file.zoir.?, file.tree.?, file.source.?, path); } else { // Either Zir or Zoir must have been loaded. unreachable; @@ -3646,20 +3936,16 @@ pub fn addModuleErrorMsg( const gpa = eb.gpa; const ip = &zcu.intern_pool; const err_src_loc = module_err_msg.src_loc.upgrade(zcu); - const err_source = err_src_loc.file_scope.getSource(gpa) catch |err| { - const file_path = try err_src_loc.file_scope.fullPath(gpa); - defer gpa.free(file_path); + const err_source = err_src_loc.file_scope.getSource(zcu) catch |err| { try eb.addRootErrorMessage(.{ - .msg = try eb.printString("unable to load '{s}': {s}", .{ - file_path, @errorName(err), + .msg = try eb.printString("unable to load '{}': {s}", .{ + err_src_loc.file_scope.path.fmt(zcu.comp), @errorName(err), }), }); return; }; - const err_span = try err_src_loc.span(gpa); + const err_span = try err_src_loc.span(zcu); const err_loc = std.zig.findLineColumn(err_source.bytes, err_span.main); - const file_path = try err_src_loc.file_scope.fullPath(gpa); - defer gpa.free(file_path); var ref_traces: std.ArrayListUnmanaged(ErrorBundle.ReferenceTrace) = .empty; defer ref_traces.deinit(gpa); @@ -3715,16 +4001,13 @@ pub fn addModuleErrorMsg( } const src_loc = try eb.addSourceLocation(.{ - .src_path = try eb.addString(file_path), + .src_path = try eb.printString("{}", .{err_src_loc.file_scope.path.fmt(zcu.comp)}), .span_start = err_span.start, .span_main = err_span.main, .span_end = err_span.end, .line = @intCast(err_loc.line), .column = @intCast(err_loc.column), - .source_line = if (err_src_loc.lazy == .entire_file) - 0 - else - try eb.addString(err_loc.source_line), + .source_line = try eb.addString(err_loc.source_line), .reference_trace_len = @intCast(ref_traces.items.len), }); @@ -3740,11 +4023,9 @@ pub fn addModuleErrorMsg( var last_note_loc: ?std.zig.Loc = null; for (module_err_msg.notes) |module_note| { const note_src_loc = module_note.src_loc.upgrade(zcu); - const source = try note_src_loc.file_scope.getSource(gpa); - const span = try note_src_loc.span(gpa); + const source = try note_src_loc.file_scope.getSource(zcu); + const span = try note_src_loc.span(zcu); const loc = std.zig.findLineColumn(source.bytes, span.main); - const note_file_path = try note_src_loc.file_scope.fullPath(gpa); - defer gpa.free(note_file_path); const omit_source_line = loc.eql(err_loc) or (last_note_loc != null and loc.eql(last_note_loc.?)); last_note_loc = loc; @@ -3752,7 +4033,7 @@ pub fn addModuleErrorMsg( const gop = try notes.getOrPutContext(gpa, .{ .msg = try eb.addString(module_note.msg), .src_loc = try eb.addSourceLocation(.{ - .src_path = try eb.addString(note_file_path), + .src_path = try eb.printString("{}", .{note_src_loc.file_scope.path.fmt(zcu.comp)}), .span_start = span.start, .span_main = span.main, .span_end = span.end, @@ -3791,15 +4072,13 @@ fn addReferenceTraceFrame( ) !void { const gpa = zcu.gpa; const src = lazy_src.upgrade(zcu); - const source = try src.file_scope.getSource(gpa); - const span = try src.span(gpa); + const source = try src.file_scope.getSource(zcu); + const span = try src.span(zcu); const loc = std.zig.findLineColumn(source.bytes, span.main); - const rt_file_path = try src.file_scope.fullPath(gpa); - defer gpa.free(rt_file_path); try ref_traces.append(gpa, .{ .decl_name = try eb.printString("{s}{s}", .{ name, if (inlined) " [inlined]" else "" }), .src_loc = try eb.addSourceLocation(.{ - .src_path = try eb.addString(rt_file_path), + .src_path = try eb.printString("{}", .{src.file_scope.path.fmt(zcu.comp)}), .span_start = span.start, .span_main = span.main, .span_end = span.end, @@ -3810,18 +4089,30 @@ fn addReferenceTraceFrame( }); } -pub fn addZirErrorMessages(eb: *ErrorBundle.Wip, file: *Zcu.File) !void { - const gpa = eb.gpa; - const src_path = try file.fullPath(gpa); - defer gpa.free(src_path); - return eb.addZirErrorMessages(file.zir.?, file.tree.?, file.source.?, src_path); -} +pub fn addWholeFileError( + zcu: *Zcu, + eb: *ErrorBundle.Wip, + file_index: Zcu.File.Index, + msg: []const u8, +) !void { + // note: "file imported here" on the import reference token + const imported_note: ?ErrorBundle.MessageIndex = switch (zcu.alive_files.get(file_index).?) { + .analysis_root => null, + .import => |import| try eb.addErrorMessage(.{ + .msg = try eb.addString("file imported here"), + .src_loc = try zcu.fileByIndex(import.importer).errorBundleTokenSrc(import.tok, zcu, eb), + }), + }; -pub fn addZoirErrorMessages(eb: *ErrorBundle.Wip, file: *Zcu.File) !void { - const gpa = eb.gpa; - const src_path = try file.fullPath(gpa); - defer gpa.free(src_path); - return eb.addZoirErrorMessages(file.zoir.?, file.tree.?, file.source.?, src_path); + try eb.addRootErrorMessage(.{ + .msg = try eb.addString(msg), + .src_loc = try zcu.fileByIndex(file_index).errorBundleWholeFileSrc(zcu, eb), + .notes_len = if (imported_note != null) 1 else 0, + }); + if (imported_note) |n| { + const note_idx = try eb.reserveNotes(1); + eb.extra.items[note_idx] = @intFromEnum(n); + } } pub fn performAllTheWork( @@ -3966,51 +4257,48 @@ fn performAllTheWorkInner( var astgen_wait_group: WaitGroup = .{}; defer astgen_wait_group.wait(); - // builtin.zig is handled specially for two reasons: - // 1. to avoid race condition of zig processes truncating each other's builtin.zig files - // 2. optimization; in the hot path it only incurs a stat() syscall, which happens - // in the `astgen_wait_group`. - if (comp.queued_jobs.update_builtin_zig) b: { - comp.queued_jobs.update_builtin_zig = false; - if (comp.zcu == null) break :b; - // TODO put all the modules in a flat array to make them easy to iterate. - var seen: std.AutoArrayHashMapUnmanaged(*Package.Module, void) = .empty; - defer seen.deinit(comp.gpa); - try seen.put(comp.gpa, comp.root_mod, {}); - var i: usize = 0; - while (i < seen.count()) : (i += 1) { - const mod = seen.keys()[i]; - for (mod.deps.values()) |dep| - try seen.put(comp.gpa, dep, {}); - - const file = mod.builtin_file orelse continue; - - comp.thread_pool.spawnWg(&astgen_wait_group, workerUpdateBuiltinZigFile, .{ - comp, mod, file, + if (comp.zcu) |zcu| { + const gpa = zcu.gpa; + + // We cannot reference `zcu.import_table` after we spawn any `workerUpdateFile` jobs, + // because on single-threaded targets the worker will be run eagerly, meaning the + // `import_table` could be mutated, and not even holding `comp.mutex` will save us. So, + // build up a list of the files to update *before* we spawn any jobs. + var astgen_work_items: std.MultiArrayList(struct { + file_index: Zcu.File.Index, + file: *Zcu.File, + }) = .empty; + defer astgen_work_items.deinit(gpa); + // Not every item in `import_table` will need updating, because some are builtin.zig + // files. However, most will, so let's just reserve sufficient capacity upfront. + try astgen_work_items.ensureTotalCapacity(gpa, zcu.import_table.count()); + for (zcu.import_table.keys()) |file_index| { + const file = zcu.fileByIndex(file_index); + if (file.is_builtin) { + // This is a `builtin.zig`, so updating is redundant. However, we want to make + // sure the file contents are still correct on disk, since it can improve the + // debugging experience better. That job only needs `file`, so we can kick it + // off right now. + comp.thread_pool.spawnWg(&astgen_wait_group, workerUpdateBuiltinFile, .{ comp, file }); + continue; + } + astgen_work_items.appendAssumeCapacity(.{ + .file_index = file_index, + .file = file, }); } - } - if (comp.zcu) |zcu| { - { - // Worker threads may append to zcu.files and zcu.import_table - // so we must hold the lock while spawning those tasks, since - // we access those tables in this loop. - comp.mutex.lock(); - defer comp.mutex.unlock(); - - while (comp.astgen_work_queue.readItem()) |file_index| { - // Pre-load these things from our single-threaded context since they - // will be needed by the worker threads. - const path_digest = zcu.filePathDigest(file_index); - const file = zcu.fileByIndex(file_index); - comp.thread_pool.spawnWgId(&astgen_wait_group, workerUpdateFile, .{ - comp, file, file_index, path_digest, zir_prog_node, &astgen_wait_group, .root, - }); - } + // Now that we're not going to touch `zcu.import_table` again, we can spawn `workerUpdateFile` jobs. + for (astgen_work_items.items(.file_index), astgen_work_items.items(.file)) |file_index, file| { + comp.thread_pool.spawnWgId(&astgen_wait_group, workerUpdateFile, .{ + comp, file, file_index, zir_prog_node, &astgen_wait_group, + }); } - for (0.., zcu.embed_table.values()) |ef_index_usize, ef| { + // On the other hand, it's fine to directly iterate `zcu.embed_table.keys()` here + // because `workerUpdateEmbedFile` can't invalidate it. The different here is that one + // `@embedFile` can't trigger analysis of a new `@embedFile`! + for (0.., zcu.embed_table.keys()) |ef_index_usize, ef| { const ef_index: Zcu.EmbedFile.Index = @enumFromInt(ef_index_usize); comp.thread_pool.spawnWgId(&astgen_wait_group, workerUpdateEmbedFile, .{ comp, ef_index, ef, @@ -4035,25 +4323,39 @@ fn performAllTheWorkInner( const pt: Zcu.PerThread = .activate(zcu, .main); defer pt.deactivate(); - // If the cache mode is `whole`, then add every source file to the cache manifest. + const gpa = zcu.gpa; + + // On an incremental update, a source file might become "dead", in that all imports of + // the file were removed. This could even change what module the file belongs to! As such, + // we do a traversal over the files, to figure out which ones are alive and the modules + // they belong to. + const any_fatal_files = try pt.computeAliveFiles(); + + // If the cache mode is `whole`, add every alive source file to the manifest. switch (comp.cache_use) { .whole => |whole| if (whole.cache_manifest) |man| { - const gpa = zcu.gpa; - for (zcu.import_table.values()) |file_index| { + for (zcu.alive_files.keys()) |file_index| { const file = zcu.fileByIndex(file_index); - const source = file.getSource(gpa) catch |err| { - try pt.reportRetryableFileError(file_index, "unable to load source: {s}", .{@errorName(err)}); - continue; + + switch (file.status) { + .never_loaded => unreachable, // AstGen tried to load it + .retryable_failure => continue, // the file cannot be read; this is a guaranteed error + .astgen_failure, .success => {}, // the file was read successfully + } + + const path = try file.path.toAbsolute(comp.dirs, gpa); + defer gpa.free(path); + + const result = res: { + whole.cache_manifest_mutex.lock(); + defer whole.cache_manifest_mutex.unlock(); + if (file.source) |source| { + break :res man.addFilePostContents(path, source, file.stat); + } else { + break :res man.addFilePost(path); + } }; - const resolved_path = try std.fs.path.resolve(gpa, &.{ - file.mod.root.root_dir.path orelse ".", - file.mod.root.sub_path, - file.sub_file_path, - }); - errdefer gpa.free(resolved_path); - whole.cache_manifest_mutex.lock(); - defer whole.cache_manifest_mutex.unlock(); - man.addFilePostContents(resolved_path, source.bytes, source.stat) catch |err| switch (err) { + result catch |err| switch (err) { error.OutOfMemory => |e| return e, else => { try pt.reportRetryableFileError(file_index, "unable to update cache: {s}", .{@errorName(err)}); @@ -4065,23 +4367,14 @@ fn performAllTheWorkInner( .incremental => {}, } - try reportMultiModuleErrors(pt); - - const any_fatal_files = for (zcu.import_table.values()) |file_index| { - const file = zcu.fileByIndex(file_index); - switch (file.status) { - .never_loaded => unreachable, // everything is loaded by the workers - .retryable_failure, .astgen_failure => break true, - .success => {}, - } - } else false; - - if (any_fatal_files or comp.alloc_failure_occurred) { + if (any_fatal_files or + zcu.multi_module_err != null or + zcu.failed_imports.items.len > 0 or + comp.alloc_failure_occurred) + { // We give up right now! No updating of ZIR refs, no nothing. The idea is that this prevents // us from invalidating lots of incremental dependencies due to files with e.g. parse errors. // However, this means our analysis data is invalid, so we want to omit all analysis errors. - - assert(zcu.failed_files.count() > 0); // we will get an error zcu.skip_analysis_this_update = true; return; } @@ -4093,6 +4386,11 @@ fn performAllTheWorkInner( } try zcu.flushRetryableFailures(); + // It's analysis time! Queue up our initial analysis. + for (zcu.analysis_roots.slice()) |mod| { + try comp.queueJob(.{ .analyze_mod = mod }); + } + zcu.sema_prog_node = main_progress_node.start("Semantic Analysis", 0); zcu.codegen_prog_node = if (comp.bin_file != null) main_progress_node.start("Code Generation", 0) else .none; } @@ -4236,7 +4534,7 @@ fn processOneJob(tid: usize, comp: *Compilation, job: Job) JobError!void { const pt: Zcu.PerThread = .activate(comp.zcu.?, @enumFromInt(tid)); defer pt.deactivate(); - pt.semaPkg(mod) catch |err| switch (err) { + pt.semaMod(mod) catch |err| switch (err) { error.OutOfMemory => return error.OutOfMemory, error.AnalysisFail => return, }; @@ -4301,7 +4599,7 @@ fn docsCopyFallible(comp: *Compilation) anyerror!void { for (&[_][]const u8{ "docs/main.js", "docs/index.html" }) |sub_path| { const basename = std.fs.path.basename(sub_path); - comp.zig_lib_directory.handle.copyFile(sub_path, out_dir, basename, .{}) catch |err| { + comp.dirs.zig_lib.handle.copyFile(sub_path, out_dir, basename, .{}) catch |err| { comp.lockAndSetMiscFailure(.docs_copy, "unable to copy {s}: {s}", .{ sub_path, @errorName(err), @@ -4338,10 +4636,12 @@ fn docsCopyFallible(comp: *Compilation) anyerror!void { fn docsCopyModule(comp: *Compilation, module: *Package.Module, name: []const u8, tar_file: std.fs.File) !void { const root = module.root; - const sub_path = if (root.sub_path.len == 0) "." else root.sub_path; - var mod_dir = root.root_dir.handle.openDir(sub_path, .{ .iterate = true }) catch |err| { + var mod_dir = d: { + const root_dir, const sub_path = root.openInfo(comp.dirs); + break :d root_dir.openDir(sub_path, .{ .iterate = true }); + } catch |err| { return comp.lockAndSetMiscFailure(.docs_copy, "unable to open directory '{}': {s}", .{ - root, @errorName(err), + root.fmt(comp), @errorName(err), }); }; defer mod_dir.close(); @@ -4363,13 +4663,13 @@ fn docsCopyModule(comp: *Compilation, module: *Package.Module, name: []const u8, } var file = mod_dir.openFile(entry.path, .{}) catch |err| { return comp.lockAndSetMiscFailure(.docs_copy, "unable to open '{}{s}': {s}", .{ - root, entry.path, @errorName(err), + root.fmt(comp), entry.path, @errorName(err), }); }; defer file.close(); archiver.writeFile(entry.path, file) catch |err| { return comp.lockAndSetMiscFailure(.docs_copy, "unable to archive '{}{s}': {s}", .{ - root, entry.path, @errorName(err), + root.fmt(comp), entry.path, @errorName(err), }); }; } @@ -4430,13 +4730,11 @@ fn workerDocsWasmFallible(comp: *Compilation, prog_node: std.Progress.Node) anye const src_basename = "main.zig"; const root_name = std.fs.path.stem(src_basename); + const dirs = comp.dirs.withoutLocalCache(); + const root_mod = try Package.Module.create(arena, .{ - .global_cache_directory = comp.global_cache_directory, .paths = .{ - .root = .{ - .root_dir = comp.zig_lib_directory, - .sub_path = "docs/wasm", - }, + .root = try .fromRoot(arena, dirs, .zig_lib, "docs/wasm"), .root_src_path = src_basename, }, .fully_qualified_name = root_name, @@ -4447,16 +4745,10 @@ fn workerDocsWasmFallible(comp: *Compilation, prog_node: std.Progress.Node) anye .global = config, .cc_argv = &.{}, .parent = null, - .builtin_mod = null, - .builtin_modules = null, }); const walk_mod = try Package.Module.create(arena, .{ - .global_cache_directory = comp.global_cache_directory, .paths = .{ - .root = .{ - .root_dir = comp.zig_lib_directory, - .sub_path = "docs/wasm", - }, + .root = try .fromRoot(arena, dirs, .zig_lib, "docs/wasm"), .root_src_path = "Walk.zig", }, .fully_qualified_name = "Walk", @@ -4467,8 +4759,6 @@ fn workerDocsWasmFallible(comp: *Compilation, prog_node: std.Progress.Node) anye .global = config, .cc_argv = &.{}, .parent = root_mod, - .builtin_mod = root_mod.getBuiltinDependency(), - .builtin_modules = null, // `builtin_mod` is set }); try root_mod.deps.put(arena, "Walk", walk_mod); const bin_basename = try std.zig.binNameAlloc(arena, .{ @@ -4478,9 +4768,7 @@ fn workerDocsWasmFallible(comp: *Compilation, prog_node: std.Progress.Node) anye }); const sub_compilation = try Compilation.create(gpa, arena, .{ - .global_cache_directory = comp.global_cache_directory, - .local_cache_directory = comp.global_cache_directory, - .zig_lib_directory = comp.zig_lib_directory, + .dirs = dirs, .self_exe_path = comp.self_exe_path, .config = config, .root_mod = root_mod, @@ -4517,14 +4805,14 @@ fn workerDocsWasmFallible(comp: *Compilation, prog_node: std.Progress.Node) anye }; defer out_dir.close(); - sub_compilation.local_cache_directory.handle.copyFile( + sub_compilation.dirs.local_cache.handle.copyFile( sub_compilation.cache_use.whole.bin_sub_path.?, out_dir, "main.wasm", .{}, ) catch |err| { return comp.lockAndSetMiscFailure(.docs_copy, "unable to copy '{}{s}' to '{}{s}': {s}", .{ - sub_compilation.local_cache_directory, + sub_compilation.dirs.local_cache, sub_compilation.cache_use.whole.bin_sub_path.?, emit.root_dir, emit.sub_path, @@ -4538,28 +4826,23 @@ fn workerUpdateFile( comp: *Compilation, file: *Zcu.File, file_index: Zcu.File.Index, - path_digest: Cache.BinDigest, prog_node: std.Progress.Node, wg: *WaitGroup, - src: Zcu.AstGenSrc, ) void { - const child_prog_node = prog_node.start(file.sub_file_path, 0); + const child_prog_node = prog_node.start(std.fs.path.basename(file.path.sub_path), 0); defer child_prog_node.end(); const pt: Zcu.PerThread = .activate(comp.zcu.?, @enumFromInt(tid)); defer pt.deactivate(); - pt.updateFile(file, path_digest) catch |err| switch (err) { - error.AnalysisFail => return, - else => { - pt.reportRetryableAstGenError(src, file_index, err) catch |oom| switch (oom) { - error.OutOfMemory => { - comp.mutex.lock(); - defer comp.mutex.unlock(); - comp.setAllocFailure(); - }, - }; - return; - }, + pt.updateFile(file_index, file) catch |err| { + pt.reportRetryableFileError(file_index, "unable to load '{s}': {s}", .{ std.fs.path.basename(file.path.sub_path), @errorName(err) }) catch |oom| switch (oom) { + error.OutOfMemory => { + comp.mutex.lock(); + defer comp.mutex.unlock(); + comp.setAllocFailure(); + }, + }; + return; }; switch (file.getMode()) { @@ -4567,9 +4850,9 @@ fn workerUpdateFile( .zon => return, // ZON can't import anything so we're done } - // Pre-emptively look for `@import` paths and queue them up. - // If we experience an error preemptively fetching the - // file, just ignore it and let it happen again later during Sema. + // Discover all imports in the file. Imports of modules we ignore for now since we don't + // know which module we're in, but imports of file paths might need us to queue up other + // AstGen jobs. const imports_index = file.zir.?.extra[@intFromEnum(Zir.ExtraIndex.imports)]; if (imports_index != 0) { const extra = file.zir.?.extraData(Zir.Inst.Imports, imports_index); @@ -4581,54 +4864,34 @@ fn workerUpdateFile( extra_index = item.end; const import_path = file.zir.?.nullTerminatedString(item.data.name); - // `@import("builtin")` is handled specially. - if (mem.eql(u8, import_path, "builtin")) continue; - - const import_result, const imported_path_digest = blk: { - comp.mutex.lock(); - defer comp.mutex.unlock(); - const res = pt.importFile(file, import_path) catch continue; - if (!res.is_pkg) { - res.file.addReference(pt.zcu, .{ .import = .{ - .file = file_index, - .token = item.data.token, - } }) catch continue; - } - if (res.is_new) if (comp.file_system_inputs) |fsi| { - comp.appendFileSystemInput(fsi, res.file.mod.root, res.file.sub_file_path) catch continue; - }; - const imported_path_digest = pt.zcu.filePathDigest(res.file_index); - break :blk .{ res, imported_path_digest }; - }; - if (import_result.is_new) { - log.debug("AstGen of {s} has import '{s}'; queuing AstGen of {s}", .{ - file.sub_file_path, import_path, import_result.file.sub_file_path, - }); - const sub_src: Zcu.AstGenSrc = .{ .import = .{ - .importing_file = file_index, - .import_tok = item.data.token, - } }; - comp.thread_pool.spawnWgId(wg, workerUpdateFile, .{ - comp, import_result.file, import_result.file_index, imported_path_digest, prog_node, wg, sub_src, - }); + if (pt.discoverImport(file.path, import_path)) |res| switch (res) { + .module, .existing_file => {}, + .new_file => |new| { + comp.thread_pool.spawnWgId(wg, workerUpdateFile, .{ + comp, new.file, new.index, prog_node, wg, + }); + }, + } else |err| switch (err) { + error.OutOfMemory => { + comp.mutex.lock(); + defer comp.mutex.unlock(); + comp.setAllocFailure(); + }, } } } } -fn workerUpdateBuiltinZigFile( - comp: *Compilation, - mod: *Package.Module, - file: *Zcu.File, -) void { - Builtin.populateFile(comp, mod, file) catch |err| { +fn workerUpdateBuiltinFile(comp: *Compilation, file: *Zcu.File) void { + Builtin.updateFileOnDisk(file, comp) catch |err| { comp.mutex.lock(); defer comp.mutex.unlock(); - - comp.setMiscFailure(.write_builtin_zig, "unable to write '{}{s}': {s}", .{ - mod.root, mod.root_src_path, @errorName(err), - }); + comp.setMiscFailure( + .write_builtin_zig, + "unable to write '{}': {s}", + .{ file.path.fmt(comp), @errorName(err) }, + ); }; } @@ -4738,10 +5001,10 @@ pub fn cImport(comp: *Compilation, c_src: []const u8, owner_mod: *Package.Module const tmp_digest = man.hash.peek(); const tmp_dir_sub_path = try std.fs.path.join(arena, &[_][]const u8{ "o", &tmp_digest }); - var zig_cache_tmp_dir = try comp.local_cache_directory.handle.makeOpenPath(tmp_dir_sub_path, .{}); + var zig_cache_tmp_dir = try comp.dirs.local_cache.handle.makeOpenPath(tmp_dir_sub_path, .{}); defer zig_cache_tmp_dir.close(); const cimport_basename = "cimport.h"; - const out_h_path = try comp.local_cache_directory.join(arena, &[_][]const u8{ + const out_h_path = try comp.dirs.local_cache.join(arena, &[_][]const u8{ tmp_dir_sub_path, cimport_basename, }); const out_dep_path = try std.fmt.allocPrint(arena, "{s}.d", .{out_h_path}); @@ -4779,7 +5042,7 @@ pub fn cImport(comp: *Compilation, c_src: []const u8, owner_mod: *Package.Module new_argv[i] = try arena.dupeZ(u8, arg); } - const c_headers_dir_path_z = try comp.zig_lib_directory.joinZ(arena, &[_][]const u8{"include"}); + const c_headers_dir_path_z = try comp.dirs.zig_lib.joinZ(arena, &.{"include"}); var errors = std.zig.ErrorBundle.empty; errdefer errors.deinit(comp.gpa); break :tree translate_c.translate( @@ -4820,7 +5083,7 @@ pub fn cImport(comp: *Compilation, c_src: []const u8, owner_mod: *Package.Module const bin_digest = man.finalBin(); const hex_digest = Cache.binToHex(bin_digest); const o_sub_path = "o" ++ std.fs.path.sep_str ++ hex_digest; - var o_dir = try comp.local_cache_directory.handle.makeOpenPath(o_sub_path, .{}); + var o_dir = try comp.dirs.local_cache.handle.makeOpenPath(o_sub_path, .{}); defer o_dir.close(); var out_zig_file = try o_dir.createFile(cimport_zig_basename, .{}); @@ -5226,7 +5489,7 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Pr // We can't know the digest until we do the C compiler invocation, // so we need a temporary filename. const out_obj_path = try comp.tmpFilePath(arena, o_basename); - var zig_cache_tmp_dir = try comp.local_cache_directory.handle.makeOpenPath("tmp", .{}); + var zig_cache_tmp_dir = try comp.dirs.local_cache.handle.makeOpenPath("tmp", .{}); defer zig_cache_tmp_dir.close(); const out_diag_path = if (comp.clang_passthrough_mode or !ext.clangSupportsDiagnostics()) @@ -5362,7 +5625,7 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Pr // Rename into place. const digest = man.final(); const o_sub_path = try std.fs.path.join(arena, &[_][]const u8{ "o", &digest }); - var o_dir = try comp.local_cache_directory.handle.makeOpenPath(o_sub_path, .{}); + var o_dir = try comp.dirs.local_cache.handle.makeOpenPath(o_sub_path, .{}); defer o_dir.close(); const tmp_basename = std.fs.path.basename(out_obj_path); try std.fs.rename(zig_cache_tmp_dir, tmp_basename, o_dir, o_basename); @@ -5386,7 +5649,7 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Pr c_object.status = .{ .success = .{ .object_path = .{ - .root_dir = comp.local_cache_directory, + .root_dir = comp.dirs.local_cache, .sub_path = try std.fs.path.join(gpa, &.{ "o", &digest, o_basename }), }, .lock = man.toOwnedLock(), @@ -5449,13 +5712,13 @@ fn updateWin32Resource(comp: *Compilation, win32_resource: *Win32Resource, win32 const digest = man.final(); const o_sub_path = try std.fs.path.join(arena, &.{ "o", &digest }); - var o_dir = try comp.local_cache_directory.handle.makeOpenPath(o_sub_path, .{}); + var o_dir = try comp.dirs.local_cache.handle.makeOpenPath(o_sub_path, .{}); defer o_dir.close(); - const in_rc_path = try comp.local_cache_directory.join(comp.gpa, &.{ + const in_rc_path = try comp.dirs.local_cache.join(comp.gpa, &.{ o_sub_path, rc_basename, }); - const out_res_path = try comp.local_cache_directory.join(comp.gpa, &.{ + const out_res_path = try comp.dirs.local_cache.join(comp.gpa, &.{ o_sub_path, res_basename, }); @@ -5517,7 +5780,7 @@ fn updateWin32Resource(comp: *Compilation, win32_resource: *Win32Resource, win32 win32_resource.status = .{ .success = .{ - .res_path = try comp.local_cache_directory.join(comp.gpa, &[_][]const u8{ + .res_path = try comp.dirs.local_cache.join(comp.gpa, &[_][]const u8{ "o", &digest, res_basename, }), .lock = man.toOwnedLock(), @@ -5535,7 +5798,7 @@ fn updateWin32Resource(comp: *Compilation, win32_resource: *Win32Resource, win32 const rc_basename_noext = src_basename[0 .. src_basename.len - std.fs.path.extension(src_basename).len]; const digest = if (try man.hit()) man.final() else blk: { - var zig_cache_tmp_dir = try comp.local_cache_directory.handle.makeOpenPath("tmp", .{}); + var zig_cache_tmp_dir = try comp.dirs.local_cache.handle.makeOpenPath("tmp", .{}); defer zig_cache_tmp_dir.close(); const res_filename = try std.fmt.allocPrint(arena, "{s}.res", .{rc_basename_noext}); @@ -5605,7 +5868,7 @@ fn updateWin32Resource(comp: *Compilation, win32_resource: *Win32Resource, win32 // Rename into place. const digest = man.final(); const o_sub_path = try std.fs.path.join(arena, &[_][]const u8{ "o", &digest }); - var o_dir = try comp.local_cache_directory.handle.makeOpenPath(o_sub_path, .{}); + var o_dir = try comp.dirs.local_cache.handle.makeOpenPath(o_sub_path, .{}); defer o_dir.close(); const tmp_basename = std.fs.path.basename(out_res_path); try std.fs.rename(zig_cache_tmp_dir, tmp_basename, o_dir, res_filename); @@ -5626,7 +5889,7 @@ fn updateWin32Resource(comp: *Compilation, win32_resource: *Win32Resource, win32 win32_resource.status = .{ .success = .{ - .res_path = try comp.local_cache_directory.join(comp.gpa, &[_][]const u8{ + .res_path = try comp.dirs.local_cache.join(comp.gpa, &[_][]const u8{ "o", &digest, res_basename, }), .lock = man.toOwnedLock(), @@ -5721,7 +5984,7 @@ fn spawnZigRc( pub fn tmpFilePath(comp: Compilation, ally: Allocator, suffix: []const u8) error{OutOfMemory}![]const u8 { const s = std.fs.path.sep_str; const rand_int = std.crypto.random.int(u64); - if (comp.local_cache_directory.path) |p| { + if (comp.dirs.local_cache.path) |p| { return std.fmt.allocPrint(ally, "{s}" ++ s ++ "tmp" ++ s ++ "{x}-{s}", .{ p, rand_int, suffix }); } else { return std.fmt.allocPrint(ally, "tmp" ++ s ++ "{x}-{s}", .{ rand_int, suffix }); @@ -5962,12 +6225,12 @@ pub fn addCCArgs( if (comp.config.link_libcpp) { try argv.append("-isystem"); try argv.append(try std.fs.path.join(arena, &[_][]const u8{ - comp.zig_lib_directory.path.?, "libcxx", "include", + comp.dirs.zig_lib.path.?, "libcxx", "include", })); try argv.append("-isystem"); try argv.append(try std.fs.path.join(arena, &[_][]const u8{ - comp.zig_lib_directory.path.?, "libcxxabi", "include", + comp.dirs.zig_lib.path.?, "libcxxabi", "include", })); try libcxx.addCxxArgs(comp, arena, argv); @@ -5977,7 +6240,7 @@ pub fn addCCArgs( // However as noted by @dimenus, appending libc headers before compiler headers breaks // intrinsics and other compiler specific items. try argv.append("-isystem"); - try argv.append(try std.fs.path.join(arena, &[_][]const u8{ comp.zig_lib_directory.path.?, "include" })); + try argv.append(try std.fs.path.join(arena, &.{ comp.dirs.zig_lib.path.?, "include" })); try argv.ensureUnusedCapacity(comp.libc_include_dir_list.len * 2); for (comp.libc_include_dir_list) |include_dir| { @@ -5996,7 +6259,7 @@ pub fn addCCArgs( if (comp.config.link_libunwind) { try argv.append("-isystem"); try argv.append(try std.fs.path.join(arena, &[_][]const u8{ - comp.zig_lib_directory.path.?, "libunwind", "include", + comp.dirs.zig_lib.path.?, "libunwind", "include", })); } @@ -6584,12 +6847,12 @@ test "classifyFileExt" { try std.testing.expectEqual(FileExt.zig, classifyFileExt("foo.zig")); } -fn get_libc_crt_file(comp: *Compilation, arena: Allocator, basename: []const u8) !Path { +fn get_libc_crt_file(comp: *Compilation, arena: Allocator, basename: []const u8) !Cache.Path { return (try crtFilePath(&comp.crt_files, basename)) orelse { const lci = comp.libc_installation orelse return error.LibCInstallationNotAvailable; const crt_dir_path = lci.crt_dir orelse return error.LibCInstallationMissingCrtDir; const full_path = try std.fs.path.join(arena, &[_][]const u8{ crt_dir_path, basename }); - return Path.initCwd(full_path); + return Cache.Path.initCwd(full_path); }; } @@ -6598,7 +6861,7 @@ pub fn crtFileAsString(comp: *Compilation, arena: Allocator, basename: []const u return path.toString(arena); } -fn crtFilePath(crt_files: *std.StringHashMapUnmanaged(CrtFile), basename: []const u8) Allocator.Error!?Path { +fn crtFilePath(crt_files: *std.StringHashMapUnmanaged(CrtFile), basename: []const u8) Allocator.Error!?Cache.Path { const crt_file = crt_files.get(basename) orelse return null; return crt_file.full_object_path; } @@ -6736,9 +6999,8 @@ fn buildOutputFromZig( }); const root_mod = try Package.Module.create(arena, .{ - .global_cache_directory = comp.global_cache_directory, .paths = .{ - .root = .{ .root_dir = comp.zig_lib_directory }, + .root = .zig_lib_root, .root_src_path = src_basename, }, .fully_qualified_name = "root", @@ -6760,8 +7022,6 @@ fn buildOutputFromZig( .global = config, .cc_argv = &.{}, .parent = null, - .builtin_mod = null, - .builtin_modules = null, // there is only one module in this compilation }); const target = comp.getTarget(); const bin_basename = try std.zig.binNameAlloc(arena, .{ @@ -6785,9 +7045,7 @@ fn buildOutputFromZig( }; const sub_compilation = try Compilation.create(gpa, arena, .{ - .global_cache_directory = comp.global_cache_directory, - .local_cache_directory = comp.global_cache_directory, - .zig_lib_directory = comp.zig_lib_directory, + .dirs = comp.dirs.withoutLocalCache(), .cache_mode = .whole, .parent_whole_cache = parent_whole_cache, .self_exe_path = comp.self_exe_path, @@ -6878,9 +7136,8 @@ pub fn build_crt_file( }, }); const root_mod = try Package.Module.create(arena, .{ - .global_cache_directory = comp.global_cache_directory, .paths = .{ - .root = .{ .root_dir = comp.zig_lib_directory }, + .root = .zig_lib_root, .root_src_path = "", }, .fully_qualified_name = "root", @@ -6908,8 +7165,6 @@ pub fn build_crt_file( .global = config, .cc_argv = &.{}, .parent = null, - .builtin_mod = null, - .builtin_modules = null, // there is only one module in this compilation }); for (c_source_files) |*item| { @@ -6917,9 +7172,7 @@ pub fn build_crt_file( } const sub_compilation = try Compilation.create(gpa, arena, .{ - .local_cache_directory = comp.global_cache_directory, - .global_cache_directory = comp.global_cache_directory, - .zig_lib_directory = comp.zig_lib_directory, + .dirs = comp.dirs.withoutLocalCache(), .self_exe_path = comp.self_exe_path, .cache_mode = .whole, .config = config, @@ -6962,7 +7215,7 @@ pub fn build_crt_file( } } -pub fn queueLinkTaskMode(comp: *Compilation, path: Path, output_mode: std.builtin.OutputMode) void { +pub fn queueLinkTaskMode(comp: *Compilation, path: Cache.Path, output_mode: std.builtin.OutputMode) void { comp.queueLinkTasks(switch (output_mode) { .Exe => unreachable, .Obj => &.{.{ .load_object = path }}, @@ -6983,7 +7236,7 @@ pub fn queueLinkTasks(comp: *Compilation, tasks: []const link.Task) void { pub fn toCrtFile(comp: *Compilation) Allocator.Error!CrtFile { return .{ .full_object_path = .{ - .root_dir = comp.local_cache_directory, + .root_dir = comp.dirs.local_cache, .sub_path = try comp.gpa.dupe(u8, comp.cache_use.whole.bin_sub_path.?), }, .lock = comp.cache_use.whole.moveLock(), |
