-
-
Notifications
You must be signed in to change notification settings - Fork 3.1k
Introduce libzigc for libc function implementations in Zig #23529
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Changes from all commits
File filter
Filter by extension
Conversations
Jump to
Diff view
Diff view
There are no files selected for viewing
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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); | ||
| } |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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; |
| Original file line number | Diff line number | Diff line change |
|---|---|---|
| @@ -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, | ||
|
Contributor
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. This is a correct implementation but has different behavior from musl, which returns the difference of the final, differing bytes instead of just -1, 0, and 1. Is that okay when the user has specifically requested musl? What level of compatibility are we aiming for? If someone does happen to rely on a musl bug or quirk, should they just manually link a different copy of the musl sources?
Member
Author
There was a problem hiding this comment. Choose a reason for hiding this commentThe reason will be displayed to describe this comment to others. Learn more. It doesn't seem like musl's behavior is particularly deliberate, but rather an artifact of how it's implemented. It's not documented as being guaranteed either. musl generally takes a strong stance on C/POSIX standards compliance, to the point of exploiting many requirements in those standards for efficiency in its implementation code, so I have a feeling that if you asked Rich if you should rely on this So I think it's fine to just go by the C standard here.
We have to handle this on a case-by-case basis since long-standing bugs do become features sometimes, especially when the standards are unclear on the matter. But in this particular case, ideally, they should fix their code because it isn't compliant with the C standard's definition of |
||
| }; | ||
| } | ||
|
|
||
| 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); | ||
| } | ||
This file was deleted.
This file was deleted.
This file was deleted.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can make this u8 instead of c_char since it's the same ABI (what's being passed is a pointer), and avoid all the ptr casts in this file.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Wouldn't that become an impediment to #20654 because we'd lose the information that the type in the signature is actually meant to be C
char, which can beu8ori8depending on target?There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
mm, good point. I did mention this though:
still I think you're right, the ability to generate viable .h files, or at least partial, would be nice.