Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
4 changes: 2 additions & 2 deletions build.zig
Original file line number Diff line number Diff line change
Expand Up @@ -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,
Expand Down
185 changes: 19 additions & 166 deletions lib/c.zig
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);
}
15 changes: 15 additions & 0 deletions lib/c/common.zig
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;
45 changes: 45 additions & 0 deletions lib/c/string.zig
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 {
Copy link
Member

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.

Copy link
Member Author

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 be u8 or i8 depending on target?

Copy link
Member

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:

Check for strict equality? Should there be a notion of compatibility? Should there be compatibility-checking settings? I'm thinking that there is indeed a notion of compatibility. For example, if the ABI defines a usize but you want to have an enum on the other side, that is well-defined behavior according to the ABI. However, one might want to tweak what kinds of compatibility are or are not counted as mismatch.

still I think you're right, the ability to generate viable .h files, or at least partial, would be nice.

// We need to perform unsigned comparisons.
return switch (std.mem.orderZ(u8, @ptrCast(s1), @ptrCast(s2))) {
.lt => -1,
.eq => 0,
.gt => 1,
Copy link
Contributor

Choose a reason for hiding this comment

The 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?

Copy link
Member Author

@alexrp alexrp Apr 11, 2025

Choose a reason for hiding this comment

The 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 strcmp implementation detail, you'd get an emphatic "no".

So I think it's fine to just go by the C standard here.

If someone does happen to rely on a musl bug or quirk, should they just manually link a different copy of the musl sources?

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 strcmp and thus non-portable.

};
}

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);
}
7 changes: 0 additions & 7 deletions lib/libc/musl/src/string/strcmp.c

This file was deleted.

22 changes: 0 additions & 22 deletions lib/libc/musl/src/string/strlen.c

This file was deleted.

9 changes: 0 additions & 9 deletions lib/libc/musl/src/string/strncmp.c

This file was deleted.

File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
5 changes: 5 additions & 0 deletions lib/std/zig/system.zig
Original file line number Diff line number Diff line change
Expand Up @@ -411,6 +411,11 @@ pub fn resolveTargetQuery(query: Target.Query) DetectError!Target {
if (result.cpu.arch.isArm() and result.abi.float() == .soft) {
result.cpu.features.removeFeature(@intFromEnum(Target.arm.Feature.vfp2));
}

// https://github.com/llvm/llvm-project/issues/135283
if (result.cpu.arch.isMIPS() and result.abi.float() == .soft) {
result.cpu.features.addFeature(@intFromEnum(Target.mips.Feature.soft_float));
}
}

// It's possible that we detect the native ABI, but fail to detect the OS version or were told
Expand Down
Loading