Commit 1f896c1bf8

Alex Rønne Petersen <alex@alexrp.com>
2025-04-10 19:17:29
Introduce libzigc for libc function implementations in Zig.
This lays the groundwork for #2879. This library will be built and linked when a static libc is going to be linked into the compilation. Currently, that means musl, wasi-libc, and MinGW-w64. As a demonstration, this commit removes the musl C code for a few string functions and implements them in libzigc. This means that those libzigc functions are now load-bearing for musl and wasi-libc. Note that if a function has an implementation in compiler-rt already, libzigc should not implement it. Instead, as we recently did for memcpy/memmove, we should delete the libc copy and rely on the compiler-rt implementation. I repurposed the existing "universal libc" code to do this. That code hadn't seen development beyond basic string functions in years, and was only usable-ish on freestanding. I think that if we want to seriously pursue the idea of Zig providing a freestanding libc, we should do so only after defining clear goals (and non-goals) for it. See also #22240 for a similar case.
1 parent ee0ff13
lib/c/common.zig
@@ -0,0 +1,15 @@
+const builtin = @import("builtin");
+const std = @import("std");
+
+pub const linkage: std.builtin.GlobalLinkage = if (builtin.is_test)
+    .internal
+else
+    .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 (builtin.cpu.arch.isWasm() and linkage != .internal)
+    .hidden
+else
+    .default;
lib/c/string.zig
@@ -0,0 +1,45 @@
+const builtin = @import("builtin");
+const std = @import("std");
+const common = @import("common.zig");
+
+comptime {
+    @export(&strcmp, .{ .name = "strcmp", .linkage = common.linkage, .visibility = common.visibility });
+    @export(&strlen, .{ .name = "strlen", .linkage = common.linkage, .visibility = common.visibility });
+    @export(&strncmp, .{ .name = "strncmp", .linkage = common.linkage, .visibility = common.visibility });
+}
+
+fn strcmp(s1: [*:0]const c_char, s2: [*:0]const c_char) callconv(.c) c_int {
+    // We need to perform unsigned comparisons.
+    return switch (std.mem.orderZ(u8, @ptrCast(s1), @ptrCast(s2))) {
+        .lt => -1,
+        .eq => 0,
+        .gt => 1,
+    };
+}
+
+fn strncmp(s1: [*:0]const c_char, s2: [*:0]const c_char, n: usize) callconv(.c) c_int {
+    if (n == 0) return 0;
+
+    var l: [*:0]const u8 = @ptrCast(s1);
+    var r: [*:0]const u8 = @ptrCast(s2);
+    var i = n - 1;
+
+    while (l[0] != 0 and r[0] != 0 and i != 0 and l[0] == r[0]) {
+        l += 1;
+        r += 1;
+        i -= 1;
+    }
+
+    return @as(c_int, l[0]) - @as(c_int, r[0]);
+}
+
+test strncmp {
+    try std.testing.expect(strncmp(@ptrCast("a"), @ptrCast("b"), 1) < 0);
+    try std.testing.expect(strncmp(@ptrCast("a"), @ptrCast("c"), 1) < 0);
+    try std.testing.expect(strncmp(@ptrCast("b"), @ptrCast("a"), 1) > 0);
+    try std.testing.expect(strncmp(@ptrCast("\xff"), @ptrCast("\x02"), 1) > 0);
+}
+
+fn strlen(s: [*:0]const c_char) callconv(.c) usize {
+    return std.mem.len(s);
+}
lib/libc/musl/src/string/strcmp.c
@@ -1,7 +0,0 @@
-#include <string.h>
-
-int strcmp(const char *l, const char *r)
-{
-	for (; *l==*r && *l; l++, r++);
-	return *(unsigned char *)l - *(unsigned char *)r;
-}
lib/libc/musl/src/string/strlen.c
@@ -1,22 +0,0 @@
-#include <string.h>
-#include <stdint.h>
-#include <limits.h>
-
-#define ALIGN (sizeof(size_t))
-#define ONES ((size_t)-1/UCHAR_MAX)
-#define HIGHS (ONES * (UCHAR_MAX/2+1))
-#define HASZERO(x) ((x)-ONES & ~(x) & HIGHS)
-
-size_t strlen(const char *s)
-{
-	const char *a = s;
-#ifdef __GNUC__
-	typedef size_t __attribute__((__may_alias__)) word;
-	const word *w;
-	for (; (uintptr_t)s % ALIGN; s++) if (!*s) return s-a;
-	for (w = (const void *)s; !HASZERO(*w); w++);
-	s = (const void *)w;
-#endif
-	for (; *s; s++);
-	return s-a;
-}
lib/libc/musl/src/string/strncmp.c
@@ -1,9 +0,0 @@
-#include <string.h>
-
-int strncmp(const char *_l, const char *_r, size_t n)
-{
-	const unsigned char *l=(void *)_l, *r=(void *)_r;
-	if (!n--) return 0;
-	for (; *l && *r && n && *l == *r ; l++, r++, n--);
-	return *l - *r;
-}
lib/c.zig
@@ -1,180 +1,33 @@
 //! This is Zig's multi-target implementation of libc.
