Skip to content

Implement some string functions for libzigc #23847

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

Open
wants to merge 10 commits into
base: master
Choose a base branch
from
Open
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
5 changes: 3 additions & 2 deletions lib/c.zig
Original file line number Diff line number Diff line change
Expand Up @@ -14,10 +14,11 @@ else
std.debug.no_panic;

comptime {
_ = @import("c/string.zig");
_ = @import("c/strings.zig");

if (builtin.target.isMuslLibC() or builtin.target.isWasiLibC()) {
// Files specific to musl and wasi-libc.
_ = @import("c/string.zig");
_ = @import("c/strings.zig");
}

if (builtin.target.isMuslLibC()) {
Expand Down
265 changes: 262 additions & 3 deletions lib/c/string.zig
Original file line number Diff line number Diff line change
Expand Up @@ -3,9 +3,36 @@ 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 });
if (builtin.target.isMuslLibC() or builtin.target.isWasiLibC()) {
@export(&strcmp, .{ .name = "strcmp", .linkage = common.linkage, .visibility = common.visibility });
@export(&strlen, .{ .name = "strlen", .linkage = common.linkage, .visibility = common.visibility });
@export(&strnlen, .{ .name = "strnlen", .linkage = common.linkage, .visibility = common.visibility });
@export(&strncmp, .{ .name = "strncmp", .linkage = common.linkage, .visibility = common.visibility });
@export(&strchr, .{ .name = "strchr", .linkage = common.linkage, .visibility = common.visibility });
@export(&strrchr, .{ .name = "strrchr", .linkage = common.linkage, .visibility = common.visibility });
@export(&strcpy, .{ .name = "strcpy", .linkage = common.linkage, .visibility = common.visibility });
@export(&strcat, .{ .name = "strcat", .linkage = common.linkage, .visibility = common.visibility });
@export(&strncpy, .{ .name = "strncpy", .linkage = common.linkage, .visibility = common.visibility });
@export(&memccpy, .{ .name = "memccpy", .linkage = common.linkage, .visibility = common.visibility });
@export(&mempcpy, .{ .name = "mempcpy", .linkage = common.linkage, .visibility = common.visibility });
@export(&memmem, .{ .name = "memmem", .linkage = common.linkage, .visibility = common.visibility });
@export(&memchr, .{ .name = "memchr", .linkage = common.linkage, .visibility = common.visibility });
@export(&strchrnul, .{ .name = "strchrnul", .linkage = .weak, .visibility = common.visibility });
@export(&memrchr, .{ .name = "memrchr", .linkage = .weak, .visibility = common.visibility });
@export(&stpcpy, .{ .name = "stpcpy", .linkage = .weak, .visibility = common.visibility });
@export(&stpncpy, .{ .name = "stpncpy", .linkage = .weak, .visibility = common.visibility });
{
// NOTE: These symbols are only depended on by other parts of the
// musl c-sources. They should be removed once libzigc is further
// along, and these functions have been re-implemented in zig.
@export(&strchrnul, .{ .name = "__strchrnul", .linkage = .strong, .visibility = .hidden });
}
}

if (builtin.target.isMinGW()) {
@export(&mempcpy, .{ .name = "mempcpy", .linkage = common.linkage, .visibility = common.visibility });
@export(&strnlen, .{ .name = "strnlen", .linkage = common.linkage, .visibility = common.visibility });
}
}

