diff options
Diffstat (limited to 'src/Compilation.zig')
| -rw-r--r-- | src/Compilation.zig | 685 |
1 files changed, 297 insertions, 388 deletions
diff --git a/src/Compilation.zig b/src/Compilation.zig index 2776d2960a..fac1ad4baa 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -10,6 +10,7 @@ const Target = std.Target; const ThreadPool = std.Thread.Pool; const WaitGroup = std.Thread.WaitGroup; const ErrorBundle = std.zig.ErrorBundle; +const Path = Cache.Path; const Value = @import("Value.zig"); const Type = @import("Type.zig"); @@ -39,15 +40,17 @@ const Air = @import("Air.zig"); const Builtin = @import("Builtin.zig"); const LlvmObject = @import("codegen/llvm.zig").Object; const dev = @import("dev.zig"); -pub const Directory = Cache.Directory; -const Path = Cache.Path; +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. gpa: Allocator, /// Arena-allocated memory, mostly used during initialization. However, it can /// be used for other things requiring the same lifetime as the `Compilation`. +/// Not thread-safe - lock `mutex` if potentially accessing from multiple +/// threads at once. arena: Allocator, /// Not every Compilation compiles .zig code! For example you could do `zig build-exe foo.o`. zcu: ?*Zcu, @@ -76,12 +79,13 @@ implib_emit: ?Path, docs_emit: ?Path, root_name: [:0]const u8, include_compiler_rt: bool, -objects: []Compilation.LinkObject, +/// Resolved into known paths, any GNU ld scripts already resolved. +link_inputs: []const link.Input, /// Needed only for passing -F args to clang. framework_dirs: []const []const u8, -/// These are *always* dynamically linked. Static libraries will be -/// provided as positional arguments. -system_libs: std.StringArrayHashMapUnmanaged(SystemLib), +/// These are only for DLLs dependencies fulfilled by the `.def` files shipped +/// with Zig. Static libraries are provided as `link.Input` values. +windows_libs: std.StringArrayHashMapUnmanaged(void), version: ?std.SemanticVersion, libc_installation: ?*const LibCInstallation, skip_linker_dependencies: bool, @@ -107,6 +111,9 @@ win32_resource_table: if (dev.env.supports(.win32_resource)) std.AutoArrayHashMa } = .{}, link_diags: link.Diags, +link_task_queue: ThreadSafeQueue(link.Task) = .empty, +/// Ensure only 1 simultaneous call to `flushTaskQueue`. +link_task_queue_safety: std.debug.SafetyLock = .{}, work_queues: [ len: { @@ -118,14 +125,6 @@ work_queues: [ } ]std.fifo.LinearFifo(Job, .Dynamic), -codegen_work: if (InternPool.single_threaded) void else struct { - mutex: std.Thread.Mutex, - cond: std.Thread.Condition, - queue: std.fifo.LinearFifo(CodegenJob, .Dynamic), - job_error: ?JobError, - done: bool, -}, - /// These jobs are to invoke the Clang compiler to create an object file, which /// gets linked with the Compilation. c_object_work_queue: std.fifo.LinearFifo(*CObject, .Dynamic), @@ -262,6 +261,9 @@ emit_asm: ?EmitLoc, emit_llvm_ir: ?EmitLoc, emit_llvm_bc: ?EmitLoc, +link_task_wait_group: WaitGroup = .{}, +work_queue_progress_node: std.Progress.Node = .none, + llvm_opt_bisect_limit: c_int, file_system_inputs: ?*std.ArrayListUnmanaged(u8), @@ -339,16 +341,14 @@ pub const RcIncludes = enum { }; const Job = union(enum) { - /// Write the constant value for a Decl to the output file. + /// Corresponds to the task in `link.Task`. + /// Only needed for backends that haven't yet been updated to not race against Sema. codegen_nav: InternPool.Nav.Index, - /// Write the machine code for a function to the output file. - codegen_func: struct { - /// This will either be a non-generic `func_decl` or a `func_instance`. - func: InternPool.Index, - /// This `Air` is owned by the `Job` and allocated with `gpa`. - /// It must be deinited when the job is processed. - air: Air, - }, + /// Corresponds to the task in `link.Task`. + /// Only needed for backends that haven't yet been updated to not race against Sema. + codegen_func: link.Task.CodegenFunc, + /// Corresponds to the task in `link.Task`. + /// Only needed for backends that haven't yet been updated to not race against Sema. codegen_type: InternPool.Index, /// The `Cau` must be semantically analyzed (and possibly export itself). /// This may be its first time being analyzed, or it may be outdated. @@ -357,9 +357,6 @@ const Job = union(enum) { /// After analysis, a `codegen_func` job will be queued. /// These must be separate jobs to ensure any needed type resolution occurs *before* codegen. analyze_func: InternPool.Index, - /// The source file containing the Decl has been updated, and so the - /// Decl may need its line number information updated in the debug info. - update_line_number: void, // TODO /// The main source file for the module needs to be analyzed. analyze_mod: *Package.Module, /// Fully resolve the given `struct` or `union` type. @@ -373,6 +370,7 @@ const Job = union(enum) { musl_crt_file: musl.CrtFile, /// one of the mingw-w64 static objects mingw_crt_file: mingw.CrtFile, + /// libunwind.a, usually needed when linking libc libunwind: void, libcxx: void, @@ -384,7 +382,7 @@ const Job = union(enum) { /// one of WASI libc static objects wasi_libc_crt_file: wasi_libc.CrtFile, - /// The value is the index into `system_libs`. + /// The value is the index into `windows_libs`. windows_import_lib: usize, const Tag = @typeInfo(Job).@"union".tag_type.?; @@ -402,17 +400,6 @@ const Job = union(enum) { } }; -const CodegenJob = union(enum) { - nav: InternPool.Nav.Index, - func: struct { - func: InternPool.Index, - /// This `Air` is owned by the `Job` and allocated with `gpa`. - /// It must be deinited when the job is processed. - air: Air, - }, - type: InternPool.Index, -}; - pub const CObject = struct { /// Relative to cwd. Owned by arena. src: CSourceFile, @@ -999,24 +986,6 @@ const CacheUse = union(CacheMode) { } }; -pub const LinkObject = struct { - path: Path, - must_link: bool = false, - needed: bool = false, - // When the library is passed via a positional argument, it will be - // added as a full path. If it's `-l<lib>`, then just the basename. - // - // Consistent with `withLOption` variable name in lld ELF driver. - loption: bool = false, - - pub fn isObject(lo: LinkObject) bool { - return switch (classifyFileExt(lo.path.sub_path)) { - .object => true, - else => false, - }; - } -}; - pub const CreateOptions = struct { zig_lib_directory: Directory, local_cache_directory: Directory, @@ -1061,18 +1030,20 @@ pub const CreateOptions = struct { /// this flag would be set to disable this machinery to avoid false positives. disable_lld_caching: bool = false, cache_mode: CacheMode = .incremental, - lib_dirs: []const []const u8 = &[0][]const u8{}, + /// 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 = &.{}, rpath_list: []const []const u8 = &[0][]const u8{}, symbol_wrap_set: std.StringArrayHashMapUnmanaged(void) = .empty, c_source_files: []const CSourceFile = &.{}, rc_source_files: []const RcSourceFile = &.{}, manifest_file: ?[]const u8 = null, rc_includes: RcIncludes = .any, - link_objects: []LinkObject = &[0]LinkObject{}, + link_inputs: []const link.Input = &.{}, framework_dirs: []const []const u8 = &[0][]const u8{}, frameworks: []const Framework = &.{}, - system_lib_names: []const []const u8 = &.{}, - system_lib_infos: []const SystemLib = &.{}, + windows_lib_names: []const []const u8 = &.{}, /// These correspond to the WASI libc emulated subcomponents including: /// * process clocks /// * getpid @@ -1455,12 +1426,8 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil }; errdefer if (opt_zcu) |zcu| zcu.deinit(); - var system_libs = try std.StringArrayHashMapUnmanaged(SystemLib).init( - gpa, - options.system_lib_names, - options.system_lib_infos, - ); - errdefer system_libs.deinit(gpa); + var windows_libs = try std.StringArrayHashMapUnmanaged(void).init(gpa, options.windows_lib_names, &.{}); + errdefer windows_libs.deinit(gpa); comp.* = .{ .gpa = gpa, @@ -1479,13 +1446,6 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil .emit_llvm_ir = options.emit_llvm_ir, .emit_llvm_bc = options.emit_llvm_bc, .work_queues = .{std.fifo.LinearFifo(Job, .Dynamic).init(gpa)} ** @typeInfo(std.meta.FieldType(Compilation, .work_queues)).array.len, - .codegen_work = if (InternPool.single_threaded) {} else .{ - .mutex = .{}, - .cond = .{}, - .queue = std.fifo.LinearFifo(CodegenJob, .Dynamic).init(gpa), - .job_error = null, - .done = false, - }, .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), @@ -1522,11 +1482,11 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil .libcxx_abi_version = options.libcxx_abi_version, .root_name = root_name, .sysroot = sysroot, - .system_libs = system_libs, + .windows_libs = windows_libs, .version = options.version, .libc_installation = libc_dirs.libc_installation, .include_compiler_rt = include_compiler_rt, - .objects = options.link_objects, + .link_inputs = options.link_inputs, .framework_dirs = options.framework_dirs, .llvm_opt_bisect_limit = options.llvm_opt_bisect_limit, .skip_linker_dependencies = options.skip_linker_dependencies, @@ -1564,7 +1524,7 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil .z_max_page_size = options.linker_z_max_page_size, .darwin_sdk_layout = libc_dirs.darwin_sdk_layout, .frameworks = options.frameworks, - .lib_dirs = options.lib_dirs, + .lib_directories = options.lib_directories, .framework_dirs = options.framework_dirs, .rpath_list = options.rpath_list, .symbol_wrap_set = options.symbol_wrap_set, @@ -1776,157 +1736,175 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil .incremental => comp.bin_file != null, }; - if (have_bin_emit and !comp.skip_linker_dependencies and target.ofmt != .c) { - if (target.isDarwin()) { - switch (target.abi) { - .none, - .simulator, - .macabi, - => {}, - else => return error.LibCUnavailable, - } - } - // If we need to build glibc for the target, add work items for it. - // We go through the work queue so that building can be done in parallel. - if (comp.wantBuildGLibCFromSource()) { - if (!std.zig.target.canBuildLibC(target)) return error.LibCUnavailable; - - if (glibc.needsCrtiCrtn(target)) { - try comp.queueJobs(&[_]Job{ - .{ .glibc_crt_file = .crti_o }, - .{ .glibc_crt_file = .crtn_o }, - }); - } - try comp.queueJobs(&[_]Job{ - .{ .glibc_crt_file = .scrt1_o }, - .{ .glibc_crt_file = .libc_nonshared_a }, - .{ .glibc_shared_objects = {} }, - }); - } - if (comp.wantBuildMuslFromSource()) { - if (!std.zig.target.canBuildLibC(target)) return error.LibCUnavailable; - - if (musl.needsCrtiCrtn(target)) { - try comp.queueJobs(&[_]Job{ - .{ .musl_crt_file = .crti_o }, - .{ .musl_crt_file = .crtn_o }, - }); - } - try comp.queueJobs(&[_]Job{ - .{ .musl_crt_file = .crt1_o }, - .{ .musl_crt_file = .scrt1_o }, - .{ .musl_crt_file = .rcrt1_o }, - switch (comp.config.link_mode) { - .static => .{ .musl_crt_file = .libc_a }, - .dynamic => .{ .musl_crt_file = .libc_so }, - }, - }); - } + if (have_bin_emit and target.ofmt != .c) { + if (!comp.skip_linker_dependencies) { + // If we need to build libc for the target, add work items for it. + // We go through the work queue so that building can be done in parallel. + // If linking against host libc installation, instead queue up jobs + // for loading those files in the linker. + if (comp.config.link_libc and is_exe_or_dyn_lib) { + // If the "is darwin" check is moved below the libc_installation check below, + // error.LibCInstallationMissingCrtDir is returned from lci.resolveCrtPaths(). + if (target.isDarwin()) { + switch (target.abi) { + .none, .simulator, .macabi => {}, + else => return error.LibCUnavailable, + } + // TODO delete logic from MachO flush() and queue up tasks here instead. + } else if (comp.libc_installation) |lci| { + const basenames = LibCInstallation.CrtBasenames.get(.{ + .target = target, + .link_libc = comp.config.link_libc, + .output_mode = comp.config.output_mode, + .link_mode = comp.config.link_mode, + .pie = comp.config.pie, + }); + const paths = try lci.resolveCrtPaths(arena, basenames, target); - if (comp.wantBuildWasiLibcFromSource()) { - if (!std.zig.target.canBuildLibC(target)) return error.LibCUnavailable; + const fields = @typeInfo(@TypeOf(paths)).@"struct".fields; + try comp.link_task_queue.shared.ensureUnusedCapacity(gpa, fields.len + 1); + inline for (fields) |field| { + if (@field(paths, field.name)) |path| { + comp.link_task_queue.shared.appendAssumeCapacity(.{ .load_object = path }); + } + } + // Loads the libraries provided by `target_util.libcFullLinkFlags(target)`. + comp.link_task_queue.shared.appendAssumeCapacity(.load_host_libc); + } else if (target.isMusl() and !target.isWasm()) { + if (!std.zig.target.canBuildLibC(target)) return error.LibCUnavailable; + + if (musl.needsCrtiCrtn(target)) { + try comp.queueJobs(&[_]Job{ + .{ .musl_crt_file = .crti_o }, + .{ .musl_crt_file = .crtn_o }, + }); + } + if (musl.needsCrt0(comp.config.output_mode, comp.config.link_mode, comp.config.pie)) |f| { + try comp.queueJobs(&.{.{ .musl_crt_file = f }}); + } + try comp.queueJobs(&.{.{ .musl_crt_file = switch (comp.config.link_mode) { + .static => .libc_a, + .dynamic => .libc_so, + } }}); + } else if (target.isGnuLibC()) { + if (!std.zig.target.canBuildLibC(target)) return error.LibCUnavailable; + + if (glibc.needsCrtiCrtn(target)) { + try comp.queueJobs(&[_]Job{ + .{ .glibc_crt_file = .crti_o }, + .{ .glibc_crt_file = .crtn_o }, + }); + } + if (!is_dyn_lib) { + try comp.queueJob(.{ .glibc_crt_file = .scrt1_o }); + } + try comp.queueJobs(&[_]Job{ + .{ .glibc_shared_objects = {} }, + .{ .glibc_crt_file = .libc_nonshared_a }, + }); + } else if (target.isWasm() and target.os.tag == .wasi) { + if (!std.zig.target.canBuildLibC(target)) return error.LibCUnavailable; - for (comp.wasi_emulated_libs) |crt_file| { - try comp.queueJob(.{ - .wasi_libc_crt_file = crt_file, - }); - } - try comp.queueJobs(&[_]Job{ - .{ .wasi_libc_crt_file = wasi_libc.execModelCrtFile(comp.config.wasi_exec_model) }, - .{ .wasi_libc_crt_file = .libc_a }, - }); - } + for (comp.wasi_emulated_libs) |crt_file| { + try comp.queueJob(.{ + .wasi_libc_crt_file = crt_file, + }); + } + try comp.queueJobs(&[_]Job{ + .{ .wasi_libc_crt_file = wasi_libc.execModelCrtFile(comp.config.wasi_exec_model) }, + .{ .wasi_libc_crt_file = .libc_a }, + }); + } else if (target.isMinGW()) { + if (!std.zig.target.canBuildLibC(target)) return error.LibCUnavailable; - if (comp.wantBuildMinGWFromSource()) { - if (!std.zig.target.canBuildLibC(target)) return error.LibCUnavailable; + const crt_job: Job = .{ .mingw_crt_file = if (is_dyn_lib) .dllcrt2_o else .crt2_o }; + try comp.queueJobs(&.{ + .{ .mingw_crt_file = .mingw32_lib }, + crt_job, + }); - const crt_job: Job = .{ .mingw_crt_file = if (is_dyn_lib) .dllcrt2_o else .crt2_o }; - try comp.queueJobs(&.{ - .{ .mingw_crt_file = .mingw32_lib }, - crt_job, - }); + // When linking mingw-w64 there are some import libs we always need. + try comp.windows_libs.ensureUnusedCapacity(gpa, mingw.always_link_libs.len); + for (mingw.always_link_libs) |name| comp.windows_libs.putAssumeCapacity(name, {}); + } else if (target.isDarwin()) { + switch (target.abi) { + .none, .simulator, .macabi => {}, + else => return error.LibCUnavailable, + } + } else if (target.os.tag == .freestanding and capable_of_building_zig_libc) { + try comp.queueJob(.{ .zig_libc = {} }); + } else { + return error.LibCUnavailable; + } + } - // When linking mingw-w64 there are some import libs we always need. - for (mingw.always_link_libs) |name| { - try comp.system_libs.put(comp.gpa, name, .{ - .needed = false, - .weak = false, - .path = null, - }); + // Generate Windows import libs. + if (target.os.tag == .windows) { + const count = comp.windows_libs.count(); + for (0..count) |i| { + try comp.queueJob(.{ .windows_import_lib = i }); + } } - } - // Generate Windows import libs. - if (target.os.tag == .windows) { - const count = comp.system_libs.count(); - for (0..count) |i| { - try comp.queueJob(.{ .windows_import_lib = i }); + if (comp.wantBuildLibUnwindFromSource()) { + try comp.queueJob(.{ .libunwind = {} }); + } + if (build_options.have_llvm and is_exe_or_dyn_lib and comp.config.link_libcpp) { + try comp.queueJob(.libcxx); + try comp.queueJob(.libcxxabi); + } + if (build_options.have_llvm and is_exe_or_dyn_lib and comp.config.any_sanitize_thread) { + try comp.queueJob(.libtsan); } - } - if (comp.wantBuildLibUnwindFromSource()) { - try comp.queueJob(.{ .libunwind = {} }); - } - if (build_options.have_llvm and is_exe_or_dyn_lib and comp.config.link_libcpp) { - try comp.queueJob(.libcxx); - try comp.queueJob(.libcxxabi); - } - if (build_options.have_llvm and comp.config.any_sanitize_thread) { - try comp.queueJob(.libtsan); - } - if (target.isMinGW() and comp.config.any_non_single_threaded) { - // LLD might drop some symbols as unused during LTO and GCing, therefore, - // we force mark them for resolution here. + if (target.isMinGW() and comp.config.any_non_single_threaded) { + // LLD might drop some symbols as unused during LTO and GCing, therefore, + // we force mark them for resolution here. - const tls_index_sym = switch (target.cpu.arch) { - .x86 => "__tls_index", - else => "_tls_index", - }; + const tls_index_sym = switch (target.cpu.arch) { + .x86 => "__tls_index", + else => "_tls_index", + }; - try comp.force_undefined_symbols.put(comp.gpa, tls_index_sym, {}); - } + try comp.force_undefined_symbols.put(comp.gpa, tls_index_sym, {}); + } - if (comp.include_compiler_rt and capable_of_building_compiler_rt) { - if (is_exe_or_dyn_lib) { - log.debug("queuing a job to build compiler_rt_lib", .{}); - comp.job_queued_compiler_rt_lib = true; - } else if (output_mode != .Obj) { - log.debug("queuing a job to build compiler_rt_obj", .{}); - // In this case we are making a static library, so we ask - // for a compiler-rt object to put in it. - comp.job_queued_compiler_rt_obj = true; + if (comp.include_compiler_rt and capable_of_building_compiler_rt) { + if (is_exe_or_dyn_lib) { + log.debug("queuing a job to build compiler_rt_lib", .{}); + comp.job_queued_compiler_rt_lib = true; + } else if (output_mode != .Obj) { + log.debug("queuing a job to build compiler_rt_obj", .{}); + // In this case we are making a static library, so we ask + // for a compiler-rt object to put in it. + comp.job_queued_compiler_rt_obj = true; + } } - } - if (comp.config.any_fuzz and capable_of_building_compiler_rt) { - if (is_exe_or_dyn_lib) { + if (is_exe_or_dyn_lib and comp.config.any_fuzz and capable_of_building_compiler_rt) { log.debug("queuing a job to build libfuzzer", .{}); comp.job_queued_fuzzer_lib = true; } } - if (!comp.skip_linker_dependencies and is_exe_or_dyn_lib and - !comp.config.link_libc and capable_of_building_zig_libc) - { - try comp.queueJob(.{ .zig_libc = {} }); - } + try comp.link_task_queue.shared.append(gpa, .load_explicitly_provided); } return comp; } pub fn destroy(comp: *Compilation) void { + const gpa = comp.gpa; + 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(); - if (!InternPool.single_threaded) comp.codegen_work.queue.deinit(); comp.c_object_work_queue.deinit(); comp.win32_resource_work_queue.deinit(); comp.astgen_work_queue.deinit(); comp.embed_file_work_queue.deinit(); - const gpa = comp.gpa; - comp.system_libs.deinit(gpa); + comp.windows_libs.deinit(gpa); { var it = comp.crt_files.iterator(); @@ -2559,12 +2537,7 @@ fn addNonIncrementalStuffToCacheManifest( cache_helpers.addModule(&man.hash, comp.root_mod); } - for (comp.objects) |obj| { - _ = try man.addFilePath(obj.path, null); - man.hash.add(obj.must_link); - man.hash.add(obj.needed); - man.hash.add(obj.loption); - } + try link.hashInputs(man, comp.link_inputs); for (comp.c_object_table.keys()) |key| { _ = try man.addFile(key.src.src_path, null); @@ -2601,7 +2574,7 @@ fn addNonIncrementalStuffToCacheManifest( man.hash.add(comp.rc_includes); man.hash.addListOfBytes(comp.force_undefined_symbols.keys()); man.hash.addListOfBytes(comp.framework_dirs); - try link.hashAddSystemLibs(man, comp.system_libs); + man.hash.addListOfBytes(comp.windows_libs.keys()); cache_helpers.addOptionalEmitLoc(&man.hash, comp.emit_asm); cache_helpers.addOptionalEmitLoc(&man.hash, comp.emit_llvm_ir); @@ -2620,12 +2593,16 @@ fn addNonIncrementalStuffToCacheManifest( man.hash.addOptional(opts.image_base); man.hash.addOptional(opts.gc_sections); man.hash.add(opts.emit_relocs); - man.hash.addListOfBytes(opts.lib_dirs); + const target = comp.root_mod.resolved_target.result; + if (target.ofmt == .macho or target.ofmt == .coff) { + // TODO remove this, libraries need to be resolved by the frontend. this is already + // done by ELF. + for (opts.lib_directories) |lib_directory| man.hash.addOptionalBytes(lib_directory.path); + } man.hash.addListOfBytes(opts.rpath_list); man.hash.addListOfBytes(opts.symbol_wrap_set.keys()); if (comp.config.link_libc) { man.hash.add(comp.libc_installation != null); - const target = comp.root_mod.resolved_target.result; if (comp.libc_installation) |libc_installation| { man.hash.addOptionalBytes(libc_installation.crt_dir); if (target.abi == .msvc or target.abi == .itanium) { @@ -3219,18 +3196,7 @@ pub fn getAllErrorsAlloc(comp: *Compilation) !ErrorBundle { })); } - for (comp.link_diags.msgs.items) |link_err| { - try bundle.addRootErrorMessage(.{ - .msg = try bundle.addString(link_err.msg), - .notes_len = @intCast(link_err.notes.len), - }); - const notes_start = try bundle.reserveNotes(@intCast(link_err.notes.len)); - for (link_err.notes, 0..) |note, i| { - bundle.extra.items[notes_start + i] = @intFromEnum(try bundle.addErrorMessage(.{ - .msg = try bundle.addString(note.msg), - })); - } - } + try comp.link_diags.addMessagesToBundle(&bundle); if (comp.zcu) |zcu| { if (bundle.root_list.items.len == 0 and zcu.compile_log_sources.count() != 0) { @@ -3482,6 +3448,9 @@ pub fn performAllTheWork( comp: *Compilation, main_progress_node: std.Progress.Node, ) JobError!void { + comp.work_queue_progress_node = main_progress_node; + defer comp.work_queue_progress_node = .none; + defer if (comp.zcu) |zcu| { zcu.sema_prog_node.end(); zcu.sema_prog_node = std.Progress.Node.none; @@ -3491,7 +3460,6 @@ pub fn performAllTheWork( zcu.generation += 1; }; try comp.performAllTheWorkInner(main_progress_node); - if (!InternPool.single_threaded) if (comp.codegen_work.job_error) |job_error| return job_error; } fn performAllTheWorkInner( @@ -3506,12 +3474,34 @@ fn performAllTheWorkInner( var work_queue_wait_group: WaitGroup = .{}; defer work_queue_wait_group.wait(); + comp.link_task_wait_group.reset(); + defer comp.link_task_wait_group.wait(); + + if (comp.link_task_queue.start()) { + comp.thread_pool.spawnWgId(&comp.link_task_wait_group, link.flushTaskQueue, .{comp}); + } + if (comp.docs_emit != null) { dev.check(.docs_emit); comp.thread_pool.spawnWg(&work_queue_wait_group, workerDocsCopy, .{comp}); work_queue_wait_group.spawnManager(workerDocsWasm, .{ comp, main_progress_node }); } + if (comp.job_queued_compiler_rt_lib) { + comp.job_queued_compiler_rt_lib = false; + comp.link_task_wait_group.spawnManager(buildRt, .{ comp, "compiler_rt.zig", .compiler_rt, .Lib, &comp.compiler_rt_lib, main_progress_node }); + } + + if (comp.job_queued_compiler_rt_obj) { + comp.job_queued_compiler_rt_obj = false; + comp.link_task_wait_group.spawnManager(buildRt, .{ comp, "compiler_rt.zig", .compiler_rt, .Obj, &comp.compiler_rt_obj, main_progress_node }); + } + + if (comp.job_queued_fuzzer_lib) { + comp.job_queued_fuzzer_lib = false; + comp.link_task_wait_group.spawnManager(buildRt, .{ comp, "fuzzer.zig", .libfuzzer, .Lib, &comp.fuzzer_lib, main_progress_node }); + } + { const astgen_frame = tracy.namedFrame("astgen"); defer astgen_frame.end(); @@ -3574,22 +3564,18 @@ fn performAllTheWorkInner( } while (comp.c_object_work_queue.readItem()) |c_object| { - comp.thread_pool.spawnWg(&work_queue_wait_group, workerUpdateCObject, .{ + comp.thread_pool.spawnWg(&comp.link_task_wait_group, workerUpdateCObject, .{ comp, c_object, main_progress_node, }); } while (comp.win32_resource_work_queue.readItem()) |win32_resource| { - comp.thread_pool.spawnWg(&work_queue_wait_group, workerUpdateWin32Resource, .{ + comp.thread_pool.spawnWg(&comp.link_task_wait_group, workerUpdateWin32Resource, .{ comp, win32_resource, main_progress_node, }); } } - if (comp.job_queued_compiler_rt_lib) work_queue_wait_group.spawnManager(buildRt, .{ comp, "compiler_rt.zig", .compiler_rt, .Lib, &comp.compiler_rt_lib, main_progress_node }); - if (comp.job_queued_compiler_rt_obj) work_queue_wait_group.spawnManager(buildRt, .{ comp, "compiler_rt.zig", .compiler_rt, .Obj, &comp.compiler_rt_obj, main_progress_node }); - if (comp.job_queued_fuzzer_lib) work_queue_wait_group.spawnManager(buildRt, .{ comp, "fuzzer.zig", .libfuzzer, .Lib, &comp.fuzzer_lib, main_progress_node }); - if (comp.zcu) |zcu| { const pt: Zcu.PerThread = .{ .zcu = zcu, .tid = .main }; if (comp.incremental) { @@ -3604,18 +3590,12 @@ fn performAllTheWorkInner( zcu.codegen_prog_node = main_progress_node.start("Code Generation", 0); } - if (!InternPool.single_threaded) { - comp.codegen_work.done = false; // may be `true` from a prior update - comp.thread_pool.spawnWgId(&work_queue_wait_group, codegenThread, .{comp}); + if (!comp.separateCodegenThreadOk()) { + // Waits until all input files have been parsed. + comp.link_task_wait_group.wait(); + comp.link_task_wait_group.reset(); + std.log.scoped(.link).debug("finished waiting for link_task_wait_group", .{}); } - defer if (!InternPool.single_threaded) { - { - comp.codegen_work.mutex.lock(); - defer comp.codegen_work.mutex.unlock(); - comp.codegen_work.done = true; - } - comp.codegen_work.cond.signal(); - }; work: while (true) { for (&comp.work_queues) |*work_queue| if (work_queue.readItem()) |job| { @@ -3659,16 +3639,14 @@ fn processOneJob(tid: usize, comp: *Compilation, job: Job, prog_node: std.Progre } } assert(nav.status == .resolved); - try comp.queueCodegenJob(tid, .{ .nav = nav_index }); + comp.dispatchCodegenTask(tid, .{ .codegen_nav = nav_index }); }, .codegen_func => |func| { - // This call takes ownership of `func.air`. - try comp.queueCodegenJob(tid, .{ .func = .{ - .func = func.func, - .air = func.air, - } }); + comp.dispatchCodegenTask(tid, .{ .codegen_func = func }); + }, + .codegen_type => |ty| { + comp.dispatchCodegenTask(tid, .{ .codegen_type = ty }); }, - .codegen_type => |ty| try comp.queueCodegenJob(tid, .{ .type = ty }), .analyze_func => |func| { const named_frame = tracy.namedFrame("analyze_func"); defer named_frame.end(); @@ -3715,31 +3693,6 @@ fn processOneJob(tid: usize, comp: *Compilation, job: Job, prog_node: std.Progre error.AnalysisFail => return, }; }, - .update_line_number => |decl_index| { - const named_frame = tracy.namedFrame("update_line_number"); - defer named_frame.end(); - - if (true) @panic("TODO: update_line_number"); - - const gpa = comp.gpa; - const pt: Zcu.PerThread = .{ .zcu = comp.zcu.?, .tid = @enumFromInt(tid) }; - const decl = pt.zcu.declPtr(decl_index); - const lf = comp.bin_file.?; - lf.updateDeclLineNumber(pt, decl_index) catch |err| { - try pt.zcu.failed_analysis.ensureUnusedCapacity(gpa, 1); - pt.zcu.failed_analysis.putAssumeCapacityNoClobber( - InternPool.AnalUnit.wrap(.{ .decl = decl_index }), - try Zcu.ErrorMsg.create( - gpa, - decl.navSrcLoc(pt.zcu), - "unable to update line number: {s}", - .{@errorName(err)}, - ), - ); - decl.analysis = .codegen_failure; - try pt.zcu.retryable_failures.append(gpa, InternPool.AnalUnit.wrap(.{ .decl = decl_index })); - }; - }, .analyze_mod => |mod| { const named_frame = tracy.namedFrame("analyze_mod"); defer named_frame.end(); @@ -3804,7 +3757,7 @@ fn processOneJob(tid: usize, comp: *Compilation, job: Job, prog_node: std.Progre const named_frame = tracy.namedFrame("windows_import_lib"); defer named_frame.end(); - const link_lib = comp.system_libs.keys()[index]; + const link_lib = comp.windows_libs.keys()[index]; mingw.buildImportLib(comp, link_lib) catch |err| { // TODO Surface more error details. comp.lockAndSetMiscFailure( @@ -3906,66 +3859,20 @@ fn processOneJob(tid: usize, comp: *Compilation, job: Job, prog_node: std.Progre } } -fn queueCodegenJob(comp: *Compilation, tid: usize, codegen_job: CodegenJob) !void { - if (InternPool.single_threaded or - !comp.zcu.?.backendSupportsFeature(.separate_thread)) - return processOneCodegenJob(tid, comp, codegen_job); - - { - comp.codegen_work.mutex.lock(); - defer comp.codegen_work.mutex.unlock(); - try comp.codegen_work.queue.writeItem(codegen_job); +/// The reason for the double-queue here is that the first queue ensures any +/// resolve_type_fully tasks are complete before this dispatch function is called. +fn dispatchCodegenTask(comp: *Compilation, tid: usize, link_task: link.Task) void { + if (comp.separateCodegenThreadOk()) { + comp.queueLinkTasks(&.{link_task}); + } else { + link.doTask(comp, tid, link_task); } - comp.codegen_work.cond.signal(); } -fn codegenThread(tid: usize, comp: *Compilation) void { - comp.codegen_work.mutex.lock(); - defer comp.codegen_work.mutex.unlock(); - - while (true) { - if (comp.codegen_work.queue.readItem()) |codegen_job| { - comp.codegen_work.mutex.unlock(); - defer comp.codegen_work.mutex.lock(); - - processOneCodegenJob(tid, comp, codegen_job) catch |job_error| { - comp.codegen_work.job_error = job_error; - break; - }; - continue; - } - - if (comp.codegen_work.done) break; - - comp.codegen_work.cond.wait(&comp.codegen_work.mutex); - } -} - -fn processOneCodegenJob(tid: usize, comp: *Compilation, codegen_job: CodegenJob) JobError!void { - switch (codegen_job) { - .nav => |nav_index| { - const named_frame = tracy.namedFrame("codegen_nav"); - defer named_frame.end(); - - const pt: Zcu.PerThread = .{ .zcu = comp.zcu.?, .tid = @enumFromInt(tid) }; - try pt.linkerUpdateNav(nav_index); - }, - .func => |func| { - const named_frame = tracy.namedFrame("codegen_func"); - defer named_frame.end(); - - const pt: Zcu.PerThread = .{ .zcu = comp.zcu.?, .tid = @enumFromInt(tid) }; - // This call takes ownership of `func.air`. - try pt.linkerUpdateFunc(func.func, func.air); - }, - .type => |ty| { - const named_frame = tracy.namedFrame("codegen_type"); - defer named_frame.end(); - - const pt: Zcu.PerThread = .{ .zcu = comp.zcu.?, .tid = @enumFromInt(tid) }; - try pt.linkerUpdateContainerType(ty); - }, - } +fn separateCodegenThreadOk(comp: *const Compilation) bool { + if (InternPool.single_threaded) return false; + const zcu = comp.zcu orelse return true; + return zcu.backendSupportsFeature(.separate_thread); } fn workerDocsCopy(comp: *Compilation) void { @@ -4717,7 +4624,7 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Pr // file and building an object we need to link them together, but with just one it should go // directly to the output file. const direct_o = comp.c_source_files.len == 1 and comp.zcu == null and - comp.config.output_mode == .Obj and comp.objects.len == 0; + comp.config.output_mode == .Obj and !link.anyObjectInputs(comp.link_inputs); const o_basename_noext = if (direct_o) comp.root_name else @@ -4956,7 +4863,9 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Pr // the contents were the same, we hit the cache but the manifest is dirty and we need to update // it to prevent doing a full file content comparison the next time around. man.writeManifest() catch |err| { - log.warn("failed to write cache manifest when compiling '{s}': {s}", .{ c_object.src.src_path, @errorName(err) }); + log.warn("failed to write cache manifest when compiling '{s}': {s}", .{ + c_object.src.src_path, @errorName(err), + }); }; } @@ -4971,6 +4880,8 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_obj_prog_node: std.Pr .lock = man.toOwnedLock(), }, }; + + comp.queueLinkTasks(&.{.{ .load_object = c_object.status.success.object_path }}); } fn updateWin32Resource(comp: *Compilation, win32_resource: *Win32Resource, win32_resource_prog_node: std.Progress.Node) !void { @@ -6075,8 +5986,8 @@ test "classifyFileExt" { try std.testing.expectEqual(FileExt.zig, classifyFileExt("foo.zig")); } -pub fn get_libc_crt_file(comp: *Compilation, arena: Allocator, basename: []const u8) !Path { - return (try crtFilePath(comp, basename)) orelse { +fn get_libc_crt_file(comp: *Compilation, arena: Allocator, basename: []const u8) !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 }); @@ -6089,40 +6000,11 @@ pub fn crtFileAsString(comp: *Compilation, arena: Allocator, basename: []const u return path.toString(arena); } -pub fn crtFilePath(comp: *Compilation, basename: []const u8) Allocator.Error!?Path { - const crt_file = comp.crt_files.get(basename) orelse return null; +fn crtFilePath(crt_files: *std.StringHashMapUnmanaged(CrtFile), basename: []const u8) Allocator.Error!?Path { + const crt_file = crt_files.get(basename) orelse return null; return crt_file.full_object_path; } -fn wantBuildLibCFromSource(comp: Compilation) bool { - const is_exe_or_dyn_lib = switch (comp.config.output_mode) { - .Obj => false, - .Lib => comp.config.link_mode == .dynamic, - .Exe => true, - }; - const ofmt = comp.root_mod.resolved_target.result.ofmt; - return comp.config.link_libc and is_exe_or_dyn_lib and - comp.libc_installation == null and ofmt != .c; -} - -fn wantBuildGLibCFromSource(comp: Compilation) bool { - return comp.wantBuildLibCFromSource() and comp.getTarget().isGnuLibC(); -} - -fn wantBuildMuslFromSource(comp: Compilation) bool { - return comp.wantBuildLibCFromSource() and comp.getTarget().isMusl() and - !comp.getTarget().isWasm(); -} - -fn wantBuildWasiLibcFromSource(comp: Compilation) bool { - return comp.wantBuildLibCFromSource() and comp.getTarget().isWasm() and - comp.getTarget().os.tag == .wasi; -} - -fn wantBuildMinGWFromSource(comp: Compilation) bool { - return comp.wantBuildLibCFromSource() and comp.getTarget().isMinGW(); -} - fn wantBuildLibUnwindFromSource(comp: *Compilation) bool { const is_exe_or_dyn_lib = switch (comp.config.output_mode) { .Obj => false, @@ -6133,7 +6015,7 @@ fn wantBuildLibUnwindFromSource(comp: *Compilation) bool { return is_exe_or_dyn_lib and comp.config.link_libunwind and ofmt != .c; } -fn setAllocFailure(comp: *Compilation) void { +pub fn setAllocFailure(comp: *Compilation) void { @branchHint(.cold); log.debug("memory allocation failure", .{}); comp.alloc_failure_occurred = true; @@ -6370,9 +6252,11 @@ fn buildOutputFromZig( try comp.updateSubCompilation(sub_compilation, misc_task_tag, prog_node); - // Under incremental compilation, `out` may already be populated from a prior update. - assert(out.* == null or comp.incremental); - out.* = try sub_compilation.toCrtFile(); + const crt_file = try sub_compilation.toCrtFile(); + assert(out.* == null); + out.* = crt_file; + + comp.queueLinkTaskMode(crt_file.full_object_path, output_mode); } pub fn build_crt_file( @@ -6479,8 +6363,33 @@ pub fn build_crt_file( try comp.updateSubCompilation(sub_compilation, misc_task_tag, prog_node); - try comp.crt_files.ensureUnusedCapacity(gpa, 1); - comp.crt_files.putAssumeCapacityNoClobber(basename, try sub_compilation.toCrtFile()); + const crt_file = try sub_compilation.toCrtFile(); + comp.queueLinkTaskMode(crt_file.full_object_path, output_mode); + + { + comp.mutex.lock(); + defer comp.mutex.unlock(); + try comp.crt_files.ensureUnusedCapacity(gpa, 1); + comp.crt_files.putAssumeCapacityNoClobber(basename, crt_file); + } +} + +pub fn queueLinkTaskMode(comp: *Compilation, path: Path, output_mode: std.builtin.OutputMode) void { + comp.queueLinkTasks(switch (output_mode) { + .Exe => unreachable, + .Obj => &.{.{ .load_object = path }}, + .Lib => &.{.{ .load_archive = path }}, + }); +} + +/// Only valid to call during `update`. Automatically handles queuing up a +/// linker worker task if there is not already one. +pub fn queueLinkTasks(comp: *Compilation, tasks: []const link.Task) void { + if (comp.link_task_queue.enqueue(comp.gpa, tasks) catch |err| switch (err) { + error.OutOfMemory => return comp.setAllocFailure(), + }) { + comp.thread_pool.spawnWgId(&comp.link_task_wait_group, link.flushTaskQueue, .{comp}); + } } pub fn toCrtFile(comp: *Compilation) Allocator.Error!CrtFile { @@ -6498,21 +6407,31 @@ pub fn getCrtPaths( arena: Allocator, ) error{ OutOfMemory, LibCInstallationMissingCrtDir }!LibCInstallation.CrtPaths { const target = comp.root_mod.resolved_target.result; + return getCrtPathsInner(arena, target, comp.config, comp.libc_installation, &comp.crt_files); +} + +fn getCrtPathsInner( + arena: Allocator, + target: std.Target, + config: Config, + libc_installation: ?*const LibCInstallation, + crt_files: *std.StringHashMapUnmanaged(CrtFile), +) error{ OutOfMemory, LibCInstallationMissingCrtDir }!LibCInstallation.CrtPaths { const basenames = LibCInstallation.CrtBasenames.get(.{ .target = target, - .link_libc = comp.config.link_libc, - .output_mode = comp.config.output_mode, - .link_mode = comp.config.link_mode, - .pie = comp.config.pie, + .link_libc = config.link_libc, + .output_mode = config.output_mode, + .link_mode = config.link_mode, + .pie = config.pie, }); - if (comp.libc_installation) |lci| return lci.resolveCrtPaths(arena, basenames, target); + if (libc_installation) |lci| return lci.resolveCrtPaths(arena, basenames, target); return .{ - .crt0 = if (basenames.crt0) |basename| try comp.crtFilePath(basename) else null, - .crti = if (basenames.crti) |basename| try comp.crtFilePath(basename) else null, - .crtbegin = if (basenames.crtbegin) |basename| try comp.crtFilePath(basename) else null, - .crtend = if (basenames.crtend) |basename| try comp.crtFilePath(basename) else null, - .crtn = if (basenames.crtn) |basename| try comp.crtFilePath(basename) else null, + .crt0 = if (basenames.crt0) |basename| try crtFilePath(crt_files, basename) else null, + .crti = if (basenames.crti) |basename| try crtFilePath(crt_files, basename) else null, + .crtbegin = if (basenames.crtbegin) |basename| try crtFilePath(crt_files, basename) else null, + .crtend = if (basenames.crtend) |basename| try crtFilePath(crt_files, basename) else null, + .crtn = if (basenames.crtn) |basename| try crtFilePath(crt_files, basename) else null, }; } @@ -6522,24 +6441,14 @@ pub fn addLinkLib(comp: *Compilation, lib_name: []const u8) !void { // then when we create a sub-Compilation for zig libc, it also tries to // build kernel32.lib. if (comp.skip_linker_dependencies) return; + const target = comp.root_mod.resolved_target.result; + if (target.os.tag != .windows or target.ofmt == .c) return; // This happens when an `extern "foo"` function is referenced. // If we haven't seen this library yet and we're targeting Windows, we need // to queue up a work item to produce the DLL import library for this. - const gop = try comp.system_libs.getOrPut(comp.gpa, lib_name); - if (!gop.found_existing) { - gop.value_ptr.* = .{ - .needed = true, - .weak = false, - .path = null, - }; - const target = comp.root_mod.resolved_target.result; - if (target.os.tag == .windows and target.ofmt != .c) { - try comp.queueJob(.{ - .windows_import_lib = comp.system_libs.count() - 1, - }); - } - } + const gop = try comp.windows_libs.getOrPut(comp.gpa, lib_name); + if (!gop.found_existing) try comp.queueJob(.{ .windows_import_lib = comp.windows_libs.count() - 1 }); } /// This decides the optimization mode for all zig-provided libraries, including |