-//! When builtin.link_libc is true, we need to export all the functions and
-//! provide an entire C API.
+//!
+//! When `builtin.link_libc` is true, we need to export all the functions and
+//! provide a libc API compatible with the target (e.g. musl, wasi-libc, ...).
 
-const std = @import("std");
 const builtin = @import("builtin");
-const math = std.math;
-const isNan = std.math.isNan;
-const maxInt = std.math.maxInt;
-const native_os = builtin.os.tag;
-const native_arch = builtin.cpu.arch;
-const native_abi = builtin.abi;
-
-const linkage: std.builtin.GlobalLinkage = if (builtin.is_test) .internal else .strong;
+const std = @import("std");
 
-const is_wasm = switch (native_arch) {
-    .wasm32, .wasm64 => true,
-    else => false,
-};
-const is_freestanding = switch (native_os) {
-    .freestanding, .other => true,
-    else => false,
-};
+// Avoid dragging in the runtime safety mechanisms into this .o file, unless
+// we're trying to test zigc.
+pub const panic = if (builtin.is_test)
+    std.debug.FullPanic(std.debug.defaultPanic)
+else
+    std.debug.no_panic;
 
 comptime {
-    if (is_freestanding and is_wasm and builtin.link_libc) {
-        @export(&wasm_start, .{ .name = "_start", .linkage = .strong });
-    }
-
-    if (builtin.link_libc) {
-        @export(&strcmp, .{ .name = "strcmp", .linkage = linkage });
-        @export(&strncmp, .{ .name = "strncmp", .linkage = linkage });
-        @export(&strerror, .{ .name = "strerror", .linkage = linkage });
-        @export(&strlen, .{ .name = "strlen", .linkage = linkage });
-        @export(&strcpy, .{ .name = "strcpy", .linkage = linkage });
-        @export(&strncpy, .{ .name = "strncpy", .linkage = linkage });
-        @export(&strcat, .{ .name = "strcat", .linkage = linkage });
-        @export(&strncat, .{ .name = "strncat", .linkage = linkage });
-    }
-}
-
-// Avoid dragging in the runtime safety mechanisms into this .o file,
-// unless we're trying to test this file.
-pub fn panic(msg: []const u8, error_return_trace: ?*std.builtin.StackTrace, _: ?usize) noreturn {
-    @branchHint(.cold);
-    _ = error_return_trace;
-    if (builtin.is_test) {
-        std.debug.panic("{s}", .{msg});
-    }
-    switch (native_os) {
-        .freestanding, .other, .amdhsa, .amdpal => while (true) {},
-        else => std.os.abort(),
+    if (builtin.target.isMuslLibC() or builtin.target.isWasiLibC()) {
+        // Files specific to musl and wasi-libc.
+        _ = @import("c/string.zig");
     }
-}
-
-extern fn main(argc: c_int, argv: [*:null]?[*:0]u8) c_int;
-fn wasm_start() callconv(.c) void {
-    _ = main(0, undefined);
-}
 
-fn strcpy(dest: [*:0]u8, src: [*:0]const u8) callconv(.c) [*:0]u8 {
-    var i: usize = 0;
-    while (src[i] != 0) : (i += 1) {
-        dest[i] = src[i];
+    if (builtin.target.isMuslLibC()) {
+        // Files specific to musl.
     }
-    dest[i] = 0;
 
-    return dest;
-}
-
-test "strcpy" {
-    var s1: [9:0]u8 = undefined;
-
-    s1[0] = 0;
-    _ = strcpy(&s1, "foobarbaz");
-    try std.testing.expectEqualSlices(u8, "foobarbaz", std.mem.sliceTo(&s1, 0));
-}
-
-fn strncpy(dest: [*:0]u8, src: [*:0]const u8, n: usize) callconv(.c) [*:0]u8 {
-    var i: usize = 0;
-    while (i < n and src[i] != 0) : (i += 1) {
-        dest[i] = src[i];
-    }
-    while (i < n) : (i += 1) {
-        dest[i] = 0;
+    if (builtin.target.isWasiLibC()) {
+        // Files specific to wasi-libc.
     }
 
-    return dest;
-}
-
-test "strncpy" {
-    var s1: [9:0]u8 = undefined;
-
-    s1[0] = 0;
-    _ = strncpy(&s1, "foobarbaz", @sizeOf(@TypeOf(s1)));
-    try std.testing.expectEqualSlices(u8, "foobarbaz", std.mem.sliceTo(&s1, 0));
-}
-
-fn strcat(dest: [*:0]u8, src: [*:0]const u8) callconv(.c) [*:0]u8 {
-    var dest_end: usize = 0;
-    while (dest[dest_end] != 0) : (dest_end += 1) {}
-
-    var i: usize = 0;
-    while (src[i] != 0) : (i += 1) {
-        dest[dest_end + i] = src[i];
+    if (builtin.target.isMinGW()) {
+        // Files specific to MinGW-w64.
     }
-    dest[dest_end + i] = 0;
-
-    return dest;
-}
-
-test "strcat" {
-    var s1: [9:0]u8 = undefined;
-
-    s1[0] = 0;
-    _ = strcat(&s1, "foo");
-    _ = strcat(&s1, "bar");
-    _ = strcat(&s1, "baz");
-    try std.testing.expectEqualSlices(u8, "foobarbaz", std.mem.sliceTo(&s1, 0));
-}
-
-fn strncat(dest: [*:0]u8, src: [*:0]const u8, avail: usize) callconv(.c) [*:0]u8 {
-    var dest_end: usize = 0;
-    while (dest[dest_end] != 0) : (dest_end += 1) {}
-
-    var i: usize = 0;
-    while (i < avail and src[i] != 0) : (i += 1) {
-        dest[dest_end + i] = src[i];
-    }
-    dest[dest_end + i] = 0;
-
-    return dest;
-}
-
-test "strncat" {
-    var s1: [9:0]u8 = undefined;
-
-    s1[0] = 0;
-    _ = strncat(&s1, "foo1111", 3);
-    _ = strncat(&s1, "bar1111", 3);
-    _ = strncat(&s1, "baz1111", 3);
-    try std.testing.expectEqualSlices(u8, "foobarbaz", std.mem.sliceTo(&s1, 0));
-}
-
-fn strcmp(s1: [*:0]const u8, s2: [*:0]const u8) callconv(.c) c_int {
-    return switch (std.mem.orderZ(u8, s1, s2)) {
-        .lt => -1,
-        .eq => 0,
-        .gt => 1,
-    };
-}
-
-fn strlen(s: [*:0]const u8) callconv(.c) usize {
-    return std.mem.len(s);
-}
-
-fn strncmp(_l: [*:0]const u8, _r: [*:0]const u8, _n: usize) callconv(.c) c_int {
-    if (_n == 0) return 0;
-    var l = _l;
-    var r = _r;
-    var n = _n - 1;
-    while (l[0] != 0 and r[0] != 0 and n != 0 and l[0] == r[0]) {
-        l += 1;
-        r += 1;
-        n -= 1;
-    }
-    return @as(c_int, l[0]) - @as(c_int, r[0]);
-}
-
-fn strerror(errnum: c_int) callconv(.c) [*:0]const u8 {
-    _ = errnum;
-    return "TODO strerror implementation";
-}
-
-test "strncmp" {
-    try std.testing.expect(strncmp("a", "b", 1) < 0);
-    try std.testing.expect(strncmp("a", "c", 1) < 0);
-    try std.testing.expect(strncmp("b", "a", 1) > 0);
-    try std.testing.expect(strncmp("\xff", "\x02", 1) > 0);
 }
src/link/Coff.zig
@@ -2147,6 +2147,12 @@ fn linkWithLLD(coff: *Coff, arena: Allocator, tid: Zcu.PerThread.Id, prog_node:
             },
         }
 
+        if (comp.config.link_libc and link_in_crt) {
+            if (comp.zigc_static_lib) |zigc| {
+                try argv.append(try zigc.full_object_path.toString(arena));
+            }
+        }
+
         // libc++ dep
         if (comp.config.link_libcpp) {
             try argv.append(try comp.libcxxabi_static_lib.?.full_object_path.toString(arena));
@@ -2172,11 +2178,6 @@ fn linkWithLLD(coff: *Coff, arena: Allocator, tid: Zcu.PerThread.Id, prog_node:
         }
 
         if (is_exe_or_dyn_lib and !comp.skip_linker_dependencies) {
-            if (!comp.config.link_libc) {
-                if (comp.libc_static_lib) |lib| {
-                    try argv.append(try lib.full_object_path.toString(arena));
-                }
-            }
             // MSVC compiler_rt is missing some stuff, so we build it unconditionally but
             // and rely on weak linkage to allow MSVC compiler_rt functions to override ours.
             if (comp.compiler_rt_obj) |obj| try argv.append(try obj.full_object_path.toString(arena));
src/link/Elf.zig
@@ -1984,16 +1984,6 @@ fn linkWithLLD(self: *Elf, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: s
             try argv.append(try p.toString(arena));
         }
 
-        // libc
-        if (is_exe_or_dyn_lib and
-            !comp.skip_linker_dependencies and
-            !comp.config.link_libc)
-        {
-            if (comp.libc_static_lib) |lib| {
-                try argv.append(try lib.full_object_path.toString(arena));
-            }
-        }
-
         // Shared libraries.
         if (is_exe_or_dyn_lib) {
             // Worst-case, we need an --as-needed argument for every lib, as well
@@ -2071,6 +2061,10 @@ fn linkWithLLD(self: *Elf, arena: Allocator, tid: Zcu.PerThread.Id, prog_node: s
                 } else {
                     diags.flags.missing_libc = true;
                 }
+
+                if (comp.zigc_static_lib) |zigc| {
+                    try argv.append(try zigc.full_object_path.toString(arena));
+                }
             }
         }
 
src/link/MachO.zig
@@ -454,6 +454,17 @@ pub fn flushModule(
         system_libs.appendAssumeCapacity(.{ .path = comp.libcxx_static_lib.?.full_object_path });
     }
 
+    const is_exe_or_dyn_lib = comp.config.output_mode == .Exe or
+        (comp.config.output_mode == .Lib and comp.config.link_mode == .dynamic);
+
+    if (comp.config.link_libc and is_exe_or_dyn_lib) {
+        if (comp.zigc_static_lib) |zigc| {
+            const path = zigc.full_object_path;
+            self.classifyInputFile(try link.openArchiveInput(diags, path, false, false)) catch |err|
+                diags.addParseError(path, "failed to parse archive: {s}", .{@errorName(err)});
+        }
+    }
+
     // libc/libSystem dep
     self.resolveLibSystem(arena, comp, &system_libs) catch |err| switch (err) {
         error.MissingLibSystem => {}, // already reported
@@ -831,6 +842,7 @@ fn dumpArgv(self: *MachO, comp: *Compilation) !void {
 
         try argv.append("-lSystem");
 
+        if (comp.zigc_static_lib) |lib| try argv.append(try lib.full_object_path.toString(arena));
         if (comp.compiler_rt_lib) |lib| try argv.append(try lib.full_object_path.toString(arena));
         if (comp.compiler_rt_obj) |obj| try argv.append(try obj.full_object_path.toString(arena));
         if (comp.ubsan_rt_lib) |lib| try argv.append(try lib.full_object_path.toString(arena));
src/link/Wasm.zig
@@ -4100,10 +4100,11 @@ fn linkWithLLD(wasm: *Wasm, arena: Allocator, tid: Zcu.PerThread.Id, prog_node:
             try argv.append("-mwasm64");
         }
 
-        if (target.os.tag == .wasi) {
-            const is_exe_or_dyn_lib = comp.config.output_mode == .Exe or
-                (comp.config.output_mode == .Lib and comp.config.link_mode == .dynamic);
-            if (is_exe_or_dyn_lib) {
+        const is_exe_or_dyn_lib = comp.config.output_mode == .Exe or
+            (comp.config.output_mode == .Lib and comp.config.link_mode == .dynamic);
+
+        if (comp.config.link_libc and is_exe_or_dyn_lib) {
+            if (target.os.tag == .wasi) {
                 for (comp.wasi_emulated_libs) |crt_file| {
                     try argv.append(try comp.crtFileAsString(
                         arena,
@@ -4111,18 +4112,20 @@ fn linkWithLLD(wasm: *Wasm, arena: Allocator, tid: Zcu.PerThread.Id, prog_node:
                     ));
                 }
 
-                if (comp.config.link_libc) {
-                    try argv.append(try comp.crtFileAsString(
-                        arena,
-                        wasi_libc.execModelCrtFileFullName(comp.config.wasi_exec_model),
-                    ));
-                    try argv.append(try comp.crtFileAsString(arena, "libc.a"));
-                }
+                try argv.append(try comp.crtFileAsString(
+                    arena,
+                    wasi_libc.execModelCrtFileFullName(comp.config.wasi_exec_model),
+                ));
+                try argv.append(try comp.crtFileAsString(arena, "libc.a"));
+            }
 
-                if (comp.config.link_libcpp) {
-                    try argv.append(try comp.libcxx_static_lib.?.full_object_path.toString(arena));
-                    try argv.append(try comp.libcxxabi_static_lib.?.full_object_path.toString(arena));
-                }
+            if (comp.zigc_static_lib) |zigc| {
+                try argv.append(try zigc.full_object_path.toString(arena));
+            }
+
+            if (comp.config.link_libcpp) {
+                try argv.append(try comp.libcxx_static_lib.?.full_object_path.toString(arena));
+                try argv.append(try comp.libcxxabi_static_lib.?.full_object_path.toString(arena));
             }
         }
 
@@ -4157,10 +4160,6 @@ fn linkWithLLD(wasm: *Wasm, arena: Allocator, tid: Zcu.PerThread.Id, prog_node:
             try argv.append(p);
         }
 
-        if (comp.libc_static_lib) |crt_file| {
-            try argv.append(try crt_file.full_object_path.toString(arena));
-        }
-
         if (compiler_rt_path) |p| {
             try argv.append(try p.toString(arena));
         }
src/Compilation.zig
@@ -235,7 +235,7 @@ ubsan_rt_lib: ?CrtFile = null,
 ubsan_rt_obj: ?CrtFile = null,
 /// Populated when we build the libc static library. A Job to build this is placed in the queue
 /// and resolved before calling linker.flush().
-libc_static_lib: ?CrtFile = null,
+zigc_static_lib: ?CrtFile = null,
 /// Populated when we build the libcompiler_rt static library. A Job to build this is indicated
 /// by setting `queued_jobs.compiler_rt_lib` and resolved before calling linker.flush().
 compiler_rt_lib: ?CrtFile = null,
@@ -307,7 +307,7 @@ const QueuedJobs = struct {
     libcxx: bool = false,
     libcxxabi: bool = false,
     libtsan: bool = false,
-    zig_libc: bool = false,
+    zigc_lib: bool = false,
 };
 
 pub const default_stack_protector_buffer_size = target_util.default_stack_protector_buffer_size;
@@ -801,7 +801,7 @@ pub const MiscTask = enum {
     libfuzzer,
     wasi_libc_crt_file,
     compiler_rt,
-    zig_libc,
+    libzigc,
     analyze_mod,
     docs_copy,
     docs_wasm,
@@ -1759,7 +1759,6 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil
     const target = comp.root_mod.resolved_target.result;
 
     const capable_of_building_compiler_rt = canBuildLibCompilerRt(target, comp.config.use_llvm);
-    const capable_of_building_zig_libc = canBuildZigLibC(target, comp.config.use_llvm);
 
     // Add a `CObject` for each `c_source_files`.
     try comp.c_object_table.ensureTotalCapacity(gpa, options.c_source_files.len);
@@ -1891,12 +1890,17 @@ pub fn create(gpa: Allocator, arena: Allocator, options: CreateOptions) !*Compil
                     // When linking mingw-w64 there are some import libs we always need.
                     try comp.windows_libs.ensureUnusedCapacity(gpa, mingw.always_link_libs.len);
                     for (mingw.always_link_libs) |name| comp.windows_libs.putAssumeCapacity(name, {});
-                } else if (target.os.tag == .freestanding and capable_of_building_zig_libc) {
-                    comp.queued_jobs.zig_libc = true;
-                    comp.remaining_prelink_tasks += 1;
                 } else {
                     return error.LibCUnavailable;
                 }
+
+                if ((target.isMuslLibC() and comp.config.link_mode == .static) or
+                    target.isWasiLibC() or
+                    target.isMinGW())
+                {
+                    comp.queued_jobs.zigc_lib = true;
+                    comp.remaining_prelink_tasks += 1;
+                }
             }
 
             // Generate Windows import libs.
@@ -2010,7 +2014,7 @@ pub fn destroy(comp: *Compilation) void {
         crt_file.deinit(gpa);
     }
 
-    if (comp.libc_static_lib) |*crt_file| {
+    if (comp.zigc_static_lib) |*crt_file| {
         crt_file.deinit(gpa);
     }
 
@@ -3761,23 +3765,23 @@ fn performAllTheWorkInner(
         // compiler-rt due to LLD bugs as well, e.g.:
         //
         // https://github.com/llvm/llvm-project/issues/43698#issuecomment-2542660611
-        comp.link_task_wait_group.spawnManager(buildRt, .{ comp, "compiler_rt.zig", .compiler_rt, .Lib, false, &comp.compiler_rt_lib, main_progress_node });
+        comp.link_task_wait_group.spawnManager(buildRt, .{ comp, "compiler_rt.zig", "compiler_rt", .compiler_rt, .Lib, false, &comp.compiler_rt_lib, main_progress_node });
     }
 
     if (comp.queued_jobs.compiler_rt_obj and comp.compiler_rt_obj == null) {
-        comp.link_task_wait_group.spawnManager(buildRt, .{ comp, "compiler_rt.zig", .compiler_rt, .Obj, false, &comp.compiler_rt_obj, main_progress_node });
+        comp.link_task_wait_group.spawnManager(buildRt, .{ comp, "compiler_rt.zig", "compiler_rt", .compiler_rt, .Obj, false, &comp.compiler_rt_obj, main_progress_node });
     }
 
     if (comp.queued_jobs.fuzzer_lib and comp.fuzzer_lib == null) {
-        comp.link_task_wait_group.spawnManager(buildRt, .{ comp, "fuzzer.zig", .libfuzzer, .Lib, true, &comp.fuzzer_lib, main_progress_node });
+        comp.link_task_wait_group.spawnManager(buildRt, .{ comp, "fuzzer.zig", "fuzzer", .libfuzzer, .Lib, true, &comp.fuzzer_lib, main_progress_node });
     }
 
     if (comp.queued_jobs.ubsan_rt_lib and comp.ubsan_rt_lib == null) {
-        comp.link_task_wait_group.spawnManager(buildRt, .{ comp, "ubsan_rt.zig", .libubsan, .Lib, false, &comp.ubsan_rt_lib, main_progress_node });
+        comp.link_task_wait_group.spawnManager(buildRt, .{ comp, "ubsan_rt.zig", "ubsan_rt", .libubsan, .Lib, false, &comp.ubsan_rt_lib, main_progress_node });
     }
 
     if (comp.queued_jobs.ubsan_rt_obj and comp.ubsan_rt_obj == null) {
-        comp.link_task_wait_group.spawnManager(buildRt, .{ comp, "ubsan_rt.zig", .libubsan, .Obj, false, &comp.ubsan_rt_obj, main_progress_node });
+        comp.link_task_wait_group.spawnManager(buildRt, .{ comp, "ubsan_rt.zig", "ubsan_rt", .libubsan, .Obj, false, &comp.ubsan_rt_obj, main_progress_node });
     }
 
     if (comp.queued_jobs.glibc_shared_objects) {
@@ -3800,8 +3804,8 @@ fn performAllTheWorkInner(
         comp.link_task_wait_group.spawnManager(buildLibTsan, .{ comp, main_progress_node });
     }
 
-    if (comp.queued_jobs.zig_libc and comp.libc_static_lib == null) {
-        comp.link_task_wait_group.spawnManager(buildZigLibc, .{ comp, main_progress_node });
+    if (comp.queued_jobs.zigc_lib and comp.zigc_static_lib == null) {
+        comp.link_task_wait_group.spawnManager(buildLibZigC, .{ comp, main_progress_node });
     }
 
     for (0..@typeInfo(musl.CrtFile).@"enum".fields.len) |i| {
@@ -4764,6 +4768,7 @@ fn workerUpdateWin32Resource(
 fn buildRt(
     comp: *Compilation,
     root_source_name: []const u8,
+    root_name: []const u8,
     misc_task: MiscTask,
     output_mode: std.builtin.OutputMode,
     allow_lto: bool,
@@ -4772,6 +4777,7 @@ fn buildRt(
 ) void {
     comp.buildOutputFromZig(
         root_source_name,
+        root_name,
         output_mode,
         allow_lto,
         out,
@@ -4877,17 +4883,18 @@ fn buildLibTsan(comp: *Compilation, prog_node: std.Progress.Node) void {
     }
 }
 
-fn buildZigLibc(comp: *Compilation, prog_node: std.Progress.Node) void {
+fn buildLibZigC(comp: *Compilation, prog_node: std.Progress.Node) void {
     comp.buildOutputFromZig(
         "c.zig",
+        "zigc",
         .Lib,
         true,
-        &comp.libc_static_lib,
-        .zig_libc,
+        &comp.zigc_static_lib,
+        .libzigc,
         prog_node,
     ) catch |err| switch (err) {
         error.SubCompilationFailed => return, // error reported already
-        else => comp.lockAndSetMiscFailure(.zig_libc, "unable to build zig's multitarget libc: {s}", .{@errorName(err)}),
+        else => comp.lockAndSetMiscFailure(.libzigc, "unable to build libzigc: {s}", .{@errorName(err)}),
     };
 }
 
@@ -6521,25 +6528,6 @@ fn canBuildLibCompilerRt(target: std.Target, use_llvm: bool) bool {
     };
 }
 
-/// Not to be confused with canBuildLibC, which builds musl, glibc, and similar.
-/// This one builds lib/c.zig.
-fn canBuildZigLibC(target: std.Target, use_llvm: bool) bool {
-    switch (target.os.tag) {
-        .plan9 => return false,
-        else => {},
-    }
-    switch (target.cpu.arch) {
-        .spirv, .spirv32, .spirv64 => return false,
-        else => {},
-    }
-    return switch (target_util.zigBackend(target, use_llvm)) {
-        .stage2_llvm => true,
-        .stage2_riscv64 => true,
-        .stage2_x86_64 => if (target.ofmt == .elf or target.ofmt == .macho) true else build_options.have_llvm,
-        else => build_options.have_llvm,
-    };
-}
-
 pub fn getZigBackend(comp: Compilation) std.builtin.CompilerBackend {
     const target = comp.root_mod.resolved_target.result;
     return target_util.zigBackend(target, comp.config.use_llvm);
@@ -6580,6 +6568,7 @@ pub fn updateSubCompilation(
 fn buildOutputFromZig(
     comp: *Compilation,
     src_basename: []const u8,
+    root_name: []const u8,
     output_mode: std.builtin.OutputMode,
     allow_lto: bool,
     out: *?CrtFile,
@@ -6643,7 +6632,6 @@ fn buildOutputFromZig(
         .builtin_mod = null,
         .builtin_modules = null, // there is only one module in this compilation
     });
-    const root_name = src_basename[0 .. src_basename.len - std.fs.path.extension(src_basename).len];
     const target = comp.getTarget();
     const bin_basename = try std.zig.binNameAlloc(arena, .{
         .root_name = root_name,
src/musl.zig
@@ -1852,17 +1852,14 @@ const src_files = [_][]const u8{
     "musl/src/string/strcat.c",
     "musl/src/string/strchr.c",
     "musl/src/string/strchrnul.c",
-    "musl/src/string/strcmp.c",
     "musl/src/string/strcpy.c",
     "musl/src/string/strcspn.c",
     "musl/src/string/strdup.c",
     "musl/src/string/strerror_r.c",
     "musl/src/string/strlcat.c",
     "musl/src/string/strlcpy.c",
-    "musl/src/string/strlen.c",
     "musl/src/string/strncasecmp.c",
     "musl/src/string/strncat.c",
-    "musl/src/string/strncmp.c",
     "musl/src/string/strncpy.c",
     "musl/src/string/strndup.c",
     "musl/src/string/strnlen.c",
src/wasi_libc.zig
@@ -1061,17 +1061,14 @@ const libc_top_half_src_files = [_][]const u8{
     "musl/src/string/strcat.c",
     "musl/src/string/strchr.c",
     "musl/src/string/strchrnul.c",
-    "musl/src/string/strcmp.c",
     "musl/src/string/strcpy.c",
     "musl/src/string/strcspn.c",
     "musl/src/string/strdup.c",
     "musl/src/string/strerror_r.c",
     "musl/src/string/strlcat.c",
     "musl/src/string/strlcpy.c",
-    "musl/src/string/strlen.c",
     "musl/src/string/strncasecmp.c",
     "musl/src/string/strncat.c",
-    "musl/src/string/strncmp.c",
     "musl/src/string/strncpy.c",
     "musl/src/string/strndup.c",
     "musl/src/string/strnlen.c",
test/tests.zig
@@ -145,7 +145,7 @@ const test_targets = blk: {
             }) catch unreachable,
             .use_llvm = false,
             .use_lld = false,
-            .skip_modules = &.{ "c-import", "universal-libc", "std" },
+            .skip_modules = &.{ "c-import", "zigc", "std" },
         },
         // https://github.com/ziglang/zig/issues/13623
         //.{
@@ -1443,9 +1443,9 @@ pub fn addModuleTests(b: *std.Build, options: ModuleTestOptions) *Step {
             test_target.use_llvm == false and mem.eql(u8, options.name, "compiler-rt"))
             continue;
 
-        // TODO get universal-libc tests passing for other self-hosted backends.
+        // TODO get zigc tests passing for other self-hosted backends.
         if (target.cpu.arch != .x86_64 and
-            test_target.use_llvm == false and mem.eql(u8, options.name, "universal-libc"))
+            test_target.use_llvm == false and mem.eql(u8, options.name, "zigc"))
             continue;
 
         // TODO get std lib tests passing for other self-hosted backends.
build.zig
@@ -482,8 +482,8 @@ pub fn build(b: *std.Build) !void {
         .test_target_filters = test_target_filters,
         .test_extra_targets = test_extra_targets,
         .root_src = "lib/c.zig",
-        .name = "universal-libc",
-        .desc = "Run the universal libc tests",
+        .name = "zigc",
+        .desc = "Run the zigc tests",
         .optimize_modes = optimization_modes,
         .include_paths = &.{},
         .skip_single_threaded = true,