fn strcmp(s1: [*:0]const c_char, s2: [*:0]const c_char) callconv(.c) c_int {
Expand Down Expand Up @@ -43,3 +70,235 @@ test strncmp {
fn strlen(s: [*:0]const c_char) callconv(.c) usize {
return std.mem.len(s);
}

fn strnlen(s: [*:0]const c_char, n: usize) callconv(.c) usize {
return if (std.mem.indexOfScalar(c_char, s[0..n], 0)) |idx| idx else n;
}

fn strchrnul(s: [*:0]const c_char, c: c_int) callconv(.c) [*:0]const c_char {
const needle: c_char = @intCast(c);
if (needle == 0) return s + strlen(s);

var it: [*:0]const c_char = s;
while (it[0] != 0 and it[0] != needle) : (it += 1) {}
return it;
}

test strchrnul {
const foo: [*:0]const c_char = @ptrCast("disco");
try std.testing.expect(strchrnul(foo, 'd') == foo);
try std.testing.expect(strchrnul(foo, 'o') == (foo + 4));
try std.testing.expect(strchrnul(foo, 'z') == (foo + 5));
try std.testing.expect(strchrnul(foo, 0) == (foo + 5));
}

pub fn strchr(s: [*:0]const c_char, c: c_int) callconv(.c) ?[*:0]const c_char {
const result = strchrnul(s, c);
const needle: c_char = @intCast(c);
return if (result[0] == needle) result else null;
}

test strchr {
const foo: [*:0]const c_char = @ptrCast("disco");
try std.testing.expect(strchr(foo, 'd') == foo);
try std.testing.expect(strchr(foo, 'o') == (foo + 4));
try std.testing.expect(strchr(foo, 'z') == null);
try std.testing.expect(strchr(foo, 0) == (foo + 5));
}

fn memchr(m: *const anyopaque, c: c_int, n: usize) callconv(.c) ?*const anyopaque {
const needle: c_char = @intCast(c);
const s: [*]const c_char = @ptrCast(m);
if (std.mem.indexOfScalar(c_char, s[0..n], needle)) |idx| {
return @ptrCast(s + idx);
} else {
return null;
}
}

test memchr {
const foo: [*:0]const c_char = @ptrCast("disco");
try std.testing.expect(memchr(foo, 'd', 5) == @as(*const anyopaque, @ptrCast(foo)));
try std.testing.expect(memchr(foo, 'o', 5) == @as(*const anyopaque, @ptrCast(foo + 4)));
try std.testing.expect(memchr(foo, 'z', 5) == null);
}

fn memrchr(m: *const anyopaque, c: c_int, n: usize) callconv(.c) ?*const anyopaque {
const needle: c_char = @intCast(c);
const s: [*]const c_char = @ptrCast(m);
if (std.mem.lastIndexOfScalar(c_char, s[0..n], needle)) |idx| {
return @ptrCast(s + idx);
} else {
return null;
}
}

test memrchr {
const foo: [*:0]const c_char = @ptrCast("disco");
try std.testing.expect(memrchr(foo, 'd', 5) == @as(*const anyopaque, @ptrCast(foo)));
try std.testing.expect(memrchr(foo, 'o', 5) == @as(*const anyopaque, @ptrCast(foo + 4)));
try std.testing.expect(memrchr(foo, 'z', 5) == null);
}

pub fn strrchr(s: [*:0]const c_char, c: c_int) callconv(.c) ?[*:0]const c_char {
return @ptrCast(memrchr(s, c, strlen(s) + 1));
}

test strrchr {
const foo: [*:0]const c_char = @ptrCast("disco");
try std.testing.expect(strrchr(foo, 'd') == foo);
try std.testing.expect(strrchr(foo, 'o') == (foo + 4));
try std.testing.expect(strrchr(foo, 'z') == null);
try std.testing.expect(strrchr(foo, 0) == (foo + 5));
}

fn __aeabi_memclr(dest: *anyopaque, n: usize) callconv(.c) *anyopaque {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This looks unused? We also have this in compiler-rt anyway.

const buf: [*]c_char = @ptrCast(dest);
@memset(buf[0..n], 0);
return dest;
}

fn memccpy(noalias dest: *anyopaque, noalias src: *const anyopaque, c: c_int, n: usize) callconv(.c) ?*anyopaque {
@setRuntimeSafety(false);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

why would this need to be different than the mode set for the compilation unit?

const d: [*]c_char = @ptrCast(dest);
const s: [*]const c_char = @ptrCast(src);
Comment on lines +163 to +164
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no need to pointer cast; you can change the parameter types.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My rationale for this was that I wanted to keep the function signature as close to the c signature as possible. I was basing this decision off of a comment that you made in a prior post here. Technically in this case this would actually increase the type information, but it would not match 1:1 with the c ABI so I am unsure which approach is better.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you, I forgot about that. If you don't mind, a comment would help remind future me, and future contributors as well, of the rationale behind this.

const needle: c_char = @intCast(c);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

please add a comment explaining why the @intCast is justified (does the spec say what happens if you pass an int that does not fit into a char?)

var idx: usize = 0;
while (idx < n) : (idx += 1) {
d[idx] = s[idx];
if (d[idx] == needle) return d + idx + 1;
}
Comment on lines +166 to +170
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

this looks like it could be implemented better using already existing std.mem functions, which are SIMD optimized.

return null;
}

test memccpy {
const src: []const u8 = "supercalifragilisticexpialidocious";
var dst: [src.len]u8 = @splat(0);
const dOffset = std.mem.indexOfScalar(u8, src, 'd').?;
const endPtr: *anyopaque = @ptrCast(@as([*]u8, &dst) + dOffset + 1);
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

no need to use @ptrCast in a unit test.

try std.testing.expect(memccpy(@ptrCast(&dst), @ptrCast(src.ptr), 'd', src.len) == endPtr);
try std.testing.expectEqualStrings("supercalifragilisticexpialid", std.mem.trimRight(u8, dst[0..], &.{0}));

dst = @splat(0);
try std.testing.expect(memccpy(@constCast(@ptrCast(&dst)), (@ptrCast(src.ptr)), 'z', src.len) == null);
try std.testing.expectEqualStrings(src, dst[0..]);
}

fn mempcpy(noalias dest: *anyopaque, noalias src: *const anyopaque, n: usize) callconv(.c) *anyopaque {
@setRuntimeSafety(false);
const d: [*]u8 = @ptrCast(dest);
const s: [*]const u8 = @ptrCast(src);
@memcpy(d[0..n], s[0..n]);
return @ptrCast(d + n);
}

test mempcpy {
const bytesToWrite = 3;
const src: []const u8 = "testing";
var dst: [src.len]u8 = @splat('z');
const endPtr: *anyopaque = @ptrCast(@as([*]u8, &dst) + bytesToWrite);
try std.testing.expect(mempcpy(@ptrCast(&dst), @ptrCast(src.ptr), bytesToWrite) == endPtr);
try std.testing.expect(@as(*u8, @ptrCast(endPtr)).* == 'z');
}

fn memmem(haystack: *const anyopaque, haystack_len: usize, needle: *const anyopaque, needle_len: usize) callconv(.c) ?*const anyopaque {
const h: [*]const c_char = @ptrCast(haystack);
const n: [*]const c_char = @ptrCast(needle);

if (std.mem.indexOf(c_char, h[0..haystack_len], n[0..needle_len])) |idx| {
return @ptrCast(h + idx);
} else {
return null;
}
}

fn stpcpy(noalias dest: [*:0]c_char, noalias src: [*:0]const c_char) callconv(.c) [*:0]c_char {
var d: [*:0]c_char = dest;
var s: [*:0]const c_char = src;
// QUESTION: is std.mem.span -> @memset more efficient here?
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think you'd have to just measure this with a few different array sizes.

while (true) {
d[0] = s[0];
if (s[0] == 0) return d;
d += 1;
s += 1;
}
}

test stpcpy {
const src: [:0]const c_char = @ptrCast("bananas");
var dst: [src.len + 1]c_char = undefined;
const endPtr: [*]c_char = @ptrCast(@as([*]c_char, &dst) + src.len);
try std.testing.expect(stpcpy(@ptrCast(&dst), @ptrCast(src.ptr)) == endPtr);
try std.testing.expect(endPtr[0] == 0);
try std.testing.expectEqualSentinel(c_char, 0, src, dst[0..src.len :0]);
}

fn strcpy(noalias dest: [*:0]c_char, noalias src: [*:0]const c_char) callconv(.c) [*:0]c_char {
_ = stpcpy(dest, src);
return dest;
}

test strcpy {
const src: [:0]const c_char = @ptrCast("bananas");
var dst: [src.len + 1]c_char = undefined;
try std.testing.expect(strcpy(@ptrCast(&dst), @ptrCast(src.ptr)) == @as([*:0]c_char, @ptrCast(&dst)));
try std.testing.expectEqualSentinel(c_char, 0, src, dst[0..src.len :0]);
}

fn stpncpy(noalias dest: [*:0]c_char, noalias src: [*:0]const c_char, n: usize) callconv(.c) [*:0]c_char {
const dlen = strnlen(src, n);
const end = mempcpy(@ptrCast(dest), @ptrCast(src), dlen);
const remaining: [*]u8 = @ptrCast(end);
@memset(remaining[0 .. n - dlen], 0);
return @ptrCast(end);
}

test stpncpy {
var buf: [5]c_char = undefined;
const b: [*:0]c_char = @ptrCast(&buf);
try std.testing.expect(stpncpy(@ptrCast(&buf), @ptrCast("1"), buf.len) == b + 1);
try std.testing.expectEqualSlices(c_char, &.{ '1', 0, 0, 0, 0 }, buf[0..]);
try std.testing.expect(stpncpy(@ptrCast(&buf), @ptrCast("12"), buf.len) == b + 2);
try std.testing.expectEqualSlices(c_char, &.{ '1', '2', 0, 0, 0 }, buf[0..]);
try std.testing.expect(stpncpy(@ptrCast(&buf), @ptrCast("123"), buf.len) == b + 3);
try std.testing.expectEqualSlices(c_char, &.{ '1', '2', '3', 0, 0 }, buf[0..]);
try std.testing.expect(stpncpy(@ptrCast(&buf), @ptrCast("1234"), buf.len) == b + 4);
try std.testing.expectEqualSlices(c_char, &.{ '1', '2', '3', '4', 0 }, buf[0..]);
try std.testing.expect(stpncpy(@ptrCast(&buf), @ptrCast("12345"), buf.len) == b + 5);
try std.testing.expectEqualSlices(c_char, &.{ '1', '2', '3', '4', '5' }, buf[0..]);
}

fn strncpy(noalias dest: [*:0]c_char, noalias src: [*:0]const c_char, n: usize) callconv(.c) [*:0]c_char {
_ = stpncpy(dest, src, n);
return dest;
}

test strncpy {
var buf: [5]c_char = undefined;
const b: [*:0]c_char = @ptrCast(&buf);
try std.testing.expect(strncpy(@ptrCast(&buf), @ptrCast("1"), buf.len) == b);
try std.testing.expectEqualSlices(c_char, &.{ '1', 0, 0, 0, 0 }, buf[0..]);
try std.testing.expect(strncpy(@ptrCast(&buf), @ptrCast("12"), buf.len) == b);
try std.testing.expectEqualSlices(c_char, &.{ '1', '2', 0, 0, 0 }, buf[0..]);
try std.testing.expect(strncpy(@ptrCast(&buf), @ptrCast("123"), buf.len) == b);
try std.testing.expectEqualSlices(c_char, &.{ '1', '2', '3', 0, 0 }, buf[0..]);
try std.testing.expect(strncpy(@ptrCast(&buf), @ptrCast("1234"), buf.len) == b);
try std.testing.expectEqualSlices(c_char, &.{ '1', '2', '3', '4', 0 }, buf[0..]);
try std.testing.expect(strncpy(@ptrCast(&buf), @ptrCast("12345"), buf.len) == b);
try std.testing.expectEqualSlices(c_char, &.{ '1', '2', '3', '4', '5' }, buf[0..]);
}

fn strcat(noalias dest: [*:0]c_char, noalias src: [*:0]const c_char) callconv(.c) [*:0]c_char {
const start = dest + strlen(dest);
_ = stpcpy(start, src);
return dest;
}

test strcat {
const start = "Hello";
const end = " World!\n";
var buf: [start.len + end.len:0]c_char = undefined;
@memcpy(buf[0 .. start.len + 1], @as([*:0]const c_char, @ptrCast(start)));
try std.testing.expect(strcat(&buf, @as([*:0]const c_char, @ptrCast(end))) == &buf);
try std.testing.expectEqualStrings("Hello World!\n", @as([]u8, @ptrCast(buf[0..])));
}
8 changes: 7 additions & 1 deletion lib/c/strings.zig
Original file line number Diff line number Diff line change
@@ -1,8 +1,14 @@
const builtin = @import("builtin");
const std = @import("std");
const common = @import("common.zig");
const string = @import("string.zig");

comptime {
@export(&bzero, .{ .name = "bzero", .linkage = common.linkage, .visibility = common.visibility });
if (builtin.target.isMuslLibC() or builtin.target.isWasiLibC()) {
@export(&bzero, .{ .name = "bzero", .linkage = common.linkage, .visibility = common.visibility });
@export(&string.strchr, .{ .name = "index", .linkage = common.linkage, .visibility = common.visibility });
@export(&string.strrchr, .{ .name = "rindex", .linkage = common.linkage, .visibility = common.visibility });
}
}

fn bzero(s: *anyopaque, n: usize) callconv(.c) void {
Expand Down
12 changes: 0 additions & 12 deletions lib/libc/mingw/misc/mempcpy.c

This file was deleted.

11 changes: 0 additions & 11 deletions lib/libc/mingw/misc/strnlen.c

This file was deleted.

Loading
Loading