authorgravatar for me@gasinfinity.devGasInfinity <me@gasinfinity.dev> 2026-01-11 16:58:04+01:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2026-01-12 00:55:11+01:00
log514f6e589ca441c3ac115b8c764bdc9f6d4aca01
tree6ff49595858126cb7b8674943ca5d6b79917278a
parente2338edb47a23e002a7e46ea8108dc7d6f621d86

feat(libzigc): use common integer `ato*` and `strto*` implementations

* also removes their musl implementation

7 files changed, 220 insertions(+), 114 deletions(-)

lib/c/stdlib.zig+220
...@@ -1,6 +1,7 @@...@@ -1,6 +1,7 @@
1const std = @import("std");1const std = @import("std");
2const common = @import("common.zig");2const common = @import("common.zig");
3const builtin = @import("builtin");3const builtin = @import("builtin");
4const assert = std.debug.assert;
4const div_t = std.c.div_t;5const div_t = std.c.div_t;
5const ldiv_t = std.c.ldiv_t;6const ldiv_t = std.c.ldiv_t;
6const lldiv_t = std.c.lldiv_t;7const lldiv_t = std.c.lldiv_t;
...@@ -16,6 +17,24 @@ comptime {...@@ -16,6 +17,24 @@ comptime {
16 @export(&ldiv, .{ .name = "ldiv", .linkage = common.linkage, .visibility = common.visibility });17 @export(&ldiv, .{ .name = "ldiv", .linkage = common.linkage, .visibility = common.visibility });
17 @export(&lldiv, .{ .name = "lldiv", .linkage = common.linkage, .visibility = common.visibility });18 @export(&lldiv, .{ .name = "lldiv", .linkage = common.linkage, .visibility = common.visibility });
1819
20 @export(&atoi, .{ .name = "atoi", .linkage = common.linkage, .visibility = common.visibility });
21 @export(&atol, .{ .name = "atol", .linkage = common.linkage, .visibility = common.visibility });
22 @export(&atoll, .{ .name = "atoll", .linkage = common.linkage, .visibility = common.visibility });
23
24 @export(&strtol, .{ .name = "strtol", .linkage = common.linkage, .visibility = common.visibility });
25 @export(&strtoll, .{ .name = "strtoll", .linkage = common.linkage, .visibility = common.visibility });
26 @export(&strtoul, .{ .name = "strtoul", .linkage = common.linkage, .visibility = common.visibility });
27 @export(&strtoull, .{ .name = "strtoull", .linkage = common.linkage, .visibility = common.visibility });
28 @export(&strtoimax, .{ .name = "strtoimax", .linkage = common.linkage, .visibility = common.visibility });
29 @export(&strtoumax, .{ .name = "strtoumax", .linkage = common.linkage, .visibility = common.visibility });
30
31 @export(&strtol, .{ .name = "__strtol_internal", .linkage = common.linkage, .visibility = common.visibility });
32 @export(&strtoll, .{ .name = "__strtoll_internal", .linkage = common.linkage, .visibility = common.visibility });
33 @export(&strtoul, .{ .name = "__strtoul_internal", .linkage = common.linkage, .visibility = common.visibility });
34 @export(&strtoull, .{ .name = "__strtoull_internal", .linkage = common.linkage, .visibility = common.visibility });
35 @export(&strtoimax, .{ .name = "__strtoimax_internal", .linkage = common.linkage, .visibility = common.visibility });
36 @export(&strtoumax, .{ .name = "__strtoumax_internal", .linkage = common.linkage, .visibility = common.visibility });
37
19 @export(&qsort_r, .{ .name = "qsort_r", .linkage = common.linkage, .visibility = common.visibility });38 @export(&qsort_r, .{ .name = "qsort_r", .linkage = common.linkage, .visibility = common.visibility });
20 @export(&qsort, .{ .name = "qsort", .linkage = common.linkage, .visibility = common.visibility });39 @export(&qsort, .{ .name = "qsort", .linkage = common.linkage, .visibility = common.visibility });
2140
...@@ -56,6 +75,160 @@ fn lldiv(a: c_longlong, b: c_longlong) callconv(.c) lldiv_t {...@@ -56,6 +75,160 @@ fn lldiv(a: c_longlong, b: c_longlong) callconv(.c) lldiv_t {
56 };75 };
57}76}
5877
78fn atoi(str: [*:0]const c_char) callconv(.c) c_int {
79 return asciiToInteger(c_int, @ptrCast(str));
80}
81
82fn atol(str: [*:0]const c_char) callconv(.c) c_long {
83 return asciiToInteger(c_long, @ptrCast(str));
84}
85
86fn atoll(str: [*:0]const c_char) callconv(.c) c_longlong {
87 return asciiToInteger(c_longlong, @ptrCast(str));
88}
89
90fn asciiToInteger(comptime T: type, buf: [*:0]const u8) T {
91 comptime assert(std.math.isPowerOfTwo(@bitSizeOf(T)));
92
93 var current = buf;
94 while (std.ascii.isWhitespace(current[0])) : (current += 1) {}
95
96 // The behaviour *is* undefined if the result cannot be represented
97 // but as they are usually called with untrusted input we can just handle overflow gracefully.
98 if (current[0] == '-') return parseDigitsWithSignGenericCharacter(T, u8, current + 1, null, 10, .neg) catch std.math.minInt(T);
99 if (current[0] == '+') current += 1;
100 return parseDigitsWithSignGenericCharacter(T, u8, current, null, 10, .pos) catch std.math.maxInt(T);
101}
102
103fn strtol(noalias str: [*:0]const c_char, noalias str_end: ?*[*:0]const c_char, base: c_int) callconv(.c) c_long {
104 return stringToInteger(c_long, @ptrCast(str), if (str_end) |end| @ptrCast(end) else null, base);
105}
106
107fn strtoll(noalias str: [*:0]const c_char, noalias str_end: ?*[*:0]const c_char, base: c_int) callconv(.c) c_longlong {
108 return stringToInteger(c_longlong, @ptrCast(str), if (str_end) |end| @ptrCast(end) else null, base);
109}
110
111fn strtoul(noalias str: [*:0]const c_char, noalias str_end: ?*[*:0]const c_char, base: c_int) callconv(.c) c_ulong {
112 return stringToInteger(c_ulong, @ptrCast(str), if (str_end) |end| @ptrCast(end) else null, base);
113}
114
115fn strtoull(noalias str: [*:0]const c_char, noalias str_end: ?*[*:0]const c_char, base: c_int) callconv(.c) c_ulonglong {
116 return stringToInteger(c_ulonglong, @ptrCast(str), if (str_end) |end| @ptrCast(end) else null, base);
117}
118
119// XXX: These belong in inttypes.zig but we'd have to make stringToInteger pub or move it somewhere else.
120fn strtoimax(noalias str: [*:0]const c_char, noalias str_end: ?*[*:0]const c_char, base: c_int) callconv(.c) std.c.intmax_t {
121 return stringToInteger(std.c.intmax_t, @ptrCast(str), if (str_end) |end| @ptrCast(end) else null, base);
122}
123
124fn strtoumax(noalias str: [*:0]const c_char, noalias str_end: ?*[*:0]const c_char, base: c_int) callconv(.c) std.c.uintmax_t {
125 return stringToInteger(std.c.uintmax_t, @ptrCast(str), if (str_end) |end| @ptrCast(end) else null, base);
126}
127
128fn stringToInteger(comptime T: type, noalias buf: [*:0]const u8, noalias maybe_end: ?*[*:0]const u8, base: c_int) T {
129 comptime assert(std.math.isPowerOfTwo(@bitSizeOf(T)));
130
131 if (base == 1 or base > 36) {
132 if (maybe_end) |end| {
133 end.* = buf;
134 }
135
136 std.c._errno().* = @intFromEnum(std.c.E.INVAL);
137 return 0;
138 }
139
140 var current = buf;
141 while (std.ascii.isWhitespace(current[0])) : (current += 1) {}
142
143 const negative: bool = switch (current[0]) {
144 '-' => blk: {
145 current += 1;
146 break :blk true;
147 },
148 '+' => blk: {
149 current += 1;
150 break :blk false;
151 },
152 else => false,
153 };
154
155 // The prefix is allowed iff base == 0 or base == base of the prefix
156 const real_base: u6 = if (current[0] == '0') blk: {
157 current += 1;
158
159 if ((base == 0 or base == 16) and std.ascii.toLower(current[0]) == 'x' and std.ascii.isHex(current[1])) {
160 current += 1;
161 break :blk 16;
162 }
163
164 if ((base == 0 or base == 8) and std.ascii.isDigit(current[0])) {
165 break :blk 8;
166 }
167
168 break :blk switch (base) {
169 0 => 10,
170 else => @intCast(base),
171 };
172 } else switch (base) {
173 0 => 10,
174 else => @intCast(base),
175 };
176
177 if (@typeInfo(T).int.signedness == .unsigned) {
178 const result = parseDigitsWithSignGenericCharacter(T, u8, current, maybe_end, real_base, .pos) catch {
179 std.c._errno().* = @intFromEnum(std.c.E.RANGE);
180 return std.math.maxInt(T);
181 };
182
183 return if (negative) -%result else result;
184 }
185
186 if (negative) return parseDigitsWithSignGenericCharacter(T, u8, current, maybe_end, real_base, .neg) catch blk: {
187 std.c._errno().* = @intFromEnum(std.c.E.RANGE);
188 break :blk std.math.minInt(T);
189 };
190
191 return parseDigitsWithSignGenericCharacter(T, u8, current, maybe_end, real_base, .pos) catch blk: {
192 std.c._errno().* = @intFromEnum(std.c.E.RANGE);
193 break :blk std.math.maxInt(T);
194 };
195}
196
197fn parseDigitsWithSignGenericCharacter(
198 comptime T: type,
199 comptime Char: type,
200 noalias buf: [*:0]const Char,
201 noalias maybe_end: ?*[*:0]const Char,
202 base: u6,
203 comptime sign: enum { pos, neg },
204) error{Overflow}!T {
205 assert(base >= 2 and base <= 36);
206
207 var current = buf;
208 defer if (maybe_end) |end| {
209 end.* = current;
210 };
211
212 const add = switch (sign) {
213 .pos => std.math.add,
214 .neg => std.math.sub,
215 };
216
217 var value: T = 0;
218 while (true) {
219 const c: u8 = std.math.cast(u8, current[0]) orelse break;
220 if (!std.ascii.isAlphanumeric(c)) break;
221
222 const digit: u6 = @intCast(std.fmt.charToDigit(c, base) catch break);
223 defer current += 1;
224
225 value = try std.math.mul(T, value, base);
226 value = try add(T, value, digit);
227 }
228
229 return value;
230}
231
59// NOTE: Despite its name, `qsort` doesn't have to use quicksort or make any complexity or stability guarantee.232// NOTE: Despite its name, `qsort` doesn't have to use quicksort or make any complexity or stability guarantee.
60fn qsort_r(base: *anyopaque, n: usize, size: usize, compare: *const fn (a: *const anyopaque, b: *const anyopaque, arg: ?*anyopaque) callconv(.c) c_int, arg: ?*anyopaque) callconv(.c) void {233fn qsort_r(base: *anyopaque, n: usize, size: usize, compare: *const fn (a: *const anyopaque, b: *const anyopaque, arg: ?*anyopaque) callconv(.c) c_int, arg: ?*anyopaque) callconv(.c) void {
61 const Context = struct {234 const Context = struct {
...@@ -147,6 +320,53 @@ test lldiv {...@@ -147,6 +320,53 @@ test lldiv {
147 try std.testing.expectEqual(expected, lldiv(5, 3));320 try std.testing.expectEqual(expected, lldiv(5, 3));
148}321}
149322
323test atoi {
324 try std.testing.expectEqual(0, atoi(@ptrCast("stop42true")));
325 try std.testing.expectEqual(42, atoi(@ptrCast("42true")));
326 try std.testing.expectEqual(-1, atoi(@ptrCast("-01")));
327 try std.testing.expectEqual(1, atoi(@ptrCast("+001")));
328 try std.testing.expectEqual(100, atoi(@ptrCast(" 100")));
329 try std.testing.expectEqual(500, atoi(@ptrCast("000000000000500")));
330 try std.testing.expectEqual(1111, atoi(@ptrCast("0000000000001111_0000")));
331 try std.testing.expectEqual(0, atoi(@ptrCast("0xAA")));
332 try std.testing.expectEqual(700, atoi(@ptrCast("700B")));
333 try std.testing.expectEqual(32453, atoi(@ptrCast("+32453more")));
334 try std.testing.expectEqual(std.math.maxInt(c_int), atoi(@ptrCast(std.fmt.comptimePrint("{d}", .{std.math.maxInt(c_int)}))));
335 try std.testing.expectEqual(std.math.minInt(c_int), atoi(@ptrCast(std.fmt.comptimePrint("{d}", .{std.math.minInt(c_int)}))));
336}
337
338test atol {
339 try std.testing.expectEqual(0, atol(@ptrCast("stop42true")));
340 try std.testing.expectEqual(42, atol(@ptrCast("42true")));
341 try std.testing.expectEqual(-1, atol(@ptrCast("-01")));
342 try std.testing.expectEqual(1, atol(@ptrCast("+001")));
343 try std.testing.expectEqual(100, atol(@ptrCast(" 100")));
344 try std.testing.expectEqual(500, atol(@ptrCast("000000000000500")));
345 try std.testing.expectEqual(1111, atol(@ptrCast("0000000000001111_0000")));
346 try std.testing.expectEqual(0, atol(@ptrCast("0xAA")));
347 try std.testing.expectEqual(700, atol(@ptrCast("700B")));
348 try std.testing.expectEqual(32453, atol(@ptrCast("+32453more")));
349 try std.testing.expectEqual(std.math.maxInt(c_long), atol(@ptrCast(std.fmt.comptimePrint("{d}", .{std.math.maxInt(c_long)}))));
350 try std.testing.expectEqual(std.math.minInt(c_long), atol(@ptrCast(std.fmt.comptimePrint("{d}", .{std.math.minInt(c_long)}))));
351}
352
353test atoll {
354 try std.testing.expectEqual(0, atoll(@ptrCast("stop42true")));
355 try std.testing.expectEqual(42, atoll(@ptrCast("42true")));
356 try std.testing.expectEqual(-1, atoll(@ptrCast("-01")));
357 try std.testing.expectEqual(1, atoll(@ptrCast("+001")));
358 try std.testing.expectEqual(100, atoll(@ptrCast(" 100")));
359 try std.testing.expectEqual(500, atoll(@ptrCast("000000000000500")));
360 try std.testing.expectEqual(1111, atoll(@ptrCast("0000000000001111_0000")));
361 try std.testing.expectEqual(0, atoll(@ptrCast("0xAA")));
362 try std.testing.expectEqual(700, atoll(@ptrCast("700B")));
363 try std.testing.expectEqual(32453, atoll(@ptrCast(" +32453more")));
364 try std.testing.expectEqual(std.math.maxInt(c_longlong), atoll(@ptrCast(std.fmt.comptimePrint("{d}", .{std.math.maxInt(c_longlong)}))));
365 try std.testing.expectEqual(std.math.minInt(c_longlong), atoll(@ptrCast(std.fmt.comptimePrint("{d}", .{std.math.minInt(c_longlong)}))));
366}
367
368// FIXME: We cannot test strtol, strtoll, strtoul, etc.. here as it must modify errno and libc is not linked in tests
369
150test bsearch {370test bsearch {
151 const Comparison = struct {371 const Comparison = struct {
152 pub fn compare(a: *const anyopaque, b: *const anyopaque) callconv(.c) c_int {372 pub fn compare(a: *const anyopaque, b: *const anyopaque) callconv(.c) c_int {
lib/libc/musl/src/stdlib/atoi.c deleted-16
...@@ -1,16 +0,0 @@
1#include <stdlib.h>
2#include <ctype.h>
3
4int atoi(const char *s)
5{
6 int n=0, neg=0;
7 while (isspace(*s)) s++;
8 switch (*s) {
9 case '-': neg=1;
10 case '+': s++;
11 }
12 /* Compute n as a negative number to avoid overflow on INT_MIN */
13 while (isdigit(*s))
14 n = 10*n - (*s++ - '0');
15 return neg ? n : -n;
16}
lib/libc/musl/src/stdlib/atol.c deleted-17
...@@ -1,17 +0,0 @@
1#include <stdlib.h>
2#include <ctype.h>
3
4long atol(const char *s)
5{
6 long n=0;
7 int neg=0;
8 while (isspace(*s)) s++;
9 switch (*s) {
10 case '-': neg=1;
11 case '+': s++;
12 }
13 /* Compute n as a negative number to avoid overflow on LONG_MIN */
14 while (isdigit(*s))
15 n = 10*n - (*s++ - '0');
16 return neg ? n : -n;
17}
lib/libc/musl/src/stdlib/atoll.c deleted-17
...@@ -1,17 +0,0 @@
1#include <stdlib.h>
2#include <ctype.h>
3
4long long atoll(const char *s)
5{
6 long long n=0;
7 int neg=0;
8 while (isspace(*s)) s++;
9 switch (*s) {
10 case '-': neg=1;
11 case '+': s++;
12 }
13 /* Compute n as a negative number to avoid overflow on LLONG_MIN */
14 while (isdigit(*s))
15 n = 10*n - (*s++ - '0');
16 return neg ? n : -n;
17}
lib/libc/musl/src/stdlib/strtol.c deleted-56
...@@ -1,56 +0,0 @@
1#include "stdio_impl.h"
2#include "intscan.h"
3#include "shgetc.h"
4#include <inttypes.h>
5#include <limits.h>
6#include <ctype.h>
7
8static unsigned long long strtox(const char *s, char **p, int base, unsigned long long lim)
9{
10 FILE f;
11 sh_fromstring(&f, s);
12 shlim(&f, 0);
13 unsigned long long y = __intscan(&f, base, 1, lim);
14 if (p) {
15 size_t cnt = shcnt(&f);
16 *p = (char *)s + cnt;
17 }
18 return y;
19}
20
21unsigned long long strtoull(const char *restrict s, char **restrict p, int base)
22{
23 return strtox(s, p, base, ULLONG_MAX);
24}
25
26long long strtoll(const char *restrict s, char **restrict p, int base)
27{
28 return strtox(s, p, base, LLONG_MIN);
29}
30
31unsigned long strtoul(const char *restrict s, char **restrict p, int base)
32{
33 return strtox(s, p, base, ULONG_MAX);
34}
35
36long strtol(const char *restrict s, char **restrict p, int base)
37{
38 return strtox(s, p, base, 0UL+LONG_MIN);
39}
40
41intmax_t strtoimax(const char *restrict s, char **restrict p, int base)
42{
43 return strtoll(s, p, base);
44}
45
46uintmax_t strtoumax(const char *restrict s, char **restrict p, int base)
47{
48 return strtoull(s, p, base);
49}
50
51weak_alias(strtol, __strtol_internal);
52weak_alias(strtoul, __strtoul_internal);
53weak_alias(strtoll, __strtoll_internal);
54weak_alias(strtoull, __strtoull_internal);
55weak_alias(strtoimax, __strtoimax_internal);
56weak_alias(strtoumax, __strtoumax_internal);
src/libs/musl.zig-4
...@@ -1620,14 +1620,10 @@ const src_files = [_][]const u8{...@@ -1620,14 +1620,10 @@ const src_files = [_][]const u8{
1620 "musl/src/stdio/wprintf.c",1620 "musl/src/stdio/wprintf.c",
1621 "musl/src/stdio/wscanf.c",1621 "musl/src/stdio/wscanf.c",
1622 "musl/src/stdlib/atof.c",1622 "musl/src/stdlib/atof.c",
1623 "musl/src/stdlib/atoi.c",
1624 "musl/src/stdlib/atol.c",
1625 "musl/src/stdlib/atoll.c",
1626 "musl/src/stdlib/ecvt.c",1623 "musl/src/stdlib/ecvt.c",
1627 "musl/src/stdlib/fcvt.c",1624 "musl/src/stdlib/fcvt.c",
1628 "musl/src/stdlib/gcvt.c",1625 "musl/src/stdlib/gcvt.c",
1629 "musl/src/stdlib/strtod.c",1626 "musl/src/stdlib/strtod.c",
1630 "musl/src/stdlib/strtol.c",
1631 "musl/src/stdlib/wcstod.c",1627 "musl/src/stdlib/wcstod.c",
1632 "musl/src/stdlib/wcstol.c",1628 "musl/src/stdlib/wcstol.c",
1633 "musl/src/string/bcopy.c",1629 "musl/src/string/bcopy.c",
src/libs/wasi_libc.zig-4
...@@ -975,13 +975,9 @@ const libc_top_half_src_files = [_][]const u8{...@@ -975,13 +975,9 @@ const libc_top_half_src_files = [_][]const u8{
975 "musl/src/stdio/wprintf.c",975 "musl/src/stdio/wprintf.c",
976 "musl/src/stdio/wscanf.c",976 "musl/src/stdio/wscanf.c",
977 "musl/src/stdlib/atof.c",977 "musl/src/stdlib/atof.c",
978 "musl/src/stdlib/atoi.c",
979 "musl/src/stdlib/atol.c",
980 "musl/src/stdlib/atoll.c",
981 "musl/src/stdlib/ecvt.c",978 "musl/src/stdlib/ecvt.c",
982 "musl/src/stdlib/fcvt.c",979 "musl/src/stdlib/fcvt.c",
983 "musl/src/stdlib/gcvt.c",980 "musl/src/stdlib/gcvt.c",
984 "musl/src/stdlib/strtol.c",
985 "musl/src/string/bcopy.c",981 "musl/src/string/bcopy.c",
986 "musl/src/string/explicit_bzero.c",982 "musl/src/string/explicit_bzero.c",
987 "musl/src/string/index.c",983 "musl/src/string/index.c",