diff options
| author | Andrew Kelley <andrew@ziglang.org> | 2021-02-25 21:04:23 -0700 |
|---|---|---|
| committer | Andrew Kelley <andrew@ziglang.org> | 2021-02-25 21:04:23 -0700 |
| commit | 0b58b617998b79a765b54f88fbe90ca2798b3d3e (patch) | |
| tree | ca6cc4b6bcc2b93166d196049ee49416afe781ad /src/Compilation.zig | |
| parent | dc325669e360f7a9dfa24f85a62fa386529dade6 (diff) | |
| parent | fd208d9d5913a0929e444deb97b91092c427bb14 (diff) | |
| download | zig-0b58b617998b79a765b54f88fbe90ca2798b3d3e.tar.gz zig-0b58b617998b79a765b54f88fbe90ca2798b3d3e.zip | |
Merge remote-tracking branch 'origin/master' into llvm12
Conflicts:
* src/clang.zig
* src/llvm.zig
- this file got moved to src/llvm/bindings.zig in master branch so I
had to put the new LLVM arch/os enum tags into it.
* lib/std/target.zig, src/stage1/target.cpp
- haiku had an inconsistency with its default target ABI, gnu vs
eabi. In this commit we make it gnu in both places to match the
latest changes by @hoanga.
* src/translate_c.zig
Diffstat (limited to 'src/Compilation.zig')
| -rw-r--r-- | src/Compilation.zig | 842 |
1 files changed, 585 insertions, 257 deletions
diff --git a/src/Compilation.zig b/src/Compilation.zig index 81a2bff37b..39e10becec 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -26,6 +26,10 @@ const Module = @import("Module.zig"); const Cache = @import("Cache.zig"); const stage1 = @import("stage1.zig"); const translate_c = @import("translate_c.zig"); +const c_codegen = @import("codegen/c.zig"); +const ThreadPool = @import("ThreadPool.zig"); +const WaitGroup = @import("WaitGroup.zig"); +const libtsan = @import("libtsan.zig"); /// General-purpose allocator. Used for both temporary and long-term storage. gpa: *Allocator, @@ -41,8 +45,13 @@ link_error_flags: link.File.ErrorFlags = .{}, work_queue: std.fifo.LinearFifo(Job, .Dynamic), +/// 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), + /// The ErrorMsg memory is owned by the `CObject`, using Compilation's general purpose allocator. -failed_c_objects: std.AutoArrayHashMapUnmanaged(*CObject, *ErrorMsg) = .{}, +/// This data is accessed by multiple threads and is protected by `mutex`. +failed_c_objects: std.AutoArrayHashMapUnmanaged(*CObject, *CObject.ErrorMsg) = .{}, keep_source_files_loaded: bool, use_clang: bool, @@ -74,7 +83,7 @@ zig_lib_directory: Directory, local_cache_directory: Directory, global_cache_directory: Directory, libc_include_dir_list: []const []const u8, -rand: *std.rand.Random, +thread_pool: *ThreadPool, /// Populated when we build the libc++ static library. A Job to build this is placed in the queue /// and resolved before calling linker.flush(). @@ -85,6 +94,9 @@ libcxxabi_static_lib: ?CRTFile = null, /// Populated when we build the libunwind static library. A Job to build this is placed in the queue /// and resolved before calling linker.flush(). libunwind_static_lib: ?CRTFile = null, +/// Populated when we build the TSAN static library. A Job to build this is placed in the queue +/// and resolved before calling linker.flush(). +tsan_static_lib: ?CRTFile = null, /// Populated when we build the libssp static library. A Job to build this is placed in the queue /// and resolved before calling linker.flush(). libssp_static_lib: ?CRTFile = null, @@ -112,16 +124,21 @@ owned_link_dir: ?std.fs.Dir, /// Don't use this for anything other than stage1 compatibility. color: @import("main.zig").Color = .auto, +/// This mutex guards all `Compilation` mutable state. +mutex: std.Thread.Mutex = .{}, + test_filter: ?[]const u8, test_name_prefix: ?[]const u8, test_evented_io: bool, +debug_compiler_runtime_libs: bool, -emit_h: ?EmitLoc, emit_asm: ?EmitLoc, emit_llvm_ir: ?EmitLoc, emit_analysis: ?EmitLoc, emit_docs: ?EmitLoc, +work_queue_wait_group: WaitGroup, + pub const InnerError = Module.InnerError; pub const CRTFile = struct { @@ -144,6 +161,8 @@ pub const CSourceFile = struct { const Job = union(enum) { /// Write the machine code for a Decl to the output file. codegen_decl: *Module.Decl, + /// Render the .h file snippet for the Decl. + emit_h_decl: *Module.Decl, /// The Decl needs to be analyzed and possibly export itself. /// It may have already be analyzed, or it may have been determined /// to be outdated; in this case perform semantic analysis again. @@ -151,9 +170,6 @@ const Job = union(enum) { /// 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: *Module.Decl, - /// Invoke the Clang compiler to create an object file, which gets linked - /// with the Compilation. - c_object: *CObject, /// one of the glibc static objects glibc_crt_file: glibc.CRTFile, @@ -167,6 +183,7 @@ const Job = union(enum) { libunwind: void, libcxx: void, libcxxabi: void, + libtsan: void, libssp: void, compiler_rt_lib: void, compiler_rt_obj: void, @@ -198,13 +215,29 @@ pub const CObject = struct { }, /// There will be a corresponding ErrorMsg in Compilation.failed_c_objects. failure, + /// A transient failure happened when trying to compile the C Object; it may + /// succeed if we try again. There may be a corresponding ErrorMsg in + /// Compilation.failed_c_objects. If there is not, the failure is out of memory. + failure_retryable, }, + pub const ErrorMsg = struct { + msg: []const u8, + line: u32, + column: u32, + + pub fn destroy(em: *ErrorMsg, gpa: *Allocator) void { + gpa.free(em.msg); + gpa.destroy(em); + em.* = undefined; + } + }; + /// Returns if there was failure. pub fn clearStatus(self: *CObject, gpa: *Allocator) bool { switch (self.status) { .new => return false, - .failure => { + .failure, .failure_retryable => { self.status = .new; return true; }, @@ -223,6 +256,11 @@ pub const CObject = struct { } }; +/// To support incremental compilation, errors are stored in various places +/// so that they can be created and destroyed appropriately. This structure +/// is used to collect all the errors from the various places into one +/// convenient place for API users to consume. It is allocated into 1 heap +/// and freed all at once. pub const AllErrors = struct { arena: std.heap.ArenaAllocator.State, list: []const Message, @@ -234,23 +272,32 @@ pub const AllErrors = struct { column: usize, byte_offset: usize, msg: []const u8, + notes: []Message = &.{}, }, plain: struct { msg: []const u8, }, - pub fn renderToStdErr(self: Message) void { - switch (self) { + pub fn renderToStdErr(msg: Message) void { + return msg.renderToStdErrInner("error"); + } + + fn renderToStdErrInner(msg: Message, kind: []const u8) void { + switch (msg) { .src => |src| { - std.debug.print("{s}:{d}:{d}: error: {s}\n", .{ + std.debug.print("{s}:{d}:{d}: {s}: {s}\n", .{ src.src_path, src.line + 1, src.column + 1, + kind, src.msg, }); + for (src.notes) |note| { + note.renderToStdErrInner("note"); + } }, .plain => |plain| { - std.debug.print("error: {s}\n", .{plain.msg}); + std.debug.print("{s}: {s}\n", .{ kind, plain.msg }); }, } } @@ -261,20 +308,38 @@ pub const AllErrors = struct { } fn add( + module: *Module, arena: *std.heap.ArenaAllocator, errors: *std.ArrayList(Message), - sub_file_path: []const u8, - source: []const u8, - simple_err_msg: ErrorMsg, + module_err_msg: Module.ErrorMsg, ) !void { - const loc = std.zig.findLineColumn(source, simple_err_msg.byte_offset); + const notes = try arena.allocator.alloc(Message, module_err_msg.notes.len); + for (notes) |*note, i| { + const module_note = module_err_msg.notes[i]; + const source = try module_note.src_loc.file_scope.getSource(module); + const loc = std.zig.findLineColumn(source, module_note.src_loc.byte_offset); + const sub_file_path = module_note.src_loc.file_scope.sub_file_path; + note.* = .{ + .src = .{ + .src_path = try arena.allocator.dupe(u8, sub_file_path), + .msg = try arena.allocator.dupe(u8, module_note.msg), + .byte_offset = module_note.src_loc.byte_offset, + .line = loc.line, + .column = loc.column, + }, + }; + } + const source = try module_err_msg.src_loc.file_scope.getSource(module); + const loc = std.zig.findLineColumn(source, module_err_msg.src_loc.byte_offset); + const sub_file_path = module_err_msg.src_loc.file_scope.sub_file_path; try errors.append(.{ .src = .{ .src_path = try arena.allocator.dupe(u8, sub_file_path), - .msg = try arena.allocator.dupe(u8, simple_err_msg.msg), - .byte_offset = simple_err_msg.byte_offset, + .msg = try arena.allocator.dupe(u8, module_err_msg.msg), + .byte_offset = module_err_msg.src_loc.byte_offset, .line = loc.line, .column = loc.column, + .notes = notes, }, }); } @@ -331,7 +396,7 @@ pub const InitOptions = struct { root_name: []const u8, root_pkg: ?*Package, output_mode: std.builtin.OutputMode, - rand: *std.rand.Random, + thread_pool: *ThreadPool, dynamic_linker: ?[]const u8 = null, /// `null` means to not emit a binary file. emit_bin: ?EmitLoc, @@ -375,8 +440,11 @@ pub const InitOptions = struct { want_pie: ?bool = null, want_sanitize_c: ?bool = null, want_stack_check: ?bool = null, + want_red_zone: ?bool = null, want_valgrind: ?bool = null, + want_tsan: ?bool = null, want_compiler_rt: ?bool = null, + want_lto: ?bool = null, use_llvm: ?bool = null, use_lld: ?bool = null, use_clang: ?bool = null, @@ -400,6 +468,11 @@ pub const InitOptions = struct { disable_c_depfile: bool = false, linker_z_nodelete: bool = false, linker_z_defs: bool = false, + linker_tsaware: bool = false, + linker_nxcompat: bool = false, + linker_dynamicbase: bool = false, + major_subsystem_version: ?u32 = null, + minor_subsystem_version: ?u32 = null, clang_passthrough_mode: bool = false, verbose_cc: bool = false, verbose_link: bool = false, @@ -411,7 +484,12 @@ pub const InitOptions = struct { verbose_llvm_cpu_features: bool = false, is_test: bool = false, test_evented_io: bool = false, - is_compiler_rt_or_libc: bool = false, + debug_compiler_runtime_libs: bool = false, + /// Normally when you create a `Compilation`, Zig will automatically build + /// and link in required dependencies, such as compiler-rt and libc. When + /// building such dependencies themselves, this flag must be set to avoid + /// infinite recursion. + skip_linker_dependencies: bool = false, parent_compilation_link_libc: bool = false, stack_size_override: ?u64 = null, image_base_override: ?u64 = null, @@ -481,7 +559,7 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation { .Lib => is_dyn_lib, .Exe => true, }; - const needs_c_symbols = !options.is_compiler_rt_or_libc and + const needs_c_symbols = !options.skip_linker_dependencies and (is_exe_or_dyn_lib or (options.target.isWasm() and options.output_mode != .Obj)); const comp: *Compilation = comp: { @@ -504,6 +582,10 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation { if (options.root_pkg == null) break :blk false; + // If we are outputting .c code we must use Zig backend. + if (ofmt == .c) + break :blk false; + // If we are the stage1 compiler, we depend on the stage1 c++ llvm backend // to compile zig code. if (build_options.is_stage1) @@ -526,6 +608,12 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation { if (ofmt == .c) break :blk false; + if (options.want_lto) |lto| { + if (lto) { + break :blk true; + } + } + // Our linker can't handle objects or most advanced options yet. if (options.link_objects.len != 0 or options.c_source_files.len != 0 or @@ -557,7 +645,7 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation { }; const darwin_options: DarwinOptions = if (build_options.have_llvm and comptime std.Target.current.isDarwin()) outer: { - const opts: DarwinOptions = if (use_lld and options.is_native_os and options.target.isDarwin()) inner: { + const opts: DarwinOptions = if (use_lld and std.builtin.os.tag == .macos and options.target.isDarwin()) inner: { // TODO Revisit this targeting versions lower than macOS 11 when LLVM 12 is out. // See https://github.com/ziglang/zig/issues/6996 const at_least_big_sur = options.target.os.getVersionRange().semver.min.major >= 11; @@ -571,7 +659,29 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation { break :outer opts; } else .{}; - const link_libc = options.link_libc or target_util.osRequiresLibC(options.target); + const lto = blk: { + if (options.want_lto) |explicit| { + if (!use_lld) + return error.LtoUnavailableWithoutLld; + break :blk explicit; + } else if (!use_lld) { + break :blk false; + } else if (options.c_source_files.len == 0) { + break :blk false; + } else if (darwin_options.system_linker_hack) { + break :blk false; + } else switch (options.output_mode) { + .Lib, .Obj => break :blk false, + .Exe => switch (options.optimize_mode) { + .Debug => break :blk false, + .ReleaseSafe, .ReleaseFast, .ReleaseSmall => break :blk true, + }, + } + }; + + const tsan = options.want_tsan orelse false; + + const link_libc = options.link_libc or target_util.osRequiresLibC(options.target) or tsan; const must_dynamic_link = dl: { if (target_util.cannotDynamicLink(options.target)) @@ -633,12 +743,12 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation { ); const must_pie = target_util.requiresPIE(options.target); - const pie = if (options.want_pie) |explicit| pie: { + const pie: bool = if (options.want_pie) |explicit| pie: { if (!explicit and must_pie) { return error.TargetRequiresPIE; } break :pie explicit; - } else must_pie; + } else must_pie or tsan; const must_pic: bool = b: { if (target_util.requiresPIC(options.target, link_libc)) @@ -657,6 +767,9 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation { break :pic explicit; } else pie or must_pic; + // TSAN is implemented in C++ so it requires linking libc++. + const link_libcpp = options.link_libcpp or tsan; + // Make a decision on whether to use Clang for translate-c and compiling C files. const use_clang = if (options.use_clang) |explicit| explicit else blk: { if (build_options.have_llvm) { @@ -706,11 +819,12 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation { } assert(mem.endsWith(u8, buf.items, ",")); buf.items[buf.items.len - 1] = 0; - buf.shrink(buf.items.len); + buf.shrinkAndFree(buf.items.len); break :blk buf.items[0 .. buf.items.len - 1 :0].ptr; } else null; const strip = options.strip or !target_util.hasDebugInfo(options.target); + const red_zone = options.want_red_zone orelse target_util.hasRedZone(options.target); // We put everything into the cache hash that *cannot be modified during an incremental update*. // For example, one cannot change the target between updates, but one can change source files, @@ -739,12 +853,15 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation { cache.hash.add(ofmt); cache.hash.add(pic); cache.hash.add(pie); + cache.hash.add(lto); + cache.hash.add(tsan); cache.hash.add(stack_check); + cache.hash.add(red_zone); cache.hash.add(link_mode); cache.hash.add(options.function_sections); cache.hash.add(strip); cache.hash.add(link_libc); - cache.hash.add(options.link_libcpp); + cache.hash.add(link_libcpp); cache.hash.add(options.output_mode); cache.hash.add(options.machine_code_model); cache.hash.addOptionalEmitLoc(options.emit_bin); @@ -772,7 +889,7 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation { hash.add(single_threaded); hash.add(dll_export_fns); hash.add(options.is_test); - hash.add(options.is_compiler_rt_or_libc); + hash.add(options.skip_linker_dependencies); hash.add(options.parent_compilation_link_libc); const digest = hash.final(); @@ -795,33 +912,27 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation { const root_scope = rs: { if (mem.endsWith(u8, root_pkg.root_src_path, ".zig")) { - const struct_payload = try gpa.create(Type.Payload.EmptyStruct); const root_scope = try gpa.create(Module.Scope.File); - struct_payload.* = .{ .scope = &root_scope.root_container }; + const struct_ty = try Type.Tag.empty_struct.create( + gpa, + &root_scope.root_container, + ); root_scope.* = .{ // TODO this is duped so it can be freed in Container.deinit .sub_file_path = try gpa.dupe(u8, root_pkg.root_src_path), .source = .{ .unloaded = {} }, - .contents = .{ .not_available = {} }, + .tree = undefined, .status = .never_loaded, .pkg = root_pkg, .root_container = .{ .file_scope = root_scope, .decls = .{}, - .ty = Type.initPayload(&struct_payload.base), + .ty = struct_ty, }, }; - break :rs &root_scope.base; + break :rs root_scope; } else if (mem.endsWith(u8, root_pkg.root_src_path, ".zir")) { - const root_scope = try gpa.create(Module.Scope.ZIRModule); - root_scope.* = .{ - .sub_file_path = root_pkg.root_src_path, - .source = .{ .unloaded = {} }, - .contents = .{ .not_available = {} }, - .status = .never_loaded, - .decls = .{}, - }; - break :rs &root_scope.base; + return error.ZirFilesUnsupported; } else { unreachable; } @@ -834,9 +945,13 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation { .root_pkg = root_pkg, .root_scope = root_scope, .zig_cache_artifact_directory = zig_cache_artifact_directory, + .emit_h = options.emit_h, }; break :blk module; - } else null; + } else blk: { + if (options.emit_h != null) return error.NoZigModuleForCHeader; + break :blk null; + }; errdefer if (module) |zm| zm.deinit(); const error_return_tracing = !strip and switch (options.optimize_mode) { @@ -888,10 +1003,6 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation { }; }; - if (!use_llvm and options.emit_h != null) { - fatal("TODO implement support for -femit-h in the self-hosted backend", .{}); - } - var system_libs: std.StringArrayHashMapUnmanaged(void) = .{}; errdefer system_libs.deinit(gpa); try system_libs.ensureCapacity(gpa, options.system_libs.len); @@ -913,7 +1024,7 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation { .use_llvm = use_llvm, .system_linker_hack = darwin_options.system_linker_hack, .link_libc = link_libc, - .link_libcpp = options.link_libcpp, + .link_libcpp = link_libcpp, .objects = options.link_objects, .frameworks = options.frameworks, .framework_dirs = options.framework_dirs, @@ -929,6 +1040,11 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation { .bind_global_refs_locally = options.linker_bind_global_refs_locally orelse false, .z_nodelete = options.linker_z_nodelete, .z_defs = options.linker_z_defs, + .tsaware = options.linker_tsaware, + .nxcompat = options.linker_nxcompat, + .dynamicbase = options.linker_dynamicbase, + .major_subsystem_version = options.major_subsystem_version, + .minor_subsystem_version = options.minor_subsystem_version, .stack_size_override = options.stack_size_override, .image_base_override = options.image_base_override, .include_compiler_rt = include_compiler_rt, @@ -944,15 +1060,18 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation { .libc_installation = libc_dirs.libc_installation, .pic = pic, .pie = pie, + .lto = lto, .valgrind = valgrind, + .tsan = tsan, .stack_check = stack_check, + .red_zone = red_zone, .single_threaded = single_threaded, .verbose_link = options.verbose_link, .machine_code_model = options.machine_code_model, .dll_export_fns = dll_export_fns, .error_return_tracing = error_return_tracing, .llvm_cpu_features = llvm_cpu_features, - .is_compiler_rt_or_libc = options.is_compiler_rt_or_libc, + .skip_linker_dependencies = options.skip_linker_dependencies, .parent_compilation_link_libc = options.parent_compilation_link_libc, .each_lib_rpath = options.each_lib_rpath orelse options.is_native_os, .disable_lld_caching = options.disable_lld_caching, @@ -967,12 +1086,12 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation { .local_cache_directory = options.local_cache_directory, .global_cache_directory = options.global_cache_directory, .bin_file = bin_file, - .emit_h = options.emit_h, .emit_asm = options.emit_asm, .emit_llvm_ir = options.emit_llvm_ir, .emit_analysis = options.emit_analysis, .emit_docs = options.emit_docs, .work_queue = std.fifo.LinearFifo(Job, .Dynamic).init(gpa), + .c_object_work_queue = std.fifo.LinearFifo(*CObject, .Dynamic).init(gpa), .keep_source_files_loaded = options.keep_source_files_loaded, .use_clang = use_clang, .clang_argv = options.clang_argv, @@ -981,7 +1100,7 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation { .self_exe_path = options.self_exe_path, .libc_include_dir_list = libc_dirs.libc_include_dir_list, .sanitize_c = sanitize_c, - .rand = options.rand, + .thread_pool = options.thread_pool, .clang_passthrough_mode = options.clang_passthrough_mode, .clang_preprocessor_mode = options.clang_preprocessor_mode, .verbose_cc = options.verbose_cc, @@ -999,11 +1118,16 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation { .test_filter = options.test_filter, .test_name_prefix = options.test_name_prefix, .test_evented_io = options.test_evented_io, + .debug_compiler_runtime_libs = options.debug_compiler_runtime_libs, + .work_queue_wait_group = undefined, }; break :comp comp; }; errdefer comp.destroy(); + try comp.work_queue_wait_group.init(); + errdefer comp.work_queue_wait_group.deinit(); + if (comp.bin_file.options.module) |mod| { try comp.work_queue.writeItem(.{ .generate_builtin_zig = {} }); } @@ -1021,7 +1145,7 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation { comp.c_object_table.putAssumeCapacityNoClobber(c_object, {}); } - if (comp.bin_file.options.emit != null and !comp.bin_file.options.is_compiler_rt_or_libc) { + if (comp.bin_file.options.emit != null and !comp.bin_file.options.skip_linker_dependencies) { // 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()) { @@ -1074,12 +1198,13 @@ pub fn create(gpa: *Allocator, options: InitOptions) !*Compilation { if (comp.wantBuildLibUnwindFromSource()) { try comp.work_queue.writeItem(.{ .libunwind = {} }); } - if (build_options.have_llvm and comp.bin_file.options.output_mode != .Obj and - comp.bin_file.options.link_libcpp) - { + if (build_options.have_llvm and is_exe_or_dyn_lib and comp.bin_file.options.link_libcpp) { try comp.work_queue.writeItem(.libcxx); try comp.work_queue.writeItem(.libcxxabi); } + if (build_options.have_llvm and comp.bin_file.options.tsan) { + try comp.work_queue.writeItem(.libtsan); + } // The `is_stage1` condition is here only because stage2 cannot yet build compiler-rt. // Once it is capable this condition should be removed. @@ -1137,6 +1262,7 @@ pub fn destroy(self: *Compilation) void { const gpa = self.gpa; self.work_queue.deinit(); + self.c_object_work_queue.deinit(); { var it = self.crt_files.iterator(); @@ -1166,6 +1292,10 @@ pub fn destroy(self: *Compilation) void { crt_file.deinit(gpa); } + if (self.glibc_so_files) |*glibc_file| { + glibc_file.deinit(gpa); + } + for (self.c_object_table.items()) |entry| { entry.key.destroy(gpa); } @@ -1180,6 +1310,8 @@ pub fn destroy(self: *Compilation) void { self.cache_parent.manifest_dir.close(); if (self.owned_link_dir) |*dir| dir.close(); + self.work_queue_wait_group.deinit(); + // This destroys `self`. self.arena_state.promote(gpa).deinit(); } @@ -1193,42 +1325,35 @@ pub fn update(self: *Compilation) !void { const tracy = trace(@src()); defer tracy.end(); + self.c_object_cache_digest_set.clearRetainingCapacity(); + // For compiling C objects, we rely on the cache hash system to avoid duplicating work. // Add a Job for each C object. - try self.work_queue.ensureUnusedCapacity(self.c_object_table.items().len); + try self.c_object_work_queue.ensureUnusedCapacity(self.c_object_table.items().len); for (self.c_object_table.items()) |entry| { - self.work_queue.writeItemAssumeCapacity(.{ .c_object = entry.key }); + self.c_object_work_queue.writeItemAssumeCapacity(entry.key); } - const use_stage1 = build_options.is_stage1 and self.bin_file.options.use_llvm; + const use_stage1 = build_options.omit_stage2 or build_options.is_stage1 and self.bin_file.options.use_llvm; if (!use_stage1) { if (self.bin_file.options.module) |module| { + module.compile_log_text.shrinkAndFree(module.gpa, 0); module.generation += 1; // TODO Detect which source files changed. // Until then we simulate a full cache miss. Source files could have been loaded for any reason; // to force a refresh we unload now. - if (module.root_scope.cast(Module.Scope.File)) |zig_file| { - zig_file.unload(module.gpa); - module.failed_root_src_file = null; - module.analyzeContainer(&zig_file.root_container) catch |err| switch (err) { - error.AnalysisFail => { - assert(self.totalErrorCount() != 0); - }, - error.OutOfMemory => return error.OutOfMemory, - else => |e| { - module.failed_root_src_file = e; - }, - }; - } else if (module.root_scope.cast(Module.Scope.ZIRModule)) |zir_module| { - zir_module.unload(module.gpa); - module.analyzeRootZIRModule(zir_module) catch |err| switch (err) { - error.AnalysisFail => { - assert(self.totalErrorCount() != 0); - }, - else => |e| return e, - }; - } + module.root_scope.unload(module.gpa); + module.failed_root_src_file = null; + module.analyzeContainer(&module.root_scope.root_container) catch |err| switch (err) { + error.AnalysisFail => { + assert(self.totalErrorCount() != 0); + }, + error.OutOfMemory => return error.OutOfMemory, + else => |e| { + module.failed_root_src_file = e; + }, + }; // TODO only analyze imports if they are still referenced for (module.import_table.items()) |entry| { @@ -1266,9 +1391,14 @@ pub fn update(self: *Compilation) !void { // This is needed before reading the error flags. try self.bin_file.flush(self); - self.link_error_flags = self.bin_file.errorFlags(); + if (!use_stage1) { + if (self.bin_file.options.module) |module| { + try link.File.C.flushEmitH(module); + } + } + // If there are any errors, we anticipate the source files being loaded // to report error messages. Otherwise we unload all source files to save memory. if (self.totalErrorCount() == 0 and !self.keep_source_files_loaded) { @@ -1294,7 +1424,8 @@ pub fn totalErrorCount(self: *Compilation) usize { var total: usize = self.failed_c_objects.items().len; if (self.bin_file.options.module) |module| { - total += module.failed_decls.items().len + + total += module.failed_decls.count() + + module.emit_h_failed_decls.count() + module.failed_exports.items().len + module.failed_files.items().len + @boolToInt(module.failed_root_src_file != null); @@ -1302,7 +1433,14 @@ pub fn totalErrorCount(self: *Compilation) usize { // The "no entry point found" error only counts if there are no other errors. if (total == 0) { - return @boolToInt(self.link_error_flags.no_entry_point_found); + total += @boolToInt(self.link_error_flags.no_entry_point_found); + } + + // Compile log errors only count if there are no other errors. + if (total == 0) { + if (self.bin_file.options.module) |module| { + total += @boolToInt(module.compile_log_decls.items().len != 0); + } } return total; @@ -1318,26 +1456,32 @@ pub fn getAllErrorsAlloc(self: *Compilation) !AllErrors { for (self.failed_c_objects.items()) |entry| { const c_object = entry.key; const err_msg = entry.value; - try AllErrors.add(&arena, &errors, c_object.src.src_path, "", err_msg.*); + // TODO these fields will need to be adjusted when we have proper + // C error reporting bubbling up. + try errors.append(.{ + .src = .{ + .src_path = try arena.allocator.dupe(u8, c_object.src.src_path), + .msg = try std.fmt.allocPrint(&arena.allocator, "unable to build C object: {s}", .{ + err_msg.msg, + }), + .byte_offset = 0, + .line = err_msg.line, + .column = err_msg.column, + }, + }); } if (self.bin_file.options.module) |module| { for (module.failed_files.items()) |entry| { - const scope = entry.key; - const err_msg = entry.value; - const source = try scope.getSource(module); - try AllErrors.add(&arena, &errors, scope.subFilePath(), source, err_msg.*); + try AllErrors.add(module, &arena, &errors, entry.value.*); } for (module.failed_decls.items()) |entry| { - const decl = entry.key; - const err_msg = entry.value; - const source = try decl.scope.getSource(module); - try AllErrors.add(&arena, &errors, decl.scope.subFilePath(), source, err_msg.*); + try AllErrors.add(module, &arena, &errors, entry.value.*); + } + for (module.emit_h_failed_decls.items()) |entry| { + try AllErrors.add(module, &arena, &errors, entry.value.*); } for (module.failed_exports.items()) |entry| { - const decl = entry.key.owner_decl; - const err_msg = entry.value; - const source = try decl.scope.getSource(module); - try AllErrors.add(&arena, &errors, decl.scope.subFilePath(), source, err_msg.*); + try AllErrors.add(module, &arena, &errors, entry.value.*); } if (module.failed_root_src_file) |err| { const file_path = try module.root_pkg.root_src_directory.join(&arena.allocator, &[_][]const u8{ @@ -1358,6 +1502,28 @@ pub fn getAllErrorsAlloc(self: *Compilation) !AllErrors { }); } + if (self.bin_file.options.module) |module| { + const compile_log_items = module.compile_log_decls.items(); + if (errors.items.len == 0 and compile_log_items.len != 0) { + // First one will be the error; subsequent ones will be notes. + const err_msg = Module.ErrorMsg{ + .src_loc = compile_log_items[0].value, + .msg = "found compile log statement", + .notes = try self.gpa.alloc(Module.ErrorMsg, compile_log_items.len - 1), + }; + defer self.gpa.free(err_msg.notes); + + for (compile_log_items[1..]) |entry, i| { + err_msg.notes[i] = .{ + .src_loc = entry.value, + .msg = "also here", + }; + } + + try AllErrors.add(module, &arena, &errors, err_msg); + } + } + assert(errors.items.len == self.totalErrorCount()); return AllErrors{ @@ -1366,15 +1532,32 @@ pub fn getAllErrorsAlloc(self: *Compilation) !AllErrors { }; } +pub fn getCompileLogOutput(self: *Compilation) []const u8 { + const module = self.bin_file.options.module orelse return &[0]u8{}; + return module.compile_log_text.items; +} + pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemory }!void { - var progress: std.Progress = .{}; - var main_progress_node = try progress.start("", null); + // If the terminal is dumb, we dont want to show the user all the + // output. + var progress: std.Progress = .{ .dont_print_on_dumb = true }; + var main_progress_node = try progress.start("", 0); defer main_progress_node.end(); if (self.color == .off) progress.terminal = null; var c_comp_progress_node = main_progress_node.start("Compile C Objects", self.c_source_files.len); defer c_comp_progress_node.end(); + self.work_queue_wait_group.reset(); + defer self.work_queue_wait_group.wait(); + + while (self.c_object_work_queue.readItem()) |c_object| { + self.work_queue_wait_group.start(); + try self.thread_pool.spawn(workerUpdateCObject, .{ + self, c_object, &c_comp_progress_node, &self.work_queue_wait_group, + }); + } + while (self.work_queue.readItem()) |work_item| switch (work_item) { .codegen_decl => |decl| switch (decl.analysis) { .unreferenced => unreachable, @@ -1388,49 +1571,106 @@ pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemor => continue, .complete, .codegen_failure_retryable => { + if (build_options.omit_stage2) + @panic("sadly stage2 is omitted from this build to save memory on the CI server"); const module = self.bin_file.options.module.?; - if (decl.typed_value.most_recent.typed_value.val.cast(Value.Payload.Function)) |payload| { - switch (payload.func.analysis) { - .queued => module.analyzeFnBody(decl, payload.func) catch |err| switch (err) { + if (decl.typed_value.most_recent.typed_value.val.castTag(.function)) |payload| { + const func = payload.data; + switch (func.state) { + .queued => module.analyzeFnBody(decl, func) catch |err| switch (err) { error.AnalysisFail => { - assert(payload.func.analysis != .in_progress); + assert(func.state != .in_progress); continue; }, error.OutOfMemory => return error.OutOfMemory, }, .in_progress => unreachable, + .inline_only => unreachable, // don't queue work for this .sema_failure, .dependency_failure => continue, .success => {}, } - // Here we tack on additional allocations to the Decl's arena. The allocations are - // lifetime annotations in the ZIR. + // Here we tack on additional allocations to the Decl's arena. The allocations + // are lifetime annotations in the ZIR. var decl_arena = decl.typed_value.most_recent.arena.?.promote(module.gpa); defer decl.typed_value.most_recent.arena.?.* = decl_arena.state; - log.debug("analyze liveness of {}\n", .{decl.name}); - try liveness.analyze(module.gpa, &decl_arena.allocator, payload.func.analysis.success); + log.debug("analyze liveness of {s}", .{decl.name}); + try liveness.analyze(module.gpa, &decl_arena.allocator, func.body); + + if (std.builtin.mode == .Debug and self.verbose_ir) { + func.dump(module.*); + } } + log.debug("calling updateDecl on '{s}', type={}", .{ + decl.name, decl.typed_value.most_recent.typed_value.ty, + }); assert(decl.typed_value.most_recent.typed_value.ty.hasCodeGenBits()); self.bin_file.updateDecl(module, decl) catch |err| switch (err) { error.OutOfMemory => return error.OutOfMemory, error.AnalysisFail => { - decl.analysis = .dependency_failure; + decl.analysis = .codegen_failure; + continue; }, else => { try module.failed_decls.ensureCapacity(module.gpa, module.failed_decls.items().len + 1); - module.failed_decls.putAssumeCapacityNoClobber(decl, try ErrorMsg.create( + module.failed_decls.putAssumeCapacityNoClobber(decl, try Module.ErrorMsg.create( module.gpa, - decl.src(), - "unable to codegen: {}", + decl.srcLoc(), + "unable to codegen: {s}", .{@errorName(err)}, )); decl.analysis = .codegen_failure_retryable; + continue; + }, + }; + }, + }, + .emit_h_decl => |decl| switch (decl.analysis) { + .unreferenced => unreachable, + .in_progress => unreachable, + .outdated => unreachable, + + .sema_failure, + .dependency_failure, + .sema_failure_retryable, + => continue, + + // emit-h only requires semantic analysis of the Decl to be complete, + // it does not depend on machine code generation to succeed. + .codegen_failure, .codegen_failure_retryable, .complete => { + if (build_options.omit_stage2) + @panic("sadly stage2 is omitted from this build to save memory on the CI server"); + const module = self.bin_file.options.module.?; + const emit_loc = module.emit_h.?; + const tv = decl.typed_value.most_recent.typed_value; + const emit_h = decl.getEmitH(module); + const fwd_decl = &emit_h.fwd_decl; + fwd_decl.shrinkRetainingCapacity(0); + + var dg: c_codegen.DeclGen = .{ + .module = module, + .error_msg = null, + .decl = decl, + .fwd_decl = fwd_decl.toManaged(module.gpa), + }; + defer dg.fwd_decl.deinit(); + + c_codegen.genHeader(&dg) catch |err| switch (err) { + error.AnalysisFail => { + try module.emit_h_failed_decls.put(module.gpa, decl, dg.error_msg.?); + continue; }, + else => |e| return e, }; + + fwd_decl.* = dg.fwd_decl.moveToUnmanaged(); + fwd_decl.shrinkAndFree(module.gpa, fwd_decl.items.len); }, }, .analyze_decl => |decl| { + if (build_options.omit_stage2) + @panic("sadly stage2 is omitted from this build to save memory on the CI server"); const module = self.bin_file.options.module.?; module.ensureDeclAnalyzed(decl) catch |err| switch (err) { error.OutOfMemory => return error.OutOfMemory, @@ -1438,80 +1678,73 @@ pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemor }; }, .update_line_number => |decl| { + if (build_options.omit_stage2) + @panic("sadly stage2 is omitted from this build to save memory on the CI server"); const module = self.bin_file.options.module.?; self.bin_file.updateDeclLineNumber(module, decl) catch |err| { try module.failed_decls.ensureCapacity(module.gpa, module.failed_decls.items().len + 1); - module.failed_decls.putAssumeCapacityNoClobber(decl, try ErrorMsg.create( + module.failed_decls.putAssumeCapacityNoClobber(decl, try Module.ErrorMsg.create( module.gpa, - decl.src(), - "unable to update line number: {}", + decl.srcLoc(), + "unable to update line number: {s}", .{@errorName(err)}, )); decl.analysis = .codegen_failure_retryable; }; }, - .c_object => |c_object| { - self.updateCObject(c_object, &c_comp_progress_node) catch |err| switch (err) { - error.AnalysisFail => continue, - else => { - try self.failed_c_objects.ensureCapacity(self.gpa, self.failed_c_objects.items().len + 1); - self.failed_c_objects.putAssumeCapacityNoClobber(c_object, try ErrorMsg.create( - self.gpa, - 0, - "unable to build C object: {s}", - .{@errorName(err)}, - )); - c_object.status = .{ .failure = {} }; - }, - }; - }, .glibc_crt_file => |crt_file| { glibc.buildCRTFile(self, crt_file) catch |err| { // TODO Expose this as a normal compile error rather than crashing here. - fatal("unable to build glibc CRT file: {}", .{@errorName(err)}); + fatal("unable to build glibc CRT file: {s}", .{@errorName(err)}); }; }, .glibc_shared_objects => { glibc.buildSharedObjects(self) catch |err| { // TODO Expose this as a normal compile error rather than crashing here. - fatal("unable to build glibc shared objects: {}", .{@errorName(err)}); + fatal("unable to build glibc shared objects: {s}", .{@errorName(err)}); }; }, .musl_crt_file => |crt_file| { musl.buildCRTFile(self, crt_file) catch |err| { // TODO Expose this as a normal compile error rather than crashing here. - fatal("unable to build musl CRT file: {}", .{@errorName(err)}); + fatal("unable to build musl CRT file: {s}", .{@errorName(err)}); }; }, .mingw_crt_file => |crt_file| { mingw.buildCRTFile(self, crt_file) catch |err| { // TODO Expose this as a normal compile error rather than crashing here. - fatal("unable to build mingw-w64 CRT file: {}", .{@errorName(err)}); + fatal("unable to build mingw-w64 CRT file: {s}", .{@errorName(err)}); }; }, .windows_import_lib => |index| { const link_lib = self.bin_file.options.system_libs.items()[index].key; mingw.buildImportLib(self, link_lib) catch |err| { // TODO Expose this as a normal compile error rather than crashing here. - fatal("unable to generate DLL import .lib file: {}", .{@errorName(err)}); + fatal("unable to generate DLL import .lib file: {s}", .{@errorName(err)}); }; }, .libunwind => { libunwind.buildStaticLib(self) catch |err| { // TODO Expose this as a normal compile error rather than crashing here. - fatal("unable to build libunwind: {}", .{@errorName(err)}); + fatal("unable to build libunwind: {s}", .{@errorName(err)}); }; }, .libcxx => { libcxx.buildLibCXX(self) catch |err| { // TODO Expose this as a normal compile error rather than crashing here. - fatal("unable to build libcxx: {}", .{@errorName(err)}); + fatal("unable to build libcxx: {s}", .{@errorName(err)}); }; }, .libcxxabi => { libcxx.buildLibCXXABI(self) catch |err| { // TODO Expose this as a normal compile error rather than crashing here. - fatal("unable to build libcxxabi: {}", .{@errorName(err)}); + fatal("unable to build libcxxabi: {s}", .{@errorName(err)}); + }; + }, + .libtsan => { + libtsan.buildTsan(self) catch |err| { + // TODO Expose this as a normal compile error rather than crashing here. + fatal("unable to build TSAN library: {s}", .{@errorName(err)}); }; }, .compiler_rt_lib => { @@ -1529,20 +1762,20 @@ pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemor .libssp => { self.buildOutputFromZig("ssp.zig", .Lib, &self.libssp_static_lib) catch |err| { // TODO Expose this as a normal compile error rather than crashing here. - fatal("unable to build libssp: {}", .{@errorName(err)}); + fatal("unable to build libssp: {s}", .{@errorName(err)}); }; }, .zig_libc => { self.buildOutputFromZig("c.zig", .Lib, &self.libc_static_lib) catch |err| { // TODO Expose this as a normal compile error rather than crashing here. - fatal("unable to build zig's multitarget libc: {}", .{@errorName(err)}); + fatal("unable to build zig's multitarget libc: {s}", .{@errorName(err)}); }; }, .generate_builtin_zig => { // This Job is only queued up if there is a zig module. self.updateBuiltinZigFile(self.bin_file.options.module.?) catch |err| { // TODO Expose this as a normal compile error rather than crashing here. - fatal("unable to update builtin.zig file: {}", .{@errorName(err)}); + fatal("unable to update builtin.zig file: {s}", .{@errorName(err)}); }; }, .stage1_module => { @@ -1550,13 +1783,13 @@ pub fn performAllTheWork(self: *Compilation) error{ TimerUnsupported, OutOfMemor unreachable; self.updateStage1Module(main_progress_node) catch |err| { - fatal("unable to build stage1 zig object: {}", .{@errorName(err)}); + fatal("unable to build stage1 zig object: {s}", .{@errorName(err)}); }; }, }; } -pub fn obtainCObjectCacheManifest(comp: *Compilation) Cache.Manifest { +pub fn obtainCObjectCacheManifest(comp: *const Compilation) Cache.Manifest { var man = comp.cache_parent.obtain(); // Only things that need to be added on top of the base hash, and only things @@ -1622,11 +1855,11 @@ pub fn cImport(comp: *Compilation, c_src: []const u8) !CImportResult { const out_h_path = try comp.local_cache_directory.join(arena, &[_][]const u8{ tmp_dir_sub_path, cimport_basename, }); - const out_dep_path = try std.fmt.allocPrint(arena, "{}.d", .{out_h_path}); + const out_dep_path = try std.fmt.allocPrint(arena, "{s}.d", .{out_h_path}); try zig_cache_tmp_dir.writeFile(cimport_basename, c_src); if (comp.verbose_cimport) { - log.info("C import source: {}", .{out_h_path}); + log.info("C import source: {s}", .{out_h_path}); } var argv = std.ArrayList([]const u8).init(comp.gpa); @@ -1651,7 +1884,7 @@ pub fn cImport(comp: *Compilation, c_src: []const u8) !CImportResult { const c_headers_dir_path = try comp.zig_lib_directory.join(arena, &[_][]const u8{"include"}); const c_headers_dir_path_z = try arena.dupeZ(u8, c_headers_dir_path); var clang_errors: []translate_c.ClangErrMsg = &[0]translate_c.ClangErrMsg{}; - const tree = translate_c.translate( + var tree = translate_c.translate( comp.gpa, new_argv.ptr, new_argv.ptr + new_argv.len, @@ -1670,10 +1903,10 @@ pub fn cImport(comp: *Compilation, c_src: []const u8) !CImportResult { }; }, }; - defer tree.deinit(); + defer tree.deinit(comp.gpa); if (comp.verbose_cimport) { - log.info("C import .d file: {}", .{out_dep_path}); + log.info("C import .d file: {s}", .{out_dep_path}); } const dep_basename = std.fs.path.basename(out_dep_path); @@ -1688,12 +1921,13 @@ pub fn cImport(comp: *Compilation, c_src: []const u8) !CImportResult { var out_zig_file = try o_dir.createFile(cimport_zig_basename, .{}); defer out_zig_file.close(); - var bos = std.io.bufferedOutStream(out_zig_file.writer()); - _ = try std.zig.render(comp.gpa, bos.writer(), tree); - try bos.flush(); + const formatted = try tree.render(comp.gpa); + defer comp.gpa.free(formatted); + + try out_zig_file.writeAll(formatted); man.writeManifest() catch |err| { - log.warn("failed to write cache manifest for C import: {}", .{@errorName(err)}); + log.warn("failed to write cache manifest for C import: {s}", .{@errorName(err)}); }; break :digest digest; @@ -1703,7 +1937,7 @@ pub fn cImport(comp: *Compilation, c_src: []const u8) !CImportResult { "o", &digest, cimport_zig_basename, }); if (comp.verbose_cimport) { - log.info("C import output: {}\n", .{out_zig_path}); + log.info("C import output: {s}", .{out_zig_path}); } return CImportResult{ .out_zig_path = out_zig_path, @@ -1711,6 +1945,49 @@ pub fn cImport(comp: *Compilation, c_src: []const u8) !CImportResult { }; } +fn workerUpdateCObject( + comp: *Compilation, + c_object: *CObject, + progress_node: *std.Progress.Node, + wg: *WaitGroup, +) void { + defer wg.finish(); + + comp.updateCObject(c_object, progress_node) catch |err| switch (err) { + error.AnalysisFail => return, + else => { + comp.reportRetryableCObjectError(c_object, err) catch |oom| switch (oom) { + // Swallowing this error is OK because it's implied to be OOM when + // there is a missing failed_c_objects error message. + error.OutOfMemory => {}, + }; + }, + }; +} + +fn reportRetryableCObjectError( + comp: *Compilation, + c_object: *CObject, + err: anyerror, +) error{OutOfMemory}!void { + c_object.status = .failure_retryable; + + const c_obj_err_msg = try comp.gpa.create(CObject.ErrorMsg); + errdefer comp.gpa.destroy(c_obj_err_msg); + const msg = try std.fmt.allocPrint(comp.gpa, "unable to build C object: {s}", .{@errorName(err)}); + errdefer comp.gpa.free(msg); + c_obj_err_msg.* = .{ + .msg = msg, + .line = 0, + .column = 0, + }; + { + const lock = comp.mutex.acquire(); + defer lock.release(); + try comp.failed_c_objects.putNoClobber(comp.gpa, c_object, c_obj_err_msg); + } +} + fn updateCObject(comp: *Compilation, c_object: *CObject, c_comp_progress_node: *std.Progress.Node) !void { if (!build_options.have_llvm) { return comp.failCObj(c_object, "clang not available: compiler built without LLVM extensions", .{}); @@ -1723,7 +2000,11 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_comp_progress_node: * if (c_object.clearStatus(comp.gpa)) { // There was previous failure. - comp.failed_c_objects.removeAssertDiscard(c_object); + const lock = comp.mutex.acquire(); + defer lock.release(); + // If the failure was OOM, there will not be an entry here, so we do + // not assert discard. + _ = comp.failed_c_objects.swapRemove(c_object); } var man = comp.obtainCObjectCacheManifest(); @@ -1750,8 +2031,16 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_comp_progress_node: * } { - const gop = try comp.c_object_cache_digest_set.getOrPut(comp.gpa, man.hash.peekBin()); - if (gop.found_existing) { + const is_collision = blk: { + const bin_digest = man.hash.peekBin(); + + const lock = comp.mutex.acquire(); + defer lock.release(); + + const gop = try comp.c_object_cache_digest_set.getOrPut(comp.gpa, bin_digest); + break :blk gop.found_existing; + }; + if (is_collision) { return comp.failCObj( c_object, "the same source file was already added to the same compilation with the same flags", @@ -1767,7 +2056,7 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_comp_progress_node: * const c_source_basename = std.fs.path.basename(c_object.src.src_path); c_comp_progress_node.activate(); - var child_progress_node = c_comp_progress_node.start(c_source_basename, null); + var child_progress_node = c_comp_progress_node.start(c_source_basename, 0); child_progress_node.activate(); defer child_progress_node.end(); @@ -1823,7 +2112,7 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_comp_progress_node: * child.stderr_behavior = .Inherit; const term = child.spawnAndWait() catch |err| { - return comp.failCObj(c_object, "unable to spawn {}: {}", .{ argv.items[0], @errorName(err) }); + return comp.failCObj(c_object, "unable to spawn {s}: {s}", .{ argv.items[0], @errorName(err) }); }; switch (term) { .Exited => |code| { @@ -1851,7 +2140,7 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_comp_progress_node: * const stderr = try stderr_reader.readAllAlloc(arena, 10 * 1024 * 1024); const term = child.wait() catch |err| { - return comp.failCObj(c_object, "unable to spawn {}: {}", .{ argv.items[0], @errorName(err) }); + return comp.failCObj(c_object, "unable to spawn {s}: {s}", .{ argv.items[0], @errorName(err) }); }; switch (term) { @@ -1859,12 +2148,12 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_comp_progress_node: * if (code != 0) { // TODO parse clang stderr and turn it into an error message // and then call failCObjWithOwnedErrorMsg - log.err("clang failed with stderr: {}", .{stderr}); - return comp.failCObj(c_object, "clang exited with code {}", .{code}); + log.err("clang failed with stderr: {s}", .{stderr}); + return comp.failCObj(c_object, "clang exited with code {d}", .{code}); } }, else => { - log.err("clang terminated with stderr: {}", .{stderr}); + log.err("clang terminated with stderr: {s}", .{stderr}); return comp.failCObj(c_object, "clang terminated unexpectedly", .{}); }, } @@ -1876,7 +2165,7 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_comp_progress_node: * try man.addDepFilePost(zig_cache_tmp_dir, dep_basename); // Just to save disk space, we delete the file because it is never needed again. zig_cache_tmp_dir.deleteFile(dep_basename) catch |err| { - log.warn("failed to delete '{}': {}", .{ dep_file_path, @errorName(err) }); + log.warn("failed to delete '{s}': {s}", .{ dep_file_path, @errorName(err) }); }; } @@ -1892,7 +2181,7 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_comp_progress_node: * try std.fs.rename(zig_cache_tmp_dir, tmp_basename, o_dir, o_basename); man.writeManifest() catch |err| { - log.warn("failed to write cache manifest when compiling '{}': {}", .{ 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) }); }; break :blk digest; }; @@ -1909,9 +2198,9 @@ fn updateCObject(comp: *Compilation, c_object: *CObject, c_comp_progress_node: * pub fn tmpFilePath(comp: *Compilation, arena: *Allocator, suffix: []const u8) error{OutOfMemory}![]const u8 { const s = std.fs.path.sep_str; - const rand_int = comp.rand.int(u64); + const rand_int = std.crypto.random.int(u64); if (comp.local_cache_directory.path) |p| { - return std.fmt.allocPrint(arena, "{}" ++ s ++ "tmp" ++ s ++ "{x}-{s}", .{ p, rand_int, suffix }); + return std.fmt.allocPrint(arena, "{s}" ++ s ++ "tmp" ++ s ++ "{x}-{s}", .{ p, rand_int, suffix }); } else { return std.fmt.allocPrint(arena, "tmp" ++ s ++ "{x}-{s}", .{ rand_int, suffix }); } @@ -1932,7 +2221,7 @@ pub fn addTranslateCCArgs( /// Add common C compiler args between translate-c and C object compilation. pub fn addCCArgs( - comp: *Compilation, + comp: *const Compilation, arena: *Allocator, argv: *std.ArrayList([]const u8), ext: FileExt, @@ -1956,12 +2245,6 @@ pub fn addCCArgs( try argv.append("-ffunction-sections"); } - try argv.ensureCapacity(argv.items.len + comp.bin_file.options.framework_dirs.len * 2); - for (comp.bin_file.options.framework_dirs) |framework_dir| { - argv.appendAssumeCapacity("-iframework"); - argv.appendAssumeCapacity(framework_dir); - } - if (comp.bin_file.options.link_libcpp) { const libcxx_include_path = try std.fs.path.join(arena, &[_][]const u8{ comp.zig_lib_directory.path.?, "libcxx", "include", @@ -1992,6 +2275,9 @@ pub fn addCCArgs( "-nostdinc", "-fno-spell-checking", }); + if (comp.bin_file.options.lto) { + try argv.append("-flto"); + } // According to Rich Felker libc headers are supposed to go before C language headers. // However as noted by @dimenus, appending libc headers before c_headers breaks intrinsics @@ -2027,7 +2313,7 @@ pub fn addCCArgs( } const mcmodel = comp.bin_file.options.machine_code_model; if (mcmodel != .default) { - try argv.append(try std.fmt.allocPrint(arena, "-mcmodel={}", .{@tagName(mcmodel)})); + try argv.append(try std.fmt.allocPrint(arena, "-mcmodel={s}", .{@tagName(mcmodel)})); } switch (target.os.tag) { @@ -2079,9 +2365,20 @@ pub fn addCCArgs( try argv.append("-fomit-frame-pointer"); } - if (comp.sanitize_c) { + if (comp.sanitize_c and !comp.bin_file.options.tsan) { try argv.append("-fsanitize=undefined"); try argv.append("-fsanitize-trap=undefined"); + } else if (comp.sanitize_c and comp.bin_file.options.tsan) { + try argv.append("-fsanitize=undefined,thread"); + try argv.append("-fsanitize-trap=undefined"); + } else if (!comp.sanitize_c and comp.bin_file.options.tsan) { + try argv.append("-fsanitize=thread"); + } + + if (comp.bin_file.options.red_zone) { + try argv.append("-mred-zone"); + } else if (target_util.hasRedZone(target)) { + try argv.append("-mno-red-zone"); } switch (comp.bin_file.options.optimize_mode) { @@ -2161,50 +2458,40 @@ pub fn addCCArgs( fn failCObj(comp: *Compilation, c_object: *CObject, comptime format: []const u8, args: anytype) InnerError { @setCold(true); - const err_msg = try ErrorMsg.create(comp.gpa, 0, "unable to build C object: " ++ format, args); + const err_msg = blk: { + const msg = try std.fmt.allocPrint(comp.gpa, format, args); + errdefer comp.gpa.free(msg); + const err_msg = try comp.gpa.create(CObject.ErrorMsg); + errdefer comp.gpa.destroy(err_msg); + err_msg.* = .{ + .msg = msg, + .line = 0, + .column = 0, + }; + break :blk err_msg; + }; return comp.failCObjWithOwnedErrorMsg(c_object, err_msg); } -fn failCObjWithOwnedErrorMsg(comp: *Compilation, c_object: *CObject, err_msg: *ErrorMsg) InnerError { +fn failCObjWithOwnedErrorMsg( + comp: *Compilation, + c_object: *CObject, + err_msg: *CObject.ErrorMsg, +) InnerError { + @setCold(true); { - errdefer err_msg.destroy(comp.gpa); - try comp.failed_c_objects.ensureCapacity(comp.gpa, comp.failed_c_objects.items().len + 1); + const lock = comp.mutex.acquire(); + defer lock.release(); + { + errdefer err_msg.destroy(comp.gpa); + try comp.failed_c_objects.ensureCapacity(comp.gpa, comp.failed_c_objects.items().len + 1); + } + comp.failed_c_objects.putAssumeCapacityNoClobber(c_object, err_msg); } - comp.failed_c_objects.putAssumeCapacityNoClobber(c_object, err_msg); c_object.status = .failure; return error.AnalysisFail; } -pub const ErrorMsg = struct { - byte_offset: usize, - msg: []const u8, - - pub fn create(gpa: *Allocator, byte_offset: usize, comptime format: []const u8, args: anytype) !*ErrorMsg { - const self = try gpa.create(ErrorMsg); - errdefer gpa.destroy(self); - self.* = try init(gpa, byte_offset, format, args); - return self; - } - - /// Assumes the ErrorMsg struct and msg were both allocated with allocator. - pub fn destroy(self: *ErrorMsg, gpa: *Allocator) void { - self.deinit(gpa); - gpa.destroy(self); - } - - pub fn init(gpa: *Allocator, byte_offset: usize, comptime format: []const u8, args: anytype) !ErrorMsg { - return ErrorMsg{ - .byte_offset = byte_offset, - .msg = try std.fmt.allocPrint(gpa, format, args), - }; - } - - pub fn deinit(self: *ErrorMsg, gpa: *Allocator) void { - gpa.free(self.msg); - self.* = undefined; - } -}; - pub const FileExt = enum { c, cpp, @@ -2327,7 +2614,7 @@ test "classifyFileExt" { std.testing.expectEqual(FileExt.zir, classifyFileExt("foo.zir")); } -fn haveFramePointer(comp: *Compilation) bool { +fn haveFramePointer(comp: *const Compilation) bool { // If you complicate this logic make sure you update the parent cache hash. // Right now it's not in the cache hash because the value depends on optimize_mode // and strip which are both already part of the hash. @@ -2371,22 +2658,22 @@ fn detectLibCIncludeDirs( const s = std.fs.path.sep_str; const arch_include_dir = try std.fmt.allocPrint( arena, - "{}" ++ s ++ "libc" ++ s ++ "include" ++ s ++ "{}-{}-{}", + "{s}" ++ s ++ "libc" ++ s ++ "include" ++ s ++ "{s}-{s}-{s}", .{ zig_lib_dir, arch_name, os_name, abi_name }, ); const generic_include_dir = try std.fmt.allocPrint( arena, - "{}" ++ s ++ "libc" ++ s ++ "include" ++ s ++ "generic-{}", + "{s}" ++ s ++ "libc" ++ s ++ "include" ++ s ++ "generic-{s}", .{ zig_lib_dir, generic_name }, ); const arch_os_include_dir = try std.fmt.allocPrint( arena, - "{}" ++ s ++ "libc" ++ s ++ "include" ++ s ++ "{}-{}-any", + "{s}" ++ s ++ "libc" ++ s ++ "include" ++ s ++ "{s}-{s}-any", .{ zig_lib_dir, @tagName(target.cpu.arch), os_name }, ); const generic_os_include_dir = try std.fmt.allocPrint( arena, - "{}" ++ s ++ "libc" ++ s ++ "include" ++ s ++ "any-{}-any", + "{s}" ++ s ++ "libc" ++ s ++ "include" ++ s ++ "any-{s}-any", .{ zig_lib_dir, os_name }, ); @@ -2505,9 +2792,9 @@ fn updateBuiltinZigFile(comp: *Compilation, mod: *Module) !void { pub fn dump_argv(argv: []const []const u8) void { for (argv[0 .. argv.len - 1]) |arg| { - std.debug.print("{} ", .{arg}); + std.debug.print("{s} ", .{arg}); } - std.debug.print("{}\n", .{argv[argv.len - 1]}); + std.debug.print("{s}\n", .{argv[argv.len - 1]}); } pub fn generateBuiltinZigSource(comp: *Compilation, allocator: *Allocator) ![]u8 { @@ -2527,6 +2814,11 @@ pub fn generateBuiltinZigSource(comp: *Compilation, allocator: *Allocator) ![]u8 \\pub const arch = Target.current.cpu.arch; \\/// Deprecated \\pub const endian = Target.current.cpu.arch.endian(); + \\ + \\/// Zig version. When writing code that supports multiple versions of Zig, prefer + \\/// feature detection (i.e. with `@hasDecl` or `@hasField`) over version checks. + \\pub const zig_version = try @import("std").SemanticVersion.parse("{s}"); + \\ \\pub const output_mode = OutputMode.{}; \\pub const link_mode = LinkMode.{}; \\pub const is_test = {}; @@ -2538,16 +2830,17 @@ pub fn generateBuiltinZigSource(comp: *Compilation, allocator: *Allocator) ![]u8 \\ .features = Target.{}.featureSet(&[_]Target.{}.Feature{{ \\ , .{ - @tagName(comp.bin_file.options.output_mode), - @tagName(comp.bin_file.options.link_mode), + build_options.version, + std.zig.fmtId(@tagName(comp.bin_file.options.output_mode)), + std.zig.fmtId(@tagName(comp.bin_file.options.link_mode)), comp.bin_file.options.is_test, comp.bin_file.options.single_threaded, - @tagName(target.abi), - @tagName(target.cpu.arch), - generic_arch_name, - target.cpu.model.name, - generic_arch_name, - generic_arch_name, + std.zig.fmtId(@tagName(target.abi)), + std.zig.fmtId(@tagName(target.cpu.arch)), + std.zig.fmtId(generic_arch_name), + std.zig.fmtId(target.cpu.model.name), + std.zig.fmtId(generic_arch_name), + std.zig.fmtId(generic_arch_name), }); for (target.cpu.arch.allFeaturesList()) |feature, index_usize| { @@ -2569,12 +2862,12 @@ pub fn generateBuiltinZigSource(comp: *Compilation, allocator: *Allocator) ![]u8 \\ .tag = .{}, \\ .version_range = .{{ , - .{@tagName(target.os.tag)}, + .{std.zig.fmtId(@tagName(target.os.tag))}, ); switch (target.os.getVersionRange()) { .none => try buffer.appendSlice(" .none = {} }\n"), - .semver => |semver| try buffer.outStream().print( + .semver => |semver| try buffer.writer().print( \\ .semver = .{{ \\ .min = .{{ \\ .major = {}, @@ -2597,7 +2890,7 @@ pub fn generateBuiltinZigSource(comp: *Compilation, allocator: *Allocator) ![]u8 semver.max.minor, semver.max.patch, }), - .linux => |linux| try buffer.outStream().print( + .linux => |linux| try buffer.writer().print( \\ .linux = .{{ \\ .range = .{{ \\ .min = .{{ @@ -2631,7 +2924,7 @@ pub fn generateBuiltinZigSource(comp: *Compilation, allocator: *Allocator) ![]u8 linux.glibc.minor, linux.glibc.patch, }), - .windows => |windows| try buffer.outStream().print( + .windows => |windows| try buffer.writer().print( \\ .windows = .{{ \\ .min = {s}, \\ .max = {s}, @@ -2649,7 +2942,7 @@ pub fn generateBuiltinZigSource(comp: *Compilation, allocator: *Allocator) ![]u8 // in. For example, compiler_rt will not export the __chkstk symbol if it // knows libc will provide it, and likewise c.zig will not export memcpy. const link_libc = comp.bin_file.options.link_libc or - (comp.bin_file.options.is_compiler_rt_or_libc and comp.bin_file.options.parent_compilation_link_libc); + (comp.bin_file.options.skip_linker_dependencies and comp.bin_file.options.parent_compilation_link_libc); try buffer.writer().print( \\pub const object_format = ObjectFormat.{}; @@ -2664,8 +2957,8 @@ pub fn generateBuiltinZigSource(comp: *Compilation, allocator: *Allocator) ![]u8 \\pub const code_model = CodeModel.{}; \\ , .{ - @tagName(comp.bin_file.options.object_format), - @tagName(comp.bin_file.options.optimize_mode), + std.zig.fmtId(@tagName(comp.bin_file.options.object_format)), + std.zig.fmtId(@tagName(comp.bin_file.options.optimize_mode)), link_libc, comp.bin_file.options.link_libcpp, comp.bin_file.options.error_return_tracing, @@ -2673,7 +2966,7 @@ pub fn generateBuiltinZigSource(comp: *Compilation, allocator: *Allocator) ![]u8 comp.bin_file.options.pic, comp.bin_file.options.pie, comp.bin_file.options.strip, - @tagName(comp.bin_file.options.machine_code_model), + std.zig.fmtId(@tagName(comp.bin_file.options.machine_code_model)), }); if (comp.bin_file.options.is_test) { @@ -2708,7 +3001,7 @@ pub fn updateSubCompilation(sub_compilation: *Compilation) !void { for (errors.list) |full_err_msg| { switch (full_err_msg) { .src => |src| { - log.err("{s}:{d}:{d}: {s}\n", .{ + log.err("{s}:{d}:{d}: {s}", .{ src.src_path, src.line + 1, src.column + 1, @@ -2762,14 +3055,6 @@ fn buildOutputFromZig( .directory = null, // Put it in the cache directory. .basename = bin_basename, }; - const optimize_mode: std.builtin.Mode = blk: { - if (comp.bin_file.options.is_test) - break :blk comp.bin_file.options.optimize_mode; - switch (comp.bin_file.options.optimize_mode) { - .Debug, .ReleaseFast, .ReleaseSafe => break :blk .ReleaseFast, - .ReleaseSmall => break :blk .ReleaseSmall, - } - }; const sub_compilation = try Compilation.create(comp.gpa, .{ .global_cache_directory = comp.global_cache_directory, .local_cache_directory = comp.global_cache_directory, @@ -2778,19 +3063,21 @@ fn buildOutputFromZig( .root_name = root_name, .root_pkg = &root_pkg, .output_mode = fixed_output_mode, - .rand = comp.rand, + .thread_pool = comp.thread_pool, .libc_installation = comp.bin_file.options.libc_installation, .emit_bin = emit_bin, - .optimize_mode = optimize_mode, + .optimize_mode = comp.compilerRtOptMode(), .link_mode = .Static, .function_sections = true, .want_sanitize_c = false, .want_stack_check = false, + .want_red_zone = comp.bin_file.options.red_zone, .want_valgrind = false, + .want_tsan = false, .want_pic = comp.bin_file.options.pic, .want_pie = comp.bin_file.options.pie, .emit_h = null, - .strip = comp.bin_file.options.strip, + .strip = comp.compilerRtStrip(), .is_native_os = comp.bin_file.options.is_native_os, .is_native_abi = comp.bin_file.options.is_native_abi, .self_exe_path = comp.self_exe_path, @@ -2803,7 +3090,7 @@ fn buildOutputFromZig( .verbose_cimport = comp.verbose_cimport, .verbose_llvm_cpu_features = comp.verbose_llvm_cpu_features, .clang_passthrough_mode = comp.clang_passthrough_mode, - .is_compiler_rt_or_libc = true, + .skip_linker_dependencies = true, .parent_compilation_link_libc = comp.bin_file.options.link_libc, }); defer sub_compilation.destroy(); @@ -2863,11 +3150,15 @@ fn updateStage1Module(comp: *Compilation, main_progress_node: *std.Progress.Node man.hash.add(comp.bin_file.options.function_sections); man.hash.add(comp.bin_file.options.is_test); man.hash.add(comp.bin_file.options.emit != null); - man.hash.addOptionalEmitLoc(comp.emit_h); + man.hash.add(mod.emit_h != null); + if (mod.emit_h) |emit_h| { + man.hash.addEmitLoc(emit_h); + } man.hash.addOptionalEmitLoc(comp.emit_asm); man.hash.addOptionalEmitLoc(comp.emit_llvm_ir); man.hash.addOptionalEmitLoc(comp.emit_analysis); man.hash.addOptionalEmitLoc(comp.emit_docs); + man.hash.add(comp.test_evented_io); man.hash.addOptionalBytes(comp.test_filter); man.hash.addOptionalBytes(comp.test_name_prefix); @@ -2875,7 +3166,12 @@ fn updateStage1Module(comp: *Compilation, main_progress_node: *std.Progress.Node const prev_hash_state = man.hash.peekBin(); const input_file_count = man.files.items.len; - if (try man.hit()) { + const hit = man.hit() catch |err| { + const i = man.failed_file_index orelse return err; + const file_path = man.files.items[i].path orelse return err; + fatal("unable to build stage1 zig object: {s}: {s}", .{ @errorName(err), file_path }); + }; + if (hit) { const digest = man.final(); // We use an extra hex-encoded byte here to store some flags. @@ -2885,7 +3181,7 @@ fn updateStage1Module(comp: *Compilation, main_progress_node: *std.Progress.Node id_symlink_basename, &prev_digest_buf, ) catch |err| blk: { - log.debug("stage1 {} new_digest={} error: {}", .{ mod.root_pkg.root_src_path, digest, @errorName(err) }); + log.debug("stage1 {s} new_digest={} error: {s}", .{ mod.root_pkg.root_src_path, digest, @errorName(err) }); // Handle this as a cache miss. break :blk prev_digest_buf[0..0]; }; @@ -2893,7 +3189,7 @@ fn updateStage1Module(comp: *Compilation, main_progress_node: *std.Progress.Node if (!mem.eql(u8, prev_digest[0..digest.len], &digest)) break :hit; - log.debug("stage1 {} digest={} match - skipping invocation", .{ mod.root_pkg.root_src_path, digest }); + log.debug("stage1 {s} digest={} match - skipping invocation", .{ mod.root_pkg.root_src_path, digest }); var flags_bytes: [1]u8 = undefined; _ = std.fmt.hexToBytes(&flags_bytes, prev_digest[digest.len..]) catch { log.warn("bad cache stage1 digest: '{s}'", .{prev_digest}); @@ -2916,7 +3212,7 @@ fn updateStage1Module(comp: *Compilation, main_progress_node: *std.Progress.Node mod.stage1_flags = @bitCast(@TypeOf(mod.stage1_flags), flags_bytes[0]); return; } - log.debug("stage1 {} prev_digest={} new_digest={}", .{ mod.root_pkg.root_src_path, prev_digest, digest }); + log.debug("stage1 {s} prev_digest={} new_digest={}", .{ mod.root_pkg.root_src_path, prev_digest, digest }); man.unhit(prev_hash_state, input_file_count); } @@ -2961,10 +3257,10 @@ fn updateStage1Module(comp: *Compilation, main_progress_node: *std.Progress.Node }); break :blk try directory.join(arena, &[_][]const u8{bin_basename}); } else ""; - if (comp.emit_h != null) { + if (mod.emit_h != null) { log.warn("-femit-h is not available in the stage1 backend; no .h file will be produced", .{}); } - const emit_h_path = try stage1LocPath(arena, comp.emit_h, directory); + const emit_h_path = try stage1LocPath(arena, mod.emit_h, directory); const emit_asm_path = try stage1LocPath(arena, comp.emit_asm, directory); const emit_llvm_ir_path = try stage1LocPath(arena, comp.emit_llvm_ir, directory); const emit_analysis_path = try stage1LocPath(arena, comp.emit_analysis, directory); @@ -3004,6 +3300,7 @@ fn updateStage1Module(comp: *Compilation, main_progress_node: *std.Progress.Node .err_color = @enumToInt(comp.color), .pic = comp.bin_file.options.pic, .pie = comp.bin_file.options.pie, + .lto = comp.bin_file.options.lto, .link_libc = comp.bin_file.options.link_libc, .link_libcpp = comp.bin_file.options.link_libcpp, .strip = comp.bin_file.options.strip, @@ -3011,8 +3308,10 @@ fn updateStage1Module(comp: *Compilation, main_progress_node: *std.Progress.Node .dll_export_fns = comp.bin_file.options.dll_export_fns, .link_mode_dynamic = comp.bin_file.options.link_mode == .Dynamic, .valgrind_enabled = comp.bin_file.options.valgrind, + .tsan_enabled = comp.bin_file.options.tsan, .function_sections = comp.bin_file.options.function_sections, .enable_stack_probing = comp.bin_file.options.stack_check, + .red_zone = comp.bin_file.options.red_zone, .enable_time_report = comp.time_report, .enable_stack_report = comp.stack_report, .test_is_evented = comp.test_evented_io, @@ -3060,7 +3359,7 @@ fn updateStage1Module(comp: *Compilation, main_progress_node: *std.Progress.Node // Update the small file with the digest. If it fails we can continue; it only // means that the next invocation will have an unnecessary cache miss. const stage1_flags_byte = @bitCast(u8, mod.stage1_flags); - log.debug("stage1 {} final digest={} flags={x}", .{ + log.debug("stage1 {s} final digest={} flags={x}", .{ mod.root_pkg.root_src_path, digest, stage1_flags_byte, }); var digest_plus_flags: [digest.len + 2]u8 = undefined; @@ -3073,11 +3372,11 @@ fn updateStage1Module(comp: *Compilation, main_progress_node: *std.Progress.Node digest_plus_flags, stage1_flags_byte, mod.stage1_flags.have_winmain_crt_startup, }); Cache.writeSmallFile(directory.handle, id_symlink_basename, &digest_plus_flags) catch |err| { - log.warn("failed to save stage1 hash digest file: {}", .{@errorName(err)}); + log.warn("failed to save stage1 hash digest file: {s}", .{@errorName(err)}); }; // Failure here only means an unnecessary cache miss. man.writeManifest() catch |err| { - log.warn("failed to write cache manifest when linking: {}", .{@errorName(err)}); + log.warn("failed to write cache manifest when linking: {s}", .{@errorName(err)}); }; // We hang on to this lock so that the output file path can be used without // other processes clobbering it. @@ -3151,17 +3450,23 @@ pub fn build_crt_file( .root_name = root_name, .root_pkg = null, .output_mode = output_mode, - .rand = comp.rand, + .thread_pool = comp.thread_pool, .libc_installation = comp.bin_file.options.libc_installation, .emit_bin = emit_bin, - .optimize_mode = comp.bin_file.options.optimize_mode, + .optimize_mode = comp.compilerRtOptMode(), .want_sanitize_c = false, .want_stack_check = false, + .want_red_zone = comp.bin_file.options.red_zone, .want_valgrind = false, + .want_tsan = false, .want_pic = comp.bin_file.options.pic, .want_pie = comp.bin_file.options.pie, + .want_lto = switch (output_mode) { + .Lib => comp.bin_file.options.lto, + .Obj, .Exe => false, + }, .emit_h = null, - .strip = comp.bin_file.options.strip, + .strip = comp.compilerRtStrip(), .is_native_os = comp.bin_file.options.is_native_os, .is_native_abi = comp.bin_file.options.is_native_abi, .self_exe_path = comp.self_exe_path, @@ -3175,7 +3480,7 @@ pub fn build_crt_file( .verbose_cimport = comp.verbose_cimport, .verbose_llvm_cpu_features = comp.verbose_llvm_cpu_features, .clang_passthrough_mode = comp.clang_passthrough_mode, - .is_compiler_rt_or_libc = true, + .skip_linker_dependencies = true, .parent_compilation_link_libc = comp.bin_file.options.link_libc, }); defer sub_compilation.destroy(); @@ -3196,7 +3501,7 @@ pub fn stage1AddLinkLib(comp: *Compilation, lib_name: []const u8) !void { // Avoid deadlocking on building import libs such as kernel32.lib // This can happen when the user uses `build-exe foo.obj -lkernel32` and then // when we create a sub-Compilation for zig libc, it also tries to build kernel32.lib. - if (comp.bin_file.options.is_compiler_rt_or_libc) return; + if (comp.bin_file.options.skip_linker_dependencies) return; // This happens when an `extern "foo"` function is referenced by the stage1 backend. // If we haven't seen this library yet and we're targeting Windows, we need to queue up @@ -3208,3 +3513,26 @@ pub fn stage1AddLinkLib(comp: *Compilation, lib_name: []const u8) !void { }); } } + +/// This decides the optimization mode for all zig-provided libraries, including +/// compiler-rt, libcxx, libc, libunwind, etc. +pub fn compilerRtOptMode(comp: Compilation) std.builtin.Mode { + if (comp.debug_compiler_runtime_libs) { + return comp.bin_file.options.optimize_mode; + } + switch (comp.bin_file.options.optimize_mode) { + .Debug, .ReleaseSafe => return target_util.defaultCompilerRtOptimizeMode(comp.getTarget()), + .ReleaseFast => return .ReleaseFast, + .ReleaseSmall => return .ReleaseSmall, + } +} + +/// This decides whether to strip debug info for all zig-provided libraries, including +/// compiler-rt, libcxx, libc, libunwind, etc. +pub fn compilerRtStrip(comp: Compilation) bool { + if (comp.debug_compiler_runtime_libs) { + return comp.bin_file.options.strip; + } else { + return true; + } +} |
