Merge pull request #23529 from alexrp/2879-groundwork

Introduce libzigc for libc function implementations in Zig
This commit is contained in:
Alex Rønne Petersen 2025-04-12 18:14:17 +02:00 committed by GitHub
commit 9352f379e8
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
293 changed files with 165 additions and 297 deletions

View File

@ -476,8 +476,8 @@ pub fn build(b: *std.Build) !void {
.test_target_filters = test_target_filters, .test_target_filters = test_target_filters,
.test_extra_targets = test_extra_targets, .test_extra_targets = test_extra_targets,
.root_src = "lib/c.zig", .root_src = "lib/c.zig",
.name = "universal-libc", .name = "zigc",
.desc = "Run the universal libc tests", .desc = "Run the zigc tests",
.optimize_modes = optimization_modes, .optimize_modes = optimization_modes,
.include_paths = &.{}, .include_paths = &.{},
.skip_single_threaded = true, .skip_single_threaded = true,

185
lib/c.zig
View File

@ -1,180 +1,33 @@
//! This is Zig's multi-target implementation of libc. //! 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 builtin = @import("builtin");
const math = std.math; const std = @import("std");
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; // Avoid dragging in the runtime safety mechanisms into this .o file, unless
// we're trying to test zigc.
const is_wasm = switch (native_arch) { pub const panic = if (builtin.is_test)
.wasm32, .wasm64 => true, std.debug.FullPanic(std.debug.defaultPanic)
else => false, else
}; std.debug.no_panic;
const is_freestanding = switch (native_os) {
.freestanding, .other => true,
else => false,
};
comptime { comptime {
if (is_freestanding and is_wasm and builtin.link_libc) { if (builtin.target.isMuslLibC() or builtin.target.isWasiLibC()) {
@export(&wasm_start, .{ .name = "_start", .linkage = .strong }); // Files specific to musl and wasi-libc.
_ = @import("c/string.zig");
} }
if (builtin.link_libc) { if (builtin.target.isMuslLibC()) {
@export(&strcmp, .{ .name = "strcmp", .linkage = linkage }); // Files specific to musl.
@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, if (builtin.target.isWasiLibC()) {
// unless we're trying to test this file. // Files specific to wasi-libc.
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(),
}
} }
extern fn main(argc: c_int, argv: [*:null]?[*:0]u8) c_int; if (builtin.target.isMinGW()) {
fn wasm_start() callconv(.c) void { // Files specific to MinGW-w64.
_ = 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];
}
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;
}
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];
}
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);
} }

15
lib/c/common.zig Normal file
View File

@ -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;

45
lib/c/string.zig Normal file
View File

@ -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);
}

View File

@ -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;
}

View File

@ -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;
}

View File

@ -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;
}

Some files were not shown because too many files have changed in this diff Show More