From e7baa09ce46181a5ffd55879a24a8cb72cea3093 Mon Sep 17 00:00:00 2001 From: GasInfinity Date: Wed, 28 Jan 2026 20:04:27 +0100 Subject: [PATCH 1/2] feat(Compilation): make libzigc share zcu if possible --- src/Compilation.zig | 65 ++++++++++++++++++++++++++++++++++++++++----- src/Zcu.zig | 2 +- src/target.zig | 6 +++++ 3 files changed, 65 insertions(+), 8 deletions(-) diff --git a/src/Compilation.zig b/src/Compilation.zig index 15c1837ec2..8047c164cb 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -80,6 +80,7 @@ sysroot: ?[]const u8, root_name: [:0]const u8, compiler_rt_strat: RtStrat, ubsan_rt_strat: RtStrat, +zigc_strat: RtStrat, /// Resolved into known paths, any GNU ld scripts already resolved. link_inputs: []const link.Input, /// Needed only for passing -F args to clang. @@ -2101,6 +2102,47 @@ pub fn create(gpa: Allocator, arena: Allocator, io: Io, diag: *CreateDiagnostic, try options.root_mod.deps.putNoClobber(arena, "ubsan_rt", ubsan_rt_mod); } + // Like with ubsan_rt we want to go through the `_ = @import("zigc")` + // approach if possible since it uses even more of the standard library + // and can thus reduce further unnecesary bloat. + const zigc_strat: RtStrat = s: { + if (options.skip_linker_dependencies) break :s .none; + if (target.ofmt == .c) break :s .none; + if (!link_libc or !is_exe_or_dyn_lib) break :s .none; + if (!target_util.wantsZigC(target, options.config.link_mode)) break :s .none; + if (have_zcu) break :s .zcu; + break :s .lib; + }; + + if (zigc_strat == .zcu) { + const zigc_mod = Package.Module.create(arena, .{ + .paths = .{ + .root = .zig_lib_root, + .root_src_path = "c.zig", + }, + .fully_qualified_name = "zigc", + .cc_argv = &.{}, + .inherited = .{}, + .global = options.config, + .parent = options.root_mod, + }) catch |err| switch (err) { + error.OutOfMemory => |e| return e, + // None of these are possible because the configuration matches the root module + // which already passed these checks. + error.ValgrindUnsupportedOnTarget => unreachable, + error.TargetRequiresSingleThreaded => unreachable, + error.BackendRequiresSingleThreaded => unreachable, + error.TargetRequiresPic => unreachable, + error.PieRequiresPic => unreachable, + error.DynamicLinkingRequiresPic => unreachable, + error.TargetHasNoRedZone => unreachable, + error.StackCheckUnsupportedByTarget => unreachable, + error.StackProtectorUnsupportedByTarget => unreachable, + error.StackProtectorUnavailableWithoutLibC => unreachable, + }; + try options.root_mod.deps.putNoClobber(arena, "zigc", zigc_mod); + } + if (options.verbose_llvm_cpu_features) { if (options.root_mod.resolved_target.llvm_cpu_features) |cf| { const stderr = try io.lockStderr(&.{}, null); @@ -2296,6 +2338,7 @@ pub fn create(gpa: Allocator, arena: Allocator, io: Io, diag: *CreateDiagnostic, .libc_installation = libc_dirs.libc_installation, .compiler_rt_strat = compiler_rt_strat, .ubsan_rt_strat = ubsan_rt_strat, + .zigc_strat = zigc_strat, .link_inputs = options.link_inputs, .framework_dirs = options.framework_dirs, .llvm_opt_bisect_limit = options.llvm_opt_bisect_limit, @@ -2651,13 +2694,6 @@ pub fn create(gpa: Allocator, arena: Allocator, io: Io, diag: *CreateDiagnostic, } else { return diag.fail(.cross_libc_unavailable); } - - if ((target.isMuslLibC() and comp.config.link_mode == .static) or - target.isWasiLibC() or - target.isMinGW()) - { - comp.queued_jobs.zigc_lib = true; - } } // Generate Windows import libs. @@ -2711,6 +2747,15 @@ pub fn create(gpa: Allocator, arena: Allocator, io: Io, diag: *CreateDiagnostic, .dyn_lib => unreachable, // hack for compiler_rt only } + switch (comp.zigc_strat) { + .none, .zcu => {}, + .lib => { + log.debug("queuing a job to build libzigc", .{}); + comp.queued_jobs.zigc_lib = true; + }, + .obj, .dyn_lib => unreachable, // only available as a static library or inside an existing ZCU + } + if (is_exe_or_dyn_lib and comp.config.any_fuzz) { log.debug("queuing a job to build libfuzzer", .{}); comp.queued_jobs.fuzzer_lib = true; @@ -3100,6 +3145,11 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) UpdateE zcu.analysis_roots_buffer[zcu.analysis_roots_len] = ubsan_rt_mod; zcu.analysis_roots_len += 1; } + + if (zcu.root_mod.deps.get("zigc")) |zigc_mod| { + zcu.analysis_roots_buffer[zcu.analysis_roots_len] = zigc_mod; + zcu.analysis_roots_len += 1; + } } // The linker progress node is set up here instead of in `performAllTheWork`, because @@ -3531,6 +3581,7 @@ fn addNonIncrementalStuffToCacheManifest( man.hash.add(comp.skip_linker_dependencies); man.hash.add(comp.compiler_rt_strat); man.hash.add(comp.ubsan_rt_strat); + man.hash.add(comp.zigc_strat); man.hash.add(comp.rc_includes); man.hash.addListOfBytes(comp.force_undefined_symbols.keys()); man.hash.addListOfBytes(comp.framework_dirs); diff --git a/src/Zcu.zig b/src/Zcu.zig index d1e20c8551..ceccc2c192 100644 --- a/src/Zcu.zig +++ b/src/Zcu.zig @@ -269,7 +269,7 @@ nav_val_analysis_queued: std.AutoArrayHashMapUnmanaged(InternPool.Nav.Index, voi /// These are the modules which we initially queue for analysis in `Compilation.update`. /// `resolveReferences` will use these as the root of its reachability traversal. -analysis_roots_buffer: [4]*Package.Module, +analysis_roots_buffer: [5]*Package.Module, analysis_roots_len: usize = 0, /// This is the cached result of `Zcu.resolveReferences`. It is computed on-demand, and /// reset to `null` when any semantic analysis occurs (since this invalidates the data). diff --git a/src/target.zig b/src/target.zig index f664a35459..ca65239a09 100644 --- a/src/target.zig +++ b/src/target.zig @@ -423,6 +423,12 @@ pub fn canBuildLibUbsanRt(target: *const std.Target) enum { no, yes, llvm_only, }; } +/// Whether libzigc can fill-in the gaps of an existing libc +/// or *is* the libc of the target. +pub fn wantsZigC(target: *const std.Target, link_mode: std.builtin.LinkMode) bool { + return (target.isMuslLibC() and link_mode == .static) or target.isWasiLibC() or target.isMinGW(); +} + pub fn hasRedZone(target: *const std.Target) bool { return switch (target.cpu.arch) { .aarch64, From ed93f0d70f5b3f954a4f52e37cab80383ca61413 Mon Sep 17 00:00:00 2001 From: GasInfinity Date: Thu, 29 Jan 2026 10:39:38 +0100 Subject: [PATCH 2/2] fix(libzigc): always apply strong linkage, even when testing * libzigc may be linked into a different test compilation Co-authored-by: Matthew Lugg --- lib/c/common.zig | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/lib/c/common.zig b/lib/c/common.zig index 8d2a79db54..5cd94cf0ce 100644 --- a/lib/c/common.zig +++ b/lib/c/common.zig @@ -1,18 +1,14 @@ const builtin = @import("builtin"); const std = @import("std"); -pub const linkage: std.builtin.GlobalLinkage = if (builtin.is_test) - .internal -else - .strong; +/// It is incorrect to make this conditional on `builtin.is_test`, because it is possible that +/// libzigc is being linked into a different test compilation, as opposed to being tested itself. +pub const linkage: std.builtin.GlobalLinkage = .strong; /// Determines the symbol's visibility to other objects. /// For WebAssembly this allows the symbol to be resolved to other modules, but will not /// export it to the host runtime. -pub const visibility: std.builtin.SymbolVisibility = if (linkage != .internal) - .hidden -else - .default; +pub const visibility: std.builtin.SymbolVisibility = .hidden; /// Given a low-level syscall return value, sets errno and returns `-1`, or on /// success returns the result.