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...@@ -1983,13 +1983,21 @@ pub fn create(gpa: Allocator, arena: Allocator, diag: *CreateDiagnostic, options
1983 if (options.skip_linker_dependencies) break :s .none;1983 if (options.skip_linker_dependencies) break :s .none;
1984 const want = options.want_compiler_rt orelse is_exe_or_dyn_lib;1984 const want = options.want_compiler_rt orelse is_exe_or_dyn_lib;
1985 if (!want) break :s .none;1985 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)) {
1987 if (output_mode == .Obj) break :s .zcu;1992 if (output_mode == .Obj) break :s .zcu;
1988 if (switch (target_util.zigBackend(target, use_llvm)) {1993 switch (target_util.zigBackend(target, use_llvm)) {
1989 else => false,1994 else => {},
1990 .stage2_aarch64, .stage2_x86_64 => target.ofmt == .coff,1995 .stage2_aarch64, .stage2_x86_64 => if (target.ofmt == .coff) {
1991 }) break :s if (is_exe_or_dyn_lib) .dyn_lib else .zcu;1996 break :s if (is_exe_or_dyn_lib) .dyn_lib else .zcu;
1997 },
1998 }
1992 }1999 }
2000 if (need_llvm and !build_options.have_llvm) break :s .none; // impossible to build without llvm
1993 if (is_exe_or_dyn_lib) break :s .lib;2001 if (is_exe_or_dyn_lib) break :s .lib;
1994 break :s .obj;2002 break :s .obj;
1995 };2003 };
...@@ -2031,14 +2039,22 @@ pub fn create(gpa: Allocator, arena: Allocator, diag: *CreateDiagnostic, options...@@ -2031,14 +2039,22 @@ pub fn create(gpa: Allocator, arena: Allocator, diag: *CreateDiagnostic, options
2031 }2039 }
20322040
2033 // unlike compiler_rt, we always want to go through the `_ = @import("ubsan-rt")`2041 // 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 library2042 // approach if possible, since the ubsan runtime uses quite a lot of the standard
2035 // and this reduces unnecessary bloat.2043 // library and this reduces unnecessary bloat.
2036 const ubsan_rt_strat: RtStrat = s: {2044 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;
2040 if (options.skip_linker_dependencies) break :s .none;2045 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
2042 if (is_exe_or_dyn_lib) break :s .lib;2058 if (is_exe_or_dyn_lib) break :s .lib;
2043 break :s .obj;2059 break :s .obj;
2044 };2060 };
...@@ -2476,8 +2492,6 @@ pub fn create(gpa: Allocator, arena: Allocator, diag: *CreateDiagnostic, options...@@ -2476,8 +2492,6 @@ pub fn create(gpa: Allocator, arena: Allocator, diag: *CreateDiagnostic, options
2476 };2492 };
2477 errdefer comp.destroy();2493 errdefer comp.destroy();
24782494
2479 const can_build_compiler_rt = target_util.canBuildLibCompilerRt(target, use_llvm, build_options.have_llvm);
2480
2481 // Add a `CObject` for each `c_source_files`.2495 // Add a `CObject` for each `c_source_files`.
2482 try comp.c_object_table.ensureTotalCapacity(gpa, options.c_source_files.len);2496 try comp.c_object_table.ensureTotalCapacity(gpa, options.c_source_files.len);
2483 for (options.c_source_files) |c_source_file| {2497 for (options.c_source_files) |c_source_file| {
...@@ -2637,33 +2651,39 @@ pub fn create(gpa: Allocator, arena: Allocator, diag: *CreateDiagnostic, options...@@ -2637,33 +2651,39 @@ pub fn create(gpa: Allocator, arena: Allocator, diag: *CreateDiagnostic, options
2637 comp.queued_jobs.libtsan = true;2651 comp.queued_jobs.libtsan = true;
2638 }2652 }
26392653
2640 if (can_build_compiler_rt) {2654 switch (comp.compiler_rt_strat) {
2641 if (comp.compiler_rt_strat == .lib) {2655 .none, .zcu => {},
2656 .lib => {
2642 log.debug("queuing a job to build compiler_rt_lib", .{});2657 log.debug("queuing a job to build compiler_rt_lib", .{});
2643 comp.queued_jobs.compiler_rt_lib = true;2658 comp.queued_jobs.compiler_rt_lib = true;
2644 } else if (comp.compiler_rt_strat == .obj) {2659 },
2660 .obj => {
2645 log.debug("queuing a job to build compiler_rt_obj", .{});2661 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.
2648 comp.queued_jobs.compiler_rt_obj = true;2662 comp.queued_jobs.compiler_rt_obj = true;
2649 } else if (comp.compiler_rt_strat == .dyn_lib) {2663 },
2664 .dyn_lib => {
2650 // hack for stage2_x86_64 + coff2665 // hack for stage2_x86_64 + coff
2651 log.debug("queuing a job to build compiler_rt_dyn_lib", .{});2666 log.debug("queuing a job to build compiler_rt_dyn_lib", .{});
2652 comp.queued_jobs.compiler_rt_dyn_lib = true;2667 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 => {
2656 log.debug("queuing a job to build ubsan_rt_lib", .{});2674 log.debug("queuing a job to build ubsan_rt_lib", .{});
2657 comp.queued_jobs.ubsan_rt_lib = true;2675 comp.queued_jobs.ubsan_rt_lib = true;
2658 } else if (comp.ubsan_rt_strat == .obj) {2676 },
2677 .obj => {
2659 log.debug("queuing a job to build ubsan_rt_obj", .{});2678 log.debug("queuing a job to build ubsan_rt_obj", .{});
2660 comp.queued_jobs.ubsan_rt_obj = true;2679 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) {2684 if (is_exe_or_dyn_lib and comp.config.any_fuzz) {
2664 log.debug("queuing a job to build libfuzzer", .{});2685 log.debug("queuing a job to build libfuzzer", .{});
2665 comp.queued_jobs.fuzzer_lib = true;2686 comp.queued_jobs.fuzzer_lib = true;
2666 }
2667 }2687 }
2668 }2688 }
26692689
...@@ -7638,11 +7658,11 @@ pub fn dump_argv(argv: []const []const u8) void {...@@ -7638,11 +7658,11 @@ pub fn dump_argv(argv: []const []const u8) void {
7638 const stderr = std.debug.lockStderrWriter(&buffer);7658 const stderr = std.debug.lockStderrWriter(&buffer);
7639 defer std.debug.unlockStderrWriter();7659 defer std.debug.unlockStderrWriter();
7640 nosuspend {7660 nosuspend {
7641 for (argv) |arg| {7661 for (argv, 0..) |arg, i| {
7662 if (i != 0) stderr.writeByte(' ') catch return;
7642 stderr.writeAll(arg) catch return;7663 stderr.writeAll(arg) catch return;
7643 (stderr.writableArray(1) catch return)[0] = ' ';
7644 }7664 }
7645 stderr.buffer[stderr.end - 1] = '\n';7665 stderr.writeByte('\n') catch return;
7646 }7666 }
7647}7667}
76487668
src/target.zig+17-19
...@@ -351,43 +351,41 @@ pub fn defaultCompilerRtOptimizeMode(target: *const std.Target) std.builtin.Opti...@@ -351,43 +351,41 @@ pub fn defaultCompilerRtOptimizeMode(target: *const std.Target) std.builtin.Opti
351 }351 }
352}352}
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 } {
355 switch (target.os.tag) {355 switch (target.os.tag) {
356 .plan9 => return false,356 .plan9 => return .no,
357 else => {},357 else => {},
358 }358 }
359 switch (target.cpu.arch) {359 switch (target.cpu.arch) {
360 .spirv32, .spirv64 => return false,360 .spirv32, .spirv64 => return .no,
361 // Remove this once https://github.com/ziglang/zig/issues/23714 is fixed361 // Remove this once https://github.com/ziglang/zig/issues/23714 is fixed
362 .amdgcn => return false,362 .amdgcn => return .no,
363 else => {},363 else => {},
364 }364 }
365 return switch (zigBackend(target, use_llvm)) {365 return switch (zigBackend(target, false)) {
366 .stage2_aarch64 => true,366 .stage2_aarch64 => .yes,
367 .stage2_llvm => true,
368 .stage2_x86_64 => switch (target.ofmt) {367 .stage2_x86_64 => switch (target.ofmt) {
369 .elf, .macho => true,368 .elf, .macho => .yes,
370 else => have_llvm,369 else => .llvm_only,
371 },370 },
372 else => have_llvm,371 else => .llvm_only,
373 };372 };
374}373}
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 } {
377 switch (target.cpu.arch) {376 switch (target.cpu.arch) {
378 .spirv32, .spirv64 => return false,377 .spirv32, .spirv64 => return .no,
379 // Remove this once https://github.com/ziglang/zig/issues/23715 is fixed378 // Remove this once https://github.com/ziglang/zig/issues/23715 is fixed
380 .nvptx, .nvptx64 => return false,379 .nvptx, .nvptx64 => return .no,
381 else => {},380 else => {},
382 }381 }
383 return switch (zigBackend(target, use_llvm)) {382 return switch (zigBackend(target, false)) {
384 .stage2_llvm => true,383 .stage2_wasm => .llvm_lld_only,
385 .stage2_wasm => false,
386 .stage2_x86_64 => switch (target.ofmt) {384 .stage2_x86_64 => switch (target.ofmt) {
387 .elf, .macho => true,385 .elf, .macho => .yes,
388 else => have_llvm,386 else => .llvm_only,
389 },387 },
390 else => have_llvm,388 else => .llvm_only,
391 };389 };
392}390}
393391