authorgravatar for mlugg@mlugg.co.ukMatthew Lugg <mlugg@mlugg.co.uk> 2025-08-15 20:49:34+01:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-08-18 13:07:40-07:00
logc1483eb05c221b4f0c0c357bf75b828f722fa44d
tree81956e1fdf1b4effd44386f02759a042e274e0ab
parent2f422372b588789a5ce208bb85c9bfeee84dd980

Compilation: fix compiler_rt and ubsan_rt strategy logic

It doesn't really make sense for `target_util.canBuildLibCompilerRt` (and its ubsan-rt friend) to take in `use_llvm`, because the caller doesn't control that: they're just going to queue a sub-compilation for the runtime. The only exception to that is the ZCU strategy, where we effectively embed `_ = @import("compiler_rt")` into the Zig compilation: there, the question does matter. Rather than trying to do multiple weird calls to model this, just have `canBuildLibCompilerRt` return not just a boolean, but also differentiate the self-hosted backend being capable of building the library vs only LLVM being capable. Logic in `Compilation` uses that difference to decide whether to use the ZCU strategy, and also to disable the library if the compiler does not support LLVM and it is required. Also, remove a redundant check later on, when actually queuing jobs. We've already checked that we can build `compiler_rt`, and `compiler_rt_strat` is set accordingly. I'm guessing this was there to work around a bug I saw in the old strategy assignment, where support was ignored in some cases. Resolves: #24623

2 files changed, 67 insertions(+), 49 deletions(-)

