Skip to content

Commit 1f896c1

Browse files
committed
Introduce libzigc for libc function implementations in Zig.
This lays the groundwork for ziglang#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 ziglang#22240 for a similar case.
1 parent ee0ff13 commit 1f896c1

File tree

15 files changed

+151
-288
lines changed

15 files changed

+151
-288
lines changed

build.zig

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -482,8 +482,8 @@ pub fn build(b: *std.Build) !void {
482482
.test_target_filters = test_target_filters,
483483
.test_extra_targets = test_extra_targets,
484484
.root_src = "lib/c.zig",
485-
.name = "universal-libc",
486-
.desc = "Run the universal libc tests",
485+
.name = "zigc",
486+
.desc = "Run the zigc tests",
487487
.optimize_modes = optimization_modes,
488488
.include_paths = &.{},
489489
.skip_single_threaded = true,

lib/c.zig

Lines changed: 19 additions & 166 deletions
Original file line numberDiff line numberDiff line change
@@ -1,180 +1,33 @@
11
//! This is Zig's multi-target implementation of libc.
2-
//! When builtin.link_libc is true, we need to export all the functions and
3-
//! provide an entire C API.
2+
//!
3+
//! When `builtin.link_libc` is true, we need to export all the functions and
4+
//! provide a libc API compatible with the target (e.g. musl, wasi-libc, ...).
45

5-
const std = @import("std");
66
const builtin = @import("builtin");
7-
const math = std.math;
8-
const isNan = std.math.isNan;
9-
const maxInt = std.math.maxInt;
10-
const native_os = builtin.os.tag;
11-
const native_arch = builtin.cpu.arch;
12-
const native_abi = builtin.abi;
13-
14-
const linkage: std.builtin.GlobalLinkage = if (builtin.is_test) .internal else .strong;
7+
const std = @import("std");
158

16-
const is_wasm = switch (native_arch) {
17-
.wasm32, .wasm64 => true,
18-
else => false,
19-
};
20-
const is_freestanding = switch (native_os) {
21-
.freestanding, .other => true,
22-
else => false,
23-
};
9+
// Avoid dragging in the runtime safety mechanisms into this .o file, unless
10+
// we're trying to test zigc.
11+
pub const panic = if (builtin.is_test)
12+
std.debug.FullPanic(std.debug.defaultPanic)
13+
else
14+
std.debug.no_panic;
2415

2516
comptime {
26-
if (is_freestanding and is_wasm and builtin.link_libc) {
27-
@export(&wasm_start, .{ .name = "_start", .linkage = .strong });
28-
}
29-
30-
if (builtin.link_libc) {
31-
@export(&strcmp, .{ .name = "strcmp", .linkage = linkage });
32-
@export(&strncmp, .{ .name = "strncmp", .linkage = linkage });
33-
@export(&strerror, .{ .name = "strerror", .linkage = linkage });
34-
@export(&strlen, .{ .name = "strlen", .linkage = linkage });
35-
@export(&strcpy, .{ .name = "strcpy", .linkage = linkage });
36-
@export(&strncpy, .{ .name = "strncpy", .linkage = linkage });
37-
@export(&strcat, .{ .name = "strcat", .linkage = linkage });
38-
@export(&strncat, .{ .name = "strncat", .linkage = linkage });
39-
}
40-
}
41-
42-
// Avoid dragging in the runtime safety mechanisms into this .o file,
43-
// unless we're trying to test this file.
44-
pub fn panic(msg: []const u8, error_return_trace: ?*std.builtin.StackTrace, _: ?usize) noreturn {
45-
@branchHint(.cold);
46-
_ = error_return_trace;
47-
if (builtin.is_test) {
48-
std.debug.panic("{s}", .{msg});
49-
}
50-
switch (native_os) {
51-
.freestanding, .other, .amdhsa, .amdpal => while (true) {},
52-
else => std.os.abort(),
17+
if (builtin.target.isMuslLibC() or builtin.target.isWasiLibC()) {
18+
// Files specific to musl and wasi-libc.
19+
_ = @import("c/string.zig");
5320
}
54-
}
55-
56-
extern fn main(argc: c_int, argv: [*:null]?[*:0]u8) c_int;
57-
fn wasm_start() callconv(.c) void {
58-
_ = main(0, undefined);
59-
}
6021

61-
fn strcpy(dest: [*:0]u8, src: [*:0]const u8) callconv(.c) [*:0]u8 {
62-
var i: usize = 0;
63-
while (src[i] != 0) : (i += 1) {
64-
dest[i] = src[i];
22+
if (builtin.target.isMuslLibC()) {
23+
// Files specific to musl.
6524
}
66-
dest[i] = 0;
6725

68-
return dest;
69-
}
70-
71-
test "strcpy" {
72-
var s1: [9:0]u8 = undefined;
73-
74-
s1[0] = 0;
75-
_ = strcpy(&s1, "foobarbaz");
76-
try std.testing.expectEqualSlices(u8, "foobarbaz", std.mem.sliceTo(&s1, 0));
77-
}
78-
79-
fn strncpy(dest: [*:0]u8, src: [*:0]const u8, n: usize) callconv(.c) [*:0]u8 {
80-
var i: usize = 0;
81-
while (i < n and src[i] != 0) : (i += 1) {
82-
dest[i] = src[i];
83-
}
84-
while (i < n) : (i += 1) {
85-
dest[i] = 0;
26+
if (builtin.target.isWasiLibC()) {
27+
// Files specific to wasi-libc.
8628
}
8729

88-
return dest;
89-
}
90-
91-
test "strncpy" {
92-
var s1: [9:0]u8 = undefined;
93-
94-
s1[0] = 0;
95-
_ = strncpy(&s1, "foobarbaz", @sizeOf(@TypeOf(s1)));
96-
try std.testing.expectEqualSlices(u8, "foobarbaz", std.mem.sliceTo(&s1, 0));
97-
}
98-
99-
fn strcat(dest: [*:0]u8, src: [*:0]const u8) callconv(.c) [*:0]u8 {
100-
var dest_end: usize = 0;
101-
while (dest[dest_end] != 0) : (dest_end += 1) {}
102-
103-
var i: usize = 0;
104-
while (src[i] != 0) : (i += 1) {
105-
dest[dest_end + i] = src[i];
30+
if (builtin.target.isMinGW()) {
31+
// Files specific to MinGW-w64.
10632
}
107-
dest[dest_end + i] = 0;
108-
109-
return dest;
110-
}
111-
112-
test "strcat" {
113-
var s1: [9:0]u8 = undefined;
114-
115-
s1[0] = 0;
116-
_ = strcat(&s1, "foo");
117-
_ = strcat(&s1, "bar");
118-
_ = strcat(&s1, "baz");
119-
try std.testing.expectEqualSlices(u8, "foobarbaz", std.mem.sliceTo(&s1, 0));
120-
}
121-
122-
fn strncat(dest: [*:0]u8, src: [*:0]const u8, avail: usize) callconv(.c) [*:0]u8 {
123-
var dest_end: usize = 0;
124-
while (dest[dest_end] != 0) : (dest_end += 1) {}
125-
126-
var i: usize = 0;
127-
while (i < avail and src[i] != 0) : (i += 1) {
128-
dest[dest_end + i] = src[i];
129-
}
130-
dest[dest_end + i] = 0;
131-
132-
return dest;
133-
}
134-
135-
test "strncat" {
136-
var s1: [9:0]u8 = undefined;
137-
138-
s1[0] = 0;
139-
_ = strncat(&s1, "foo1111", 3);
140-
_ = strncat(&s1, "bar1111", 3);
141-
_ = strncat(&s1, "baz1111", 3);
142-
try std.testing.expectEqualSlices(u8, "foobarbaz", std.mem.sliceTo(&s1, 0));
143-
}
144-
145-
fn strcmp(s1: [*:0]const u8, s2: [*:0]const u8) callconv(.c) c_int {
146-
return switch (std.mem.orderZ(u8, s1, s2)) {
147-
.lt => -1,
148-
.eq => 0,
149-
.gt => 1,
150-
};
151-
}
152-
153-
fn strlen(s: [*:0]const u8) callconv(.c) usize {
154-
return std.mem.len(s);
155-
}
156-
157-
fn strncmp(_l: [*:0]const u8, _r: [*:0]const u8, _n: usize) callconv(.c) c_int {
158-
if (_n == 0) return 0;
159-
var l = _l;
160-
var r = _r;
161-
var n = _n - 1;
162-
while (l[0] != 0 and r[0] != 0 and n != 0 and l[0] == r[0]) {
163-
l += 1;
164-
r += 1;
165-
n -= 1;
166-
}
167-
return @as(c_int, l[0]) - @as(c_int, r[0]);
168-
}
169-
170-
fn strerror(errnum: c_int) callconv(.c) [*:0]const u8 {
171-
_ = errnum;
172-
return "TODO strerror implementation";
173-
}
174-
175-
test "strncmp" {
176-
try std.testing.expect(strncmp("a", "b", 1) < 0);
177-
try std.testing.expect(strncmp("a", "c", 1) < 0);
178-
try std.testing.expect(strncmp("b", "a", 1) > 0);
179-
try std.testing.expect(strncmp("\xff", "\x02", 1) > 0);
18033
}

lib/c/common.zig

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,15 @@
1+
const builtin = @import("builtin");
2+
const std = @import("std");
3+
4+
pub const linkage: std.builtin.GlobalLinkage = if (builtin.is_test)
5+
.internal
6+
else
7+
.strong;
8+
9+
/// Determines the symbol's visibility to other objects.
10+
/// For WebAssembly this allows the symbol to be resolved to other modules, but will not
11+
/// export it to the host runtime.
12+
pub const visibility: std.builtin.SymbolVisibility = if (builtin.cpu.arch.isWasm() and linkage != .internal)
13+
.hidden
14+
else
15+
.default;

lib/c/string.zig

Lines changed: 45 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,45 @@
1+
const builtin = @import("builtin");
2+
const std = @import("std");
3+
const common = @import("common.zig");
4+
5+
comptime {
6+
@export(&strcmp, .{ .name = "strcmp", .linkage = common.linkage, .visibility = common.visibility });
7+
@export(&strlen, .{ .name = "strlen", .linkage = common.linkage, .visibility = common.visibility });
8+
@export(&strncmp, .{ .name = "strncmp", .linkage = common.linkage, .visibility = common.visibility });
9+
}
10+
11+
fn strcmp(s1: [*:0]const c_char, s2: [*:0]const c_char) callconv(.c) c_int {
12+
// We need to perform unsigned comparisons.
13+
return switch (std.mem.orderZ(u8, @ptrCast(s1), @ptrCast(s2))) {
14+
.lt => -1,
15+
.eq => 0,
16+
.gt => 1,
17+
};
18+
}
19+
20+
fn strncmp(s1: [*:0]const c_char, s2: [*:0]const c_char, n: usize) callconv(.c) c_int {
21+
if (n == 0) return 0;
22+
23+
var l: [*:0]const u8 = @ptrCast(s1);
24+
var r: [*:0]const u8 = @ptrCast(s2);
25+
var i = n - 1;
26+
27+
while (l[0] != 0 and r[0] != 0 and i != 0 and l[0] == r[0]) {
28+
l += 1;
29+
r += 1;
30+
i -= 1;
31+
}
32+
33+
return @as(c_int, l[0]) - @as(c_int, r[0]);
34+
}
35+
36+
test strncmp {
37+
try std.testing.expect(strncmp(@ptrCast("a"), @ptrCast("b"), 1) < 0);
38+
try std.testing.expect(strncmp(@ptrCast("a"), @ptrCast("c"), 1) < 0);
39+
try std.testing.expect(strncmp(@ptrCast("b"), @ptrCast("a"), 1) > 0);
40+
try std.testing.expect(strncmp(@ptrCast("\xff"), @ptrCast("\x02"), 1) > 0);
41+
}
42+
43+
fn strlen(s: [*:0]const c_char) callconv(.c) usize {
44+
return std.mem.len(s);
45+
}

lib/libc/musl/src/string/strcmp.c

Lines changed: 0 additions & 7 deletions
This file was deleted.

lib/libc/musl/src/string/strlen.c

Lines changed: 0 additions & 22 deletions
This file was deleted.

lib/libc/musl/src/string/strncmp.c

Lines changed: 0 additions & 9 deletions
This file was deleted.

0 commit comments

Comments
 (0)