src/Compilation.zig+50-30
......@@ -1983,13 +1983,21 @@ pub fn create(gpa: Allocator, arena: Allocator, diag: *CreateDiagnostic, options
19831983 if (options.skip_linker_dependencies) break :s .none;
19841984 const want = options.want_compiler_rt orelse is_exe_or_dyn_lib;
19851985 if (!want) break :s .none;
1986 if (have_zcu and target_util.canBuildLibCompilerRt(target, use_llvm, build_options.have_llvm and use_llvm)) {
1986 const need_llvm = switch (target_util.canBuildLibCompilerRt(target)) {
1987 .no => break :s .none, // impossible to build
1988 .yes => false,
1989 .llvm_only => true,
1990 };
1991 if (have_zcu and (!need_llvm or use_llvm)) {
19871992 if (output_mode == .Obj) break :s .zcu;
1988 if (switch (target_util.zigBackend(target, use_llvm)) {
1989 else => false,
1990 .stage2_aarch64, .stage2_x86_64 => target.ofmt == .coff,
1991 }) break :s if (is_exe_or_dyn_lib) .dyn_lib else .zcu;
1993 switch (target_util.zigBackend(target, use_llvm)) {
1994 else => {},
1995 .stage2_aarch64, .stage2_x86_64 => if (target.ofmt == .coff) {
1996 break :s if (is_exe_or_dyn_lib) .dyn_lib else .zcu;
1997 },
1998 }
19921999 }
2000 if (need_llvm and !build_options.have_llvm) break :s .none; // impossible to build without llvm
19932001 if (is_exe_or_dyn_lib) break :s .lib;
19942002 break :s .obj;
19952003 };
......@@ -2031,14 +2039,22 @@ pub fn create(gpa: Allocator, arena: Allocator, diag: *CreateDiagnostic, options
20312039 }
20322040
20332041 // unlike compiler_rt, we always want to go through the `_ = @import("ubsan-rt")`
2034 // approach, since the ubsan runtime uses quite a lot of the standard library
2035 // and this reduces unnecessary bloat.
2042 // approach if possible, since the ubsan runtime uses quite a lot of the standard
2043 // library and this reduces unnecessary bloat.
20362044 const ubsan_rt_strat: RtStrat = s: {
2037 const can_build_ubsan_rt = target_util.canBuildLibUbsanRt(target, use_llvm, build_options.have_llvm);
2038 const want_ubsan_rt = options.want_ubsan_rt orelse (can_build_ubsan_rt and any_sanitize_c == .full and is_exe_or_dyn_lib);
2039 if (!want_ubsan_rt) break :s .none;
20402045 if (options.skip_linker_dependencies) break :s .none;
2041 if (have_zcu and target_util.canBuildLibUbsanRt(target, use_llvm, build_options.have_llvm and use_llvm)) break :s .zcu;
2046 const want = options.want_ubsan_rt orelse (any_sanitize_c == .full and is_exe_or_dyn_lib);
2047 if (!want) break :s .none;
2048 const need_llvm = switch (target_util.canBuildLibUbsanRt(target)) {
2049 .no => break :s .none, // impossible to build
2050 .yes => false,
2051 .llvm_only => true,
2052 .llvm_lld_only => if (!options.config.use_lld) {
2053 break :s .none; // only LLD can handle ubsan-rt for this target
2054 } else true,
2055 };
2056 if (have_zcu and (!need_llvm or use_llvm)) break :s .zcu;
2057 if (need_llvm and !build_options.have_llvm) break :s .none; // impossible to build without llvm
20422058 if (is_exe_or_dyn_lib) break :s .lib;
20432059 break :s .obj;
20442060 };
......@@ -2476,8 +2492,6 @@ pub fn create(gpa: Allocator, arena: Allocator, diag: *CreateDiagnostic, options
24762492 };
24772493 errdefer comp.destroy();
24782494
2479 const can_build_compiler_rt = target_util.canBuildLibCompilerRt(target, use_llvm, build_options.have_llvm);
2480
24812495 // Add a `CObject` for each `c_source_files`.
24822496 try comp.c_object_table.ensureTotalCapacity(gpa, options.c_source_files.len);
24832497 for (options.c_source_files) |c_source_file| {
......@@ -2637,33 +2651,39 @@ pub fn create(gpa: Allocator, arena: Allocator, diag: *CreateDiagnostic, options
26372651 comp.queued_jobs.libtsan = true;
26382652 }
26392653
2640 if (can_build_compiler_rt) {
2641 if (comp.compiler_rt_strat == .lib) {
2654 switch (comp.compiler_rt_strat) {
2655 .none, .zcu => {},
2656 .lib => {
26422657 log.debug("queuing a job to build compiler_rt_lib", .{});
26432658 comp.queued_jobs.compiler_rt_lib = true;
2644 } else if (comp.compiler_rt_strat == .obj) {
2659 },
2660 .obj => {
26452661 log.debug("queuing a job to build compiler_rt_obj", .{});
2646 // In this case we are making a static library, so we ask
2647 // for a compiler-rt object to put in it.
26482662 comp.queued_jobs.compiler_rt_obj = true;
2649 } else if (comp.compiler_rt_strat == .dyn_lib) {
2663 },
2664 .dyn_lib => {
26502665 // hack for stage2_x86_64 + coff
26512666 log.debug("queuing a job to build compiler_rt_dyn_lib", .{});
26522667 comp.queued_jobs.compiler_rt_dyn_lib = true;
2653 }
2668 },
2669 }
26542670
2655 if (comp.ubsan_rt_strat == .lib) {
2671 switch (comp.ubsan_rt_strat) {
2672 .none, .zcu => {},
2673 .lib => {
26562674 log.debug("queuing a job to build ubsan_rt_lib", .{});
26572675 comp.queued_jobs.ubsan_rt_lib = true;
2658 } else if (comp.ubsan_rt_strat == .obj) {
2676 },
2677 .obj => {
26592678 log.debug("queuing a job to build ubsan_rt_obj", .{});
26602679 comp.queued_jobs.ubsan_rt_obj = true;
2661 }
2680 },
2681 .dyn_lib => unreachable, // hack for compiler_rt only
2682 }
26622683
2663 if (is_exe_or_dyn_lib and comp.config.any_fuzz) {
2664 log.debug("queuing a job to build libfuzzer", .{});
2665 comp.queued_jobs.fuzzer_lib = true;
2666 }
2684 if (is_exe_or_dyn_lib and comp.config.any_fuzz) {
2685 log.debug("queuing a job to build libfuzzer", .{});
2686 comp.queued_jobs.fuzzer_lib = true;
26672687 }
26682688 }
26692689
......@@ -7638,11 +7658,11 @@ pub fn dump_argv(argv: []const []const u8) void {
76387658 const stderr = std.debug.lockStderrWriter(&buffer);
76397659 defer std.debug.unlockStderrWriter();
76407660 nosuspend {
7641 for (argv) |arg| {
7661 for (argv, 0..) |arg, i| {
7662 if (i != 0) stderr.writeByte(' ') catch return;
76427663 stderr.writeAll(arg) catch return;
7643 (stderr.writableArray(1) catch return)[0] = ' ';
76447664 }
7645 stderr.buffer[stderr.end - 1] = '\n';
7665 stderr.writeByte('\n') catch return;
76467666 }
76477667}
76487668
src/target.zig+17-19
......@@ -351,43 +351,41 @@ pub fn defaultCompilerRtOptimizeMode(target: *const std.Target) std.builtin.Opti
351351 }
352352}
353353
354pub fn canBuildLibCompilerRt(target: *const std.Target, use_llvm: bool, have_llvm: bool) bool {
354pub fn canBuildLibCompilerRt(target: *const std.Target) enum { no, yes, llvm_only } {
355355 switch (target.os.tag) {
356 .plan9 => return false,
356 .plan9 => return .no,
357357 else => {},
358358 }
359359 switch (target.cpu.arch) {
360 .spirv32, .spirv64 => return false,
360 .spirv32, .spirv64 => return .no,
361361 // Remove this once https://github.com/ziglang/zig/issues/23714 is fixed
362 .amdgcn => return false,
362 .amdgcn => return .no,
363363 else => {},
364364 }
365 return switch (zigBackend(target, use_llvm)) {
366 .stage2_aarch64 => true,
367 .stage2_llvm => true,
365 return switch (zigBackend(target, false)) {
366 .stage2_aarch64 => .yes,
368367 .stage2_x86_64 => switch (target.ofmt) {
369 .elf, .macho => true,
370 else => have_llvm,
368 .elf, .macho => .yes,
369 else => .llvm_only,
371370 },
372 else => have_llvm,
371 else => .llvm_only,
373372 };
374373}
375374
376pub fn canBuildLibUbsanRt(target: *const std.Target, use_llvm: bool, have_llvm: bool) bool {
375pub fn canBuildLibUbsanRt(target: *const std.Target) enum { no, yes, llvm_only, llvm_lld_only } {
377376 switch (target.cpu.arch) {
378 .spirv32, .spirv64 => return false,
377 .spirv32, .spirv64 => return .no,
379378 // Remove this once https://github.com/ziglang/zig/issues/23715 is fixed
380 .nvptx, .nvptx64 => return false,
379 .nvptx, .nvptx64 => return .no,
381380 else => {},
382381 }
383 return switch (zigBackend(target, use_llvm)) {
384 .stage2_llvm => true,
385 .stage2_wasm => false,
382 return switch (zigBackend(target, false)) {
383 .stage2_wasm => .llvm_lld_only,
386384 .stage2_x86_64 => switch (target.ofmt) {
387 .elf, .macho => true,
388 else => have_llvm,
385 .elf, .macho => .yes,
386 else => .llvm_only,
389387 },
390 else => have_llvm,
388 else => .llvm_only,
391389 };
392390}
393391