From 46fcf22630849f68f92aec64d38f44b0c4b3a943 Mon Sep 17 00:00:00 2001 From: Brandon Black Date: Wed, 14 Jan 2026 18:14:43 -0600 Subject: [PATCH 001/499] Io.Select: docs nits --- lib/std/Io.zig | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/std/Io.zig b/lib/std/Io.zig index b4d6efc715f163e0b59300d355284423d8b33382..18aa1e127abb1d679439f4ed8e28be01f2d315d7 100644 --- a/lib/std/Io.zig +++ b/lib/std/Io.zig @@ -1250,7 +1250,7 @@ pub fn Select(comptime U: type) type { /// already been called and completed, or it has successfully been /// assigned a unit of concurrency. /// - /// After this is called, `wait` or `cancel` must be called before the + /// After this is called, `await` or `cancel` must be called before the /// select is deinitialized. /// /// Threadsafe. @@ -1293,12 +1293,12 @@ pub fn Select(comptime U: type) type { }; } - /// Equivalent to `wait` but requests cancelation on all remaining + /// Equivalent to `await` but requests cancelation on all remaining /// tasks owned by the select. /// /// For a description of cancelation and cancelation points, see `Future.cancel`. /// - /// It is illegal to call `wait` after this. + /// It is illegal to call `await` after this. /// /// Idempotent. Not threadsafe. pub fn cancel(s: *S) void { -- 2.54.0 From 376320a5e9bfc32162c81b592784e6f54aeae62b Mon Sep 17 00:00:00 2001 From: Brandon Black Date: Wed, 14 Jan 2026 18:15:18 -0600 Subject: [PATCH 002/499] Io.Select: do not swallow error.Canceled of task --- lib/std/Io.zig | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/std/Io.zig b/lib/std/Io.zig index 18aa1e127abb1d679439f4ed8e28be01f2d315d7..38ed8c865a0eacee50db950cc707d9e2a32b3bc3 100644 --- a/lib/std/Io.zig +++ b/lib/std/Io.zig @@ -1269,10 +1269,13 @@ pub fn Select(comptime U: type) type { args: @TypeOf(args), fn start(type_erased_context: *const anyopaque) Cancelable!void { const context: *const @This() = @ptrCast(@alignCast(type_erased_context)); - const elem = @unionInit(U, @tagName(field), @call(.auto, function, context.args)); + const raw_result = @call(.auto, function, context.args); + const elem = @unionInit(U, @tagName(field), raw_result); context.select.queue.putOneUncancelable(context.select.io, elem) catch |err| switch (err) { error.Closed => unreachable, }; + if (@typeInfo(@TypeOf(raw_result)) == .error_union) + raw_result catch |err| if (err == error.Canceled) return error.Canceled; } }; const context: Context = .{ .select = s, .args = args }; -- 2.54.0 From 6b733537abec526a878c3fd2f62d7ec2386ded56 Mon Sep 17 00:00:00 2001 From: Brandon Black Date: Wed, 14 Jan 2026 18:18:29 -0600 Subject: [PATCH 003/499] Io.Select: add fn concurrent --- lib/std/Io.zig | 40 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 40 insertions(+) diff --git a/lib/std/Io.zig b/lib/std/Io.zig index 38ed8c865a0eacee50db950cc707d9e2a32b3bc3..f757e747ca0f6b865b91fcc2a3f7dbd1b988cd59 100644 --- a/lib/std/Io.zig +++ b/lib/std/Io.zig @@ -1283,6 +1283,46 @@ pub fn Select(comptime U: type) type { s.io.vtable.groupAsync(s.io.userdata, &s.group, @ptrCast(&context), .of(Context), Context.start); } + /// Calls `function` with `args` concurrently. The resource spawned is + /// owned by the select. + /// + /// `function` must have return type matching the `field` field of `Union`. + /// + /// After this function returns successfully, it is guaranteed that + /// `function` has been assigned a unit of concurrency, and `await` or + /// `cancel` must be called before the select is deinitialized. + /// + /// + /// Threadsafe. + /// + /// Related: + /// * `Io.concurrent` + /// * `Group.concurrent` + pub fn concurrent( + s: *S, + comptime field: Field, + function: anytype, + args: std.meta.ArgsTuple(@TypeOf(function)), + ) ConcurrentError!void { + const Context = struct { + select: *S, + args: @TypeOf(args), + fn start(type_erased_context: *const anyopaque) Cancelable!void { + const context: *const @This() = @ptrCast(@alignCast(type_erased_context)); + const raw_result = @call(.auto, function, context.args); + const elem = @unionInit(U, @tagName(field), raw_result); + context.select.queue.putOneUncancelable(context.select.io, elem) catch |err| switch (err) { + error.Closed => unreachable, + }; + if (@typeInfo(@TypeOf(raw_result)) == .error_union) + raw_result catch |err| if (err == error.Canceled) return error.Canceled; + } + }; + const context: Context = .{ .select = s, .args = args }; + try s.io.vtable.groupConcurrent(s.io.userdata, &s.group, @ptrCast(&context), .of(Context), Context.start); + _ = @atomicRmw(usize, &s.outstanding, .Add, 1, .monotonic); + } + /// Blocks until another task of the select finishes. /// /// Asserts there is at least one more `outstanding` task. -- 2.54.0 From f67d21f736fdd058cc8607523a1a370ac1b2f58f Mon Sep 17 00:00:00 2001 From: GasInfinity Date: Tue, 20 Jan 2026 23:14:23 +0100 Subject: [PATCH 004/499] chore(libc/musl): remove unused `ffs` impls * forgot to remove them in the previous PR --- lib/libc/musl/src/misc/ffs.c | 7 ------- lib/libc/musl/src/misc/ffsl.c | 7 ------- lib/libc/musl/src/misc/ffsll.c | 7 ------- 3 files changed, 21 deletions(-) delete mode 100644 lib/libc/musl/src/misc/ffs.c delete mode 100644 lib/libc/musl/src/misc/ffsl.c delete mode 100644 lib/libc/musl/src/misc/ffsll.c diff --git a/lib/libc/musl/src/misc/ffs.c b/lib/libc/musl/src/misc/ffs.c deleted file mode 100644 index 673ce5a9758284c7eca182b32de3902c9b353b3f..0000000000000000000000000000000000000000 --- a/lib/libc/musl/src/misc/ffs.c +++ /dev/null @@ -1,7 +0,0 @@ -#include -#include "atomic.h" - -int ffs(int i) -{ - return i ? a_ctz_l(i)+1 : 0; -} diff --git a/lib/libc/musl/src/misc/ffsl.c b/lib/libc/musl/src/misc/ffsl.c deleted file mode 100644 index 0105c66af9d18946ce9b3f8616c8baa6c7a55cde..0000000000000000000000000000000000000000 --- a/lib/libc/musl/src/misc/ffsl.c +++ /dev/null @@ -1,7 +0,0 @@ -#include -#include "atomic.h" - -int ffsl(long i) -{ - return i ? a_ctz_l(i)+1 : 0; -} diff --git a/lib/libc/musl/src/misc/ffsll.c b/lib/libc/musl/src/misc/ffsll.c deleted file mode 100644 index 0c5ced826657993210a021f49d76560a03a416d6..0000000000000000000000000000000000000000 --- a/lib/libc/musl/src/misc/ffsll.c +++ /dev/null @@ -1,7 +0,0 @@ -#include -#include "atomic.h" - -int ffsll(long long i) -{ - return i ? a_ctz_64(i)+1 : 0; -} -- 2.54.0 From 9d3e9054a7a154730261d5fae96631cc04bdef93 Mon Sep 17 00:00:00 2001 From: GasInfinity Date: Tue, 20 Jan 2026 23:17:04 +0100 Subject: [PATCH 005/499] feat(std.c): add `wasi` definition for `utsname` --- lib/std/c.zig | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/std/c.zig b/lib/std/c.zig index 8e9f5e8c44a064965fc8b614e44bd1187af2ae0e..1fa47a3121e26fcfcad7cfb2d34666b6c4d6a4a0 100644 --- a/lib/std/c.zig +++ b/lib/std/c.zig @@ -7069,6 +7069,14 @@ pub const user_desc = switch (native_os) { pub const utsname = switch (native_os) { .linux => linux.utsname, .emscripten => emscripten.utsname, + .wasi => extern struct { + sysname: [64:0]u8, + nodename: [64:0]u8, + release: [64:0]u8, + version: [64:0]u8, + machine: [64:0]u8, + domainname: [64:0]u8, + }, .illumos => extern struct { sysname: [256:0]u8, nodename: [256:0]u8, -- 2.54.0 From 4f652fb4e36c7f2791bb4c7fb1909a897a662c3b Mon Sep 17 00:00:00 2001 From: GasInfinity Date: Tue, 20 Jan 2026 23:18:34 +0100 Subject: [PATCH 006/499] feat(libzigc): use common implementations for `sys/utsname.h` * and remove their musl/wasi implementation --- lib/c.zig | 2 + lib/c/sys.zig | 3 ++ lib/c/sys/utsname.zig | 40 +++++++++++++++++++ lib/libc/musl/src/misc/uname.c | 7 ---- .../wasi/libc-top-half/musl/src/misc/uname.c | 32 --------------- src/libs/musl.zig | 1 - src/libs/wasi_libc.zig | 1 - 7 files changed, 45 insertions(+), 41 deletions(-) create mode 100644 lib/c/sys.zig create mode 100644 lib/c/sys/utsname.zig delete mode 100644 lib/libc/musl/src/misc/uname.c delete mode 100644 lib/libc/wasi/libc-top-half/musl/src/misc/uname.c diff --git a/lib/c.zig b/lib/c.zig index dce7ae560831c04bfd3dd9580d6c71d237166694..31176d04c3ead690d17302fcf907afc639ebeced 100644 --- a/lib/c.zig +++ b/lib/c.zig @@ -25,6 +25,8 @@ comptime { _ = @import("c/strings.zig"); _ = @import("c/wchar.zig"); + _ = @import("c/sys.zig"); + if (builtin.target.isMuslLibC() or builtin.target.isWasiLibC()) { // Files specific to musl and wasi-libc. } diff --git a/lib/c/sys.zig b/lib/c/sys.zig new file mode 100644 index 0000000000000000000000000000000000000000..d19baf1b0e963777cc9c2f96d6ed0ffb384c8943 --- /dev/null +++ b/lib/c/sys.zig @@ -0,0 +1,3 @@ +comptime { + _ = @import("sys/utsname.zig"); +} diff --git a/lib/c/sys/utsname.zig b/lib/c/sys/utsname.zig new file mode 100644 index 0000000000000000000000000000000000000000..4aab4124efd2cd544b83f49c687008289127ee21 --- /dev/null +++ b/lib/c/sys/utsname.zig @@ -0,0 +1,40 @@ +const std = @import("std"); +const common = @import("../common.zig"); +const builtin = @import("builtin"); + +comptime { + if (builtin.target.isMuslLibC()) { + @export(&unameLinux, .{ .name = "uname", .linkage = common.linkage, .visibility = common.visibility }); + } + + if (builtin.target.isWasiLibC()) { + @export(&unameWasi, .{ .name = "uname", .linkage = common.linkage, .visibility = common.visibility }); + } +} + +fn unameLinux(uts: *std.os.linux.utsname) callconv(.c) c_int { + const linux = std.os.linux; + + return switch (linux.errno(linux.uname(uts))) { + .SUCCESS => 0, + else => |err| blk: { + std.c._errno().* = @intFromEnum(err); + break :blk -1; + }, + }; +} + +fn unameWasi(uts: *std.c.utsname) callconv(.c) c_int { + // note the @bitCast's for NUL termination! + uts.sysname[0..5].* = @bitCast("wasi".*); + uts.nodename[0..7].* = @bitCast("(none)".*); + uts.release[0..6].* = @bitCast("0.0.0".*); + uts.version[0..6].* = @bitCast("0.0.0".*); + uts.machine[0..7].* = @bitCast(switch (builtin.target.cpu.arch) { + .wasm32 => "wasm32", + .wasm64 => "wasm64", + else => comptime unreachable, + }.*); + uts.domainname[0..7].* = @bitCast("(none)".*); + return 0; +} diff --git a/lib/libc/musl/src/misc/uname.c b/lib/libc/musl/src/misc/uname.c deleted file mode 100644 index 55ea3420232c9999bd3bbcf5f31c5bf74946ac9e..0000000000000000000000000000000000000000 --- a/lib/libc/musl/src/misc/uname.c +++ /dev/null @@ -1,7 +0,0 @@ -#include -#include "syscall.h" - -int uname(struct utsname *uts) -{ - return syscall(SYS_uname, uts); -} diff --git a/lib/libc/wasi/libc-top-half/musl/src/misc/uname.c b/lib/libc/wasi/libc-top-half/musl/src/misc/uname.c deleted file mode 100644 index 36e816aa27117a8d0fc3796094fda5569802df6a..0000000000000000000000000000000000000000 --- a/lib/libc/wasi/libc-top-half/musl/src/misc/uname.c +++ /dev/null @@ -1,32 +0,0 @@ -#include -#ifdef __wasilibc_unmodified_upstream // Implement uname with placeholders -#include "syscall.h" -#else -#include -#endif - -int uname(struct utsname *uts) -{ -#ifdef __wasilibc_unmodified_upstream // Implement uname with placeholders - return syscall(SYS_uname, uts); -#else - // Just fill in the fields with placeholder values. - strcpy(uts->sysname, "wasi"); - strcpy(uts->nodename, "(none)"); - strcpy(uts->release, "0.0.0"); - strcpy(uts->version, "0.0.0"); -#if defined(__wasm32__) - strcpy(uts->machine, "wasm32"); -#elif defined(__wasm64__) - strcpy(uts->machine, "wasm64"); -#else - strcpy(uts->machine, "unknown"); -#endif -#ifdef _GNU_SOURCE - strcpy(uts->domainname, "(none)"); -#else - strcpy(uts->__domainname, "(none)"); -#endif - return 0; -#endif -} diff --git a/src/libs/musl.zig b/src/libs/musl.zig index 25d31accf77df2315ee644bfb94c5b550ca33701..295e5169308a3200bbeec50a709b0035b68d72ca 100644 --- a/src/libs/musl.zig +++ b/src/libs/musl.zig @@ -1156,7 +1156,6 @@ const src_files = [_][]const u8{ "musl/src/misc/setrlimit.c", "musl/src/misc/syscall.c", "musl/src/misc/syslog.c", - "musl/src/misc/uname.c", "musl/src/misc/wordexp.c", "musl/src/mman/madvise.c", "musl/src/mman/mincore.c", diff --git a/src/libs/wasi_libc.zig b/src/libs/wasi_libc.zig index db222c115f25fcefa3efa7132bbb06e00ba00225..61d0f2cd0cb2d432d130aecd4b5f2b97fef1ad81 100644 --- a/src/libs/wasi_libc.zig +++ b/src/libs/wasi_libc.zig @@ -1054,7 +1054,6 @@ const libc_top_half_src_files = [_][]const u8{ "wasi/libc-top-half/musl/src/math/sinhf.c", "wasi/libc-top-half/musl/src/misc/fmtmsg.c", "wasi/libc-top-half/musl/src/misc/nftw.c", - "wasi/libc-top-half/musl/src/misc/uname.c", "wasi/libc-top-half/musl/src/prng/random.c", "wasi/libc-top-half/musl/src/regex/glob.c", "wasi/libc-top-half/musl/src/regex/regcomp.c", -- 2.54.0 From 1ab6bf59a6722f816edce15574c2dfcaefeebd31 Mon Sep 17 00:00:00 2001 From: GasInfinity Date: Wed, 21 Jan 2026 12:02:08 +0100 Subject: [PATCH 007/499] feat(libzigc): add common linux errno syscall helper --- lib/c/common.zig | 14 ++++++++++++++ lib/c/sys/utsname.zig | 10 +--------- 2 files changed, 15 insertions(+), 9 deletions(-) diff --git a/lib/c/common.zig b/lib/c/common.zig index 4419b65cc61a920068cb5fe44bdccf8979256da7..09dc5a8c0cbf2fe18160a2310ab4d33ffbfa7545 100644 --- a/lib/c/common.zig +++ b/lib/c/common.zig @@ -13,3 +13,17 @@ pub const visibility: std.builtin.SymbolVisibility = if (linkage != .internal) .hidden else .default; + +/// Checks whether the syscall has had an error, storing it in `std.c.errno` and returning -1. +/// Otherwise returns the result. +pub fn linuxErrno(r: usize) isize { + const linux = std.os.linux; + + return switch (linux.errno(r)) { + .SUCCESS => @bitCast(r), + else => |err| blk: { + std.c._errno().* = @intFromEnum(err); + break :blk -1; + }, + }; +} diff --git a/lib/c/sys/utsname.zig b/lib/c/sys/utsname.zig index 4aab4124efd2cd544b83f49c687008289127ee21..06f849ba6df4e8aa642e1361e6f9e5d57fff7367 100644 --- a/lib/c/sys/utsname.zig +++ b/lib/c/sys/utsname.zig @@ -13,15 +13,7 @@ comptime { } fn unameLinux(uts: *std.os.linux.utsname) callconv(.c) c_int { - const linux = std.os.linux; - - return switch (linux.errno(linux.uname(uts))) { - .SUCCESS => 0, - else => |err| blk: { - std.c._errno().* = @intFromEnum(err); - break :blk -1; - }, - }; + return @intCast(common.linuxErrno(std.os.linux.uname(uts))); } fn unameWasi(uts: *std.c.utsname) callconv(.c) c_int { -- 2.54.0 From fc59f0e7f0681096cf99fbd27330ed7c5ba5ffaa Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Thu, 15 Jan 2026 08:18:54 +0100 Subject: [PATCH 008/499] std.Io.test: skip atime check in `setTimestamps` on NetBSD --- lib/std/Io/test.zig | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/std/Io/test.zig b/lib/std/Io/test.zig index 6d06ebacaaefffcec8d1b4f377b38bb983a42f78..0842f5d092ec2a6d2fb5717f5bfe3515cc82d948 100644 --- a/lib/std/Io/test.zig +++ b/lib/std/Io/test.zig @@ -181,7 +181,10 @@ test "setTimestamps" { .modify_timestamp = .{ .new = stat_old.mtime.subDuration(.fromSeconds(5)) }, }); const stat_new = try file.stat(io); - if (stat_old.atime) |old_atime| try expect(stat_new.atime.?.nanoseconds < old_atime.nanoseconds); + // NetBSD with noatime will just not update the timestamp, and noatime is default in at least NetBSD 11+. + if (builtin.os.tag != .netbsd) { + if (stat_old.atime) |old_atime| try expect(stat_new.atime.?.nanoseconds < old_atime.nanoseconds); + } try expect(stat_new.mtime.nanoseconds < stat_old.mtime.nanoseconds); } -- 2.54.0 From 32f977a4b7b17b774ff09157a902d4e22adf7384 Mon Sep 17 00:00:00 2001 From: Michael Dusan Date: Fri, 16 Jan 2026 15:38:01 -0500 Subject: [PATCH 009/499] std.fs.test: fix tests using Dir.realPath * add fn isRealPathSupported * incorporate into tests that depends on Dir.realPath --- lib/std/fs/test.zig | 60 ++++++++++++++++++++---------------------- lib/std/posix/test.zig | 5 ++-- 2 files changed, 32 insertions(+), 33 deletions(-) diff --git a/lib/std/fs/test.zig b/lib/std/fs/test.zig index 2d5dd78b23c753bc49d50b02785330728dfa8d60..a1987765659a54263d263d93e6ea2e2c73220414 100644 --- a/lib/std/fs/test.zig +++ b/lib/std/fs/test.zig @@ -20,6 +20,28 @@ const expectEqualStrings = std.testing.expectEqualStrings; const expectError = std.testing.expectError; const tmpDir = std.testing.tmpDir; +// This is kept in sync with Io.Threaded.realPath . +pub inline fn isRealPathSupported() bool { + return switch (native_os) { + .windows, + .driverkit, + .ios, + .maccatalyst, + .macos, + .tvos, + .visionos, + .watchos, + .linux, + .serenity, + .illumos, + .freebsd, + => true, + .dragonfly => builtin.os.version_range.semver.min.order(.{ .major = 6, .minor = 0, .patch = 0 }) != .lt, + .netbsd => builtin.os.version_range.semver.min.order(.{ .major = 10, .minor = 0, .patch = 0 }) != .lt, + else => false, + }; +} + const PathType = enum { relative, absolute, @@ -28,25 +50,7 @@ const PathType = enum { fn isSupported(self: PathType, target_os: std.Target.Os) bool { return switch (self) { .relative => true, - .absolute => switch (target_os.tag) { - .windows, - .driverkit, - .ios, - .maccatalyst, - .macos, - .tvos, - .visionos, - .watchos, - .linux, - .illumos, - .freebsd, - .serenity, - => true, - - .dragonfly => target_os.version_range.semver.max.order(.{ .major = 6, .minor = 0, .patch = 0 }) != .lt, - .netbsd => target_os.version_range.semver.max.order(.{ .major = 10, .minor = 0, .patch = 0 }) != .lt, - else => false, - }, + .absolute => isRealPathSupported(), .unc => target_os.tag == .windows, }; } @@ -314,8 +318,7 @@ test "openDir" { } test "accessAbsolute" { - if (native_os == .wasi) return error.SkipZigTest; - if (native_os == .openbsd) return error.SkipZigTest; + if (!isRealPathSupported()) return error.SkipZigTest; const io = testing.io; const gpa = testing.allocator; @@ -330,8 +333,7 @@ test "accessAbsolute" { } test "openDirAbsolute" { - if (native_os == .wasi) return error.SkipZigTest; - if (native_os == .openbsd) return error.SkipZigTest; + if (!isRealPathSupported()) return error.SkipZigTest; const io = testing.io; const gpa = testing.allocator; @@ -428,8 +430,7 @@ test "openDir non-cwd parent '..'" { } test "readLinkAbsolute" { - if (native_os == .wasi) return error.SkipZigTest; - if (native_os == .openbsd) return error.SkipZigTest; + if (!isRealPathSupported()) return error.SkipZigTest; const io = testing.io; @@ -645,8 +646,7 @@ fn contains(entries: *const std.array_list.Managed(Dir.Entry), el: Dir.Entry) bo } test "Dir.realPath smoke test" { - if (native_os == .wasi) return error.SkipZigTest; - if (native_os == .openbsd) return error.SkipZigTest; + if (!isRealPathSupported()) return error.SkipZigTest; try testWithAllSupportedPathTypes(struct { fn impl(ctx: *TestContext) !void { @@ -1074,8 +1074,7 @@ test "rename" { } test "renameAbsolute" { - if (native_os == .wasi) return error.SkipZigTest; - if (native_os == .openbsd) return error.SkipZigTest; + if (!isRealPathSupported()) return error.SkipZigTest; const io = testing.io; @@ -2029,8 +2028,7 @@ test "'.' and '..' in Dir functions" { } test "'.' and '..' in absolute functions" { - if (native_os == .wasi) return error.SkipZigTest; - if (native_os == .openbsd) return error.SkipZigTest; + if (!isRealPathSupported()) return error.SkipZigTest; const io = testing.io; diff --git a/lib/std/posix/test.zig b/lib/std/posix/test.zig index 616c6901c0a935521288991c4c67c30e095e5fe7..e720c4a4e6d23aee4573fa082582e5e0c4c066e8 100644 --- a/lib/std/posix/test.zig +++ b/lib/std/posix/test.zig @@ -20,6 +20,8 @@ const expectEqualStrings = std.testing.expectEqualStrings; const expectError = std.testing.expectError; const tmpDir = std.testing.tmpDir; +const fstest = @import("../fs/test.zig"); + test "check WASI CWD" { if (native_os == .wasi) { const cwd: Dir = .cwd(); @@ -444,9 +446,8 @@ test "getppid" { } test "rename smoke test" { - if (native_os == .wasi) return error.SkipZigTest; if (native_os == .windows) return error.SkipZigTest; - if (native_os == .openbsd) return error.SkipZigTest; + if (!fstest.isRealPathSupported()) return error.SkipZigTest; const io = testing.io; const gpa = testing.allocator; -- 2.54.0 From a3ea3bd31dc0b8c6ff98c9bc95ffcd36bd8aba82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Mon, 19 Jan 2026 18:46:00 +0100 Subject: [PATCH 010/499] std: NetBSD doesn't have a reliable F_GETPATH It can fail arbitrarily with ENOENT if the kernel happens to not have the FD in its name cache. That makes it useless for our purposes. closes https://codeberg.org/ziglang/zig/issues/30843 --- lib/std/Io/Threaded.zig | 2 +- lib/std/fs/test.zig | 1 - 2 files changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index daca7bf55ce9819575800e8432f4a40a6e980f19..f190cc7146a2de5a771fc24adcdd2813b92d0b79 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -5209,7 +5209,7 @@ fn fileRealPathPosix(userdata: ?*anyopaque, file: File, out_buffer: []u8) File.R fn realPathPosix(fd: posix.fd_t, out_buffer: []u8) File.RealPathError!usize { switch (native_os) { - .netbsd, .dragonfly, .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => { + .dragonfly, .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => { var sufficient_buffer: [posix.PATH_MAX]u8 = undefined; @memset(&sufficient_buffer, 0); const syscall: Syscall = try .start(); diff --git a/lib/std/fs/test.zig b/lib/std/fs/test.zig index a1987765659a54263d263d93e6ea2e2c73220414..63bd75748b5a3d484ea7eaf65333de5ce6abcfe1 100644 --- a/lib/std/fs/test.zig +++ b/lib/std/fs/test.zig @@ -37,7 +37,6 @@ pub inline fn isRealPathSupported() bool { .freebsd, => true, .dragonfly => builtin.os.version_range.semver.min.order(.{ .major = 6, .minor = 0, .patch = 0 }) != .lt, - .netbsd => builtin.os.version_range.semver.min.order(.{ .major = 10, .minor = 0, .patch = 0 }) != .lt, else => false, }; } -- 2.54.0 From 35a191ec1ca8b39ce6b9460e00a5efaf56851345 Mon Sep 17 00:00:00 2001 From: Matthew Lugg Date: Wed, 21 Jan 2026 13:52:14 +0100 Subject: [PATCH 011/499] std.Io.Threaded: fix futex timeout race handling --- lib/std/Io/Threaded.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index f190cc7146a2de5a771fc24adcdd2813b92d0b79..c42545563c37cdf9bdcd2643ec9dc6c16b3c58a2 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -15791,7 +15791,7 @@ const parking_futex = struct { ); switch (old_status.cancelation) { .parked => {}, // state updated to `.none` - .none => unreachable, // if another `wake` call is unparking this thread, it should have removed it from the list + .none => continue, // race with timeout; they are about to lock `bucket.mutex` and remove themselves from the bucket .canceling => continue, // race with a canceler who hasn't called `removeCanceledWaiter` yet .canceled => unreachable, .blocked => unreachable, -- 2.54.0 From 9f33c339c72d8aff92aefea01331b76a7829eb12 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Mon, 19 Jan 2026 14:37:09 +0100 Subject: [PATCH 012/499] std.fs.test: fix `file operations on directories` on NetBSD As noted earlier in this test, reading directories does not fail on NetBSD. --- lib/std/fs/test.zig | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/std/fs/test.zig b/lib/std/fs/test.zig index 63bd75748b5a3d484ea7eaf65333de5ce6abcfe1..4d7077bfc31ed62cf3597e6cf7df24a6dbc058a6 100644 --- a/lib/std/fs/test.zig +++ b/lib/std/fs/test.zig @@ -845,9 +845,11 @@ test "file operations on directories" { defer handle.close(io); // Reading from the handle should fail - var buf: [1]u8 = undefined; - try expectError(error.IsDir, handle.readStreaming(io, &.{&buf})); - try expectError(error.IsDir, handle.readPositional(io, &.{&buf}, 0)); + if (native_os != .netbsd) { + var buf: [1]u8 = undefined; + try expectError(error.IsDir, handle.readStreaming(io, &.{&buf})); + try expectError(error.IsDir, handle.readPositional(io, &.{&buf}, 0)); + } } try expectError(error.IsDir, ctx.dir.openFile(io, test_dir_name, .{ .allow_directory = false, .mode = .read_only })); -- 2.54.0 From eb3f16db5e20a7de0695e1a42ff32526faaff9f4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Mon, 19 Jan 2026 18:49:32 +0100 Subject: [PATCH 013/499] test: clarify that `self_exe_symlink` fails on NetBSD due to bad F_GETPATH closes https://codeberg.org/ziglang/zig/issues/30841 --- test/standalone/self_exe_symlink/build.zig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/test/standalone/self_exe_symlink/build.zig b/test/standalone/self_exe_symlink/build.zig index 1a0c8f054204d687eeeba736863c68363518e92b..9e031dcdaab91b3624ee8ea5ae6c0916d56e53b7 100644 --- a/test/standalone/self_exe_symlink/build.zig +++ b/test/standalone/self_exe_symlink/build.zig @@ -9,8 +9,8 @@ pub fn build(b: *std.Build) void { const optimize: std.builtin.OptimizeMode = .Debug; const target = b.graph.host; - if (target.result.os.tag == .netbsd) return; // https://codeberg.org/ziglang/zig/issues/30841 - if (target.result.os.tag == .openbsd) return; // realpath not supported + if (target.result.os.tag == .netbsd) return; // F_GETPATH not reliable + if (target.result.os.tag == .openbsd) return; // F_GETPATH not supported const main = b.addExecutable(.{ .name = "main", -- 2.54.0 From b7a4756e1dd4d35da8f4c14dde1e84cca16d4833 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Thu, 15 Jan 2026 11:13:20 +0100 Subject: [PATCH 014/499] langref: disable @cImport tests on NetBSD https://github.com/Vexu/arocc/issues/960 --- doc/langref/cImport_builtin.zig | 1 + doc/langref/verbose_cimport_flag.zig | 1 + 2 files changed, 2 insertions(+) diff --git a/doc/langref/cImport_builtin.zig b/doc/langref/cImport_builtin.zig index daed710f9ddbf826f361141297d670f6e2fca66d..e0cdf7bc2793a6fd0930f7387c28f7fde8e610f4 100644 --- a/doc/langref/cImport_builtin.zig +++ b/doc/langref/cImport_builtin.zig @@ -4,6 +4,7 @@ const c = @cImport({ @cInclude("stdio.h"); }); pub fn main() void { + if (@import("builtin").os.tag == .netbsd) return; // https://github.com/Vexu/arocc/issues/960 _ = c.printf("hello\n"); } diff --git a/doc/langref/verbose_cimport_flag.zig b/doc/langref/verbose_cimport_flag.zig index 82b83073782d0d24ed00ab9b24e6e720cffa6a7f..3adc37b5078e961c92a0a6f976037802263e9d6a 100644 --- a/doc/langref/verbose_cimport_flag.zig +++ b/doc/langref/verbose_cimport_flag.zig @@ -3,6 +3,7 @@ const c = @cImport({ @cInclude("stdio.h"); }); pub fn main() void { + if (@import("builtin").os.tag == .netbsd) return; // https://github.com/Vexu/arocc/issues/960 _ = c; } -- 2.54.0 From 85580951a7755cf418776ed93ed9c5ad3011c3e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Fri, 28 Nov 2025 01:45:23 +0100 Subject: [PATCH 015/499] ci: add x86_64-netbsd scripts --- ci/x86_64-netbsd-debug.sh | 62 +++++++++++++++++++++++++++++++++ ci/x86_64-netbsd-release.sh | 68 +++++++++++++++++++++++++++++++++++++ 2 files changed, 130 insertions(+) create mode 100755 ci/x86_64-netbsd-debug.sh create mode 100755 ci/x86_64-netbsd-release.sh diff --git a/ci/x86_64-netbsd-debug.sh b/ci/x86_64-netbsd-debug.sh new file mode 100755 index 0000000000000000000000000000000000000000..68e9081f3ba040c83e4116b194ba17a46692798e --- /dev/null +++ b/ci/x86_64-netbsd-debug.sh @@ -0,0 +1,62 @@ +#!/bin/sh + +# Requires cmake ninja-build + +set -x +set -e + +TARGET="x86_64-netbsd-none" +MCPU="baseline" +CACHE_BASENAME="zig+llvm+lld+clang-$TARGET-0.16.0-dev.2287+eb3f16db5" +PREFIX="$HOME/deps/$CACHE_BASENAME" +ZIG="$PREFIX/bin/zig" + +# Override the cache directories because they won't actually help other CI runs +# which will be testing alternate versions of zig, and ultimately would just +# fill up space on the hard drive for no reason. +export ZIG_GLOBAL_CACHE_DIR="$PWD/zig-global-cache" +export ZIG_LOCAL_CACHE_DIR="$PWD/zig-local-cache" + +mkdir build-debug +cd build-debug + +export CC="$ZIG cc -target $TARGET -mcpu=$MCPU" +export CXX="$ZIG c++ -target $TARGET -mcpu=$MCPU" + +cmake .. \ + -DCMAKE_INSTALL_PREFIX="stage3-debug" \ + -DCMAKE_PREFIX_PATH="$PREFIX" \ + -DCMAKE_BUILD_TYPE=Debug \ + -DZIG_TARGET_TRIPLE="$TARGET" \ + -DZIG_TARGET_MCPU="$MCPU" \ + -DZIG_STATIC=ON \ + -DZIG_NO_LIB=ON \ + -GNinja \ + -DCMAKE_C_LINKER_DEPFILE_SUPPORTED=FALSE \ + -DCMAKE_CXX_LINKER_DEPFILE_SUPPORTED=FALSE +# https://github.com/ziglang/zig/issues/22213 + +# Now cmake will use zig as the C/C++ compiler. We reset the environment variables +# so that installation and testing do not get affected by them. +unset CC +unset CXX + +ninja install + +stage3-debug/bin/zig build test docs \ + --maxrss ${ZSF_MAX_RSS:-0} \ + -Dstatic-llvm \ + -Dskip-non-native \ + --search-prefix "$PREFIX" \ + --zig-lib-dir "$PWD/../lib" \ + --test-timeout 2m + +stage3-debug/bin/zig build \ + --prefix stage4-debug \ + -Denable-llvm \ + -Dno-lib \ + -Dtarget=$TARGET \ + -Duse-zig-libcxx \ + -Dversion-string="$(stage3-debug/bin/zig version)" + +stage4-debug/bin/zig test ../test/behavior.zig diff --git a/ci/x86_64-netbsd-release.sh b/ci/x86_64-netbsd-release.sh new file mode 100755 index 0000000000000000000000000000000000000000..225a527686ac06cc1da2241f0fb624956f2d5a58 --- /dev/null +++ b/ci/x86_64-netbsd-release.sh @@ -0,0 +1,68 @@ +#!/bin/sh + +# Requires cmake ninja-build + +set -x +set -e + +TARGET="x86_64-netbsd-none" +MCPU="baseline" +CACHE_BASENAME="zig+llvm+lld+clang-$TARGET-0.16.0-dev.2287+eb3f16db5" +PREFIX="$HOME/deps/$CACHE_BASENAME" +ZIG="$PREFIX/bin/zig" + +# Override the cache directories because they won't actually help other CI runs +# which will be testing alternate versions of zig, and ultimately would just +# fill up space on the hard drive for no reason. +export ZIG_GLOBAL_CACHE_DIR="$PWD/zig-global-cache" +export ZIG_LOCAL_CACHE_DIR="$PWD/zig-local-cache" + +mkdir build-release +cd build-release + +export CC="$ZIG cc -target $TARGET -mcpu=$MCPU" +export CXX="$ZIG c++ -target $TARGET -mcpu=$MCPU" + +cmake .. \ + -DCMAKE_INSTALL_PREFIX="stage3-release" \ + -DCMAKE_PREFIX_PATH="$PREFIX" \ + -DCMAKE_BUILD_TYPE=Release \ + -DZIG_TARGET_TRIPLE="$TARGET" \ + -DZIG_TARGET_MCPU="$MCPU" \ + -DZIG_STATIC=ON \ + -DZIG_NO_LIB=ON \ + -GNinja \ + -DCMAKE_C_LINKER_DEPFILE_SUPPORTED=FALSE \ + -DCMAKE_CXX_LINKER_DEPFILE_SUPPORTED=FALSE +# https://github.com/ziglang/zig/issues/22213 + +# Now cmake will use zig as the C/C++ compiler. We reset the environment variables +# so that installation and testing do not get affected by them. +unset CC +unset CXX + +ninja install + +stage3-release/bin/zig build test docs \ + --maxrss ${ZSF_MAX_RSS:-0} \ + -Dstatic-llvm \ + -Dskip-non-native \ + --search-prefix "$PREFIX" \ + --zig-lib-dir "$PWD/../lib" \ + --test-timeout 2m + +# Ensure that stage3 and stage4 are byte-for-byte identical. +stage3-release/bin/zig build \ + --prefix stage4-release \ + -Denable-llvm \ + -Dno-lib \ + -Doptimize=ReleaseFast \ + -Dstrip \ + -Dtarget=$TARGET \ + -Duse-zig-libcxx \ + -Dversion-string="$(stage3-release/bin/zig version)" + +# diff returns an error code if the files differ. +echo "If the following command fails, it means nondeterminism has been" +echo "introduced, making stage3 and stage4 no longer byte-for-byte identical." +diff stage3-release/bin/zig stage4-release/bin/zig -- 2.54.0 From 7337946875382402ae81ab8f08e27bd5ecd45ef4 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Fri, 28 Nov 2025 01:46:21 +0100 Subject: [PATCH 016/499] ci: enable x86_64-netbsd in the workflow --- .forgejo/workflows/ci.yaml | 21 +++++++++++++++++++++ 1 file changed, 21 insertions(+) diff --git a/.forgejo/workflows/ci.yaml b/.forgejo/workflows/ci.yaml index 64085d1da6d03820b3ed8b36dacdd16f88df5f10..7cc50e71c302fe054f49f25d3ee8f262a5d4ea77 100644 --- a/.forgejo/workflows/ci.yaml +++ b/.forgejo/workflows/ci.yaml @@ -197,6 +197,27 @@ jobs: run: sh ci/x86_64-linux-release.sh timeout-minutes: 360 + x86_64-netbsd-debug: + runs-on: [self-hosted, x86_64-netbsd] + steps: + - name: Checkout + uses: https://codeberg.org/ziglang/checkout@19af6bac491e2534a4687a50ee84fa7f13258d28 + with: + fetch-depth: 0 + - name: Build and Test + run: sh ci/x86_64-netbsd-debug.sh + timeout-minutes: 120 + x86_64-netbsd-release: + runs-on: [self-hosted, x86_64-netbsd] + steps: + - name: Checkout + uses: https://codeberg.org/ziglang/checkout@19af6bac491e2534a4687a50ee84fa7f13258d28 + with: + fetch-depth: 0 + - name: Build and Test + run: sh ci/x86_64-netbsd-release.sh + timeout-minutes: 120 + x86_64-openbsd-debug: runs-on: [self-hosted, x86_64-openbsd] steps: -- 2.54.0 From 953ca759c2446adc21648a9017b656ac84171291 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Wed, 21 Jan 2026 15:15:43 +0100 Subject: [PATCH 017/499] ci: disable incremental tests Too much flakiness across the board: https://codeberg.org/ziglang/zig/issues?q=&type=all&sort=relevance&labels=747060&state=open&milestone=0&project=0&assignee=0&poster=0 --- ci/aarch64-linux-debug.sh | 1 + ci/aarch64-linux-release.sh | 1 + ci/aarch64-macos-debug.sh | 1 + ci/aarch64-macos-release.sh | 1 + ci/aarch64-windows.ps1 | 1 + ci/loongarch64-linux-debug.sh | 1 + ci/loongarch64-linux-release.sh | 1 + ci/powerpc64le-linux-debug.sh | 1 + ci/powerpc64le-linux-release.sh | 1 + ci/s390x-linux-debug.sh | 1 + ci/s390x-linux-release.sh | 1 + ci/x86_64-freebsd-debug.sh | 1 + ci/x86_64-freebsd-release.sh | 1 + ci/x86_64-linux-debug-llvm.sh | 1 + ci/x86_64-linux-debug.sh | 1 + ci/x86_64-linux-release.sh | 1 + ci/x86_64-netbsd-debug.sh | 1 + ci/x86_64-netbsd-release.sh | 1 + ci/x86_64-openbsd-debug.sh | 1 + ci/x86_64-openbsd-release.sh | 1 + 20 files changed, 20 insertions(+) diff --git a/ci/aarch64-linux-debug.sh b/ci/aarch64-linux-debug.sh index 7a4a6daa2aef60e1a9194184b1307f85992db3de..37a09b539845be9864c06f336eca482ab02983c9 100755 --- a/ci/aarch64-linux-debug.sh +++ b/ci/aarch64-linux-debug.sh @@ -47,6 +47,7 @@ stage3-debug/bin/zig build test docs \ --maxrss ${ZSF_MAX_RSS:-0} \ -Dstatic-llvm \ -Dskip-non-native \ + -Dskip-test-incremental \ -Dtarget=native-native-musl \ --search-prefix "$PREFIX" \ --zig-lib-dir "$PWD/../lib" \ diff --git a/ci/aarch64-linux-release.sh b/ci/aarch64-linux-release.sh index 39ad9767ab62264b19846ef8bc22e2d6ad23ed85..8cde024ab11450376913d68816fd9d02a3a9c00e 100755 --- a/ci/aarch64-linux-release.sh +++ b/ci/aarch64-linux-release.sh @@ -47,6 +47,7 @@ stage3-release/bin/zig build test docs \ --maxrss ${ZSF_MAX_RSS:-0} \ -Dstatic-llvm \ -Dskip-non-native \ + -Dskip-test-incremental \ -Dtarget=native-native-musl \ --search-prefix "$PREFIX" \ --zig-lib-dir "$PWD/../lib" \ diff --git a/ci/aarch64-macos-debug.sh b/ci/aarch64-macos-debug.sh index 7dc60c1f4ed13f8835538a2609955423ef8d551e..369afc8d9e94a02ed77965bffc9692a5b5e344c4 100755 --- a/ci/aarch64-macos-debug.sh +++ b/ci/aarch64-macos-debug.sh @@ -47,6 +47,7 @@ stage3-debug/bin/zig build test docs \ -Denable-macos-sdk \ -Dstatic-llvm \ -Dskip-non-native \ + -Dskip-test-incremental \ --search-prefix "$PREFIX" \ --test-timeout 2m diff --git a/ci/aarch64-macos-release.sh b/ci/aarch64-macos-release.sh index 00b6571f170a7fece1dcfb53755f9b271f438aae..f7e6ae6fd3f4dba1970b94f6bc8e98305b2585be 100755 --- a/ci/aarch64-macos-release.sh +++ b/ci/aarch64-macos-release.sh @@ -46,6 +46,7 @@ stage3-release/bin/zig build test docs \ -Denable-macos-sdk \ -Dstatic-llvm \ -Dskip-non-native \ + -Dskip-test-incremental \ --search-prefix "$PREFIX" \ --test-timeout 2m diff --git a/ci/aarch64-windows.ps1 b/ci/aarch64-windows.ps1 index 96e07642565019c9a805ecc8c5ac29f461341df1..57d77993519f230c2fc6e14de5a7e6957c51062a 100644 --- a/ci/aarch64-windows.ps1 +++ b/ci/aarch64-windows.ps1 @@ -60,6 +60,7 @@ Write-Output "Main test suite..." --search-prefix "$PREFIX_PATH" ` -Dstatic-llvm ` -Dskip-non-native ` + -Dskip-test-incremental ` -Denable-symlinks-windows ` --test-timeout 30m CheckLastExitCode diff --git a/ci/loongarch64-linux-debug.sh b/ci/loongarch64-linux-debug.sh index 4cba17b0319039daf2d132ece4908cfdf542cffa..2d966f37427b1408febbc92abd516cebca096047 100755 --- a/ci/loongarch64-linux-debug.sh +++ b/ci/loongarch64-linux-debug.sh @@ -48,6 +48,7 @@ stage3-debug/bin/zig build test docs \ --maxrss ${ZSF_MAX_RSS:-0} \ -Dstatic-llvm \ -Dskip-non-native \ + -Dskip-test-incremental \ -Dtarget=native-native-musl \ --search-prefix "$PREFIX" \ --zig-lib-dir "$PWD/../lib" \ diff --git a/ci/loongarch64-linux-release.sh b/ci/loongarch64-linux-release.sh index 5b05284d26668d01020f204f09d3a73921883ce5..558177a8d99ea2c6885cb2ab0d405cecf4cb8ca5 100755 --- a/ci/loongarch64-linux-release.sh +++ b/ci/loongarch64-linux-release.sh @@ -48,6 +48,7 @@ stage3-release/bin/zig build test docs \ --maxrss ${ZSF_MAX_RSS:-0} \ -Dstatic-llvm \ -Dskip-non-native \ + -Dskip-test-incremental \ -Dtarget=native-native-musl \ --search-prefix "$PREFIX" \ --zig-lib-dir "$PWD/../lib" \ diff --git a/ci/powerpc64le-linux-debug.sh b/ci/powerpc64le-linux-debug.sh index 1b9a51e44debff61729207ec1529e1d494b8c84b..2875dcba955eeee80cda7eeadc38b3a0526f01be 100755 --- a/ci/powerpc64le-linux-debug.sh +++ b/ci/powerpc64le-linux-debug.sh @@ -48,6 +48,7 @@ stage3-debug/bin/zig build test docs \ --maxrss ${ZSF_MAX_RSS:-0} \ -Dstatic-llvm \ -Dskip-non-native \ + -Dskip-test-incremental \ -Dtarget=native-native-musl \ -Dcpu=native+longcall \ --search-prefix "$PREFIX" \ diff --git a/ci/powerpc64le-linux-release.sh b/ci/powerpc64le-linux-release.sh index 77e1ca803ae27db49ed7a2b8fed48392f485c4d4..fffcbe2bd2c8bc72f18f21db76b557df1f1b8798 100755 --- a/ci/powerpc64le-linux-release.sh +++ b/ci/powerpc64le-linux-release.sh @@ -48,6 +48,7 @@ stage3-release/bin/zig build test docs \ --maxrss ${ZSF_MAX_RSS:-0} \ -Dstatic-llvm \ -Dskip-non-native \ + -Dskip-test-incremental \ -Dtarget=native-native-musl \ -Dcpu=native+longcall \ --search-prefix "$PREFIX" \ diff --git a/ci/s390x-linux-debug.sh b/ci/s390x-linux-debug.sh index ffe4d0f02b3cada3b36e24fcc3572a1a14d5b389..a76ed6f04df534ee6d124bc0848a0d8ce3b0669f 100755 --- a/ci/s390x-linux-debug.sh +++ b/ci/s390x-linux-debug.sh @@ -48,6 +48,7 @@ stage3-debug/bin/zig build test docs \ --maxrss ${ZSF_MAX_RSS:-0} \ -Dstatic-llvm \ -Dskip-non-native \ + -Dskip-test-incremental \ -Dtarget=native-native-musl \ --search-prefix "$PREFIX" \ --zig-lib-dir "$PWD/../lib" \ diff --git a/ci/s390x-linux-release.sh b/ci/s390x-linux-release.sh index 7fb6cd3641fa73752ca4f6eb1967f1a2de9dd8bf..0a9b82620d5a9176a3542b5fd953054ee4f1feed 100755 --- a/ci/s390x-linux-release.sh +++ b/ci/s390x-linux-release.sh @@ -48,6 +48,7 @@ stage3-release/bin/zig build test docs \ --maxrss ${ZSF_MAX_RSS:-0} \ -Dstatic-llvm \ -Dskip-non-native \ + -Dskip-test-incremental \ -Dtarget=native-native-musl \ --search-prefix "$PREFIX" \ --zig-lib-dir "$PWD/../lib" \ diff --git a/ci/x86_64-freebsd-debug.sh b/ci/x86_64-freebsd-debug.sh index a4d7034325b33dc92b9d8ba97442d4449ad6a4c5..8bf492540eadd7cc68db8d43d06a5c0296165ab3 100755 --- a/ci/x86_64-freebsd-debug.sh +++ b/ci/x86_64-freebsd-debug.sh @@ -53,6 +53,7 @@ stage3-debug/bin/zig build test docs \ -Dskip-openbsd \ -Dskip-windows \ -Dskip-darwin \ + -Dskip-test-incremental \ --search-prefix "$PREFIX" \ --zig-lib-dir "$PWD/../lib" \ --test-timeout 2m diff --git a/ci/x86_64-freebsd-release.sh b/ci/x86_64-freebsd-release.sh index 0ce708c63d7715c4e6ad4e4e3927d4a100b9f65e..44c4e76da2558d4fd399cee1452f4bc76ea8404d 100755 --- a/ci/x86_64-freebsd-release.sh +++ b/ci/x86_64-freebsd-release.sh @@ -53,6 +53,7 @@ stage3-release/bin/zig build test docs \ -Dskip-openbsd \ -Dskip-windows \ -Dskip-darwin \ + -Dskip-test-incremental \ --search-prefix "$PREFIX" \ --zig-lib-dir "$PWD/../lib" \ --test-timeout 2m diff --git a/ci/x86_64-linux-debug-llvm.sh b/ci/x86_64-linux-debug-llvm.sh index 2696ac60e79f8c25e7ab493f78df7d3f7dca80cc..6ef1b5a00939d8adfb1e2c52f555ce6593c03a25 100755 --- a/ci/x86_64-linux-debug-llvm.sh +++ b/ci/x86_64-linux-debug-llvm.sh @@ -62,6 +62,7 @@ stage3-debug/bin/zig build test docs \ -Dskip-openbsd \ -Dskip-windows \ -Dskip-darwin \ + -Dskip-test-incremental \ -Dtarget=native-native-musl \ --search-prefix "$PREFIX" \ --zig-lib-dir "$PWD/../lib" \ diff --git a/ci/x86_64-linux-debug.sh b/ci/x86_64-linux-debug.sh index baba340333404914f5ed1dbf63c89053a62d6f84..d644d5e6439c586a02f97fc29122d223d8bffa18 100755 --- a/ci/x86_64-linux-debug.sh +++ b/ci/x86_64-linux-debug.sh @@ -62,6 +62,7 @@ stage3-debug/bin/zig build test docs \ -Dskip-windows \ -Dskip-darwin \ -Dskip-llvm \ + -Dskip-test-incremental \ -Dtarget=native-native-musl \ --search-prefix "$PREFIX" \ --zig-lib-dir "$PWD/../lib" \ diff --git a/ci/x86_64-linux-release.sh b/ci/x86_64-linux-release.sh index 56ce115e80b18cd4bc310eea8a1bab995eb61212..99781406e273ffd10dccbe3e2f20d3fdda08b55c 100755 --- a/ci/x86_64-linux-release.sh +++ b/ci/x86_64-linux-release.sh @@ -62,6 +62,7 @@ stage3-release/bin/zig build test docs \ -fqemu \ -fwasmtime \ -Dstatic-llvm \ + -Dskip-test-incremental \ -Dtarget=native-native-musl \ --search-prefix "$PREFIX" \ --zig-lib-dir "$PWD/../lib" \ diff --git a/ci/x86_64-netbsd-debug.sh b/ci/x86_64-netbsd-debug.sh index 68e9081f3ba040c83e4116b194ba17a46692798e..416ab6a5e0168464e4295d1dc54c5e2c19ac4366 100755 --- a/ci/x86_64-netbsd-debug.sh +++ b/ci/x86_64-netbsd-debug.sh @@ -47,6 +47,7 @@ stage3-debug/bin/zig build test docs \ --maxrss ${ZSF_MAX_RSS:-0} \ -Dstatic-llvm \ -Dskip-non-native \ + -Dskip-test-incremental \ --search-prefix "$PREFIX" \ --zig-lib-dir "$PWD/../lib" \ --test-timeout 2m diff --git a/ci/x86_64-netbsd-release.sh b/ci/x86_64-netbsd-release.sh index 225a527686ac06cc1da2241f0fb624956f2d5a58..d8c54ca28d095b3a7e65a53fab1f572162fc4ea5 100755 --- a/ci/x86_64-netbsd-release.sh +++ b/ci/x86_64-netbsd-release.sh @@ -47,6 +47,7 @@ stage3-release/bin/zig build test docs \ --maxrss ${ZSF_MAX_RSS:-0} \ -Dstatic-llvm \ -Dskip-non-native \ + -Dskip-test-incremental \ --search-prefix "$PREFIX" \ --zig-lib-dir "$PWD/../lib" \ --test-timeout 2m diff --git a/ci/x86_64-openbsd-debug.sh b/ci/x86_64-openbsd-debug.sh index 133c8dd4d642a9d4a019d297a3648567231d6078..58363e52d3b9339395b4270f35f9e6b9427d7df1 100755 --- a/ci/x86_64-openbsd-debug.sh +++ b/ci/x86_64-openbsd-debug.sh @@ -47,6 +47,7 @@ stage3-debug/bin/zig build test docs \ --maxrss ${ZSF_MAX_RSS:-0} \ -Dstatic-llvm \ -Dskip-non-native \ + -Dskip-test-incremental \ --search-prefix "$PREFIX" \ --zig-lib-dir "$PWD/../lib" \ --test-timeout 2m diff --git a/ci/x86_64-openbsd-release.sh b/ci/x86_64-openbsd-release.sh index 535b1a147166a1b724dd39358db56f18f17e09cd..ebdaac1ee0c4e0c092929534517096bd5051a450 100755 --- a/ci/x86_64-openbsd-release.sh +++ b/ci/x86_64-openbsd-release.sh @@ -47,6 +47,7 @@ stage3-release/bin/zig build test docs \ --maxrss ${ZSF_MAX_RSS:-0} \ -Dstatic-llvm \ -Dskip-non-native \ + -Dskip-test-incremental \ --search-prefix "$PREFIX" \ --zig-lib-dir "$PWD/../lib" \ --test-timeout 2m -- 2.54.0 From 3245eddcb12b5023bbbbd63eacb5eaf4968be9cb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Wed, 21 Jan 2026 17:53:11 +0100 Subject: [PATCH 018/499] musl: fix typo in 171b1046408bca46508ed205ff4f9a3ee4c95d80 --- lib/libc/include/loongarch64-linux-musl/bits/fenv.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/libc/include/loongarch64-linux-musl/bits/fenv.h b/lib/libc/include/loongarch64-linux-musl/bits/fenv.h index 31a10bb78eadf5f1c3c59c2e398cb9b068aeb9c6..6f98053a0dd796d4da73a17700eb31b213f7d66a 100644 --- a/lib/libc/include/loongarch64-linux-musl/bits/fenv.h +++ b/lib/libc/include/loongarch64-linux-musl/bits/fenv.h @@ -1,4 +1,4 @@ -#ifdef __longarch_soft_float +#ifdef __loongarch_soft_float #define FE_ALL_EXCEPT 0 #define FE_TONEAREST 0 #else -- 2.54.0 From 34aa1bb94fd70c36005f08ced380898fecd9d511 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Wed, 21 Jan 2026 17:53:38 +0100 Subject: [PATCH 019/499] test-libc: enable loongarch64-linux-muslsf --- test/tests.zig | 11 +++++------ 1 file changed, 5 insertions(+), 6 deletions(-) diff --git a/test/tests.zig b/test/tests.zig index 7873774c2a3023995ba2b3f6c62e0d46f11ceada..179f80b3060a48642af431907e220a5966e4887b 100644 --- a/test/tests.zig +++ b/test/tests.zig @@ -2885,12 +2885,11 @@ const libc_targets: []const std.Target.Query = &.{ .os_tag = .linux, .abi = .musl, }, - // Macros like FE_INVALID are defined by musl, but they shouldn't. - // .{ - // .cpu_arch = .loongarch64, - // .os_tag = .linux, - // .abi = .muslsf, - // }, + .{ + .cpu_arch = .loongarch64, + .os_tag = .linux, + .abi = .muslsf, + }, // .{ // .cpu_arch = .mips, // .os_tag = .linux, -- 2.54.0 From 200fb7c2ac460ea40fc032cf95d1bd78e72b046a Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Wed, 21 Jan 2026 18:46:46 +0100 Subject: [PATCH 020/499] test-libc: disable raise-race.c --- test/libc.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/test/libc.zig b/test/libc.zig index d7f8c3cceb9f3bf5550b6615d13bad93840647f8..e1d866eb3d22293999b1307e55904147242167b4 100644 --- a/test/libc.zig +++ b/test/libc.zig @@ -113,7 +113,7 @@ pub fn addCases(cases: *tests.LibcContext) void { cases.addLibcTestCase("regression/pthread_once-deadlock.c", false, .{}); cases.addLibcTestCase("regression/pthread_rwlock-ebusy.c", false, .{}); cases.addLibcTestCase("regression/putenv-doublefree.c", true, .{}); - cases.addLibcTestCase("regression/raise-race.c", false, .{}); + // cases.addLibcTestCase("regression/raise-race.c", false, .{}); - Sometimes hangs when run natively on x86_64-linux. cases.addLibcTestCase("regression/regex-backref-0.c", true, .{}); cases.addLibcTestCase("regression/regex-bracket-icase.c", true, .{}); cases.addLibcTestCase("regression/regex-ere-backref.c", true, .{}); -- 2.54.0 From fd3657bf8c3a2861e1b35cf36d469d342d853880 Mon Sep 17 00:00:00 2001 From: Chadwain Holness Date: Tue, 20 Jan 2026 15:19:36 -0500 Subject: [PATCH 021/499] Io.Threaded: remove WSA_FLAG_OVERLAPPED from socket call --- lib/std/Io/Threaded.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index c42545563c37cdf9bdcd2643ec9dc6c16b3c58a2..849a2d437512946b27cfbaeca5b55055eca32ca1 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -10824,7 +10824,7 @@ fn openSocketWsa( ) !ws2_32.SOCKET { const mode = posixSocketMode(options.mode); const protocol = posixProtocol(options.protocol); - const flags: u32 = ws2_32.WSA_FLAG_OVERLAPPED | ws2_32.WSA_FLAG_NO_HANDLE_INHERIT; + const flags: u32 = ws2_32.WSA_FLAG_NO_HANDLE_INHERIT; var syscall: Syscall = try .start(); while (true) { const rc = ws2_32.WSASocketW(family, @bitCast(mode), @bitCast(protocol), null, 0, flags); -- 2.54.0 From 37288e53ae4e617a56fa79d5b718f3b66957d4f5 Mon Sep 17 00:00:00 2001 From: bartimaeusnek Date: Thu, 22 Jan 2026 19:18:47 +0100 Subject: [PATCH 022/499] std.zig.system.loongarch: implement individual cpu feature bit tests (#30915) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit closes #30902 Co-authored-by: bartimaeusnek <33183715+bartimaeusnek@users.noreply.github.com> Reviewed-on: https://codeberg.org/ziglang/zig/pulls/30915 Reviewed-by: Alex Rønne Petersen Co-authored-by: bartimaeusnek Co-committed-by: bartimaeusnek --- lib/std/zig/system/loongarch.zig | 38 +++++++++++++++++++++++++++++--- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/lib/std/zig/system/loongarch.zig b/lib/std/zig/system/loongarch.zig index cdeeb10564a140a904c8dbe4803f9a82d4f55053..7a2c62279131a70dafe52759fdb754b48d5e90fc 100644 --- a/lib/std/zig/system/loongarch.zig +++ b/lib/std/zig/system/loongarch.zig @@ -1,6 +1,16 @@ const builtin = @import("builtin"); const std = @import("std"); +inline fn bit(input: u32, offset: u5) bool { + return (input >> offset) & 1 != 0; +} + +fn setFeature(cpu: *std.Target.Cpu, feature: std.Target.loongarch.Feature, enabled: bool) void { + const idx = @as(std.Target.Cpu.Feature.Set.Index, @intFromEnum(feature)); + + if (enabled) cpu.features.addFeature(idx) else cpu.features.removeFeature(idx); +} + pub fn detectNativeCpuAndFeatures( arch: std.Target.Cpu.Arch, os: std.Target.Os, @@ -9,9 +19,6 @@ pub fn detectNativeCpuAndFeatures( _ = os; _ = query; - // Clearly this code could do better in the future by actually querying specific CPU features - // with the cpucfg instruction like on x86. But with the small number of well-known LoongArch - // models that exist at the moment, simply checking the PRID is plenty. var cpu: std.Target.Cpu = .{ .arch = arch, .model = switch (cpucfg(0) & 0xf000) { @@ -23,6 +30,31 @@ pub fn detectNativeCpuAndFeatures( }; cpu.features.addFeatureSet(cpu.model.features); + + const cfg1 = cpucfg(1); + const cfg2 = cpucfg(2); + const cfg3 = cpucfg(3); + + setFeature(&cpu, .ual, bit(cfg1, 20)); + + const has_fpu = bit(cfg2, 0); + setFeature(&cpu, .f, has_fpu and bit(cfg2, 1)); + setFeature(&cpu, .d, has_fpu and bit(cfg2, 2)); + + setFeature(&cpu, .lsx, bit(cfg2, 6)); + setFeature(&cpu, .lasx, bit(cfg2, 7)); + setFeature(&cpu, .lvz, bit(cfg2, 10)); + + setFeature(&cpu, .lbt, bit(cfg2, 18) and bit(cfg2, 19) and bit(cfg2, 20)); + + setFeature(&cpu, .frecipe, bit(cfg2, 25)); + setFeature(&cpu, .div32, bit(cfg2, 26)); + setFeature(&cpu, .lam_bh, bit(cfg2, 27)); + setFeature(&cpu, .lamcas, bit(cfg2, 28)); + setFeature(&cpu, .scq, bit(cfg2, 30)); + + setFeature(&cpu, .ld_seq_sa, bit(cfg3, 23)); + cpu.features.populateDependencies(cpu.arch.allFeaturesList()); return cpu; -- 2.54.0 From 305fd06756d0a90e330c957d85738f195b94bc10 Mon Sep 17 00:00:00 2001 From: InKryption Date: Mon, 21 Apr 2025 15:17:39 +0200 Subject: [PATCH 023/499] Build: check if dynamic lib installed for symlinks If the library isn't actually installed, `generated_bin` will be null, causing this to panic about a missing dependency for itself; checking for this state avoids this. --- lib/std/Build/Step/Compile.zig | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/std/Build/Step/Compile.zig b/lib/std/Build/Step/Compile.zig index 92db0ca0a018030abfcc41753d0c1bd1922cd5ed..941f01dd75549436a497a52b554403d57e78d4b1 100644 --- a/lib/std/Build/Step/Compile.zig +++ b/lib/std/Build/Step/Compile.zig @@ -1786,7 +1786,8 @@ fn make(step: *Step, options: Step.MakeOptions) !void { } if (compile.kind == .lib and compile.linkage != null and compile.linkage.? == .dynamic and - compile.version != null and std.Build.wantSharedLibSymLinks(compile.rootModuleTarget())) + compile.version != null and compile.generated_bin != null and + std.Build.wantSharedLibSymLinks(compile.rootModuleTarget())) { try doAtomicSymLinks( step, -- 2.54.0 From 1badb2a840c47d49b49fa685db7a1553c0a40ee7 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 22 Jan 2026 18:08:13 -0800 Subject: [PATCH 024/499] std.Io.Threaded: dirCreateFileWindows uses NtCreateFile directly --- lib/std/Io/Dir.zig | 6 +- lib/std/Io/File.zig | 30 ++++- lib/std/Io/File/Writer.zig | 2 +- lib/std/Io/Threaded.zig | 229 +++++++++++++++++++++++------------ lib/std/Progress.zig | 4 +- lib/std/os/windows.zig | 134 +++++++++++++------- lib/std/os/windows/ntdll.zig | 18 ++- src/link/Lld.zig | 14 ++- 8 files changed, 305 insertions(+), 132 deletions(-) diff --git a/lib/std/Io/Dir.zig b/lib/std/Io/Dir.zig index 67d1ec849c6c34360d565ea22ab85b12c8182d07..425e220b1cfdb3d16980a29b394527ccc355cbcf 100644 --- a/lib/std/Io/Dir.zig +++ b/lib/std/Io/Dir.zig @@ -12,6 +12,8 @@ const Allocator = std.mem.Allocator; handle: Handle, +pub const Handle = std.posix.fd_t; + pub const path = std.fs.path; /// The maximum length of a file path that the operating system will accept. @@ -396,8 +398,6 @@ pub fn walk(dir: Dir, allocator: Allocator) Allocator.Error!Walker { return .{ .inner = try walkSelectively(dir, allocator) }; } -pub const Handle = std.posix.fd_t; - pub const PathNameError = error{ /// Returned when an insufficient buffer is provided that cannot fit the /// path name. @@ -1698,7 +1698,7 @@ pub fn copyFile( options: CopyFileOptions, ) CopyFileError!void { const file = try source_dir.openFile(io, source_path, .{}); - var file_reader: File.Reader = .init(.{ .handle = file.handle }, io, &.{}); + var file_reader: File.Reader = .init(file, io, &.{}); defer file_reader.file.close(io); const permissions = options.permissions orelse blk: { diff --git a/lib/std/Io/File.zig b/lib/std/Io/File.zig index cc1a58a15e2fa78878eca1fd84458397cb4b6412..6323a39454194e58be8a53e4f8f9879cfc1daa0e 100644 --- a/lib/std/Io/File.zig +++ b/lib/std/Io/File.zig @@ -11,13 +11,14 @@ const Dir = std.Io.Dir; handle: Handle, +pub const Handle = std.posix.fd_t; + pub const Reader = @import("File/Reader.zig"); pub const Writer = @import("File/Writer.zig"); pub const Atomic = @import("File/Atomic.zig"); /// Memory intended to remain consistent with file contents. pub const MemoryMap = @import("File/MemoryMap.zig"); -pub const Handle = std.posix.fd_t; pub const INode = std.posix.ino_t; pub const NLink = std.posix.nlink_t; pub const Uid = std.posix.uid_t; @@ -73,15 +74,36 @@ pub const Stat = struct { }; pub fn stdout() File { - return .{ .handle = if (is_windows) std.os.windows.peb().ProcessParameters.hStdOutput else std.posix.STDOUT_FILENO }; + return switch (native_os) { + .windows => .{ + .handle = std.os.windows.peb().ProcessParameters.hStdOutput, + }, + else => .{ + .handle = std.posix.STDOUT_FILENO, + }, + }; } pub fn stderr() File { - return .{ .handle = if (is_windows) std.os.windows.peb().ProcessParameters.hStdError else std.posix.STDERR_FILENO }; + return switch (native_os) { + .windows => .{ + .handle = std.os.windows.peb().ProcessParameters.hStdError, + }, + else => .{ + .handle = std.posix.STDERR_FILENO, + }, + }; } pub fn stdin() File { - return .{ .handle = if (is_windows) std.os.windows.peb().ProcessParameters.hStdInput else std.posix.STDIN_FILENO }; + return switch (native_os) { + .windows => .{ + .handle = std.os.windows.peb().ProcessParameters.hStdInput, + }, + else => .{ + .handle = std.posix.STDIN_FILENO, + }, + }; } pub const StatError = error{ diff --git a/lib/std/Io/File/Writer.zig b/lib/std/Io/File/Writer.zig index 52bbe83513f26f6b2fdd7b8ed4686a3643363c1c..68a68e28ec706bf9b1863fd3b329c78f4255ad9a 100644 --- a/lib/std/Io/File/Writer.zig +++ b/lib/std/Io/File/Writer.zig @@ -101,7 +101,7 @@ pub fn moveToReader(w: *Writer) File.Reader { defer w.* = undefined; return .{ .io = w.io, - .file = .{ .handle = w.file.handle }, + .file = w.file, .mode = w.mode, .pos = w.pos, .interface = File.Reader.initInterface(w.interface.buffer), diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 849a2d437512946b27cfbaeca5b55055eca32ca1..42816b52767343192821a7d432568c303bcaeeee 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -571,7 +571,7 @@ const Future = struct { num_completed: *std.atomic.Value(u32), thread: ?*Thread, ) void { - var need_signal: bool = thread != null and thread.?.cancelAwaitable(.fromFuture(future)); + var need_signal: bool = if (thread) |th| th.cancelAwaitable(.fromFuture(future)) else false; var timeout_ns: u64 = 1 << 10; while (true) { need_signal = need_signal and thread.?.signalCanceledSyscall(t, .fromFuture(future)); @@ -628,7 +628,7 @@ const Thread = struct { const Handle = Handle: { if (std.Thread.use_pthreads) break :Handle std.c.pthread_t; - if (builtin.target.os.tag == .windows) break :Handle windows.HANDLE; + if (is_windows) break :Handle windows.HANDLE; break :Handle void; }; @@ -1364,7 +1364,7 @@ fn worker(t: *Threaded) void { .id = std.Thread.getCurrentId(), .handle = handle: { if (std.Thread.use_pthreads) break :handle std.c.pthread_self(); - if (builtin.target.os.tag == .windows) break :handle undefined; // populated below + if (is_windows) break :handle undefined; // populated below }, .status = .init(.{ .cancelation = .none, @@ -1376,7 +1376,7 @@ fn worker(t: *Threaded) void { }; Thread.current = &thread; - if (builtin.target.os.tag == .windows) { + if (is_windows) { assert(windows.ntdll.NtOpenThread( &thread.handle, .{ @@ -1397,7 +1397,7 @@ fn worker(t: *Threaded) void { &windows.teb().ClientId, ) == .SUCCESS); } - defer if (builtin.target.os.tag == .windows) { + defer if (is_windows) { windows.CloseHandle(thread.handle); }; @@ -3430,53 +3430,133 @@ fn dirCreateFileWindows( sub_path: []const u8, flags: File.CreateFlags, ) File.OpenError!File { - const w = windows; const t: *Threaded = @ptrCast(@alignCast(userdata)); _ = t; - const sub_path_w_array = try w.sliceToPrefixedFileW(dir.handle, sub_path); + if (std.mem.eql(u8, sub_path, ".")) return error.IsDir; + if (std.mem.eql(u8, sub_path, "..")) return error.IsDir; + + const sub_path_w_array = try windows.sliceToPrefixedFileW(dir.handle, sub_path); const sub_path_w = sub_path_w_array.span(); + const path_len_bytes = std.math.cast(u16, sub_path_w.len * 2) orelse return error.NameTooLong; - const handle = handle: { - const syscall: Syscall = try .start(); - while (true) { - if (w.OpenFile(sub_path_w, .{ - .dir = dir.handle, - .access_mask = .{ - .STANDARD = .{ .SYNCHRONIZE = true }, - .GENERIC = .{ - .WRITE = true, - .READ = flags.read, - }, - }, - .creation = if (flags.exclusive) - .CREATE - else if (flags.truncate) - .OVERWRITE_IF - else - .OPEN_IF, - })) |handle| { - syscall.finish(); - break :handle handle; - } else |err| switch (err) { - error.OperationCanceled => { - try syscall.checkCancel(); - continue; - }, - else => |e| return syscall.fail(e), - } - } + var nt_name: windows.UNICODE_STRING = .{ + .Length = path_len_bytes, + .MaximumLength = path_len_bytes, + .Buffer = @constCast(sub_path_w.ptr), }; - errdefer w.CloseHandle(handle); + const attr: windows.OBJECT_ATTRIBUTES = .{ + .Length = @sizeOf(windows.OBJECT_ATTRIBUTES), + .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle, + .Attributes = .{ + .INHERIT = false, + }, + .ObjectName = &nt_name, + .SecurityDescriptor = null, + .SecurityQualityOfService = null, + }; + const create_disposition: windows.FILE.CREATE_DISPOSITION = if (flags.exclusive) + .CREATE + else if (flags.truncate) + .OVERWRITE_IF + else + .OPEN_IF; + + const access_mask: windows.ACCESS_MASK = .{ + .STANDARD = .{ .SYNCHRONIZE = true }, + .GENERIC = .{ + .WRITE = true, + .READ = flags.read, + }, + }; + + var io_status_block: windows.IO_STATUS_BLOCK = undefined; + + // There are multiple kernel bugs being worked around with retries. + const max_attempts = 13; + var attempt: u5 = 0; + + var handle: windows.HANDLE = undefined; + var syscall: Syscall = try .start(); + while (true) switch (windows.ntdll.NtCreateFile( + &handle, + access_mask, + &attr, + &io_status_block, + null, + .{ .NORMAL = true }, + .VALID_FLAGS, // share access + create_disposition, + .{ + .NON_DIRECTORY_FILE = true, + .IO = .SYNCHRONOUS_NONALERT, + }, + null, + 0, + )) { + .SUCCESS => { + syscall.finish(); + break; + }, + .CANCELLED => { + try syscall.checkCancel(); + continue; + }, + .SHARING_VIOLATION => { + // This occurs if the file attempting to be opened is a running + // executable. However, there's a kernel bug: the error may be + // incorrectly returned for an indeterminate amount of time + // after an executable file is closed. Here we work around the + // kernel bug with retry attempts. + syscall.finish(); + if (max_attempts - attempt == 0) return error.SharingViolation; + try parking_sleep.windowsRetrySleep((@as(u32, 1) << attempt) >> 1); + attempt += 1; + syscall = try .start(); + continue; + }, + .DELETE_PENDING => { + // This error means that there *was* a file in this location on + // the file system, but it was deleted. However, the OS is not + // finished with the deletion operation, and so this CreateFile + // call has failed. Here, we simulate the kernel bug being + // fixed by sleeping and retrying until the error goes away. + syscall.finish(); + if (max_attempts - attempt == 0) return error.SharingViolation; + try parking_sleep.windowsRetrySleep((@as(u32, 1) << attempt) >> 1); + attempt += 1; + syscall = try .start(); + continue; + }, + .OBJECT_NAME_INVALID => return syscall.fail(error.BadPathName), + .OBJECT_NAME_NOT_FOUND => return syscall.fail(error.FileNotFound), + .OBJECT_PATH_NOT_FOUND => return syscall.fail(error.FileNotFound), + .BAD_NETWORK_PATH => return syscall.fail(error.NetworkNotFound), // \\server was not found + .BAD_NETWORK_NAME => return syscall.fail(error.NetworkNotFound), // \\server was found but \\server\share wasn't + .NO_MEDIA_IN_DEVICE => return syscall.fail(error.NoDevice), + .ACCESS_DENIED => return syscall.fail(error.AccessDenied), + .PIPE_BUSY => return syscall.fail(error.PipeBusy), + .PIPE_NOT_AVAILABLE => return syscall.fail(error.NoDevice), + .OBJECT_NAME_COLLISION => return syscall.fail(error.PathAlreadyExists), + .FILE_IS_A_DIRECTORY => return syscall.fail(error.IsDir), + .NOT_A_DIRECTORY => return syscall.fail(error.NotDir), + .USER_MAPPED_FILE => return syscall.fail(error.AccessDenied), + .VIRUS_INFECTED, .VIRUS_DELETED => return syscall.fail(error.AntivirusInterference), + .INVALID_PARAMETER => |err| return syscall.ntstatusBug(err), + .OBJECT_PATH_SYNTAX_BAD => |err| return syscall.ntstatusBug(err), + .INVALID_HANDLE => |err| return syscall.ntstatusBug(err), + else => |err| return syscall.unexpectedNtstatus(err), + }; + errdefer windows.CloseHandle(handle); - var io_status_block: w.IO_STATUS_BLOCK = undefined; const exclusive = switch (flags.lock) { .none => return .{ .handle = handle }, .shared => false, .exclusive => true, }; - const syscall: Syscall = try .start(); - while (true) switch (w.ntdll.NtLockFile( + + syscall = try .start(); + while (true) switch (windows.ntdll.NtLockFile( handle, null, null, @@ -3968,7 +4048,10 @@ pub fn dirOpenFileWtf16( var attr: w.OBJECT_ATTRIBUTES = .{ .Length = @sizeOf(w.OBJECT_ATTRIBUTES), .RootDirectory = dir_handle, - .Attributes = .{}, + .Attributes = .{ + // TODO should we set INHERIT=false? + //.INHERIT = false, + }, .ObjectName = &nt_name, .SecurityDescriptor = null, .SecurityQualityOfService = null, @@ -7923,15 +8006,14 @@ fn fileClose(userdata: ?*anyopaque, files: []const File) void { for (files) |file| posix.close(file.handle); } -const fileReadStreaming = switch (native_os) { - .windows => fileReadStreamingWindows, - else => fileReadStreamingPosix, -}; - -fn fileReadStreamingPosix(userdata: ?*anyopaque, file: File, data: []const []u8) File.Reader.Error!usize { +fn fileReadStreaming(userdata: ?*anyopaque, file: File, data: []const []u8) File.Reader.Error!usize { const t: *Threaded = @ptrCast(@alignCast(userdata)); _ = t; + if (is_windows) return fileReadStreamingWindows(file, data); + return fileReadStreamingPosix(file, data); +} +fn fileReadStreamingPosix(file: File, data: []const []u8) File.Reader.Error!usize { var iovecs_buffer: [max_iovecs_len]posix.iovec = undefined; var i: usize = 0; for (data) |buf| { @@ -8013,10 +8095,7 @@ fn fileReadStreamingPosix(userdata: ?*anyopaque, file: File, data: []const []u8) } } -fn fileReadStreamingWindows(userdata: ?*anyopaque, file: File, data: []const []u8) File.Reader.Error!usize { - const t: *Threaded = @ptrCast(@alignCast(userdata)); - _ = t; - +fn fileReadStreamingWindows(file: File, data: []const []u8) File.Reader.Error!usize { const DWORD = windows.DWORD; var index: usize = 0; while (index < data.len and data[index].len == 0) index += 1; @@ -8059,10 +8138,7 @@ fn fileReadStreamingWindows(userdata: ?*anyopaque, file: File, data: []const []u } } -fn fileReadPositionalPosix(userdata: ?*anyopaque, file: File, data: []const []u8, offset: u64) File.ReadPositionalError!usize { - const t: *Threaded = @ptrCast(@alignCast(userdata)); - _ = t; - +fn fileReadPositionalPosix(file: File, data: []const []u8, offset: u64) File.ReadPositionalError!usize { if (!have_preadv) @compileError("TODO implement fileReadPositionalPosix for cursed operating systems that don't support preadv (it's only Haiku)"); var iovecs_buffer: [max_iovecs_len]posix.iovec = undefined; @@ -8144,15 +8220,14 @@ fn fileReadPositionalPosix(userdata: ?*anyopaque, file: File, data: []const []u8 } } -const fileReadPositional = switch (native_os) { - .windows => fileReadPositionalWindows, - else => fileReadPositionalPosix, -}; - -fn fileReadPositionalWindows(userdata: ?*anyopaque, file: File, data: []const []u8, offset: u64) File.ReadPositionalError!usize { +fn fileReadPositional(userdata: ?*anyopaque, file: File, data: []const []u8, offset: u64) File.ReadPositionalError!usize { const t: *Threaded = @ptrCast(@alignCast(userdata)); _ = t; + if (is_windows) return fileReadPositionalWindows(file, data, offset); + return fileReadPositionalPosix(file, data, offset); +} +fn fileReadPositionalWindows(file: File, data: []const []u8, offset: u64) File.ReadPositionalError!usize { var index: usize = 0; while (index < data.len and data[index].len == 0) index += 1; if (index == data.len) return 0; @@ -8244,7 +8319,7 @@ fn fileSeekBy(userdata: ?*anyopaque, file: File, offset: i64) File.SeekError!voi } } - if (native_os == .windows) { + if (is_windows) { const syscall: Syscall = try .start(); while (true) { if (windows.kernel32.SetFilePointerEx(fd, offset, null, windows.FILE_CURRENT) != 0) { @@ -8329,7 +8404,7 @@ fn fileSeekTo(userdata: ?*anyopaque, file: File, offset: u64) File.SeekError!voi _ = t; const fd = file.handle; - if (native_os == .windows) { + if (is_windows) { // "The starting point is zero or the beginning of the file. If [FILE_BEGIN] // is specified, then the liDistanceToMove parameter is interpreted as an unsigned value." // https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/nf-fileapi-setfilepointerex @@ -11628,7 +11703,7 @@ fn netWriteWindows( splat: usize, ) net.Stream.Writer.Error!usize { const t: *Threaded = @ptrCast(@alignCast(userdata)); - comptime assert(native_os == .windows); + comptime assert(is_windows); var iovecs: [max_iovecs_len]ws2_32.WSABUF = undefined; var len: u32 = 0; @@ -11896,7 +11971,7 @@ fn netInterfaceNameResolve( } } - if (native_os == .windows) { + if (is_windows) { try Thread.checkCancel(); @panic("TODO implement netInterfaceNameResolve for Windows"); } @@ -11930,7 +12005,7 @@ fn netInterfaceName(userdata: ?*anyopaque, interface: net.Interface) net.Interfa @panic("TODO implement netInterfaceName for linux"); } - if (native_os == .windows) { + if (is_windows) { @panic("TODO implement netInterfaceName for windows"); } @@ -15247,11 +15322,13 @@ fn progressParentFile(userdata: ?*anyopaque) std.Progress.ParentFileError!File { const int = try t.environ.zig_progress_handle; - return .{ .handle = switch (@typeInfo(Io.File.Handle)) { - .int => int, - .pointer => @ptrFromInt(int), - else => return error.UnsupportedOperation, - } }; + return .{ + .handle = switch (@typeInfo(Io.File.Handle)) { + .int => int, + .pointer => @ptrFromInt(int), + else => return error.UnsupportedOperation, + }, + }; } pub fn environString(t: *Threaded, comptime name: []const u8) ?[:0]const u8 { @@ -15562,13 +15639,13 @@ test { _ = @import("Threaded/test.zig"); } -const use_parking_futex = switch (builtin.target.os.tag) { +const use_parking_futex = switch (native_os) { .windows => true, // RtlWaitOnAddress is a userland implementation anyway .netbsd => true, // NetBSD has `futex(2)`, but it's historically been quite buggy. TODO: evaluate whether it's okay to use now. .illumos => true, // Illumos has no futex mechanism else => false, }; -const use_parking_sleep = switch (builtin.target.os.tag) { +const use_parking_sleep = switch (native_os) { // On Windows, we can implement sleep either with `NtDelayExecution` (which is how `SleepEx` in // kernel32 works) or `NtWaitForAlertByThreadId` (thread parking). We're already using the // latter for futex, so we may as well use it for sleeping too, to maximise code reuse. I'm @@ -15926,7 +16003,7 @@ const parking_sleep = struct { /// `addr_hint` has no semantic effect, but may allow the OS to optimize this operation. fn park(opt_deadline: ?std.Io.Clock.Timestamp, addr_hint: ?*const anyopaque) error{Timeout}!void { comptime assert(use_parking_futex or use_parking_sleep); - switch (builtin.target.os.tag) { + switch (native_os) { .windows => { var timeout_buf: windows.LARGE_INTEGER = undefined; const raw_timeout: ?*windows.LARGE_INTEGER = if (opt_deadline) |deadline| timeout: { @@ -15980,7 +16057,7 @@ fn park(opt_deadline: ?std.Io.Clock.Timestamp, addr_hint: ?*const anyopaque) err } } -const UnparkTid = switch (builtin.target.os.tag) { +const UnparkTid = switch (native_os) { // `NtAlertMultipleThreadByThreadId` is weird and wants 64-bit thread handles? .windows => usize, else => std.Thread.Id, @@ -15988,7 +16065,7 @@ const UnparkTid = switch (builtin.target.os.tag) { /// `addr_hint` has no semantic effect, but may allow the OS to optimize this operation. fn unpark(tids: []const UnparkTid, addr_hint: ?*const anyopaque) void { comptime assert(use_parking_futex or use_parking_sleep); - switch (builtin.target.os.tag) { + switch (native_os) { .windows => { // TODO: this condition is currently disabled because mingw-w64 does not contain this // symbol. Once it's added, enable this check to use the new bulk API where possible. diff --git a/lib/std/Progress.zig b/lib/std/Progress.zig index 75ef13ca91f16307ca028977f5db6e0144996e77..5ccc46778b43d0260ee1a6323e13eae20e31a2c9 100644 --- a/lib/std/Progress.zig +++ b/lib/std/Progress.zig @@ -977,7 +977,9 @@ fn serializeIpc(start_serialized_len: usize, serialized_buffer: *Serialized.Buff 0.., ) |main_parent, *main_storage, main_index| { if (main_parent == .unused) continue; - const file: Io.File = .{ .handle = main_storage.getIpcFd() orelse continue }; + const file: Io.File = .{ + .handle = main_storage.getIpcFd() orelse continue, + }; const opt_saved_metadata = findOld(file.handle, old_ipc_metadata_fds, old_ipc_metadata); var bytes_read: usize = 0; while (true) { diff --git a/lib/std/os/windows.zig b/lib/std/os/windows.zig index c94596de8e55be5c8d5689c5e4a53b815aafc7ae..262eaef049a0a6cb198b70195ad85f8356916346 100644 --- a/lib/std/os/windows.zig +++ b/lib/std/os/windows.zig @@ -392,6 +392,8 @@ pub const FILE = struct { Characteristics: ULONG, }; + pub const USE_FILE_POINTER_POSITION = -2; + // ref: um/WinBase.h pub const ATTRIBUTE_TAG_INFO = extern struct { @@ -466,9 +468,11 @@ pub const FILE = struct { pub const CREATE_DISPOSITION = enum(ULONG) { /// If the file already exists, replace it with the given file. If it does not, create the given file. SUPERSEDE = 0x00000000, - /// If the file already exists, open it instead of creating a new file. If it does not, fail the request and do not create a new file. + /// If the file already exists, open it instead of creating a new file. + /// If it does not, fail the request and do not create a new file. OPEN = 0x00000001, - /// If the file already exists, fail the request and do not create or open the given file. If it does not, create the given file. + /// If the file already exists, fail the request and do not create or + /// open the given file. If it does not, create the given file. CREATE = 0x00000002, /// If the file already exists, open it. If it does not, create the given file. OPEN_IF = 0x00000003, @@ -482,75 +486,122 @@ pub const FILE = struct { /// Define the create/open option flags pub const MODE = packed struct(ULONG) { - /// The file being created or opened is a directory file. With this flag, the CreateDisposition parameter must be set to `.CREATE`, `.FILE_OPEN`, or `.OPEN_IF`. - /// With this flag, other compatible CreateOptions flags include only the following: `SYNCHRONOUS_IO`, `WRITE_THROUGH`, `OPEN_FOR_BACKUP_INTENT`, and `OPEN_BY_FILE_ID`. + /// The file being created or opened is a directory file. With this + /// flag, the CreateDisposition parameter must be set to `.CREATE`, + /// `.FILE_OPEN`, or `.OPEN_IF`. With this flag, other compatible + /// CreateOptions flags include only the following: `SYNCHRONOUS_IO`, + /// `WRITE_THROUGH`, `OPEN_FOR_BACKUP_INTENT`, and `OPEN_BY_FILE_ID`. DIRECTORY_FILE: bool = false, - /// Applications that write data to the file must actually transfer the data into the file before any requested write operation is considered complete. - /// This flag is automatically set if the CreateOptions flag `NO_INTERMEDIATE_BUFFERING` is set. + /// Applications that write data to the file must actually transfer the + /// data into the file before any requested write operation is + /// considered complete. This flag is automatically set if the + /// CreateOptions flag `NO_INTERMEDIATE_BUFFERING` is set. WRITE_THROUGH: bool = false, /// All accesses to the file are sequential. SEQUENTIAL_ONLY: bool = false, - /// The file cannot be cached or buffered in a driver's internal buffers. This flag is incompatible with the DesiredAccess `FILE_APPEND_DATA` flag. + /// The file cannot be cached or buffered in a driver's internal + /// buffers. This flag is incompatible with the DesiredAccess + /// `FILE_APPEND_DATA` flag. NO_INTERMEDIATE_BUFFERING: bool = false, IO: enum(u2) { /// All operations on the file are performed asynchronously. ASYNCHRONOUS = 0b00, - /// All operations on the file are performed synchronously. Any wait on behalf of the caller is subject to premature termination from alerts. - /// This flag also causes the I/O system to maintain the file position context. If this flag is set, the DesiredAccess `SYNCHRONIZE` flag also must be set. + /// All operations on the file are performed synchronously. Any + /// wait on behalf of the caller is subject to premature + /// termination from alerts. This flag also causes the I/O system + /// to maintain the file position context. If this flag is set, the + /// DesiredAccess `SYNCHRONIZE` flag also must be set. SYNCHRONOUS_ALERT = 0b01, - /// All operations on the file are performed synchronously. Waits in the system to synchronize I/O queuing and completion are not subject to alerts. - /// This flag also causes the I/O system to maintain the file position context. If this flag is set, the DesiredAccess `SYNCHRONIZE` flag also must be set. + /// All operations on the file are performed synchronously. Waits + /// in the system to synchronize I/O queuing and completion are not + /// subject to alerts. This flag also causes the I/O system to + /// maintain the file position context. If this flag is set, the + /// DesiredAccess `SYNCHRONIZE` flag also must be set. SYNCHRONOUS_NONALERT = 0b10, _, pub const VALID_FLAGS: @This() = @enumFromInt(0b11); } = .ASYNCHRONOUS, - /// The file being opened must not be a directory file or this call fails. The file object being opened can represent a data file, a logical, virtual, or physical - /// device, or a volume. + /// The file being opened must not be a directory file or this call + /// fails. The file object being opened can represent a data file, a + /// logical, virtual, or physical device, or a volume. NON_DIRECTORY_FILE: bool = false, - /// Create a tree connection for this file in order to open it over the network. This flag is not used by device and intermediate drivers. + /// Create a tree connection for this file in order to open it over the + /// network. This flag is not used by device and intermediate drivers. CREATE_TREE_CONNECTION: bool = false, - /// Complete this operation immediately with an alternate success code of `STATUS_OPLOCK_BREAK_IN_PROGRESS` if the target file is oplocked, rather than blocking - /// the caller's thread. If the file is oplocked, another caller already has access to the file. This flag is not used by device and intermediate drivers. + /// Complete this operation immediately with an alternate success code + /// of `STATUS_OPLOCK_BREAK_IN_PROGRESS` if the target file is + /// oplocked, rather than blocking the caller's thread. If the file is + /// oplocked, another caller already has access to the file. This flag + /// is not used by device and intermediate drivers. COMPLETE_IF_OPLOCKED: bool = false, - /// If the extended attributes on an existing file being opened indicate that the caller must understand EAs to properly interpret the file, fail this request - /// because the caller does not understand how to deal with EAs. This flag is irrelevant for device and intermediate drivers. + /// If the extended attributes on an existing file being opened + /// indicate that the caller must understand EAs to properly interpret + /// the file, fail this request because the caller does not understand + /// how to deal with EAs. This flag is irrelevant for device and + /// intermediate drivers. NO_EA_KNOWLEDGE: bool = false, OPEN_REMOTE_INSTANCE: bool = false, - /// Accesses to the file can be random, so no sequential read-ahead operations should be performed on the file by FSDs or the system. + /// Accesses to the file can be random, so no sequential read-ahead + /// operations should be performed on the file by FSDs or the system. RANDOM_ACCESS: bool = false, - /// Delete the file when the last handle to it is passed to `NtClose`. If this flag is set, the `DELETE` flag must be set in the DesiredAccess parameter. + /// Delete the file when the last handle to it is passed to `NtClose`. + /// If this flag is set, the `DELETE` flag must be set in the + /// DesiredAccess parameter. DELETE_ON_CLOSE: bool = false, - /// The file name that is specified by the `ObjectAttributes` parameter includes the 8-byte file reference number for the file. This number is assigned by and - /// specific to the particular file system. If the file is a reparse point, the file name will also include the name of a device. Note that the FAT file system - /// does not support this flag. This flag is not used by device and intermediate drivers. + /// The file name that is specified by the `ObjectAttributes` parameter + /// includes the 8-byte file reference number for the file. This number + /// is assigned by and specific to the particular file system. If the + /// file is a reparse point, the file name will also include the name + /// of a device. Note that the FAT file system does not support this + /// flag. This flag is not used by device and intermediate drivers. OPEN_BY_FILE_ID: bool = false, - /// The file is being opened for backup intent. Therefore, the system should check for certain access rights and grant the caller the appropriate access to the - /// file before checking the DesiredAccess parameter against the file's security descriptor. This flag not used by device and intermediate drivers. + /// The file is being opened for backup intent. Therefore, the system + /// should check for certain access rights and grant the caller the + /// appropriate access to the file before checking the DesiredAccess + /// parameter against the file's security descriptor. This flag not + /// used by device and intermediate drivers. OPEN_FOR_BACKUP_INTENT: bool = false, - /// Suppress inheritance of `FILE_ATTRIBUTE.COMPRESSED` from the parent directory. This allows creation of a non-compressed file in a directory that is marked - /// compressed. + /// Suppress inheritance of `FILE_ATTRIBUTE.COMPRESSED` from the parent + /// directory. This allows creation of a non-compressed file in a + /// directory that is marked compressed. NO_COMPRESSION: bool = false, - /// The file is being opened and an opportunistic lock on the file is being requested as a single atomic operation. The file system checks for oplocks before it - /// performs the create operation and will fail the create with a return code of STATUS_CANNOT_BREAK_OPLOCK if the result would be to break an existing oplock. - /// For more information, see the Remarks section. + /// The file is being opened and an opportunistic lock on the file is + /// being requested as a single atomic operation. The file system + /// checks for oplocks before it performs the create operation and will + /// fail the create with a return code of STATUS_CANNOT_BREAK_OPLOCK if + /// the result would be to break an existing oplock. For more + /// information, see the Remarks section. /// - /// Windows Server 2008, Windows Vista, Windows Server 2003 and Windows XP: This flag is not supported. + /// Windows Server 2008, Windows Vista, Windows Server 2003 and Windows + /// XP: This flag is not supported. /// - /// This flag is supported on the following file systems: NTFS, FAT, and exFAT. + /// This flag is supported on the following file systems: NTFS, FAT, + /// and exFAT. OPEN_REQUIRING_OPLOCK: bool = false, Reserved17: u3 = 0, - /// This flag allows an application to request a filter opportunistic lock to prevent other applications from getting share violations. If there are already open - /// handles, the create request will fail with STATUS_OPLOCK_NOT_GRANTED. For more information, see the Remarks section. + /// This flag allows an application to request a filter opportunistic + /// lock to prevent other applications from getting share violations. + /// If there are already open handles, the create request will fail + /// with STATUS_OPLOCK_NOT_GRANTED. For more information, see the + /// Remarks section. RESERVE_OPFILTER: bool = false, - /// Open a file with a reparse point and bypass normal reparse point processing for the file. For more information, see the Remarks section. + /// Open a file with a reparse point and bypass normal reparse point + /// processing for the file. For more information, see the Remarks + /// section. OPEN_REPARSE_POINT: bool = false, - /// Instructs any filters that perform offline storage or virtualization to not recall the contents of the file as a result of this open. + /// Instructs any filters that perform offline storage or + /// virtualization to not recall the contents of the file as a result + /// of this open. OPEN_NO_RECALL: bool = false, - /// This flag instructs the file system to capture the user associated with the calling thread. Any subsequent calls to `FltQueryVolumeInformation` or - /// `ZwQueryVolumeInformationFile` using the returned handle will assume the captured user, rather than the calling user at the time, for purposes of computing - /// the free space available to the caller. This applies to the following FsInformationClass values: `FileFsSizeInformation`, `FileFsFullSizeInformation`, and - /// `FileFsFullSizeInformationEx`. + /// This flag instructs the file system to capture the user associated + /// with the calling thread. Any subsequent calls to + /// `FltQueryVolumeInformation` or `ZwQueryVolumeInformationFile` using + /// the returned handle will assume the captured user, rather than the + /// calling user at the time, for purposes of computing the free space + /// available to the caller. This applies to the following + /// FsInformationClass values: `FileFsSizeInformation`, + /// `FileFsFullSizeInformation`, and `FileFsFullSizeInformationEx`. OPEN_FOR_FREE_SPACE_QUERY: bool = false, Reserved24: u8 = 0, @@ -597,7 +648,8 @@ pub const FILE = struct { // ref: km/ntifs.h pub const INFORMATION = extern struct { - /// The set of flags that specify the mode in which the file can be accessed. These flags are a subset of `MODE`. + /// The set of flags that specify the mode in which the file can be + /// accessed. These flags are a subset of `MODE`. Mode: MODE, }; }; diff --git a/lib/std/os/windows/ntdll.zig b/lib/std/os/windows/ntdll.zig index 1cc17c0be6f56ef03909df8af7742c15472c9b93..978987e787f9661259e53335667c76271aa39f99 100644 --- a/lib/std/os/windows/ntdll.zig +++ b/lib/std/os/windows/ntdll.zig @@ -567,9 +567,8 @@ pub extern "ntdll" fn NtWaitForAlertByThreadId( Address: ?*const anyopaque, Timeout: ?*const LARGE_INTEGER, ) callconv(.winapi) NTSTATUS; -pub extern "ntdll" fn NtAlertThreadByThreadId( - ThreadId: DWORD, -) callconv(.winapi) NTSTATUS; +pub extern "ntdll" fn NtAlertThreadByThreadId(ThreadId: DWORD) callconv(.winapi) NTSTATUS; +pub extern "ntdll" fn NtAlertThread(ThreadHandle: HANDLE) callconv(.winapi) NTSTATUS; pub extern "ntdll" fn NtAlertMultipleThreadByThreadId( ThreadIds: [*]const ULONG_PTR, ThreadCount: ULONG, @@ -589,3 +588,16 @@ pub extern "ntdll" fn NtCancelSynchronousIoFile( RequestToCancel: ?*IO_STATUS_BLOCK, IoStatusBlock: *IO_STATUS_BLOCK, ) callconv(.winapi) NTSTATUS; + +pub extern "ntdll" fn NtDelayExecution( + Alertable: BOOLEAN, + DelayInterval: *const LARGE_INTEGER, +) callconv(.winapi) NTSTATUS; + +pub extern "ntdll" fn NtCancelIoFileEx( + FileHandle: HANDLE, + /// Documentation has this as IO_STATUS_BLOCK but it's actually the APC + /// context parameter. + IoRequestToCancel: ?*anyopaque, + IoStatusBlock: *IO_STATUS_BLOCK, +) callconv(.winapi) NTSTATUS; diff --git a/src/link/Lld.zig b/src/link/Lld.zig index dd84da15d41d92f3b3af6454057b58ac72d3da80..a94b1111cd5ee3f5b20ee715d0b557869f0860e4 100644 --- a/src/link/Lld.zig +++ b/src/link/Lld.zig @@ -278,7 +278,7 @@ pub fn flush( }; result catch |err| switch (err) { error.OutOfMemory, error.LinkFailure => |e| return e, - else => |e| return lld.base.comp.link_diags.fail("failed to link with LLD: {s}", .{@errorName(e)}), + else => |e| return lld.base.comp.link_diags.fail("failed to link with LLD: {t}", .{e}), }; } @@ -1630,7 +1630,11 @@ fn spawnLld(comp: *Compilation, arena: Allocator, argv: []const []const u8) !voi }) catch |err| break :term err; var stderr_reader = child.stderr.?.readerStreaming(io, &.{}); - stderr = try stderr_reader.interface.allocRemaining(gpa, .unlimited); + stderr = stderr_reader.interface.allocRemaining(gpa, .unlimited) catch |err| switch (err) { + error.StreamTooLong => unreachable, // unlimited + error.OutOfMemory => |e| return e, + error.ReadFailed => return stderr_reader.err.?, + }; break :term child.wait(io); }) catch |first_err| term: { const err = switch (first_err) { @@ -1682,7 +1686,11 @@ fn spawnLld(comp: *Compilation, arena: Allocator, argv: []const []const u8) !voi break :term rsp_child.wait(io) catch |err| break :err err; } else { var stderr_reader = rsp_child.stderr.?.readerStreaming(io, &.{}); - stderr = try stderr_reader.interface.allocRemaining(gpa, .unlimited); + stderr = stderr_reader.interface.allocRemaining(gpa, .unlimited) catch |err| switch (err) { + error.StreamTooLong => unreachable, // unlimited + error.OutOfMemory => |e| return e, + error.ReadFailed => return stderr_reader.err.?, + }; break :term rsp_child.wait(io) catch |err| break :err err; } }, -- 2.54.0 From 193c747b03da8ec7af55cb5fcb53f8f1bee39d5c Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 22 Jan 2026 19:06:52 -0800 Subject: [PATCH 025/499] link.MappedFile: refactor std.Io -> Io --- src/link/MappedFile.zig | 30 +++++++++++++++--------------- 1 file changed, 15 insertions(+), 15 deletions(-) diff --git a/src/link/MappedFile.zig b/src/link/MappedFile.zig index 2580ed5c2a36cee4d9cc63c35f3843f2b8ba881d..773959fae62a4a8754fac11120c4322e102c102f 100644 --- a/src/link/MappedFile.zig +++ b/src/link/MappedFile.zig @@ -11,7 +11,7 @@ const linux = std.os.linux; const windows = std.os.windows; io: Io, -file: std.Io.File, +file: Io.File, flags: packed struct { block_size: std.mem.Alignment, copy_file_range_unsupported: bool, @@ -29,7 +29,7 @@ writers: std.SinglyLinkedList, pub const growth_factor = 4; -pub const Error = std.posix.MMapError || std.posix.MRemapError || Io.File.LengthError || error{ +pub const Error = Io.File.MemoryMap.CreateError || Io.File.LengthError || error{ NotFile, SystemResources, IsDir, @@ -42,7 +42,7 @@ pub const Error = std.posix.MMapError || std.posix.MRemapError || Io.File.Length NonResizable, }; -pub fn init(file: std.Io.File, gpa: std.mem.Allocator, io: Io) !MappedFile { +pub fn init(file: Io.File, gpa: std.mem.Allocator, io: Io) !MappedFile { var mf: MappedFile = .{ .io = io, .file = file, @@ -396,7 +396,7 @@ pub const Node = extern struct { mf: *MappedFile, writer_node: std.SinglyLinkedList.Node, ni: Node.Index, - interface: std.Io.Writer, + interface: Io.Writer, err: ?Error, pub fn deinit(w: *Writer) void { @@ -404,18 +404,18 @@ pub const Node = extern struct { w.* = undefined; } - const vtable: std.Io.Writer.VTable = .{ + const vtable: Io.Writer.VTable = .{ .drain = drain, .sendFile = sendFile, - .flush = std.Io.Writer.noopFlush, + .flush = Io.Writer.noopFlush, .rebase = growingRebase, }; fn drain( - interface: *std.Io.Writer, + interface: *Io.Writer, data: []const []const u8, splat: usize, - ) std.Io.Writer.Error!usize { + ) Io.Writer.Error!usize { const pattern = data[data.len - 1]; const splat_len = pattern.len * splat; const start_len = interface.end; @@ -442,10 +442,10 @@ pub const Node = extern struct { } fn sendFile( - interface: *std.Io.Writer, - file_reader: *std.Io.File.Reader, - limit: std.Io.Limit, - ) std.Io.Writer.FileError!usize { + interface: *Io.Writer, + file_reader: *Io.File.Reader, + limit: Io.Limit, + ) Io.Writer.FileError!usize { if (limit == .nothing) return 0; const pos = file_reader.logicalPos(); const additional = if (file_reader.getSize()) |size| size - pos else |_| std.atomic.cache_line; @@ -489,10 +489,10 @@ pub const Node = extern struct { } fn growingRebase( - interface: *std.Io.Writer, + interface: *Io.Writer, preserve: usize, unused_capacity: usize, - ) std.Io.Writer.Error!void { + ) Io.Writer.Error!void { _ = preserve; const total_capacity = interface.end + unused_capacity; if (interface.buffer.len >= total_capacity) return; @@ -941,7 +941,7 @@ fn copyRange(mf: *MappedFile, old_file_offset: u64, new_file_offset: u64, size: fn copyFileRange( mf: *MappedFile, - old_file: std.Io.File, + old_file: Io.File, old_file_offset: u64, new_file_offset: u64, size: u64, -- 2.54.0 From 499ba5d55c00b9d32691dc9ff49db49ba6bbded6 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 22 Jan 2026 19:41:13 -0800 Subject: [PATCH 026/499] compiler: use Io.MemoryMap Also make setLength return error.OperationUnsupported when it cannot be done atomically. --- lib/std/Io.zig | 2 +- lib/std/Io/File/MemoryMap.zig | 16 +-- lib/std/Io/Threaded.zig | 36 +----- lib/std/Io/Threaded/test.zig | 9 +- lib/std/Io/test.zig | 11 +- src/link.zig | 10 +- src/link/Elf2.zig | 15 ++- src/link/MappedFile.zig | 203 +++++++++------------------------- 8 files changed, 93 insertions(+), 209 deletions(-) diff --git a/lib/std/Io.zig b/lib/std/Io.zig index bf12ee6c6cd6098e3d4a9e218a3b18326724747f..80a0e248074c08ac83b53151fcd8680b20eadc46 100644 --- a/lib/std/Io.zig +++ b/lib/std/Io.zig @@ -656,7 +656,7 @@ pub const VTable = struct { fileMemoryMapCreate: *const fn (?*anyopaque, File, File.MemoryMap.CreateOptions) File.MemoryMap.CreateError!File.MemoryMap, fileMemoryMapDestroy: *const fn (?*anyopaque, *File.MemoryMap) void, - fileMemoryMapSetLength: *const fn (?*anyopaque, *File.MemoryMap, File.MemoryMap.CreateOptions) File.MemoryMap.SetLengthError!void, + fileMemoryMapSetLength: *const fn (?*anyopaque, *File.MemoryMap, usize) File.MemoryMap.SetLengthError!void, fileMemoryMapRead: *const fn (?*anyopaque, *File.MemoryMap) File.ReadPositionalError!void, fileMemoryMapWrite: *const fn (?*anyopaque, *File.MemoryMap) File.WritePositionalError!void, diff --git a/lib/std/Io/File/MemoryMap.zig b/lib/std/Io/File/MemoryMap.zig index b3196aab3e420a95113822afd4bf99d584556d3e..2c1265d05a3a205bf28f803deae6107ed9ccce05 100644 --- a/lib/std/Io/File/MemoryMap.zig +++ b/lib/std/Io/File/MemoryMap.zig @@ -74,6 +74,9 @@ pub fn destroy(mm: *MemoryMap, io: Io) void { } pub const SetLengthError = error{ + /// Changing the mapping length could not be done atomically. Caller must + /// use `destroy` and `create` to resize the mapping. + OperationUnsupported, /// One of the following: /// * The `File.Kind` is not `file`. /// * The file is not open for reading and read access protections enabled. @@ -91,17 +94,8 @@ pub const SetLengthError = error{ /// of the file after calling this is unspecified until `write` is called. /// /// May change the pointer address of `memory`. -/// -/// `options` is needed because the mapping may need to be destroyed and -/// re-created. All the same options must be provided except for `len` which is -/// the new length. -/// -/// This operation cannot be completed atomically on all operating systems. -/// When this function fails, the `MemoryMap` may be left in an unmapped state, -/// which can be detected by checking if `memory.len` is zero. In such case it -/// is safe to call `destroy` which will have no effect. -pub fn setLength(mm: *MemoryMap, io: Io, options: CreateOptions) SetLengthError!void { - return io.vtable.fileMemoryMapSetLength(io.userdata, mm, options); +pub fn setLength(mm: *MemoryMap, io: Io, new_len: usize) SetLengthError!void { + return io.vtable.fileMemoryMapSetLength(io.userdata, mm, new_len); } /// Synchronizes the contents of `memory` from `file`. diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 42816b52767343192821a7d432568c303bcaeeee..1c588436cea84012ffe653dc19426cecc20c1b1a 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -16466,27 +16466,21 @@ fn fileMemoryMapDestroy(userdata: ?*anyopaque, mm: *File.MemoryMap) void { fn fileMemoryMapSetLength( userdata: ?*anyopaque, mm: *File.MemoryMap, - options: File.MemoryMap.CreateOptions, + new_len: usize, ) File.MemoryMap.SetLengthError!void { const t: *Threaded = @ptrCast(@alignCast(userdata)); const page_size = std.heap.pageSize(); const alignment: Alignment = .fromByteUnits(page_size); const page_align = std.heap.page_size_min; const old_memory = mm.memory; - const new_len = options.len; if (mm.section) |section| { + _ = section; if (alignment.forward(new_len) == alignment.forward(old_memory.len)) { mm.memory.len = new_len; return; } switch (native_os) { - .windows => { - _ = windows.ntdll.NtUnmapViewOfSection(windows.current_process, old_memory.ptr); - windows.CloseHandle(section); - mm.section = windows.INVALID_HANDLE_VALUE; - mm.memory = &.{}; - }, .wasi => unreachable, .linux => { const flags: posix.MREMAP = .{ .MAYMOVE = true }; @@ -16516,31 +16510,7 @@ fn fileMemoryMapSetLength( mm.memory = new_memory; return; }, - else => { - switch (posix.errno(posix.system.munmap(old_memory.ptr, old_memory.len))) { - .SUCCESS => {}, - else => |e| { - if (builtin.mode == .Debug) std.log.err("failed to unmap {d} bytes at {*}: {t}", .{ - old_memory.len, old_memory.ptr, e, - }); - // munmap must be infallible, or we cannot design reliable software. - return error.Unexpected; - }, - } - mm.memory = &.{}; - }, - } - if (createFileMap(mm.file, options.protection, mm.offset, options.populate, new_len)) |result| { - mm.* = result; - return; - } else |err| switch (err) { - error.OperationUnsupported, - error.Unseekable, - error.SectionOversize, - error.MappingAlreadyExists, - error.FileLockConflict, - => return error.Unexpected, // It worked before on the same open file. - else => |e| return e, + else => return error.OperationUnsupported, } } else { const gpa = t.allocator; diff --git a/lib/std/Io/Threaded/test.zig b/lib/std/Io/Threaded/test.zig index 9ab9c6dcd98b51414611c7e492663d6e01aa795f..ffda1e7601c81b790c4bf9148a6835d4f448de88 100644 --- a/lib/std/Io/Threaded/test.zig +++ b/lib/std/Io/Threaded/test.zig @@ -260,7 +260,14 @@ test "memory mapping fallback" { try testing.expectEqualStrings("this9is9my", mm.memory); - try mm.setLength(io, .{ .len = "this9is9my data123".len }); + const new_len = "this9is9my data123".len; + mm.setLength(io, new_len) catch |err| switch (err) { + error.OperationUnsupported => { + mm.destroy(io); + mm = try file.createMemoryMap(io, .{ .len = new_len }); + }, + else => |e| return e, + }; try mm.read(io); try testing.expectEqualStrings("this9is9my data123", mm.memory); diff --git a/lib/std/Io/test.zig b/lib/std/Io/test.zig index 0842f5d092ec2a6d2fb5717f5bfe3515cc82d948..b022b10e6e64c91d358aaba4dfff6bf70c986837 100644 --- a/lib/std/Io/test.zig +++ b/lib/std/Io/test.zig @@ -643,9 +643,14 @@ test "memory mapping" { try expectEqualStrings("this9is9my", mm.memory); // Cross a page boundary to require an actual remap. - try mm.setLength(io, .{ - .len = std.heap.pageSize() * 2, - }); + const new_len = std.heap.pageSize() * 2; + mm.setLength(io, new_len) catch |err| switch (err) { + error.OperationUnsupported => { + mm.destroy(io); + mm = try file.createMemoryMap(io, .{ .len = new_len }); + }, + else => |e| return e, + }; try mm.read(io); try expectEqualStrings("this9is9my data123\x00\x00", mm.memory[0.."this9is9my data123\x00\x00".len]); diff --git a/src/link.zig b/src/link.zig index 0e897232962b739e6b328d9afcc2312e0c99edf0..6f19ec0e583010a18cc450f9ac22629bf09a141a 100644 --- a/src/link.zig +++ b/src/link.zig @@ -651,10 +651,10 @@ pub const File = struct { &coff.mf else unreachable; - mf.file = try base.emit.root_dir.handle.openFile(io, base.emit.sub_path, .{ + mf.memory_map.file = try base.emit.root_dir.handle.openFile(io, base.emit.sub_path, .{ .mode = .read_write, }); - base.file = mf.file; + base.file = mf.memory_map.file; try mf.ensureTotalCapacity(@intCast(mf.nodes.items[0].location().resolve(mf)[1])); }, .c, .spirv => dev.checkAny(&.{ .c_linker, .spirv_linker }), @@ -729,9 +729,9 @@ pub const File = struct { else unreachable; mf.unmap(); - assert(mf.file.handle == f.handle); - mf.file.close(io); - mf.file = undefined; + assert(mf.memory_map.file.handle == f.handle); + mf.memory_map.file.close(io); + mf.memory_map.file = undefined; base.file = null; }, .c, .spirv => dev.checkAny(&.{ .c_linker, .spirv_linker }), diff --git a/src/link/Elf2.zig b/src/link/Elf2.zig index e33cf86696948536db25c5d34b5badb0b454c575..81e6c23af82805f96d09409637b0e14e0dc49165 100644 --- a/src/link/Elf2.zig +++ b/src/link/Elf2.zig @@ -1691,10 +1691,10 @@ fn computeNodeVAddr(elf: *Elf, ni: MappedFile.Node.Index) u64 { } pub fn identClass(elf: *const Elf) std.elf.CLASS { - return @enumFromInt(elf.mf.contents[std.elf.EI.CLASS]); + return @enumFromInt(elf.mf.memory_map.memory[std.elf.EI.CLASS]); } pub fn identData(elf: *const Elf) std.elf.DATA { - return @enumFromInt(elf.mf.contents[std.elf.EI.DATA]); + return @enumFromInt(elf.mf.memory_map.memory[std.elf.EI.DATA]); } pub fn targetEndian(elf: *const Elf) std.builtin.Endian { @@ -2102,7 +2102,7 @@ fn loadObject( log.debug("loadObject({f}{f})", .{ path.fmtEscapeString(), fmtMemberString(member) }); const ident = try r.peek(std.elf.EI.OSABI); if (!std.mem.eql(u8, ident[0..std.elf.MAGIC.len], std.elf.MAGIC)) return error.BadMagic; - if (!std.mem.eql(u8, ident[std.elf.MAGIC.len..], elf.mf.contents[std.elf.MAGIC.len..ident.len])) + if (!std.mem.eql(u8, ident[std.elf.MAGIC.len..], elf.mf.memory_map.memory[std.elf.MAGIC.len..ident.len])) return diags.failParse(path, "bad ident", .{}); try elf.symtab.ensureUnusedCapacity(gpa, 1); try elf.inputs.ensureUnusedCapacity(gpa, 1); @@ -2341,7 +2341,7 @@ fn loadDso(elf: *Elf, path: std.Build.Cache.Path, fr: *Io.File.Reader) !void { log.debug("loadDso({f})", .{path.fmtEscapeString()}); const ident = try r.peek(std.elf.EI.NIDENT); if (!std.mem.eql(u8, ident[0..std.elf.MAGIC.len], std.elf.MAGIC)) return error.BadMagic; - if (!std.mem.eql(u8, ident[std.elf.MAGIC.len..], elf.mf.contents[std.elf.MAGIC.len..ident.len])) + if (!std.mem.eql(u8, ident[std.elf.MAGIC.len..], elf.mf.memory_map.memory[std.elf.MAGIC.len..ident.len])) return diags.failParse(path, "bad ident", .{}); const target_endian = elf.targetEndian(); switch (elf.identClass()) { @@ -3090,9 +3090,14 @@ pub fn flush( tid: Zcu.PerThread.Id, prog_node: std.Progress.Node, ) !void { + const comp = elf.base.comp; _ = arena; _ = prog_node; while (try elf.idle(tid)) {} + elf.mf.flush() catch |err| switch (err) { + error.Canceled => |e| return e, + else => |e| return comp.link_diags.fail("flush write failed: {t}", .{e}), + }; } pub fn idle(elf: *Elf, tid: Zcu.PerThread.Id) !bool { @@ -3839,7 +3844,7 @@ pub fn printNode( const line_len = 0x10; var line_it = std.mem.window( u8, - elf.mf.contents[@intCast(file_loc.offset)..][0..@intCast(file_loc.size)], + elf.mf.memory_map.memory[@intCast(file_loc.offset)..][0..@intCast(file_loc.size)], line_len, line_len, ); diff --git a/src/link/MappedFile.zig b/src/link/MappedFile.zig index 773959fae62a4a8754fac11120c4322e102c102f..8ac2e0c6a57d4b4ee92e608caad0984e11274fe4 100644 --- a/src/link/MappedFile.zig +++ b/src/link/MappedFile.zig @@ -11,15 +11,13 @@ const linux = std.os.linux; const windows = std.os.windows; io: Io, -file: Io.File, flags: packed struct { block_size: std.mem.Alignment, copy_file_range_unsupported: bool, fallocate_punch_hole_unsupported: bool, fallocate_insert_range_unsupported: bool, }, -section: if (is_windows) windows.HANDLE else void, -contents: []align(std.heap.page_size_min) u8, +memory_map: Io.File.MemoryMap, nodes: std.ArrayList(Node), free_ni: Node.Index, large: std.ArrayList(u64), @@ -29,26 +27,20 @@ writers: std.SinglyLinkedList, pub const growth_factor = 4; -pub const Error = Io.File.MemoryMap.CreateError || Io.File.LengthError || error{ +pub const Error = error{ NotFile, - SystemResources, - IsDir, - Unseekable, - NoSpaceLeft, - - InputOutput, - FileTooBig, - FileBusy, - NonResizable, -}; +} || Io.File.MemoryMap.CreateError || Io.File.MemoryMap.SetLengthError || Io.File.WritePositionalError; pub fn init(file: Io.File, gpa: std.mem.Allocator, io: Io) !MappedFile { var mf: MappedFile = .{ .io = io, - .file = file, .flags = undefined, - .section = if (is_windows) windows.INVALID_HANDLE_VALUE else {}, - .contents = &.{}, + .memory_map = .{ + .file = file, + .memory = &.{}, + .offset = 0, + .section = null, + }, .nodes = .empty, .free_ni = .none, .large = .empty, @@ -58,61 +50,9 @@ pub fn init(file: Io.File, gpa: std.mem.Allocator, io: Io) !MappedFile { }; errdefer mf.deinit(gpa); const size: u64, const block_size = stat: { - if (is_windows) { - var sbi: windows.SYSTEM_BASIC_INFORMATION = undefined; - break :stat .{ - try windows.GetFileSizeEx(file.handle), - switch (windows.ntdll.NtQuerySystemInformation( - .SystemBasicInformation, - &sbi, - @sizeOf(windows.SYSTEM_BASIC_INFORMATION), - null, - )) { - .SUCCESS => @max(sbi.PageSize, sbi.AllocationGranularity), - else => std.heap.page_size_max, - }, - }; - } - if (is_linux) { - const use_c = std.c.versionCheck(if (builtin.abi.isAndroid()) - .{ .major = 30, .minor = 0, .patch = 0 } - else - .{ .major = 2, .minor = 28, .patch = 0 }); - const sys = if (use_c) std.c else std.os.linux; - while (true) { - var statx = std.mem.zeroes(linux.Statx); - const rc = sys.statx( - mf.file.handle, - "", - std.posix.AT.EMPTY_PATH, - .{ .TYPE = true, .SIZE = true, .BLOCKS = true }, - &statx, - ); - switch (sys.errno(rc)) { - .SUCCESS => { - assert(statx.mask.TYPE); - assert(statx.mask.SIZE); - assert(statx.mask.BLOCKS); - if (!std.posix.S.ISREG(statx.mode)) return error.PathAlreadyExists; - break :stat .{ statx.size, @max(std.heap.pageSize(), statx.blksize) }; - }, - .INTR => continue, - .ACCES => return error.AccessDenied, - .BADF => if (std.debug.runtime_safety) unreachable else return error.Unexpected, - .FAULT => if (std.debug.runtime_safety) unreachable else return error.Unexpected, - .INVAL => if (std.debug.runtime_safety) unreachable else return error.Unexpected, - .LOOP => return error.SymLinkLoop, - .NAMETOOLONG => return error.NameTooLong, - .NOENT => return error.FileNotFound, - .NOTDIR => return error.FileNotFound, - .NOMEM => return error.SystemResources, - else => |err| return std.posix.unexpectedErrno(err), - } - } - } - const stat = try std.posix.fstat(mf.file.handle); - if (!std.posix.S.ISREG(stat.mode)) return error.PathAlreadyExists; - break :stat .{ @bitCast(stat.size), @max(std.heap.pageSize(), stat.blksize) }; + const stat = try file.stat(io); + if (stat.kind != .file) return error.PathAlreadyExists; + break :stat .{ stat.size, @max(std.heap.pageSize(), stat.block_size) }; }; mf.flags = .{ .block_size = .fromByteUnits(std.math.ceilPowerOfTwoAssert(usize, block_size)), @@ -348,12 +288,12 @@ pub const Node = extern struct { pub fn slice(ni: Node.Index, mf: *const MappedFile) []u8 { const file_loc = ni.fileLocation(mf, true); - return mf.contents[@intCast(file_loc.offset)..][0..@intCast(file_loc.size)]; + return mf.memory_map.memory[@intCast(file_loc.offset)..][0..@intCast(file_loc.size)]; } pub fn sliceConst(ni: Node.Index, mf: *const MappedFile) []const u8 { const file_loc = ni.fileLocation(mf, false); - return mf.contents[@intCast(file_loc.offset)..][0..@intCast(file_loc.size)]; + return mf.memory_map.memory[@intCast(file_loc.offset)..][0..@intCast(file_loc.size)]; } pub fn resize(ni: Node.Index, mf: *MappedFile, gpa: std.mem.Allocator, size: u64) !void { @@ -661,7 +601,8 @@ fn resizeNode(mf: *MappedFile, gpa: std.mem.Allocator, ni: Node.Index, requested // Resize the entire file if (ni == Node.Index.root) { try mf.ensureCapacityForSetLocation(gpa); - try mf.file.setLength(io, new_size); + try mf.memory_map.write(io); + try mf.memory_map.file.setLength(io, new_size); try mf.ensureTotalCapacity(@intCast(new_size)); ni.setLocationAssumeCapacity(mf, old_offset, new_size); return; @@ -685,6 +626,7 @@ fn resizeNode(mf: *MappedFile, gpa: std.mem.Allocator, ni: Node.Index, requested if (is_linux and !mf.flags.fallocate_insert_range_unsupported and node.flags.alignment.order(mf.flags.block_size).compare(.gte)) insert_range: { + try mf.memory_map.write(io); // Ask the filesystem driver to insert extents into the file without copying any data const last_offset, const last_size = parent.last.location(mf).resolve(mf); const last_end = last_offset + last_size; @@ -696,12 +638,12 @@ fn resizeNode(mf: *MappedFile, gpa: std.mem.Allocator, ni: Node.Index, requested _, const file_size = Node.Index.root.location(mf).resolve(mf); while (true) switch (linux.errno(switch (std.math.order(range_file_offset, file_size)) { .lt => linux.fallocate( - mf.file.handle, + mf.memory_map.file.handle, linux.FALLOC.FL_INSERT_RANGE, @intCast(range_file_offset), @intCast(range_size), ), - .eq => linux.ftruncate(mf.file.handle, @intCast(range_file_offset + range_size)), + .eq => linux.ftruncate(mf.memory_map.file.handle, @intCast(range_file_offset + range_size)), .gt => unreachable, })) { .SUCCESS => { @@ -908,7 +850,7 @@ fn moveRange(mf: *MappedFile, old_file_offset: u64, new_file_offset: u64, size: if (is_linux and !mf.flags.fallocate_punch_hole_unsupported and size >= mf.flags.block_size.toByteUnits() * 2 - 1) while (true) switch (linux.errno(linux.fallocate( - mf.file.handle, + mf.memory_map.file.handle, linux.FALLOC.FL_PUNCH_HOLE | linux.FALLOC.FL_KEEP_SIZE, @intCast(old_file_offset), @intCast(size), @@ -928,14 +870,14 @@ fn moveRange(mf: *MappedFile, old_file_offset: u64, new_file_offset: u64, size: .TXTBSY => return error.FileBusy, else => |e| return std.posix.unexpectedErrno(e), }; - @memset(mf.contents[@intCast(old_file_offset)..][0..@intCast(size)], 0); + @memset(mf.memory_map.memory[@intCast(old_file_offset)..][0..@intCast(size)], 0); } fn copyRange(mf: *MappedFile, old_file_offset: u64, new_file_offset: u64, size: u64) !void { - const copy_size = try mf.copyFileRange(mf.file, old_file_offset, new_file_offset, size); + const copy_size = try mf.copyFileRange(mf.memory_map.file, old_file_offset, new_file_offset, size); if (copy_size < size) @memcpy( - mf.contents[@intCast(new_file_offset + copy_size)..][0..@intCast(size - copy_size)], - mf.contents[@intCast(old_file_offset + copy_size)..][0..@intCast(size - copy_size)], + mf.memory_map.memory[@intCast(new_file_offset + copy_size)..][0..@intCast(size - copy_size)], + mf.memory_map.memory[@intCast(old_file_offset + copy_size)..][0..@intCast(size - copy_size)], ); } @@ -946,6 +888,8 @@ fn copyFileRange( new_file_offset: u64, size: u64, ) !u64 { + const io = mf.io; + try mf.memory_map.write(io); var remaining_size = size; if (is_linux and !mf.flags.copy_file_range_unsupported) { var old_file_offset_mut: i64 = @intCast(old_file_offset); @@ -954,7 +898,7 @@ fn copyFileRange( const copy_len = linux.copy_file_range( old_file.handle, &old_file_offset_mut, - mf.file.handle, + mf.memory_map.file.handle, &new_file_offset_mut, @intCast(remaining_size), 0, @@ -990,82 +934,41 @@ fn ensureCapacityForSetLocation(mf: *MappedFile, gpa: std.mem.Allocator) !void { } pub fn ensureTotalCapacity(mf: *MappedFile, new_capacity: usize) !void { - if (mf.contents.len >= new_capacity) return; + if (mf.memory_map.memory.len >= new_capacity) return; try mf.ensureTotalCapacityPrecise(new_capacity +| new_capacity / growth_factor); } pub fn ensureTotalCapacityPrecise(mf: *MappedFile, new_capacity: usize) !void { - if (mf.contents.len >= new_capacity) return; + if (mf.memory_map.memory.len >= new_capacity) return; + const io = mf.io; const aligned_capacity = mf.flags.block_size.forward(new_capacity); - if (!is_linux) mf.unmap() else if (mf.contents.len > 0) { - mf.contents = try std.posix.mremap( - mf.contents.ptr, - mf.contents.len, - aligned_capacity, - .{ .MAYMOVE = true }, - null, - ); - return; - } - if (is_windows) { - if (mf.section == windows.INVALID_HANDLE_VALUE) switch (windows.ntdll.NtCreateSection( - &mf.section, - .{ - .SPECIFIC = .{ .SECTION = .{ - .QUERY = true, - .MAP_WRITE = true, - .MAP_READ = true, - .EXTEND_SIZE = true, - } }, - .STANDARD = .{ .RIGHTS = .REQUIRED }, - }, - null, - @constCast(&@as(i64, @intCast(aligned_capacity))), - .{ .READWRITE = true }, - .{ .COMMIT = true }, - mf.file.handle, - )) { - .SUCCESS => {}, - else => return error.MemoryMappingNotSupported, - }; - var contents_ptr: ?[*]align(std.heap.page_size_min) u8 = null; - var contents_len = aligned_capacity; - switch (windows.ntdll.NtMapViewOfSection( - mf.section, - windows.GetCurrentProcess(), - @ptrCast(&contents_ptr), - null, - 0, - null, - &contents_len, - .Unmap, - .{}, - .{ .READWRITE = true }, - )) { - .SUCCESS => mf.contents = contents_ptr.?[0..contents_len], - else => return error.MemoryMappingNotSupported, + + if (mf.memory_map.memory.len > 0) { + if (mf.memory_map.setLength(io, aligned_capacity)) |_| { + return; + } else |err| switch (err) { + error.OperationUnsupported => {}, + else => |e| return e, } - } else mf.contents = try std.posix.mmap( - null, - aligned_capacity, - .{ .READ = true, .WRITE = true }, - .{ .TYPE = if (is_linux) .SHARED_VALIDATE else .SHARED }, - mf.file.handle, - 0, - ); + unmap(mf); + } + + const file = mf.memory_map.file; + mf.memory_map = try .create(io, file, .{ .len = aligned_capacity }); } pub fn unmap(mf: *MappedFile) void { - if (mf.contents.len == 0) return; - if (is_windows) - _ = windows.ntdll.NtUnmapViewOfSection(windows.GetCurrentProcess(), mf.contents.ptr) - else - std.posix.munmap(mf.contents); - mf.contents = &.{}; - if (is_windows and mf.section != windows.INVALID_HANDLE_VALUE) { - windows.CloseHandle(mf.section); - mf.section = windows.INVALID_HANDLE_VALUE; - } + if (mf.memory_map.memory.len == 0) return; + const io = mf.io; + const file = mf.memory_map.file; + mf.memory_map.destroy(io); + mf.memory_map.memory = &.{}; + mf.memory_map.file = file; +} + +pub fn flush(mf: *MappedFile) Io.File.WritePositionalError!void { + const io = mf.io; + try mf.memory_map.write(io); } fn verify(mf: *MappedFile) void { -- 2.54.0 From 2774436a831194b5a97fc2774ad5570762ed0965 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Fri, 23 Jan 2026 16:17:36 +0100 Subject: [PATCH 027/499] ci: bump riscv64-linux-debug timeout by 1 hour --- .forgejo/workflows/ci.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.forgejo/workflows/ci.yaml b/.forgejo/workflows/ci.yaml index 7cc50e71c302fe054f49f25d3ee8f262a5d4ea77..48813b9001d0463d39b7ce8e2566ae5dad555b87 100644 --- a/.forgejo/workflows/ci.yaml +++ b/.forgejo/workflows/ci.yaml @@ -111,7 +111,7 @@ jobs: fetch-depth: 0 - name: Build and Test run: sh ci/riscv64-linux-debug.sh - timeout-minutes: 600 + timeout-minutes: 660 riscv64-linux-release: if: github.event_name != 'pull_request' runs-on: [self-hosted, riscv64-linux] -- 2.54.0 From 20fae334acb79dd367e74a3b90af5fa1445364cd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Fri, 23 Jan 2026 19:42:23 +0100 Subject: [PATCH 028/499] compiler: UEFI does not support dynamic linking --- src/target.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/target.zig b/src/target.zig index 87d0522ce7277549364ee6baf464320216eb11c8..100c0f690ba8fd099b13fa4e53626c8daf0234c7 100644 --- a/src/target.zig +++ b/src/target.zig @@ -12,7 +12,7 @@ pub const default_stack_protector_buffer_size = 4; pub fn cannotDynamicLink(target: *const std.Target) bool { return switch (target.os.tag) { - .freestanding => true, + .freestanding, .uefi => true, else => target.cpu.arch.isSpirV(), }; } -- 2.54.0 From 909159ad8ea9203297c0b670446381c537554525 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Fri, 23 Jan 2026 19:45:34 +0100 Subject: [PATCH 029/499] compiler: don't enforce PIC for x86-windows and thumb-windows Only x86_64-windows and aarch64-windows actually require PIC. --- src/Compilation/Config.zig | 2 +- src/Package/Module.zig | 2 +- src/Sema.zig | 10 +++++++++- src/target.zig | 33 ++++++++++++++++++++++++++++----- 4 files changed, 39 insertions(+), 8 deletions(-) diff --git a/src/Compilation/Config.zig b/src/Compilation/Config.zig index 1506a58497805ab61d5a12bb944eb21edb475aa1..e4f85b7a48094d82911c1831ca00e30b5f75c85d 100644 --- a/src/Compilation/Config.zig +++ b/src/Compilation/Config.zig @@ -255,7 +255,7 @@ pub fn resolve(options: Options) ResolveError!Config { .Exe => true, }; - if (target_util.cannotDynamicLink(target)) { + if (!target_util.canDynamicLink(target)) { if (options.link_mode == .dynamic) return error.TargetCannotDynamicLink; break :b .static; } diff --git a/src/Package/Module.zig b/src/Package/Module.zig index cd7f573046fd41b8dd15beb54e0a3a189400d690..a922af2da5c26e2c2f2001889c1212d3af57893d 100644 --- a/src/Package/Module.zig +++ b/src/Package/Module.zig @@ -178,7 +178,7 @@ pub fn create(arena: Allocator, options: CreateOptions) !*Package.Module { return error.PieRequiresPic; break :b true; } - if (options.global.link_mode == .dynamic) { + if (options.global.link_mode == .dynamic and target_util.requiresPicForDynamicLink(target)) { if (options.inherited.pic == false) return error.DynamicLinkingRequiresPic; break :b true; diff --git a/src/Sema.zig b/src/Sema.zig index 65311cafc188da131802235a34bc2582e76444a8..191da7c30dda58400f157c61d5739412cf08b7e0 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -9180,7 +9180,15 @@ pub fn handleExternLibName( ); break :blk; } - if (!target.cpu.arch.isWasm() and !block.ownerModule().pic) { + if (!target_util.canDynamicLink(target)) { + return sema.fail( + block, + src_loc, + "dependency on dynamic library '{s}' cannot be satisfied because target does not support dynamic linking", + .{lib_name}, + ); + } + if (!block.ownerModule().pic and target_util.requiresPicForDynamicLink(target)) { return sema.fail( block, src_loc, diff --git a/src/target.zig b/src/target.zig index 100c0f690ba8fd099b13fa4e53626c8daf0234c7..fd6edbf0697958c376e82eaa9704459cec37a07e 100644 --- a/src/target.zig +++ b/src/target.zig @@ -10,10 +10,24 @@ const Feature = @import("Zcu.zig").Feature; pub const default_stack_protector_buffer_size = 4; -pub fn cannotDynamicLink(target: *const std.Target) bool { - return switch (target.os.tag) { - .freestanding, .uefi => true, - else => target.cpu.arch.isSpirV(), +pub fn canDynamicLink(target: *const std.Target) bool { + return switch (target.cpu.arch) { + .amdgcn, + .bpfeb, + .bpfel, + .nvptx, + .nvptx64, + .spirv32, + .spirv64, + => false, + .wasm32, + .wasm64, + => true, + else => switch (target.os.tag) { + // This list is likely incomplete. + .freestanding, .uefi => false, + else => true, + }, }; } @@ -41,11 +55,20 @@ pub fn libCxxNeedsLibUnwind(target: *const std.Target) bool { /// This function returns whether non-pic code is completely invalid on the given target. pub fn requiresPIC(target: *const std.Target, linking_libc: bool) bool { return target.abi.isAndroid() or - target.os.tag == .windows or target.os.tag == .uefi or + ((target.os.tag == .windows or target.os.tag == .uefi) and (target.cpu.arch == .aarch64 or target.cpu.arch == .x86_64)) or target.requiresLibC() or (linking_libc and target.isGnuLibC()); } +pub fn requiresPicForDynamicLink(target: *const std.Target) bool { + assert(canDynamicLink(target)); + + return switch (target.os.tag) { + .windows => target.cpu.arch == .aarch64 or target.cpu.arch == .x86_64, + else => !target.cpu.arch.isWasm(), + }; +} + pub fn picLevel(target: *const std.Target) u32 { // MIPS always uses PIC level 1; other platforms vary in their default PIC levels, but they // support both level 1 and 2, in which case we prefer 2. -- 2.54.0 From c699bb81347dc0c9371c7c6c2cddab457cbe02ba Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Fri, 23 Jan 2026 19:45:39 +0100 Subject: [PATCH 030/499] zig cc: don't bother passing -fPIC to Clang for Windows and UEFI targets It's completely ignored anyway, by design, for compatibility reasons. --- src/target.zig | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/src/target.zig b/src/target.zig index fd6edbf0697958c376e82eaa9704459cec37a07e..f664a354592ddff2d0c8cebbc5b9629d9c4d9c8c 100644 --- a/src/target.zig +++ b/src/target.zig @@ -79,9 +79,7 @@ pub fn picLevel(target: *const std.Target) u32 { /// C compiler argument is valid to Clang. pub fn supports_fpic(target: *const std.Target) bool { return switch (target.os.tag) { - .windows, - .uefi, - => target.abi == .gnu, + .windows, .uefi => false, // Technically allowed for `Abi.gnu`, but completely ignored by Clang (by design) anyway. else => true, }; } -- 2.54.0 From e437efd6015cdc369a2ca632565d105e9f04f20f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Fri, 23 Jan 2026 19:47:12 +0100 Subject: [PATCH 031/499] test: enable thumb-windows-gnu module tests We use long calls for these just like thumb*-linux-* to prevent range issues as the binaries grow larger over time. We also need function and data sections due to the many __stack_chk_guard references within the std test binary; without these options, the linker is not able to insert range thunks in between functions because the std binary just has one giant .text section that's opaque to the linker. closes https://codeberg.org/ziglang/zig/issues/30923 --- test/tests.zig | 65 +++++++++++++++++++++++++++++--------------------- 1 file changed, 38 insertions(+), 27 deletions(-) diff --git a/test/tests.zig b/test/tests.zig index 179f80b3060a48642af431907e220a5966e4887b..ede30a131d804934f8c0b63355965b80c168c35f 100644 --- a/test/tests.zig +++ b/test/tests.zig @@ -28,6 +28,8 @@ const TestTarget = struct { use_lld: ?bool = null, pic: ?bool = null, strip: ?bool = null, + function_sections: ?bool = null, + data_sections: ?bool = null, skip_modules: []const []const u8 = &.{}, // This is intended for targets that, for any reason, shouldn't be run as part of a normal test @@ -40,7 +42,7 @@ const test_targets = blk: { // getBaselineCpuFeatures calls populateDependencies which has a O(N ^ 2) algorithm // (where N is roughly 160, which technically makes it O(1), but it adds up to a // lot of branches) - @setEvalBranchQuota(60000); + @setEvalBranchQuota(80_000); break :blk [_]TestTarget{ // Native Targets @@ -1526,36 +1528,43 @@ const test_targets = blk: { }, .{ - .target = .{ - .cpu_arch = .thumb, - .os_tag = .windows, - .abi = .msvc, - }, + .target = std.Target.Query.parse(.{ + .arch_os_abi = "thumb-windows-msvc", + .cpu_features = "baseline+long_calls", + }) catch unreachable, + .pic = false, // Long calls don't work with PIC. + .function_sections = true, + .data_sections = true, }, .{ - .target = .{ - .cpu_arch = .thumb, - .os_tag = .windows, - .abi = .msvc, - }, + .target = std.Target.Query.parse(.{ + .arch_os_abi = "thumb-windows-msvc", + .cpu_features = "baseline+long_calls", + }) catch unreachable, .link_libc = true, + .pic = false, // Long calls don't work with PIC. + .function_sections = true, + .data_sections = true, + }, + .{ + .target = std.Target.Query.parse(.{ + .arch_os_abi = "thumb-windows-gnu", + .cpu_features = "baseline+long_calls", + }) catch unreachable, + .pic = false, // Long calls don't work with PIC. + .function_sections = true, + .data_sections = true, + }, + .{ + .target = std.Target.Query.parse(.{ + .arch_os_abi = "thumb-windows-gnu", + .cpu_features = "baseline+long_calls", + }) catch unreachable, + .link_libc = true, + .pic = false, // Long calls don't work with PIC. + .function_sections = true, + .data_sections = true, }, - // https://github.com/ziglang/zig/issues/24016 - // .{ - // .target = .{ - // .cpu_arch = .thumb, - // .os_tag = .windows, - // .abi = .gnu, - // }, - // }, - // .{ - // .target = .{ - // .cpu_arch = .thumb, - // .os_tag = .windows, - // .abi = .gnu, - // }, - // .link_libc = true, - // }, .{ .target = .{ @@ -2454,6 +2463,8 @@ fn addOneModuleTest( if (options.build_options) |build_options| { these_tests.root_module.addOptions("build_options", build_options); } + if (test_target.function_sections) |fs| these_tests.link_function_sections = fs; + if (test_target.data_sections) |ds| these_tests.link_data_sections = ds; const single_threaded_suffix = if (test_target.single_threaded == true) "-single" else ""; const backend_suffix = if (test_target.use_llvm == true) "-llvm" -- 2.54.0 From 5c42193b177e4b56ca91acb256cad36dd7cd4dec Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 23 Jan 2026 12:59:36 -0800 Subject: [PATCH 032/499] std.Io.Threaded: rework cancelation Now it can handle sync cancelation and alertable cancelation on Windows. Also fix the API of NtCancelIoFileEx --- lib/std/Io/Threaded.zig | 243 +++++++++++++++++++++++++++-------- lib/std/os/windows/ntdll.zig | 9 +- 2 files changed, 193 insertions(+), 59 deletions(-) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 42816b52767343192821a7d432568c303bcaeeee..3460a05d7543f28b579dff5fb62c6a2188dde7b6 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -339,7 +339,8 @@ const Group = struct { .canceled => true, .parked => unreachable, .blocked => unreachable, - .blocked_windows_dns => unreachable, + .blocked_alertable => unreachable, + .blocked_alertable_canceling => unreachable, .blocked_canceling => unreachable, }; if (result) { @@ -542,7 +543,8 @@ const Future = struct { .canceled => true, .parked => unreachable, .blocked => unreachable, - .blocked_windows_dns => unreachable, + .blocked_alertable => unreachable, + .blocked_alertable_canceling => unreachable, .blocked_canceling => unreachable, }; thread.status.store(.{ .cancelation = .none, .awaitable = .null }, .monotonic); @@ -650,9 +652,11 @@ const Thread = struct { /// To request cancelation, set the status to `.blocked_canceling` and repeatedly interrupt the system call until the status changes. blocked = 0b011, - /// Windows-only: the thread is blocked in a call to `GetAddrInfoExW`. - /// To request cancelation, set the status to `.canceling` and call `GetAddrInfoExCancel`. - blocked_windows_dns = 0b010, + /// Windows-only: the thread is blocked in an alertable wait via + /// `NtDelayExecution`. To request cancelation, set the status to + /// `blocked_alertable_canceling` and repeatedly alert the thread + /// until the status changes. + blocked_alertable = 0b010, /// The thread has an outstanding cancelation request but is not in a cancelable operation. /// When it acknowledges the cancelation, it will set the status to `.canceled`. @@ -663,9 +667,16 @@ const Thread = struct { /// will not change for the remainder of this task's execution. canceled = 0b111, - /// The thread is blocked in a cancelable system call, and is being canceled. The thread which triggered the cancelation will send signals to this thread - /// until its status changes. + /// The thread is blocked in a cancelable system call, and is being + /// canceled. The thread which triggered the cancelation will send + /// signals to this thread until its status changes. blocked_canceling = 0b101, + + /// Windows-only: the thread is blocked in an alertable wait via + /// `NtDelayExecution`, and is being canceled. The thread which + /// triggered the cancelation will send signals to this thread + /// until its status changes. + blocked_alertable_canceling = 0b100, }, /// We cannot turn this value back into a pointer. Instead, it exists so that a task can be @@ -694,7 +705,8 @@ const Thread = struct { switch (status.cancelation) { .parked => unreachable, .blocked => unreachable, - .blocked_windows_dns => unreachable, + .blocked_alertable => unreachable, + .blocked_alertable_canceling => unreachable, .blocked_canceling => unreachable, .none, .canceled => {}, .canceling => { @@ -1007,19 +1019,14 @@ const Thread = struct { .monotonic, ) orelse return true, - .blocked_windows_dns => thread.status.cmpxchgWeak( - .{ .cancelation = .blocked_windows_dns, .awaitable = awaitable }, - .{ .cancelation = .canceling, .awaitable = awaitable }, + .blocked_alertable => thread.status.cmpxchgWeak( + .{ .cancelation = .blocked_alertable, .awaitable = awaitable }, + .{ .cancelation = .blocked_alertable_canceling, .awaitable = awaitable }, .monotonic, .monotonic, ) orelse { - if (builtin.target.os.tag != .windows) unreachable; - if (true) { - // TODO: cancel Windows DNS queries. This code path is currently impossible - // as `netLookupFallible` doesn't actually use `.blocked_windows_dns` yet. - unreachable; - } - return false; + if (!is_windows) unreachable; + return true; }, .canceling, .canceled => { @@ -1029,7 +1036,8 @@ const Thread = struct { return false; }, - .blocked_canceling => unreachable, + .blocked_canceling => unreachable, // `awaitable` has not been canceled before now + .blocked_alertable_canceling => unreachable, // `awaitable` has not been canceled before now }; } } @@ -1044,40 +1052,59 @@ const Thread = struct { /// `Group.waitForCancelWithSignaling`: they use exponential backoff starting at a 1us delay and /// doubling each call. In practice, it is rare to send more than one signal. fn signalCanceledSyscall(thread: *Thread, t: *Threaded, awaitable: AwaitableId) bool { - const bad_status: Status = .{ .cancelation = .blocked_canceling, .awaitable = awaitable }; - if (thread.status.load(.monotonic) != bad_status) return false; + const status = thread.status.load(.monotonic); + if (status.awaitable != awaitable) { + // The thread has moved on and is working on something totally different. + return false; + } // The thread ID and/or handle can be read non-atomically because they never change and were // released by the store that made `thread` available to us. - if (std.Thread.use_pthreads) { - return switch (std.c.pthread_kill(thread.handle, .IO)) { - 0 => true, - else => false, - }; - } else switch (builtin.target.os.tag) { - .linux => { - const pid: posix.pid_t = pid: { - const cached_pid = @atomicLoad(Pid, &t.pid, .monotonic); - if (cached_pid != .unknown) break :pid @intFromEnum(cached_pid); - const pid = std.os.linux.getpid(); - @atomicStore(Pid, &t.pid, @enumFromInt(pid), .monotonic); - break :pid pid; - }; - return switch (std.os.linux.tgkill(pid, @bitCast(thread.id), .IO)) { + switch (status.cancelation) { + .blocked_canceling => if (std.Thread.use_pthreads) { + return switch (std.c.pthread_kill(thread.handle, .IO)) { 0 => true, else => false, }; + } else switch (native_os) { + .linux => { + const pid: posix.pid_t = pid: { + const cached_pid = @atomicLoad(Pid, &t.pid, .monotonic); + if (cached_pid != .unknown) break :pid @intFromEnum(cached_pid); + const pid = std.os.linux.getpid(); + @atomicStore(Pid, &t.pid, @enumFromInt(pid), .monotonic); + break :pid pid; + }; + return switch (std.os.linux.tgkill(pid, @bitCast(thread.id), .IO)) { + 0 => true, + else => false, + }; + }, + .windows => { + var iosb: windows.IO_STATUS_BLOCK = undefined; + return switch (windows.ntdll.NtCancelSynchronousIoFile(thread.handle, null, &iosb)) { + .NOT_FOUND => true, // this might mean the operation hasn't started yet + .SUCCESS => false, // the OS confirmed that our cancelation worked + else => false, + }; + }, + else => return false, }, - .windows => { - var iosb: windows.IO_STATUS_BLOCK = undefined; - return switch (windows.ntdll.NtCancelSynchronousIoFile(thread.handle, null, &iosb)) { - .NOT_FOUND => true, // this might mean the operation hasn't started yet - .SUCCESS => false, // the OS confirmed that our cancelation worked + + .blocked_alertable_canceling => { + if (!is_windows) unreachable; + return switch (windows.ntdll.NtAlertThread(thread.handle)) { + .SUCCESS => true, else => false, }; }, - else => return false, + + else => { + // The thread is working on `awaitable`, but no longer needs signaling (they already + // woke up and saw the cancelation). + return false; + }, } } @@ -1118,7 +1145,8 @@ const Syscall = struct { }, .monotonic).cancelation) { .parked => unreachable, .blocked => unreachable, - .blocked_windows_dns => unreachable, + .blocked_alertable => unreachable, + .blocked_alertable_canceling => unreachable, .blocked_canceling => unreachable, .none => return .{ .thread = thread }, // new status is `.blocked` .canceling => return error.Canceled, // new status is `.canceled` @@ -1137,7 +1165,8 @@ const Syscall = struct { }, .monotonic).cancelation) { .none => unreachable, .parked => unreachable, - .blocked_windows_dns => unreachable, + .blocked_alertable => unreachable, + .blocked_alertable_canceling => unreachable, .canceling => unreachable, .canceled => unreachable, .blocked => {}, // new status is `.blocked` (unchanged) @@ -1153,13 +1182,41 @@ const Syscall = struct { }, .monotonic).cancelation) { .none => unreachable, .parked => unreachable, - .blocked_windows_dns => unreachable, + .blocked_alertable => unreachable, + .blocked_alertable_canceling => unreachable, .canceling => unreachable, .canceled => unreachable, .blocked => {}, // new status is `.none` .blocked_canceling => {}, // new status is `.canceling` } } + /// Indicates instead of `NtCancelSynchronousIoFile` we need to use + /// `NtAlertThread` to interrupt the wait. + /// + /// Windows only, called from blocked state only. + fn toAlertable(s: Syscall) Io.Cancelable!AlertableSyscall { + comptime assert(is_windows); + const thread = s.thread orelse return .{ .thread = null }; + var prev = thread.status.load(.monotonic); + while (true) prev = switch (prev.cancelation) { + .none => unreachable, + .parked => unreachable, + .blocked_alertable => unreachable, + .blocked_alertable_canceling => unreachable, + .canceling => unreachable, + .canceled => unreachable, + + .blocked => thread.status.cmpxchgWeak(prev, .{ + .cancelation = .blocked_alertable, + .awaitable = prev.awaitable, + }, .monotonic, .monotonic) orelse return .{ .thread = thread }, + + .blocked_canceling => thread.status.cmpxchgWeak(prev, .{ + .cancelation = .canceled, + .awaitable = prev.awaitable, + }, .monotonic, .monotonic) orelse return error.Canceled, + }; + } /// Convenience wrapper which calls `finish`, then returns `err`. fn fail(s: Syscall, err: anytype) @TypeOf(err) { s.finish(); @@ -1191,6 +1248,72 @@ const Syscall = struct { } }; +const AlertableSyscall = struct { + thread: ?*Thread, + + comptime { + assert(is_windows); + } + + fn checkCancel(s: AlertableSyscall) Io.Cancelable!void { + comptime assert(is_windows); + const thread = s.thread orelse return; + const old_status = thread.status.fetchOr(.{ + .cancelation = @enumFromInt(0b010), + .awaitable = .null, + }, .monotonic); + switch (old_status.cancelation) { + .none => unreachable, + .parked => unreachable, + .blocked => unreachable, + .blocked_canceling => unreachable, + .canceling => unreachable, + .canceled => unreachable, + .blocked_alertable => {}, // new status is `.blocked_alertable` (unchanged) + .blocked_alertable_canceling => { + // New status is `.canceling`---change to `.canceled` before return. + thread.status.store(.{ .cancelation = .canceled, .awaitable = old_status.awaitable }, .monotonic); + return error.Canceled; + }, + } + } + + fn finish(s: AlertableSyscall) void { + comptime assert(is_windows); + const thread = s.thread orelse return; + switch (thread.status.fetchXor(.{ + .cancelation = @enumFromInt(0b010), + .awaitable = .null, + }, .monotonic).cancelation) { + .none => unreachable, + .parked => unreachable, + .blocked => unreachable, + .blocked_canceling => unreachable, + .canceling => unreachable, + .canceled => unreachable, + .blocked_alertable => {}, // new status is `.none` + .blocked_alertable_canceling => {}, // new status is `.canceling` + } + } + + fn fail(s: AlertableSyscall, err: anytype) @TypeOf(err) { + s.finish(); + return err; + } + + fn ntstatusBug(s: AlertableSyscall, status: windows.NTSTATUS) Io.UnexpectedError { + @branchHint(.cold); + s.finish(); + return windows.statusBug(status); + } + + fn unexpectedNtstatus(s: AlertableSyscall, status: windows.NTSTATUS) Io.UnexpectedError { + @branchHint(.cold); + s.finish(); + return windows.unexpectedStatus(status); + } +}; + const max_iovecs_len = 8; const splat_buffer_size = 64; const default_PATH = "/usr/local/bin:/bin/:/usr/bin"; @@ -1977,7 +2100,8 @@ fn groupAsyncEager( .canceled => true, .parked => unreachable, .blocked => unreachable, - .blocked_windows_dns => unreachable, + .blocked_alertable => unreachable, + .blocked_alertable_canceling => unreachable, .blocked_canceling => unreachable, }; } else false; @@ -1988,7 +2112,8 @@ fn groupAsyncEager( .canceled => true, .parked => unreachable, .blocked => unreachable, - .blocked_windows_dns => unreachable, + .blocked_alertable => unreachable, + .blocked_alertable_canceling => unreachable, .blocked_canceling => unreachable, }; } else false; @@ -2167,7 +2292,8 @@ fn recancelInner() void { .canceling => unreachable, // called `recancel` but cancelation was already pending .parked => unreachable, .blocked => unreachable, - .blocked_windows_dns => unreachable, + .blocked_alertable => unreachable, + .blocked_alertable_canceling => unreachable, .blocked_canceling => unreachable, } } @@ -12096,8 +12222,7 @@ fn netLookupFallible( var res: *ws2_32.ADDRINFOEXW = undefined; const timeout: ?*ws2_32.timeval = null; while (true) { - // TODO: hook this up to cancelation with `Thread.Status.cancelation.blocked_windows_dns`. - // See matching TODO in `Thread.cancelAwaitable`. + // TODO: hook this up to cancelation with `NtDelayExecution` and APC callbacks. try Thread.checkCancel(); // TODO make this append to the queue eagerly rather than blocking until the whole thing finishes const rc: ws2_32.WinsockError = @enumFromInt(ws2_32.GetAddrInfoExW(name_w, port_w, .DNS, null, &hints, &res, timeout, null, null, null)); @@ -15780,7 +15905,8 @@ const parking_futex = struct { .canceled => break :cancelable, // status is still `.canceled` .parked => unreachable, .blocked => unreachable, - .blocked_windows_dns => unreachable, + .blocked_alertable => unreachable, + .blocked_alertable_canceling => unreachable, .blocked_canceling => unreachable, } // We could now be unparked for a cancelation at any time! @@ -15831,7 +15957,8 @@ const parking_futex = struct { }, .canceled => unreachable, .blocked => unreachable, - .blocked_windows_dns => unreachable, + .blocked_alertable => unreachable, + .blocked_alertable_canceling => unreachable, .blocked_canceling => unreachable, }, } @@ -15872,7 +15999,8 @@ const parking_futex = struct { .canceling => continue, // race with a canceler who hasn't called `removeCanceledWaiter` yet .canceled => unreachable, .blocked => unreachable, - .blocked_windows_dns => unreachable, + .blocked_alertable => unreachable, + .blocked_alertable_canceling => unreachable, .blocked_canceling => unreachable, } // We're waking this waiter. Remove them from the bucket and add them to our local list. @@ -15938,7 +16066,8 @@ const parking_sleep = struct { .canceled => break :cancelable, // status is still `.canceled` .parked => unreachable, .blocked => unreachable, - .blocked_windows_dns => unreachable, + .blocked_alertable => unreachable, + .blocked_alertable_canceling => unreachable, .blocked_canceling => unreachable, } while (park(deadline, null)) { @@ -15956,7 +16085,8 @@ const parking_sleep = struct { .none => unreachable, .canceled => unreachable, .blocked => unreachable, - .blocked_windows_dns => unreachable, + .blocked_alertable => unreachable, + .blocked_alertable_canceling => unreachable, .blocked_canceling => unreachable, } } else |err| switch (err) { @@ -15975,7 +16105,8 @@ const parking_sleep = struct { .none => unreachable, .canceled => unreachable, .blocked => unreachable, - .blocked_windows_dns => unreachable, + .blocked_alertable => unreachable, + .blocked_alertable_canceling => unreachable, .blocked_canceling => unreachable, }, } diff --git a/lib/std/os/windows/ntdll.zig b/lib/std/os/windows/ntdll.zig index 978987e787f9661259e53335667c76271aa39f99..ee3cdeb069d01719322a634965dfa0a3882e3a9b 100644 --- a/lib/std/os/windows/ntdll.zig +++ b/lib/std/os/windows/ntdll.zig @@ -596,8 +596,11 @@ pub extern "ntdll" fn NtDelayExecution( pub extern "ntdll" fn NtCancelIoFileEx( FileHandle: HANDLE, - /// Documentation has this as IO_STATUS_BLOCK but it's actually the APC - /// context parameter. - IoRequestToCancel: ?*anyopaque, + IoRequestToCancel: *const IO_STATUS_BLOCK, IoStatusBlock: *IO_STATUS_BLOCK, ) callconv(.winapi) NTSTATUS; + +pub extern "ntdll" fn NtCancelIoFile( + handle: HANDLE, + iosbToCancel: *const IO_STATUS_BLOCK, +) callconv(.winapi) NTSTATUS; -- 2.54.0 From 5f950884a1390f135ea825b035b460ff680ebea3 Mon Sep 17 00:00:00 2001 From: Lukas Lalinsky Date: Sat, 24 Jan 2026 12:26:00 +0100 Subject: [PATCH 033/499] std.c: add IPPROTO_RAW for Darwin platforms IPPROTO_RAW (255) was missing from the Darwin/macOS IPPROTO struct, even though it is defined in system headers and supported by the platform. This is a commonly used protocol for raw IP sockets. --- lib/std/c.zig | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/std/c.zig b/lib/std/c.zig index 1fa47a3121e26fcfcad7cfb2d34666b6c4d6a4a0..6bbf3cbe4692dab92e544e066c5a02808d6eae27 100644 --- a/lib/std/c.zig +++ b/lib/std/c.zig @@ -6026,6 +6026,7 @@ pub const IPPROTO = switch (native_os) { pub const UDP = 17; pub const IP = 0; pub const IPV6 = 41; + pub const RAW = 255; }, .freebsd => struct { /// dummy for IP -- 2.54.0 From cf48041b55fbc2df0a2e12ae2a9490fc39e11486 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Sat, 24 Jan 2026 05:30:27 +0100 Subject: [PATCH 034/499] std.Thread.Condition: use pthread_cond_t impl when OS has no futex primitive Same principle as #30835. --- lib/std/Thread/Condition.zig | 65 ++++++++++++++++++++++++++++++++---- 1 file changed, 59 insertions(+), 6 deletions(-) diff --git a/lib/std/Thread/Condition.zig b/lib/std/Thread/Condition.zig index 788b24038e7131ec67a1aaceae2119f94f7cccdb..8917e07a4fee3191c18ffa2c22ca36b0347fb3a8 100644 --- a/lib/std/Thread/Condition.zig +++ b/lib/std/Thread/Condition.zig @@ -107,12 +107,30 @@ pub fn broadcast(self: *Condition) void { self.impl.wake(.all); } -const Impl = if (builtin.single_threaded) - SingleThreadedImpl -else if (builtin.os.tag == .windows) - WindowsImpl -else - FutexImpl; +const Impl = Impl: { + if (builtin.single_threaded) break :Impl SingleThreadedImpl; + if (builtin.os.tag == .windows) break :Impl WindowsImpl; + + if (builtin.os.tag.isDarwin() or + builtin.target.os.tag == .linux or + builtin.target.os.tag == .freebsd or + builtin.target.os.tag == .openbsd or + builtin.target.os.tag == .dragonfly or + builtin.target.cpu.arch.isWasm()) + { + // Futex is the system's synchronization primitive; use that. + break :Impl FutexImpl; + } + + if (std.Thread.use_pthreads) { + // This system doesn't have a futex primitive, so `std.Thread.Futex` is using `PosixImpl`, + // which implements futex *on top of* pthread mutexes and conditions. Therefore, instead + // of going through that long inefficient path, just use pthread condition variable directly. + break :Impl PosixImpl; + } + + break :Impl FutexImpl; +}; const Notify = enum { one, // wake up only one thread @@ -291,6 +309,41 @@ const FutexImpl = struct { } }; +const PosixImpl = struct { + cond: std.c.pthread_cond_t = .{}, + + fn wait(self: *Impl, mutex: *Mutex, timeout: ?u64) error{Timeout}!void { + if (builtin.mode == .Debug) { + mutex.impl.locking_thread.store(0, .unordered); + } + defer if (builtin.mode == .Debug) { + mutex.impl.locking_thread.store(std.Thread.getCurrentId(), .unordered); + }; + + const mtx = if (builtin.mode == .Debug) &mutex.impl.impl.mutex else &mutex.impl.mutex; + + if (timeout) |t| { + switch (std.c.pthread_cond_timedwait(&self.cond, mtx, &.{ + .sec = @intCast(@divFloor(t, std.time.ns_per_s)), + .nsec = @intCast(@mod(t, std.time.ns_per_s)), + })) { + .SUCCESS => return, + .TIMEDOUT => return error.Timeout, + else => unreachable, + } + } + + assert(std.c.pthread_cond_wait(&self.cond, mtx) == .SUCCESS); + } + + fn wake(self: *Impl, comptime notify: Notify) void { + assert(switch (notify) { + .one => std.c.pthread_cond_signal(&self.cond), + .all => std.c.pthread_cond_broadcast(&self.cond), + } == .SUCCESS); + } +}; + test "smoke test" { var mutex = Mutex{}; var cond = Condition{}; -- 2.54.0 From 9d63dfaa81716a87b9c8c6dc5bdf1d07a8278b55 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Sat, 24 Jan 2026 18:30:17 +0100 Subject: [PATCH 035/499] link.Lld: give better exit status information for the lld child process It's not nice to just throw away useful information. --- src/link/Lld.zig | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/src/link/Lld.zig b/src/link/Lld.zig index a94b1111cd5ee3f5b20ee715d0b557869f0860e4..fa94e534593a81c853c85911bf7054af03b65eab 100644 --- a/src/link/Lld.zig +++ b/src/link/Lld.zig @@ -1707,9 +1707,17 @@ fn spawnLld(comp: *Compilation, arena: Allocator, argv: []const []const u8) !voi diags.lockAndParseLldStderr(argv[1], stderr); return error.LinkFailure; }, - else => { + .signal => |sig| { if (comp.clang_passthrough_mode) std.process.abort(); - return diags.fail("{s} terminated with stderr:\n{s}", .{ argv[0], stderr }); + return diags.fail("{s} terminated with signal {t} and stderr:\n{s}", .{ argv[0], sig, stderr }); + }, + .stopped => |sig| { + if (comp.clang_passthrough_mode) std.process.abort(); + return diags.fail("{s} stopped with signal {d} and stderr:\n{s}", .{ argv[0], sig, stderr }); + }, + .unknown => |code| { + if (comp.clang_passthrough_mode) std.process.abort(); + return diags.fail("{s} terminated for unknown reason with code {d} and stderr:\n{s}", .{ argv[0], code, stderr }); }, } -- 2.54.0 From a2ea36a51767659edf7feeee87eb237263e0dc4a Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 21 Jan 2026 18:11:16 -0800 Subject: [PATCH 036/499] zig libc: modify errno helper to eliminate `@intCast` The vast majority of libc functions return `c_int` for the return value, when setting errno. This utility function is for those cases. Other cases can hand-roll the logic, or additional helpers can be added. --- lib/c/common.zig | 24 ++++++++++++++---------- lib/c/sys/utsname.zig | 2 +- 2 files changed, 15 insertions(+), 11 deletions(-) diff --git a/lib/c/common.zig b/lib/c/common.zig index 09dc5a8c0cbf2fe18160a2310ab4d33ffbfa7545..8d2a79db54b33515ef75c98bf5eb1defe23cb19a 100644 --- a/lib/c/common.zig +++ b/lib/c/common.zig @@ -14,16 +14,20 @@ pub const visibility: std.builtin.SymbolVisibility = if (linkage != .internal) else .default; -/// Checks whether the syscall has had an error, storing it in `std.c.errno` and returning -1. -/// Otherwise returns the result. -pub fn linuxErrno(r: usize) isize { - const linux = std.os.linux; - - return switch (linux.errno(r)) { - .SUCCESS => @bitCast(r), - else => |err| blk: { - std.c._errno().* = @intFromEnum(err); - break :blk -1; +/// Given a low-level syscall return value, sets errno and returns `-1`, or on +/// success returns the result. +pub fn errno(syscall_return_value: usize) c_int { + return switch (builtin.os.tag) { + .linux => { + const signed: isize = @bitCast(syscall_return_value); + const casted: c_int = @intCast(signed); + if (casted < 0) { + @branchHint(.unlikely); + std.c._errno().* = -casted; + return -1; + } + return casted; }, + else => comptime unreachable, }; } diff --git a/lib/c/sys/utsname.zig b/lib/c/sys/utsname.zig index 06f849ba6df4e8aa642e1361e6f9e5d57fff7367..6082c1b6cecf6c15c8454cf362fef9e49cc1e3e8 100644 --- a/lib/c/sys/utsname.zig +++ b/lib/c/sys/utsname.zig @@ -13,7 +13,7 @@ comptime { } fn unameLinux(uts: *std.os.linux.utsname) callconv(.c) c_int { - return @intCast(common.linuxErrno(std.os.linux.uname(uts))); + return common.errno(std.os.linux.uname(uts)); } fn unameWasi(uts: *std.c.utsname) callconv(.c) c_int { -- 2.54.0 From d5c3bf25dc7fedfe57b7142828ac83e49dd9f8d0 Mon Sep 17 00:00:00 2001 From: GasInfinity Date: Sat, 24 Jan 2026 15:58:46 +0100 Subject: [PATCH 037/499] feat(std.c): add `_Exit` --- lib/std/c.zig | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/std/c.zig b/lib/std/c.zig index 6bbf3cbe4692dab92e544e066c5a02808d6eae27..e68d5a71c51397b3028da96ee1c2d4f6e6f2a847 100644 --- a/lib/std/c.zig +++ b/lib/std/c.zig @@ -10649,6 +10649,7 @@ pub extern "c" fn fread(noalias ptr: [*]u8, size_of_type: usize, item_count: usi pub extern "c" fn printf(format: [*:0]const u8, ...) c_int; pub extern "c" fn abort() noreturn; pub extern "c" fn exit(code: c_int) noreturn; +pub extern "c" fn _Exit(code: c_int) noreturn; pub extern "c" fn _exit(code: c_int) noreturn; pub extern "c" fn isatty(fd: fd_t) c_int; pub extern "c" fn lseek(fd: fd_t, offset: off_t, whence: whence_t) off_t; -- 2.54.0 From b430cd62e40afd30048a4d9992ec62bb34f5e041 Mon Sep 17 00:00:00 2001 From: GasInfinity Date: Sat, 24 Jan 2026 16:00:16 +0100 Subject: [PATCH 038/499] feat(std.os.linux): add some missing syscalls --- lib/std/os/linux.zig | 32 ++++++++++++++++++++++++++++++-- 1 file changed, 30 insertions(+), 2 deletions(-) diff --git a/lib/std/os/linux.zig b/lib/std/os/linux.zig index 22a5b4299908cb2f6c9b5599a5d5f332c05ce01e..f5868f71e9af718e2464e03a9e241f95f104ae89 100644 --- a/lib/std/os/linux.zig +++ b/lib/std/os/linux.zig @@ -1391,6 +1391,10 @@ pub fn faccessat(dirfd: i32, path: [*:0]const u8, mode: u32, flags: u32) usize { return syscall4(.faccessat2, @as(usize, @bitCast(@as(isize, dirfd))), @intFromPtr(path), mode, flags); } +pub fn acct(path: [*:0]const u8) usize { + return syscall1(.acct, @intFromPtr(path)); +} + pub fn pipe(fd: *[2]i32) usize { if (comptime (native_arch.isMIPS() or native_arch.isSPARC())) { return syscall_pipe(fd); @@ -1601,11 +1605,27 @@ pub fn fchown(fd: i32, owner: uid_t, group: gid_t) usize { } } +pub fn fchownat(fd: i32, path: [*:0]const u8, owner: uid_t, group: gid_t, flags: u32) usize { + return syscall5(.fchownat, @as(usize, @bitCast(@as(isize, fd))), @intFromPtr(path), owner, group, flags); +} + pub fn chown(path: [*:0]const u8, owner: uid_t, group: gid_t) usize { if (@hasField(SYS, "chown32")) { return syscall3(.chown32, @intFromPtr(path), owner, group); - } else { + } else if (@hasField(SYS, "chown")) { return syscall3(.chown, @intFromPtr(path), owner, group); + } else { + return fchownat(AT.FDCWD, path, owner, group, 0); + } +} + +pub fn lchown(path: [*:0]const u8, owner: uid_t, group: gid_t) usize { + if (@hasField(SYS, "lchown32")) { + return syscall3(.lchown32, @intFromPtr(path), owner, group); + } else if (@hasField(SYS, "lchown")) { + return syscall3(.lchown, @intFromPtr(path), owner, group); + } else { + return fchownat(AT.FDCWD, path, owner, group, AT.SYMLINK_NOFOLLOW); } } @@ -2064,7 +2084,11 @@ pub fn setpgid(pid: pid_t, pgid: pid_t) usize { return syscall2(.setpgid, @intCast(pid), @intCast(pgid)); } -pub fn getgroups(size: usize, list: ?*gid_t) usize { +pub fn getpgid(pid: pid_t) usize { + return syscall1(.getpgid, @intCast(pid)); +} + +pub fn getgroups(size: usize, list: ?[*]gid_t) usize { if (@hasField(SYS, "getgroups32")) { return syscall2(.getgroups32, size, @intFromPtr(list)); } else { @@ -2084,6 +2108,10 @@ pub fn setsid() usize { return syscall0(.setsid); } +pub fn getsid(pid: pid_t) usize { + return syscall1(.getsid, @intCast(pid)); +} + pub fn getpid() pid_t { // Casts result to a pid_t, safety-checking >= 0, because getpid() cannot fail return @intCast(@as(u32, @truncate(syscall0(.getpid)))); -- 2.54.0 From 9cf34a8d81f31917acdcf9c25b8cbfe241646c3a Mon Sep 17 00:00:00 2001 From: GasInfinity Date: Sat, 24 Jan 2026 16:01:18 +0100 Subject: [PATCH 039/499] feat(libzigc): move over some linux syscalls * does not move all of them, only those which map almost 1:1 * also removes their musl implementation --- lib/c.zig | 1 + lib/c/sys.zig | 4 + lib/c/sys/capability.zig | 18 +++ lib/c/sys/file.zig | 13 ++ lib/c/sys/mman.zig | 57 ++++++++ lib/c/sys/reboot.zig | 13 ++ lib/c/unistd.zig | 183 ++++++++++++++++++++++++ lib/libc/musl/src/linux/cap.c | 11 -- lib/libc/musl/src/linux/chroot.c | 8 -- lib/libc/musl/src/linux/flock.c | 7 - lib/libc/musl/src/linux/reboot.c | 7 - lib/libc/musl/src/mman/madvise.c | 9 -- lib/libc/musl/src/mman/mincore.c | 8 -- lib/libc/musl/src/mman/mlock.c | 11 -- lib/libc/musl/src/mman/mlockall.c | 7 - lib/libc/musl/src/mman/mprotect.c | 13 -- lib/libc/musl/src/mman/munlock.c | 7 - lib/libc/musl/src/mman/munlockall.c | 7 - lib/libc/musl/src/mman/posix_madvise.c | 9 -- lib/libc/musl/src/process/execve.c | 8 -- lib/libc/musl/src/unistd/_exit.c | 7 - lib/libc/musl/src/unistd/access.c | 12 -- lib/libc/musl/src/unistd/acct.c | 8 -- lib/libc/musl/src/unistd/chdir.c | 7 - lib/libc/musl/src/unistd/chown.c | 12 -- lib/libc/musl/src/unistd/ctermid.c | 7 - lib/libc/musl/src/unistd/dup.c | 7 - lib/libc/musl/src/unistd/fchownat.c | 7 - lib/libc/musl/src/unistd/getegid.c | 7 - lib/libc/musl/src/unistd/geteuid.c | 7 - lib/libc/musl/src/unistd/getgid.c | 7 - lib/libc/musl/src/unistd/getgroups.c | 7 - lib/libc/musl/src/unistd/getpgid.c | 7 - lib/libc/musl/src/unistd/getpgrp.c | 7 - lib/libc/musl/src/unistd/getpid.c | 7 - lib/libc/musl/src/unistd/getppid.c | 7 - lib/libc/musl/src/unistd/getsid.c | 7 - lib/libc/musl/src/unistd/getuid.c | 7 - lib/libc/musl/src/unistd/lchown.c | 12 -- lib/libc/musl/src/unistd/link.c | 12 -- lib/libc/musl/src/unistd/linkat.c | 7 - lib/libc/musl/src/unistd/mips/pipe.s | 20 --- lib/libc/musl/src/unistd/mips64/pipe.s | 19 --- lib/libc/musl/src/unistd/mipsn32/pipe.s | 19 --- lib/libc/musl/src/unistd/pipe.c | 11 -- lib/libc/musl/src/unistd/renameat.c | 11 -- lib/libc/musl/src/unistd/rmdir.c | 12 -- lib/libc/musl/src/unistd/setpgid.c | 7 - lib/libc/musl/src/unistd/setpgrp.c | 6 - lib/libc/musl/src/unistd/symlink.c | 12 -- lib/libc/musl/src/unistd/symlinkat.c | 7 - lib/libc/musl/src/unistd/sync.c | 7 - lib/libc/musl/src/unistd/unlink.c | 12 -- lib/libc/musl/src/unistd/unlinkat.c | 7 - src/libs/musl.zig | 47 ------ 55 files changed, 289 insertions(+), 477 deletions(-) create mode 100644 lib/c/sys/capability.zig create mode 100644 lib/c/sys/file.zig create mode 100644 lib/c/sys/mman.zig create mode 100644 lib/c/sys/reboot.zig create mode 100644 lib/c/unistd.zig delete mode 100644 lib/libc/musl/src/linux/cap.c delete mode 100644 lib/libc/musl/src/linux/chroot.c delete mode 100644 lib/libc/musl/src/linux/flock.c delete mode 100644 lib/libc/musl/src/linux/reboot.c delete mode 100644 lib/libc/musl/src/mman/madvise.c delete mode 100644 lib/libc/musl/src/mman/mincore.c delete mode 100644 lib/libc/musl/src/mman/mlock.c delete mode 100644 lib/libc/musl/src/mman/mlockall.c delete mode 100644 lib/libc/musl/src/mman/mprotect.c delete mode 100644 lib/libc/musl/src/mman/munlock.c delete mode 100644 lib/libc/musl/src/mman/munlockall.c delete mode 100644 lib/libc/musl/src/mman/posix_madvise.c delete mode 100644 lib/libc/musl/src/process/execve.c delete mode 100644 lib/libc/musl/src/unistd/_exit.c delete mode 100644 lib/libc/musl/src/unistd/access.c delete mode 100644 lib/libc/musl/src/unistd/acct.c delete mode 100644 lib/libc/musl/src/unistd/chdir.c delete mode 100644 lib/libc/musl/src/unistd/chown.c delete mode 100644 lib/libc/musl/src/unistd/ctermid.c delete mode 100644 lib/libc/musl/src/unistd/dup.c delete mode 100644 lib/libc/musl/src/unistd/fchownat.c delete mode 100644 lib/libc/musl/src/unistd/getegid.c delete mode 100644 lib/libc/musl/src/unistd/geteuid.c delete mode 100644 lib/libc/musl/src/unistd/getgid.c delete mode 100644 lib/libc/musl/src/unistd/getgroups.c delete mode 100644 lib/libc/musl/src/unistd/getpgid.c delete mode 100644 lib/libc/musl/src/unistd/getpgrp.c delete mode 100644 lib/libc/musl/src/unistd/getpid.c delete mode 100644 lib/libc/musl/src/unistd/getppid.c delete mode 100644 lib/libc/musl/src/unistd/getsid.c delete mode 100644 lib/libc/musl/src/unistd/getuid.c delete mode 100644 lib/libc/musl/src/unistd/lchown.c delete mode 100644 lib/libc/musl/src/unistd/link.c delete mode 100644 lib/libc/musl/src/unistd/linkat.c delete mode 100644 lib/libc/musl/src/unistd/mips/pipe.s delete mode 100644 lib/libc/musl/src/unistd/mips64/pipe.s delete mode 100644 lib/libc/musl/src/unistd/mipsn32/pipe.s delete mode 100644 lib/libc/musl/src/unistd/pipe.c delete mode 100644 lib/libc/musl/src/unistd/renameat.c delete mode 100644 lib/libc/musl/src/unistd/rmdir.c delete mode 100644 lib/libc/musl/src/unistd/setpgid.c delete mode 100644 lib/libc/musl/src/unistd/setpgrp.c delete mode 100644 lib/libc/musl/src/unistd/symlink.c delete mode 100644 lib/libc/musl/src/unistd/symlinkat.c delete mode 100644 lib/libc/musl/src/unistd/sync.c delete mode 100644 lib/libc/musl/src/unistd/unlink.c delete mode 100644 lib/libc/musl/src/unistd/unlinkat.c diff --git a/lib/c.zig b/lib/c.zig index 31176d04c3ead690d17302fcf907afc639ebeced..681a6f73a58d136e9f8f29679cf4c7de91b38286 100644 --- a/lib/c.zig +++ b/lib/c.zig @@ -26,6 +26,7 @@ comptime { _ = @import("c/wchar.zig"); _ = @import("c/sys.zig"); + _ = @import("c/unistd.zig"); if (builtin.target.isMuslLibC() or builtin.target.isWasiLibC()) { // Files specific to musl and wasi-libc. diff --git a/lib/c/sys.zig b/lib/c/sys.zig index d19baf1b0e963777cc9c2f96d6ed0ffb384c8943..71e068616b28b78f8b2f341a313310430106e4cf 100644 --- a/lib/c/sys.zig +++ b/lib/c/sys.zig @@ -1,3 +1,7 @@ comptime { + _ = @import("sys/mman.zig"); + _ = @import("sys/file.zig"); + _ = @import("sys/reboot.zig"); + _ = @import("sys/capability.zig"); _ = @import("sys/utsname.zig"); } diff --git a/lib/c/sys/capability.zig b/lib/c/sys/capability.zig new file mode 100644 index 0000000000000000000000000000000000000000..19718a45e3f925129859d25d738a60b84d4ccd98 --- /dev/null +++ b/lib/c/sys/capability.zig @@ -0,0 +1,18 @@ +const std = @import("std"); +const common = @import("../common.zig"); +const builtin = @import("builtin"); + +comptime { + if (builtin.target.isMuslLibC()) { + @export(&capsetLinux, .{ .name = "capset", .linkage = common.linkage, .visibility = common.visibility }); + @export(&capgetLinux, .{ .name = "capget", .linkage = common.linkage, .visibility = common.visibility }); + } +} + +fn capsetLinux(hdrp: *anyopaque, datap: *anyopaque) callconv(.c) c_int { + return common.errno(std.os.linux.capset(@ptrCast(@alignCast(hdrp)), @ptrCast(@alignCast(datap)))); +} + +fn capgetLinux(hdrp: *anyopaque, datap: *anyopaque) callconv(.c) c_int { + return common.errno(std.os.linux.capget(@ptrCast(@alignCast(hdrp)), @ptrCast(@alignCast(datap)))); +} diff --git a/lib/c/sys/file.zig b/lib/c/sys/file.zig new file mode 100644 index 0000000000000000000000000000000000000000..643d3fcd037f9459cdb679e301c8cd9da3f5f28a --- /dev/null +++ b/lib/c/sys/file.zig @@ -0,0 +1,13 @@ +const std = @import("std"); +const common = @import("../common.zig"); +const builtin = @import("builtin"); + +comptime { + if (builtin.target.isMuslLibC()) { + @export(&flockLinux, .{ .name = "flock", .linkage = common.linkage, .visibility = common.visibility }); + } +} + +fn flockLinux(fd: c_int, operation: c_int) callconv(.c) c_int { + return common.errno(std.os.linux.flock(fd, operation)); +} diff --git a/lib/c/sys/mman.zig b/lib/c/sys/mman.zig new file mode 100644 index 0000000000000000000000000000000000000000..07ce732bba203db6c5314835fadb4f250913e5b0 --- /dev/null +++ b/lib/c/sys/mman.zig @@ -0,0 +1,57 @@ +const std = @import("std"); +const common = @import("../common.zig"); +const builtin = @import("builtin"); + +comptime { + if (builtin.target.isMuslLibC()) { + @export(&madviseLinux, .{ .name = "madvise", .linkage = common.linkage, .visibility = common.visibility }); + @export(&madviseLinux, .{ .name = "__madvise", .linkage = common.linkage, .visibility = common.visibility }); + + @export(&mlockLinux, .{ .name = "mlock", .linkage = common.linkage, .visibility = common.visibility }); + @export(&mlockallLinux, .{ .name = "mlockall", .linkage = common.linkage, .visibility = common.visibility }); + + @export(&mprotectLinux, .{ .name = "mprotect", .linkage = common.linkage, .visibility = common.visibility }); + @export(&mprotectLinux, .{ .name = "__mprotect", .linkage = common.linkage, .visibility = common.visibility }); + + @export(&munlockLinux, .{ .name = "munlock", .linkage = common.linkage, .visibility = common.visibility }); + @export(&munlockallLinux, .{ .name = "munlockall", .linkage = common.linkage, .visibility = common.visibility }); + + @export(&posix_madviseLinux, .{ .name = "posix_madvise", .linkage = common.linkage, .visibility = common.visibility }); + } +} + +fn madviseLinux(addr: *anyopaque, len: usize, advice: c_int) callconv(.c) c_int { + return common.errno(std.os.linux.madvise(@ptrCast(addr), len, @bitCast(advice))); +} + +fn mincoreLinux(addr: *anyopaque, len: usize, vec: [*]u8) callconv(.c) c_int { + return common.errno(std.os.linux.mincore(@ptrCast(addr), len, vec)); +} + +fn mlockLinux(addr: *const anyopaque, len: usize) callconv(.c) c_int { + return common.errno(std.os.linux.mlock(@ptrCast(addr), len)); +} + +fn mlockallLinux(flags: c_int) callconv(.c) c_int { + return common.errno(std.os.linux.mlockall(@bitCast(flags))); +} + +fn mprotectLinux(addr: *anyopaque, len: usize, prot: c_int) callconv(.c) c_int { + const page_size = std.heap.pageSize(); + const start = std.mem.alignBackward(usize, @intFromPtr(addr), page_size); + const aligned_len = std.mem.alignForward(usize, len, page_size); + return common.errno(std.os.linux.mprotect(@ptrFromInt(start), aligned_len, @bitCast(prot))); +} + +fn munlockLinux(addr: *const anyopaque, len: usize) callconv(.c) c_int { + return common.errno(std.os.linux.munlock(@ptrCast(addr), len)); +} + +fn munlockallLinux() callconv(.c) c_int { + return common.errno(std.os.linux.munlockall()); +} + +fn posix_madviseLinux(addr: *anyopaque, len: usize, advice: c_int) callconv(.c) c_int { + if (advice == std.os.linux.MADV.DONTNEED) return 0; + return @intCast(-@as(isize, @bitCast(std.os.linux.madvise(@ptrCast(addr), len, @bitCast(advice))))); +} diff --git a/lib/c/sys/reboot.zig b/lib/c/sys/reboot.zig new file mode 100644 index 0000000000000000000000000000000000000000..8b7b0503bd14ff7868cd17b9b22cc211bf529833 --- /dev/null +++ b/lib/c/sys/reboot.zig @@ -0,0 +1,13 @@ +const std = @import("std"); +const common = @import("../common.zig"); +const builtin = @import("builtin"); + +comptime { + if (builtin.target.isMuslLibC()) { + @export(&rebootLinux, .{ .name = "reboot", .linkage = common.linkage, .visibility = common.visibility }); + } +} + +fn rebootLinux(cmd: c_int) callconv(.c) c_int { + return common.errno(std.os.linux.reboot(.MAGIC1, .MAGIC2, @enumFromInt(cmd), null)); +} diff --git a/lib/c/unistd.zig b/lib/c/unistd.zig new file mode 100644 index 0000000000000000000000000000000000000000..f903ef78a8d16a280b959e48a54b91a7635a5e00 --- /dev/null +++ b/lib/c/unistd.zig @@ -0,0 +1,183 @@ +const std = @import("std"); +const common = @import("common.zig"); +const builtin = @import("builtin"); +const linux = std.os.linux; + +comptime { + if (builtin.target.isMuslLibC()) { + @export(&_exit, .{ .name = "_exit", .linkage = common.linkage, .visibility = common.visibility }); + + @export(&accessLinux, .{ .name = "access", .linkage = common.linkage, .visibility = common.visibility }); + @export(&acctLinux, .{ .name = "acct", .linkage = common.linkage, .visibility = common.visibility }); + @export(&chdirLinux, .{ .name = "chdir", .linkage = common.linkage, .visibility = common.visibility }); + @export(&chownLinux, .{ .name = "chown", .linkage = common.linkage, .visibility = common.visibility }); + @export(&fchownatLinux, .{ .name = "fchownat", .linkage = common.linkage, .visibility = common.visibility }); + @export(&lchownLinux, .{ .name = "lchown", .linkage = common.linkage, .visibility = common.visibility }); + @export(&chrootLinux, .{ .name = "chroot", .linkage = common.linkage, .visibility = common.visibility }); + @export(&ctermidLinux, .{ .name = "ctermid", .linkage = common.linkage, .visibility = common.visibility }); + @export(&dupLinux, .{ .name = "dup", .linkage = common.linkage, .visibility = common.visibility }); + + @export(&getegidLinux, .{ .name = "getegid", .linkage = common.linkage, .visibility = common.visibility }); + @export(&geteuidLinux, .{ .name = "geteuid", .linkage = common.linkage, .visibility = common.visibility }); + @export(&getgidLinux, .{ .name = "getgid", .linkage = common.linkage, .visibility = common.visibility }); + @export(&getgroupsLinux, .{ .name = "getgroups", .linkage = common.linkage, .visibility = common.visibility }); + @export(&getpgidLinux, .{ .name = "getpgid", .linkage = common.linkage, .visibility = common.visibility }); + @export(&getpgrpLinux, .{ .name = "getpgrp", .linkage = common.linkage, .visibility = common.visibility }); + @export(&setpgidLinux, .{ .name = "setpgid", .linkage = common.linkage, .visibility = common.visibility }); + @export(&setpgrpLinux, .{ .name = "setpgrp", .linkage = common.linkage, .visibility = common.visibility }); + @export(&getsidLinux, .{ .name = "getsid", .linkage = common.linkage, .visibility = common.visibility }); + @export(&getpidLinux, .{ .name = "getpid", .linkage = common.linkage, .visibility = common.visibility }); + @export(&getppidLinux, .{ .name = "getppid", .linkage = common.linkage, .visibility = common.visibility }); + @export(&getuidLinux, .{ .name = "getuid", .linkage = common.linkage, .visibility = common.visibility }); + + @export(&rmdirLinux, .{ .name = "rmdir", .linkage = common.linkage, .visibility = common.visibility }); + @export(&linkLinux, .{ .name = "link", .linkage = common.linkage, .visibility = common.visibility }); + @export(&linkatLinux, .{ .name = "linkat", .linkage = common.linkage, .visibility = common.visibility }); + @export(&pipeLinux, .{ .name = "pipe", .linkage = common.linkage, .visibility = common.visibility }); + @export(&renameatLinux, .{ .name = "renameat", .linkage = common.linkage, .visibility = common.visibility }); + @export(&symlinkLinux, .{ .name = "symlink", .linkage = common.linkage, .visibility = common.visibility }); + @export(&symlinkatLinux, .{ .name = "symlinkat", .linkage = common.linkage, .visibility = common.visibility }); + @export(&syncLinux, .{ .name = "sync", .linkage = common.linkage, .visibility = common.visibility }); + @export(&unlinkLinux, .{ .name = "unlink", .linkage = common.linkage, .visibility = common.visibility }); + @export(&unlinkatLinux, .{ .name = "unlinkat", .linkage = common.linkage, .visibility = common.visibility }); + + @export(&execveLinux, .{ .name = "execve", .linkage = common.linkage, .visibility = common.visibility }); + } +} + +fn _exit(exit_code: c_int) callconv(.c) noreturn { + std.c._Exit(exit_code); +} + +fn accessLinux(path: [*:0]const c_char, amode: c_int) callconv(.c) c_int { + return common.errno(linux.access(@ptrCast(path), @bitCast(amode))); +} + +fn acctLinux(path: [*:0]const c_char) callconv(.c) c_int { + return common.errno(linux.acct(@ptrCast(path))); +} + +fn chdirLinux(path: [*:0]const c_char) callconv(.c) c_int { + return common.errno(linux.chdir(@ptrCast(path))); +} + +fn chownLinux(path: [*:0]const c_char, uid: linux.uid_t, gid: linux.gid_t) callconv(.c) c_int { + return common.errno(linux.chown(@ptrCast(path), uid, gid)); +} + +fn fchownatLinux(fd: c_int, path: [*:0]const c_char, uid: linux.uid_t, gid: linux.gid_t, flags: c_int) callconv(.c) c_int { + return common.errno(linux.fchownat(fd, @ptrCast(path), uid, gid, @bitCast(flags))); +} + +fn lchownLinux(path: [*:0]const c_char, uid: linux.uid_t, gid: linux.gid_t) callconv(.c) c_int { + return common.errno(linux.lchown(@ptrCast(path), uid, gid)); +} + +fn chrootLinux(path: [*:0]const c_char) callconv(.c) c_int { + return common.errno(linux.chroot(@ptrCast(path))); +} + +fn ctermidLinux(maybe_path: ?[*]c_char) callconv(.c) [*:0]c_char { + const default_tty = "/dev/tty"; + + return if (maybe_path) |path| blk: { + path[0..(default_tty.len + 1)].* = @bitCast(default_tty.*); + break :blk path[0..default_tty.len :0].ptr; + } else @ptrCast(@constCast(default_tty)); +} + +fn dupLinux(fd: c_int) callconv(.c) c_int { + return common.errno(linux.dup(fd)); +} + +fn getegidLinux() callconv(.c) linux.gid_t { + return linux.getegid(); +} + +fn geteuidLinux() callconv(.c) linux.uid_t { + return linux.geteuid(); +} + +fn getgidLinux() callconv(.c) linux.gid_t { + return linux.getgid(); +} + +fn getgroupsLinux(size: c_int, list: ?[*]linux.gid_t) callconv(.c) c_int { + return common.errno(linux.getgroups(@intCast(size), list)); +} + +fn getpgidLinux(pid: linux.pid_t) callconv(.c) linux.pid_t { + return common.errno(linux.getpgid(pid)); +} + +fn getpgrpLinux() callconv(.c) linux.pid_t { + return @intCast(linux.getpgid(0)); // @intCast as it cannot fail +} + +fn setpgidLinux(pid: linux.pid_t, pgid: linux.pid_t) callconv(.c) c_int { + return common.errno(linux.setpgid(pid, pgid)); +} + +fn setpgrpLinux() callconv(.c) linux.pid_t { + return @intCast(linux.setpgid(0, 0)); // @intCast as it cannot fail +} + +fn getpidLinux() callconv(.c) linux.pid_t { + return linux.getpid(); +} + +fn getppidLinux() callconv(.c) linux.pid_t { + return linux.getppid(); +} + +fn getsidLinux(pid: linux.pid_t) callconv(.c) linux.pid_t { + return common.errno(linux.getsid(pid)); +} + +fn getuidLinux() callconv(.c) linux.uid_t { + return linux.getuid(); +} + +fn linkLinux(old: [*:0]const c_char, new: [*:0]const c_char) callconv(.c) c_int { + return common.errno(linux.link(@ptrCast(old), @ptrCast(new))); +} + +fn linkatLinux(old_fd: c_int, old: [*:0]const c_char, new_fd: c_int, new: [*:0]const c_char, flags: c_int) callconv(.c) c_int { + return common.errno(linux.linkat(old_fd, @ptrCast(old), new_fd, @ptrCast(new), @bitCast(flags))); +} + +fn pipeLinux(fd: *[2]c_int) callconv(.c) c_int { + return common.errno(linux.pipe(@ptrCast(fd))); +} + +fn renameatLinux(old_fd: c_int, old: [*:0]const c_char, new_fd: c_int, new: [*:0]const c_char) callconv(.c) c_int { + return common.errno(linux.renameat(old_fd, @ptrCast(old), new_fd, @ptrCast(new))); +} + +fn rmdirLinux(path: [*:0]const c_char) callconv(.c) c_int { + return common.errno(linux.rmdir(@ptrCast(path))); +} + +fn symlinkLinux(existing: [*:0]const c_char, new: [*:0]const c_char) callconv(.c) c_int { + return common.errno(linux.symlink(@ptrCast(existing), @ptrCast(new))); +} + +fn symlinkatLinux(existing: [*:0]const c_char, fd: c_int, new: [*:0]const c_char) callconv(.c) c_int { + return common.errno(linux.symlinkat(@ptrCast(existing), fd, @ptrCast(new))); +} + +fn syncLinux() callconv(.c) void { + linux.sync(); +} + +fn unlinkLinux(path: [*:0]const c_char) callconv(.c) c_int { + return common.errno(linux.unlink(@ptrCast(path))); +} + +fn unlinkatLinux(fd: c_int, path: [*:0]const c_char, flags: c_int) callconv(.c) c_int { + return common.errno(linux.unlinkat(fd, @ptrCast(path), @bitCast(flags))); +} + +fn execveLinux(path: [*:0]const c_char, argv: [*:null]const ?[*:0]c_char, envp: [*:null]const ?[*:0]c_char) callconv(.c) c_int { + return common.errno(linux.execve(@ptrCast(path), @ptrCast(argv), @ptrCast(envp))); +} diff --git a/lib/libc/musl/src/linux/cap.c b/lib/libc/musl/src/linux/cap.c deleted file mode 100644 index 8d035e07a4c97896e4fbadfd76ba79a40e1a8ea0..0000000000000000000000000000000000000000 --- a/lib/libc/musl/src/linux/cap.c +++ /dev/null @@ -1,11 +0,0 @@ -#include "syscall.h" - -int capset(void *a, void *b) -{ - return syscall(SYS_capset, a, b); -} - -int capget(void *a, void *b) -{ - return syscall(SYS_capget, a, b); -} diff --git a/lib/libc/musl/src/linux/chroot.c b/lib/libc/musl/src/linux/chroot.c deleted file mode 100644 index 0e69f145dee37cbdc865f6cf6e222a1658d58cdc..0000000000000000000000000000000000000000 --- a/lib/libc/musl/src/linux/chroot.c +++ /dev/null @@ -1,8 +0,0 @@ -#define _GNU_SOURCE -#include -#include "syscall.h" - -int chroot(const char *path) -{ - return syscall(SYS_chroot, path); -} diff --git a/lib/libc/musl/src/linux/flock.c b/lib/libc/musl/src/linux/flock.c deleted file mode 100644 index 87aa5cfed275bf475f01bcbf63184415d01745f4..0000000000000000000000000000000000000000 --- a/lib/libc/musl/src/linux/flock.c +++ /dev/null @@ -1,7 +0,0 @@ -#include -#include "syscall.h" - -int flock(int fd, int op) -{ - return syscall(SYS_flock, fd, op); -} diff --git a/lib/libc/musl/src/linux/reboot.c b/lib/libc/musl/src/linux/reboot.c deleted file mode 100644 index 7f12af79bc1ba13220cfca6f0c48357546019246..0000000000000000000000000000000000000000 --- a/lib/libc/musl/src/linux/reboot.c +++ /dev/null @@ -1,7 +0,0 @@ -#include -#include "syscall.h" - -int reboot(int type) -{ - return syscall(SYS_reboot, 0xfee1dead, 672274793, type); -} diff --git a/lib/libc/musl/src/mman/madvise.c b/lib/libc/musl/src/mman/madvise.c deleted file mode 100644 index e0c7c0ec92b81782fdb6424f842756d434e63881..0000000000000000000000000000000000000000 --- a/lib/libc/musl/src/mman/madvise.c +++ /dev/null @@ -1,9 +0,0 @@ -#include -#include "syscall.h" - -int __madvise(void *addr, size_t len, int advice) -{ - return syscall(SYS_madvise, addr, len, advice); -} - -weak_alias(__madvise, madvise); diff --git a/lib/libc/musl/src/mman/mincore.c b/lib/libc/musl/src/mman/mincore.c deleted file mode 100644 index 4bb19f857c66c8114587908d816dcb2d0c9ce2db..0000000000000000000000000000000000000000 --- a/lib/libc/musl/src/mman/mincore.c +++ /dev/null @@ -1,8 +0,0 @@ -#define _GNU_SOURCE -#include -#include "syscall.h" - -int mincore (void *addr, size_t len, unsigned char *vec) -{ - return syscall(SYS_mincore, addr, len, vec); -} diff --git a/lib/libc/musl/src/mman/mlock.c b/lib/libc/musl/src/mman/mlock.c deleted file mode 100644 index 71af582fe6a3eb1538a59a1dded009d16ab062b8..0000000000000000000000000000000000000000 --- a/lib/libc/musl/src/mman/mlock.c +++ /dev/null @@ -1,11 +0,0 @@ -#include -#include "syscall.h" - -int mlock(const void *addr, size_t len) -{ -#ifdef SYS_mlock - return syscall(SYS_mlock, addr, len); -#else - return syscall(SYS_mlock2, addr, len, 0); -#endif -} diff --git a/lib/libc/musl/src/mman/mlockall.c b/lib/libc/musl/src/mman/mlockall.c deleted file mode 100644 index 0ba4e662c8fa42a0bfb213f0ca411cb40826bf22..0000000000000000000000000000000000000000 --- a/lib/libc/musl/src/mman/mlockall.c +++ /dev/null @@ -1,7 +0,0 @@ -#include -#include "syscall.h" - -int mlockall(int flags) -{ - return syscall(SYS_mlockall, flags); -} diff --git a/lib/libc/musl/src/mman/mprotect.c b/lib/libc/musl/src/mman/mprotect.c deleted file mode 100644 index 535787b9ec527bb12acbb80a53aadc4ab760c7b3..0000000000000000000000000000000000000000 --- a/lib/libc/musl/src/mman/mprotect.c +++ /dev/null @@ -1,13 +0,0 @@ -#include -#include "libc.h" -#include "syscall.h" - -int __mprotect(void *addr, size_t len, int prot) -{ - size_t start, end; - start = (size_t)addr & -PAGE_SIZE; - end = (size_t)((char *)addr + len + PAGE_SIZE-1) & -PAGE_SIZE; - return syscall(SYS_mprotect, start, end-start, prot); -} - -weak_alias(__mprotect, mprotect); diff --git a/lib/libc/musl/src/mman/munlock.c b/lib/libc/musl/src/mman/munlock.c deleted file mode 100644 index 2cccef0c5077f1ec7085ff9bbd2158b4fe297033..0000000000000000000000000000000000000000 --- a/lib/libc/musl/src/mman/munlock.c +++ /dev/null @@ -1,7 +0,0 @@ -#include -#include "syscall.h" - -int munlock(const void *addr, size_t len) -{ - return syscall(SYS_munlock, addr, len); -} diff --git a/lib/libc/musl/src/mman/munlockall.c b/lib/libc/musl/src/mman/munlockall.c deleted file mode 100644 index 6e9d39d68480150d43c0e8e9d3c98544798328c5..0000000000000000000000000000000000000000 --- a/lib/libc/musl/src/mman/munlockall.c +++ /dev/null @@ -1,7 +0,0 @@ -#include -#include "syscall.h" - -int munlockall(void) -{ - return syscall(SYS_munlockall); -} diff --git a/lib/libc/musl/src/mman/posix_madvise.c b/lib/libc/musl/src/mman/posix_madvise.c deleted file mode 100644 index e5e5acb84aba85765096b00058c3804aa8a2402a..0000000000000000000000000000000000000000 --- a/lib/libc/musl/src/mman/posix_madvise.c +++ /dev/null @@ -1,9 +0,0 @@ -#define _GNU_SOURCE -#include -#include "syscall.h" - -int posix_madvise(void *addr, size_t len, int advice) -{ - if (advice == MADV_DONTNEED) return 0; - return -__syscall(SYS_madvise, addr, len, advice); -} diff --git a/lib/libc/musl/src/process/execve.c b/lib/libc/musl/src/process/execve.c deleted file mode 100644 index 70286a17397da61e6dc31ff6db7f0c4dd2e77ef4..0000000000000000000000000000000000000000 --- a/lib/libc/musl/src/process/execve.c +++ /dev/null @@ -1,8 +0,0 @@ -#include -#include "syscall.h" - -int execve(const char *path, char *const argv[], char *const envp[]) -{ - /* do we need to use environ if envp is null? */ - return syscall(SYS_execve, path, argv, envp); -} diff --git a/lib/libc/musl/src/unistd/_exit.c b/lib/libc/musl/src/unistd/_exit.c deleted file mode 100644 index 769948232e46e8be03cd2530f37b60b62d864ae1..0000000000000000000000000000000000000000 --- a/lib/libc/musl/src/unistd/_exit.c +++ /dev/null @@ -1,7 +0,0 @@ -#include -#include - -_Noreturn void _exit(int status) -{ - _Exit(status); -} diff --git a/lib/libc/musl/src/unistd/access.c b/lib/libc/musl/src/unistd/access.c deleted file mode 100644 index d6eed6839822f32bebf333218b6e020d08bbbd28..0000000000000000000000000000000000000000 --- a/lib/libc/musl/src/unistd/access.c +++ /dev/null @@ -1,12 +0,0 @@ -#include -#include -#include "syscall.h" - -int access(const char *filename, int amode) -{ -#ifdef SYS_access - return syscall(SYS_access, filename, amode); -#else - return syscall(SYS_faccessat, AT_FDCWD, filename, amode, 0); -#endif -} diff --git a/lib/libc/musl/src/unistd/acct.c b/lib/libc/musl/src/unistd/acct.c deleted file mode 100644 index 308ffc3821f77e8b2ae801c74f21f30cbd551e27..0000000000000000000000000000000000000000 --- a/lib/libc/musl/src/unistd/acct.c +++ /dev/null @@ -1,8 +0,0 @@ -#define _GNU_SOURCE -#include -#include "syscall.h" - -int acct(const char *filename) -{ - return syscall(SYS_acct, filename); -} diff --git a/lib/libc/musl/src/unistd/chdir.c b/lib/libc/musl/src/unistd/chdir.c deleted file mode 100644 index 5ba78b6317dc34ef50bc3278889b357afcfc978b..0000000000000000000000000000000000000000 --- a/lib/libc/musl/src/unistd/chdir.c +++ /dev/null @@ -1,7 +0,0 @@ -#include -#include "syscall.h" - -int chdir(const char *path) -{ - return syscall(SYS_chdir, path); -} diff --git a/lib/libc/musl/src/unistd/chown.c b/lib/libc/musl/src/unistd/chown.c deleted file mode 100644 index 14b032550d675e6594302a6107c13a599d57443a..0000000000000000000000000000000000000000 --- a/lib/libc/musl/src/unistd/chown.c +++ /dev/null @@ -1,12 +0,0 @@ -#include -#include -#include "syscall.h" - -int chown(const char *path, uid_t uid, gid_t gid) -{ -#ifdef SYS_chown - return syscall(SYS_chown, path, uid, gid); -#else - return syscall(SYS_fchownat, AT_FDCWD, path, uid, gid, 0); -#endif -} diff --git a/lib/libc/musl/src/unistd/ctermid.c b/lib/libc/musl/src/unistd/ctermid.c deleted file mode 100644 index 1612770af158ce068e2b9a1ecc89c4d910d76edd..0000000000000000000000000000000000000000 --- a/lib/libc/musl/src/unistd/ctermid.c +++ /dev/null @@ -1,7 +0,0 @@ -#include -#include - -char *ctermid(char *s) -{ - return s ? strcpy(s, "/dev/tty") : "/dev/tty"; -} diff --git a/lib/libc/musl/src/unistd/dup.c b/lib/libc/musl/src/unistd/dup.c deleted file mode 100644 index 7fee01201b82709a3cd0b04b3a457a846243d1a8..0000000000000000000000000000000000000000 --- a/lib/libc/musl/src/unistd/dup.c +++ /dev/null @@ -1,7 +0,0 @@ -#include -#include "syscall.h" - -int dup(int fd) -{ - return syscall(SYS_dup, fd); -} diff --git a/lib/libc/musl/src/unistd/fchownat.c b/lib/libc/musl/src/unistd/fchownat.c deleted file mode 100644 index 62457a3ec0eb794d63043f8dc8be0e32415347b7..0000000000000000000000000000000000000000 --- a/lib/libc/musl/src/unistd/fchownat.c +++ /dev/null @@ -1,7 +0,0 @@ -#include -#include "syscall.h" - -int fchownat(int fd, const char *path, uid_t uid, gid_t gid, int flag) -{ - return syscall(SYS_fchownat, fd, path, uid, gid, flag); -} diff --git a/lib/libc/musl/src/unistd/getegid.c b/lib/libc/musl/src/unistd/getegid.c deleted file mode 100644 index 6287490da2c023153ec0bbe856a7bba3801a9d53..0000000000000000000000000000000000000000 --- a/lib/libc/musl/src/unistd/getegid.c +++ /dev/null @@ -1,7 +0,0 @@ -#include -#include "syscall.h" - -gid_t getegid(void) -{ - return __syscall(SYS_getegid); -} diff --git a/lib/libc/musl/src/unistd/geteuid.c b/lib/libc/musl/src/unistd/geteuid.c deleted file mode 100644 index 88f2cd538252b679b56ae4af475e0ea89b11c204..0000000000000000000000000000000000000000 --- a/lib/libc/musl/src/unistd/geteuid.c +++ /dev/null @@ -1,7 +0,0 @@ -#include -#include "syscall.h" - -uid_t geteuid(void) -{ - return __syscall(SYS_geteuid); -} diff --git a/lib/libc/musl/src/unistd/getgid.c b/lib/libc/musl/src/unistd/getgid.c deleted file mode 100644 index 1c9fe7157b351c8858c2e6d1e2aba96eb2231e20..0000000000000000000000000000000000000000 --- a/lib/libc/musl/src/unistd/getgid.c +++ /dev/null @@ -1,7 +0,0 @@ -#include -#include "syscall.h" - -gid_t getgid(void) -{ - return __syscall(SYS_getgid); -} diff --git a/lib/libc/musl/src/unistd/getgroups.c b/lib/libc/musl/src/unistd/getgroups.c deleted file mode 100644 index 0e6e63af0129d7c9a6cc8ddb8d86a5e534a315b7..0000000000000000000000000000000000000000 --- a/lib/libc/musl/src/unistd/getgroups.c +++ /dev/null @@ -1,7 +0,0 @@ -#include -#include "syscall.h" - -int getgroups(int count, gid_t list[]) -{ - return syscall(SYS_getgroups, count, list); -} diff --git a/lib/libc/musl/src/unistd/getpgid.c b/lib/libc/musl/src/unistd/getpgid.c deleted file mode 100644 index d295bfd59b0748e6ae0dae27ed2365a1888442b1..0000000000000000000000000000000000000000 --- a/lib/libc/musl/src/unistd/getpgid.c +++ /dev/null @@ -1,7 +0,0 @@ -#include -#include "syscall.h" - -pid_t getpgid(pid_t pid) -{ - return syscall(SYS_getpgid, pid); -} diff --git a/lib/libc/musl/src/unistd/getpgrp.c b/lib/libc/musl/src/unistd/getpgrp.c deleted file mode 100644 index 90e9bb07f6e3e19378957524d425847cec6df1c5..0000000000000000000000000000000000000000 --- a/lib/libc/musl/src/unistd/getpgrp.c +++ /dev/null @@ -1,7 +0,0 @@ -#include -#include "syscall.h" - -pid_t getpgrp(void) -{ - return __syscall(SYS_getpgid, 0); -} diff --git a/lib/libc/musl/src/unistd/getpid.c b/lib/libc/musl/src/unistd/getpid.c deleted file mode 100644 index a6d4e6d1bc9fc610a54b3a177a7f34bebfe50eb7..0000000000000000000000000000000000000000 --- a/lib/libc/musl/src/unistd/getpid.c +++ /dev/null @@ -1,7 +0,0 @@ -#include -#include "syscall.h" - -pid_t getpid(void) -{ - return __syscall(SYS_getpid); -} diff --git a/lib/libc/musl/src/unistd/getppid.c b/lib/libc/musl/src/unistd/getppid.c deleted file mode 100644 index 05cade53b689226bff4a6726eafe2e97a5535b01..0000000000000000000000000000000000000000 --- a/lib/libc/musl/src/unistd/getppid.c +++ /dev/null @@ -1,7 +0,0 @@ -#include -#include "syscall.h" - -pid_t getppid(void) -{ - return __syscall(SYS_getppid); -} diff --git a/lib/libc/musl/src/unistd/getsid.c b/lib/libc/musl/src/unistd/getsid.c deleted file mode 100644 index 93ba690e7ec22dde72bcd902924ae89dfb359306..0000000000000000000000000000000000000000 --- a/lib/libc/musl/src/unistd/getsid.c +++ /dev/null @@ -1,7 +0,0 @@ -#include -#include "syscall.h" - -pid_t getsid(pid_t pid) -{ - return syscall(SYS_getsid, pid); -} diff --git a/lib/libc/musl/src/unistd/getuid.c b/lib/libc/musl/src/unistd/getuid.c deleted file mode 100644 index 61309d1b791497663f6942b0b9197078597f14a0..0000000000000000000000000000000000000000 --- a/lib/libc/musl/src/unistd/getuid.c +++ /dev/null @@ -1,7 +0,0 @@ -#include -#include "syscall.h" - -uid_t getuid(void) -{ - return __syscall(SYS_getuid); -} diff --git a/lib/libc/musl/src/unistd/lchown.c b/lib/libc/musl/src/unistd/lchown.c deleted file mode 100644 index ccd5ee0255eea2af0dcec6f134e21ce07a896faf..0000000000000000000000000000000000000000 --- a/lib/libc/musl/src/unistd/lchown.c +++ /dev/null @@ -1,12 +0,0 @@ -#include -#include -#include "syscall.h" - -int lchown(const char *path, uid_t uid, gid_t gid) -{ -#ifdef SYS_lchown - return syscall(SYS_lchown, path, uid, gid); -#else - return syscall(SYS_fchownat, AT_FDCWD, path, uid, gid, AT_SYMLINK_NOFOLLOW); -#endif -} diff --git a/lib/libc/musl/src/unistd/link.c b/lib/libc/musl/src/unistd/link.c deleted file mode 100644 index feec18e533d9595a2f37dd7fc8005b37c79b60ed..0000000000000000000000000000000000000000 --- a/lib/libc/musl/src/unistd/link.c +++ /dev/null @@ -1,12 +0,0 @@ -#include -#include -#include "syscall.h" - -int link(const char *existing, const char *new) -{ -#ifdef SYS_link - return syscall(SYS_link, existing, new); -#else - return syscall(SYS_linkat, AT_FDCWD, existing, AT_FDCWD, new, 0); -#endif -} diff --git a/lib/libc/musl/src/unistd/linkat.c b/lib/libc/musl/src/unistd/linkat.c deleted file mode 100644 index 6a9a0b77591a2273ad99d46cd228ead6a60b73f6..0000000000000000000000000000000000000000 --- a/lib/libc/musl/src/unistd/linkat.c +++ /dev/null @@ -1,7 +0,0 @@ -#include -#include "syscall.h" - -int linkat(int fd1, const char *existing, int fd2, const char *new, int flag) -{ - return syscall(SYS_linkat, fd1, existing, fd2, new, flag); -} diff --git a/lib/libc/musl/src/unistd/mips/pipe.s b/lib/libc/musl/src/unistd/mips/pipe.s deleted file mode 100644 index ba2c39a304da9e5085108f3295857b6902d1c1ad..0000000000000000000000000000000000000000 --- a/lib/libc/musl/src/unistd/mips/pipe.s +++ /dev/null @@ -1,20 +0,0 @@ -.set noreorder - -.global pipe -.type pipe,@function -pipe: - lui $gp, %hi(_gp_disp) - addiu $gp, %lo(_gp_disp) - addu $gp, $gp, $25 - li $2, 4042 - syscall - beq $7, $0, 1f - nop - lw $25, %call16(__syscall_ret)($gp) - jr $25 - subu $4, $0, $2 -1: sw $2, 0($4) - sw $3, 4($4) - move $2, $0 - jr $ra - nop diff --git a/lib/libc/musl/src/unistd/mips64/pipe.s b/lib/libc/musl/src/unistd/mips64/pipe.s deleted file mode 100644 index f8a27dccfbc2fcccaf92b17ddad60bc05079bc99..0000000000000000000000000000000000000000 --- a/lib/libc/musl/src/unistd/mips64/pipe.s +++ /dev/null @@ -1,19 +0,0 @@ -.set noreorder -.global pipe -.type pipe,@function -pipe: - lui $3, %hi(%neg(%gp_rel(pipe))) - daddiu $3, $3, %lo(%neg(%gp_rel(pipe))) - daddu $3, $3, $25 - li $2, 5021 - syscall - beq $7, $0, 1f - nop - ld $25, %got_disp(__syscall_ret)($3) - jr $25 - dsubu $4, $0, $2 -1: sw $2, 0($4) - sw $3, 4($4) - move $2, $0 - jr $ra - nop diff --git a/lib/libc/musl/src/unistd/mipsn32/pipe.s b/lib/libc/musl/src/unistd/mipsn32/pipe.s deleted file mode 100644 index 80f882e2b1c48a2fccf13a778cba02871786fd44..0000000000000000000000000000000000000000 --- a/lib/libc/musl/src/unistd/mipsn32/pipe.s +++ /dev/null @@ -1,19 +0,0 @@ -.set noreorder -.global pipe -.type pipe,@function -pipe: - lui $3, %hi(%neg(%gp_rel(pipe))) - addiu $3, $3, %lo(%neg(%gp_rel(pipe))) - addu $3, $3, $25 - li $2, 6021 - syscall - beq $7, $0, 1f - nop - lw $25, %got_disp(__syscall_ret)($3) - jr $25 - subu $4, $0, $2 -1: sw $2, 0($4) - sw $3, 4($4) - move $2, $0 - jr $ra - nop diff --git a/lib/libc/musl/src/unistd/pipe.c b/lib/libc/musl/src/unistd/pipe.c deleted file mode 100644 index d07b8d24ae3b1f55126700e64994ab55d453fb16..0000000000000000000000000000000000000000 --- a/lib/libc/musl/src/unistd/pipe.c +++ /dev/null @@ -1,11 +0,0 @@ -#include -#include "syscall.h" - -int pipe(int fd[2]) -{ -#ifdef SYS_pipe - return syscall(SYS_pipe, fd); -#else - return syscall(SYS_pipe2, fd, 0); -#endif -} diff --git a/lib/libc/musl/src/unistd/renameat.c b/lib/libc/musl/src/unistd/renameat.c deleted file mode 100644 index c3b40a258b2822214ce05a0cf1788d9af6298067..0000000000000000000000000000000000000000 --- a/lib/libc/musl/src/unistd/renameat.c +++ /dev/null @@ -1,11 +0,0 @@ -#include -#include "syscall.h" - -int renameat(int oldfd, const char *old, int newfd, const char *new) -{ -#ifdef SYS_renameat - return syscall(SYS_renameat, oldfd, old, newfd, new); -#else - return syscall(SYS_renameat2, oldfd, old, newfd, new, 0); -#endif -} diff --git a/lib/libc/musl/src/unistd/rmdir.c b/lib/libc/musl/src/unistd/rmdir.c deleted file mode 100644 index 6825ffc8359a557790d278c8827061b9ed18564a..0000000000000000000000000000000000000000 --- a/lib/libc/musl/src/unistd/rmdir.c +++ /dev/null @@ -1,12 +0,0 @@ -#include -#include -#include "syscall.h" - -int rmdir(const char *path) -{ -#ifdef SYS_rmdir - return syscall(SYS_rmdir, path); -#else - return syscall(SYS_unlinkat, AT_FDCWD, path, AT_REMOVEDIR); -#endif -} diff --git a/lib/libc/musl/src/unistd/setpgid.c b/lib/libc/musl/src/unistd/setpgid.c deleted file mode 100644 index 061606951d4f170ad9bf80b31ee3414a12c64bac..0000000000000000000000000000000000000000 --- a/lib/libc/musl/src/unistd/setpgid.c +++ /dev/null @@ -1,7 +0,0 @@ -#include -#include "syscall.h" - -int setpgid(pid_t pid, pid_t pgid) -{ - return syscall(SYS_setpgid, pid, pgid); -} diff --git a/lib/libc/musl/src/unistd/setpgrp.c b/lib/libc/musl/src/unistd/setpgrp.c deleted file mode 100644 index a2a37f65f3b7ec1c0aff38da28949b1972ec80a5..0000000000000000000000000000000000000000 --- a/lib/libc/musl/src/unistd/setpgrp.c +++ /dev/null @@ -1,6 +0,0 @@ -#include - -pid_t setpgrp(void) -{ - return setpgid(0, 0); -} diff --git a/lib/libc/musl/src/unistd/symlink.c b/lib/libc/musl/src/unistd/symlink.c deleted file mode 100644 index 0973d78a8936bca2cef633f361abd2401bd82f4a..0000000000000000000000000000000000000000 --- a/lib/libc/musl/src/unistd/symlink.c +++ /dev/null @@ -1,12 +0,0 @@ -#include -#include -#include "syscall.h" - -int symlink(const char *existing, const char *new) -{ -#ifdef SYS_symlink - return syscall(SYS_symlink, existing, new); -#else - return syscall(SYS_symlinkat, existing, AT_FDCWD, new); -#endif -} diff --git a/lib/libc/musl/src/unistd/symlinkat.c b/lib/libc/musl/src/unistd/symlinkat.c deleted file mode 100644 index d1c59b4db0991d18e2d15895709d72c636f09556..0000000000000000000000000000000000000000 --- a/lib/libc/musl/src/unistd/symlinkat.c +++ /dev/null @@ -1,7 +0,0 @@ -#include -#include "syscall.h" - -int symlinkat(const char *existing, int fd, const char *new) -{ - return syscall(SYS_symlinkat, existing, fd, new); -} diff --git a/lib/libc/musl/src/unistd/sync.c b/lib/libc/musl/src/unistd/sync.c deleted file mode 100644 index f18765aa85063b6052ae6d6ffd625cfac617e705..0000000000000000000000000000000000000000 --- a/lib/libc/musl/src/unistd/sync.c +++ /dev/null @@ -1,7 +0,0 @@ -#include -#include "syscall.h" - -void sync(void) -{ - __syscall(SYS_sync); -} diff --git a/lib/libc/musl/src/unistd/unlink.c b/lib/libc/musl/src/unistd/unlink.c deleted file mode 100644 index c40c28d50be4e59951afb14dea3436f768e01cc1..0000000000000000000000000000000000000000 --- a/lib/libc/musl/src/unistd/unlink.c +++ /dev/null @@ -1,12 +0,0 @@ -#include -#include -#include "syscall.h" - -int unlink(const char *path) -{ -#ifdef SYS_unlink - return syscall(SYS_unlink, path); -#else - return syscall(SYS_unlinkat, AT_FDCWD, path, 0); -#endif -} diff --git a/lib/libc/musl/src/unistd/unlinkat.c b/lib/libc/musl/src/unistd/unlinkat.c deleted file mode 100644 index e0e25d22a30c47efc6e98df9f2d4c89953039919..0000000000000000000000000000000000000000 --- a/lib/libc/musl/src/unistd/unlinkat.c +++ /dev/null @@ -1,7 +0,0 @@ -#include -#include "syscall.h" - -int unlinkat(int fd, const char *path, int flag) -{ - return syscall(SYS_unlinkat, fd, path, flag); -} diff --git a/src/libs/musl.zig b/src/libs/musl.zig index 295e5169308a3200bbeec50a709b0035b68d72ca..3c2c4dd25fed0d205bbbf9d9be0b9f3fb78467a3 100644 --- a/src/libs/musl.zig +++ b/src/libs/musl.zig @@ -704,8 +704,6 @@ const src_files = [_][]const u8{ "musl/src/linux/arch_prctl.c", "musl/src/linux/brk.c", "musl/src/linux/cache.c", - "musl/src/linux/cap.c", - "musl/src/linux/chroot.c", "musl/src/linux/clock_adjtime.c", "musl/src/linux/clone.c", "musl/src/linux/copy_file_range.c", @@ -713,7 +711,6 @@ const src_files = [_][]const u8{ "musl/src/linux/eventfd.c", "musl/src/linux/fallocate.c", "musl/src/linux/fanotify.c", - "musl/src/linux/flock.c", "musl/src/linux/getdents.c", "musl/src/linux/getrandom.c", "musl/src/linux/gettid.c", @@ -738,7 +735,6 @@ const src_files = [_][]const u8{ "musl/src/linux/pwritev2.c", "musl/src/linux/quotactl.c", "musl/src/linux/readahead.c", - "musl/src/linux/reboot.c", "musl/src/linux/remap_file_pages.c", "musl/src/linux/sbrk.c", "musl/src/linux/sendfile.c", @@ -1157,18 +1153,10 @@ const src_files = [_][]const u8{ "musl/src/misc/syscall.c", "musl/src/misc/syslog.c", "musl/src/misc/wordexp.c", - "musl/src/mman/madvise.c", - "musl/src/mman/mincore.c", - "musl/src/mman/mlockall.c", - "musl/src/mman/mlock.c", "musl/src/mman/mmap.c", - "musl/src/mman/mprotect.c", "musl/src/mman/mremap.c", "musl/src/mman/msync.c", - "musl/src/mman/munlockall.c", - "musl/src/mman/munlock.c", "musl/src/mman/munmap.c", - "musl/src/mman/posix_madvise.c", "musl/src/mman/shm_open.c", "musl/src/mq/mq_close.c", "musl/src/mq/mq_getattr.c", @@ -1304,7 +1292,6 @@ const src_files = [_][]const u8{ "musl/src/process/execle.c", "musl/src/process/execlp.c", "musl/src/process/execv.c", - "musl/src/process/execve.c", "musl/src/process/execvp.c", "musl/src/process/fexecve.c", "musl/src/process/fork.c", @@ -1871,51 +1858,26 @@ const src_files = [_][]const u8{ "musl/src/time/utime.c", "musl/src/time/wcsftime.c", "musl/src/time/__year_to_secs.c", - "musl/src/unistd/access.c", - "musl/src/unistd/acct.c", "musl/src/unistd/alarm.c", - "musl/src/unistd/chdir.c", - "musl/src/unistd/chown.c", "musl/src/unistd/close.c", - "musl/src/unistd/ctermid.c", "musl/src/unistd/dup2.c", "musl/src/unistd/dup3.c", - "musl/src/unistd/dup.c", - "musl/src/unistd/_exit.c", "musl/src/unistd/faccessat.c", "musl/src/unistd/fchdir.c", - "musl/src/unistd/fchownat.c", "musl/src/unistd/fchown.c", "musl/src/unistd/fdatasync.c", "musl/src/unistd/fsync.c", "musl/src/unistd/ftruncate.c", "musl/src/unistd/getcwd.c", - "musl/src/unistd/getegid.c", - "musl/src/unistd/geteuid.c", - "musl/src/unistd/getgid.c", - "musl/src/unistd/getgroups.c", "musl/src/unistd/gethostname.c", "musl/src/unistd/getlogin.c", "musl/src/unistd/getlogin_r.c", - "musl/src/unistd/getpgid.c", - "musl/src/unistd/getpgrp.c", - "musl/src/unistd/getpid.c", - "musl/src/unistd/getppid.c", - "musl/src/unistd/getsid.c", - "musl/src/unistd/getuid.c", "musl/src/unistd/isatty.c", - "musl/src/unistd/lchown.c", - "musl/src/unistd/linkat.c", - "musl/src/unistd/link.c", "musl/src/unistd/lseek.c", - "musl/src/unistd/mips64/pipe.s", "musl/src/unistd/mipsn32/lseek.c", - "musl/src/unistd/mipsn32/pipe.s", - "musl/src/unistd/mips/pipe.s", "musl/src/unistd/nice.c", "musl/src/unistd/pause.c", "musl/src/unistd/pipe2.c", - "musl/src/unistd/pipe.c", "musl/src/unistd/posix_close.c", "musl/src/unistd/pread.c", "musl/src/unistd/preadv.c", @@ -1925,13 +1887,9 @@ const src_files = [_][]const u8{ "musl/src/unistd/readlinkat.c", "musl/src/unistd/readlink.c", "musl/src/unistd/readv.c", - "musl/src/unistd/renameat.c", - "musl/src/unistd/rmdir.c", "musl/src/unistd/setegid.c", "musl/src/unistd/seteuid.c", "musl/src/unistd/setgid.c", - "musl/src/unistd/setpgid.c", - "musl/src/unistd/setpgrp.c", "musl/src/unistd/setregid.c", "musl/src/unistd/setresgid.c", "musl/src/unistd/setresuid.c", @@ -1940,17 +1898,12 @@ const src_files = [_][]const u8{ "musl/src/unistd/setuid.c", "musl/src/unistd/setxid.c", "musl/src/unistd/sleep.c", - "musl/src/unistd/symlinkat.c", - "musl/src/unistd/symlink.c", - "musl/src/unistd/sync.c", "musl/src/unistd/tcgetpgrp.c", "musl/src/unistd/tcsetpgrp.c", "musl/src/unistd/truncate.c", "musl/src/unistd/ttyname.c", "musl/src/unistd/ttyname_r.c", "musl/src/unistd/ualarm.c", - "musl/src/unistd/unlinkat.c", - "musl/src/unistd/unlink.c", "musl/src/unistd/usleep.c", "musl/src/unistd/write.c", "musl/src/unistd/writev.c", -- 2.54.0 From 99ec1ee3536b577bd1d14facde523c503108886d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Sat, 24 Jan 2026 21:07:53 +0100 Subject: [PATCH 040/499] ci: temporarily disable x86_64-netbsd while I investigate failures --- .forgejo/workflows/ci.yaml | 40 +++++++++++++++++++------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/.forgejo/workflows/ci.yaml b/.forgejo/workflows/ci.yaml index 48813b9001d0463d39b7ce8e2566ae5dad555b87..7e758bb67db1b935fd7fb4efeb7174a8a2f92ba1 100644 --- a/.forgejo/workflows/ci.yaml +++ b/.forgejo/workflows/ci.yaml @@ -197,26 +197,26 @@ jobs: run: sh ci/x86_64-linux-release.sh timeout-minutes: 360 - x86_64-netbsd-debug: - runs-on: [self-hosted, x86_64-netbsd] - steps: - - name: Checkout - uses: https://codeberg.org/ziglang/checkout@19af6bac491e2534a4687a50ee84fa7f13258d28 - with: - fetch-depth: 0 - - name: Build and Test - run: sh ci/x86_64-netbsd-debug.sh - timeout-minutes: 120 - x86_64-netbsd-release: - runs-on: [self-hosted, x86_64-netbsd] - steps: - - name: Checkout - uses: https://codeberg.org/ziglang/checkout@19af6bac491e2534a4687a50ee84fa7f13258d28 - with: - fetch-depth: 0 - - name: Build and Test - run: sh ci/x86_64-netbsd-release.sh - timeout-minutes: 120 + #x86_64-netbsd-debug: + # runs-on: [self-hosted, x86_64-netbsd] + # steps: + # - name: Checkout + # uses: https://codeberg.org/ziglang/checkout@19af6bac491e2534a4687a50ee84fa7f13258d28 + # with: + # fetch-depth: 0 + # - name: Build and Test + # run: sh ci/x86_64-netbsd-debug.sh + # timeout-minutes: 120 + #x86_64-netbsd-release: + # runs-on: [self-hosted, x86_64-netbsd] + # steps: + # - name: Checkout + # uses: https://codeberg.org/ziglang/checkout@19af6bac491e2534a4687a50ee84fa7f13258d28 + # with: + # fetch-depth: 0 + # - name: Build and Test + # run: sh ci/x86_64-netbsd-release.sh + # timeout-minutes: 120 x86_64-openbsd-debug: runs-on: [self-hosted, x86_64-openbsd] -- 2.54.0 From 8709f53d440ed8479f711d871a2d6c2c35dc1014 Mon Sep 17 00:00:00 2001 From: Frank Denis Date: Sun, 25 Jan 2026 17:42:01 +0100 Subject: [PATCH 041/499] crypto.ff: allow seamless chaining regardless of representation (#30913) Finite field elements can be in regular or Montgomery form, and chaining different operations use to require manual and error-prone conversions. Now: - `add`, `sub` and `mul` convert the second operand to match the first operand's form - `sq` and `pow` preserve the input's Montgomery form - `toPrimitive` and `toBytes` return `UnexpectedRepresentation` if the element is in Montgomery form, preventing incorrect serialization This is fully backwards compatible and allows seamless chaining of operations regardless of their representation. --- lib/std/crypto/ff.zig | 166 ++++++++++++++++++++++++++++++++++-------- 1 file changed, 134 insertions(+), 32 deletions(-) diff --git a/lib/std/crypto/ff.zig b/lib/std/crypto/ff.zig index ff018b3c9b04cae4114180601dbd9ede8acc2c43..1b89514db3e46967f99aca8b34df2cfc1aadc520 100644 --- a/lib/std/crypto/ff.zig +++ b/lib/std/crypto/ff.zig @@ -329,7 +329,11 @@ fn Fe_(comptime bits: comptime_int) type { /// Converts the field element to a primitive. /// This function may not run in constant time. - pub fn toPrimitive(self: Self, comptime T: type) OverflowError!T { + /// Returns an error if the element is in Montgomery form. + pub fn toPrimitive(self: Self, comptime T: type) (OverflowError || RepresentationError)!T { + if (self.montgomery) { + return error.UnexpectedRepresentation; + } return self.v.toPrimitive(T); } @@ -343,7 +347,11 @@ fn Fe_(comptime bits: comptime_int) type { } /// Converts the field element to a byte string. - pub fn toBytes(self: Self, bytes: []u8, comptime endian: Endian) OverflowError!void { + /// Returns an error if the element is in Montgomery form. + pub fn toBytes(self: Self, bytes: []u8, comptime endian: Endian) (OverflowError || RepresentationError)!void { + if (self.montgomery) { + return error.UnexpectedRepresentation; + } return self.v.toBytes(bytes, endian); } @@ -530,19 +538,46 @@ pub fn Modulus(comptime max_bits: comptime_int) type { /// Adds two field elements (mod m). pub fn add(self: Self, x: Fe, y: Fe) Fe { var out = x; - const overflow = out.v.addWithOverflow(y.v); - const underflow: u1 = @bitCast(ct.limbsCmpLt(out.v, self.v)); - const need_sub = ct.eql(overflow, underflow); - _ = out.v.conditionalSubWithOverflow(need_sub, self.v); - return out; + if (x.montgomery == y.montgomery) { + @branchHint(.likely); + const overflow = out.v.addWithOverflow(y.v); + const underflow: u1 = @bitCast(ct.limbsCmpLt(out.v, self.v)); + const need_sub = ct.eql(overflow, underflow); + _ = out.v.conditionalSubWithOverflow(need_sub, self.v); + return out; + } else { + var y_ = y; + if (y.montgomery) { + self.fromMontgomery(&y_) catch unreachable; + } else { + self.toMontgomery(&y_) catch unreachable; + } + const overflow = out.v.addWithOverflow(y_.v); + const underflow: u1 = @bitCast(ct.limbsCmpLt(out.v, self.v)); + const need_sub = ct.eql(overflow, underflow); + _ = out.v.conditionalSubWithOverflow(need_sub, self.v); + return out; + } } /// Subtracts two field elements (mod m). pub fn sub(self: Self, x: Fe, y: Fe) Fe { var out = x; - const underflow: bool = @bitCast(out.v.subWithOverflow(y.v)); - _ = out.v.conditionalAddWithOverflow(underflow, self.v); - return out; + if (x.montgomery == y.montgomery) { + const underflow: bool = @bitCast(out.v.subWithOverflow(y.v)); + _ = out.v.conditionalAddWithOverflow(underflow, self.v); + return out; + } else { + var y_ = y; + if (y.montgomery) { + self.fromMontgomery(&y_) catch unreachable; + } else { + self.toMontgomery(&y_) catch unreachable; + } + const underflow: bool = @bitCast(out.v.subWithOverflow(y_.v)); + _ = out.v.conditionalAddWithOverflow(underflow, self.v); + return out; + } } /// Converts a field element to the Montgomery form. @@ -663,13 +698,15 @@ pub fn Modulus(comptime max_bits: comptime_int) type { for (e) |b| acc |= b; if (acc == 0) return error.NullExponent; + const was_montgomery = x.montgomery; + var out = self.one(); self.toMontgomery(&out) catch unreachable; if (public and e.len < 3 or (e.len == 3 and e[if (endian == .big) 0 else 2] <= 0b1111)) { // Do not use a precomputation table for short, public exponents var x_m = x; - if (x.montgomery == false) { + if (!x.montgomery) { self.toMontgomery(&x_m) catch unreachable; } var s = switch (endian) { @@ -702,7 +739,7 @@ pub fn Modulus(comptime max_bits: comptime_int) type { } else { // Use a precomputation table for large exponents var pc = [1]Fe{x} ++ [_]Fe{self.zero} ** 14; - if (x.montgomery == false) { + if (!x.montgomery) { self.toMontgomery(&pc[0]) catch unreachable; } for (1..pc.len) |i| { @@ -747,38 +784,55 @@ pub fn Modulus(comptime max_bits: comptime_int) type { } } } - self.fromMontgomery(&out) catch unreachable; + if (!was_montgomery) { + self.fromMontgomery(&out) catch unreachable; + } return out; } /// Multiplies two field elements. + /// Result preserves the first operand's form. pub fn mul(self: Self, x: Fe, y: Fe) Fe { - if (x.montgomery != y.montgomery) { - return self.montgomeryMul(x, y); - } - var a_ = x; - if (x.montgomery == false) { - self.toMontgomery(&a_) catch unreachable; + if (x.montgomery) { + const y_ = if (!y.montgomery) blk: { + var yy = y; + self.toMontgomery(&yy) catch unreachable; + break :blk yy; + } else y; + return self.montgomeryMul(x, y_); } else { - self.fromMontgomery(&a_) catch unreachable; + var x_m = x; + var y_m = if (y.montgomery) blk: { + var yy = y; + self.fromMontgomery(&yy) catch unreachable; + break :blk yy; + } else y; + self.toMontgomery(&x_m) catch unreachable; + self.toMontgomery(&y_m) catch unreachable; + var out = self.montgomeryMul(x_m, y_m); + self.fromMontgomery(&out) catch unreachable; + return out; } - return self.montgomeryMul(a_, y); } /// Squares a field element. pub fn sq(self: Self, x: Fe) Fe { - var out = x; - if (x.montgomery == true) { + if (x.montgomery) { + return self.montgomerySq(x); + } else { + var out = x; + self.toMontgomery(&out) catch unreachable; + out = self.montgomerySq(out); self.fromMontgomery(&out) catch unreachable; + return out; } - out = self.montgomerySq(out); - out.montgomery = false; - self.toMontgomery(&out) catch unreachable; - return out; } /// Returns x^e (mod m) in constant time. - pub fn pow(self: Self, x: Fe, e: Fe) NullExponentError!Fe { + pub fn pow(self: Self, x: Fe, e: Fe) (NullExponentError || RepresentationError)!Fe { + if (e.montgomery) { + return error.UnexpectedRepresentation; + } var buf: [Fe.encoded_bytes]u8 = undefined; e.toBytes(&buf, native_endian) catch unreachable; return self.powWithEncodedExponent(x, &buf, native_endian); @@ -786,7 +840,10 @@ pub fn Modulus(comptime max_bits: comptime_int) type { /// Returns x^e (mod m), assuming that the exponent is public. /// The function remains constant time with respect to `x`. - pub fn powPublic(self: Self, x: Fe, e: Fe) NullExponentError!Fe { + pub fn powPublic(self: Self, x: Fe, e: Fe) (NullExponentError || RepresentationError)!Fe { + if (e.montgomery) { + return error.UnexpectedRepresentation; + } var e_normalized = Fe{ .v = e.v.normalize() }; var buf_: [Fe.encoded_bytes]u8 = undefined; var buf = buf_[0 .. math.divCeil(usize, e_normalized.v.limbs_len * t_bits, 8) catch unreachable]; @@ -927,6 +984,8 @@ test "finite field arithmetic" { try m.toMontgomery(&x); x_y = m.mul(x, y); + try testing.expect(x_y.montgomery); // result preserves first operand's form + try m.fromMontgomery(&x_y); try testing.expectEqual(x_y.toPrimitive(u256), 1666576607955767413750776202132407807424848069716933450241); try m.fromMontgomery(&x); @@ -941,8 +1000,11 @@ test "finite field arithmetic" { const x_pow_y = try m.powPublic(x, y); try testing.expectEqual(x_pow_y.toPrimitive(u256), 1631933139300737762906024873185789093007782131928298618473); + try testing.expect(!x_pow_y.montgomery); try m.toMontgomery(&x); - const x_pow_y2 = try m.powPublic(x, y); + var x_pow_y2 = try m.powPublic(x, y); + try testing.expect(x_pow_y2.montgomery); + try m.fromMontgomery(&x_pow_y2); try m.fromMontgomery(&x); try testing.expect(x_pow_y2.eql(x_pow_y)); try testing.expectError(error.NullExponent, m.powPublic(x, m.zero)); @@ -953,13 +1015,53 @@ test "finite field arithmetic" { const x_sq = m.sq(x); const x_sq2 = m.mul(x, x); + try testing.expect(!x_sq.montgomery); + try testing.expect(!x_sq2.montgomery); try testing.expect(x_sq.eql(x_sq2)); try m.toMontgomery(&x); - const x_sq3 = m.sq(x); - const x_sq4 = m.mul(x, x); + var x_sq3 = m.sq(x); + var x_sq4 = m.mul(x, x); + try testing.expect(x_sq3.montgomery); + try testing.expect(x_sq4.montgomery); + try m.fromMontgomery(&x_sq3); + try m.fromMontgomery(&x_sq4); try testing.expect(x_sq.eql(x_sq3)); try testing.expect(x_sq3.eql(x_sq4)); try m.fromMontgomery(&x); + + var x_mont = x; + try m.toMontgomery(&x_mont); + + // Non-montgomery + montgomery + const add_nm_m = m.add(x, x_mont); + try testing.expect(!add_nm_m.montgomery); + var add_m_nm = m.add(x_mont, x); + try testing.expect(add_m_nm.montgomery); + try m.fromMontgomery(&add_m_nm); + try testing.expect(add_nm_m.eql(add_m_nm)); + + // Non-montgomery - montgomery + const sub_nm_m = m.sub(x, y); + try testing.expect(!sub_nm_m.montgomery); + var y_mont = y; + try m.toMontgomery(&y_mont); + var sub_m_nm = m.sub(x_mont, y); + try testing.expect(sub_m_nm.montgomery); + try m.fromMontgomery(&sub_m_nm); + try testing.expect(sub_nm_m.eql(sub_m_nm)); + + // mul: preserves first operand's form + const mul_nm_m = m.mul(x, x_mont); + try testing.expect(!mul_nm_m.montgomery); + const mul_nm_nm = m.mul(x, x); + try testing.expect(mul_nm_m.eql(mul_nm_nm)); + var mul_m_nm = m.mul(x_mont, x); + try testing.expect(mul_m_nm.montgomery); + try m.fromMontgomery(&mul_m_nm); + try testing.expect(mul_m_nm.eql(mul_nm_nm)); + + try testing.expectEqual(x.toPrimitive(u256), 80169837251094269539116136208111827396136208141182357733); + try testing.expectError(error.UnexpectedRepresentation, x_mont.toPrimitive(u256)); } fn testCt(ct_: anytype) !void { -- 2.54.0 From f186809caf3a14cbfbe6778f946c8597ebe7d561 Mon Sep 17 00:00:00 2001 From: Michael Dusan Date: Sun, 25 Jan 2026 13:03:22 -0500 Subject: [PATCH 042/499] std: impl process.totalSystemMemory for netbsd --- lib/std/process.zig | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/std/process.zig b/lib/std/process.zig index 8c531830e54240c320ffab82f12db006bbdf47d6..dbf2fbe666311a8a7236630e19f20c4b32d54933 100644 --- a/lib/std/process.zig +++ b/lib/std/process.zig @@ -558,14 +558,17 @@ pub fn totalSystemMemory() TotalSystemMemoryError!u64 { // Promote to u64 to avoid overflow on systems where info.totalram is a 32-bit usize return @as(u64, info.totalram) * info.mem_unit; }, - .freebsd => { + .dragonfly, .freebsd, .netbsd => { + const name = if (native_os == .netbsd) "hw.physmem64" else "hw.physmem"; var physmem: c_ulong = undefined; var len: usize = @sizeOf(c_ulong); - posix.sysctlbynameZ("hw.physmem", &physmem, &len, null, 0) catch |err| switch (err) { + posix.sysctlbynameZ(name, &physmem, &len, null, 0) catch |err| switch (err) { + error.PermissionDenied => unreachable, // only when setting values, + error.SystemResources => unreachable, // memory already on the stack error.UnknownName => unreachable, else => return error.UnknownTotalSystemMemory, }; - return @as(u64, @intCast(physmem)); + return @intCast(physmem); }, // whole Darwin family .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => { -- 2.54.0 From f6ed859cb0f6281680675cd6d8ed118ef3500f02 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Mon, 26 Jan 2026 05:35:53 +0100 Subject: [PATCH 043/499] std.zig.target: update glibc triples for loongarch64 targets --- lib/std/zig/target.zig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/std/zig/target.zig b/lib/std/zig/target.zig index 9f1aeb2310acc877a93bd75db4170f2fc1e397ae..b8e13d4a606009e9a6fa551b1af211f7bfb0e37e 100644 --- a/lib/std/zig/target.zig +++ b/lib/std/zig/target.zig @@ -46,8 +46,8 @@ pub const available_libcs = [_]ArchOsAbi{ .{ .arch = .csky, .os = .linux, .abi = .gnueabi, .os_ver = .{ .major = 4, .minor = 20, .patch = 0 }, .glibc_min = .{ .major = 2, .minor = 29, .patch = 0 }, .glibc_triple = "csky-linux-gnuabiv2-soft" }, .{ .arch = .csky, .os = .linux, .abi = .gnueabihf, .os_ver = .{ .major = 4, .minor = 20, .patch = 0 }, .glibc_min = .{ .major = 2, .minor = 29, .patch = 0 }, .glibc_triple = "csky-linux-gnuabiv2" }, .{ .arch = .hexagon, .os = .linux, .abi = .musl, .os_ver = .{ .major = 3, .minor = 2, .patch = 102 } }, - .{ .arch = .loongarch64, .os = .linux, .abi = .gnu, .os_ver = .{ .major = 5, .minor = 19, .patch = 0 }, .glibc_min = .{ .major = 2, .minor = 36, .patch = 0 }, .glibc_triple = "loongarch64-linux-gnu-lp64d" }, - .{ .arch = .loongarch64, .os = .linux, .abi = .gnusf, .os_ver = .{ .major = 5, .minor = 19, .patch = 0 }, .glibc_min = .{ .major = 2, .minor = 36, .patch = 0 }, .glibc_triple = "loongarch64-linux-gnu-lp64s" }, + .{ .arch = .loongarch64, .os = .linux, .abi = .gnu, .os_ver = .{ .major = 5, .minor = 19, .patch = 0 }, .glibc_min = .{ .major = 2, .minor = 36, .patch = 0 }, .glibc_triple = "loongarch64-linux-gnuf64" }, + .{ .arch = .loongarch64, .os = .linux, .abi = .gnusf, .os_ver = .{ .major = 5, .minor = 19, .patch = 0 }, .glibc_min = .{ .major = 2, .minor = 36, .patch = 0 }, .glibc_triple = "loongarch64-linux-gnusf" }, .{ .arch = .loongarch64, .os = .linux, .abi = .musl, .os_ver = .{ .major = 5, .minor = 19, .patch = 0 } }, .{ .arch = .loongarch64, .os = .linux, .abi = .muslsf, .os_ver = .{ .major = 5, .minor = 19, .patch = 0 } }, .{ .arch = .m68k, .os = .linux, .abi = .gnu, .os_ver = .{ .major = 1, .minor = 3, .patch = 94 } }, -- 2.54.0 From 49afd7eee04675a29c388c8b315b66678579075f Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Mon, 26 Jan 2026 05:55:28 +0100 Subject: [PATCH 044/499] libc: update glibc headers to 2.43 --- .../include/aarch64-linux-gnu/bits/fcntl.h | 2 +- .../include/aarch64-linux-gnu/bits/fenv.h | 2 +- .../include/aarch64-linux-gnu/bits/fp-fast.h | 2 +- .../include/aarch64-linux-gnu/bits/hwcap.h | 7 +- .../aarch64-linux-gnu/bits/indirect-return.h | 2 +- .../include/aarch64-linux-gnu/bits/link.h | 2 +- .../aarch64-linux-gnu/bits/long-double.h | 2 +- .../aarch64-linux-gnu/bits/math-vector.h | 42 +++- .../include/aarch64-linux-gnu/bits/mman.h | 2 +- .../include/aarch64-linux-gnu/bits/procfs.h | 2 +- .../bits/pthread_stack_min.h | 2 +- .../bits/pthreadtypes-arch.h | 2 +- .../include/aarch64-linux-gnu/bits/rseq.h | 2 +- .../aarch64-linux-gnu/bits/semaphore.h | 2 +- .../include/aarch64-linux-gnu/bits/setjmp.h | 2 +- .../include/aarch64-linux-gnu/bits/sigstack.h | 2 +- .../aarch64-linux-gnu/bits/struct_rwlock.h | 2 +- .../aarch64-linux-gnu/bits/struct_stat.h | 2 +- .../include/aarch64-linux-gnu/bits/timesize.h | 2 +- .../include/aarch64-linux-gnu/bits/wordsize.h | 2 +- .../finclude/math-vector-fortran.h | 59 ++++- .../include/aarch64-linux-gnu/fpu_control.h | 3 +- lib/libc/include/aarch64-linux-gnu/ieee754.h | 2 +- lib/libc/include/aarch64-linux-gnu/sys/elf.h | 2 +- .../include/aarch64-linux-gnu/sys/ptrace.h | 2 +- .../include/aarch64-linux-gnu/sys/ucontext.h | 4 +- lib/libc/include/aarch64-linux-gnu/sys/user.h | 2 +- lib/libc/include/arc-linux-gnu/bits/fcntl.h | 2 +- lib/libc/include/arc-linux-gnu/bits/fenv.h | 2 +- lib/libc/include/arc-linux-gnu/bits/floatn.h | 2 +- lib/libc/include/arc-linux-gnu/bits/link.h | 2 +- .../include/arc-linux-gnu/bits/long-double.h | 2 +- lib/libc/include/arc-linux-gnu/bits/procfs.h | 2 +- lib/libc/include/arc-linux-gnu/bits/rseq.h | 2 +- lib/libc/include/arc-linux-gnu/bits/setjmp.h | 2 +- .../include/arc-linux-gnu/bits/struct_stat.h | 2 +- .../include/arc-linux-gnu/bits/timesize.h | 2 +- .../include/arc-linux-gnu/bits/wordsize.h | 2 +- lib/libc/include/arc-linux-gnu/fpu_control.h | 2 +- lib/libc/include/arc-linux-gnu/sys/cachectl.h | 2 +- lib/libc/include/arc-linux-gnu/sys/ucontext.h | 2 +- lib/libc/include/arc-linux-gnu/sys/user.h | 2 +- .../arm-linux-gnu/bits/dl_find_object.h | 2 +- lib/libc/include/arm-linux-gnu/bits/fcntl.h | 2 +- lib/libc/include/arm-linux-gnu/bits/fenv.h | 2 +- lib/libc/include/arm-linux-gnu/bits/floatn.h | 2 +- lib/libc/include/arm-linux-gnu/bits/hwcap.h | 2 +- lib/libc/include/arm-linux-gnu/bits/link.h | 2 +- .../include/arm-linux-gnu/bits/long-double.h | 2 +- .../include/arm-linux-gnu/bits/procfs-id.h | 2 +- lib/libc/include/arm-linux-gnu/bits/procfs.h | 2 +- lib/libc/include/arm-linux-gnu/bits/rseq.h | 2 +- lib/libc/include/arm-linux-gnu/bits/setjmp.h | 2 +- lib/libc/include/arm-linux-gnu/bits/shmlba.h | 2 +- .../include/arm-linux-gnu/bits/struct_stat.h | 2 +- .../include/arm-linux-gnu/bits/timesize.h | 2 +- .../include/arm-linux-gnu/bits/typesizes.h | 2 +- .../include/arm-linux-gnu/bits/wordsize.h | 2 +- lib/libc/include/arm-linux-gnu/fpu_control.h | 2 +- lib/libc/include/arm-linux-gnu/sys/ptrace.h | 2 +- lib/libc/include/arm-linux-gnu/sys/ucontext.h | 2 +- lib/libc/include/arm-linux-gnu/sys/user.h | 2 +- lib/libc/include/csky-linux-gnu/bits/fcntl.h | 2 +- lib/libc/include/csky-linux-gnu/bits/fenv.h | 2 +- lib/libc/include/csky-linux-gnu/bits/floatn.h | 2 +- lib/libc/include/csky-linux-gnu/bits/link.h | 2 +- .../include/csky-linux-gnu/bits/long-double.h | 2 +- lib/libc/include/csky-linux-gnu/bits/procfs.h | 2 +- lib/libc/include/csky-linux-gnu/bits/rseq.h | 2 +- lib/libc/include/csky-linux-gnu/bits/setjmp.h | 2 +- lib/libc/include/csky-linux-gnu/bits/shmlba.h | 2 +- lib/libc/include/csky-linux-gnu/bits/statfs.h | 2 +- .../include/csky-linux-gnu/bits/struct_stat.h | 2 +- .../include/csky-linux-gnu/bits/timesize.h | 2 +- .../include/csky-linux-gnu/bits/wordsize.h | 2 +- lib/libc/include/csky-linux-gnu/fpu_control.h | 2 +- .../include/csky-linux-gnu/sys/cachectl.h | 2 +- .../include/csky-linux-gnu/sys/ucontext.h | 2 +- lib/libc/include/csky-linux-gnu/sys/user.h | 2 +- lib/libc/include/generic-glibc/aio.h | 2 +- lib/libc/include/generic-glibc/aliases.h | 2 +- lib/libc/include/generic-glibc/alloca.h | 2 +- lib/libc/include/generic-glibc/ar.h | 2 +- lib/libc/include/generic-glibc/argp.h | 12 +- lib/libc/include/generic-glibc/argz.h | 2 +- lib/libc/include/generic-glibc/arpa/inet.h | 2 +- lib/libc/include/generic-glibc/assert.h | 60 +++++- .../include/generic-glibc/bits/argp-ldbl.h | 2 +- .../generic-glibc/bits/atomic_wide_counter.h | 2 +- .../include/generic-glibc/bits/byteswap.h | 2 +- .../include/generic-glibc/bits/cmathcalls.h | 2 +- .../include/generic-glibc/bits/confname.h | 2 +- lib/libc/include/generic-glibc/bits/cpu-set.h | 2 +- lib/libc/include/generic-glibc/bits/dirent.h | 2 +- .../include/generic-glibc/bits/dirent_ext.h | 2 +- .../generic-glibc/bits/dl_find_object.h | 2 +- lib/libc/include/generic-glibc/bits/dlfcn.h | 2 +- lib/libc/include/generic-glibc/bits/endian.h | 2 +- .../include/generic-glibc/bits/environments.h | 2 +- lib/libc/include/generic-glibc/bits/epoll.h | 2 +- .../include/generic-glibc/bits/err-ldbl.h | 2 +- lib/libc/include/generic-glibc/bits/errno.h | 2 +- .../include/generic-glibc/bits/error-ldbl.h | 2 +- lib/libc/include/generic-glibc/bits/error.h | 2 +- lib/libc/include/generic-glibc/bits/eventfd.h | 2 +- .../generic-glibc/bits/fcntl-linux-fortify.h | 49 +++++ .../include/generic-glibc/bits/fcntl-linux.h | 37 +++- lib/libc/include/generic-glibc/bits/fcntl.h | 2 +- lib/libc/include/generic-glibc/bits/fcntl2.h | 2 +- lib/libc/include/generic-glibc/bits/fenv.h | 2 +- .../generic-glibc/bits/floatn-common.h | 2 +- lib/libc/include/generic-glibc/bits/floatn.h | 2 +- .../generic-glibc/bits/flt-eval-method.h | 2 +- lib/libc/include/generic-glibc/bits/fp-fast.h | 2 +- lib/libc/include/generic-glibc/bits/fp-logb.h | 2 +- .../include/generic-glibc/bits/getopt_core.h | 2 +- .../include/generic-glibc/bits/getopt_ext.h | 2 +- .../include/generic-glibc/bits/getopt_posix.h | 2 +- lib/libc/include/generic-glibc/bits/hwcap.h | 2 +- lib/libc/include/generic-glibc/bits/in.h | 2 +- .../generic-glibc/bits/indirect-return.h | 2 +- .../generic-glibc/bits/inet-fortified-decl.h | 2 +- .../generic-glibc/bits/inet-fortified.h | 12 +- lib/libc/include/generic-glibc/bits/inotify.h | 2 +- .../include/generic-glibc/bits/ioctl-types.h | 2 +- lib/libc/include/generic-glibc/bits/ioctls.h | 2 +- .../include/generic-glibc/bits/ipc-perm.h | 2 +- lib/libc/include/generic-glibc/bits/ipc.h | 2 +- .../include/generic-glibc/bits/ipctypes.h | 2 +- .../include/generic-glibc/bits/iscanonical.h | 2 +- .../generic-glibc/bits/libc-header-start.h | 2 +- .../generic-glibc/bits/libm-simd-decl-stubs.h | 57 ++++- lib/libc/include/generic-glibc/bits/link.h | 2 +- .../generic-glibc/bits/link_lavcurrent.h | 2 +- .../include/generic-glibc/bits/local_lim.h | 2 +- lib/libc/include/generic-glibc/bits/locale.h | 2 +- .../include/generic-glibc/bits/long-double.h | 2 +- .../include/generic-glibc/bits/math-vector.h | 2 +- .../bits/mathcalls-helper-functions.h | 2 +- .../generic-glibc/bits/mathcalls-macros.h | 2 +- .../generic-glibc/bits/mathcalls-narrow.h | 2 +- .../include/generic-glibc/bits/mathcalls.h | 24 +-- lib/libc/include/generic-glibc/bits/mathdef.h | 2 +- .../include/generic-glibc/bits/mman-linux.h | 2 +- .../bits/mman-map-flags-generic.h | 2 +- .../include/generic-glibc/bits/mman-shared.h | 14 +- lib/libc/include/generic-glibc/bits/mman.h | 2 +- .../include/generic-glibc/bits/mman_ext.h | 2 +- .../generic-glibc/bits/monetary-ldbl.h | 2 +- lib/libc/include/generic-glibc/bits/mqueue.h | 2 +- lib/libc/include/generic-glibc/bits/mqueue2.h | 2 +- lib/libc/include/generic-glibc/bits/msq.h | 2 +- lib/libc/include/generic-glibc/bits/netdb.h | 2 +- lib/libc/include/generic-glibc/bits/openat2.h | 60 ++++++ lib/libc/include/generic-glibc/bits/param.h | 2 +- .../generic-glibc/bits/platform/features.h | 2 +- .../include/generic-glibc/bits/platform/x86.h | 2 +- lib/libc/include/generic-glibc/bits/poll.h | 2 +- lib/libc/include/generic-glibc/bits/poll2.h | 2 +- .../include/generic-glibc/bits/posix1_lim.h | 2 +- .../include/generic-glibc/bits/posix2_lim.h | 2 +- .../include/generic-glibc/bits/posix_opt.h | 4 +- lib/libc/include/generic-glibc/bits/ppc.h | 2 +- .../include/generic-glibc/bits/printf-ldbl.h | 2 +- .../include/generic-glibc/bits/procfs-extra.h | 2 +- .../include/generic-glibc/bits/procfs-id.h | 2 +- .../generic-glibc/bits/procfs-prregset.h | 2 +- lib/libc/include/generic-glibc/bits/procfs.h | 2 +- .../bits/pthread_stack_min-dynamic.h | 2 +- .../generic-glibc/bits/pthread_stack_min.h | 2 +- .../generic-glibc/bits/pthreadtypes-arch.h | 2 +- .../include/generic-glibc/bits/pthreadtypes.h | 2 +- .../generic-glibc/bits/ptrace-shared.h | 2 +- .../include/generic-glibc/bits/resource.h | 2 +- lib/libc/include/generic-glibc/bits/rseq.h | 2 +- lib/libc/include/generic-glibc/bits/sched.h | 2 +- .../include/generic-glibc/bits/select-decl.h | 2 +- lib/libc/include/generic-glibc/bits/select.h | 2 +- lib/libc/include/generic-glibc/bits/select2.h | 2 +- lib/libc/include/generic-glibc/bits/sem.h | 2 +- .../include/generic-glibc/bits/semaphore.h | 2 +- lib/libc/include/generic-glibc/bits/setjmp.h | 2 +- lib/libc/include/generic-glibc/bits/setjmp2.h | 2 +- lib/libc/include/generic-glibc/bits/shm.h | 2 +- lib/libc/include/generic-glibc/bits/shmlba.h | 2 +- .../include/generic-glibc/bits/sigaction.h | 2 +- .../include/generic-glibc/bits/sigcontext.h | 2 +- .../generic-glibc/bits/sigevent-consts.h | 2 +- .../generic-glibc/bits/siginfo-consts.h | 18 +- .../include/generic-glibc/bits/signal_ext.h | 2 +- .../include/generic-glibc/bits/signalfd.h | 2 +- .../include/generic-glibc/bits/signum-arch.h | 2 +- .../generic-glibc/bits/signum-generic.h | 2 +- .../include/generic-glibc/bits/sigstack.h | 2 +- .../include/generic-glibc/bits/sigstksz.h | 2 +- .../include/generic-glibc/bits/sigthread.h | 2 +- .../include/generic-glibc/bits/sockaddr.h | 2 +- .../generic-glibc/bits/socket-constants.h | 2 +- lib/libc/include/generic-glibc/bits/socket.h | 2 +- lib/libc/include/generic-glibc/bits/socket2.h | 2 +- .../include/generic-glibc/bits/socket_type.h | 2 +- .../include/generic-glibc/bits/spawn_ext.h | 2 +- .../include/generic-glibc/bits/ss_flags.h | 2 +- lib/libc/include/generic-glibc/bits/stab.def | 2 +- lib/libc/include/generic-glibc/bits/stat.h | 2 +- lib/libc/include/generic-glibc/bits/statfs.h | 2 +- lib/libc/include/generic-glibc/bits/statvfs.h | 2 +- .../generic-glibc/bits/statx-generic.h | 3 +- lib/libc/include/generic-glibc/bits/statx.h | 2 +- .../include/generic-glibc/bits/stdint-intn.h | 2 +- .../include/generic-glibc/bits/stdint-least.h | 2 +- .../include/generic-glibc/bits/stdint-uintn.h | 2 +- .../include/generic-glibc/bits/stdio-ldbl.h | 2 +- lib/libc/include/generic-glibc/bits/stdio.h | 2 +- .../include/generic-glibc/bits/stdio2-decl.h | 2 +- lib/libc/include/generic-glibc/bits/stdio2.h | 2 +- .../include/generic-glibc/bits/stdio_lim.h | 2 +- .../generic-glibc/bits/stdlib-bsearch.h | 2 +- .../include/generic-glibc/bits/stdlib-float.h | 2 +- .../include/generic-glibc/bits/stdlib-ldbl.h | 2 +- lib/libc/include/generic-glibc/bits/stdlib.h | 2 +- .../generic-glibc/bits/string_fortified.h | 14 +- .../generic-glibc/bits/strings_fortified.h | 2 +- .../include/generic-glibc/bits/struct_mutex.h | 22 +- .../generic-glibc/bits/struct_rwlock.h | 6 +- .../include/generic-glibc/bits/struct_stat.h | 2 +- .../bits/struct_stat_time64_helper.h | 2 +- lib/libc/include/generic-glibc/bits/syscall.h | 12 +- .../include/generic-glibc/bits/syslog-decl.h | 2 +- .../include/generic-glibc/bits/syslog-ldbl.h | 2 +- .../include/generic-glibc/bits/syslog-path.h | 2 +- lib/libc/include/generic-glibc/bits/syslog.h | 2 +- .../include/generic-glibc/bits/sysmacros.h | 2 +- .../include/generic-glibc/bits/termios-baud.h | 2 +- .../include/generic-glibc/bits/termios-c_cc.h | 2 +- .../generic-glibc/bits/termios-c_cflag.h | 2 +- .../generic-glibc/bits/termios-c_iflag.h | 2 +- .../generic-glibc/bits/termios-c_lflag.h | 2 +- .../generic-glibc/bits/termios-c_oflag.h | 2 +- .../generic-glibc/bits/termios-cbaud.h | 2 +- .../include/generic-glibc/bits/termios-misc.h | 2 +- .../generic-glibc/bits/termios-struct.h | 2 +- .../generic-glibc/bits/termios-tcflow.h | 2 +- lib/libc/include/generic-glibc/bits/termios.h | 2 +- .../generic-glibc/bits/thread-shared-types.h | 4 +- lib/libc/include/generic-glibc/bits/time.h | 2 +- lib/libc/include/generic-glibc/bits/time64.h | 2 +- lib/libc/include/generic-glibc/bits/timerfd.h | 2 +- .../include/generic-glibc/bits/timesize.h | 2 +- lib/libc/include/generic-glibc/bits/timex.h | 2 +- lib/libc/include/generic-glibc/bits/types.h | 2 +- .../generic-glibc/bits/types/__locale_t.h | 2 +- .../generic-glibc/bits/types/__sigval_t.h | 2 +- .../bits/types/cookie_io_functions_t.h | 2 +- .../generic-glibc/bits/types/error_t.h | 2 +- .../generic-glibc/bits/types/locale_t.h | 2 +- .../bits/types/once_flag.h} | 17 +- .../generic-glibc/bits/types/stack_t.h | 2 +- .../generic-glibc/bits/types/struct_FILE.h | 2 +- .../bits/types/struct___jmp_buf_tag.h | 2 +- .../generic-glibc/bits/types/struct_iovec.h | 2 +- .../bits/types/struct_msqid64_ds.h | 2 +- .../bits/types/struct_msqid64_ds_helper.h | 2 +- .../bits/types/struct_msqid_ds.h | 2 +- .../generic-glibc/bits/types/struct_rusage.h | 2 +- .../bits/types/struct_sched_param.h | 2 +- .../bits/types/struct_semid64_ds.h | 2 +- .../bits/types/struct_semid64_ds_helper.h | 2 +- .../bits/types/struct_semid_ds.h | 2 +- .../bits/types/struct_shmid64_ds.h | 2 +- .../bits/types/struct_shmid64_ds_helper.h | 2 +- .../bits/types/struct_shmid_ds.h | 2 +- .../bits/types/struct_sigstack.h | 2 +- .../generic-glibc/bits/types/struct_statx.h | 14 +- .../bits/types/struct_statx_timestamp.h | 2 +- .../include/generic-glibc/bits/typesizes.h | 2 +- .../generic-glibc/bits/uintn-identity.h | 2 +- lib/libc/include/generic-glibc/bits/uio-ext.h | 3 +- lib/libc/include/generic-glibc/bits/uio_lim.h | 2 +- .../include/generic-glibc/bits/unistd-decl.h | 2 +- lib/libc/include/generic-glibc/bits/unistd.h | 2 +- .../include/generic-glibc/bits/unistd_ext.h | 2 +- lib/libc/include/generic-glibc/bits/utmp.h | 2 +- lib/libc/include/generic-glibc/bits/utmpx.h | 2 +- lib/libc/include/generic-glibc/bits/utsname.h | 2 +- .../include/generic-glibc/bits/waitflags.h | 2 +- .../include/generic-glibc/bits/waitstatus.h | 2 +- .../include/generic-glibc/bits/wchar-ldbl.h | 2 +- lib/libc/include/generic-glibc/bits/wchar.h | 2 +- .../include/generic-glibc/bits/wchar2-decl.h | 2 +- lib/libc/include/generic-glibc/bits/wchar2.h | 2 +- .../include/generic-glibc/bits/wctype-wchar.h | 2 +- .../include/generic-glibc/bits/wordsize.h | 2 +- .../include/generic-glibc/bits/xopen_lim.h | 2 +- lib/libc/include/generic-glibc/byteswap.h | 2 +- lib/libc/include/generic-glibc/complex.h | 6 +- lib/libc/include/generic-glibc/cpio.h | 2 +- lib/libc/include/generic-glibc/ctype.h | 2 +- lib/libc/include/generic-glibc/dirent.h | 2 +- lib/libc/include/generic-glibc/dlfcn.h | 2 +- lib/libc/include/generic-glibc/elf.h | 4 +- lib/libc/include/generic-glibc/endian.h | 2 +- lib/libc/include/generic-glibc/envz.h | 2 +- lib/libc/include/generic-glibc/err.h | 2 +- lib/libc/include/generic-glibc/errno.h | 2 +- lib/libc/include/generic-glibc/error.h | 2 +- lib/libc/include/generic-glibc/execinfo.h | 2 +- lib/libc/include/generic-glibc/fcntl.h | 4 +- .../include/generic-glibc/features-time64.h | 2 +- lib/libc/include/generic-glibc/features.h | 50 +++-- lib/libc/include/generic-glibc/fenv.h | 6 +- .../finclude/math-vector-fortran.h | 2 +- lib/libc/include/generic-glibc/fmtmsg.h | 2 +- lib/libc/include/generic-glibc/fnmatch.h | 2 +- lib/libc/include/generic-glibc/fpregdef.h | 2 +- lib/libc/include/generic-glibc/fpu_control.h | 2 +- lib/libc/include/generic-glibc/fts.h | 2 +- lib/libc/include/generic-glibc/ftw.h | 2 +- lib/libc/include/generic-glibc/gconv.h | 2 +- lib/libc/include/generic-glibc/getopt.h | 2 +- lib/libc/include/generic-glibc/glob.h | 2 +- lib/libc/include/generic-glibc/gnu-versions.h | 2 +- .../include/generic-glibc/gnu/libc-version.h | 2 +- lib/libc/include/generic-glibc/grp.h | 2 +- lib/libc/include/generic-glibc/gshadow.h | 2 +- lib/libc/include/generic-glibc/iconv.h | 2 +- lib/libc/include/generic-glibc/ieee754.h | 2 +- lib/libc/include/generic-glibc/ifaddrs.h | 2 +- lib/libc/include/generic-glibc/inttypes.h | 89 ++++---- lib/libc/include/generic-glibc/langinfo.h | 2 +- lib/libc/include/generic-glibc/libgen.h | 2 +- lib/libc/include/generic-glibc/libintl.h | 2 +- lib/libc/include/generic-glibc/limits.h | 7 +- lib/libc/include/generic-glibc/link.h | 2 +- lib/libc/include/generic-glibc/locale.h | 2 +- lib/libc/include/generic-glibc/malloc.h | 12 +- lib/libc/include/generic-glibc/math.h | 186 +++++++++++++++- lib/libc/include/generic-glibc/mcheck.h | 2 +- lib/libc/include/generic-glibc/memory.h | 2 +- lib/libc/include/generic-glibc/mntent.h | 2 +- lib/libc/include/generic-glibc/monetary.h | 2 +- lib/libc/include/generic-glibc/mqueue.h | 2 +- lib/libc/include/generic-glibc/net/ethernet.h | 2 +- lib/libc/include/generic-glibc/net/if.h | 2 +- lib/libc/include/generic-glibc/net/if_arp.h | 2 +- .../include/generic-glibc/net/if_packet.h | 2 +- .../include/generic-glibc/net/if_shaper.h | 2 +- lib/libc/include/generic-glibc/net/if_slip.h | 2 +- lib/libc/include/generic-glibc/net/route.h | 2 +- lib/libc/include/generic-glibc/netash/ash.h | 2 +- lib/libc/include/generic-glibc/netatalk/at.h | 2 +- lib/libc/include/generic-glibc/netax25/ax25.h | 2 +- lib/libc/include/generic-glibc/netdb.h | 2 +- lib/libc/include/generic-glibc/neteconet/ec.h | 2 +- .../include/generic-glibc/netinet/ether.h | 2 +- .../include/generic-glibc/netinet/icmp6.h | 2 +- .../include/generic-glibc/netinet/if_ether.h | 2 +- .../include/generic-glibc/netinet/if_fddi.h | 2 +- .../include/generic-glibc/netinet/if_tr.h | 2 +- lib/libc/include/generic-glibc/netinet/igmp.h | 2 +- lib/libc/include/generic-glibc/netinet/in.h | 2 +- .../include/generic-glibc/netinet/in_systm.h | 2 +- lib/libc/include/generic-glibc/netinet/ip.h | 2 +- lib/libc/include/generic-glibc/netinet/ip6.h | 2 +- .../include/generic-glibc/netinet/ip_icmp.h | 2 +- lib/libc/include/generic-glibc/netinet/tcp.h | 202 ++++++++++++++++++ lib/libc/include/generic-glibc/netinet/udp.h | 2 +- lib/libc/include/generic-glibc/netipx/ipx.h | 2 +- lib/libc/include/generic-glibc/netiucv/iucv.h | 2 +- .../include/generic-glibc/netpacket/packet.h | 2 +- .../include/generic-glibc/netrom/netrom.h | 2 +- lib/libc/include/generic-glibc/netrose/rose.h | 2 +- lib/libc/include/generic-glibc/nl_types.h | 2 +- lib/libc/include/generic-glibc/nss.h | 2 +- lib/libc/include/generic-glibc/obstack.h | 2 +- lib/libc/include/generic-glibc/printf.h | 2 +- lib/libc/include/generic-glibc/proc_service.h | 2 +- lib/libc/include/generic-glibc/pthread.h | 2 +- lib/libc/include/generic-glibc/pty.h | 2 +- lib/libc/include/generic-glibc/pwd.h | 2 +- lib/libc/include/generic-glibc/re_comp.h | 2 +- lib/libc/include/generic-glibc/regdef.h | 2 +- lib/libc/include/generic-glibc/regex.h | 2 +- lib/libc/include/generic-glibc/regexp.h | 2 +- lib/libc/include/generic-glibc/resolv.h | 2 +- lib/libc/include/generic-glibc/sched.h | 2 +- lib/libc/include/generic-glibc/scsi/scsi.h | 2 +- .../include/generic-glibc/scsi/scsi_ioctl.h | 2 +- lib/libc/include/generic-glibc/scsi/sg.h | 2 +- lib/libc/include/generic-glibc/search.h | 2 +- lib/libc/include/generic-glibc/semaphore.h | 2 +- lib/libc/include/generic-glibc/setjmp.h | 6 +- lib/libc/include/generic-glibc/sgidefs.h | 2 +- lib/libc/include/generic-glibc/sgtty.h | 2 +- lib/libc/include/generic-glibc/shadow.h | 2 +- lib/libc/include/generic-glibc/signal.h | 2 +- lib/libc/include/generic-glibc/spawn.h | 2 +- lib/libc/include/generic-glibc/stdbit.h | 2 +- lib/libc/include/generic-glibc/stdc-predef.h | 2 +- lib/libc/include/generic-glibc/stdint.h | 8 +- lib/libc/include/generic-glibc/stdio.h | 12 +- lib/libc/include/generic-glibc/stdio_ext.h | 2 +- lib/libc/include/generic-glibc/stdlib.h | 43 +++- lib/libc/include/generic-glibc/string.h | 36 +++- lib/libc/include/generic-glibc/strings.h | 2 +- lib/libc/include/generic-glibc/sys/acct.h | 2 +- lib/libc/include/generic-glibc/sys/asm.h | 18 +- lib/libc/include/generic-glibc/sys/auxv.h | 2 +- lib/libc/include/generic-glibc/sys/cachectl.h | 2 +- lib/libc/include/generic-glibc/sys/cdefs.h | 30 ++- lib/libc/include/generic-glibc/sys/debugreg.h | 2 +- lib/libc/include/generic-glibc/sys/dir.h | 2 +- lib/libc/include/generic-glibc/sys/elf.h | 2 +- lib/libc/include/generic-glibc/sys/epoll.h | 2 +- lib/libc/include/generic-glibc/sys/eventfd.h | 2 +- lib/libc/include/generic-glibc/sys/fanotify.h | 2 +- lib/libc/include/generic-glibc/sys/file.h | 2 +- lib/libc/include/generic-glibc/sys/fpregdef.h | 2 +- lib/libc/include/generic-glibc/sys/fsuid.h | 2 +- lib/libc/include/generic-glibc/sys/gmon_out.h | 2 +- lib/libc/include/generic-glibc/sys/hwprobe.h | 2 +- lib/libc/include/generic-glibc/sys/ifunc.h | 2 +- lib/libc/include/generic-glibc/sys/inotify.h | 2 +- lib/libc/include/generic-glibc/sys/io.h | 2 +- lib/libc/include/generic-glibc/sys/ioctl.h | 2 +- lib/libc/include/generic-glibc/sys/ipc.h | 2 +- lib/libc/include/generic-glibc/sys/kd.h | 2 +- lib/libc/include/generic-glibc/sys/klog.h | 2 +- lib/libc/include/generic-glibc/sys/mman.h | 2 +- lib/libc/include/generic-glibc/sys/mount.h | 2 +- lib/libc/include/generic-glibc/sys/msg.h | 2 +- lib/libc/include/generic-glibc/sys/mtio.h | 2 +- lib/libc/include/generic-glibc/sys/param.h | 2 +- lib/libc/include/generic-glibc/sys/pci.h | 2 +- lib/libc/include/generic-glibc/sys/perm.h | 2 +- .../include/generic-glibc/sys/personality.h | 2 +- lib/libc/include/generic-glibc/sys/pidfd.h | 60 +++++- .../include/generic-glibc/sys/platform/ppc.h | 2 +- .../include/generic-glibc/sys/platform/x86.h | 2 +- lib/libc/include/generic-glibc/sys/poll.h | 2 +- lib/libc/include/generic-glibc/sys/prctl.h | 2 +- lib/libc/include/generic-glibc/sys/procfs.h | 2 +- lib/libc/include/generic-glibc/sys/profil.h | 2 +- lib/libc/include/generic-glibc/sys/ptrace.h | 2 +- lib/libc/include/generic-glibc/sys/quota.h | 2 +- lib/libc/include/generic-glibc/sys/random.h | 2 +- lib/libc/include/generic-glibc/sys/raw.h | 2 +- lib/libc/include/generic-glibc/sys/reboot.h | 2 +- lib/libc/include/generic-glibc/sys/reg.h | 2 +- lib/libc/include/generic-glibc/sys/regdef.h | 2 +- lib/libc/include/generic-glibc/sys/resource.h | 2 +- lib/libc/include/generic-glibc/sys/rseq.h | 2 +- lib/libc/include/generic-glibc/sys/select.h | 2 +- lib/libc/include/generic-glibc/sys/sem.h | 2 +- lib/libc/include/generic-glibc/sys/sendfile.h | 2 +- lib/libc/include/generic-glibc/sys/shm.h | 2 +- lib/libc/include/generic-glibc/sys/signalfd.h | 2 +- .../generic-glibc/sys/single_threaded.h | 2 +- lib/libc/include/generic-glibc/sys/socket.h | 2 +- lib/libc/include/generic-glibc/sys/stat.h | 2 +- lib/libc/include/generic-glibc/sys/statfs.h | 2 +- lib/libc/include/generic-glibc/sys/statvfs.h | 2 +- lib/libc/include/generic-glibc/sys/swap.h | 2 +- lib/libc/include/generic-glibc/sys/syscall.h | 2 +- lib/libc/include/generic-glibc/sys/sysinfo.h | 2 +- .../include/generic-glibc/sys/sysmacros.h | 2 +- lib/libc/include/generic-glibc/sys/sysmips.h | 2 +- lib/libc/include/generic-glibc/sys/tas.h | 2 +- lib/libc/include/generic-glibc/sys/time.h | 2 +- lib/libc/include/generic-glibc/sys/timeb.h | 2 +- lib/libc/include/generic-glibc/sys/timerfd.h | 2 +- lib/libc/include/generic-glibc/sys/times.h | 2 +- lib/libc/include/generic-glibc/sys/timex.h | 2 +- lib/libc/include/generic-glibc/sys/types.h | 2 +- lib/libc/include/generic-glibc/sys/ucontext.h | 2 +- lib/libc/include/generic-glibc/sys/uio.h | 2 +- lib/libc/include/generic-glibc/sys/un.h | 2 +- lib/libc/include/generic-glibc/sys/user.h | 2 +- lib/libc/include/generic-glibc/sys/utsname.h | 2 +- lib/libc/include/generic-glibc/sys/vlimit.h | 2 +- lib/libc/include/generic-glibc/sys/vm86.h | 2 +- lib/libc/include/generic-glibc/sys/wait.h | 2 +- lib/libc/include/generic-glibc/sys/xattr.h | 2 +- lib/libc/include/generic-glibc/tar.h | 2 +- lib/libc/include/generic-glibc/termios.h | 2 +- lib/libc/include/generic-glibc/tgmath.h | 43 ++-- lib/libc/include/generic-glibc/thread_db.h | 2 +- lib/libc/include/generic-glibc/threads.h | 6 +- lib/libc/include/generic-glibc/time.h | 11 +- lib/libc/include/generic-glibc/uchar.h | 6 +- lib/libc/include/generic-glibc/ucontext.h | 2 +- lib/libc/include/generic-glibc/ulimit.h | 2 +- lib/libc/include/generic-glibc/unistd.h | 2 +- lib/libc/include/generic-glibc/utime.h | 2 +- lib/libc/include/generic-glibc/utmp.h | 2 +- lib/libc/include/generic-glibc/utmpx.h | 2 +- lib/libc/include/generic-glibc/values.h | 2 +- lib/libc/include/generic-glibc/wchar.h | 27 ++- lib/libc/include/generic-glibc/wctype.h | 2 +- lib/libc/include/generic-glibc/wordexp.h | 2 +- .../include/loongarch-linux-gnu/bits/fcntl.h | 2 +- .../include/loongarch-linux-gnu/bits/fenv.h | 2 +- .../include/loongarch-linux-gnu/bits/hwcap.h | 2 +- .../include/loongarch-linux-gnu/bits/link.h | 2 +- .../bits/link_lavcurrent.h | 2 +- .../loongarch-linux-gnu/bits/long-double.h | 2 +- .../include/loongarch-linux-gnu/bits/procfs.h | 2 +- .../bits/pthread_stack_min.h | 2 +- .../include/loongarch-linux-gnu/bits/rseq.h | 2 +- .../include/loongarch-linux-gnu/bits/setjmp.h | 2 +- .../loongarch-linux-gnu/bits/sigstack.h | 2 +- .../loongarch-linux-gnu/bits/struct_stat.h | 2 +- .../loongarch-linux-gnu/bits/timesize.h | 2 +- .../loongarch-linux-gnu/bits/wordsize.h | 2 +- .../include/loongarch-linux-gnu/fpu_control.h | 2 +- .../gnu/lib-names-lp64d.h | 0 .../loongarch-linux-gnu/gnu/lib-names-lp64s.h | 27 +++ .../gnu/stubs-lp64d.h | 0 .../loongarch-linux-gnu/gnu/stubs-lp64s.h | 2 +- .../include/loongarch-linux-gnu/ieee754.h | 2 +- .../include/loongarch-linux-gnu/sys/asm.h | 2 +- .../loongarch-linux-gnu/sys/ucontext.h | 2 +- .../include/loongarch-linux-gnu/sys/user.h | 2 +- lib/libc/include/m68k-linux-gnu/bits/fcntl.h | 2 +- lib/libc/include/m68k-linux-gnu/bits/fenv.h | 2 +- lib/libc/include/m68k-linux-gnu/bits/floatn.h | 2 +- .../m68k-linux-gnu/bits/flt-eval-method.h | 2 +- .../include/m68k-linux-gnu/bits/fp-logb.h | 2 +- .../include/m68k-linux-gnu/bits/iscanonical.h | 2 +- lib/libc/include/m68k-linux-gnu/bits/link.h | 2 +- .../include/m68k-linux-gnu/bits/long-double.h | 2 +- lib/libc/include/m68k-linux-gnu/bits/poll.h | 2 +- .../include/m68k-linux-gnu/bits/procfs-id.h | 2 +- lib/libc/include/m68k-linux-gnu/bits/procfs.h | 2 +- .../m68k-linux-gnu/bits/pthreadtypes-arch.h | 2 +- lib/libc/include/m68k-linux-gnu/bits/rseq.h | 2 +- .../include/m68k-linux-gnu/bits/semaphore.h | 2 +- lib/libc/include/m68k-linux-gnu/bits/setjmp.h | 2 +- .../include/m68k-linux-gnu/bits/sockaddr.h | 2 +- .../include/m68k-linux-gnu/bits/struct_stat.h | 2 +- .../include/m68k-linux-gnu/bits/timesize.h | 2 +- .../include/m68k-linux-gnu/bits/typesizes.h | 2 +- .../include/m68k-linux-gnu/bits/wordsize.h | 2 +- lib/libc/include/m68k-linux-gnu/fpu_control.h | 2 +- lib/libc/include/m68k-linux-gnu/sys/reg.h | 2 +- .../include/m68k-linux-gnu/sys/ucontext.h | 2 +- lib/libc/include/m68k-linux-gnu/sys/user.h | 2 +- lib/libc/include/mips-linux-gnu/bits/dlfcn.h | 2 +- lib/libc/include/mips-linux-gnu/bits/errno.h | 2 +- .../include/mips-linux-gnu/bits/eventfd.h | 2 +- lib/libc/include/mips-linux-gnu/bits/floatn.h | 2 +- .../include/mips-linux-gnu/bits/inotify.h | 2 +- .../include/mips-linux-gnu/bits/ioctl-types.h | 2 +- .../include/mips-linux-gnu/bits/ipctypes.h | 2 +- lib/libc/include/mips-linux-gnu/bits/mman.h | 2 +- lib/libc/include/mips-linux-gnu/bits/poll.h | 2 +- .../mips-linux-gnu/bits/pthread_stack_min.h | 2 +- .../mips-linux-gnu/bits/pthreadtypes-arch.h | 2 +- .../include/mips-linux-gnu/bits/resource.h | 2 +- .../include/mips-linux-gnu/bits/semaphore.h | 2 +- lib/libc/include/mips-linux-gnu/bits/shmlba.h | 2 +- .../include/mips-linux-gnu/bits/sigaction.h | 2 +- .../include/mips-linux-gnu/bits/sigcontext.h | 2 +- .../include/mips-linux-gnu/bits/signalfd.h | 2 +- .../include/mips-linux-gnu/bits/signum-arch.h | 2 +- .../mips-linux-gnu/bits/socket-constants.h | 2 +- .../include/mips-linux-gnu/bits/socket_type.h | 2 +- lib/libc/include/mips-linux-gnu/bits/statfs.h | 2 +- .../mips-linux-gnu/bits/struct_mutex.h | 2 +- .../mips-linux-gnu/bits/struct_rwlock.h | 2 +- .../mips-linux-gnu/bits/termios-c_cc.h | 2 +- .../mips-linux-gnu/bits/termios-c_lflag.h | 2 +- .../mips-linux-gnu/bits/termios-tcflow.h | 2 +- .../include/mips-linux-gnu/bits/timerfd.h | 2 +- .../mips-linux-gnu/bits/types/stack_t.h | 2 +- .../bits/types/struct_msqid_ds.h | 2 +- .../bits/types/struct_semid_ds.h | 2 +- .../bits/types/struct_shmid_ds.h | 2 +- .../include/mips-linux-gnu/bits/typesizes.h | 2 +- lib/libc/include/mips-linux-gnu/ieee754.h | 2 +- .../powerpc-linux-gnu/bits/environments.h | 2 +- .../include/powerpc-linux-gnu/bits/fcntl.h | 2 +- .../include/powerpc-linux-gnu/bits/fenv.h | 2 +- .../include/powerpc-linux-gnu/bits/floatn.h | 2 +- .../include/powerpc-linux-gnu/bits/fp-fast.h | 2 +- .../include/powerpc-linux-gnu/bits/hwcap.h | 2 +- .../powerpc-linux-gnu/bits/ioctl-types.h | 2 +- .../include/powerpc-linux-gnu/bits/ipc-perm.h | 2 +- .../powerpc-linux-gnu/bits/iscanonical.h | 2 +- .../include/powerpc-linux-gnu/bits/link.h | 2 +- .../powerpc-linux-gnu/bits/long-double.h | 2 +- .../include/powerpc-linux-gnu/bits/mman.h | 2 +- .../include/powerpc-linux-gnu/bits/procfs.h | 2 +- .../bits/pthread_stack_min.h | 2 +- .../include/powerpc-linux-gnu/bits/rseq.h | 2 +- .../include/powerpc-linux-gnu/bits/setjmp.h | 2 +- .../include/powerpc-linux-gnu/bits/sigstack.h | 2 +- .../powerpc-linux-gnu/bits/socket-constants.h | 2 +- .../powerpc-linux-gnu/bits/struct_mutex.h | 13 +- .../powerpc-linux-gnu/bits/struct_rwlock.h | 13 +- .../powerpc-linux-gnu/bits/struct_stat.h | 2 +- .../powerpc-linux-gnu/bits/termios-c_cc.h | 2 +- .../powerpc-linux-gnu/bits/termios-c_cflag.h | 2 +- .../powerpc-linux-gnu/bits/termios-c_iflag.h | 2 +- .../powerpc-linux-gnu/bits/termios-c_lflag.h | 2 +- .../powerpc-linux-gnu/bits/termios-c_oflag.h | 2 +- .../powerpc-linux-gnu/bits/termios-cbaud.h | 2 +- .../powerpc-linux-gnu/bits/termios-misc.h | 2 +- .../include/powerpc-linux-gnu/bits/timesize.h | 2 +- .../bits/types/struct_msqid_ds.h | 2 +- .../bits/types/struct_semid_ds.h | 2 +- .../bits/types/struct_shmid_ds.h | 2 +- .../powerpc-linux-gnu/bits/typesizes.h | 2 +- .../include/powerpc-linux-gnu/fpu_control.h | 2 +- lib/libc/include/powerpc-linux-gnu/ieee754.h | 2 +- .../include/powerpc-linux-gnu/sys/ptrace.h | 2 +- .../include/powerpc-linux-gnu/sys/ucontext.h | 2 +- lib/libc/include/powerpc-linux-gnu/sys/user.h | 2 +- .../riscv-linux-gnu/bits/environments.h | 2 +- lib/libc/include/riscv-linux-gnu/bits/fcntl.h | 2 +- lib/libc/include/riscv-linux-gnu/bits/fenv.h | 2 +- lib/libc/include/riscv-linux-gnu/bits/link.h | 2 +- .../riscv-linux-gnu/bits/long-double.h | 2 +- .../include/riscv-linux-gnu/bits/procfs.h | 2 +- .../riscv-linux-gnu/bits/pthreadtypes-arch.h | 2 +- lib/libc/include/riscv-linux-gnu/bits/rseq.h | 2 +- .../include/riscv-linux-gnu/bits/setjmp.h | 2 +- .../include/riscv-linux-gnu/bits/sigcontext.h | 2 +- .../riscv-linux-gnu/bits/struct_rwlock.h | 2 +- .../riscv-linux-gnu/bits/struct_stat.h | 2 +- .../include/riscv-linux-gnu/bits/time64.h | 2 +- .../include/riscv-linux-gnu/bits/timesize.h | 2 +- .../include/riscv-linux-gnu/bits/wordsize.h | 2 +- .../include/riscv-linux-gnu/fpu_control.h | 4 +- lib/libc/include/riscv-linux-gnu/ieee754.h | 2 +- lib/libc/include/riscv-linux-gnu/sys/asm.h | 2 +- .../include/riscv-linux-gnu/sys/cachectl.h | 2 +- .../include/riscv-linux-gnu/sys/ucontext.h | 2 +- lib/libc/include/riscv-linux-gnu/sys/user.h | 2 +- .../include/s390x-linux-gnu/bits/elfclass.h | 2 +- .../s390x-linux-gnu/bits/environments.h | 2 +- lib/libc/include/s390x-linux-gnu/bits/fcntl.h | 2 +- lib/libc/include/s390x-linux-gnu/bits/fenv.h | 2 +- lib/libc/include/s390x-linux-gnu/bits/hwcap.h | 2 +- lib/libc/include/s390x-linux-gnu/bits/link.h | 2 +- .../s390x-linux-gnu/bits/long-double.h | 2 +- .../s390x-linux-gnu/bits/procfs-extra.h | 2 +- .../include/s390x-linux-gnu/bits/procfs-id.h | 2 +- .../include/s390x-linux-gnu/bits/procfs.h | 2 +- lib/libc/include/s390x-linux-gnu/bits/rseq.h | 2 +- .../include/s390x-linux-gnu/bits/setjmp.h | 2 +- .../include/s390x-linux-gnu/bits/sigaction.h | 2 +- .../include/s390x-linux-gnu/bits/statfs.h | 2 +- .../s390x-linux-gnu/bits/struct_mutex.h | 13 +- .../s390x-linux-gnu/bits/struct_rwlock.h | 2 +- .../s390x-linux-gnu/bits/struct_stat.h | 2 +- .../include/s390x-linux-gnu/bits/timesize.h | 2 +- .../include/s390x-linux-gnu/bits/typesizes.h | 2 +- lib/libc/include/s390x-linux-gnu/bits/utmp.h | 2 +- lib/libc/include/s390x-linux-gnu/bits/utmpx.h | 2 +- .../include/s390x-linux-gnu/fpu_control.h | 2 +- lib/libc/include/s390x-linux-gnu/ieee754.h | 2 +- lib/libc/include/s390x-linux-gnu/sys/elf.h | 2 +- lib/libc/include/s390x-linux-gnu/sys/ptrace.h | 2 +- .../include/s390x-linux-gnu/sys/ucontext.h | 2 +- lib/libc/include/s390x-linux-gnu/sys/user.h | 2 +- .../sparc-linux-gnu/bits/environments.h | 2 +- lib/libc/include/sparc-linux-gnu/bits/epoll.h | 2 +- lib/libc/include/sparc-linux-gnu/bits/errno.h | 2 +- .../include/sparc-linux-gnu/bits/eventfd.h | 2 +- lib/libc/include/sparc-linux-gnu/bits/fcntl.h | 2 +- lib/libc/include/sparc-linux-gnu/bits/fenv.h | 2 +- lib/libc/include/sparc-linux-gnu/bits/hwcap.h | 2 +- .../include/sparc-linux-gnu/bits/inotify.h | 2 +- .../include/sparc-linux-gnu/bits/ioctls.h | 2 +- .../include/sparc-linux-gnu/bits/ipc-perm.h | 2 +- lib/libc/include/sparc-linux-gnu/bits/link.h | 2 +- .../sparc-linux-gnu/bits/long-double.h | 2 +- lib/libc/include/sparc-linux-gnu/bits/mman.h | 2 +- lib/libc/include/sparc-linux-gnu/bits/poll.h | 2 +- .../sparc-linux-gnu/bits/procfs-extra.h | 2 +- .../include/sparc-linux-gnu/bits/procfs-id.h | 2 +- .../include/sparc-linux-gnu/bits/procfs.h | 2 +- .../sparc-linux-gnu/bits/pthread_stack_min.h | 2 +- .../include/sparc-linux-gnu/bits/resource.h | 2 +- lib/libc/include/sparc-linux-gnu/bits/rseq.h | 2 +- .../include/sparc-linux-gnu/bits/setjmp.h | 2 +- .../include/sparc-linux-gnu/bits/shmlba.h | 2 +- .../include/sparc-linux-gnu/bits/sigaction.h | 2 +- .../include/sparc-linux-gnu/bits/sigcontext.h | 2 +- .../include/sparc-linux-gnu/bits/signalfd.h | 2 +- .../sparc-linux-gnu/bits/signum-arch.h | 2 +- .../include/sparc-linux-gnu/bits/sigstack.h | 2 +- .../sparc-linux-gnu/bits/socket-constants.h | 2 +- .../sparc-linux-gnu/bits/socket_type.h | 2 +- .../sparc-linux-gnu/bits/struct_rwlock.h | 2 +- .../sparc-linux-gnu/bits/struct_stat.h | 2 +- .../sparc-linux-gnu/bits/termios-c_cc.h | 2 +- .../sparc-linux-gnu/bits/termios-c_oflag.h | 2 +- .../sparc-linux-gnu/bits/termios-cbaud.h | 2 +- .../include/sparc-linux-gnu/bits/timerfd.h | 2 +- .../include/sparc-linux-gnu/bits/timesize.h | 2 +- .../bits/types/struct_msqid_ds.h | 2 +- .../bits/types/struct_semid_ds.h | 2 +- .../bits/types/struct_shmid_ds.h | 2 +- .../include/sparc-linux-gnu/bits/typesizes.h | 2 +- .../include/sparc-linux-gnu/fpu_control.h | 2 +- lib/libc/include/sparc-linux-gnu/ieee754.h | 2 +- lib/libc/include/sparc-linux-gnu/sys/ptrace.h | 2 +- .../include/sparc-linux-gnu/sys/ucontext.h | 2 +- lib/libc/include/sparc-linux-gnu/sys/user.h | 2 +- .../x86-linux-gnu/bits/dl_find_object.h | 2 +- .../include/x86-linux-gnu/bits/environments.h | 2 +- lib/libc/include/x86-linux-gnu/bits/epoll.h | 2 +- lib/libc/include/x86-linux-gnu/bits/fcntl.h | 2 +- lib/libc/include/x86-linux-gnu/bits/fenv.h | 2 +- lib/libc/include/x86-linux-gnu/bits/floatn.h | 2 +- .../x86-linux-gnu/bits/flt-eval-method.h | 2 +- lib/libc/include/x86-linux-gnu/bits/fp-logb.h | 2 +- .../x86-linux-gnu/bits/indirect-return.h | 2 +- .../include/x86-linux-gnu/bits/ipctypes.h | 2 +- .../include/x86-linux-gnu/bits/iscanonical.h | 2 +- lib/libc/include/x86-linux-gnu/bits/link.h | 2 +- .../include/x86-linux-gnu/bits/long-double.h | 2 +- .../include/x86-linux-gnu/bits/math-vector.h | 2 +- lib/libc/include/x86-linux-gnu/bits/mman.h | 2 +- .../include/x86-linux-gnu/bits/procfs-id.h | 2 +- lib/libc/include/x86-linux-gnu/bits/procfs.h | 2 +- .../x86-linux-gnu/bits/pthreadtypes-arch.h | 2 +- lib/libc/include/x86-linux-gnu/bits/rseq.h | 2 +- lib/libc/include/x86-linux-gnu/bits/setjmp.h | 2 +- .../include/x86-linux-gnu/bits/sigcontext.h | 2 +- .../include/x86-linux-gnu/bits/struct_mutex.h | 13 +- .../x86-linux-gnu/bits/struct_rwlock.h | 15 +- .../include/x86-linux-gnu/bits/struct_stat.h | 2 +- .../include/x86-linux-gnu/bits/timesize.h | 2 +- .../bits/types/struct_semid_ds.h | 2 +- .../include/x86-linux-gnu/bits/typesizes.h | 2 +- .../finclude/math-vector-fortran.h | 2 +- lib/libc/include/x86-linux-gnu/fpu_control.h | 2 +- lib/libc/include/x86-linux-gnu/sys/elf.h | 2 +- lib/libc/include/x86-linux-gnu/sys/ptrace.h | 2 +- lib/libc/include/x86-linux-gnu/sys/ucontext.h | 2 +- lib/libc/include/x86-linux-gnu/sys/user.h | 2 +- 744 files changed, 1942 insertions(+), 959 deletions(-) create mode 100644 lib/libc/include/generic-glibc/bits/fcntl-linux-fortify.h create mode 100644 lib/libc/include/generic-glibc/bits/openat2.h rename lib/libc/include/{loongarch-linux-gnu/bits/shmlba.h => generic-glibc/bits/types/once_flag.h} (72%) rename lib/libc/include/{generic-glibc => loongarch-linux-gnu}/gnu/lib-names-lp64d.h (100%) create mode 100644 lib/libc/include/loongarch-linux-gnu/gnu/lib-names-lp64s.h rename lib/libc/include/{generic-glibc => loongarch-linux-gnu}/gnu/stubs-lp64d.h (100%) diff --git a/lib/libc/include/aarch64-linux-gnu/bits/fcntl.h b/lib/libc/include/aarch64-linux-gnu/bits/fcntl.h index 52dee64f6f1db38ed4cfb31c5463206d8cb459c7..c317abbbf675f5f01971725bb6dbe588878d63c6 100644 --- a/lib/libc/include/aarch64-linux-gnu/bits/fcntl.h +++ b/lib/libc/include/aarch64-linux-gnu/bits/fcntl.h @@ -1,5 +1,5 @@ /* O_*, F_*, FD_* bit values for the AArch64 Linux ABI. - Copyright (C) 2011-2025 Free Software Foundation, Inc. + Copyright (C) 2011-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. diff --git a/lib/libc/include/aarch64-linux-gnu/bits/fenv.h b/lib/libc/include/aarch64-linux-gnu/bits/fenv.h index 702717e1e697f9b5bd7bd164dd9b0517cb94c747..d7bd07fc73babe81aa764359b81ef21fd9da6534 100644 --- a/lib/libc/include/aarch64-linux-gnu/bits/fenv.h +++ b/lib/libc/include/aarch64-linux-gnu/bits/fenv.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2004-2025 Free Software Foundation, Inc. +/* Copyright (C) 2004-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. diff --git a/lib/libc/include/aarch64-linux-gnu/bits/fp-fast.h b/lib/libc/include/aarch64-linux-gnu/bits/fp-fast.h index dc002b3edacdc4079152f966cfe24721c17abab5..8a75e4d867769c329c822e802814bc9689709a6f 100644 --- a/lib/libc/include/aarch64-linux-gnu/bits/fp-fast.h +++ b/lib/libc/include/aarch64-linux-gnu/bits/fp-fast.h @@ -1,5 +1,5 @@ /* Define FP_FAST_* macros. AArch64 version. - Copyright (C) 2016-2025 Free Software Foundation, Inc. + Copyright (C) 2016-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/aarch64-linux-gnu/bits/hwcap.h b/lib/libc/include/aarch64-linux-gnu/bits/hwcap.h index a2089de39f7dd89d444ee7cbe16794eb3915285c..f4189aa1bfca6909be4967e00821932faf753c7a 100644 --- a/lib/libc/include/aarch64-linux-gnu/bits/hwcap.h +++ b/lib/libc/include/aarch64-linux-gnu/bits/hwcap.h @@ -1,5 +1,5 @@ /* Defines for bits in AT_HWCAP. AArch64 Linux version. - Copyright (C) 2016-2025 Free Software Foundation, Inc. + Copyright (C) 2016-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or @@ -119,4 +119,7 @@ #define HWCAP2_SME_SF8FMA (1UL << 60) #define HWCAP2_SME_SF8DP4 (1UL << 61) #define HWCAP2_SME_SF8DP2 (1UL << 62) -#define HWCAP2_POE (1UL << 63) \ No newline at end of file +#define HWCAP2_POE (1UL << 63) + +#define HWCAP3_MTE_FAR (1UL << 0) +#define HWCAP3_MTE_STORE_ONLY (1UL << 1) \ No newline at end of file diff --git a/lib/libc/include/aarch64-linux-gnu/bits/indirect-return.h b/lib/libc/include/aarch64-linux-gnu/bits/indirect-return.h index 8f6f790cdf688daab650c7ca7662142aa8a5b6e5..d310966075c0d3a4b867759994024df0f3006170 100644 --- a/lib/libc/include/aarch64-linux-gnu/bits/indirect-return.h +++ b/lib/libc/include/aarch64-linux-gnu/bits/indirect-return.h @@ -1,5 +1,5 @@ /* Definition of __INDIRECT_RETURN. AArch64 version. - Copyright (C) 2024-2025 Free Software Foundation, Inc. + Copyright (C) 2024-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/aarch64-linux-gnu/bits/link.h b/lib/libc/include/aarch64-linux-gnu/bits/link.h index 693f135477df5919e330bf286c2b6cbcd39fe2e8..e8d9138f03c78597575905b8baa2c4e3b1d2831f 100644 --- a/lib/libc/include/aarch64-linux-gnu/bits/link.h +++ b/lib/libc/include/aarch64-linux-gnu/bits/link.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2005-2025 Free Software Foundation, Inc. +/* Copyright (C) 2005-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. diff --git a/lib/libc/include/aarch64-linux-gnu/bits/long-double.h b/lib/libc/include/aarch64-linux-gnu/bits/long-double.h index 57d7be04a685d57ab6e8e672e08d7effa853b06b..af7784dbe6dd85cd9538b5a7b437ab45de22eeb8 100644 --- a/lib/libc/include/aarch64-linux-gnu/bits/long-double.h +++ b/lib/libc/include/aarch64-linux-gnu/bits/long-double.h @@ -1,5 +1,5 @@ /* Properties of long double type. ldbl-128 version. - Copyright (C) 2016-2025 Free Software Foundation, Inc. + Copyright (C) 2016-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/aarch64-linux-gnu/bits/math-vector.h b/lib/libc/include/aarch64-linux-gnu/bits/math-vector.h index c4506228796577d29ffb3feef1ae910a36221004..68043a976c58a8f126799f7c386006471a4996eb 100644 --- a/lib/libc/include/aarch64-linux-gnu/bits/math-vector.h +++ b/lib/libc/include/aarch64-linux-gnu/bits/math-vector.h @@ -1,6 +1,6 @@ /* Platform-specific SIMD declarations of math functions. - Copyright (C) 2023-2025 Free Software Foundation, Inc. + Copyright (C) 2023-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or @@ -113,6 +113,14 @@ # define __DECL_SIMD_expm1 __DECL_SIMD_aarch64 # undef __DECL_SIMD_expm1f # define __DECL_SIMD_expm1f __DECL_SIMD_aarch64 +# undef __DECL_SIMD_exp2m1 +# define __DECL_SIMD_exp2m1 __DECL_SIMD_aarch64 +# undef __DECL_SIMD_exp2m1f +# define __DECL_SIMD_exp2m1f __DECL_SIMD_aarch64 +# undef __DECL_SIMD_exp10m1 +# define __DECL_SIMD_exp10m1 __DECL_SIMD_aarch64 +# undef __DECL_SIMD_exp10m1f +# define __DECL_SIMD_exp10m1f __DECL_SIMD_aarch64 # undef __DECL_SIMD_hypot # define __DECL_SIMD_hypot __DECL_SIMD_aarch64 # undef __DECL_SIMD_hypotf @@ -125,6 +133,10 @@ # define __DECL_SIMD_log10 __DECL_SIMD_aarch64 # undef __DECL_SIMD_log10f # define __DECL_SIMD_log10f __DECL_SIMD_aarch64 +# undef __DECL_SIMD_log10p1 +# define __DECL_SIMD_log10p1 __DECL_SIMD_aarch64 +# undef __DECL_SIMD_log10p1f +# define __DECL_SIMD_log10p1f __DECL_SIMD_aarch64 # undef __DECL_SIMD_log1p # define __DECL_SIMD_log1p __DECL_SIMD_aarch64 # undef __DECL_SIMD_log1pf @@ -133,6 +145,10 @@ # define __DECL_SIMD_log2 __DECL_SIMD_aarch64 # undef __DECL_SIMD_log2f # define __DECL_SIMD_log2f __DECL_SIMD_aarch64 +# undef __DECL_SIMD_log2p1 +# define __DECL_SIMD_log2p1 __DECL_SIMD_aarch64 +# undef __DECL_SIMD_log2p1f +# define __DECL_SIMD_log2p1f __DECL_SIMD_aarch64 # undef __DECL_SIMD_logp1 # define __DECL_SIMD_logp1 __DECL_SIMD_aarch64 # undef __DECL_SIMD_logp1f @@ -141,6 +157,10 @@ # define __DECL_SIMD_pow __DECL_SIMD_aarch64 # undef __DECL_SIMD_powf # define __DECL_SIMD_powf __DECL_SIMD_aarch64 +# undef __DECL_SIMD_rsqrt +# define __DECL_SIMD_rsqrt __DECL_SIMD_aarch64 +# undef __DECL_SIMD_rsqrtf +# define __DECL_SIMD_rsqrtf __DECL_SIMD_aarch64 # undef __DECL_SIMD_sin # define __DECL_SIMD_sin __DECL_SIMD_aarch64 # undef __DECL_SIMD_sinf @@ -212,13 +232,18 @@ __vpcs __f32x4_t _ZGVnN4v_expf (__f32x4_t); __vpcs __f32x4_t _ZGVnN4v_exp10f (__f32x4_t); __vpcs __f32x4_t _ZGVnN4v_exp2f (__f32x4_t); __vpcs __f32x4_t _ZGVnN4v_expm1f (__f32x4_t); +__vpcs __f32x4_t _ZGVnN4v_exp2m1f (__f32x4_t); +__vpcs __f32x4_t _ZGVnN4v_exp10m1f (__f32x4_t); __vpcs __f32x4_t _ZGVnN4vv_hypotf (__f32x4_t, __f32x4_t); __vpcs __f32x4_t _ZGVnN4v_logf (__f32x4_t); __vpcs __f32x4_t _ZGVnN4v_log10f (__f32x4_t); +__vpcs __f32x4_t _ZGVnN4v_log10p1f (__f32x4_t); __vpcs __f32x4_t _ZGVnN4v_log1pf (__f32x4_t); __vpcs __f32x4_t _ZGVnN4v_log2f (__f32x4_t); +__vpcs __f32x4_t _ZGVnN4v_log2p1f (__f32x4_t); __vpcs __f32x4_t _ZGVnN4v_logp1f (__f32x4_t); __vpcs __f32x4_t _ZGVnN4vv_powf (__f32x4_t, __f32x4_t); +__vpcs __f32x4_t _ZGVnN4v_rsqrtf (__f32x4_t); __vpcs __f32x4_t _ZGVnN4v_sinf (__f32x4_t); __vpcs __f32x4_t _ZGVnN4v_sinhf (__f32x4_t); __vpcs __f32x4_t _ZGVnN4v_sinpif (__f32x4_t); @@ -247,13 +272,18 @@ __vpcs __f64x2_t _ZGVnN2v_exp (__f64x2_t); __vpcs __f64x2_t _ZGVnN2v_exp10 (__f64x2_t); __vpcs __f64x2_t _ZGVnN2v_exp2 (__f64x2_t); __vpcs __f64x2_t _ZGVnN2v_expm1 (__f64x2_t); +__vpcs __f64x2_t _ZGVnN2v_exp2m1 (__f64x2_t); +__vpcs __f64x2_t _ZGVnN2v_exp10m1 (__f64x2_t); __vpcs __f64x2_t _ZGVnN2vv_hypot (__f64x2_t, __f64x2_t); __vpcs __f64x2_t _ZGVnN2v_log (__f64x2_t); __vpcs __f64x2_t _ZGVnN2v_log10 (__f64x2_t); +__vpcs __f64x2_t _ZGVnN2v_log10p1 (__f64x2_t); __vpcs __f64x2_t _ZGVnN2v_log1p (__f64x2_t); __vpcs __f64x2_t _ZGVnN2v_log2 (__f64x2_t); +__vpcs __f64x2_t _ZGVnN2v_log2p1 (__f64x2_t); __vpcs __f64x2_t _ZGVnN2v_logp1 (__f64x2_t); __vpcs __f64x2_t _ZGVnN2vv_pow (__f64x2_t, __f64x2_t); +__vpcs __f64x2_t _ZGVnN2v_rsqrt (__f64x2_t); __vpcs __f64x2_t _ZGVnN2v_sin (__f64x2_t); __vpcs __f64x2_t _ZGVnN2v_sinh (__f64x2_t); __vpcs __f64x2_t _ZGVnN2v_sinpi (__f64x2_t); @@ -287,13 +317,18 @@ __sv_f32_t _ZGVsMxv_expf (__sv_f32_t, __sv_bool_t); __sv_f32_t _ZGVsMxv_exp10f (__sv_f32_t, __sv_bool_t); __sv_f32_t _ZGVsMxv_exp2f (__sv_f32_t, __sv_bool_t); __sv_f32_t _ZGVsMxv_expm1f (__sv_f32_t, __sv_bool_t); +__sv_f32_t _ZGVsMxv_exp2m1f (__sv_f32_t, __sv_bool_t); +__sv_f32_t _ZGVsMxv_exp10m1f (__sv_f32_t, __sv_bool_t); __sv_f32_t _ZGVsMxvv_hypotf (__sv_f32_t, __sv_f32_t, __sv_bool_t); __sv_f32_t _ZGVsMxv_logf (__sv_f32_t, __sv_bool_t); __sv_f32_t _ZGVsMxv_log10f (__sv_f32_t, __sv_bool_t); +__sv_f32_t _ZGVsMxv_log10p1f (__sv_f32_t, __sv_bool_t); __sv_f32_t _ZGVsMxv_log1pf (__sv_f32_t, __sv_bool_t); __sv_f32_t _ZGVsMxv_log2f (__sv_f32_t, __sv_bool_t); +__sv_f32_t _ZGVsMxv_log2p1f (__sv_f32_t, __sv_bool_t); __sv_f32_t _ZGVsMxv_logp1f (__sv_f32_t, __sv_bool_t); __sv_f32_t _ZGVsMxvv_powf (__sv_f32_t, __sv_f32_t, __sv_bool_t); +__sv_f32_t _ZGVsMxv_rsqrtf (__sv_f32_t, __sv_bool_t); __sv_f32_t _ZGVsMxv_sinf (__sv_f32_t, __sv_bool_t); __sv_f32_t _ZGVsMxv_sinhf (__sv_f32_t, __sv_bool_t); __sv_f32_t _ZGVsMxv_sinpif (__sv_f32_t, __sv_bool_t); @@ -322,13 +357,18 @@ __sv_f64_t _ZGVsMxv_exp (__sv_f64_t, __sv_bool_t); __sv_f64_t _ZGVsMxv_exp10 (__sv_f64_t, __sv_bool_t); __sv_f64_t _ZGVsMxv_exp2 (__sv_f64_t, __sv_bool_t); __sv_f64_t _ZGVsMxv_expm1 (__sv_f64_t, __sv_bool_t); +__sv_f64_t _ZGVsMxv_exp2m1 (__sv_f64_t, __sv_bool_t); +__sv_f64_t _ZGVsMxv_exp10m1 (__sv_f64_t, __sv_bool_t); __sv_f64_t _ZGVsMxvv_hypot (__sv_f64_t, __sv_f64_t, __sv_bool_t); __sv_f64_t _ZGVsMxv_log (__sv_f64_t, __sv_bool_t); __sv_f64_t _ZGVsMxv_log10 (__sv_f64_t, __sv_bool_t); +__sv_f64_t _ZGVsMxv_log10p1 (__sv_f64_t, __sv_bool_t); __sv_f64_t _ZGVsMxv_log1p (__sv_f64_t, __sv_bool_t); __sv_f64_t _ZGVsMxv_log2 (__sv_f64_t, __sv_bool_t); +__sv_f64_t _ZGVsMxv_log2p1 (__sv_f64_t, __sv_bool_t); __sv_f64_t _ZGVsMxv_logp1 (__sv_f64_t, __sv_bool_t); __sv_f64_t _ZGVsMxvv_pow (__sv_f64_t, __sv_f64_t, __sv_bool_t); +__sv_f64_t _ZGVsMxv_rsqrt (__sv_f64_t, __sv_bool_t); __sv_f64_t _ZGVsMxv_sin (__sv_f64_t, __sv_bool_t); __sv_f64_t _ZGVsMxv_sinh (__sv_f64_t, __sv_bool_t); __sv_f64_t _ZGVsMxv_sinpi (__sv_f64_t, __sv_bool_t); diff --git a/lib/libc/include/aarch64-linux-gnu/bits/mman.h b/lib/libc/include/aarch64-linux-gnu/bits/mman.h index f09eebcbcde3c9fbed42512b6baf3a9801465101..e07d498d15128a41c01c3982245bb02bee8ef168 100644 --- a/lib/libc/include/aarch64-linux-gnu/bits/mman.h +++ b/lib/libc/include/aarch64-linux-gnu/bits/mman.h @@ -1,5 +1,5 @@ /* Definitions for POSIX memory map interface. Linux/AArch64 version. - Copyright (C) 2020-2025 Free Software Foundation, Inc. + Copyright (C) 2020-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/aarch64-linux-gnu/bits/procfs.h b/lib/libc/include/aarch64-linux-gnu/bits/procfs.h index 075f1db99b69801636a3235eefcdaeac51e26261..9ecaa7d1fa8e0d4d5df079af7b4f5a8719bbe9e8 100644 --- a/lib/libc/include/aarch64-linux-gnu/bits/procfs.h +++ b/lib/libc/include/aarch64-linux-gnu/bits/procfs.h @@ -1,5 +1,5 @@ /* Types for registers for sys/procfs.h. AArch64 version. - Copyright (C) 1996-2025 Free Software Foundation, Inc. + Copyright (C) 1996-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. diff --git a/lib/libc/include/aarch64-linux-gnu/bits/pthread_stack_min.h b/lib/libc/include/aarch64-linux-gnu/bits/pthread_stack_min.h index 0e0020a9e1c05a1e9ab46fcc5b53e2c26eb962e0..99a96d0d984bd2c0fd9ebcd4654dd69f774a0778 100644 --- a/lib/libc/include/aarch64-linux-gnu/bits/pthread_stack_min.h +++ b/lib/libc/include/aarch64-linux-gnu/bits/pthread_stack_min.h @@ -1,5 +1,5 @@ /* Definition of PTHREAD_STACK_MIN. Linux/aarch64 version. - Copyright (C) 2021-2025 Free Software Foundation, Inc. + Copyright (C) 2021-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. diff --git a/lib/libc/include/aarch64-linux-gnu/bits/pthreadtypes-arch.h b/lib/libc/include/aarch64-linux-gnu/bits/pthreadtypes-arch.h index 2dbe12da0ca4cdde3426daf253d84375a36b9bd1..d53eef2452b531fc1f5db1621d35eadc4074c852 100644 --- a/lib/libc/include/aarch64-linux-gnu/bits/pthreadtypes-arch.h +++ b/lib/libc/include/aarch64-linux-gnu/bits/pthreadtypes-arch.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2002-2025 Free Software Foundation, Inc. +/* Copyright (C) 2002-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. diff --git a/lib/libc/include/aarch64-linux-gnu/bits/rseq.h b/lib/libc/include/aarch64-linux-gnu/bits/rseq.h index f3769e9fe0beff68dd885fad216dd23d7d34d5ef..3e670c7aebdb811cba90543cead0e9bf43b40d53 100644 --- a/lib/libc/include/aarch64-linux-gnu/bits/rseq.h +++ b/lib/libc/include/aarch64-linux-gnu/bits/rseq.h @@ -1,5 +1,5 @@ /* Restartable Sequences Linux aarch64 architecture header. - Copyright (C) 2021-2025 Free Software Foundation, Inc. + Copyright (C) 2021-2026 Free Software Foundation, Inc. The GNU C Library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public diff --git a/lib/libc/include/aarch64-linux-gnu/bits/semaphore.h b/lib/libc/include/aarch64-linux-gnu/bits/semaphore.h index 7d3d93431e67f2a62e40c4fdc1c4e169aa71e096..f2e5f3635faf1d0e40aab89c41601d80a59b15b7 100644 --- a/lib/libc/include/aarch64-linux-gnu/bits/semaphore.h +++ b/lib/libc/include/aarch64-linux-gnu/bits/semaphore.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2002-2025 Free Software Foundation, Inc. +/* Copyright (C) 2002-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. diff --git a/lib/libc/include/aarch64-linux-gnu/bits/setjmp.h b/lib/libc/include/aarch64-linux-gnu/bits/setjmp.h index 36912770cb2c44c383ca40e55c6a666e5a1531f1..faecee9c63a5961732cb6e3701a4e8abc31c9c12 100644 --- a/lib/libc/include/aarch64-linux-gnu/bits/setjmp.h +++ b/lib/libc/include/aarch64-linux-gnu/bits/setjmp.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1997-2025 Free Software Foundation, Inc. +/* Copyright (C) 1997-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. diff --git a/lib/libc/include/aarch64-linux-gnu/bits/sigstack.h b/lib/libc/include/aarch64-linux-gnu/bits/sigstack.h index 0e273c571193ce6f6abb50e37707a8d50005ca7a..3cf88c099f4e3b3f462e2e3d5fd2662329b2a911 100644 --- a/lib/libc/include/aarch64-linux-gnu/bits/sigstack.h +++ b/lib/libc/include/aarch64-linux-gnu/bits/sigstack.h @@ -1,5 +1,5 @@ /* sigstack, sigaltstack definitions. - Copyright (C) 2015-2025 Free Software Foundation, Inc. + Copyright (C) 2015-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/aarch64-linux-gnu/bits/struct_rwlock.h b/lib/libc/include/aarch64-linux-gnu/bits/struct_rwlock.h index a05896c684cdc56aa6bf30e46f6677658a6f04d5..e8ceba6bf608315c4c0a8e4451b57a9ac7938266 100644 --- a/lib/libc/include/aarch64-linux-gnu/bits/struct_rwlock.h +++ b/lib/libc/include/aarch64-linux-gnu/bits/struct_rwlock.h @@ -1,5 +1,5 @@ /* AArch64 internal rwlock struct definitions. - Copyright (C) 2019-2025 Free Software Foundation, Inc. + Copyright (C) 2019-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. diff --git a/lib/libc/include/aarch64-linux-gnu/bits/struct_stat.h b/lib/libc/include/aarch64-linux-gnu/bits/struct_stat.h index 724450a6679649260300d400798787a515849f1d..0462d37a6849812f485a6830a7c0dca303332636 100644 --- a/lib/libc/include/aarch64-linux-gnu/bits/struct_stat.h +++ b/lib/libc/include/aarch64-linux-gnu/bits/struct_stat.h @@ -1,5 +1,5 @@ /* Definition for struct stat. - Copyright (C) 2020-2025 Free Software Foundation, Inc. + Copyright (C) 2020-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/aarch64-linux-gnu/bits/timesize.h b/lib/libc/include/aarch64-linux-gnu/bits/timesize.h index 04251ea75c82b9450e5c2bee30c3bbf06cbe55fb..dff2da5ed6bf30ce6f5580aee352e0958d58b623 100644 --- a/lib/libc/include/aarch64-linux-gnu/bits/timesize.h +++ b/lib/libc/include/aarch64-linux-gnu/bits/timesize.h @@ -1,5 +1,5 @@ /* Bit size of the time_t type at glibc build time, general case. - Copyright (C) 2018-2025 Free Software Foundation, Inc. + Copyright (C) 2018-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/aarch64-linux-gnu/bits/wordsize.h b/lib/libc/include/aarch64-linux-gnu/bits/wordsize.h index 3f84507cc16e18556599b2f90bda882e6e283173..d6528d5b9910b2a503c09b65d6e47f0fcb4a45e9 100644 --- a/lib/libc/include/aarch64-linux-gnu/bits/wordsize.h +++ b/lib/libc/include/aarch64-linux-gnu/bits/wordsize.h @@ -1,6 +1,6 @@ /* Determine the wordsize from the preprocessor defines. - Copyright (C) 2016-2025 Free Software Foundation, Inc. + Copyright (C) 2016-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/aarch64-linux-gnu/finclude/math-vector-fortran.h b/lib/libc/include/aarch64-linux-gnu/finclude/math-vector-fortran.h index c17752e02327e7aba89122ed2101c7ba8c0cfa5c..59c7f2db4a8dfefe910714f9a659b8e61680df63 100644 --- a/lib/libc/include/aarch64-linux-gnu/finclude/math-vector-fortran.h +++ b/lib/libc/include/aarch64-linux-gnu/finclude/math-vector-fortran.h @@ -1,5 +1,5 @@ ! Platform-specific declarations of SIMD math functions for Fortran. -*- f90 -*- -! Copyright (C) 2019-2025 Free Software Foundation, Inc. +! Copyright (C) 2019-2026 Free Software Foundation, Inc. ! This file is part of the GNU C Library. ! ! The GNU C Library is free software; you can redistribute it and/or @@ -15,33 +15,82 @@ ! You should have received a copy of the GNU Lesser General Public ! License along with the GNU C Library; if not, see ! . + !GCC$ builtin (acos) attributes simd (notinbranch) !GCC$ builtin (acosf) attributes simd (notinbranch) +!GCC$ builtin (acosh) attributes simd (notinbranch) +!GCC$ builtin (acoshf) attributes simd (notinbranch) +!GCC$ builtin (acospi) attributes simd (notinbranch) +!GCC$ builtin (acospif) attributes simd (notinbranch) !GCC$ builtin (asin) attributes simd (notinbranch) !GCC$ builtin (asinf) attributes simd (notinbranch) +!GCC$ builtin (asinh) attributes simd (notinbranch) +!GCC$ builtin (asinhf) attributes simd (notinbranch) +!GCC$ builtin (asinpi) attributes simd (notinbranch) +!GCC$ builtin (asinpif) attributes simd (notinbranch) !GCC$ builtin (atan) attributes simd (notinbranch) -!GCC$ builtin (atanf) attributes simd (notinbranch) !GCC$ builtin (atan2) attributes simd (notinbranch) !GCC$ builtin (atan2f) attributes simd (notinbranch) +!GCC$ builtin (atan2pi) attributes simd (notinbranch) +!GCC$ builtin (atan2pif) attributes simd (notinbranch) +!GCC$ builtin (atanf) attributes simd (notinbranch) +!GCC$ builtin (atanh) attributes simd (notinbranch) +!GCC$ builtin (atanhf) attributes simd (notinbranch) +!GCC$ builtin (atanpi) attributes simd (notinbranch) +!GCC$ builtin (atanpif) attributes simd (notinbranch) +!GCC$ builtin (cbrt) attributes simd (notinbranch) +!GCC$ builtin (cbrtf) attributes simd (notinbranch) !GCC$ builtin (cos) attributes simd (notinbranch) !GCC$ builtin (cosf) attributes simd (notinbranch) +!GCC$ builtin (cosh) attributes simd (notinbranch) +!GCC$ builtin (coshf) attributes simd (notinbranch) +!GCC$ builtin (cospi) attributes simd (notinbranch) +!GCC$ builtin (cospif) attributes simd (notinbranch) +!GCC$ builtin (erf) attributes simd (notinbranch) +!GCC$ builtin (erfc) attributes simd (notinbranch) +!GCC$ builtin (erfcf) attributes simd (notinbranch) +!GCC$ builtin (erff) attributes simd (notinbranch) !GCC$ builtin (exp) attributes simd (notinbranch) -!GCC$ builtin (expf) attributes simd (notinbranch) !GCC$ builtin (exp10) attributes simd (notinbranch) !GCC$ builtin (exp10f) attributes simd (notinbranch) +!GCC$ builtin (exp10m1) attributes simd (notinbranch) +!GCC$ builtin (exp10m1f) attributes simd (notinbranch) !GCC$ builtin (exp2) attributes simd (notinbranch) !GCC$ builtin (exp2f) attributes simd (notinbranch) +!GCC$ builtin (exp2m1) attributes simd (notinbranch) +!GCC$ builtin (exp2m1f) attributes simd (notinbranch) +!GCC$ builtin (expf) attributes simd (notinbranch) !GCC$ builtin (expm1) attributes simd (notinbranch) !GCC$ builtin (expm1f) attributes simd (notinbranch) +!GCC$ builtin (hypot) attributes simd (notinbranch) +!GCC$ builtin (hypotf) attributes simd (notinbranch) !GCC$ builtin (log) attributes simd (notinbranch) -!GCC$ builtin (logf) attributes simd (notinbranch) !GCC$ builtin (log10) attributes simd (notinbranch) !GCC$ builtin (log10f) attributes simd (notinbranch) +!GCC$ builtin (log10p1) attributes simd (notinbranch) +!GCC$ builtin (log10p1f) attributes simd (notinbranch) !GCC$ builtin (log1p) attributes simd (notinbranch) !GCC$ builtin (log1pf) attributes simd (notinbranch) !GCC$ builtin (log2) attributes simd (notinbranch) !GCC$ builtin (log2f) attributes simd (notinbranch) +!GCC$ builtin (log2p1) attributes simd (notinbranch) +!GCC$ builtin (log2p1f) attributes simd (notinbranch) +!GCC$ builtin (logf) attributes simd (notinbranch) +!GCC$ builtin (logp1) attributes simd (notinbranch) +!GCC$ builtin (logp1f) attributes simd (notinbranch) +!GCC$ builtin (pow) attributes simd (notinbranch) +!GCC$ builtin (powf) attributes simd (notinbranch) +!GCC$ builtin (rsqrt) attributes simd (notinbranch) +!GCC$ builtin (rsqrtf) attributes simd (notinbranch) !GCC$ builtin (sin) attributes simd (notinbranch) !GCC$ builtin (sinf) attributes simd (notinbranch) +!GCC$ builtin (sinh) attributes simd (notinbranch) +!GCC$ builtin (sinhf) attributes simd (notinbranch) +!GCC$ builtin (sinpi) attributes simd (notinbranch) +!GCC$ builtin (sinpif) attributes simd (notinbranch) !GCC$ builtin (tan) attributes simd (notinbranch) -!GCC$ builtin (tanf) attributes simd (notinbranch) \ No newline at end of file +!GCC$ builtin (tanf) attributes simd (notinbranch) +!GCC$ builtin (tanh) attributes simd (notinbranch) +!GCC$ builtin (tanhf) attributes simd (notinbranch) +!GCC$ builtin (tanpi) attributes simd (notinbranch) +!GCC$ builtin (tanpif) attributes simd (notinbranch) \ No newline at end of file diff --git a/lib/libc/include/aarch64-linux-gnu/fpu_control.h b/lib/libc/include/aarch64-linux-gnu/fpu_control.h index fcb6cf7f96221183d0ab59c11717a145bd735f0a..8c7466ad16a84d6d7d4b58ccbfb023133fc87cfe 100644 --- a/lib/libc/include/aarch64-linux-gnu/fpu_control.h +++ b/lib/libc/include/aarch64-linux-gnu/fpu_control.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1996-2025 Free Software Foundation, Inc. +/* Copyright (C) 1996-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. @@ -20,6 +20,7 @@ #define _AARCH64_FPU_CONTROL_H #include +#include /* Macros for accessing the FPCR and FPSR. */ diff --git a/lib/libc/include/aarch64-linux-gnu/ieee754.h b/lib/libc/include/aarch64-linux-gnu/ieee754.h index a49523c3d8faa01c2f79bd6e565578db52ddb727..a19fd8dcd1d39c39a1b5000ca0b4d3f8da2c2299 100644 --- a/lib/libc/include/aarch64-linux-gnu/ieee754.h +++ b/lib/libc/include/aarch64-linux-gnu/ieee754.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1992-2025 Free Software Foundation, Inc. +/* Copyright (C) 1992-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/aarch64-linux-gnu/sys/elf.h b/lib/libc/include/aarch64-linux-gnu/sys/elf.h index 3611e2908d0f138ab83ef310ed36f55774eb759d..68b7bb210d1161d9a37d55a2f14e2a39a1ce4c76 100644 --- a/lib/libc/include/aarch64-linux-gnu/sys/elf.h +++ b/lib/libc/include/aarch64-linux-gnu/sys/elf.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1996-2025 Free Software Foundation, Inc. +/* Copyright (C) 1996-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. diff --git a/lib/libc/include/aarch64-linux-gnu/sys/ptrace.h b/lib/libc/include/aarch64-linux-gnu/sys/ptrace.h index 9dda96614873460fdf7f022d455cce417798125a..cb3ddcbc697a28961d7decde1c349f95c3d82cc7 100644 --- a/lib/libc/include/aarch64-linux-gnu/sys/ptrace.h +++ b/lib/libc/include/aarch64-linux-gnu/sys/ptrace.h @@ -1,5 +1,5 @@ /* `ptrace' debugger support interface. Linux/AArch64 version. - Copyright (C) 1996-2025 Free Software Foundation, Inc. + Copyright (C) 1996-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. diff --git a/lib/libc/include/aarch64-linux-gnu/sys/ucontext.h b/lib/libc/include/aarch64-linux-gnu/sys/ucontext.h index 4866e4fd961d35bf24d862c5cbed29e4ff280699..b84911aa1492b04102864e2cb12a692c54bbe00d 100644 --- a/lib/libc/include/aarch64-linux-gnu/sys/ucontext.h +++ b/lib/libc/include/aarch64-linux-gnu/sys/ucontext.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1998-2025 Free Software Foundation, Inc. +/* Copyright (C) 1998-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. @@ -63,7 +63,7 @@ typedef struct unsigned char __reserved[4096] __attribute__ ((__aligned__ (16))); } mcontext_t; -/* Userlevel context. */ +/* User-level context. */ typedef struct ucontext_t { unsigned long __ctx(uc_flags); diff --git a/lib/libc/include/aarch64-linux-gnu/sys/user.h b/lib/libc/include/aarch64-linux-gnu/sys/user.h index b370717dc2f1c3d21038a48e784c84555ce93278..3d622faaecf8e5bf4d50c33c8fb5e73d6bd06f4a 100644 --- a/lib/libc/include/aarch64-linux-gnu/sys/user.h +++ b/lib/libc/include/aarch64-linux-gnu/sys/user.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2009-2025 Free Software Foundation, Inc. +/* Copyright (C) 2009-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. diff --git a/lib/libc/include/arc-linux-gnu/bits/fcntl.h b/lib/libc/include/arc-linux-gnu/bits/fcntl.h index e033990adc664eaa42782c108779ce3c03e63567..000bc724699be2e3240d7be6fdf3b51a79550207 100644 --- a/lib/libc/include/arc-linux-gnu/bits/fcntl.h +++ b/lib/libc/include/arc-linux-gnu/bits/fcntl.h @@ -1,5 +1,5 @@ /* O_*, F_*, FD_* bit values for the generic Linux ABI. - Copyright (C) 2011-2025 Free Software Foundation, Inc. + Copyright (C) 2011-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/arc-linux-gnu/bits/fenv.h b/lib/libc/include/arc-linux-gnu/bits/fenv.h index 382f0d08c46c7b63031f564d45cef414276258af..fbda571543a1715844be341cdd255afdd009803a 100644 --- a/lib/libc/include/arc-linux-gnu/bits/fenv.h +++ b/lib/libc/include/arc-linux-gnu/bits/fenv.h @@ -1,5 +1,5 @@ /* Floating point environment. ARC version. - Copyright (C) 2020-2025 Free Software Foundation, Inc. + Copyright (C) 2020-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/arc-linux-gnu/bits/floatn.h b/lib/libc/include/arc-linux-gnu/bits/floatn.h index 9005dc8601d52334aa53591640f009c59f6e6672..d3408f15249d51ab748cb0313f4cc3f36b09cdda 100644 --- a/lib/libc/include/arc-linux-gnu/bits/floatn.h +++ b/lib/libc/include/arc-linux-gnu/bits/floatn.h @@ -1,5 +1,5 @@ /* Macros to control TS 18661-3 glibc features. - Copyright (C) 2017-2025 Free Software Foundation, Inc. + Copyright (C) 2017-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/arc-linux-gnu/bits/link.h b/lib/libc/include/arc-linux-gnu/bits/link.h index 594c6c8098af495baa8ddf23678a6eecc934c6fd..eadaf80a4a7d34d083d1e9a3b636668d282c8e8d 100644 --- a/lib/libc/include/arc-linux-gnu/bits/link.h +++ b/lib/libc/include/arc-linux-gnu/bits/link.h @@ -1,5 +1,5 @@ /* Machine-specific declarations for dynamic linker interface, ARC version. - Copyright (C) 2020-2025 Free Software Foundation, Inc. + Copyright (C) 2020-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/arc-linux-gnu/bits/long-double.h b/lib/libc/include/arc-linux-gnu/bits/long-double.h index d07f1a74182121c8f23b5008dada66f34b6b1a86..ffaee6c9be5dc2b72f99833c8773fc72b1f6c2e8 100644 --- a/lib/libc/include/arc-linux-gnu/bits/long-double.h +++ b/lib/libc/include/arc-linux-gnu/bits/long-double.h @@ -1,5 +1,5 @@ /* Properties of long double type. - Copyright (C) 2016-2025 Free Software Foundation, Inc. + Copyright (C) 2016-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/arc-linux-gnu/bits/procfs.h b/lib/libc/include/arc-linux-gnu/bits/procfs.h index f5c1a9db48d542e3f402d4a6b59e94b7b28d71bf..9d59fd0f3106f124ba59b7ecf2c0d349f61172c7 100644 --- a/lib/libc/include/arc-linux-gnu/bits/procfs.h +++ b/lib/libc/include/arc-linux-gnu/bits/procfs.h @@ -1,5 +1,5 @@ /* Types for registers for sys/procfs.h. ARC version. - Copyright (C) 2020-2025 Free Software Foundation, Inc. + Copyright (C) 2020-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/arc-linux-gnu/bits/rseq.h b/lib/libc/include/arc-linux-gnu/bits/rseq.h index 90b3e9804a5b827612f37a31f9805ee0d82ad9a4..a844012dd3a527f6948a027145ecc10a1f01903f 100644 --- a/lib/libc/include/arc-linux-gnu/bits/rseq.h +++ b/lib/libc/include/arc-linux-gnu/bits/rseq.h @@ -1,5 +1,5 @@ /* Restartable Sequences architecture header. Stub version. - Copyright (C) 2021-2025 Free Software Foundation, Inc. + Copyright (C) 2021-2026 Free Software Foundation, Inc. The GNU C Library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public diff --git a/lib/libc/include/arc-linux-gnu/bits/setjmp.h b/lib/libc/include/arc-linux-gnu/bits/setjmp.h index 232c097bd5c940e0fb54f73fff592e131d25f9b1..f6273a7471eff91c10501aeb4c2c54475e686fba 100644 --- a/lib/libc/include/arc-linux-gnu/bits/setjmp.h +++ b/lib/libc/include/arc-linux-gnu/bits/setjmp.h @@ -1,5 +1,5 @@ /* Define the machine-dependent type 'jmp_buf'. ARC version. - Copyright (C) 2020-2025 Free Software Foundation, Inc. + Copyright (C) 2020-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/arc-linux-gnu/bits/struct_stat.h b/lib/libc/include/arc-linux-gnu/bits/struct_stat.h index 724450a6679649260300d400798787a515849f1d..0462d37a6849812f485a6830a7c0dca303332636 100644 --- a/lib/libc/include/arc-linux-gnu/bits/struct_stat.h +++ b/lib/libc/include/arc-linux-gnu/bits/struct_stat.h @@ -1,5 +1,5 @@ /* Definition for struct stat. - Copyright (C) 2020-2025 Free Software Foundation, Inc. + Copyright (C) 2020-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/arc-linux-gnu/bits/timesize.h b/lib/libc/include/arc-linux-gnu/bits/timesize.h index 04251ea75c82b9450e5c2bee30c3bbf06cbe55fb..dff2da5ed6bf30ce6f5580aee352e0958d58b623 100644 --- a/lib/libc/include/arc-linux-gnu/bits/timesize.h +++ b/lib/libc/include/arc-linux-gnu/bits/timesize.h @@ -1,5 +1,5 @@ /* Bit size of the time_t type at glibc build time, general case. - Copyright (C) 2018-2025 Free Software Foundation, Inc. + Copyright (C) 2018-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/arc-linux-gnu/bits/wordsize.h b/lib/libc/include/arc-linux-gnu/bits/wordsize.h index 6b841cc0f50c1acb397ec38a53ff16cb3b4875e6..3f6991890c6ab3413fe26fe4cbf6f3008db7a1fc 100644 --- a/lib/libc/include/arc-linux-gnu/bits/wordsize.h +++ b/lib/libc/include/arc-linux-gnu/bits/wordsize.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1999-2025 Free Software Foundation, Inc. +/* Copyright (C) 1999-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/arc-linux-gnu/fpu_control.h b/lib/libc/include/arc-linux-gnu/fpu_control.h index c4db39e9a64d6c90a561457b54abdd2ad5d6552a..f2d30f37a9c685a09130b6d8e09c640bbcc60cbc 100644 --- a/lib/libc/include/arc-linux-gnu/fpu_control.h +++ b/lib/libc/include/arc-linux-gnu/fpu_control.h @@ -1,5 +1,5 @@ /* FPU control word bits. ARC version. - Copyright (C) 2020-2025 Free Software Foundation, Inc. + Copyright (C) 2020-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/arc-linux-gnu/sys/cachectl.h b/lib/libc/include/arc-linux-gnu/sys/cachectl.h index 8300c25094f5d272b13cfca28f320328c9220688..ea9c31999c47a56ae4e057bbe9c22200864f7131 100644 --- a/lib/libc/include/arc-linux-gnu/sys/cachectl.h +++ b/lib/libc/include/arc-linux-gnu/sys/cachectl.h @@ -1,5 +1,5 @@ /* cacheflush - flush contents of instruction and/or data cache. - Copyright (C) 2020-2025 Free Software Foundation, Inc. + Copyright (C) 2020-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/arc-linux-gnu/sys/ucontext.h b/lib/libc/include/arc-linux-gnu/sys/ucontext.h index 299dcfa2cf347dff9909ca0d79acaeef895400ba..998d13adf426b7e5686ac9019aec9eb371d17ae0 100644 --- a/lib/libc/include/arc-linux-gnu/sys/ucontext.h +++ b/lib/libc/include/arc-linux-gnu/sys/ucontext.h @@ -1,5 +1,5 @@ /* struct ucontext definition, ARC version. - Copyright (C) 2020-2025 Free Software Foundation, Inc. + Copyright (C) 2020-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/arc-linux-gnu/sys/user.h b/lib/libc/include/arc-linux-gnu/sys/user.h index f108a30177d779d0372d4c79ae710187d3a9a2fa..661b16287255c5de6ca2abfbc2c91f04a84c9c2c 100644 --- a/lib/libc/include/arc-linux-gnu/sys/user.h +++ b/lib/libc/include/arc-linux-gnu/sys/user.h @@ -1,5 +1,5 @@ /* ptrace register data format definitions. - Copyright (C) 2020-2025 Free Software Foundation, Inc. + Copyright (C) 2020-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/arm-linux-gnu/bits/dl_find_object.h b/lib/libc/include/arm-linux-gnu/bits/dl_find_object.h index 46912bb1d346b4c2a0939ce3f44abc24d57845e7..38dad3f062d2b27fdcbbeb0a38fb7bfb8e4bdcae 100644 --- a/lib/libc/include/arm-linux-gnu/bits/dl_find_object.h +++ b/lib/libc/include/arm-linux-gnu/bits/dl_find_object.h @@ -1,5 +1,5 @@ /* arm definitions for finding objects. - Copyright (C) 2021-2025 Free Software Foundation, Inc. + Copyright (C) 2021-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/arm-linux-gnu/bits/fcntl.h b/lib/libc/include/arm-linux-gnu/bits/fcntl.h index 14a203d05069acf0b065edfed03e6d0b14d31b19..e0ab5fa08226837096d3f981525ad2177130f5ab 100644 --- a/lib/libc/include/arm-linux-gnu/bits/fcntl.h +++ b/lib/libc/include/arm-linux-gnu/bits/fcntl.h @@ -1,5 +1,5 @@ /* O_*, F_*, FD_* bit values for Linux. - Copyright (C) 1995-2025 Free Software Foundation, Inc. + Copyright (C) 1995-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/arm-linux-gnu/bits/fenv.h b/lib/libc/include/arm-linux-gnu/bits/fenv.h index 4815a06d47eebd9626acf0629282c783ef4cc434..e897e42603eeb2d599b1c5607f837abc88ece2aa 100644 --- a/lib/libc/include/arm-linux-gnu/bits/fenv.h +++ b/lib/libc/include/arm-linux-gnu/bits/fenv.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2004-2025 Free Software Foundation, Inc. +/* Copyright (C) 2004-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/arm-linux-gnu/bits/floatn.h b/lib/libc/include/arm-linux-gnu/bits/floatn.h index 9005dc8601d52334aa53591640f009c59f6e6672..d3408f15249d51ab748cb0313f4cc3f36b09cdda 100644 --- a/lib/libc/include/arm-linux-gnu/bits/floatn.h +++ b/lib/libc/include/arm-linux-gnu/bits/floatn.h @@ -1,5 +1,5 @@ /* Macros to control TS 18661-3 glibc features. - Copyright (C) 2017-2025 Free Software Foundation, Inc. + Copyright (C) 2017-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/arm-linux-gnu/bits/hwcap.h b/lib/libc/include/arm-linux-gnu/bits/hwcap.h index 7b16f0e9d699353c91ea50fece74e44f14a52dea..335e4935c64e5c8d83d79e09b25a7b088a40f91c 100644 --- a/lib/libc/include/arm-linux-gnu/bits/hwcap.h +++ b/lib/libc/include/arm-linux-gnu/bits/hwcap.h @@ -1,5 +1,5 @@ /* Defines for bits in AT_HWCAP. ARM Linux version. - Copyright (C) 2012-2025 Free Software Foundation, Inc. + Copyright (C) 2012-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/arm-linux-gnu/bits/link.h b/lib/libc/include/arm-linux-gnu/bits/link.h index 8060167ec18ad909c5dfc33a06537937f1239922..6d0cdd01968d273a12f5c305443f0aa362acff54 100644 --- a/lib/libc/include/arm-linux-gnu/bits/link.h +++ b/lib/libc/include/arm-linux-gnu/bits/link.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2005-2025 Free Software Foundation, Inc. +/* Copyright (C) 2005-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/arm-linux-gnu/bits/long-double.h b/lib/libc/include/arm-linux-gnu/bits/long-double.h index d07f1a74182121c8f23b5008dada66f34b6b1a86..ffaee6c9be5dc2b72f99833c8773fc72b1f6c2e8 100644 --- a/lib/libc/include/arm-linux-gnu/bits/long-double.h +++ b/lib/libc/include/arm-linux-gnu/bits/long-double.h @@ -1,5 +1,5 @@ /* Properties of long double type. - Copyright (C) 2016-2025 Free Software Foundation, Inc. + Copyright (C) 2016-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/arm-linux-gnu/bits/procfs-id.h b/lib/libc/include/arm-linux-gnu/bits/procfs-id.h index 58c2162a88389d76131cf50c8642c8fce8b78cdb..8b0d8a502e3c29a3c92e74c49309d5767a15d463 100644 --- a/lib/libc/include/arm-linux-gnu/bits/procfs-id.h +++ b/lib/libc/include/arm-linux-gnu/bits/procfs-id.h @@ -1,5 +1,5 @@ /* Types of pr_uid and pr_gid in struct elf_prpsinfo. Arm version. - Copyright (C) 2018-2025 Free Software Foundation, Inc. + Copyright (C) 2018-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. diff --git a/lib/libc/include/arm-linux-gnu/bits/procfs.h b/lib/libc/include/arm-linux-gnu/bits/procfs.h index 2a60d31099c9fce19c253ee399477557fa95edd7..37497a28990bb5613503782859c2ff7247306fee 100644 --- a/lib/libc/include/arm-linux-gnu/bits/procfs.h +++ b/lib/libc/include/arm-linux-gnu/bits/procfs.h @@ -1,5 +1,5 @@ /* Types for registers for sys/procfs.h. Arm version. - Copyright (C) 1996-2025 Free Software Foundation, Inc. + Copyright (C) 1996-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/arm-linux-gnu/bits/rseq.h b/lib/libc/include/arm-linux-gnu/bits/rseq.h index 1d6a1639407b3ce56734e324e3dedb45f541b617..8f53196582e8804bd5dcb1e67c702580130c2211 100644 --- a/lib/libc/include/arm-linux-gnu/bits/rseq.h +++ b/lib/libc/include/arm-linux-gnu/bits/rseq.h @@ -1,5 +1,5 @@ /* Restartable Sequences Linux arm architecture header. - Copyright (C) 2021-2025 Free Software Foundation, Inc. + Copyright (C) 2021-2026 Free Software Foundation, Inc. The GNU C Library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public diff --git a/lib/libc/include/arm-linux-gnu/bits/setjmp.h b/lib/libc/include/arm-linux-gnu/bits/setjmp.h index 7fdd2e4f5216d627f37ac28f750c22a9d55bb41d..43c5fc77bdf5cb3338bc7d9ef1acf6530e72f118 100644 --- a/lib/libc/include/arm-linux-gnu/bits/setjmp.h +++ b/lib/libc/include/arm-linux-gnu/bits/setjmp.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2004-2025 Free Software Foundation, Inc. +/* Copyright (C) 2004-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/arm-linux-gnu/bits/shmlba.h b/lib/libc/include/arm-linux-gnu/bits/shmlba.h index c615e207966dc2ae9c0b93866329744fa3f20612..b12474c3f126dbdaf523bd9ce019ad719df488f8 100644 --- a/lib/libc/include/arm-linux-gnu/bits/shmlba.h +++ b/lib/libc/include/arm-linux-gnu/bits/shmlba.h @@ -1,5 +1,5 @@ /* Define SHMLBA. ARM version. - Copyright (C) 2018-2025 Free Software Foundation, Inc. + Copyright (C) 2018-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/arm-linux-gnu/bits/struct_stat.h b/lib/libc/include/arm-linux-gnu/bits/struct_stat.h index 71196f193f51ff5e39d8d82ce08080e976a4cc19..8a057c16332fbc6c9fd0ab02e1c516c48ed4b5b5 100644 --- a/lib/libc/include/arm-linux-gnu/bits/struct_stat.h +++ b/lib/libc/include/arm-linux-gnu/bits/struct_stat.h @@ -1,5 +1,5 @@ /* Definition for struct stat. Linux/arm version. - Copyright (C) 2020-2025 Free Software Foundation, Inc. + Copyright (C) 2020-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/arm-linux-gnu/bits/timesize.h b/lib/libc/include/arm-linux-gnu/bits/timesize.h index 3b50f81112d242016b8bdc49619fbc29ef69ee86..9b0a0ec2e9b71a9c51d3a37e00b9bb1b6a03d7bd 100644 --- a/lib/libc/include/arm-linux-gnu/bits/timesize.h +++ b/lib/libc/include/arm-linux-gnu/bits/timesize.h @@ -1,5 +1,5 @@ /* Bit size of the time_t type at glibc build time, Linux/ARM. - Copyright (C) 2021-2025 Free Software Foundation, Inc. + Copyright (C) 2021-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/arm-linux-gnu/bits/typesizes.h b/lib/libc/include/arm-linux-gnu/bits/typesizes.h index 11790d7eacb44275ba257c83a60b30396821f92a..200578b9f26caac4d2f4e81867e8bf560771321e 100644 --- a/lib/libc/include/arm-linux-gnu/bits/typesizes.h +++ b/lib/libc/include/arm-linux-gnu/bits/typesizes.h @@ -1,5 +1,5 @@ /* bits/typesizes.h -- underlying types for *_t. ARM version. - Copyright (C) 2002-2025 Free Software Foundation, Inc. + Copyright (C) 2002-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/arm-linux-gnu/bits/wordsize.h b/lib/libc/include/arm-linux-gnu/bits/wordsize.h index db329914346120034ed744f869ca475dda80c060..6e4c479fe4392ef20c85cd861dcde13f3825051d 100644 --- a/lib/libc/include/arm-linux-gnu/bits/wordsize.h +++ b/lib/libc/include/arm-linux-gnu/bits/wordsize.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1999-2025 Free Software Foundation, Inc. +/* Copyright (C) 1999-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/arm-linux-gnu/fpu_control.h b/lib/libc/include/arm-linux-gnu/fpu_control.h index 57a0c0548b0804b63ea5b2bc79e997980b122588..4dfc9182f38ba78eae2e45f88d2ade42a2db8a56 100644 --- a/lib/libc/include/arm-linux-gnu/fpu_control.h +++ b/lib/libc/include/arm-linux-gnu/fpu_control.h @@ -1,5 +1,5 @@ /* FPU control word definitions. ARM VFP version. - Copyright (C) 2004-2025 Free Software Foundation, Inc. + Copyright (C) 2004-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/arm-linux-gnu/sys/ptrace.h b/lib/libc/include/arm-linux-gnu/sys/ptrace.h index 781cc3578de338a87b73f8e38a01dc8d019fec8e..de859feabcb089f7eac1be4283a5b0ccdd3ceb8f 100644 --- a/lib/libc/include/arm-linux-gnu/sys/ptrace.h +++ b/lib/libc/include/arm-linux-gnu/sys/ptrace.h @@ -1,5 +1,5 @@ /* `ptrace' debugger support interface. Linux/ARM version. - Copyright (C) 1996-2025 Free Software Foundation, Inc. + Copyright (C) 1996-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. diff --git a/lib/libc/include/arm-linux-gnu/sys/ucontext.h b/lib/libc/include/arm-linux-gnu/sys/ucontext.h index 56c60d929a6b53700ca6e930d0ec3378b3f406a5..737037437a7170417776e7a58b6e2d1255a284d1 100644 --- a/lib/libc/include/arm-linux-gnu/sys/ucontext.h +++ b/lib/libc/include/arm-linux-gnu/sys/ucontext.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1998-2025 Free Software Foundation, Inc. +/* Copyright (C) 1998-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/arm-linux-gnu/sys/user.h b/lib/libc/include/arm-linux-gnu/sys/user.h index 37a8cc78f8a436662e529411da779b71c78d08cd..f5669eca1a5f8008fee0fe87509ce8f457b5691a 100644 --- a/lib/libc/include/arm-linux-gnu/sys/user.h +++ b/lib/libc/include/arm-linux-gnu/sys/user.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1998-2025 Free Software Foundation, Inc. +/* Copyright (C) 1998-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/csky-linux-gnu/bits/fcntl.h b/lib/libc/include/csky-linux-gnu/bits/fcntl.h index e033990adc664eaa42782c108779ce3c03e63567..000bc724699be2e3240d7be6fdf3b51a79550207 100644 --- a/lib/libc/include/csky-linux-gnu/bits/fcntl.h +++ b/lib/libc/include/csky-linux-gnu/bits/fcntl.h @@ -1,5 +1,5 @@ /* O_*, F_*, FD_* bit values for the generic Linux ABI. - Copyright (C) 2011-2025 Free Software Foundation, Inc. + Copyright (C) 2011-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/csky-linux-gnu/bits/fenv.h b/lib/libc/include/csky-linux-gnu/bits/fenv.h index 474af2596c3c00741feb0fd669e6b3bc35e6826d..0bba92429353378a73114c060c329f0058f49b74 100644 --- a/lib/libc/include/csky-linux-gnu/bits/fenv.h +++ b/lib/libc/include/csky-linux-gnu/bits/fenv.h @@ -1,5 +1,5 @@ /* Floating point environment. C-SKY version. - Copyright (C) 2018-2025 Free Software Foundation, Inc. + Copyright (C) 2018-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/csky-linux-gnu/bits/floatn.h b/lib/libc/include/csky-linux-gnu/bits/floatn.h index 9005dc8601d52334aa53591640f009c59f6e6672..d3408f15249d51ab748cb0313f4cc3f36b09cdda 100644 --- a/lib/libc/include/csky-linux-gnu/bits/floatn.h +++ b/lib/libc/include/csky-linux-gnu/bits/floatn.h @@ -1,5 +1,5 @@ /* Macros to control TS 18661-3 glibc features. - Copyright (C) 2017-2025 Free Software Foundation, Inc. + Copyright (C) 2017-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/csky-linux-gnu/bits/link.h b/lib/libc/include/csky-linux-gnu/bits/link.h index ca027d0e88466c7ab34f449fde90eba92c0fe9d6..9c46039e1b10adb64203f011a981b8bfe5ef3574 100644 --- a/lib/libc/include/csky-linux-gnu/bits/link.h +++ b/lib/libc/include/csky-linux-gnu/bits/link.h @@ -1,5 +1,5 @@ /* Machine-specific declarations for dynamic linker interface. C-SKY version. - Copyright (C) 2018-2025 Free Software Foundation, Inc. + Copyright (C) 2018-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/csky-linux-gnu/bits/long-double.h b/lib/libc/include/csky-linux-gnu/bits/long-double.h index d07f1a74182121c8f23b5008dada66f34b6b1a86..ffaee6c9be5dc2b72f99833c8773fc72b1f6c2e8 100644 --- a/lib/libc/include/csky-linux-gnu/bits/long-double.h +++ b/lib/libc/include/csky-linux-gnu/bits/long-double.h @@ -1,5 +1,5 @@ /* Properties of long double type. - Copyright (C) 2016-2025 Free Software Foundation, Inc. + Copyright (C) 2016-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/csky-linux-gnu/bits/procfs.h b/lib/libc/include/csky-linux-gnu/bits/procfs.h index 91bc54a3fc7ca5637423570856e0de26422d4dd1..a2faeb5fbdd534fd47fa0ac01be5a92664dded2b 100644 --- a/lib/libc/include/csky-linux-gnu/bits/procfs.h +++ b/lib/libc/include/csky-linux-gnu/bits/procfs.h @@ -1,5 +1,5 @@ /* Types for registers for sys/procfs.h. C-SKY version. - Copyright (C) 2018-2025 Free Software Foundation, Inc. + Copyright (C) 2018-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/csky-linux-gnu/bits/rseq.h b/lib/libc/include/csky-linux-gnu/bits/rseq.h index 90b3e9804a5b827612f37a31f9805ee0d82ad9a4..a844012dd3a527f6948a027145ecc10a1f01903f 100644 --- a/lib/libc/include/csky-linux-gnu/bits/rseq.h +++ b/lib/libc/include/csky-linux-gnu/bits/rseq.h @@ -1,5 +1,5 @@ /* Restartable Sequences architecture header. Stub version. - Copyright (C) 2021-2025 Free Software Foundation, Inc. + Copyright (C) 2021-2026 Free Software Foundation, Inc. The GNU C Library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public diff --git a/lib/libc/include/csky-linux-gnu/bits/setjmp.h b/lib/libc/include/csky-linux-gnu/bits/setjmp.h index 8edb4c0c1a6a31027e1ae6e10d670f0122b2f730..5486c273e6db1ebe899671c51b57429547ba3a0f 100644 --- a/lib/libc/include/csky-linux-gnu/bits/setjmp.h +++ b/lib/libc/include/csky-linux-gnu/bits/setjmp.h @@ -1,5 +1,5 @@ /* Define the machine-dependent type `jmp_buf'. C-SKY version. - Copyright (C) 2018-2025 Free Software Foundation, Inc. + Copyright (C) 2018-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/csky-linux-gnu/bits/shmlba.h b/lib/libc/include/csky-linux-gnu/bits/shmlba.h index 79e12b823706a000a95fb682e4bfbbe2c183c0ee..5d7d35e3c97ef03db7d72e67cef665e8bcadfadc 100644 --- a/lib/libc/include/csky-linux-gnu/bits/shmlba.h +++ b/lib/libc/include/csky-linux-gnu/bits/shmlba.h @@ -1,5 +1,5 @@ /* Define SHMLBA. C-SKY version. - Copyright (C) 2018-2025 Free Software Foundation, Inc. + Copyright (C) 2018-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/csky-linux-gnu/bits/statfs.h b/lib/libc/include/csky-linux-gnu/bits/statfs.h index 13479a330b5df6558e4bc43694583f8dec07bd00..8188a1106b02625663d4ed15c700543eb70f6f4e 100644 --- a/lib/libc/include/csky-linux-gnu/bits/statfs.h +++ b/lib/libc/include/csky-linux-gnu/bits/statfs.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2011-2025 Free Software Foundation, Inc. +/* Copyright (C) 2011-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/csky-linux-gnu/bits/struct_stat.h b/lib/libc/include/csky-linux-gnu/bits/struct_stat.h index 4064985100f6f71bbc12906020bb3685a7357a3a..b5693ad4ef518fea3870d4045000f3962588e84d 100644 --- a/lib/libc/include/csky-linux-gnu/bits/struct_stat.h +++ b/lib/libc/include/csky-linux-gnu/bits/struct_stat.h @@ -1,5 +1,5 @@ /* Definition for struct stat. Linux/csky version. - Copyright (C) 2020-2025 Free Software Foundation, Inc. + Copyright (C) 2020-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/csky-linux-gnu/bits/timesize.h b/lib/libc/include/csky-linux-gnu/bits/timesize.h index f97e401df5b1495c5b6c3490df7fc74762c70131..72234351344bad7992324022f8e4608d2b8d61d6 100644 --- a/lib/libc/include/csky-linux-gnu/bits/timesize.h +++ b/lib/libc/include/csky-linux-gnu/bits/timesize.h @@ -1,5 +1,5 @@ /* Bit size of the time_t type at glibc build time, Linux/csky. - Copyright (C) 2021-2025 Free Software Foundation, Inc. + Copyright (C) 2021-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/csky-linux-gnu/bits/wordsize.h b/lib/libc/include/csky-linux-gnu/bits/wordsize.h index db329914346120034ed744f869ca475dda80c060..6e4c479fe4392ef20c85cd861dcde13f3825051d 100644 --- a/lib/libc/include/csky-linux-gnu/bits/wordsize.h +++ b/lib/libc/include/csky-linux-gnu/bits/wordsize.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1999-2025 Free Software Foundation, Inc. +/* Copyright (C) 1999-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/csky-linux-gnu/fpu_control.h b/lib/libc/include/csky-linux-gnu/fpu_control.h index f7a844d2fd7d7be382c90dc9a94b105ef9984160..99edc54621423c4b1ac23a4c36c29e5c6784c4b7 100644 --- a/lib/libc/include/csky-linux-gnu/fpu_control.h +++ b/lib/libc/include/csky-linux-gnu/fpu_control.h @@ -1,5 +1,5 @@ /* FPU control word bits. C-SKY version. - Copyright (C) 2018-2025 Free Software Foundation, Inc. + Copyright (C) 2018-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/csky-linux-gnu/sys/cachectl.h b/lib/libc/include/csky-linux-gnu/sys/cachectl.h index 13ea674214b1e4459208eabbaccf7cb8cf072119..1b855735be55606f0e348b6c542887b9d5091960 100644 --- a/lib/libc/include/csky-linux-gnu/sys/cachectl.h +++ b/lib/libc/include/csky-linux-gnu/sys/cachectl.h @@ -1,5 +1,5 @@ /* C-SKY cache flushing interface. - Copyright (C) 2018-2025 Free Software Foundation, Inc. + Copyright (C) 2018-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/csky-linux-gnu/sys/ucontext.h b/lib/libc/include/csky-linux-gnu/sys/ucontext.h index 629a841f77407d923a1e4ce5525dcb67a21b4301..2917185c9f977da8e982e95fbb4c5d86ad016428 100644 --- a/lib/libc/include/csky-linux-gnu/sys/ucontext.h +++ b/lib/libc/include/csky-linux-gnu/sys/ucontext.h @@ -1,5 +1,5 @@ /* struct ucontext definition, C-SKY version. - Copyright (C) 2018-2025 Free Software Foundation, Inc. + Copyright (C) 2018-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/csky-linux-gnu/sys/user.h b/lib/libc/include/csky-linux-gnu/sys/user.h index b6b16709f710a13bec3159d64ad486bc6c779ef5..8360978ed969855532963410f7929f3fce8237c0 100644 --- a/lib/libc/include/csky-linux-gnu/sys/user.h +++ b/lib/libc/include/csky-linux-gnu/sys/user.h @@ -1,6 +1,6 @@ /* This file is not used by C-SKY GDB. ptrace can use pt_regs definition from linux kernel directly. - Copyright (C) 2018-2025 Free Software Foundation, Inc. + Copyright (C) 2018-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/aio.h b/lib/libc/include/generic-glibc/aio.h index 045f9d0897114745537b2ee26739339424beed26..3f25c83b31796402368dd7adf94db7c25c36255a 100644 --- a/lib/libc/include/generic-glibc/aio.h +++ b/lib/libc/include/generic-glibc/aio.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1996-2025 Free Software Foundation, Inc. +/* Copyright (C) 1996-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/aliases.h b/lib/libc/include/generic-glibc/aliases.h index 4198827e0742971a80069808b108af1ba4d0a77b..a74245fd650c3897a7639048beb9bd847df6c10f 100644 --- a/lib/libc/include/generic-glibc/aliases.h +++ b/lib/libc/include/generic-glibc/aliases.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1996-2025 Free Software Foundation, Inc. +/* Copyright (C) 1996-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/alloca.h b/lib/libc/include/generic-glibc/alloca.h index 86aaae0ed0ace9520bd93907443766fd19a5fa91..eb230b210418037b2fa3b1d0a09788e166cd4d33 100644 --- a/lib/libc/include/generic-glibc/alloca.h +++ b/lib/libc/include/generic-glibc/alloca.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1992-2025 Free Software Foundation, Inc. +/* Copyright (C) 1992-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/ar.h b/lib/libc/include/generic-glibc/ar.h index b42f96feddae628d7be05401f0321390e74b87d8..560897719d8caef570a8ec01c755c769bce11bb7 100644 --- a/lib/libc/include/generic-glibc/ar.h +++ b/lib/libc/include/generic-glibc/ar.h @@ -1,5 +1,5 @@ /* Header describing `ar' archive file format. - Copyright (C) 1996-2025 Free Software Foundation, Inc. + Copyright (C) 1996-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/argp.h b/lib/libc/include/generic-glibc/argp.h index cd619e4b197d3c3815000cf3af4e266b2f4b2049..4e510b88e785cb01e9e43adbf524fcd22b4c54dc 100644 --- a/lib/libc/include/generic-glibc/argp.h +++ b/lib/libc/include/generic-glibc/argp.h @@ -1,5 +1,5 @@ /* Hierarchical argument parsing, layered over getopt. - Copyright (C) 1995-2025 Free Software Foundation, Inc. + Copyright (C) 1995-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. Written by Miles Bader . @@ -518,17 +518,13 @@ extern void *__argp_input (const struct argp *__restrict __argp, # define __option_is_end _option_is_end # endif -# ifndef ARGP_EI -# define ARGP_EI __extern_inline -# endif - -ARGP_EI void +__extern_inline void __argp_usage (const struct argp_state *__state) { __argp_state_help (__state, stderr, ARGP_HELP_STD_USAGE); } -ARGP_EI int +__extern_inline int __NTH (__option_is_short (const struct argp_option *__opt)) { if (__opt->flags & OPTION_DOC) @@ -540,7 +536,7 @@ __NTH (__option_is_short (const struct argp_option *__opt)) } } -ARGP_EI int +__extern_inline int __NTH (__option_is_end (const struct argp_option *__opt)) { return !__opt->key && !__opt->name && !__opt->doc && !__opt->group; diff --git a/lib/libc/include/generic-glibc/argz.h b/lib/libc/include/generic-glibc/argz.h index dd13743521e27ab76519c10ceb69f89ad1f123ce..6272cbcb569f8ab83fd3c97de44a6f76a8f783d0 100644 --- a/lib/libc/include/generic-glibc/argz.h +++ b/lib/libc/include/generic-glibc/argz.h @@ -1,5 +1,5 @@ /* Routines for dealing with '\0' separated arg vectors. - Copyright (C) 1995-2025 Free Software Foundation, Inc. + Copyright (C) 1995-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/arpa/inet.h b/lib/libc/include/generic-glibc/arpa/inet.h index 07113deb0f0c5a2e80f1f7316332ef1ffa133519..f1b39ad7f8d65438dbefc9488bcd37765f60976a 100644 --- a/lib/libc/include/generic-glibc/arpa/inet.h +++ b/lib/libc/include/generic-glibc/arpa/inet.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1997-2025 Free Software Foundation, Inc. +/* Copyright (C) 1997-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/assert.h b/lib/libc/include/generic-glibc/assert.h index df877364641b656e76e62c6f62371f8edcdf961c..73562fec4b9cf794aeb03cc1f3e4c5988eeb42cf 100644 --- a/lib/libc/include/generic-glibc/assert.h +++ b/lib/libc/include/generic-glibc/assert.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1991-2025 Free Software Foundation, Inc. +/* Copyright (C) 1991-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or @@ -34,12 +34,36 @@ #define _ASSERT_H 1 #include +#if __GLIBC_USE (ISOC23) +# ifndef __STDC_VERSION_ASSERT_H__ +# define __STDC_VERSION_ASSERT_H__ 202311L +# endif +#endif + #if defined __cplusplus && __GNUC_PREREQ (2,95) # define __ASSERT_VOID_CAST static_cast #else # define __ASSERT_VOID_CAST (void) #endif +/* C23 makes assert a variadic macro so that expressions with a comma + not between parentheses, but that would still be valid as a single + function argument, such as those involving compound literals with a + comma in the initializer list, can be passed to assert. This + depends on support for variadic macros (added in C99 and GCC 2.95), + and on support for _Bool (added in C99 and GCC 3.0) in order to + validate that only a single expression is passed as an argument, + and is currently implemented only for C. */ +#if (__GLIBC_USE (ISOC23) \ + && (defined __GNUC__ \ + ? __GNUC_PREREQ (3, 0) \ + : defined __STDC_VERSION__ && __STDC_VERSION__ >= 199901L) \ + && !defined __cplusplus) +# define __ASSERT_VARIADIC 1 +#else +# define __ASSERT_VARIADIC 0 +#endif + /* void assert (int expression); If NDEBUG is defined, do nothing. @@ -47,7 +71,11 @@ #ifdef NDEBUG -# define assert(expr) (__ASSERT_VOID_CAST (0)) +# if __ASSERT_VARIADIC +# define assert(...) (__ASSERT_VOID_CAST (0)) +# else +# define assert(expr) (__ASSERT_VOID_CAST (0)) +# endif /* void assert_perror (int errnum); @@ -80,6 +108,13 @@ extern void __assert (const char *__assertion, const char *__file, int __line) __THROW __attribute__ ((__noreturn__)) __COLD; +# if __ASSERT_VARIADIC +/* This function is not defined and is not called outside of an + unevaluated sizeof, but serves to verify that the argument to + assert is a single expression. */ +extern _Bool __assert_single_arg (_Bool); +# endif + __END_DECLS /* When possible, define assert so that it does not add extra @@ -102,23 +137,40 @@ __END_DECLS : __assert_fail (#expr, __ASSERT_FILE, __ASSERT_LINE, \ __ASSERT_FUNCTION)) # elif !defined __GNUC__ || defined __STRICT_ANSI__ -# define assert(expr) \ +# if __ASSERT_VARIADIC +# define assert(...) \ + (((void) sizeof (__assert_single_arg (__VA_ARGS__)), __VA_ARGS__) \ + ? __ASSERT_VOID_CAST (0) \ + : __assert_fail (#__VA_ARGS__, __FILE__, __LINE__, __ASSERT_FUNCTION)) +# else +# define assert(expr) \ ((expr) \ ? __ASSERT_VOID_CAST (0) \ : __assert_fail (#expr, __FILE__, __LINE__, __ASSERT_FUNCTION)) +# endif # else +# if __ASSERT_VARIADIC +# define assert(...) \ + ((void) sizeof (__assert_single_arg (__VA_ARGS__)), __extension__ ({ \ + if (__VA_ARGS__) \ + ; /* empty */ \ + else \ + __assert_fail (#__VA_ARGS__, __FILE__, __LINE__, __ASSERT_FUNCTION); \ + })) +# else /* The first occurrence of EXPR is not evaluated due to the sizeof, but will trigger any pedantic warnings masked by the __extension__ for the second occurrence. The ternary operator is required to support function pointers and bit fields in this context, and to suppress the evaluation of variable length arrays. */ -# define assert(expr) \ +# define assert(expr) \ ((void) sizeof ((expr) ? 1 : 0), __extension__ ({ \ if (expr) \ ; /* empty */ \ else \ __assert_fail (#expr, __FILE__, __LINE__, __ASSERT_FUNCTION); \ })) +# endif # endif # ifdef __USE_GNU diff --git a/lib/libc/include/generic-glibc/bits/argp-ldbl.h b/lib/libc/include/generic-glibc/bits/argp-ldbl.h index 4ebf41e04a92719cdfe6a2a3c6a6deb8e93e8abe..9ea9c2ad05ee4108fcff137232ede04478b52a1d 100644 --- a/lib/libc/include/generic-glibc/bits/argp-ldbl.h +++ b/lib/libc/include/generic-glibc/bits/argp-ldbl.h @@ -1,5 +1,5 @@ /* Redirections for argp functions for -mlong-double-64. - Copyright (C) 2019-2025 Free Software Foundation, Inc. + Copyright (C) 2019-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/atomic_wide_counter.h b/lib/libc/include/generic-glibc/bits/atomic_wide_counter.h index 57e96b70647d7e0565b1358539a3eea7399c887c..d864d71bcba1e3529c40c43c5da2b767b0f13419 100644 --- a/lib/libc/include/generic-glibc/bits/atomic_wide_counter.h +++ b/lib/libc/include/generic-glibc/bits/atomic_wide_counter.h @@ -1,5 +1,5 @@ /* Monotonically increasing wide counters (at least 62 bits). - Copyright (C) 2016-2025 Free Software Foundation, Inc. + Copyright (C) 2016-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/byteswap.h b/lib/libc/include/generic-glibc/bits/byteswap.h index fd956dbd5577c4aca0c004255fe3d5d5811372ce..b9cdbbc59ae091dd76e3f4fcd09ded2e62445e2d 100644 --- a/lib/libc/include/generic-glibc/bits/byteswap.h +++ b/lib/libc/include/generic-glibc/bits/byteswap.h @@ -1,5 +1,5 @@ /* Macros and inline functions to swap the order of bytes in integer values. - Copyright (C) 1997-2025 Free Software Foundation, Inc. + Copyright (C) 1997-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/cmathcalls.h b/lib/libc/include/generic-glibc/bits/cmathcalls.h index 8babe1b32d66d3dc03368929f013b187cb4ee436..66ebc6e4783c7808136f76115bfcd9623d4459ae 100644 --- a/lib/libc/include/generic-glibc/bits/cmathcalls.h +++ b/lib/libc/include/generic-glibc/bits/cmathcalls.h @@ -1,6 +1,6 @@ /* Prototype declarations for complex math functions; helper file for . - Copyright (C) 1997-2025 Free Software Foundation, Inc. + Copyright (C) 1997-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/confname.h b/lib/libc/include/generic-glibc/bits/confname.h index c10efca2fa6907162bf1a1738a80387fb86b216b..8de1e82927cb895d91ab3ae45e2dddbafb812061 100644 --- a/lib/libc/include/generic-glibc/bits/confname.h +++ b/lib/libc/include/generic-glibc/bits/confname.h @@ -1,5 +1,5 @@ /* `sysconf', `pathconf', and `confstr' NAME values. Generic version. - Copyright (C) 1993-2025 Free Software Foundation, Inc. + Copyright (C) 1993-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/cpu-set.h b/lib/libc/include/generic-glibc/bits/cpu-set.h index b7db570584da24bd50f34a254e31c481b490b7ec..4ba1bb97c8ddf65a05d3c4891fa0db9215bd87fc 100644 --- a/lib/libc/include/generic-glibc/bits/cpu-set.h +++ b/lib/libc/include/generic-glibc/bits/cpu-set.h @@ -1,6 +1,6 @@ /* Definition of the cpu_set_t structure used by the POSIX 1003.1b-1993 scheduling interface. - Copyright (C) 1996-2025 Free Software Foundation, Inc. + Copyright (C) 1996-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/dirent.h b/lib/libc/include/generic-glibc/bits/dirent.h index 149dce4277a3499bbc42eb5113559363ccd85e19..493665660d6fc3958a2016e3abb632ac092e71cf 100644 --- a/lib/libc/include/generic-glibc/bits/dirent.h +++ b/lib/libc/include/generic-glibc/bits/dirent.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1996-2025 Free Software Foundation, Inc. +/* Copyright (C) 1996-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/dirent_ext.h b/lib/libc/include/generic-glibc/bits/dirent_ext.h index 34b947227537006385f0793b42739d716ecbd4be..481e0b20e5c6f300fb17e11a09d7be5125ad940c 100644 --- a/lib/libc/include/generic-glibc/bits/dirent_ext.h +++ b/lib/libc/include/generic-glibc/bits/dirent_ext.h @@ -1,5 +1,5 @@ /* System-specific extensions of . Linux version. - Copyright (C) 2019-2025 Free Software Foundation, Inc. + Copyright (C) 2019-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/dl_find_object.h b/lib/libc/include/generic-glibc/bits/dl_find_object.h index 8bade10008388f57704d0dc3cebfee012739fafe..d9d90e4cba88e4a227e7c5c7f0761edbe09c0a0d 100644 --- a/lib/libc/include/generic-glibc/bits/dl_find_object.h +++ b/lib/libc/include/generic-glibc/bits/dl_find_object.h @@ -1,5 +1,5 @@ /* System dependent definitions for finding objects by address. - Copyright (C) 2021-2025 Free Software Foundation, Inc. + Copyright (C) 2021-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/dlfcn.h b/lib/libc/include/generic-glibc/bits/dlfcn.h index c45163e6aef8bc451c53552d32b7eea7f25b29c6..a2814450ca9b22c49c0d1c7f227b1f718998e65e 100644 --- a/lib/libc/include/generic-glibc/bits/dlfcn.h +++ b/lib/libc/include/generic-glibc/bits/dlfcn.h @@ -1,5 +1,5 @@ /* System dependent definitions for run-time dynamic loading. - Copyright (C) 1996-2025 Free Software Foundation, Inc. + Copyright (C) 1996-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/endian.h b/lib/libc/include/generic-glibc/bits/endian.h index 871f16a18b07be1d1c44657e48f2bed72bcf3acd..3d88d675e4bc86986aaf5d24bf873c5add5c2d00 100644 --- a/lib/libc/include/generic-glibc/bits/endian.h +++ b/lib/libc/include/generic-glibc/bits/endian.h @@ -1,5 +1,5 @@ /* Endian macros for string.h functions - Copyright (C) 1992-2025 Free Software Foundation, Inc. + Copyright (C) 1992-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/environments.h b/lib/libc/include/generic-glibc/bits/environments.h index bbc4956f1f93df0c470d7417fc0eaecd36c828ef..c6ecdefcf24cbb5109d0c8e56adff7e8504a759c 100644 --- a/lib/libc/include/generic-glibc/bits/environments.h +++ b/lib/libc/include/generic-glibc/bits/environments.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1999-2025 Free Software Foundation, Inc. +/* Copyright (C) 1999-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/epoll.h b/lib/libc/include/generic-glibc/bits/epoll.h index ced9da193c3fd342c07b3ecea14349a8178b4401..3a794bfbe77c2971736fd09752eadc4396534cdf 100644 --- a/lib/libc/include/generic-glibc/bits/epoll.h +++ b/lib/libc/include/generic-glibc/bits/epoll.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2002-2025 Free Software Foundation, Inc. +/* Copyright (C) 2002-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/err-ldbl.h b/lib/libc/include/generic-glibc/bits/err-ldbl.h index 6e9f330d7d3ac1bbaf831e2a7c359c45f7fdd362..29ad81c3401ec4435859d4d7b4ed5675d9dd731a 100644 --- a/lib/libc/include/generic-glibc/bits/err-ldbl.h +++ b/lib/libc/include/generic-glibc/bits/err-ldbl.h @@ -1,5 +1,5 @@ /* Redirections for err.h functions for -mlong-double-64. - Copyright (C) 2019-2025 Free Software Foundation, Inc. + Copyright (C) 2019-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/errno.h b/lib/libc/include/generic-glibc/bits/errno.h index 492ed3b4ffd596d700129f393c3ffa67df73b644..5eecdee6defae228ae31054adb4294e70cc974bf 100644 --- a/lib/libc/include/generic-glibc/bits/errno.h +++ b/lib/libc/include/generic-glibc/bits/errno.h @@ -1,5 +1,5 @@ /* Error constants. Linux specific version. - Copyright (C) 1996-2025 Free Software Foundation, Inc. + Copyright (C) 1996-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/error-ldbl.h b/lib/libc/include/generic-glibc/bits/error-ldbl.h index bd1e5ed6575cdcff147a4543711b6c9956871424..260721848b61283c46f8f8811e72eb191104685e 100644 --- a/lib/libc/include/generic-glibc/bits/error-ldbl.h +++ b/lib/libc/include/generic-glibc/bits/error-ldbl.h @@ -1,5 +1,5 @@ /* Redirections for error.h functions for -mlong-double-64. - Copyright (C) 2019-2025 Free Software Foundation, Inc. + Copyright (C) 2019-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/error.h b/lib/libc/include/generic-glibc/bits/error.h index 0713e10a7f01105c47a535623795c2615c42d2fe..4841dc3364a8c6d8d48f8c8ecb1255aa50615ae9 100644 --- a/lib/libc/include/generic-glibc/bits/error.h +++ b/lib/libc/include/generic-glibc/bits/error.h @@ -1,5 +1,5 @@ /* Specializations for error functions. - Copyright (C) 2007-2025 Free Software Foundation, Inc. + Copyright (C) 2007-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/eventfd.h b/lib/libc/include/generic-glibc/bits/eventfd.h index c5d2dbfa31e9a03f327b0c29bcf2d0a7d7a84f14..c0a1d7e3b649b44fb454c5396469ed5774e9ff3f 100644 --- a/lib/libc/include/generic-glibc/bits/eventfd.h +++ b/lib/libc/include/generic-glibc/bits/eventfd.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2007-2025 Free Software Foundation, Inc. +/* Copyright (C) 2007-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/fcntl-linux-fortify.h b/lib/libc/include/generic-glibc/bits/fcntl-linux-fortify.h new file mode 100644 index 0000000000000000000000000000000000000000..86c095b67342902bf33ca8af1e827085630d8720 --- /dev/null +++ b/lib/libc/include/generic-glibc/bits/fcntl-linux-fortify.h @@ -0,0 +1,49 @@ +/* Checking macros for fcntl functions. Linux version. + Copyright (C) 2025-2026 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + . */ + +#ifndef _FCNTL_H +# error "Never include directly; use instead." +#endif + +#ifdef __USE_GNU + +extern int __REDIRECT (__openat2_alias, (int __dfd, const char *__filename, + const struct open_how *__how, + size_t __usize), openat2) + __nonnull ((2, 3)); + +#if !__fortify_use_clang +__errordecl (__openat2_invalid_size, + "the specified size is larger than sizeof (struct open_how)"); +#endif + +__fortify_function int +openat2 (int __dfd, const char *__filename, const struct open_how *__how, + size_t __usize) + __fortify_clang_warning (__builtin_constant_p (__usize) + && __usize > sizeof (struct open_how), + "the specified size is larger than sizeof (struct open_how)") +{ +#if !__fortify_use_clang + if (__builtin_constant_p (__usize) && __usize > sizeof (struct open_how)) + __openat2_invalid_size (); +#endif + return __openat2_alias (__dfd, __filename, __how, __usize); +} + +#endif /* use GNU */ \ No newline at end of file diff --git a/lib/libc/include/generic-glibc/bits/fcntl-linux.h b/lib/libc/include/generic-glibc/bits/fcntl-linux.h index 56169a7ba49047e7442aadc3593b6592c04c028b..b6a2979287a93d686f1ca0b44b9faf511eea6b9f 100644 --- a/lib/libc/include/generic-glibc/bits/fcntl-linux.h +++ b/lib/libc/include/generic-glibc/bits/fcntl-linux.h @@ -1,5 +1,5 @@ /* O_*, F_*, FD_* bit values for Linux. - Copyright (C) 2001-2025 Free Software Foundation, Inc. + Copyright (C) 2001-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or @@ -219,6 +219,9 @@ /* For F_[GET|SET]FD. */ #define FD_CLOEXEC 1 /* Actually anything with low bit set goes */ +#ifdef __USE_GNU +# define FD_PIDFS_ROOT -10002 /* Root of the pidfs filesystem */ +#endif #ifndef F_RDLCK /* For posix fcntl() and `l_type' field of a `struct flock' for lockf(). */ @@ -381,6 +384,11 @@ struct file_handle open_by_handle_at. */ # define AT_HANDLE_MNT_ID_UNIQUE 1 /* Return the 64-bit unique mount ID. */ +# define AT_HANDLE_CONNECTABLE 2 /* Request a connectable file handle */ + +/* Flags for execveat2(2). */ +# define AT_EXECVE_CHECK 0x10000 /* Only perform a check if execution + would be allowed */ #endif __BEGIN_DECLS @@ -455,6 +463,33 @@ extern int name_to_handle_at (int __dfd, const char *__name, extern int open_by_handle_at (int __mountdirfd, struct file_handle *__handle, int __flags); +#ifdef __has_include +# if __has_include ("linux/openat2.h") +# include "linux/openat2.h" +# define __glibc_has_open_how 1 +# endif +#endif + +#include + +/* Similar to `openat' but the arguments are packed on HOW with the size + USIZE. If flags and mode from HOW are non-zero, then openat2 operates + like openat. + + Unlike openat, unknown or invalid flags result in an error (EINVAL), + rather than being ignored. The mode must be zero unless one of O_CREAT + or O_TMPFILE are set. + + The kernel does not support legacy non-LFS interface. */ +extern int openat2 (int __dfd, const char * __filename, + const struct open_how * __how, + __SIZE_TYPE__ __usize) + __nonnull ((2, 3)); + #endif /* use GNU */ +#if __USE_FORTIFY_LEVEL > 0 && defined __fortify_function +# include +#endif + __END_DECLS \ No newline at end of file diff --git a/lib/libc/include/generic-glibc/bits/fcntl.h b/lib/libc/include/generic-glibc/bits/fcntl.h index 3f9b254f7dd64e2e4dd41b17c1290d65cbe452d3..588d80193fa2413ec195f56afc2d2092a8b9d866 100644 --- a/lib/libc/include/generic-glibc/bits/fcntl.h +++ b/lib/libc/include/generic-glibc/bits/fcntl.h @@ -1,5 +1,5 @@ /* O_*, F_*, FD_* bit values for Linux. - Copyright (C) 1995-2025 Free Software Foundation, Inc. + Copyright (C) 1995-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/fcntl2.h b/lib/libc/include/generic-glibc/bits/fcntl2.h index f09c4a9cc8c11c13b79331fe6c5bea305f3128d5..9878bd99460020aad4ae0c84caf9d831a4a4b2c5 100644 --- a/lib/libc/include/generic-glibc/bits/fcntl2.h +++ b/lib/libc/include/generic-glibc/bits/fcntl2.h @@ -1,5 +1,5 @@ /* Checking macros for fcntl functions. - Copyright (C) 2007-2025 Free Software Foundation, Inc. + Copyright (C) 2007-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/fenv.h b/lib/libc/include/generic-glibc/bits/fenv.h index 5dab6355dd28921adb95267412d459b3dae02b73..675ec3dc8ca191d58f71c1330507d06dea76c489 100644 --- a/lib/libc/include/generic-glibc/bits/fenv.h +++ b/lib/libc/include/generic-glibc/bits/fenv.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1998-2025 Free Software Foundation, Inc. +/* Copyright (C) 1998-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/floatn-common.h b/lib/libc/include/generic-glibc/bits/floatn-common.h index 6a70ca5d5d5901cb40a3a77de8e6b119d2ead926..e0bcddc9144541da970d5b2b941797c13421a429 100644 --- a/lib/libc/include/generic-glibc/bits/floatn-common.h +++ b/lib/libc/include/generic-glibc/bits/floatn-common.h @@ -1,6 +1,6 @@ /* Macros to control TS 18661-3 glibc features where the same definitions are appropriate for all platforms. - Copyright (C) 2017-2025 Free Software Foundation, Inc. + Copyright (C) 2017-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/floatn.h b/lib/libc/include/generic-glibc/bits/floatn.h index 45bed17fddc9035939658e311013dcc6f45a7c41..23dd2abf0a21e6a4733a68c40710086a25b18704 100644 --- a/lib/libc/include/generic-glibc/bits/floatn.h +++ b/lib/libc/include/generic-glibc/bits/floatn.h @@ -1,5 +1,5 @@ /* Macros to control TS 18661-3 glibc features on ldbl-128 platforms. - Copyright (C) 2017-2025 Free Software Foundation, Inc. + Copyright (C) 2017-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/flt-eval-method.h b/lib/libc/include/generic-glibc/bits/flt-eval-method.h index 5556c5dae791332638d428a89462c5d6d551d851..a64af2fe400120f246cb41648b0f69280f989263 100644 --- a/lib/libc/include/generic-glibc/bits/flt-eval-method.h +++ b/lib/libc/include/generic-glibc/bits/flt-eval-method.h @@ -1,5 +1,5 @@ /* Define __GLIBC_FLT_EVAL_METHOD. - Copyright (C) 2016-2025 Free Software Foundation, Inc. + Copyright (C) 2016-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/fp-fast.h b/lib/libc/include/generic-glibc/bits/fp-fast.h index f6ba20a204c2205c08613fc3d59f00650e79d358..ae2d8206e49a30a9d187c50d4451183e11f4a4dc 100644 --- a/lib/libc/include/generic-glibc/bits/fp-fast.h +++ b/lib/libc/include/generic-glibc/bits/fp-fast.h @@ -1,5 +1,5 @@ /* Define FP_FAST_* macros. - Copyright (C) 2016-2025 Free Software Foundation, Inc. + Copyright (C) 2016-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/fp-logb.h b/lib/libc/include/generic-glibc/bits/fp-logb.h index 3a4c9b0c343eed2884fa809a65dac6dd2761e726..ae61591f33439d3bf52f26bbd332421c7132d1ca 100644 --- a/lib/libc/include/generic-glibc/bits/fp-logb.h +++ b/lib/libc/include/generic-glibc/bits/fp-logb.h @@ -1,5 +1,5 @@ /* Define __FP_LOGB0_IS_MIN and __FP_LOGBNAN_IS_MIN. - Copyright (C) 2016-2025 Free Software Foundation, Inc. + Copyright (C) 2016-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/getopt_core.h b/lib/libc/include/generic-glibc/bits/getopt_core.h index 817e7785b5d164d50df092ce02303a4356da371f..dfd4920a57c5cdb174b2ba9cdc5036f77e3a0ece 100644 --- a/lib/libc/include/generic-glibc/bits/getopt_core.h +++ b/lib/libc/include/generic-glibc/bits/getopt_core.h @@ -1,5 +1,5 @@ /* Declarations for getopt (basic, portable features only). - Copyright (C) 1989-2025 Free Software Foundation, Inc. + Copyright (C) 1989-2026 Free Software Foundation, Inc. This file is part of the GNU C Library and is also part of gnulib. Patches to this file should be submitted to both projects. diff --git a/lib/libc/include/generic-glibc/bits/getopt_ext.h b/lib/libc/include/generic-glibc/bits/getopt_ext.h index ecbfee5409ed3e404187ffd100e9d0b7c3c660a6..3d6aef5b43af68c7a194eeaf601e455844ddbd19 100644 --- a/lib/libc/include/generic-glibc/bits/getopt_ext.h +++ b/lib/libc/include/generic-glibc/bits/getopt_ext.h @@ -1,5 +1,5 @@ /* Declarations for getopt (GNU extensions). - Copyright (C) 1989-2025 Free Software Foundation, Inc. + Copyright (C) 1989-2026 Free Software Foundation, Inc. This file is part of the GNU C Library and is also part of gnulib. Patches to this file should be submitted to both projects. diff --git a/lib/libc/include/generic-glibc/bits/getopt_posix.h b/lib/libc/include/generic-glibc/bits/getopt_posix.h index 7f8096a43c8c3714f589a3811b511fba2cf5514e..6374a0778dc0053a3d674c391aaa3a15e59461ad 100644 --- a/lib/libc/include/generic-glibc/bits/getopt_posix.h +++ b/lib/libc/include/generic-glibc/bits/getopt_posix.h @@ -1,5 +1,5 @@ /* Declarations for getopt (POSIX compatibility shim). - Copyright (C) 1989-2025 Free Software Foundation, Inc. + Copyright (C) 1989-2026 Free Software Foundation, Inc. Unlike the bulk of the getopt implementation, this file is NOT part of gnulib. diff --git a/lib/libc/include/generic-glibc/bits/hwcap.h b/lib/libc/include/generic-glibc/bits/hwcap.h index c978f78936db4314dc0713740c886f81346dbf5c..38f3c42f256dac36df783ac67c4dda597c5e02a3 100644 --- a/lib/libc/include/generic-glibc/bits/hwcap.h +++ b/lib/libc/include/generic-glibc/bits/hwcap.h @@ -1,5 +1,5 @@ /* Defines for bits in AT_HWCAP. - Copyright (C) 2012-2025 Free Software Foundation, Inc. + Copyright (C) 2012-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/in.h b/lib/libc/include/generic-glibc/bits/in.h index 0e940cc5a6ab8c057d1eed352add751451bc7460..e235d430153051b6de2eee70916fbbf65dcbdbf4 100644 --- a/lib/libc/include/generic-glibc/bits/in.h +++ b/lib/libc/include/generic-glibc/bits/in.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1991-2025 Free Software Foundation, Inc. +/* Copyright (C) 1991-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/indirect-return.h b/lib/libc/include/generic-glibc/bits/indirect-return.h index 7ba1bd20385abe7bcc0634d561034930515ce731..0fc202e8addd671dd350b6f12754e65f08a0f9d8 100644 --- a/lib/libc/include/generic-glibc/bits/indirect-return.h +++ b/lib/libc/include/generic-glibc/bits/indirect-return.h @@ -1,5 +1,5 @@ /* Definition of __INDIRECT_RETURN. Generic version. - Copyright (C) 2018-2025 Free Software Foundation, Inc. + Copyright (C) 2018-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/inet-fortified-decl.h b/lib/libc/include/generic-glibc/bits/inet-fortified-decl.h index a77a0477cda6cc1ee8f12046c3ae501db9eccf2c..018e00209f04eff376922d819c362e52503ef43e 100644 --- a/lib/libc/include/generic-glibc/bits/inet-fortified-decl.h +++ b/lib/libc/include/generic-glibc/bits/inet-fortified-decl.h @@ -1,5 +1,5 @@ /* Declarations of checking macros for inet functions. - Copyright (C) 2025 Free Software Foundation, Inc. + Copyright (C) 2025-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/inet-fortified.h b/lib/libc/include/generic-glibc/bits/inet-fortified.h index 88aafbe8b20b48dde9190733f0a80561259f4f66..fe404d4d06d496e9372a2121fb8a996bc9b09b41 100644 --- a/lib/libc/include/generic-glibc/bits/inet-fortified.h +++ b/lib/libc/include/generic-glibc/bits/inet-fortified.h @@ -1,5 +1,5 @@ /* Checking macros for inet functions. - Copyright (C) 2025 Free Software Foundation, Inc. + Copyright (C) 2025-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or @@ -45,15 +45,17 @@ __NTH (inet_pton (int __af, __fortify_clang_warning_only_if_bos0_lt (4, __dst, "inet_pton called with destination buffer size less than 4") { - size_t sz = 0; +#if !__fortify_use_clang + size_t __sz = 0; if (__af == AF_INET) - sz = sizeof (struct in_addr); + __sz = sizeof (struct in_addr); else if (__af == AF_INET6) - sz = sizeof (struct in6_addr); + __sz = sizeof (struct in6_addr); else return __inet_pton_alias (__af, __src, __dst); +#endif - return __glibc_fortify (inet_pton, sz, sizeof (char), + return __glibc_fortify (inet_pton, __sz, sizeof (char), __glibc_objsize (__dst), __af, __src, __dst); }; diff --git a/lib/libc/include/generic-glibc/bits/inotify.h b/lib/libc/include/generic-glibc/bits/inotify.h index 4bc5510f71e3b6d04d09be53db1060e9889d048a..f646147563dc91be65b1598125c305af863fb283 100644 --- a/lib/libc/include/generic-glibc/bits/inotify.h +++ b/lib/libc/include/generic-glibc/bits/inotify.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2005-2025 Free Software Foundation, Inc. +/* Copyright (C) 2005-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/ioctl-types.h b/lib/libc/include/generic-glibc/bits/ioctl-types.h index 13c5bc0d455f20c1beedf540540d869e075f9c7f..28bbfa7c2c804ddb5baeb4f5222da013941d5304 100644 --- a/lib/libc/include/generic-glibc/bits/ioctl-types.h +++ b/lib/libc/include/generic-glibc/bits/ioctl-types.h @@ -1,5 +1,5 @@ /* Structure types for pre-termios terminal ioctls. Linux version. - Copyright (C) 1996-2025 Free Software Foundation, Inc. + Copyright (C) 1996-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/ioctls.h b/lib/libc/include/generic-glibc/bits/ioctls.h index 84594dd239475382b220094e13d871dd45b58682..7dfc0dc801e52e6fcc21a194869c59995919f6da 100644 --- a/lib/libc/include/generic-glibc/bits/ioctls.h +++ b/lib/libc/include/generic-glibc/bits/ioctls.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1996-2025 Free Software Foundation, Inc. +/* Copyright (C) 1996-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/ipc-perm.h b/lib/libc/include/generic-glibc/bits/ipc-perm.h index e5c8cfb66aaca6b277b8e8d699b9b035cc7ad416..2dc2424579751c5694d9485a4b848c87e2bb0f97 100644 --- a/lib/libc/include/generic-glibc/bits/ipc-perm.h +++ b/lib/libc/include/generic-glibc/bits/ipc-perm.h @@ -1,5 +1,5 @@ /* struct ipc_perm definition. - Copyright (C) 2019-2025 Free Software Foundation, Inc. + Copyright (C) 2019-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/ipc.h b/lib/libc/include/generic-glibc/bits/ipc.h index ee51271f893ecf9b48b0a68e8bdf2d958fa75a94..c44dc1155d19cd7aa964d14183efad9ec01396b4 100644 --- a/lib/libc/include/generic-glibc/bits/ipc.h +++ b/lib/libc/include/generic-glibc/bits/ipc.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1995-2025 Free Software Foundation, Inc. +/* Copyright (C) 1995-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/ipctypes.h b/lib/libc/include/generic-glibc/bits/ipctypes.h index a747e9c6b745bc22a1aaf912e5b65bb3579f1183..92e5bca75dbe445c45aaca8bbf42971cd5b70ce7 100644 --- a/lib/libc/include/generic-glibc/bits/ipctypes.h +++ b/lib/libc/include/generic-glibc/bits/ipctypes.h @@ -1,5 +1,5 @@ /* bits/ipctypes.h -- Define some types used by SysV IPC/MSG/SHM. Generic. - Copyright (C) 2002-2025 Free Software Foundation, Inc. + Copyright (C) 2002-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/iscanonical.h b/lib/libc/include/generic-glibc/bits/iscanonical.h index 35d241066f0960dd2dbb5e1790246403db210895..c5295802a2204c042b9f3d0ceeea8abbc5932c9e 100644 --- a/lib/libc/include/generic-glibc/bits/iscanonical.h +++ b/lib/libc/include/generic-glibc/bits/iscanonical.h @@ -1,5 +1,5 @@ /* Define iscanonical macro. - Copyright (C) 2016-2025 Free Software Foundation, Inc. + Copyright (C) 2016-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/libc-header-start.h b/lib/libc/include/generic-glibc/bits/libc-header-start.h index 874d697a9829fd078c353c39bd897d26fae056fb..4a5cd012a0838d8ce20a1256bf1bf28bd68e36cd 100644 --- a/lib/libc/include/generic-glibc/bits/libc-header-start.h +++ b/lib/libc/include/generic-glibc/bits/libc-header-start.h @@ -1,5 +1,5 @@ /* Handle feature test macros at the start of a header. - Copyright (C) 2016-2025 Free Software Foundation, Inc. + Copyright (C) 2016-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/libm-simd-decl-stubs.h b/lib/libc/include/generic-glibc/bits/libm-simd-decl-stubs.h index 15df4ea88fd10afc9e4a47cbce89951aa76477b1..d3b43f59cc01e21655c009ec365eb49b30d15e03 100644 --- a/lib/libc/include/generic-glibc/bits/libm-simd-decl-stubs.h +++ b/lib/libc/include/generic-glibc/bits/libm-simd-decl-stubs.h @@ -1,5 +1,5 @@ /* Empty definitions required for __MATHCALL_VEC unfolding in mathcalls.h. - Copyright (C) 2014-2025 Free Software Foundation, Inc. + Copyright (C) 2014-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or @@ -187,6 +187,28 @@ #define __DECL_SIMD_expm1f64x #define __DECL_SIMD_expm1f128x +#define __DECL_SIMD_exp2m1 +#define __DECL_SIMD_exp2m1f +#define __DECL_SIMD_exp2m1l +#define __DECL_SIMD_exp2m1f16 +#define __DECL_SIMD_exp2m1f32 +#define __DECL_SIMD_exp2m1f64 +#define __DECL_SIMD_exp2m1f128 +#define __DECL_SIMD_exp2m1f32x +#define __DECL_SIMD_exp2m1f64x +#define __DECL_SIMD_exp2m1f128x + +#define __DECL_SIMD_exp10m1 +#define __DECL_SIMD_exp10m1f +#define __DECL_SIMD_exp10m1l +#define __DECL_SIMD_exp10m1f16 +#define __DECL_SIMD_exp10m1f32 +#define __DECL_SIMD_exp10m1f64 +#define __DECL_SIMD_exp10m1f128 +#define __DECL_SIMD_exp10m1f32x +#define __DECL_SIMD_exp10m1f64x +#define __DECL_SIMD_exp10m1f128x + #define __DECL_SIMD_sinh #define __DECL_SIMD_sinhf #define __DECL_SIMD_sinhl @@ -220,6 +242,17 @@ #define __DECL_SIMD_atan2f64x #define __DECL_SIMD_atan2f128x +#define __DECL_SIMD_rsqrt +#define __DECL_SIMD_rsqrtf +#define __DECL_SIMD_rsqrtl +#define __DECL_SIMD_rsqrtf16 +#define __DECL_SIMD_rsqrtf32 +#define __DECL_SIMD_rsqrtf64 +#define __DECL_SIMD_rsqrtf128 +#define __DECL_SIMD_rsqrtf32x +#define __DECL_SIMD_rsqrtf64x +#define __DECL_SIMD_rsqrtf128x + #define __DECL_SIMD_log10 #define __DECL_SIMD_log10f #define __DECL_SIMD_log10l @@ -231,6 +264,17 @@ #define __DECL_SIMD_log10f64x #define __DECL_SIMD_log10f128x +#define __DECL_SIMD_log10p1 +#define __DECL_SIMD_log10p1f +#define __DECL_SIMD_log10p1l +#define __DECL_SIMD_log10p1f16 +#define __DECL_SIMD_log10p1f32 +#define __DECL_SIMD_log10p1f64 +#define __DECL_SIMD_log10p1f128 +#define __DECL_SIMD_log10p1f32x +#define __DECL_SIMD_log10p1f64x +#define __DECL_SIMD_log10p1f128x + #define __DECL_SIMD_log2 #define __DECL_SIMD_log2f #define __DECL_SIMD_log2l @@ -242,6 +286,17 @@ #define __DECL_SIMD_log2f64x #define __DECL_SIMD_log2f128x +#define __DECL_SIMD_log2p1 +#define __DECL_SIMD_log2p1f +#define __DECL_SIMD_log2p1l +#define __DECL_SIMD_log2p1f16 +#define __DECL_SIMD_log2p1f32 +#define __DECL_SIMD_log2p1f64 +#define __DECL_SIMD_log2p1f128 +#define __DECL_SIMD_log2p1f32x +#define __DECL_SIMD_log2p1f64x +#define __DECL_SIMD_log2p1f128x + #define __DECL_SIMD_log1p #define __DECL_SIMD_log1pf #define __DECL_SIMD_log1pl diff --git a/lib/libc/include/generic-glibc/bits/link.h b/lib/libc/include/generic-glibc/bits/link.h index dd48323d4225237bbf5c887f774ef77b023fc749..b3eb6c20bfcae88b2a486ffaf9306a550ef6d81d 100644 --- a/lib/libc/include/generic-glibc/bits/link.h +++ b/lib/libc/include/generic-glibc/bits/link.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2005-2025 Free Software Foundation, Inc. +/* Copyright (C) 2005-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/link_lavcurrent.h b/lib/libc/include/generic-glibc/bits/link_lavcurrent.h index dab34fb38da0e394ca9fb9697698400116e33976..ee89bde862b73e1edcc462683fa6b17b986c50f6 100644 --- a/lib/libc/include/generic-glibc/bits/link_lavcurrent.h +++ b/lib/libc/include/generic-glibc/bits/link_lavcurrent.h @@ -1,6 +1,6 @@ /* Data structure for communication from the run-time dynamic linker for loaded ELF shared objects. LAV_CURRENT definition. - Copyright (C) 2021-2025 Free Software Foundation, Inc. + Copyright (C) 2021-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/local_lim.h b/lib/libc/include/generic-glibc/bits/local_lim.h index 307c15f46a2bbafe97d33f38a885de15e5d3a876..ac5116dd31858783f8740e6b7a04ad83e373fd5d 100644 --- a/lib/libc/include/generic-glibc/bits/local_lim.h +++ b/lib/libc/include/generic-glibc/bits/local_lim.h @@ -1,5 +1,5 @@ /* Minimum guaranteed maximum values for system limits. Linux version. - Copyright (C) 1993-2025 Free Software Foundation, Inc. + Copyright (C) 1993-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/locale.h b/lib/libc/include/generic-glibc/bits/locale.h index f327ed10cc48a364f025f7e0617fd6f25594f84c..5f2f5d03c6946c81bda9809ab5cb98a987aa85fd 100644 --- a/lib/libc/include/generic-glibc/bits/locale.h +++ b/lib/libc/include/generic-glibc/bits/locale.h @@ -1,5 +1,5 @@ /* Definition of locale category symbol values. - Copyright (C) 2001-2025 Free Software Foundation, Inc. + Copyright (C) 2001-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/long-double.h b/lib/libc/include/generic-glibc/bits/long-double.h index d745334ed356518141bbc633645156a3c7e78030..ebf6ac878fbf42241d0b2bcaf8c6049d08d827c5 100644 --- a/lib/libc/include/generic-glibc/bits/long-double.h +++ b/lib/libc/include/generic-glibc/bits/long-double.h @@ -1,5 +1,5 @@ /* Properties of long double type. MIPS version. - Copyright (C) 2016-2025 Free Software Foundation, Inc. + Copyright (C) 2016-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/math-vector.h b/lib/libc/include/generic-glibc/bits/math-vector.h index eb2bb2a25a9e2998a7e1023305f3e75817d20058..02b8a1a937dcf900ef089258abc3155a0676ec66 100644 --- a/lib/libc/include/generic-glibc/bits/math-vector.h +++ b/lib/libc/include/generic-glibc/bits/math-vector.h @@ -1,5 +1,5 @@ /* Platform-specific SIMD declarations of math functions. - Copyright (C) 2014-2025 Free Software Foundation, Inc. + Copyright (C) 2014-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/mathcalls-helper-functions.h b/lib/libc/include/generic-glibc/bits/mathcalls-helper-functions.h index d43de427bfb37bcfa454e564258b5b952f5dc296..cdfabc8a7375395ee0566a93bdad9e85350126c0 100644 --- a/lib/libc/include/generic-glibc/bits/mathcalls-helper-functions.h +++ b/lib/libc/include/generic-glibc/bits/mathcalls-helper-functions.h @@ -1,5 +1,5 @@ /* Prototype declarations for math classification macros helpers. - Copyright (C) 2017-2025 Free Software Foundation, Inc. + Copyright (C) 2017-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/mathcalls-macros.h b/lib/libc/include/generic-glibc/bits/mathcalls-macros.h index d5b71472bb53796288a42f51a99525beefe49a70..3c2c0a600d0bc0f9d8938be45f9e67a371ec0ecd 100644 --- a/lib/libc/include/generic-glibc/bits/mathcalls-macros.h +++ b/lib/libc/include/generic-glibc/bits/mathcalls-macros.h @@ -1,5 +1,5 @@ /* Macros for math function declarations. - Copyright (C) 2024-2025 Free Software Foundation, Inc. + Copyright (C) 2024-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/mathcalls-narrow.h b/lib/libc/include/generic-glibc/bits/mathcalls-narrow.h index 9e73c83fe02e237bd0805ddf3ff65553c5ac60c8..fb6ad62951e6550252926631e4347b9d414971d1 100644 --- a/lib/libc/include/generic-glibc/bits/mathcalls-narrow.h +++ b/lib/libc/include/generic-glibc/bits/mathcalls-narrow.h @@ -1,5 +1,5 @@ /* Declare functions returning a narrower type. - Copyright (C) 2018-2025 Free Software Foundation, Inc. + Copyright (C) 2018-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/mathcalls.h b/lib/libc/include/generic-glibc/bits/mathcalls.h index 265c1b867503e5f4a763f9c7ae99f3251590667b..7ff83e5acbeebe7f5fd3b1185735822e436aa5dd 100644 --- a/lib/libc/include/generic-glibc/bits/mathcalls.h +++ b/lib/libc/include/generic-glibc/bits/mathcalls.h @@ -1,5 +1,5 @@ /* Prototype declarations for math functions; helper file for . - Copyright (C) 1996-2025 Free Software Foundation, Inc. + Copyright (C) 1996-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or @@ -136,16 +136,16 @@ __MATHCALL (modf,, (_Mdouble_ __x, _Mdouble_ *__iptr)) __nonnull ((2)); __MATHCALL_VEC (exp10,, (_Mdouble_ __x)); /* Return exp2(X) - 1. */ -__MATHCALL (exp2m1,, (_Mdouble_ __x)); +__MATHCALL_VEC (exp2m1,, (_Mdouble_ __x)); /* Return exp10(X) - 1. */ -__MATHCALL (exp10m1,, (_Mdouble_ __x)); +__MATHCALL_VEC (exp10m1,, (_Mdouble_ __x)); /* Return log2(1 + X). */ -__MATHCALL (log2p1,, (_Mdouble_ __x)); +__MATHCALL_VEC (log2p1,, (_Mdouble_ __x)); /* Return log10(1 + X). */ -__MATHCALL (log10p1,, (_Mdouble_ __x)); +__MATHCALL_VEC (log10p1,, (_Mdouble_ __x)); /* Return log(1 + X). */ __MATHCALL_VEC (logp1,, (_Mdouble_ __x)); @@ -203,7 +203,7 @@ __MATHCALL (powr,, (_Mdouble_ __x, _Mdouble_ __y)); __MATHCALL (rootn,, (_Mdouble_ __x, long long int __y)); /* Return the reciprocal of the square root of X. */ -__MATHCALL (rsqrt,, (_Mdouble_ __x)); +__MATHCALL_VEC (rsqrt,, (_Mdouble_ __x)); #endif @@ -400,25 +400,21 @@ __MATHCALLX (roundeven,, (_Mdouble_ __x), (__const__)); /* Round X to nearest signed integer value, not raising inexact, with control of rounding direction and width of result. */ -__MATHDECL (__intmax_t, fromfp,, (_Mdouble_ __x, int __round, - unsigned int __width)); +__MATHCALL (fromfp,, (_Mdouble_ __x, int __round, unsigned int __width)); /* Round X to nearest unsigned integer value, not raising inexact, with control of rounding direction and width of result. */ -__MATHDECL (__uintmax_t, ufromfp,, (_Mdouble_ __x, int __round, - unsigned int __width)); +__MATHCALL (ufromfp,, (_Mdouble_ __x, int __round, unsigned int __width)); /* Round X to nearest signed integer value, raising inexact for non-integers, with control of rounding direction and width of result. */ -__MATHDECL (__intmax_t, fromfpx,, (_Mdouble_ __x, int __round, - unsigned int __width)); +__MATHCALL (fromfpx,, (_Mdouble_ __x, int __round, unsigned int __width)); /* Round X to nearest unsigned integer value, raising inexact for non-integers, with control of rounding direction and width of result. */ -__MATHDECL (__uintmax_t, ufromfpx,, (_Mdouble_ __x, int __round, - unsigned int __width)); +__MATHCALL (ufromfpx,, (_Mdouble_ __x, int __round, unsigned int __width)); /* Canonicalize floating-point representation. */ __MATHDECL_1 (int, canonicalize,, (_Mdouble_ *__cx, const _Mdouble_ *__x)); diff --git a/lib/libc/include/generic-glibc/bits/mathdef.h b/lib/libc/include/generic-glibc/bits/mathdef.h index 3dfada4812a19e2cb8ad70389519d830cbdcf371..30700d1f890ae740a9bcc35b06ab549bd98a5347 100644 --- a/lib/libc/include/generic-glibc/bits/mathdef.h +++ b/lib/libc/include/generic-glibc/bits/mathdef.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1997-2025 Free Software Foundation, Inc. +/* Copyright (C) 1997-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/mman-linux.h b/lib/libc/include/generic-glibc/bits/mman-linux.h index 2dbd4d3a06f73383b43724bd45a013a488363277..f1813f9f8eb0a236c2bbf6ab42f7037d6f65cbb6 100644 --- a/lib/libc/include/generic-glibc/bits/mman-linux.h +++ b/lib/libc/include/generic-glibc/bits/mman-linux.h @@ -1,5 +1,5 @@ /* Definitions for POSIX memory map interface. Linux generic version. - Copyright (C) 2001-2025 Free Software Foundation, Inc. + Copyright (C) 2001-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/mman-map-flags-generic.h b/lib/libc/include/generic-glibc/bits/mman-map-flags-generic.h index 6a1390fcba77e0ef32a4d686e98f44336da2d2f1..1c79e356496cde23b319ec921885909975c3179c 100644 --- a/lib/libc/include/generic-glibc/bits/mman-map-flags-generic.h +++ b/lib/libc/include/generic-glibc/bits/mman-map-flags-generic.h @@ -1,5 +1,5 @@ /* Definitions for POSIX memory map interface. Linux/generic version. - Copyright (C) 1997-2025 Free Software Foundation, Inc. + Copyright (C) 1997-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/mman-shared.h b/lib/libc/include/generic-glibc/bits/mman-shared.h index 39e82407cd5a9c722e74b81db37c7c5895187457..6081d111d259bda1708a05d3595991e7e13d113b 100644 --- a/lib/libc/include/generic-glibc/bits/mman-shared.h +++ b/lib/libc/include/generic-glibc/bits/mman-shared.h @@ -1,5 +1,5 @@ /* Memory-mapping-related declarations/definitions, not architecture-specific. - Copyright (C) 2017-2025 Free Software Foundation, Inc. + Copyright (C) 2017-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or @@ -85,6 +85,16 @@ int pkey_free (int __key) __THROW; range. */ int pkey_mprotect (void *__addr, size_t __len, int __prot, int __pkey) __THROW; +/* Seal the address range to avoid further modifications, such as remapping to + shrink or expand the VMA, changing protection permission with mprotect, + unmap with munmap, or destructive semantics such as madvise with + MADV_DONTNEED. + + The address range must be a valid VMA, without any gaps (unallocated + memory) between the start and end, and ADDR must be page-aligned (LEN will + be page-aligned implicitly). */ +int mseal (void *__addr, size_t __len, unsigned long flags) __THROW; + __END_DECLS -#endif /* __USE_GNU */ +#endif /* __USE_GNU */ \ No newline at end of file diff --git a/lib/libc/include/generic-glibc/bits/mman.h b/lib/libc/include/generic-glibc/bits/mman.h index de1d81f6ddacdf5a7294cd3b6494637e130df3dd..d77652012a10439ceb61bf2500dac723e05ed744 100644 --- a/lib/libc/include/generic-glibc/bits/mman.h +++ b/lib/libc/include/generic-glibc/bits/mman.h @@ -1,5 +1,5 @@ /* Definitions for POSIX memory map interface. Linux/generic version. - Copyright (C) 1997-2025 Free Software Foundation, Inc. + Copyright (C) 1997-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/mman_ext.h b/lib/libc/include/generic-glibc/bits/mman_ext.h index 20729a008127e4e5d5c02d4da95bd666b18e3693..35570768ac814abf985ddb4eb7708f92cd7302d2 100644 --- a/lib/libc/include/generic-glibc/bits/mman_ext.h +++ b/lib/libc/include/generic-glibc/bits/mman_ext.h @@ -1,5 +1,5 @@ /* System-specific extensions of , Linux version. - Copyright (C) 2022-2025 Free Software Foundation, Inc. + Copyright (C) 2022-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/monetary-ldbl.h b/lib/libc/include/generic-glibc/bits/monetary-ldbl.h index 5ee13e30a5257eaee24fd3d5a737366b6ac8457f..4cd6a82d8197595eb7742acb5dd7952f5681e455 100644 --- a/lib/libc/include/generic-glibc/bits/monetary-ldbl.h +++ b/lib/libc/include/generic-glibc/bits/monetary-ldbl.h @@ -1,5 +1,5 @@ /* -mlong-double-64 compatibility mode for monetary functions. - Copyright (C) 2006-2025 Free Software Foundation, Inc. + Copyright (C) 2006-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/mqueue.h b/lib/libc/include/generic-glibc/bits/mqueue.h index 8bc2e803ed7ffdd5909838b5c1c9b073841c5edf..7922cee1e484d2935ce5b84c330456f80af51900 100644 --- a/lib/libc/include/generic-glibc/bits/mqueue.h +++ b/lib/libc/include/generic-glibc/bits/mqueue.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2004-2025 Free Software Foundation, Inc. +/* Copyright (C) 2004-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/mqueue2.h b/lib/libc/include/generic-glibc/bits/mqueue2.h index 5e00de211ddf7d78c7132267e0d05cad758f55ba..49d5e4ccf6cc4a263ba0f47ca5073f6af203c430 100644 --- a/lib/libc/include/generic-glibc/bits/mqueue2.h +++ b/lib/libc/include/generic-glibc/bits/mqueue2.h @@ -1,5 +1,5 @@ /* Checking macros for mq functions. - Copyright (C) 2007-2025 Free Software Foundation, Inc. + Copyright (C) 2007-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/msq.h b/lib/libc/include/generic-glibc/bits/msq.h index 8df20c3d397e5a7e9149844574170bd4c5bbefcf..a5fa185e6e9be9cf5879c136cdcd00edfe9b9fb6 100644 --- a/lib/libc/include/generic-glibc/bits/msq.h +++ b/lib/libc/include/generic-glibc/bits/msq.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1995-2025 Free Software Foundation, Inc. +/* Copyright (C) 1995-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/netdb.h b/lib/libc/include/generic-glibc/bits/netdb.h index 17011caa40a9e66a47f01bd804530cd0260ab9e1..e97e5e720a2d0b9e3dc688bc20995f79910d028a 100644 --- a/lib/libc/include/generic-glibc/bits/netdb.h +++ b/lib/libc/include/generic-glibc/bits/netdb.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1996-2025 Free Software Foundation, Inc. +/* Copyright (C) 1996-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/openat2.h b/lib/libc/include/generic-glibc/bits/openat2.h new file mode 100644 index 0000000000000000000000000000000000000000..4136a174202a79629cb74095628165d973bff01c --- /dev/null +++ b/lib/libc/include/generic-glibc/bits/openat2.h @@ -0,0 +1,60 @@ +/* openat2 definition. Linux specific. + Copyright (C) 2025-2026 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + . */ + +#ifndef _FCNTL_H +# error "Never use directly; include instead." +#endif + +#ifndef __glibc_has_open_how +/* Arguments for how openat2 should open the target path. */ +struct open_how +{ + __uint64_t flags; + __uint64_t mode; + __uint64_t resolve; +}; +#endif + +/* how->resolve flags for openat2. */ +#ifndef RESOLVE_NO_XDEV +# define RESOLVE_NO_XDEV 0x01 /* Block mount-point crossings + (includes bind-mounts). */ +#endif +#ifndef RESOLVE_NO_MAGICLINKS +# define RESOLVE_NO_MAGICLINKS 0x02 /* Block traversal through procfs-style + "magic-links". */ +#endif +#ifndef RESOLVE_NO_SYMLINKS +# define RESOLVE_NO_SYMLINKS 0x04 /* Block traversal through all symlinks. */ +#endif +#ifndef RESOLVE_BENEATH +# define RESOLVE_BENEATH 0x08 /* Block "lexical" trickery like + "..", symlinks, and absolute + paths which escape the dirfd. */ +#endif +#ifndef RESOLVE_IN_ROOT +# define RESOLVE_IN_ROOT 0x10 /* Make all jumps to "/" and ".." + be scoped inside the dirfd + (similar to chroot). */ +#endif +#ifndef RESOLVE_CACHED +# define RESOLVE_CACHED 0x20 /* Only complete if resolution can be + completed through cached lookup. May + return -EAGAIN if that's not + possible. */ +#endif \ No newline at end of file diff --git a/lib/libc/include/generic-glibc/bits/param.h b/lib/libc/include/generic-glibc/bits/param.h index e8c5c9da603f263e4998f2864685e8568743c71d..9a41566e0cb6d0587dda7c976dd14de597db7abd 100644 --- a/lib/libc/include/generic-glibc/bits/param.h +++ b/lib/libc/include/generic-glibc/bits/param.h @@ -1,5 +1,5 @@ /* Old-style Unix parameters and limits. Linux version. - Copyright (C) 1995-2025 Free Software Foundation, Inc. + Copyright (C) 1995-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/platform/features.h b/lib/libc/include/generic-glibc/bits/platform/features.h index 6123494cf60deb90aaec2670b98c6659ac816f21..e26cbc2b45a94d9e028773febba54f0d7bcde339 100644 --- a/lib/libc/include/generic-glibc/bits/platform/features.h +++ b/lib/libc/include/generic-glibc/bits/platform/features.h @@ -1,6 +1,6 @@ /* Inline functions for x86 CPU features. This file is part of the GNU C Library. - Copyright (C) 2024-2025 Free Software Foundation, Inc. + Copyright (C) 2024-2026 Free Software Foundation, Inc. The GNU C Library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public diff --git a/lib/libc/include/generic-glibc/bits/platform/x86.h b/lib/libc/include/generic-glibc/bits/platform/x86.h index 5b2ad573fe7cee809871fb1f6a3835053a9dac7b..f4a94eec5421181e9ff9c2f732a10723f79df9c7 100644 --- a/lib/libc/include/generic-glibc/bits/platform/x86.h +++ b/lib/libc/include/generic-glibc/bits/platform/x86.h @@ -1,6 +1,6 @@ /* Constants and data structures for x86 CPU features. This file is part of the GNU C Library. - Copyright (C) 2008-2025 Free Software Foundation, Inc. + Copyright (C) 2008-2026 Free Software Foundation, Inc. The GNU C Library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public diff --git a/lib/libc/include/generic-glibc/bits/poll.h b/lib/libc/include/generic-glibc/bits/poll.h index 50cc20cb094c94762f2ad639649514493eae2421..4ba71517ae561932d909f64810bd70876530e281 100644 --- a/lib/libc/include/generic-glibc/bits/poll.h +++ b/lib/libc/include/generic-glibc/bits/poll.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1997-2025 Free Software Foundation, Inc. +/* Copyright (C) 1997-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/poll2.h b/lib/libc/include/generic-glibc/bits/poll2.h index 6e912e6d70ba378d9449b3b7425685c82c4de62f..85cd62dbc6e7037982a63004e686cc5c62d84de8 100644 --- a/lib/libc/include/generic-glibc/bits/poll2.h +++ b/lib/libc/include/generic-glibc/bits/poll2.h @@ -1,5 +1,5 @@ /* Checking macros for poll functions. - Copyright (C) 2012-2025 Free Software Foundation, Inc. + Copyright (C) 2012-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/posix1_lim.h b/lib/libc/include/generic-glibc/bits/posix1_lim.h index 13d18f529713f3dc317ef1a54df4b5d2f0eb019e..63d138f0a467322190f29e66a8ce3af2a1dfdc1b 100644 --- a/lib/libc/include/generic-glibc/bits/posix1_lim.h +++ b/lib/libc/include/generic-glibc/bits/posix1_lim.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1991-2025 Free Software Foundation, Inc. +/* Copyright (C) 1991-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/posix2_lim.h b/lib/libc/include/generic-glibc/bits/posix2_lim.h index edeab6fc6f52279b8c33547e26e871abd539683d..0a22bd055b257f043ed58c364f2c853f6436a33a 100644 --- a/lib/libc/include/generic-glibc/bits/posix2_lim.h +++ b/lib/libc/include/generic-glibc/bits/posix2_lim.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1991-2025 Free Software Foundation, Inc. +/* Copyright (C) 1991-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/posix_opt.h b/lib/libc/include/generic-glibc/bits/posix_opt.h index 2bd0c183e8d341d6da876673f006cd614b2109a1..35c13aa7d76830086bac3180d755877fa3e5fb86 100644 --- a/lib/libc/include/generic-glibc/bits/posix_opt.h +++ b/lib/libc/include/generic-glibc/bits/posix_opt.h @@ -1,5 +1,5 @@ /* Define POSIX options for Linux. - Copyright (C) 1996-2025 Free Software Foundation, Inc. + Copyright (C) 1996-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or @@ -54,7 +54,7 @@ /* `c_cc' member of 'struct termios' structure can be disabled by using the value _POSIX_VDISABLE. */ -#define _POSIX_VDISABLE '\0' +#define _POSIX_VDISABLE 0 /* Filenames are not silently truncated. */ #define _POSIX_NO_TRUNC 1 diff --git a/lib/libc/include/generic-glibc/bits/ppc.h b/lib/libc/include/generic-glibc/bits/ppc.h index af91d4ca3ca482bb3d4f4acc88fbd7f9745d12ac..5e98f066e0a95606f78a7129795ff5f06cbe55ad 100644 --- a/lib/libc/include/generic-glibc/bits/ppc.h +++ b/lib/libc/include/generic-glibc/bits/ppc.h @@ -1,5 +1,5 @@ /* Facilities specific to the PowerPC architecture on Linux - Copyright (C) 2012-2025 Free Software Foundation, Inc. + Copyright (C) 2012-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/printf-ldbl.h b/lib/libc/include/generic-glibc/bits/printf-ldbl.h index c1bd5c3820da49624f678035359281f56c1fcf31..034c5c0ce67bba7965c5e8e1c312ab0fad3cf422 100644 --- a/lib/libc/include/generic-glibc/bits/printf-ldbl.h +++ b/lib/libc/include/generic-glibc/bits/printf-ldbl.h @@ -1,5 +1,5 @@ /* -mlong-double-64 compatibility mode for functions. - Copyright (C) 2006-2025 Free Software Foundation, Inc. + Copyright (C) 2006-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/procfs-extra.h b/lib/libc/include/generic-glibc/bits/procfs-extra.h index f89b7d8e9596ec26bc0d74b73c6432e161605ef2..114d9a77a2277b8aae0014fadeed69bdf04d9c54 100644 --- a/lib/libc/include/generic-glibc/bits/procfs-extra.h +++ b/lib/libc/include/generic-glibc/bits/procfs-extra.h @@ -1,5 +1,5 @@ /* Extra sys/procfs.h definitions. Generic Linux version. - Copyright (C) 2018-2025 Free Software Foundation, Inc. + Copyright (C) 2018-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. diff --git a/lib/libc/include/generic-glibc/bits/procfs-id.h b/lib/libc/include/generic-glibc/bits/procfs-id.h index 9ad086fe8007f886f03284a2d2349c5b35a1f02f..ee42fb67e0b0177665bd65734fe19f8bf85bf509 100644 --- a/lib/libc/include/generic-glibc/bits/procfs-id.h +++ b/lib/libc/include/generic-glibc/bits/procfs-id.h @@ -1,5 +1,5 @@ /* Types of pr_uid and pr_gid in struct elf_prpsinfo. Generic Linux version. - Copyright (C) 2018-2025 Free Software Foundation, Inc. + Copyright (C) 2018-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. diff --git a/lib/libc/include/generic-glibc/bits/procfs-prregset.h b/lib/libc/include/generic-glibc/bits/procfs-prregset.h index 2bed448895db7c6503d7288f61ef3b31fe293873..2d51ab3fc26bb09f93f463823b95740c81c9298c 100644 --- a/lib/libc/include/generic-glibc/bits/procfs-prregset.h +++ b/lib/libc/include/generic-glibc/bits/procfs-prregset.h @@ -1,5 +1,5 @@ /* Types of prgregset_t and prfpregset_t. Generic Linux version. - Copyright (C) 2018-2025 Free Software Foundation, Inc. + Copyright (C) 2018-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. diff --git a/lib/libc/include/generic-glibc/bits/procfs.h b/lib/libc/include/generic-glibc/bits/procfs.h index f32dc14a7b34a8b1db8acf358895848e6bdd189d..a0fe6eec91daa3f92ee134e68dbfd340c1d4c8f8 100644 --- a/lib/libc/include/generic-glibc/bits/procfs.h +++ b/lib/libc/include/generic-glibc/bits/procfs.h @@ -1,5 +1,5 @@ /* Types for registers for sys/procfs.h. MIPS version. - Copyright (C) 1996-2025 Free Software Foundation, Inc. + Copyright (C) 1996-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/pthread_stack_min-dynamic.h b/lib/libc/include/generic-glibc/bits/pthread_stack_min-dynamic.h index a0420827bdd0c5cfe9dd9499417904c9b6ba390b..643fa7e903af6905dbfbe8a32979cbc5b08b893e 100644 --- a/lib/libc/include/generic-glibc/bits/pthread_stack_min-dynamic.h +++ b/lib/libc/include/generic-glibc/bits/pthread_stack_min-dynamic.h @@ -1,5 +1,5 @@ /* Definition of PTHREAD_STACK_MIN, possibly dynamic. - Copyright (C) 2021-2025 Free Software Foundation, Inc. + Copyright (C) 2021-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/pthread_stack_min.h b/lib/libc/include/generic-glibc/bits/pthread_stack_min.h index ecd3479f6adf5bfa094cc4c8e12d1c7fd99086dc..5a9a10a0d517f49f7b1a15c110c1917bccc7faea 100644 --- a/lib/libc/include/generic-glibc/bits/pthread_stack_min.h +++ b/lib/libc/include/generic-glibc/bits/pthread_stack_min.h @@ -1,5 +1,5 @@ /* Definition of PTHREAD_STACK_MIN. Linux version. - Copyright (C) 2021-2025 Free Software Foundation, Inc. + Copyright (C) 2021-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/pthreadtypes-arch.h b/lib/libc/include/generic-glibc/bits/pthreadtypes-arch.h index d86351fd4173058c5dc8d6bec331cc2d602f5ce8..1c332be795b8383278828f8340491ff67f7b82e7 100644 --- a/lib/libc/include/generic-glibc/bits/pthreadtypes-arch.h +++ b/lib/libc/include/generic-glibc/bits/pthreadtypes-arch.h @@ -1,5 +1,5 @@ /* Machine-specific pthread type layouts. Generic version. - Copyright (C) 2019-2025 Free Software Foundation, Inc. + Copyright (C) 2019-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. diff --git a/lib/libc/include/generic-glibc/bits/pthreadtypes.h b/lib/libc/include/generic-glibc/bits/pthreadtypes.h index 3510b0a208f9f200858ca198ead37dbeff0835b7..a2a29e841c5b56c9bd9c0b20e36e08c2963a19c5 100644 --- a/lib/libc/include/generic-glibc/bits/pthreadtypes.h +++ b/lib/libc/include/generic-glibc/bits/pthreadtypes.h @@ -1,5 +1,5 @@ /* Declaration of common pthread types for all architectures. - Copyright (C) 2017-2025 Free Software Foundation, Inc. + Copyright (C) 2017-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/ptrace-shared.h b/lib/libc/include/generic-glibc/bits/ptrace-shared.h index 8016ba27c6c56520f4588559482a01d572e620a1..8ce2e902148728882846661897413a7d95ed3a2b 100644 --- a/lib/libc/include/generic-glibc/bits/ptrace-shared.h +++ b/lib/libc/include/generic-glibc/bits/ptrace-shared.h @@ -1,6 +1,6 @@ /* `ptrace' debugger support interface. Linux version, not architecture-specific. - Copyright (C) 1996-2025 Free Software Foundation, Inc. + Copyright (C) 1996-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. diff --git a/lib/libc/include/generic-glibc/bits/resource.h b/lib/libc/include/generic-glibc/bits/resource.h index 8711f4616420bbeee1aa66a0b035184fea92c649..7ab2550b3cf00f6769f217b7e2a4dea2acb89924 100644 --- a/lib/libc/include/generic-glibc/bits/resource.h +++ b/lib/libc/include/generic-glibc/bits/resource.h @@ -1,5 +1,5 @@ /* Bit values & structures for resource limits. Linux version. - Copyright (C) 1994-2025 Free Software Foundation, Inc. + Copyright (C) 1994-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/rseq.h b/lib/libc/include/generic-glibc/bits/rseq.h index 60f35a626167aff6da78942735db674168b855fb..520d973af2e6681e6d9e2fd5dae73d7fca86a0be 100644 --- a/lib/libc/include/generic-glibc/bits/rseq.h +++ b/lib/libc/include/generic-glibc/bits/rseq.h @@ -1,5 +1,5 @@ /* Restartable Sequences Linux mips architecture header. - Copyright (C) 2021-2025 Free Software Foundation, Inc. + Copyright (C) 2021-2026 Free Software Foundation, Inc. The GNU C Library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public diff --git a/lib/libc/include/generic-glibc/bits/sched.h b/lib/libc/include/generic-glibc/bits/sched.h index c0e7cd3ead0630fcec65b47a0878b5e57a27c581..aa49876c1c353c46701b02b6f7f19b2f565e06e5 100644 --- a/lib/libc/include/generic-glibc/bits/sched.h +++ b/lib/libc/include/generic-glibc/bits/sched.h @@ -1,6 +1,6 @@ /* Definitions of constants and data structure for POSIX 1003.1b-1993 scheduling interface. - Copyright (C) 1996-2025 Free Software Foundation, Inc. + Copyright (C) 1996-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/select-decl.h b/lib/libc/include/generic-glibc/bits/select-decl.h index fb3a3a6ae998f298b9f2556667074cf21fb5275f..0796f7f3e4f947290077cee94b78e5db80580b28 100644 --- a/lib/libc/include/generic-glibc/bits/select-decl.h +++ b/lib/libc/include/generic-glibc/bits/select-decl.h @@ -1,5 +1,5 @@ /* Checking routines for select functions. Declaration only. - Copyright (C) 2023-2025 Free Software Foundation, Inc. + Copyright (C) 2023-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/select.h b/lib/libc/include/generic-glibc/bits/select.h index 2683668a052c6feefb2e96f4b7e85a56accefe5b..36cce0ec34056c0442174c4f4c926441ba3d80ec 100644 --- a/lib/libc/include/generic-glibc/bits/select.h +++ b/lib/libc/include/generic-glibc/bits/select.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1997-2025 Free Software Foundation, Inc. +/* Copyright (C) 1997-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/select2.h b/lib/libc/include/generic-glibc/bits/select2.h index 0144b4092d345751c0d073419a50c1674a8f1686..8d82cd504930cc465650bb9b0e1008ba2e2f7b27 100644 --- a/lib/libc/include/generic-glibc/bits/select2.h +++ b/lib/libc/include/generic-glibc/bits/select2.h @@ -1,5 +1,5 @@ /* Checking macros for select functions. - Copyright (C) 2011-2025 Free Software Foundation, Inc. + Copyright (C) 2011-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/sem.h b/lib/libc/include/generic-glibc/bits/sem.h index 8b2d5f5eb4b2e98c798a06b00776940cbf809116..5a6afb70eb5a4f9d04002a25dad500ab58f45a2f 100644 --- a/lib/libc/include/generic-glibc/bits/sem.h +++ b/lib/libc/include/generic-glibc/bits/sem.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1995-2025 Free Software Foundation, Inc. +/* Copyright (C) 1995-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/semaphore.h b/lib/libc/include/generic-glibc/bits/semaphore.h index c84a8d70a2548e9cbfafbb42f7fdb3688edceab6..bc0b489262428019e3449203d98d4861e52af22e 100644 --- a/lib/libc/include/generic-glibc/bits/semaphore.h +++ b/lib/libc/include/generic-glibc/bits/semaphore.h @@ -1,5 +1,5 @@ /* Generic POSIX semaphore type layout - Copyright (C) 1995-2025 Free Software Foundation, Inc. + Copyright (C) 1995-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/setjmp.h b/lib/libc/include/generic-glibc/bits/setjmp.h index 5eefe2f900c83ea5e7fd2f5e55a002169c193f55..f7ba985a9f2a49f95184e0b6fdff3d3f7bab6745 100644 --- a/lib/libc/include/generic-glibc/bits/setjmp.h +++ b/lib/libc/include/generic-glibc/bits/setjmp.h @@ -1,5 +1,5 @@ /* Define the machine-dependent type `jmp_buf'. MIPS version. - Copyright (C) 1992-2025 Free Software Foundation, Inc. + Copyright (C) 1992-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/setjmp2.h b/lib/libc/include/generic-glibc/bits/setjmp2.h index 9542c0560df1e16f311195d3d9350a0aae4f56aa..9f98fe0b7d57c78c387e1138e4837588cf3860a2 100644 --- a/lib/libc/include/generic-glibc/bits/setjmp2.h +++ b/lib/libc/include/generic-glibc/bits/setjmp2.h @@ -1,5 +1,5 @@ /* Checking macros for setjmp functions. - Copyright (C) 2009-2025 Free Software Foundation, Inc. + Copyright (C) 2009-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/shm.h b/lib/libc/include/generic-glibc/bits/shm.h index 89562d77a10b61920f33e1a1d97d4c919c4ca1a6..59ebb918b0013b2b75815fcef3cdad444b855cd0 100644 --- a/lib/libc/include/generic-glibc/bits/shm.h +++ b/lib/libc/include/generic-glibc/bits/shm.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1995-2025 Free Software Foundation, Inc. +/* Copyright (C) 1995-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/shmlba.h b/lib/libc/include/generic-glibc/bits/shmlba.h index 45a3b0713a1e7f691f82f5c680b5357c2225e5ce..396ac08af12492d857b254e9b39d340ec59646de 100644 --- a/lib/libc/include/generic-glibc/bits/shmlba.h +++ b/lib/libc/include/generic-glibc/bits/shmlba.h @@ -1,5 +1,5 @@ /* Define SHMLBA. Generic version. - Copyright (C) 2018-2025 Free Software Foundation, Inc. + Copyright (C) 2018-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/sigaction.h b/lib/libc/include/generic-glibc/bits/sigaction.h index 130895b1329823088974b204f515153e14188fa9..64836caa87fb57149b1b42b41abdfc63c4a54a86 100644 --- a/lib/libc/include/generic-glibc/bits/sigaction.h +++ b/lib/libc/include/generic-glibc/bits/sigaction.h @@ -1,5 +1,5 @@ /* The proper definitions for Linux's sigaction. - Copyright (C) 1993-2025 Free Software Foundation, Inc. + Copyright (C) 1993-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/sigcontext.h b/lib/libc/include/generic-glibc/bits/sigcontext.h index ac320c03d0b683cb12f5a902edd3c48bd2292580..c551fb8e1e1695c0fe25aa392f7f520117d1b39d 100644 --- a/lib/libc/include/generic-glibc/bits/sigcontext.h +++ b/lib/libc/include/generic-glibc/bits/sigcontext.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1996-2025 Free Software Foundation, Inc. +/* Copyright (C) 1996-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/sigevent-consts.h b/lib/libc/include/generic-glibc/bits/sigevent-consts.h index 93eb14f80a0f5e3f42e5052df8df5053f0b21536..18a1c9b2a70286d4c929ac99742f4278a65a58e3 100644 --- a/lib/libc/include/generic-glibc/bits/sigevent-consts.h +++ b/lib/libc/include/generic-glibc/bits/sigevent-consts.h @@ -1,5 +1,5 @@ /* sigevent constants. Linux version. - Copyright (C) 1997-2025 Free Software Foundation, Inc. + Copyright (C) 1997-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/siginfo-consts.h b/lib/libc/include/generic-glibc/bits/siginfo-consts.h index 5958670bfa10039712dd4d1357340aa44b86a455..f4a1a39f776a9909d1f805da786749092baf7591 100644 --- a/lib/libc/include/generic-glibc/bits/siginfo-consts.h +++ b/lib/libc/include/generic-glibc/bits/siginfo-consts.h @@ -1,5 +1,5 @@ /* siginfo constants. Linux version. - Copyright (C) 1997-2025 Free Software Foundation, Inc. + Copyright (C) 1997-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or @@ -168,8 +168,10 @@ enum # define TRAP_BRANCH TRAP_BRANCH TRAP_HWBKPT, /* Hardware breakpoint/watchpoint. */ # define TRAP_HWBKPT TRAP_HWBKPT - TRAP_UNK /* Undiagnosed trap. */ + TRAP_UNK, /* Undiagnosed trap. */ # define TRAP_UNK TRAP_UNK + TRAP_PERF /* Perf event with sigtrap=1. */ +# define TRAP_PERF TRAP_PERF }; # endif @@ -209,6 +211,18 @@ enum }; # endif +/* The Linux-specific SIGSYS values are all considered GNU extensions. */ +#ifdef __USE_GNU +/* `si_code' values for SIGSYS signal. */ +enum +{ + SYS_SECCOMP = 1, /* Seccomp triggered. */ +# define SYS_SECCOMP SYS_SECCOMP + SYS_USER_DISPATCH /* Syscall user dispatch triggered. */ +# define SYS_USER_DISPATCH SYS_USER_DISPATCH +}; +#endif + /* Architectures might also add architecture-specific constants. These are all considered GNU extensions. */ #ifdef __USE_GNU diff --git a/lib/libc/include/generic-glibc/bits/signal_ext.h b/lib/libc/include/generic-glibc/bits/signal_ext.h index e19d2026ba7e094af15601a986a61bdb2e959fca..1e87f9d2eb88bd8e227d8505d5a23dfe15fde94d 100644 --- a/lib/libc/include/generic-glibc/bits/signal_ext.h +++ b/lib/libc/include/generic-glibc/bits/signal_ext.h @@ -1,5 +1,5 @@ /* System-specific extensions of , Linux version. - Copyright (C) 2019-2025 Free Software Foundation, Inc. + Copyright (C) 2019-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/signalfd.h b/lib/libc/include/generic-glibc/bits/signalfd.h index b54daab5623b2b2aeb0ed24c219ed12f7b1b0a99..1cc97d89962497b225b98c3c193e1924111a5793 100644 --- a/lib/libc/include/generic-glibc/bits/signalfd.h +++ b/lib/libc/include/generic-glibc/bits/signalfd.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2007-2025 Free Software Foundation, Inc. +/* Copyright (C) 2007-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/signum-arch.h b/lib/libc/include/generic-glibc/bits/signum-arch.h index a1e34a31918499bbbf80fe09e322622cf8991802..5ae8d4973ebc31a464c0e75fa0175c0a00ecc856 100644 --- a/lib/libc/include/generic-glibc/bits/signum-arch.h +++ b/lib/libc/include/generic-glibc/bits/signum-arch.h @@ -1,5 +1,5 @@ /* Signal number definitions. Linux version. - Copyright (C) 1995-2025 Free Software Foundation, Inc. + Copyright (C) 1995-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/signum-generic.h b/lib/libc/include/generic-glibc/bits/signum-generic.h index a6f4511a0608708fdaf2469827389232cd206b5e..7c37e9d40218bdbbe00159447fee42f70659cb17 100644 --- a/lib/libc/include/generic-glibc/bits/signum-generic.h +++ b/lib/libc/include/generic-glibc/bits/signum-generic.h @@ -1,5 +1,5 @@ /* Signal number constants. Generic template. - Copyright (C) 1991-2025 Free Software Foundation, Inc. + Copyright (C) 1991-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/sigstack.h b/lib/libc/include/generic-glibc/bits/sigstack.h index 3f238ad9bd93db95206616b710f88f2e9d3fca1b..bd790e051595371bad56316486fcbe3d71d7d7ab 100644 --- a/lib/libc/include/generic-glibc/bits/sigstack.h +++ b/lib/libc/include/generic-glibc/bits/sigstack.h @@ -1,5 +1,5 @@ /* sigstack, sigaltstack definitions. - Copyright (C) 1998-2025 Free Software Foundation, Inc. + Copyright (C) 1998-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/sigstksz.h b/lib/libc/include/generic-glibc/bits/sigstksz.h index dd535d7208325b57f29847b4ae986ee8ce3a32a3..6210586a24aa527f4ca983382c34d0e0e3474997 100644 --- a/lib/libc/include/generic-glibc/bits/sigstksz.h +++ b/lib/libc/include/generic-glibc/bits/sigstksz.h @@ -1,5 +1,5 @@ /* Definition of MINSIGSTKSZ and SIGSTKSZ. Linux version. - Copyright (C) 2020-2025 Free Software Foundation, Inc. + Copyright (C) 2020-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/sigthread.h b/lib/libc/include/generic-glibc/bits/sigthread.h index 752c13ccf65dcb5cd3904b77e32a0a9a185b0bc1..071af39be9300db1aa8b090d5020956ffb4cb3eb 100644 --- a/lib/libc/include/generic-glibc/bits/sigthread.h +++ b/lib/libc/include/generic-glibc/bits/sigthread.h @@ -1,5 +1,5 @@ /* Signal handling function for threaded programs. - Copyright (C) 1998-2025 Free Software Foundation, Inc. + Copyright (C) 1998-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/sockaddr.h b/lib/libc/include/generic-glibc/bits/sockaddr.h index cc2e91efcc5414f7f82b5f6283f15f63ab1ddd1f..60095d188f39f87a9a63cce3f4ae495ddd7c57f0 100644 --- a/lib/libc/include/generic-glibc/bits/sockaddr.h +++ b/lib/libc/include/generic-glibc/bits/sockaddr.h @@ -1,5 +1,5 @@ /* Definition of struct sockaddr_* common members and sizes, generic version. - Copyright (C) 1995-2025 Free Software Foundation, Inc. + Copyright (C) 1995-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/socket-constants.h b/lib/libc/include/generic-glibc/bits/socket-constants.h index d4efd385d124a72830a2c43f8c9750de8ef26b3b..fd8897fed1300efa66717d61069bcc8da848bed6 100644 --- a/lib/libc/include/generic-glibc/bits/socket-constants.h +++ b/lib/libc/include/generic-glibc/bits/socket-constants.h @@ -1,5 +1,5 @@ /* Socket constants which vary among Linux architectures. - Copyright (C) 2019-2025 Free Software Foundation, Inc. + Copyright (C) 2019-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/socket.h b/lib/libc/include/generic-glibc/bits/socket.h index 22cdb822c6a4474f2a72982479ad2c773fb0f95d..62c91e9ba982e4618eab608c7abe8289d3f7d61d 100644 --- a/lib/libc/include/generic-glibc/bits/socket.h +++ b/lib/libc/include/generic-glibc/bits/socket.h @@ -1,5 +1,5 @@ /* System-specific socket constants and types. Linux version. - Copyright (C) 1991-2025 Free Software Foundation, Inc. + Copyright (C) 1991-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/socket2.h b/lib/libc/include/generic-glibc/bits/socket2.h index 1365126b680ab47c4d74283bca495f826b9e43cd..326deaf3aaa87ded048e2caf9b9b9910b4065526 100644 --- a/lib/libc/include/generic-glibc/bits/socket2.h +++ b/lib/libc/include/generic-glibc/bits/socket2.h @@ -1,5 +1,5 @@ /* Checking macros for socket functions. - Copyright (C) 2005-2025 Free Software Foundation, Inc. + Copyright (C) 2005-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/socket_type.h b/lib/libc/include/generic-glibc/bits/socket_type.h index a95843234213621771c22648b7edd3d1af8d3ab3..a763872fa93f40a01cc39d0daf897c510fcde1fa 100644 --- a/lib/libc/include/generic-glibc/bits/socket_type.h +++ b/lib/libc/include/generic-glibc/bits/socket_type.h @@ -1,5 +1,5 @@ /* Define enum __socket_type for generic Linux. - Copyright (C) 1991-2025 Free Software Foundation, Inc. + Copyright (C) 1991-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/spawn_ext.h b/lib/libc/include/generic-glibc/bits/spawn_ext.h index 534d1c8fb163ca8347566bab8e204f05175180e2..e70b4be563c407c4f211538a6870319b0ddf788f 100644 --- a/lib/libc/include/generic-glibc/bits/spawn_ext.h +++ b/lib/libc/include/generic-glibc/bits/spawn_ext.h @@ -1,5 +1,5 @@ /* POSIX spawn extensions. Linux version. - Copyright (C) 2023-2025 Free Software Foundation, Inc. + Copyright (C) 2023-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/ss_flags.h b/lib/libc/include/generic-glibc/bits/ss_flags.h index 642a09231ce73b4ccc626e4d2fde8bd80e12cfc1..2ba640d263e9b2a6911c409c10543cb8b26aaa4e 100644 --- a/lib/libc/include/generic-glibc/bits/ss_flags.h +++ b/lib/libc/include/generic-glibc/bits/ss_flags.h @@ -1,5 +1,5 @@ /* ss_flags values for stack_t. Linux version. - Copyright (C) 1998-2025 Free Software Foundation, Inc. + Copyright (C) 1998-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/stab.def b/lib/libc/include/generic-glibc/bits/stab.def index 2d684acbee010c7aa7cf863a38d971403220f63a..1967bf9983f7e454974ac0ec7a87a636616c7002 100644 --- a/lib/libc/include/generic-glibc/bits/stab.def +++ b/lib/libc/include/generic-glibc/bits/stab.def @@ -1,5 +1,5 @@ /* Table of DBX symbol codes for the GNU system. - Copyright (C) 1988, 1997-2025 Free Software Foundation, Inc. + Copyright (C) 1988, 1997-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/stat.h b/lib/libc/include/generic-glibc/bits/stat.h index 72ff0a8d8b3e1ffd3ff81804475b092814d0694d..aba780ffd3891883d66d12febc04f552e8c05a0b 100644 --- a/lib/libc/include/generic-glibc/bits/stat.h +++ b/lib/libc/include/generic-glibc/bits/stat.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1992-2025 Free Software Foundation, Inc. +/* Copyright (C) 1992-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/statfs.h b/lib/libc/include/generic-glibc/bits/statfs.h index ed77ad1575c0692a944bde04e93bace1be42e764..15ec2e10ff7aacdc618147e77c1570a23462996e 100644 --- a/lib/libc/include/generic-glibc/bits/statfs.h +++ b/lib/libc/include/generic-glibc/bits/statfs.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1997-2025 Free Software Foundation, Inc. +/* Copyright (C) 1997-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/statvfs.h b/lib/libc/include/generic-glibc/bits/statvfs.h index 38f11407d5d235e7e2e123460ad62656a6541055..a10c21ac5e8cad598a54335a6b682e92b299f6fc 100644 --- a/lib/libc/include/generic-glibc/bits/statvfs.h +++ b/lib/libc/include/generic-glibc/bits/statvfs.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1997-2025 Free Software Foundation, Inc. +/* Copyright (C) 1997-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/statx-generic.h b/lib/libc/include/generic-glibc/bits/statx-generic.h index c197005d8ba28cfca0d4aa0c16491d352943ba7f..6663449e195dd176a92307803c4ed7b5fcfa1f77 100644 --- a/lib/libc/include/generic-glibc/bits/statx-generic.h +++ b/lib/libc/include/generic-glibc/bits/statx-generic.h @@ -1,5 +1,5 @@ /* Generic statx-related definitions and declarations. - Copyright (C) 2018-2025 Free Software Foundation, Inc. + Copyright (C) 2018-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or @@ -48,6 +48,7 @@ # define STATX_MNT_ID_UNIQUE 0x4000U # define STATX_SUBVOL 0x8000U # define STATX_WRITE_ATOMIC 0x00010000U +# define STATX_DIO_READ_ALIGN 0x00020000U # define STATX__RESERVED 0x80000000U # define STATX_ATTR_COMPRESSED 0x0004 diff --git a/lib/libc/include/generic-glibc/bits/statx.h b/lib/libc/include/generic-glibc/bits/statx.h index 7b2e0ceb7c0cccc7fd3be145eae86f59ae6427bb..e2b325c216d6afe1265a3fb70b80d4df88db82f5 100644 --- a/lib/libc/include/generic-glibc/bits/statx.h +++ b/lib/libc/include/generic-glibc/bits/statx.h @@ -1,5 +1,5 @@ /* statx-related definitions and declarations. Linux version. - Copyright (C) 2018-2025 Free Software Foundation, Inc. + Copyright (C) 2018-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/stdint-intn.h b/lib/libc/include/generic-glibc/bits/stdint-intn.h index c66b42ee53084a0bc2d1b03e9033130cefdbd7bf..4ce6b7c45b5d4f0c71498bec5ae0f78f84863b29 100644 --- a/lib/libc/include/generic-glibc/bits/stdint-intn.h +++ b/lib/libc/include/generic-glibc/bits/stdint-intn.h @@ -1,5 +1,5 @@ /* Define intN_t types. - Copyright (C) 2017-2025 Free Software Foundation, Inc. + Copyright (C) 2017-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/stdint-least.h b/lib/libc/include/generic-glibc/bits/stdint-least.h index 2aae8a7b14f9e9a7457ed3366b3973445b43fd8e..95ec33cabcdfc57b5cb5b086302fb188ae365812 100644 --- a/lib/libc/include/generic-glibc/bits/stdint-least.h +++ b/lib/libc/include/generic-glibc/bits/stdint-least.h @@ -1,5 +1,5 @@ /* Define int_leastN_t and uint_leastN types. - Copyright (C) 2024-2025 Free Software Foundation, Inc. + Copyright (C) 2024-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/stdint-uintn.h b/lib/libc/include/generic-glibc/bits/stdint-uintn.h index 1a5333937008f2c980cb5a40ef55a700537a55c0..f198de37b14d070343ffb66db6e1aebf518c7455 100644 --- a/lib/libc/include/generic-glibc/bits/stdint-uintn.h +++ b/lib/libc/include/generic-glibc/bits/stdint-uintn.h @@ -1,5 +1,5 @@ /* Define uintN_t types. - Copyright (C) 2017-2025 Free Software Foundation, Inc. + Copyright (C) 2017-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/stdio-ldbl.h b/lib/libc/include/generic-glibc/bits/stdio-ldbl.h index 87bba9b4b8bd6a3f7433195b8fee1c77fc9b9ec7..4efb79675115f19d88ba576d2dd185fc582b7cc2 100644 --- a/lib/libc/include/generic-glibc/bits/stdio-ldbl.h +++ b/lib/libc/include/generic-glibc/bits/stdio-ldbl.h @@ -1,5 +1,5 @@ /* -mlong-double-64 compatibility mode for stdio functions. - Copyright (C) 2006-2025 Free Software Foundation, Inc. + Copyright (C) 2006-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/stdio.h b/lib/libc/include/generic-glibc/bits/stdio.h index 4dd08a8b2984dc8f932a9ffca1c3f0d237200828..f5d0753a774defcaf8bed7b98fdaf48bb429bc89 100644 --- a/lib/libc/include/generic-glibc/bits/stdio.h +++ b/lib/libc/include/generic-glibc/bits/stdio.h @@ -1,5 +1,5 @@ /* Optimizing macros and inline functions for stdio functions. - Copyright (C) 1998-2025 Free Software Foundation, Inc. + Copyright (C) 1998-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/stdio2-decl.h b/lib/libc/include/generic-glibc/bits/stdio2-decl.h index 8591cafdf5260d595ad3b48e5c89cbfed6ee676d..59a7d14bca8c38f2b4fca4b0b2206cb443c5e3df 100644 --- a/lib/libc/include/generic-glibc/bits/stdio2-decl.h +++ b/lib/libc/include/generic-glibc/bits/stdio2-decl.h @@ -1,5 +1,5 @@ /* Checking macros for stdio functions. Declarations only. - Copyright (C) 2004-2025 Free Software Foundation, Inc. + Copyright (C) 2004-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/stdio2.h b/lib/libc/include/generic-glibc/bits/stdio2.h index 355ab080eddad0418547c6627f85448945be8a73..7761b193e38e31d34430d468bfa789b30f47a63d 100644 --- a/lib/libc/include/generic-glibc/bits/stdio2.h +++ b/lib/libc/include/generic-glibc/bits/stdio2.h @@ -1,5 +1,5 @@ /* Checking macros for stdio functions. - Copyright (C) 2004-2025 Free Software Foundation, Inc. + Copyright (C) 2004-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/stdio_lim.h b/lib/libc/include/generic-glibc/bits/stdio_lim.h index 93fa44af20732a8eeb98b0b76cffda1b6e68bec3..71c45757d660c17237d42d11da17fe83f11c3f1e 100644 --- a/lib/libc/include/generic-glibc/bits/stdio_lim.h +++ b/lib/libc/include/generic-glibc/bits/stdio_lim.h @@ -1,5 +1,5 @@ /* System specific stdio.h definitions. Linux version. - Copyright (C) 2023-2025 Free Software Foundation, Inc. + Copyright (C) 2023-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/stdlib-bsearch.h b/lib/libc/include/generic-glibc/bits/stdlib-bsearch.h index 959676e3b6958f03e3bdd1952a4623ed10e28282..2168bf70d3b29f27af868df7b173e04e584ad2a5 100644 --- a/lib/libc/include/generic-glibc/bits/stdlib-bsearch.h +++ b/lib/libc/include/generic-glibc/bits/stdlib-bsearch.h @@ -1,5 +1,5 @@ /* Perform binary search - inline version. - Copyright (C) 1991-2025 Free Software Foundation, Inc. + Copyright (C) 1991-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/stdlib-float.h b/lib/libc/include/generic-glibc/bits/stdlib-float.h index ff5f0a052dfeb7941a24fec7b7a501ebff33c0e6..3df3257fcd91dabdf4abc92bc9037cb66391bcac 100644 --- a/lib/libc/include/generic-glibc/bits/stdlib-float.h +++ b/lib/libc/include/generic-glibc/bits/stdlib-float.h @@ -1,5 +1,5 @@ /* Floating-point inline functions for stdlib.h. - Copyright (C) 2012-2025 Free Software Foundation, Inc. + Copyright (C) 2012-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/stdlib-ldbl.h b/lib/libc/include/generic-glibc/bits/stdlib-ldbl.h index 332a7af8e1e02dc0b8224a5dd1b08e6e9f1d7687..08d0b18d4ca8b1c77a1d3c416c3320a4e13dec1a 100644 --- a/lib/libc/include/generic-glibc/bits/stdlib-ldbl.h +++ b/lib/libc/include/generic-glibc/bits/stdlib-ldbl.h @@ -1,5 +1,5 @@ /* -mlong-double-64 compatibility mode for functions. - Copyright (C) 2006-2025 Free Software Foundation, Inc. + Copyright (C) 2006-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/stdlib.h b/lib/libc/include/generic-glibc/bits/stdlib.h index e159bea9c7508edff4f0c7988a9f3ea76b555c99..d52a6d18eed43e81f603c4ec923d82c4404d0382 100644 --- a/lib/libc/include/generic-glibc/bits/stdlib.h +++ b/lib/libc/include/generic-glibc/bits/stdlib.h @@ -1,5 +1,5 @@ /* Checking macros for stdlib functions. - Copyright (C) 2005-2025 Free Software Foundation, Inc. + Copyright (C) 2005-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/string_fortified.h b/lib/libc/include/generic-glibc/bits/string_fortified.h index 9c36d02fb1c87876117aef872b857fdef724b000..b056dbbbf3b2b9b392acc9c1ac842345be9df1e9 100644 --- a/lib/libc/include/generic-glibc/bits/string_fortified.h +++ b/lib/libc/include/generic-glibc/bits/string_fortified.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2004-2025 Free Software Foundation, Inc. +/* Copyright (C) 2004-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or @@ -60,6 +60,18 @@ __NTH (memset (void *__dest, int __ch, size_t __len)) __glibc_objsize0 (__dest)); } +#if defined __USE_MISC || __GLIBC_USE (ISOC23) +void *__memset_explicit_chk (void *__s, int __c, size_t __n, size_t __destlen) + __THROW __nonnull ((1)) __fortified_attr_access (__write_only__, 1, 3); + +__fortify_function void * +__NTH (memset_explicit (void *__dest, int __ch, size_t __len)) +{ + return __memset_explicit_chk (__dest, __ch, __len, + __glibc_objsize0 (__dest)); +} +#endif + #ifdef __USE_MISC # include diff --git a/lib/libc/include/generic-glibc/bits/strings_fortified.h b/lib/libc/include/generic-glibc/bits/strings_fortified.h index aad1684f89f40b26f8ae0f1549effa6a110a8ae4..18c423af17e6a904254c36dae1229c0baf58bab0 100644 --- a/lib/libc/include/generic-glibc/bits/strings_fortified.h +++ b/lib/libc/include/generic-glibc/bits/strings_fortified.h @@ -1,5 +1,5 @@ /* Fortify macros for strings.h functions. - Copyright (C) 2017-2025 Free Software Foundation, Inc. + Copyright (C) 2017-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/struct_mutex.h b/lib/libc/include/generic-glibc/bits/struct_mutex.h index d794fea0d5ba70e6f6524e15f6ad1395976aa52b..0476a87aa35e1071a9679e976ab9c2a9ca49b409 100644 --- a/lib/libc/include/generic-glibc/bits/struct_mutex.h +++ b/lib/libc/include/generic-glibc/bits/struct_mutex.h @@ -1,5 +1,5 @@ /* Default mutex implementation struct definitions. - Copyright (C) 2019-2025 Free Software Foundation, Inc. + Copyright (C) 2019-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or @@ -21,8 +21,8 @@ /* Generic struct for both POSIX and C11 mutexes. New ports are expected to use the default layout, however architecture can redefine it to - add arch-specific extension (such as lock-elision). The struct have - a size of 32 bytes on LP32 and 40 bytes on LP64 architectures. */ + add arch-specific extension. The struct have a size of 32 bytes on LP32 + and 40 bytes on LP64 architectures. */ struct __pthread_mutex_s { @@ -40,21 +40,7 @@ struct __pthread_mutex_s PTHREAD_MUTEX_INITIALIZER or by a call to pthread_mutex_init. After a mutex has been initialized, the __kind of a mutex is usually not - changed. BUT it can be set to -1 in pthread_mutex_destroy or elision can - be enabled. This is done concurrently in the pthread_mutex_*lock - functions by using the macro FORCE_ELISION. This macro is only defined - for architectures which supports lock elision. - - For elision, there are the flags PTHREAD_MUTEX_ELISION_NP and - PTHREAD_MUTEX_NO_ELISION_NP which can be set in addition to the already - set type of a mutex. Before a mutex is initialized, only - PTHREAD_MUTEX_NO_ELISION_NP can be set with pthread_mutexattr_settype. - - After a mutex has been initialized, the functions pthread_mutex_*lock can - enable elision - if the mutex-type and the machine supports it - by - setting the flag PTHREAD_MUTEX_ELISION_NP. This is done concurrently. - Afterwards the lock / unlock functions are using specific elision - code-paths. */ + changed. BUT it can be set to -1 in pthread_mutex_destroy. */ int __kind; #if __WORDSIZE != 64 unsigned int __nusers; diff --git a/lib/libc/include/generic-glibc/bits/struct_rwlock.h b/lib/libc/include/generic-glibc/bits/struct_rwlock.h index d1f2e41e34e0a66c703ca2840a38eaa1cd9b655e..8fe478d7c82a0436e45e0cfc19d05c37a8cafa12 100644 --- a/lib/libc/include/generic-glibc/bits/struct_rwlock.h +++ b/lib/libc/include/generic-glibc/bits/struct_rwlock.h @@ -1,5 +1,5 @@ /* Default read-write lock implementation struct definitions. - Copyright (C) 2019-2025 Free Software Foundation, Inc. + Copyright (C) 2019-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or @@ -23,8 +23,8 @@ /* Generic struct for both POSIX read-write lock. New ports are expected to use the default layout, however archictetures can redefine it to add - arch-specific extensions (such as lock-elision). The struct have a size - of 32 bytes on both LP32 and LP64 architectures. */ + arch-specific extensions. The struct have a size of 32 bytes on both LP32 + and LP64 architectures. */ struct __pthread_rwlock_arch_t { diff --git a/lib/libc/include/generic-glibc/bits/struct_stat.h b/lib/libc/include/generic-glibc/bits/struct_stat.h index c07c9a76609b21e947bc9b0e3687ee2ae9a1ce94..231c875e7ab2c703e50746e2191ddc411c0878a2 100644 --- a/lib/libc/include/generic-glibc/bits/struct_stat.h +++ b/lib/libc/include/generic-glibc/bits/struct_stat.h @@ -1,5 +1,5 @@ /* Definition for struct stat. - Copyright (C) 2020-2025 Free Software Foundation, Inc. + Copyright (C) 2020-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/struct_stat_time64_helper.h b/lib/libc/include/generic-glibc/bits/struct_stat_time64_helper.h index 9d927e834f593303263ae00ae8447af38e450743..7dce3ade823ecdd74d69546595c5bd25fd2a4cdc 100644 --- a/lib/libc/include/generic-glibc/bits/struct_stat_time64_helper.h +++ b/lib/libc/include/generic-glibc/bits/struct_stat_time64_helper.h @@ -1,5 +1,5 @@ /* Definition for helper to define struct stat with 64-bit time. - Copyright (C) 2021-2025 Free Software Foundation, Inc. + Copyright (C) 2021-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/syscall.h b/lib/libc/include/generic-glibc/bits/syscall.h index 3c70ff32d493f92febe6d4aaf567c305e35844b9..43e6645f7d238e0805be9874cadff3bfd5889520 100644 --- a/lib/libc/include/generic-glibc/bits/syscall.h +++ b/lib/libc/include/generic-glibc/bits/syscall.h @@ -1,11 +1,11 @@ /* Generated at libc build time from syscall list. */ -/* The system call list corresponds to kernel 6.15. */ +/* The system call list corresponds to kernel 6.17. */ #ifndef _SYSCALL_H # error "Never use directly; include instead." #endif -#define __GLIBC_LINUX_VERSION_CODE 397056 +#define __GLIBC_LINUX_VERSION_CODE 397568 #ifdef __NR_FAST_atomic_update # define SYS_FAST_atomic_update __NR_FAST_atomic_update @@ -411,6 +411,14 @@ # define SYS_fgetxattr __NR_fgetxattr #endif +#ifdef __NR_file_getattr +# define SYS_file_getattr __NR_file_getattr +#endif + +#ifdef __NR_file_setattr +# define SYS_file_setattr __NR_file_setattr +#endif + #ifdef __NR_finit_module # define SYS_finit_module __NR_finit_module #endif diff --git a/lib/libc/include/generic-glibc/bits/syslog-decl.h b/lib/libc/include/generic-glibc/bits/syslog-decl.h index a6984dcd2c1414a95c520aeba7cc50b9848092fc..25786a5a92163ddb44924a6cbd11bf572293adaf 100644 --- a/lib/libc/include/generic-glibc/bits/syslog-decl.h +++ b/lib/libc/include/generic-glibc/bits/syslog-decl.h @@ -1,5 +1,5 @@ /* Checking routines for syslog functions. Declaration only. - Copyright (C) 2023-2025 Free Software Foundation, Inc. + Copyright (C) 2023-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/syslog-ldbl.h b/lib/libc/include/generic-glibc/bits/syslog-ldbl.h index 4b038631dca162fe93633446a1aa03b1024c4594..8d682f900e6b7a961864b1ea2ceca67c9385651e 100644 --- a/lib/libc/include/generic-glibc/bits/syslog-ldbl.h +++ b/lib/libc/include/generic-glibc/bits/syslog-ldbl.h @@ -1,5 +1,5 @@ /* -mlong-double-64 compatibility mode for syslog functions. - Copyright (C) 2006-2025 Free Software Foundation, Inc. + Copyright (C) 2006-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/syslog-path.h b/lib/libc/include/generic-glibc/bits/syslog-path.h index 808f2cb1d5f94271ce75f21b5ab5c82b41a024d4..8ee09d71e070f773b777f0e1296b149482cba84a 100644 --- a/lib/libc/include/generic-glibc/bits/syslog-path.h +++ b/lib/libc/include/generic-glibc/bits/syslog-path.h @@ -1,5 +1,5 @@ /* -- _PATH_LOG definition - Copyright (C) 2006-2025 Free Software Foundation, Inc. + Copyright (C) 2006-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/syslog.h b/lib/libc/include/generic-glibc/bits/syslog.h index 322e9ba010afdbe7dd85992d53c08a8ca2ef2c9b..2c691c8abf510cf8faf75477f4219d01f0437731 100644 --- a/lib/libc/include/generic-glibc/bits/syslog.h +++ b/lib/libc/include/generic-glibc/bits/syslog.h @@ -1,5 +1,5 @@ /* Checking macros for syslog functions. - Copyright (C) 2005-2025 Free Software Foundation, Inc. + Copyright (C) 2005-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/sysmacros.h b/lib/libc/include/generic-glibc/bits/sysmacros.h index 3322015c54fd48ab62b68523c12d785dd265d75b..cf1a1fc11f680bbd72a2a7f3ada6e6a86133dca9 100644 --- a/lib/libc/include/generic-glibc/bits/sysmacros.h +++ b/lib/libc/include/generic-glibc/bits/sysmacros.h @@ -1,5 +1,5 @@ /* Definitions of macros to access `dev_t' values. - Copyright (C) 1996-2025 Free Software Foundation, Inc. + Copyright (C) 1996-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/termios-baud.h b/lib/libc/include/generic-glibc/bits/termios-baud.h index cd11a7e55b5d5a6454b20d66b51ca731104e4102..c270c9ffa4523c3ea94221afa9d9e564e5613799 100644 --- a/lib/libc/include/generic-glibc/bits/termios-baud.h +++ b/lib/libc/include/generic-glibc/bits/termios-baud.h @@ -1,5 +1,5 @@ /* termios baud rate selection definitions. Universal version for sane speed_t. - Copyright (C) 2019-2025 Free Software Foundation, Inc. + Copyright (C) 2019-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/termios-c_cc.h b/lib/libc/include/generic-glibc/bits/termios-c_cc.h index 964069a38e6d0b0fa1fa84e21ec48cc72b62bedd..25b7668a32a7c303757a1a9634c098822e15bddb 100644 --- a/lib/libc/include/generic-glibc/bits/termios-c_cc.h +++ b/lib/libc/include/generic-glibc/bits/termios-c_cc.h @@ -1,5 +1,5 @@ /* termios c_cc symbolic constant definitions. Linux/generic version. - Copyright (C) 2019-2025 Free Software Foundation, Inc. + Copyright (C) 2019-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/termios-c_cflag.h b/lib/libc/include/generic-glibc/bits/termios-c_cflag.h index 7a4c0a53fe348037f5412d2785151aa1c04fcc1e..9cd9efe0f13fc17bc24da3f9b9e89d33f615c2fe 100644 --- a/lib/libc/include/generic-glibc/bits/termios-c_cflag.h +++ b/lib/libc/include/generic-glibc/bits/termios-c_cflag.h @@ -1,5 +1,5 @@ /* termios control mode definitions. Linux/generic version. - Copyright (C) 2019-2025 Free Software Foundation, Inc. + Copyright (C) 2019-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/termios-c_iflag.h b/lib/libc/include/generic-glibc/bits/termios-c_iflag.h index af8f549e200796033cd88441f776ce7c6e6c47d0..62771829ec1708d012900690123f9417bd3d61d0 100644 --- a/lib/libc/include/generic-glibc/bits/termios-c_iflag.h +++ b/lib/libc/include/generic-glibc/bits/termios-c_iflag.h @@ -1,5 +1,5 @@ /* termios input mode definitions. Linux/generic version. - Copyright (C) 2019-2025 Free Software Foundation, Inc. + Copyright (C) 2019-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/termios-c_lflag.h b/lib/libc/include/generic-glibc/bits/termios-c_lflag.h index 35ff14842b774f8dd91fde493be41fa0e84651c1..b407dfd6d51caa56b4fbe63eb44193a807378612 100644 --- a/lib/libc/include/generic-glibc/bits/termios-c_lflag.h +++ b/lib/libc/include/generic-glibc/bits/termios-c_lflag.h @@ -1,5 +1,5 @@ /* termios local mode definitions. Linux/generic version. - Copyright (C) 2019-2025 Free Software Foundation, Inc. + Copyright (C) 2019-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/termios-c_oflag.h b/lib/libc/include/generic-glibc/bits/termios-c_oflag.h index 0604a35e9aae8aae0dcbb2e5537479946e71358d..4cfeff7ba61a1569a13b3d4b2922684353903040 100644 --- a/lib/libc/include/generic-glibc/bits/termios-c_oflag.h +++ b/lib/libc/include/generic-glibc/bits/termios-c_oflag.h @@ -1,5 +1,5 @@ /* termios output mode definitions. Linux/generic version. - Copyright (C) 2019-2025 Free Software Foundation, Inc. + Copyright (C) 2019-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/termios-cbaud.h b/lib/libc/include/generic-glibc/bits/termios-cbaud.h index 38bf2ec1aae085543a8e9290793ebf14f6503de9..9f46d9dde901791d6ae0dc4c2631090d068aa97f 100644 --- a/lib/libc/include/generic-glibc/bits/termios-cbaud.h +++ b/lib/libc/include/generic-glibc/bits/termios-cbaud.h @@ -1,5 +1,5 @@ /* termios baud rate selection definitions. Linux/generic version. - Copyright (C) 2019-2025 Free Software Foundation, Inc. + Copyright (C) 2019-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/termios-misc.h b/lib/libc/include/generic-glibc/bits/termios-misc.h index 42382bfc05c6431cfd2d4d94399b17a39e8e7605..f56475588bf3b7ce7ceb24ac77ea97ec0e60b19f 100644 --- a/lib/libc/include/generic-glibc/bits/termios-misc.h +++ b/lib/libc/include/generic-glibc/bits/termios-misc.h @@ -1,5 +1,5 @@ /* termios baud platform specific definitions. Linux/generic version. - Copyright (C) 2019-2025 Free Software Foundation, Inc. + Copyright (C) 2019-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/termios-struct.h b/lib/libc/include/generic-glibc/bits/termios-struct.h index 40d20e442d494026e28ffc6813a69f033beaddcf..1c07dc7525657005e0ea16f651a36bb74c24c695 100644 --- a/lib/libc/include/generic-glibc/bits/termios-struct.h +++ b/lib/libc/include/generic-glibc/bits/termios-struct.h @@ -1,5 +1,5 @@ /* struct termios definition. Linux/generic version. - Copyright (C) 2019-2025 Free Software Foundation, Inc. + Copyright (C) 2019-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/termios-tcflow.h b/lib/libc/include/generic-glibc/bits/termios-tcflow.h index 7c78b3c7c58b6f75db765390e9aa8f16171af744..dceb13d5b181217bffdb2d94622385a748d81116 100644 --- a/lib/libc/include/generic-glibc/bits/termios-tcflow.h +++ b/lib/libc/include/generic-glibc/bits/termios-tcflow.h @@ -1,5 +1,5 @@ /* termios tcflag symbolic constant definitions. Linux/generic version. - Copyright (C) 2019-2025 Free Software Foundation, Inc. + Copyright (C) 2019-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/termios.h b/lib/libc/include/generic-glibc/bits/termios.h index 4af6204a30b0b073221afe8f85ee9209881a0e15..38a9e0f173dc05c2303381175cfa9468124a83ec 100644 --- a/lib/libc/include/generic-glibc/bits/termios.h +++ b/lib/libc/include/generic-glibc/bits/termios.h @@ -1,5 +1,5 @@ /* termios type and macro definitions. Linux version. - Copyright (C) 1993-2025 Free Software Foundation, Inc. + Copyright (C) 1993-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/thread-shared-types.h b/lib/libc/include/generic-glibc/bits/thread-shared-types.h index 119dd8ae559201a8491824dba0e025d9fbc161cd..52c533aa70e61d57a1098d0c33696425b96a330f 100644 --- a/lib/libc/include/generic-glibc/bits/thread-shared-types.h +++ b/lib/libc/include/generic-glibc/bits/thread-shared-types.h @@ -1,5 +1,5 @@ /* Common threading primitives definitions for both POSIX and C11. - Copyright (C) 2017-2025 Free Software Foundation, Inc. + Copyright (C) 2017-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or @@ -75,7 +75,7 @@ typedef struct __pthread_internal_slist #include -/* Arch-sepecific read-write lock definitions. A generic implementation is +/* Arch-specific read-write lock definitions. A generic implementation is provided by struct_rwlock.h. If required, an architecture can override it by defining: diff --git a/lib/libc/include/generic-glibc/bits/time.h b/lib/libc/include/generic-glibc/bits/time.h index 433b755e3be6f5d415d679480aed69b06913fae6..99bddef7f1e0128e3c2efa36b4020534484941a7 100644 --- a/lib/libc/include/generic-glibc/bits/time.h +++ b/lib/libc/include/generic-glibc/bits/time.h @@ -1,5 +1,5 @@ /* System-dependent timing definitions. Linux version. - Copyright (C) 1996-2025 Free Software Foundation, Inc. + Copyright (C) 1996-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/time64.h b/lib/libc/include/generic-glibc/bits/time64.h index cc81e2c3189013717acf4610780b5d551901952e..df1dc0b475d690aabe344651b1254433b1e1c08f 100644 --- a/lib/libc/include/generic-glibc/bits/time64.h +++ b/lib/libc/include/generic-glibc/bits/time64.h @@ -1,5 +1,5 @@ /* bits/time64.h -- underlying types for __time64_t. Generic version. - Copyright (C) 2018-2025 Free Software Foundation, Inc. + Copyright (C) 2018-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/timerfd.h b/lib/libc/include/generic-glibc/bits/timerfd.h index b73c8793b80b2c5541a93d16507f051da05ac334..540e035c925509a38c681a3ae74e233e4c7c5e1b 100644 --- a/lib/libc/include/generic-glibc/bits/timerfd.h +++ b/lib/libc/include/generic-glibc/bits/timerfd.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2008-2025 Free Software Foundation, Inc. +/* Copyright (C) 2008-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/timesize.h b/lib/libc/include/generic-glibc/bits/timesize.h index 5e73e22f26276a7b4f9ea65a9cb951f129277b4b..114eea77753240de1509241efac873c28a59e9d0 100644 --- a/lib/libc/include/generic-glibc/bits/timesize.h +++ b/lib/libc/include/generic-glibc/bits/timesize.h @@ -1,5 +1,5 @@ /* Bit size of the time_t type at glibc build time, Linux/MIPS. - Copyright (C) 2021-2025 Free Software Foundation, Inc. + Copyright (C) 2021-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/timex.h b/lib/libc/include/generic-glibc/bits/timex.h index 1d02f8a8b893c67638b020aaab9509d9aceb32c8..35559154b131ceabff9db499d7da27fa11c535ea 100644 --- a/lib/libc/include/generic-glibc/bits/timex.h +++ b/lib/libc/include/generic-glibc/bits/timex.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1995-2025 Free Software Foundation, Inc. +/* Copyright (C) 1995-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/types.h b/lib/libc/include/generic-glibc/bits/types.h index 795731e82cc64ebb77b88881b4414bce109caa5b..39dabbe8194a2982fba0339d428b2e3390a406ba 100644 --- a/lib/libc/include/generic-glibc/bits/types.h +++ b/lib/libc/include/generic-glibc/bits/types.h @@ -1,5 +1,5 @@ /* bits/types.h -- definitions of __*_t types underlying *_t types. - Copyright (C) 2002-2025 Free Software Foundation, Inc. + Copyright (C) 2002-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/types/__locale_t.h b/lib/libc/include/generic-glibc/bits/types/__locale_t.h index 4789e2178d415a7bb10d5470bbba80559cb7c626..796d4c88a7e57e95fb47fd2a431e331262710aaf 100644 --- a/lib/libc/include/generic-glibc/bits/types/__locale_t.h +++ b/lib/libc/include/generic-glibc/bits/types/__locale_t.h @@ -1,5 +1,5 @@ /* Definition of struct __locale_struct and __locale_t. - Copyright (C) 1997-2025 Free Software Foundation, Inc. + Copyright (C) 1997-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/types/__sigval_t.h b/lib/libc/include/generic-glibc/bits/types/__sigval_t.h index 59ea360f8ed2cb45f11fc7984a4e75a7425f991b..545b2f95f51a73b11a6164993d26d2c6f0408651 100644 --- a/lib/libc/include/generic-glibc/bits/types/__sigval_t.h +++ b/lib/libc/include/generic-glibc/bits/types/__sigval_t.h @@ -1,5 +1,5 @@ /* Define __sigval_t. - Copyright (C) 1997-2025 Free Software Foundation, Inc. + Copyright (C) 1997-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/types/cookie_io_functions_t.h b/lib/libc/include/generic-glibc/bits/types/cookie_io_functions_t.h index 41f17720467f8424d4c3db51ac0297228311ca41..a23b774821a1f84fe7f5afd952a8475e65601975 100644 --- a/lib/libc/include/generic-glibc/bits/types/cookie_io_functions_t.h +++ b/lib/libc/include/generic-glibc/bits/types/cookie_io_functions_t.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1991-2025 Free Software Foundation, Inc. +/* Copyright (C) 1991-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/types/error_t.h b/lib/libc/include/generic-glibc/bits/types/error_t.h index 89be14ed26d0cf61a5ea01c5bf067b802fbeb016..b0486fbc419b6dd6ed8662e0b81858c9974a9acd 100644 --- a/lib/libc/include/generic-glibc/bits/types/error_t.h +++ b/lib/libc/include/generic-glibc/bits/types/error_t.h @@ -1,5 +1,5 @@ /* Define error_t. - Copyright (C) 1991-2025 Free Software Foundation, Inc. + Copyright (C) 1991-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/types/locale_t.h b/lib/libc/include/generic-glibc/bits/types/locale_t.h index 3c64f46684a35129d960a01861be968adaa46725..5d5f2af6b4c8534fc9dea7ee3f03e8c663fe0270 100644 --- a/lib/libc/include/generic-glibc/bits/types/locale_t.h +++ b/lib/libc/include/generic-glibc/bits/types/locale_t.h @@ -1,5 +1,5 @@ /* Definition of locale_t. - Copyright (C) 2017-2025 Free Software Foundation, Inc. + Copyright (C) 2017-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/loongarch-linux-gnu/bits/shmlba.h b/lib/libc/include/generic-glibc/bits/types/once_flag.h similarity index 72% rename from lib/libc/include/loongarch-linux-gnu/bits/shmlba.h rename to lib/libc/include/generic-glibc/bits/types/once_flag.h index 202e6b651456fc8ef0a9798d2c109772e108cb8e..becbdc248c19fdc58d50fc129743e5154958ba20 100644 --- a/lib/libc/include/loongarch-linux-gnu/bits/shmlba.h +++ b/lib/libc/include/generic-glibc/bits/types/once_flag.h @@ -1,5 +1,5 @@ -/* Define SHMLBA. LoongArch version. - Copyright (C) 2023-2025 Free Software Foundation, Inc. +/* Define once_flag and ONCE_FLAG_INIT. + Copyright (C) 2018-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or @@ -16,9 +16,12 @@ License along with the GNU C Library; if not, see . */ -#ifndef _SYS_SHM_H -# error "Never use directly; include instead." -#endif +#ifndef __once_flag_defined +#define __once_flag_defined 1 -/* Segment low boundary address multiple. */ -#define SHMLBA 0x10000 \ No newline at end of file +#include + +typedef __once_flag once_flag; +#define ONCE_FLAG_INIT __ONCE_FLAG_INIT + +#endif \ No newline at end of file diff --git a/lib/libc/include/generic-glibc/bits/types/stack_t.h b/lib/libc/include/generic-glibc/bits/types/stack_t.h index 64abc5138b77399316f46d1b5c6e4f02af491199..40822af706d3738ea824774faf0d6f022f7bf128 100644 --- a/lib/libc/include/generic-glibc/bits/types/stack_t.h +++ b/lib/libc/include/generic-glibc/bits/types/stack_t.h @@ -1,5 +1,5 @@ /* Define stack_t. Linux version. - Copyright (C) 1998-2025 Free Software Foundation, Inc. + Copyright (C) 1998-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/types/struct_FILE.h b/lib/libc/include/generic-glibc/bits/types/struct_FILE.h index b02bb0c15400c5e8d177cbe0ab006b3c4427e4b6..03f67c5a692741a59c64d21f313142ba5c9a1f81 100644 --- a/lib/libc/include/generic-glibc/bits/types/struct_FILE.h +++ b/lib/libc/include/generic-glibc/bits/types/struct_FILE.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1991-2025 Free Software Foundation, Inc. +/* Copyright (C) 1991-2026 Free Software Foundation, Inc. Copyright The GNU Toolchain Authors. This file is part of the GNU C Library. diff --git a/lib/libc/include/generic-glibc/bits/types/struct___jmp_buf_tag.h b/lib/libc/include/generic-glibc/bits/types/struct___jmp_buf_tag.h index ade4721247a1d2b3471e7da076aba85010477a16..530da903032e72ec8fda6a6aefa2bd1a9f9b5f2a 100644 --- a/lib/libc/include/generic-glibc/bits/types/struct___jmp_buf_tag.h +++ b/lib/libc/include/generic-glibc/bits/types/struct___jmp_buf_tag.h @@ -1,5 +1,5 @@ /* Define struct __jmp_buf_tag. - Copyright (C) 1991-2025 Free Software Foundation, Inc. + Copyright (C) 1991-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/types/struct_iovec.h b/lib/libc/include/generic-glibc/bits/types/struct_iovec.h index 28b98cdf1a5c9bf1c558d753520993f7eda1820b..3c8b0c36719efed9c30d8d3fb8e498f4645ef655 100644 --- a/lib/libc/include/generic-glibc/bits/types/struct_iovec.h +++ b/lib/libc/include/generic-glibc/bits/types/struct_iovec.h @@ -1,5 +1,5 @@ /* Define struct iovec. - Copyright (C) 1996-2025 Free Software Foundation, Inc. + Copyright (C) 1996-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/types/struct_msqid64_ds.h b/lib/libc/include/generic-glibc/bits/types/struct_msqid64_ds.h index 0f4d5baeab34e2b38fc254f85d0a94a19ede8267..c1dce5edfdbe9b73f4e064a8079a8c645faa4f0e 100644 --- a/lib/libc/include/generic-glibc/bits/types/struct_msqid64_ds.h +++ b/lib/libc/include/generic-glibc/bits/types/struct_msqid64_ds.h @@ -1,5 +1,5 @@ /* Generic implementation of the SysV message struct msqid64_ds. - Copyright (C) 2020-2025 Free Software Foundation, Inc. + Copyright (C) 2020-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/types/struct_msqid64_ds_helper.h b/lib/libc/include/generic-glibc/bits/types/struct_msqid64_ds_helper.h index 2971aa7bd4174eba450dacf3f06220bce5deb8f3..3359bec9bdf40826d289a337d3953a8594f77ea6 100644 --- a/lib/libc/include/generic-glibc/bits/types/struct_msqid64_ds_helper.h +++ b/lib/libc/include/generic-glibc/bits/types/struct_msqid64_ds_helper.h @@ -1,5 +1,5 @@ /* Common definitions for struct msqid_ds with 64-bit time. - Copyright (C) 2021-2025 Free Software Foundation, Inc. + Copyright (C) 2021-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/types/struct_msqid_ds.h b/lib/libc/include/generic-glibc/bits/types/struct_msqid_ds.h index d619a22c2b0191ff9951ad3c4b4b83f4f4eb49e5..95c93977ffd6d589360d3164d810e8e9e9dbe025 100644 --- a/lib/libc/include/generic-glibc/bits/types/struct_msqid_ds.h +++ b/lib/libc/include/generic-glibc/bits/types/struct_msqid_ds.h @@ -1,5 +1,5 @@ /* Generic implementation of the SysV message struct msqid_ds. - Copyright (C) 2020-2025 Free Software Foundation, Inc. + Copyright (C) 2020-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/types/struct_rusage.h b/lib/libc/include/generic-glibc/bits/types/struct_rusage.h index 75a03e2745b86bd197087b29eb4bfffb19793b78..f095b71ef80ed03c95318e24a48fcb38a32584a4 100644 --- a/lib/libc/include/generic-glibc/bits/types/struct_rusage.h +++ b/lib/libc/include/generic-glibc/bits/types/struct_rusage.h @@ -1,5 +1,5 @@ /* Define struct rusage. - Copyright (C) 1994-2025 Free Software Foundation, Inc. + Copyright (C) 1994-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/types/struct_sched_param.h b/lib/libc/include/generic-glibc/bits/types/struct_sched_param.h index 8dc14a02b1ca727f52a115013a306942914b92da..301bdf71342e2d9f70eee9875e3b14f4d8a4d084 100644 --- a/lib/libc/include/generic-glibc/bits/types/struct_sched_param.h +++ b/lib/libc/include/generic-glibc/bits/types/struct_sched_param.h @@ -1,5 +1,5 @@ /* Sched parameter structure. Generic version. - Copyright (C) 1996-2025 Free Software Foundation, Inc. + Copyright (C) 1996-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/types/struct_semid64_ds.h b/lib/libc/include/generic-glibc/bits/types/struct_semid64_ds.h index c2f60a45b95213055b8e6c4d780f189cec88407c..ddeed6a9fb9099b69530cc1ab986f7947ebd80a2 100644 --- a/lib/libc/include/generic-glibc/bits/types/struct_semid64_ds.h +++ b/lib/libc/include/generic-glibc/bits/types/struct_semid64_ds.h @@ -1,5 +1,5 @@ /* Generic implementation of the semaphore struct semid64_ds. - Copyright (C) 2020-2025 Free Software Foundation, Inc. + Copyright (C) 2020-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/types/struct_semid64_ds_helper.h b/lib/libc/include/generic-glibc/bits/types/struct_semid64_ds_helper.h index dd5ae895b0a276900b0f8e703619f8ffc599925e..bef6a93f1e234db4deeb64ab625163b0a280267f 100644 --- a/lib/libc/include/generic-glibc/bits/types/struct_semid64_ds_helper.h +++ b/lib/libc/include/generic-glibc/bits/types/struct_semid64_ds_helper.h @@ -1,5 +1,5 @@ /* Common definitions for struct semid_ds with 64-bit time. - Copyright (C) 2020-2025 Free Software Foundation, Inc. + Copyright (C) 2020-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/types/struct_semid_ds.h b/lib/libc/include/generic-glibc/bits/types/struct_semid_ds.h index 842edeeaed41fa6c81ad65b5d75a2fa3553bf4bd..fbafc46cd884c7eadd2daee850e23eeb20c48594 100644 --- a/lib/libc/include/generic-glibc/bits/types/struct_semid_ds.h +++ b/lib/libc/include/generic-glibc/bits/types/struct_semid_ds.h @@ -1,5 +1,5 @@ /* Generic implementation of the semaphore struct semid_ds. - Copyright (C) 1995-2025 Free Software Foundation, Inc. + Copyright (C) 1995-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/types/struct_shmid64_ds.h b/lib/libc/include/generic-glibc/bits/types/struct_shmid64_ds.h index 052eacadcb4fc30df74b5b308ce400af4a3eef61..c4c255c5704ed64191ca0878e3f990b196ce2542 100644 --- a/lib/libc/include/generic-glibc/bits/types/struct_shmid64_ds.h +++ b/lib/libc/include/generic-glibc/bits/types/struct_shmid64_ds.h @@ -1,5 +1,5 @@ /* Generic implementation of the shared memory struct shmid_ds. - Copyright (C) 2020-2025 Free Software Foundation, Inc. + Copyright (C) 2020-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/types/struct_shmid64_ds_helper.h b/lib/libc/include/generic-glibc/bits/types/struct_shmid64_ds_helper.h index d2fb8a3e697f3a0c09a790d10f0c7a0c19b0a3c5..9509db561b2f07666be3561100b7a3e9afd667b6 100644 --- a/lib/libc/include/generic-glibc/bits/types/struct_shmid64_ds_helper.h +++ b/lib/libc/include/generic-glibc/bits/types/struct_shmid64_ds_helper.h @@ -1,5 +1,5 @@ /* Common definitions for struct semid_ds with 64-bit time. - Copyright (C) 2020-2025 Free Software Foundation, Inc. + Copyright (C) 2020-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/types/struct_shmid_ds.h b/lib/libc/include/generic-glibc/bits/types/struct_shmid_ds.h index 9ec2974b11bcca00c315d38bde186e18b9f401e6..ecb80203a10f1afafc10635dddb832952b908886 100644 --- a/lib/libc/include/generic-glibc/bits/types/struct_shmid_ds.h +++ b/lib/libc/include/generic-glibc/bits/types/struct_shmid_ds.h @@ -1,5 +1,5 @@ /* Generic implementation of the shared memory struct shmid_ds. - Copyright (C) 2020-2025 Free Software Foundation, Inc. + Copyright (C) 2020-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/types/struct_sigstack.h b/lib/libc/include/generic-glibc/bits/types/struct_sigstack.h index c24f04a7875868b2ea8aab7007a5161e902b8962..c43339e5e0f14e1d5e471b78a3632df5c6dc90f3 100644 --- a/lib/libc/include/generic-glibc/bits/types/struct_sigstack.h +++ b/lib/libc/include/generic-glibc/bits/types/struct_sigstack.h @@ -1,5 +1,5 @@ /* Define struct sigstack. - Copyright (C) 1998-2025 Free Software Foundation, Inc. + Copyright (C) 1998-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/types/struct_statx.h b/lib/libc/include/generic-glibc/bits/types/struct_statx.h index 280873cd4867b7d5f20dee632a964adf62e668b0..6f8fa4dacee976722ecbf70bcf78b21be7c7091d 100644 --- a/lib/libc/include/generic-glibc/bits/types/struct_statx.h +++ b/lib/libc/include/generic-glibc/bits/types/struct_statx.h @@ -1,5 +1,5 @@ /* Definition of the generic version of struct statx. - Copyright (C) 2018-2025 Free Software Foundation, Inc. + Copyright (C) 2018-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or @@ -49,7 +49,17 @@ struct statx __uint32_t stx_rdev_minor; __uint32_t stx_dev_major; __uint32_t stx_dev_minor; - __uint64_t __statx_pad2[14]; + __uint64_t stx_mnt_id; + __uint32_t stx_dio_mem_align; + __uint32_t stx_dio_offset_align; + __uint64_t stx_subvol; + __uint32_t stx_atomic_write_unit_min; + __uint32_t stx_atomic_write_unit_max; + __uint32_t stx_atomic_write_segments_max; + __uint32_t stx_dio_read_offset_align; + __uint32_t stx_atomic_write_unit_max_opt; + __uint32_t __statx_pad2; + __uint64_t __statx_pad3[8]; }; #endif /* __statx_defined */ \ No newline at end of file diff --git a/lib/libc/include/generic-glibc/bits/types/struct_statx_timestamp.h b/lib/libc/include/generic-glibc/bits/types/struct_statx_timestamp.h index 7720e9a5c09fd88d0423273ae3cdc69f8d519731..3861a3b912b73a55a58a35ac5a2ec68b17e9791d 100644 --- a/lib/libc/include/generic-glibc/bits/types/struct_statx_timestamp.h +++ b/lib/libc/include/generic-glibc/bits/types/struct_statx_timestamp.h @@ -1,5 +1,5 @@ /* Definition of the generic version of struct statx_timestamp. - Copyright (C) 2018-2025 Free Software Foundation, Inc. + Copyright (C) 2018-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/typesizes.h b/lib/libc/include/generic-glibc/bits/typesizes.h index 13c7a28ac382da92a8508c0a93deb49101f5010e..6921428cfe8acbd836e6b71c3b82a9241c022084 100644 --- a/lib/libc/include/generic-glibc/bits/typesizes.h +++ b/lib/libc/include/generic-glibc/bits/typesizes.h @@ -1,5 +1,5 @@ /* bits/typesizes.h -- underlying types for *_t. For the generic Linux ABI. - Copyright (C) 2011-2025 Free Software Foundation, Inc. + Copyright (C) 2011-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/uintn-identity.h b/lib/libc/include/generic-glibc/bits/uintn-identity.h index b60841a5134bbfea5c004b29637c0bf2457497df..658b24e471bb01f425fd312a250d1d391b8298a6 100644 --- a/lib/libc/include/generic-glibc/bits/uintn-identity.h +++ b/lib/libc/include/generic-glibc/bits/uintn-identity.h @@ -1,5 +1,5 @@ /* Inline functions to return unsigned integer values unchanged. - Copyright (C) 2017-2025 Free Software Foundation, Inc. + Copyright (C) 2017-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/uio-ext.h b/lib/libc/include/generic-glibc/bits/uio-ext.h index 2d27c7d94bbeb7b15c8d8d96ec24628e3d7aecf7..e65cf77f433fcc0e7b4ce718f10c093fcc1d2097 100644 --- a/lib/libc/include/generic-glibc/bits/uio-ext.h +++ b/lib/libc/include/generic-glibc/bits/uio-ext.h @@ -1,5 +1,5 @@ /* Operating system-specific extensions to sys/uio.h - Linux version. - Copyright (C) 1996-2025 Free Software Foundation, Inc. + Copyright (C) 1996-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or @@ -50,6 +50,7 @@ extern ssize_t process_vm_writev (pid_t __pid, const struct iovec *__lvec, #define RWF_NOAPPEND 0x00000020 /* per-IO negation of O_APPEND */ #define RWF_ATOMIC 0x00000040 /* Write is to be issued with torn-write prevention. */ +#define RWF_DONTCACHE 0x00000080 /* Uncached buffered IO. */ __END_DECLS diff --git a/lib/libc/include/generic-glibc/bits/uio_lim.h b/lib/libc/include/generic-glibc/bits/uio_lim.h index 482173177475ad58e28af775df677529805016c2..af26587e6c09b34e6e0b472663004f47e5a29603 100644 --- a/lib/libc/include/generic-glibc/bits/uio_lim.h +++ b/lib/libc/include/generic-glibc/bits/uio_lim.h @@ -1,5 +1,5 @@ /* Implementation limits related to sys/uio.h - Linux version. - Copyright (C) 2017-2025 Free Software Foundation, Inc. + Copyright (C) 2017-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/unistd-decl.h b/lib/libc/include/generic-glibc/bits/unistd-decl.h index fdb6905c0c29a9b45ef73f5bb032e2f607002a6c..eba4d37142fcdc4349d389de8f28c6f5daec8dc2 100644 --- a/lib/libc/include/generic-glibc/bits/unistd-decl.h +++ b/lib/libc/include/generic-glibc/bits/unistd-decl.h @@ -1,5 +1,5 @@ /* Checking routines for unistd functions. Declaration only. - Copyright (C) 2023-2025 Free Software Foundation, Inc. + Copyright (C) 2023-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/unistd.h b/lib/libc/include/generic-glibc/bits/unistd.h index 4af036619d84eb74df3b53967409820ebb200c40..b476e58a43eae931da867dc9256f3f478e540fa1 100644 --- a/lib/libc/include/generic-glibc/bits/unistd.h +++ b/lib/libc/include/generic-glibc/bits/unistd.h @@ -1,5 +1,5 @@ /* Checking macros for unistd functions. - Copyright (C) 2005-2025 Free Software Foundation, Inc. + Copyright (C) 2005-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/unistd_ext.h b/lib/libc/include/generic-glibc/bits/unistd_ext.h index 691a57e30e185e8525fa7b1095402094efc2b1c9..04099e5c58577ddb009debda5f9b3c5bb2fa4879 100644 --- a/lib/libc/include/generic-glibc/bits/unistd_ext.h +++ b/lib/libc/include/generic-glibc/bits/unistd_ext.h @@ -1,5 +1,5 @@ /* System-specific extensions of , Linux version. - Copyright (C) 2019-2025 Free Software Foundation, Inc. + Copyright (C) 2019-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/utmp.h b/lib/libc/include/generic-glibc/bits/utmp.h index e945937f32de6b756a3673f86031dd873d7baa6d..f655ae6d78b036027ce7eda9a7e4c72b655be4da 100644 --- a/lib/libc/include/generic-glibc/bits/utmp.h +++ b/lib/libc/include/generic-glibc/bits/utmp.h @@ -1,5 +1,5 @@ /* The `struct utmp' type, describing entries in the utmp file. - Copyright (C) 1993-2025 Free Software Foundation, Inc. + Copyright (C) 1993-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/utmpx.h b/lib/libc/include/generic-glibc/bits/utmpx.h index ff77f8c4edd7e5c0ebc1c129ad3f13e262b9c90f..d8eed1815693a786843b3093e991ce85b879301b 100644 --- a/lib/libc/include/generic-glibc/bits/utmpx.h +++ b/lib/libc/include/generic-glibc/bits/utmpx.h @@ -1,5 +1,5 @@ /* Structures and definitions for the user accounting database. GNU version. - Copyright (C) 1997-2025 Free Software Foundation, Inc. + Copyright (C) 1997-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/utsname.h b/lib/libc/include/generic-glibc/bits/utsname.h index 625d7e1be6198596bdb27da093db3d4563785df7..0a2e52a78481464deae0d36434842a78b187d386 100644 --- a/lib/libc/include/generic-glibc/bits/utsname.h +++ b/lib/libc/include/generic-glibc/bits/utsname.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1995-2025 Free Software Foundation, Inc. +/* Copyright (C) 1995-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/waitflags.h b/lib/libc/include/generic-glibc/bits/waitflags.h index 07e69e60041ad43058bb5ab4eee09f9889c4c5a5..e3931616763b6a53755185b45532aab3ff5eaec0 100644 --- a/lib/libc/include/generic-glibc/bits/waitflags.h +++ b/lib/libc/include/generic-glibc/bits/waitflags.h @@ -1,5 +1,5 @@ /* Definitions of flag bits for `waitpid' et al. - Copyright (C) 1992-2025 Free Software Foundation, Inc. + Copyright (C) 1992-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/waitstatus.h b/lib/libc/include/generic-glibc/bits/waitstatus.h index bbc22daaf75cbcd2e8f27153891f61784e9a189e..a6cd056fff97ed26477424b22d268e4724dd6f40 100644 --- a/lib/libc/include/generic-glibc/bits/waitstatus.h +++ b/lib/libc/include/generic-glibc/bits/waitstatus.h @@ -1,5 +1,5 @@ /* Definitions of status bits for `wait' et al. - Copyright (C) 1992-2025 Free Software Foundation, Inc. + Copyright (C) 1992-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/wchar-ldbl.h b/lib/libc/include/generic-glibc/bits/wchar-ldbl.h index d13bf5f5b762b84ed93a34f13d576d48f4f56ef1..4814b417e5a92c9306c51178f22b3b745523c3bc 100644 --- a/lib/libc/include/generic-glibc/bits/wchar-ldbl.h +++ b/lib/libc/include/generic-glibc/bits/wchar-ldbl.h @@ -1,5 +1,5 @@ /* -mlong-double-64 compatibility mode for functions. - Copyright (C) 2006-2025 Free Software Foundation, Inc. + Copyright (C) 2006-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/wchar.h b/lib/libc/include/generic-glibc/bits/wchar.h index bba9a4687a03e665edf24e0500b935afd73bbd77..936d2d555f857c6652f5d9e2982e7ae45dc51677 100644 --- a/lib/libc/include/generic-glibc/bits/wchar.h +++ b/lib/libc/include/generic-glibc/bits/wchar.h @@ -1,5 +1,5 @@ /* wchar_t type related definitions. - Copyright (C) 2000-2025 Free Software Foundation, Inc. + Copyright (C) 2000-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/wchar2-decl.h b/lib/libc/include/generic-glibc/bits/wchar2-decl.h index 2cf2f7baae5e148d48cd6b8224c2c40dfe580340..ce42c43864d9a77dd64f55ec52f052246a7abde0 100644 --- a/lib/libc/include/generic-glibc/bits/wchar2-decl.h +++ b/lib/libc/include/generic-glibc/bits/wchar2-decl.h @@ -1,5 +1,5 @@ /* Checking macros for wchar functions. Declarations only. - Copyright (C) 2004-2025 Free Software Foundation, Inc. + Copyright (C) 2004-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/wchar2.h b/lib/libc/include/generic-glibc/bits/wchar2.h index 0bf64438d15f54b112c2af45d8c6c8a755f278c1..f61b3999b88993c4af48a43c96b2fc93fb26c0d3 100644 --- a/lib/libc/include/generic-glibc/bits/wchar2.h +++ b/lib/libc/include/generic-glibc/bits/wchar2.h @@ -1,5 +1,5 @@ /* Checking macros for wchar functions. - Copyright (C) 2005-2025 Free Software Foundation, Inc. + Copyright (C) 2005-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/wctype-wchar.h b/lib/libc/include/generic-glibc/bits/wctype-wchar.h index 3a73e92364cb19ef02f7a2a6d4d6b124aa55804c..00947a3bae561496b8ac13f96ac5eba1cf989e85 100644 --- a/lib/libc/include/generic-glibc/bits/wctype-wchar.h +++ b/lib/libc/include/generic-glibc/bits/wctype-wchar.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1996-2025 Free Software Foundation, Inc. +/* Copyright (C) 1996-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/wordsize.h b/lib/libc/include/generic-glibc/bits/wordsize.h index 487102dc9c5379e67fb093f388df3987cf894273..209ee56ebe7bc1a844173125e4a57ac9d071226b 100644 --- a/lib/libc/include/generic-glibc/bits/wordsize.h +++ b/lib/libc/include/generic-glibc/bits/wordsize.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2002-2025 Free Software Foundation, Inc. +/* Copyright (C) 2002-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/bits/xopen_lim.h b/lib/libc/include/generic-glibc/bits/xopen_lim.h index a6c7c2ca675a702a1b7b7ffdbf6cc2e35c003deb..658583a0bcc0a3b8f736ae8ae4098055d373bbbf 100644 --- a/lib/libc/include/generic-glibc/bits/xopen_lim.h +++ b/lib/libc/include/generic-glibc/bits/xopen_lim.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1996-2025 Free Software Foundation, Inc. +/* Copyright (C) 1996-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/byteswap.h b/lib/libc/include/generic-glibc/byteswap.h index 08f0786799d686176d2b19e541fbf40e643b9fcf..149dcfbc66a38c721096082f2ad1da53f3df958d 100644 --- a/lib/libc/include/generic-glibc/byteswap.h +++ b/lib/libc/include/generic-glibc/byteswap.h @@ -1,5 +1,5 @@ /* Swap byte order for 16, 32 and 64 bit values - Copyright (C) 1997-2025 Free Software Foundation, Inc. + Copyright (C) 1997-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/complex.h b/lib/libc/include/generic-glibc/complex.h index 294bfb211f257f16eb4b55eca11d75d739f19d4b..87938c9e0b7438e2cbf9cfb640f520e36c1fbdae 100644 --- a/lib/libc/include/generic-glibc/complex.h +++ b/lib/libc/include/generic-glibc/complex.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1997-2025 Free Software Foundation, Inc. +/* Copyright (C) 1997-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or @@ -33,6 +33,10 @@ __BEGIN_DECLS +#if __GLIBC_USE (ISOC23) +# define __STDC_VERSION_COMPLEX_H__ 202311L +#endif + /* We might need to add support for more compilers here. But since ISO C99 is out hopefully all maintained compilers will soon provide the data types `float complex' and `double complex'. */ diff --git a/lib/libc/include/generic-glibc/cpio.h b/lib/libc/include/generic-glibc/cpio.h index 7671ebd3c1d8115d4942a03017219e29519b5d53..6ad236eaf2d59c67d982cf2fe5a97cef3e89b812 100644 --- a/lib/libc/include/generic-glibc/cpio.h +++ b/lib/libc/include/generic-glibc/cpio.h @@ -1,6 +1,6 @@ /* Extended cpio format from POSIX.1. This file is part of the GNU C Library. - Copyright (C) 1992-2025 Free Software Foundation, Inc. + Copyright (C) 1992-2026 Free Software Foundation, Inc. NOTE: The canonical source of this file is maintained with the GNU cpio. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/ctype.h b/lib/libc/include/generic-glibc/ctype.h index 61a1f174d0b78f4de31a4cc118d718a9c66e6173..53f76a935ad67c53942ee28300419ccebfac16d2 100644 --- a/lib/libc/include/generic-glibc/ctype.h +++ b/lib/libc/include/generic-glibc/ctype.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1991-2025 Free Software Foundation, Inc. +/* Copyright (C) 1991-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/dirent.h b/lib/libc/include/generic-glibc/dirent.h index 39457fdd801a3e843c9773092994de78f70969f9..2e3f38cc6fabaef45f0c910493633f83ecdfd4de 100644 --- a/lib/libc/include/generic-glibc/dirent.h +++ b/lib/libc/include/generic-glibc/dirent.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1991-2025 Free Software Foundation, Inc. +/* Copyright (C) 1991-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/dlfcn.h b/lib/libc/include/generic-glibc/dlfcn.h index fe5059b8d53fa8b6efecdf8a3f6d34c9b99c3134..f12e3d68cd4e31c856a23e1c63bd5a2d6b597146 100644 --- a/lib/libc/include/generic-glibc/dlfcn.h +++ b/lib/libc/include/generic-glibc/dlfcn.h @@ -1,5 +1,5 @@ /* User functions for run-time dynamic loading. - Copyright (C) 1995-2025 Free Software Foundation, Inc. + Copyright (C) 1995-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/elf.h b/lib/libc/include/generic-glibc/elf.h index 11dc6453b50e5ac3fd639598dfb28ecb9253ab8f..a45dea41210184770afb26e837fb86f03d226b9a 100644 --- a/lib/libc/include/generic-glibc/elf.h +++ b/lib/libc/include/generic-glibc/elf.h @@ -1,5 +1,5 @@ /* This file defines standard ELF types, structures, and macros. - Copyright (C) 1995-2025 Free Software Foundation, Inc. + Copyright (C) 1995-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or @@ -924,7 +924,7 @@ typedef struct #define DT_SYMTAB_SHNDX 34 /* Address of SYMTAB_SHNDX section */ #define DT_RELRSZ 35 /* Total size of RELR relative relocations */ #define DT_RELR 36 /* Address of RELR relative relocations */ -#define DT_RELRENT 37 /* Size of one RELR relative relocaction */ +#define DT_RELRENT 37 /* Size of one RELR relative relocation */ #define DT_NUM 38 /* Number used */ #define DT_LOOS 0x6000000d /* Start of OS-specific */ #define DT_HIOS 0x6ffff000 /* End of OS-specific */ diff --git a/lib/libc/include/generic-glibc/endian.h b/lib/libc/include/generic-glibc/endian.h index 2636bb8497e12ed832895cb67b0238aec1591108..e74cf6021064fc63978747cbd9f64946e8069583 100644 --- a/lib/libc/include/generic-glibc/endian.h +++ b/lib/libc/include/generic-glibc/endian.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1992-2025 Free Software Foundation, Inc. +/* Copyright (C) 1992-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/envz.h b/lib/libc/include/generic-glibc/envz.h index 8e415b0be349c4d383e7bee4440b7ce4bc50805e..543b158f43115dc7bde2f67f647acf7378aca8d6 100644 --- a/lib/libc/include/generic-glibc/envz.h +++ b/lib/libc/include/generic-glibc/envz.h @@ -1,5 +1,5 @@ /* Routines for dealing with '\0' separated environment vectors - Copyright (C) 1995-2025 Free Software Foundation, Inc. + Copyright (C) 1995-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/err.h b/lib/libc/include/generic-glibc/err.h index fa7e1f0952bf97d9cc337b4dcd56a85ba3f7fc11..015218379c7d384a354f940d55b6d7b04e311562 100644 --- a/lib/libc/include/generic-glibc/err.h +++ b/lib/libc/include/generic-glibc/err.h @@ -1,5 +1,5 @@ /* 4.4BSD utility functions for error messages. - Copyright (C) 1995-2025 Free Software Foundation, Inc. + Copyright (C) 1995-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/errno.h b/lib/libc/include/generic-glibc/errno.h index eeb7a227e02a7a32ddddff73d2fee5f6146e76ef..920eb12c990e994c7ff93379348004c6f0e579a0 100644 --- a/lib/libc/include/generic-glibc/errno.h +++ b/lib/libc/include/generic-glibc/errno.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1991-2025 Free Software Foundation, Inc. +/* Copyright (C) 1991-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/error.h b/lib/libc/include/generic-glibc/error.h index 47b5752843ea1fc19c837965b1a3c26d99980f20..29191c8255703d84247b97fe0a185433dcc1b7cc 100644 --- a/lib/libc/include/generic-glibc/error.h +++ b/lib/libc/include/generic-glibc/error.h @@ -1,5 +1,5 @@ /* Declaration for error-reporting function - Copyright (C) 1995-2025 Free Software Foundation, Inc. + Copyright (C) 1995-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/execinfo.h b/lib/libc/include/generic-glibc/execinfo.h index 13ee44029426d6e34c732408a219b39b53f65a3e..ce6ba957393bf28abd59c38aef3537bde835de2e 100644 --- a/lib/libc/include/generic-glibc/execinfo.h +++ b/lib/libc/include/generic-glibc/execinfo.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1998-2025 Free Software Foundation, Inc. +/* Copyright (C) 1998-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/fcntl.h b/lib/libc/include/generic-glibc/fcntl.h index 51add4e1464e936bddd102d0cd39af9b9e023812..d2254d0441cd34df5354c13c9f064c42fcf8ba5c 100644 --- a/lib/libc/include/generic-glibc/fcntl.h +++ b/lib/libc/include/generic-glibc/fcntl.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1991-2025 Free Software Foundation, Inc. +/* Copyright (C) 1991-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or @@ -352,4 +352,4 @@ extern int posix_fallocate64 (int __fd, off64_t __offset, off64_t __len); __END_DECLS -#endif /* fcntl.h */ +#endif /* fcntl.h */ \ No newline at end of file diff --git a/lib/libc/include/generic-glibc/features-time64.h b/lib/libc/include/generic-glibc/features-time64.h index 5842168a3cd53bbfbecc5fb698e9362e40478d21..cd6c8d5492125343313e8ace5b4ce376bd92c7c5 100644 --- a/lib/libc/include/generic-glibc/features-time64.h +++ b/lib/libc/include/generic-glibc/features-time64.h @@ -1,5 +1,5 @@ /* Features part to handle 64-bit time_t support. - Copyright (C) 2021-2025 Free Software Foundation, Inc. + Copyright (C) 2021-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/features.h b/lib/libc/include/generic-glibc/features.h index 8a918c60176d685f3d151830a0c173691f88538d..5ef3af3beb712cf7cbb02d380cc68ef6f1b18d4d 100644 --- a/lib/libc/include/generic-glibc/features.h +++ b/lib/libc/include/generic-glibc/features.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1991-2025 Free Software Foundation, Inc. +/* Copyright (C) 1991-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or @@ -44,9 +44,11 @@ if >=199506L, add IEEE Std 1003.1c-1995; if >=200112L, all of IEEE 1003.1-2004 if >=200809L, all of IEEE 1003.1-2008 + if >=202405L, all of IEEE 1003.1-2024 _XOPEN_SOURCE Includes POSIX and XPG things. Set to 500 if Single Unix conformance is wanted, to 600 for the - sixth revision, to 700 for the seventh revision. + sixth revision, to 700 for the seventh revision, + to 800 for the eighth revision. _XOPEN_SOURCE_EXTENDED XPG things and X/Open Unix extensions. _LARGEFILE_SOURCE Some more functions for correct standard I/O. _LARGEFILE64_SOURCE Additional functionality from LFS for large files. @@ -69,7 +71,7 @@ options such as `-std=c99', define __STRICT_ANSI__. If none of these are defined, or if _DEFAULT_SOURCE is defined, the default is to have _POSIX_SOURCE set to one and _POSIX_C_SOURCE set to - 200809L, as well as enabling miscellaneous functions from BSD and + 202405L, as well as enabling miscellaneous functions from BSD and SVID. If more than one of these are defined, they accumulate. For example __STRICT_ANSI__, _POSIX_SOURCE and _POSIX_C_SOURCE together give you ISO C, 1003.1, and 1003.2, but nothing else. @@ -96,6 +98,8 @@ __USE_XOPEN2KXSI Define XPG6 XSI things. __USE_XOPEN2K8 Define XPG7 things. __USE_XOPEN2K8XSI Define XPG7 XSI things. + __USE_XOPEN2K24 Define XPG8 things. + __USE_XOPEN2K24XSI Define XPG8 XSI things. __USE_LARGEFILE Define correct standard I/O things. __USE_LARGEFILE64 Define LFS things with separate names. __USE_FILE_OFFSET64 Define 64bit interface as default. @@ -141,6 +145,8 @@ #undef __USE_XOPEN2KXSI #undef __USE_XOPEN2K8 #undef __USE_XOPEN2K8XSI +#undef __USE_XOPEN2K24 +#undef __USE_XOPEN2K24XSI #undef __USE_LARGEFILE #undef __USE_LARGEFILE64 #undef __USE_FILE_OFFSET64 @@ -162,14 +168,6 @@ # define __KERNEL_STRICT_NAMES #endif -/* Major and minor version number of the GNU C library package. Use - these macros to test for features in specific releases. */ -#define __GLIBC__ 2 -/* Zig patch: we pass `-D__GLIBC_MINOR__=XX` depending on the target. */ - -#define __GLIBC_PREREQ(maj, min) \ - ((__GLIBC__ << 16) + __GLIBC_MINOR__ >= ((maj) << 16) + (min)) - /* Convenience macro to test the version of gcc. Use like this: #if __GNUC_PREREQ (2,8) @@ -231,9 +229,9 @@ # undef _POSIX_SOURCE # define _POSIX_SOURCE 1 # undef _POSIX_C_SOURCE -# define _POSIX_C_SOURCE 200809L +# define _POSIX_C_SOURCE 202405L # undef _XOPEN_SOURCE -# define _XOPEN_SOURCE 700 +# define _XOPEN_SOURCE 800 # undef _XOPEN_SOURCE_EXTENDED # define _XOPEN_SOURCE_EXTENDED 1 # undef _LARGEFILE64_SOURCE @@ -314,7 +312,7 @@ #endif /* If none of the ANSI/POSIX macros are defined, or if _DEFAULT_SOURCE - is defined, use POSIX.1-2008 (or another version depending on + is defined, use POSIX.1-2024 (or another version depending on _XOPEN_SOURCE). */ #ifdef _DEFAULT_SOURCE # if !defined _POSIX_SOURCE && !defined _POSIX_C_SOURCE @@ -323,7 +321,7 @@ # undef _POSIX_SOURCE # define _POSIX_SOURCE 1 # undef _POSIX_C_SOURCE -# define _POSIX_C_SOURCE 200809L +# define _POSIX_C_SOURCE 202405L #endif #if ((!defined __STRICT_ANSI__ \ @@ -336,8 +334,10 @@ # define _POSIX_C_SOURCE 199506L # elif defined _XOPEN_SOURCE && (_XOPEN_SOURCE - 0) < 700 # define _POSIX_C_SOURCE 200112L -# else +# elif defined _XOPEN_SOURCE && (_XOPEN_SOURCE - 0) < 800 # define _POSIX_C_SOURCE 200809L +# else +# define _POSIX_C_SOURCE 202405L # endif # define __USE_POSIX_IMPLICITLY 1 #endif @@ -387,6 +387,10 @@ # define _ATFILE_SOURCE 1 #endif +#if defined _POSIX_C_SOURCE && (_POSIX_C_SOURCE - 0) >= 202405L +# define __USE_XOPEN2K24 1 +#endif + #ifdef _XOPEN_SOURCE # define __USE_XOPEN 1 # if (_XOPEN_SOURCE - 0) >= 500 @@ -398,6 +402,10 @@ # if (_XOPEN_SOURCE - 0) >= 700 # define __USE_XOPEN2K8 1 # define __USE_XOPEN2K8XSI 1 +# if (_XOPEN_SOURCE - 0) >= 800 +# define __USE_XOPEN2K24 1 +# define __USE_XOPEN2K24XSI 1 +# endif # endif # define __USE_XOPEN2K 1 # define __USE_XOPEN2KXSI 1 @@ -534,6 +542,14 @@ #undef __GNU_LIBRARY__ #define __GNU_LIBRARY__ 6 +/* Major and minor version number of the GNU C library package. Use + these macros to test for features in specific releases. */ +#define __GLIBC__ 2 +/* zig patch: we pass `-D__GLIBC_MINOR__=XX` depending on the target. */ + +#define __GLIBC_PREREQ(maj, min) \ + ((__GLIBC__ << 16) + __GLIBC_MINOR__ >= ((maj) << 16) + (min)) + /* This is here only because every header file already includes this one. */ #ifndef __ASSEMBLER__ # ifndef _SYS_CDEFS_H @@ -552,7 +568,7 @@ /* Decide whether we can define 'extern inline' functions in headers. */ #if __GNUC_PREREQ (2, 7) && defined __OPTIMIZE__ \ && !defined __OPTIMIZE_SIZE__ && !defined __NO_INLINE__ \ - && defined __extern_inline + && defined __extern_inline && !(defined __clang__ && defined _LIBC) # define __USE_EXTERN_INLINES 1 #endif diff --git a/lib/libc/include/generic-glibc/fenv.h b/lib/libc/include/generic-glibc/fenv.h index 5c9fc5d2c53eed6f7685dee525e5d0626d9eeb4e..e924477486a6c839add7020ff61888275a312815 100644 --- a/lib/libc/include/generic-glibc/fenv.h +++ b/lib/libc/include/generic-glibc/fenv.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1997-2025 Free Software Foundation, Inc. +/* Copyright (C) 1997-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or @@ -65,6 +65,10 @@ __BEGIN_DECLS +#if __GLIBC_USE (ISOC23) +# define __STDC_VERSION_FENV_H__ 202311L +#endif + /* Floating-point exception handling. */ /* Clear the supported exceptions represented by EXCEPTS. */ diff --git a/lib/libc/include/generic-glibc/finclude/math-vector-fortran.h b/lib/libc/include/generic-glibc/finclude/math-vector-fortran.h index 76faab88b48400b8252f56b2db6d741742b21ebb..dc777d841afe06162f3290493d65d8c7cabfad59 100644 --- a/lib/libc/include/generic-glibc/finclude/math-vector-fortran.h +++ b/lib/libc/include/generic-glibc/finclude/math-vector-fortran.h @@ -1,5 +1,5 @@ ! Platform-specific declarations of SIMD math functions for Fortran. -*- f90 -*- -! Copyright (C) 2019-2025 Free Software Foundation, Inc. +! Copyright (C) 2019-2026 Free Software Foundation, Inc. ! This file is part of the GNU C Library. ! ! The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/fmtmsg.h b/lib/libc/include/generic-glibc/fmtmsg.h index 3d56c4d30e2db0aadd15f476eb964c61d389b35e..8ad0abc1a379e87c548575416d95601ad2d43c7e 100644 --- a/lib/libc/include/generic-glibc/fmtmsg.h +++ b/lib/libc/include/generic-glibc/fmtmsg.h @@ -1,5 +1,5 @@ /* Message display handling. - Copyright (C) 1997-2025 Free Software Foundation, Inc. + Copyright (C) 1997-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/fnmatch.h b/lib/libc/include/generic-glibc/fnmatch.h index b13fe45bfe350f97d45947816eeebcfbaa2e326f..e7909cc1d9b695075a3f2807d4d3e4570de1d406 100644 --- a/lib/libc/include/generic-glibc/fnmatch.h +++ b/lib/libc/include/generic-glibc/fnmatch.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1991-2025 Free Software Foundation, Inc. +/* Copyright (C) 1991-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/fpregdef.h b/lib/libc/include/generic-glibc/fpregdef.h index e4b38635cdc776fc6a123c79256d110bab5f6f31..a0a3d5b7d336c9a783a62e2cfbf4c3b99d921ebe 100644 --- a/lib/libc/include/generic-glibc/fpregdef.h +++ b/lib/libc/include/generic-glibc/fpregdef.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1991-2025 Free Software Foundation, Inc. +/* Copyright (C) 1991-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/fpu_control.h b/lib/libc/include/generic-glibc/fpu_control.h index 5a1c5154714dd46cad3df09d192a8f4a4897248b..5c893891b0bc34e0429e3495060edc076f1870d3 100644 --- a/lib/libc/include/generic-glibc/fpu_control.h +++ b/lib/libc/include/generic-glibc/fpu_control.h @@ -1,5 +1,5 @@ /* FPU control word bits. Mips version. - Copyright (C) 1996-2025 Free Software Foundation, Inc. + Copyright (C) 1996-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/fts.h b/lib/libc/include/generic-glibc/fts.h index 42cf8da8e98cc339a87a805c582855f5d189bd4f..97d0f4a8a299fb9f4e439232fc9e33cd851b605e 100644 --- a/lib/libc/include/generic-glibc/fts.h +++ b/lib/libc/include/generic-glibc/fts.h @@ -1,5 +1,5 @@ /* File tree traversal functions declarations. - Copyright (C) 1994-2025 Free Software Foundation, Inc. + Copyright (C) 1994-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/ftw.h b/lib/libc/include/generic-glibc/ftw.h index 227a13af6f5d42d957862b011e9527e00fbb6b13..9040142e6720dba0f38db5ac27bb5b9173d28f8b 100644 --- a/lib/libc/include/generic-glibc/ftw.h +++ b/lib/libc/include/generic-glibc/ftw.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1992-2025 Free Software Foundation, Inc. +/* Copyright (C) 1992-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/gconv.h b/lib/libc/include/generic-glibc/gconv.h index 676935099f84490b4212d7611c11d08b40bacb95..cb8d891822f99dbab8e84be9214131987ec1e9cb 100644 --- a/lib/libc/include/generic-glibc/gconv.h +++ b/lib/libc/include/generic-glibc/gconv.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1997-2025 Free Software Foundation, Inc. +/* Copyright (C) 1997-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/getopt.h b/lib/libc/include/generic-glibc/getopt.h index 33e20a25586d02187654ac494bc59a8f427e05f2..5d68fc3daf06c894825e873d400fcc0bd415b73b 100644 --- a/lib/libc/include/generic-glibc/getopt.h +++ b/lib/libc/include/generic-glibc/getopt.h @@ -1,5 +1,5 @@ /* Declarations for getopt. - Copyright (C) 1989-2025 Free Software Foundation, Inc. + Copyright (C) 1989-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. Unlike the bulk of the getopt implementation, this file is NOT part of gnulib; gnulib also has a getopt.h but it is different. diff --git a/lib/libc/include/generic-glibc/glob.h b/lib/libc/include/generic-glibc/glob.h index 14340f5fae38311cc1fdb7d72943fe30f037a626..81fb6432529d5718769b466c48ec0d2478e838ae 100644 --- a/lib/libc/include/generic-glibc/glob.h +++ b/lib/libc/include/generic-glibc/glob.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1991-2025 Free Software Foundation, Inc. +/* Copyright (C) 1991-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/gnu-versions.h b/lib/libc/include/generic-glibc/gnu-versions.h index e46400d801dcf2d8ff28d628ad27d409f1f58044..e750f1f0c45eedb05d0be05b17c71a2153e373f8 100644 --- a/lib/libc/include/generic-glibc/gnu-versions.h +++ b/lib/libc/include/generic-glibc/gnu-versions.h @@ -1,5 +1,5 @@ /* Header with interface version macros for library pieces copied elsewhere. - Copyright (C) 1995-2025 Free Software Foundation, Inc. + Copyright (C) 1995-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/gnu/libc-version.h b/lib/libc/include/generic-glibc/gnu/libc-version.h index c432a2c11407790d710e21e4315c75ea33501825..95099e7cb491245c06bdae938b0a2db1d4af30f8 100644 --- a/lib/libc/include/generic-glibc/gnu/libc-version.h +++ b/lib/libc/include/generic-glibc/gnu/libc-version.h @@ -1,5 +1,5 @@ /* Interface to GNU libc specific functions for version information. - Copyright (C) 1998-2025 Free Software Foundation, Inc. + Copyright (C) 1998-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/grp.h b/lib/libc/include/generic-glibc/grp.h index e6367f2252ada31de2a249801458868105eef6c6..adf73f26759c029c3b939b8bb651ce6502bee69b 100644 --- a/lib/libc/include/generic-glibc/grp.h +++ b/lib/libc/include/generic-glibc/grp.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1991-2025 Free Software Foundation, Inc. +/* Copyright (C) 1991-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/gshadow.h b/lib/libc/include/generic-glibc/gshadow.h index 7e73c0d70c5455c20997db60d14319f4240a6712..88f774a491b85bed81f37aef96515f212f526fa0 100644 --- a/lib/libc/include/generic-glibc/gshadow.h +++ b/lib/libc/include/generic-glibc/gshadow.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2009-2025 Free Software Foundation, Inc. +/* Copyright (C) 2009-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/iconv.h b/lib/libc/include/generic-glibc/iconv.h index 3f45af196833f5651bfbf19be7801c098be53531..a4fb9579e5dcc1b9e99fcf746aa66fee581797f5 100644 --- a/lib/libc/include/generic-glibc/iconv.h +++ b/lib/libc/include/generic-glibc/iconv.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1997-2025 Free Software Foundation, Inc. +/* Copyright (C) 1997-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/ieee754.h b/lib/libc/include/generic-glibc/ieee754.h index 7eb1f762ac616ea500ed717af9f62a56426d5eb3..dc8f15e2398f519c2716e8c65c3890d3898aef02 100644 --- a/lib/libc/include/generic-glibc/ieee754.h +++ b/lib/libc/include/generic-glibc/ieee754.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1992-2025 Free Software Foundation, Inc. +/* Copyright (C) 1992-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/ifaddrs.h b/lib/libc/include/generic-glibc/ifaddrs.h index 41bd1048f8f83adc939c332419bb20ae45d2d339..86737f63d3c91e2f2e198b3be428eb71d38cd762 100644 --- a/lib/libc/include/generic-glibc/ifaddrs.h +++ b/lib/libc/include/generic-glibc/ifaddrs.h @@ -1,5 +1,5 @@ /* ifaddrs.h -- declarations for getting network interface addresses - Copyright (C) 2002-2025 Free Software Foundation, Inc. + Copyright (C) 2002-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/inttypes.h b/lib/libc/include/generic-glibc/inttypes.h index be384f223e52627c68d4feb99c08ab0b3e282ed5..345bed9faa1f64a9b116504872c18249f598e3ea 100644 --- a/lib/libc/include/generic-glibc/inttypes.h +++ b/lib/libc/include/generic-glibc/inttypes.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1997-2025 Free Software Foundation, Inc. +/* Copyright (C) 1997-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or @@ -23,6 +23,11 @@ #define _INTTYPES_H 1 #include + +#if __GLIBC_USE (ISOC23) +# define __STDC_VERSION_INTTYPES_H__ 202311L +#endif + /* Get the type definitions. */ #include @@ -51,97 +56,97 @@ typedef wchar_t __gwchar_t; /* Macros for printing format specifiers. */ /* Decimal notation. */ -# define PRId8 "d" -# define PRId16 "d" +# define PRId8 "hhd" +# define PRId16 "hd" # define PRId32 "d" # define PRId64 __PRI64_PREFIX "d" -# define PRIdLEAST8 "d" -# define PRIdLEAST16 "d" +# define PRIdLEAST8 "hhd" +# define PRIdLEAST16 "hd" # define PRIdLEAST32 "d" # define PRIdLEAST64 __PRI64_PREFIX "d" -# define PRIdFAST8 "d" +# define PRIdFAST8 "hhd" # define PRIdFAST16 __PRIPTR_PREFIX "d" # define PRIdFAST32 __PRIPTR_PREFIX "d" # define PRIdFAST64 __PRI64_PREFIX "d" -# define PRIi8 "i" -# define PRIi16 "i" +# define PRIi8 "hhi" +# define PRIi16 "hi" # define PRIi32 "i" # define PRIi64 __PRI64_PREFIX "i" -# define PRIiLEAST8 "i" -# define PRIiLEAST16 "i" +# define PRIiLEAST8 "hhi" +# define PRIiLEAST16 "hi" # define PRIiLEAST32 "i" # define PRIiLEAST64 __PRI64_PREFIX "i" -# define PRIiFAST8 "i" +# define PRIiFAST8 "hhi" # define PRIiFAST16 __PRIPTR_PREFIX "i" # define PRIiFAST32 __PRIPTR_PREFIX "i" # define PRIiFAST64 __PRI64_PREFIX "i" /* Octal notation. */ -# define PRIo8 "o" -# define PRIo16 "o" +# define PRIo8 "hho" +# define PRIo16 "ho" # define PRIo32 "o" # define PRIo64 __PRI64_PREFIX "o" -# define PRIoLEAST8 "o" -# define PRIoLEAST16 "o" +# define PRIoLEAST8 "hho" +# define PRIoLEAST16 "ho" # define PRIoLEAST32 "o" # define PRIoLEAST64 __PRI64_PREFIX "o" -# define PRIoFAST8 "o" +# define PRIoFAST8 "hho" # define PRIoFAST16 __PRIPTR_PREFIX "o" # define PRIoFAST32 __PRIPTR_PREFIX "o" # define PRIoFAST64 __PRI64_PREFIX "o" /* Unsigned integers. */ -# define PRIu8 "u" -# define PRIu16 "u" +# define PRIu8 "hhu" +# define PRIu16 "hu" # define PRIu32 "u" # define PRIu64 __PRI64_PREFIX "u" -# define PRIuLEAST8 "u" -# define PRIuLEAST16 "u" +# define PRIuLEAST8 "hhu" +# define PRIuLEAST16 "hu" # define PRIuLEAST32 "u" # define PRIuLEAST64 __PRI64_PREFIX "u" -# define PRIuFAST8 "u" +# define PRIuFAST8 "hhu" # define PRIuFAST16 __PRIPTR_PREFIX "u" # define PRIuFAST32 __PRIPTR_PREFIX "u" # define PRIuFAST64 __PRI64_PREFIX "u" /* lowercase hexadecimal notation. */ -# define PRIx8 "x" -# define PRIx16 "x" +# define PRIx8 "hhx" +# define PRIx16 "hx" # define PRIx32 "x" # define PRIx64 __PRI64_PREFIX "x" -# define PRIxLEAST8 "x" -# define PRIxLEAST16 "x" +# define PRIxLEAST8 "hhx" +# define PRIxLEAST16 "hx" # define PRIxLEAST32 "x" # define PRIxLEAST64 __PRI64_PREFIX "x" -# define PRIxFAST8 "x" +# define PRIxFAST8 "hhx" # define PRIxFAST16 __PRIPTR_PREFIX "x" # define PRIxFAST32 __PRIPTR_PREFIX "x" # define PRIxFAST64 __PRI64_PREFIX "x" /* UPPERCASE hexadecimal notation. */ -# define PRIX8 "X" -# define PRIX16 "X" +# define PRIX8 "hhX" +# define PRIX16 "hX" # define PRIX32 "X" # define PRIX64 __PRI64_PREFIX "X" -# define PRIXLEAST8 "X" -# define PRIXLEAST16 "X" +# define PRIXLEAST8 "hhX" +# define PRIXLEAST16 "hX" # define PRIXLEAST32 "X" # define PRIXLEAST64 __PRI64_PREFIX "X" -# define PRIXFAST8 "X" +# define PRIXFAST8 "hhX" # define PRIXFAST16 __PRIPTR_PREFIX "X" # define PRIXFAST32 __PRIPTR_PREFIX "X" # define PRIXFAST64 __PRI64_PREFIX "X" @@ -166,17 +171,17 @@ typedef wchar_t __gwchar_t; /* Binary notation. */ # if __GLIBC_USE (ISOC23) -# define PRIb8 "b" -# define PRIb16 "b" +# define PRIb8 "hhb" +# define PRIb16 "hb" # define PRIb32 "b" # define PRIb64 __PRI64_PREFIX "b" -# define PRIbLEAST8 "b" -# define PRIbLEAST16 "b" +# define PRIbLEAST8 "hhb" +# define PRIbLEAST16 "hb" # define PRIbLEAST32 "b" # define PRIbLEAST64 __PRI64_PREFIX "b" -# define PRIbFAST8 "b" +# define PRIbFAST8 "hhb" # define PRIbFAST16 __PRIPTR_PREFIX "b" # define PRIbFAST32 __PRIPTR_PREFIX "b" # define PRIbFAST64 __PRI64_PREFIX "b" @@ -184,17 +189,17 @@ typedef wchar_t __gwchar_t; # define PRIbMAX __PRI64_PREFIX "b" # define PRIbPTR __PRIPTR_PREFIX "b" -# define PRIB8 "B" -# define PRIB16 "B" +# define PRIB8 "hhB" +# define PRIB16 "hB" # define PRIB32 "B" # define PRIB64 __PRI64_PREFIX "B" -# define PRIBLEAST8 "B" -# define PRIBLEAST16 "B" +# define PRIBLEAST8 "hhB" +# define PRIBLEAST16 "hB" # define PRIBLEAST32 "B" # define PRIBLEAST64 __PRI64_PREFIX "B" -# define PRIBFAST8 "B" +# define PRIBFAST8 "hhB" # define PRIBFAST16 __PRIPTR_PREFIX "B" # define PRIBFAST32 __PRIPTR_PREFIX "B" # define PRIBFAST64 __PRI64_PREFIX "B" @@ -352,7 +357,7 @@ extern intmax_t imaxabs (intmax_t __n) __THROW __attribute__ ((__const__)); #if __GLIBC_USE (ISOC2Y) -extern uintmax_t uimaxabs (intmax_t __n) __THROW __attribute__ ((__const__)); +extern uintmax_t umaxabs (intmax_t __n) __THROW __attribute__ ((__const__)); #endif /* Return the `imaxdiv_t' representation of the value of NUMER over DENOM. */ diff --git a/lib/libc/include/generic-glibc/langinfo.h b/lib/libc/include/generic-glibc/langinfo.h index 3a5e70bd239a967ee1b8665aad84ad079ad852e0..cbaccf9ad27a712b021d8414bd2cc339adddc490 100644 --- a/lib/libc/include/generic-glibc/langinfo.h +++ b/lib/libc/include/generic-glibc/langinfo.h @@ -1,5 +1,5 @@ /* Access to locale-dependent parameters. - Copyright (C) 1995-2025 Free Software Foundation, Inc. + Copyright (C) 1995-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/libgen.h b/lib/libc/include/generic-glibc/libgen.h index 4e489e79e3ac807e98571cef98becaf984f89615..981b01cd1e6201c7f1cf07923e05b3fa1f929b06 100644 --- a/lib/libc/include/generic-glibc/libgen.h +++ b/lib/libc/include/generic-glibc/libgen.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1996-2025 Free Software Foundation, Inc. +/* Copyright (C) 1996-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/libintl.h b/lib/libc/include/generic-glibc/libintl.h index 1032dd899e71a01c4646b7267fb06b1feb9fecc5..4ca85c7bdbada38f5c9108dd305486cfc60801da 100644 --- a/lib/libc/include/generic-glibc/libintl.h +++ b/lib/libc/include/generic-glibc/libintl.h @@ -1,5 +1,5 @@ /* Message catalogs for internationalization. - Copyright (C) 1995-2025 Free Software Foundation, Inc. + Copyright (C) 1995-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. This file is derived from the file libgettext.h in the GNU gettext package. diff --git a/lib/libc/include/generic-glibc/limits.h b/lib/libc/include/generic-glibc/limits.h index 88a0949f70d3c8a24380cbcbdbbf71bde0251fed..f495504af8cd3e9036fa7f7f83752f0aa71aed76 100644 --- a/lib/libc/include/generic-glibc/limits.h +++ b/lib/libc/include/generic-glibc/limits.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1991-2025 Free Software Foundation, Inc. +/* Copyright (C) 1991-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or @@ -180,7 +180,7 @@ /* The macros for _Bool are not defined by GCC's before GCC 11, or if _GNU_SOURCE is defined rather than enabling C23 support - with -std. */ + with -std; likewise for the version macro before GCC 13. */ #if __GLIBC_USE (ISOC23) # ifndef BOOL_MAX # define BOOL_MAX 1 @@ -188,6 +188,9 @@ # ifndef BOOL_WIDTH # define BOOL_WIDTH 1 # endif +# ifndef __STDC_VERSION_LIMITS_H__ +# define __STDC_VERSION_LIMITS_H__ 202311L +# endif #endif #ifdef __USE_POSIX diff --git a/lib/libc/include/generic-glibc/link.h b/lib/libc/include/generic-glibc/link.h index 23fd442437dca38e9446600e2131f4bc8a07ad77..7c0e06f979b557eaad048982f468f50edf76cd2e 100644 --- a/lib/libc/include/generic-glibc/link.h +++ b/lib/libc/include/generic-glibc/link.h @@ -1,6 +1,6 @@ /* Data structure for communication from the run-time dynamic linker for loaded ELF shared objects. - Copyright (C) 1995-2025 Free Software Foundation, Inc. + Copyright (C) 1995-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/locale.h b/lib/libc/include/generic-glibc/locale.h index 405a96d9f2e870cc9b0a7af0d2dc5f936767ee31..99b02fcf828366bb396b9c0b43da33b246d98931 100644 --- a/lib/libc/include/generic-glibc/locale.h +++ b/lib/libc/include/generic-glibc/locale.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1991-2025 Free Software Foundation, Inc. +/* Copyright (C) 1991-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/malloc.h b/lib/libc/include/generic-glibc/malloc.h index 187457d42131e12c92649a85f52ef0d62c5ca5f9..7332af4a7255f973bf99abaf65e03b2ee9bb0315 100644 --- a/lib/libc/include/generic-glibc/malloc.h +++ b/lib/libc/include/generic-glibc/malloc.h @@ -1,5 +1,5 @@ /* Prototypes and definition for malloc implementation. - Copyright (C) 1996-2025 Free Software Foundation, Inc. + Copyright (C) 1996-2026 Free Software Foundation, Inc. Copyright The GNU Toolchain Authors. This file is part of the GNU C Library. @@ -89,11 +89,11 @@ struct mallinfo { int arena; /* non-mmapped space allocated from system */ int ordblks; /* number of free chunks */ - int smblks; /* number of fastbin blocks */ + int smblks; /* number of fastbin blocks (deprecated) */ int hblks; /* number of mmapped regions */ int hblkhd; /* space in mmapped regions */ int usmblks; /* always 0, preserved for backwards compatibility */ - int fsmblks; /* space available in freed fastbin blocks */ + int fsmblks; /* space available in freed fastbin blocks (deprecated) */ int uordblks; /* total allocated space */ int fordblks; /* total free space */ int keepcost; /* top-most, releasable (via malloc_trim) space */ @@ -106,11 +106,11 @@ struct mallinfo2 { size_t arena; /* non-mmapped space allocated from system */ size_t ordblks; /* number of free chunks */ - size_t smblks; /* number of fastbin blocks */ + size_t smblks; /* number of fastbin blocks (deprecated) */ size_t hblks; /* number of mmapped regions */ size_t hblkhd; /* space in mmapped regions */ size_t usmblks; /* always 0, preserved for backwards compatibility */ - size_t fsmblks; /* space available in freed fastbin blocks */ + size_t fsmblks; /* space available in freed fastbin blocks (deprecated) */ size_t uordblks; /* total allocated space */ size_t fordblks; /* total free space */ size_t keepcost; /* top-most, releasable (via malloc_trim) space */ @@ -164,4 +164,4 @@ extern void malloc_stats (void) __THROW; extern int malloc_info (int __options, FILE *__fp) __THROW; __END_DECLS -#endif /* malloc.h */ +#endif /* malloc.h */ \ No newline at end of file diff --git a/lib/libc/include/generic-glibc/math.h b/lib/libc/include/generic-glibc/math.h index 2260d5d0b47e188a38d5a42eb32d39eecbc95f1e..99e75ab8435eb066422a335b40bcebe8058f1942 100644 --- a/lib/libc/include/generic-glibc/math.h +++ b/lib/libc/include/generic-glibc/math.h @@ -1,5 +1,5 @@ /* Declarations for math functions. - Copyright (C) 1991-2025 Free Software Foundation, Inc. + Copyright (C) 1991-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or @@ -33,15 +33,16 @@ __BEGIN_DECLS -/* Get definitions of __intmax_t and __uintmax_t. */ -#include - /* Get machine-dependent vector math functions declarations. */ #include /* Gather machine dependent type support. */ #include +#if __GLIBC_USE (ISOC23) +# define __STDC_VERSION_MATH_H__ 202311L +#endif + /* Value returned on overflow. With IEEE 754 floating point, this is +Infinity, otherwise the largest representable positive value. */ #if __GNUC_PREREQ (3, 3) @@ -162,34 +163,201 @@ __BEGIN_DECLS to evaluate `float' expressions double_t floating-point type at least as wide as `double' used to evaluate `double' expressions + + TS 18661-3 and C23 additionally define long_double_t and _FloatN_t. */ -# if __GLIBC_FLT_EVAL_METHOD == 0 || __GLIBC_FLT_EVAL_METHOD == 16 +# if __GLIBC_FLT_EVAL_METHOD == 0 typedef float float_t; typedef double double_t; +# if __GLIBC_USE (IEC_60559_TYPES_EXT) +typedef long double long_double_t; +# if __HAVE_FLOAT16 +typedef float _Float16_t; +# endif +# if __HAVE_FLOAT32 +typedef _Float32 _Float32_t; +# endif +# if __HAVE_FLOAT64 +typedef _Float64 _Float64_t; +# endif +# if __HAVE_FLOAT128 +typedef _Float128 _Float128_t; +# endif +# endif # elif __GLIBC_FLT_EVAL_METHOD == 1 typedef double float_t; typedef double double_t; +# if __GLIBC_USE (IEC_60559_TYPES_EXT) +typedef long double long_double_t; +# if __HAVE_FLOAT16 +typedef double _Float16_t; +# endif +# if __HAVE_FLOAT32 +typedef double _Float32_t; +# endif +# if __HAVE_FLOAT64 +typedef _Float64 _Float64_t; +# endif +# if __HAVE_FLOAT128 +typedef _Float128 _Float128_t; +# endif +# endif # elif __GLIBC_FLT_EVAL_METHOD == 2 typedef long double float_t; typedef long double double_t; +# if __GLIBC_USE (IEC_60559_TYPES_EXT) +typedef long double long_double_t; +# if __HAVE_FLOAT16 +typedef long double _Float16_t; +# endif +# if __HAVE_FLOAT32 +typedef long double _Float32_t; +# endif +# if __HAVE_FLOAT64 +# ifdef __NO_LONG_DOUBLE_MATH +typedef _Float64 _Float64_t; +# else +typedef long double _Float64_t; +# endif +# endif +# if __HAVE_FLOAT128 +typedef _Float128 _Float128_t; +# endif +# endif +# elif __GLIBC_FLT_EVAL_METHOD == 16 +typedef float float_t; +typedef double double_t; +# if __GLIBC_USE (IEC_60559_TYPES_EXT) +typedef long double long_double_t; +# if __HAVE_FLOAT16 +typedef _Float16 _Float16_t; +# endif +# if __HAVE_FLOAT32 +typedef _Float32 _Float32_t; +# endif +# if __HAVE_FLOAT64 +typedef _Float64 _Float64_t; +# endif +# if __HAVE_FLOAT128 +typedef _Float128 _Float128_t; +# endif +# endif # elif __GLIBC_FLT_EVAL_METHOD == 32 -typedef _Float32 float_t; +typedef float float_t; typedef double double_t; +# if __GLIBC_USE (IEC_60559_TYPES_EXT) +typedef long double long_double_t; +# if __HAVE_FLOAT16 +typedef _Float32 _Float16_t; +# endif +# if __HAVE_FLOAT32 +typedef _Float32 _Float32_t; +# endif +# if __HAVE_FLOAT64 +typedef _Float64 _Float64_t; +# endif +# if __HAVE_FLOAT128 +typedef _Float128 _Float128_t; +# endif +# endif # elif __GLIBC_FLT_EVAL_METHOD == 33 typedef _Float32x float_t; -typedef _Float32x double_t; +typedef double double_t; +# if __GLIBC_USE (IEC_60559_TYPES_EXT) +typedef long double long_double_t; +# if __HAVE_FLOAT16 +typedef _Float32x _Float16_t; +# endif +# if __HAVE_FLOAT32 +typedef _Float32x _Float32_t; +# endif +# if __HAVE_FLOAT64 +typedef _Float64 _Float64_t; +# endif +# if __HAVE_FLOAT128 +typedef _Float128 _Float128_t; +# endif +# endif # elif __GLIBC_FLT_EVAL_METHOD == 64 typedef _Float64 float_t; -typedef _Float64 double_t; +typedef double double_t; +# if __GLIBC_USE (IEC_60559_TYPES_EXT) +typedef long double long_double_t; +# if __HAVE_FLOAT16 +typedef _Float64 _Float16_t; +# endif +# if __HAVE_FLOAT32 +typedef _Float64 _Float32_t; +# endif +# if __HAVE_FLOAT64 +typedef _Float64 _Float64_t; +# endif +# if __HAVE_FLOAT128 +typedef _Float128 _Float128_t; +# endif +# endif # elif __GLIBC_FLT_EVAL_METHOD == 65 typedef _Float64x float_t; typedef _Float64x double_t; +# if __GLIBC_USE (IEC_60559_TYPES_EXT) +typedef long double long_double_t; +# if __HAVE_FLOAT16 +typedef _Float64x _Float16_t; +# endif +# if __HAVE_FLOAT32 +typedef _Float64x _Float32_t; +# endif +# if __HAVE_FLOAT64 +typedef _Float64x _Float64_t; +# endif +# if __HAVE_FLOAT128 +typedef _Float128 _Float128_t; +# endif +# endif # elif __GLIBC_FLT_EVAL_METHOD == 128 typedef _Float128 float_t; typedef _Float128 double_t; +# if __GLIBC_USE (IEC_60559_TYPES_EXT) +# if __HAVE_FLOAT128_UNLIKE_LDBL && __LDBL_MANT_DIG__ != 106 +typedef _Float128 long_double_t; +# else +typedef long double long_double_t; +# endif +# if __HAVE_FLOAT16 +typedef _Float128 _Float16_t; +# endif +# if __HAVE_FLOAT32 +typedef _Float128 _Float32_t; +# endif +# if __HAVE_FLOAT64 +typedef _Float128 _Float64_t; +# endif +# if __HAVE_FLOAT128 +typedef _Float128 _Float128_t; +# endif +# endif # elif __GLIBC_FLT_EVAL_METHOD == 129 typedef _Float128x float_t; typedef _Float128x double_t; +# if __GLIBC_USE (IEC_60559_TYPES_EXT) +# if __LDBL_MANT_DIG__ != 106 +typedef _Float128x long_double_t; +# else +typedef long double long_double_t; +# endif +# if __HAVE_FLOAT16 +typedef _Float128x _Float16_t; +# endif +# if __HAVE_FLOAT32 +typedef _Float128x _Float32_t; +# endif +# if __HAVE_FLOAT64 +typedef _Float128x _Float64_t; +# endif +# if __HAVE_FLOAT128 +typedef _Float128x _Float128_t; +# endif +# endif # else # error "Unknown __GLIBC_FLT_EVAL_METHOD" # endif @@ -1265,7 +1433,7 @@ iszero (__T __val) #endif #ifdef __USE_ISOC99 -# if __GNUC_PREREQ (3, 1) +# if __GNUC_PREREQ (3, 1) && !defined __clang__ /* ISO C99 defines some macros to compare number while taking care for unordered numbers. Many FPUs provide special instructions to support these operations. Generic support in GCC for these as builtins went diff --git a/lib/libc/include/generic-glibc/mcheck.h b/lib/libc/include/generic-glibc/mcheck.h index f8c86b7aea38ce353deb07790ecb0b31577ac15b..b189ae152426d083d25bd103303481542185d6d4 100644 --- a/lib/libc/include/generic-glibc/mcheck.h +++ b/lib/libc/include/generic-glibc/mcheck.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1996-2025 Free Software Foundation, Inc. +/* Copyright (C) 1996-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/memory.h b/lib/libc/include/generic-glibc/memory.h index 9dc8823e45ab36586b5265a89d9de933f30c54fb..7c6d7d96e91a0b52e3277056709148b819a204e5 100644 --- a/lib/libc/include/generic-glibc/memory.h +++ b/lib/libc/include/generic-glibc/memory.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1991-2025 Free Software Foundation, Inc. +/* Copyright (C) 1991-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/mntent.h b/lib/libc/include/generic-glibc/mntent.h index 3bd819b322cca1fb5ebf57d6bcc0870390984979..352739b033bf1c89b0400bc3ade9daa4e74d89f8 100644 --- a/lib/libc/include/generic-glibc/mntent.h +++ b/lib/libc/include/generic-glibc/mntent.h @@ -1,5 +1,5 @@ /* Utilities for reading/writing fstab, mtab, etc. - Copyright (C) 1995-2025 Free Software Foundation, Inc. + Copyright (C) 1995-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/monetary.h b/lib/libc/include/generic-glibc/monetary.h index 348e044f7e2464795c64e494b1b9f480ce70bec6..046a7ee1c1a12dcd4a8d14dc9c31352ec01d2ce1 100644 --- a/lib/libc/include/generic-glibc/monetary.h +++ b/lib/libc/include/generic-glibc/monetary.h @@ -1,5 +1,5 @@ /* Header file for monetary value formatting functions. - Copyright (C) 1996-2025 Free Software Foundation, Inc. + Copyright (C) 1996-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/mqueue.h b/lib/libc/include/generic-glibc/mqueue.h index 0e5d4bf733fbf306219200dc3fc756669253f147..7b9f7a54ee3a8a4ab522b049e34a7ee9d7fa20eb 100644 --- a/lib/libc/include/generic-glibc/mqueue.h +++ b/lib/libc/include/generic-glibc/mqueue.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2004-2025 Free Software Foundation, Inc. +/* Copyright (C) 2004-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/net/ethernet.h b/lib/libc/include/generic-glibc/net/ethernet.h index ff9a5250d0b53916d443a6888581c2ea13b36352..6c2bb4d64d6e126377e62924a1effc7de930ca82 100644 --- a/lib/libc/include/generic-glibc/net/ethernet.h +++ b/lib/libc/include/generic-glibc/net/ethernet.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1997-2025 Free Software Foundation, Inc. +/* Copyright (C) 1997-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/net/if.h b/lib/libc/include/generic-glibc/net/if.h index 71e70c3baf790adc81981756cc836d1ff5a4778d..759016aec6fecb5ddfece30e76e585df586c9678 100644 --- a/lib/libc/include/generic-glibc/net/if.h +++ b/lib/libc/include/generic-glibc/net/if.h @@ -1,5 +1,5 @@ /* net/if.h -- declarations for inquiring about network interfaces - Copyright (C) 1997-2025 Free Software Foundation, Inc. + Copyright (C) 1997-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/net/if_arp.h b/lib/libc/include/generic-glibc/net/if_arp.h index 59d9a84b53b1c949d22c2bf1b2dc22a4054d7352..929a85efbc507cb135032afab979355ac7caf907 100644 --- a/lib/libc/include/generic-glibc/net/if_arp.h +++ b/lib/libc/include/generic-glibc/net/if_arp.h @@ -1,5 +1,5 @@ /* Definitions for Address Resolution Protocol. - Copyright (C) 1997-2025 Free Software Foundation, Inc. + Copyright (C) 1997-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/net/if_packet.h b/lib/libc/include/generic-glibc/net/if_packet.h index f89f7f9d8aebf7f3803c0bdae55551012f45ac08..fe64c7cf3e501d01b92811c0dc6f3e0a4136ad49 100644 --- a/lib/libc/include/generic-glibc/net/if_packet.h +++ b/lib/libc/include/generic-glibc/net/if_packet.h @@ -1,5 +1,5 @@ /* Definitions for use with Linux SOCK_PACKET sockets. - Copyright (C) 1997-2025 Free Software Foundation, Inc. + Copyright (C) 1997-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/net/if_shaper.h b/lib/libc/include/generic-glibc/net/if_shaper.h index 51c5716cc9cded1b5aacfbf1a13cb4e2a6d46cd1..45b0e17f05326c74a77e123a6b505038749be65a 100644 --- a/lib/libc/include/generic-glibc/net/if_shaper.h +++ b/lib/libc/include/generic-glibc/net/if_shaper.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1998-2025 Free Software Foundation, Inc. +/* Copyright (C) 1998-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/net/if_slip.h b/lib/libc/include/generic-glibc/net/if_slip.h index a46311d84af576a70cf42d25b6fbd4a3823e9647..08472f5c2d4f4c1a30128ab67101f9d19cfb1896 100644 --- a/lib/libc/include/generic-glibc/net/if_slip.h +++ b/lib/libc/include/generic-glibc/net/if_slip.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1997-2025 Free Software Foundation, Inc. +/* Copyright (C) 1997-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/net/route.h b/lib/libc/include/generic-glibc/net/route.h index 3cc87c171540579b1f8f23c391d9ddf2e177e3c5..afeef70e524587215389b98972230b775167c6d0 100644 --- a/lib/libc/include/generic-glibc/net/route.h +++ b/lib/libc/include/generic-glibc/net/route.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1997-2025 Free Software Foundation, Inc. +/* Copyright (C) 1997-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/netash/ash.h b/lib/libc/include/generic-glibc/netash/ash.h index 465ec38cb3633b7a7192e80cf810a1a339cbc254..acf28136f0f9754428cdfcf2980e1c547ab2a929 100644 --- a/lib/libc/include/generic-glibc/netash/ash.h +++ b/lib/libc/include/generic-glibc/netash/ash.h @@ -1,5 +1,5 @@ /* Definitions for use with Linux AF_ASH sockets. - Copyright (C) 1998-2025 Free Software Foundation, Inc. + Copyright (C) 1998-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/netatalk/at.h b/lib/libc/include/generic-glibc/netatalk/at.h index d3d5fe50d66f378ac354951bb82c4e88e2d845f9..fa25cdc4b1e5b72580533997e44c8b8c5d243173 100644 --- a/lib/libc/include/generic-glibc/netatalk/at.h +++ b/lib/libc/include/generic-glibc/netatalk/at.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1991-2025 Free Software Foundation, Inc. +/* Copyright (C) 1991-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/netax25/ax25.h b/lib/libc/include/generic-glibc/netax25/ax25.h index f08b91f0de248a56211417ce61fcafc6099ec92a..b39eba1afd7a6ab4f62a389c64f4259ee4ce3994 100644 --- a/lib/libc/include/generic-glibc/netax25/ax25.h +++ b/lib/libc/include/generic-glibc/netax25/ax25.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1997-2025 Free Software Foundation, Inc. +/* Copyright (C) 1997-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/netdb.h b/lib/libc/include/generic-glibc/netdb.h index 0c7574af8c3d8e981c8e14fa5cdf437cf32a5e7b..f134a0ef3d927c2d999ec12d708738d7eb968a9d 100644 --- a/lib/libc/include/generic-glibc/netdb.h +++ b/lib/libc/include/generic-glibc/netdb.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1996-2025 Free Software Foundation, Inc. +/* Copyright (C) 1996-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/neteconet/ec.h b/lib/libc/include/generic-glibc/neteconet/ec.h index 40d4af82b50660e4ee7650a03d96f4745df32358..4fa4d659f04a2be619ff147efd228266cbcba196 100644 --- a/lib/libc/include/generic-glibc/neteconet/ec.h +++ b/lib/libc/include/generic-glibc/neteconet/ec.h @@ -1,5 +1,5 @@ /* Definitions for use with Linux AF_ECONET sockets. - Copyright (C) 1998-2025 Free Software Foundation, Inc. + Copyright (C) 1998-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/netinet/ether.h b/lib/libc/include/generic-glibc/netinet/ether.h index 693b4c6f7059cee062904c2f25b2f869d8eab8aa..20b1f4115d8835921c71ae2a32df121739058330 100644 --- a/lib/libc/include/generic-glibc/netinet/ether.h +++ b/lib/libc/include/generic-glibc/netinet/ether.h @@ -1,5 +1,5 @@ /* Functions for storing Ethernet addresses in ASCII and mapping to hostnames. - Copyright (C) 1996-2025 Free Software Foundation, Inc. + Copyright (C) 1996-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/netinet/icmp6.h b/lib/libc/include/generic-glibc/netinet/icmp6.h index e45d704e9960797d9a8f91c776728a490b1f0044..0fd25b01f5ad3ea8e2b884651824c4795eb6159b 100644 --- a/lib/libc/include/generic-glibc/netinet/icmp6.h +++ b/lib/libc/include/generic-glibc/netinet/icmp6.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1991-2025 Free Software Foundation, Inc. +/* Copyright (C) 1991-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/netinet/if_ether.h b/lib/libc/include/generic-glibc/netinet/if_ether.h index 6cf2cacd4b95f98c8ef410fe14f20d87a38c0146..63f494284d4c8d7afd90c87e197cad085837a721 100644 --- a/lib/libc/include/generic-glibc/netinet/if_ether.h +++ b/lib/libc/include/generic-glibc/netinet/if_ether.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1996-2025 Free Software Foundation, Inc. +/* Copyright (C) 1996-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/netinet/if_fddi.h b/lib/libc/include/generic-glibc/netinet/if_fddi.h index 1d5b50d32e3302a65d2a6de14ab25936db9b615e..aaf5559c354d6f2843b87e6e01086620af6f44df 100644 --- a/lib/libc/include/generic-glibc/netinet/if_fddi.h +++ b/lib/libc/include/generic-glibc/netinet/if_fddi.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1997-2025 Free Software Foundation, Inc. +/* Copyright (C) 1997-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/netinet/if_tr.h b/lib/libc/include/generic-glibc/netinet/if_tr.h index 003c33f43492806b54e702032e655d0aead4f038..a8abf534c838a85f5862f017a384d156fa4dc5f8 100644 --- a/lib/libc/include/generic-glibc/netinet/if_tr.h +++ b/lib/libc/include/generic-glibc/netinet/if_tr.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1997-2025 Free Software Foundation, Inc. +/* Copyright (C) 1997-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/netinet/igmp.h b/lib/libc/include/generic-glibc/netinet/igmp.h index 58df64655c372a72c17f58ed5a56246b2784a2d3..63e24c6cd037cd8c65e894213c55841847c3a8d3 100644 --- a/lib/libc/include/generic-glibc/netinet/igmp.h +++ b/lib/libc/include/generic-glibc/netinet/igmp.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1997-2025 Free Software Foundation, Inc. +/* Copyright (C) 1997-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/netinet/in.h b/lib/libc/include/generic-glibc/netinet/in.h index cecb7b9d36620dd8b6c840c9eef9f3ccc387af8b..450338576f11be16bcf50d09d016d593f9ab5ca7 100644 --- a/lib/libc/include/generic-glibc/netinet/in.h +++ b/lib/libc/include/generic-glibc/netinet/in.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1991-2025 Free Software Foundation, Inc. +/* Copyright (C) 1991-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/netinet/in_systm.h b/lib/libc/include/generic-glibc/netinet/in_systm.h index 3f6e4f15c8ee3cf30b96699327e1d2dadee26776..bea6cf82f1b22d481f886699549284f28c65c60a 100644 --- a/lib/libc/include/generic-glibc/netinet/in_systm.h +++ b/lib/libc/include/generic-glibc/netinet/in_systm.h @@ -1,5 +1,5 @@ /* System specific type definitions for networking code. - Copyright (C) 1997-2025 Free Software Foundation, Inc. + Copyright (C) 1997-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/netinet/ip.h b/lib/libc/include/generic-glibc/netinet/ip.h index 4ef5f6cd393ba9b23621d2cdbaf86bd5ecd8ba83..eeb5d175f902f0afdcb1f2fc5837045852d3e0a3 100644 --- a/lib/libc/include/generic-glibc/netinet/ip.h +++ b/lib/libc/include/generic-glibc/netinet/ip.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1991-2025 Free Software Foundation, Inc. +/* Copyright (C) 1991-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/netinet/ip6.h b/lib/libc/include/generic-glibc/netinet/ip6.h index 2ef059bea286afb7e1e7a2978ce88219e2ee9e91..e9509ae49146b8b7f08a1021ebf0ffde6ea90d78 100644 --- a/lib/libc/include/generic-glibc/netinet/ip6.h +++ b/lib/libc/include/generic-glibc/netinet/ip6.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1991-2025 Free Software Foundation, Inc. +/* Copyright (C) 1991-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/netinet/ip_icmp.h b/lib/libc/include/generic-glibc/netinet/ip_icmp.h index c5b386ddfda96ba3bd94267117a049ae22d82982..5e09c0efea6399162e2742ba109a4938d50b866d 100644 --- a/lib/libc/include/generic-glibc/netinet/ip_icmp.h +++ b/lib/libc/include/generic-glibc/netinet/ip_icmp.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1991-2025 Free Software Foundation, Inc. +/* Copyright (C) 1991-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/netinet/tcp.h b/lib/libc/include/generic-glibc/netinet/tcp.h index 357da4371277a57ef430bd1adf5e55affc1d9d45..49764361e72d032438c000c278a16c8f77bbc555 100644 --- a/lib/libc/include/generic-glibc/netinet/tcp.h +++ b/lib/libc/include/generic-glibc/netinet/tcp.h @@ -267,8 +267,93 @@ struct tcp_info uint32_t tcpi_rcv_space; uint32_t tcpi_total_retrans; + + uint64_t tcpi_pacing_rate; + uint64_t tcpi_max_pacing_rate; + uint64_t tcpi_bytes_acked; /* RFC4898 tcpEStatsAppHCThruOctetsAcked */ + uint64_t tcpi_bytes_received; /* RFC4898 tcpEStatsAppHCThruOctetsReceived */ + uint32_t tcpi_segs_out; /* RFC4898 tcpEStatsPerfSegsOut */ + uint32_t tcpi_segs_in; /* RFC4898 tcpEStatsPerfSegsIn */ + + uint32_t tcpi_notsent_bytes; + uint32_t tcpi_min_rtt; + uint32_t tcpi_data_segs_in; /* RFC4898 tcpEStatsDataSegsIn */ + uint32_t tcpi_data_segs_out; /* RFC4898 tcpEStatsDataSegsOut */ + + uint64_t tcpi_delivery_rate; + + uint64_t tcpi_busy_time; /* Time (usec) busy sending data */ + uint64_t tcpi_rwnd_limited; /* Time (usec) limited by receive window */ + uint64_t tcpi_sndbuf_limited; /* Time (usec) limited by send buffer */ + + uint32_t tcpi_delivered; + uint32_t tcpi_delivered_ce; + + uint64_t tcpi_bytes_sent; /* RFC4898 tcpEStatsPerfHCDataOctetsOut */ + uint64_t tcpi_bytes_retrans; /* RFC4898 tcpEStatsPerfOctetsRetrans */ + uint32_t tcpi_dsack_dups; /* RFC4898 tcpEStatsStackDSACKDups */ + uint32_t tcpi_reord_seen; /* reordering events seen */ + + + uint32_t tcpi_rcv_ooopack; /* Out-of-order packets received */ + /* Peer's advertised receive window after scaling (bytes) */ + uint32_t tcpi_snd_wnd; + /* Local advertised receive window after scaling (bytes) */ + uint32_t tcpi_rcv_wnd; + + uint32_t tcpi_rehash; /* PLB or timeout triggered rehash attempts */ + /* Total number of RTO timeouts, including + * SYN/SYN-ACK and recurring timeouts + */ + uint16_t tcpi_total_rto; + /* Total number of RTO recoveries, including any unfinished recovery. */ + uint16_t tcpi_total_rto_recoveries; + /* Total time spent in RTO recoveries in milliseconds, including any + * unfinished recovery. + */ + uint32_t tcpi_total_rto_time; + uint32_t tcpi_received_ce; /* # of CE marks received */ + uint32_t tcpi_delivered_e1_bytes; /* Accurate ECN byte counters */ + uint32_t tcpi_delivered_e0_bytes; + uint32_t tcpi_delivered_ce_bytes; + uint32_t tcpi_received_e1_bytes; + uint32_t tcpi_received_e0_bytes; + uint32_t tcpi_received_ce_bytes; + uint16_t tcpi_accecn_fail_mode; + uint16_t tcpi_accecn_opt_seen; }; +/* Netlink attributes types for SCM_TIMESTAMPING_OPT_STATS */ +enum { + TCP_NLA_PAD, + TCP_NLA_BUSY, /* Time (usec) busy sending data */ + TCP_NLA_RWND_LIMITED, /* Time (usec) limited by receive window */ + TCP_NLA_SNDBUF_LIMITED, /* Time (usec) limited by send buffer */ + TCP_NLA_DATA_SEGS_OUT, /* Data pkts sent including retransmission */ + TCP_NLA_TOTAL_RETRANS, /* Data pkts retransmitted */ + TCP_NLA_PACING_RATE, /* Pacing rate in bytes per second */ + TCP_NLA_DELIVERY_RATE, /* Delivery rate in bytes per second */ + TCP_NLA_SND_CWND, /* Sending congestion window */ + TCP_NLA_REORDERING, /* Reordering metric */ + TCP_NLA_MIN_RTT, /* minimum RTT */ + TCP_NLA_RECUR_RETRANS, /* Recurring retransmits for the current pkt */ + TCP_NLA_DELIVERY_RATE_APP_LMT, /* delivery rate application limited ? */ + TCP_NLA_SNDQ_SIZE, /* Data (bytes) pending in send queue */ + TCP_NLA_CA_STATE, /* ca_state of socket */ + TCP_NLA_SND_SSTHRESH, /* Slow start size threshold */ + TCP_NLA_DELIVERED, /* Data pkts delivered incl. out-of-order */ + TCP_NLA_DELIVERED_CE, /* Like above but only ones w/ CE marks */ + TCP_NLA_BYTES_SENT, /* Data bytes sent including retransmission */ + TCP_NLA_BYTES_RETRANS, /* Data bytes retransmitted */ + TCP_NLA_DSACK_DUPS, /* DSACK blocks received */ + TCP_NLA_REORD_SEEN, /* reordering events seen */ + TCP_NLA_SRTT, /* smoothed RTT in usecs */ + TCP_NLA_TIMEOUT_REHASH, /* Timeout-triggered rehash attempts */ + TCP_NLA_BYTES_NOTSENT, /* Bytes in write queue not yet sent */ + TCP_NLA_EDT, /* Earliest departure time (CLOCK_MONOTONIC) */ + TCP_NLA_TTL, /* TTL or hop limit of a packet received */ + TCP_NLA_REHASH, /* PLB and timeout triggered rehash attempts */ +}; /* For TCP_MD5SIG socket option. */ #define TCP_MD5SIG_MAXKEYLEN 80 @@ -287,6 +372,114 @@ struct tcp_md5sig uint8_t tcpm_key[TCP_MD5SIG_MAXKEYLEN]; /* Key (binary). */ }; +/* INET_DIAG_MD5SIG */ +struct tcp_diag_md5sig { + uint8_t tcpm_family; + uint8_t tcpm_prefixlen; + uint16_t tcpm_keylen; + uint32_t tcpm_addr[4]; + uint8_t tcpm_key[TCP_MD5SIG_MAXKEYLEN]; +}; + +#define TCP_AO_MAXKEYLEN 80 + +#define TCP_AO_KEYF_IFINDEX (1 << 0) /* L3 ifindex for VRF */ +#define TCP_AO_KEYF_EXCLUDE_OPT (1 << 1) /* Indicates whether TCP options + * other than TCP-AO are included + * in the MAC calculation + */ + +struct tcp_ao_add { /* setsockopt(TCP_AO_ADD_KEY) */ + struct sockaddr_storage addr; /* Peer's address for the key */ + int8_t alg_name[64]; /* Crypto hash algorithm to use */ + int32_t ifindex; /* L3 dev index for VRF */ + uint32_t set_current :1, /* Set key as Current_key at once */ + set_rnext :1, /* Request it from peer with RNext_key */ + reserved :30; /* Must be 0 */ + uint16_t reserved2; /* Padding, must be 0 */ + uint8_t prefix; /* Peer's address prefix */ + uint8_t sndid; /* SendID for outgoing segments */ + uint8_t rcvid; /* RecvID to match for incoming seg */ + uint8_t maclen; /* length of authentication code (hash) */ + uint8_t keyflags; /* See TCP_AO_KEYF_ */ + uint8_t keylen; /* Length of ::key */ + uint8_t key[TCP_AO_MAXKEYLEN]; +} __attribute__((aligned(8))); + +struct tcp_ao_del { /* setsockopt(TCP_AO_DEL_KEY) */ + struct sockaddr_storage addr; /* Peer's address for the key */ + int32_t ifindex; /* L3 dev index for VRF */ + uint32_t set_current :1, /* Corresponding ::current_key */ + set_rnext :1, /* Corresponding ::rnext */ + del_async :1, /* Only valid for listen sockets */ + reserved :29; /* Must be 0 */ + uint16_t reserved2; /* Padding, must be 0 */ + uint8_t prefix; /* Peer's address prefix */ + uint8_t sndid; /* SendID for outgoing segments */ + uint8_t rcvid; /* RecvID to match for incoming seg */ + uint8_t current_key; /* KeyID to set as Current_key */ + uint8_t rnext; /* KeyID to set as Rnext_key */ + uint8_t keyflags; /* See TCP_AO_KEYF_ */ +} __attribute__((aligned(8))); + +struct tcp_ao_info_opt { /* setsockopt(TCP_AO_INFO), getsockopt(TCP_AO_INFO) + */ + /* Here 'in' is for setsockopt(), 'out' is for getsockopt() */ + uint32_t set_current :1, /* In/out: corresponding ::current_key */ + set_rnext :1, /* In/out: corresponding ::rnext */ + ao_required :1, /* In/out: don't accept non-AO connects */ + set_counters :1, /* In: set/clear ::pkt_* counters */ + accept_icmps :1, /* In/out: accept incoming ICMPs */ + reserved :27; /* must be 0 */ + uint16_t reserved2; /* Padding, must be 0 */ + uint8_t current_key; /* In/out: KeyID of Current_key */ + uint8_t rnext; /* In/out: keyid of RNext_key */ + uint64_t pkt_good; /* In/out: verified segments */ + uint64_t pkt_bad; /* In/out: failed verification */ + uint64_t pkt_key_not_found; /* In/out: could not find a key to verify */ + uint64_t pkt_ao_required; /* In/out: segments missing TCP-AO sign */ + uint64_t pkt_dropped_icmp; /* In/out: ICMPs that were ignored */ +} __attribute__((aligned(8))); + +struct tcp_ao_getsockopt { /* getsockopt(TCP_AO_GET_KEYS) */ + struct sockaddr_storage addr; /* In/out: dump keys for peer + * with this address/prefix + */ + uint8_t alg_name[64]; /* out: crypto hash algorithm */ + uint8_t key[TCP_AO_MAXKEYLEN]; + uint32_t nkeys; /* In: size of the userspace buffer + * @optval, measured in @optlen - the + * sizeof(struct tcp_ao_getsockopt) + * Out: number of keys that matched + */ + uint16_t is_current :1, /* In: match and dump Current_key, + * Out: the dumped key is Current_key + */ + is_rnext :1, /* In: match and dump RNext_key, + * Out: the dumped key is RNext_key + */ + get_all :1, /* In: dump all keys */ + reserved :13; /* Padding, must be 0 */ + uint8_t sndid; /* In/out: dump keys with SendID */ + uint8_t rcvid; /* In/out: dump keys with RecvID */ + uint8_t prefix; /* In/out: dump keys with address/prefix */ + uint8_t maclen; /* Out: key's length of authentication + * code (hash) + */ + uint8_t keyflags; /* In/out: see TCP_AO_KEYF_ */ + uint8_t keylen; /* Out: length of ::key */ + int32_t ifindex; /* In/out: L3 dev index for VRF */ + uint64_t pkt_good; /* Out: verified segments */ + uint64_t pkt_bad; /* Out: segments that failed verification */ +} __attribute__((aligned(8))); + +struct tcp_ao_repair { /* {s,g}etsockopt(TCP_AO_REPAIR) */ + uint32_t snt_isn; + uint32_t rcv_isn; + uint32_t snd_sne; + uint32_t rcv_sne; +} __attribute__((aligned(8))); + /* For socket repair options. */ struct tcp_repair_opt { @@ -346,6 +539,15 @@ struct tcp_zerocopy_receive uint64_t address; /* In: address of mapping. */ uint32_t length; /* In/out: number of bytes to map/mapped. */ uint32_t recv_skip_hint; /* Out: amount of bytes to skip. */ + uint32_t inq; /* Out: amount of bytes in read queue. */ + int32_t err; /* Out: socket error. */ + uint64_t copybuf_address; /* On: copybuf address (small reads). */ + int32_t copybuf_len; /* In/Out: copybuf bytes avail/used or error. */ + uint32_t flags; /* In: flags. */ + uint64_t msg_control; /* Ancillary data. */ + uint64_t msg_controllen; + uint32_t msg_flags; + uint32_t reserved; /* Set to 0 for now. */ }; #endif /* Misc. */ diff --git a/lib/libc/include/generic-glibc/netinet/udp.h b/lib/libc/include/generic-glibc/netinet/udp.h index c2fd3250821cf1dae2e07652e5f307b79747534a..afc034d3a6ddd77bf5e187db9f66cf4e20385b98 100644 --- a/lib/libc/include/generic-glibc/netinet/udp.h +++ b/lib/libc/include/generic-glibc/netinet/udp.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1991-2025 Free Software Foundation, Inc. +/* Copyright (C) 1991-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/netipx/ipx.h b/lib/libc/include/generic-glibc/netipx/ipx.h index d67d3cb86b77365530cf35bbc62356cb4b07d958..f72532980f8f0418af70c11862879950babd68b8 100644 --- a/lib/libc/include/generic-glibc/netipx/ipx.h +++ b/lib/libc/include/generic-glibc/netipx/ipx.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1991-2025 Free Software Foundation, Inc. +/* Copyright (C) 1991-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/netiucv/iucv.h b/lib/libc/include/generic-glibc/netiucv/iucv.h index b1125c1c87a9fcde8c679defd3487059cd65f403..27735c0e52d8f1ead64161c3fe735a2595963c7f 100644 --- a/lib/libc/include/generic-glibc/netiucv/iucv.h +++ b/lib/libc/include/generic-glibc/netiucv/iucv.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2007-2025 Free Software Foundation, Inc. +/* Copyright (C) 2007-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/netpacket/packet.h b/lib/libc/include/generic-glibc/netpacket/packet.h index af9f7f49ece5fa4ab2b4b913d0a90cd9fefafb21..15477efb1bede35b87fce20e31f6af07dc669695 100644 --- a/lib/libc/include/generic-glibc/netpacket/packet.h +++ b/lib/libc/include/generic-glibc/netpacket/packet.h @@ -1,5 +1,5 @@ /* Definitions for use with Linux AF_PACKET sockets. - Copyright (C) 1998-2025 Free Software Foundation, Inc. + Copyright (C) 1998-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/netrom/netrom.h b/lib/libc/include/generic-glibc/netrom/netrom.h index 78ab32c0f3d49df6d04e3876b35794b5f0726dda..8fd4caf5c4b8b264b02d54c507a89042bf1d2bf8 100644 --- a/lib/libc/include/generic-glibc/netrom/netrom.h +++ b/lib/libc/include/generic-glibc/netrom/netrom.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1997-2025 Free Software Foundation, Inc. +/* Copyright (C) 1997-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/netrose/rose.h b/lib/libc/include/generic-glibc/netrose/rose.h index d85179ec1320a5562700a9d3de22711bead83af7..ee864a459b1871e048092a8e867dd213f39d0edc 100644 --- a/lib/libc/include/generic-glibc/netrose/rose.h +++ b/lib/libc/include/generic-glibc/netrose/rose.h @@ -1,5 +1,5 @@ /* Definitions for Rose packet radio address family. - Copyright (C) 1998-2025 Free Software Foundation, Inc. + Copyright (C) 1998-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/nl_types.h b/lib/libc/include/generic-glibc/nl_types.h index 87ee5341c0e321ba1a18b24e09b589a0403691ed..54577d60a11a901cf607786cc2b21c0829fae7bb 100644 --- a/lib/libc/include/generic-glibc/nl_types.h +++ b/lib/libc/include/generic-glibc/nl_types.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1996-2025 Free Software Foundation, Inc. +/* Copyright (C) 1996-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/nss.h b/lib/libc/include/generic-glibc/nss.h index ab632b5b41da86f6d0e21ec9a5acbb04b927f172..a57c1366d3fb8c549cae4bf4e572c22a9a5ee7c6 100644 --- a/lib/libc/include/generic-glibc/nss.h +++ b/lib/libc/include/generic-glibc/nss.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1996-2025 Free Software Foundation, Inc. +/* Copyright (C) 1996-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/obstack.h b/lib/libc/include/generic-glibc/obstack.h index a80542eb4e05a3686fa57444b5bc830f22126c7d..37d0e9b2c8af66c085bfe5dacbaa1de442ddd172 100644 --- a/lib/libc/include/generic-glibc/obstack.h +++ b/lib/libc/include/generic-glibc/obstack.h @@ -1,5 +1,5 @@ /* obstack.h - object stack macros - Copyright (C) 1988-2025 Free Software Foundation, Inc. + Copyright (C) 1988-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/printf.h b/lib/libc/include/generic-glibc/printf.h index 29d0baaa40248698ee2d06442968a760b0232938..321a3375577842281ca580bdadc67367175c342a 100644 --- a/lib/libc/include/generic-glibc/printf.h +++ b/lib/libc/include/generic-glibc/printf.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1991-2025 Free Software Foundation, Inc. +/* Copyright (C) 1991-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/proc_service.h b/lib/libc/include/generic-glibc/proc_service.h index 96fc4872d8c5e69140edd8b0a73514e121321624..47da4c2a9851af9b4c431641154a5826ee809017 100644 --- a/lib/libc/include/generic-glibc/proc_service.h +++ b/lib/libc/include/generic-glibc/proc_service.h @@ -1,5 +1,5 @@ /* Callback interface for libthread_db, functions users must define. - Copyright (C) 1999-2025 Free Software Foundation, Inc. + Copyright (C) 1999-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/pthread.h b/lib/libc/include/generic-glibc/pthread.h index cfce06cef914646764069c7dfbf367fbe76f280e..638d23c8875a5a33b546daa4cced1205d3494f94 100644 --- a/lib/libc/include/generic-glibc/pthread.h +++ b/lib/libc/include/generic-glibc/pthread.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2002-2025 Free Software Foundation, Inc. +/* Copyright (C) 2002-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/pty.h b/lib/libc/include/generic-glibc/pty.h index d6b4b7ee36e9836601bd35dbe2cb5f7b1d781d33..9091c173219a14b503b834941d995861aa32fb78 100644 --- a/lib/libc/include/generic-glibc/pty.h +++ b/lib/libc/include/generic-glibc/pty.h @@ -1,5 +1,5 @@ /* Functions for pseudo TTY handling. - Copyright (C) 1996-2025 Free Software Foundation, Inc. + Copyright (C) 1996-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/pwd.h b/lib/libc/include/generic-glibc/pwd.h index e4b85ac27e1bb5966e48754f21bfe5f3b6520d49..daf70436bf45ece2e7f9a887010a9e16e736f909 100644 --- a/lib/libc/include/generic-glibc/pwd.h +++ b/lib/libc/include/generic-glibc/pwd.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1991-2025 Free Software Foundation, Inc. +/* Copyright (C) 1991-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/re_comp.h b/lib/libc/include/generic-glibc/re_comp.h index 807da5a58dbbfa910b25acd600575b0686562fb0..baf343bdc19816d3e6b84e1ee2400df69c5ee7c8 100644 --- a/lib/libc/include/generic-glibc/re_comp.h +++ b/lib/libc/include/generic-glibc/re_comp.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1996-2025 Free Software Foundation, Inc. +/* Copyright (C) 1996-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/regdef.h b/lib/libc/include/generic-glibc/regdef.h index db737482b10ede5c1be83c37c3213e851a4e23bb..55ce5b9f0497475d0bd914aa3a56325a6e4e8802 100644 --- a/lib/libc/include/generic-glibc/regdef.h +++ b/lib/libc/include/generic-glibc/regdef.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1994-2025 Free Software Foundation, Inc. +/* Copyright (C) 1994-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/regex.h b/lib/libc/include/generic-glibc/regex.h index 8bbd92fc4fb036f6b93122d00b78786618213580..29964af5b251c24a729a3326c173a0ae96455d96 100644 --- a/lib/libc/include/generic-glibc/regex.h +++ b/lib/libc/include/generic-glibc/regex.h @@ -1,6 +1,6 @@ /* Definitions for data structures and routines for the regular expression library. - Copyright (C) 1985, 1989-2025 Free Software Foundation, Inc. + Copyright (C) 1985, 1989-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/regexp.h b/lib/libc/include/generic-glibc/regexp.h index 02ff44637eac509cf85c3031e5d8aea25f64f11b..3ecab12e76ba6a267cf66fd34b2a87f068958834 100644 --- a/lib/libc/include/generic-glibc/regexp.h +++ b/lib/libc/include/generic-glibc/regexp.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1996-2025 Free Software Foundation, Inc. +/* Copyright (C) 1996-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/resolv.h b/lib/libc/include/generic-glibc/resolv.h index 206ff771fb87c2372501d9d7a6345033c628c181..052341aa37f315cb91ed054544bbd8a847e9df30 100644 --- a/lib/libc/include/generic-glibc/resolv.h +++ b/lib/libc/include/generic-glibc/resolv.h @@ -336,4 +336,4 @@ void res_nclose (res_state) __THROW; __END_DECLS -#endif /* !_RESOLV_H_ */ +#endif /* !_RESOLV_H_ */ \ No newline at end of file diff --git a/lib/libc/include/generic-glibc/sched.h b/lib/libc/include/generic-glibc/sched.h index 0a44b8d5d8ed6dae107922b8a36de0bffdc69bf5..6a7358c795b410a6fbe56d78f08c1e34099db734 100644 --- a/lib/libc/include/generic-glibc/sched.h +++ b/lib/libc/include/generic-glibc/sched.h @@ -1,5 +1,5 @@ /* Definitions for POSIX 1003.1b-1993 (aka POSIX.4) scheduling interface. - Copyright (C) 1996-2025 Free Software Foundation, Inc. + Copyright (C) 1996-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/scsi/scsi.h b/lib/libc/include/generic-glibc/scsi/scsi.h index 925b2d8a313bdf3e21fbf2313bda658cec6f9796..45ec5e91f304eb06b49e15a9dd1ba7ae2132c456 100644 --- a/lib/libc/include/generic-glibc/scsi/scsi.h +++ b/lib/libc/include/generic-glibc/scsi/scsi.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1998-2025 Free Software Foundation, Inc. +/* Copyright (C) 1998-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/scsi/scsi_ioctl.h b/lib/libc/include/generic-glibc/scsi/scsi_ioctl.h index 59f10b7ce18575a05474527faaa7d1543da6e64f..65ca9f6824a677a3691325bd4df199e541d0acdd 100644 --- a/lib/libc/include/generic-glibc/scsi/scsi_ioctl.h +++ b/lib/libc/include/generic-glibc/scsi/scsi_ioctl.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1999-2025 Free Software Foundation, Inc. +/* Copyright (C) 1999-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/scsi/sg.h b/lib/libc/include/generic-glibc/scsi/sg.h index 6129b0dd620ae426b03b1dca67df0d750c4b746c..c82da1ebc2d3460e77af18ef87b8e54402e622ce 100644 --- a/lib/libc/include/generic-glibc/scsi/sg.h +++ b/lib/libc/include/generic-glibc/scsi/sg.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1997-2025 Free Software Foundation, Inc. +/* Copyright (C) 1997-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/search.h b/lib/libc/include/generic-glibc/search.h index c28ee21932b994136292354c847f43baa656ab7e..5d930245437201c531b178fbfc8eb2e956ac5d79 100644 --- a/lib/libc/include/generic-glibc/search.h +++ b/lib/libc/include/generic-glibc/search.h @@ -1,5 +1,5 @@ /* Declarations for System V style searching functions. - Copyright (C) 1995-2025 Free Software Foundation, Inc. + Copyright (C) 1995-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/semaphore.h b/lib/libc/include/generic-glibc/semaphore.h index 3f856a5ef7185a7a7d8a4b629e53d4071cfc7328..2724da048deea0c64910a76641165472c99bc2bd 100644 --- a/lib/libc/include/generic-glibc/semaphore.h +++ b/lib/libc/include/generic-glibc/semaphore.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2002-2025 Free Software Foundation, Inc. +/* Copyright (C) 2002-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/setjmp.h b/lib/libc/include/generic-glibc/setjmp.h index e28e85dfa9eb091f42f791e3dbc587deb8dbdf74..5d726b389cc836d5f940b7a244750b9b94baa60e 100644 --- a/lib/libc/include/generic-glibc/setjmp.h +++ b/lib/libc/include/generic-glibc/setjmp.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1991-2025 Free Software Foundation, Inc. +/* Copyright (C) 1991-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or @@ -26,6 +26,10 @@ __BEGIN_DECLS +#if __GLIBC_USE (ISOC23) +# define __STDC_VERSION_SETJMP_H__ 202311L +#endif + #include /* Get `__jmp_buf'. */ #include diff --git a/lib/libc/include/generic-glibc/sgidefs.h b/lib/libc/include/generic-glibc/sgidefs.h index c0ba3fb4580f63420c2f3e55988dcb04733eee7c..dd5a5f49b5e182a6ab423bcf7ca6df70c7b745f7 100644 --- a/lib/libc/include/generic-glibc/sgidefs.h +++ b/lib/libc/include/generic-glibc/sgidefs.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1996-2025 Free Software Foundation, Inc. +/* Copyright (C) 1996-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/sgtty.h b/lib/libc/include/generic-glibc/sgtty.h index 0b6dc1ac31149ecb7a3c0643151bfa199f7d90c8..1a1dfe28d1017f6ce7f701316056c6bb0cef0f70 100644 --- a/lib/libc/include/generic-glibc/sgtty.h +++ b/lib/libc/include/generic-glibc/sgtty.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1991-2025 Free Software Foundation, Inc. +/* Copyright (C) 1991-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/shadow.h b/lib/libc/include/generic-glibc/shadow.h index 090437158ebd1e79d1b097b7e19695ed9fe57a26..663365355b4a550e682570b25daa9cb0e5ab95c8 100644 --- a/lib/libc/include/generic-glibc/shadow.h +++ b/lib/libc/include/generic-glibc/shadow.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1996-2025 Free Software Foundation, Inc. +/* Copyright (C) 1996-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/signal.h b/lib/libc/include/generic-glibc/signal.h index 13a7fed1262da26f3e58009f9e420c3544a3edae..9c7d3e87b61b2374ec9096f5f920b7f8077223bc 100644 --- a/lib/libc/include/generic-glibc/signal.h +++ b/lib/libc/include/generic-glibc/signal.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1991-2025 Free Software Foundation, Inc. +/* Copyright (C) 1991-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/spawn.h b/lib/libc/include/generic-glibc/spawn.h index e466dd62d6ac2bb5fb8ec7878b39ebf08bed9d92..b8e1eaee8f8c1432cb21fb069d2134573536827e 100644 --- a/lib/libc/include/generic-glibc/spawn.h +++ b/lib/libc/include/generic-glibc/spawn.h @@ -1,5 +1,5 @@ /* Definitions for POSIX spawn interface. - Copyright (C) 2000-2025 Free Software Foundation, Inc. + Copyright (C) 2000-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/stdbit.h b/lib/libc/include/generic-glibc/stdbit.h index 7938eb3b5cf83d134cbb665462eed65009bb2c82..fd9252fbcb25a7ce3df7d6980e8261f8275b8b5e 100644 --- a/lib/libc/include/generic-glibc/stdbit.h +++ b/lib/libc/include/generic-glibc/stdbit.h @@ -1,5 +1,5 @@ /* ISO C23 Standard: 7.18 - Bit and byte utilities . - Copyright (C) 2024-2025 Free Software Foundation, Inc. + Copyright (C) 2024-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/stdc-predef.h b/lib/libc/include/generic-glibc/stdc-predef.h index 8bf36b70cab549961488a369a44bab2d9fbf3ea2..6150a345c7b6778eb7f2d5029b5b69285a606ee3 100644 --- a/lib/libc/include/generic-glibc/stdc-predef.h +++ b/lib/libc/include/generic-glibc/stdc-predef.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1991-2025 Free Software Foundation, Inc. +/* Copyright (C) 1991-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/stdint.h b/lib/libc/include/generic-glibc/stdint.h index 191506080a9cdc3b309fe752db372c0e2d6db784..4e17612886387a8b42e50fdadfb249f7beceef99 100644 --- a/lib/libc/include/generic-glibc/stdint.h +++ b/lib/libc/include/generic-glibc/stdint.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1997-2025 Free Software Foundation, Inc. +/* Copyright (C) 1997-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or @@ -28,6 +28,10 @@ #include #include +#if __GLIBC_USE (ISOC23) +# define __STDC_VERSION_STDINT_H__ 202311L +#endif + /* Exact integral types. */ /* Signed. */ @@ -91,6 +95,8 @@ typedef __intmax_t intmax_t; typedef __uintmax_t uintmax_t; +# undef __INT64_C +# undef __UINT64_C # if __WORDSIZE == 64 # define __INT64_C(c) c ## L # define __UINT64_C(c) c ## UL diff --git a/lib/libc/include/generic-glibc/stdio.h b/lib/libc/include/generic-glibc/stdio.h index e80e85755094b569778e697af1a6c1f6bdfcb342..c4a057b552264b51e6e3f6d410bd0f539db29d8f 100644 --- a/lib/libc/include/generic-glibc/stdio.h +++ b/lib/libc/include/generic-glibc/stdio.h @@ -1,5 +1,5 @@ /* Define ISO C stdio on top of C++ iostreams. - Copyright (C) 1991-2025 Free Software Foundation, Inc. + Copyright (C) 1991-2026 Free Software Foundation, Inc. Copyright The GNU Toolchain Authors. This file is part of the GNU C Library. @@ -29,6 +29,10 @@ __BEGIN_DECLS +#if __GLIBC_USE (ISOC23) +# define __STDC_VERSION_STDIO_H__ 202311L +#endif + #define __need_size_t #define __need_NULL #include @@ -168,11 +172,11 @@ extern int renameat (int __oldfd, const char *__old, int __newfd, #ifdef __USE_GNU /* Flags for renameat2. */ # define RENAME_NOREPLACE (1 << 0) -# define AT_RENAME_NOREPLACE RENAME_NOREPLACE +# define AT_RENAME_NOREPLACE 0x0001 # define RENAME_EXCHANGE (1 << 1) -# define AT_RENAME_EXCHANGE RENAME_EXCHANGE +# define AT_RENAME_EXCHANGE 0x0002 # define RENAME_WHITEOUT (1 << 2) -# define AT_RENAME_WHITEOUT RENAME_WHITEOUT +# define AT_RENAME_WHITEOUT 0x0004 /* Rename file OLD relative to OLDFD to NEW relative to NEWFD, with additional flags. */ diff --git a/lib/libc/include/generic-glibc/stdio_ext.h b/lib/libc/include/generic-glibc/stdio_ext.h index 35ba67d43e0065b63d01429884bec7883ff0c94f..984bac3e374325e147ba0f165d1c148c070f891c 100644 --- a/lib/libc/include/generic-glibc/stdio_ext.h +++ b/lib/libc/include/generic-glibc/stdio_ext.h @@ -1,5 +1,5 @@ /* Functions to access FILE structure internals. - Copyright (C) 2000-2025 Free Software Foundation, Inc. + Copyright (C) 2000-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/stdlib.h b/lib/libc/include/generic-glibc/stdlib.h index b6b4ff601ff8b673f9a3de1cacf25723884c2c9d..3ca2bb1f91bc38a0f6244c99113a3fb520f15b20 100644 --- a/lib/libc/include/generic-glibc/stdlib.h +++ b/lib/libc/include/generic-glibc/stdlib.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1991-2025 Free Software Foundation, Inc. +/* Copyright (C) 1991-2026 Free Software Foundation, Inc. Copyright The GNU Toolchain Authors. This file is part of the GNU C Library. @@ -35,6 +35,10 @@ __BEGIN_DECLS #define _STDLIB_H 1 +#if __GLIBC_USE (ISOC23) +# define __STDC_VERSION_STDLIB_H__ 202311L +#endif + #if (defined __USE_XOPEN || defined __USE_XOPEN2K8) && !defined _SYS_WAIT_H /* XPG requires a few symbols from being defined. */ # include @@ -692,6 +696,24 @@ extern void *realloc (void *__ptr, size_t __size) /* Free a block allocated by `malloc', `realloc' or `calloc'. */ extern void free (void *__ptr) __THROW; +#if __GLIBC_USE(ISOC23) +/* Free a block allocated by `malloc', `realloc' or `calloc' but not + `aligned_alloc', `memalign', `posix_memalign', `valloc' or + `pvalloc'. SIZE must be equal to the original requested size + provided to `malloc', `realloc' or `calloc'. For `calloc' SIZE is + NMEMB elements * SIZE bytes. It is forbidden to call `free_sized' + for allocations which the caller did not directly allocate but + must still deallocate, such as `strdup' or `strndup'. Instead + continue using `free` for these cases. */ +extern void free_sized (void *__ptr, size_t __size) __THROW; + +/* Free a block allocated by `aligned_alloc', `memalign' or + `posix_memalign'. ALIGNMENT and SIZE must be the same as the values + provided to `aligned_alloc', `memalign' or `posix_memalign'. */ +extern void free_aligned_sized (void *__ptr, size_t __alignment, size_t __size) + __THROW; +#endif + /* * zig patch: reallocarray introduced in glibc 2.26 * https://sourceware.org/git/?p=glibc.git;a=commit;h=2e0bbbfbf95fc9e22692e93658a6fbdd2d4554da @@ -977,6 +999,12 @@ extern void *bsearch (const void *__key, const void *__base, # include #endif +#if __GLIBC_USE (ISOC23) && defined __glibc_const_generic && !defined _LIBC +# define bsearch(KEY, BASE, NMEMB, SIZE, COMPAR) \ + __glibc_const_generic (BASE, const void *, \ + bsearch (KEY, BASE, NMEMB, SIZE, COMPAR)) +#endif + /* Sort NMEMB elements of BASE, of SIZE bytes each, using COMPAR to perform the comparisons. */ extern void qsort (void *__base, size_t __nmemb, size_t __size, @@ -1170,6 +1198,19 @@ extern int getloadavg (double __loadavg[], int __nelem) extern int ttyslot (void) __THROW; #endif +#if __GLIBC_USE (ISOC23) +# ifndef __cplusplus +# include + +/* Call function __FUNC exactly once, even if invoked from several threads. + All calls must be made with the same __FLAGS object. */ +extern void call_once (once_flag *__flag, void (*__func)(void)); +# endif /* !__cplusplus */ + +/* Return the alignment of P. */ +extern size_t memalignment (const void *__p); +#endif + #include /* Define some macros helping to catch buffer overflows. */ diff --git a/lib/libc/include/generic-glibc/string.h b/lib/libc/include/generic-glibc/string.h index d6794d21d1763e1a946e3897e5b30ce8a3bca169..8eb5b75f94132b2b970e0f8c5e528e6265829f49 100644 --- a/lib/libc/include/generic-glibc/string.h +++ b/lib/libc/include/generic-glibc/string.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1991-2025 Free Software Foundation, Inc. +/* Copyright (C) 1991-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or @@ -27,6 +27,10 @@ __BEGIN_DECLS +#if __GLIBC_USE (ISOC23) +# define __STDC_VERSION_STRING_H__ 202311L +#endif + /* Get size_t and NULL from . */ #define __need_size_t #define __need_NULL @@ -60,6 +64,13 @@ extern void *memccpy (void *__restrict __dest, const void *__restrict __src, /* Set N bytes of S to C. */ extern void *memset (void *__s, int __c, size_t __n) __THROW __nonnull ((1)); +#if defined __USE_MISC || __GLIBC_USE (ISOC23) +/* Like memset, but the compiler will not delete a call to this + function, even if S is dead after the call. */ +extern void *memset_explicit (void *__s, int __c, size_t __n) + __THROW __nonnull ((1)) __fortified_attr_access (__write_only__, 1, 3); +#endif + /* Compare N bytes of S1 and S2. */ extern int memcmp (const void *__s1, const void *__s2, size_t __n) __THROW __attribute_pure__ __nonnull ((1, 2)); @@ -106,6 +117,10 @@ memchr (const void *__s, int __c, size_t __n) __THROW #else extern void *memchr (const void *__s, int __c, size_t __n) __THROW __attribute_pure__ __nonnull ((1)); +# if __GLIBC_USE (ISOC23) && defined __glibc_const_generic && !defined _LIBC +# define memchr(S, C, N) \ + __glibc_const_generic (S, const void *, memchr (S, C, N)) +# endif #endif #ifdef __USE_GNU @@ -245,6 +260,10 @@ strchr (const char *__s, int __c) __THROW #else extern char *strchr (const char *__s, int __c) __THROW __attribute_pure__ __nonnull ((1)); +# if __GLIBC_USE (ISOC23) && defined __glibc_const_generic && !defined _LIBC +# define strchr(S, C) \ + __glibc_const_generic (S, const char *, strchr (S, C)) +# endif #endif /* Find the last occurrence of C in S. */ #ifdef __CORRECT_ISO_CPP_STRING_H_PROTO @@ -272,6 +291,10 @@ strrchr (const char *__s, int __c) __THROW #else extern char *strrchr (const char *__s, int __c) __THROW __attribute_pure__ __nonnull ((1)); +# if __GLIBC_USE (ISOC23) && defined __glibc_const_generic && !defined _LIBC +# define strrchr(S, C) \ + __glibc_const_generic (S, const char *, strrchr (S, C)) +# endif #endif #ifdef __USE_MISC @@ -322,6 +345,10 @@ strpbrk (const char *__s, const char *__accept) __THROW #else extern char *strpbrk (const char *__s, const char *__accept) __THROW __attribute_pure__ __nonnull ((1, 2)); +# if __GLIBC_USE (ISOC23) && defined __glibc_const_generic && !defined _LIBC +# define strpbrk(S, ACCEPT) \ + __glibc_const_generic (S, const char *, strpbrk (S, ACCEPT)) +# endif #endif /* Find the first occurrence of NEEDLE in HAYSTACK. */ #ifdef __CORRECT_ISO_CPP_STRING_H_PROTO @@ -349,6 +376,11 @@ strstr (const char *__haystack, const char *__needle) __THROW #else extern char *strstr (const char *__haystack, const char *__needle) __THROW __attribute_pure__ __nonnull ((1, 2)); +# if __GLIBC_USE (ISOC23) && defined __glibc_const_generic && !defined _LIBC +# define strstr(HAYSTACK, NEEDLE) \ + __glibc_const_generic (HAYSTACK, const char *, \ + strstr (HAYSTACK, NEEDLE)) +# endif #endif @@ -557,4 +589,4 @@ extern char *basename (const char *__filename) __THROW __nonnull ((1)); __END_DECLS -#endif /* string.h */ +#endif /* string.h */ \ No newline at end of file diff --git a/lib/libc/include/generic-glibc/strings.h b/lib/libc/include/generic-glibc/strings.h index bcc5399e81efb41696136e1c412ba3a652726f30..ddd4f467eb66bc9a8b6dd733d3ba2ed3e81dd1b1 100644 --- a/lib/libc/include/generic-glibc/strings.h +++ b/lib/libc/include/generic-glibc/strings.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1991-2025 Free Software Foundation, Inc. +/* Copyright (C) 1991-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/sys/acct.h b/lib/libc/include/generic-glibc/sys/acct.h index a0f6fd6ab24c52520b5068240b9ccf8a59cadadc..6e603afcafc924bd0f4ae07522523cf498c52ce2 100644 --- a/lib/libc/include/generic-glibc/sys/acct.h +++ b/lib/libc/include/generic-glibc/sys/acct.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1996-2025 Free Software Foundation, Inc. +/* Copyright (C) 1996-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/sys/asm.h b/lib/libc/include/generic-glibc/sys/asm.h index a10baf11efa2d36dc09157c380aa6a577cd675b5..4cd32eccbb35a9465868b723616be34e535faabe 100644 --- a/lib/libc/include/generic-glibc/sys/asm.h +++ b/lib/libc/include/generic-glibc/sys/asm.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1997-2025 Free Software Foundation, Inc. +/* Copyright (C) 1997-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or @@ -478,20 +478,4 @@ symbol = value # define MTC0 dmtc0 #endif -/* The MIPS architectures do not have a uniform memory model. Particular - platforms may provide additional guarantees - for instance, the R4000 - LL and SC instructions implicitly perform a SYNC, and the 4K promises - strong ordering. - - However, in the absence of those guarantees, we must assume weak ordering - and SYNC explicitly where necessary. - - Some obsolete MIPS processors may not support the SYNC instruction. This - applies to "true" MIPS I processors; most of the processors which compile - using MIPS I implement parts of MIPS II. */ - -#ifndef MIPS_SYNC -# define MIPS_SYNC sync -#endif - #endif /* sys/asm.h */ \ No newline at end of file diff --git a/lib/libc/include/generic-glibc/sys/auxv.h b/lib/libc/include/generic-glibc/sys/auxv.h index ccb6e70ccbcde60ee3b011f5a4f719e2103f1b68..66c0a651adecf60b6f621a78e6d2e9e866ccccc6 100644 --- a/lib/libc/include/generic-glibc/sys/auxv.h +++ b/lib/libc/include/generic-glibc/sys/auxv.h @@ -1,5 +1,5 @@ /* Access to the auxiliary vector. - Copyright (C) 2012-2025 Free Software Foundation, Inc. + Copyright (C) 2012-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/sys/cachectl.h b/lib/libc/include/generic-glibc/sys/cachectl.h index d3a4ba4982d87f87dc63d0e89b3134511fe107e6..daa369ca0bf417e4cd9da6df606acd0f94e53ab6 100644 --- a/lib/libc/include/generic-glibc/sys/cachectl.h +++ b/lib/libc/include/generic-glibc/sys/cachectl.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1995-2025 Free Software Foundation, Inc. +/* Copyright (C) 1995-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/sys/cdefs.h b/lib/libc/include/generic-glibc/sys/cdefs.h index a8315571aa93d2d7a370d4717e6334911bda1075..3970013e72e1c291952b46995437f139e324e3d0 100644 --- a/lib/libc/include/generic-glibc/sys/cdefs.h +++ b/lib/libc/include/generic-glibc/sys/cdefs.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1992-2025 Free Software Foundation, Inc. +/* Copyright (C) 1992-2026 Free Software Foundation, Inc. Copyright The GNU Toolchain Authors. This file is part of the GNU C Library. @@ -438,10 +438,10 @@ */ #endif -/* GCC and clang have various useful declarations that can be made with - the '__attribute__' syntax. All of the ways we use this do fine if - they are omitted for compilers that don't understand it. */ -#if !(defined __GNUC__ || defined __clang__) +/* GCC, clang, and compatible compilers have various useful declarations + that can be made with the '__attribute__' syntax. All of the ways we use + this do fine if they are omitted for compilers that don't understand it. */ +#if !(defined __GNUC__ || defined __clang__ || defined __TINYC__) # define __attribute__(xyz) /* Ignore */ #endif @@ -606,14 +606,14 @@ # define __attribute_artificial__ /* Ignore */ #endif -/* GCC 4.3 and above with -std=c99 or -std=gnu99 implements ISO C99 - inline semantics, unless -fgnu89-inline is used. Using __GNUC_STDC_INLINE__ - or __GNUC_GNU_INLINE is not a good enough check for gcc because gcc versions +/* GCC 4.3 and above with -std=c99 or -std=gnu99 implements ISO C99 inline + semantics, unless -fgnu89-inline is used. Using __GNUC_STDC_INLINE__ or + __GNUC_GNU_INLINE__ is not a good enough check for gcc because gcc versions older than 4.3 may define these macros and still not guarantee GNU inlining semantics. clang++ identifies itself as gcc-4.2, but has support for GNU inlining - semantics, that can be checked for by using the __GNUC_STDC_INLINE_ and + semantics, that can be checked for by using the __GNUC_STDC_INLINE__ and __GNUC_GNU_INLINE__ macro definitions. */ #if (!defined __cplusplus || __GNUC_PREREQ (4,3) \ || (defined __clang__ && (defined __GNUC_STDC_INLINE__ \ @@ -828,6 +828,18 @@ _Static_assert (0, "IEEE 128-bits long double requires redirection on this platf # define __HAVE_GENERIC_SELECTION 0 #endif +#if __HAVE_GENERIC_SELECTION +/* If PTR is a pointer to const, return CALL cast to type CTYPE, + otherwise return CALL. Pointers to types with non-const qualifiers + are not valid. This should not be defined for C++, as macros are + not an appropriate way of implementing such qualifier-generic + operations for C++. */ +# define __glibc_const_generic(PTR, CTYPE, CALL) \ + _Generic (0 ? (PTR) : (void *) 1, \ + const void *: (CTYPE) (CALL), \ + default: CALL) +#endif + #if __GNUC_PREREQ (10, 0) /* Designates a 1-based positional argument ref-index of pointer type that can be used to access size-index elements of the pointed-to diff --git a/lib/libc/include/generic-glibc/sys/debugreg.h b/lib/libc/include/generic-glibc/sys/debugreg.h index 206706490b79c53fed98b71c1f48ee24aa276d71..0915d533cfb538eec7227d53dbb5196a203c40aa 100644 --- a/lib/libc/include/generic-glibc/sys/debugreg.h +++ b/lib/libc/include/generic-glibc/sys/debugreg.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2001-2025 Free Software Foundation, Inc. +/* Copyright (C) 2001-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/sys/dir.h b/lib/libc/include/generic-glibc/sys/dir.h index 26caaf121e1b2fb74c65b1d63e91a4290b731e59..c664312b4b3050f2e1d9ecc4b383539b2716cee8 100644 --- a/lib/libc/include/generic-glibc/sys/dir.h +++ b/lib/libc/include/generic-glibc/sys/dir.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1991-2025 Free Software Foundation, Inc. +/* Copyright (C) 1991-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/sys/elf.h b/lib/libc/include/generic-glibc/sys/elf.h index 5517dccb2b01a5d6cab050d8af8832cb3d6f71cc..aae414e6af687ccfae5d24497d2284cca7d1e517 100644 --- a/lib/libc/include/generic-glibc/sys/elf.h +++ b/lib/libc/include/generic-glibc/sys/elf.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1996-2025 Free Software Foundation, Inc. +/* Copyright (C) 1996-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/sys/epoll.h b/lib/libc/include/generic-glibc/sys/epoll.h index e1beed29ea9a04817285f8ea846b60b2d4ce46fa..0ca1ea780d96469877b1e32a3c126dbb345fc74d 100644 --- a/lib/libc/include/generic-glibc/sys/epoll.h +++ b/lib/libc/include/generic-glibc/sys/epoll.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2002-2025 Free Software Foundation, Inc. +/* Copyright (C) 2002-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/sys/eventfd.h b/lib/libc/include/generic-glibc/sys/eventfd.h index dafde87348ba72acc7c9c0fbb57c05503c155d92..d4e89fbdf81a45d3787b8a7085a231e40506e0d4 100644 --- a/lib/libc/include/generic-glibc/sys/eventfd.h +++ b/lib/libc/include/generic-glibc/sys/eventfd.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2007-2025 Free Software Foundation, Inc. +/* Copyright (C) 2007-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/sys/fanotify.h b/lib/libc/include/generic-glibc/sys/fanotify.h index 371384834e8603dddea23b07adf5ac114dd66dc9..4155e61af615b00a2b6891186ebca22d81f6a96a 100644 --- a/lib/libc/include/generic-glibc/sys/fanotify.h +++ b/lib/libc/include/generic-glibc/sys/fanotify.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2010-2025 Free Software Foundation, Inc. +/* Copyright (C) 2010-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/sys/file.h b/lib/libc/include/generic-glibc/sys/file.h index c791c7b8f982b8cd8ef5f6db6922d9b54a5bf2de..68a1efb13feb06ffd95224cfb86e4893015b0a63 100644 --- a/lib/libc/include/generic-glibc/sys/file.h +++ b/lib/libc/include/generic-glibc/sys/file.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1991-2025 Free Software Foundation, Inc. +/* Copyright (C) 1991-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/sys/fpregdef.h b/lib/libc/include/generic-glibc/sys/fpregdef.h index a72e1868aef31708482c68f2f2460bfb30ed7429..19db3de6bdbc5c7f95eae059bec5a317e2971675 100644 --- a/lib/libc/include/generic-glibc/sys/fpregdef.h +++ b/lib/libc/include/generic-glibc/sys/fpregdef.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1991-2025 Free Software Foundation, Inc. +/* Copyright (C) 1991-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/sys/fsuid.h b/lib/libc/include/generic-glibc/sys/fsuid.h index 2975d1b8cf691b8254bc78bcfd9e449a209a0698..635a57bd182bb910326c0b40d3f6501a252a907e 100644 --- a/lib/libc/include/generic-glibc/sys/fsuid.h +++ b/lib/libc/include/generic-glibc/sys/fsuid.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1997-2025 Free Software Foundation, Inc. +/* Copyright (C) 1997-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/sys/gmon_out.h b/lib/libc/include/generic-glibc/sys/gmon_out.h index 5e29305736dbd20d45568e738b782ae38da0714c..651c3534ab2664a23d8fbfb25d7f9353ca291c80 100644 --- a/lib/libc/include/generic-glibc/sys/gmon_out.h +++ b/lib/libc/include/generic-glibc/sys/gmon_out.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1996-2025 Free Software Foundation, Inc. +/* Copyright (C) 1996-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/sys/hwprobe.h b/lib/libc/include/generic-glibc/sys/hwprobe.h index 056a98dc5182784e8c74c40331cb5a8b4e7bb00d..6c4c82f9c858123b13ab449406fa079cbc85513f 100644 --- a/lib/libc/include/generic-glibc/sys/hwprobe.h +++ b/lib/libc/include/generic-glibc/sys/hwprobe.h @@ -1,5 +1,5 @@ /* RISC-V architecture probe interface - Copyright (C) 2024-2025 Free Software Foundation, Inc. + Copyright (C) 2024-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. diff --git a/lib/libc/include/generic-glibc/sys/ifunc.h b/lib/libc/include/generic-glibc/sys/ifunc.h index c4ca0a983ccc513588dc84ce3821e5b0086a7c32..324950053e37c058acfa3fdd018a81bfda28dfa4 100644 --- a/lib/libc/include/generic-glibc/sys/ifunc.h +++ b/lib/libc/include/generic-glibc/sys/ifunc.h @@ -1,5 +1,5 @@ /* Definitions used by AArch64 indirect function resolvers. - Copyright (C) 2019-2025 Free Software Foundation, Inc. + Copyright (C) 2019-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/sys/inotify.h b/lib/libc/include/generic-glibc/sys/inotify.h index a901da49bfc92c7ecc2b2252283ae593b4a1d6c8..af9a9bbe1890fb020917d9cbc0bbae35f2e7ddc4 100644 --- a/lib/libc/include/generic-glibc/sys/inotify.h +++ b/lib/libc/include/generic-glibc/sys/inotify.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2005-2025 Free Software Foundation, Inc. +/* Copyright (C) 2005-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/sys/io.h b/lib/libc/include/generic-glibc/sys/io.h index 6d9f3e883454fbe9663082d2e90dbc09308a6516..c2780ef5c2a29557037c7dc383de2ffe60142735 100644 --- a/lib/libc/include/generic-glibc/sys/io.h +++ b/lib/libc/include/generic-glibc/sys/io.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1996-2025 Free Software Foundation, Inc. +/* Copyright (C) 1996-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/sys/ioctl.h b/lib/libc/include/generic-glibc/sys/ioctl.h index c72a90bf7881c39c1b334f4cc1dec6e761153286..b0493878d46373832c1fc9baf84d16df76022a7d 100644 --- a/lib/libc/include/generic-glibc/sys/ioctl.h +++ b/lib/libc/include/generic-glibc/sys/ioctl.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1991-2025 Free Software Foundation, Inc. +/* Copyright (C) 1991-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/sys/ipc.h b/lib/libc/include/generic-glibc/sys/ipc.h index ac30bcaba3849baa13cdf356b8160f79b7b23d03..84a8b6fe189a68f04d16d79db2dab289f5ca95ef 100644 --- a/lib/libc/include/generic-glibc/sys/ipc.h +++ b/lib/libc/include/generic-glibc/sys/ipc.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1995-2025 Free Software Foundation, Inc. +/* Copyright (C) 1995-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/sys/kd.h b/lib/libc/include/generic-glibc/sys/kd.h index f59730d247691818eadfdd05ed3312d29ec0ebcd..88bbc25a35c934bce9f4dfd466e5f2281f894785 100644 --- a/lib/libc/include/generic-glibc/sys/kd.h +++ b/lib/libc/include/generic-glibc/sys/kd.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1996-2025 Free Software Foundation, Inc. +/* Copyright (C) 1996-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/sys/klog.h b/lib/libc/include/generic-glibc/sys/klog.h index 3d8531cc660599a30111d3ede324f665d1088dfd..ebc6fd7ea3bbfc887030e8a003f0180b66628d8c 100644 --- a/lib/libc/include/generic-glibc/sys/klog.h +++ b/lib/libc/include/generic-glibc/sys/klog.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1996-2025 Free Software Foundation, Inc. +/* Copyright (C) 1996-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/sys/mman.h b/lib/libc/include/generic-glibc/sys/mman.h index 3577564df0aa784f5b7b83cf83eadb2cfdcab483..f8acac917246e0060e208bfe93232b53e9b95ad2 100644 --- a/lib/libc/include/generic-glibc/sys/mman.h +++ b/lib/libc/include/generic-glibc/sys/mman.h @@ -1,5 +1,5 @@ /* Definitions for BSD-style memory management. - Copyright (C) 1994-2025 Free Software Foundation, Inc. + Copyright (C) 1994-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/sys/mount.h b/lib/libc/include/generic-glibc/sys/mount.h index fbb157810e243939be3b90310458ee8eec739f6e..bf226f5a738b6369caba0558d98357d3cb06e975 100644 --- a/lib/libc/include/generic-glibc/sys/mount.h +++ b/lib/libc/include/generic-glibc/sys/mount.h @@ -1,5 +1,5 @@ /* Header file for mounting/unmount Linux filesystems. - Copyright (C) 1996-2025 Free Software Foundation, Inc. + Copyright (C) 1996-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/sys/msg.h b/lib/libc/include/generic-glibc/sys/msg.h index 8c86c14cf89a75de16d088a3cdc332cba8844ec8..4c05ef89108fad09745bb59e8640f44eb71404a3 100644 --- a/lib/libc/include/generic-glibc/sys/msg.h +++ b/lib/libc/include/generic-glibc/sys/msg.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1995-2025 Free Software Foundation, Inc. +/* Copyright (C) 1995-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/sys/mtio.h b/lib/libc/include/generic-glibc/sys/mtio.h index 7c53ec28fe2e317cae8f4e4026fcfc9ed4eca7f8..cdb3899009e167ace832cfa92c9301062214401b 100644 --- a/lib/libc/include/generic-glibc/sys/mtio.h +++ b/lib/libc/include/generic-glibc/sys/mtio.h @@ -1,5 +1,5 @@ /* Structures and definitions for magnetic tape I/O control commands. - Copyright (C) 1996-2025 Free Software Foundation, Inc. + Copyright (C) 1996-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/sys/param.h b/lib/libc/include/generic-glibc/sys/param.h index e66c19eaae7ba99b1ac3cf04f12f636f6b372b6d..c4ea4544d45936f0661dd3c5e6457435bd5cb12d 100644 --- a/lib/libc/include/generic-glibc/sys/param.h +++ b/lib/libc/include/generic-glibc/sys/param.h @@ -1,5 +1,5 @@ /* Compatibility header for old-style Unix parameters and limits. - Copyright (C) 1995-2025 Free Software Foundation, Inc. + Copyright (C) 1995-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/sys/pci.h b/lib/libc/include/generic-glibc/sys/pci.h index ec6b7ce780c9722480dcfb3cb294610f7788373a..b5475dd02b24d3a68e27d2a24645cd572d81d551 100644 --- a/lib/libc/include/generic-glibc/sys/pci.h +++ b/lib/libc/include/generic-glibc/sys/pci.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1997-2025 Free Software Foundation, Inc. +/* Copyright (C) 1997-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/sys/perm.h b/lib/libc/include/generic-glibc/sys/perm.h index 5a95dce3845b5a357e044997081a5154861e6f69..e864375c1931be45f207ca3c9a021d897a8fb013 100644 --- a/lib/libc/include/generic-glibc/sys/perm.h +++ b/lib/libc/include/generic-glibc/sys/perm.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1996-2025 Free Software Foundation, Inc. +/* Copyright (C) 1996-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/sys/personality.h b/lib/libc/include/generic-glibc/sys/personality.h index 500950aa5ccab951b43ac4ea61fd1f008987498f..54510789bc5fe07ce95ae7759dbe013da3ff5c44 100644 --- a/lib/libc/include/generic-glibc/sys/personality.h +++ b/lib/libc/include/generic-glibc/sys/personality.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2002-2025 Free Software Foundation, Inc. +/* Copyright (C) 2002-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/sys/pidfd.h b/lib/libc/include/generic-glibc/sys/pidfd.h index 406bf2f5fb90028208fdf7c9bdc33ba372c53b18..3d9ab9430c72d2b947860d9fa6b9525a6ebc11aa 100644 --- a/lib/libc/include/generic-glibc/sys/pidfd.h +++ b/lib/libc/include/generic-glibc/sys/pidfd.h @@ -1,5 +1,5 @@ /* Wrapper for file descriptors that refers to a process functions. - Copyright (C) 2022-2025 Free Software Foundation, Inc. + Copyright (C) 2022-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or @@ -45,6 +45,64 @@ #define PIDFD_GET_USER_NAMESPACE _IO(PIDFS_IOCTL_MAGIC, 9) #define PIDFD_GET_UTS_NAMESPACE _IO(PIDFS_IOCTL_MAGIC, 10) +/* Sentinels to avoid allocating a file descriptor to refer to own process. */ +#define PIDFD_SELF_THREAD -10000 +#define PIDFD_SELF_THREAD_GROUP -10001 +#define PIDFD_SELF PIDFD_SELF_THREAD +#define PIDFD_SELF_PROCESS PIDFD_SELF_THREAD_GROUP + + +/* Flags for pidfd_info. */ + +/* Always returned, even if not requested */ +#define PIDFD_INFO_PID (1UL << 0) +/* Always returned, even if not requested */ +#define PIDFD_INFO_CREDS (1UL << 1) +/* Always returned if available, even if not requested */ +#define PIDFD_INFO_CGROUPID (1UL << 2) +/* Only returned if requested. */ +#define PIDFD_INFO_EXIT (1UL << 3) +/* Only returned if requested. */ +#define PIDFD_INFO_COREDUMP (1UL << 4) + + +/* Value for coredump_mask in pidfd_info. Only valid if PIDFD_INFO_COREDUMP + is set in mask. */ + +/* Did crash and... */ +#define PIDFD_COREDUMPED (1U << 0) +/* coredumping generation was skipped. */ +#define PIDFD_COREDUMP_SKIP (1U << 1) +/* coredump was done as the user. */ +#define PIDFD_COREDUMP_USER (1U << 2) +/* coredump was done as root. */ +#define PIDFD_COREDUMP_ROOT (1U << 3) + +struct pidfd_info +{ + __uint64_t mask; + __uint64_t cgroupid; + __uint32_t pid; + __uint32_t tgid; + __uint32_t ppid; + __uint32_t ruid; + __uint32_t rgid; + __uint32_t euid; + __uint32_t egid; + __uint32_t suid; + __uint32_t sgid; + __uint32_t fsuid; + __uint32_t fsgid; + __int32_t exit_code; + __uint32_t coredump_mask; + __uint32_t __spare1; +}; + +/* sizeof first published struct */ +#define PIDFD_INFO_SIZE_VER0 64 + +#define PIDFD_GET_INFO _IOWR(PIDFS_IOCTL_MAGIC, 11, struct pidfd_info) + /* Returns a file descriptor that refers to the process PID. The close-on-exec is set on the file descriptor. */ extern int pidfd_open (__pid_t __pid, unsigned int __flags) __THROW; diff --git a/lib/libc/include/generic-glibc/sys/platform/ppc.h b/lib/libc/include/generic-glibc/sys/platform/ppc.h index 9a4dbd6c3663525df31749143824eb72dd9fda6a..6e7df94235d78f7b3ad6c8644301f8d0441a264e 100644 --- a/lib/libc/include/generic-glibc/sys/platform/ppc.h +++ b/lib/libc/include/generic-glibc/sys/platform/ppc.h @@ -1,5 +1,5 @@ /* Facilities specific to the PowerPC architecture - Copyright (C) 2012-2025 Free Software Foundation, Inc. + Copyright (C) 2012-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/sys/platform/x86.h b/lib/libc/include/generic-glibc/sys/platform/x86.h index 40c91f375f864e6464ad21baa9a5dc60f47856c8..6ef7dfcec3ed5c8ad1135c000d73bac29892566c 100644 --- a/lib/libc/include/generic-glibc/sys/platform/x86.h +++ b/lib/libc/include/generic-glibc/sys/platform/x86.h @@ -1,6 +1,6 @@ /* Data structure for x86 CPU features. This file is part of the GNU C Library. - Copyright (C) 2008-2025 Free Software Foundation, Inc. + Copyright (C) 2008-2026 Free Software Foundation, Inc. The GNU C Library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public diff --git a/lib/libc/include/generic-glibc/sys/poll.h b/lib/libc/include/generic-glibc/sys/poll.h index 7c4a6caecd48272ff9802368218446ae30939e96..d42c12762217bbe57763f8a43254c745245e0712 100644 --- a/lib/libc/include/generic-glibc/sys/poll.h +++ b/lib/libc/include/generic-glibc/sys/poll.h @@ -1,5 +1,5 @@ /* Compatibility definitions for System V `poll' interface. - Copyright (C) 1994-2025 Free Software Foundation, Inc. + Copyright (C) 1994-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/sys/prctl.h b/lib/libc/include/generic-glibc/sys/prctl.h index 7b9871fa28f3c4fb5953206ed1637d23372c7d3d..e83d37d64d631e5cdc2f048b7ab2043e65353ea1 100644 --- a/lib/libc/include/generic-glibc/sys/prctl.h +++ b/lib/libc/include/generic-glibc/sys/prctl.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1997-2025 Free Software Foundation, Inc. +/* Copyright (C) 1997-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/sys/procfs.h b/lib/libc/include/generic-glibc/sys/procfs.h index 03b03eb154706c077e57fa8e0ef87dcd571b8636..0cb97976cb49c98feef0d864523ecc5a5a49190c 100644 --- a/lib/libc/include/generic-glibc/sys/procfs.h +++ b/lib/libc/include/generic-glibc/sys/procfs.h @@ -1,5 +1,5 @@ /* Definitions for core files and libthread_db. Generic Linux version. - Copyright (C) 1996-2025 Free Software Foundation, Inc. + Copyright (C) 1996-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. diff --git a/lib/libc/include/generic-glibc/sys/profil.h b/lib/libc/include/generic-glibc/sys/profil.h index aa62432612d2a94fb06875deb888036de434be94..77650613858832b6712c934f976e8eced5aeef6f 100644 --- a/lib/libc/include/generic-glibc/sys/profil.h +++ b/lib/libc/include/generic-glibc/sys/profil.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2001-2025 Free Software Foundation, Inc. +/* Copyright (C) 2001-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/sys/ptrace.h b/lib/libc/include/generic-glibc/sys/ptrace.h index 6a8779c2a3709d9c04a373c081b2905d10230338..ff5510d07fdd87b709875a5ed64eec3162675354 100644 --- a/lib/libc/include/generic-glibc/sys/ptrace.h +++ b/lib/libc/include/generic-glibc/sys/ptrace.h @@ -1,5 +1,5 @@ /* `ptrace' debugger support interface. Linux version. - Copyright (C) 1996-2025 Free Software Foundation, Inc. + Copyright (C) 1996-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. diff --git a/lib/libc/include/generic-glibc/sys/quota.h b/lib/libc/include/generic-glibc/sys/quota.h index e057a86392ac58542e5c356b95d40c9727415af4..f9f76f3113d245723fae2bfe9f206a12c99c703e 100644 --- a/lib/libc/include/generic-glibc/sys/quota.h +++ b/lib/libc/include/generic-glibc/sys/quota.h @@ -1,5 +1,5 @@ /* This just represents the non-kernel parts of . - Copyright (C) 1998-2025 Free Software Foundation, Inc. + Copyright (C) 1998-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/sys/random.h b/lib/libc/include/generic-glibc/sys/random.h index b1df7f43385731e1fe014593a4c6f7cb0d30089b..7a2928ba5f52029a80a547f0cf8489334999ca2f 100644 --- a/lib/libc/include/generic-glibc/sys/random.h +++ b/lib/libc/include/generic-glibc/sys/random.h @@ -1,5 +1,5 @@ /* Interfaces for obtaining random bytes. - Copyright (C) 2016-2025 Free Software Foundation, Inc. + Copyright (C) 2016-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/sys/raw.h b/lib/libc/include/generic-glibc/sys/raw.h index a215a84c91d7db36d1408175a59d5332581dda9b..e38181803449cbf45f32f7a4eba1ce929384af93 100644 --- a/lib/libc/include/generic-glibc/sys/raw.h +++ b/lib/libc/include/generic-glibc/sys/raw.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1999-2025 Free Software Foundation, Inc. +/* Copyright (C) 1999-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/sys/reboot.h b/lib/libc/include/generic-glibc/sys/reboot.h index 95819b9cfb5261ed33d5047783b75a27b81a62ba..04a13f0b1d1d9cf764a95cdad51f51048e550bf0 100644 --- a/lib/libc/include/generic-glibc/sys/reboot.h +++ b/lib/libc/include/generic-glibc/sys/reboot.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1996-2025 Free Software Foundation, Inc. +/* Copyright (C) 1996-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/sys/reg.h b/lib/libc/include/generic-glibc/sys/reg.h index 5cf5759ef52c931bf05cd1724afe6b4173ccc1e8..819876e9cf786ff8d760fcb38686bfd29f178125 100644 --- a/lib/libc/include/generic-glibc/sys/reg.h +++ b/lib/libc/include/generic-glibc/sys/reg.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2001-2025 Free Software Foundation, Inc. +/* Copyright (C) 2001-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/sys/regdef.h b/lib/libc/include/generic-glibc/sys/regdef.h index 947681c6db4bcf40b40d8a54d11fd1efd4f39d88..82bf4f9558f2122172222bd099af04a3d6c03eec 100644 --- a/lib/libc/include/generic-glibc/sys/regdef.h +++ b/lib/libc/include/generic-glibc/sys/regdef.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1997-2025 Free Software Foundation, Inc. +/* Copyright (C) 1997-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/sys/resource.h b/lib/libc/include/generic-glibc/sys/resource.h index 13b06c36e8e2acbb3b354778c72c48e2b1f2a2b2..0752d50d52ecc5b0e115fb9d137b1f2c8f828c1b 100644 --- a/lib/libc/include/generic-glibc/sys/resource.h +++ b/lib/libc/include/generic-glibc/sys/resource.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1992-2025 Free Software Foundation, Inc. +/* Copyright (C) 1992-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/sys/rseq.h b/lib/libc/include/generic-glibc/sys/rseq.h index 81856aeddf39799e31bbd90f68e3cdcc405aedcc..f6f18e1b4eb8167a0141c8a27963c343dd5055fc 100644 --- a/lib/libc/include/generic-glibc/sys/rseq.h +++ b/lib/libc/include/generic-glibc/sys/rseq.h @@ -1,5 +1,5 @@ /* Restartable Sequences exported symbols. Linux header. - Copyright (C) 2021-2025 Free Software Foundation, Inc. + Copyright (C) 2021-2026 Free Software Foundation, Inc. The GNU C Library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public diff --git a/lib/libc/include/generic-glibc/sys/select.h b/lib/libc/include/generic-glibc/sys/select.h index cd8c69ee6d9680d3f7e37eeb09fb9053e62ac170..7308c98432e5ea475b46e654a70d098c8e7be52f 100644 --- a/lib/libc/include/generic-glibc/sys/select.h +++ b/lib/libc/include/generic-glibc/sys/select.h @@ -1,5 +1,5 @@ /* `fd_set' type and related macros, and `select'/`pselect' declarations. - Copyright (C) 1996-2025 Free Software Foundation, Inc. + Copyright (C) 1996-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/sys/sem.h b/lib/libc/include/generic-glibc/sys/sem.h index f0d3b00dbe71eec7b9c323008798ab197d475d6d..7c362679feb52e4d474ed6fe27acb359e7d50ead 100644 --- a/lib/libc/include/generic-glibc/sys/sem.h +++ b/lib/libc/include/generic-glibc/sys/sem.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1995-2025 Free Software Foundation, Inc. +/* Copyright (C) 1995-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/sys/sendfile.h b/lib/libc/include/generic-glibc/sys/sendfile.h index 46b0f3cc8dafbdebad9c3e573906e3ff7ddea706..a980436ceadd12af19beccee9ce4c7a6cbae7290 100644 --- a/lib/libc/include/generic-glibc/sys/sendfile.h +++ b/lib/libc/include/generic-glibc/sys/sendfile.h @@ -1,5 +1,5 @@ /* sendfile -- copy data directly from one file descriptor to another - Copyright (C) 1998-2025 Free Software Foundation, Inc. + Copyright (C) 1998-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/sys/shm.h b/lib/libc/include/generic-glibc/sys/shm.h index 54ff00ee99d07ca658340dfb76a12a7a6ec23f1d..c7b6bc19d2b7d91a7f9c7dc031ec4b185f496514 100644 --- a/lib/libc/include/generic-glibc/sys/shm.h +++ b/lib/libc/include/generic-glibc/sys/shm.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1995-2025 Free Software Foundation, Inc. +/* Copyright (C) 1995-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/sys/signalfd.h b/lib/libc/include/generic-glibc/sys/signalfd.h index 33d06ff14742d9dfcd7af1cb64337f31a81b0abc..c8da4d7cfd9ec348d505fd367918102fbe376d23 100644 --- a/lib/libc/include/generic-glibc/sys/signalfd.h +++ b/lib/libc/include/generic-glibc/sys/signalfd.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2007-2025 Free Software Foundation, Inc. +/* Copyright (C) 2007-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/sys/single_threaded.h b/lib/libc/include/generic-glibc/sys/single_threaded.h index 5fec9a477c4896039776345c2458b1fa144ba692..1e3dbac23038def741abea8a27cfd181fab9709b 100644 --- a/lib/libc/include/generic-glibc/sys/single_threaded.h +++ b/lib/libc/include/generic-glibc/sys/single_threaded.h @@ -1,5 +1,5 @@ /* Support for single-thread optimizations. - Copyright (C) 2020-2025 Free Software Foundation, Inc. + Copyright (C) 2020-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/sys/socket.h b/lib/libc/include/generic-glibc/sys/socket.h index 890b8cd2e6792788a30e2c5d2502c4f5203056bc..db2c0d50cd58b9de9f0706e58de3b9d50314899b 100644 --- a/lib/libc/include/generic-glibc/sys/socket.h +++ b/lib/libc/include/generic-glibc/sys/socket.h @@ -1,5 +1,5 @@ /* Declarations of socket constants, types, and functions. - Copyright (C) 1991-2025 Free Software Foundation, Inc. + Copyright (C) 1991-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/sys/stat.h b/lib/libc/include/generic-glibc/sys/stat.h index 68c2c889e88307f1dfebcf868a83761333b74337..38d6719324e299a2a8a1e7c9b6a9bae0784d1fc4 100644 --- a/lib/libc/include/generic-glibc/sys/stat.h +++ b/lib/libc/include/generic-glibc/sys/stat.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1991-2025 Free Software Foundation, Inc. +/* Copyright (C) 1991-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/sys/statfs.h b/lib/libc/include/generic-glibc/sys/statfs.h index 1e7bd78d22512538978fb93203c2c834ee80b0f7..c24106980a9525ad513c27d72db8685194a2005b 100644 --- a/lib/libc/include/generic-glibc/sys/statfs.h +++ b/lib/libc/include/generic-glibc/sys/statfs.h @@ -1,5 +1,5 @@ /* Definitions for getting information about a filesystem. - Copyright (C) 1996-2025 Free Software Foundation, Inc. + Copyright (C) 1996-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/sys/statvfs.h b/lib/libc/include/generic-glibc/sys/statvfs.h index 599ac139f4fa92f5c5784c55c289e6bc5c236a95..5877e0d122f11ef9b2c766c5b719a43a55fa9d2a 100644 --- a/lib/libc/include/generic-glibc/sys/statvfs.h +++ b/lib/libc/include/generic-glibc/sys/statvfs.h @@ -1,5 +1,5 @@ /* Definitions for getting information about a filesystem. - Copyright (C) 1998-2025 Free Software Foundation, Inc. + Copyright (C) 1998-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/sys/swap.h b/lib/libc/include/generic-glibc/sys/swap.h index f67cfebb1f103561a1310536a5ee32d1697667fb..a85f64a57a4604a50906ec4c99bd039a184601d3 100644 --- a/lib/libc/include/generic-glibc/sys/swap.h +++ b/lib/libc/include/generic-glibc/sys/swap.h @@ -1,5 +1,5 @@ /* Calls to enable and disable swapping on specified locations. Linux version. - Copyright (C) 1996-2025 Free Software Foundation, Inc. + Copyright (C) 1996-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/sys/syscall.h b/lib/libc/include/generic-glibc/sys/syscall.h index 20bebd9b72a57de841b38d8995159535e94abdb9..1b589aa1383f67de2680840ef21aa54381bbc193 100644 --- a/lib/libc/include/generic-glibc/sys/syscall.h +++ b/lib/libc/include/generic-glibc/sys/syscall.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1995-2025 Free Software Foundation, Inc. +/* Copyright (C) 1995-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/sys/sysinfo.h b/lib/libc/include/generic-glibc/sys/sysinfo.h index 2c1456dd16f212807c0a0955cf88c464235466d7..41634f45dc4c4bc52f42e94f0e8c2997f5fffcef 100644 --- a/lib/libc/include/generic-glibc/sys/sysinfo.h +++ b/lib/libc/include/generic-glibc/sys/sysinfo.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1996-2025 Free Software Foundation, Inc. +/* Copyright (C) 1996-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/sys/sysmacros.h b/lib/libc/include/generic-glibc/sys/sysmacros.h index 08c384dbfae4c1042ac215803e635c4e2d57a95e..54ae56a2cc8cc6baf9040e135953e204d7696fee 100644 --- a/lib/libc/include/generic-glibc/sys/sysmacros.h +++ b/lib/libc/include/generic-glibc/sys/sysmacros.h @@ -1,5 +1,5 @@ /* Definitions of macros to access `dev_t' values. - Copyright (C) 1996-2025 Free Software Foundation, Inc. + Copyright (C) 1996-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/sys/sysmips.h b/lib/libc/include/generic-glibc/sys/sysmips.h index c5ef1a5987041c9557d02d094edc64141f7be739..0b25a9fbbc89d48b6bc9f7c2d77ea747d282b02e 100644 --- a/lib/libc/include/generic-glibc/sys/sysmips.h +++ b/lib/libc/include/generic-glibc/sys/sysmips.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1995-2025 Free Software Foundation, Inc. +/* Copyright (C) 1995-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/sys/tas.h b/lib/libc/include/generic-glibc/sys/tas.h index 2f9a9bb665b3aaf767c2511a58fb2605a4b9e7b6..5af1c307ce8966ea98a933108874cc34642a5b66 100644 --- a/lib/libc/include/generic-glibc/sys/tas.h +++ b/lib/libc/include/generic-glibc/sys/tas.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2000-2025 Free Software Foundation, Inc. +/* Copyright (C) 2000-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/sys/time.h b/lib/libc/include/generic-glibc/sys/time.h index 7f9e9e81515f82af290fd41f800f9ead19b47679..c07530e3a2e14d2f0840775bbd82773f4532a6de 100644 --- a/lib/libc/include/generic-glibc/sys/time.h +++ b/lib/libc/include/generic-glibc/sys/time.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1991-2025 Free Software Foundation, Inc. +/* Copyright (C) 1991-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/sys/timeb.h b/lib/libc/include/generic-glibc/sys/timeb.h index 30cef7dcb76081005a35e00b54020878303809a4..d7c8381c3123fc59430636f274ed5b8527597722 100644 --- a/lib/libc/include/generic-glibc/sys/timeb.h +++ b/lib/libc/include/generic-glibc/sys/timeb.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1994-2025 Free Software Foundation, Inc. +/* Copyright (C) 1994-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/sys/timerfd.h b/lib/libc/include/generic-glibc/sys/timerfd.h index 4fcf48d27f961a8f1d07ef6d18f64355db2c7233..f72a5e139bb9b572bccca422ccda83b652fae2ac 100644 --- a/lib/libc/include/generic-glibc/sys/timerfd.h +++ b/lib/libc/include/generic-glibc/sys/timerfd.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2008-2025 Free Software Foundation, Inc. +/* Copyright (C) 2008-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/sys/times.h b/lib/libc/include/generic-glibc/sys/times.h index eccd085d85310b2af9d9198b332d4e7d451038fe..8cd21a5bbcc1a05fdbf32144acfbe6e3be43be62 100644 --- a/lib/libc/include/generic-glibc/sys/times.h +++ b/lib/libc/include/generic-glibc/sys/times.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1991-2025 Free Software Foundation, Inc. +/* Copyright (C) 1991-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/sys/timex.h b/lib/libc/include/generic-glibc/sys/timex.h index 40e405c0adcb643c9bd44d6cb81593c6db9b6268..3f8c65443fd4769bbe9e0996ea351bba97c2ccf1 100644 --- a/lib/libc/include/generic-glibc/sys/timex.h +++ b/lib/libc/include/generic-glibc/sys/timex.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1995-2025 Free Software Foundation, Inc. +/* Copyright (C) 1995-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/sys/types.h b/lib/libc/include/generic-glibc/sys/types.h index 3e0c7f44e15b59170ceb58f9f956c6ffce173d9a..595ac4e9e0575a45430a043c6c16159f82ddf77f 100644 --- a/lib/libc/include/generic-glibc/sys/types.h +++ b/lib/libc/include/generic-glibc/sys/types.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1991-2025 Free Software Foundation, Inc. +/* Copyright (C) 1991-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/sys/ucontext.h b/lib/libc/include/generic-glibc/sys/ucontext.h index 90543bcf30ebbddfdc0627f89438aef709cb78b5..3faf674935dafa18d104764cc0587ce20d4228f5 100644 --- a/lib/libc/include/generic-glibc/sys/ucontext.h +++ b/lib/libc/include/generic-glibc/sys/ucontext.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1997-2025 Free Software Foundation, Inc. This file is part of the GNU C Library. +/* Copyright (C) 1997-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public diff --git a/lib/libc/include/generic-glibc/sys/uio.h b/lib/libc/include/generic-glibc/sys/uio.h index bfebc145f26db8afd010774d07f06ad5786451ca..856c15c3a0fb95fc3132a3f47e53c4ecfd9502b9 100644 --- a/lib/libc/include/generic-glibc/sys/uio.h +++ b/lib/libc/include/generic-glibc/sys/uio.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1991-2025 Free Software Foundation, Inc. +/* Copyright (C) 1991-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/sys/un.h b/lib/libc/include/generic-glibc/sys/un.h index 0c77b772616e81f89c541d888191312082cacc56..bb7f12b96d99479f6a039c02e943749f90ec6f00 100644 --- a/lib/libc/include/generic-glibc/sys/un.h +++ b/lib/libc/include/generic-glibc/sys/un.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1991-2025 Free Software Foundation, Inc. +/* Copyright (C) 1991-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/sys/user.h b/lib/libc/include/generic-glibc/sys/user.h index 08a54ee5a4c4e465d3444ac39a94f41a5433924a..64d46ba5b24dac6d3f65ec0cf49f86f2fbca47cf 100644 --- a/lib/libc/include/generic-glibc/sys/user.h +++ b/lib/libc/include/generic-glibc/sys/user.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2002-2025 Free Software Foundation, Inc. +/* Copyright (C) 2002-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/sys/utsname.h b/lib/libc/include/generic-glibc/sys/utsname.h index c48a67a105d469b76c561f5fc284a8e5882a5bbf..c9daa88cf94a0096a6e0453a058bfdfd02c64d59 100644 --- a/lib/libc/include/generic-glibc/sys/utsname.h +++ b/lib/libc/include/generic-glibc/sys/utsname.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1991-2025 Free Software Foundation, Inc. +/* Copyright (C) 1991-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/sys/vlimit.h b/lib/libc/include/generic-glibc/sys/vlimit.h index 179271da656b936ffdfe19c91854a672300e3631..4730fc37daf4c7e73ebb56c355e5390c699af2a3 100644 --- a/lib/libc/include/generic-glibc/sys/vlimit.h +++ b/lib/libc/include/generic-glibc/sys/vlimit.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1991-2025 Free Software Foundation, Inc. +/* Copyright (C) 1991-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/sys/vm86.h b/lib/libc/include/generic-glibc/sys/vm86.h index ff38ebedaffa156ec50db044282e271143348900..44d4cdd19959d4e10c8bcf2acab1ee214c261556 100644 --- a/lib/libc/include/generic-glibc/sys/vm86.h +++ b/lib/libc/include/generic-glibc/sys/vm86.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1996-2025 Free Software Foundation, Inc. +/* Copyright (C) 1996-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/sys/wait.h b/lib/libc/include/generic-glibc/sys/wait.h index f11f89f7cb62784c7753e00359ab45913f2813a3..23d689a05166f4fb3fc32f26196e346f0d2af746 100644 --- a/lib/libc/include/generic-glibc/sys/wait.h +++ b/lib/libc/include/generic-glibc/sys/wait.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1991-2025 Free Software Foundation, Inc. +/* Copyright (C) 1991-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/sys/xattr.h b/lib/libc/include/generic-glibc/sys/xattr.h index 669781b6a2afa06e885e28ea99e436e9791614d2..736074937f3c530a7959842e2d1b62ea31239be4 100644 --- a/lib/libc/include/generic-glibc/sys/xattr.h +++ b/lib/libc/include/generic-glibc/sys/xattr.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2002-2025 Free Software Foundation, Inc. +/* Copyright (C) 2002-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/tar.h b/lib/libc/include/generic-glibc/tar.h index 68dddef340f123fcdb303b1f2b29d033b7a2810c..f2da3fd9eb90adae522a0de665db775401cb4a46 100644 --- a/lib/libc/include/generic-glibc/tar.h +++ b/lib/libc/include/generic-glibc/tar.h @@ -1,5 +1,5 @@ /* Extended tar format from POSIX.1. - Copyright (C) 1992-2025 Free Software Foundation, Inc. + Copyright (C) 1992-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/termios.h b/lib/libc/include/generic-glibc/termios.h index ef7b166b4c422e51270f1409f7c2abf8d546a797..cfdf0dfad72de68d495f64e7d65f518b018046c7 100644 --- a/lib/libc/include/generic-glibc/termios.h +++ b/lib/libc/include/generic-glibc/termios.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1991-2025 Free Software Foundation, Inc. +/* Copyright (C) 1991-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/tgmath.h b/lib/libc/include/generic-glibc/tgmath.h index 4eae43da5e045050d1b0652a308e74ae6c38fee0..d2c5da3436163c2d1cfab0b65005dbcf9d322df0 100644 --- a/lib/libc/include/generic-glibc/tgmath.h +++ b/lib/libc/include/generic-glibc/tgmath.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1997-2025 Free Software Foundation, Inc. +/* Copyright (C) 1997-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or @@ -30,6 +30,10 @@ #include #include +#if __GLIBC_USE (ISOC23) +# define __STDC_VERSION_TGMATH_H__ 202311L +#endif + /* There are two variant implementations of type-generic macros in this file: one for GCC 8 and later, using __builtin_tgmath and @@ -386,7 +390,7 @@ # define __TGMATH_TERNARY_REAL_ONLY(Val1, Val2, Val3, Fct) \ __TGMATH_3 (Fct, (Val1), (Val2), (Val3)) # endif -# define __TGMATH_TERNARY_FIRST_REAL_RET_ONLY(Val1, Val2, Val3, Fct) \ +# define __TGMATH_TERNARY_FIRST_REAL_ONLY(Val1, Val2, Val3, Fct) \ __TGMATH_3 (Fct, (Val1), (Val2), (Val3)) # define __TGMATH_UNARY_REAL_IMAG(Val, Fct, Cfct) \ __TGMATH_1C (Fct, Cfct, (Val)) @@ -520,14 +524,17 @@ # endif # if !__HAVE_BUILTIN_TGMATH -# define __TGMATH_TERNARY_FIRST_REAL_RET_ONLY(Val1, Val2, Val3, Fct) \ - (__extension__ ((sizeof (+(Val1)) == sizeof (double) \ - || __builtin_classify_type (Val1) != 8) \ - ? Fct (Val1, Val2, Val3) \ - : (sizeof (+(Val1)) == sizeof (float)) \ - ? Fct##f (Val1, Val2, Val3) \ - : __TGMATH_F128 ((Val1), Fct, (Val1, Val2, Val3)) \ - __tgml(Fct) (Val1, Val2, Val3))) +# define __TGMATH_TERNARY_FIRST_REAL_ONLY(Val1, Val2, Val3, Fct) \ + (__extension__ ((sizeof (+(Val1)) == sizeof (double) \ + || __builtin_classify_type (Val1) != 8) \ + ? (__tgmath_real_type (Val1)) Fct (Val1, Val2, Val3) \ + : (sizeof (+(Val1)) == sizeof (float)) \ + ? (__tgmath_real_type (Val1)) Fct##f (Val1, Val2, Val3) \ + : __TGMATH_F128 ((Val1), \ + (__tgmath_real_type (Val1)) Fct, \ + (Val1, Val2, Val3)) \ + (__tgmath_real_type (Val1)) __tgml(Fct) (Val1, Val2, \ + Val3))) /* XXX This definition has to be changed as soon as the compiler understands the imaginary keyword. */ @@ -1056,17 +1063,17 @@ /* Round X to nearest integer value, rounding halfway cases to even. */ # define roundeven(Val) __TGMATH_UNARY_REAL_ONLY (Val, roundeven) -# define fromfp(Val1, Val2, Val3) \ - __TGMATH_TERNARY_FIRST_REAL_RET_ONLY (Val1, Val2, Val3, fromfp) +# define fromfp(Val1, Val2, Val3) \ + __TGMATH_TERNARY_FIRST_REAL_ONLY (Val1, Val2, Val3, fromfp) -# define ufromfp(Val1, Val2, Val3) \ - __TGMATH_TERNARY_FIRST_REAL_RET_ONLY (Val1, Val2, Val3, ufromfp) +# define ufromfp(Val1, Val2, Val3) \ + __TGMATH_TERNARY_FIRST_REAL_ONLY (Val1, Val2, Val3, ufromfp) -# define fromfpx(Val1, Val2, Val3) \ - __TGMATH_TERNARY_FIRST_REAL_RET_ONLY (Val1, Val2, Val3, fromfpx) +# define fromfpx(Val1, Val2, Val3) \ + __TGMATH_TERNARY_FIRST_REAL_ONLY (Val1, Val2, Val3, fromfpx) -# define ufromfpx(Val1, Val2, Val3) \ - __TGMATH_TERNARY_FIRST_REAL_RET_ONLY (Val1, Val2, Val3, ufromfpx) +# define ufromfpx(Val1, Val2, Val3) \ + __TGMATH_TERNARY_FIRST_REAL_ONLY (Val1, Val2, Val3, ufromfpx) /* Like ilogb, but returning long int. */ # define llogb(Val) __TGMATH_UNARY_REAL_RET_ONLY (Val, llogb) diff --git a/lib/libc/include/generic-glibc/thread_db.h b/lib/libc/include/generic-glibc/thread_db.h index 6097635a684640b5edb19b37a4b720025c644807..cf14aaf04b41fd8439e410bc3acdcc0ed2eed933 100644 --- a/lib/libc/include/generic-glibc/thread_db.h +++ b/lib/libc/include/generic-glibc/thread_db.h @@ -1,5 +1,5 @@ /* thread_db.h -- interface to libthread_db.so library for debugging -lpthread - Copyright (C) 1999-2025 Free Software Foundation, Inc. + Copyright (C) 1999-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/threads.h b/lib/libc/include/generic-glibc/threads.h index 44febdd5e6118501c36332b378e724f3f50084e5..46982cb74482a030a2b3518243a53197764bc032 100644 --- a/lib/libc/include/generic-glibc/threads.h +++ b/lib/libc/include/generic-glibc/threads.h @@ -1,5 +1,5 @@ /* ISO C11 Standard: 7.26 - Thread support library . - Copyright (C) 2018-2025 Free Software Foundation, Inc. + Copyright (C) 2018-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or @@ -31,6 +31,7 @@ __BEGIN_DECLS #include +#include #include #if (!defined __STDC_VERSION__ \ @@ -64,9 +65,6 @@ enum mtx_timed = 2 }; -typedef __once_flag once_flag; -#define ONCE_FLAG_INIT __ONCE_FLAG_INIT - typedef union { char __size[__SIZEOF_PTHREAD_MUTEX_T]; diff --git a/lib/libc/include/generic-glibc/time.h b/lib/libc/include/generic-glibc/time.h index dae39625df6e158eda19e14c413aaae9d0b5eabb..10e59b0fdece92e39220719b33d64774d2e254f6 100644 --- a/lib/libc/include/generic-glibc/time.h +++ b/lib/libc/include/generic-glibc/time.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1991-2025 Free Software Foundation, Inc. +/* Copyright (C) 1991-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or @@ -28,6 +28,10 @@ #define __need_NULL #include +#if __GLIBC_USE (ISOC23) +# define __STDC_VERSION_TIME_H__ 202311L +#endif + /* This defines CLOCKS_PER_SEC, which is the number of processor clock ticks per second, and possibly a number of other constants. */ #include @@ -64,6 +68,11 @@ typedef __pid_t pid_t; /* Time base values for timespec_get. */ # define TIME_UTC 1 #endif +#if __GLIBC_USE (ISOC23) +# define TIME_MONOTONIC 2 +# define TIME_ACTIVE 3 +# define TIME_THREAD_ACTIVE 4 +#endif __BEGIN_DECLS diff --git a/lib/libc/include/generic-glibc/uchar.h b/lib/libc/include/generic-glibc/uchar.h index ba74f0f65b57a2ddc3e2e1ec05df35da460d2a33..d982dbd2d4de91889b45c0e65dda6f62d975859d 100644 --- a/lib/libc/include/generic-glibc/uchar.h +++ b/lib/libc/include/generic-glibc/uchar.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2011-2025 Free Software Foundation, Inc. +/* Copyright (C) 2011-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or @@ -31,6 +31,10 @@ #include #include +#if __GLIBC_USE (ISOC23) +# define __STDC_VERSION_UCHAR_H__ 202311L +#endif + /* Declare the C23 char8_t typedef in C23 modes, but only if the C++ __cpp_char8_t feature test macro is not defined. */ #if __GLIBC_USE (ISOC23) && !defined __cpp_char8_t diff --git a/lib/libc/include/generic-glibc/ucontext.h b/lib/libc/include/generic-glibc/ucontext.h index a5335fb044c0f8ec72d99d2ed88cdfee4459e5fb..647139a8d2f70e80722090f7b05fe562d6129296 100644 --- a/lib/libc/include/generic-glibc/ucontext.h +++ b/lib/libc/include/generic-glibc/ucontext.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1997-2025 Free Software Foundation, Inc. +/* Copyright (C) 1997-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/ulimit.h b/lib/libc/include/generic-glibc/ulimit.h index 538a824196e31034615617222d9aff65df0bfe3d..2db9cd138fbd339bf5919f22945db5d3eadc7f8e 100644 --- a/lib/libc/include/generic-glibc/ulimit.h +++ b/lib/libc/include/generic-glibc/ulimit.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1997-2025 Free Software Foundation, Inc. +/* Copyright (C) 1997-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/unistd.h b/lib/libc/include/generic-glibc/unistd.h index 6aaa1bfa90b37f2ad0071fe5eb3a5fb76623e1cd..84c8077a45220b63e3ee082b9b595252e7d7da7c 100644 --- a/lib/libc/include/generic-glibc/unistd.h +++ b/lib/libc/include/generic-glibc/unistd.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1991-2025 Free Software Foundation, Inc. +/* Copyright (C) 1991-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/utime.h b/lib/libc/include/generic-glibc/utime.h index 104fb07a59dc52dedc5f426c4613dcd08acd9fc9..09c1dd44add59347bd02646fa653920a5cdee718 100644 --- a/lib/libc/include/generic-glibc/utime.h +++ b/lib/libc/include/generic-glibc/utime.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1991-2025 Free Software Foundation, Inc. +/* Copyright (C) 1991-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/utmp.h b/lib/libc/include/generic-glibc/utmp.h index 7d9d41a12dda15fcd1736466eab00b42255e8b44..a6cf1db1da5058dd71865c87ff174901f2f52f92 100644 --- a/lib/libc/include/generic-glibc/utmp.h +++ b/lib/libc/include/generic-glibc/utmp.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1993-2025 Free Software Foundation, Inc. +/* Copyright (C) 1993-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/utmpx.h b/lib/libc/include/generic-glibc/utmpx.h index 2409d6049d1d6ced24d7c4708a7166ea03b95c7a..708f5b5e9ffd40c9d01912d1acf1ea198fa1eb30 100644 --- a/lib/libc/include/generic-glibc/utmpx.h +++ b/lib/libc/include/generic-glibc/utmpx.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1997-2025 Free Software Foundation, Inc. +/* Copyright (C) 1997-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/values.h b/lib/libc/include/generic-glibc/values.h index af02e71c9a9076e1ac37eff944c376f7334ac4ee..037e0ff7361ff7fa767903c72c768f17bb97febe 100644 --- a/lib/libc/include/generic-glibc/values.h +++ b/lib/libc/include/generic-glibc/values.h @@ -1,5 +1,5 @@ /* Old compatibility names for and constants. - Copyright (C) 1995-2025 Free Software Foundation, Inc. + Copyright (C) 1995-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/wchar.h b/lib/libc/include/generic-glibc/wchar.h index b8fb4b832bef999d106f07ec8d0a9a1b14fe1630..ea02fbe93b8b7b06a547411da8573701c63711c5 100644 --- a/lib/libc/include/generic-glibc/wchar.h +++ b/lib/libc/include/generic-glibc/wchar.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1995-2025 Free Software Foundation, Inc. +/* Copyright (C) 1995-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or @@ -60,6 +60,10 @@ typedef __gnuc_va_list va_list; # include #endif +#if __GLIBC_USE (ISOC23) +# define __STDC_VERSION_WCHAR_H__ 202311L +#endif + /* Tell the caller that we provide correct C++ prototypes. */ #if defined __cplusplus && __GNUC_PREREQ (4, 4) # define __CORRECT_ISO_CPP_WCHAR_H_PROTO @@ -188,6 +192,10 @@ extern "C++" const wchar_t *wcschr (const wchar_t *__wcs, wchar_t __wc) #else extern wchar_t *wcschr (const wchar_t *__wcs, wchar_t __wc) __THROW __attribute_pure__; +# if __GLIBC_USE (ISOC23) && defined __glibc_const_generic && !defined _LIBC +# define wcschr(WCS, WC) \ + __glibc_const_generic (WCS, const wchar_t *, wcschr (WCS, WC)) +# endif #endif /* Find the last occurrence of WC in WCS. */ #ifdef __CORRECT_ISO_CPP_WCHAR_H_PROTO @@ -198,6 +206,10 @@ extern "C++" const wchar_t *wcsrchr (const wchar_t *__wcs, wchar_t __wc) #else extern wchar_t *wcsrchr (const wchar_t *__wcs, wchar_t __wc) __THROW __attribute_pure__; +# if __GLIBC_USE (ISOC23) && defined __glibc_const_generic && !defined _LIBC +# define wcsrchr(WCS, WC) \ + __glibc_const_generic (WCS, const wchar_t *, wcsrchr (WCS, WC)) +# endif #endif #ifdef __USE_GNU @@ -225,6 +237,10 @@ extern "C++" const wchar_t *wcspbrk (const wchar_t *__wcs, #else extern wchar_t *wcspbrk (const wchar_t *__wcs, const wchar_t *__accept) __THROW __attribute_pure__; +# if __GLIBC_USE (ISOC23) && defined __glibc_const_generic && !defined _LIBC +# define wcspbrk(WCS, ACCEPT) \ + __glibc_const_generic (WCS, const wchar_t *, wcspbrk (WCS, ACCEPT)) +# endif #endif /* Find the first occurrence of NEEDLE in HAYSTACK. */ #ifdef __CORRECT_ISO_CPP_WCHAR_H_PROTO @@ -236,6 +252,11 @@ extern "C++" const wchar_t *wcsstr (const wchar_t *__haystack, #else extern wchar_t *wcsstr (const wchar_t *__haystack, const wchar_t *__needle) __THROW __attribute_pure__; +# if __GLIBC_USE (ISOC23) && defined __glibc_const_generic && !defined _LIBC +# define wcsstr(HAYSTACK, NEEDLE) \ + __glibc_const_generic (HAYSTACK, const wchar_t *, \ + wcsstr (HAYSTACK, NEEDLE)) +# endif #endif /* Divide WCS into tokens separated by characters in DELIM. */ @@ -277,6 +298,10 @@ extern "C++" const wchar_t *wmemchr (const wchar_t *__s, wchar_t __c, #else extern wchar_t *wmemchr (const wchar_t *__s, wchar_t __c, size_t __n) __THROW __attribute_pure__; +# if __GLIBC_USE (ISOC23) && defined __glibc_const_generic && !defined _LIBC +# define wmemchr(S, C, N) \ + __glibc_const_generic (S, const wchar_t *, wmemchr (S, C, N)) +# endif #endif /* Compare N wide characters of S1 and S2. */ diff --git a/lib/libc/include/generic-glibc/wctype.h b/lib/libc/include/generic-glibc/wctype.h index 200c96e89d6de83b3f7d6be6e9bc13ad7a67a002..097703b08ee774f29f64c03e1b4395a20ff90494 100644 --- a/lib/libc/include/generic-glibc/wctype.h +++ b/lib/libc/include/generic-glibc/wctype.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1996-2025 Free Software Foundation, Inc. +/* Copyright (C) 1996-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/wordexp.h b/lib/libc/include/generic-glibc/wordexp.h index 115723b859508762cff82aef4f9d261d8b473b93..b0db95f47164a0a665aacb25c1cc05af8fc2989d 100644 --- a/lib/libc/include/generic-glibc/wordexp.h +++ b/lib/libc/include/generic-glibc/wordexp.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1991-2025 Free Software Foundation, Inc. +/* Copyright (C) 1991-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/loongarch-linux-gnu/bits/fcntl.h b/lib/libc/include/loongarch-linux-gnu/bits/fcntl.h index 8b96b7a9c28d97841d610f8c35dbd6c6ce42c2ae..6c36c9f0bd79849ecb06a209ecff9289042a7d4d 100644 --- a/lib/libc/include/loongarch-linux-gnu/bits/fcntl.h +++ b/lib/libc/include/loongarch-linux-gnu/bits/fcntl.h @@ -1,5 +1,5 @@ /* O_*, F_*, FD_* bit values for the generic Linux/LoongArch ABI. - Copyright (C) 2022-2025 Free Software Foundation, Inc. + Copyright (C) 2022-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/loongarch-linux-gnu/bits/fenv.h b/lib/libc/include/loongarch-linux-gnu/bits/fenv.h index 318c579edbc5459e6c76fa99b0293dc09ec0af10..4c19156d32f667c34e7b0953d26da84fda73c275 100644 --- a/lib/libc/include/loongarch-linux-gnu/bits/fenv.h +++ b/lib/libc/include/loongarch-linux-gnu/bits/fenv.h @@ -1,5 +1,5 @@ /* Floating point environment. - Copyright (C) 2022-2025 Free Software Foundation, Inc. + Copyright (C) 2022-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/loongarch-linux-gnu/bits/hwcap.h b/lib/libc/include/loongarch-linux-gnu/bits/hwcap.h index 4cdf84fec872bb1588fc1cb5dccd3c26003cace1..d6335ffaa97c3c61cd1452cd3f01b8fc0903aa9d 100644 --- a/lib/libc/include/loongarch-linux-gnu/bits/hwcap.h +++ b/lib/libc/include/loongarch-linux-gnu/bits/hwcap.h @@ -1,5 +1,5 @@ /* Defines for bits in AT_HWCAP. LoongArch64 Linux version. - Copyright (C) 2022-2025 Free Software Foundation, Inc. + Copyright (C) 2022-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/loongarch-linux-gnu/bits/link.h b/lib/libc/include/loongarch-linux-gnu/bits/link.h index 787518441210647a1a30b93a8fdf94c802c5567c..7e963fc047f7449ddf5faf540dcf1cba442db0ac 100644 --- a/lib/libc/include/loongarch-linux-gnu/bits/link.h +++ b/lib/libc/include/loongarch-linux-gnu/bits/link.h @@ -1,5 +1,5 @@ /* Machine-specific declarations for dynamic linker interface. - Copyright (C) 2022-2025 Free Software Foundation, Inc. + Copyright (C) 2022-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/loongarch-linux-gnu/bits/link_lavcurrent.h b/lib/libc/include/loongarch-linux-gnu/bits/link_lavcurrent.h index 0d0fcc213e6f191e5c7d47ad9c95001dd9473f16..d9827f7cb5930ac6836d1956a1a86c9f937e27b9 100644 --- a/lib/libc/include/loongarch-linux-gnu/bits/link_lavcurrent.h +++ b/lib/libc/include/loongarch-linux-gnu/bits/link_lavcurrent.h @@ -1,6 +1,6 @@ /* Data structure for communication from the run-time dynamic linker for loaded ELF shared objects. LAV_CURRENT definition. - Copyright (C) 2023-2025 Free Software Foundation, Inc. + Copyright (C) 2023-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/loongarch-linux-gnu/bits/long-double.h b/lib/libc/include/loongarch-linux-gnu/bits/long-double.h index 57d7be04a685d57ab6e8e672e08d7effa853b06b..af7784dbe6dd85cd9538b5a7b437ab45de22eeb8 100644 --- a/lib/libc/include/loongarch-linux-gnu/bits/long-double.h +++ b/lib/libc/include/loongarch-linux-gnu/bits/long-double.h @@ -1,5 +1,5 @@ /* Properties of long double type. ldbl-128 version. - Copyright (C) 2016-2025 Free Software Foundation, Inc. + Copyright (C) 2016-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/loongarch-linux-gnu/bits/procfs.h b/lib/libc/include/loongarch-linux-gnu/bits/procfs.h index 757bcd0ffa13c485c468d49f7a3050aa6a2e5bc3..82667049ea4da105674708596c0d73bd6c25c0df 100644 --- a/lib/libc/include/loongarch-linux-gnu/bits/procfs.h +++ b/lib/libc/include/loongarch-linux-gnu/bits/procfs.h @@ -1,5 +1,5 @@ /* Types for registers for sys/procfs.h. - Copyright (C) 2022-2025 Free Software Foundation, Inc. + Copyright (C) 2022-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. diff --git a/lib/libc/include/loongarch-linux-gnu/bits/pthread_stack_min.h b/lib/libc/include/loongarch-linux-gnu/bits/pthread_stack_min.h index aea77125cb772564b083f4b9a7e93dbc8da67e38..856803a17b4cfbd7e8cc5d2a7778b6e2ab160431 100644 --- a/lib/libc/include/loongarch-linux-gnu/bits/pthread_stack_min.h +++ b/lib/libc/include/loongarch-linux-gnu/bits/pthread_stack_min.h @@ -1,5 +1,5 @@ /* Definition of PTHREAD_STACK_MIN. LoongArch Linux version. - Copyright (C) 2022-2025 Free Software Foundation, Inc. + Copyright (C) 2022-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/loongarch-linux-gnu/bits/rseq.h b/lib/libc/include/loongarch-linux-gnu/bits/rseq.h index dd0e4a7f8d941f53bd23ae6e866e07456ba0f898..956e00c544d2ca595bcf07c6afcb3daf95121c0f 100644 --- a/lib/libc/include/loongarch-linux-gnu/bits/rseq.h +++ b/lib/libc/include/loongarch-linux-gnu/bits/rseq.h @@ -1,5 +1,5 @@ /* Restartable Sequences Linux LoongArch architecture header. - Copyright (C) 2024-2025 Free Software Foundation, Inc. + Copyright (C) 2024-2026 Free Software Foundation, Inc. The GNU C Library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public diff --git a/lib/libc/include/loongarch-linux-gnu/bits/setjmp.h b/lib/libc/include/loongarch-linux-gnu/bits/setjmp.h index dc384c17178265924e53ada99b1ed3416a274ea6..a22192a8fef78f5d71da8ccd0c922a0a77085aab 100644 --- a/lib/libc/include/loongarch-linux-gnu/bits/setjmp.h +++ b/lib/libc/include/loongarch-linux-gnu/bits/setjmp.h @@ -1,5 +1,5 @@ /* Define the machine-dependent type `jmp_buf'. - Copyright (C) 2022-2025 Free Software Foundation, Inc. + Copyright (C) 2022-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/loongarch-linux-gnu/bits/sigstack.h b/lib/libc/include/loongarch-linux-gnu/bits/sigstack.h index ee18b492d11c013baa4d3abfb4ebcf7ed217bbea..e1e74c25bfb52366a496ad1e848560e20b04cb01 100644 --- a/lib/libc/include/loongarch-linux-gnu/bits/sigstack.h +++ b/lib/libc/include/loongarch-linux-gnu/bits/sigstack.h @@ -1,5 +1,5 @@ /* sigstack, sigaltstack definitions. - Copyright (C) 2022-2025 Free Software Foundation, Inc. + Copyright (C) 2022-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/loongarch-linux-gnu/bits/struct_stat.h b/lib/libc/include/loongarch-linux-gnu/bits/struct_stat.h index 724450a6679649260300d400798787a515849f1d..0462d37a6849812f485a6830a7c0dca303332636 100644 --- a/lib/libc/include/loongarch-linux-gnu/bits/struct_stat.h +++ b/lib/libc/include/loongarch-linux-gnu/bits/struct_stat.h @@ -1,5 +1,5 @@ /* Definition for struct stat. - Copyright (C) 2020-2025 Free Software Foundation, Inc. + Copyright (C) 2020-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/loongarch-linux-gnu/bits/timesize.h b/lib/libc/include/loongarch-linux-gnu/bits/timesize.h index 04251ea75c82b9450e5c2bee30c3bbf06cbe55fb..dff2da5ed6bf30ce6f5580aee352e0958d58b623 100644 --- a/lib/libc/include/loongarch-linux-gnu/bits/timesize.h +++ b/lib/libc/include/loongarch-linux-gnu/bits/timesize.h @@ -1,5 +1,5 @@ /* Bit size of the time_t type at glibc build time, general case. - Copyright (C) 2018-2025 Free Software Foundation, Inc. + Copyright (C) 2018-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/loongarch-linux-gnu/bits/wordsize.h b/lib/libc/include/loongarch-linux-gnu/bits/wordsize.h index 5e7e1d15eeccd5d1ea9311d7aa74c7f7b1b412f4..5038df494751b3446f652bcb4d1c5d3e721a61ec 100644 --- a/lib/libc/include/loongarch-linux-gnu/bits/wordsize.h +++ b/lib/libc/include/loongarch-linux-gnu/bits/wordsize.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1999-2025 Free Software Foundation, Inc. +/* Copyright (C) 1999-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/loongarch-linux-gnu/fpu_control.h b/lib/libc/include/loongarch-linux-gnu/fpu_control.h index 8b39333ca06fd342218e70f20d93f66514423cfc..69cd4213c79b5db9e6c6ec60dc454c8177c8ba6d 100644 --- a/lib/libc/include/loongarch-linux-gnu/fpu_control.h +++ b/lib/libc/include/loongarch-linux-gnu/fpu_control.h @@ -1,5 +1,5 @@ /* FPU control word bits. - Copyright (C) 2022-2025 Free Software Foundation, Inc. + Copyright (C) 2022-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/generic-glibc/gnu/lib-names-lp64d.h b/lib/libc/include/loongarch-linux-gnu/gnu/lib-names-lp64d.h similarity index 100% rename from lib/libc/include/generic-glibc/gnu/lib-names-lp64d.h rename to lib/libc/include/loongarch-linux-gnu/gnu/lib-names-lp64d.h diff --git a/lib/libc/include/loongarch-linux-gnu/gnu/lib-names-lp64s.h b/lib/libc/include/loongarch-linux-gnu/gnu/lib-names-lp64s.h new file mode 100644 index 0000000000000000000000000000000000000000..7c8b796194b2f179d2dfddbd0a7040a76ba37aeb --- /dev/null +++ b/lib/libc/include/loongarch-linux-gnu/gnu/lib-names-lp64s.h @@ -0,0 +1,27 @@ +/* This file is automatically generated. */ +#ifndef __GNU_LIB_NAMES_H +# error "Never use directly; include instead." +#endif + +#define LD_LINUX_LOONGARCH_LP64S_SO "ld-linux-loongarch-lp64s.so.1" +#define LD_SO "ld-linux-loongarch-lp64s.so.1" +#define LIBANL_SO "libanl.so.1" +#define LIBBROKENLOCALE_SO "libBrokenLocale.so.1" +#define LIBC_MALLOC_DEBUG_SO "libc_malloc_debug.so.0" +#define LIBC_SO "libc.so.6" +#define LIBDL_SO "libdl.so.2" +#define LIBGCC_S_SO "libgcc_s.so.1" +#define LIBMVEC_SO "libmvec.so.1" +#define LIBM_SO "libm.so.6" +#define LIBNSL_SO "libnsl.so.1" +#define LIBNSS_COMPAT_SO "libnss_compat.so.2" +#define LIBNSS_DB_SO "libnss_db.so.2" +#define LIBNSS_DNS_SO "libnss_dns.so.2" +#define LIBNSS_FILES_SO "libnss_files.so.2" +#define LIBNSS_HESIOD_SO "libnss_hesiod.so.2" +#define LIBNSS_LDAP_SO "libnss_ldap.so.2" +#define LIBPTHREAD_SO "libpthread.so.0" +#define LIBRESOLV_SO "libresolv.so.2" +#define LIBRT_SO "librt.so.1" +#define LIBTHREAD_DB_SO "libthread_db.so.1" +#define LIBUTIL_SO "libutil.so.1" \ No newline at end of file diff --git a/lib/libc/include/generic-glibc/gnu/stubs-lp64d.h b/lib/libc/include/loongarch-linux-gnu/gnu/stubs-lp64d.h similarity index 100% rename from lib/libc/include/generic-glibc/gnu/stubs-lp64d.h rename to lib/libc/include/loongarch-linux-gnu/gnu/stubs-lp64d.h diff --git a/lib/libc/include/loongarch-linux-gnu/gnu/stubs-lp64s.h b/lib/libc/include/loongarch-linux-gnu/gnu/stubs-lp64s.h index b9af396166e6dfc0c19e99ac906269c7008453e3..6ce02418e69609f4642a522910708769e96d6ee9 100644 --- a/lib/libc/include/loongarch-linux-gnu/gnu/stubs-lp64s.h +++ b/lib/libc/include/loongarch-linux-gnu/gnu/stubs-lp64s.h @@ -35,4 +35,4 @@ #define __stub_revoke #define __stub_setlogin #define __stub_sigreturn -#define __stub_stty +#define __stub_stty \ No newline at end of file diff --git a/lib/libc/include/loongarch-linux-gnu/ieee754.h b/lib/libc/include/loongarch-linux-gnu/ieee754.h index a49523c3d8faa01c2f79bd6e565578db52ddb727..a19fd8dcd1d39c39a1b5000ca0b4d3f8da2c2299 100644 --- a/lib/libc/include/loongarch-linux-gnu/ieee754.h +++ b/lib/libc/include/loongarch-linux-gnu/ieee754.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1992-2025 Free Software Foundation, Inc. +/* Copyright (C) 1992-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/loongarch-linux-gnu/sys/asm.h b/lib/libc/include/loongarch-linux-gnu/sys/asm.h index 9bedcac8e27d42b3b1631092b1073f020a06485a..973feb6effa26ea10d6b1af85ce2784a32ec8135 100644 --- a/lib/libc/include/loongarch-linux-gnu/sys/asm.h +++ b/lib/libc/include/loongarch-linux-gnu/sys/asm.h @@ -1,5 +1,5 @@ /* Miscellaneous macros. - Copyright (C) 2022-2025 Free Software Foundation, Inc. + Copyright (C) 2022-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/loongarch-linux-gnu/sys/ucontext.h b/lib/libc/include/loongarch-linux-gnu/sys/ucontext.h index a36bb8145e2c2168bff5491fd2cd19cb1d155fa4..c13e11dcd83db32e2d9410c58f62d56f5dfd2339 100644 --- a/lib/libc/include/loongarch-linux-gnu/sys/ucontext.h +++ b/lib/libc/include/loongarch-linux-gnu/sys/ucontext.h @@ -1,5 +1,5 @@ /* struct ucontext definition. - Copyright (C) 2022-2025 Free Software Foundation, Inc. + Copyright (C) 2022-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. diff --git a/lib/libc/include/loongarch-linux-gnu/sys/user.h b/lib/libc/include/loongarch-linux-gnu/sys/user.h index 122590639e325ee0e8979781e66c7e202c0ea730..6a8fed0bb79ff4dac6fa989de9d3da7d3a917d56 100644 --- a/lib/libc/include/loongarch-linux-gnu/sys/user.h +++ b/lib/libc/include/loongarch-linux-gnu/sys/user.h @@ -1,5 +1,5 @@ /* struct user_regs_struct definition for LoongArch. - Copyright (C) 2022-2025 Free Software Foundation, Inc. + Copyright (C) 2022-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/m68k-linux-gnu/bits/fcntl.h b/lib/libc/include/m68k-linux-gnu/bits/fcntl.h index a9dc395f27c48c417cb8a65ed28db4a4a65d66fc..ff8e9ccc93dbcab1548df965bb5267836a4307dc 100644 --- a/lib/libc/include/m68k-linux-gnu/bits/fcntl.h +++ b/lib/libc/include/m68k-linux-gnu/bits/fcntl.h @@ -1,5 +1,5 @@ /* O_*, F_*, FD_* bit values for Linux. - Copyright (C) 2000-2025 Free Software Foundation, Inc. + Copyright (C) 2000-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/m68k-linux-gnu/bits/fenv.h b/lib/libc/include/m68k-linux-gnu/bits/fenv.h index 1edbdff77480583f939a0e0d5710d337b634a333..921c6aa22a7a8c40c314c765b074893d9874623d 100644 --- a/lib/libc/include/m68k-linux-gnu/bits/fenv.h +++ b/lib/libc/include/m68k-linux-gnu/bits/fenv.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1997-2025 Free Software Foundation, Inc. +/* Copyright (C) 1997-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/m68k-linux-gnu/bits/floatn.h b/lib/libc/include/m68k-linux-gnu/bits/floatn.h index 9005dc8601d52334aa53591640f009c59f6e6672..d3408f15249d51ab748cb0313f4cc3f36b09cdda 100644 --- a/lib/libc/include/m68k-linux-gnu/bits/floatn.h +++ b/lib/libc/include/m68k-linux-gnu/bits/floatn.h @@ -1,5 +1,5 @@ /* Macros to control TS 18661-3 glibc features. - Copyright (C) 2017-2025 Free Software Foundation, Inc. + Copyright (C) 2017-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/m68k-linux-gnu/bits/flt-eval-method.h b/lib/libc/include/m68k-linux-gnu/bits/flt-eval-method.h index ff2e29d6d864f44f34dbbb43ead6603e1927f6fb..11283dbeb1d5d07fd8384dd6fe263fef12fa557e 100644 --- a/lib/libc/include/m68k-linux-gnu/bits/flt-eval-method.h +++ b/lib/libc/include/m68k-linux-gnu/bits/flt-eval-method.h @@ -1,5 +1,5 @@ /* Define __GLIBC_FLT_EVAL_METHOD. M68K version. - Copyright (C) 2016-2025 Free Software Foundation, Inc. + Copyright (C) 2016-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/m68k-linux-gnu/bits/fp-logb.h b/lib/libc/include/m68k-linux-gnu/bits/fp-logb.h index 582b38925cc350bc7d3075dbae395a6c92b3119b..10abc3c0eb83e58907a2d9fa6ab3fb8b090674d1 100644 --- a/lib/libc/include/m68k-linux-gnu/bits/fp-logb.h +++ b/lib/libc/include/m68k-linux-gnu/bits/fp-logb.h @@ -1,5 +1,5 @@ /* Define __FP_LOGB0_IS_MIN and __FP_LOGBNAN_IS_MIN. M68K version. - Copyright (C) 2016-2025 Free Software Foundation, Inc. + Copyright (C) 2016-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/m68k-linux-gnu/bits/iscanonical.h b/lib/libc/include/m68k-linux-gnu/bits/iscanonical.h index 4d0b3c8c86bf0f2848187b96ff00044ed97e845c..874cd33ddf0ff0491227c49aebecd8e42ecf8cd5 100644 --- a/lib/libc/include/m68k-linux-gnu/bits/iscanonical.h +++ b/lib/libc/include/m68k-linux-gnu/bits/iscanonical.h @@ -1,5 +1,5 @@ /* Define iscanonical macro. ldbl-96 version. - Copyright (C) 2016-2025 Free Software Foundation, Inc. + Copyright (C) 2016-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/m68k-linux-gnu/bits/link.h b/lib/libc/include/m68k-linux-gnu/bits/link.h index f7cff8eea4b910a329656287e1c7e087758c708b..8177a2fa8b2246b9723bf29786080006131b4dda 100644 --- a/lib/libc/include/m68k-linux-gnu/bits/link.h +++ b/lib/libc/include/m68k-linux-gnu/bits/link.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2005-2025 Free Software Foundation, Inc. +/* Copyright (C) 2005-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/m68k-linux-gnu/bits/long-double.h b/lib/libc/include/m68k-linux-gnu/bits/long-double.h index 601eef7f1a86f269fc401db183e077ae69990f38..aae8ed389111b6ed9ff725e9f5e12abaf7fe99c1 100644 --- a/lib/libc/include/m68k-linux-gnu/bits/long-double.h +++ b/lib/libc/include/m68k-linux-gnu/bits/long-double.h @@ -1,5 +1,5 @@ /* Properties of long double type. ldbl-96 version. - Copyright (C) 2016-2025 Free Software Foundation, Inc. + Copyright (C) 2016-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/m68k-linux-gnu/bits/poll.h b/lib/libc/include/m68k-linux-gnu/bits/poll.h index 7043ba8468e5200088d81e0e49e31b25518bf46b..4a33b3a969f1a8ebdde7af696221b0adca31b9d3 100644 --- a/lib/libc/include/m68k-linux-gnu/bits/poll.h +++ b/lib/libc/include/m68k-linux-gnu/bits/poll.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1997-2025 Free Software Foundation, Inc. +/* Copyright (C) 1997-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/m68k-linux-gnu/bits/procfs-id.h b/lib/libc/include/m68k-linux-gnu/bits/procfs-id.h index 29aba43c37acb275bfc9109efeab05491c0f7526..055c426cbf6f1d9b45b138c484754a047b594928 100644 --- a/lib/libc/include/m68k-linux-gnu/bits/procfs-id.h +++ b/lib/libc/include/m68k-linux-gnu/bits/procfs-id.h @@ -1,5 +1,5 @@ /* Types of pr_uid and pr_gid in struct elf_prpsinfo. M68K version. - Copyright (C) 2018-2025 Free Software Foundation, Inc. + Copyright (C) 2018-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. diff --git a/lib/libc/include/m68k-linux-gnu/bits/procfs.h b/lib/libc/include/m68k-linux-gnu/bits/procfs.h index b9e8923967610d8a278251581c4ee43c3d937a5a..76a9fb481327381ffe7e1ddbb3b2b4a988e7c1c5 100644 --- a/lib/libc/include/m68k-linux-gnu/bits/procfs.h +++ b/lib/libc/include/m68k-linux-gnu/bits/procfs.h @@ -1,5 +1,5 @@ /* Types for registers for sys/procfs.h. M68K version. - Copyright (C) 1996-2025 Free Software Foundation, Inc. + Copyright (C) 1996-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/m68k-linux-gnu/bits/pthreadtypes-arch.h b/lib/libc/include/m68k-linux-gnu/bits/pthreadtypes-arch.h index 9fbaf82a3748b8b26eb7b51480114a24f0ecdb43..6e8757a91e316788f05a428e0286a78e971470fd 100644 --- a/lib/libc/include/m68k-linux-gnu/bits/pthreadtypes-arch.h +++ b/lib/libc/include/m68k-linux-gnu/bits/pthreadtypes-arch.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2010-2025 Free Software Foundation, Inc. +/* Copyright (C) 2010-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/m68k-linux-gnu/bits/rseq.h b/lib/libc/include/m68k-linux-gnu/bits/rseq.h index 90b3e9804a5b827612f37a31f9805ee0d82ad9a4..a844012dd3a527f6948a027145ecc10a1f01903f 100644 --- a/lib/libc/include/m68k-linux-gnu/bits/rseq.h +++ b/lib/libc/include/m68k-linux-gnu/bits/rseq.h @@ -1,5 +1,5 @@ /* Restartable Sequences architecture header. Stub version. - Copyright (C) 2021-2025 Free Software Foundation, Inc. + Copyright (C) 2021-2026 Free Software Foundation, Inc. The GNU C Library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public diff --git a/lib/libc/include/m68k-linux-gnu/bits/semaphore.h b/lib/libc/include/m68k-linux-gnu/bits/semaphore.h index a4487453a4f493599e1a0fb59467e31b2a8dd8d1..9df365a63232926bece3bca813648025565217ba 100644 --- a/lib/libc/include/m68k-linux-gnu/bits/semaphore.h +++ b/lib/libc/include/m68k-linux-gnu/bits/semaphore.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2010-2025 Free Software Foundation, Inc. +/* Copyright (C) 2010-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/m68k-linux-gnu/bits/setjmp.h b/lib/libc/include/m68k-linux-gnu/bits/setjmp.h index dd73bb69c2f4c4c17e60135863b744c2afdc10dc..86550f920b899baa2065db6eba2e9b8a0702fe0f 100644 --- a/lib/libc/include/m68k-linux-gnu/bits/setjmp.h +++ b/lib/libc/include/m68k-linux-gnu/bits/setjmp.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1997-2025 Free Software Foundation, Inc. +/* Copyright (C) 1997-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/m68k-linux-gnu/bits/sockaddr.h b/lib/libc/include/m68k-linux-gnu/bits/sockaddr.h index 0c44604c6c6e3659f64d8fc6c7dcf55a18c733ac..c142853d6cfb9b4dc9e677f64b3bed2e9689eb62 100644 --- a/lib/libc/include/m68k-linux-gnu/bits/sockaddr.h +++ b/lib/libc/include/m68k-linux-gnu/bits/sockaddr.h @@ -1,5 +1,5 @@ /* Definition of struct sockaddr_* members and sizes, Linux/m68k version. - Copyright (C) 1995-2025 Free Software Foundation, Inc. + Copyright (C) 1995-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/m68k-linux-gnu/bits/struct_stat.h b/lib/libc/include/m68k-linux-gnu/bits/struct_stat.h index 4ac869dc175d4f9a813a04f7feddf3a710f28bd2..0ebb90a43cc14879d8a8f8f72558f780a4892a32 100644 --- a/lib/libc/include/m68k-linux-gnu/bits/struct_stat.h +++ b/lib/libc/include/m68k-linux-gnu/bits/struct_stat.h @@ -1,5 +1,5 @@ /* Definition for struct stat. - Copyright (C) 2020-2025 Free Software Foundation, Inc. + Copyright (C) 2020-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/m68k-linux-gnu/bits/timesize.h b/lib/libc/include/m68k-linux-gnu/bits/timesize.h index 36ba5733d55eaf5ec1367461f4ba7581cde3c118..878982d5afe3f458e666271fb0aa3912fe7b727d 100644 --- a/lib/libc/include/m68k-linux-gnu/bits/timesize.h +++ b/lib/libc/include/m68k-linux-gnu/bits/timesize.h @@ -1,5 +1,5 @@ /* Bit size of the time_t type at glibc build time, Linux/m68k. - Copyright (C) 2021-2025 Free Software Foundation, Inc. + Copyright (C) 2021-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/m68k-linux-gnu/bits/typesizes.h b/lib/libc/include/m68k-linux-gnu/bits/typesizes.h index 74ad928d8e020b7d94579f74501f46b322c77c31..8771f100852620fefa1072f5a9c23a6ee1c16421 100644 --- a/lib/libc/include/m68k-linux-gnu/bits/typesizes.h +++ b/lib/libc/include/m68k-linux-gnu/bits/typesizes.h @@ -1,5 +1,5 @@ /* bits/typesizes.h -- underlying types for *_t. m68k version. - Copyright (C) 2002-2025 Free Software Foundation, Inc. + Copyright (C) 2002-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/m68k-linux-gnu/bits/wordsize.h b/lib/libc/include/m68k-linux-gnu/bits/wordsize.h index db329914346120034ed744f869ca475dda80c060..6e4c479fe4392ef20c85cd861dcde13f3825051d 100644 --- a/lib/libc/include/m68k-linux-gnu/bits/wordsize.h +++ b/lib/libc/include/m68k-linux-gnu/bits/wordsize.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1999-2025 Free Software Foundation, Inc. +/* Copyright (C) 1999-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/m68k-linux-gnu/fpu_control.h b/lib/libc/include/m68k-linux-gnu/fpu_control.h index a9ee0ef254fe012a1ff38112c4f6afe1507d2f45..8c3b4b936be93508238a58b042a6d703f480b7bb 100644 --- a/lib/libc/include/m68k-linux-gnu/fpu_control.h +++ b/lib/libc/include/m68k-linux-gnu/fpu_control.h @@ -1,5 +1,5 @@ /* 68k FPU control word definitions. - Copyright (C) 1996-2025 Free Software Foundation, Inc. + Copyright (C) 1996-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/m68k-linux-gnu/sys/reg.h b/lib/libc/include/m68k-linux-gnu/sys/reg.h index c2477b54f7e669a1da54e5146eeebde53a27ecc3..59328014e330237ee945b022068b77091f88d5e2 100644 --- a/lib/libc/include/m68k-linux-gnu/sys/reg.h +++ b/lib/libc/include/m68k-linux-gnu/sys/reg.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1998-2025 Free Software Foundation, Inc. +/* Copyright (C) 1998-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/m68k-linux-gnu/sys/ucontext.h b/lib/libc/include/m68k-linux-gnu/sys/ucontext.h index fe4b8b295a4c38a62859c9763ab3920f6f1d6467..75ea0dd02f88957d6a8fc41dc1484f2bea300a70 100644 --- a/lib/libc/include/m68k-linux-gnu/sys/ucontext.h +++ b/lib/libc/include/m68k-linux-gnu/sys/ucontext.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1997-2025 Free Software Foundation, Inc. +/* Copyright (C) 1997-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/m68k-linux-gnu/sys/user.h b/lib/libc/include/m68k-linux-gnu/sys/user.h index 577e775b0bc44f3436bf1ec9aeb2a17b75fcaa4d..501b9dcbf65908d9554f71d1663ea48bf80e0690 100644 --- a/lib/libc/include/m68k-linux-gnu/sys/user.h +++ b/lib/libc/include/m68k-linux-gnu/sys/user.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2008-2025 Free Software Foundation, Inc. +/* Copyright (C) 2008-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/mips-linux-gnu/bits/dlfcn.h b/lib/libc/include/mips-linux-gnu/bits/dlfcn.h index 65b934d43fab299b557ad81b4921579e33a09bb8..24982842aa9a867da89f838f7fb84777d3a86b71 100644 --- a/lib/libc/include/mips-linux-gnu/bits/dlfcn.h +++ b/lib/libc/include/mips-linux-gnu/bits/dlfcn.h @@ -1,5 +1,5 @@ /* System dependent definitions for run-time dynamic loading. - Copyright (C) 1996-2025 Free Software Foundation, Inc. + Copyright (C) 1996-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/mips-linux-gnu/bits/errno.h b/lib/libc/include/mips-linux-gnu/bits/errno.h index 73757ea5c33196252864c48b1f07b8a5050b313f..30e850a337b100bad1e433ec8e890685282da384 100644 --- a/lib/libc/include/mips-linux-gnu/bits/errno.h +++ b/lib/libc/include/mips-linux-gnu/bits/errno.h @@ -1,5 +1,5 @@ /* Error constants. MIPS/Linux specific version. - Copyright (C) 1996-2025 Free Software Foundation, Inc. + Copyright (C) 1996-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/mips-linux-gnu/bits/eventfd.h b/lib/libc/include/mips-linux-gnu/bits/eventfd.h index 9527e5f5c62dc59a5b16b35bf4bbd0d9454b8851..399250f328cc94b6c08075bcaaeb1c9d9433c01c 100644 --- a/lib/libc/include/mips-linux-gnu/bits/eventfd.h +++ b/lib/libc/include/mips-linux-gnu/bits/eventfd.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2007-2025 Free Software Foundation, Inc. +/* Copyright (C) 2007-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/mips-linux-gnu/bits/floatn.h b/lib/libc/include/mips-linux-gnu/bits/floatn.h index 3a2de29dee4428e08b0db2fd3241906cb27633e3..e0957c3d3571502c87f6b7add46252baf59082f2 100644 --- a/lib/libc/include/mips-linux-gnu/bits/floatn.h +++ b/lib/libc/include/mips-linux-gnu/bits/floatn.h @@ -1,5 +1,5 @@ /* Macros to control TS 18661-3 glibc features on MIPS platforms. - Copyright (C) 2017-2025 Free Software Foundation, Inc. + Copyright (C) 2017-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/mips-linux-gnu/bits/inotify.h b/lib/libc/include/mips-linux-gnu/bits/inotify.h index 20e56c411d64f12856557c92640fba18d6b31fd5..5b03c30da42d9501eae033d6f52f2861a716de4e 100644 --- a/lib/libc/include/mips-linux-gnu/bits/inotify.h +++ b/lib/libc/include/mips-linux-gnu/bits/inotify.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2005-2025 Free Software Foundation, Inc. +/* Copyright (C) 2005-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/mips-linux-gnu/bits/ioctl-types.h b/lib/libc/include/mips-linux-gnu/bits/ioctl-types.h index 511fe1c96b30153f8acf0c2c8ca845dbb8e1bb3a..6e2a67b789696ca8fbb39a176edabc2b284b0452 100644 --- a/lib/libc/include/mips-linux-gnu/bits/ioctl-types.h +++ b/lib/libc/include/mips-linux-gnu/bits/ioctl-types.h @@ -1,5 +1,5 @@ /* Structure types for pre-termios terminal ioctls. Linux/MIPS version. - Copyright (C) 1997-2025 Free Software Foundation, Inc. + Copyright (C) 1997-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/mips-linux-gnu/bits/ipctypes.h b/lib/libc/include/mips-linux-gnu/bits/ipctypes.h index b7bec61581923378c0cb0d8498204101072aaa3e..5ee3ebe38f190945351cba1f01b9b2f8db903580 100644 --- a/lib/libc/include/mips-linux-gnu/bits/ipctypes.h +++ b/lib/libc/include/mips-linux-gnu/bits/ipctypes.h @@ -1,5 +1,5 @@ /* bits/ipctypes.h -- Define some types used by SysV IPC/MSG/SHM. MIPS version - Copyright (C) 2002-2025 Free Software Foundation, Inc. + Copyright (C) 2002-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/mips-linux-gnu/bits/mman.h b/lib/libc/include/mips-linux-gnu/bits/mman.h index ee158cf726302435303af788d3438267f873c414..0b5f513e076a516fac6facfef40df3bc6b7467e5 100644 --- a/lib/libc/include/mips-linux-gnu/bits/mman.h +++ b/lib/libc/include/mips-linux-gnu/bits/mman.h @@ -1,5 +1,5 @@ /* Definitions for POSIX memory map interface. Linux/MIPS version. - Copyright (C) 1997-2025 Free Software Foundation, Inc. + Copyright (C) 1997-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/mips-linux-gnu/bits/poll.h b/lib/libc/include/mips-linux-gnu/bits/poll.h index 7043ba8468e5200088d81e0e49e31b25518bf46b..4a33b3a969f1a8ebdde7af696221b0adca31b9d3 100644 --- a/lib/libc/include/mips-linux-gnu/bits/poll.h +++ b/lib/libc/include/mips-linux-gnu/bits/poll.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1997-2025 Free Software Foundation, Inc. +/* Copyright (C) 1997-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/mips-linux-gnu/bits/pthread_stack_min.h b/lib/libc/include/mips-linux-gnu/bits/pthread_stack_min.h index 6bc4157059eca04b6cd5ed13def1c921dbfd46ef..05d4234ed3115af19db8cd532149da255a3ca39c 100644 --- a/lib/libc/include/mips-linux-gnu/bits/pthread_stack_min.h +++ b/lib/libc/include/mips-linux-gnu/bits/pthread_stack_min.h @@ -1,5 +1,5 @@ /* Definition of PTHREAD_STACK_MIN. MIPS Linux version. - Copyright (C) 2021-2025 Free Software Foundation, Inc. + Copyright (C) 2021-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/mips-linux-gnu/bits/pthreadtypes-arch.h b/lib/libc/include/mips-linux-gnu/bits/pthreadtypes-arch.h index 226d51c2a259ee66b25fe4777ae955a3d41121b7..53ecb74974cc32a401f32c3ed4e7a1172818cd56 100644 --- a/lib/libc/include/mips-linux-gnu/bits/pthreadtypes-arch.h +++ b/lib/libc/include/mips-linux-gnu/bits/pthreadtypes-arch.h @@ -1,5 +1,5 @@ /* Machine-specific pthread type layouts. MIPS version. - Copyright (C) 2005-2025 Free Software Foundation, Inc. + Copyright (C) 2005-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/mips-linux-gnu/bits/resource.h b/lib/libc/include/mips-linux-gnu/bits/resource.h index 021ce359ddf6a28ea1775bbfe8f3614efb55d729..0a5a0769042aafd27f16816ec188ceef878cd8ef 100644 --- a/lib/libc/include/mips-linux-gnu/bits/resource.h +++ b/lib/libc/include/mips-linux-gnu/bits/resource.h @@ -1,5 +1,5 @@ /* Bit values & structures for resource limits. Linux/MIPS version. - Copyright (C) 1994-2025 Free Software Foundation, Inc. + Copyright (C) 1994-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/mips-linux-gnu/bits/semaphore.h b/lib/libc/include/mips-linux-gnu/bits/semaphore.h index b220656c882853bb3350bd05d9030b1e09d7bded..eac6ab0e5cf20330bc93d0ecd2f9acb6206e87c2 100644 --- a/lib/libc/include/mips-linux-gnu/bits/semaphore.h +++ b/lib/libc/include/mips-linux-gnu/bits/semaphore.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2002-2025 Free Software Foundation, Inc. +/* Copyright (C) 2002-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/mips-linux-gnu/bits/shmlba.h b/lib/libc/include/mips-linux-gnu/bits/shmlba.h index 350b518ba81292fcd7f353bcef06b2fb628d210b..7b5b1136f8ceb48c77ac9712e674fc055a1b4e84 100644 --- a/lib/libc/include/mips-linux-gnu/bits/shmlba.h +++ b/lib/libc/include/mips-linux-gnu/bits/shmlba.h @@ -1,5 +1,5 @@ /* Define SHMLBA. MIPS version. - Copyright (C) 2018-2025 Free Software Foundation, Inc. + Copyright (C) 2018-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/mips-linux-gnu/bits/sigaction.h b/lib/libc/include/mips-linux-gnu/bits/sigaction.h index 5505891b7ae86e1caa177a604673d539299fabd0..619d56cbf1c0abbf94450e716092c96acefa279f 100644 --- a/lib/libc/include/mips-linux-gnu/bits/sigaction.h +++ b/lib/libc/include/mips-linux-gnu/bits/sigaction.h @@ -1,5 +1,5 @@ /* The proper definitions for Linux/MIPS's sigaction. - Copyright (C) 1993-2025 Free Software Foundation, Inc. + Copyright (C) 1993-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/mips-linux-gnu/bits/sigcontext.h b/lib/libc/include/mips-linux-gnu/bits/sigcontext.h index f605e22dae54544a6f2ec4b8f3e404195a1789b0..d47e3e7c5900590b808a3f11724376817d98cdc0 100644 --- a/lib/libc/include/mips-linux-gnu/bits/sigcontext.h +++ b/lib/libc/include/mips-linux-gnu/bits/sigcontext.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1996-2025 Free Software Foundation, Inc. This file is part of the GNU C Library. +/* Copyright (C) 1996-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public diff --git a/lib/libc/include/mips-linux-gnu/bits/signalfd.h b/lib/libc/include/mips-linux-gnu/bits/signalfd.h index af0460ab2a4bcab1ccccc94d94624efe77481435..35bfe6548e316a34650b4bf822c660a8a705afe1 100644 --- a/lib/libc/include/mips-linux-gnu/bits/signalfd.h +++ b/lib/libc/include/mips-linux-gnu/bits/signalfd.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2007-2025 Free Software Foundation, Inc. +/* Copyright (C) 2007-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/mips-linux-gnu/bits/signum-arch.h b/lib/libc/include/mips-linux-gnu/bits/signum-arch.h index f8201472e17c6bbdc7c7b0932b705d2d2d890917..c43e83fcf77e1e8d07e642218076d460db85eb06 100644 --- a/lib/libc/include/mips-linux-gnu/bits/signum-arch.h +++ b/lib/libc/include/mips-linux-gnu/bits/signum-arch.h @@ -1,5 +1,5 @@ /* Signal number definitions. Linux/MIPS version. - Copyright (C) 1995-2025 Free Software Foundation, Inc. + Copyright (C) 1995-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/mips-linux-gnu/bits/socket-constants.h b/lib/libc/include/mips-linux-gnu/bits/socket-constants.h index ec962210624e20cf27ef3ee6ce3199a1a05a7d3f..24c30f9d98e7de678baaf279ee874dfcfbb7866f 100644 --- a/lib/libc/include/mips-linux-gnu/bits/socket-constants.h +++ b/lib/libc/include/mips-linux-gnu/bits/socket-constants.h @@ -1,5 +1,5 @@ /* Socket constants which vary among Linux architectures. Version for MIPS. - Copyright (C) 2019-2025 Free Software Foundation, Inc. + Copyright (C) 2019-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/mips-linux-gnu/bits/socket_type.h b/lib/libc/include/mips-linux-gnu/bits/socket_type.h index 50649dcc5f71b73d412d732034eb1e5b614a1ecc..92f304d2aa083d12aafa1eb8cbcb2f764af0192c 100644 --- a/lib/libc/include/mips-linux-gnu/bits/socket_type.h +++ b/lib/libc/include/mips-linux-gnu/bits/socket_type.h @@ -1,5 +1,5 @@ /* Define enum __socket_type for Linux/MIPS. - Copyright (C) 1991-2025 Free Software Foundation, Inc. + Copyright (C) 1991-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/mips-linux-gnu/bits/statfs.h b/lib/libc/include/mips-linux-gnu/bits/statfs.h index 179b003f888d0f85a02ab3d33b3545bac36cffe1..9e68db3490d5560a0d76ca9b9066f9c9d33c3b1b 100644 --- a/lib/libc/include/mips-linux-gnu/bits/statfs.h +++ b/lib/libc/include/mips-linux-gnu/bits/statfs.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1997-2025 Free Software Foundation, Inc. +/* Copyright (C) 1997-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/mips-linux-gnu/bits/struct_mutex.h b/lib/libc/include/mips-linux-gnu/bits/struct_mutex.h index fd005c0b9c34ff11a128721b67e6e786da984153..804676bebf59dffe3fc4708c09827489fab93111 100644 --- a/lib/libc/include/mips-linux-gnu/bits/struct_mutex.h +++ b/lib/libc/include/mips-linux-gnu/bits/struct_mutex.h @@ -1,5 +1,5 @@ /* MIPS internal mutex struct definitions. - Copyright (C) 2019-2025 Free Software Foundation, Inc. + Copyright (C) 2019-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/mips-linux-gnu/bits/struct_rwlock.h b/lib/libc/include/mips-linux-gnu/bits/struct_rwlock.h index f1daa9496db2bc9c88eb44f94c226180b9a75091..631c3057516b4b6ac58721a1d3dff26848694967 100644 --- a/lib/libc/include/mips-linux-gnu/bits/struct_rwlock.h +++ b/lib/libc/include/mips-linux-gnu/bits/struct_rwlock.h @@ -1,5 +1,5 @@ /* MIPS internal rwlock struct definitions. - Copyright (C) 2019-2025 Free Software Foundation, Inc. + Copyright (C) 2019-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/mips-linux-gnu/bits/termios-c_cc.h b/lib/libc/include/mips-linux-gnu/bits/termios-c_cc.h index 530680ff2fde412f04d12a08d23ba41a30bcff90..a572e88edce585c4eeaade0955edc403fa2320e5 100644 --- a/lib/libc/include/mips-linux-gnu/bits/termios-c_cc.h +++ b/lib/libc/include/mips-linux-gnu/bits/termios-c_cc.h @@ -1,5 +1,5 @@ /* termios c_cc symbolic constant definitions. Linux/mips version. - Copyright (C) 2019-2025 Free Software Foundation, Inc. + Copyright (C) 2019-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/mips-linux-gnu/bits/termios-c_lflag.h b/lib/libc/include/mips-linux-gnu/bits/termios-c_lflag.h index 0920d035c7d1525bc3b7906e38557552df550332..1ff38be11df1881c4674ca44274a62d2eaab2c84 100644 --- a/lib/libc/include/mips-linux-gnu/bits/termios-c_lflag.h +++ b/lib/libc/include/mips-linux-gnu/bits/termios-c_lflag.h @@ -1,5 +1,5 @@ /* termios local mode definitions. Linux/mips version. - Copyright (C) 2019-2025 Free Software Foundation, Inc. + Copyright (C) 2019-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/mips-linux-gnu/bits/termios-tcflow.h b/lib/libc/include/mips-linux-gnu/bits/termios-tcflow.h index 97705b188723bdf56fd4e7ad2de13cfc764b4609..8ab349de28ead813e9e3fb81ee5e3a915cc3238f 100644 --- a/lib/libc/include/mips-linux-gnu/bits/termios-tcflow.h +++ b/lib/libc/include/mips-linux-gnu/bits/termios-tcflow.h @@ -1,5 +1,5 @@ /* termios local mode definitions. Linux/mips version. - Copyright (C) 2019-2025 Free Software Foundation, Inc. + Copyright (C) 2019-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/mips-linux-gnu/bits/timerfd.h b/lib/libc/include/mips-linux-gnu/bits/timerfd.h index 58172ec3af9ab6e8216b4cf30d3b1e96311caa95..abcb014125fa67b97434673b45423105819e343e 100644 --- a/lib/libc/include/mips-linux-gnu/bits/timerfd.h +++ b/lib/libc/include/mips-linux-gnu/bits/timerfd.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2008-2025 Free Software Foundation, Inc. +/* Copyright (C) 2008-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/mips-linux-gnu/bits/types/stack_t.h b/lib/libc/include/mips-linux-gnu/bits/types/stack_t.h index ca303c5d20b95d948a03919c467c8dbd85736f9b..5869efefaccf445af4cf2f628a668dd4f7749b2e 100644 --- a/lib/libc/include/mips-linux-gnu/bits/types/stack_t.h +++ b/lib/libc/include/mips-linux-gnu/bits/types/stack_t.h @@ -1,5 +1,5 @@ /* Define stack_t. MIPS Linux version. - Copyright (C) 1998-2025 Free Software Foundation, Inc. + Copyright (C) 1998-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/mips-linux-gnu/bits/types/struct_msqid_ds.h b/lib/libc/include/mips-linux-gnu/bits/types/struct_msqid_ds.h index 5539b03f3a2fccf7ff03d021e8c254d892649b68..ca9c04ffbafe038d0810e51eb1ffdd85ce5798b9 100644 --- a/lib/libc/include/mips-linux-gnu/bits/types/struct_msqid_ds.h +++ b/lib/libc/include/mips-linux-gnu/bits/types/struct_msqid_ds.h @@ -1,5 +1,5 @@ /* Linux/MIPS implementation of the SysV message struct msqid_ds. - Copyright (C) 2020-2025 Free Software Foundation, Inc. + Copyright (C) 2020-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/mips-linux-gnu/bits/types/struct_semid_ds.h b/lib/libc/include/mips-linux-gnu/bits/types/struct_semid_ds.h index 2545ab94013c121db630ad47c44c4f3ee7612f2c..dcbc64f74b714f252195fa1f05851b8834ae3db2 100644 --- a/lib/libc/include/mips-linux-gnu/bits/types/struct_semid_ds.h +++ b/lib/libc/include/mips-linux-gnu/bits/types/struct_semid_ds.h @@ -1,5 +1,5 @@ /* MIPS implementation of the semaphore struct semid_ds - Copyright (C) 1995-2025 Free Software Foundation, Inc. + Copyright (C) 1995-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/mips-linux-gnu/bits/types/struct_shmid_ds.h b/lib/libc/include/mips-linux-gnu/bits/types/struct_shmid_ds.h index bf252474c1018accc728c1f7e109b4c0b303e522..dfd1415c60faa88f5f523f55699bf17b22538276 100644 --- a/lib/libc/include/mips-linux-gnu/bits/types/struct_shmid_ds.h +++ b/lib/libc/include/mips-linux-gnu/bits/types/struct_shmid_ds.h @@ -1,5 +1,5 @@ /* Linux/MIPS implementation of the shared memory struct shmid_ds. - Copyright (C) 2020-2025 Free Software Foundation, Inc. + Copyright (C) 2020-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/mips-linux-gnu/bits/typesizes.h b/lib/libc/include/mips-linux-gnu/bits/typesizes.h index 8e99e59de9e231b8ac497fb26ab19e80fdb73ec1..b70c6888219450545687944bec6065ff55c4ec9a 100644 --- a/lib/libc/include/mips-linux-gnu/bits/typesizes.h +++ b/lib/libc/include/mips-linux-gnu/bits/typesizes.h @@ -1,5 +1,5 @@ /* bits/typesizes.h -- underlying types for *_t. MIPS version. - Copyright (C) 2002-2025 Free Software Foundation, Inc. + Copyright (C) 2002-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/mips-linux-gnu/ieee754.h b/lib/libc/include/mips-linux-gnu/ieee754.h index b3800f55945252fcd2cbae85b3edc25e983c99d1..731c2a937e9d0334e5b36f59c351f72ddae1a0d4 100644 --- a/lib/libc/include/mips-linux-gnu/ieee754.h +++ b/lib/libc/include/mips-linux-gnu/ieee754.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1992-2025 Free Software Foundation, Inc. +/* Copyright (C) 1992-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/powerpc-linux-gnu/bits/environments.h b/lib/libc/include/powerpc-linux-gnu/bits/environments.h index 5a19b4cbd48af000a5e8a98fce1ce4d3db924ed2..1f7533af49568f229f835f8755b8e8f71ad50ea8 100644 --- a/lib/libc/include/powerpc-linux-gnu/bits/environments.h +++ b/lib/libc/include/powerpc-linux-gnu/bits/environments.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1999-2025 Free Software Foundation, Inc. +/* Copyright (C) 1999-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/powerpc-linux-gnu/bits/fcntl.h b/lib/libc/include/powerpc-linux-gnu/bits/fcntl.h index 8e255ea462df727986cc25ab6da6b4f438544a0c..01a481d7a46a369c149f9f488fb88351427a1bae 100644 --- a/lib/libc/include/powerpc-linux-gnu/bits/fcntl.h +++ b/lib/libc/include/powerpc-linux-gnu/bits/fcntl.h @@ -1,5 +1,5 @@ /* O_*, F_*, FD_* bit values for Linux/PowerPC. - Copyright (C) 1995-2025 Free Software Foundation, Inc. + Copyright (C) 1995-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/powerpc-linux-gnu/bits/fenv.h b/lib/libc/include/powerpc-linux-gnu/bits/fenv.h index 464f652309cf099dbfe5d9ef213353a7cfc4ec71..b75483708fc0b25365342e3146548fa845b50e53 100644 --- a/lib/libc/include/powerpc-linux-gnu/bits/fenv.h +++ b/lib/libc/include/powerpc-linux-gnu/bits/fenv.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1997-2025 Free Software Foundation, Inc. +/* Copyright (C) 1997-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/powerpc-linux-gnu/bits/floatn.h b/lib/libc/include/powerpc-linux-gnu/bits/floatn.h index b07b9abf33c2d01bb101f2bdeadbfa6f76d1cbaa..db53131df6090e3ce3e75e5dd5a27c86b88dd63c 100644 --- a/lib/libc/include/powerpc-linux-gnu/bits/floatn.h +++ b/lib/libc/include/powerpc-linux-gnu/bits/floatn.h @@ -1,5 +1,5 @@ /* Macros to control TS 18661-3 glibc features on powerpc. - Copyright (C) 2017-2025 Free Software Foundation, Inc. + Copyright (C) 2017-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/powerpc-linux-gnu/bits/fp-fast.h b/lib/libc/include/powerpc-linux-gnu/bits/fp-fast.h index 4d38958f0534be1f022b342909fa984f3cbf5837..99a9b6a68d783d8f5625da2be6026e00c416605b 100644 --- a/lib/libc/include/powerpc-linux-gnu/bits/fp-fast.h +++ b/lib/libc/include/powerpc-linux-gnu/bits/fp-fast.h @@ -1,5 +1,5 @@ /* Define FP_FAST_* macros. PowerPC version. - Copyright (C) 2016-2025 Free Software Foundation, Inc. + Copyright (C) 2016-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/powerpc-linux-gnu/bits/hwcap.h b/lib/libc/include/powerpc-linux-gnu/bits/hwcap.h index 5e3ab0182b26b4bb2d0463e196a114da1b04591d..17e805f059c5070d2d04349ed8fca5a79b788c0c 100644 --- a/lib/libc/include/powerpc-linux-gnu/bits/hwcap.h +++ b/lib/libc/include/powerpc-linux-gnu/bits/hwcap.h @@ -1,5 +1,5 @@ /* Defines for bits in AT_HWCAP and AT_HWCAP2. - Copyright (C) 2012-2025 Free Software Foundation, Inc. + Copyright (C) 2012-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/powerpc-linux-gnu/bits/ioctl-types.h b/lib/libc/include/powerpc-linux-gnu/bits/ioctl-types.h index ecb6295b249bb7a28b282c979ed11a8f38c4ad81..fa0215e5cffaaf371e5b06e58447a0fa5fd1566f 100644 --- a/lib/libc/include/powerpc-linux-gnu/bits/ioctl-types.h +++ b/lib/libc/include/powerpc-linux-gnu/bits/ioctl-types.h @@ -1,5 +1,5 @@ /* Structure types for pre-termios terminal ioctls. Linux/powerpc version. - Copyright (C) 2014-2025 Free Software Foundation, Inc. + Copyright (C) 2014-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/powerpc-linux-gnu/bits/ipc-perm.h b/lib/libc/include/powerpc-linux-gnu/bits/ipc-perm.h index 06ba7e6ca791cfdcbc0c2a6e449c0a4a87058c64..2ed12c3136828fb79d54efbdab083f5ecf5332e7 100644 --- a/lib/libc/include/powerpc-linux-gnu/bits/ipc-perm.h +++ b/lib/libc/include/powerpc-linux-gnu/bits/ipc-perm.h @@ -1,5 +1,5 @@ /* struct ipc_perm definition. Linux/powerpc version. - Copyright (C) 1995-2025 Free Software Foundation, Inc. + Copyright (C) 1995-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/powerpc-linux-gnu/bits/iscanonical.h b/lib/libc/include/powerpc-linux-gnu/bits/iscanonical.h index 49ea731c913bdefd284e6ab4ffdbb64a6a3fb5a9..b7cfc3ef410492c7298fc5f580568fce5b3527c5 100644 --- a/lib/libc/include/powerpc-linux-gnu/bits/iscanonical.h +++ b/lib/libc/include/powerpc-linux-gnu/bits/iscanonical.h @@ -1,5 +1,5 @@ /* Define iscanonical macro. ldbl-128ibm version. - Copyright (C) 2016-2025 Free Software Foundation, Inc. + Copyright (C) 2016-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/powerpc-linux-gnu/bits/link.h b/lib/libc/include/powerpc-linux-gnu/bits/link.h index 90aaa912deebd838f8af252b22d772516cfb57e6..251210f7bcd54174e682b2403cbb0167bd033a51 100644 --- a/lib/libc/include/powerpc-linux-gnu/bits/link.h +++ b/lib/libc/include/powerpc-linux-gnu/bits/link.h @@ -1,5 +1,5 @@ /* Machine-specific declarations for dynamic linker interface. PowerPC version - Copyright (C) 2004-2025 Free Software Foundation, Inc. + Copyright (C) 2004-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/powerpc-linux-gnu/bits/long-double.h b/lib/libc/include/powerpc-linux-gnu/bits/long-double.h index ecf2982b04aa44eb7ff5a290cfa398b4c5db9c27..6c9cf2949f916b13700552fd7c1fe852fa507460 100644 --- a/lib/libc/include/powerpc-linux-gnu/bits/long-double.h +++ b/lib/libc/include/powerpc-linux-gnu/bits/long-double.h @@ -1,5 +1,5 @@ /* Properties of long double type. ldbl-opt version. - Copyright (C) 2016-2025 Free Software Foundation, Inc. + Copyright (C) 2016-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/powerpc-linux-gnu/bits/mman.h b/lib/libc/include/powerpc-linux-gnu/bits/mman.h index 1fc6762a7a22377f7c8e9170ca37d22d0487df9f..e68abd8cc99504b34592f042821390f878dddc44 100644 --- a/lib/libc/include/powerpc-linux-gnu/bits/mman.h +++ b/lib/libc/include/powerpc-linux-gnu/bits/mman.h @@ -1,5 +1,5 @@ /* Definitions for POSIX memory map interface. Linux/PowerPC version. - Copyright (C) 1997-2025 Free Software Foundation, Inc. + Copyright (C) 1997-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/powerpc-linux-gnu/bits/procfs.h b/lib/libc/include/powerpc-linux-gnu/bits/procfs.h index 14652268ec21fe77ace172a4e1a9c0039a8b1b7f..22d5a4af6b39a52b607f8f9c1262bc6070b4cbcc 100644 --- a/lib/libc/include/powerpc-linux-gnu/bits/procfs.h +++ b/lib/libc/include/powerpc-linux-gnu/bits/procfs.h @@ -1,5 +1,5 @@ /* Types for registers for sys/procfs.h. PowerPC version. - Copyright (C) 1996-2025 Free Software Foundation, Inc. + Copyright (C) 1996-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/powerpc-linux-gnu/bits/pthread_stack_min.h b/lib/libc/include/powerpc-linux-gnu/bits/pthread_stack_min.h index 0b6dcb9131cf681ecf7069dc7d1e08f699902dec..a54ee42eb098e3ea940be72be4cda38c2275616b 100644 --- a/lib/libc/include/powerpc-linux-gnu/bits/pthread_stack_min.h +++ b/lib/libc/include/powerpc-linux-gnu/bits/pthread_stack_min.h @@ -1,5 +1,5 @@ /* Definition of PTHREAD_STACK_MIN. Linux/PPC version. - Copyright (C) 2021-2025 Free Software Foundation, Inc. + Copyright (C) 2021-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/powerpc-linux-gnu/bits/rseq.h b/lib/libc/include/powerpc-linux-gnu/bits/rseq.h index 5ba280909af90ac5cda1025f0d8c7c03d96dcf02..7d44f470b1af46aad9090006573e7e9da67c557c 100644 --- a/lib/libc/include/powerpc-linux-gnu/bits/rseq.h +++ b/lib/libc/include/powerpc-linux-gnu/bits/rseq.h @@ -1,5 +1,5 @@ /* Restartable Sequences Linux powerpc architecture header. - Copyright (C) 2021-2025 Free Software Foundation, Inc. + Copyright (C) 2021-2026 Free Software Foundation, Inc. The GNU C Library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public diff --git a/lib/libc/include/powerpc-linux-gnu/bits/setjmp.h b/lib/libc/include/powerpc-linux-gnu/bits/setjmp.h index b8774299cd565da3c0e3749a500931a71a05f7e8..54cff5c6c8c3079aa1f0bb2ba69c1ca6ff32f75c 100644 --- a/lib/libc/include/powerpc-linux-gnu/bits/setjmp.h +++ b/lib/libc/include/powerpc-linux-gnu/bits/setjmp.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1997-2025 Free Software Foundation, Inc. +/* Copyright (C) 1997-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/powerpc-linux-gnu/bits/sigstack.h b/lib/libc/include/powerpc-linux-gnu/bits/sigstack.h index cc26e7b543aa339d9e1d20c481282e74ade4a9ad..c72ef19072a1d6b6ed9328f71e7ab42e07901c6e 100644 --- a/lib/libc/include/powerpc-linux-gnu/bits/sigstack.h +++ b/lib/libc/include/powerpc-linux-gnu/bits/sigstack.h @@ -1,5 +1,5 @@ /* sigstack, sigaltstack definitions. - Copyright (C) 1998-2025 Free Software Foundation, Inc. + Copyright (C) 1998-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/powerpc-linux-gnu/bits/socket-constants.h b/lib/libc/include/powerpc-linux-gnu/bits/socket-constants.h index b5b6051a0ce68781c119b349a12fecc88e04ae13..c5e8c71cbf9a922ce58825e45859f2c632b2f313 100644 --- a/lib/libc/include/powerpc-linux-gnu/bits/socket-constants.h +++ b/lib/libc/include/powerpc-linux-gnu/bits/socket-constants.h @@ -1,5 +1,5 @@ /* Socket constants which vary among Linux architectures. Version for POWER. - Copyright (C) 2019-2025 Free Software Foundation, Inc. + Copyright (C) 2019-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/powerpc-linux-gnu/bits/struct_mutex.h b/lib/libc/include/powerpc-linux-gnu/bits/struct_mutex.h index c7f90aa47d97f7a4c314e59d785694f658c1ec40..00a85da7667b8540cc89ad17d4c4f51648687920 100644 --- a/lib/libc/include/powerpc-linux-gnu/bits/struct_mutex.h +++ b/lib/libc/include/powerpc-linux-gnu/bits/struct_mutex.h @@ -1,5 +1,5 @@ /* PowerPC internal mutex struct definitions. - Copyright (C) 2019-2025 Free Software Foundation, Inc. + Copyright (C) 2019-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or @@ -32,7 +32,7 @@ struct __pthread_mutex_s int __kind; #if __WORDSIZE == 64 short __spins; - short __elision; + short __unused; __pthread_list_t __list; # define __PTHREAD_MUTEX_HAVE_PREV 1 #else @@ -41,11 +41,10 @@ struct __pthread_mutex_s { struct { - short __espins; - short __elision; -# define __spins __elision_data.__espins -# define __elision __elision_data.__elision - } __elision_data; + short __data_spins; + short __data_unused; +# define __spins __data.__data_spins + } __data; __pthread_slist_t __list; }; # define __PTHREAD_MUTEX_HAVE_PREV 0 diff --git a/lib/libc/include/powerpc-linux-gnu/bits/struct_rwlock.h b/lib/libc/include/powerpc-linux-gnu/bits/struct_rwlock.h index 6ca8f577e5a81aff9ce992356d7225bbeabc6bd7..d3e18ca54f01f2e0660bc614c30c22aaef7a830f 100644 --- a/lib/libc/include/powerpc-linux-gnu/bits/struct_rwlock.h +++ b/lib/libc/include/powerpc-linux-gnu/bits/struct_rwlock.h @@ -1,5 +1,5 @@ /* PowerPC internal rwlock struct definitions. - Copyright (C) 2019-2025 Free Software Foundation, Inc. + Copyright (C) 2019-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. @@ -31,31 +31,28 @@ struct __pthread_rwlock_arch_t #if __WORDSIZE == 64 int __cur_writer; int __shared; - unsigned char __rwelision; - unsigned char __pad1[7]; + unsigned long int __pad1; unsigned long int __pad2; /* FLAGS must stay at this position in the structure to maintain binary compatibility. */ unsigned int __flags; -# define __PTHREAD_RWLOCK_ELISION_EXTRA 0, {0, 0, 0, 0, 0, 0, 0 } #else - unsigned char __rwelision; + unsigned char __pad1; unsigned char __pad2; unsigned char __shared; /* FLAGS must stay at this position in the structure to maintain binary compatibility. */ unsigned char __flags; int __cur_writer; -# define __PTHREAD_RWLOCK_ELISION_EXTRA 0 #endif }; #if __WORDSIZE == 64 # define __PTHREAD_RWLOCK_INITIALIZER(__flags) \ - 0, 0, 0, 0, 0, 0, 0, 0, __PTHREAD_RWLOCK_ELISION_EXTRA, 0, __flags + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, __flags #else # define __PTHREAD_RWLOCK_INITIALIZER(__flags) \ - 0, 0, 0, 0, 0, 0, __PTHREAD_RWLOCK_ELISION_EXTRA, 0, 0, __flags, 0 + 0, 0, 0, 0, 0, 0, 0, 0, 0, __flags, 0 #endif #endif \ No newline at end of file diff --git a/lib/libc/include/powerpc-linux-gnu/bits/struct_stat.h b/lib/libc/include/powerpc-linux-gnu/bits/struct_stat.h index 4b2c05e425b44f1662e8343dec8b27d956042315..053329e24cf8bff340a156324430c0be01172400 100644 --- a/lib/libc/include/powerpc-linux-gnu/bits/struct_stat.h +++ b/lib/libc/include/powerpc-linux-gnu/bits/struct_stat.h @@ -1,5 +1,5 @@ /* Definition for struct stat. - Copyright (C) 2020-2025 Free Software Foundation, Inc. + Copyright (C) 2020-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/powerpc-linux-gnu/bits/termios-c_cc.h b/lib/libc/include/powerpc-linux-gnu/bits/termios-c_cc.h index 2a533e982e51ed203c16cc699cebfa45cb66fd9a..47887ecfe146ed4dc6531a5378e4ba8540e2b6cd 100644 --- a/lib/libc/include/powerpc-linux-gnu/bits/termios-c_cc.h +++ b/lib/libc/include/powerpc-linux-gnu/bits/termios-c_cc.h @@ -1,5 +1,5 @@ /* termios c_cc symbolic constant definitions. Linux/powerpc version. - Copyright (C) 2019-2025 Free Software Foundation, Inc. + Copyright (C) 2019-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/powerpc-linux-gnu/bits/termios-c_cflag.h b/lib/libc/include/powerpc-linux-gnu/bits/termios-c_cflag.h index 86d5639c460e9a1b398633b1345c3f1fae8e5baf..37dd83e796403f50ce4d961e53dcf97b577dee2f 100644 --- a/lib/libc/include/powerpc-linux-gnu/bits/termios-c_cflag.h +++ b/lib/libc/include/powerpc-linux-gnu/bits/termios-c_cflag.h @@ -1,5 +1,5 @@ /* termios control mode definitions. Linux/powerpc version. - Copyright (C) 2019-2025 Free Software Foundation, Inc. + Copyright (C) 2019-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/powerpc-linux-gnu/bits/termios-c_iflag.h b/lib/libc/include/powerpc-linux-gnu/bits/termios-c_iflag.h index b19f14d9ea9f3bc0d8f9a621c13ca13c5abc6589..0c4e34104344867d8bffaa4aed0ebd23b6b837b0 100644 --- a/lib/libc/include/powerpc-linux-gnu/bits/termios-c_iflag.h +++ b/lib/libc/include/powerpc-linux-gnu/bits/termios-c_iflag.h @@ -1,5 +1,5 @@ /* termios input mode definitions. Linux/powerpc version. - Copyright (C) 2019-2025 Free Software Foundation, Inc. + Copyright (C) 2019-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/powerpc-linux-gnu/bits/termios-c_lflag.h b/lib/libc/include/powerpc-linux-gnu/bits/termios-c_lflag.h index 28d361816dde0fec928df76d0afde24771b5d03e..6c8abad464bd1965cbf1f9674fbaed7b8f26deb1 100644 --- a/lib/libc/include/powerpc-linux-gnu/bits/termios-c_lflag.h +++ b/lib/libc/include/powerpc-linux-gnu/bits/termios-c_lflag.h @@ -1,5 +1,5 @@ /* termios local mode definitions. Linux/powerpc version. - Copyright (C) 2019-2025 Free Software Foundation, Inc. + Copyright (C) 2019-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/powerpc-linux-gnu/bits/termios-c_oflag.h b/lib/libc/include/powerpc-linux-gnu/bits/termios-c_oflag.h index 7884494268e1befff5deaca03326191610a488e7..8d6fd3ceb02011bd4dd6ec1cc7b0d18826927d8b 100644 --- a/lib/libc/include/powerpc-linux-gnu/bits/termios-c_oflag.h +++ b/lib/libc/include/powerpc-linux-gnu/bits/termios-c_oflag.h @@ -1,5 +1,5 @@ /* termios output mode definitions. Linux/powerpc version. - Copyright (C) 2019-2025 Free Software Foundation, Inc. + Copyright (C) 2019-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/powerpc-linux-gnu/bits/termios-cbaud.h b/lib/libc/include/powerpc-linux-gnu/bits/termios-cbaud.h index 6e0257e59d42ad45f0ab7bd0e226195a9cb87c02..1f56bad97b3ae042ceb327b8df9cc347acc9671c 100644 --- a/lib/libc/include/powerpc-linux-gnu/bits/termios-cbaud.h +++ b/lib/libc/include/powerpc-linux-gnu/bits/termios-cbaud.h @@ -1,5 +1,5 @@ /* termios baud rate selection definitions. Linux/powerpc version. - Copyright (C) 2019-2025 Free Software Foundation, Inc. + Copyright (C) 2019-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/powerpc-linux-gnu/bits/termios-misc.h b/lib/libc/include/powerpc-linux-gnu/bits/termios-misc.h index 8d3504478dc42f14366a3c10a95510b9e84392aa..789236ad3163bbf471804f9c26d22f4812cbfc0d 100644 --- a/lib/libc/include/powerpc-linux-gnu/bits/termios-misc.h +++ b/lib/libc/include/powerpc-linux-gnu/bits/termios-misc.h @@ -1,5 +1,5 @@ /* termios baud platform specific definitions. Linux/powerpc version. - Copyright (C) 2019-2025 Free Software Foundation, Inc. + Copyright (C) 2019-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/powerpc-linux-gnu/bits/timesize.h b/lib/libc/include/powerpc-linux-gnu/bits/timesize.h index 9cbad9b500a47a06bfa63c07060a06cc77d6921a..5e47e22bc066d049db2211f5280924c515a469b8 100644 --- a/lib/libc/include/powerpc-linux-gnu/bits/timesize.h +++ b/lib/libc/include/powerpc-linux-gnu/bits/timesize.h @@ -1,5 +1,5 @@ /* Bit size of the time_t type at glibc build time, Linux/PowerPC. - Copyright (C) 2021-2025 Free Software Foundation, Inc. + Copyright (C) 2021-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/powerpc-linux-gnu/bits/types/struct_msqid_ds.h b/lib/libc/include/powerpc-linux-gnu/bits/types/struct_msqid_ds.h index f6b3e4d6552f8aa5d6a7b9bc053ab508e9b09861..8624e326dc77fb09551532e38d89c23831b0baed 100644 --- a/lib/libc/include/powerpc-linux-gnu/bits/types/struct_msqid_ds.h +++ b/lib/libc/include/powerpc-linux-gnu/bits/types/struct_msqid_ds.h @@ -1,5 +1,5 @@ /* Linux/PowerPC implementation of the SysV message struct msqid_ds. - Copyright (C) 2020-2025 Free Software Foundation, Inc. + Copyright (C) 2020-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/powerpc-linux-gnu/bits/types/struct_semid_ds.h b/lib/libc/include/powerpc-linux-gnu/bits/types/struct_semid_ds.h index 3b208ff2fd58f750d232f87d2ee414af31677e75..550efd66075360ffd41b13ef1b797e11c818ba3d 100644 --- a/lib/libc/include/powerpc-linux-gnu/bits/types/struct_semid_ds.h +++ b/lib/libc/include/powerpc-linux-gnu/bits/types/struct_semid_ds.h @@ -1,5 +1,5 @@ /* PowerPC implementation of the semaphore struct semid_ds. - Copyright (C) 1995-2025 Free Software Foundation, Inc. + Copyright (C) 1995-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/powerpc-linux-gnu/bits/types/struct_shmid_ds.h b/lib/libc/include/powerpc-linux-gnu/bits/types/struct_shmid_ds.h index 2ed27e039d2aa7b7f98220cdd9e702e887a5bdf7..e739d5e857aeb73aa5af800178f74d08947b308d 100644 --- a/lib/libc/include/powerpc-linux-gnu/bits/types/struct_shmid_ds.h +++ b/lib/libc/include/powerpc-linux-gnu/bits/types/struct_shmid_ds.h @@ -1,5 +1,5 @@ /* Linux/PowerPC implementation of the shared memory struct shmid_ds. - Copyright (C) 2020-2025 Free Software Foundation, Inc. + Copyright (C) 2020-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/powerpc-linux-gnu/bits/typesizes.h b/lib/libc/include/powerpc-linux-gnu/bits/typesizes.h index 47bb8610e66cd3ae4f103a3c5b5c6435a784f066..dc1da9054a9ab9c11ee323a45c762475f57a75ca 100644 --- a/lib/libc/include/powerpc-linux-gnu/bits/typesizes.h +++ b/lib/libc/include/powerpc-linux-gnu/bits/typesizes.h @@ -1,5 +1,5 @@ /* bits/typesizes.h -- underlying types for *_t. PowerPC version. - Copyright (C) 2002-2025 Free Software Foundation, Inc. + Copyright (C) 2002-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/powerpc-linux-gnu/fpu_control.h b/lib/libc/include/powerpc-linux-gnu/fpu_control.h index cdedca96cffd94c0284cf410371009a6fe20cf18..dec65fa0108553f0a62865f4492ce44adbebe01b 100644 --- a/lib/libc/include/powerpc-linux-gnu/fpu_control.h +++ b/lib/libc/include/powerpc-linux-gnu/fpu_control.h @@ -1,5 +1,5 @@ /* FPU control word definitions. PowerPC version. - Copyright (C) 1996-2025 Free Software Foundation, Inc. + Copyright (C) 1996-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/powerpc-linux-gnu/ieee754.h b/lib/libc/include/powerpc-linux-gnu/ieee754.h index 38008b560fa7d7fab6e6032c51cc4b2d6dea01c3..18b79270e6d96f0eb54a3f93b4beb6ebc18e7060 100644 --- a/lib/libc/include/powerpc-linux-gnu/ieee754.h +++ b/lib/libc/include/powerpc-linux-gnu/ieee754.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1992-2025 Free Software Foundation, Inc. +/* Copyright (C) 1992-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/powerpc-linux-gnu/sys/ptrace.h b/lib/libc/include/powerpc-linux-gnu/sys/ptrace.h index 99cf115cb6ac7371c3dc4abbc5ff51a708f83f92..882a09c350ebc7efe4046d8f7951df3c0ebacb73 100644 --- a/lib/libc/include/powerpc-linux-gnu/sys/ptrace.h +++ b/lib/libc/include/powerpc-linux-gnu/sys/ptrace.h @@ -1,5 +1,5 @@ /* `ptrace' debugger support interface. Linux/PowerPC version. - Copyright (C) 2001-2025 Free Software Foundation, Inc. + Copyright (C) 2001-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/powerpc-linux-gnu/sys/ucontext.h b/lib/libc/include/powerpc-linux-gnu/sys/ucontext.h index 7d354e529b92e16c592d72ed5d8ff8473014aa7d..df7b7b4a5f7dd59015085064e47840bd58963343 100644 --- a/lib/libc/include/powerpc-linux-gnu/sys/ucontext.h +++ b/lib/libc/include/powerpc-linux-gnu/sys/ucontext.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1998-2025 Free Software Foundation, Inc. +/* Copyright (C) 1998-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/powerpc-linux-gnu/sys/user.h b/lib/libc/include/powerpc-linux-gnu/sys/user.h index 19396e1bc3d520b93c2ecf7d9d4bbf91e5d77837..706662749f14f66a4c5a44dda94597ee6c6f2d22 100644 --- a/lib/libc/include/powerpc-linux-gnu/sys/user.h +++ b/lib/libc/include/powerpc-linux-gnu/sys/user.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1998-2025 Free Software Foundation, Inc. +/* Copyright (C) 1998-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/riscv-linux-gnu/bits/environments.h b/lib/libc/include/riscv-linux-gnu/bits/environments.h index 26a15733b3eff99f30f871d07081d59c675addb2..5cf0a2c910cac2102e9dadbc45696e4891c5d975 100644 --- a/lib/libc/include/riscv-linux-gnu/bits/environments.h +++ b/lib/libc/include/riscv-linux-gnu/bits/environments.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2020-2025 Free Software Foundation, Inc. +/* Copyright (C) 2020-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/riscv-linux-gnu/bits/fcntl.h b/lib/libc/include/riscv-linux-gnu/bits/fcntl.h index 7a8e74e78819106a776b428ebe88d47d07f78d8f..ed62d1829d9d9f7c4c1278c902c619570a59623b 100644 --- a/lib/libc/include/riscv-linux-gnu/bits/fcntl.h +++ b/lib/libc/include/riscv-linux-gnu/bits/fcntl.h @@ -1,5 +1,5 @@ /* O_*, F_*, FD_* bit values for Linux / RISC-V. - Copyright (C) 2011-2025 Free Software Foundation, Inc. + Copyright (C) 2011-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. diff --git a/lib/libc/include/riscv-linux-gnu/bits/fenv.h b/lib/libc/include/riscv-linux-gnu/bits/fenv.h index 385b2d68984ec089e8f7dd28dc7b9864a2636d60..aa191274b48a453e090b97172528a7722c5232fb 100644 --- a/lib/libc/include/riscv-linux-gnu/bits/fenv.h +++ b/lib/libc/include/riscv-linux-gnu/bits/fenv.h @@ -1,5 +1,5 @@ /* Floating point environment, RISC-V version. - Copyright (C) 1998-2025 Free Software Foundation, Inc. + Copyright (C) 1998-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/riscv-linux-gnu/bits/link.h b/lib/libc/include/riscv-linux-gnu/bits/link.h index 044077794837c85bdcf521a2e11986473ed4a03f..c82422106be6c045ea595169d7c3b1710cda79c9 100644 --- a/lib/libc/include/riscv-linux-gnu/bits/link.h +++ b/lib/libc/include/riscv-linux-gnu/bits/link.h @@ -1,5 +1,5 @@ /* Machine-specific declarations for dynamic linker interface. RISC-V version. - Copyright (C) 2005-2025 Free Software Foundation, Inc. + Copyright (C) 2005-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/riscv-linux-gnu/bits/long-double.h b/lib/libc/include/riscv-linux-gnu/bits/long-double.h index 57d7be04a685d57ab6e8e672e08d7effa853b06b..af7784dbe6dd85cd9538b5a7b437ab45de22eeb8 100644 --- a/lib/libc/include/riscv-linux-gnu/bits/long-double.h +++ b/lib/libc/include/riscv-linux-gnu/bits/long-double.h @@ -1,5 +1,5 @@ /* Properties of long double type. ldbl-128 version. - Copyright (C) 2016-2025 Free Software Foundation, Inc. + Copyright (C) 2016-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/riscv-linux-gnu/bits/procfs.h b/lib/libc/include/riscv-linux-gnu/bits/procfs.h index 41fe44e0a3adb50375ee7078e596585f1eb488c3..278397840bd2810abf8830666b13ee34bcda2b79 100644 --- a/lib/libc/include/riscv-linux-gnu/bits/procfs.h +++ b/lib/libc/include/riscv-linux-gnu/bits/procfs.h @@ -1,5 +1,5 @@ /* Types for registers for sys/procfs.h. RISC-V version. - Copyright (C) 1996-2025 Free Software Foundation, Inc. + Copyright (C) 1996-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/riscv-linux-gnu/bits/pthreadtypes-arch.h b/lib/libc/include/riscv-linux-gnu/bits/pthreadtypes-arch.h index 1e0839e949fa704d24fca3fc53c98561b5ed02ca..6218317e767f617e0686d3134710b3ff5f99142e 100644 --- a/lib/libc/include/riscv-linux-gnu/bits/pthreadtypes-arch.h +++ b/lib/libc/include/riscv-linux-gnu/bits/pthreadtypes-arch.h @@ -1,5 +1,5 @@ /* Machine-specific pthread type layouts. RISC-V version. - Copyright (C) 2011-2025 Free Software Foundation, Inc. + Copyright (C) 2011-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/riscv-linux-gnu/bits/rseq.h b/lib/libc/include/riscv-linux-gnu/bits/rseq.h index 0300604af999b73f01db7bb37978d78d2480db4c..5b82daaaaadd95da21f476aec2515bda966efaab 100644 --- a/lib/libc/include/riscv-linux-gnu/bits/rseq.h +++ b/lib/libc/include/riscv-linux-gnu/bits/rseq.h @@ -1,5 +1,5 @@ /* Restartable Sequences Linux riscv architecture header. - Copyright (C) 2021-2025 Free Software Foundation, Inc. + Copyright (C) 2021-2026 Free Software Foundation, Inc. The GNU C Library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public diff --git a/lib/libc/include/riscv-linux-gnu/bits/setjmp.h b/lib/libc/include/riscv-linux-gnu/bits/setjmp.h index d3dca3d9b1b21f29f65e4b2fb56e0b91213c55b8..75b2d8912398bff370d29f1fc3dc12fb6eed6e57 100644 --- a/lib/libc/include/riscv-linux-gnu/bits/setjmp.h +++ b/lib/libc/include/riscv-linux-gnu/bits/setjmp.h @@ -1,5 +1,5 @@ /* Define the machine-dependent type `jmp_buf'. RISC-V version. - Copyright (C) 2011-2025 Free Software Foundation, Inc. + Copyright (C) 2011-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/riscv-linux-gnu/bits/sigcontext.h b/lib/libc/include/riscv-linux-gnu/bits/sigcontext.h index 7634c22cff608362909f1f1246d36efd0c693f37..004e0bf5bd6d5414ab7efd769fc257856c73c355 100644 --- a/lib/libc/include/riscv-linux-gnu/bits/sigcontext.h +++ b/lib/libc/include/riscv-linux-gnu/bits/sigcontext.h @@ -1,5 +1,5 @@ /* Machine-dependent signal context structure for Linux. RISC-V version. - Copyright (C) 1996-2025 Free Software Foundation, Inc. This file is part of the GNU C Library. + Copyright (C) 1996-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public diff --git a/lib/libc/include/riscv-linux-gnu/bits/struct_rwlock.h b/lib/libc/include/riscv-linux-gnu/bits/struct_rwlock.h index b0674ce80c3f6cf2a7113961852f810d62aea9fc..41ef083aa0b7c68d47dc8b6c419c8fd289784c1f 100644 --- a/lib/libc/include/riscv-linux-gnu/bits/struct_rwlock.h +++ b/lib/libc/include/riscv-linux-gnu/bits/struct_rwlock.h @@ -1,5 +1,5 @@ /* RISC-V internal rwlock struct definitions. - Copyright (C) 2019-2025 Free Software Foundation, Inc. + Copyright (C) 2019-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. diff --git a/lib/libc/include/riscv-linux-gnu/bits/struct_stat.h b/lib/libc/include/riscv-linux-gnu/bits/struct_stat.h index 724450a6679649260300d400798787a515849f1d..0462d37a6849812f485a6830a7c0dca303332636 100644 --- a/lib/libc/include/riscv-linux-gnu/bits/struct_stat.h +++ b/lib/libc/include/riscv-linux-gnu/bits/struct_stat.h @@ -1,5 +1,5 @@ /* Definition for struct stat. - Copyright (C) 2020-2025 Free Software Foundation, Inc. + Copyright (C) 2020-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/riscv-linux-gnu/bits/time64.h b/lib/libc/include/riscv-linux-gnu/bits/time64.h index 1f82375244922aa8df2e5599cead61840d4ae66f..81476a4ac047e49282ef9741140afa7e19ae723d 100644 --- a/lib/libc/include/riscv-linux-gnu/bits/time64.h +++ b/lib/libc/include/riscv-linux-gnu/bits/time64.h @@ -1,5 +1,5 @@ /* bits/time64.h -- underlying types for __time64_t. RISC-V version. - Copyright (C) 2020-2025 Free Software Foundation, Inc. + Copyright (C) 2020-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/riscv-linux-gnu/bits/timesize.h b/lib/libc/include/riscv-linux-gnu/bits/timesize.h index 04251ea75c82b9450e5c2bee30c3bbf06cbe55fb..dff2da5ed6bf30ce6f5580aee352e0958d58b623 100644 --- a/lib/libc/include/riscv-linux-gnu/bits/timesize.h +++ b/lib/libc/include/riscv-linux-gnu/bits/timesize.h @@ -1,5 +1,5 @@ /* Bit size of the time_t type at glibc build time, general case. - Copyright (C) 2018-2025 Free Software Foundation, Inc. + Copyright (C) 2018-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/riscv-linux-gnu/bits/wordsize.h b/lib/libc/include/riscv-linux-gnu/bits/wordsize.h index 45cc378a99bb4f5c5d31b06989fc34c5ad146882..3c79368ccc13de4bf15eaf9becf336e870abcb6f 100644 --- a/lib/libc/include/riscv-linux-gnu/bits/wordsize.h +++ b/lib/libc/include/riscv-linux-gnu/bits/wordsize.h @@ -1,5 +1,5 @@ /* Determine the wordsize from the preprocessor defines. RISC-V version. - Copyright (C) 2002-2025 Free Software Foundation, Inc. + Copyright (C) 2002-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/riscv-linux-gnu/fpu_control.h b/lib/libc/include/riscv-linux-gnu/fpu_control.h index 6a621ca8840974c55bc756b19df9a0ba20e96947..838af6f139f326c5f4aebc63534ee363569fe606 100644 --- a/lib/libc/include/riscv-linux-gnu/fpu_control.h +++ b/lib/libc/include/riscv-linux-gnu/fpu_control.h @@ -1,5 +1,5 @@ /* FPU control word bits. RISC-V version. - Copyright (C) 1996-2025 Free Software Foundation, Inc. + Copyright (C) 1996-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or @@ -27,7 +27,7 @@ # define _FPU_DEFAULT 0x00000000 typedef unsigned int fpu_control_t; # define _FPU_GETCW(cw) (cw) = 0 -# define _FPU_SETCW(cw) do { } while (0) +# define _FPU_SETCW(cw) (void) (cw) extern fpu_control_t __fpu_control; #else /* __riscv_flen */ diff --git a/lib/libc/include/riscv-linux-gnu/ieee754.h b/lib/libc/include/riscv-linux-gnu/ieee754.h index a49523c3d8faa01c2f79bd6e565578db52ddb727..a19fd8dcd1d39c39a1b5000ca0b4d3f8da2c2299 100644 --- a/lib/libc/include/riscv-linux-gnu/ieee754.h +++ b/lib/libc/include/riscv-linux-gnu/ieee754.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1992-2025 Free Software Foundation, Inc. +/* Copyright (C) 1992-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/riscv-linux-gnu/sys/asm.h b/lib/libc/include/riscv-linux-gnu/sys/asm.h index ff8205da63ec2f829c2d3b790121eb4fc7bdd032..e66bb6eb5e175bf2e6e9e7a34fa8a4c1bb49c86c 100644 --- a/lib/libc/include/riscv-linux-gnu/sys/asm.h +++ b/lib/libc/include/riscv-linux-gnu/sys/asm.h @@ -1,5 +1,5 @@ /* Miscellaneous macros. - Copyright (C) 2000-2025 Free Software Foundation, Inc. + Copyright (C) 2000-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/riscv-linux-gnu/sys/cachectl.h b/lib/libc/include/riscv-linux-gnu/sys/cachectl.h index 599118a8aac516241a4c8a62ddd245d61b8275ec..3f25ca7fbb214b7ed55b3f3ee7ef161576a99336 100644 --- a/lib/libc/include/riscv-linux-gnu/sys/cachectl.h +++ b/lib/libc/include/riscv-linux-gnu/sys/cachectl.h @@ -1,5 +1,5 @@ /* RISC-V instruction cache flushing interface - Copyright (C) 2017-2025 Free Software Foundation, Inc. + Copyright (C) 2017-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. diff --git a/lib/libc/include/riscv-linux-gnu/sys/ucontext.h b/lib/libc/include/riscv-linux-gnu/sys/ucontext.h index 4a110dbafa2ff8178e926799dec486da5c8b54a7..ed06a30fe8acbdfe843453664ce1d22d3bc75b9d 100644 --- a/lib/libc/include/riscv-linux-gnu/sys/ucontext.h +++ b/lib/libc/include/riscv-linux-gnu/sys/ucontext.h @@ -1,5 +1,5 @@ /* struct ucontext definition, RISC-V version. - Copyright (C) 1997-2025 Free Software Foundation, Inc. + Copyright (C) 1997-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/riscv-linux-gnu/sys/user.h b/lib/libc/include/riscv-linux-gnu/sys/user.h index 3b4e3e9452d632e83c6e2c043a5814ac5c80b989..d8d1d86bfd8c673b4e79f9cac735eb26c7d30afd 100644 --- a/lib/libc/include/riscv-linux-gnu/sys/user.h +++ b/lib/libc/include/riscv-linux-gnu/sys/user.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2001-2025 Free Software Foundation, Inc. +/* Copyright (C) 2001-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/s390x-linux-gnu/bits/elfclass.h b/lib/libc/include/s390x-linux-gnu/bits/elfclass.h index a71bd938e73a0deafb92cd7dc3aae0366c9b4b31..325ca0fe4b41d0af0242fb38611aca0b39d4b525 100644 --- a/lib/libc/include/s390x-linux-gnu/bits/elfclass.h +++ b/lib/libc/include/s390x-linux-gnu/bits/elfclass.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2001-2025 Free Software Foundation, Inc. +/* Copyright (C) 2001-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/s390x-linux-gnu/bits/environments.h b/lib/libc/include/s390x-linux-gnu/bits/environments.h index 2bfd9501fbcc8f63b84b08ad9d83db1f075d839c..6a5d3e997c9e6592b4c5077da6e5f1d9e40d9b40 100644 --- a/lib/libc/include/s390x-linux-gnu/bits/environments.h +++ b/lib/libc/include/s390x-linux-gnu/bits/environments.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1999-2025 Free Software Foundation, Inc. +/* Copyright (C) 1999-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/s390x-linux-gnu/bits/fcntl.h b/lib/libc/include/s390x-linux-gnu/bits/fcntl.h index 4d58e01a3e4875f6b2ac49f950c29b1b3eee77e6..1d209c616c834cb7d1e8c4220a68852418694265 100644 --- a/lib/libc/include/s390x-linux-gnu/bits/fcntl.h +++ b/lib/libc/include/s390x-linux-gnu/bits/fcntl.h @@ -1,5 +1,5 @@ /* O_*, F_*, FD_* bit values for Linux. - Copyright (C) 2000-2025 Free Software Foundation, Inc. + Copyright (C) 2000-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/s390x-linux-gnu/bits/fenv.h b/lib/libc/include/s390x-linux-gnu/bits/fenv.h index d24ac91812d3393ce0e23560ec022656c5b8e314..eafcaa425b6835d5dd985020e748f3aa64e0c4a5 100644 --- a/lib/libc/include/s390x-linux-gnu/bits/fenv.h +++ b/lib/libc/include/s390x-linux-gnu/bits/fenv.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2000-2025 Free Software Foundation, Inc. +/* Copyright (C) 2000-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/s390x-linux-gnu/bits/hwcap.h b/lib/libc/include/s390x-linux-gnu/bits/hwcap.h index 3c98a2d6a1a795729f74801b72d546c832d272af..7fbcacd2d629d36d38a2dd1ef86920dfa04d5e1c 100644 --- a/lib/libc/include/s390x-linux-gnu/bits/hwcap.h +++ b/lib/libc/include/s390x-linux-gnu/bits/hwcap.h @@ -1,5 +1,5 @@ /* Defines for bits in AT_HWCAP. - Copyright (C) 2012-2025 Free Software Foundation, Inc. + Copyright (C) 2012-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/s390x-linux-gnu/bits/link.h b/lib/libc/include/s390x-linux-gnu/bits/link.h index 059bd600530c32edb76d5263ba39a38c136268c0..96694b37a417838cb4452446e0217fd7e3d4ebed 100644 --- a/lib/libc/include/s390x-linux-gnu/bits/link.h +++ b/lib/libc/include/s390x-linux-gnu/bits/link.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2005-2025 Free Software Foundation, Inc. +/* Copyright (C) 2005-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/s390x-linux-gnu/bits/long-double.h b/lib/libc/include/s390x-linux-gnu/bits/long-double.h index c83ef568eb4f7e74fbc7c0d8e77c588da1538d94..eede7634fbfad05505df72b0422e4f16eb2189a2 100644 --- a/lib/libc/include/s390x-linux-gnu/bits/long-double.h +++ b/lib/libc/include/s390x-linux-gnu/bits/long-double.h @@ -1,5 +1,5 @@ /* Properties of long double type. ldbl-opt version. - Copyright (C) 2016-2025 Free Software Foundation, Inc. + Copyright (C) 2016-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/s390x-linux-gnu/bits/procfs-extra.h b/lib/libc/include/s390x-linux-gnu/bits/procfs-extra.h index 166e47492a3f0141d83632c80bab426c1c8efc8a..7fb00d4146dae8fca24958e976e831c961090952 100644 --- a/lib/libc/include/s390x-linux-gnu/bits/procfs-extra.h +++ b/lib/libc/include/s390x-linux-gnu/bits/procfs-extra.h @@ -1,5 +1,5 @@ /* Extra sys/procfs.h definitions. S/390 version. - Copyright (C) 2000-2025 Free Software Foundation, Inc. + Copyright (C) 2000-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/s390x-linux-gnu/bits/procfs-id.h b/lib/libc/include/s390x-linux-gnu/bits/procfs-id.h index cf3fd750a4411cb363412ce64c2f06f7ce940cbf..9e8570cfe612efd5d35867d0a70a20593fde16f9 100644 --- a/lib/libc/include/s390x-linux-gnu/bits/procfs-id.h +++ b/lib/libc/include/s390x-linux-gnu/bits/procfs-id.h @@ -1,5 +1,5 @@ /* Types of pr_uid and pr_gid in struct elf_prpsinfo. S/390 version. - Copyright (C) 2018-2025 Free Software Foundation, Inc. + Copyright (C) 2018-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. diff --git a/lib/libc/include/s390x-linux-gnu/bits/procfs.h b/lib/libc/include/s390x-linux-gnu/bits/procfs.h index b725d09955750f28c43deb86d71fd613b18d9b86..2805da7ab7a4e153ccf4c9baa072477b4003dd14 100644 --- a/lib/libc/include/s390x-linux-gnu/bits/procfs.h +++ b/lib/libc/include/s390x-linux-gnu/bits/procfs.h @@ -1,5 +1,5 @@ /* Types for registers for sys/procfs.h. S/390 version. - Copyright (C) 2000-2025 Free Software Foundation, Inc. + Copyright (C) 2000-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/s390x-linux-gnu/bits/rseq.h b/lib/libc/include/s390x-linux-gnu/bits/rseq.h index ec56973f6e31791889552b0c9006e9e1ae15dd8c..44674e28778ce1c0776bc10a6ce628a2e099bfce 100644 --- a/lib/libc/include/s390x-linux-gnu/bits/rseq.h +++ b/lib/libc/include/s390x-linux-gnu/bits/rseq.h @@ -1,5 +1,5 @@ /* Restartable Sequences Linux s390 architecture header. - Copyright (C) 2021-2025 Free Software Foundation, Inc. + Copyright (C) 2021-2026 Free Software Foundation, Inc. The GNU C Library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public diff --git a/lib/libc/include/s390x-linux-gnu/bits/setjmp.h b/lib/libc/include/s390x-linux-gnu/bits/setjmp.h index 822f0fb3255e78f51f681006951e9866dd087a01..7d660921ab0295c49a77d9fb12cf4b021546d8c3 100644 --- a/lib/libc/include/s390x-linux-gnu/bits/setjmp.h +++ b/lib/libc/include/s390x-linux-gnu/bits/setjmp.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2000-2025 Free Software Foundation, Inc. +/* Copyright (C) 2000-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/s390x-linux-gnu/bits/sigaction.h b/lib/libc/include/s390x-linux-gnu/bits/sigaction.h index 5f38a9cb5c31b23d77b4e9b73aa79c1bd1f3da61..44883efbe768731d6953e626f57588887505c712 100644 --- a/lib/libc/include/s390x-linux-gnu/bits/sigaction.h +++ b/lib/libc/include/s390x-linux-gnu/bits/sigaction.h @@ -1,5 +1,5 @@ /* Definitions for 31 & 64 bit S/390 sigaction. - Copyright (C) 2001-2025 Free Software Foundation, Inc. + Copyright (C) 2001-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/s390x-linux-gnu/bits/statfs.h b/lib/libc/include/s390x-linux-gnu/bits/statfs.h index 577bb4eace56e7f4c00c1f8ee875701823be1927..b30fb52202dbf66a0d81129b8c7a86b2a58893f2 100644 --- a/lib/libc/include/s390x-linux-gnu/bits/statfs.h +++ b/lib/libc/include/s390x-linux-gnu/bits/statfs.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1997-2025 Free Software Foundation, Inc. +/* Copyright (C) 1997-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/s390x-linux-gnu/bits/struct_mutex.h b/lib/libc/include/s390x-linux-gnu/bits/struct_mutex.h index 504aec7fef757fde9874b39840d33fb0f0f01f86..7fc73321151dba01025bdf6f61e525f4a82d8e70 100644 --- a/lib/libc/include/s390x-linux-gnu/bits/struct_mutex.h +++ b/lib/libc/include/s390x-linux-gnu/bits/struct_mutex.h @@ -1,5 +1,5 @@ /* S390 internal mutex struct definitions. - Copyright (C) 2019-2025 Free Software Foundation, Inc. + Copyright (C) 2019-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or @@ -32,7 +32,7 @@ struct __pthread_mutex_s int __kind; #if __WORDSIZE == 64 short __spins; - short __elision; + short __unused; __pthread_list_t __list; # define __PTHREAD_MUTEX_HAVE_PREV 1 #else @@ -41,11 +41,10 @@ struct __pthread_mutex_s { struct { - short __espins; - short __elision; - } _d; -# define __spins _d.__espins -# define __elision _d.__elision + short __data_spins; + short __data_unused; + } __data; +# define __spins __data.__data_spins __pthread_slist_t __list; }; # define __PTHREAD_MUTEX_HAVE_PREV 0 diff --git a/lib/libc/include/s390x-linux-gnu/bits/struct_rwlock.h b/lib/libc/include/s390x-linux-gnu/bits/struct_rwlock.h index d7abdf7b31dde627028a67f9711ae7f592312102..e532dcddf63eef3facc4ec0021639de7ca1b49bc 100644 --- a/lib/libc/include/s390x-linux-gnu/bits/struct_rwlock.h +++ b/lib/libc/include/s390x-linux-gnu/bits/struct_rwlock.h @@ -1,5 +1,5 @@ /* S390 internal rwlock struct definitions. - Copyright (C) 2019-2025 Free Software Foundation, Inc. + Copyright (C) 2019-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. diff --git a/lib/libc/include/s390x-linux-gnu/bits/struct_stat.h b/lib/libc/include/s390x-linux-gnu/bits/struct_stat.h index 95897d90ec94f3936af71742a593b9b16e836a4e..3d77809e22d16b08b9329cb01c33a48e61177218 100644 --- a/lib/libc/include/s390x-linux-gnu/bits/struct_stat.h +++ b/lib/libc/include/s390x-linux-gnu/bits/struct_stat.h @@ -1,5 +1,5 @@ /* Definition for struct stat. - Copyright (C) 2020-2025 Free Software Foundation, Inc. + Copyright (C) 2020-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/s390x-linux-gnu/bits/timesize.h b/lib/libc/include/s390x-linux-gnu/bits/timesize.h index 95574667cb84a3233abe64d57c8017ddcf8503c9..5c231fe380193665c3e791dc1c16ef284787d758 100644 --- a/lib/libc/include/s390x-linux-gnu/bits/timesize.h +++ b/lib/libc/include/s390x-linux-gnu/bits/timesize.h @@ -1,5 +1,5 @@ /* Bit size of the time_t type at glibc build time, Linux/s390. - Copyright (C) 2021-2025 Free Software Foundation, Inc. + Copyright (C) 2021-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/s390x-linux-gnu/bits/typesizes.h b/lib/libc/include/s390x-linux-gnu/bits/typesizes.h index 1a9765ac61186023ad3778f0f0e5afea4dcc9643..5302d79b87a3bcdf2e8f9052fc3404030f9a3b35 100644 --- a/lib/libc/include/s390x-linux-gnu/bits/typesizes.h +++ b/lib/libc/include/s390x-linux-gnu/bits/typesizes.h @@ -1,5 +1,5 @@ /* bits/typesizes.h -- underlying types for *_t. Linux/s390 version. - Copyright (C) 2003-2025 Free Software Foundation, Inc. + Copyright (C) 2003-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/s390x-linux-gnu/bits/utmp.h b/lib/libc/include/s390x-linux-gnu/bits/utmp.h index dfb546be1ad3dab9b20f27cbcf5b091d8b705400..87db119de9415b8be0386c7bca655c4046fb2e36 100644 --- a/lib/libc/include/s390x-linux-gnu/bits/utmp.h +++ b/lib/libc/include/s390x-linux-gnu/bits/utmp.h @@ -1,5 +1,5 @@ /* The `struct utmp' type, describing entries in the utmp file. GNU version. - Copyright (C) 1993-2025 Free Software Foundation, Inc. + Copyright (C) 1993-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/s390x-linux-gnu/bits/utmpx.h b/lib/libc/include/s390x-linux-gnu/bits/utmpx.h index 5ee9a5ca3ab07a6cb6f6237ef5ae881f50670b67..00ed2f21a1c4853e7f45dafe7d8ca46a0b4b44a7 100644 --- a/lib/libc/include/s390x-linux-gnu/bits/utmpx.h +++ b/lib/libc/include/s390x-linux-gnu/bits/utmpx.h @@ -1,5 +1,5 @@ /* Structures and definitions for the user accounting database. GNU version. - Copyright (C) 1997-2025 Free Software Foundation, Inc. + Copyright (C) 1997-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/s390x-linux-gnu/fpu_control.h b/lib/libc/include/s390x-linux-gnu/fpu_control.h index ed1ced78849bb05371fbc628c0c3179cd8c23ae2..fc58388f4fb56a7c82d600a823f6b169e4659f7c 100644 --- a/lib/libc/include/s390x-linux-gnu/fpu_control.h +++ b/lib/libc/include/s390x-linux-gnu/fpu_control.h @@ -1,5 +1,5 @@ /* FPU control word definitions. Stub version. - Copyright (C) 2000-2025 Free Software Foundation, Inc. + Copyright (C) 2000-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/s390x-linux-gnu/ieee754.h b/lib/libc/include/s390x-linux-gnu/ieee754.h index a49523c3d8faa01c2f79bd6e565578db52ddb727..a19fd8dcd1d39c39a1b5000ca0b4d3f8da2c2299 100644 --- a/lib/libc/include/s390x-linux-gnu/ieee754.h +++ b/lib/libc/include/s390x-linux-gnu/ieee754.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1992-2025 Free Software Foundation, Inc. +/* Copyright (C) 1992-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/s390x-linux-gnu/sys/elf.h b/lib/libc/include/s390x-linux-gnu/sys/elf.h index ae26bc6b867338796dd19805bf80a8bb681be63f..70e50aba7435922469f88950028aaed3acf30678 100644 --- a/lib/libc/include/s390x-linux-gnu/sys/elf.h +++ b/lib/libc/include/s390x-linux-gnu/sys/elf.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2000-2025 Free Software Foundation, Inc. +/* Copyright (C) 2000-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/s390x-linux-gnu/sys/ptrace.h b/lib/libc/include/s390x-linux-gnu/sys/ptrace.h index 524fdcae4bb629bc7d0905cb27cbe319496f560b..d1eb596e8f40cd3d20cc6f571adc7be1df0ab7c0 100644 --- a/lib/libc/include/s390x-linux-gnu/sys/ptrace.h +++ b/lib/libc/include/s390x-linux-gnu/sys/ptrace.h @@ -1,5 +1,5 @@ /* `ptrace' debugger support interface. Linux/S390 version. - Copyright (C) 2000-2025 Free Software Foundation, Inc. + Copyright (C) 2000-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/s390x-linux-gnu/sys/ucontext.h b/lib/libc/include/s390x-linux-gnu/sys/ucontext.h index 4e3ece22a75a88f984cac026c3f6bbc330872c9d..a766145563a3c44816cf2985502cfc5f11507948 100644 --- a/lib/libc/include/s390x-linux-gnu/sys/ucontext.h +++ b/lib/libc/include/s390x-linux-gnu/sys/ucontext.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2000-2025 Free Software Foundation, Inc. +/* Copyright (C) 2000-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/s390x-linux-gnu/sys/user.h b/lib/libc/include/s390x-linux-gnu/sys/user.h index 6e93a49d111397138941c834bcf4977f3e6cf999..0155571f744a4d01ebce98386c039ad02fa68bf7 100644 --- a/lib/libc/include/s390x-linux-gnu/sys/user.h +++ b/lib/libc/include/s390x-linux-gnu/sys/user.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2000-2025 Free Software Foundation, Inc. +/* Copyright (C) 2000-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/sparc-linux-gnu/bits/environments.h b/lib/libc/include/sparc-linux-gnu/bits/environments.h index 5a19b4cbd48af000a5e8a98fce1ce4d3db924ed2..1f7533af49568f229f835f8755b8e8f71ad50ea8 100644 --- a/lib/libc/include/sparc-linux-gnu/bits/environments.h +++ b/lib/libc/include/sparc-linux-gnu/bits/environments.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1999-2025 Free Software Foundation, Inc. +/* Copyright (C) 1999-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/sparc-linux-gnu/bits/epoll.h b/lib/libc/include/sparc-linux-gnu/bits/epoll.h index 424774be9388aa689b07ccb214b47df2e1df8f94..a941b8d08be8550036f96b2921e910905754e4b1 100644 --- a/lib/libc/include/sparc-linux-gnu/bits/epoll.h +++ b/lib/libc/include/sparc-linux-gnu/bits/epoll.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2002-2025 Free Software Foundation, Inc. +/* Copyright (C) 2002-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/sparc-linux-gnu/bits/errno.h b/lib/libc/include/sparc-linux-gnu/bits/errno.h index 64526c7bf3cc2c557410491f0cc60f489855f09e..906432afa28f59298d76e0d84d216bc86439ec27 100644 --- a/lib/libc/include/sparc-linux-gnu/bits/errno.h +++ b/lib/libc/include/sparc-linux-gnu/bits/errno.h @@ -1,5 +1,5 @@ /* Error constants. Linux/Sparc specific version. - Copyright (C) 1996-2025 Free Software Foundation, Inc. + Copyright (C) 1996-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/sparc-linux-gnu/bits/eventfd.h b/lib/libc/include/sparc-linux-gnu/bits/eventfd.h index fac0f292271ed34c3136f5c6b2cbc939c915d2e5..df3bb9acddc50159cfeba0a1d701b0f89c95edce 100644 --- a/lib/libc/include/sparc-linux-gnu/bits/eventfd.h +++ b/lib/libc/include/sparc-linux-gnu/bits/eventfd.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2007-2025 Free Software Foundation, Inc. +/* Copyright (C) 2007-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/sparc-linux-gnu/bits/fcntl.h b/lib/libc/include/sparc-linux-gnu/bits/fcntl.h index d51a7873a35a725b4273d69176ecc529cc43f40d..4f14c87748fba6979206e9c2d574b1b40df51d91 100644 --- a/lib/libc/include/sparc-linux-gnu/bits/fcntl.h +++ b/lib/libc/include/sparc-linux-gnu/bits/fcntl.h @@ -1,5 +1,5 @@ /* O_*, F_*, FD_* bit values for Linux/SPARC. - Copyright (C) 1995-2025 Free Software Foundation, Inc. + Copyright (C) 1995-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/sparc-linux-gnu/bits/fenv.h b/lib/libc/include/sparc-linux-gnu/bits/fenv.h index 2fac04873e8539a23a30790bc9ba83ada29f6d49..08a73630a17b5d78a40bdbda8d85335a25855bff 100644 --- a/lib/libc/include/sparc-linux-gnu/bits/fenv.h +++ b/lib/libc/include/sparc-linux-gnu/bits/fenv.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1997-2025 Free Software Foundation, Inc. +/* Copyright (C) 1997-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/sparc-linux-gnu/bits/hwcap.h b/lib/libc/include/sparc-linux-gnu/bits/hwcap.h index 409da7f39cbaf29c18c1b33ac5ffb2fe1dc97ddf..75283675a005204a08d31d6ad4c0df8f2d6ee15e 100644 --- a/lib/libc/include/sparc-linux-gnu/bits/hwcap.h +++ b/lib/libc/include/sparc-linux-gnu/bits/hwcap.h @@ -1,5 +1,5 @@ /* Defines for bits in AT_HWCAP. - Copyright (C) 2011-2025 Free Software Foundation, Inc. + Copyright (C) 2011-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/sparc-linux-gnu/bits/inotify.h b/lib/libc/include/sparc-linux-gnu/bits/inotify.h index e68756189b228003b70ab76f88957f7666af3664..e40de6bae24381fc4b6bb9979b6da075691f43b8 100644 --- a/lib/libc/include/sparc-linux-gnu/bits/inotify.h +++ b/lib/libc/include/sparc-linux-gnu/bits/inotify.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2005-2025 Free Software Foundation, Inc. +/* Copyright (C) 2005-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/sparc-linux-gnu/bits/ioctls.h b/lib/libc/include/sparc-linux-gnu/bits/ioctls.h index f35894212f7e28c656107f41be7ed729c910e488..38af369b1265cf47014b274f02101f3f8a25028f 100644 --- a/lib/libc/include/sparc-linux-gnu/bits/ioctls.h +++ b/lib/libc/include/sparc-linux-gnu/bits/ioctls.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1996-2025 Free Software Foundation, Inc. +/* Copyright (C) 1996-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/sparc-linux-gnu/bits/ipc-perm.h b/lib/libc/include/sparc-linux-gnu/bits/ipc-perm.h index cfa9c532fc664628d62802fdd6c24b3d4a3af7af..13e4fc3db5f8107c6184482acd689d2444ca16c6 100644 --- a/lib/libc/include/sparc-linux-gnu/bits/ipc-perm.h +++ b/lib/libc/include/sparc-linux-gnu/bits/ipc-perm.h @@ -1,5 +1,5 @@ /* struct ipc_perm definition. Linux/sparc version. - Copyright (C) 1995-2025 Free Software Foundation, Inc. + Copyright (C) 1995-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/sparc-linux-gnu/bits/link.h b/lib/libc/include/sparc-linux-gnu/bits/link.h index b785272609be88ec26a4410125513de0e8eb671c..b42bcae68399580466795e3051b227dd20c96b86 100644 --- a/lib/libc/include/sparc-linux-gnu/bits/link.h +++ b/lib/libc/include/sparc-linux-gnu/bits/link.h @@ -1,5 +1,5 @@ /* Machine-specific audit interfaces for dynamic linker. SPARC version. - Copyright (C) 2005-2025 Free Software Foundation, Inc. + Copyright (C) 2005-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/sparc-linux-gnu/bits/long-double.h b/lib/libc/include/sparc-linux-gnu/bits/long-double.h index 608d7e066fbe357d5dd4b74591e3a05b5fbd6cbe..da1ea20fb3d14254397e0e0c743fce14918793d6 100644 --- a/lib/libc/include/sparc-linux-gnu/bits/long-double.h +++ b/lib/libc/include/sparc-linux-gnu/bits/long-double.h @@ -1,5 +1,5 @@ /* Properties of long double type. SPARC version. - Copyright (C) 2016-2025 Free Software Foundation, Inc. + Copyright (C) 2016-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/sparc-linux-gnu/bits/mman.h b/lib/libc/include/sparc-linux-gnu/bits/mman.h index 28c384b7618abc6aa8126f48a0d6513b885b7222..1e58534e4540391d1c6c2cab8ab95ec2fc4d845e 100644 --- a/lib/libc/include/sparc-linux-gnu/bits/mman.h +++ b/lib/libc/include/sparc-linux-gnu/bits/mman.h @@ -1,5 +1,5 @@ /* Definitions for POSIX memory map interface. Linux/SPARC version. - Copyright (C) 1997-2025 Free Software Foundation, Inc. + Copyright (C) 1997-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/sparc-linux-gnu/bits/poll.h b/lib/libc/include/sparc-linux-gnu/bits/poll.h index b881b0803f0467147d3082391e285f6f8e6bd07c..fedffb047a8513c8ddc3074ced64980f7d74ccd1 100644 --- a/lib/libc/include/sparc-linux-gnu/bits/poll.h +++ b/lib/libc/include/sparc-linux-gnu/bits/poll.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1997-2025 Free Software Foundation, Inc. +/* Copyright (C) 1997-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/sparc-linux-gnu/bits/procfs-extra.h b/lib/libc/include/sparc-linux-gnu/bits/procfs-extra.h index 3149f4c84e822410d7f6fe888ba935a65dd1b154..3e02bcfcff3d7db069b0e371b6a3a49eb2a7a55e 100644 --- a/lib/libc/include/sparc-linux-gnu/bits/procfs-extra.h +++ b/lib/libc/include/sparc-linux-gnu/bits/procfs-extra.h @@ -1,5 +1,5 @@ /* Extra sys/procfs.h definitions. SPARC version. - Copyright (C) 1996-2025 Free Software Foundation, Inc. + Copyright (C) 1996-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/sparc-linux-gnu/bits/procfs-id.h b/lib/libc/include/sparc-linux-gnu/bits/procfs-id.h index 08db45b76d2ee46604d2e7c4dabaccfcf92c2aa5..e738ed239cbd06155e3f33ba36d90d557b667a1d 100644 --- a/lib/libc/include/sparc-linux-gnu/bits/procfs-id.h +++ b/lib/libc/include/sparc-linux-gnu/bits/procfs-id.h @@ -1,5 +1,5 @@ /* Types of pr_uid and pr_gid in struct elf_prpsinfo. SPARC version. - Copyright (C) 2018-2025 Free Software Foundation, Inc. + Copyright (C) 2018-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. diff --git a/lib/libc/include/sparc-linux-gnu/bits/procfs.h b/lib/libc/include/sparc-linux-gnu/bits/procfs.h index f4f204a2bb112a8331e93047fd18e9640a781050..14503b5968c2cbf300d5b960a4928a496b9cd774 100644 --- a/lib/libc/include/sparc-linux-gnu/bits/procfs.h +++ b/lib/libc/include/sparc-linux-gnu/bits/procfs.h @@ -1,5 +1,5 @@ /* Types for registers for sys/procfs.h. SPARC version. - Copyright (C) 1996-2025 Free Software Foundation, Inc. + Copyright (C) 1996-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/sparc-linux-gnu/bits/pthread_stack_min.h b/lib/libc/include/sparc-linux-gnu/bits/pthread_stack_min.h index baa588c020eb0354cf607a08251a852779cf3d85..355026a739ebf15e1acd7f854496dfca26be0acf 100644 --- a/lib/libc/include/sparc-linux-gnu/bits/pthread_stack_min.h +++ b/lib/libc/include/sparc-linux-gnu/bits/pthread_stack_min.h @@ -1,5 +1,5 @@ /* Definition of PTHREAD_STACK_MIN. Linux/SPARC version. - Copyright (C) 2021-2025 Free Software Foundation, Inc. + Copyright (C) 2021-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/sparc-linux-gnu/bits/resource.h b/lib/libc/include/sparc-linux-gnu/bits/resource.h index c9b20dff544cf4d068393045f4c04b3a1e8c6d8e..7947dfc385f5c6e1169fd5eefac17e50ab4855bf 100644 --- a/lib/libc/include/sparc-linux-gnu/bits/resource.h +++ b/lib/libc/include/sparc-linux-gnu/bits/resource.h @@ -1,5 +1,5 @@ /* Bit values & structures for resource limits. Linux/SPARC version. - Copyright (C) 1994-2025 Free Software Foundation, Inc. + Copyright (C) 1994-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/sparc-linux-gnu/bits/rseq.h b/lib/libc/include/sparc-linux-gnu/bits/rseq.h index 90b3e9804a5b827612f37a31f9805ee0d82ad9a4..a844012dd3a527f6948a027145ecc10a1f01903f 100644 --- a/lib/libc/include/sparc-linux-gnu/bits/rseq.h +++ b/lib/libc/include/sparc-linux-gnu/bits/rseq.h @@ -1,5 +1,5 @@ /* Restartable Sequences architecture header. Stub version. - Copyright (C) 2021-2025 Free Software Foundation, Inc. + Copyright (C) 2021-2026 Free Software Foundation, Inc. The GNU C Library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public diff --git a/lib/libc/include/sparc-linux-gnu/bits/setjmp.h b/lib/libc/include/sparc-linux-gnu/bits/setjmp.h index 8fb2bb1167c085865f736ab013b2386b387eed5a..2ed4b06c588648d0b4d82f30734494349a465f42 100644 --- a/lib/libc/include/sparc-linux-gnu/bits/setjmp.h +++ b/lib/libc/include/sparc-linux-gnu/bits/setjmp.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1997-2025 Free Software Foundation, Inc. +/* Copyright (C) 1997-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/sparc-linux-gnu/bits/shmlba.h b/lib/libc/include/sparc-linux-gnu/bits/shmlba.h index 49f9ae58af29f3560109631eaa7a52b4e37c8250..8bc00d9bd044fecf1fdaef51739111316cabc9fb 100644 --- a/lib/libc/include/sparc-linux-gnu/bits/shmlba.h +++ b/lib/libc/include/sparc-linux-gnu/bits/shmlba.h @@ -1,5 +1,5 @@ /* Define SHMLBA. SPARC version. - Copyright (C) 2018-2025 Free Software Foundation, Inc. + Copyright (C) 2018-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/sparc-linux-gnu/bits/sigaction.h b/lib/libc/include/sparc-linux-gnu/bits/sigaction.h index 3ef3e0d97ad218fc6bea7efd35e3eac1d03ae30d..9b1925e6a7f715c77592e6b014e1da17e5925079 100644 --- a/lib/libc/include/sparc-linux-gnu/bits/sigaction.h +++ b/lib/libc/include/sparc-linux-gnu/bits/sigaction.h @@ -1,5 +1,5 @@ /* The proper definitions for Linux/SPARC sigaction. - Copyright (C) 1996-2025 Free Software Foundation, Inc. + Copyright (C) 1996-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/sparc-linux-gnu/bits/sigcontext.h b/lib/libc/include/sparc-linux-gnu/bits/sigcontext.h index 20ca9e2435c741188a0ebd7656ad40f3de51dbea..836c2cf39253ef14c1d306cd8fc5de35255caf13 100644 --- a/lib/libc/include/sparc-linux-gnu/bits/sigcontext.h +++ b/lib/libc/include/sparc-linux-gnu/bits/sigcontext.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2000-2025 Free Software Foundation, Inc. +/* Copyright (C) 2000-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/sparc-linux-gnu/bits/signalfd.h b/lib/libc/include/sparc-linux-gnu/bits/signalfd.h index 2dc0e606c927cce246a91a9f4037d9a30d78913d..e4c5fc661527a964ad6afba0ab07b69c7f173a38 100644 --- a/lib/libc/include/sparc-linux-gnu/bits/signalfd.h +++ b/lib/libc/include/sparc-linux-gnu/bits/signalfd.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2007-2025 Free Software Foundation, Inc. +/* Copyright (C) 2007-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/sparc-linux-gnu/bits/signum-arch.h b/lib/libc/include/sparc-linux-gnu/bits/signum-arch.h index 152e9a40380debdcee0810b2b21985f3e7f1ba39..f3bdc98869e760e0d9a61a3d6fe8eb7cfc7b91eb 100644 --- a/lib/libc/include/sparc-linux-gnu/bits/signum-arch.h +++ b/lib/libc/include/sparc-linux-gnu/bits/signum-arch.h @@ -1,5 +1,5 @@ /* Signal number definitions. Linux/SPARC version. - Copyright (C) 1996-2025 Free Software Foundation, Inc. + Copyright (C) 1996-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/sparc-linux-gnu/bits/sigstack.h b/lib/libc/include/sparc-linux-gnu/bits/sigstack.h index 42d3c1ba97907509d1ffb08b474d637638afde2d..54469ca3a68d45721748b3a35fe90de5ed4188fd 100644 --- a/lib/libc/include/sparc-linux-gnu/bits/sigstack.h +++ b/lib/libc/include/sparc-linux-gnu/bits/sigstack.h @@ -1,5 +1,5 @@ /* sigstack, sigaltstack definitions. - Copyright (C) 1998-2025 Free Software Foundation, Inc. + Copyright (C) 1998-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/sparc-linux-gnu/bits/socket-constants.h b/lib/libc/include/sparc-linux-gnu/bits/socket-constants.h index e9e2715d00dfe015f9819485365ff6ce8f5bb1ce..8f50b2f50a1b4e97b8f4ae772101d0569aadc140 100644 --- a/lib/libc/include/sparc-linux-gnu/bits/socket-constants.h +++ b/lib/libc/include/sparc-linux-gnu/bits/socket-constants.h @@ -1,5 +1,5 @@ /* Socket constants which vary among Linux architectures. Version for SPARC. - Copyright (C) 2019-2025 Free Software Foundation, Inc. + Copyright (C) 2019-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/sparc-linux-gnu/bits/socket_type.h b/lib/libc/include/sparc-linux-gnu/bits/socket_type.h index f9acac32675abb21c19c5c2aa899763650910657..d3dcbc2dc46f73dc2a60224842a43a149cbd460e 100644 --- a/lib/libc/include/sparc-linux-gnu/bits/socket_type.h +++ b/lib/libc/include/sparc-linux-gnu/bits/socket_type.h @@ -1,5 +1,5 @@ /* Define enum __socket_type for Linux/SPARC. - Copyright (C) 1991-2025 Free Software Foundation, Inc. + Copyright (C) 1991-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/sparc-linux-gnu/bits/struct_rwlock.h b/lib/libc/include/sparc-linux-gnu/bits/struct_rwlock.h index 462460872476a6997d23c8fd6ea8056967c95fe1..e9df79b378cf799703da6857dae50843e8c60234 100644 --- a/lib/libc/include/sparc-linux-gnu/bits/struct_rwlock.h +++ b/lib/libc/include/sparc-linux-gnu/bits/struct_rwlock.h @@ -1,5 +1,5 @@ /* SPARC internal rwlock struct definitions. - Copyright (C) 2019-2025 Free Software Foundation, Inc. + Copyright (C) 2019-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. diff --git a/lib/libc/include/sparc-linux-gnu/bits/struct_stat.h b/lib/libc/include/sparc-linux-gnu/bits/struct_stat.h index a43cb63dde6f7bda638a11baacc4c43c5bc60bdb..4fc383d6e02b478780b6e8f9627e1137d7dde828 100644 --- a/lib/libc/include/sparc-linux-gnu/bits/struct_stat.h +++ b/lib/libc/include/sparc-linux-gnu/bits/struct_stat.h @@ -1,5 +1,5 @@ /* Definition for struct stat. - Copyright (C) 2020-2025 Free Software Foundation, Inc. + Copyright (C) 2020-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/sparc-linux-gnu/bits/termios-c_cc.h b/lib/libc/include/sparc-linux-gnu/bits/termios-c_cc.h index 989b895de675fbb305ced445dbdcc01e985c1f87..164558d553669ef7267d53a4748b37caf51e5fab 100644 --- a/lib/libc/include/sparc-linux-gnu/bits/termios-c_cc.h +++ b/lib/libc/include/sparc-linux-gnu/bits/termios-c_cc.h @@ -1,5 +1,5 @@ /* termios c_cc symbolic constant definitions. Linux/sparc version. - Copyright (C) 2019-2025 Free Software Foundation, Inc. + Copyright (C) 2019-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/sparc-linux-gnu/bits/termios-c_oflag.h b/lib/libc/include/sparc-linux-gnu/bits/termios-c_oflag.h index 036be52447bd28483b7f841b5aa0a99b257012b8..51990c0387d7979d4a0915c8c18175adc4054aad 100644 --- a/lib/libc/include/sparc-linux-gnu/bits/termios-c_oflag.h +++ b/lib/libc/include/sparc-linux-gnu/bits/termios-c_oflag.h @@ -1,5 +1,5 @@ /* termios output mode definitions. Linux/sparc version. - Copyright (C) 2019-2025 Free Software Foundation, Inc. + Copyright (C) 2019-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/sparc-linux-gnu/bits/termios-cbaud.h b/lib/libc/include/sparc-linux-gnu/bits/termios-cbaud.h index e848a18538c9fb8c5880fc6f9cd76cf3e160b07d..7e916fddc9158f6b2f556d4e4d32ec8f1faec5ed 100644 --- a/lib/libc/include/sparc-linux-gnu/bits/termios-cbaud.h +++ b/lib/libc/include/sparc-linux-gnu/bits/termios-cbaud.h @@ -1,5 +1,5 @@ /* termios baud rate selection definitions. Linux/sparc version. - Copyright (C) 2019-2025 Free Software Foundation, Inc. + Copyright (C) 2019-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/sparc-linux-gnu/bits/timerfd.h b/lib/libc/include/sparc-linux-gnu/bits/timerfd.h index 32e03256e3abd63d1919f4342275d6b7e1e359e9..496aff1cc9237f0fb8ddc219864e56a81b252907 100644 --- a/lib/libc/include/sparc-linux-gnu/bits/timerfd.h +++ b/lib/libc/include/sparc-linux-gnu/bits/timerfd.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2008-2025 Free Software Foundation, Inc. +/* Copyright (C) 2008-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/sparc-linux-gnu/bits/timesize.h b/lib/libc/include/sparc-linux-gnu/bits/timesize.h index d020c10c6e460c908d16dcc8abfa68ff925238ee..06bd725e1ea11418cd9f0e5b25826d554f803fed 100644 --- a/lib/libc/include/sparc-linux-gnu/bits/timesize.h +++ b/lib/libc/include/sparc-linux-gnu/bits/timesize.h @@ -1,5 +1,5 @@ /* Bit size of the time_t type at glibc build time, Linux/sparc. - Copyright (C) 2021-2025 Free Software Foundation, Inc. + Copyright (C) 2021-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/sparc-linux-gnu/bits/types/struct_msqid_ds.h b/lib/libc/include/sparc-linux-gnu/bits/types/struct_msqid_ds.h index 6d0f2b9a8ee66711c5ace2dfac3630d3527b8b9c..4566442ad07148a6536870f7755de2c0b4a7e5fb 100644 --- a/lib/libc/include/sparc-linux-gnu/bits/types/struct_msqid_ds.h +++ b/lib/libc/include/sparc-linux-gnu/bits/types/struct_msqid_ds.h @@ -1,5 +1,5 @@ /* Linux/SPARC implementation of the SysV message struct msqid_ds. - Copyright (C) 2020-2025 Free Software Foundation, Inc. + Copyright (C) 2020-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/sparc-linux-gnu/bits/types/struct_semid_ds.h b/lib/libc/include/sparc-linux-gnu/bits/types/struct_semid_ds.h index f8db7f00d3e49beb28e688b3d06ff81eb1b4fdca..b3f59d96b82fe1b2170813e4776e927b06759937 100644 --- a/lib/libc/include/sparc-linux-gnu/bits/types/struct_semid_ds.h +++ b/lib/libc/include/sparc-linux-gnu/bits/types/struct_semid_ds.h @@ -1,5 +1,5 @@ /* Sparc implementation of the semaphore struct semid_ds - Copyright (C) 1995-2025 Free Software Foundation, Inc. + Copyright (C) 1995-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/sparc-linux-gnu/bits/types/struct_shmid_ds.h b/lib/libc/include/sparc-linux-gnu/bits/types/struct_shmid_ds.h index 88d72bb4af7b6bb262a8b25b3d1bebc6199bdb90..678737c3e4cd10931ee4a24ce71c988d221faf8e 100644 --- a/lib/libc/include/sparc-linux-gnu/bits/types/struct_shmid_ds.h +++ b/lib/libc/include/sparc-linux-gnu/bits/types/struct_shmid_ds.h @@ -1,5 +1,5 @@ /* Linux/SPARC implementation of the shared memory struct shmid_ds. - Copyright (C) 2020-2025 Free Software Foundation, Inc. + Copyright (C) 2020-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/sparc-linux-gnu/bits/typesizes.h b/lib/libc/include/sparc-linux-gnu/bits/typesizes.h index e283787261ba3f7bff5359f0ae9a5010fb212f08..2eb70bdf1dbf06f3652faa42ee1fcf7e13cb73e9 100644 --- a/lib/libc/include/sparc-linux-gnu/bits/typesizes.h +++ b/lib/libc/include/sparc-linux-gnu/bits/typesizes.h @@ -1,5 +1,5 @@ /* bits/typesizes.h -- underlying types for *_t. Linux/SPARC version. - Copyright (C) 2002-2025 Free Software Foundation, Inc. + Copyright (C) 2002-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/sparc-linux-gnu/fpu_control.h b/lib/libc/include/sparc-linux-gnu/fpu_control.h index 94d7424edb28ab60428ecc8a815d597ea5b8d7f3..40b1a50e1e042f56592db3637a92273941b0c3a0 100644 --- a/lib/libc/include/sparc-linux-gnu/fpu_control.h +++ b/lib/libc/include/sparc-linux-gnu/fpu_control.h @@ -1,5 +1,5 @@ /* FPU control word bits. SPARC version. - Copyright (C) 1997-2025 Free Software Foundation, Inc. + Copyright (C) 1997-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/sparc-linux-gnu/ieee754.h b/lib/libc/include/sparc-linux-gnu/ieee754.h index a49523c3d8faa01c2f79bd6e565578db52ddb727..a19fd8dcd1d39c39a1b5000ca0b4d3f8da2c2299 100644 --- a/lib/libc/include/sparc-linux-gnu/ieee754.h +++ b/lib/libc/include/sparc-linux-gnu/ieee754.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1992-2025 Free Software Foundation, Inc. +/* Copyright (C) 1992-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/sparc-linux-gnu/sys/ptrace.h b/lib/libc/include/sparc-linux-gnu/sys/ptrace.h index 3add6c11885b6ac0b62224634a959111c7018a33..76ab8464aa11d0355725536a62cd91eb8d61391d 100644 --- a/lib/libc/include/sparc-linux-gnu/sys/ptrace.h +++ b/lib/libc/include/sparc-linux-gnu/sys/ptrace.h @@ -1,5 +1,5 @@ /* `ptrace' debugger support interface. Linux/SPARC version. - Copyright (C) 1996-2025 Free Software Foundation, Inc. + Copyright (C) 1996-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/sparc-linux-gnu/sys/ucontext.h b/lib/libc/include/sparc-linux-gnu/sys/ucontext.h index cd5938a7542fd84de224401152d9a0a53c372281..8b386aa17f4ff724caf9eca61b315bb8167ec750 100644 --- a/lib/libc/include/sparc-linux-gnu/sys/ucontext.h +++ b/lib/libc/include/sparc-linux-gnu/sys/ucontext.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1998-2025 Free Software Foundation, Inc. +/* Copyright (C) 1998-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/sparc-linux-gnu/sys/user.h b/lib/libc/include/sparc-linux-gnu/sys/user.h index b45cb81aa6c12f6db5df6dda9a6fd9be6c2b09a3..50bc7604065ed423501aacba5f9e8b7931e0af4c 100644 --- a/lib/libc/include/sparc-linux-gnu/sys/user.h +++ b/lib/libc/include/sparc-linux-gnu/sys/user.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2003-2025 Free Software Foundation, Inc. +/* Copyright (C) 2003-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/x86-linux-gnu/bits/dl_find_object.h b/lib/libc/include/x86-linux-gnu/bits/dl_find_object.h index dfe01a86516db0c13f58773d7bed10b124d10ba3..00095a2f02c11ec4c2d392b24a50100411642102 100644 --- a/lib/libc/include/x86-linux-gnu/bits/dl_find_object.h +++ b/lib/libc/include/x86-linux-gnu/bits/dl_find_object.h @@ -1,5 +1,5 @@ /* x86 definitions for finding objects. - Copyright (C) 2021-2025 Free Software Foundation, Inc. + Copyright (C) 2021-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/x86-linux-gnu/bits/environments.h b/lib/libc/include/x86-linux-gnu/bits/environments.h index a7c4ebb29ceb8c2f640317efe3e3b448217e8739..77316861ec9d8cd31e0190caab9db2524704906a 100644 --- a/lib/libc/include/x86-linux-gnu/bits/environments.h +++ b/lib/libc/include/x86-linux-gnu/bits/environments.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1999-2025 Free Software Foundation, Inc. +/* Copyright (C) 1999-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/x86-linux-gnu/bits/epoll.h b/lib/libc/include/x86-linux-gnu/bits/epoll.h index 3fd9ba0e8f11783059a5b92dbf6063ccaea20b4a..7861c15c74422fc6553ac9b9c03b18e42311394e 100644 --- a/lib/libc/include/x86-linux-gnu/bits/epoll.h +++ b/lib/libc/include/x86-linux-gnu/bits/epoll.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2002-2025 Free Software Foundation, Inc. +/* Copyright (C) 2002-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/x86-linux-gnu/bits/fcntl.h b/lib/libc/include/x86-linux-gnu/bits/fcntl.h index 2fe90f292de1d9157695d3cb702f23487a957678..234f66f87c3a989e97717232d44cc5cd0feaab77 100644 --- a/lib/libc/include/x86-linux-gnu/bits/fcntl.h +++ b/lib/libc/include/x86-linux-gnu/bits/fcntl.h @@ -1,5 +1,5 @@ /* O_*, F_*, FD_* bit values for Linux/x86. - Copyright (C) 2001-2025 Free Software Foundation, Inc. + Copyright (C) 2001-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/x86-linux-gnu/bits/fenv.h b/lib/libc/include/x86-linux-gnu/bits/fenv.h index 9f1b96d90d2d15a8ae059bb8e3876e35a7c6dcf3..698f3f47c34b37150b5c5d454a1d33d4d9b662b0 100644 --- a/lib/libc/include/x86-linux-gnu/bits/fenv.h +++ b/lib/libc/include/x86-linux-gnu/bits/fenv.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1997-2025 Free Software Foundation, Inc. +/* Copyright (C) 1997-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/x86-linux-gnu/bits/floatn.h b/lib/libc/include/x86-linux-gnu/bits/floatn.h index b0bab474a1aaffa8ded90c4e5426662d23a080d8..789de21a23a9565d4c763dac566009109112e52b 100644 --- a/lib/libc/include/x86-linux-gnu/bits/floatn.h +++ b/lib/libc/include/x86-linux-gnu/bits/floatn.h @@ -1,5 +1,5 @@ /* Macros to control TS 18661-3 glibc features on x86. - Copyright (C) 2017-2025 Free Software Foundation, Inc. + Copyright (C) 2017-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/x86-linux-gnu/bits/flt-eval-method.h b/lib/libc/include/x86-linux-gnu/bits/flt-eval-method.h index 633df10484ef0b386cba73b4b62e7fd87cd3b43a..ff9e1b68a8c4d54ccd577383594354cdd67a1e5e 100644 --- a/lib/libc/include/x86-linux-gnu/bits/flt-eval-method.h +++ b/lib/libc/include/x86-linux-gnu/bits/flt-eval-method.h @@ -1,5 +1,5 @@ /* Define __GLIBC_FLT_EVAL_METHOD. x86 version. - Copyright (C) 2016-2025 Free Software Foundation, Inc. + Copyright (C) 2016-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/x86-linux-gnu/bits/fp-logb.h b/lib/libc/include/x86-linux-gnu/bits/fp-logb.h index fafda47ccad27bdb7efa3d97d42119d4c9c2aed7..3fc7604558d384cf635ccbdcbd8ce6814ca001ab 100644 --- a/lib/libc/include/x86-linux-gnu/bits/fp-logb.h +++ b/lib/libc/include/x86-linux-gnu/bits/fp-logb.h @@ -1,5 +1,5 @@ /* Define __FP_LOGB0_IS_MIN and __FP_LOGBNAN_IS_MIN. x86 version. - Copyright (C) 2016-2025 Free Software Foundation, Inc. + Copyright (C) 2016-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/x86-linux-gnu/bits/indirect-return.h b/lib/libc/include/x86-linux-gnu/bits/indirect-return.h index cf42eab6cc91f92ecdb0c1814187d65e9bd21e8a..e740865a9c335ecc0021754e83f04bf908b26ce0 100644 --- a/lib/libc/include/x86-linux-gnu/bits/indirect-return.h +++ b/lib/libc/include/x86-linux-gnu/bits/indirect-return.h @@ -1,5 +1,5 @@ /* Definition of __INDIRECT_RETURN. x86 version. - Copyright (C) 2018-2025 Free Software Foundation, Inc. + Copyright (C) 2018-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/x86-linux-gnu/bits/ipctypes.h b/lib/libc/include/x86-linux-gnu/bits/ipctypes.h index 312a812c81bb74bc9d5c05c715f45152ffc609bb..8471589f03880840cfb536c090f9b86c48bd3c2a 100644 --- a/lib/libc/include/x86-linux-gnu/bits/ipctypes.h +++ b/lib/libc/include/x86-linux-gnu/bits/ipctypes.h @@ -1,5 +1,5 @@ /* bits/ipctypes.h -- Define some types used by SysV IPC/MSG/SHM. - Copyright (C) 2012-2025 Free Software Foundation, Inc. + Copyright (C) 2012-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/x86-linux-gnu/bits/iscanonical.h b/lib/libc/include/x86-linux-gnu/bits/iscanonical.h index 4d0b3c8c86bf0f2848187b96ff00044ed97e845c..874cd33ddf0ff0491227c49aebecd8e42ecf8cd5 100644 --- a/lib/libc/include/x86-linux-gnu/bits/iscanonical.h +++ b/lib/libc/include/x86-linux-gnu/bits/iscanonical.h @@ -1,5 +1,5 @@ /* Define iscanonical macro. ldbl-96 version. - Copyright (C) 2016-2025 Free Software Foundation, Inc. + Copyright (C) 2016-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/x86-linux-gnu/bits/link.h b/lib/libc/include/x86-linux-gnu/bits/link.h index 947918ae3ce3aa8aa290d3eb9dad03e08358bcf2..0682716a3adcf7fedb05a80b92e76356c833656e 100644 --- a/lib/libc/include/x86-linux-gnu/bits/link.h +++ b/lib/libc/include/x86-linux-gnu/bits/link.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2004-2025 Free Software Foundation, Inc. +/* Copyright (C) 2004-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/x86-linux-gnu/bits/long-double.h b/lib/libc/include/x86-linux-gnu/bits/long-double.h index 601eef7f1a86f269fc401db183e077ae69990f38..aae8ed389111b6ed9ff725e9f5e12abaf7fe99c1 100644 --- a/lib/libc/include/x86-linux-gnu/bits/long-double.h +++ b/lib/libc/include/x86-linux-gnu/bits/long-double.h @@ -1,5 +1,5 @@ /* Properties of long double type. ldbl-96 version. - Copyright (C) 2016-2025 Free Software Foundation, Inc. + Copyright (C) 2016-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/x86-linux-gnu/bits/math-vector.h b/lib/libc/include/x86-linux-gnu/bits/math-vector.h index 7a0ab120220d5ddc93b83a98a144a5fd5decdfdf..fc7dafe11e105a71262fb53bf341891c84570171 100644 --- a/lib/libc/include/x86-linux-gnu/bits/math-vector.h +++ b/lib/libc/include/x86-linux-gnu/bits/math-vector.h @@ -1,5 +1,5 @@ /* Platform-specific SIMD declarations of math functions. - Copyright (C) 2014-2025 Free Software Foundation, Inc. + Copyright (C) 2014-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/x86-linux-gnu/bits/mman.h b/lib/libc/include/x86-linux-gnu/bits/mman.h index 7a074b1a41901f76b90cfba4554679a9a6f81584..40cfcd79784788da9b19f2f1052cf69395c3417f 100644 --- a/lib/libc/include/x86-linux-gnu/bits/mman.h +++ b/lib/libc/include/x86-linux-gnu/bits/mman.h @@ -1,5 +1,5 @@ /* Definitions for POSIX memory map interface. Linux/x86_64 version. - Copyright (C) 2001-2025 Free Software Foundation, Inc. + Copyright (C) 2001-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/x86-linux-gnu/bits/procfs-id.h b/lib/libc/include/x86-linux-gnu/bits/procfs-id.h index a56dcacb1a1f041d29588d30ab78b8b8928dff83..08758ee9573ece9085470eda83fb159bc57e4985 100644 --- a/lib/libc/include/x86-linux-gnu/bits/procfs-id.h +++ b/lib/libc/include/x86-linux-gnu/bits/procfs-id.h @@ -1,5 +1,5 @@ /* Types of pr_uid and pr_gid in struct elf_prpsinfo. x86 version. - Copyright (C) 2018-2025 Free Software Foundation, Inc. + Copyright (C) 2018-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. diff --git a/lib/libc/include/x86-linux-gnu/bits/procfs.h b/lib/libc/include/x86-linux-gnu/bits/procfs.h index 7df2897b5d7b22a4d30f8406ce8210f4f606cb90..a0a5f7cb78ab55b6530e7f6f5974a9c8054a3285 100644 --- a/lib/libc/include/x86-linux-gnu/bits/procfs.h +++ b/lib/libc/include/x86-linux-gnu/bits/procfs.h @@ -1,5 +1,5 @@ /* Types for registers for sys/procfs.h. x86 version. - Copyright (C) 2001-2025 Free Software Foundation, Inc. + Copyright (C) 2001-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/x86-linux-gnu/bits/pthreadtypes-arch.h b/lib/libc/include/x86-linux-gnu/bits/pthreadtypes-arch.h index b13a1e4e3ba0206337e347fbea4bfd85502c9399..97e081118efaa3c1326b486844a84b1d5581473a 100644 --- a/lib/libc/include/x86-linux-gnu/bits/pthreadtypes-arch.h +++ b/lib/libc/include/x86-linux-gnu/bits/pthreadtypes-arch.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2002-2025 Free Software Foundation, Inc. +/* Copyright (C) 2002-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/x86-linux-gnu/bits/rseq.h b/lib/libc/include/x86-linux-gnu/bits/rseq.h index 2aeee59139bd9dba56ed5a84d3a558215bee38ab..e2fe6feebe37e2051c1c43b83659b351cff106f8 100644 --- a/lib/libc/include/x86-linux-gnu/bits/rseq.h +++ b/lib/libc/include/x86-linux-gnu/bits/rseq.h @@ -1,5 +1,5 @@ /* Restartable Sequences Linux x86 architecture header. - Copyright (C) 2021-2025 Free Software Foundation, Inc. + Copyright (C) 2021-2026 Free Software Foundation, Inc. The GNU C Library is free software; you can redistribute it and/or modify it under the terms of the GNU Lesser General Public diff --git a/lib/libc/include/x86-linux-gnu/bits/setjmp.h b/lib/libc/include/x86-linux-gnu/bits/setjmp.h index a447e3ae1e5cc0f7c5eb02eb0a90fa97115da023..bd649c9a6368c54a4f95ff6b72f53422b414027f 100644 --- a/lib/libc/include/x86-linux-gnu/bits/setjmp.h +++ b/lib/libc/include/x86-linux-gnu/bits/setjmp.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2001-2025 Free Software Foundation, Inc. +/* Copyright (C) 2001-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/x86-linux-gnu/bits/sigcontext.h b/lib/libc/include/x86-linux-gnu/bits/sigcontext.h index fb5a9fec670ad8466621c06222a40935d87e98e0..979232418c1e572a52586d085d5b46cad326d7e1 100644 --- a/lib/libc/include/x86-linux-gnu/bits/sigcontext.h +++ b/lib/libc/include/x86-linux-gnu/bits/sigcontext.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2002-2025 Free Software Foundation, Inc. +/* Copyright (C) 2002-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/x86-linux-gnu/bits/struct_mutex.h b/lib/libc/include/x86-linux-gnu/bits/struct_mutex.h index 408ed99e4acd62567e07b87faa5aa7694ad9443e..aecbadb5fdbdc2ece477ae5d6c09e96dd5f39f52 100644 --- a/lib/libc/include/x86-linux-gnu/bits/struct_mutex.h +++ b/lib/libc/include/x86-linux-gnu/bits/struct_mutex.h @@ -1,5 +1,5 @@ /* x86 internal mutex struct definitions. - Copyright (C) 2019-2025 Free Software Foundation, Inc. + Copyright (C) 2019-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or @@ -32,7 +32,7 @@ struct __pthread_mutex_s int __kind; #ifdef __x86_64__ short __spins; - short __elision; + short __unused; __pthread_list_t __list; # define __PTHREAD_MUTEX_HAVE_PREV 1 #else @@ -41,11 +41,10 @@ struct __pthread_mutex_s { struct { - short __espins; - short __eelision; -# define __spins __elision_data.__espins -# define __elision __elision_data.__eelision - } __elision_data; + short __data_spins; + short __data_unused; +# define __spins __data.__data_spins + } __data; __pthread_slist_t __list; }; # define __PTHREAD_MUTEX_HAVE_PREV 0 diff --git a/lib/libc/include/x86-linux-gnu/bits/struct_rwlock.h b/lib/libc/include/x86-linux-gnu/bits/struct_rwlock.h index 9c97ebdb560f4a52436efaae1498350ceb14f5db..b4fd4d691ede25f7a005c86cdbefee6da4b73e5d 100644 --- a/lib/libc/include/x86-linux-gnu/bits/struct_rwlock.h +++ b/lib/libc/include/x86-linux-gnu/bits/struct_rwlock.h @@ -1,5 +1,5 @@ /* x86 internal rwlock struct definitions. - Copyright (C) 2019-2025 Free Software Foundation, Inc. + Copyright (C) 2019-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. @@ -31,14 +31,7 @@ struct __pthread_rwlock_arch_t #ifdef __x86_64__ int __cur_writer; int __shared; - signed char __rwelision; -# ifdef __ILP32__ - unsigned char __pad1[3]; -# define __PTHREAD_RWLOCK_ELISION_EXTRA 0, { 0, 0, 0 } -# else - unsigned char __pad1[7]; -# define __PTHREAD_RWLOCK_ELISION_EXTRA 0, { 0, 0, 0, 0, 0, 0, 0 } -# endif + unsigned long int __pad1; unsigned long int __pad2; /* FLAGS must stay at this position in the structure to maintain binary compatibility. */ @@ -48,7 +41,7 @@ struct __pthread_rwlock_arch_t binary compatibility. */ unsigned char __flags; unsigned char __shared; - signed char __rwelision; + unsigned char __pad1; unsigned char __pad2; int __cur_writer; #endif @@ -56,7 +49,7 @@ struct __pthread_rwlock_arch_t #ifdef __x86_64__ # define __PTHREAD_RWLOCK_INITIALIZER(__flags) \ - 0, 0, 0, 0, 0, 0, 0, 0, __PTHREAD_RWLOCK_ELISION_EXTRA, 0, __flags + 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, __flags #else # define __PTHREAD_RWLOCK_INITIALIZER(__flags) \ 0, 0, 0, 0, 0, 0, __flags, 0, 0, 0, 0 diff --git a/lib/libc/include/x86-linux-gnu/bits/struct_stat.h b/lib/libc/include/x86-linux-gnu/bits/struct_stat.h index 2978a4dca4cea953ab0e3a46ac23bbbfbf77998d..938ab40b4938c8e2b2130778a7d16c8631f1c61a 100644 --- a/lib/libc/include/x86-linux-gnu/bits/struct_stat.h +++ b/lib/libc/include/x86-linux-gnu/bits/struct_stat.h @@ -1,5 +1,5 @@ /* Definition for struct stat. - Copyright (C) 2020-2025 Free Software Foundation, Inc. + Copyright (C) 2020-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/x86-linux-gnu/bits/timesize.h b/lib/libc/include/x86-linux-gnu/bits/timesize.h index 88f07ae6351a951e32377aa42ab0b7ba49bc04db..905260617f04029492b2b24c1b618d82ebffe73c 100644 --- a/lib/libc/include/x86-linux-gnu/bits/timesize.h +++ b/lib/libc/include/x86-linux-gnu/bits/timesize.h @@ -1,5 +1,5 @@ /* Bit size of the time_t type at glibc build time, x86-64 and x32 case. - Copyright (C) 2018-2025 Free Software Foundation, Inc. + Copyright (C) 2018-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/x86-linux-gnu/bits/types/struct_semid_ds.h b/lib/libc/include/x86-linux-gnu/bits/types/struct_semid_ds.h index 77fcfc080a720f4c8e4c4b854bd1a84e1d4591c9..aa85084d3b13f19614cb7b510d8a0461b050da9e 100644 --- a/lib/libc/include/x86-linux-gnu/bits/types/struct_semid_ds.h +++ b/lib/libc/include/x86-linux-gnu/bits/types/struct_semid_ds.h @@ -1,5 +1,5 @@ /* x86 implementation of the semaphore struct semid_ds. - Copyright (C) 1995-2025 Free Software Foundation, Inc. + Copyright (C) 1995-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/x86-linux-gnu/bits/typesizes.h b/lib/libc/include/x86-linux-gnu/bits/typesizes.h index 3210b86ddc459da43ac4571433a6a7a1eebfc44d..02fab5a6ca47feea8aa3074a3321cf36663ed855 100644 --- a/lib/libc/include/x86-linux-gnu/bits/typesizes.h +++ b/lib/libc/include/x86-linux-gnu/bits/typesizes.h @@ -1,5 +1,5 @@ /* bits/typesizes.h -- underlying types for *_t. Linux/x86-64 version. - Copyright (C) 2012-2025 Free Software Foundation, Inc. + Copyright (C) 2012-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/x86-linux-gnu/finclude/math-vector-fortran.h b/lib/libc/include/x86-linux-gnu/finclude/math-vector-fortran.h index 7760ea7a8d46a04a25db40ab02eff60f91b215d4..3bb1f569ed0e3fa26b1cd555c83008bb159a56c1 100644 --- a/lib/libc/include/x86-linux-gnu/finclude/math-vector-fortran.h +++ b/lib/libc/include/x86-linux-gnu/finclude/math-vector-fortran.h @@ -1,5 +1,5 @@ ! Platform-specific declarations of SIMD math functions for Fortran. -*- f90 -*- -! Copyright (C) 2019-2025 Free Software Foundation, Inc. +! Copyright (C) 2019-2026 Free Software Foundation, Inc. ! This file is part of the GNU C Library. ! ! The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/x86-linux-gnu/fpu_control.h b/lib/libc/include/x86-linux-gnu/fpu_control.h index 2765f7b4442bdec14160cb4b65721652f3e00c31..64c3614414811732cf7be07aa0dd7c8668b2c660 100644 --- a/lib/libc/include/x86-linux-gnu/fpu_control.h +++ b/lib/libc/include/x86-linux-gnu/fpu_control.h @@ -1,5 +1,5 @@ /* FPU control word bits. x86 version. - Copyright (C) 1993-2025 Free Software Foundation, Inc. + Copyright (C) 1993-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/x86-linux-gnu/sys/elf.h b/lib/libc/include/x86-linux-gnu/sys/elf.h index 4a898f9ea5b627161de3e5da158446af2b955f1b..ca9a4c837dd8577b4b7433c18965d1d89047325d 100644 --- a/lib/libc/include/x86-linux-gnu/sys/elf.h +++ b/lib/libc/include/x86-linux-gnu/sys/elf.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1998-2025 Free Software Foundation, Inc. +/* Copyright (C) 1998-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/x86-linux-gnu/sys/ptrace.h b/lib/libc/include/x86-linux-gnu/sys/ptrace.h index bc58b8168ca9188cd1e0593e6da32a7e10d6d846..1d50aaa769ae3fc2881cea25419591b175d1a39b 100644 --- a/lib/libc/include/x86-linux-gnu/sys/ptrace.h +++ b/lib/libc/include/x86-linux-gnu/sys/ptrace.h @@ -1,5 +1,5 @@ /* `ptrace' debugger support interface. Linux/x86 version. - Copyright (C) 1996-2025 Free Software Foundation, Inc. + Copyright (C) 1996-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. diff --git a/lib/libc/include/x86-linux-gnu/sys/ucontext.h b/lib/libc/include/x86-linux-gnu/sys/ucontext.h index f2526fac2c8a3b402fdc8016acccf33d6260f614..d317519d59c59ec0122c1c637b3cc4373f6cfe2b 100644 --- a/lib/libc/include/x86-linux-gnu/sys/ucontext.h +++ b/lib/libc/include/x86-linux-gnu/sys/ucontext.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2001-2025 Free Software Foundation, Inc. +/* Copyright (C) 2001-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/include/x86-linux-gnu/sys/user.h b/lib/libc/include/x86-linux-gnu/sys/user.h index 4572023bfd1292b7cc6c7dc0bd3fc65c8c5c6878..02527f2dc8d61fed7cc49b1ffca556c1c98dcb07 100644 --- a/lib/libc/include/x86-linux-gnu/sys/user.h +++ b/lib/libc/include/x86-linux-gnu/sys/user.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2001-2025 Free Software Foundation, Inc. +/* Copyright (C) 2001-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or -- 2.54.0 From e4058f2c27e9b1b91194ab7ed46c74203ac92a24 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Mon, 26 Jan 2026 05:30:25 +0100 Subject: [PATCH 045/499] libc: update glibc abilists to 2.43 --- lib/libc/glibc/abilists | Bin 248210 -> 248691 bytes 1 file changed, 0 insertions(+), 0 deletions(-) diff --git a/lib/libc/glibc/abilists b/lib/libc/glibc/abilists index be8d67ebfb8ec6c97fa488bf127d6034d8b4e0aa..3dec0a31a51a74d915e94a8c647416555d175a13 100644 GIT binary patch delta 2695 zcmbtWZERCj7{2G6wOvP{UD!vLtx4qW&@Zqu$e{o14Adg!adI zZ+dUu_c_n|yw5qOW&59R+E3M>mNCyojO#zVaSWTBBV0Tr+m3cA?l3Z|xhs-JIPTjJ(Z z9LENtk#L*~u|nhpF2tm@pM3}tNiH5u$khXWCCTGL*}d1Yq4s}}{LJQMWbJz^@}%x$ z@0_o61^JS6H9ccxKrTMns7{XbV!eIwvB02mrwvu9&94rbw=X4m$HE|x&V74=_Qs%* z>fG3Ds zr49|-a<>;cb|9~H_fpp$V(u0PWQter{)4-e#52SMfB8|}8lxRQwhw(%o*NYv9XuIA zj~huJrUp>R@FTB)ZW^@^>_^Zq#Riy_-_DagaBu{DMX(p3`8AX#PMu!#%hkts%jc)e zFnS8r;=I%iKb}S@Yx$H8tgP)SIW5M|wKr&Du)6nFh_9X;}8=X}EtMjbq&noc#k0ugj02rw~3+s*C<= z8J@D_W+3HrPLqopoh`!}Gcc&hU8LM3vhZ#g`j&*;*LK4Qujaqvt(bwys12nmOZQTwGj2 z*wH|qhMAA>A=K$vaol_Ub4+N@^uVWqswxvTS!Lp~t~{U|_yoV0A5;tAWPPc|Xds%! z$g+Z%5)&b1_8#trTla7k$*+d%dtB+#oMOd|LX5 zMav(ghOeE&5555iM}=S_uAmas34asvQl(>6si?B>9ja6=Y$V4YeujJ9FJTwDc`1^iP%_h*$xuf4DU{aCN4Vj{s3Zl+AJ@vNtT5JFRxBmV8V*Gg?e{KP+ z4lcaaJ9rEuBD>s;wPJn{w}`K0Xi~aaQ4GJ?k*}mD)FNlW>-44r)bKB z%A{-DsVo2Nav={rw1~SFOi=u%f<`gj26@W1^T&PZ)c54$=1i^utEK@nex5aE8ede$|k`KN}qLOa75hYydkWaaH!BLbMba#kPj?N~uyz5+*8CXp_Dhm&zS`aZIR#lg-k^k9N!ugM@zF@#JIlI_TGXY-VEBY89sE16tGgYdJ=Y8kkFq zM3RrlHNHlVaF(+TbS8owovZi5Q(zSDXK=PymCics_U)IbXCu*{i*WKFGAba`o-C0y}g6*`a)i~ Date: Mon, 26 Jan 2026 05:30:07 +0100 Subject: [PATCH 046/499] libc: update glibc crt0 code to 2.43 --- lib/libc/glibc/bits/byteswap.h | 2 +- lib/libc/glibc/bits/floatn-common.h | 2 +- lib/libc/glibc/bits/libc-header-start.h | 2 +- lib/libc/glibc/bits/long-double.h | 2 +- lib/libc/glibc/bits/select.h | 2 +- lib/libc/glibc/bits/signum-generic.h | 2 +- lib/libc/glibc/bits/stat.h | 2 +- lib/libc/glibc/bits/stdint-intn.h | 2 +- lib/libc/glibc/bits/stdlib-bsearch.h | 2 +- lib/libc/glibc/bits/time64.h | 2 +- lib/libc/glibc/bits/timesize.h | 2 +- .../glibc/bits/types/struct_sched_param.h | 2 +- lib/libc/glibc/bits/typesizes.h | 2 +- lib/libc/glibc/bits/uintn-identity.h | 2 +- lib/libc/glibc/bits/waitflags.h | 2 +- lib/libc/glibc/bits/waitstatus.h | 2 +- lib/libc/glibc/csu/errno.c | 2 +- lib/libc/glibc/csu/init.c | 2 +- lib/libc/glibc/debug/stack_chk_fail_local.c | 2 +- lib/libc/glibc/elf/elf.h | 4 +- lib/libc/glibc/include/alloca.h | 2 +- lib/libc/glibc/include/libc-diag.h | 99 +++++++++++++++++++ lib/libc/glibc/include/libc-misc.h | 2 +- lib/libc/glibc/include/libc-pointer-arith.h | 2 +- lib/libc/glibc/include/libc-symbols.h | 22 ++++- lib/libc/glibc/include/pthread.h | 4 - lib/libc/glibc/include/stap-probe.h | 2 +- lib/libc/glibc/io/bits/statx.h | 2 +- lib/libc/glibc/io/fcntl.h | 2 +- lib/libc/glibc/io/mknod.c | 2 +- lib/libc/glibc/io/sys/stat.h | 2 +- lib/libc/glibc/locale/bits/types/__locale_t.h | 2 +- lib/libc/glibc/locale/bits/types/locale_t.h | 2 +- lib/libc/glibc/misc/sys/cdefs.h | 30 ++++-- lib/libc/glibc/misc/sys/select.h | 2 +- lib/libc/glibc/posix/bits/cpu-set.h | 2 +- lib/libc/glibc/posix/bits/types.h | 2 +- lib/libc/glibc/posix/sys/types.h | 2 +- lib/libc/glibc/signal/signal.h | 2 +- lib/libc/glibc/stdlib/alloca.h | 2 +- lib/libc/glibc/stdlib/bits/stdlib-float.h | 2 +- lib/libc/glibc/stdlib/errno.h | 2 +- lib/libc/glibc/stdlib/exit.h | 2 +- lib/libc/glibc/stdlib/stdlib.h | 43 +++++++- lib/libc/glibc/string/bits/endian.h | 2 +- lib/libc/glibc/string/endian.h | 2 +- .../aarch64/nptl/bits/pthreadtypes-arch.h | 2 +- lib/libc/glibc/sysdeps/aarch64/start.S | 2 +- lib/libc/glibc/sysdeps/aarch64/sysdep.h | 2 +- lib/libc/glibc/sysdeps/arc/start.S | 2 +- lib/libc/glibc/sysdeps/arc/sysdep.h | 2 +- lib/libc/glibc/sysdeps/arm/arm-features.h | 2 +- lib/libc/glibc/sysdeps/arm/start.S | 2 +- lib/libc/glibc/sysdeps/arm/sysdep.h | 2 +- lib/libc/glibc/sysdeps/csky/abiv2/start.S | 2 +- lib/libc/glibc/sysdeps/csky/sysdep.h | 2 +- lib/libc/glibc/sysdeps/generic/dl-dtprocnum.h | 2 +- lib/libc/glibc/sysdeps/generic/dl-sysdep.h | 2 +- lib/libc/glibc/sysdeps/generic/dwarf2.h | 2 +- lib/libc/glibc/sysdeps/generic/libc-lock.h | 2 +- lib/libc/glibc/sysdeps/generic/libc-symver.h | 2 +- .../glibc/sysdeps/generic/single-thread.h | 2 +- lib/libc/glibc/sysdeps/generic/symbol-hacks.h | 16 +++ lib/libc/glibc/sysdeps/generic/sysdep.h | 2 +- lib/libc/glibc/sysdeps/generic/tls.h | 2 +- lib/libc/glibc/sysdeps/htl/bits/pthread.h | 2 +- .../sysdeps/htl/bits/thread-shared-types.h | 2 +- lib/libc/glibc/sysdeps/htl/libc-lockP.h | 37 +------ lib/libc/glibc/sysdeps/htl/pthread.h | 2 +- .../sysdeps/i386/htl/bits/pthreadtypes-arch.h | 2 +- lib/libc/glibc/sysdeps/i386/start.S | 2 +- lib/libc/glibc/sysdeps/i386/symbol-hacks.h | 2 +- lib/libc/glibc/sysdeps/i386/sysdep.h | 2 +- lib/libc/glibc/sysdeps/loongarch/start.S | 2 +- lib/libc/glibc/sysdeps/loongarch/sys/regdef.h | 2 +- lib/libc/glibc/sysdeps/m68k/coldfire/sysdep.h | 2 +- lib/libc/glibc/sysdeps/m68k/m680x0/sysdep.h | 2 +- .../m68k/nptl/bits/pthreadtypes-arch.h | 2 +- lib/libc/glibc/sysdeps/m68k/start.S | 2 +- lib/libc/glibc/sysdeps/m68k/symbol-hacks.h | 2 +- lib/libc/glibc/sysdeps/m68k/sysdep.h | 2 +- lib/libc/glibc/sysdeps/mach/libc-lock.h | 2 +- lib/libc/glibc/sysdeps/mach/sysdep.h | 2 +- lib/libc/glibc/sysdeps/mips/dl-dtprocnum.h | 2 +- lib/libc/glibc/sysdeps/mips/isarev.h | 8 ++ .../mips/nptl/bits/pthreadtypes-arch.h | 2 +- lib/libc/glibc/sysdeps/mips/start.S | 2 +- .../glibc/sysdeps/nptl/bits/pthreadtypes.h | 2 +- .../sysdeps/nptl/bits/thread-shared-types.h | 4 +- lib/libc/glibc/sysdeps/nptl/libc-lock.h | 2 +- lib/libc/glibc/sysdeps/nptl/libc-lockP.h | 9 +- lib/libc/glibc/sysdeps/nptl/pthread.h | 2 +- .../glibc/sysdeps/powerpc/powerpc32/start.S | 2 +- .../sysdeps/powerpc/powerpc32/symbol-hacks.h | 2 +- .../glibc/sysdeps/powerpc/powerpc32/sysdep.h | 2 +- .../sysdeps/powerpc/powerpc64/dl-dtprocnum.h | 2 +- .../glibc/sysdeps/powerpc/powerpc64/start.S | 2 +- .../glibc/sysdeps/powerpc/powerpc64/sysdep.h | 2 +- lib/libc/glibc/sysdeps/powerpc/sysdep.h | 2 +- .../riscv/nptl/bits/pthreadtypes-arch.h | 2 +- lib/libc/glibc/sysdeps/riscv/start.S | 2 +- lib/libc/glibc/sysdeps/s390/s390-64/start.S | 2 +- lib/libc/glibc/sysdeps/s390/s390-64/sysdep.h | 2 +- lib/libc/glibc/sysdeps/sparc/dl-dtprocnum.h | 2 +- lib/libc/glibc/sysdeps/sparc/sparc32/start.S | 2 +- lib/libc/glibc/sysdeps/sparc/sparc64/start.S | 2 +- lib/libc/glibc/sysdeps/sparc/sysdep.h | 2 +- lib/libc/glibc/sysdeps/unix/arm/sysdep.h | 2 +- lib/libc/glibc/sysdeps/unix/i386/sysdep.h | 2 +- .../glibc/sysdeps/unix/mips/mips32/sysdep.h | 2 +- .../glibc/sysdeps/unix/mips/mips64/sysdep.h | 2 +- lib/libc/glibc/sysdeps/unix/mips/sysdep.h | 6 +- lib/libc/glibc/sysdeps/unix/powerpc/sysdep.h | 2 +- lib/libc/glibc/sysdeps/unix/sh/sysdep.h | 2 +- lib/libc/glibc/sysdeps/unix/sysdep.h | 4 +- .../unix/sysv/linux/aarch64/kernel-features.h | 2 +- .../sysdeps/unix/sysv/linux/aarch64/sys/elf.h | 2 +- .../sysdeps/unix/sysv/linux/aarch64/sysdep.h | 41 +++++++- .../sysdeps/unix/sysv/linux/arc/sysdep.h | 2 +- .../unix/sysv/linux/arm/kernel-features.h | 2 +- .../sysdeps/unix/sysv/linux/arm/sys/elf.h | 2 +- .../sysdeps/unix/sysv/linux/arm/sysdep.h | 2 +- .../glibc/sysdeps/unix/sysv/linux/bits/stat.h | 2 +- .../sysdeps/unix/sysv/linux/bits/timex.h | 2 +- .../unix/sysv/linux/csky/kernel_stat.h | 2 +- .../sysdeps/unix/sysv/linux/csky/sysdep.h | 2 +- .../glibc/sysdeps/unix/sysv/linux/dl-sysdep.h | 2 +- .../glibc/sysdeps/unix/sysv/linux/fstat.c | 2 +- .../glibc/sysdeps/unix/sysv/linux/fstat64.c | 2 +- .../glibc/sysdeps/unix/sysv/linux/fstatat.c | 2 +- .../glibc/sysdeps/unix/sysv/linux/fstatat64.c | 2 +- .../sysdeps/unix/sysv/linux/i386/dl-sysdep.h | 2 +- .../unix/sysv/linux/i386/kernel-features.h | 2 +- .../sysdeps/unix/sysv/linux/i386/sysdep.h | 2 +- .../unix/sysv/linux/include/sys/timex.h | 2 +- .../sysdeps/unix/sysv/linux/kernel-features.h | 7 +- .../sysdeps/unix/sysv/linux/kernel_stat.h | 2 +- .../unix/sysv/linux/loongarch/sysdep.h | 2 +- .../glibc/sysdeps/unix/sysv/linux/lstat.c | 2 +- .../glibc/sysdeps/unix/sysv/linux/lstat64.c | 2 +- .../unix/sysv/linux/m68k/coldfire/sysdep.h | 2 +- .../unix/sysv/linux/m68k/kernel-features.h | 2 +- .../unix/sysv/linux/m68k/m680x0/sysdep.h | 2 +- .../sysdeps/unix/sysv/linux/m68k/sysdep.h | 2 +- .../unix/sysv/linux/mips/kernel-features.h | 2 +- .../unix/sysv/linux/mips/mips32/sysdep.h | 2 +- .../unix/sysv/linux/mips/mips64/kstat_cp.h | 2 +- .../unix/sysv/linux/mips/mips64/sysdep.h | 2 +- .../sysdeps/unix/sysv/linux/mips/sysdep.h | 2 +- .../glibc/sysdeps/unix/sysv/linux/mknodat.c | 2 +- .../unix/sysv/linux/powerpc/kernel-features.h | 2 +- .../linux/powerpc/powerpc32/kernel_stat.h | 2 +- .../sysv/linux/powerpc/powerpc64/sysdep.h | 2 +- .../sysdeps/unix/sysv/linux/powerpc/sysdep.h | 2 +- .../unix/sysv/linux/riscv/kernel-features.h | 2 +- .../sysdeps/unix/sysv/linux/riscv/sysdep.h | 7 ++ .../unix/sysv/linux/s390/bits/typesizes.h | 2 +- .../unix/sysv/linux/s390/kernel-features.h | 2 +- .../unix/sysv/linux/s390/s390-64/sysdep.h | 2 +- .../sysdeps/unix/sysv/linux/s390/sys/elf.h | 2 +- .../sysdeps/unix/sysv/linux/s390/sysdep.h | 2 +- .../sysdeps/unix/sysv/linux/single-thread.h | 2 +- .../unix/sysv/linux/sparc/bits/typesizes.h | 2 +- .../unix/sysv/linux/sparc/kernel-features.h | 2 +- .../unix/sysv/linux/sparc/sparc32/sysdep.h | 2 +- .../unix/sysv/linux/sparc/sparc64/kstat_cp.h | 2 +- .../unix/sysv/linux/sparc/sparc64/sysdep.h | 2 +- .../sysdeps/unix/sysv/linux/sparc/sysdep.h | 2 +- lib/libc/glibc/sysdeps/unix/sysv/linux/stat.c | 2 +- .../glibc/sysdeps/unix/sysv/linux/stat64.c | 2 +- .../sysdeps/unix/sysv/linux/stat_t64_cp.c | 2 +- .../sysdeps/unix/sysv/linux/stat_t64_cp.h | 2 +- .../unix/sysv/linux/struct_stat_time64.h | 2 +- .../sysdeps/unix/sysv/linux/sys/syscall.h | 2 +- .../glibc/sysdeps/unix/sysv/linux/sys/timex.h | 2 +- .../glibc/sysdeps/unix/sysv/linux/sysdep.h | 2 +- .../unix/sysv/linux/x86/bits/typesizes.h | 2 +- .../sysdeps/unix/sysv/linux/x86/sys/elf.h | 2 +- .../unix/sysv/linux/x86_64/kernel-features.h | 2 +- .../sysdeps/unix/sysv/linux/x86_64/sysdep.h | 2 +- .../unix/sysv/linux/x86_64/x32/sysdep.h | 2 +- lib/libc/glibc/sysdeps/unix/x86_64/sysdep.h | 2 +- .../sysdeps/wordsize-32/divdi3-symbol-hacks.h | 2 +- .../sysdeps/x86/nptl/bits/pthreadtypes-arch.h | 2 +- lib/libc/glibc/sysdeps/x86/sysdep.h | 2 +- lib/libc/glibc/sysdeps/x86_64/start.S | 2 +- lib/libc/glibc/sysdeps/x86_64/sysdep.h | 2 +- lib/libc/glibc/sysdeps/x86_64/x32/sysdep.h | 2 +- 188 files changed, 439 insertions(+), 246 deletions(-) create mode 100644 lib/libc/glibc/include/libc-diag.h create mode 100644 lib/libc/glibc/sysdeps/mips/isarev.h diff --git a/lib/libc/glibc/bits/byteswap.h b/lib/libc/glibc/bits/byteswap.h index c0841a3574456dc55401bad1d05a619dc5ce02d8..34533ffd5a3ae4a151d772670105e925818ffd2d 100644 --- a/lib/libc/glibc/bits/byteswap.h +++ b/lib/libc/glibc/bits/byteswap.h @@ -1,5 +1,5 @@ /* Macros and inline functions to swap the order of bytes in integer values. - Copyright (C) 1997-2025 Free Software Foundation, Inc. + Copyright (C) 1997-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/bits/floatn-common.h b/lib/libc/glibc/bits/floatn-common.h index 01a579687e765d9c509535408413c120e6e53e61..4c79094828a93c4e87edae9447f7cfea5d17caef 100644 --- a/lib/libc/glibc/bits/floatn-common.h +++ b/lib/libc/glibc/bits/floatn-common.h @@ -1,6 +1,6 @@ /* Macros to control TS 18661-3 glibc features where the same definitions are appropriate for all platforms. - Copyright (C) 2017-2025 Free Software Foundation, Inc. + Copyright (C) 2017-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/bits/libc-header-start.h b/lib/libc/glibc/bits/libc-header-start.h index e8477f972a9216652f2081acd8f5803bc6110ff0..45897cd66316b16f3eba1f5aeb5bc6a10fa63f46 100644 --- a/lib/libc/glibc/bits/libc-header-start.h +++ b/lib/libc/glibc/bits/libc-header-start.h @@ -1,5 +1,5 @@ /* Handle feature test macros at the start of a header. - Copyright (C) 2016-2025 Free Software Foundation, Inc. + Copyright (C) 2016-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/bits/long-double.h b/lib/libc/glibc/bits/long-double.h index 77421347b533f7bc3c06dc18cd9168b5b3b4e5ba..4ff6c03eb124c76f11dad0798658c2c4b704d94e 100644 --- a/lib/libc/glibc/bits/long-double.h +++ b/lib/libc/glibc/bits/long-double.h @@ -1,5 +1,5 @@ /* Properties of long double type. - Copyright (C) 2016-2025 Free Software Foundation, Inc. + Copyright (C) 2016-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/bits/select.h b/lib/libc/glibc/bits/select.h index 065e1d1dde6599415ea0083dd212c4067d05d382..5edc32dbbfa1db8f237c52036343e69cf8366bb7 100644 --- a/lib/libc/glibc/bits/select.h +++ b/lib/libc/glibc/bits/select.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1997-2025 Free Software Foundation, Inc. +/* Copyright (C) 1997-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/bits/signum-generic.h b/lib/libc/glibc/bits/signum-generic.h index 31dc2cd3959299236d0e5439caed2d16fa2892de..12cbe6678f9b1dc576569cd5933bbfef3f6ae505 100644 --- a/lib/libc/glibc/bits/signum-generic.h +++ b/lib/libc/glibc/bits/signum-generic.h @@ -1,5 +1,5 @@ /* Signal number constants. Generic template. - Copyright (C) 1991-2025 Free Software Foundation, Inc. + Copyright (C) 1991-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/bits/stat.h b/lib/libc/glibc/bits/stat.h index 9dae670f675b2602e64eb0ea4625340e8c64be2d..10fa5aa8a8d0ffdcefbeca57bc180103d32a98a3 100644 --- a/lib/libc/glibc/bits/stat.h +++ b/lib/libc/glibc/bits/stat.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1992-2025 Free Software Foundation, Inc. +/* Copyright (C) 1992-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/bits/stdint-intn.h b/lib/libc/glibc/bits/stdint-intn.h index a7fde320e125f66e07ded4361fa37d92a4dd84e7..cbe07dff85f145e77a7eea47af10acdfc59ec5b4 100644 --- a/lib/libc/glibc/bits/stdint-intn.h +++ b/lib/libc/glibc/bits/stdint-intn.h @@ -1,5 +1,5 @@ /* Define intN_t types. - Copyright (C) 2017-2025 Free Software Foundation, Inc. + Copyright (C) 2017-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/bits/stdlib-bsearch.h b/lib/libc/glibc/bits/stdlib-bsearch.h index 5ca67857e59f9f88c19149a18fb2183d8d15bf36..0ee8cdb8c632c81db4da43290edb76037db39dce 100644 --- a/lib/libc/glibc/bits/stdlib-bsearch.h +++ b/lib/libc/glibc/bits/stdlib-bsearch.h @@ -1,5 +1,5 @@ /* Perform binary search - inline version. - Copyright (C) 1991-2025 Free Software Foundation, Inc. + Copyright (C) 1991-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/bits/time64.h b/lib/libc/glibc/bits/time64.h index e858cefdffb034f3878e940f8e03412ac629faca..3fd9941f994fd7842fb41126c011ba930ac1877b 100644 --- a/lib/libc/glibc/bits/time64.h +++ b/lib/libc/glibc/bits/time64.h @@ -1,5 +1,5 @@ /* bits/time64.h -- underlying types for __time64_t. Generic version. - Copyright (C) 2018-2025 Free Software Foundation, Inc. + Copyright (C) 2018-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/bits/timesize.h b/lib/libc/glibc/bits/timesize.h index 9accad9658cc1ef7a8a242e71250ad687b170aed..e3ba2b52faadea04fe1a386a9c0497118fe89a39 100644 --- a/lib/libc/glibc/bits/timesize.h +++ b/lib/libc/glibc/bits/timesize.h @@ -1,5 +1,5 @@ /* Bit size of the time_t type at glibc build time, general case. - Copyright (C) 2018-2025 Free Software Foundation, Inc. + Copyright (C) 2018-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/bits/types/struct_sched_param.h b/lib/libc/glibc/bits/types/struct_sched_param.h index 7a2e06b83447ace598f7e8356f4403fad015ee2e..dce2e5772b9577279dd69f3ae31cd4f57cefb7be 100644 --- a/lib/libc/glibc/bits/types/struct_sched_param.h +++ b/lib/libc/glibc/bits/types/struct_sched_param.h @@ -1,5 +1,5 @@ /* Sched parameter structure. Generic version. - Copyright (C) 1996-2025 Free Software Foundation, Inc. + Copyright (C) 1996-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/bits/typesizes.h b/lib/libc/glibc/bits/typesizes.h index b6db5c3637702a760f8318881342f7c327ed3fe0..466dd36681a7364952e49b73067d66174543cffd 100644 --- a/lib/libc/glibc/bits/typesizes.h +++ b/lib/libc/glibc/bits/typesizes.h @@ -1,5 +1,5 @@ /* bits/typesizes.h -- underlying types for *_t. Generic version. - Copyright (C) 2002-2025 Free Software Foundation, Inc. + Copyright (C) 2002-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/bits/uintn-identity.h b/lib/libc/glibc/bits/uintn-identity.h index 96c58abe6554ea245dee4ac14cc53831a775624a..d78bda636b4c7b0dcacff2ad80fd6eae3dd25242 100644 --- a/lib/libc/glibc/bits/uintn-identity.h +++ b/lib/libc/glibc/bits/uintn-identity.h @@ -1,5 +1,5 @@ /* Inline functions to return unsigned integer values unchanged. - Copyright (C) 2017-2025 Free Software Foundation, Inc. + Copyright (C) 2017-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/bits/waitflags.h b/lib/libc/glibc/bits/waitflags.h index 5e7c0f506d9b1142ec450e5a687e13cdd9ce4b41..b36e2d0fad244da8c8ad57d90fbf1ac1ce508785 100644 --- a/lib/libc/glibc/bits/waitflags.h +++ b/lib/libc/glibc/bits/waitflags.h @@ -1,5 +1,5 @@ /* Definitions of flag bits for `waitpid' et al. - Copyright (C) 1992-2025 Free Software Foundation, Inc. + Copyright (C) 1992-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/bits/waitstatus.h b/lib/libc/glibc/bits/waitstatus.h index ccc223947371ee9ef2cdd6ac180f7c20c892e2ca..8a8dd047843e42f861cfb67694cc82d04c517bb9 100644 --- a/lib/libc/glibc/bits/waitstatus.h +++ b/lib/libc/glibc/bits/waitstatus.h @@ -1,5 +1,5 @@ /* Definitions of status bits for `wait' et al. - Copyright (C) 1992-2025 Free Software Foundation, Inc. + Copyright (C) 1992-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/csu/errno.c b/lib/libc/glibc/csu/errno.c index 558315365f36cc411e3b01dafdef0de92762186f..efdf6545c9bfc71f7ace93e8fbbc4db5031d223a 100644 --- a/lib/libc/glibc/csu/errno.c +++ b/lib/libc/glibc/csu/errno.c @@ -1,5 +1,5 @@ /* Definition of `errno' variable. Canonical version. - Copyright (C) 2002-2025 Free Software Foundation, Inc. + Copyright (C) 2002-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/csu/init.c b/lib/libc/glibc/csu/init.c index 92a22de162d2873406f8bb5759f58e050d80cbf7..7fe1b387812a5924d0203ab566cebd8bbe0f5734 100644 --- a/lib/libc/glibc/csu/init.c +++ b/lib/libc/glibc/csu/init.c @@ -1,5 +1,5 @@ /* Special startup support. - Copyright (C) 1997-2025 Free Software Foundation, Inc. + Copyright (C) 1997-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/debug/stack_chk_fail_local.c b/lib/libc/glibc/debug/stack_chk_fail_local.c index 4ba407637fb0b510239e1fda7f1dae97595023ab..7feccea23ea95b5825fd850fe8655bc82f16f40a 100644 --- a/lib/libc/glibc/debug/stack_chk_fail_local.c +++ b/lib/libc/glibc/debug/stack_chk_fail_local.c @@ -1,4 +1,4 @@ -/* Copyright (C) 2005-2025 Free Software Foundation, Inc. +/* Copyright (C) 2005-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/elf/elf.h b/lib/libc/glibc/elf/elf.h index 2f29a47c0bb8c43d2ed0f8faab60dc8840b3f495..46a01281cb0fb5322d5124f0443c11dea4d5b721 100644 --- a/lib/libc/glibc/elf/elf.h +++ b/lib/libc/glibc/elf/elf.h @@ -1,5 +1,5 @@ /* This file defines standard ELF types, structures, and macros. - Copyright (C) 1995-2025 Free Software Foundation, Inc. + Copyright (C) 1995-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or @@ -924,7 +924,7 @@ typedef struct #define DT_SYMTAB_SHNDX 34 /* Address of SYMTAB_SHNDX section */ #define DT_RELRSZ 35 /* Total size of RELR relative relocations */ #define DT_RELR 36 /* Address of RELR relative relocations */ -#define DT_RELRENT 37 /* Size of one RELR relative relocaction */ +#define DT_RELRENT 37 /* Size of one RELR relative relocation */ #define DT_NUM 38 /* Number used */ #define DT_LOOS 0x6000000d /* Start of OS-specific */ #define DT_HIOS 0x6ffff000 /* End of OS-specific */ diff --git a/lib/libc/glibc/include/alloca.h b/lib/libc/glibc/include/alloca.h index c0b83954436ed4c1e7c3246e914bc36ac1606dd9..5f2df32b46252a4fb33f2b7800889733b001e502 100644 --- a/lib/libc/glibc/include/alloca.h +++ b/lib/libc/glibc/include/alloca.h @@ -4,7 +4,7 @@ # ifndef _ISOMAC -#include +#include #undef __alloca diff --git a/lib/libc/glibc/include/libc-diag.h b/lib/libc/glibc/include/libc-diag.h new file mode 100644 index 0000000000000000000000000000000000000000..b2f7fb5de02515750bd055e937109612b836152b --- /dev/null +++ b/lib/libc/glibc/include/libc-diag.h @@ -0,0 +1,99 @@ +/* Macros for controlling diagnostic output from the compiler. + Copyright (C) 2014-2026 Free Software Foundation, Inc. + This file is part of the GNU C Library. + + The GNU C Library is free software; you can redistribute it and/or + modify it under the terms of the GNU Lesser General Public + License as published by the Free Software Foundation; either + version 2.1 of the License, or (at your option) any later version. + + The GNU C Library is distributed in the hope that it will be useful, + but WITHOUT ANY WARRANTY; without even the implied warranty of + MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU + Lesser General Public License for more details. + + You should have received a copy of the GNU Lesser General Public + License along with the GNU C Library; if not, see + . */ + +#ifndef _LIBC_DIAG_H +#define _LIBC_DIAG_H 1 + +/* Ignore the value of an expression when a cast to void does not + suffice (in particular, for a call to a function declared with + attribute warn_unused_result). */ +#define ignore_value(x) \ + ({ __typeof__ (x) __ignored_value = (x); (void) __ignored_value; }) + +/* The macros to control diagnostics are structured like this, rather + than a single macro that both pushes and pops diagnostic state and + takes the affected code as an argument, because the GCC pragmas + work by disabling the diagnostic for a range of source locations + and do not work when all the pragmas and the affected code are in a + single macro expansion. */ + +/* Push diagnostic state. */ +#define DIAG_PUSH_NEEDS_COMMENT _Pragma ("GCC diagnostic push") + +/* Pop diagnostic state. */ +#define DIAG_POP_NEEDS_COMMENT _Pragma ("GCC diagnostic pop") + +/* These macros are used to push/pop diagnostic states for warnings only + supported by clang. */ +#ifdef __clang__ +# define DIAG_PUSH_NEEDS_COMMENT_CLANG _Pragma ("clang diagnostic push") +# define DIAG_POP_NEEDS_COMMENT_CLANG _Pragma ("clang diagnostic pop") +#else +# define DIAG_PUSH_NEEDS_COMMENT_CLANG +# define DIAG_POP_NEEDS_COMMENT_CLANG +#endif + +#define _DIAG_STR1(s) #s +#define _DIAG_STR(s) _DIAG_STR1(s) + +/* Ignore the diagnostic OPTION. VERSION is the most recent GCC + version for which the diagnostic has been confirmed to appear in + the absence of the pragma (in the form MAJOR.MINOR for GCC 4.x, + just MAJOR for GCC 5 and later). Uses of this pragma should be + reviewed when the GCC version given is no longer supported for + building glibc; the version number should always be on the same + source line as the macro name, so such uses can be found with grep. + Uses should come with a comment giving more details of the + diagnostic, and an architecture on which it is seen if possibly + optimization-related and not in architecture-specific code. This + macro should only be used if the diagnostic seems hard to fix (for + example, optimization-related false positives). */ +#define DIAG_IGNORE_NEEDS_COMMENT(version, option) \ + _Pragma (_DIAG_STR (GCC diagnostic ignored option)) + +/* Similar to DIAG_IGNORE_NEEDS_COMMENT the following macro ignores the + diagnostic OPTION but only if optimizations for size are enabled. + This is required because different warnings may be generated for + different optimization levels. For example a key piece of code may + only generate a warning when compiled at -Os, but at -O2 you could + still want the warning to be enabled to catch errors. In this case + you would use DIAG_IGNORE_Os_NEEDS_COMMENT to disable the warning + only for -Os. */ +#ifdef __OPTIMIZE_SIZE__ +# define DIAG_IGNORE_Os_NEEDS_COMMENT(version, option) \ + _Pragma (_DIAG_STR (GCC diagnostic ignored option)) +#else +# define DIAG_IGNORE_Os_NEEDS_COMMENT(version, option) +#endif + +/* Similar to DIAG_IGNORE_NEEDS_COMMENT, these macros should be used + to suppress warning supported by the specific compiler. */ +#ifndef __clang__ +# define DIAG_IGNORE_NEEDS_COMMENT_GCC(VERSION, WARNING) \ + DIAG_IGNORE_NEEDS_COMMENT (VERSION, WARNING) +# define DIAG_IGNORE_Os_NEEDS_COMMENT_GCC(VERSION, WARNING) \ + DIAG_IGNORE_Os_NEEDS_COMMENT (VERSION, WARNING) +# define DIAG_IGNORE_NEEDS_COMMENT_CLANG(version, option) +#else +# define DIAG_IGNORE_NEEDS_COMMENT_GCC(VERSION, WARNING) +# define DIAG_IGNORE_Os_NEEDS_COMMENT_GCC(VERSION, WARNING) +# define DIAG_IGNORE_NEEDS_COMMENT_CLANG(version, option) \ + _Pragma (_DIAG_STR (clang diagnostic ignored option)) +#endif + +#endif /* libc-diag.h */ diff --git a/lib/libc/glibc/include/libc-misc.h b/lib/libc/glibc/include/libc-misc.h index e76a8097d8b5d6ca21002716dd351168fe936fe7..01984398a32aa31dc9118328f0d6b32812bee62f 100644 --- a/lib/libc/glibc/include/libc-misc.h +++ b/lib/libc/glibc/include/libc-misc.h @@ -1,5 +1,5 @@ /* Miscellaneous definitions for both glibc build and test. - Copyright (C) 2024-2025 Free Software Foundation, Inc. + Copyright (C) 2024-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/include/libc-pointer-arith.h b/lib/libc/glibc/include/libc-pointer-arith.h index 815ba65ec9bd55653f905d0ac390a3d47fe38b53..e5b9748559118c9a789abdbd80e08fd7460f7838 100644 --- a/lib/libc/glibc/include/libc-pointer-arith.h +++ b/lib/libc/glibc/include/libc-pointer-arith.h @@ -1,5 +1,5 @@ /* Helper macros for pointer arithmetic. - Copyright (C) 2012-2025 Free Software Foundation, Inc. + Copyright (C) 2012-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/include/libc-symbols.h b/lib/libc/glibc/include/libc-symbols.h index 7f2c8938b6cd9d5aaba9dd4ba1daa3d4cda1a1cf..bebfc67cec098bf106bb9231bd3ff47e8f85d31f 100644 --- a/lib/libc/glibc/include/libc-symbols.h +++ b/lib/libc/glibc/include/libc-symbols.h @@ -1,6 +1,6 @@ /* Support macros for making weak and strong aliases for symbols, and for using symbol sets and linker warnings with GNU ld. - Copyright (C) 1995-2025 Free Software Foundation, Inc. + Copyright (C) 1995-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or @@ -86,6 +86,7 @@ /* Obtain the definition of symbol_version_reference. */ #include +#include /* When PIC is defined and SHARED isn't defined, we are building PIE by default. */ @@ -167,6 +168,16 @@ __attribute_copy__ (name); #endif +/* Define a strong_alias for SHARED, or weak_alias otherwise. It is used to + avoid potential compiler warnings for weak alias indirection (when a weak + alias is always resolved to a symbol even if a weak definition also + exists). */ +# ifdef SHARED +# define static_weak_alias(name, aliasname) strong_alias (name, aliasname) +# else +# define static_weak_alias(name, aliasname) weak_alias (name, aliasname) +# endif + /* Declare SYMBOL as weak undefined symbol (resolved to 0 if not defined). */ # define weak_extern(symbol) _weak_extern (weak symbol) # define _weak_extern(expr) _Pragma (#expr) @@ -280,7 +291,7 @@ for linking") /* - + */ #ifdef HAVE_GNU_RETAIN @@ -683,7 +694,10 @@ for linking") # define __ifunc_args(type_name, name, expr, init, ...) \ extern __typeof (type_name) name __attribute__ \ ((ifunc (#name "_ifunc"))); \ - __ifunc_resolver (type_name, name, expr, init, static, __VA_ARGS__) + DIAG_PUSH_NEEDS_COMMENT_CLANG; \ + DIAG_IGNORE_NEEDS_COMMENT_CLANG (13, "-Wunused-function"); \ + __ifunc_resolver (type_name, name, expr, init, static, __VA_ARGS__); \ + DIAG_POP_NEEDS_COMMENT_CLANG; # define __ifunc_args_hidden(type_name, name, expr, init, ...) \ __ifunc_args (type_name, name, expr, init, __VA_ARGS__) @@ -807,7 +821,7 @@ for linking") #define libm_ifunc_init() #define libm_ifunc(name, expr) \ __ifunc (name, name, expr, void, libm_ifunc_init) - + /* These macros facilitate sharing source files with gnulib. They are here instead of sys/cdefs.h because they should not be diff --git a/lib/libc/glibc/include/pthread.h b/lib/libc/glibc/include/pthread.h index 819bf3f235e36d5988c84a9dca5037b23a9cc449..9e31b74916d78c6faa5aa7b05186d76f0a3c731c 100644 --- a/lib/libc/glibc/include/pthread.h +++ b/lib/libc/glibc/include/pthread.h @@ -8,14 +8,10 @@ extern int __pthread_barrier_init (pthread_barrier_t *__restrict __barrier, const pthread_barrierattr_t *__restrict __attr, unsigned int __count) __THROW __nonnull ((1)); -#if PTHREAD_IN_LIBC libc_hidden_proto (__pthread_barrier_init) -#endif extern int __pthread_barrier_wait (pthread_barrier_t *__barrier) __THROWNL __nonnull ((1)); -#if PTHREAD_IN_LIBC libc_hidden_proto (__pthread_barrier_wait) -#endif /* This function is called to initialize the pthread library. */ extern void __pthread_initialize (void) __attribute__ ((weak)); diff --git a/lib/libc/glibc/include/stap-probe.h b/lib/libc/glibc/include/stap-probe.h index 9ff4ca83e5a7d720096364b66369baf7a8708333..32e4e77f2a62e64ffcebd87bcb0e9b1b4cfc3cdc 100644 --- a/lib/libc/glibc/include/stap-probe.h +++ b/lib/libc/glibc/include/stap-probe.h @@ -1,5 +1,5 @@ /* Macros for defining Systemtap static probe points. - Copyright (C) 2012-2025 Free Software Foundation, Inc. + Copyright (C) 2012-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/io/bits/statx.h b/lib/libc/glibc/io/bits/statx.h index 5c1228c3bffc356fd1172500436519d4f29def73..ec94398bdc5442710f2f0d7f8996c09d51d86b91 100644 --- a/lib/libc/glibc/io/bits/statx.h +++ b/lib/libc/glibc/io/bits/statx.h @@ -1,5 +1,5 @@ /* statx-related definitions and declarations. Generic version. - Copyright (C) 2018-2025 Free Software Foundation, Inc. + Copyright (C) 2018-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/io/fcntl.h b/lib/libc/glibc/io/fcntl.h index d99dc68a88742bf898f6ac501d20a8afb989e58e..7bbccf05725b75667448608f6a915d5446d19cd0 100644 --- a/lib/libc/glibc/io/fcntl.h +++ b/lib/libc/glibc/io/fcntl.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1991-2025 Free Software Foundation, Inc. +/* Copyright (C) 1991-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/io/mknod.c b/lib/libc/glibc/io/mknod.c index 66020d58ca3a2c8a25790c1c218ee67feb35207f..bd2a4a1268596dd106bf3882bfc8d24bc8750e71 100644 --- a/lib/libc/glibc/io/mknod.c +++ b/lib/libc/glibc/io/mknod.c @@ -1,4 +1,4 @@ -/* Copyright (C) 1995-2025 Free Software Foundation, Inc. +/* Copyright (C) 1995-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/io/sys/stat.h b/lib/libc/glibc/io/sys/stat.h index 4bea9e9a7785f51af8eefd035874f475b92bb26d..3069e187b079461dec1d5c699d49b5aa570a205d 100644 --- a/lib/libc/glibc/io/sys/stat.h +++ b/lib/libc/glibc/io/sys/stat.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1991-2025 Free Software Foundation, Inc. +/* Copyright (C) 1991-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/locale/bits/types/__locale_t.h b/lib/libc/glibc/locale/bits/types/__locale_t.h index 746b1209f1f389bfd135d4206e5400a2fa33118f..c59a107941a2e344a98b26ae7cf7162f813af79f 100644 --- a/lib/libc/glibc/locale/bits/types/__locale_t.h +++ b/lib/libc/glibc/locale/bits/types/__locale_t.h @@ -1,5 +1,5 @@ /* Definition of struct __locale_struct and __locale_t. - Copyright (C) 1997-2025 Free Software Foundation, Inc. + Copyright (C) 1997-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/locale/bits/types/locale_t.h b/lib/libc/glibc/locale/bits/types/locale_t.h index fe8864ca6be90926c3bce77ec58d4a1b588dfa81..a825f11fdfa14407462fe399cca89ee0d545341e 100644 --- a/lib/libc/glibc/locale/bits/types/locale_t.h +++ b/lib/libc/glibc/locale/bits/types/locale_t.h @@ -1,5 +1,5 @@ /* Definition of locale_t. - Copyright (C) 2017-2025 Free Software Foundation, Inc. + Copyright (C) 2017-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/misc/sys/cdefs.h b/lib/libc/glibc/misc/sys/cdefs.h index 215ff937ee9c8eb85ebd9412c0ff5a93c6809f3f..8d27f26da8d9e1dc3a702123c5208095624b3166 100644 --- a/lib/libc/glibc/misc/sys/cdefs.h +++ b/lib/libc/glibc/misc/sys/cdefs.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1992-2025 Free Software Foundation, Inc. +/* Copyright (C) 1992-2026 Free Software Foundation, Inc. Copyright The GNU Toolchain Authors. This file is part of the GNU C Library. @@ -438,10 +438,10 @@ */ #endif -/* GCC and clang have various useful declarations that can be made with - the '__attribute__' syntax. All of the ways we use this do fine if - they are omitted for compilers that don't understand it. */ -#if !(defined __GNUC__ || defined __clang__) +/* GCC, clang, and compatible compilers have various useful declarations + that can be made with the '__attribute__' syntax. All of the ways we use + this do fine if they are omitted for compilers that don't understand it. */ +#if !(defined __GNUC__ || defined __clang__ || defined __TINYC__) # define __attribute__(xyz) /* Ignore */ #endif @@ -606,14 +606,14 @@ # define __attribute_artificial__ /* Ignore */ #endif -/* GCC 4.3 and above with -std=c99 or -std=gnu99 implements ISO C99 - inline semantics, unless -fgnu89-inline is used. Using __GNUC_STDC_INLINE__ - or __GNUC_GNU_INLINE is not a good enough check for gcc because gcc versions +/* GCC 4.3 and above with -std=c99 or -std=gnu99 implements ISO C99 inline + semantics, unless -fgnu89-inline is used. Using __GNUC_STDC_INLINE__ or + __GNUC_GNU_INLINE__ is not a good enough check for gcc because gcc versions older than 4.3 may define these macros and still not guarantee GNU inlining semantics. clang++ identifies itself as gcc-4.2, but has support for GNU inlining - semantics, that can be checked for by using the __GNUC_STDC_INLINE_ and + semantics, that can be checked for by using the __GNUC_STDC_INLINE__ and __GNUC_GNU_INLINE__ macro definitions. */ #if (!defined __cplusplus || __GNUC_PREREQ (4,3) \ || (defined __clang__ && (defined __GNUC_STDC_INLINE__ \ @@ -828,6 +828,18 @@ _Static_assert (0, "IEEE 128-bits long double requires redirection on this platf # define __HAVE_GENERIC_SELECTION 0 #endif +#if __HAVE_GENERIC_SELECTION +/* If PTR is a pointer to const, return CALL cast to type CTYPE, + otherwise return CALL. Pointers to types with non-const qualifiers + are not valid. This should not be defined for C++, as macros are + not an appropriate way of implementing such qualifier-generic + operations for C++. */ +# define __glibc_const_generic(PTR, CTYPE, CALL) \ + _Generic (0 ? (PTR) : (void *) 1, \ + const void *: (CTYPE) (CALL), \ + default: CALL) +#endif + #if __GNUC_PREREQ (10, 0) /* Designates a 1-based positional argument ref-index of pointer type that can be used to access size-index elements of the pointed-to diff --git a/lib/libc/glibc/misc/sys/select.h b/lib/libc/glibc/misc/sys/select.h index d2cdc0f1cdb82f16c61d85cb766ad83d436ceffd..fdc3c89ce61e447715ee441214576d8470dd2e32 100644 --- a/lib/libc/glibc/misc/sys/select.h +++ b/lib/libc/glibc/misc/sys/select.h @@ -1,5 +1,5 @@ /* `fd_set' type and related macros, and `select'/`pselect' declarations. - Copyright (C) 1996-2025 Free Software Foundation, Inc. + Copyright (C) 1996-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/posix/bits/cpu-set.h b/lib/libc/glibc/posix/bits/cpu-set.h index 9dc2d31fa7c3e0bf054f88888cce8e73adac77d2..ddb79cefc2e5d93003f8da84eca6302f99153a22 100644 --- a/lib/libc/glibc/posix/bits/cpu-set.h +++ b/lib/libc/glibc/posix/bits/cpu-set.h @@ -1,6 +1,6 @@ /* Definition of the cpu_set_t structure used by the POSIX 1003.1b-1993 scheduling interface. - Copyright (C) 1996-2025 Free Software Foundation, Inc. + Copyright (C) 1996-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/posix/bits/types.h b/lib/libc/glibc/posix/bits/types.h index a6638467c8bcda24edde5bdea54d933dbd65feeb..fdef71b4c2d613acc12836ad6d6da6a924288cd3 100644 --- a/lib/libc/glibc/posix/bits/types.h +++ b/lib/libc/glibc/posix/bits/types.h @@ -1,5 +1,5 @@ /* bits/types.h -- definitions of __*_t types underlying *_t types. - Copyright (C) 2002-2025 Free Software Foundation, Inc. + Copyright (C) 2002-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/posix/sys/types.h b/lib/libc/glibc/posix/sys/types.h index ab3037a9dab25fdbbe4eccb3875b2a3305c7f342..2b524761748fc2e0576e3b86fd5558e8a9ebcb18 100644 --- a/lib/libc/glibc/posix/sys/types.h +++ b/lib/libc/glibc/posix/sys/types.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1991-2025 Free Software Foundation, Inc. +/* Copyright (C) 1991-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/signal/signal.h b/lib/libc/glibc/signal/signal.h index 413b4fd23f3947ef9922599b310317e8e7458f3a..0cfcdd62176059908ed838e4599612ad4e3c130d 100644 --- a/lib/libc/glibc/signal/signal.h +++ b/lib/libc/glibc/signal/signal.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1991-2025 Free Software Foundation, Inc. +/* Copyright (C) 1991-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/stdlib/alloca.h b/lib/libc/glibc/stdlib/alloca.h index ec36f825ad123e1881bf97a22add20ef73be445d..5700dd7123359573c175e98618d029818d7db7ad 100644 --- a/lib/libc/glibc/stdlib/alloca.h +++ b/lib/libc/glibc/stdlib/alloca.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1992-2025 Free Software Foundation, Inc. +/* Copyright (C) 1992-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/stdlib/bits/stdlib-float.h b/lib/libc/glibc/stdlib/bits/stdlib-float.h index 5f2902949e76d6965e7d85dc5040d70018c47fa1..d75221470b35706514264e3f09908a8e0f729d56 100644 --- a/lib/libc/glibc/stdlib/bits/stdlib-float.h +++ b/lib/libc/glibc/stdlib/bits/stdlib-float.h @@ -1,5 +1,5 @@ /* Floating-point inline functions for stdlib.h. - Copyright (C) 2012-2025 Free Software Foundation, Inc. + Copyright (C) 2012-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/stdlib/errno.h b/lib/libc/glibc/stdlib/errno.h index 64517aa2fb70fd74aa1db1c751c09be884de8de3..f7828f0e6a62b6744b7d1b0a68259380ea68868f 100644 --- a/lib/libc/glibc/stdlib/errno.h +++ b/lib/libc/glibc/stdlib/errno.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1991-2025 Free Software Foundation, Inc. +/* Copyright (C) 1991-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/stdlib/exit.h b/lib/libc/glibc/stdlib/exit.h index bbe80292e4354bdc0df2bdd265274725d06da792..841f64f2d323419f8e89a52276a875228ed6d801 100644 --- a/lib/libc/glibc/stdlib/exit.h +++ b/lib/libc/glibc/stdlib/exit.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1991-2025 Free Software Foundation, Inc. +/* Copyright (C) 1991-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/stdlib/stdlib.h b/lib/libc/glibc/stdlib/stdlib.h index cd4503c761887324ddda8f0fd630e99c2bff196d..1c67d8e13f348e55e9ef82059213bb1b7a02f734 100644 --- a/lib/libc/glibc/stdlib/stdlib.h +++ b/lib/libc/glibc/stdlib/stdlib.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1991-2025 Free Software Foundation, Inc. +/* Copyright (C) 1991-2026 Free Software Foundation, Inc. Copyright The GNU Toolchain Authors. This file is part of the GNU C Library. @@ -35,6 +35,10 @@ __BEGIN_DECLS #define _STDLIB_H 1 +#if __GLIBC_USE (ISOC23) +# define __STDC_VERSION_STDLIB_H__ 202311L +#endif + #if (defined __USE_XOPEN || defined __USE_XOPEN2K8) && !defined _SYS_WAIT_H /* XPG requires a few symbols from being defined. */ # include @@ -686,6 +690,24 @@ extern void *realloc (void *__ptr, size_t __size) /* Free a block allocated by `malloc', `realloc' or `calloc'. */ extern void free (void *__ptr) __THROW; +#if __GLIBC_USE(ISOC23) +/* Free a block allocated by `malloc', `realloc' or `calloc' but not + `aligned_alloc', `memalign', `posix_memalign', `valloc' or + `pvalloc'. SIZE must be equal to the original requested size + provided to `malloc', `realloc' or `calloc'. For `calloc' SIZE is + NMEMB elements * SIZE bytes. It is forbidden to call `free_sized' + for allocations which the caller did not directly allocate but + must still deallocate, such as `strdup' or `strndup'. Instead + continue using `free` for these cases. */ +extern void free_sized (void *__ptr, size_t __size) __THROW; + +/* Free a block allocated by `aligned_alloc', `memalign' or + `posix_memalign'. ALIGNMENT and SIZE must be the same as the values + provided to `aligned_alloc', `memalign' or `posix_memalign'. */ +extern void free_aligned_sized (void *__ptr, size_t __alignment, size_t __size) + __THROW; +#endif + #ifdef __USE_MISC /* Re-allocate the previously allocated block in PTR, making the new block large enough for NMEMB elements of SIZE bytes each. */ @@ -965,6 +987,12 @@ extern void *bsearch (const void *__key, const void *__base, # include #endif +#if __GLIBC_USE (ISOC23) && defined __glibc_const_generic && !defined _LIBC +# define bsearch(KEY, BASE, NMEMB, SIZE, COMPAR) \ + __glibc_const_generic (BASE, const void *, \ + bsearch (KEY, BASE, NMEMB, SIZE, COMPAR)) +#endif + /* Sort NMEMB elements of BASE, of SIZE bytes each, using COMPAR to perform the comparisons. */ extern void qsort (void *__base, size_t __nmemb, size_t __size, @@ -1158,6 +1186,19 @@ extern int getloadavg (double __loadavg[], int __nelem) extern int ttyslot (void) __THROW; #endif +#if __GLIBC_USE (ISOC23) +# ifndef __cplusplus +# include + +/* Call function __FUNC exactly once, even if invoked from several threads. + All calls must be made with the same __FLAGS object. */ +extern void call_once (once_flag *__flag, void (*__func)(void)); +# endif /* !__cplusplus */ + +/* Return the alignment of P. */ +extern size_t memalignment (const void *__p); +#endif + #include /* Define some macros helping to catch buffer overflows. */ diff --git a/lib/libc/glibc/string/bits/endian.h b/lib/libc/glibc/string/bits/endian.h index e267b2b5135b330159db993a2531b3908bf1ecd6..d60ddfdc12f6b6e026a823479ebfbe176128ca1d 100644 --- a/lib/libc/glibc/string/bits/endian.h +++ b/lib/libc/glibc/string/bits/endian.h @@ -1,5 +1,5 @@ /* Endian macros for string.h functions - Copyright (C) 1992-2025 Free Software Foundation, Inc. + Copyright (C) 1992-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/string/endian.h b/lib/libc/glibc/string/endian.h index 6b5d65f967af6babdd68b69c3bc03a05b096a2be..5b732541d3bb806083c882fdd30122fed628c204 100644 --- a/lib/libc/glibc/string/endian.h +++ b/lib/libc/glibc/string/endian.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1992-2025 Free Software Foundation, Inc. +/* Copyright (C) 1992-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/sysdeps/aarch64/nptl/bits/pthreadtypes-arch.h b/lib/libc/glibc/sysdeps/aarch64/nptl/bits/pthreadtypes-arch.h index c2825603353ee2013726ac0ffd8678523453cafe..4dc5245e0608146758e620a73df1c1bbd378c39a 100644 --- a/lib/libc/glibc/sysdeps/aarch64/nptl/bits/pthreadtypes-arch.h +++ b/lib/libc/glibc/sysdeps/aarch64/nptl/bits/pthreadtypes-arch.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2002-2025 Free Software Foundation, Inc. +/* Copyright (C) 2002-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. diff --git a/lib/libc/glibc/sysdeps/aarch64/start.S b/lib/libc/glibc/sysdeps/aarch64/start.S index 694c338c8be3f6ab642e88f84eeec804d6946580..52a1d3bb83386168ac9ebdacd0e69cb0035b731f 100644 --- a/lib/libc/glibc/sysdeps/aarch64/start.S +++ b/lib/libc/glibc/sysdeps/aarch64/start.S @@ -1,4 +1,4 @@ -/* Copyright (C) 1995-2025 Free Software Foundation, Inc. +/* Copyright (C) 1995-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. diff --git a/lib/libc/glibc/sysdeps/aarch64/sysdep.h b/lib/libc/glibc/sysdeps/aarch64/sysdep.h index f5e28cb2427e0f7ce5e1de749b9f93ca86507131..da4b7f3fd32c4fbd62ce777d1857baeda279dcbb 100644 --- a/lib/libc/glibc/sysdeps/aarch64/sysdep.h +++ b/lib/libc/glibc/sysdeps/aarch64/sysdep.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1997-2025 Free Software Foundation, Inc. +/* Copyright (C) 1997-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. diff --git a/lib/libc/glibc/sysdeps/arc/start.S b/lib/libc/glibc/sysdeps/arc/start.S index 372dd3e299b604d25bff35616be69074c8084fdc..57fe4a15f9287baf9976ef6c222f86e8cf5608e7 100644 --- a/lib/libc/glibc/sysdeps/arc/start.S +++ b/lib/libc/glibc/sysdeps/arc/start.S @@ -1,5 +1,5 @@ /* Startup code for ARC. - Copyright (C) 2020-2025 Free Software Foundation, Inc. + Copyright (C) 2020-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/sysdeps/arc/sysdep.h b/lib/libc/glibc/sysdeps/arc/sysdep.h index b831b5f79b9bad5ce18bf3d49fcd39f9beb12fa3..1e244b040dff4bc842773e72e5452494ddf74b5b 100644 --- a/lib/libc/glibc/sysdeps/arc/sysdep.h +++ b/lib/libc/glibc/sysdeps/arc/sysdep.h @@ -1,5 +1,5 @@ /* Assembler macros for ARC. - Copyright (C) 2020-2025 Free Software Foundation, Inc. + Copyright (C) 2020-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/sysdeps/arm/arm-features.h b/lib/libc/glibc/sysdeps/arm/arm-features.h index 04adcc90a84d275e01f51c022855f6ddc33bd96b..b2504ccebb566718d33f328a390395f226933063 100644 --- a/lib/libc/glibc/sysdeps/arm/arm-features.h +++ b/lib/libc/glibc/sysdeps/arm/arm-features.h @@ -1,5 +1,5 @@ /* Macros to test for CPU features on ARM. Generic ARM version. - Copyright (C) 2012-2025 Free Software Foundation, Inc. + Copyright (C) 2012-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/sysdeps/arm/start.S b/lib/libc/glibc/sysdeps/arm/start.S index 64eead31d5ed49fe18aa6839105363d995b9c9d1..a7e62b39346d18be9d46f64048b092e7c873b068 100644 --- a/lib/libc/glibc/sysdeps/arm/start.S +++ b/lib/libc/glibc/sysdeps/arm/start.S @@ -1,5 +1,5 @@ /* Startup code for ARM & ELF - Copyright (C) 1995-2025 Free Software Foundation, Inc. + Copyright (C) 1995-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/sysdeps/arm/sysdep.h b/lib/libc/glibc/sysdeps/arm/sysdep.h index 8e66fa5666e9ff09f0d9c785587e4ad4246fe3e1..7f9d740772e1e527a8ab6112c5392bb99f4180b1 100644 --- a/lib/libc/glibc/sysdeps/arm/sysdep.h +++ b/lib/libc/glibc/sysdeps/arm/sysdep.h @@ -1,5 +1,5 @@ /* Assembler macros for ARM. - Copyright (C) 1997-2025 Free Software Foundation, Inc. + Copyright (C) 1997-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/sysdeps/csky/abiv2/start.S b/lib/libc/glibc/sysdeps/csky/abiv2/start.S index ce1d7d56f615860361ffd49dea77f2d6e157e94e..973b79693cca4a7ef9cb1674d3b40ca62a121eff 100644 --- a/lib/libc/glibc/sysdeps/csky/abiv2/start.S +++ b/lib/libc/glibc/sysdeps/csky/abiv2/start.S @@ -1,5 +1,5 @@ /* Startup code compliant to the ELF C-SKY ABIV2. - Copyright (C) 2018-2025 Free Software Foundation, Inc. + Copyright (C) 2018-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/sysdeps/csky/sysdep.h b/lib/libc/glibc/sysdeps/csky/sysdep.h index 8ca062b06ddde8dbaa3eabcf8e30df849a3378ec..a2436632c9fc5fabaea8478fc3f2c55857b045bf 100644 --- a/lib/libc/glibc/sysdeps/csky/sysdep.h +++ b/lib/libc/glibc/sysdeps/csky/sysdep.h @@ -1,5 +1,5 @@ /* Assembler macros for C-SKY. - Copyright (C) 2018-2025 Free Software Foundation, Inc. + Copyright (C) 2018-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/sysdeps/generic/dl-dtprocnum.h b/lib/libc/glibc/sysdeps/generic/dl-dtprocnum.h index 8cf83a37256d133802f6f65f175a966dd1011aec..0d3a43d17608fd177965a06da3db47d0aeafc85c 100644 --- a/lib/libc/glibc/sysdeps/generic/dl-dtprocnum.h +++ b/lib/libc/glibc/sysdeps/generic/dl-dtprocnum.h @@ -1,5 +1,5 @@ /* Configuration of lookup functions. - Copyright (C) 2000-2025 Free Software Foundation, Inc. + Copyright (C) 2000-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/sysdeps/generic/dl-sysdep.h b/lib/libc/glibc/sysdeps/generic/dl-sysdep.h index d7a90f23d51dc7898d986d3181ac3eca8b818db1..9aac6e7bc707701cd745ef55498c2f7aa4a6521b 100644 --- a/lib/libc/glibc/sysdeps/generic/dl-sysdep.h +++ b/lib/libc/glibc/sysdeps/generic/dl-sysdep.h @@ -1,5 +1,5 @@ /* System-specific settings for dynamic linker code. Generic version. - Copyright (C) 2002-2025 Free Software Foundation, Inc. + Copyright (C) 2002-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/sysdeps/generic/dwarf2.h b/lib/libc/glibc/sysdeps/generic/dwarf2.h index cdd0f96102b37f5c14dfc88aebbcadcff3236cf4..6dfa3c562c30cc1bea3f49169e6a20ff806f409d 100644 --- a/lib/libc/glibc/sysdeps/generic/dwarf2.h +++ b/lib/libc/glibc/sysdeps/generic/dwarf2.h @@ -1,6 +1,6 @@ /* Declarations and definitions of codes relating to the DWARF2 symbolic debugging information format. - Copyright (C) 1992-2025 Free Software Foundation, Inc. + Copyright (C) 1992-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. diff --git a/lib/libc/glibc/sysdeps/generic/libc-lock.h b/lib/libc/glibc/sysdeps/generic/libc-lock.h index fafaf8c932560d8cae82a694679a5332c2a211bc..4dbd0bed63fdaf162783aea23256a51a67a4d4da 100644 --- a/lib/libc/glibc/sysdeps/generic/libc-lock.h +++ b/lib/libc/glibc/sysdeps/generic/libc-lock.h @@ -1,5 +1,5 @@ /* libc-internal interface for mutex locks. Stub version. - Copyright (C) 1996-2025 Free Software Foundation, Inc. + Copyright (C) 1996-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/sysdeps/generic/libc-symver.h b/lib/libc/glibc/sysdeps/generic/libc-symver.h index 0725e0c8e3eef21740a9c27011f515a99b6c3272..dd0425117c9ae2a0c1d32d7e5def48240e3e1a36 100644 --- a/lib/libc/glibc/sysdeps/generic/libc-symver.h +++ b/lib/libc/glibc/sysdeps/generic/libc-symver.h @@ -1,5 +1,5 @@ /* Symbol version management. - Copyright (C) 1995-2025 Free Software Foundation, Inc. + Copyright (C) 1995-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/sysdeps/generic/single-thread.h b/lib/libc/glibc/sysdeps/generic/single-thread.h index 6a37563e5f9dcb7b867d9343dec6a5a6cd843e33..3f576308a16ae72b473638dc0eebc62b9535f432 100644 --- a/lib/libc/glibc/sysdeps/generic/single-thread.h +++ b/lib/libc/glibc/sysdeps/generic/single-thread.h @@ -1,5 +1,5 @@ /* Single thread optimization, generic version. - Copyright (C) 2019-2025 Free Software Foundation, Inc. + Copyright (C) 2019-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/sysdeps/generic/symbol-hacks.h b/lib/libc/glibc/sysdeps/generic/symbol-hacks.h index 1115e4c0a7af113e21eefb99fb04ca794470e68a..0d728cce9154781be7848eee706d22d309690b12 100644 --- a/lib/libc/glibc/sysdeps/generic/symbol-hacks.h +++ b/lib/libc/glibc/sysdeps/generic/symbol-hacks.h @@ -6,6 +6,22 @@ asm ("memmove = __GI_memmove"); asm ("memset = __GI_memset"); asm ("memcpy = __GI_memcpy"); +/* clang might generate the internal fortfify calls when it is enabled, + through the buitintin. */ +asm ("__vfprintf_chk = __GI___vfprintf_chk"); +asm ("__vsprintf_chk = __GI___vsprintf_chk"); +asm ("__vsyslog_chk = __GI___vsyslog_chk"); +asm ("__memcpy_chk = __GI___memcpy_chk"); +asm ("__memmove_chk = __GI___memmove_chk"); +asm ("__memset_chk = __GI___memset_chk"); +asm ("__mempcpy_chk = __GI___mempcpy_chk"); +asm ("__stpcpy_chk = __GI___stpcpy_chk"); +asm ("__strcpy_chk = __GI___strcpy_chk"); +asm ("strcpy = __GI_strcpy"); +asm ("strncpy = __GI_strncpy"); +asm ("strcat = __GI_strcat"); +asm ("strlen = __GI_strlen"); + /* Some targets do not use __stack_chk_fail_local. In libc.so, redirect __stack_chk_fail to a hidden reference __stack_chk_fail_local, to avoid the PLT reference. diff --git a/lib/libc/glibc/sysdeps/generic/sysdep.h b/lib/libc/glibc/sysdeps/generic/sysdep.h index ef5eba2c87f513ca6083c0dfde8558a25dde6fc2..fb07f6747cd736a1f55d26ed0d102c972cd0418d 100644 --- a/lib/libc/glibc/sysdeps/generic/sysdep.h +++ b/lib/libc/glibc/sysdeps/generic/sysdep.h @@ -1,5 +1,5 @@ /* Generic asm macros used on many machines. - Copyright (C) 1991-2025 Free Software Foundation, Inc. + Copyright (C) 1991-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/sysdeps/generic/tls.h b/lib/libc/glibc/sysdeps/generic/tls.h index f6155b5ba67bb74cbecda2c13b9a467b999ae8f1..a569d074cabb04c45ec845098897795501e545a2 100644 --- a/lib/libc/glibc/sysdeps/generic/tls.h +++ b/lib/libc/glibc/sysdeps/generic/tls.h @@ -1,5 +1,5 @@ /* Definition for thread-local data handling. Generic version. - Copyright (C) 2002-2025 Free Software Foundation, Inc. + Copyright (C) 2002-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/sysdeps/htl/bits/pthread.h b/lib/libc/glibc/sysdeps/htl/bits/pthread.h index c0ec3932ae688000e56050a6ed262f0e85437b64..2209c51e016d02cfef4997c82c11b5a8376718ed 100644 --- a/lib/libc/glibc/sysdeps/htl/bits/pthread.h +++ b/lib/libc/glibc/sysdeps/htl/bits/pthread.h @@ -1,5 +1,5 @@ /* Pthread data structures. Generic version. - Copyright (C) 2002-2025 Free Software Foundation, Inc. + Copyright (C) 2002-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/sysdeps/htl/bits/thread-shared-types.h b/lib/libc/glibc/sysdeps/htl/bits/thread-shared-types.h index 32a545014ce8e6736cbfafe5d408567082cd20de..52a01426b02ea17521b7d0f02a1712664f746782 100644 --- a/lib/libc/glibc/sysdeps/htl/bits/thread-shared-types.h +++ b/lib/libc/glibc/sysdeps/htl/bits/thread-shared-types.h @@ -1,5 +1,5 @@ /* Common threading primitives definitions for both POSIX and C11. - Copyright (C) 2017-2025 Free Software Foundation, Inc. + Copyright (C) 2017-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/sysdeps/htl/libc-lockP.h b/lib/libc/glibc/sysdeps/htl/libc-lockP.h index e9977e46a1a9973fc78e6b7858636fb202acf97d..a88eea4344004d7bcacdcd6bb827547ab9a41026 100644 --- a/lib/libc/glibc/sysdeps/htl/libc-lockP.h +++ b/lib/libc/glibc/sysdeps/htl/libc-lockP.h @@ -1,5 +1,5 @@ /* Private libc-internal interface for mutex locks. - Copyright (C) 2015-2025 Free Software Foundation, Inc. + Copyright (C) 2015-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or @@ -20,7 +20,6 @@ #define _BITS_LIBC_LOCKP_H 1 #include -#include /* If we check for a weakly referenced symbol and then perform a normal jump to it te code generated for some platforms in case of @@ -36,40 +35,6 @@ (FUNC != NULL ? FUNC ARGS : ELSE) #endif -/* Call thread functions through the function pointer table. */ -#if defined SHARED && IS_IN (libc) -# define PTFAVAIL(NAME) __libc_pthread_functions_init -# define __libc_ptf_call(FUNC, ARGS, ELSE) \ - (__libc_pthread_functions_init ? PTHFCT_CALL (ptr_##FUNC, ARGS) : ELSE) -# define __libc_ptf_call_always(FUNC, ARGS) \ - PTHFCT_CALL (ptr_##FUNC, ARGS) -#elif IS_IN (libpthread) -# define PTFAVAIL(NAME) 1 -# define __libc_ptf_call(FUNC, ARGS, ELSE) \ - FUNC ARGS -# define __libc_ptf_call_always(FUNC, ARGS) \ - FUNC ARGS -#else -# define PTFAVAIL(NAME) (NAME != NULL) -# define __libc_ptf_call(FUNC, ARGS, ELSE) \ - __libc_maybe_call (FUNC, ARGS, ELSE) -# define __libc_ptf_call_always(FUNC, ARGS) \ - FUNC ARGS -#endif - -/* Create thread-specific key. */ -#define __libc_key_create(KEY, DESTRUCTOR) \ - __libc_ptf_call (__pthread_key_create, (KEY, DESTRUCTOR), 1) - -/* Get thread-specific data. */ -#define __libc_getspecific(KEY) \ - __libc_ptf_call (__pthread_getspecific, (KEY), NULL) - -/* Set thread-specific data. */ -#define __libc_setspecific(KEY, VALUE) \ - __libc_ptf_call (__pthread_setspecific, (KEY, VALUE), 0) - - /* Functions that are used by this file and are internal to the GNU C library. */ diff --git a/lib/libc/glibc/sysdeps/htl/pthread.h b/lib/libc/glibc/sysdeps/htl/pthread.h index a299fec2783ee8250cb82343e1094d221c3c743f..0db8fef7c33a0e319693bf3713082fb57e78bed6 100644 --- a/lib/libc/glibc/sysdeps/htl/pthread.h +++ b/lib/libc/glibc/sysdeps/htl/pthread.h @@ -1,5 +1,5 @@ /* Posix threads. Hurd version. - Copyright (C) 2000-2025 Free Software Foundation, Inc. + Copyright (C) 2000-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/sysdeps/i386/htl/bits/pthreadtypes-arch.h b/lib/libc/glibc/sysdeps/i386/htl/bits/pthreadtypes-arch.h index 4aed38fc63a004fb14a9059e99f7dc07355320ba..44c051fc48e489d65849a0f88bcf220f447a0cf5 100644 --- a/lib/libc/glibc/sysdeps/i386/htl/bits/pthreadtypes-arch.h +++ b/lib/libc/glibc/sysdeps/i386/htl/bits/pthreadtypes-arch.h @@ -1,5 +1,5 @@ /* Machine-specific pthread type layouts. Hurd i386 version. - Copyright (C) 2002-2025 Free Software Foundation, Inc. + Copyright (C) 2002-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/sysdeps/i386/start.S b/lib/libc/glibc/sysdeps/i386/start.S index 01f8098b58eff02ec5f68504aac2c9cb0f7b840f..135fb06527957a69d607715de3b81f760b2627de 100644 --- a/lib/libc/glibc/sysdeps/i386/start.S +++ b/lib/libc/glibc/sysdeps/i386/start.S @@ -1,5 +1,5 @@ /* Startup code compliant to the ELF i386 ABI. - Copyright (C) 1995-2025 Free Software Foundation, Inc. + Copyright (C) 1995-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/sysdeps/i386/symbol-hacks.h b/lib/libc/glibc/sysdeps/i386/symbol-hacks.h index f263d736a69584bb64d426030071be70bb2c3ed9..da59cb8928eee5efa4de2fe1f23de6486fbbe066 100644 --- a/lib/libc/glibc/sysdeps/i386/symbol-hacks.h +++ b/lib/libc/glibc/sysdeps/i386/symbol-hacks.h @@ -1,5 +1,5 @@ /* Hacks needed for symbol manipulation. i386 version. - Copyright (C) 2017-2025 Free Software Foundation, Inc. + Copyright (C) 2017-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/sysdeps/i386/sysdep.h b/lib/libc/glibc/sysdeps/i386/sysdep.h index 3aefe7af1e5713e232cd5c2d1633ed78c176d955..d01115d0d6dd4aad4e86c45849f61972d0acb6c7 100644 --- a/lib/libc/glibc/sysdeps/i386/sysdep.h +++ b/lib/libc/glibc/sysdeps/i386/sysdep.h @@ -1,5 +1,5 @@ /* Assembler macros for i386. - Copyright (C) 1991-2025 Free Software Foundation, Inc. + Copyright (C) 1991-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/sysdeps/loongarch/start.S b/lib/libc/glibc/sysdeps/loongarch/start.S index 754c08dc1f9d30bc3338c52a8b5bc561391f9fd2..72452f5307ef430c06fd5852190c68d5f1295366 100644 --- a/lib/libc/glibc/sysdeps/loongarch/start.S +++ b/lib/libc/glibc/sysdeps/loongarch/start.S @@ -1,5 +1,5 @@ /* Startup code compliant to the ELF LoongArch ABI. - Copyright (C) 2022-2025 Free Software Foundation, Inc. + Copyright (C) 2022-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/sysdeps/loongarch/sys/regdef.h b/lib/libc/glibc/sysdeps/loongarch/sys/regdef.h index c65a2c46620a2cf13b4cb40c9fc1c8a176e54d2d..49ac57477a82b7b8199300502203fd9b4c79d20b 100644 --- a/lib/libc/glibc/sysdeps/loongarch/sys/regdef.h +++ b/lib/libc/glibc/sysdeps/loongarch/sys/regdef.h @@ -1,5 +1,5 @@ /* Register Macro definitions - Copyright (C) 2022-2025 Free Software Foundation, Inc. + Copyright (C) 2022-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. diff --git a/lib/libc/glibc/sysdeps/m68k/coldfire/sysdep.h b/lib/libc/glibc/sysdeps/m68k/coldfire/sysdep.h index 563a67266e632d038d2f6010c25787a4d47132cf..353112cc3ff9cf9e2bace8643cd85ade7a81bd3a 100644 --- a/lib/libc/glibc/sysdeps/m68k/coldfire/sysdep.h +++ b/lib/libc/glibc/sysdeps/m68k/coldfire/sysdep.h @@ -1,5 +1,5 @@ /* Assembler macros for Coldfire. - Copyright (C) 1998-2025 Free Software Foundation, Inc. + Copyright (C) 1998-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/sysdeps/m68k/m680x0/sysdep.h b/lib/libc/glibc/sysdeps/m68k/m680x0/sysdep.h index 7faceb35dc002c7bc526217e1bb520f5cffb98b3..2139eccce2e450b9ee624d81c12ca7316863a15f 100644 --- a/lib/libc/glibc/sysdeps/m68k/m680x0/sysdep.h +++ b/lib/libc/glibc/sysdeps/m68k/m680x0/sysdep.h @@ -1,5 +1,5 @@ /* Assembler macros for m680x0. - Copyright (C) 2010-2025 Free Software Foundation, Inc. + Copyright (C) 2010-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/sysdeps/m68k/nptl/bits/pthreadtypes-arch.h b/lib/libc/glibc/sysdeps/m68k/nptl/bits/pthreadtypes-arch.h index 56beb0ed19e505f7c693ad4ddb0900f1708cc335..bda8c53eef44d97dfbd4eba3357d38731eb086b8 100644 --- a/lib/libc/glibc/sysdeps/m68k/nptl/bits/pthreadtypes-arch.h +++ b/lib/libc/glibc/sysdeps/m68k/nptl/bits/pthreadtypes-arch.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2010-2025 Free Software Foundation, Inc. +/* Copyright (C) 2010-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/sysdeps/m68k/start.S b/lib/libc/glibc/sysdeps/m68k/start.S index 9b2ea124bab436d34ae30f4630737b9134ca04bc..b091caea4a07780615bfbabe7e60738d5387ec60 100644 --- a/lib/libc/glibc/sysdeps/m68k/start.S +++ b/lib/libc/glibc/sysdeps/m68k/start.S @@ -1,5 +1,5 @@ /* Startup code compliant to the ELF m68k ABI. - Copyright (C) 1996-2025 Free Software Foundation, Inc. + Copyright (C) 1996-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/sysdeps/m68k/symbol-hacks.h b/lib/libc/glibc/sysdeps/m68k/symbol-hacks.h index d9c1f96c2ee8f8e24ef65d5c51a0ac814d1c2b61..d072b08425b515a677a29063d27f3f55cd1e9951 100644 --- a/lib/libc/glibc/sysdeps/m68k/symbol-hacks.h +++ b/lib/libc/glibc/sysdeps/m68k/symbol-hacks.h @@ -1,5 +1,5 @@ /* Hacks needed for symbol manipulation. m68k version. - Copyright (C) 2017-2025 Free Software Foundation, Inc. + Copyright (C) 2017-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/sysdeps/m68k/sysdep.h b/lib/libc/glibc/sysdeps/m68k/sysdep.h index 26448d26c59401f063b270514a06c1421ea79966..0851d0bc5eeaa2178aee557d1e3a2aa6f470e369 100644 --- a/lib/libc/glibc/sysdeps/m68k/sysdep.h +++ b/lib/libc/glibc/sysdeps/m68k/sysdep.h @@ -1,5 +1,5 @@ /* Assembler macros for m68k. - Copyright (C) 1998-2025 Free Software Foundation, Inc. + Copyright (C) 1998-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/sysdeps/mach/libc-lock.h b/lib/libc/glibc/sysdeps/mach/libc-lock.h index 41fd1c6b8513ec87488f4e5d6b6dd16de313a338..236a24ad807ea292bb25258d64ffb5658f5ddcf5 100644 --- a/lib/libc/glibc/sysdeps/mach/libc-lock.h +++ b/lib/libc/glibc/sysdeps/mach/libc-lock.h @@ -1,5 +1,5 @@ /* libc-internal interface for mutex locks. Mach cthreads version. - Copyright (C) 1996-2025 Free Software Foundation, Inc. + Copyright (C) 1996-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/sysdeps/mach/sysdep.h b/lib/libc/glibc/sysdeps/mach/sysdep.h index 581bdcd54d57296c5b17ee2686e94a451e2548d5..06ca34db55adfa99fc2993e0452d6737256de24b 100644 --- a/lib/libc/glibc/sysdeps/mach/sysdep.h +++ b/lib/libc/glibc/sysdeps/mach/sysdep.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1994-2025 Free Software Foundation, Inc. +/* Copyright (C) 1994-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/sysdeps/mips/dl-dtprocnum.h b/lib/libc/glibc/sysdeps/mips/dl-dtprocnum.h index 801f8b71f16d23703f288dd745e34aa9dc6e2792..caa6240990deab9e43c5ae893b2019e00f11485d 100644 --- a/lib/libc/glibc/sysdeps/mips/dl-dtprocnum.h +++ b/lib/libc/glibc/sysdeps/mips/dl-dtprocnum.h @@ -1,5 +1,5 @@ /* Configuration of lookup functions. MIPS version. - Copyright (C) 2000-2025 Free Software Foundation, Inc. + Copyright (C) 2000-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/sysdeps/mips/isarev.h b/lib/libc/glibc/sysdeps/mips/isarev.h new file mode 100644 index 0000000000000000000000000000000000000000..d3da6a9cb362d3a7e00e6f49c80fe52318b5b324 --- /dev/null +++ b/lib/libc/glibc/sysdeps/mips/isarev.h @@ -0,0 +1,8 @@ +#ifndef _ISAREV_H +#define _ISAREV_H + +#ifndef __mips_isa_rev +# define __mips_isa_rev 0 +#endif + +#endif diff --git a/lib/libc/glibc/sysdeps/mips/nptl/bits/pthreadtypes-arch.h b/lib/libc/glibc/sysdeps/mips/nptl/bits/pthreadtypes-arch.h index ab3cdfb794fef616dd3456641208b28923e3911f..d6e83254568186e0eb47abd21cfca25face784d2 100644 --- a/lib/libc/glibc/sysdeps/mips/nptl/bits/pthreadtypes-arch.h +++ b/lib/libc/glibc/sysdeps/mips/nptl/bits/pthreadtypes-arch.h @@ -1,5 +1,5 @@ /* Machine-specific pthread type layouts. MIPS version. - Copyright (C) 2005-2025 Free Software Foundation, Inc. + Copyright (C) 2005-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/sysdeps/mips/start.S b/lib/libc/glibc/sysdeps/mips/start.S index c58915c13446360a3a155e6a1a8ea9e974541052..409154dd22b51f6e9308fd1c7c4ca93adfe8a909 100644 --- a/lib/libc/glibc/sysdeps/mips/start.S +++ b/lib/libc/glibc/sysdeps/mips/start.S @@ -1,5 +1,5 @@ /* Startup code compliant to the ELF Mips ABI. - Copyright (C) 1995-2025 Free Software Foundation, Inc. + Copyright (C) 1995-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/sysdeps/nptl/bits/pthreadtypes.h b/lib/libc/glibc/sysdeps/nptl/bits/pthreadtypes.h index 1afb017bec56186662026f6d1d61b0f0fbdf2f09..6f4c5c41712f84c7ee984f4a93be4b7063b6d419 100644 --- a/lib/libc/glibc/sysdeps/nptl/bits/pthreadtypes.h +++ b/lib/libc/glibc/sysdeps/nptl/bits/pthreadtypes.h @@ -1,5 +1,5 @@ /* Declaration of common pthread types for all architectures. - Copyright (C) 2017-2025 Free Software Foundation, Inc. + Copyright (C) 2017-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/sysdeps/nptl/bits/thread-shared-types.h b/lib/libc/glibc/sysdeps/nptl/bits/thread-shared-types.h index e614c7f3c900eba098d978ed4ce972146ad85338..624d616fdc6bc2fc43b1559ec269a70e5c5b46b6 100644 --- a/lib/libc/glibc/sysdeps/nptl/bits/thread-shared-types.h +++ b/lib/libc/glibc/sysdeps/nptl/bits/thread-shared-types.h @@ -1,5 +1,5 @@ /* Common threading primitives definitions for both POSIX and C11. - Copyright (C) 2017-2025 Free Software Foundation, Inc. + Copyright (C) 2017-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or @@ -75,7 +75,7 @@ typedef struct __pthread_internal_slist #include -/* Arch-sepecific read-write lock definitions. A generic implementation is +/* Arch-specific read-write lock definitions. A generic implementation is provided by struct_rwlock.h. If required, an architecture can override it by defining: diff --git a/lib/libc/glibc/sysdeps/nptl/libc-lock.h b/lib/libc/glibc/sysdeps/nptl/libc-lock.h index 37755479946156a605613be09f4f9de7f9ee1e05..28bc23c36f8edde1666e0596981c9d7ac1b2ca07 100644 --- a/lib/libc/glibc/sysdeps/nptl/libc-lock.h +++ b/lib/libc/glibc/sysdeps/nptl/libc-lock.h @@ -1,5 +1,5 @@ /* libc-internal interface for mutex locks. NPTL version. - Copyright (C) 1996-2025 Free Software Foundation, Inc. + Copyright (C) 1996-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/sysdeps/nptl/libc-lockP.h b/lib/libc/glibc/sysdeps/nptl/libc-lockP.h index 1be3dd1ec17a1df2e6a8730f65b879a06032b9b3..9c45c5ce88a507f58b6803bf379e0b2daec22a7e 100644 --- a/lib/libc/glibc/sysdeps/nptl/libc-lockP.h +++ b/lib/libc/glibc/sysdeps/nptl/libc-lockP.h @@ -1,5 +1,5 @@ /* Private libc-internal interface for mutex locks. NPTL version. - Copyright (C) 1996-2025 Free Software Foundation, Inc. + Copyright (C) 1996-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or @@ -90,13 +90,6 @@ _Static_assert (LLL_LOCK_INITIALIZER == 0, "LLL_LOCK_INITIALIZER != 0"); (FUNC != NULL ? FUNC ARGS : ELSE) #endif -/* All previously forwarded functions are now called directly (either - via local call in libc, or through a __export), but __libc_ptf_call - is still used in generic code shared with Hurd. */ -#define PTFAVAIL(NAME) 1 -#define __libc_ptf_call(FUNC, ARGS, ELSE) FUNC ARGS -#define __libc_ptf_call_always(FUNC, ARGS) FUNC ARGS - /* Initialize the named lock variable, leaving it in a consistent, unlocked state. */ #define __libc_lock_init(NAME) ((void) ((NAME) = LLL_LOCK_INITIALIZER)) diff --git a/lib/libc/glibc/sysdeps/nptl/pthread.h b/lib/libc/glibc/sysdeps/nptl/pthread.h index 92957a620d9584685dbbd2fb3bf5dc8f050d1041..95c0eb7e0341a4a37ec9df767e80fa7245aee1ef 100644 --- a/lib/libc/glibc/sysdeps/nptl/pthread.h +++ b/lib/libc/glibc/sysdeps/nptl/pthread.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2002-2025 Free Software Foundation, Inc. +/* Copyright (C) 2002-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/sysdeps/powerpc/powerpc32/start.S b/lib/libc/glibc/sysdeps/powerpc/powerpc32/start.S index d1a7c548596ad246b42bcd1079d60395f52c6227..7832fbc5c751a084675264690cc2e6c74e846cac 100644 --- a/lib/libc/glibc/sysdeps/powerpc/powerpc32/start.S +++ b/lib/libc/glibc/sysdeps/powerpc/powerpc32/start.S @@ -1,5 +1,5 @@ /* Startup code for programs linked with GNU libc. - Copyright (C) 1998-2025 Free Software Foundation, Inc. + Copyright (C) 1998-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/sysdeps/powerpc/powerpc32/symbol-hacks.h b/lib/libc/glibc/sysdeps/powerpc/powerpc32/symbol-hacks.h index 898495ac00a81327312e97429f2454f1c32a7c70..1faf282601aaade476ff9bf6cea2d8afe734ccb2 100644 --- a/lib/libc/glibc/sysdeps/powerpc/powerpc32/symbol-hacks.h +++ b/lib/libc/glibc/sysdeps/powerpc/powerpc32/symbol-hacks.h @@ -1,5 +1,5 @@ /* Hacks needed for symbol manipulation. powerpc version. - Copyright (C) 2017-2025 Free Software Foundation, Inc. + Copyright (C) 2017-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/sysdeps/powerpc/powerpc32/sysdep.h b/lib/libc/glibc/sysdeps/powerpc/powerpc32/sysdep.h index 863b011764bb56fa33da248f0305efd7a9dcdf4c..ef7b62029d5ceb7fb46a981d618d34e5fb4fd48b 100644 --- a/lib/libc/glibc/sysdeps/powerpc/powerpc32/sysdep.h +++ b/lib/libc/glibc/sysdeps/powerpc/powerpc32/sysdep.h @@ -1,5 +1,5 @@ /* Assembly macros for 32-bit PowerPC. - Copyright (C) 1999-2025 Free Software Foundation, Inc. + Copyright (C) 1999-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/sysdeps/powerpc/powerpc64/dl-dtprocnum.h b/lib/libc/glibc/sysdeps/powerpc/powerpc64/dl-dtprocnum.h index 452e3b82b5f1d211f0f7824d88167eca6d84f557..842ffcd3cdc89f0d7e2f879c536004350f06ef0e 100644 --- a/lib/libc/glibc/sysdeps/powerpc/powerpc64/dl-dtprocnum.h +++ b/lib/libc/glibc/sysdeps/powerpc/powerpc64/dl-dtprocnum.h @@ -1,5 +1,5 @@ /* Configuration of lookup functions. PowerPC64 version. - Copyright (C) 2002-2025 Free Software Foundation, Inc. + Copyright (C) 2002-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/sysdeps/powerpc/powerpc64/start.S b/lib/libc/glibc/sysdeps/powerpc/powerpc64/start.S index b9a5205edb40a8dc23110349bd94a769a2df3dc7..14e145b4e9516755b6e635ce91694f2b8ad9e854 100644 --- a/lib/libc/glibc/sysdeps/powerpc/powerpc64/start.S +++ b/lib/libc/glibc/sysdeps/powerpc/powerpc64/start.S @@ -1,5 +1,5 @@ /* Startup code for programs linked with GNU libc. PowerPC64 version. - Copyright (C) 1998-2025 Free Software Foundation, Inc. + Copyright (C) 1998-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/sysdeps/powerpc/powerpc64/sysdep.h b/lib/libc/glibc/sysdeps/powerpc/powerpc64/sysdep.h index f05dae71f6fc496c351901c3b49494bba16f778f..26cccbf26913a0a766f2c6f068c4bc9abc802174 100644 --- a/lib/libc/glibc/sysdeps/powerpc/powerpc64/sysdep.h +++ b/lib/libc/glibc/sysdeps/powerpc/powerpc64/sysdep.h @@ -1,5 +1,5 @@ /* Assembly macros for 64-bit PowerPC. - Copyright (C) 2002-2025 Free Software Foundation, Inc. + Copyright (C) 2002-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/sysdeps/powerpc/sysdep.h b/lib/libc/glibc/sysdeps/powerpc/sysdep.h index 8ce71565b17bc91b5d628864a965720154cdd42d..ebbc2d68cde70c634062f0888db5f25eab4e7688 100644 --- a/lib/libc/glibc/sysdeps/powerpc/sysdep.h +++ b/lib/libc/glibc/sysdeps/powerpc/sysdep.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1999-2025 Free Software Foundation, Inc. +/* Copyright (C) 1999-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/sysdeps/riscv/nptl/bits/pthreadtypes-arch.h b/lib/libc/glibc/sysdeps/riscv/nptl/bits/pthreadtypes-arch.h index ec246b4f3f14238099d06230b9dc30aefe7ab3e2..6fe9d1cc1fd1ab011ef9364c9c0eb8bf3e3b6c07 100644 --- a/lib/libc/glibc/sysdeps/riscv/nptl/bits/pthreadtypes-arch.h +++ b/lib/libc/glibc/sysdeps/riscv/nptl/bits/pthreadtypes-arch.h @@ -1,5 +1,5 @@ /* Machine-specific pthread type layouts. RISC-V version. - Copyright (C) 2011-2025 Free Software Foundation, Inc. + Copyright (C) 2011-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/sysdeps/riscv/start.S b/lib/libc/glibc/sysdeps/riscv/start.S index 2db79c0ae6f2481ce7727fa406690a5545b48752..bc3bc04219e820d3a2bf38bcb053fc5435f94055 100644 --- a/lib/libc/glibc/sysdeps/riscv/start.S +++ b/lib/libc/glibc/sysdeps/riscv/start.S @@ -1,5 +1,5 @@ /* Startup code compliant to the ELF RISC-V ABI. - Copyright (C) 1995-2025 Free Software Foundation, Inc. + Copyright (C) 1995-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/sysdeps/s390/s390-64/start.S b/lib/libc/glibc/sysdeps/s390/s390-64/start.S index ab40519307ac60a884fa679315b656b75092bf62..b555503811657b26de5fef1ea9d1feaaea66e359 100644 --- a/lib/libc/glibc/sysdeps/s390/s390-64/start.S +++ b/lib/libc/glibc/sysdeps/s390/s390-64/start.S @@ -1,5 +1,5 @@ /* Startup code compliant to the 64 bit S/390 ELF ABI. - Copyright (C) 2001-2025 Free Software Foundation, Inc. + Copyright (C) 2001-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/sysdeps/s390/s390-64/sysdep.h b/lib/libc/glibc/sysdeps/s390/s390-64/sysdep.h index cb5e432fc5ff927220efe5b891a980df04012950..18ed5f1b039fe6555493d8e230918bf24a41a2bb 100644 --- a/lib/libc/glibc/sysdeps/s390/s390-64/sysdep.h +++ b/lib/libc/glibc/sysdeps/s390/s390-64/sysdep.h @@ -1,5 +1,5 @@ /* Assembler macros for 64 bit S/390. - Copyright (C) 2001-2025 Free Software Foundation, Inc. + Copyright (C) 2001-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/sysdeps/sparc/dl-dtprocnum.h b/lib/libc/glibc/sysdeps/sparc/dl-dtprocnum.h index fca73324af4b346f2bbec1fed1b427f88fe173d5..2e573d8915264733cff80b28307fcedfa246ac38 100644 --- a/lib/libc/glibc/sysdeps/sparc/dl-dtprocnum.h +++ b/lib/libc/glibc/sysdeps/sparc/dl-dtprocnum.h @@ -1,5 +1,5 @@ /* Configuration of lookup functions. SPARC version. - Copyright (C) 2000-2025 Free Software Foundation, Inc. + Copyright (C) 2000-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/sysdeps/sparc/sparc32/start.S b/lib/libc/glibc/sysdeps/sparc/sparc32/start.S index 8393760da684d4f333090d2dfb2aecbe8a8e87e9..89be42dcb059979bb88d0666e99b11a8aa45a7ce 100644 --- a/lib/libc/glibc/sysdeps/sparc/sparc32/start.S +++ b/lib/libc/glibc/sysdeps/sparc/sparc32/start.S @@ -1,5 +1,5 @@ /* Startup code for elf32-sparc - Copyright (C) 1997-2025 Free Software Foundation, Inc. + Copyright (C) 1997-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/sysdeps/sparc/sparc64/start.S b/lib/libc/glibc/sysdeps/sparc/sparc64/start.S index 08e1e77210540cdac66493c22fe9b3c6859c9cd1..5e85fa8aa4b068f378f1a2c3425e20f424bf4e02 100644 --- a/lib/libc/glibc/sysdeps/sparc/sparc64/start.S +++ b/lib/libc/glibc/sysdeps/sparc/sparc64/start.S @@ -1,5 +1,5 @@ /* Startup code for elf64-sparc - Copyright (C) 1997-2025 Free Software Foundation, Inc. + Copyright (C) 1997-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/sysdeps/sparc/sysdep.h b/lib/libc/glibc/sysdeps/sparc/sysdep.h index 8381b0570de4d0fa7668b2b49b35ebed62cf729d..bac213b470045c74332e6bf9f01913b303ef1d96 100644 --- a/lib/libc/glibc/sysdeps/sparc/sysdep.h +++ b/lib/libc/glibc/sysdeps/sparc/sysdep.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2011-2025 Free Software Foundation, Inc. +/* Copyright (C) 2011-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/sysdeps/unix/arm/sysdep.h b/lib/libc/glibc/sysdeps/unix/arm/sysdep.h index 814d16fbade9f15cdf1f37dc25dea0c083b25459..96614a5df972cfd4171e60a2645f972d2f407903 100644 --- a/lib/libc/glibc/sysdeps/unix/arm/sysdep.h +++ b/lib/libc/glibc/sysdeps/unix/arm/sysdep.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1997-2025 Free Software Foundation, Inc. +/* Copyright (C) 1997-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/sysdeps/unix/i386/sysdep.h b/lib/libc/glibc/sysdeps/unix/i386/sysdep.h index e58f841f8bd6726045cffa71802e5169e6391fed..8a9d35ef080b4cf88b4b498379326b78b8f9f236 100644 --- a/lib/libc/glibc/sysdeps/unix/i386/sysdep.h +++ b/lib/libc/glibc/sysdeps/unix/i386/sysdep.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1991-2025 Free Software Foundation, Inc. +/* Copyright (C) 1991-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/sysdeps/unix/mips/mips32/sysdep.h b/lib/libc/glibc/sysdeps/unix/mips/mips32/sysdep.h index e09e4be5b091de5dbae6592beec7c764e3a305ed..4ea03efb4a9b9c4d581e70bf20d7cc214139a3c3 100644 --- a/lib/libc/glibc/sysdeps/unix/mips/mips32/sysdep.h +++ b/lib/libc/glibc/sysdeps/unix/mips/mips32/sysdep.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1992-2025 Free Software Foundation, Inc. +/* Copyright (C) 1992-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/sysdeps/unix/mips/mips64/sysdep.h b/lib/libc/glibc/sysdeps/unix/mips/mips64/sysdep.h index 206569357ab48981b6e578e67205b7f58124ca32..1304a1752df1f3f15b361c531e2da13ed60e6e30 100644 --- a/lib/libc/glibc/sysdeps/unix/mips/mips64/sysdep.h +++ b/lib/libc/glibc/sysdeps/unix/mips/mips64/sysdep.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1992-2025 Free Software Foundation, Inc. +/* Copyright (C) 1992-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/sysdeps/unix/mips/sysdep.h b/lib/libc/glibc/sysdeps/unix/mips/sysdep.h index bb2794bd71ac8dbb34aeecabe2fb6e8c797fa938..6ff6da13d198b4ebb891ac41cab58a74412e25b3 100644 --- a/lib/libc/glibc/sysdeps/unix/mips/sysdep.h +++ b/lib/libc/glibc/sysdeps/unix/mips/sysdep.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1992-2025 Free Software Foundation, Inc. +/* Copyright (C) 1992-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or @@ -15,12 +15,10 @@ License along with the GNU C Library. If not, see . */ +#include #include #include -#ifndef __mips_isa_rev -# define __mips_isa_rev 0 -#endif #ifdef __ASSEMBLER__ diff --git a/lib/libc/glibc/sysdeps/unix/powerpc/sysdep.h b/lib/libc/glibc/sysdeps/unix/powerpc/sysdep.h index a58083487243e43b4601e03ba43299f0adda4bdf..0eacbdce1fdba57c81ac716e62ad4de73b94b023 100644 --- a/lib/libc/glibc/sysdeps/unix/powerpc/sysdep.h +++ b/lib/libc/glibc/sysdeps/unix/powerpc/sysdep.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1991-2025 Free Software Foundation, Inc. +/* Copyright (C) 1991-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/sysdeps/unix/sh/sysdep.h b/lib/libc/glibc/sysdeps/unix/sh/sysdep.h index 28cb75ba1a673561ce9c0cd191db472af2cb9683..9c05baa7ac6212c3b52342d10c45e339b9e21298 100644 --- a/lib/libc/glibc/sysdeps/unix/sh/sysdep.h +++ b/lib/libc/glibc/sysdeps/unix/sh/sysdep.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1999-2025 Free Software Foundation, Inc. +/* Copyright (C) 1999-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/sysdeps/unix/sysdep.h b/lib/libc/glibc/sysdeps/unix/sysdep.h index 2cc98725c386f2f00e46cdca66a80d4a4beebb57..a6ce5348ec30dd4a9d73784fb2827bc496526059 100644 --- a/lib/libc/glibc/sysdeps/unix/sysdep.h +++ b/lib/libc/glibc/sysdeps/unix/sysdep.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1991-2025 Free Software Foundation, Inc. +/* Copyright (C) 1991-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or @@ -138,7 +138,7 @@ #include /* Adjust both the __syscall_cancel and the SYSCALL_CANCEL macro to support - 7 arguments instead of default 6 (curently only mip32). It avoid add + 7 arguments instead of default 6 (currently only mip32). It avoid add the requirement to each architecture to support 7 argument macros {INTERNAL,INLINE}_SYSCALL. */ #ifdef HAVE_CANCELABLE_SYSCALL_WITH_7_ARGS diff --git a/lib/libc/glibc/sysdeps/unix/sysv/linux/aarch64/kernel-features.h b/lib/libc/glibc/sysdeps/unix/sysv/linux/aarch64/kernel-features.h index 8cdd6ed112f06ad93d8b2372b1b24cc47b56330e..fee03ac76bf17b3970b4655c8ddd23bf779f5c37 100644 --- a/lib/libc/glibc/sysdeps/unix/sysv/linux/aarch64/kernel-features.h +++ b/lib/libc/glibc/sysdeps/unix/sysv/linux/aarch64/kernel-features.h @@ -1,6 +1,6 @@ /* Set flags signalling availability of kernel features based on given kernel version number. AArch64 version. - Copyright (C) 2018-2025 Free Software Foundation, Inc. + Copyright (C) 2018-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/sysdeps/unix/sysv/linux/aarch64/sys/elf.h b/lib/libc/glibc/sysdeps/unix/sysv/linux/aarch64/sys/elf.h index 7ccf9a2588fedea03ef2c9dc0278ff10b7330303..354928e0e1ffc2c88bce82630d9e983bbbc04f64 100644 --- a/lib/libc/glibc/sysdeps/unix/sysv/linux/aarch64/sys/elf.h +++ b/lib/libc/glibc/sysdeps/unix/sysv/linux/aarch64/sys/elf.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1996-2025 Free Software Foundation, Inc. +/* Copyright (C) 1996-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. diff --git a/lib/libc/glibc/sysdeps/unix/sysv/linux/aarch64/sysdep.h b/lib/libc/glibc/sysdeps/unix/sysv/linux/aarch64/sysdep.h index f0e8d64eefac3581108d93a96fc60941aec00dc1..16f5a917b37d67299a8515eeee410b3c925f9129 100644 --- a/lib/libc/glibc/sysdeps/unix/sysv/linux/aarch64/sysdep.h +++ b/lib/libc/glibc/sysdeps/unix/sysv/linux/aarch64/sysdep.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2005-2025 Free Software Foundation, Inc. +/* Copyright (C) 2005-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. @@ -150,6 +150,19 @@ mov x8, SYS_ify (syscall_name); \ svc 0 +/* Clear ZA state of SME (ASM version). */ +/* The __libc_arm_za_disable function has special calling convention + that allows to call it without stack manipulation and preserving + most of the registers. */ + .macro CALL_LIBC_ARM_ZA_DISABLE + cfi_remember_state + mov x13, x30 + cfi_register(x30, x13) + bl __libc_arm_za_disable + mov x30, x13 + cfi_restore_state + .endm + #else /* not __ASSEMBLER__ */ # define VDSO_NAME "LINUX_2.6.39" @@ -230,6 +243,32 @@ #undef HAVE_INTERNAL_BRK_ADDR_SYMBOL #define HAVE_INTERNAL_BRK_ADDR_SYMBOL 1 +/* Clear ZA state of SME (C version). */ +/* The __libc_arm_za_disable function has special calling convention + that allows to call it without stack manipulation and preserving + most of the registers. */ +#define CALL_LIBC_ARM_ZA_DISABLE() \ +({ \ + unsigned long int __tmp; \ + asm volatile ( \ + " .cfi_remember_state\n" \ + " mov %0, x30\n" \ + " .cfi_register x30, %0\n" \ + " bl __libc_arm_za_disable\n" \ + " mov x30, %0\n" \ + " .cfi_restore_state\n" \ + : "=r" (__tmp) \ + : \ + : "x14", "x15", "x16", "x17", "x18", "memory" ); \ +}) + +/* Do clear ZA state of SME before making normal clone syscall. */ +#define INLINE_CLONE_SYSCALL(a0, a1, a2, a3, a4) \ +({ \ + CALL_LIBC_ARM_ZA_DISABLE (); \ + INLINE_SYSCALL_CALL (clone, a0, a1, a2, a3, a4); \ +}) + #endif /* __ASSEMBLER__ */ #endif /* linux/aarch64/sysdep.h */ diff --git a/lib/libc/glibc/sysdeps/unix/sysv/linux/arc/sysdep.h b/lib/libc/glibc/sysdeps/unix/sysv/linux/arc/sysdep.h index 06e31404ec0d157da77561dcb0f538088dc05ab9..90cea3f840f177da4e2ec9aa7ff09211c1ee12c5 100644 --- a/lib/libc/glibc/sysdeps/unix/sysv/linux/arc/sysdep.h +++ b/lib/libc/glibc/sysdeps/unix/sysv/linux/arc/sysdep.h @@ -1,5 +1,5 @@ /* Assembler macros for ARC. - Copyright (C) 2020-2025 Free Software Foundation, Inc. + Copyright (C) 2020-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/sysdeps/unix/sysv/linux/arm/kernel-features.h b/lib/libc/glibc/sysdeps/unix/sysv/linux/arm/kernel-features.h index 10caae8b9155b0422b076485e53a517e8106e446..d169bf58946aa365ceca5502d9861bda13954c4a 100644 --- a/lib/libc/glibc/sysdeps/unix/sysv/linux/arm/kernel-features.h +++ b/lib/libc/glibc/sysdeps/unix/sysv/linux/arm/kernel-features.h @@ -1,6 +1,6 @@ /* Set flags signalling availability of kernel features based on given kernel version number. - Copyright (C) 2006-2025 Free Software Foundation, Inc. + Copyright (C) 2006-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/sysdeps/unix/sysv/linux/arm/sys/elf.h b/lib/libc/glibc/sysdeps/unix/sysv/linux/arm/sys/elf.h index 9acb39e5ea07416d85576ead7b07e9574aad45c7..779f7c4e092dd8d2a5a6424302af37d6c95edc58 100644 --- a/lib/libc/glibc/sysdeps/unix/sysv/linux/arm/sys/elf.h +++ b/lib/libc/glibc/sysdeps/unix/sysv/linux/arm/sys/elf.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1996-2025 Free Software Foundation, Inc. +/* Copyright (C) 1996-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/sysdeps/unix/sysv/linux/arm/sysdep.h b/lib/libc/glibc/sysdeps/unix/sysv/linux/arm/sysdep.h index 6a477068ba1b7ab0bb5fd9ac6d24b6dd3cf2d240..e45b8acac27d262add3e98804ce9569c7b0b0577 100644 --- a/lib/libc/glibc/sysdeps/unix/sysv/linux/arm/sysdep.h +++ b/lib/libc/glibc/sysdeps/unix/sysv/linux/arm/sysdep.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1992-2025 Free Software Foundation, Inc. +/* Copyright (C) 1992-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/sysdeps/unix/sysv/linux/bits/stat.h b/lib/libc/glibc/sysdeps/unix/sysv/linux/bits/stat.h index e42a3058297a9b9f1660e2100270b129d680b35a..a68992929102b2a26134cbd3f57b61daeb1b6525 100644 --- a/lib/libc/glibc/sysdeps/unix/sysv/linux/bits/stat.h +++ b/lib/libc/glibc/sysdeps/unix/sysv/linux/bits/stat.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1992-2025 Free Software Foundation, Inc. +/* Copyright (C) 1992-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/sysdeps/unix/sysv/linux/bits/timex.h b/lib/libc/glibc/sysdeps/unix/sysv/linux/bits/timex.h index 56ba6c25bfbc85dd2e3009868f08febaf4765da3..f32cf2b1c0823cbfc8382406c7c81d14252a10ff 100644 --- a/lib/libc/glibc/sysdeps/unix/sysv/linux/bits/timex.h +++ b/lib/libc/glibc/sysdeps/unix/sysv/linux/bits/timex.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1995-2025 Free Software Foundation, Inc. +/* Copyright (C) 1995-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/sysdeps/unix/sysv/linux/csky/kernel_stat.h b/lib/libc/glibc/sysdeps/unix/sysv/linux/csky/kernel_stat.h index f39c7c1decfe368da7cf9bbc2e3b3080ff9b1d14..be7a2feffffe98c73789b4186d4267c10c0aca27 100644 --- a/lib/libc/glibc/sysdeps/unix/sysv/linux/csky/kernel_stat.h +++ b/lib/libc/glibc/sysdeps/unix/sysv/linux/csky/kernel_stat.h @@ -1,5 +1,5 @@ /* Internal definitions for stat functions. Linux/csky. - Copyright (C) 2011-2025 Free Software Foundation, Inc. + Copyright (C) 2011-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/sysdeps/unix/sysv/linux/csky/sysdep.h b/lib/libc/glibc/sysdeps/unix/sysv/linux/csky/sysdep.h index 6ef105d3301ba0ff98f58a2bdbaf5a6f61675b46..305474b873aeff51dd9384b28ebbffb9e11cf383 100644 --- a/lib/libc/glibc/sysdeps/unix/sysv/linux/csky/sysdep.h +++ b/lib/libc/glibc/sysdeps/unix/sysv/linux/csky/sysdep.h @@ -1,5 +1,5 @@ /* Assembly macros for C-SKY. - Copyright (C) 2018-2025 Free Software Foundation, Inc. + Copyright (C) 2018-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/sysdeps/unix/sysv/linux/dl-sysdep.h b/lib/libc/glibc/sysdeps/unix/sysv/linux/dl-sysdep.h index 6381243a489e7b5b45388b9488cb3575a0f8e0e9..32f17cc221eef1643a4f066fae8c299988021e8f 100644 --- a/lib/libc/glibc/sysdeps/unix/sysv/linux/dl-sysdep.h +++ b/lib/libc/glibc/sysdeps/unix/sysv/linux/dl-sysdep.h @@ -1,5 +1,5 @@ /* System-specific settings for dynamic linker code. Linux version. - Copyright (C) 2005-2025 Free Software Foundation, Inc. + Copyright (C) 2005-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/sysdeps/unix/sysv/linux/fstat.c b/lib/libc/glibc/sysdeps/unix/sysv/linux/fstat.c index 0df9da322290ad2d3978e69d2aa05d9fe8f855ba..0c5b1357a5c64683e2756510767d2bcbf74b6e1c 100644 --- a/lib/libc/glibc/sysdeps/unix/sysv/linux/fstat.c +++ b/lib/libc/glibc/sysdeps/unix/sysv/linux/fstat.c @@ -1,5 +1,5 @@ /* Get file status. Linux version. - Copyright (C) 2020-2025 Free Software Foundation, Inc. + Copyright (C) 2020-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/sysdeps/unix/sysv/linux/fstat64.c b/lib/libc/glibc/sysdeps/unix/sysv/linux/fstat64.c index 8da9be35e42fb0af8d6b0dadae87e19e9564f41a..b48d5ca66f0f35deb316a36b6205b52c2eae3dc7 100644 --- a/lib/libc/glibc/sysdeps/unix/sysv/linux/fstat64.c +++ b/lib/libc/glibc/sysdeps/unix/sysv/linux/fstat64.c @@ -1,5 +1,5 @@ /* Get file status. Linux version. - Copyright (C) 2020-2025 Free Software Foundation, Inc. + Copyright (C) 2020-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/sysdeps/unix/sysv/linux/fstatat.c b/lib/libc/glibc/sysdeps/unix/sysv/linux/fstatat.c index f7c13dba0c3edfb92730a35e54d92c6f5a40126f..893958a3f4de27a6bb0cef30a9bd94e6913237a5 100644 --- a/lib/libc/glibc/sysdeps/unix/sysv/linux/fstatat.c +++ b/lib/libc/glibc/sysdeps/unix/sysv/linux/fstatat.c @@ -1,5 +1,5 @@ /* Get file status. Linux version. - Copyright (C) 2020-2025 Free Software Foundation, Inc. + Copyright (C) 2020-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/sysdeps/unix/sysv/linux/fstatat64.c b/lib/libc/glibc/sysdeps/unix/sysv/linux/fstatat64.c index 0c27744b791313dbdf29928c8bfdcaa9b638e487..0ba58c73498677a144388024258bc60644685e7f 100644 --- a/lib/libc/glibc/sysdeps/unix/sysv/linux/fstatat64.c +++ b/lib/libc/glibc/sysdeps/unix/sysv/linux/fstatat64.c @@ -1,5 +1,5 @@ /* Get file status. Linux version. - Copyright (C) 2020-2025 Free Software Foundation, Inc. + Copyright (C) 2020-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/sysdeps/unix/sysv/linux/i386/dl-sysdep.h b/lib/libc/glibc/sysdeps/unix/sysv/linux/i386/dl-sysdep.h index 60f6d4dc398d232ce8cfec4a2cf0679779e35c27..35f92ed16279a27efd08fbfccde8ff281a132a4c 100644 --- a/lib/libc/glibc/sysdeps/unix/sysv/linux/i386/dl-sysdep.h +++ b/lib/libc/glibc/sysdeps/unix/sysv/linux/i386/dl-sysdep.h @@ -1,5 +1,5 @@ /* System-specific settings for dynamic linker code. i386 version. - Copyright (C) 2002-2025 Free Software Foundation, Inc. + Copyright (C) 2002-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/sysdeps/unix/sysv/linux/i386/kernel-features.h b/lib/libc/glibc/sysdeps/unix/sysv/linux/i386/kernel-features.h index d21e3bae466acc52972fc6684f8fb2e3d3e148fa..e922a501f384c5e0b12e8b889b4717cda64f548a 100644 --- a/lib/libc/glibc/sysdeps/unix/sysv/linux/i386/kernel-features.h +++ b/lib/libc/glibc/sysdeps/unix/sysv/linux/i386/kernel-features.h @@ -1,6 +1,6 @@ /* Set flags signalling availability of kernel features based on given kernel version number. i386 version. - Copyright (C) 1999-2025 Free Software Foundation, Inc. + Copyright (C) 1999-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/sysdeps/unix/sysv/linux/i386/sysdep.h b/lib/libc/glibc/sysdeps/unix/sysv/linux/i386/sysdep.h index 87806a7a978a9c25062cd5d20b3ee46a2d9b97dc..c6ff0417363c54f68bac79f2d9f57d50137e55e8 100644 --- a/lib/libc/glibc/sysdeps/unix/sysv/linux/i386/sysdep.h +++ b/lib/libc/glibc/sysdeps/unix/sysv/linux/i386/sysdep.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1992-2025 Free Software Foundation, Inc. +/* Copyright (C) 1992-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/sysdeps/unix/sysv/linux/include/sys/timex.h b/lib/libc/glibc/sysdeps/unix/sysv/linux/include/sys/timex.h index 8af305d4108987c745a6f7737d17fa680f20f28a..0b32ed780915db460fd4b67847aa74d46f653c48 100644 --- a/lib/libc/glibc/sysdeps/unix/sysv/linux/include/sys/timex.h +++ b/lib/libc/glibc/sysdeps/unix/sysv/linux/include/sys/timex.h @@ -1,5 +1,5 @@ /* Internal declarations for sys/timex.h. - Copyright (C) 2014-2025 Free Software Foundation, Inc. + Copyright (C) 2014-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/sysdeps/unix/sysv/linux/kernel-features.h b/lib/libc/glibc/sysdeps/unix/sysv/linux/kernel-features.h index a49a9159cfa7d4496395581e64a2a7ad2d8b99a8..42318b0a6fe51b9f1c6a61ba9b298904c16493d2 100644 --- a/lib/libc/glibc/sysdeps/unix/sysv/linux/kernel-features.h +++ b/lib/libc/glibc/sysdeps/unix/sysv/linux/kernel-features.h @@ -1,6 +1,6 @@ /* Set flags signalling availability of kernel features based on given kernel version number. - Copyright (C) 1999-2025 Free Software Foundation, Inc. + Copyright (C) 1999-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or @@ -261,4 +261,9 @@ # define __ASSUME_FCHMODAT2 0 #endif +/* The mseal system call was introduced across all architectures in Linux 6.10 + (although only supported on 64-bit CPUs). */ +/* zig patch: don't assume kernel version */ +#define __ASSUME_MSEAL 0 + #endif /* kernel-features.h */ diff --git a/lib/libc/glibc/sysdeps/unix/sysv/linux/kernel_stat.h b/lib/libc/glibc/sysdeps/unix/sysv/linux/kernel_stat.h index a861c94a80e6267af9933c4368bec62e2e71e433..db1bb1b7d543b41c9e1b1fdf5b931421de2c0e88 100644 --- a/lib/libc/glibc/sysdeps/unix/sysv/linux/kernel_stat.h +++ b/lib/libc/glibc/sysdeps/unix/sysv/linux/kernel_stat.h @@ -1,5 +1,5 @@ /* Internal definitions for stat functions. - Copyright (C) 2021-2025 Free Software Foundation, Inc. + Copyright (C) 2021-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/sysdeps/unix/sysv/linux/loongarch/sysdep.h b/lib/libc/glibc/sysdeps/unix/sysv/linux/loongarch/sysdep.h index b9835d839158db40826ff44ac5a7c7bf23576c08..ac5b36f8c02218289d06f1f9be3dc40b42952cc1 100644 --- a/lib/libc/glibc/sysdeps/unix/sysv/linux/loongarch/sysdep.h +++ b/lib/libc/glibc/sysdeps/unix/sysv/linux/loongarch/sysdep.h @@ -1,5 +1,5 @@ /* Assembly macros for LoongArch. - Copyright (C) 2022-2025 Free Software Foundation, Inc. + Copyright (C) 2022-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/sysdeps/unix/sysv/linux/lstat.c b/lib/libc/glibc/sysdeps/unix/sysv/linux/lstat.c index 5b441718a479c8afae92db04b2b7b9e20801b775..6f4fd5d63ec385c302a6db43d341bc60dcaaa467 100644 --- a/lib/libc/glibc/sysdeps/unix/sysv/linux/lstat.c +++ b/lib/libc/glibc/sysdeps/unix/sysv/linux/lstat.c @@ -1,5 +1,5 @@ /* Get file status. Linux version. - Copyright (C) 2020-2025 Free Software Foundation, Inc. + Copyright (C) 2020-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/sysdeps/unix/sysv/linux/lstat64.c b/lib/libc/glibc/sysdeps/unix/sysv/linux/lstat64.c index 3d70ef8c0049e1b84065dbfb1ca918913482bfd4..490991134312b1b68ab8c471d938e22edd9a8998 100644 --- a/lib/libc/glibc/sysdeps/unix/sysv/linux/lstat64.c +++ b/lib/libc/glibc/sysdeps/unix/sysv/linux/lstat64.c @@ -1,5 +1,5 @@ /* Get file status. - Copyright (C) 1996-2025 Free Software Foundation, Inc. + Copyright (C) 1996-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/sysdeps/unix/sysv/linux/m68k/coldfire/sysdep.h b/lib/libc/glibc/sysdeps/unix/sysv/linux/m68k/coldfire/sysdep.h index 30b182fe1c54c28254c78cb667b8eec0dc7f064d..5b060bc45b664e417ceee4968ede65242ecb7f31 100644 --- a/lib/libc/glibc/sysdeps/unix/sysv/linux/m68k/coldfire/sysdep.h +++ b/lib/libc/glibc/sysdeps/unix/sysv/linux/m68k/coldfire/sysdep.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2010-2025 Free Software Foundation, Inc. +/* Copyright (C) 2010-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/sysdeps/unix/sysv/linux/m68k/kernel-features.h b/lib/libc/glibc/sysdeps/unix/sysv/linux/m68k/kernel-features.h index 3515b20433bfab1ab6326b402cbca2d2860f338b..d66fe16fa8d5eaa79daa5e8dbec27a9756e21a66 100644 --- a/lib/libc/glibc/sysdeps/unix/sysv/linux/m68k/kernel-features.h +++ b/lib/libc/glibc/sysdeps/unix/sysv/linux/m68k/kernel-features.h @@ -1,6 +1,6 @@ /* Set flags signalling availability of kernel features based on given kernel version number. - Copyright (C) 2008-2025 Free Software Foundation, Inc. + Copyright (C) 2008-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/sysdeps/unix/sysv/linux/m68k/m680x0/sysdep.h b/lib/libc/glibc/sysdeps/unix/sysv/linux/m68k/m680x0/sysdep.h index 02bde90490c3797d35c3976901c20e00deea332a..36b70aa26e71a2e46e766afbfe74c2fb0e70cbd8 100644 --- a/lib/libc/glibc/sysdeps/unix/sysv/linux/m68k/m680x0/sysdep.h +++ b/lib/libc/glibc/sysdeps/unix/sysv/linux/m68k/m680x0/sysdep.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2010-2025 Free Software Foundation, Inc. +/* Copyright (C) 2010-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/sysdeps/unix/sysv/linux/m68k/sysdep.h b/lib/libc/glibc/sysdeps/unix/sysv/linux/m68k/sysdep.h index 6f6b46e026d33fd46e32dcd3ef179e939d161e14..342b0bfc9091bcec3356c094963ae78e2a42fa1f 100644 --- a/lib/libc/glibc/sysdeps/unix/sysv/linux/m68k/sysdep.h +++ b/lib/libc/glibc/sysdeps/unix/sysv/linux/m68k/sysdep.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1996-2025 Free Software Foundation, Inc. +/* Copyright (C) 1996-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/sysdeps/unix/sysv/linux/mips/kernel-features.h b/lib/libc/glibc/sysdeps/unix/sysv/linux/mips/kernel-features.h index d86ac9235214632d86b1c576798847ae4303fac2..7790f0d14b58995392f69d30e22899277b444b9b 100644 --- a/lib/libc/glibc/sysdeps/unix/sysv/linux/mips/kernel-features.h +++ b/lib/libc/glibc/sysdeps/unix/sysv/linux/mips/kernel-features.h @@ -1,6 +1,6 @@ /* Set flags signalling availability of kernel features based on given kernel version number. - Copyright (C) 1999-2025 Free Software Foundation, Inc. + Copyright (C) 1999-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/sysdeps/unix/sysv/linux/mips/mips32/sysdep.h b/lib/libc/glibc/sysdeps/unix/sysv/linux/mips/mips32/sysdep.h index 83ccfb08afde878e165e3c1b14f7ce8278712405..4314fc990e251a469cb38d873f7266a3698fb9e3 100644 --- a/lib/libc/glibc/sysdeps/unix/sysv/linux/mips/mips32/sysdep.h +++ b/lib/libc/glibc/sysdeps/unix/sysv/linux/mips/mips32/sysdep.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2000-2025 Free Software Foundation, Inc. +/* Copyright (C) 2000-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/sysdeps/unix/sysv/linux/mips/mips64/kstat_cp.h b/lib/libc/glibc/sysdeps/unix/sysv/linux/mips/mips64/kstat_cp.h index 6e8375d54bc8dfd30421c70d9413a65e0974329e..b20a6297883c466dabf9fa64d7388ecfe142b352 100644 --- a/lib/libc/glibc/sysdeps/unix/sysv/linux/mips/mips64/kstat_cp.h +++ b/lib/libc/glibc/sysdeps/unix/sysv/linux/mips/mips64/kstat_cp.h @@ -1,5 +1,5 @@ /* Struct stat/stat64 to stat/stat64 conversion for Linux. - Copyright (C) 2020-2025 Free Software Foundation, Inc. + Copyright (C) 2020-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/sysdeps/unix/sysv/linux/mips/mips64/sysdep.h b/lib/libc/glibc/sysdeps/unix/sysv/linux/mips/mips64/sysdep.h index 78044d669db84f55542b94008994bb992ee3cb9a..3ee86888d6987e5ff9e0f2149ad5ac2d0e3b8161 100644 --- a/lib/libc/glibc/sysdeps/unix/sysv/linux/mips/mips64/sysdep.h +++ b/lib/libc/glibc/sysdeps/unix/sysv/linux/mips/mips64/sysdep.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2000-2025 Free Software Foundation, Inc. +/* Copyright (C) 2000-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/sysdeps/unix/sysv/linux/mips/sysdep.h b/lib/libc/glibc/sysdeps/unix/sysv/linux/mips/sysdep.h index 76e121a11f60e127d592df72ba8e62f6773b70c9..6434bd6b8413c29162c2702565f0688af1b15437 100644 --- a/lib/libc/glibc/sysdeps/unix/sysv/linux/mips/sysdep.h +++ b/lib/libc/glibc/sysdeps/unix/sysv/linux/mips/sysdep.h @@ -1,5 +1,5 @@ /* Syscall definitions, Linux MIPS generic version. - Copyright (C) 2019-2025 Free Software Foundation, Inc. + Copyright (C) 2019-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/sysdeps/unix/sysv/linux/mknodat.c b/lib/libc/glibc/sysdeps/unix/sysv/linux/mknodat.c index 291b5bc6589c0df04ce71dba024b0ecf34b44407..d6fc150dfd93c72ba79620f88049a0f3d852b4b6 100644 --- a/lib/libc/glibc/sysdeps/unix/sysv/linux/mknodat.c +++ b/lib/libc/glibc/sysdeps/unix/sysv/linux/mknodat.c @@ -1,5 +1,5 @@ /* Create a special or ordinary file. Linux version. - Copyright (C) 2020-2025 Free Software Foundation, Inc. + Copyright (C) 2020-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/sysdeps/unix/sysv/linux/powerpc/kernel-features.h b/lib/libc/glibc/sysdeps/unix/sysv/linux/powerpc/kernel-features.h index 6e5adb97b3d51b99ac8c1485076c16af2f497c47..40538d89099a54c11f92b6c33a4ce802d170857b 100644 --- a/lib/libc/glibc/sysdeps/unix/sysv/linux/powerpc/kernel-features.h +++ b/lib/libc/glibc/sysdeps/unix/sysv/linux/powerpc/kernel-features.h @@ -1,6 +1,6 @@ /* Set flags signalling availability of kernel features based on given kernel version number. PowerPC version. - Copyright (C) 1999-2025 Free Software Foundation, Inc. + Copyright (C) 1999-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/sysdeps/unix/sysv/linux/powerpc/powerpc32/kernel_stat.h b/lib/libc/glibc/sysdeps/unix/sysv/linux/powerpc/powerpc32/kernel_stat.h index 40b5163ae9a80df11283e7e0024cc1f733ea3c87..6596c5bdb2b8c7b1e334f79d04a27b58cdfce7db 100644 --- a/lib/libc/glibc/sysdeps/unix/sysv/linux/powerpc/powerpc32/kernel_stat.h +++ b/lib/libc/glibc/sysdeps/unix/sysv/linux/powerpc/powerpc32/kernel_stat.h @@ -1,5 +1,5 @@ /* Definition of `struct stat' used in the kernel. - Copyright (C) 1997-2025 Free Software Foundation, Inc. + Copyright (C) 1997-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/sysdeps/unix/sysv/linux/powerpc/powerpc64/sysdep.h b/lib/libc/glibc/sysdeps/unix/sysv/linux/powerpc/powerpc64/sysdep.h index 4018c3079e82274f4ba09cb891dad1a31fed3e47..469cca0f18246d24f42982f4b4cfa1099a2dacab 100644 --- a/lib/libc/glibc/sysdeps/unix/sysv/linux/powerpc/powerpc64/sysdep.h +++ b/lib/libc/glibc/sysdeps/unix/sysv/linux/powerpc/powerpc64/sysdep.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1992-2025 Free Software Foundation, Inc. +/* Copyright (C) 1992-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/sysdeps/unix/sysv/linux/powerpc/sysdep.h b/lib/libc/glibc/sysdeps/unix/sysv/linux/powerpc/sysdep.h index 929784df553afde644017b486c0580a2f6763291..855a10d7e802dc0eac8fa750df2af4b3de46ba72 100644 --- a/lib/libc/glibc/sysdeps/unix/sysv/linux/powerpc/sysdep.h +++ b/lib/libc/glibc/sysdeps/unix/sysv/linux/powerpc/sysdep.h @@ -1,5 +1,5 @@ /* Syscall definitions, Linux PowerPC generic version. - Copyright (C) 2019-2025 Free Software Foundation, Inc. + Copyright (C) 2019-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/sysdeps/unix/sysv/linux/riscv/kernel-features.h b/lib/libc/glibc/sysdeps/unix/sysv/linux/riscv/kernel-features.h index dce50835d1ba796859a03027be648788915160bc..32087c0602c11ed7e0328f97570a8c18fb3a7400 100644 --- a/lib/libc/glibc/sysdeps/unix/sysv/linux/riscv/kernel-features.h +++ b/lib/libc/glibc/sysdeps/unix/sysv/linux/riscv/kernel-features.h @@ -1,6 +1,6 @@ /* Set flags signalling availability of kernel features based on given kernel version number. RISC-V version. - Copyright (C) 2018-2025 Free Software Foundation, Inc. + Copyright (C) 2018-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/sysdeps/unix/sysv/linux/riscv/sysdep.h b/lib/libc/glibc/sysdeps/unix/sysv/linux/riscv/sysdep.h index 05e0e0523d3f744cd7ec1341afc5126746bc80e8..7f0eb070455a661cd367bf2ed7085918f299288b 100644 --- a/lib/libc/glibc/sysdeps/unix/sysv/linux/riscv/sysdep.h +++ b/lib/libc/glibc/sysdeps/unix/sysv/linux/riscv/sysdep.h @@ -355,7 +355,14 @@ _sys_result; \ }) +#ifdef __riscv_v +# define __SYSCALL_CLOBBERS "v0", "v1", "v2", "v3", "v4", "v5", "v6", "v7", "v8", "v9", \ + "v10", "v11", "v12", "v13", "v14", "v15", "v16", "v17", "v18", "v19", \ + "v20", "v21", "v22", "v23", "v24", "v25", "v26", "v27", "v28", "v29", \ + "v30", "v31", "vl", "vtype", "vxrm", "vxsat", "memory" +#else # define __SYSCALL_CLOBBERS "memory" +#endif extern long int __syscall_error (long int neg_errno); diff --git a/lib/libc/glibc/sysdeps/unix/sysv/linux/s390/bits/typesizes.h b/lib/libc/glibc/sysdeps/unix/sysv/linux/s390/bits/typesizes.h index 2bbf72f71d36a179e4ec1940cacd16d660499de7..826a1e425c1a2b3b1ee712643d53284412275bfa 100644 --- a/lib/libc/glibc/sysdeps/unix/sysv/linux/s390/bits/typesizes.h +++ b/lib/libc/glibc/sysdeps/unix/sysv/linux/s390/bits/typesizes.h @@ -1,5 +1,5 @@ /* bits/typesizes.h -- underlying types for *_t. Linux/s390 version. - Copyright (C) 2003-2025 Free Software Foundation, Inc. + Copyright (C) 2003-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/sysdeps/unix/sysv/linux/s390/kernel-features.h b/lib/libc/glibc/sysdeps/unix/sysv/linux/s390/kernel-features.h index 4d9f2232e0f3cf2d0ad168b59e7e8ae2733eba65..a955c18738ad557e51a261b279eb2e3111870924 100644 --- a/lib/libc/glibc/sysdeps/unix/sysv/linux/s390/kernel-features.h +++ b/lib/libc/glibc/sysdeps/unix/sysv/linux/s390/kernel-features.h @@ -1,6 +1,6 @@ /* Set flags signalling availability of kernel features based on given kernel version number. S/390 version. - Copyright (C) 1999-2025 Free Software Foundation, Inc. + Copyright (C) 1999-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/sysdeps/unix/sysv/linux/s390/s390-64/sysdep.h b/lib/libc/glibc/sysdeps/unix/sysv/linux/s390/s390-64/sysdep.h index a282e1222e99e1434e9f2cfffb3aa597e3f811ab..9c9e2a271f57c5ce5d2efef9fc23c36ea55da6ac 100644 --- a/lib/libc/glibc/sysdeps/unix/sysv/linux/s390/s390-64/sysdep.h +++ b/lib/libc/glibc/sysdeps/unix/sysv/linux/s390/s390-64/sysdep.h @@ -1,5 +1,5 @@ /* Assembler macros for 64 bit S/390. - Copyright (C) 2001-2025 Free Software Foundation, Inc. + Copyright (C) 2001-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/sysdeps/unix/sysv/linux/s390/sys/elf.h b/lib/libc/glibc/sysdeps/unix/sysv/linux/s390/sys/elf.h index 1e3581b65f64db8be039e5678825df2f03f5131c..4d7b62442d269cfca2eebe56ca03568f133d15e7 100644 --- a/lib/libc/glibc/sysdeps/unix/sysv/linux/s390/sys/elf.h +++ b/lib/libc/glibc/sysdeps/unix/sysv/linux/s390/sys/elf.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2000-2025 Free Software Foundation, Inc. +/* Copyright (C) 2000-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/sysdeps/unix/sysv/linux/s390/sysdep.h b/lib/libc/glibc/sysdeps/unix/sysv/linux/s390/sysdep.h index c802980013ca198b17cbe8fd5d505d4a36818734..ee2d92edff1f0ee2fb8e0a25217e35dccbd0ff30 100644 --- a/lib/libc/glibc/sysdeps/unix/sysv/linux/s390/sysdep.h +++ b/lib/libc/glibc/sysdeps/unix/sysv/linux/s390/sysdep.h @@ -1,5 +1,5 @@ /* Syscall definitions, Linux s390 version. - Copyright (C) 2019-2025 Free Software Foundation, Inc. + Copyright (C) 2019-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/sysdeps/unix/sysv/linux/single-thread.h b/lib/libc/glibc/sysdeps/unix/sysv/linux/single-thread.h index eecaa2dc66a11e3b449eea6f5d45ac4e2e471bd1..449db8503c26fd7dc5733ded89204e5b40869bed 100644 --- a/lib/libc/glibc/sysdeps/unix/sysv/linux/single-thread.h +++ b/lib/libc/glibc/sysdeps/unix/sysv/linux/single-thread.h @@ -1,5 +1,5 @@ /* Single thread optimization, Linux version. - Copyright (C) 2019-2025 Free Software Foundation, Inc. + Copyright (C) 2019-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/sysdeps/unix/sysv/linux/sparc/bits/typesizes.h b/lib/libc/glibc/sysdeps/unix/sysv/linux/sparc/bits/typesizes.h index 97e9641ab7dcebe5449030822d6618ff3ddc47a4..78c87964a2448563051cea5269bc4de2795add77 100644 --- a/lib/libc/glibc/sysdeps/unix/sysv/linux/sparc/bits/typesizes.h +++ b/lib/libc/glibc/sysdeps/unix/sysv/linux/sparc/bits/typesizes.h @@ -1,5 +1,5 @@ /* bits/typesizes.h -- underlying types for *_t. Linux/SPARC version. - Copyright (C) 2002-2025 Free Software Foundation, Inc. + Copyright (C) 2002-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/sysdeps/unix/sysv/linux/sparc/kernel-features.h b/lib/libc/glibc/sysdeps/unix/sysv/linux/sparc/kernel-features.h index 24423db127d1ba2b6096bea190296f50b851fdaf..eb293411135b74dba9ff884bafaf8e2b90ea372b 100644 --- a/lib/libc/glibc/sysdeps/unix/sysv/linux/sparc/kernel-features.h +++ b/lib/libc/glibc/sysdeps/unix/sysv/linux/sparc/kernel-features.h @@ -1,6 +1,6 @@ /* Set flags signalling availability of kernel features based on given kernel version number. SPARC version. - Copyright (C) 1999-2025 Free Software Foundation, Inc. + Copyright (C) 1999-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/sysdeps/unix/sysv/linux/sparc/sparc32/sysdep.h b/lib/libc/glibc/sysdeps/unix/sysv/linux/sparc/sparc32/sysdep.h index 9ad5192dec21b0aff1e64115224499ec6b7dae84..d687670f47b95eb2b5d143f2c2b0a5b7a65c1e64 100644 --- a/lib/libc/glibc/sysdeps/unix/sysv/linux/sparc/sparc32/sysdep.h +++ b/lib/libc/glibc/sysdeps/unix/sysv/linux/sparc/sparc32/sysdep.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1997-2025 Free Software Foundation, Inc. +/* Copyright (C) 1997-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/sysdeps/unix/sysv/linux/sparc/sparc64/kstat_cp.h b/lib/libc/glibc/sysdeps/unix/sysv/linux/sparc/sparc64/kstat_cp.h index 8f4f9f85a7b2c6ed7c54f10d1fb0caddaec611c8..324c614fb519b240aa0a53ce7ae07732a0058ecf 100644 --- a/lib/libc/glibc/sysdeps/unix/sysv/linux/sparc/sparc64/kstat_cp.h +++ b/lib/libc/glibc/sysdeps/unix/sysv/linux/sparc/sparc64/kstat_cp.h @@ -1,5 +1,5 @@ /* Struct kernel_stat64 to stat64. Linux/SPARC version. - Copyright (C) 2020-2025 Free Software Foundation, Inc. + Copyright (C) 2020-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/sysdeps/unix/sysv/linux/sparc/sparc64/sysdep.h b/lib/libc/glibc/sysdeps/unix/sysv/linux/sparc/sparc64/sysdep.h index 1781dec0d354cd62b37415a3f723f1d68d0a4391..9da3499e8b5ac9163a64e65542ea05ba8ad70038 100644 --- a/lib/libc/glibc/sysdeps/unix/sysv/linux/sparc/sparc64/sysdep.h +++ b/lib/libc/glibc/sysdeps/unix/sysv/linux/sparc/sparc64/sysdep.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1997-2025 Free Software Foundation, Inc. +/* Copyright (C) 1997-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/sysdeps/unix/sysv/linux/sparc/sysdep.h b/lib/libc/glibc/sysdeps/unix/sysv/linux/sparc/sysdep.h index 0051f8b4ca134b34bccf5d1dfb2f2313f1e21863..180ac83db79816fae6cc1b5d52e348b31e510366 100644 --- a/lib/libc/glibc/sysdeps/unix/sysv/linux/sparc/sysdep.h +++ b/lib/libc/glibc/sysdeps/unix/sysv/linux/sparc/sysdep.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2000-2025 Free Software Foundation, Inc. +/* Copyright (C) 2000-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/sysdeps/unix/sysv/linux/stat.c b/lib/libc/glibc/sysdeps/unix/sysv/linux/stat.c index ce5175b34c2d504dc5e6daa069c18223c02b4eed..8fb0210397720b66fb2600cb10d322a7545676e8 100644 --- a/lib/libc/glibc/sysdeps/unix/sysv/linux/stat.c +++ b/lib/libc/glibc/sysdeps/unix/sysv/linux/stat.c @@ -1,5 +1,5 @@ /* Get file status. Linux version. - Copyright (C) 2020-2025 Free Software Foundation, Inc. + Copyright (C) 2020-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/sysdeps/unix/sysv/linux/stat64.c b/lib/libc/glibc/sysdeps/unix/sysv/linux/stat64.c index 61b6f5e66918ef1968b31ad6ad4ef38c81fd5e56..a21c758bb6015d2f7c1c6a33ed97213c816080bf 100644 --- a/lib/libc/glibc/sysdeps/unix/sysv/linux/stat64.c +++ b/lib/libc/glibc/sysdeps/unix/sysv/linux/stat64.c @@ -1,5 +1,5 @@ /* Get file status. Linux version. - Copyright (C) 2020-2025 Free Software Foundation, Inc. + Copyright (C) 2020-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/sysdeps/unix/sysv/linux/stat_t64_cp.c b/lib/libc/glibc/sysdeps/unix/sysv/linux/stat_t64_cp.c index a409f9580cd12c3b0d1305d5a72a71c0dd966237..54239b8efb4d3e09eea5603b6c988eacada831a8 100644 --- a/lib/libc/glibc/sysdeps/unix/sysv/linux/stat_t64_cp.c +++ b/lib/libc/glibc/sysdeps/unix/sysv/linux/stat_t64_cp.c @@ -1,5 +1,5 @@ /* Struct stat/stat64 to stat/stat64 conversion for Linux. - Copyright (C) 2020-2025 Free Software Foundation, Inc. + Copyright (C) 2020-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/sysdeps/unix/sysv/linux/stat_t64_cp.h b/lib/libc/glibc/sysdeps/unix/sysv/linux/stat_t64_cp.h index 7f34f783e2b67cbf9e1795a1288786435745f650..d68b4075801874cc1055d63ab4ec6f452169077a 100644 --- a/lib/libc/glibc/sysdeps/unix/sysv/linux/stat_t64_cp.h +++ b/lib/libc/glibc/sysdeps/unix/sysv/linux/stat_t64_cp.h @@ -1,5 +1,5 @@ /* Copy to/from struct stat with and without 64-bit time_t support. - Copyright (C) 2020-2025 Free Software Foundation, Inc. + Copyright (C) 2020-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/sysdeps/unix/sysv/linux/struct_stat_time64.h b/lib/libc/glibc/sysdeps/unix/sysv/linux/struct_stat_time64.h index c2efdff3ac3fa266e5d9d2946c774c98c7a434e7..e3b878e1bed056f461ba298336f28e9582a00ed9 100644 --- a/lib/libc/glibc/sysdeps/unix/sysv/linux/struct_stat_time64.h +++ b/lib/libc/glibc/sysdeps/unix/sysv/linux/struct_stat_time64.h @@ -1,5 +1,5 @@ /* Struct stat with 64-bit time support. - Copyright (C) 2020-2025 Free Software Foundation, Inc. + Copyright (C) 2020-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/sysdeps/unix/sysv/linux/sys/syscall.h b/lib/libc/glibc/sysdeps/unix/sysv/linux/sys/syscall.h index 781f1780d0d3b6190be07cd6e46886db194e32f9..216f75704e0119dae819a3080ab8dd59fe9a4bd0 100644 --- a/lib/libc/glibc/sysdeps/unix/sysv/linux/sys/syscall.h +++ b/lib/libc/glibc/sysdeps/unix/sysv/linux/sys/syscall.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1995-2025 Free Software Foundation, Inc. +/* Copyright (C) 1995-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/sysdeps/unix/sysv/linux/sys/timex.h b/lib/libc/glibc/sysdeps/unix/sysv/linux/sys/timex.h index db67ca26a3ede5bebc0680cb6a5e28ae2c38d946..63e6610c9fbc91bc2f619465ec915ce23bbd5512 100644 --- a/lib/libc/glibc/sysdeps/unix/sysv/linux/sys/timex.h +++ b/lib/libc/glibc/sysdeps/unix/sysv/linux/sys/timex.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1995-2025 Free Software Foundation, Inc. +/* Copyright (C) 1995-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/sysdeps/unix/sysv/linux/sysdep.h b/lib/libc/glibc/sysdeps/unix/sysv/linux/sysdep.h index 1385082f7bd75a97a7c9a70f8fc707a28e56dddd..8b221c3ade4b0f79055e7a05cdc039e0ebde16e3 100644 --- a/lib/libc/glibc/sysdeps/unix/sysv/linux/sysdep.h +++ b/lib/libc/glibc/sysdeps/unix/sysv/linux/sysdep.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2015-2025 Free Software Foundation, Inc. +/* Copyright (C) 2015-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/sysdeps/unix/sysv/linux/x86/bits/typesizes.h b/lib/libc/glibc/sysdeps/unix/sysv/linux/x86/bits/typesizes.h index 8ba6e7fa9562f46d74e7e6a4b1d7303dd7a0275e..18336f844f63c896e6615ba6034388a04196125e 100644 --- a/lib/libc/glibc/sysdeps/unix/sysv/linux/x86/bits/typesizes.h +++ b/lib/libc/glibc/sysdeps/unix/sysv/linux/x86/bits/typesizes.h @@ -1,5 +1,5 @@ /* bits/typesizes.h -- underlying types for *_t. Linux/x86-64 version. - Copyright (C) 2012-2025 Free Software Foundation, Inc. + Copyright (C) 2012-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/sysdeps/unix/sysv/linux/x86/sys/elf.h b/lib/libc/glibc/sysdeps/unix/sysv/linux/x86/sys/elf.h index e8108efe2c08672fa7bb1c2f54c3bd4bee105db5..1ea28eb13fd2ab52fd4d3913b00576cca7eba6e5 100644 --- a/lib/libc/glibc/sysdeps/unix/sysv/linux/x86/sys/elf.h +++ b/lib/libc/glibc/sysdeps/unix/sysv/linux/x86/sys/elf.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1998-2025 Free Software Foundation, Inc. +/* Copyright (C) 1998-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/sysdeps/unix/sysv/linux/x86_64/kernel-features.h b/lib/libc/glibc/sysdeps/unix/sysv/linux/x86_64/kernel-features.h index 77781718751198e665810d74d42f542eef508607..8798309651132ecca594b7d0cce031c9daa854af 100644 --- a/lib/libc/glibc/sysdeps/unix/sysv/linux/x86_64/kernel-features.h +++ b/lib/libc/glibc/sysdeps/unix/sysv/linux/x86_64/kernel-features.h @@ -1,6 +1,6 @@ /* Set flags signalling availability of kernel features based on given kernel version number. x86-64 version. - Copyright (C) 1999-2025 Free Software Foundation, Inc. + Copyright (C) 1999-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/sysdeps/unix/sysv/linux/x86_64/sysdep.h b/lib/libc/glibc/sysdeps/unix/sysv/linux/x86_64/sysdep.h index 1d175dfb1338e77dd02b77ed168bd7b7c395c807..104acff3a36cd08ba9bb5953703fc7953cf79613 100644 --- a/lib/libc/glibc/sysdeps/unix/sysv/linux/x86_64/sysdep.h +++ b/lib/libc/glibc/sysdeps/unix/sysv/linux/x86_64/sysdep.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2001-2025 Free Software Foundation, Inc. +/* Copyright (C) 2001-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/sysdeps/unix/sysv/linux/x86_64/x32/sysdep.h b/lib/libc/glibc/sysdeps/unix/sysv/linux/x86_64/x32/sysdep.h index 707261ade329f5e669e1c28d3bee7f15305d006a..63e8b99be2bc459a7d9c4871250cb94195e1fe36 100644 --- a/lib/libc/glibc/sysdeps/unix/sysv/linux/x86_64/x32/sysdep.h +++ b/lib/libc/glibc/sysdeps/unix/sysv/linux/x86_64/x32/sysdep.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2012-2025 Free Software Foundation, Inc. +/* Copyright (C) 2012-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/sysdeps/unix/x86_64/sysdep.h b/lib/libc/glibc/sysdeps/unix/x86_64/sysdep.h index ac789a9d4591fbd22792aac9f83c89dd4510e02e..830dd099136b135d67d3f2d412a9b03ed7251b79 100644 --- a/lib/libc/glibc/sysdeps/unix/x86_64/sysdep.h +++ b/lib/libc/glibc/sysdeps/unix/x86_64/sysdep.h @@ -1,4 +1,4 @@ -/* Copyright (C) 1991-2025 Free Software Foundation, Inc. +/* Copyright (C) 1991-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/sysdeps/wordsize-32/divdi3-symbol-hacks.h b/lib/libc/glibc/sysdeps/wordsize-32/divdi3-symbol-hacks.h index e51d84780aaefbc33842f5b8c8e0c0067d6f3f81..30a007b3010629d1b1ef386a67df5bed78f47508 100644 --- a/lib/libc/glibc/sysdeps/wordsize-32/divdi3-symbol-hacks.h +++ b/lib/libc/glibc/sysdeps/wordsize-32/divdi3-symbol-hacks.h @@ -1,5 +1,5 @@ /* Hacks needed for divdi3 symbol manipulation. - Copyright (C) 2004-2025 Free Software Foundation, Inc. + Copyright (C) 2004-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/sysdeps/x86/nptl/bits/pthreadtypes-arch.h b/lib/libc/glibc/sysdeps/x86/nptl/bits/pthreadtypes-arch.h index bcdd76a2ab1ebc0a72e1881615cb6f9f98da8e0e..403ad38344ed8e05f464cf7ffb2644d867742bb6 100644 --- a/lib/libc/glibc/sysdeps/x86/nptl/bits/pthreadtypes-arch.h +++ b/lib/libc/glibc/sysdeps/x86/nptl/bits/pthreadtypes-arch.h @@ -1,4 +1,4 @@ -/* Copyright (C) 2002-2025 Free Software Foundation, Inc. +/* Copyright (C) 2002-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/sysdeps/x86/sysdep.h b/lib/libc/glibc/sysdeps/x86/sysdep.h index b8e963b654c4cb6964e2cf8a10f1fe143ea2c2f2..41b040d51bde0073a6c438384ed27acb05394253 100644 --- a/lib/libc/glibc/sysdeps/x86/sysdep.h +++ b/lib/libc/glibc/sysdeps/x86/sysdep.h @@ -1,5 +1,5 @@ /* Assembler macros for x86. - Copyright (C) 2017-2025 Free Software Foundation, Inc. + Copyright (C) 2017-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/sysdeps/x86_64/start.S b/lib/libc/glibc/sysdeps/x86_64/start.S index 6cb661bf04eb0657cf63dfd5b2085e8281c17421..40d6ed85b54e6318c5880496d0ff2aa4d5ed941a 100644 --- a/lib/libc/glibc/sysdeps/x86_64/start.S +++ b/lib/libc/glibc/sysdeps/x86_64/start.S @@ -1,5 +1,5 @@ /* Startup code compliant to the ELF x86-64 ABI. - Copyright (C) 2001-2025 Free Software Foundation, Inc. + Copyright (C) 2001-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/sysdeps/x86_64/sysdep.h b/lib/libc/glibc/sysdeps/x86_64/sysdep.h index 0356f09379ac8f5ba665f0b4603c752503bf68f8..017540c78bc6628a4303d63aae977af2b3301b23 100644 --- a/lib/libc/glibc/sysdeps/x86_64/sysdep.h +++ b/lib/libc/glibc/sysdeps/x86_64/sysdep.h @@ -1,5 +1,5 @@ /* Assembler macros for x86-64. - Copyright (C) 2001-2025 Free Software Foundation, Inc. + Copyright (C) 2001-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or diff --git a/lib/libc/glibc/sysdeps/x86_64/x32/sysdep.h b/lib/libc/glibc/sysdeps/x86_64/x32/sysdep.h index 91beeabf05ee177cdc163ec68b918a1051d15008..fcd5c1803949667f62053480dfa4f2464831df9f 100644 --- a/lib/libc/glibc/sysdeps/x86_64/x32/sysdep.h +++ b/lib/libc/glibc/sysdeps/x86_64/x32/sysdep.h @@ -1,5 +1,5 @@ /* Assembler macros for x32. - Copyright (C) 2012-2025 Free Software Foundation, Inc. + Copyright (C) 2012-2026 Free Software Foundation, Inc. This file is part of the GNU C Library. The GNU C Library is free software; you can redistribute it and/or -- 2.54.0 From b4c86c850d5fa6c0944549cd0209b333f9f6b004 Mon Sep 17 00:00:00 2001 From: jsentity Date: Mon, 26 Jan 2026 11:41:22 +0100 Subject: [PATCH 047/499] Fix std.uefi.protocol.DevicePath.next() and add utility function isEnd() (#30887) Fix for https://codeberg.org/ziglang/zig/issues/30884 and https://codeberg.org/ziglang/zig/issues/30885 Co-authored-by: jsentity Co-committed-by: jsentity --- lib/std/os/uefi/protocol/device_path.zig | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/lib/std/os/uefi/protocol/device_path.zig b/lib/std/os/uefi/protocol/device_path.zig index 7fa8090df78cd208dd85ffb015bba0316570c74a..ebd2463be459451a6b9c9b831315cfb1e790a3d7 100644 --- a/lib/std/os/uefi/protocol/device_path.zig +++ b/lib/std/os/uefi/protocol/device_path.zig @@ -26,12 +26,10 @@ pub const DevicePath = extern struct { /// Returns the next DevicePath node in the sequence, if any. pub fn next(self: *const DevicePath) ?*const DevicePath { + const subtype: uefi.DevicePath.End.Subtype = @enumFromInt(self.subtype); + if (self.type == .end and subtype == .end_entire) return null; const bytes: [*]const u8 = @ptrCast(self); - const next_node: *const DevicePath = @ptrCast(bytes + self.length); - if (next_node.type == .end and @as(uefi.DevicePath.End.Subtype, @enumFromInt(next_node.subtype)) == .end_entire) - return null; - - return next_node; + return @ptrCast(bytes + self.length); } /// Calculates the total length of the device path structure in bytes, including the end of device path node. -- 2.54.0 From 1b235540c1d1acdd59aee3c2a8756bc7f0fa55a3 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 26 Jan 2026 14:11:45 -0800 Subject: [PATCH 048/499] Revert "Io.Threaded: remove WSA_FLAG_OVERLAPPED from socket call" The stated reason for this commit was cancelation didn't work. On further review, we know why cancelation didn't work, and recent enhancements on master branch make it easy to make it work. Meanwhile, not using overlapped means that multiple threads cannot use the same open socket handle. I also added line comments to explain the choice in this revert commit. This reverts commit fd3657bf8c3a2861e1b35cf36d469d342d853880. closes #31011 reopens #30865 --- lib/std/Io/Threaded.zig | 6 +++++- 1 file changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 4d5073f65924d0a4e0cccec01a46557a5b7e7eda..38f6199f464933112ae6987e13852585805e0428 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -11025,7 +11025,11 @@ fn openSocketWsa( ) !ws2_32.SOCKET { const mode = posixSocketMode(options.mode); const protocol = posixProtocol(options.protocol); - const flags: u32 = ws2_32.WSA_FLAG_NO_HANDLE_INHERIT; + // WSA_FLAG_OVERLAPPED is chosen here because without this different + // threads cannot use the same open socket handle. + // TODO: the below code needs to use the AlertableSyscall mechanism instead in + // order to make cancelation work. + const flags: u32 = ws2_32.WSA_FLAG_OVERLAPPED | ws2_32.WSA_FLAG_NO_HANDLE_INHERIT; var syscall: Syscall = try .start(); while (true) { const rc = ws2_32.WSASocketW(family, @bitCast(mode), @bitCast(protocol), null, 0, flags); -- 2.54.0 From 97986184ca67abdb877bda39707dd129ed6473ae Mon Sep 17 00:00:00 2001 From: Jay Petacat Date: Sat, 24 Jan 2026 11:49:26 -0700 Subject: [PATCH 049/499] langref: Add table of largest integer types that can coerce to floats Add vertical margin to the `.table-wrapper` class so that there's space between the table and the test figures. It does not affect any of the existing tables because the margin collapses with the adjacent `

`. --- doc/langref.html.in | 37 +++++++++++++++++++++++++++++++++++++ 1 file changed, 37 insertions(+) diff --git a/doc/langref.html.in b/doc/langref.html.in index 3671047ec0f3e0db8dad982a2e6f07871292e504..e87decdf4a9ab51de65bc8ae0c8d6a8378b47dcd 100644 --- a/doc/langref.html.in +++ b/doc/langref.html.in @@ -128,6 +128,7 @@ } .table-wrapper { width: 100%; + margin: 1em auto; overflow-x: auto; } @@ -3471,6 +3472,42 @@ void do_a_thing(struct Foo *foo) { without rounding (i.e. the integer's precision does not exceed the float's significand precision). Larger integer types that cannot be safely coerced must be explicitly casted with {#link|@floatFromInt#}.

+
+ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + +
Float TypeLargest Integer Types
{#syntax#}f16{#endsyntax#}{#syntax#}i12{#endsyntax#} and {#syntax#}u11{#endsyntax#}
{#syntax#}f32{#endsyntax#}{#syntax#}i25{#endsyntax#} and {#syntax#}u24{#endsyntax#}
{#syntax#}f64{#endsyntax#}{#syntax#}i54{#endsyntax#} and {#syntax#}u53{#endsyntax#}
{#syntax#}f80{#endsyntax#}{#syntax#}i65{#endsyntax#} and {#syntax#}u64{#endsyntax#}
{#syntax#}f128{#endsyntax#}{#syntax#}i114{#endsyntax#} and {#syntax#}u113{#endsyntax#}
{#syntax#}c_longdouble{#endsyntax#}Varies by target
+
{#code|test_int_to_float_coercion.zig#} {#code|test_failed_int_to_float_coercion.zig#} -- 2.54.0 From 5e9c484745b2a2dd997d1870b70f0303fa8ae2c7 Mon Sep 17 00:00:00 2001 From: Carmen Date: Sat, 24 Jan 2026 13:00:38 +0100 Subject: [PATCH 050/499] std.Io.Reader.takeStruct: dont assert buffer capacity is sizeOf(T) --- lib/std/Io/Reader.zig | 5 ++--- 1 file changed, 2 insertions(+), 3 deletions(-) diff --git a/lib/std/Io/Reader.zig b/lib/std/Io/Reader.zig index 08df1e99bc9d0ad80c254f66b5aab923dc03f66c..a2b70afc67db4edc9c1a3afed0eb0fe79737d471 100644 --- a/lib/std/Io/Reader.zig +++ b/lib/std/Io/Reader.zig @@ -1192,8 +1192,6 @@ pub fn peekStructPointer(r: *Reader, comptime T: type) Error!*align(1) T { return @ptrCast(try r.peekArray(@sizeOf(T))); } -/// Asserts the buffer was initialized with a capacity at least `@sizeOf(T)`. -/// /// This function is inline to avoid referencing `std.mem.byteSwapAllFields` /// when `endian` is comptime-known and matches the host endianness. /// @@ -1205,7 +1203,8 @@ pub inline fn takeStruct(r: *Reader, comptime T: type, endian: std.builtin.Endia .@"struct" => |info| switch (info.layout) { .auto => @compileError("ill-defined memory layout"), .@"extern" => { - var res = (try r.takeStructPointer(T)).*; + var res: T = undefined; + try r.readSliceAll(std.mem.asBytes(&res)); if (native_endian != endian) std.mem.byteSwapAllFields(T, &res); return res; }, -- 2.54.0 From 73ed3510220d387e17d4a4a25837cb982373f12c Mon Sep 17 00:00:00 2001 From: GasInfinity Date: Tue, 27 Jan 2026 00:06:18 +0100 Subject: [PATCH 051/499] fix(libzigc): export `mincore` --- lib/c/sys/mman.zig | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/c/sys/mman.zig b/lib/c/sys/mman.zig index 07ce732bba203db6c5314835fadb4f250913e5b0..92de9ad07660f48af93e334b50b0d63c63b27ff3 100644 --- a/lib/c/sys/mman.zig +++ b/lib/c/sys/mman.zig @@ -7,6 +7,8 @@ comptime { @export(&madviseLinux, .{ .name = "madvise", .linkage = common.linkage, .visibility = common.visibility }); @export(&madviseLinux, .{ .name = "__madvise", .linkage = common.linkage, .visibility = common.visibility }); + @export(&mincoreLinux, .{ .name = "mincore", .linkage = common.linkage, .visibility = common.visibility }); + @export(&mlockLinux, .{ .name = "mlock", .linkage = common.linkage, .visibility = common.visibility }); @export(&mlockallLinux, .{ .name = "mlockall", .linkage = common.linkage, .visibility = common.visibility }); -- 2.54.0 From 4e3fadd90ea6ddb24b5f98b049ad3374d1db0ab8 Mon Sep 17 00:00:00 2001 From: Brian Orora Date: Sat, 24 Jan 2026 20:10:45 +0300 Subject: [PATCH 052/499] std.heap.DebugAllocator: fix account `total_requested_bytes` on `resizeSmall` --- lib/std/heap/debug_allocator.zig | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/std/heap/debug_allocator.zig b/lib/std/heap/debug_allocator.zig index c5abf6709c8d4d900f2c862b8e1f3110095303a0..3ea4a28f96f97a34292ba1461b56b0dc3903316b 100644 --- a/lib/std/heap/debug_allocator.zig +++ b/lib/std/heap/debug_allocator.zig @@ -1010,7 +1010,14 @@ pub fn DebugAllocator(comptime config: Config) type { size_class_index: usize, ) bool { const new_size_class_index: usize = @max(@bitSizeOf(usize) - @clz(new_len - 1), @intFromEnum(alignment)); - if (!config.safety) return new_size_class_index == size_class_index; + if (!config.safety) { + if (new_size_class_index != size_class_index) return false; + // Still account for total even if safety is off + if (config.enable_memory_limit) + self.total_requested_bytes = self.total_requested_bytes - memory.len + new_len; + return true; + } + const slot_count = slot_counts[size_class_index]; const memory_addr = @intFromPtr(memory.ptr); const page_addr = memory_addr & ~(page_size - 1); -- 2.54.0 From 519f1eb3613a95343d4401f0b3643636b1ee721e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Tue, 27 Jan 2026 05:23:02 +0100 Subject: [PATCH 053/499] musl: zero fp/lr registers in _start() and clone() on hexagon --- lib/libc/musl/arch/hexagon/crt_arch.h | 1 + lib/libc/musl/src/thread/hexagon/clone.s | 3 +++ 2 files changed, 4 insertions(+) diff --git a/lib/libc/musl/arch/hexagon/crt_arch.h b/lib/libc/musl/arch/hexagon/crt_arch.h index 9f9428cf1927a98bcd5aac0bf8c90cdb87747c73..395b38d158059480f0bf532f5e6440abe58d618f 100644 --- a/lib/libc/musl/arch/hexagon/crt_arch.h +++ b/lib/libc/musl/arch/hexagon/crt_arch.h @@ -13,6 +13,7 @@ START ": \n" " r1 = memw(r2)\n" " r1 = add(r2, r1)\n" " r30 = #0 // Signals the end of backtrace\n" +" r31 = #0\n" " r0 = r29 // Pointer to argc/argv\n" " r29 = and(r29, #-16) // Align\n" " memw(r29+#-8) = r29\n" diff --git a/lib/libc/musl/src/thread/hexagon/clone.s b/lib/libc/musl/src/thread/hexagon/clone.s index 42aab67a375923b106c51d77a0a617fadbeefce1..b91bda60eb24c11ed318c5385508496de1dd96ab 100644 --- a/lib/libc/musl/src/thread/hexagon/clone.s +++ b/lib/libc/musl/src/thread/hexagon/clone.s @@ -29,6 +29,9 @@ __clone: p0 = cmp.eq(r0, #0) if (!p0) dealloc_return + { r30 = #0 + r31 = #0 } + { r0 = r10 callr r11 } -- 2.54.0 From 951ab1b18bf43cafa8ef0b07892eeaf704551482 Mon Sep 17 00:00:00 2001 From: Pablo Alessandro Santos Hugen Date: Mon, 26 Jan 2026 22:34:52 -0300 Subject: [PATCH 054/499] std.Build.Step.Compile: pass target by pointer to isLibC*LibName --- lib/std/Build/Step/Compile.zig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/std/Build/Step/Compile.zig b/lib/std/Build/Step/Compile.zig index 941f01dd75549436a497a52b554403d57e78d4b1..8c3bb5c568387cec06411adbb04f8cdd5dfb7e46 100644 --- a/lib/std/Build/Step/Compile.zig +++ b/lib/std/Build/Step/Compile.zig @@ -637,11 +637,11 @@ pub fn dependsOnSystemLibrary(compile: *Compile, name: []const u8) bool { const target = compile.rootModuleTarget(); - if (std.zig.target.isLibCLibName(target, name)) { + if (std.zig.target.isLibCLibName(&target, name)) { return is_linking_libc; } - if (std.zig.target.isLibCxxLibName(target, name)) { + if (std.zig.target.isLibCxxLibName(&target, name)) { return is_linking_libcpp; } -- 2.54.0 From 0af79e7b8c50e80764e9833767967db038e3cbf1 Mon Sep 17 00:00:00 2001 From: Robert Ancell Date: Tue, 27 Jan 2026 05:29:51 +0100 Subject: [PATCH 055/499] std.mem.readVarInt: Fix type name in doc comment (#31007) Code used `ReturnType`, comment used `T` (which is what is used in similar functions). Reviewed-on: https://codeberg.org/ziglang/zig/pulls/31007 Co-authored-by: Robert Ancell Co-committed-by: Robert Ancell --- lib/std/mem.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/std/mem.zig b/lib/std/mem.zig index 437faa2eb65a983eef21eaf0e90eafe81c147ad8..1b109cdfa030394d732234950248ad43172905cf 100644 --- a/lib/std/mem.zig +++ b/lib/std/mem.zig @@ -1823,7 +1823,7 @@ test containsAtLeastScalar2 { } /// Reads an integer from memory with size equal to bytes.len. -/// T specifies the return type, which must be large enough to store +/// ReturnType specifies the return type, which must be large enough to store /// the result. pub fn readVarInt(comptime ReturnType: type, bytes: []const u8, endian: Endian) ReturnType { assert(@typeInfo(ReturnType).int.bits >= bytes.len * 8); -- 2.54.0 From d4d210fb377fdf34dc29dd1bc54d59dd5b94a75e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Sat, 24 Jan 2026 17:24:44 +0100 Subject: [PATCH 056/499] std.Build.WebServer: use Io futex operations instead of std.Thread.Futex --- lib/std/Build/WebServer.zig | 17 ++++++++++++++--- 1 file changed, 14 insertions(+), 3 deletions(-) diff --git a/lib/std/Build/WebServer.zig b/lib/std/Build/WebServer.zig index cb201e991d78a6256426535d0d86cb3517b998bd..28b85bdcd3590357f343ba82d4fdda4b3b4ddac1 100644 --- a/lib/std/Build/WebServer.zig +++ b/lib/std/Build/WebServer.zig @@ -24,7 +24,7 @@ time_report_update_times: []i64, build_status: std.atomic.Value(abi.BuildStatus), /// When an event occurs which means WebSocket clients should be sent updates, call `notifyUpdate` -/// to increment this value. Each client thread waits for this increment with `std.Thread.Futex`, so +/// to increment this value. Each client thread waits for this increment with `Io.futexWaitTimeout`, so /// `notifyUpdate` will wake those threads. Updates are sent on a short interval regardless, so it /// is recommended to only use `notifyUpdate` for changes which the user should see immediately. For /// instance, we do not call `notifyUpdate` when the number of "unique runs" in the fuzzer changes, @@ -46,7 +46,7 @@ pub const base_clock: Io.Clock = .awake; /// Thread-safe. Triggers updates to be sent to connected WebSocket clients; see `update_id`. pub fn notifyUpdate(ws: *WebServer) void { _ = ws.update_id.rmw(.Add, 1, .release); - std.Thread.Futex.wake(&ws.update_id, 16); + ws.graph.io.futexWake(u32, &ws.update_id.raw, 16); } pub const Options = struct { @@ -377,7 +377,18 @@ fn serveWebSocket(ws: *WebServer, sock: *http.Server.WebSocket) !noreturn { } prev_time = start_time; - std.Thread.Futex.timedWait(&ws.update_id, start_update_id, std.time.ns_per_ms * default_update_interval_ms) catch {}; + + const old_cp = io.swapCancelProtection(.blocked); + defer _ = io.swapCancelProtection(old_cp); + io.futexWaitTimeout( + u32, + &ws.update_id.raw, + start_update_id, + .{ .duration = .{ + .clock = .awake, + .raw = .fromMilliseconds(default_update_interval_ms), + } }, + ) catch |err| switch (err) { error.Canceled => unreachable }; } } fn recvWebSocketMessages(ws: *WebServer, sock: *http.Server.WebSocket) void { -- 2.54.0 From 2c7d3c8007dcc8e46c91e92e1d2676af87d1fbcc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Sat, 24 Jan 2026 17:00:36 +0100 Subject: [PATCH 057/499] std.debug: use debug_io for the futex in waitForOtherThreadToFinishPanicking --- lib/std/debug.zig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/std/debug.zig b/lib/std/debug.zig index d07988d7ec5baaf9ef6b61e83253a82936fc06e5..4cf8cc5dc626128112bc8f190cfadcdf18026299 100644 --- a/lib/std/debug.zig +++ b/lib/std/debug.zig @@ -596,8 +596,8 @@ fn waitForOtherThreadToFinishPanicking() void { if (builtin.single_threaded) unreachable; // Sleep forever without hammering the CPU - var futex = std.atomic.Value(u32).init(0); - while (true) std.Thread.Futex.wait(&futex, 0); + var futex: u32 = 0; + while (true) std.Options.debug_io.futexWaitUncancelable(u32, &futex, 0); unreachable; } } -- 2.54.0 From 5652288e5d86e906e1558a7fdfc26126e96e9d82 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Tue, 27 Jan 2026 07:05:14 +0100 Subject: [PATCH 058/499] zig fmt oops --- lib/std/Build/WebServer.zig | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/std/Build/WebServer.zig b/lib/std/Build/WebServer.zig index 28b85bdcd3590357f343ba82d4fdda4b3b4ddac1..e1536fb8fa8bb2ad6e30a0e2a6fcaeda8d299c64 100644 --- a/lib/std/Build/WebServer.zig +++ b/lib/std/Build/WebServer.zig @@ -388,7 +388,9 @@ fn serveWebSocket(ws: *WebServer, sock: *http.Server.WebSocket) !noreturn { .clock = .awake, .raw = .fromMilliseconds(default_update_interval_ms), } }, - ) catch |err| switch (err) { error.Canceled => unreachable }; + ) catch |err| switch (err) { + error.Canceled => unreachable, + }; } } fn recvWebSocketMessages(ws: *WebServer, sock: *http.Server.WebSocket) void { -- 2.54.0 From 29b7214027b0c9bddc4c67587ee75b4adcdab4f8 Mon Sep 17 00:00:00 2001 From: Ryan Liptak Date: Mon, 26 Jan 2026 23:55:17 -0800 Subject: [PATCH 059/499] Disentangle from `error.CurrentWorkingDirectoryUnlinked` This error is actually only ever directly returned from `std.posix.getcwd` (and only on POSIX systems, so never on Windows). Its inclusion in almost all of the error sets its currently found in is a leftover from when `std.fs.path.resolve` called `std.process.getCwdAlloc` (https://github.com/ziglang/zig/issues/13613). --- lib/std/posix.zig | 1 + lib/std/process.zig | 7 ++++--- lib/std/zig/system.zig | 1 - src/Compilation.zig | 3 --- src/Sema.zig | 4 ---- src/Zcu/PerThread.zig | 1 - 6 files changed, 5 insertions(+), 12 deletions(-) diff --git a/lib/std/posix.zig b/lib/std/posix.zig index f1f4e279d1726ad4997dc2f2009755887aa3c520..a12182b4065d1ee518bd7c82ac938c4b4e693ea0 100644 --- a/lib/std/posix.zig +++ b/lib/std/posix.zig @@ -521,6 +521,7 @@ pub fn getppid() pid_t { pub const GetCwdError = error{ NameTooLong, + /// Not possible on Windows. CurrentWorkingDirectoryUnlinked, } || UnexpectedError; diff --git a/lib/std/process.zig b/lib/std/process.zig index dbf2fbe666311a8a7236630e19f20c4b32d54933..6798bba675b9cbe98da2d8fe438c04a285e5d1f6 100644 --- a/lib/std/process.zig +++ b/lib/std/process.zig @@ -73,7 +73,10 @@ pub fn getCwd(out_buffer: []u8) GetCwdError![]u8 { } // Same as GetCwdError, minus error.NameTooLong + Allocator.Error -pub const GetCwdAllocError = Allocator.Error || error{CurrentWorkingDirectoryUnlinked} || posix.UnexpectedError; +pub const GetCwdAllocError = Allocator.Error || error{ + /// Not possible on Windows. + CurrentWorkingDirectoryUnlinked, +} || posix.UnexpectedError; /// Caller must free the returned memory. /// On Windows, the result is encoded as [WTF-8](https://wtf-8.codeberg.page/). @@ -342,8 +345,6 @@ pub const SpawnError = error{ /// Windows-only. `cwd` or `argv` was provided and it was invalid WTF-8. /// https://wtf-8.codeberg.page/ InvalidWtf8, - /// Windows-only. `cwd` was provided, but the path did not exist when spawning the child process. - CurrentWorkingDirectoryUnlinked, /// Windows-only. NUL (U+0000), LF (U+000A), CR (U+000D) are not allowed /// within arguments when executing a `.bat`/`.cmd` script. /// - NUL/LF signifiies end of arguments, so anything afterwards diff --git a/lib/std/zig/system.zig b/lib/std/zig/system.zig index 0e7d814a71f1f28f424509d481a1106282924bd2..8bb1678e7d0244a0206ab14952fd5dc3d35fe19a 100644 --- a/lib/std/zig/system.zig +++ b/lib/std/zig/system.zig @@ -504,7 +504,6 @@ pub fn resolveTargetQuery(io: Io, query: Target.Query) DetectError!Target { if (builtin.os.tag == .linux and result.isBionicLibC() and query.os_tag == null and query.android_api_level == null) { result.os.version_range.linux.android = detectAndroidApiLevel(io) catch |err| return switch (err) { error.InvalidWtf8, - error.CurrentWorkingDirectoryUnlinked, error.InvalidBatchScriptArg, => unreachable, // Windows-only error.ApiLevelQueryFailed => |e| e, diff --git a/src/Compilation.zig b/src/Compilation.zig index 85fceebfb88aab25eeedd87946fb7477c2ce09fc..15c1837ec2dfb6a8f23691868b3f414828d4af5e 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -1851,7 +1851,6 @@ fn addModuleTableToCacheHash( ) error{ OutOfMemory, Unexpected, - CurrentWorkingDirectoryUnlinked, }!void { assert(zcu.module_roots.count() != 0); // module_roots is populated @@ -1919,7 +1918,6 @@ pub const CreateError = error{ OutOfMemory, Canceled, Unexpected, - CurrentWorkingDirectoryUnlinked, /// An error has been stored to `diag`. CreateFail, }; @@ -2906,7 +2904,6 @@ pub const UpdateError = error{ OutOfMemory, Canceled, Unexpected, - CurrentWorkingDirectoryUnlinked, }; /// Detect changes to source files, perform semantic analysis, and update the output files. diff --git a/src/Sema.zig b/src/Sema.zig index 191da7c30dda58400f157c61d5739412cf08b7e0..ea2890ee6a220a0cc013f6735057f8ee27e92ed3 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -13604,10 +13604,6 @@ fn zirEmbedFile(sema: *Sema, block: *Block, inst: Zir.Inst.Index) CompileError!A error.ImportOutsideModulePath => { return sema.fail(block, operand_src, "embed of file outside package path: '{s}'", .{name}); }, - error.CurrentWorkingDirectoryUnlinked => { - // TODO: this should be some kind of retryable failure, in case the cwd is put back - return sema.fail(block, operand_src, "unable to resolve '{s}': working directory has been unlinked", .{name}); - }, error.OutOfMemory => |e| return e, error.Canceled => |e| return e, }; diff --git a/src/Zcu/PerThread.zig b/src/Zcu/PerThread.zig index d79b66b7c2a6b8b4216fbd372c01e6871f23c830..950a3e5a19f5c08524df7087aa201f54e4f05400 100644 --- a/src/Zcu/PerThread.zig +++ b/src/Zcu/PerThread.zig @@ -2388,7 +2388,6 @@ pub fn embedFile( OutOfMemory, Canceled, ImportOutsideModulePath, - CurrentWorkingDirectoryUnlinked, }!Zcu.EmbedFile.Index { const zcu = pt.zcu; const gpa = zcu.gpa; -- 2.54.0 From 1655a666d5695ac974e107233a90a75eebe5fc2f Mon Sep 17 00:00:00 2001 From: Ryan Liptak Date: Tue, 13 Jan 2026 02:36:21 -0800 Subject: [PATCH 060/499] windows_resources standalone test: Load a resource and check its data Just a potential way to catch regressions and to ensure the resources actually make it into the binary correctly. --- test/standalone/windows_resources/build.zig | 7 ++- test/standalone/windows_resources/main.zig | 48 ++++++++++++++++++++- 2 files changed, 52 insertions(+), 3 deletions(-) diff --git a/test/standalone/windows_resources/build.zig b/test/standalone/windows_resources/build.zig index d3afadb60d5d4a6e16cc3f98f04a45c75c350307..3b140c90f79e650cc9fb4b944ac1452ac9996c97 100644 --- a/test/standalone/windows_resources/build.zig +++ b/test/standalone/windows_resources/build.zig @@ -46,7 +46,10 @@ fn add( .gnu => .gnu, }; - _ = exe.getEmittedBin(); + const exe_run_step = b.addRunArtifact(exe); + exe_run_step.skip_foreign_checks = true; + exe_run_step.expectStdErrEqual(""); + exe_run_step.expectStdOutEqual(""); - test_step.dependOn(&exe.step); + test_step.dependOn(&exe_run_step.step); } diff --git a/test/standalone/windows_resources/main.zig b/test/standalone/windows_resources/main.zig index f92e18124bb86f9b42fa47ec0a8291c7b0d9ad35..9af794103652656d6dd25c2139cfb2b7a45d6a0d 100644 --- a/test/standalone/windows_resources/main.zig +++ b/test/standalone/windows_resources/main.zig @@ -1,5 +1,51 @@ const std = @import("std"); +const builtin = @import("builtin"); +const w = std.os.windows; pub fn main() !void { - std.debug.print("All your {s} are belong to us.\n", .{"codebase"}); + if (builtin.os.tag == .windows) { + const name = std.unicode.wtf8ToWtf16LeStringLiteral("FOO"); + const RT_RCDATA = MAKEINTRESOURCEW(10); + const handle = FindResourceW(null, name, RT_RCDATA) orelse { + std.debug.print("unable to find resource: {t}\n", .{w.GetLastError()}); + return error.FailedToLoadResource; + }; + const res = LoadResource(null, handle) orelse { + std.debug.print("unable to load resource: {t}\n", .{w.GetLastError()}); + return error.FailedToLoadResource; + }; + const data_ptr = LockResource(res) orelse { + std.debug.print("unable to lock resource: {t}\n", .{w.GetLastError()}); + return error.FailedToLoadResource; + }; + const size = SizeofResource(null, handle); + const data = @as([*]const u8, @ptrCast(data_ptr))[0..size]; + try std.testing.expectEqualSlices(u8, "foo", data); + } } + +const HRSRC = *opaque {}; +const HGLOBAL = *opaque {}; +fn MAKEINTRESOURCEW(id: u16) [*:0]align(1) const w.WCHAR { + return @ptrFromInt(id); +} + +extern "kernel32" fn FindResourceW( + hModule: ?w.HMODULE, + lpName: [*:0]align(1) const w.WCHAR, + lpType: [*:0]align(1) const w.WCHAR, +) callconv(.winapi) ?HRSRC; + +extern "kernel32" fn LoadResource( + hModule: ?w.HMODULE, + hResInfo: HRSRC, +) callconv(.winapi) ?HGLOBAL; + +extern "kernel32" fn LockResource( + hResData: HGLOBAL, +) callconv(.winapi) ?w.LPVOID; + +extern "kernel32" fn SizeofResource( + hModule: ?w.HMODULE, + hResInfo: HRSRC, +) callconv(.winapi) w.DWORD; -- 2.54.0 From 06cf86abeb3a062c6c3e21f4379c7fea9e6d71b6 Mon Sep 17 00:00:00 2001 From: just_some_entity Date: Tue, 27 Jan 2026 23:09:51 +0100 Subject: [PATCH 061/499] Fix BootServices.locateHandleLen() (#30877) Fixes https://codeberg.org/ziglang/zig/issues/30876 Reviewed-on: https://codeberg.org/ziglang/zig/pulls/30877 Reviewed-by: linus Co-authored-by: just_some_entity Co-committed-by: just_some_entity --- lib/std/os/uefi/tables/boot_services.zig | 17 ++++++++++++----- 1 file changed, 12 insertions(+), 5 deletions(-) diff --git a/lib/std/os/uefi/tables/boot_services.zig b/lib/std/os/uefi/tables/boot_services.zig index f2a8b73b832ad0cbb9b1e952e8221129c1ac20d3..506961315dcf2a001b7914072b441a9d8c9c816f 100644 --- a/lib/std/os/uefi/tables/boot_services.zig +++ b/lib/std/os/uefi/tables/boot_services.zig @@ -235,9 +235,7 @@ pub const BootServices = extern struct { InvalidParameter, }; - pub const NumHandlesError = uefi.UnexpectedError || error{ - OutOfResources, - }; + pub const NumHandlesError = uefi.UnexpectedError; pub const LocateHandleError = uefi.UnexpectedError || error{ BufferTooSmall, @@ -702,8 +700,17 @@ pub const BootServices = extern struct { &len, null, )) { - .success => return @divExact(len, @sizeOf(Handle)), - .out_of_resources => return error.OutOfResources, + // If len is zero, it should return not_found, otherwise buffer_too_small. + // This is because it can/should only return success when a valid buffer is + // passed with a non zero size, which is not the case. + // Thus this status is considered unreachable and will return error.Unexpected + // .success => unreachable, + .buffer_too_small => return @divExact(len, @sizeOf(uefi.Handle)), + .not_found => return 0, + // This function accounts for all possible causes of this error code + // as per the most recent UEFI spec 2.10A, therefore this branch is + // considered unreachable and will return error.Unexpected instead + // .invalid_parameter => unreachable else => |status| return uefi.unexpectedStatus(status), } } -- 2.54.0 From 0f51f663f06728f38f518073a23d69a7c1b0d792 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Tue, 27 Jan 2026 23:24:33 +0100 Subject: [PATCH 062/499] musl: update some hexagon headers from the quic fork --- .../include/hexagon-linux-musl/bits/hwcap.h | 31 +++++++++++++++++++ .../include/hexagon-linux-musl/bits/signal.h | 8 ++--- lib/libc/musl/arch/hexagon/bits/hwcap.h | 31 +++++++++++++++++++ lib/libc/musl/arch/hexagon/bits/signal.h | 5 +-- 4 files changed, 69 insertions(+), 6 deletions(-) create mode 100644 lib/libc/include/hexagon-linux-musl/bits/hwcap.h create mode 100644 lib/libc/musl/arch/hexagon/bits/hwcap.h diff --git a/lib/libc/include/hexagon-linux-musl/bits/hwcap.h b/lib/libc/include/hexagon-linux-musl/bits/hwcap.h new file mode 100644 index 0000000000000000000000000000000000000000..40b13e18f93622c10d83840c3a2add7788421e61 --- /dev/null +++ b/lib/libc/include/hexagon-linux-musl/bits/hwcap.h @@ -0,0 +1,31 @@ +/* ISA version encoding in bits 0-6 (7 bits) */ +#define HWCAP_HEXAGON_ISA_MASK 0x7F /* ISA version mask */ + +/* ISA version enumeration values */ +#define HWCAP_HEXAGON_ISA_V2 1 /* Hexagon V2 */ +#define HWCAP_HEXAGON_ISA_V3 2 /* Hexagon V3 */ +#define HWCAP_HEXAGON_ISA_V4 3 /* Hexagon V4 */ +#define HWCAP_HEXAGON_ISA_V5 4 /* Hexagon V5 */ +#define HWCAP_HEXAGON_ISA_V55 5 /* Hexagon V55 */ +#define HWCAP_HEXAGON_ISA_V60 6 /* Hexagon V60 */ +#define HWCAP_HEXAGON_ISA_V62 7 /* Hexagon V62 */ +#define HWCAP_HEXAGON_ISA_V65 8 /* Hexagon V65 */ +#define HWCAP_HEXAGON_ISA_V66 9 /* Hexagon V66 */ +#define HWCAP_HEXAGON_ISA_V67 10 /* Hexagon V67 */ +#define HWCAP_HEXAGON_ISA_V68 11 /* Hexagon V68 */ +#define HWCAP_HEXAGON_ISA_V69 12 /* Hexagon V69 */ +#define HWCAP_HEXAGON_ISA_V71 13 /* Hexagon V71 */ +#define HWCAP_HEXAGON_ISA_V73 14 /* Hexagon V73 */ +#define HWCAP_HEXAGON_ISA_V79 15 /* Hexagon V79 */ + +/* Essential feature flags */ +#define HWCAP_HEXAGON_HVX (1 << 7) /* HVX (Hexagon Vector eXtensions) */ +#define HWCAP_HEXAGON_CABAC (1 << 8) /* CABAC acceleration */ +#define HWCAP_HEXAGON_HVX_LENGTH_128B (1 << 9) /* HVX 128-byte vector length */ +#define HWCAP_HEXAGON_HVX_IEEE_FP (1 << 10) /* HVX IEEE floating point */ +#define HWCAP_HEXAGON_AUDIO (1 << 11) /* Audio ISA extensions */ + +/* Utility macros for userspace applications */ +#define HWCAP_HEXAGON_GET_ISA(hwcap) ((hwcap) & HWCAP_HEXAGON_ISA_MASK) +#define HWCAP_HEXAGON_IS_ISA(hwcap, version) (HWCAP_HEXAGON_GET_ISA(hwcap) == (version)) +#define HWCAP_HEXAGON_HAS_ISA(hwcap, version) (HWCAP_HEXAGON_GET_ISA(hwcap) >= (version)) diff --git a/lib/libc/include/hexagon-linux-musl/bits/signal.h b/lib/libc/include/hexagon-linux-musl/bits/signal.h index ec67d3ba01e005f06f717136a8b9a9cb0837c94d..9753066b6d840cf11d1f86cdc9cd2b48a16952c5 100644 --- a/lib/libc/include/hexagon-linux-musl/bits/signal.h +++ b/lib/libc/include/hexagon-linux-musl/bits/signal.h @@ -31,9 +31,10 @@ typedef struct sigcontext unsigned long pc; unsigned long cause; unsigned long badva; + unsigned long cs0; + unsigned long cs1; unsigned long pad1; - unsigned long long pad2; -} mcontext_t; +} __attribute__((__aligned__(8))) mcontext_t; #else typedef struct { unsigned long __regs[48]; @@ -61,7 +62,6 @@ typedef struct __ucontext { #define SA_RESTART 0x10000000 #define SA_NODEFER 0x40000000 #define SA_RESETHAND 0x80000000 -#define SA_RESTORER 0x04000000 #endif @@ -100,4 +100,4 @@ typedef struct __ucontext { #define SIGSYS 31 #define SIGUNUSED SIGSYS -#define _NSIG 65 \ No newline at end of file +#define _NSIG 65 diff --git a/lib/libc/musl/arch/hexagon/bits/hwcap.h b/lib/libc/musl/arch/hexagon/bits/hwcap.h new file mode 100644 index 0000000000000000000000000000000000000000..40b13e18f93622c10d83840c3a2add7788421e61 --- /dev/null +++ b/lib/libc/musl/arch/hexagon/bits/hwcap.h @@ -0,0 +1,31 @@ +/* ISA version encoding in bits 0-6 (7 bits) */ +#define HWCAP_HEXAGON_ISA_MASK 0x7F /* ISA version mask */ + +/* ISA version enumeration values */ +#define HWCAP_HEXAGON_ISA_V2 1 /* Hexagon V2 */ +#define HWCAP_HEXAGON_ISA_V3 2 /* Hexagon V3 */ +#define HWCAP_HEXAGON_ISA_V4 3 /* Hexagon V4 */ +#define HWCAP_HEXAGON_ISA_V5 4 /* Hexagon V5 */ +#define HWCAP_HEXAGON_ISA_V55 5 /* Hexagon V55 */ +#define HWCAP_HEXAGON_ISA_V60 6 /* Hexagon V60 */ +#define HWCAP_HEXAGON_ISA_V62 7 /* Hexagon V62 */ +#define HWCAP_HEXAGON_ISA_V65 8 /* Hexagon V65 */ +#define HWCAP_HEXAGON_ISA_V66 9 /* Hexagon V66 */ +#define HWCAP_HEXAGON_ISA_V67 10 /* Hexagon V67 */ +#define HWCAP_HEXAGON_ISA_V68 11 /* Hexagon V68 */ +#define HWCAP_HEXAGON_ISA_V69 12 /* Hexagon V69 */ +#define HWCAP_HEXAGON_ISA_V71 13 /* Hexagon V71 */ +#define HWCAP_HEXAGON_ISA_V73 14 /* Hexagon V73 */ +#define HWCAP_HEXAGON_ISA_V79 15 /* Hexagon V79 */ + +/* Essential feature flags */ +#define HWCAP_HEXAGON_HVX (1 << 7) /* HVX (Hexagon Vector eXtensions) */ +#define HWCAP_HEXAGON_CABAC (1 << 8) /* CABAC acceleration */ +#define HWCAP_HEXAGON_HVX_LENGTH_128B (1 << 9) /* HVX 128-byte vector length */ +#define HWCAP_HEXAGON_HVX_IEEE_FP (1 << 10) /* HVX IEEE floating point */ +#define HWCAP_HEXAGON_AUDIO (1 << 11) /* Audio ISA extensions */ + +/* Utility macros for userspace applications */ +#define HWCAP_HEXAGON_GET_ISA(hwcap) ((hwcap) & HWCAP_HEXAGON_ISA_MASK) +#define HWCAP_HEXAGON_IS_ISA(hwcap, version) (HWCAP_HEXAGON_GET_ISA(hwcap) == (version)) +#define HWCAP_HEXAGON_HAS_ISA(hwcap, version) (HWCAP_HEXAGON_GET_ISA(hwcap) >= (version)) diff --git a/lib/libc/musl/arch/hexagon/bits/signal.h b/lib/libc/musl/arch/hexagon/bits/signal.h index 1a2715dd5496889a3b5ead0b40c5c723a58dadb0..9753066b6d840cf11d1f86cdc9cd2b48a16952c5 100644 --- a/lib/libc/musl/arch/hexagon/bits/signal.h +++ b/lib/libc/musl/arch/hexagon/bits/signal.h @@ -31,9 +31,10 @@ typedef struct sigcontext unsigned long pc; unsigned long cause; unsigned long badva; + unsigned long cs0; + unsigned long cs1; unsigned long pad1; - unsigned long long pad2; -} mcontext_t; +} __attribute__((__aligned__(8))) mcontext_t; #else typedef struct { unsigned long __regs[48]; -- 2.54.0 From 11c3b4bd4178c78af103f5397fc3aafd09a2a979 Mon Sep 17 00:00:00 2001 From: llogick <16590917+llogick@users.noreply.github.com> Date: Tue, 27 Jan 2026 14:58:24 -0800 Subject: [PATCH 063/499] Fix std.process.run leaking memory if child.wait returned an error --- lib/std/process.zig | 12 +++++++++--- 1 file changed, 9 insertions(+), 3 deletions(-) diff --git a/lib/std/process.zig b/lib/std/process.zig index 6798bba675b9cbe98da2d8fe438c04a285e5d1f6..f7dd7e30173504deade22fee09fb361bc629f012 100644 --- a/lib/std/process.zig +++ b/lib/std/process.zig @@ -533,10 +533,16 @@ pub fn run(gpa: Allocator, io: Io, options: RunOptions) RunError!RunResult { try child.collectOutput(gpa, &stdout, &stderr, options.max_output_bytes); + const term = try child.wait(io); + + const owned_stdout = try stdout.toOwnedSlice(gpa); + errdefer gpa.free(owned_stdout); + const owned_stderr = try stderr.toOwnedSlice(gpa); + return .{ - .stdout = try stdout.toOwnedSlice(gpa), - .stderr = try stderr.toOwnedSlice(gpa), - .term = try child.wait(io), + .stdout = owned_stdout, + .stderr = owned_stderr, + .term = term, }; } -- 2.54.0 From 204fa8959a8f9d5457705f338eb0461e54155796 Mon Sep 17 00:00:00 2001 From: Krzysztof Wolicki Date: Tue, 27 Jan 2026 20:49:30 +0100 Subject: [PATCH 064/499] Make functions on EnumMap always take a pointer to avoid copies of big EnumMaps --- lib/std/enums.zig | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/std/enums.zig b/lib/std/enums.zig index 76deff5421ad8272804a9fba751442c4a4bc83bc..6e21713e6d28c33e2088e88b3c98e5b75a2e5756 100644 --- a/lib/std/enums.zig +++ b/lib/std/enums.zig @@ -504,25 +504,25 @@ pub fn EnumMap(comptime E: type, comptime V: type) type { } /// The number of items in the map. - pub fn count(self: Self) usize { + pub fn count(self: *const Self) usize { return self.bits.count(); } /// Checks if the map contains an item. - pub fn contains(self: Self, key: Key) bool { + pub fn contains(self: *const Self, key: Key) bool { return self.bits.isSet(Indexer.indexOf(key)); } /// Gets the value associated with a key. /// If the key is not in the map, returns null. - pub fn get(self: Self, key: Key) ?Value { + pub fn get(self: *const Self, key: Key) ?Value { const index = Indexer.indexOf(key); return if (self.bits.isSet(index)) self.values[index] else null; } /// Gets the value associated with a key, which must /// exist in the map. - pub fn getAssertContains(self: Self, key: Key) Value { + pub fn getAssertContains(self: *const Self, key: Key) Value { const index = Indexer.indexOf(key); assert(self.bits.isSet(index)); return self.values[index]; -- 2.54.0 From 3b10383114f7aee96f18f7b3aac7c0e08d840b42 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 28 Jan 2026 00:43:20 -0800 Subject: [PATCH 065/499] std.meta: delete declList dubious. if people want this logic they should take responsibility for it in their own code. --- lib/std/meta.zig | 19 ------------------- 1 file changed, 19 deletions(-) diff --git a/lib/std/meta.zig b/lib/std/meta.zig index a4ce8161327e9ebebe5003810aac81e0d5c18c32..f1afa9bc7a049c5e4e13546e307b551e93e863c9 100644 --- a/lib/std/meta.zig +++ b/lib/std/meta.zig @@ -752,25 +752,6 @@ pub fn fieldIndex(comptime T: type, comptime name: []const u8) ?comptime_int { return null; } -/// Returns a slice of pointers to public declarations of a namespace. -pub fn declList(comptime Namespace: type, comptime Decl: type) []const *const Decl { - const S = struct { - fn declNameLessThan(context: void, lhs: *const Decl, rhs: *const Decl) bool { - _ = context; - return mem.lessThan(u8, lhs.name, rhs.name); - } - }; - comptime { - const decls = declarations(Namespace); - var array: [decls.len]*const Decl = undefined; - for (decls, 0..) |decl, i| { - array[i] = &@field(Namespace, decl.name); - } - mem.sort(*const Decl, &array, {}, S.declNameLessThan); - return &array; - } -} - /// Deprecated: use @Int pub fn Int(comptime signedness: std.builtin.Signedness, comptime bit_count: u16) type { return @Int(signedness, bit_count); -- 2.54.0 From 757ec185f0eb91a15c4bdbe0201f0d998f30258c Mon Sep 17 00:00:00 2001 From: lzm-build <3575188313@qq.com> Date: Wed, 28 Jan 2026 23:33:35 +0100 Subject: [PATCH 066/499] Add `f16`, `f80` and `f128` support for `acos` and `asin` (#30997) The software impl of `acos` and `asin` depends on the `sqrt` op. Since support for `sqrt` in `f16`, `f80`, and `f128` has been added, the impl of `acos` and `asin` for `f16`, `f80`, and `f128` is now being supplemented. Reviewed-on: https://codeberg.org/ziglang/zig/pulls/30997 Reviewed-by: Andrew Kelley Co-authored-by: lzm-build <3575188313@qq.com> Co-committed-by: lzm-build <3575188313@qq.com> --- lib/std/math/acos.zig | 409 +++++++++++++++++++++++++++++++-------- lib/std/math/asin.zig | 434 +++++++++++++++++++++++++++++++++--------- 2 files changed, 675 insertions(+), 168 deletions(-) diff --git a/lib/std/math/acos.zig b/lib/std/math/acos.zig index 6f734dbacb8cb198e606452a9afb5ff3039e1470..dae3f70c82dfa1839e6112ba7135e32e9c65ef1f 100644 --- a/lib/std/math/acos.zig +++ b/lib/std/math/acos.zig @@ -3,10 +3,13 @@ // // https://git.musl-libc.org/cgit/musl/tree/src/math/acosf.c // https://git.musl-libc.org/cgit/musl/tree/src/math/acos.c +// https://git.musl-libc.org/cgit/musl/tree/src/math/acosl.c const std = @import("../std.zig"); const math = std.math; -const expect = std.testing.expect; +const testing = std.testing; +const builtin = @import("builtin"); +const native_endian = builtin.cpu.arch.endian(); /// Returns the arc-cosine of x. /// @@ -15,71 +18,120 @@ const expect = std.testing.expect; pub fn acos(x: anytype) @TypeOf(x) { const T = @TypeOf(x); return switch (T) { - f32 => acos32(x), - f64 => acos64(x), + f16 => acosBinary16(x), + f32 => acosBinary32(x), + f64 => acosBinary64(x), + f80 => acosExtended80(x), + f128 => acosBinary128(x), else => @compileError("acos not implemented for " ++ @typeName(T)), }; } -fn r32(z: f32) f32 { - const pS0 = 1.6666586697e-01; - const pS1 = -4.2743422091e-02; - const pS2 = -8.6563630030e-03; - const qS1 = -7.0662963390e-01; +fn approxBinary16(z: f32) f32 { + const S0: f32 = 1.0000001e0; + const S1: f32 = 1.6664918e-1; + const S2: f32 = 7.55022e-2; + const S3: f32 = 3.9513987e-2; + const S4: f32 = 5.0883885e-2; + return S0 + z * (S1 + z * (S2 + z * (S3 + z * S4))); +} + +fn acosBinary16(x: f16) f16 { + const pio2: f32 = math.pi / 2.0; + + const hx: u16 = @bitCast(x); + const ix: u16 = hx & 0x7fff; + + // |x| >= 1 or nan + if (ix >= 0x3c00) { + if (ix == 0x3c00) { + if (hx >> 15 != 0) { + return @floatCast(2.0 * pio2 + 0x1p-120); + } + return 0.0; + } + return 0.0 / (x - x); + } + + const xf: f32 = @floatCast(x); + + // |x| < 0.5 + if (ix < 0x3800) { + return @floatCast(pio2 - xf * approxBinary16(xf * xf)); + } + + // x < -0.5 + if (hx >> 15 != 0) { + const z = (1.0 + xf) * 0.5; + const s = @sqrt(z); + const w = approxBinary16(z) * s; + return @floatCast(2.0 * (pio2 - w)); + } + + // x > 0.5 + const z = (1.0 - xf) * 0.5; + const s = @sqrt(z); + const w = approxBinary16(z) * s; + return @floatCast(2.0 * w); +} + +fn rationalApproxBinary32(z: f32) f32 { + const pS0: f32 = 1.6666586697e-01; + const pS1: f32 = -4.2743422091e-02; + const pS2: f32 = -8.6563630030e-03; + const qS1: f32 = -7.0662963390e-01; const p = z * (pS0 + z * (pS1 + z * pS2)); const q = 1.0 + z * qS1; return p / q; } -fn acos32(x: f32) f32 { - const pio2_hi = 1.5707962513e+00; - const pio2_lo = 7.5497894159e-08; +fn acosBinary32(x: f32) f32 { + const pio2_hi: f32 = 1.5707962513e+00; + const pio2_lo: f32 = 7.5497894159e-08; - const hx: u32 = @as(u32, @bitCast(x)); - const ix: u32 = hx & 0x7FFFFFFF; + const hx: u32 = @bitCast(x); + const ix: u32 = hx & 0x7fff_ffff; // |x| >= 1 or nan - if (ix >= 0x3F800000) { - if (ix == 0x3F800000) { + if (ix >= 0x3f800000) { + if (ix == 0x3f800000) { if (hx >> 31 != 0) { return 2.0 * pio2_hi + 0x1.0p-120; - } else { - return 0.0; } - } else { - return (x - x) / 0; + return 0.0; } + return 0.0 / (x - x); } // |x| < 0.5 - if (ix < 0x3F000000) { - if (ix <= 0x32800000) { // |x| < 2^(-26) + if (ix < 0x3f00_0000) { + // |x| < 2^(-26) + if (ix <= 0x3280_0000) { return pio2_hi + 0x1.0p-120; - } else { - return pio2_hi - (x - (pio2_lo - x * r32(x * x))); } + return pio2_hi - (x - (pio2_lo - x * rationalApproxBinary32(x * x))); } // x < -0.5 if (hx >> 31 != 0) { const z = (1 + x) * 0.5; const s = @sqrt(z); - const w = r32(z) * s - pio2_lo; - return 2 * (pio2_hi - (s + w)); + const w = rationalApproxBinary32(z) * s - pio2_lo; + return 2.0 * (pio2_hi - (s + w)); } // x > 0.5 const z = (1.0 - x) * 0.5; const s = @sqrt(z); - const jx = @as(u32, @bitCast(s)); - const df = @as(f32, @bitCast(jx & 0xFFFFF000)); + const hs: u32 = @bitCast(s); + const df: f32 = @bitCast(hs & 0xffff_f000); const c = (z - df * df) / (s + df); - const w = r32(z) * s + c; - return 2 * (df + w); + const w = rationalApproxBinary32(z) * s + c; + return 2.0 * (df + w); } -fn r64(z: f64) f64 { +fn rationalApproxBinary64(z: f64) f64 { const pS0: f64 = 1.66666666666666657415e-01; const pS1: f64 = -3.25565818622400915405e-01; const pS2: f64 = 2.01212532134862925881e-01; @@ -96,91 +148,292 @@ fn r64(z: f64) f64 { return p / q; } -fn acos64(x: f64) f64 { +fn acosBinary64(x: f64) f64 { const pio2_hi: f64 = 1.57079632679489655800e+00; const pio2_lo: f64 = 6.12323399573676603587e-17; - const ux = @as(u64, @bitCast(x)); - const hx = @as(u32, @intCast(ux >> 32)); - const ix = hx & 0x7FFFFFFF; + const hx: u32 = @intCast(@as(u64, @bitCast(x)) >> 32); + const ix: u32 = hx & 0x7fff_ffff; // |x| >= 1 or nan - if (ix >= 0x3FF00000) { - const lx = @as(u32, @intCast(ux & 0xFFFFFFFF)); - - // acos(1) = 0, acos(-1) = pi - if ((ix - 0x3FF00000) | lx == 0) { + if (ix >= 0x3ff0_0000) { + const lx: u32 = @truncate(@as(u64, @bitCast(x))); + if ((ix - 0x3ff0_0000 | lx) == 0) { if (hx >> 31 != 0) { - return 2 * pio2_hi + 0x1.0p-120; - } else { - return 0; + return 2.0 * pio2_hi + 0x1.0p-120; } + return 0.0; } - - return (x - x) / 0; + return 0.0 / (x - x); } // |x| < 0.5 - if (ix < 0x3FE00000) { + if (ix < 0x3fe0_0000) { // |x| < 2^(-57) - if (ix <= 0x3C600000) { + if (ix <= 0x3c60_0000) { return pio2_hi + 0x1.0p-120; - } else { - return pio2_hi - (x - (pio2_lo - x * r64(x * x))); } + return pio2_hi - (x - (pio2_lo - x * rationalApproxBinary64(x * x))); } // x < -0.5 if (hx >> 31 != 0) { const z = (1.0 + x) * 0.5; const s = @sqrt(z); - const w = r64(z) * s - pio2_lo; + const w = rationalApproxBinary64(z) * s - pio2_lo; return 2 * (pio2_hi - (s + w)); } // x > 0.5 const z = (1.0 - x) * 0.5; const s = @sqrt(z); - const jx = @as(u64, @bitCast(s)); - const df = @as(f64, @bitCast(jx & 0xFFFFFFFF00000000)); + const df: f64 = @bitCast(@as(u64, @bitCast(s)) & 0xffff_ffff_0000_0000); const c = (z - df * df) / (s + df); - const w = r64(z) * s + c; - return 2 * (df + w); + const w = rationalApproxBinary64(z) * s + c; + return 2.0 * (df + w); } -test acos { - try expect(acos(@as(f32, 0.0)) == acos32(0.0)); - try expect(acos(@as(f64, 0.0)) == acos64(0.0)); +fn rationalApproxExtended80(z: f80) f80 { + const pS0: f80 = 1.66666666666666666631e-01; + const pS1: f80 = -4.16313987993683104320e-01; + const pS2: f80 = 3.69068046323246813704e-01; + const pS3: f80 = -1.36213932016738603108e-01; + const pS4: f80 = 1.78324189708471965733e-02; + const pS5: f80 = -2.19216428382605211588e-04; + const pS6: f80 = -7.10526623669075243183e-06; + const qS1: f80 = -2.94788392796209867269e+00; + const qS2: f80 = 3.27309890266528636716e+00; + const qS3: f80 = -1.68285799854822427013e+00; + const qS4: f80 = 3.90699412641738801874e-01; + const qS5: f80 = -3.14365703596053263322e-02; + + const p = z * (pS0 + z * (pS1 + z * (pS2 + z * (pS3 + z * (pS4 + z * (pS5 + z * pS6)))))); + const q = 1.0 + z * (qS1 + z * (qS2 + z * (qS3 + z * (qS4 + z * qS5)))); + return p / q; +} + +fn acosExtended80(x: f80) f80 { + const pio2_hi: f80 = 1.57079632679489661926; + const pio2_lo: f80 = -2.50827880633416601173e-20; + + const hx: u80 = @bitCast(x); + const se: u16 = @truncate(hx >> 64); + const e = se & 0x7fff; + + // |x| >= 1 or nan + if (e >= 0x3fff) { + if (x == 1.0) { + return 0.0; + } + if (x == -1.0) { + return 2.0 * pio2_hi + 0x1p-120; + } + return 0.0 / (x - x); + } + // |x| < 0.5 + if (e < 0x3fff - 1) { + if (e < 0x3fff - math.floatFractionalBits(f80)) { + return pio2_hi + 0x1p-120; + } + return pio2_hi - (rationalApproxExtended80(x * x) * x - pio2_lo + x); + } + // x < -0.5 + if (se >> 15 != 0) { + const z = (1 + x) * 0.5; + const s = @sqrt(z); + return 2.0 * (pio2_hi - (rationalApproxExtended80(z) * s - pio2_lo + s)); + } + // x > 0.5 + const z = (1.0 - x) * 0.5; + const s = @sqrt(z); + const hs: u80 = @bitCast(s); + const f: f80 = @bitCast(hs & 0xffff_ffff_ffff_0000_0000); + const c = (z - f * f) / (s + f); + return 2.0 * (rationalApproxExtended80(z) * s + c + f); +} + +fn rationalApproxBinary128(z: f128) f128 { + const pS0: f128 = 1.66666666666666666666666666666700314e-01; + const pS1: f128 = -7.32816946414566252574527475428622708e-01; + const pS2: f128 = 1.34215708714992334609030036562143589e+00; + const pS3: f128 = -1.32483151677116409805070261790752040e+00; + const pS4: f128 = 7.61206183613632558824485341162121989e-01; + const pS5: f128 = -2.56165783329023486777386833928147375e-01; + const pS6: f128 = 4.80718586374448793411019434585413855e-02; + const pS7: f128 = -4.42523267167024279410230886239774718e-03; + const pS8: f128 = 1.44551535183911458253205638280410064e-04; + const pS9: f128 = -2.10558957916600254061591040482706179e-07; + const qS1: f128 = -4.84690167848739751544716485245697428e+00; + const qS2: f128 = 9.96619113536172610135016921140206980e+00; + const qS3: f128 = -1.13177895428973036660836798461641458e+01; + const qS4: f128 = 7.74004374389488266169304117714658761e+00; + const qS5: f128 = -3.25871986053534084709023539900339905e+00; + const qS6: f128 = 8.27830318881232209752469022352928864e-01; + const qS7: f128 = -1.18768052702942805423330715206348004e-01; + const qS8: f128 = 8.32600764660522313269101537926539470e-03; + const qS9: f128 = -1.99407384882605586705979504567947007e-04; + + const p = z * (pS0 + z * (pS1 + z * (pS2 + z * (pS3 + z * (pS4 + z * (pS5 + z * (pS6 + z * (pS7 + z * (pS8 + z * pS9))))))))); + const q = 1.0 + z * (qS1 + z * (qS2 + z * (qS3 + z * (qS4 + z * (qS5 + z * (qS6 + z * (qS7 + z * (qS8 + z * qS9)))))))); + return p / q; +} + +fn acosBinary128(x: f128) f128 { + const pio2_hi: f128 = 1.57079632679489661923132169163975140; + const pio2_lo: f128 = 4.33590506506189051239852201302167613e-35; + + const hx: u128 = @bitCast(x); + const se: u16 = @truncate(hx >> 112); + const e = se & 0x7fff; + + // |x| >= 1 or nan + if (e >= 0x3fff) { + if (x == 1.0) { + return 0.0; + } + if (x == -1.0) { + return 2 * pio2_hi + 0x1p-120; + } + return 0.0 / (x - x); + } + // |x| < 0.5 + if (e < 0x3fff - 1) { + if (e < 0x3fff - math.floatFractionalBits(f128)) { + return pio2_hi + 0x1p-120; + } + return pio2_hi - (rationalApproxBinary128(x * x) * x - pio2_lo + x); + } + // x < -0.5 + if (se >> 15 != 0) { + const z = (1 + x) * 0.5; + const s = @sqrt(z); + return 2 * (pio2_hi - (rationalApproxBinary128(z) * s - pio2_lo + s)); + } + // x > 0.5 + const z = (1.0 - x) * 0.5; + const s = @sqrt(z); + const hs: u128 = @bitCast(s); + const f: f128 = @bitCast(hs & 0xffff_ffff_ffff_ffff_0000_0000_0000_0000); + const c = (z - f * f) / (s + f); + return 2.0 * (rationalApproxBinary128(z) * s + c + f); +} + +test "acosBinary16.special" { + try testing.expectApproxEqAbs(acosBinary16(0x0p+0), 0x1.92p0, math.floatEpsAt(f16, 0x1.92p0)); + try testing.expectApproxEqAbs(acosBinary16(-0x1p+0), 0x1.92p1, math.floatEpsAt(f16, 0x1.92p1)); + try testing.expectEqual(acosBinary16(0x1p+0), 0x0p+0); + try testing.expect(math.isNan(acosBinary16(0x1.004p0))); + try testing.expect(math.isNan(acosBinary16(-0x1.004p0))); + try testing.expect(math.isNan(acosBinary16(math.inf(f16)))); + try testing.expect(math.isNan(acosBinary16(-math.inf(f16)))); + try testing.expect(math.isNan(acosBinary16(math.nan(f16)))); } -test acos32 { - const epsilon = 0.000001; +test "acosBinary16" { + try testing.expectApproxEqAbs(acosBinary16(0x1.db4p-5), 0x1.834p0, math.floatEpsAt(f16, 0x1.834p0)); + try testing.expectApproxEqAbs(acosBinary16(-0x1.068p-2), 0x1.d48p0, math.floatEpsAt(f16, 0x1.d48p0)); + try testing.expectApproxEqAbs(acosBinary16(-0x1.2c4p-3), 0x1.b7cp0, math.floatEpsAt(f16, 0x1.b7cp0)); + try testing.expectApproxEqAbs(acosBinary16(0x1.65p-3), 0x1.654p0, math.floatEpsAt(f16, 0x1.654p0)); + try testing.expectApproxEqAbs(acosBinary16(0x1.dfcp-1), 0x1.6d8p-2, math.floatEpsAt(f16, 0x1.6d8p-2)); + try testing.expectApproxEqAbs(acosBinary16(-0x1.764p-1), 0x1.32p1, math.floatEpsAt(f16, 0x1.32p1)); + try testing.expectApproxEqAbs(acosBinary16(0x1.b18p-3), 0x1.5b8p0, math.floatEpsAt(f16, 0x1.5b8p0)); + try testing.expectApproxEqAbs(acosBinary16(0x1.5acp-3), 0x1.668p0, math.floatEpsAt(f16, 0x1.668p0)); + try testing.expectApproxEqAbs(acosBinary16(-0x1.18cp-1), 0x1.134p1, math.floatEpsAt(f16, 0x1.134p1)); + try testing.expectApproxEqAbs(acosBinary16(-0x1.03p-1), 0x1.0dp1, math.floatEpsAt(f16, 0x1.0dp1)); +} + +test "acosBinary32.special" { + try testing.expectApproxEqAbs(acosBinary32(0x0p+0), 0x1.921fb6p+0, math.floatEpsAt(f32, 0x1.921fb6p+0)); + try testing.expectApproxEqAbs(acosBinary32(-0x1p+0), 0x1.921fb6p+1, math.floatEpsAt(f32, 0x1.921fb6p+1)); + try testing.expectEqual(acosBinary32(0x1p+0), 0x0p+0); + try testing.expect(math.isNan(acosBinary32(0x1.000002p+0))); + try testing.expect(math.isNan(acosBinary32(-0x1.000002p+0))); + try testing.expect(math.isNan(acosBinary32(math.inf(f32)))); + try testing.expect(math.isNan(acosBinary32(-math.inf(f32)))); + try testing.expect(math.isNan(acosBinary32(math.nan(f32)))); +} - try expect(math.approxEqAbs(f32, acos32(0.0), 1.570796, epsilon)); - try expect(math.approxEqAbs(f32, acos32(0.2), 1.369438, epsilon)); - try expect(math.approxEqAbs(f32, acos32(0.3434), 1.220262, epsilon)); - try expect(math.approxEqAbs(f32, acos32(0.5), 1.047198, epsilon)); - try expect(math.approxEqAbs(f32, acos32(0.8923), 0.468382, epsilon)); - try expect(math.approxEqAbs(f32, acos32(-0.2), 1.772154, epsilon)); +test "acosBinary32" { + try testing.expectApproxEqAbs(acosBinary32(-0x1.13284cp-2), 0x1.d7c4e6p+0, math.floatEpsAt(f32, 0x1.d7c4e6p+0)); + try testing.expectApproxEqAbs(acosBinary32(0x1.6ca8ep-1), 0x1.8e6756p-1, math.floatEpsAt(f32, 0x1.8e6756p-1)); + try testing.expectApproxEqAbs(acosBinary32(0x1.c2ca6p-1), 0x1.f9d74cp-2, math.floatEpsAt(f32, 0x1.f9d74cp-2)); + try testing.expectApproxEqAbs(acosBinary32(-0x1.55f12p-1), 0x1.26abdcp+1, math.floatEpsAt(f32, 0x1.26abdcp+1)); + try testing.expectApproxEqAbs(acosBinary32(-0x1.15679ep-2), 0x1.d85a44p+0, math.floatEpsAt(f32, 0x1.d85a44p+0)); + try testing.expectApproxEqAbs(acosBinary32(-0x1.41e132p-5), 0x1.9c2f68p+0, math.floatEpsAt(f32, 0x1.9c2f68p+0)); + try testing.expectApproxEqAbs(acosBinary32(0x1.281b0ep-1), 0x1.e881bp-1, math.floatEpsAt(f32, 0x1.e881bp-1)); + try testing.expectApproxEqAbs(acosBinary32(0x1.b5ce34p-1), 0x1.1713f6p-1, math.floatEpsAt(f32, 0x1.1713f6p-1)); + try testing.expectApproxEqAbs(acosBinary32(-0x1.583482p-3), 0x1.bd5accp+0, math.floatEpsAt(f32, 0x1.bd5accp+0)); + try testing.expectApproxEqAbs(acosBinary32(-0x1.ea8224p-1), 0x1.6ce7d8p+1, math.floatEpsAt(f32, 0x1.6ce7d8p+1)); } -test acos64 { - const epsilon = 0.000001; +test "acosBinary64.special" { + try testing.expectApproxEqAbs(acosBinary64(0x0p+0), 0x1.921fb54442d18p+0, math.floatEpsAt(f64, 0x1.921fb54442d18p+0)); + try testing.expectApproxEqAbs(acosBinary64(-0x1p+0), 0x1.921fb54442d18p+1, math.floatEpsAt(f64, 0x1.921fb54442d18p+1)); + try testing.expectEqual(acosBinary64(0x1p+0), 0x0p+0); + try testing.expect(math.isNan(acosBinary64(0x1.0000000000001p+0))); + try testing.expect(math.isNan(acosBinary64(-0x1.0000000000001p+0))); + try testing.expect(math.isNan(acosBinary64(math.inf(f64)))); + try testing.expect(math.isNan(acosBinary64(-math.inf(f64)))); + try testing.expect(math.isNan(acosBinary64(math.nan(f64)))); +} + +test "acosBinary64" { + try testing.expectApproxEqAbs(acosBinary64(-0x1.13284b2b5006dp-2), 0x1.d7c4e61020905p+0, math.floatEpsAt(f64, 0x1.d7c4e61020905p+0)); + try testing.expectApproxEqAbs(acosBinary64(0x1.6ca8dfb825911p-1), 0x1.8e6756e27c366p-1, math.floatEpsAt(f64, 0x1.8e6756e27c366p-1)); + try testing.expectApproxEqAbs(acosBinary64(0x1.c2ca609de7505p-1), 0x1.f9d748eaf956p-2, math.floatEpsAt(f64, 0x1.f9d748eaf956p-2)); + try testing.expectApproxEqAbs(acosBinary64(-0x1.55f11fba96889p-1), 0x1.26abdc68d07aap+1, math.floatEpsAt(f64, 0x1.26abdc68d07aap+1)); + try testing.expectApproxEqAbs(acosBinary64(-0x1.15679e27084ddp-2), 0x1.d85a44ea44fe4p+0, math.floatEpsAt(f64, 0x1.d85a44ea44fe4p+0)); + try testing.expectApproxEqAbs(acosBinary64(-0x1.41e131b093c41p-5), 0x1.9c2f688eee8abp+0, math.floatEpsAt(f64, 0x1.9c2f688eee8abp+0)); + try testing.expectApproxEqAbs(acosBinary64(0x1.281b0d18455f5p-1), 0x1.e881b1d4eb2a1p-1, math.floatEpsAt(f64, 0x1.e881b1d4eb2a1p-1)); + try testing.expectApproxEqAbs(acosBinary64(0x1.b5ce34a51b239p-1), 0x1.1713f567a87efp-1, math.floatEpsAt(f64, 0x1.1713f567a87efp-1)); + try testing.expectApproxEqAbs(acosBinary64(-0x1.583481079de4dp-3), 0x1.bd5acbe8fcc59p+0, math.floatEpsAt(f64, 0x1.bd5acbe8fcc59p+0)); + try testing.expectApproxEqAbs(acosBinary64(-0x1.ea8223103b871p-1), 0x1.6ce7d66f628e5p+1, math.floatEpsAt(f64, 0x1.6ce7d66f628e5p+1)); +} + +test "acosExtended80.special" { + try testing.expectApproxEqAbs(acosExtended80(0x0p+0), 0x1.921fb54442d1846ap+0, math.floatEpsAt(f80, 0x1.921fb54442d1846ap+0)); + try testing.expectApproxEqAbs(acosExtended80(-0x1p+0), 0x1.921fb54442d1846ap+1, math.floatEpsAt(f80, 0x1.921fb54442d1846ap+1)); + try testing.expectEqual(acosExtended80(0x1p+0), 0x0p+0); + try testing.expect(math.isNan(acosExtended80(0x1.0000000000000002p+0))); + try testing.expect(math.isNan(acosExtended80(-0x1.0000000000000002p+0))); + try testing.expect(math.isNan(acosExtended80(math.inf(f80)))); + try testing.expect(math.isNan(acosExtended80(-math.inf(f80)))); + try testing.expect(math.isNan(acosExtended80(math.nan(f80)))); +} - try expect(math.approxEqAbs(f64, acos64(0.0), 1.570796, epsilon)); - try expect(math.approxEqAbs(f64, acos64(0.2), 1.369438, epsilon)); - try expect(math.approxEqAbs(f64, acos64(0.3434), 1.220262, epsilon)); - try expect(math.approxEqAbs(f64, acos64(0.5), 1.047198, epsilon)); - try expect(math.approxEqAbs(f64, acos64(0.8923), 0.468382, epsilon)); - try expect(math.approxEqAbs(f64, acos64(-0.2), 1.772154, epsilon)); +test "acosExtended80" { + try testing.expectApproxEqAbs(acosExtended80(0x1.72068a321edc8804p-1), 0x1.86b349040d28f794p-1, math.floatEpsAt(f80, 0x1.86b349040d28f794p-1)); + try testing.expectApproxEqAbs(acosExtended80(-0x1.06d0a467d22977ecp-2), 0x1.d4923ade73ec379cp0, math.floatEpsAt(f80, 0x1.d4923ade73ec379cp0)); + try testing.expectApproxEqAbs(acosExtended80(0x1.77d21385faa9798ap-3), 0x1.62e0e8898c6d04f2p0, math.floatEpsAt(f80, 0x1.62e0e8898c6d04f2p0)); + try testing.expectApproxEqAbs(acosExtended80(-0x1.73ee3e8bc2a44dbep-1), 0x1.3123cbcd5dc4bd58p1, math.floatEpsAt(f80, 0x1.3123cbcd5dc4bd58p1)); + try testing.expectApproxEqAbs(acosExtended80(0x1.0a2dd1f6ffcf668ap-1), 0x1.062a6d562df2d316p0, math.floatEpsAt(f80, 0x1.062a6d562df2d316p0)); + try testing.expectApproxEqAbs(acosExtended80(0x1.8e835c490a3aff9ep-3), 0x1.5ffd68b520aa55fap0, math.floatEpsAt(f80, 0x1.5ffd68b520aa55fap0)); + try testing.expectApproxEqAbs(acosExtended80(0x1.add20cdc1565064cp-3), 0x1.5bfe6cabda700684p0, math.floatEpsAt(f80, 0x1.5bfe6cabda700684p0)); + try testing.expectApproxEqAbs(acosExtended80(0x1.21986d43727fca72p-8), 0x1.90fe1c993b571924p0, math.floatEpsAt(f80, 0x1.90fe1c993b571924p0)); + try testing.expectApproxEqAbs(acosExtended80(0x1.d61e0b3fae6a0564p-2), 0x1.18044ccc626e7f9ep0, math.floatEpsAt(f80, 0x1.18044ccc626e7f9ep0)); + try testing.expectApproxEqAbs(acosExtended80(-0x1.171e7c4a41883ccap-4), 0x1.a39513b6c16532b4p0, math.floatEpsAt(f80, 0x1.a39513b6c16532b4p0)); } -test "acos32.special" { - try expect(math.isNan(acos32(-2))); - try expect(math.isNan(acos32(1.5))); +test "acosBinary128.special" { + try testing.expectApproxEqAbs(acosBinary128(0x0p+0), 0x1.921fb54442d18469898cc51701b8p0, math.floatEpsAt(f128, 0x1.921fb54442d18469898cc51701b8p0)); + try testing.expectApproxEqAbs(acosBinary128(-0x1p+0), 0x1.921fb54442d18469898cc51701b8p1, math.floatEpsAt(f128, 0x1.921fb54442d18469898cc51701b8p1)); + try testing.expectEqual(acosBinary128(0x1p+0), 0x0p+0); + try testing.expect(math.isNan(acosBinary128(0x1.0000000000000000000000000001p0))); + try testing.expect(math.isNan(acosBinary128(-0x1.0000000000000000000000000001p0))); + try testing.expect(math.isNan(acosBinary128(math.inf(f128)))); + try testing.expect(math.isNan(acosBinary128(-math.inf(f128)))); + try testing.expect(math.isNan(acosBinary128(math.nan(f128)))); } -test "acos64.special" { - try expect(math.isNan(acos64(-2))); - try expect(math.isNan(acos64(1.5))); +test "acosBinary128" { + try testing.expectApproxEqAbs(acosBinary128(-0x1.511bdb99a3c4373bedf834ef4f68p-1), 0x1.250e9a58f049eeafa99db4360c88p1, math.floatEpsAt(f128, 0x1.250e9a58f049eeafa99db4360c88p1)); + try testing.expectApproxEqAbs(acosBinary128(-0x1.5879cc3ad6dfd2a52e9891c69808p-1), 0x1.2786664b1c676c99437b68590004p1, math.floatEpsAt(f128, 0x1.2786664b1c676c99437b68590004p1)); + try testing.expectApproxEqAbs(acosBinary128(0x1.3f988ba64a7eb97a751c5f0b3077p-1), 0x1.cb190cd361c7c03a09c470b4caebp-1, math.floatEpsAt(f128, 0x1.cb190cd361c7c03a09c470b4caebp-1)); + try testing.expectApproxEqAbs(acosBinary128(-0x1.3f2d96c7768e4c4fa02315727959p-1), 0x1.1f373be697880111758f582b1a96p1, math.floatEpsAt(f128, 0x1.1f373be697880111758f582b1a96p1)); + try testing.expectApproxEqAbs(acosBinary128(0x1.fad303c2e28c1f4d8f9fd0e5686fp-2), 0x1.0d92fd2a0a6ca3e4853c1de9ea6ap0, math.floatEpsAt(f128, 0x1.0d92fd2a0a6ca3e4853c1de9ea6ap0)); + try testing.expectApproxEqAbs(acosBinary128(0x1.ddde322bd1a2ee50c5ba30c9c617p-2), 0x1.15d4b306e16fbf9ea4f29e82b154p0, math.floatEpsAt(f128, 0x1.15d4b306e16fbf9ea4f29e82b154p0)); + try testing.expectApproxEqAbs(acosBinary128(-0x1.b02f6adefcbeb1d48666b827ff17p-1), 0x1.49b0a0355a5539052388e8a6dc11p1, math.floatEpsAt(f128, 0x1.49b0a0355a5539052388e8a6dc11p1)); + try testing.expectApproxEqAbs(acosBinary128(0x1.c8581cce7cd3f6efab0fc60d9b7dp-2), 0x1.1be0b757f4cef022f5d2422b9c78p0, math.floatEpsAt(f128, 0x1.1be0b757f4cef022f5d2422b9c78p0)); + try testing.expectApproxEqAbs(acosBinary128(-0x1.bf887b8c4e33cbef59993056f3dep-1), 0x1.513270e671db2d840f20b0186c2cp1, math.floatEpsAt(f128, 0x1.513270e671db2d840f20b0186c2cp1)); + try testing.expectApproxEqAbs(acosBinary128(0x1.0c0f600ab6f9c84c6102942044cep-3), 0x1.70851a509f0e8bfbe780aa8f29f9p0, math.floatEpsAt(f128, 0x1.70851a509f0e8bfbe780aa8f29f9p0)); } diff --git a/lib/std/math/asin.zig b/lib/std/math/asin.zig index cb4571c24e6d0471c8b945dcc807a0ce3e707a58..113fb584bbc3fac8c8d0636f692ebfe2b4553623 100644 --- a/lib/std/math/asin.zig +++ b/lib/std/math/asin.zig @@ -3,10 +3,14 @@ // // https://git.musl-libc.org/cgit/musl/tree/src/math/asinf.c // https://git.musl-libc.org/cgit/musl/tree/src/math/asin.c +// https://git.musl-libc.org/cgit/musl/tree/src/math/asinl.c const std = @import("../std.zig"); const math = std.math; -const expect = std.testing.expect; +const mem = std.mem; +const testing = std.testing; +const builtin = @import("builtin"); +const native_endian = builtin.cpu.arch.endian(); /// Returns the arc-sin of x. /// @@ -16,62 +20,101 @@ const expect = std.testing.expect; pub fn asin(x: anytype) @TypeOf(x) { const T = @TypeOf(x); return switch (T) { - f32 => asin32(x), - f64 => asin64(x), + f16 => asinBinary16(x), + f32 => asinBinary32(x), + f64 => asinBinary64(x), + f80 => asinExtended80(x), + f128 => asinBinary128(x), else => @compileError("asin not implemented for " ++ @typeName(T)), }; } -fn r32(z: f32) f32 { - const pS0 = 1.6666586697e-01; - const pS1 = -4.2743422091e-02; - const pS2 = -8.6563630030e-03; - const qS1 = -7.0662963390e-01; +fn approxBinary16(z: f32) f32 { + const S0: f32 = 1.0000001e0; + const S1: f32 = 1.6664918e-1; + const S2: f32 = 7.55022e-2; + const S3: f32 = 3.9513987e-2; + const S4: f32 = 5.0883885e-2; + return S0 + z * (S1 + z * (S2 + z * (S3 + z * S4))); +} + +fn asinBinary16(x: f16) f16 { + const pio2: f32 = math.pi / 2.0; + + const hx: u16 = @bitCast(x); + const ix = hx & 0x7fff; + + // |x| >= 1 + if (ix >= 0x3c00) { + // |x| == 1 + if (ix == 0x3c00) { + // asin(+-1) = +-pi/2 with inexact + return @floatCast(x * pio2 + 0x1.0p-120); + } + // asin(|x| > 1) is nan + return 0.0 / (x - x); + } + + // |x| < 0.5 + if (ix < 0x3800) { + return @floatCast(x * approxBinary16(x * x)); + } + + // 1 > |x| >= 0.5 + const z = (1.0 - @abs(x)) * 0.5; + const s = @sqrt(z); + const x_local = pio2 - 2.0 * s * approxBinary16(z); + if (hx >> 15 != 0) { + return @floatCast(-x_local); + } + return @floatCast(x_local); +} + +fn rationalApproxBinary32(z: f32) f32 { + const pS0: f32 = 1.6666586697e-01; + const pS1: f32 = -4.2743422091e-02; + const pS2: f32 = -8.6563630030e-03; + const qS1: f32 = -7.0662963390e-01; const p = z * (pS0 + z * (pS1 + z * pS2)); const q = 1.0 + z * qS1; return p / q; } -fn asin32(x: f32) f32 { - const pio2 = 1.570796326794896558e+00; +fn asinBinary32(x: f32) f32 { + const pio2: f64 = 1.570796326794896558e+00; - const hx: u32 = @as(u32, @bitCast(x)); - const ix: u32 = hx & 0x7FFFFFFF; + const hx: u32 = @bitCast(x); + const ix = hx & 0x7fff_ffff; // |x| >= 1 - if (ix >= 0x3F800000) { - // |x| >= 1 - if (ix == 0x3F800000) { - return x * pio2 + 0x1.0p-120; // asin(+-1) = +-pi/2 with inexact - } else { - return math.nan(f32); // asin(|x| > 1) is nan + if (ix >= 0x3f80_0000) { + // |x| == 1 + if (ix == 0x3f80_0000) { + // asin(+-1) = +-pi/2 with inexact + return @floatCast(@as(f64, @floatCast(x)) * pio2 + 0x1.0p-120); } + // asin(|x| > 1) is nan + return 0.0 / (x - x); } // |x| < 0.5 - if (ix < 0x3F000000) { + if (ix < 0x3f00_0000) { // 0x1p-126 <= |x| < 0x1p-12 - if (ix < 0x39800000 and ix >= 0x00800000) { + if (ix < 0x3980_0000 and ix >= 0x0080_0000) { return x; - } else { - return x + x * r32(x * x); } + return x + x * rationalApproxBinary32(x * x); } // 1 > |x| >= 0.5 - const z = (1 - @abs(x)) * 0.5; - const s = @sqrt(z); - const fx = pio2 - 2 * (s + s * r32(z)); - - if (hx >> 31 != 0) { - return -fx; - } else { - return fx; - } + const z = (1.0 - @abs(x)) * 0.5; + const s: f64 = @floatCast(@sqrt(z)); + const x_local: f32 = @floatCast(pio2 - 2.0 * (s + s * @as(f64, @floatCast(rationalApproxBinary32(z))))); + return if (hx >> 31 != 0) -x_local else x_local; } -fn r64(z: f64) f64 { +fn rationalApproxBinary64(z: f64) f64 { const pS0: f64 = 1.66666666666666657415e-01; const pS1: f64 = -3.25565818622400915405e-01; const pS2: f64 = 2.01212532134862925881e-01; @@ -88,96 +131,307 @@ fn r64(z: f64) f64 { return p / q; } -fn asin64(x: f64) f64 { +fn asinBinary64(x: f64) f64 { const pio2_hi: f64 = 1.57079632679489655800e+00; const pio2_lo: f64 = 6.12323399573676603587e-17; - const ux = @as(u64, @bitCast(x)); - const hx = @as(u32, @intCast(ux >> 32)); - const ix = hx & 0x7FFFFFFF; + const hx: u32 = @intCast(@as(u64, @bitCast(x)) >> 32); + const ix = hx & 0x7fffffff; // |x| >= 1 or nan - if (ix >= 0x3FF00000) { - const lx = @as(u32, @intCast(ux & 0xFFFFFFFF)); - + if (ix >= 0x3ff0_0000) { + const lx: u32 = @truncate(@as(u64, @bitCast(x))); // asin(1) = +-pi/2 with inexact - if ((ix - 0x3FF00000) | lx == 0) { + if ((ix - 0x3ff0_0000 | lx) == 0) { return x * pio2_hi + 0x1.0p-120; - } else { - return math.nan(f64); } + return 0.0 / (x - x); } // |x| < 0.5 - if (ix < 0x3FE00000) { + if (ix < 0x3fe0_0000) { // if 0x1p-1022 <= |x| < 0x1p-26 avoid raising overflow - if (ix < 0x3E500000 and ix >= 0x00100000) { + if (ix < 0x3e50_0000 and ix >= 0x0010_0000) { return x; - } else { - return x + x * r64(x * x); } + return x + x * rationalApproxBinary64(x * x); } // 1 > |x| >= 0.5 - const z = (1 - @abs(x)) * 0.5; + const z = (1.0 - @abs(x)) * 0.5; const s = @sqrt(z); - const r = r64(z); - var fx: f64 = undefined; - + const r = rationalApproxBinary64(z); // |x| > 0.975 - if (ix >= 0x3FEF3333) { - fx = pio2_hi - 2 * (s + s * r); - } else { - const jx = @as(u64, @bitCast(s)); - const df = @as(f64, @bitCast(jx & 0xFFFFFFFF00000000)); - const c = (z - df * df) / (s + df); - fx = 0.5 * pio2_hi - (2 * s * r - (pio2_lo - 2 * c) - (0.5 * pio2_hi - 2 * df)); + if (ix >= 0x3fef_3333) { + const x_local = pio2_hi - (2 * (s + s * r) - pio2_lo); + return if (hx >> 31 != 0) -x_local else x_local; + } + // f+c = sqrt(z) + const hs: u64 = @bitCast(s); + const f: f64 = @bitCast(hs & 0xffff_ffff_0000_0000); + const c: f64 = (z - f * f) / (s + f); + const x_local = 0.5 * pio2_hi - (2.0 * s * r - (pio2_lo - 2.0 * c) - (0.5 * pio2_hi - 2.0 * f)); + return if (hx >> 31 != 0) -x_local else x_local; +} + +fn rationalApproxExtended80(z: f80) f80 { + const pS0: f80 = 1.66666666666666666631e-01; + const pS1: f80 = -4.16313987993683104320e-01; + const pS2: f80 = 3.69068046323246813704e-01; + const pS3: f80 = -1.36213932016738603108e-01; + const pS4: f80 = 1.78324189708471965733e-02; + const pS5: f80 = -2.19216428382605211588e-04; + const pS6: f80 = -7.10526623669075243183e-06; + const qS1: f80 = -2.94788392796209867269e+00; + const qS2: f80 = 3.27309890266528636716e+00; + const qS3: f80 = -1.68285799854822427013e+00; + const qS4: f80 = 3.90699412641738801874e-01; + const qS5: f80 = -3.14365703596053263322e-02; + + const p = z * (pS0 + z * (pS1 + z * (pS2 + z * (pS3 + z * (pS4 + z * (pS5 + z * pS6)))))); + const q = 1.0 + z * (qS1 + z * (qS2 + z * (qS3 + z * (qS4 + z * qS5)))); + return p / q; +} + +fn asinExtended80(x: f80) f80 { + const pio2_hi: f80 = 1.57079632679489661926; + const pio2_lo: f80 = -2.50827880633416601173e-20; + + const hx: u80 = @bitCast(x); + const se: u16 = @truncate(hx >> 64); + const e = se & 0x7fff; + const sign = se >> 15 != 0; + + // |x| >= 1 or nan + if (e >= 0x3fff) { + // asin(+-1)=+-pi/2 with inexact + if (x == 1.0 or x == -1.0) { + return x * pio2_hi + 0x1p-120; + } + return 0.0 / (x - x); + } + + // |x| < 0.5 + if (e < 0x3fff - 1) { + if (e < 0x3fff - (math.floatMantissaBits(f80) + 1) / 2) { + // return x with inexact if x!=0 + mem.doNotOptimizeAway(x + 0x1p120); + return x; + } + return x + x * rationalApproxExtended80(x * x); + } + + // 1 > |x| >= 0.5 + const z = (1.0 - @abs(x)) * 0.5; + const s = @sqrt(z); + const r = rationalApproxExtended80(z); + + const m: u64 = @truncate(hx & 0x0000_ffff_ffff_ffff_ffff); + if ((m >> 56) >= 0xf7) { + const x_local = pio2_hi - (2.0 * (s + s * r) - pio2_lo); + return if (sign) -x_local else x_local; + } + + const hs: u80 = @bitCast(s); + const f: f80 = @bitCast(hs & 0xffff_ffff_ffff_0000_0000); + const c = (z - f * f) / (s + f); + const x_local = 0.5 * pio2_hi - (2.0 * s * r - (pio2_lo - 2.0 * c) - (0.5 * pio2_hi - 2.0 * f)); + return if (sign) -x_local else x_local; +} + +fn rationalApproxBinary128(z: f128) f128 { + const pS0: f128 = 1.66666666666666666666666666666700314e-01; + const pS1: f128 = -7.32816946414566252574527475428622708e-01; + const pS2: f128 = 1.34215708714992334609030036562143589e+00; + const pS3: f128 = -1.32483151677116409805070261790752040e+00; + const pS4: f128 = 7.61206183613632558824485341162121989e-01; + const pS5: f128 = -2.56165783329023486777386833928147375e-01; + const pS6: f128 = 4.80718586374448793411019434585413855e-02; + const pS7: f128 = -4.42523267167024279410230886239774718e-03; + const pS8: f128 = 1.44551535183911458253205638280410064e-04; + const pS9: f128 = -2.10558957916600254061591040482706179e-07; + const qS1: f128 = -4.84690167848739751544716485245697428e+00; + const qS2: f128 = 9.96619113536172610135016921140206980e+00; + const qS3: f128 = -1.13177895428973036660836798461641458e+01; + const qS4: f128 = 7.74004374389488266169304117714658761e+00; + const qS5: f128 = -3.25871986053534084709023539900339905e+00; + const qS6: f128 = 8.27830318881232209752469022352928864e-01; + const qS7: f128 = -1.18768052702942805423330715206348004e-01; + const qS8: f128 = 8.32600764660522313269101537926539470e-03; + const qS9: f128 = -1.99407384882605586705979504567947007e-04; + + const p = z * (pS0 + z * (pS1 + z * (pS2 + z * (pS3 + z * (pS4 + z * (pS5 + z * (pS6 + z * (pS7 + z * (pS8 + z * pS9))))))))); + const q = 1.0 + z * (qS1 + z * (qS2 + z * (qS3 + z * (qS4 + z * (qS5 + z * (qS6 + z * (qS7 + z * (qS8 + z * qS9)))))))); + return p / q; +} + +fn asinBinary128(x: f128) f128 { + const pio2_hi: f128 = 1.57079632679489661923132169163975140; + const pio2_lo: f128 = 4.33590506506189051239852201302167613e-35; + + const hx: u128 = @bitCast(x); + const se: u16 = @truncate(hx >> 112); + const e = se & 0x7fff; + const sign = se >> 15 != 0; + + // |x| >= 1 or nan + if (e >= 0x3fff) { + // asin(+-1)=+-pi/2 with inexact + if (x == 1.0 or x == -1.0) { + return x * pio2_hi + 0x1p-120; + } + return 0.0 / (x - x); } - if (hx >> 31 != 0) { - return -fx; - } else { - return fx; + // |x| < 0.5 + if (e < 0x3fff - 1) { + if (e < 0x3fff - (math.floatMantissaBits(f128) + 2) / 2) { + // return x with inexact if x!=0 + mem.doNotOptimizeAway(x + 0x1p120); + return x; + } + return x + x * rationalApproxBinary128(x * x); } + + // 1 > |x| >= 0.5 + const z = (1.0 - @abs(x)) * 0.5; + const s = @sqrt(z); + const r = rationalApproxBinary128(z); + + const top: u16 = @truncate((hx >> 96) & 0x0000_ffff); + if (top >= 0xee00) { + const x_local = pio2_hi - (2.0 * (s + s * r) - pio2_lo); + return if (sign) -x_local else x_local; + } + + const hs: u128 = @bitCast(s); + const f: f128 = @bitCast(hs & 0xffff_ffff_ffff_ffff_0000_0000_0000_0000); + const c = (z - f * f) / (s + f); + const x_local = 0.5 * pio2_hi - (2.0 * s * r - (pio2_lo - 2.0 * c) - (0.5 * pio2_hi - 2.0 * f)); + return if (sign) -x_local else x_local; } -test asin { - try expect(asin(@as(f32, 0.0)) == asin32(0.0)); - try expect(asin(@as(f64, 0.0)) == asin64(0.0)); +test "asinBinary16.special" { + try testing.expectApproxEqAbs(asinBinary16(0x1p+0), 0x1.92p0, math.floatEpsAt(f16, 0x1.92p0)); + try testing.expectApproxEqAbs(asinBinary16(-0x1p+0), -0x1.92p0, math.floatEpsAt(f16, -0x1.92p0)); + try testing.expectEqual(asinBinary16(0x0p+0), 0x0p+0); + try testing.expectEqual(asinBinary16(-0x0p+0), 0x0p+0); + try testing.expect(math.isNan(asinBinary16(0x1.004p0))); + try testing.expect(math.isNan(asinBinary16(-0x1.004p0))); + try testing.expect(math.isNan(asinBinary16(math.inf(f16)))); + try testing.expect(math.isNan(asinBinary16(-math.inf(f16)))); + try testing.expect(math.isNan(asinBinary16(math.nan(f16)))); } -test asin32 { - const epsilon = 0.000001; +test "asinBinary16" { + try testing.expectApproxEqAbs(asinBinary16(-0x1.e4cp-6), -0x1.e4cp-6, math.floatEpsAt(f16, -0x1.e4cp-6)); + try testing.expectApproxEqAbs(asinBinary16(0x1.d68p-1), 0x1.2a8p0, math.floatEpsAt(f16, 0x1.2a8p0)); + try testing.expectApproxEqAbs(asinBinary16(-0x1.a4cp-1), -0x1.eep-1, math.floatEpsAt(f16, -0x1.eep-1)); + try testing.expectApproxEqAbs(asinBinary16(-0x1.0a4p-2), -0x1.0d4p-2, math.floatEpsAt(f16, -0x1.0d4p-2)); + try testing.expectApproxEqAbs(asinBinary16(0x1.28cp-1), 0x1.3c8p-1, math.floatEpsAt(f16, 0x1.3c8p-1)); + try testing.expectApproxEqAbs(asinBinary16(0x1.284p-3), 0x1.298p-3, math.floatEpsAt(f16, 0x1.298p-3)); + try testing.expectApproxEqAbs(asinBinary16(-0x1.574p-1), -0x1.784p-1, math.floatEpsAt(f16, -0x1.784p-1)); + try testing.expectApproxEqAbs(asinBinary16(-0x1.4ccp-1), -0x1.6a4p-1, math.floatEpsAt(f16, -0x1.6a4p-1)); + try testing.expectApproxEqAbs(asinBinary16(0x1.a18p-1), 0x1.e84p-1, math.floatEpsAt(f16, 0x1.e84p-1)); + try testing.expectApproxEqAbs(asinBinary16(0x1.7a8p-2), 0x1.83cp-2, math.floatEpsAt(f16, 0x1.83cp-2)); +} + +test "asinBinary32.special" { + try testing.expectApproxEqAbs(asinBinary32(0x1p+0), 0x1.921fb6p+0, math.floatEpsAt(f32, 0x1.921fb6p+0)); + try testing.expectApproxEqAbs(asinBinary32(-0x1p+0), -0x1.921fb6p+0, math.floatEpsAt(f32, -0x1.921fb6p+0)); + try testing.expectEqual(asinBinary32(0x0p+0), 0x0p+0); + try testing.expectEqual(asinBinary32(-0x0p+0), 0x0p+0); + try testing.expect(math.isNan(asinBinary32(0x1.000002p+0))); + try testing.expect(math.isNan(asinBinary32(-0x1.000002p+0))); + try testing.expect(math.isNan(asinBinary32(math.inf(f32)))); + try testing.expect(math.isNan(asinBinary32(-math.inf(f32)))); + try testing.expect(math.isNan(asinBinary32(math.nan(f32)))); +} + +test "asinBinary32" { + try testing.expectApproxEqAbs(asinBinary32(-0x1.4c2906p-4), -0x1.4c868p-4, math.floatEpsAt(f32, -0x1.4c868p-4)); + try testing.expectApproxEqAbs(asinBinary32(0x1.05fcfap-1), 0x1.130648p-1, math.floatEpsAt(f32, 0x1.130648p-1)); + try testing.expectApproxEqAbs(asinBinary32(0x1.fab976p-2), 0x1.090abcp-1, math.floatEpsAt(f32, 0x1.090abcp-1)); + try testing.expectApproxEqAbs(asinBinary32(0x1.8b4b8cp-1), 0x1.c39fa2p-1, math.floatEpsAt(f32, 0x1.c39fa2p-1)); + try testing.expectApproxEqAbs(asinBinary32(0x1.7117c2p-1), 0x1.9c332p-1, math.floatEpsAt(f32, 0x1.9c332p-1)); + try testing.expectApproxEqAbs(asinBinary32(0x1.e5e112p-5), 0x1.e62a1cp-5, math.floatEpsAt(f32, 0x1.e62a1cp-5)); + try testing.expectApproxEqAbs(asinBinary32(-0x1.07673p-2), -0x1.0a65dep-2, math.floatEpsAt(f32, -0x1.0a65dep-2)); + try testing.expectApproxEqAbs(asinBinary32(-0x1.2108dep-2), -0x1.25046p-2, math.floatEpsAt(f32, -0x1.25046p-2)); + try testing.expectApproxEqAbs(asinBinary32(-0x1.4e6e6cp-1), -0x1.6c6f0cp-1, math.floatEpsAt(f32, -0x1.6c6f0cp-1)); + try testing.expectApproxEqAbs(asinBinary32(0x1.22a16ap-1), 0x1.350f7ap-1, math.floatEpsAt(f32, 0x1.350f7ap-1)); +} + +test "asinBinary64.special" { + try testing.expectApproxEqAbs(asinBinary64(0x1p+0), 0x1.921fb54442d18p+0, math.floatEpsAt(f64, 0x1.921fb54442d18p+0)); + try testing.expectApproxEqAbs(asinBinary64(-0x1p+0), -0x1.921fb54442d18p+0, math.floatEpsAt(f64, -0x1.921fb54442d18p+0)); + try testing.expectEqual(asinBinary64(0x0p+0), 0x0p+0); + try testing.expectEqual(asinBinary64(-0x0p+0), 0x0p+0); + try testing.expect(math.isNan(asinBinary64(0x1.000002p+0))); + try testing.expect(math.isNan(asinBinary64(-0x1.000002p+0))); + try testing.expect(math.isNan(asinBinary64(math.inf(f64)))); + try testing.expect(math.isNan(asinBinary64(-math.inf(f64)))); + try testing.expect(math.isNan(asinBinary64(math.nan(f64)))); +} - try expect(math.approxEqAbs(f32, asin32(0.0), 0.0, epsilon)); - try expect(math.approxEqAbs(f32, asin32(0.2), 0.201358, epsilon)); - try expect(math.approxEqAbs(f32, asin32(-0.2), -0.201358, epsilon)); - try expect(math.approxEqAbs(f32, asin32(0.3434), 0.350535, epsilon)); - try expect(math.approxEqAbs(f32, asin32(0.5), 0.523599, epsilon)); - try expect(math.approxEqAbs(f32, asin32(0.8923), 1.102415, epsilon)); +test "asinBinary64" { + try testing.expectApproxEqAbs(asinBinary64(0x1.e674fba3e40d5p-2), 0x1.fae86c5941692p-2, math.floatEpsAt(f64, 0x1.fae86c5941692p-2)); + try testing.expectApproxEqAbs(asinBinary64(-0x1.30fd0566fd979p-1), -0x1.46b6ad730c93ap-1, math.floatEpsAt(f64, -0x1.46b6ad730c93ap-1)); + try testing.expectApproxEqAbs(asinBinary64(0x1.6444a25abfeaap-2), 0x1.6be0be8074eep-2, math.floatEpsAt(f64, 0x1.6be0be8074eep-2)); + try testing.expectApproxEqAbs(asinBinary64(0x1.40a53228d1a13p-1), 0x1.5a7e98f53f717p-1, math.floatEpsAt(f64, 0x1.5a7e98f53f717p-1)); + try testing.expectApproxEqAbs(asinBinary64(-0x1.ccc6d64845cfdp-1), -0x1.1ea2602d14e8p0, math.floatEpsAt(f64, -0x1.1ea2602d14e8p0)); + try testing.expectApproxEqAbs(asinBinary64(-0x1.94bd91b7fc74bp-1), -0x1.d2c2634193158p-1, math.floatEpsAt(f64, -0x1.d2c2634193158p-1)); + try testing.expectApproxEqAbs(asinBinary64(-0x1.8d741b5797fccp-2), -0x1.982d5f1895d2p-2, math.floatEpsAt(f64, -0x1.982d5f1895d2p-2)); + try testing.expectApproxEqAbs(asinBinary64(-0x1.3e8e7e15881c5p-3), -0x1.3fdaf7dfdc864p-3, math.floatEpsAt(f64, -0x1.3fdaf7dfdc864p-3)); + try testing.expectApproxEqAbs(asinBinary64(-0x1.88222d8ab8ca9p-2), -0x1.9269540735b7bp-2, math.floatEpsAt(f64, -0x1.9269540735b7bp-2)); + try testing.expectApproxEqAbs(asinBinary64(-0x1.41c0e9babcbd2p-2), -0x1.474c4c6625527p-2, math.floatEpsAt(f64, -0x1.474c4c6625527p-2)); } -test asin64 { - const epsilon = 0.000001; +test "asinExtended80.special" { + try testing.expectApproxEqAbs(asinExtended80(0x1p+0), 0x1.921fb54442d1846ap+0, math.floatEpsAt(f80, 0x1.921fb54442d1846ap+0)); + try testing.expectApproxEqAbs(asinExtended80(-0x1p+0), -0x1.921fb54442d1846ap+0, math.floatEpsAt(f80, -0x1.921fb54442d1846ap+0)); + try testing.expectEqual(asinExtended80(0x0p+0), 0x0p+0); + try testing.expectEqual(asinExtended80(-0x0p+0), 0x0p+0); + try testing.expect(math.isNan(asinExtended80(0x1.0000000000000002p+0))); + try testing.expect(math.isNan(asinExtended80(-0x1.0000000000000002p+0))); + try testing.expect(math.isNan(asinExtended80(math.inf(f80)))); + try testing.expect(math.isNan(asinExtended80(-math.inf(f80)))); + try testing.expect(math.isNan(asinExtended80(math.nan(f80)))); +} - try expect(math.approxEqAbs(f64, asin64(0.0), 0.0, epsilon)); - try expect(math.approxEqAbs(f64, asin64(0.2), 0.201358, epsilon)); - try expect(math.approxEqAbs(f64, asin64(-0.2), -0.201358, epsilon)); - try expect(math.approxEqAbs(f64, asin64(0.3434), 0.350535, epsilon)); - try expect(math.approxEqAbs(f64, asin64(0.5), 0.523599, epsilon)); - try expect(math.approxEqAbs(f64, asin64(0.8923), 1.102415, epsilon)); +test "asinExtended80" { + try testing.expectApproxEqAbs(asinExtended80(0x1.63cf98bc52ce0da8p-9), 0x1.63cfb560149daa9p-9, math.floatEpsAt(f80, 0x1.63cfb560149daa9p-9)); + try testing.expectApproxEqAbs(asinExtended80(-0x1.0473756f7ae930dp-1), -0x1.113cbacd8cd1b96cp-1, math.floatEpsAt(f80, -0x1.113cbacd8cd1b96cp-1)); + try testing.expectApproxEqAbs(asinExtended80(-0x1.2310057e005cc288p-2), -0x1.2721b231d197b064p-2, math.floatEpsAt(f80, -0x1.2721b231d197b064p-2)); + try testing.expectApproxEqAbs(asinExtended80(0x1.f13b03bd685d96eap-1), 0x1.547c408c5d2b05aap0, math.floatEpsAt(f80, 0x1.547c408c5d2b05aap0)); + try testing.expectApproxEqAbs(asinExtended80(-0x1.d5c507e3ef84041cp-1), -0x1.296b76bfadbb5cecp0, math.floatEpsAt(f80, -0x1.296b76bfadbb5cecp0)); + try testing.expectApproxEqAbs(asinExtended80(0x1.8222cbc9147153d8p-1), 0x1.b572da8729a84f2ap-1, math.floatEpsAt(f80, 0x1.b572da8729a84f2ap-1)); + try testing.expectApproxEqAbs(asinExtended80(-0x1.42c9e6b4a088a246p-11), -0x1.42c9e80ac0524dap-11, math.floatEpsAt(f80, -0x1.42c9e80ac0524dap-11)); + try testing.expectApproxEqAbs(asinExtended80(-0x1.8f78d49deadb521cp-3), -0x1.920ca86aef6c3028p-3, math.floatEpsAt(f80, -0x1.920ca86aef6c3028p-3)); + try testing.expectApproxEqAbs(asinExtended80(-0x1.ab98792783515774p-2), -0x1.b91cb4f7204d92fp-2, math.floatEpsAt(f80, -0x1.b91cb4f7204d92fp-2)); + try testing.expectApproxEqAbs(asinExtended80(-0x1.104fe30cef6800aap-1), -0x1.1f20815fdc4c5304p-1, math.floatEpsAt(f80, -0x1.1f20815fdc4c5304p-1)); } -test "asin32.special" { - try expect(math.isPositiveZero(asin32(0.0))); - try expect(math.isNegativeZero(asin32(-0.0))); - try expect(math.isNan(asin32(-2))); - try expect(math.isNan(asin32(1.5))); +test "asinBinary128.special" { + try testing.expectApproxEqAbs(asinBinary128(0x1p+0), 0x1.921fb54442d18469898cc51701b8p0, math.floatEpsAt(f128, 0x1.921fb54442d18469898cc51701b8p0)); + try testing.expectApproxEqAbs(asinBinary128(-0x1p+0), -0x1.921fb54442d18469898cc51701b8p0, math.floatEpsAt(f128, -0x1.921fb54442d18469898cc51701b8p0)); + try testing.expectEqual(asinBinary128(0x0p+0), 0x0p+0); + try testing.expectEqual(asinBinary128(-0x0p+0), 0x0p+0); + try testing.expect(math.isNan(asinBinary128(0x1.0000000000000000000000000001p0))); + try testing.expect(math.isNan(asinBinary128(-0x1.0000000000000000000000000001p0))); + try testing.expect(math.isNan(asinBinary128(math.inf(f128)))); + try testing.expect(math.isNan(asinBinary128(-math.inf(f128)))); + try testing.expect(math.isNan(asinBinary128(math.nan(f128)))); } -test "asin64.special" { - try expect(math.isPositiveZero(asin64(0.0))); - try expect(math.isNegativeZero(asin64(-0.0))); - try expect(math.isNan(asin64(-2))); - try expect(math.isNan(asin64(1.5))); +test "asinBinary128" { + try testing.expectApproxEqAbs(asinBinary128(0x1.85868ce287ca0196b01c25fec5ffp-3), 0x1.87e9c740d7837f8e8fa667988fbep-3, math.floatEpsAt(f128, 0x1.87e9c740d7837f8e8fa667988fbep-3)); + try testing.expectApproxEqAbs(asinBinary128(0x1.8718d6d30b4daed08d04ef59f478p-1), 0x1.bd11a474e864213b48e0f005f1f4p-1, math.floatEpsAt(f128, 0x1.bd11a474e864213b48e0f005f1f4p-1)); + try testing.expectApproxEqAbs(asinBinary128(0x1.11a67640cd7f0ba5d5e362f3abfap-1), 0x1.20b56f8b42649fe72d1f8d68a378p-1, math.floatEpsAt(f128, 0x1.20b56f8b42649fe72d1f8d68a378p-1)); + try testing.expectApproxEqAbs(asinBinary128(-0x1.bd13bf14a9dce22188e52650daa7p-1), -0x1.0dc3a7ddb9736e5ad699bf338566p0, math.floatEpsAt(f128, -0x1.0dc3a7ddb9736e5ad699bf338566p0)); + try testing.expectApproxEqAbs(asinBinary128(-0x1.dee0bc217fc462af57c484eefa71p-2), -0x1.f250716038f70fa50a5826c03802p-2, math.floatEpsAt(f128, -0x1.f250716038f70fa50a5826c03802p-2)); + try testing.expectApproxEqAbs(asinBinary128(-0x1.ea7df9139371c10b9d6fd2bbccd3p-1), -0x1.47a8b4cdd327f90056722feddbabp0, math.floatEpsAt(f128, -0x1.47a8b4cdd327f90056722feddbabp0)); + try testing.expectApproxEqAbs(asinBinary128(0x1.04aaea6de3b5a616460702f26dfcp-2), 0x1.079178d52be662dec67e2cd7f6e9p-2, math.floatEpsAt(f128, 0x1.079178d52be662dec67e2cd7f6e9p-2)); + try testing.expectApproxEqAbs(asinBinary128(-0x1.c7ea85e6b61be666435a7d99444cp-1), -0x1.192df5a8d71702cf1e27014887b2p0, math.floatEpsAt(f128, -0x1.192df5a8d71702cf1e27014887b2p0)); + try testing.expectApproxEqAbs(asinBinary128(-0x1.6e210214e40edf6c8479998189d1p-1), -0x1.97f1092fd94ac0fdfddae2e1222bp-1, math.floatEpsAt(f128, -0x1.97f1092fd94ac0fdfddae2e1222bp-1)); + try testing.expectApproxEqAbs(asinBinary128(-0x1.95061bf93ed6986a45d20f0e1064p-3), -0x1.97b62bc5ae6512093828828325e1p-3, math.floatEpsAt(f128, -0x1.97b62bc5ae6512093828828325e1p-3)); } -- 2.54.0 From 57742480414b2060411b0b07f258e9187b4e8ca0 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 28 Jan 2026 15:32:40 -0800 Subject: [PATCH 067/499] std.os.windows: delete unused CreateSymbolicLink --- lib/std/Io/Threaded.zig | 3 +- lib/std/os/windows.zig | 117 ---------------------------------------- 2 files changed, 1 insertion(+), 119 deletions(-) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 38f6199f464933112ae6987e13852585805e0428..16608c50a9bdbca1a73817642705f9420b162cfa 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -6353,8 +6353,7 @@ fn dirSymLinkWindows( // Target path does not use sliceToPrefixedFileW because certain paths // are handled differently when creating a symlink than they would be - // when converting to an NT namespaced path. CreateSymbolicLink in - // symLinkW will handle the necessary conversion. + // when converting to an NT namespaced path. var target_path_w: w.PathSpace = undefined; target_path_w.len = try w.wtf8ToWtf16Le(&target_path_w.data, target_path); target_path_w.data[target_path_w.len] = 0; diff --git a/lib/std/os/windows.zig b/lib/std/os/windows.zig index 262eaef049a0a6cb198b70195ad85f8356916346..7414ec060876b4b837d05dbc15fba9345b66a9fa 100644 --- a/lib/std/os/windows.zig +++ b/lib/std/os/windows.zig @@ -2908,123 +2908,6 @@ pub fn GetCurrentDirectory(buffer: []u8) GetCurrentDirectoryError![]u8 { return buffer[0..end_index]; } -pub const CreateSymbolicLinkError = error{ - AccessDenied, - PathAlreadyExists, - FileNotFound, - NameTooLong, - NoDevice, - NetworkNotFound, - BadPathName, - Unexpected, -}; - -/// Needs either: -/// - `SeCreateSymbolicLinkPrivilege` privilege -/// or -/// - Developer mode on Windows 10 -/// otherwise fails with `error.AccessDenied`. In which case `sym_link_path` may still -/// be created on the file system but will lack reparse processing data applied to it. -pub fn CreateSymbolicLink( - dir: ?HANDLE, - sym_link_path: []const u16, - target_path: [:0]const u16, - is_directory: bool, -) CreateSymbolicLinkError!void { - const SYMLINK_DATA = extern struct { - ReparseTag: IO_REPARSE_TAG, - ReparseDataLength: USHORT, - Reserved: USHORT, - SubstituteNameOffset: USHORT, - SubstituteNameLength: USHORT, - PrintNameOffset: USHORT, - PrintNameLength: USHORT, - Flags: ULONG, - }; - - const symlink_handle = OpenFile(sym_link_path, .{ - .access_mask = .{ - .STANDARD = .{ .SYNCHRONIZE = true }, - .GENERIC = .{ .WRITE = true, .READ = true }, - }, - .dir = dir, - .creation = .CREATE, - .filter = if (is_directory) .dir_only else .non_directory_only, - }) catch |err| switch (err) { - error.IsDir => return error.PathAlreadyExists, - error.NotDir => return error.Unexpected, - error.WouldBlock => return error.Unexpected, - error.PipeBusy => return error.Unexpected, - error.NoDevice => return error.Unexpected, - error.AntivirusInterference => return error.Unexpected, - else => |e| return e, - }; - defer CloseHandle(symlink_handle); - - // Relevant portions of the documentation: - // > Relative links are specified using the following conventions: - // > - Root relative—for example, "\Windows\System32" resolves to "current drive:\Windows\System32". - // > - Current working directory–relative—for example, if the current working directory is - // > C:\Windows\System32, "C:File.txt" resolves to "C:\Windows\System32\File.txt". - // > Note: If you specify a current working directory–relative link, it is created as an absolute - // > link, due to the way the current working directory is processed based on the user and the thread. - // https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createsymboliclinkw - var is_target_absolute = false; - const final_target_path = target_path: { - if (hasCommonNtPrefix(u16, target_path)) { - // Already an NT path, no need to do anything to it - break :target_path target_path; - } else { - switch (std.fs.path.getWin32PathType(u16, target_path)) { - // Rooted paths need to avoid getting put through wToPrefixedFileW - // (and they are treated as relative in this context) - // Note: It seems that rooted paths in symbolic links are relative to - // the drive that the symbolic exists on, not to the CWD's drive. - // So, if the symlink is on C:\ and the CWD is on D:\, - // it will still resolve the path relative to the root of - // the C:\ drive. - .rooted => break :target_path target_path, - // Keep relative paths relative, but anything else needs to get NT-prefixed. - else => if (!std.fs.path.isAbsoluteWindowsWtf16(target_path)) - break :target_path target_path, - } - } - var prefixed_target_path = try wToPrefixedFileW(dir, target_path); - // We do this after prefixing to ensure that drive-relative paths are treated as absolute - is_target_absolute = std.fs.path.isAbsoluteWindowsWtf16(prefixed_target_path.span()); - break :target_path prefixed_target_path.span(); - }; - - // prepare reparse data buffer - var buffer: [MAXIMUM_REPARSE_DATA_BUFFER_SIZE]u8 = undefined; - const buf_len = @sizeOf(SYMLINK_DATA) + final_target_path.len * 4; - const header_len = @sizeOf(ULONG) + @sizeOf(USHORT) * 2; - const target_is_absolute = std.fs.path.isAbsoluteWindowsWtf16(final_target_path); - const symlink_data: SYMLINK_DATA = .{ - .ReparseTag = .SYMLINK, - .ReparseDataLength = @intCast(buf_len - header_len), - .Reserved = 0, - .SubstituteNameOffset = @intCast(final_target_path.len * 2), - .SubstituteNameLength = @intCast(final_target_path.len * 2), - .PrintNameOffset = 0, - .PrintNameLength = @intCast(final_target_path.len * 2), - .Flags = if (!target_is_absolute) SYMLINK_FLAG_RELATIVE else 0, - }; - - @memcpy(buffer[0..@sizeOf(SYMLINK_DATA)], std.mem.asBytes(&symlink_data)); - @memcpy(buffer[@sizeOf(SYMLINK_DATA)..][0 .. final_target_path.len * 2], @as([*]const u8, @ptrCast(final_target_path))); - const paths_start = @sizeOf(SYMLINK_DATA) + final_target_path.len * 2; - @memcpy(buffer[paths_start..][0 .. final_target_path.len * 2], @as([*]const u8, @ptrCast(final_target_path))); - const rc = DeviceIoControl(symlink_handle, FSCTL.SET_REPARSE_POINT, .{ .in = buffer[0..buf_len] }); - switch (rc) { - .SUCCESS => {}, - .PRIVILEGE_NOT_HELD => return error.AccessDenied, - .ACCESS_DENIED => return error.AccessDenied, - .INVALID_DEVICE_REQUEST => return error.AccessDenied, // Not supported by the underlying filesystem - else => return unexpectedStatus(rc), - } -} - pub const ReadLinkError = error{ FileNotFound, NetworkNotFound, -- 2.54.0 From 18c6abc0ba9a58a3d25908c47df3bb9374d51c35 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 28 Jan 2026 16:18:43 -0800 Subject: [PATCH 068/499] std: finish moving os.windows.ReadLink logic to Io.Threaded - remove error.SharingViolation from all error sets since it has the same meaning as FileBusy - add error.FileBusy to CreateFileAtomicError and ReadLinkError - update dirReadLinkWindows to use NtCreateFile and NtFsControlFile and integrate with cancelation properly. - move windows CTL_CODE constants to the proper namespace - delete os.windows.ReadLink --- lib/std/Io/Dir.zig | 6 +- lib/std/Io/File.zig | 3 +- lib/std/Io/Threaded.zig | 273 +++++++++++++++++++++-------- lib/std/debug/SelfInfo/Windows.zig | 1 - lib/std/os/windows.zig | 104 ++--------- lib/std/process.zig | 1 - lib/std/zig/system.zig | 3 +- 7 files changed, 218 insertions(+), 173 deletions(-) diff --git a/lib/std/Io/Dir.zig b/lib/std/Io/Dir.zig index 425e220b1cfdb3d16980a29b394527ccc355cbcf..85ab6b77d4990420c32086efaf634dc0fc830938 100644 --- a/lib/std/Io/Dir.zig +++ b/lib/std/Io/Dir.zig @@ -940,6 +940,7 @@ pub const RenameError = error{ /// Attempted to replace a nonempty directory. DirNotEmpty, PermissionDenied, + /// The file attempted to be moved or replaced is a running executable. FileBusy, DiskQuota, IsDir, @@ -952,7 +953,6 @@ pub const RenameError = error{ ReadOnlyFileSystem, CrossDevice, NoDevice, - SharingViolation, PipeBusy, /// On Windows, `\\server` or `\\server\share` was not found. NetworkNotFound, @@ -1167,6 +1167,8 @@ pub const ReadLinkError = error{ /// intercepts file system operations and makes them significantly slower /// in addition to possibly failing with this error code. AntivirusInterference, + /// File attempted to be opened is a running executable. + FileBusy, } || PathNameError || Io.Cancelable || Io.UnexpectedError; /// Obtain target of a symbolic link. @@ -1791,6 +1793,8 @@ pub const CreateFileAtomicError = error{ NotDir, WouldBlock, ReadOnlyFileSystem, + /// The file attempted to be created is a running executable. + FileBusy, } || Io.Dir.PathNameError || Io.Cancelable || Io.UnexpectedError; /// Create an unnamed ephemeral file that can eventually be atomically diff --git a/lib/std/Io/File.zig b/lib/std/Io/File.zig index 6323a39454194e58be8a53e4f8f9879cfc1daa0e..e537755a3365de8bab78d79fb55a40da0c33fe03 100644 --- a/lib/std/Io/File.zig +++ b/lib/std/Io/File.zig @@ -249,7 +249,6 @@ pub const CreateFlags = struct { }; pub const OpenError = error{ - SharingViolation, PipeBusy, NoDevice, /// On Windows, `\\server` or `\\server\share` was not found. @@ -757,7 +756,7 @@ pub const RealPathError = error{ NoSpaceLeft, FileSystem, DeviceBusy, - SharingViolation, + FileBusy, PipeBusy, /// On Windows, `\\server` or `\\server\share` was not found. NetworkNotFound, diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 16608c50a9bdbca1a73817642705f9420b162cfa..01bcacbc2989d5610f01df7a37041623b5262077 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -3635,7 +3635,7 @@ fn dirCreateFileWindows( // after an executable file is closed. Here we work around the // kernel bug with retry attempts. syscall.finish(); - if (max_attempts - attempt == 0) return error.SharingViolation; + if (max_attempts - attempt == 0) return error.FileBusy; try parking_sleep.windowsRetrySleep((@as(u32, 1) << attempt) >> 1); attempt += 1; syscall = try .start(); @@ -3648,7 +3648,7 @@ fn dirCreateFileWindows( // call has failed. Here, we simulate the kernel bug being // fixed by sleeping and retrying until the error goes away. syscall.finish(); - if (max_attempts - attempt == 0) return error.SharingViolation; + if (max_attempts - attempt == 0) return error.FileBusy; try parking_sleep.windowsRetrySleep((@as(u32, 1) << attempt) >> 1); attempt += 1; syscall = try .start(); @@ -3668,10 +3668,10 @@ fn dirCreateFileWindows( .NOT_A_DIRECTORY => return syscall.fail(error.NotDir), .USER_MAPPED_FILE => return syscall.fail(error.AccessDenied), .VIRUS_INFECTED, .VIRUS_DELETED => return syscall.fail(error.AntivirusInterference), - .INVALID_PARAMETER => |err| return syscall.ntstatusBug(err), - .OBJECT_PATH_SYNTAX_BAD => |err| return syscall.ntstatusBug(err), - .INVALID_HANDLE => |err| return syscall.ntstatusBug(err), - else => |err| return syscall.unexpectedNtstatus(err), + .INVALID_PARAMETER => |status| return syscall.ntstatusBug(status), + .OBJECT_PATH_SYNTAX_BAD => |status| return syscall.ntstatusBug(status), + .INVALID_HANDLE => |status| return syscall.ntstatusBug(status), + else => |status| return syscall.unexpectedNtstatus(status), }; errdefer windows.CloseHandle(handle); @@ -3819,7 +3819,6 @@ fn dirCreateFileAtomic( error.DiskQuota, error.PathAlreadyExists, error.LinkQuotaExceeded, - error.SharingViolation, error.PipeBusy, error.FileTooBig, error.DeviceBusy, @@ -3889,11 +3888,9 @@ fn dirCreateFileAtomic( error.DiskQuota, error.PathAlreadyExists, error.LinkQuotaExceeded, - error.SharingViolation, error.PipeBusy, error.FileTooBig, error.FileLocksUnsupported, - error.FileBusy, error.DeviceBusy, => return error.Unexpected, @@ -3926,7 +3923,6 @@ fn atomicFileInit( error.PathAlreadyExists => continue, error.DeviceBusy => continue, error.FileBusy => continue, - error.SharingViolation => continue, error.IsDir => return error.Unexpected, // No path components. error.FileTooBig => return error.Unexpected, // Creating, not opening. @@ -4236,7 +4232,7 @@ pub fn dirOpenFileWtf16( // after an executable file is closed. Here we work around the // kernel bug with retry attempts. syscall.finish(); - if (max_attempts - attempt == 0) return error.SharingViolation; + if (max_attempts - attempt == 0) return error.FileBusy; try parking_sleep.windowsRetrySleep((@as(u32, 1) << attempt) >> 1); attempt += 1; syscall = try .start(); @@ -4258,7 +4254,7 @@ pub fn dirOpenFileWtf16( // call has failed. Here, we simulate the kernel bug being // fixed by sleeping and retrying until the error goes away. syscall.finish(); - if (max_attempts - attempt == 0) return error.SharingViolation; + if (max_attempts - attempt == 0) return error.FileBusy; try parking_sleep.windowsRetrySleep((@as(u32, 1) << attempt) >> 1); attempt += 1; syscall = try .start(); @@ -6464,7 +6460,7 @@ fn dirSymLinkWindows( @memcpy(buffer[@sizeOf(SYMLINK_DATA)..][0 .. final_target_path.len * 2], @as([*]const u8, @ptrCast(final_target_path))); const paths_start = @sizeOf(SYMLINK_DATA) + final_target_path.len * 2; @memcpy(buffer[paths_start..][0 .. final_target_path.len * 2], @as([*]const u8, @ptrCast(final_target_path))); - const rc = w.DeviceIoControl(symlink_handle, w.FSCTL.SET_REPARSE_POINT, .{ .in = buffer[0..buf_len] }); + const rc = w.DeviceIoControl(symlink_handle, .SET_REPARSE_POINT, .{ .in = buffer[0..buf_len] }); switch (rc) { .SUCCESS => {}, .PRIVILEGE_NOT_HELD => return error.PermissionDenied, @@ -6571,44 +6567,189 @@ fn dirSymLinkPosix( } } -const dirReadLink = switch (native_os) { - .windows => dirReadLinkWindows, - .wasi => dirReadLinkWasi, - else => dirReadLinkPosix, -}; - -fn dirReadLinkWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, buffer: []u8) Dir.ReadLinkError!usize { +fn dirReadLink(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, buffer: []u8) Dir.ReadLinkError!usize { const t: *Threaded = @ptrCast(@alignCast(userdata)); _ = t; - const w = windows; + switch (native_os) { + .windows => return dirReadLinkWindows(dir, sub_path, buffer), + .wasi => return dirReadLinkWasi(dir, sub_path, buffer), + else => return dirReadLinkPosix(dir, sub_path, buffer), + } +} +fn dirReadLinkWindows(dir: Dir, sub_path: []const u8, buffer: []u8) Dir.ReadLinkError!usize { + // This gets used once for `sub_path` and then reused again temporarily + // before converting back to `buffer`. var sub_path_w_buf = try windows.sliceToPrefixedFileW(dir.handle, sub_path); + const sub_path_w = sub_path_w_buf.span(); + const path_len_bytes = std.math.cast(u16, sub_path_w.len * 2) orelse return error.NameTooLong; + var nt_name: windows.UNICODE_STRING = .{ + .Length = path_len_bytes, + .MaximumLength = path_len_bytes, + .Buffer = @constCast(sub_path_w.ptr), + }; + const attr: windows.OBJECT_ATTRIBUTES = .{ + .Length = @sizeOf(windows.OBJECT_ATTRIBUTES), + .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle, + .Attributes = .{ + .INHERIT = false, + }, + .ObjectName = &nt_name, + .SecurityDescriptor = null, + .SecurityQualityOfService = null, + }; + var io_status_block: windows.IO_STATUS_BLOCK = undefined; + var result_handle: windows.HANDLE = undefined; + + // There are multiple kernel bugs being worked around with retries. + const max_attempts = 13; + var attempt: u5 = 0; + + var syscall: Syscall = try .start(); + while (true) switch (windows.ntdll.NtCreateFile( + &result_handle, + .{ + .SPECIFIC = .{ .FILE = .{ + .READ_ATTRIBUTES = true, + } }, + .STANDARD = .{ .SYNCHRONIZE = true }, + }, + &attr, + &io_status_block, + null, + .{ .NORMAL = true }, + .VALID_FLAGS, + .OPEN, + .{ + .DIRECTORY_FILE = false, + .NON_DIRECTORY_FILE = false, + .IO = .ASYNCHRONOUS, + .OPEN_REPARSE_POINT = true, + }, + null, + 0, + )) { + .SUCCESS => { + syscall.finish(); + break; + }, + .CANCELLED => { + try syscall.checkCancel(); + continue; + }, + .SHARING_VIOLATION => { + // This occurs if the file attempting to be opened is a running + // executable. However, there's a kernel bug: the error may be + // incorrectly returned for an indeterminate amount of time + // after an executable file is closed. Here we work around the + // kernel bug with retry attempts. + syscall.finish(); + if (max_attempts - attempt == 0) return error.FileBusy; + try parking_sleep.windowsRetrySleep((@as(u32, 1) << attempt) >> 1); + attempt += 1; + syscall = try .start(); + continue; + }, + .DELETE_PENDING => { + // This error means that there *was* a file in this location on + // the file system, but it was deleted. However, the OS is not + // finished with the deletion operation, and so this CreateFile + // call has failed. Here, we simulate the kernel bug being + // fixed by sleeping and retrying until the error goes away. + syscall.finish(); + if (max_attempts - attempt == 0) return error.FileBusy; + try parking_sleep.windowsRetrySleep((@as(u32, 1) << attempt) >> 1); + attempt += 1; + syscall = try .start(); + continue; + }, + .OBJECT_NAME_INVALID => return syscall.fail(error.BadPathName), + .OBJECT_NAME_NOT_FOUND => return syscall.fail(error.FileNotFound), + .OBJECT_PATH_NOT_FOUND => return syscall.fail(error.FileNotFound), + .BAD_NETWORK_PATH => return syscall.fail(error.NetworkNotFound), // \\server was not found + .BAD_NETWORK_NAME => return syscall.fail(error.NetworkNotFound), // \\server was found but \\server\share wasn't + .NO_MEDIA_IN_DEVICE => return syscall.fail(error.FileNotFound), + .ACCESS_DENIED => return syscall.fail(error.AccessDenied), + .PIPE_BUSY => return syscall.fail(error.AccessDenied), + .PIPE_NOT_AVAILABLE => return syscall.fail(error.FileNotFound), + .USER_MAPPED_FILE => return syscall.fail(error.AccessDenied), + .VIRUS_INFECTED, .VIRUS_DELETED => return syscall.fail(error.AntivirusInterference), + .INVALID_PARAMETER => |status| return syscall.ntstatusBug(status), + .OBJECT_PATH_SYNTAX_BAD => |status| return syscall.ntstatusBug(status), + .INVALID_HANDLE => |status| return syscall.ntstatusBug(status), + else => |status| return syscall.unexpectedNtstatus(status), + }; + defer windows.CloseHandle(result_handle); - const syscall: Syscall = try .start(); - const result_w = while (true) { - if (w.ReadLink(dir.handle, sub_path_w_buf.span(), &sub_path_w_buf.data)) |res| { + var reparse_buf: [windows.MAXIMUM_REPARSE_DATA_BUFFER_SIZE]u8 align(@alignOf(windows.REPARSE_DATA_BUFFER)) = undefined; + + syscall = try .start(); + while (true) switch (windows.ntdll.NtFsControlFile( + result_handle, + null, // event + null, // APC routine + null, // APC context + &io_status_block, + .GET_REPARSE_POINT, + null, // input buffer + 0, // input buffer length + &reparse_buf, + reparse_buf.len, + )) { + .SUCCESS => { syscall.finish(); - break res; - } else |err| switch (err) { - error.OperationCanceled => { - try syscall.checkCancel(); - continue; - }, - else => |e| return syscall.fail(e), - } + break; + }, + .CANCELLED => { + try syscall.checkCancel(); + continue; + }, + .NOT_A_REPARSE_POINT => return syscall.fail(error.NotLink), + else => |status| return syscall.unexpectedNtstatus(status), }; + const reparse_struct: *const windows.REPARSE_DATA_BUFFER = @ptrCast(@alignCast(&reparse_buf)); + const IoReparseTagInt = @typeInfo(windows.IO_REPARSE_TAG).@"struct".backing_integer.?; + const result_w = switch (@as(IoReparseTagInt, @bitCast(reparse_struct.ReparseTag))) { + @as(IoReparseTagInt, @bitCast(windows.IO_REPARSE_TAG.SYMLINK)) => r: { + const buf: *const windows.SYMBOLIC_LINK_REPARSE_BUFFER = @ptrCast(@alignCast(&reparse_struct.DataBuffer[0])); + const offset = buf.SubstituteNameOffset >> 1; + const len = buf.SubstituteNameLength >> 1; + const path_buf = @as([*]const u16, &buf.PathBuffer); + const is_relative = buf.Flags & windows.SYMLINK_FLAG_RELATIVE != 0; + break :r try parseReadLinkPath(path_buf[offset..][0..len], is_relative, &sub_path_w_buf.data); + }, + @as(IoReparseTagInt, @bitCast(windows.IO_REPARSE_TAG.MOUNT_POINT)) => r: { + const buf: *const windows.MOUNT_POINT_REPARSE_BUFFER = @ptrCast(@alignCast(&reparse_struct.DataBuffer[0])); + const offset = buf.SubstituteNameOffset >> 1; + const len = buf.SubstituteNameLength >> 1; + const path_buf = @as([*]const u16, &buf.PathBuffer); + break :r try parseReadLinkPath(path_buf[offset..][0..len], false, &sub_path_w_buf.data); + }, + else => return error.UnsupportedReparsePointType, + }; const len = std.unicode.calcWtf8Len(result_w); if (len > buffer.len) return error.NameTooLong; return std.unicode.wtf16LeToWtf8(buffer, result_w); } -fn dirReadLinkWasi(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, buffer: []u8) Dir.ReadLinkError!usize { - if (builtin.link_libc) return dirReadLinkPosix(userdata, dir, sub_path, buffer); +fn parseReadLinkPath(path: []const u16, is_relative: bool, out_buffer: []u16) error{NameTooLong}![]u16 { + path: { + if (is_relative) break :path; + return windows.ntToWin32Namespace(path, out_buffer) catch |err| switch (err) { + error.NameTooLong => |e| return e, + error.NotNtPath => break :path, + }; + } + if (out_buffer.len < path.len) return error.NameTooLong; + const dest = out_buffer[0..path.len]; + @memcpy(dest, path); + return dest; +} - const t: *Threaded = @ptrCast(@alignCast(userdata)); - _ = t; +fn dirReadLinkWasi(dir: Dir, sub_path: []const u8, buffer: []u8) Dir.ReadLinkError!usize { + if (builtin.link_libc) return dirReadLinkPosix(dir, sub_path, buffer); var n: usize = undefined; const syscall: Syscall = try .start(); @@ -6643,10 +6784,7 @@ fn dirReadLinkWasi(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, buffer } } -fn dirReadLinkPosix(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, buffer: []u8) Dir.ReadLinkError!usize { - const t: *Threaded = @ptrCast(@alignCast(userdata)); - _ = t; - +fn dirReadLinkPosix(dir: Dir, sub_path: []const u8, buffer: []u8) Dir.ReadLinkError!usize { var sub_path_buffer: [posix.PATH_MAX]u8 = undefined; const sub_path_posix = try pathToPosix(sub_path, &sub_path_buffer); @@ -8708,45 +8846,41 @@ fn processExecutablePath(userdata: ?*anyopaque, out_buffer: []u8) process.Execut const symlink_path = std.mem.sliceTo(&symlink_path_buf, 0); return Io.Dir.realPathFileAbsolute(ioBasic(t), symlink_path, out_buffer) catch |err| switch (err) { error.NetworkNotFound => unreachable, // Windows-only + error.FileBusy => unreachable, // Windows-only else => |e| return e, }; }, .linux, .serenity => return Io.Dir.readLinkAbsolute(ioBasic(t), "/proc/self/exe", out_buffer) catch |err| switch (err) { error.UnsupportedReparsePointType => unreachable, // Windows-only error.NetworkNotFound => unreachable, // Windows-only + error.FileBusy => unreachable, // Windows-only else => |e| return e, }, .illumos => return Io.Dir.readLinkAbsolute(ioBasic(t), "/proc/self/path/a.out", out_buffer) catch |err| switch (err) { error.UnsupportedReparsePointType => unreachable, // Windows-only error.NetworkNotFound => unreachable, // Windows-only + error.FileBusy => unreachable, // Windows-only else => |e| return e, }, .freebsd, .dragonfly => { var mib: [4]c_int = .{ posix.CTL.KERN, posix.KERN.PROC, posix.KERN.PROC_PATHNAME, -1 }; var out_len: usize = out_buffer.len; const syscall: Syscall = try .start(); - while (true) { - switch (posix.errno(posix.system.sysctl(&mib, mib.len, out_buffer.ptr, &out_len, null, 0))) { - .SUCCESS => { - syscall.finish(); - return out_len - 1; // discard terminating NUL - }, - .INTR => { - try syscall.checkCancel(); - continue; - }, - else => |e| { - syscall.finish(); - switch (e) { - .FAULT => |err| return errnoBug(err), - .PERM => return error.PermissionDenied, - .NOMEM => return error.SystemResources, - .NOENT => |err| return errnoBug(err), - else => |err| return posix.unexpectedErrno(err), - } - }, - } - } + while (true) switch (posix.errno(posix.system.sysctl(&mib, mib.len, out_buffer.ptr, &out_len, null, 0))) { + .SUCCESS => { + syscall.finish(); + return out_len - 1; // discard terminating NUL + }, + .INTR => { + try syscall.checkCancel(); + continue; + }, + .PERM => return syscall.fail(error.PermissionDenied), + .NOMEM => return syscall.fail(error.SystemResources), + .FAULT => |err| return syscall.errnoBug(err), + .NOENT => |err| return syscall.errnoBug(err), + else => |err| return syscall.unexpectedErrno(err), + }; }, .netbsd => { var mib = [4]c_int{ posix.CTL.KERN, posix.KERN.PROC_ARGS, -1, posix.KERN.PROC_PATHNAME }; @@ -8762,16 +8896,11 @@ fn processExecutablePath(userdata: ?*anyopaque, out_buffer: []u8) process.Execut try syscall.checkCancel(); continue; }, - else => |e| { - syscall.finish(); - switch (e) { - .FAULT => |err| return errnoBug(err), - .PERM => return error.PermissionDenied, - .NOMEM => return error.SystemResources, - .NOENT => |err| return errnoBug(err), - else => |err| return posix.unexpectedErrno(err), - } - }, + .PERM => return syscall.fail(error.PermissionDenied), + .NOMEM => return syscall.fail(error.SystemResources), + .FAULT => |err| return syscall.errnoBug(err), + .NOENT => |err| return syscall.errnoBug(err), + else => |err| return syscall.unexpectedErrno(err), } } }, diff --git a/lib/std/debug/SelfInfo/Windows.zig b/lib/std/debug/SelfInfo/Windows.zig index 0c81d0012cabe037af3c83bcfa6ea5bb3c4d165b..b26883778dcc803bd3daaf0ce2acae82b55333ef 100644 --- a/lib/std/debug/SelfInfo/Windows.zig +++ b/lib/std/debug/SelfInfo/Windows.zig @@ -335,7 +335,6 @@ const Module = struct { error.NoSpaceLeft, error.DeviceBusy, error.NoDevice, - error.SharingViolation, error.PathAlreadyExists, error.PipeBusy, error.NetworkNotFound, diff --git a/lib/std/os/windows.zig b/lib/std/os/windows.zig index 7414ec060876b4b837d05dbc15fba9345b66a9fa..6f4e80f599908c6d34291a17b8768d331a91d52e 100644 --- a/lib/std/os/windows.zig +++ b/lib/std/os/windows.zig @@ -1135,19 +1135,7 @@ pub const CTL_CODE = packed struct(ULONG) { _, }; -}; -pub const IOCTL = struct { - pub const KSEC = struct { - pub const GEN_RANDOM: CTL_CODE = .{ .DeviceType = .KSEC, .Function = 2, .Method = .BUFFERED, .Access = .ANY }; - }; - pub const MOUNTMGR = struct { - pub const QUERY_POINTS: CTL_CODE = .{ .DeviceType = .MOUNTMGRCONTROLTYPE, .Function = 2, .Method = .BUFFERED, .Access = .ANY }; - pub const QUERY_DOS_VOLUME_PATH: CTL_CODE = .{ .DeviceType = .MOUNTMGRCONTROLTYPE, .Function = 12, .Method = .BUFFERED, .Access = .ANY }; - }; -}; - -pub const FSCTL = struct { pub const SET_REPARSE_POINT: CTL_CODE = .{ .DeviceType = .FILE_SYSTEM, .Function = 41, .Method = .BUFFERED, .Access = .SPECIAL }; pub const GET_REPARSE_POINT: CTL_CODE = .{ .DeviceType = .FILE_SYSTEM, .Function = 42, .Method = .BUFFERED, .Access = .ANY }; @@ -1177,6 +1165,16 @@ pub const FSCTL = struct { }; }; +pub const IOCTL = struct { + pub const KSEC = struct { + pub const GEN_RANDOM: CTL_CODE = .{ .DeviceType = .KSEC, .Function = 2, .Method = .BUFFERED, .Access = .ANY }; + }; + pub const MOUNTMGR = struct { + pub const QUERY_POINTS: CTL_CODE = .{ .DeviceType = .MOUNTMGRCONTROLTYPE, .Function = 2, .Method = .BUFFERED, .Access = .ANY }; + pub const QUERY_DOS_VOLUME_PATH: CTL_CODE = .{ .DeviceType = .MOUNTMGRCONTROLTYPE, .Function = 12, .Method = .BUFFERED, .Access = .ANY }; + }; +}; + pub const MAXIMUM_REPARSE_DATA_BUFFER_SIZE: ULONG = 16 * 1024; pub const IO_REPARSE_TAG = packed struct(ULONG) { @@ -2908,88 +2906,6 @@ pub fn GetCurrentDirectory(buffer: []u8) GetCurrentDirectoryError![]u8 { return buffer[0..end_index]; } -pub const ReadLinkError = error{ - FileNotFound, - NetworkNotFound, - AccessDenied, - Unexpected, - NameTooLong, - BadPathName, - AntivirusInterference, - UnsupportedReparsePointType, - NotLink, - OperationCanceled, -}; - -/// `sub_path_w` will never be accessed after `out_buffer` has been written to, so it -/// is safe to reuse a single buffer for both. -pub fn ReadLink(dir: ?HANDLE, sub_path_w: []const u16, out_buffer: []u16) ReadLinkError![]u16 { - const result_handle = OpenFile(sub_path_w, .{ - .access_mask = .{ - .SPECIFIC = .{ .FILE = .{ - .READ_ATTRIBUTES = true, - } }, - .STANDARD = .{ .SYNCHRONIZE = true }, - }, - .dir = dir, - .creation = .OPEN, - .follow_symlinks = false, - .filter = .any, - }) catch |err| switch (err) { - error.IsDir, error.NotDir => return error.Unexpected, // filter = .any - error.PathAlreadyExists => return error.Unexpected, // FILE_OPEN - error.WouldBlock => return error.Unexpected, - error.NoDevice => return error.FileNotFound, - error.PipeBusy => return error.AccessDenied, - else => |e| return e, - }; - defer CloseHandle(result_handle); - - var reparse_buf: [MAXIMUM_REPARSE_DATA_BUFFER_SIZE]u8 align(@alignOf(REPARSE_DATA_BUFFER)) = undefined; - const rc = DeviceIoControl(result_handle, FSCTL.GET_REPARSE_POINT, .{ .out = reparse_buf[0..] }); - switch (rc) { - .SUCCESS => {}, - .CANCELLED => return error.OperationCanceled, - .NOT_A_REPARSE_POINT => return error.NotLink, - else => return unexpectedStatus(rc), - } - - const reparse_struct: *const REPARSE_DATA_BUFFER = @ptrCast(@alignCast(&reparse_buf[0])); - const IoReparseTagInt = @typeInfo(IO_REPARSE_TAG).@"struct".backing_integer.?; - switch (@as(IoReparseTagInt, @bitCast(reparse_struct.ReparseTag))) { - @as(IoReparseTagInt, @bitCast(IO_REPARSE_TAG.SYMLINK)) => { - const buf: *const SYMBOLIC_LINK_REPARSE_BUFFER = @ptrCast(@alignCast(&reparse_struct.DataBuffer[0])); - const offset = buf.SubstituteNameOffset >> 1; - const len = buf.SubstituteNameLength >> 1; - const path_buf = @as([*]const u16, &buf.PathBuffer); - const is_relative = buf.Flags & SYMLINK_FLAG_RELATIVE != 0; - return parseReadLinkPath(path_buf[offset..][0..len], is_relative, out_buffer); - }, - @as(IoReparseTagInt, @bitCast(IO_REPARSE_TAG.MOUNT_POINT)) => { - const buf: *const MOUNT_POINT_REPARSE_BUFFER = @ptrCast(@alignCast(&reparse_struct.DataBuffer[0])); - const offset = buf.SubstituteNameOffset >> 1; - const len = buf.SubstituteNameLength >> 1; - const path_buf = @as([*]const u16, &buf.PathBuffer); - return parseReadLinkPath(path_buf[offset..][0..len], false, out_buffer); - }, - else => return error.UnsupportedReparsePointType, - } -} - -fn parseReadLinkPath(path: []const u16, is_relative: bool, out_buffer: []u16) error{NameTooLong}![]u16 { - path: { - if (is_relative) break :path; - return ntToWin32Namespace(path, out_buffer) catch |err| switch (err) { - error.NameTooLong => |e| return e, - error.NotNtPath => break :path, - }; - } - if (out_buffer.len < path.len) return error.NameTooLong; - const dest = out_buffer[0..path.len]; - @memcpy(dest, path); - return dest; -} - pub const DeleteFileError = error{ FileNotFound, AccessDenied, diff --git a/lib/std/process.zig b/lib/std/process.zig index f7dd7e30173504deade22fee09fb361bc629f012..a3547e2b0ffebe1164519a056117540868ac54cd 100644 --- a/lib/std/process.zig +++ b/lib/std/process.zig @@ -704,7 +704,6 @@ pub const ExecutablePathBaseError = error{ FileSystem, BadPathName, DeviceBusy, - SharingViolation, PipeBusy, NotLink, PathAlreadyExists, diff --git a/lib/std/zig/system.zig b/lib/std/zig/system.zig index 8bb1678e7d0244a0206ab14952fd5dc3d35fe19a..efcf569de5a5ef12170c7c7a3ea82a9774569b1a 100644 --- a/lib/std/zig/system.zig +++ b/lib/std/zig/system.zig @@ -723,6 +723,7 @@ fn abiAndDynamicLinkerFromFile( error.UnsupportedReparsePointType => unreachable, // Windows only error.NetworkNotFound => unreachable, // Windows only error.AntivirusInterference => unreachable, // Windows only + error.FileBusy => unreachable, // Windows only error.AccessDenied, error.PermissionDenied, @@ -844,7 +845,6 @@ fn glibcVerFromRPath(io: Io, rpath: []const u8) !std.SemanticVersion { error.NameTooLong => return error.Unexpected, error.BadPathName => return error.Unexpected, error.PipeBusy => return error.Unexpected, // Windows-only - error.SharingViolation => return error.Unexpected, // Windows-only error.NetworkNotFound => return error.Unexpected, // Windows-only error.AntivirusInterference => return error.Unexpected, // Windows-only error.FileLocksUnsupported => return error.Unexpected, // No lock requested. @@ -1052,7 +1052,6 @@ fn detectAbiAndDynamicLinker(io: Io, cpu: Target.Cpu, os: Target.Os, query: Targ error.NoSpaceLeft => return error.Unexpected, error.NameTooLong => return error.Unexpected, error.PathAlreadyExists => return error.Unexpected, - error.SharingViolation => return error.Unexpected, error.BadPathName => return error.Unexpected, error.PipeBusy => return error.Unexpected, error.FileLocksUnsupported => return error.Unexpected, -- 2.54.0 From e7baa09ce46181a5ffd55879a24a8cb72cea3093 Mon Sep 17 00:00:00 2001 From: GasInfinity Date: Wed, 28 Jan 2026 20:04:27 +0100 Subject: [PATCH 069/499] feat(Compilation): make libzigc share zcu if possible --- src/Compilation.zig | 65 ++++++++++++++++++++++++++++++++++++++++----- src/Zcu.zig | 2 +- src/target.zig | 6 +++++ 3 files changed, 65 insertions(+), 8 deletions(-) diff --git a/src/Compilation.zig b/src/Compilation.zig index 15c1837ec2dfb6a8f23691868b3f414828d4af5e..8047c164cb91d1eb25509a4b5d74f44eb7cd2835 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -80,6 +80,7 @@ sysroot: ?[]const u8, root_name: [:0]const u8, compiler_rt_strat: RtStrat, ubsan_rt_strat: RtStrat, +zigc_strat: RtStrat, /// Resolved into known paths, any GNU ld scripts already resolved. link_inputs: []const link.Input, /// Needed only for passing -F args to clang. @@ -2101,6 +2102,47 @@ pub fn create(gpa: Allocator, arena: Allocator, io: Io, diag: *CreateDiagnostic, try options.root_mod.deps.putNoClobber(arena, "ubsan_rt", ubsan_rt_mod); } + // Like with ubsan_rt we want to go through the `_ = @import("zigc")` + // approach if possible since it uses even more of the standard library + // and can thus reduce further unnecesary bloat. + const zigc_strat: RtStrat = s: { + if (options.skip_linker_dependencies) break :s .none; + if (target.ofmt == .c) break :s .none; + if (!link_libc or !is_exe_or_dyn_lib) break :s .none; + if (!target_util.wantsZigC(target, options.config.link_mode)) break :s .none; + if (have_zcu) break :s .zcu; + break :s .lib; + }; + + if (zigc_strat == .zcu) { + const zigc_mod = Package.Module.create(arena, .{ + .paths = .{ + .root = .zig_lib_root, + .root_src_path = "c.zig", + }, + .fully_qualified_name = "zigc", + .cc_argv = &.{}, + .inherited = .{}, + .global = options.config, + .parent = options.root_mod, + }) catch |err| switch (err) { + error.OutOfMemory => |e| return e, + // None of these are possible because the configuration matches the root module + // which already passed these checks. + error.ValgrindUnsupportedOnTarget => unreachable, + error.TargetRequiresSingleThreaded => unreachable, + error.BackendRequiresSingleThreaded => unreachable, + error.TargetRequiresPic => unreachable, + error.PieRequiresPic => unreachable, + error.DynamicLinkingRequiresPic => unreachable, + error.TargetHasNoRedZone => unreachable, + error.StackCheckUnsupportedByTarget => unreachable, + error.StackProtectorUnsupportedByTarget => unreachable, + error.StackProtectorUnavailableWithoutLibC => unreachable, + }; + try options.root_mod.deps.putNoClobber(arena, "zigc", zigc_mod); + } + if (options.verbose_llvm_cpu_features) { if (options.root_mod.resolved_target.llvm_cpu_features) |cf| { const stderr = try io.lockStderr(&.{}, null); @@ -2296,6 +2338,7 @@ pub fn create(gpa: Allocator, arena: Allocator, io: Io, diag: *CreateDiagnostic, .libc_installation = libc_dirs.libc_installation, .compiler_rt_strat = compiler_rt_strat, .ubsan_rt_strat = ubsan_rt_strat, + .zigc_strat = zigc_strat, .link_inputs = options.link_inputs, .framework_dirs = options.framework_dirs, .llvm_opt_bisect_limit = options.llvm_opt_bisect_limit, @@ -2651,13 +2694,6 @@ pub fn create(gpa: Allocator, arena: Allocator, io: Io, diag: *CreateDiagnostic, } else { return diag.fail(.cross_libc_unavailable); } - - if ((target.isMuslLibC() and comp.config.link_mode == .static) or - target.isWasiLibC() or - target.isMinGW()) - { - comp.queued_jobs.zigc_lib = true; - } } // Generate Windows import libs. @@ -2711,6 +2747,15 @@ pub fn create(gpa: Allocator, arena: Allocator, io: Io, diag: *CreateDiagnostic, .dyn_lib => unreachable, // hack for compiler_rt only } + switch (comp.zigc_strat) { + .none, .zcu => {}, + .lib => { + log.debug("queuing a job to build libzigc", .{}); + comp.queued_jobs.zigc_lib = true; + }, + .obj, .dyn_lib => unreachable, // only available as a static library or inside an existing ZCU + } + if (is_exe_or_dyn_lib and comp.config.any_fuzz) { log.debug("queuing a job to build libfuzzer", .{}); comp.queued_jobs.fuzzer_lib = true; @@ -3100,6 +3145,11 @@ pub fn update(comp: *Compilation, main_progress_node: std.Progress.Node) UpdateE zcu.analysis_roots_buffer[zcu.analysis_roots_len] = ubsan_rt_mod; zcu.analysis_roots_len += 1; } + + if (zcu.root_mod.deps.get("zigc")) |zigc_mod| { + zcu.analysis_roots_buffer[zcu.analysis_roots_len] = zigc_mod; + zcu.analysis_roots_len += 1; + } } // The linker progress node is set up here instead of in `performAllTheWork`, because @@ -3531,6 +3581,7 @@ fn addNonIncrementalStuffToCacheManifest( man.hash.add(comp.skip_linker_dependencies); man.hash.add(comp.compiler_rt_strat); man.hash.add(comp.ubsan_rt_strat); + man.hash.add(comp.zigc_strat); man.hash.add(comp.rc_includes); man.hash.addListOfBytes(comp.force_undefined_symbols.keys()); man.hash.addListOfBytes(comp.framework_dirs); diff --git a/src/Zcu.zig b/src/Zcu.zig index d1e20c8551b10cc622a086e0731d4b3132487e41..ceccc2c192cd8a8fef9248179b82535476d34cf3 100644 --- a/src/Zcu.zig +++ b/src/Zcu.zig @@ -269,7 +269,7 @@ nav_val_analysis_queued: std.AutoArrayHashMapUnmanaged(InternPool.Nav.Index, voi /// These are the modules which we initially queue for analysis in `Compilation.update`. /// `resolveReferences` will use these as the root of its reachability traversal. -analysis_roots_buffer: [4]*Package.Module, +analysis_roots_buffer: [5]*Package.Module, analysis_roots_len: usize = 0, /// This is the cached result of `Zcu.resolveReferences`. It is computed on-demand, and /// reset to `null` when any semantic analysis occurs (since this invalidates the data). diff --git a/src/target.zig b/src/target.zig index f664a354592ddff2d0c8cebbc5b9629d9c4d9c8c..ca65239a0950bb4d52508655b9040d1cf2b62872 100644 --- a/src/target.zig +++ b/src/target.zig @@ -423,6 +423,12 @@ pub fn canBuildLibUbsanRt(target: *const std.Target) enum { no, yes, llvm_only, }; } +/// Whether libzigc can fill-in the gaps of an existing libc +/// or *is* the libc of the target. +pub fn wantsZigC(target: *const std.Target, link_mode: std.builtin.LinkMode) bool { + return (target.isMuslLibC() and link_mode == .static) or target.isWasiLibC() or target.isMinGW(); +} + pub fn hasRedZone(target: *const std.Target) bool { return switch (target.cpu.arch) { .aarch64, -- 2.54.0 From ed93f0d70f5b3f954a4f52e37cab80383ca61413 Mon Sep 17 00:00:00 2001 From: GasInfinity Date: Thu, 29 Jan 2026 10:39:38 +0100 Subject: [PATCH 070/499] fix(libzigc): always apply strong linkage, even when testing * libzigc may be linked into a different test compilation Co-authored-by: Matthew Lugg --- lib/c/common.zig | 12 ++++-------- 1 file changed, 4 insertions(+), 8 deletions(-) diff --git a/lib/c/common.zig b/lib/c/common.zig index 8d2a79db54b33515ef75c98bf5eb1defe23cb19a..5cd94cf0ceedf73986b5b0217d4e285fac45f735 100644 --- a/lib/c/common.zig +++ b/lib/c/common.zig @@ -1,18 +1,14 @@ const builtin = @import("builtin"); const std = @import("std"); -pub const linkage: std.builtin.GlobalLinkage = if (builtin.is_test) - .internal -else - .strong; +/// It is incorrect to make this conditional on `builtin.is_test`, because it is possible that +/// libzigc is being linked into a different test compilation, as opposed to being tested itself. +pub const linkage: std.builtin.GlobalLinkage = .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 (linkage != .internal) - .hidden -else - .default; +pub const visibility: std.builtin.SymbolVisibility = .hidden; /// Given a low-level syscall return value, sets errno and returns `-1`, or on /// success returns the result. -- 2.54.0 From 4d6f4e9cfd00669efc1c688d9509010ddc4e840f Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 28 Jan 2026 14:10:27 -0800 Subject: [PATCH 071/499] behavior: add coverage for extern struct field overalignment --- test/behavior/struct.zig | 49 ++++++++++++++++++++++++++++++++++++++++ 1 file changed, 49 insertions(+) diff --git a/test/behavior/struct.zig b/test/behavior/struct.zig index 08aca075d7ec8e219b4d3ff699dd5c0fdd1b581c..6422aef034bf3b4fbcf1e16acce3742944828a04 100644 --- a/test/behavior/struct.zig +++ b/test/behavior/struct.zig @@ -2184,3 +2184,52 @@ test "pass a pointer to a comptime-only struct field to a function" { const s: struct { x: type } = .{ .x = u42 }; try S.checkField(&s.x); } + +test "overaligned extern struct fields" { + const A = struct { + a: *anyopaque, + b: u64, + c: [1][]u8, + d: ?anyerror, + }; + + const B = union(enum) { + a: struct { + a: [2]usize, + b: C, + }, + b: struct { + a: *anyopaque, + b: []const []u8, + c: C, + }, + const C = union { + a: void, + b: *anyopaque, + c: anyerror!usize, + }; + }; + + const D = extern struct { + a: u32, + }; + + const E = extern struct { + a: u32, + b: [2][@sizeOf(A)]u8 align(@alignOf(A)), + c: [2]u32, + d: [2][@sizeOf(B)]u8 align(@alignOf(B)), + + fn cast(e: *@This()) *D { + e.a = 2; + return @ptrCast(e); + } + }; + + var e: E = undefined; + const d = e.cast(); + try expect(d.a == 2); + try expect(std.mem.isAligned(@intFromPtr(&e.b), @alignOf(A))); + try expect(std.mem.isAligned(@intFromPtr(&e.c), @alignOf(u32))); + try expect(std.mem.isAligned(@intFromPtr(&e.d), @alignOf(B))); +} -- 2.54.0 From 5571c08e6603173378db7cfe1436e33e902f9c0a Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 28 Jan 2026 15:26:11 -0800 Subject: [PATCH 072/499] add behavior test for i96 operations --- test/behavior/math.zig | 44 ++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 44 insertions(+) diff --git a/test/behavior/math.zig b/test/behavior/math.zig index 867567ea404ddc8550c60b6e4c3041c7128e5fda..841d608d35bce5e28a1807a9633d2b27b344fa2d 100644 --- a/test/behavior/math.zig +++ b/test/behavior/math.zig @@ -1946,3 +1946,47 @@ test "comptime float vector multiplication of zero by nan is nan" { comptime assert(math.isNan((ct_zero * ct_nan)[0])); comptime assert(math.isNan((ct_nan * ct_zero)[0])); } + +test "i96 operations" { + // This is coverage for some stuff used by std.Io timestamps, to catch + // issues earlier than bootstrapping. + const Op_i96 = union(enum) { + a, + b: B, + c: C, + + const B = struct { + inner: struct { x: i96 }, + flag: bool, + }; + + const C = struct { + inner: struct { x: i96 }, + flag: bool, + }; + + fn do(op: @This()) i64 { + switch (op) { + .a => { + return std.math.minInt(i64); + }, + .b => |b| { + const x = b.inner.x; + return @intCast(@divTrunc(x, 100)); + }, + .c => |c| { + const a = get() catch unreachable; + const b = a.x + c.inner.x; + return @intCast(@divTrunc(b, 100)); + }, + } + } + + fn get() anyerror!struct { x: i96 } { + return .{ .x = 999999999 }; + } + }; + try expect(-9223372036854775808 == Op_i96.do(.{ .a = {} })); + try expect(12345678910111213 == Op_i96.do(.{ .b = .{ .inner = .{ .x = 1234567891011121314 }, .flag = true } })); + try expect(1234567891021121314 == Op_i96.do(.{ .c = .{ .inner = .{ .x = 123456789101112131415 }, .flag = true } })); +} -- 2.54.0 From 9b415761dd66904aef363e387b4501f3ddd0bf76 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 28 Jan 2026 20:05:20 -0800 Subject: [PATCH 073/499] std.os.windows: delete unused APIs Intention is to go through std.Io for these things. --- lib/std/Io/File/Reader.zig | 2 +- lib/std/Io/Threaded.zig | 15 +- lib/std/os/windows.zig | 372 +------------------------ lib/std/os/windows/kernel32.zig | 19 -- test/standalone/windows_argv/fuzz.zig | 7 +- test/standalone/windows_spawn/main.zig | 19 +- 6 files changed, 36 insertions(+), 398 deletions(-) diff --git a/lib/std/Io/File/Reader.zig b/lib/std/Io/File/Reader.zig index f400f2c51439b65bbe7e11ef3dfa0d5b49b4f3bf..2e0e192cb2326bd62eecbfaac6d30ade3ba81f18 100644 --- a/lib/std/Io/File/Reader.zig +++ b/lib/std/Io/File/Reader.zig @@ -48,7 +48,7 @@ pub const Error = error{ LockViolation, } || Io.Cancelable || Io.UnexpectedError; -pub const SizeError = std.os.windows.GetFileSizeError || File.StatError || error{ +pub const SizeError = File.StatError || error{ /// Occurs if, for example, the file handle is a network socket and therefore does not have a size. Streaming, }; diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 01bcacbc2989d5610f01df7a37041623b5262077..c74850ca0a3b653f011cb30ab971f9e5a8c46958 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -13960,8 +13960,19 @@ fn childWaitWindows(child: *process.Child) process.Child.WaitError!process.Child fn childCleanupWindows(child: *process.Child) void { const handle = child.id orelse return; - if (child.request_resource_usage_statistics) - child.resource_usage_statistics.rusage = windows.GetProcessMemoryInfo(handle) catch null; + if (child.request_resource_usage_statistics) { + var vmc: windows.VM_COUNTERS = undefined; + switch (windows.ntdll.NtQueryInformationProcess( + handle, + .VmCounters, + &vmc, + @sizeOf(windows.VM_COUNTERS), + null, + )) { + .SUCCESS => child.resource_usage_statistics.rusage = vmc, + else => child.resource_usage_statistics.rusage = null, + } + } windows.CloseHandle(handle); child.id = null; diff --git a/lib/std/os/windows.zig b/lib/std/os/windows.zig index 6f4e80f599908c6d34291a17b8768d331a91d52e..47c5a4d8567326a46709250d6a070fae752cce14 100644 --- a/lib/std/os/windows.zig +++ b/lib/std/os/windows.zig @@ -2906,275 +2906,6 @@ pub fn GetCurrentDirectory(buffer: []u8) GetCurrentDirectoryError![]u8 { return buffer[0..end_index]; } -pub const DeleteFileError = error{ - FileNotFound, - AccessDenied, - NameTooLong, - /// Also known as sharing violation. - FileBusy, - Unexpected, - NotDir, - IsDir, - DirNotEmpty, - NetworkNotFound, -}; - -pub const DeleteFileOptions = struct { - dir: ?HANDLE, - remove_dir: bool = false, -}; - -pub fn DeleteFile(sub_path_w: []const u16, options: DeleteFileOptions) DeleteFileError!void { - const path_len_bytes = @as(u16, @intCast(sub_path_w.len * 2)); - var nt_name: UNICODE_STRING = .{ - .Length = path_len_bytes, - .MaximumLength = path_len_bytes, - // The Windows API makes this mutable, but it will not mutate here. - .Buffer = @constCast(sub_path_w.ptr), - }; - - if (sub_path_w[0] == '.' and sub_path_w[1] == 0) { - // Windows does not recognize this, but it does work with empty string. - nt_name.Length = 0; - } - if (sub_path_w[0] == '.' and sub_path_w[1] == '.' and sub_path_w[2] == 0) { - // Can't remove the parent directory with an open handle. - return error.FileBusy; - } - - var io: IO_STATUS_BLOCK = undefined; - var tmp_handle: HANDLE = undefined; - var rc = ntdll.NtCreateFile( - &tmp_handle, - .{ .STANDARD = .{ - .RIGHTS = .{ .DELETE = true }, - .SYNCHRONIZE = true, - } }, - &.{ - .Length = @sizeOf(OBJECT_ATTRIBUTES), - .RootDirectory = if (std.fs.path.isAbsoluteWindowsWtf16(sub_path_w)) null else options.dir, - .Attributes = .{}, - .ObjectName = &nt_name, - .SecurityDescriptor = null, - .SecurityQualityOfService = null, - }, - &io, - null, - .{}, - .VALID_FLAGS, - .OPEN, - .{ - .DIRECTORY_FILE = options.remove_dir, - .NON_DIRECTORY_FILE = !options.remove_dir, - .OPEN_REPARSE_POINT = true, // would we ever want to delete the target instead? - }, - null, - 0, - ); - switch (rc) { - .SUCCESS => {}, - .OBJECT_NAME_INVALID => unreachable, - .OBJECT_NAME_NOT_FOUND => return error.FileNotFound, - .OBJECT_PATH_NOT_FOUND => return error.FileNotFound, - .BAD_NETWORK_PATH => return error.NetworkNotFound, // \\server was not found - .BAD_NETWORK_NAME => return error.NetworkNotFound, // \\server was found but \\server\share wasn't - .INVALID_PARAMETER => unreachable, - .FILE_IS_A_DIRECTORY => return error.IsDir, - .NOT_A_DIRECTORY => return error.NotDir, - .SHARING_VIOLATION => return error.FileBusy, - .ACCESS_DENIED => return error.AccessDenied, - .DELETE_PENDING => return, - else => return unexpectedStatus(rc), - } - defer CloseHandle(tmp_handle); - - // FileDispositionInformationEx has varying levels of support: - // - FILE_DISPOSITION_INFORMATION_EX requires >= win10_rs1 - // (INVALID_INFO_CLASS is returned if not supported) - // - Requires the NTFS filesystem - // (on filesystems like FAT32, INVALID_PARAMETER is returned) - // - FILE_DISPOSITION_POSIX_SEMANTICS requires >= win10_rs1 - // - FILE_DISPOSITION_IGNORE_READONLY_ATTRIBUTE requires >= win10_rs5 - // (NOT_SUPPORTED is returned if a flag is unsupported) - // - // The strategy here is just to try using FileDispositionInformationEx and fall back to - // FileDispositionInformation if the return value lets us know that some aspect of it is not supported. - const need_fallback = need_fallback: { - // Deletion with posix semantics if the filesystem supports it. - var info: FILE.DISPOSITION.INFORMATION.EX = .{ .Flags = .{ - .DELETE = true, - .POSIX_SEMANTICS = true, - .IGNORE_READONLY_ATTRIBUTE = true, - } }; - rc = ntdll.NtSetInformationFile( - tmp_handle, - &io, - &info, - @sizeOf(FILE.DISPOSITION.INFORMATION.EX), - .DispositionEx, - ); - switch (rc) { - .SUCCESS => return, - // The filesystem does not support FileDispositionInformationEx - .INVALID_PARAMETER, - // The operating system does not support FileDispositionInformationEx - .INVALID_INFO_CLASS, - // The operating system does not support one of the flags - .NOT_SUPPORTED, - => break :need_fallback true, - // For all other statuses, fall down to the switch below to handle them. - else => break :need_fallback false, - } - }; - - if (need_fallback) { - // Deletion with file pending semantics, which requires waiting or moving - // files to get them removed (from here). - var file_dispo: FILE.DISPOSITION.INFORMATION = .{ - .DeleteFile = TRUE, - }; - rc = ntdll.NtSetInformationFile( - tmp_handle, - &io, - &file_dispo, - @sizeOf(FILE.DISPOSITION.INFORMATION), - .Disposition, - ); - } - switch (rc) { - .SUCCESS => {}, - .DIRECTORY_NOT_EMPTY => return error.DirNotEmpty, - .INVALID_PARAMETER => unreachable, - .CANNOT_DELETE => return error.AccessDenied, - .MEDIA_WRITE_PROTECTED => return error.AccessDenied, - .ACCESS_DENIED => return error.AccessDenied, - else => return unexpectedStatus(rc), - } -} - -pub const RenameError = error{ - IsDir, - NotDir, - FileNotFound, - NoDevice, - AccessDenied, - PipeBusy, - PathAlreadyExists, - Unexpected, - NameTooLong, - NetworkNotFound, - AntivirusInterference, - BadPathName, - CrossDevice, -} || UnexpectedError; - -pub fn RenameFile( - /// May only be `null` if `old_path_w` is a fully-qualified absolute path. - old_dir_fd: ?HANDLE, - old_path_w: []const u16, - /// May only be `null` if `new_path_w` is a fully-qualified absolute path, - /// or if the file is not being moved to a different directory. - new_dir_fd: ?HANDLE, - new_path_w: []const u16, - replace_if_exists: bool, -) RenameError!void { - const src_fd = OpenFile(old_path_w, .{ - .dir = old_dir_fd, - .access_mask = .{ - .STANDARD = .{ - .RIGHTS = .{ .DELETE = true }, - .SYNCHRONIZE = true, - }, - .GENERIC = .{ .WRITE = true }, - }, - .creation = .OPEN, - .filter = .any, // This function is supposed to rename both files and directories. - .follow_symlinks = false, - }) catch |err| switch (err) { - error.WouldBlock => unreachable, // Not possible without `.share_access_nonblocking = true`. - else => |e| return e, - }; - defer CloseHandle(src_fd); - - var rc: NTSTATUS = undefined; - // FileRenameInformationEx has varying levels of support: - // - FILE_RENAME_INFORMATION_EX requires >= win10_rs1 - // (INVALID_INFO_CLASS is returned if not supported) - // - Requires the NTFS filesystem - // (on filesystems like FAT32, INVALID_PARAMETER is returned) - // - FILE_RENAME_POSIX_SEMANTICS requires >= win10_rs1 - // - FILE_RENAME_IGNORE_READONLY_ATTRIBUTE requires >= win10_rs5 - // (NOT_SUPPORTED is returned if a flag is unsupported) - // - // The strategy here is just to try using FileRenameInformationEx and fall back to - // FileRenameInformation if the return value lets us know that some aspect of it is not supported. - const need_fallback = need_fallback: { - var rename_info: FILE.RENAME_INFORMATION = .init(.{ - .Flags = .{ - .REPLACE_IF_EXISTS = replace_if_exists, - .POSIX_SEMANTICS = true, - .IGNORE_READONLY_ATTRIBUTE = true, - }, - .RootDirectory = if (std.fs.path.isAbsoluteWindowsWtf16(new_path_w)) null else new_dir_fd, - .FileName = new_path_w, - }); - var io_status_block: IO_STATUS_BLOCK = undefined; - const rename_info_buf = rename_info.toBuffer(); - rc = ntdll.NtSetInformationFile( - src_fd, - &io_status_block, - rename_info_buf.ptr, - @intCast(rename_info_buf.len), // already checked for error.NameTooLong - .RenameEx, - ); - switch (rc) { - .SUCCESS => return, - // The filesystem does not support FileDispositionInformationEx - .INVALID_PARAMETER, - // The operating system does not support FileDispositionInformationEx - .INVALID_INFO_CLASS, - // The operating system does not support one of the flags - .NOT_SUPPORTED, - => break :need_fallback true, - // For all other statuses, fall down to the switch below to handle them. - else => break :need_fallback false, - } - }; - - if (need_fallback) { - var rename_info: FILE.RENAME_INFORMATION = .init(.{ - .Flags = .{ .REPLACE_IF_EXISTS = replace_if_exists }, - .RootDirectory = if (std.fs.path.isAbsoluteWindowsWtf16(new_path_w)) null else new_dir_fd, - .FileName = new_path_w, - }); - var io_status_block: IO_STATUS_BLOCK = undefined; - const rename_info_buf = rename_info.toBuffer(); - rc = ntdll.NtSetInformationFile( - src_fd, - &io_status_block, - rename_info_buf.ptr, - @intCast(rename_info_buf.len), // already checked for error.NameTooLong - .Rename, - ); - } - - switch (rc) { - .SUCCESS => {}, - .INVALID_HANDLE => unreachable, - .INVALID_PARAMETER => unreachable, - .OBJECT_PATH_SYNTAX_BAD => unreachable, - .ACCESS_DENIED => return error.AccessDenied, - .OBJECT_NAME_NOT_FOUND => return error.FileNotFound, - .OBJECT_PATH_NOT_FOUND => return error.FileNotFound, - .NOT_SAME_DEVICE => return error.CrossDevice, - .OBJECT_NAME_COLLISION => return error.PathAlreadyExists, - .DIRECTORY_NOT_EMPTY => return error.PathAlreadyExists, - .FILE_IS_A_DIRECTORY => return error.IsDir, - .NOT_A_DIRECTORY => return error.NotDir, - else => return unexpectedStatus(rc), - } -} - pub const GetStdHandleError = error{ NoStandardHandleAttached, Unexpected, @@ -3508,18 +3239,6 @@ test GetFinalPathNameByHandle { _ = try GetFinalPathNameByHandle(handle, .{ .volume_name = .Dos }, buffer[0..required_len_in_u16]); } -pub const GetFileSizeError = error{Unexpected}; - -pub fn GetFileSizeEx(hFile: HANDLE) GetFileSizeError!u64 { - var file_size: LARGE_INTEGER = undefined; - if (kernel32.GetFileSizeEx(hFile, &file_size) == 0) { - switch (GetLastError()) { - else => |err| return unexpectedError(err), - } - } - return @as(u64, @bitCast(file_size)); -} - pub fn getpeername(s: ws2_32.SOCKET, name: *ws2_32.sockaddr, namelen: *ws2_32.socklen_t) i32 { return ws2_32.getpeername(s, name, @as(*i32, @ptrCast(namelen))); } @@ -3714,69 +3433,6 @@ pub const CreateProcessFlags = packed struct(u32) { create_ignore_system_default: bool = false, }; -pub fn CreateProcessW( - lpApplicationName: ?LPCWSTR, - lpCommandLine: ?LPWSTR, - lpProcessAttributes: ?*SECURITY_ATTRIBUTES, - lpThreadAttributes: ?*SECURITY_ATTRIBUTES, - bInheritHandles: BOOL, - dwCreationFlags: CreateProcessFlags, - lpEnvironment: ?[*:0]u16, - lpCurrentDirectory: ?LPCWSTR, - lpStartupInfo: *STARTUPINFOW, - lpProcessInformation: *PROCESS_INFORMATION, -) CreateProcessError!void { - if (kernel32.CreateProcessW( - lpApplicationName, - lpCommandLine, - lpProcessAttributes, - lpThreadAttributes, - bInheritHandles, - dwCreationFlags, - lpEnvironment, - lpCurrentDirectory, - lpStartupInfo, - lpProcessInformation, - ) == 0) { - switch (GetLastError()) { - .FILE_NOT_FOUND => return error.FileNotFound, - .PATH_NOT_FOUND => return error.FileNotFound, - .DIRECTORY => return error.FileNotFound, - .ACCESS_DENIED => return error.AccessDenied, - .INVALID_PARAMETER => unreachable, - .INVALID_NAME => return error.InvalidName, - .FILENAME_EXCED_RANGE => return error.NameTooLong, - .SHARING_VIOLATION => return error.FileBusy, - // These are all the system errors that are mapped to ENOEXEC by - // the undocumented _dosmaperr (old CRT) or __acrt_errno_map_os_error - // (newer CRT) functions. Their code can be found in crt/src/dosmap.c (old SDK) - // or urt/misc/errno.cpp (newer SDK) in the Windows SDK. - .BAD_FORMAT, - .INVALID_STARTING_CODESEG, // MIN_EXEC_ERROR in errno.cpp - .INVALID_STACKSEG, - .INVALID_MODULETYPE, - .INVALID_EXE_SIGNATURE, - .EXE_MARKED_INVALID, - .BAD_EXE_FORMAT, - .ITERATED_DATA_EXCEEDS_64k, - .INVALID_MINALLOCSIZE, - .DYNLINK_FROM_INVALID_RING, - .IOPL_NOT_ENABLED, - .INVALID_SEGDPL, - .AUTODATASEG_EXCEEDS_64k, - .RING2SEG_MUST_BE_MOVABLE, - .RELOC_CHAIN_XEEDS_SEGLIM, - .INFLOOP_IN_RELOC_CHAIN, // MAX_EXEC_ERROR in errno.cpp - // This one is not mapped to ENOEXEC but it is possible, for example - // when calling CreateProcessW on a plain text file with a .exe extension - .EXE_MACHINE_TYPE_MISMATCH, - => return error.InvalidExe, - .COMMITMENT_LIMIT => return error.SystemResources, - else => |err| return unexpectedError(err), - } - } -} - pub const LoadLibraryError = error{ FileNotFound, Unexpected, @@ -3843,10 +3499,6 @@ pub fn QueryPerformanceCounter() u64 { return @as(u64, @bitCast(result)); } -pub fn InitOnceExecuteOnce(InitOnce: *INIT_ONCE, InitFn: INIT_ONCE_FN, Parameter: ?*anyopaque, Context: ?*anyopaque) void { - assert(kernel32.InitOnceExecuteOnce(InitOnce, InitFn, Parameter, Context) != 0); -} - /// This is a workaround for the C backend until zig has the ability to put /// C code in inline assembly. extern fn zig_thumb_windows_teb() callconv(.c) *anyopaque; @@ -6072,24 +5724,6 @@ pub const PROCESS_MEMORY_COUNTERS_EX = extern struct { PrivateUsage: SIZE_T, }; -pub const GetProcessMemoryInfoError = error{ - AccessDenied, - InvalidHandle, - Unexpected, -}; - -pub fn GetProcessMemoryInfo(hProcess: HANDLE) GetProcessMemoryInfoError!VM_COUNTERS { - var vmc: VM_COUNTERS = undefined; - const rc = ntdll.NtQueryInformationProcess(hProcess, .VmCounters, &vmc, @sizeOf(VM_COUNTERS), null); - switch (rc) { - .SUCCESS => return vmc, - .ACCESS_DENIED => return error.AccessDenied, - .INVALID_HANDLE => return error.InvalidHandle, - .INVALID_PARAMETER => unreachable, - else => return unexpectedStatus(rc), - } -} - pub const PERFORMANCE_INFORMATION = extern struct { cb: DWORD, CommitTotal: SIZE_T, @@ -6623,7 +6257,11 @@ pub fn WriteProcessMemory(handle: HANDLE, addr: ?LPVOID, buffer: []const u8) Wri } } -pub const ProcessBaseAddressError = GetProcessMemoryInfoError || ReadMemoryError; +pub const ProcessBaseAddressError = error{ + AccessDenied, + InvalidHandle, + Unexpected, +} || ReadMemoryError; /// Returns the base address of the process loaded into memory. pub fn ProcessBaseAddress(handle: HANDLE) ProcessBaseAddressError!HMODULE { diff --git a/lib/std/os/windows/kernel32.zig b/lib/std/os/windows/kernel32.zig index 1ef3d0e55ec6985da80d57881f0a48feeb2dd0c3..ad232046ef87f7790397592cac41c7955722740f 100644 --- a/lib/std/os/windows/kernel32.zig +++ b/lib/std/os/windows/kernel32.zig @@ -117,12 +117,6 @@ pub extern "kernel32" fn WriteFile( in_out_lpOverlapped: ?*OVERLAPPED, ) callconv(.winapi) BOOL; -// TODO: wrapper for NtQueryInformationFile + `FILE_STANDARD_INFORMATION` -pub extern "kernel32" fn GetFileSizeEx( - hFile: HANDLE, - lpFileSize: *LARGE_INTEGER, -) callconv(.winapi) BOOL; - // TODO: Wrapper around GetStdHandle + NtFlushBuffersFile. pub extern "kernel32" fn FlushFileBuffers( hFile: HANDLE, @@ -283,12 +277,6 @@ pub extern "kernel32" fn GetExitCodeProcess( lpExitCode: *DWORD, ) callconv(.winapi) BOOL; -// TODO: Wrapper around RtlSetEnvironmentVar. -pub extern "kernel32" fn SetEnvironmentVariableW( - lpName: LPCWSTR, - lpValue: ?LPCWSTR, -) callconv(.winapi) BOOL; - pub extern "kernel32" fn CreateToolhelp32Snapshot( dwFlags: DWORD, th32ProcessID: DWORD, @@ -311,13 +299,6 @@ pub extern "kernel32" fn CreateThread( // Locks, critical sections, initializers -pub extern "kernel32" fn InitOnceExecuteOnce( - InitOnce: *INIT_ONCE, - InitFn: INIT_ONCE_FN, - Parameter: ?*anyopaque, - Context: ?*anyopaque, -) callconv(.winapi) BOOL; - // TODO: // - dwMilliseconds -> LARGE_INTEGER. // - RtlSleepConditionVariableSRW diff --git a/test/standalone/windows_argv/fuzz.zig b/test/standalone/windows_argv/fuzz.zig index fde26ee5f481ff06a241fbacdac1593577c96266..b955697d3867afe69bf1396699ce96c41c7a7e15 100644 --- a/test/standalone/windows_argv/fuzz.zig +++ b/test/standalone/windows_argv/fuzz.zig @@ -129,7 +129,7 @@ fn spawnVerify(verify_path: [:0]const u16, cmd_line: [:0]const u16) !windows.DWO }; var proc_info: windows.PROCESS_INFORMATION = undefined; - try windows.CreateProcessW( + if (windows.kernel32.CreateProcessW( @constCast(verify_path.ptr), @constCast(cmd_line.ptr), null, @@ -140,7 +140,10 @@ fn spawnVerify(verify_path: [:0]const u16, cmd_line: [:0]const u16) !windows.DWO null, &startup_info, &proc_info, - ); + ) == 0) { + std.process.fatal("kernel32 CreateProcessW failed with {t}", .{windows.kernel32.GetLastError()}); + } + windows.CloseHandle(proc_info.hThread); break :spawn proc_info.hProcess; diff --git a/test/standalone/windows_spawn/main.zig b/test/standalone/windows_spawn/main.zig index e26b581f59db5b97be70c1c483ed42eb851a64b0..add20921b5d4b51bf2570d62077329e6f11efd3b 100644 --- a/test/standalone/windows_spawn/main.zig +++ b/test/standalone/windows_spawn/main.zig @@ -31,13 +31,13 @@ pub fn main(init: std.process.Init) !void { defer gpa.free(tmp_relative_path); // Clear PATH - std.debug.assert(windows.kernel32.SetEnvironmentVariableW( + std.debug.assert(SetEnvironmentVariableW( utf16Literal("PATH"), null, ) == windows.TRUE); // Set PATHEXT to something predictable - std.debug.assert(windows.kernel32.SetEnvironmentVariableW( + std.debug.assert(SetEnvironmentVariableW( utf16Literal("PATHEXT"), utf16Literal(".COM;.EXE;.BAT;.CMD;.JS"), ) == windows.TRUE); @@ -48,7 +48,7 @@ pub fn main(init: std.process.Init) !void { // make sure we don't get error.BadPath traversing out of cwd with a relative path try testExecError(error.FileNotFound, gpa, io, "..\\.\\.\\.\\\\..\\more_missing"); - std.debug.assert(windows.kernel32.SetEnvironmentVariableW( + std.debug.assert(SetEnvironmentVariableW( utf16Literal("PATH"), tmp_absolute_path_w, ) == windows.TRUE); @@ -131,7 +131,7 @@ pub fn main(init: std.process.Init) !void { const something_subdir_abs_path = try std.mem.concatWithSentinel(gpa, u16, &.{ tmp_absolute_path_w, utf16Literal("\\something") }, 0); defer gpa.free(something_subdir_abs_path); - std.debug.assert(windows.kernel32.SetEnvironmentVariableW( + std.debug.assert(SetEnvironmentVariableW( utf16Literal("PATH"), something_subdir_abs_path, ) == windows.TRUE); @@ -171,7 +171,7 @@ pub fn main(init: std.process.Init) !void { defer gpa.free(denormed_something_subdir_wtf8); // clear the path to ensure that the match comes from the cwd - std.debug.assert(windows.kernel32.SetEnvironmentVariableW( + std.debug.assert(SetEnvironmentVariableW( utf16Literal("PATH"), null, ) == windows.TRUE); @@ -179,7 +179,7 @@ pub fn main(init: std.process.Init) !void { try testExecWithCwd(gpa, io, "goodbye", denormed_something_subdir_wtf8, "hello from exe\n"); // normalization should also work if the non-normalized path is found in the PATH var. - std.debug.assert(windows.kernel32.SetEnvironmentVariableW( + std.debug.assert(SetEnvironmentVariableW( utf16Literal("PATH"), denormed_something_subdir_abs_path, ) == windows.TRUE); @@ -193,7 +193,7 @@ pub fn main(init: std.process.Init) !void { try std.process.setCurrentDir(io, subdir_cwd); // clear the PATH again - std.debug.assert(windows.kernel32.SetEnvironmentVariableW( + std.debug.assert(SetEnvironmentVariableW( utf16Literal("PATH"), null, ) == windows.TRUE); @@ -235,3 +235,8 @@ fn renameExe(dir: Io.Dir, io: Io, old_sub_path: []const u8, new_sub_path: []cons else => |e| return e, }; } + +pub extern "kernel32" fn SetEnvironmentVariableW( + lpName: windows.LPCWSTR, + lpValue: ?windows.LPCWSTR, +) callconv(.winapi) windows.BOOL; -- 2.54.0 From 649aaf4814f8b74d9f90c0aaa09cbaf1901a6650 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 28 Jan 2026 19:40:43 -0800 Subject: [PATCH 074/499] std: migrate getcwd to Io progress towards #30150 --- lib/compiler/build_runner.zig | 2 +- lib/std/Build/Cache.zig | 8 +-- lib/std/Build/Step/Options.zig | 2 +- lib/std/Io.zig | 1 + lib/std/Io/Threaded.zig | 42 ++++++++++++ lib/std/fs/test.zig | 2 +- lib/std/os/windows.zig | 28 -------- lib/std/os/windows/kernel32.zig | 6 -- lib/std/os/windows/ntdll.zig | 5 ++ lib/std/posix.zig | 34 ---------- lib/std/process.zig | 65 ++++++++----------- src/Compilation.zig | 2 +- src/introspect.zig | 10 +-- src/main.zig | 10 +-- test/standalone/child_process/main.zig | 16 ++--- test/standalone/posix/cwd.zig | 27 ++++---- .../self_exe_symlink/create-symlink.zig | 2 +- test/standalone/windows_paths/relative.zig | 2 +- test/standalone/windows_paths/test.zig | 2 +- test/standalone/windows_spawn/main.zig | 2 +- tools/doctest.zig | 2 +- tools/incr-check.zig | 2 +- tools/process_headers.zig | 2 +- tools/update-linux-headers.zig | 2 +- 24 files changed, 122 insertions(+), 154 deletions(-) diff --git a/lib/compiler/build_runner.zig b/lib/compiler/build_runner.zig index 7efee4bac4c78f0cc2b1db807180cf57d7cd271b..6ef8e71f7eea56aa1d5e7efd58c2f3af716e5b05 100644 --- a/lib/compiler/build_runner.zig +++ b/lib/compiler/build_runner.zig @@ -83,7 +83,7 @@ pub fn main(init: process.Init.Minimal) !void { .io = io, .gpa = gpa, .manifest_dir = try local_cache_directory.handle.createDirPathOpen(io, "h", .{}), - .cwd = try process.getCwdAlloc(single_threaded_arena.allocator()), + .cwd = try process.currentDirAlloc(io, single_threaded_arena.allocator()), }, .zig_exe = zig_exe, .environ_map = try init.environ.createMap(arena), diff --git a/lib/std/Build/Cache.zig b/lib/std/Build/Cache.zig index e35beca617d3d1913571195f17e4cb88795f10f2..f595435749554ce81fa8b183792e918ae3ee9aca 100644 --- a/lib/std/Build/Cache.zig +++ b/lib/std/Build/Cache.zig @@ -1315,7 +1315,7 @@ test "cache file and then recall it" { var tmp = testing.tmpDir(.{}); defer tmp.cleanup(); - const cwd = try std.process.getCwdAlloc(testing.allocator); + const cwd = try std.process.currentDirAlloc(io, testing.allocator); defer testing.allocator.free(cwd); const temp_file = "test.txt"; @@ -1383,7 +1383,7 @@ test "check that changing a file makes cache fail" { var tmp = testing.tmpDir(.{}); defer tmp.cleanup(); - const cwd = try std.process.getCwdAlloc(testing.allocator); + const cwd = try std.process.currentDirAlloc(io, testing.allocator); defer testing.allocator.free(cwd); const temp_file = "cache_hash_change_file_test.txt"; @@ -1459,7 +1459,7 @@ test "no file inputs" { var tmp = testing.tmpDir(.{}); defer tmp.cleanup(); - const cwd = try std.process.getCwdAlloc(testing.allocator); + const cwd = try std.process.currentDirAlloc(io, testing.allocator); defer testing.allocator.free(cwd); const temp_manifest_dir = "no_file_inputs_manifest_dir"; @@ -1509,7 +1509,7 @@ test "Manifest with files added after initial hash work" { var tmp = testing.tmpDir(.{}); defer tmp.cleanup(); - const cwd = try std.process.getCwdAlloc(testing.allocator); + const cwd = try std.process.currentDirAlloc(io, testing.allocator); defer testing.allocator.free(cwd); const temp_file1 = "cache_hash_post_file_test1.txt"; diff --git a/lib/std/Build/Step/Options.zig b/lib/std/Build/Step/Options.zig index 6c316b35fccd47ccf21872fd72a1cb9fb8fab8cd..adef0484237e8bde983662998eee35d764393c1d 100644 --- a/lib/std/Build/Step/Options.zig +++ b/lib/std/Build/Step/Options.zig @@ -519,7 +519,7 @@ test Options { var arena = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena.deinit(); - const cwd = try std.process.getCwdAlloc(std.testing.allocator); + const cwd = try std.process.currentDirAlloc(io, std.testing.allocator); defer std.testing.allocator.free(cwd); var graph: std.Build.Graph = .{ diff --git a/lib/std/Io.zig b/lib/std/Io.zig index 80a0e248074c08ac83b53151fcd8680b20eadc46..20c23e804ec3b05e9ada2f55fa82f579bc136a09 100644 --- a/lib/std/Io.zig +++ b/lib/std/Io.zig @@ -665,6 +665,7 @@ pub const VTable = struct { lockStderr: *const fn (?*anyopaque, ?Terminal.Mode) Cancelable!LockedStderr, tryLockStderr: *const fn (?*anyopaque, ?Terminal.Mode) Cancelable!?LockedStderr, unlockStderr: *const fn (?*anyopaque) void, + processCurrentDir: *const fn (?*anyopaque, buffer: []u8) std.process.CurrentDirError!usize, processSetCurrentDir: *const fn (?*anyopaque, Dir) std.process.SetCurrentDirError!void, processReplace: *const fn (?*anyopaque, std.process.ReplaceOptions) std.process.ReplaceError, processReplacePath: *const fn (?*anyopaque, Dir, std.process.ReplaceOptions) std.process.ReplaceError, diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index c74850ca0a3b653f011cb30ab971f9e5a8c46958..2690734e87c60e1fe41a95329e38b2a10cc3bf22 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -1643,6 +1643,7 @@ pub fn io(t: *Threaded) Io { .lockStderr = lockStderr, .tryLockStderr = tryLockStderr, .unlockStderr = unlockStderr, + .processCurrentDir = processCurrentDir, .processSetCurrentDir = processSetCurrentDir, .processReplace = processReplace, .processReplacePath = processReplacePath, @@ -1801,6 +1802,7 @@ pub fn ioBasic(t: *Threaded) Io { .lockStderr = lockStderr, .tryLockStderr = tryLockStderr, .unlockStderr = unlockStderr, + .processCurrentDir = processCurrentDir, .processSetCurrentDir = processSetCurrentDir, .processReplace = processReplace, .processReplacePath = processReplacePath, @@ -12600,6 +12602,46 @@ fn unlockStderr(userdata: ?*anyopaque) void { process.stderr_thread_mutex.unlock(); } +fn processCurrentDir(userdata: ?*anyopaque, buffer: []u8) process.CurrentDirError!usize { + const t: *Threaded = @ptrCast(@alignCast(userdata)); + _ = t; + if (is_windows) { + var wtf16le_buf: [windows.PATH_MAX_WIDE:0]u16 = undefined; + const n = windows.ntdll.RtlGetCurrentDirectory_U(wtf16le_buf.len + 1, &wtf16le_buf); + if (n == 0) return error.Unexpected; + assert(n <= wtf16le_buf.len); + const wtf16le_slice = wtf16le_buf[0..n]; + var end_index: usize = 0; + var it = std.unicode.Wtf16LeIterator.init(wtf16le_slice); + while (it.nextCodepoint()) |codepoint| { + const seq_len = std.unicode.utf8CodepointSequenceLength(codepoint) catch unreachable; + if (end_index + seq_len >= buffer.len) + return error.NameTooLong; + end_index += std.unicode.wtf8Encode(codepoint, buffer[end_index..]) catch unreachable; + } + return end_index; + } else if (native_os == .wasi and !builtin.link_libc) { + if (buffer.len == 0) return error.NameTooLong; + buffer[0] = '.'; + return 1; + } + + const err: posix.E = if (builtin.link_libc) err: { + const c_err = if (std.c.getcwd(buffer.ptr, buffer.len)) |_| 0 else std.c._errno().*; + break :err @enumFromInt(c_err); + } else err: { + break :err posix.errno(posix.system.getcwd(buffer.ptr, buffer.len)); + }; + switch (err) { + .SUCCESS => return std.mem.findScalar(u8, buffer, 0).?, + .NOENT => return error.CurrentWorkingDirectoryUnlinked, + .RANGE => return error.NameTooLong, + .FAULT => |e| return errnoBug(e), + .INVAL => |e| return errnoBug(e), + else => return posix.unexpectedErrno(err), + } +} + fn processSetCurrentDir(userdata: ?*anyopaque, dir: Dir) process.SetCurrentDirError!void { if (native_os == .wasi) return error.OperationUnsupported; const t: *Threaded = @ptrCast(@alignCast(userdata)); diff --git a/lib/std/fs/test.zig b/lib/std/fs/test.zig index 4d7077bfc31ed62cf3597e6cf7df24a6dbc058a6..be59393e3da32ee7e1ace2bab04d37fb1fc45b87 100644 --- a/lib/std/fs/test.zig +++ b/lib/std/fs/test.zig @@ -1787,7 +1787,7 @@ test "open file with exclusive nonblocking lock twice (absolute paths)" { const gpa = testing.allocator; - const cwd = try std.process.getCwdAlloc(gpa); + const cwd = try std.process.currentDirAlloc(io, gpa); defer gpa.free(cwd); const filename = try Dir.path.resolve(gpa, &.{ cwd, sub_path }); diff --git a/lib/std/os/windows.zig b/lib/std/os/windows.zig index 47c5a4d8567326a46709250d6a070fae752cce14..b9653b3ac9979d9eb5144822fb7ed27f802a55a7 100644 --- a/lib/std/os/windows.zig +++ b/lib/std/os/windows.zig @@ -2878,34 +2878,6 @@ pub fn CloseHandle(hObject: HANDLE) void { assert(ntdll.NtClose(hObject) == .SUCCESS); } -pub const GetCurrentDirectoryError = error{ - NameTooLong, - Unexpected, -}; - -/// The result is a slice of `buffer`, indexed from 0. -/// The result is encoded as [WTF-8](https://wtf-8.codeberg.page/). -pub fn GetCurrentDirectory(buffer: []u8) GetCurrentDirectoryError![]u8 { - var wtf16le_buf: [PATH_MAX_WIDE:0]u16 = undefined; - const result = kernel32.GetCurrentDirectoryW(wtf16le_buf.len + 1, &wtf16le_buf); - if (result == 0) { - switch (GetLastError()) { - else => |err| return unexpectedError(err), - } - } - assert(result <= wtf16le_buf.len); - const wtf16le_slice = wtf16le_buf[0..result]; - var end_index: usize = 0; - var it = std.unicode.Wtf16LeIterator.init(wtf16le_slice); - while (it.nextCodepoint()) |codepoint| { - const seq_len = std.unicode.utf8CodepointSequenceLength(codepoint) catch unreachable; - if (end_index + seq_len >= buffer.len) - return error.NameTooLong; - end_index += std.unicode.wtf8Encode(codepoint, buffer[end_index..]) catch unreachable; - } - return buffer[0..end_index]; -} - pub const GetStdHandleError = error{ NoStandardHandleAttached, Unexpected, diff --git a/lib/std/os/windows/kernel32.zig b/lib/std/os/windows/kernel32.zig index ad232046ef87f7790397592cac41c7955722740f..fe28e40cbbedbd7381c2b787c27798589d75b7cc 100644 --- a/lib/std/os/windows/kernel32.zig +++ b/lib/std/os/windows/kernel32.zig @@ -128,12 +128,6 @@ pub extern "kernel32" fn SetFileCompletionNotificationModes( Flags: UCHAR, ) callconv(.winapi) BOOL; -// TODO: `RtlGetCurrentDirectory_U(nBufferLength * 2, lpBuffer)` -pub extern "kernel32" fn GetCurrentDirectoryW( - nBufferLength: DWORD, - lpBuffer: ?[*]WCHAR, -) callconv(.winapi) DWORD; - pub extern "kernel32" fn ReadFile( hFile: HANDLE, lpBuffer: LPVOID, diff --git a/lib/std/os/windows/ntdll.zig b/lib/std/os/windows/ntdll.zig index ee3cdeb069d01719322a634965dfa0a3882e3a9b..52361ed982942864af6bd395a947e35e600b7adc 100644 --- a/lib/std/os/windows/ntdll.zig +++ b/lib/std/os/windows/ntdll.zig @@ -496,6 +496,11 @@ pub extern "ntdll" fn RtlGetFullPathName_U( ShortName: ?*[*:0]const u16, ) callconv(.winapi) ULONG; +pub extern "ntdll" fn RtlGetCurrentDirectory_U( + BufferLength: ULONG, + Buffer: [*]u16, +) callconv(.winapi) ULONG; + pub extern "ntdll" fn RtlGetSystemTimePrecise() callconv(.winapi) LARGE_INTEGER; pub extern "ntdll" fn RtlInitializeCriticalSection( diff --git a/lib/std/posix.zig b/lib/std/posix.zig index a12182b4065d1ee518bd7c82ac938c4b4e693ea0..6bcb18155248f291d12848b93da4d6576f819b57 100644 --- a/lib/std/posix.zig +++ b/lib/std/posix.zig @@ -519,40 +519,6 @@ pub fn getppid() pid_t { return system.getppid(); } -pub const GetCwdError = error{ - NameTooLong, - /// Not possible on Windows. - CurrentWorkingDirectoryUnlinked, -} || UnexpectedError; - -/// The result is a slice of out_buffer, indexed from 0. -pub fn getcwd(out_buffer: []u8) GetCwdError![]u8 { - if (native_os == .windows) { - return windows.GetCurrentDirectory(out_buffer); - } else if (native_os == .wasi and !builtin.link_libc) { - const path = "."; - if (out_buffer.len < path.len) return error.NameTooLong; - const result = out_buffer[0..path.len]; - @memcpy(result, path); - return result; - } - - const err: E = if (builtin.link_libc) err: { - const c_err = if (std.c.getcwd(out_buffer.ptr, out_buffer.len)) |_| 0 else std.c._errno().*; - break :err @enumFromInt(c_err); - } else err: { - break :err errno(system.getcwd(out_buffer.ptr, out_buffer.len)); - }; - switch (err) { - .SUCCESS => return mem.sliceTo(out_buffer, 0), - .FAULT => unreachable, - .INVAL => unreachable, - .NOENT => return error.CurrentWorkingDirectoryUnlinked, - .RANGE => return error.NameTooLong, - else => return unexpectedErrno(err), - } -} - pub const SocketError = error{ /// Permission to create a socket of the specified type and/or /// pro‐tocol is denied. diff --git a/lib/std/process.zig b/lib/std/process.zig index a3547e2b0ffebe1164519a056117540868ac54cd..dd3e07c1eaf62dfd7011eeeaada3f8e44711b0bb 100644 --- a/lib/std/process.zig +++ b/lib/std/process.zig @@ -63,53 +63,40 @@ pub const Init = struct { }; }; -pub const GetCwdError = posix.GetCwdError; +pub const CurrentDirError = error{ + NameTooLong, + /// Not possible on Windows. Always returned on WASI. + CurrentWorkingDirectoryUnlinked, +} || Io.UnexpectedError; -/// The result is a slice of `out_buffer`, from index `0`. /// On Windows, the result is encoded as [WTF-8](https://wtf-8.codeberg.page/). -/// On other platforms, the result is an opaque sequence of bytes with no particular encoding. -pub fn getCwd(out_buffer: []u8) GetCwdError![]u8 { - return posix.getcwd(out_buffer); +/// On other platforms, the result is an opaque sequence of bytes with no +/// particular encoding. +pub fn currentDir(io: Io, buffer: []u8) CurrentDirError!usize { + return io.vtable.processCurrentDir(io.userdata, buffer); } -// Same as GetCwdError, minus error.NameTooLong + Allocator.Error -pub const GetCwdAllocError = Allocator.Error || error{ - /// Not possible on Windows. +pub const CurrentDirAllocError = Allocator.Error || error{ + /// Not possible on Windows. Always returned on WASI. CurrentWorkingDirectoryUnlinked, -} || posix.UnexpectedError; +} || Io.UnexpectedError; -/// Caller must free the returned memory. /// On Windows, the result is encoded as [WTF-8](https://wtf-8.codeberg.page/). -/// On other platforms, the result is an opaque sequence of bytes with no particular encoding. -pub fn getCwdAlloc(allocator: Allocator) GetCwdAllocError![]u8 { - // The use of max_path_bytes here is just a heuristic: most paths will fit - // in stack_buf, avoiding an extra allocation in the common case. - var stack_buf: [max_path_bytes]u8 = undefined; - var heap_buf: ?[]u8 = null; - defer if (heap_buf) |buf| allocator.free(buf); - - var current_buf: []u8 = &stack_buf; - while (true) { - if (posix.getcwd(current_buf)) |slice| { - return allocator.dupe(u8, slice); - } else |err| switch (err) { - error.NameTooLong => { - // The path is too long to fit in stack_buf. Allocate geometrically - // increasing buffers until we find one that works - const new_capacity = current_buf.len * 2; - if (heap_buf) |buf| allocator.free(buf); - current_buf = try allocator.alloc(u8, new_capacity); - heap_buf = current_buf; - }, - else => |e| return e, - } - } +/// On other platforms, the result is an opaque sequence of bytes with no +/// particular encoding. +/// +/// Caller owns returned memory. +pub fn currentDirAlloc(io: Io, allocator: Allocator) CurrentDirAllocError![:0]u8 { + var buffer: [max_path_bytes]u8 = undefined; + const n = currentDir(io, &buffer) catch |err| switch (err) { + error.NameTooLong => unreachable, + else => |e| return e, + }; + return allocator.dupeZ(u8, buffer[0..n]); } -test getCwdAlloc { - if (native_os == .wasi) return error.SkipZigTest; - - const cwd = try getCwdAlloc(testing.allocator); +test currentDirAlloc { + const cwd = try currentDirAlloc(testing.io, testing.allocator); testing.allocator.free(cwd); } @@ -466,7 +453,7 @@ pub fn spawnPath(io: Io, dir: Io.Dir, options: SpawnOptions) SpawnError!Child { return io.vtable.processSpawnPath(io.userdata, dir, options); } -pub const RunError = posix.GetCwdError || posix.ReadError || SpawnError || posix.PollError || error{ +pub const RunError = CurrentDirError || posix.ReadError || SpawnError || posix.PollError || error{ StdoutStreamTooLong, StderrStreamTooLong, }; diff --git a/src/Compilation.zig b/src/Compilation.zig index 15c1837ec2dfb6a8f23691868b3f414828d4af5e..180968ac0cf69716e0d310fbc779f6be7ceda52f 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -767,7 +767,7 @@ pub const Directories = struct { ) Directories { const wasi = builtin.target.os.tag == .wasi; - const cwd = introspect.getResolvedCwd(arena) catch |err| { + const cwd = introspect.getResolvedCwd(io, arena) catch |err| { fatal("unable to get cwd: {t}", .{err}); }; diff --git a/src/introspect.zig b/src/introspect.zig index fa04e7de58a1fda94f9311fc1bb21a7266558e3f..36e94e979fc14e1ff92408d585544b2524901e7c 100644 --- a/src/introspect.zig +++ b/src/introspect.zig @@ -43,7 +43,7 @@ fn testZigInstallPrefix(io: Io, base_dir: Io.Dir) ?Cache.Directory { /// Both the directory handle and the path are newly allocated resources which the caller now owns. pub fn findZigLibDir(gpa: Allocator, io: Io) !Cache.Directory { - const cwd_path = try getResolvedCwd(gpa); + const cwd_path = try getResolvedCwd(io, gpa); defer gpa.free(cwd_path); const self_exe_path = try std.process.executablePathAlloc(io, gpa); defer gpa.free(self_exe_path); @@ -51,23 +51,23 @@ pub fn findZigLibDir(gpa: Allocator, io: Io) !Cache.Directory { return findZigLibDirFromSelfExe(gpa, io, cwd_path, self_exe_path); } -/// Like `std.process.getCwdAlloc`, but also resolves the path with `Dir.path.resolve`. This +/// Like `std.process.currentDirAlloc`, but also resolves the path with `Dir.path.resolve`. This /// means the path has no repeated separators, no "." or ".." components, and no trailing separator. /// On WASI, "" is returned instead of ".". -pub fn getResolvedCwd(gpa: Allocator) error{ +pub fn getResolvedCwd(io: Io, gpa: Allocator) error{ OutOfMemory, CurrentWorkingDirectoryUnlinked, Unexpected, }![]u8 { if (builtin.target.os.tag == .wasi) { if (std.debug.runtime_safety) { - const cwd = try std.process.getCwdAlloc(gpa); + const cwd = try std.process.currentDirAlloc(io, gpa); defer gpa.free(cwd); assert(mem.eql(u8, cwd, ".")); } return ""; } - const cwd = try std.process.getCwdAlloc(gpa); + const cwd = try std.process.currentDirAlloc(io, gpa); defer gpa.free(cwd); const resolved = try Dir.path.resolve(gpa, &.{cwd}); assert(Dir.path.isAbsolute(resolved)); diff --git a/src/main.zig b/src/main.zig index f914593bce3a53cc513c1dec51075f5aad70f5b1..0efab88f6ada4d8f830a0d7614fa2e3462ba0332 100644 --- a/src/main.zig +++ b/src/main.zig @@ -4775,7 +4775,7 @@ fn cmdInit(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8) ! } } - const cwd_path = try introspect.getResolvedCwd(arena); + const cwd_path = try introspect.getResolvedCwd(io, arena); const cwd_basename = fs.path.basename(cwd_path); const sanitized_root_name = try sanitizeExampleName(arena, cwd_basename); @@ -5141,7 +5141,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8, process.raiseFileDescriptorLimit(); - const cwd_path = try introspect.getResolvedCwd(arena); + const cwd_path = try introspect.getResolvedCwd(io, arena); const build_root = try findBuildRoot(arena, io, .{ .cwd_path = cwd_path, .build_file = build_file, @@ -7077,7 +7077,7 @@ fn cmdFetch( }, }; - const cwd_path = try introspect.getResolvedCwd(arena); + const cwd_path = try introspect.getResolvedCwd(io, arena); var build_root = try findBuildRoot(arena, io, .{ .cwd_path = cwd_path, @@ -7288,7 +7288,7 @@ const FindBuildRootOptions = struct { }; fn findBuildRoot(arena: Allocator, io: Io, options: FindBuildRootOptions) !BuildRoot { - const cwd_path = options.cwd_path orelse try introspect.getResolvedCwd(arena); + const cwd_path = options.cwd_path orelse try introspect.getResolvedCwd(io, arena); const build_zig_basename = if (options.build_file) |bf| fs.path.basename(bf) else @@ -7490,7 +7490,7 @@ fn writeSimpleTemplateFile(io: Io, file_name: []const u8, comptime fmt: []const } fn findTemplates(gpa: Allocator, arena: Allocator, io: Io) Templates { - const cwd_path = introspect.getResolvedCwd(arena) catch |err| { + const cwd_path = introspect.getResolvedCwd(io, arena) catch |err| { fatal("unable to get cwd: {t}", .{err}); }; const self_exe_path = process.executablePathAlloc(io, arena) catch |err| { diff --git a/test/standalone/child_process/main.zig b/test/standalone/child_process/main.zig index 2776c3e95c88d5158b9db122d6b9669871ba23e9..159be62a9d1e6beb8495a628baabb18898e126ef 100644 --- a/test/standalone/child_process/main.zig +++ b/test/standalone/child_process/main.zig @@ -9,7 +9,14 @@ pub fn main(init: std.process.Init.Minimal) !void { }; const gpa = gpa_state.allocator(); - const process_cwd_path = try std.process.getCwdAlloc(gpa); + var threaded: Io.Threaded = .init(gpa, .{ + .argv0 = .init(init.args), + .environ = init.environ, + }); + defer threaded.deinit(); + const io = threaded.io(); + + const process_cwd_path = try std.process.currentDirAlloc(io, gpa); defer gpa.free(process_cwd_path); var environ_map = try init.environ.createMap(gpa); @@ -27,13 +34,6 @@ pub fn main(init: std.process.Init.Minimal) !void { }; defer if (needs_free) gpa.free(child_path); - var threaded: Io.Threaded = .init(gpa, .{ - .argv0 = .init(init.args), - .environ = init.environ, - }); - defer threaded.deinit(); - const io = threaded.io(); - var child = try std.process.spawn(io, .{ .argv = &.{ child_path, "hello arg" }, .stdin = .pipe, diff --git a/test/standalone/posix/cwd.zig b/test/standalone/posix/cwd.zig index 7a9517b412814aa2e6f7f76b28e8220333e82814..e5e376784a1d64dbb2323a10a70680d69edbe399 100644 --- a/test/standalone/posix/cwd.zig +++ b/test/standalone/posix/cwd.zig @@ -13,43 +13,44 @@ pub fn main(init: std.process.Init) !void { .windows => return, // POSIX is not implemented by Windows else => {}, } + const io = init.io; const args = try init.minimal.args.toSlice(init.arena.allocator()); const tmp_dir_path = args[1]; var tmp_dir = try Io.Dir.cwd().openDir(init.io, tmp_dir_path, .{}); defer tmp_dir.close(init.io); - try test_chdir_self(); - try test_chdir_absolute(); - try test_chdir_relative(init.gpa, init.io, tmp_dir); + try test_chdir_self(io); + try test_chdir_absolute(io); + try test_chdir_relative(init.gpa, io, tmp_dir); } // get current working directory and expect it to match given path -fn expect_cwd(expected_cwd: []const u8) !void { +fn expect_cwd(io: Io, expected_cwd: []const u8) !void { var cwd_buf: [path_max]u8 = undefined; - const actual_cwd = try std.posix.getcwd(cwd_buf[0..]); + const actual_cwd = cwd_buf[0..try std.process.currentDir(io, &cwd_buf)]; try std.testing.expectEqualStrings(actual_cwd, expected_cwd); } -fn test_chdir_self() !void { +fn test_chdir_self(io: Io) !void { var old_cwd_buf: [path_max]u8 = undefined; - const old_cwd = try std.posix.getcwd(old_cwd_buf[0..]); + const old_cwd = old_cwd_buf[0..try std.process.currentDir(io, &old_cwd_buf)]; // Try changing to the current directory try std.Io.Threaded.chdir(old_cwd); - try expect_cwd(old_cwd); + try expect_cwd(io, old_cwd); } -fn test_chdir_absolute() !void { +fn test_chdir_absolute(io: Io) !void { var old_cwd_buf: [path_max]u8 = undefined; - const old_cwd = try std.posix.getcwd(old_cwd_buf[0..]); + const old_cwd = old_cwd_buf[0..try std.process.currentDir(io, &old_cwd_buf)]; const parent = std.fs.path.dirname(old_cwd) orelse unreachable; // old_cwd should be absolute // Try changing to the parent via a full path try std.Io.Threaded.chdir(parent); - try expect_cwd(parent); + try expect_cwd(io, parent); } fn test_chdir_relative(gpa: Allocator, io: Io, tmp_dir: Io.Dir) !void { @@ -61,7 +62,7 @@ fn test_chdir_relative(gpa: Allocator, io: Io, tmp_dir: Io.Dir) !void { // Capture base working directory path, to build expected full path var base_cwd_buf: [path_max]u8 = undefined; - const base_cwd = try std.posix.getcwd(base_cwd_buf[0..]); + const base_cwd = base_cwd_buf[0..try std.process.currentDir(io, &base_cwd_buf)]; const expected_path = try std.fs.path.resolve(gpa, &.{ base_cwd, subdir_path }); defer gpa.free(expected_path); @@ -70,7 +71,7 @@ fn test_chdir_relative(gpa: Allocator, io: Io, tmp_dir: Io.Dir) !void { try std.Io.Threaded.chdir(subdir_path); var new_cwd_buf: [path_max]u8 = undefined; - const new_cwd = try std.posix.getcwd(new_cwd_buf[0..]); + const new_cwd = new_cwd_buf[0..try std.process.currentDir(io, &new_cwd_buf)]; // On Windows, fs.path.resolve returns an uppercase drive letter, but the drive letter returned by getcwd may be lowercase const resolved_cwd = try std.fs.path.resolve(gpa, &.{new_cwd}); diff --git a/test/standalone/self_exe_symlink/create-symlink.zig b/test/standalone/self_exe_symlink/create-symlink.zig index 32610ebcde6d07a4d1ef14b1dca4fab3dd3bd90b..aaa015c4b0d4332c5677851072a204d6fcd94e90 100644 --- a/test/standalone/self_exe_symlink/create-symlink.zig +++ b/test/standalone/self_exe_symlink/create-symlink.zig @@ -9,7 +9,7 @@ pub fn main(init: std.process.Init) !void { const exe_path = it.next() orelse unreachable; const symlink_path = it.next() orelse unreachable; - const cwd = try std.process.getCwdAlloc(init.arena.allocator()); + const cwd = try std.process.currentDirAlloc(io, init.arena.allocator()); // If `exe_path` is relative to our cwd, we need to convert it to be relative to the dirname of `symlink_path`. const exe_rel_path = try std.fs.path.relative(gpa, cwd, init.environ_map, std.fs.path.dirname(symlink_path) orelse ".", exe_path); diff --git a/test/standalone/windows_paths/relative.zig b/test/standalone/windows_paths/relative.zig index 7b3725e4eec0b5e5774f7ac3650321b959193504..7dcde62d50dbfc13663251ae4600e3340cd79e8d 100644 --- a/test/standalone/windows_paths/relative.zig +++ b/test/standalone/windows_paths/relative.zig @@ -4,7 +4,7 @@ pub fn main(init: std.process.Init) !void { const arena = init.arena.allocator(); const args = try init.minimal.args.toSlice(arena); const io = init.io; - const cwd_path = try std.process.getCwdAlloc(arena); + const cwd_path = try std.process.currentDirAlloc(io, arena); if (args.len < 3) return error.MissingArgs; diff --git a/test/standalone/windows_paths/test.zig b/test/standalone/windows_paths/test.zig index 94afe163372b1cfdceaa5bd4ac32908b0c711657..47b13415a8396bb5f499966c9c42c4dbd2cc5665 100644 --- a/test/standalone/windows_paths/test.zig +++ b/test/standalone/windows_paths/test.zig @@ -10,7 +10,7 @@ pub fn main(init: std.process.Init) !void { const exe_path = args[1]; - const cwd_path = try std.process.getCwdAlloc(arena); + const cwd_path = try std.process.currentDirAlloc(io, arena); const parsed_cwd_path = std.fs.path.parsePathWindows(u8, cwd_path); if (parsed_cwd_path.kind == .drive_absolute and !std.ascii.isAlphabetic(cwd_path[0])) { diff --git a/test/standalone/windows_spawn/main.zig b/test/standalone/windows_spawn/main.zig index add20921b5d4b51bf2570d62077329e6f11efd3b..0bb8e7fe16dfba1676fa08013dddcd857250e0e4 100644 --- a/test/standalone/windows_spawn/main.zig +++ b/test/standalone/windows_spawn/main.zig @@ -8,7 +8,7 @@ const utf16Literal = std.unicode.utf8ToUtf16LeStringLiteral; pub fn main(init: std.process.Init) !void { const gpa = init.gpa; const io = init.io; - const process_cwd_path = try std.process.getCwdAlloc(init.arena.allocator()); + const process_cwd_path = try std.process.currentDirAlloc(io, init.arena.allocator()); var initial_process_cwd = try Io.Dir.cwd().openDir(io, ".", .{}); defer initial_process_cwd.close(io); diff --git a/tools/doctest.zig b/tools/doctest.zig index 5037bac2c2fc1aa712e0d3e4217b2925b59e6cd2..5afec8e62c7313b577a5eeb9ab05d88a560f21b2 100644 --- a/tools/doctest.zig +++ b/tools/doctest.zig @@ -33,7 +33,7 @@ pub fn main(init: std.process.Init) !void { const arena = init.arena.allocator(); const io = init.io; const environ_map = init.environ_map; - const cwd_path = try std.process.getCwdAlloc(arena); + const cwd_path = try std.process.currentDirAlloc(io, arena); try environ_map.put("CLICOLOR_FORCE", "1"); diff --git a/tools/incr-check.zig b/tools/incr-check.zig index daf93be73f8e1dcf7beb294fa4307f4d1726d80a..e15a91b548b4f3e74bcd3bd30b23ba56e7287935 100644 --- a/tools/incr-check.zig +++ b/tools/incr-check.zig @@ -32,7 +32,7 @@ pub fn main(init: std.process.Init) !void { const arena = init.arena.allocator(); const io = init.io; const environ_map = init.environ_map; - const cwd_path = try std.process.getCwdAlloc(arena); + const cwd_path = try std.process.currentDirAlloc(io, arena); var opt_zig_exe: ?[]const u8 = null; var opt_input_file_name: ?[]const u8 = null; diff --git a/tools/process_headers.zig b/tools/process_headers.zig index 0c72299055e11d1c02364d8e8d5afd0da2c460e2..6e5ab387979566901bb7406dc87a9d330b989a2a 100644 --- a/tools/process_headers.zig +++ b/tools/process_headers.zig @@ -145,7 +145,7 @@ pub fn main(init: std.process.Init) !void { const arena = init.arena.allocator(); const io = init.io; const args = try init.minimal.args.toSlice(arena); - const cwd_path = try std.process.getCwdAlloc(arena); + const cwd_path = try std.process.currentDirAlloc(io, arena); const environ_map = init.environ_map; var search_paths = std.array_list.Managed([]const u8).init(arena); diff --git a/tools/update-linux-headers.zig b/tools/update-linux-headers.zig index e05ba082031cbee62f0aab9638f127bfbc2ae0ff..649459a634b94cd73c6845d656b36e5c078dfbec 100644 --- a/tools/update-linux-headers.zig +++ b/tools/update-linux-headers.zig @@ -146,7 +146,7 @@ pub fn main(init: std.process.Init) !void { const io = init.io; const args = try init.minimal.args.toSlice(arena); const environ_map = init.environ_map; - const cwd = try std.process.getCwdAlloc(arena); + const cwd = try std.process.currentDirAlloc(io, arena); var search_paths = std.array_list.Managed([]const u8).init(arena); var opt_out_dir: ?[]const u8 = null; -- 2.54.0 From 3cc5dda7568b845d5002be4dd3181145ecc3f8bd Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 28 Jan 2026 23:18:24 -0800 Subject: [PATCH 075/499] fix RtlGetCurrentDirectory_U parameter it's the byte length not number of wchars --- lib/std/Io/Threaded.zig | 2 +- lib/std/os/windows/ntdll.zig | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 2690734e87c60e1fe41a95329e38b2a10cc3bf22..1a15bae33b89b60895f5130bae24cd61b9da4e4c 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -12607,7 +12607,7 @@ fn processCurrentDir(userdata: ?*anyopaque, buffer: []u8) process.CurrentDirErro _ = t; if (is_windows) { var wtf16le_buf: [windows.PATH_MAX_WIDE:0]u16 = undefined; - const n = windows.ntdll.RtlGetCurrentDirectory_U(wtf16le_buf.len + 1, &wtf16le_buf); + const n = windows.ntdll.RtlGetCurrentDirectory_U(wtf16le_buf.len * 2 + 2, &wtf16le_buf) / 2; if (n == 0) return error.Unexpected; assert(n <= wtf16le_buf.len); const wtf16le_slice = wtf16le_buf[0..n]; diff --git a/lib/std/os/windows/ntdll.zig b/lib/std/os/windows/ntdll.zig index 52361ed982942864af6bd395a947e35e600b7adc..f61cbbf5b8fe8bc468ad2f93638ca60503c54ed2 100644 --- a/lib/std/os/windows/ntdll.zig +++ b/lib/std/os/windows/ntdll.zig @@ -497,7 +497,7 @@ pub extern "ntdll" fn RtlGetFullPathName_U( ) callconv(.winapi) ULONG; pub extern "ntdll" fn RtlGetCurrentDirectory_U( - BufferLength: ULONG, + BufferByteLength: ULONG, Buffer: [*]u16, ) callconv(.winapi) ULONG; -- 2.54.0 From 0a37ad2ec4daa04a845142508e0dd1326db7ee2d Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 29 Jan 2026 01:13:32 -0800 Subject: [PATCH 076/499] std.Io.File: handle DISK_FULL on windows --- lib/std/Io/Threaded.zig | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 1a15bae33b89b60895f5130bae24cd61b9da4e4c..1a01795213ad9aafa46e8492cfdd13cb4a7c677a 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -2730,6 +2730,7 @@ fn dirCreateDirPathOpenWindows( // This can happen if the directory has 'List folder contents' permission set to 'Deny' // and the directory is trying to be opened for iteration. .ACCESS_DENIED => return syscall.fail(error.AccessDenied), + .DISK_FULL => return syscall.fail(error.NoSpaceLeft), .INVALID_PARAMETER => |s| return syscall.ntstatusBug(s), else => |s| return syscall.unexpectedNtstatus(s), }; @@ -3670,6 +3671,7 @@ fn dirCreateFileWindows( .NOT_A_DIRECTORY => return syscall.fail(error.NotDir), .USER_MAPPED_FILE => return syscall.fail(error.AccessDenied), .VIRUS_INFECTED, .VIRUS_DELETED => return syscall.fail(error.AntivirusInterference), + .DISK_FULL => return syscall.fail(error.NoSpaceLeft), .INVALID_PARAMETER => |status| return syscall.ntstatusBug(status), .OBJECT_PATH_SYNTAX_BAD => |status| return syscall.ntstatusBug(status), .INVALID_HANDLE => |status| return syscall.ntstatusBug(status), @@ -9215,6 +9217,7 @@ fn writeFilePositionalWindows( .LOCK_VIOLATION => return syscall.fail(error.LockViolation), .ACCESS_DENIED => return syscall.fail(error.AccessDenied), .WORKING_SET_QUOTA => return syscall.fail(error.SystemResources), + .DISK_FULL => return syscall.fail(error.NoSpaceLeft), else => |err| { syscall.finish(); return windows.unexpectedError(err); @@ -9375,6 +9378,7 @@ fn writeFileStreamingWindows( .LOCK_VIOLATION => return syscall.fail(error.LockViolation), .ACCESS_DENIED => return syscall.fail(error.AccessDenied), .WORKING_SET_QUOTA => return syscall.fail(error.SystemResources), + .DISK_FULL => return syscall.fail(error.NoSpaceLeft), else => |err| { syscall.finish(); return windows.unexpectedError(err); -- 2.54.0 From b1d1806fef56a72d0df14ad3a54c3074b1e55a69 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 29 Jan 2026 18:47:58 -0800 Subject: [PATCH 077/499] std.process: currentDir -> currentPath In Zig standard library, Dir means an open directory handle. path represents a file system identifier string. This function is better named after "current path" than "current dir". "get" and "working" are superfluous. --- lib/compiler/build_runner.zig | 2 +- lib/std/Build/Cache.zig | 8 +++---- lib/std/Build/Step/Options.zig | 2 +- lib/std/Io.zig | 2 +- lib/std/Io/Threaded.zig | 8 +++---- lib/std/fs/test.zig | 2 +- lib/std/process.zig | 22 +++++++++---------- src/introspect.zig | 8 +++---- test/standalone/child_process/main.zig | 2 +- test/standalone/posix/cwd.zig | 10 ++++----- .../self_exe_symlink/create-symlink.zig | 2 +- test/standalone/windows_paths/relative.zig | 2 +- test/standalone/windows_paths/test.zig | 2 +- test/standalone/windows_spawn/main.zig | 2 +- tools/doctest.zig | 2 +- tools/incr-check.zig | 2 +- tools/process_headers.zig | 2 +- tools/update-linux-headers.zig | 2 +- 18 files changed, 41 insertions(+), 41 deletions(-) diff --git a/lib/compiler/build_runner.zig b/lib/compiler/build_runner.zig index 6ef8e71f7eea56aa1d5e7efd58c2f3af716e5b05..5ef74adabab9e651d9cc65730b43c4fd783a2aa0 100644 --- a/lib/compiler/build_runner.zig +++ b/lib/compiler/build_runner.zig @@ -83,7 +83,7 @@ pub fn main(init: process.Init.Minimal) !void { .io = io, .gpa = gpa, .manifest_dir = try local_cache_directory.handle.createDirPathOpen(io, "h", .{}), - .cwd = try process.currentDirAlloc(io, single_threaded_arena.allocator()), + .cwd = try process.currentPathAlloc(io, single_threaded_arena.allocator()), }, .zig_exe = zig_exe, .environ_map = try init.environ.createMap(arena), diff --git a/lib/std/Build/Cache.zig b/lib/std/Build/Cache.zig index f595435749554ce81fa8b183792e918ae3ee9aca..0cf9dde5313a5c084c7c259542d6c0a83d415339 100644 --- a/lib/std/Build/Cache.zig +++ b/lib/std/Build/Cache.zig @@ -1315,7 +1315,7 @@ test "cache file and then recall it" { var tmp = testing.tmpDir(.{}); defer tmp.cleanup(); - const cwd = try std.process.currentDirAlloc(io, testing.allocator); + const cwd = try std.process.currentPathAlloc(io, testing.allocator); defer testing.allocator.free(cwd); const temp_file = "test.txt"; @@ -1383,7 +1383,7 @@ test "check that changing a file makes cache fail" { var tmp = testing.tmpDir(.{}); defer tmp.cleanup(); - const cwd = try std.process.currentDirAlloc(io, testing.allocator); + const cwd = try std.process.currentPathAlloc(io, testing.allocator); defer testing.allocator.free(cwd); const temp_file = "cache_hash_change_file_test.txt"; @@ -1459,7 +1459,7 @@ test "no file inputs" { var tmp = testing.tmpDir(.{}); defer tmp.cleanup(); - const cwd = try std.process.currentDirAlloc(io, testing.allocator); + const cwd = try std.process.currentPathAlloc(io, testing.allocator); defer testing.allocator.free(cwd); const temp_manifest_dir = "no_file_inputs_manifest_dir"; @@ -1509,7 +1509,7 @@ test "Manifest with files added after initial hash work" { var tmp = testing.tmpDir(.{}); defer tmp.cleanup(); - const cwd = try std.process.currentDirAlloc(io, testing.allocator); + const cwd = try std.process.currentPathAlloc(io, testing.allocator); defer testing.allocator.free(cwd); const temp_file1 = "cache_hash_post_file_test1.txt"; diff --git a/lib/std/Build/Step/Options.zig b/lib/std/Build/Step/Options.zig index adef0484237e8bde983662998eee35d764393c1d..34073264e888553f1ff59b74959ede7a675b0241 100644 --- a/lib/std/Build/Step/Options.zig +++ b/lib/std/Build/Step/Options.zig @@ -519,7 +519,7 @@ test Options { var arena = std.heap.ArenaAllocator.init(std.testing.allocator); defer arena.deinit(); - const cwd = try std.process.currentDirAlloc(io, std.testing.allocator); + const cwd = try std.process.currentPathAlloc(io, std.testing.allocator); defer std.testing.allocator.free(cwd); var graph: std.Build.Graph = .{ diff --git a/lib/std/Io.zig b/lib/std/Io.zig index 20c23e804ec3b05e9ada2f55fa82f579bc136a09..72df9e34f3ec9523577a921a43eb3086586fca4e 100644 --- a/lib/std/Io.zig +++ b/lib/std/Io.zig @@ -665,7 +665,7 @@ pub const VTable = struct { lockStderr: *const fn (?*anyopaque, ?Terminal.Mode) Cancelable!LockedStderr, tryLockStderr: *const fn (?*anyopaque, ?Terminal.Mode) Cancelable!?LockedStderr, unlockStderr: *const fn (?*anyopaque) void, - processCurrentDir: *const fn (?*anyopaque, buffer: []u8) std.process.CurrentDirError!usize, + processCurrentPath: *const fn (?*anyopaque, buffer: []u8) std.process.CurrentPathError!usize, processSetCurrentDir: *const fn (?*anyopaque, Dir) std.process.SetCurrentDirError!void, processReplace: *const fn (?*anyopaque, std.process.ReplaceOptions) std.process.ReplaceError, processReplacePath: *const fn (?*anyopaque, Dir, std.process.ReplaceOptions) std.process.ReplaceError, diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 1a01795213ad9aafa46e8492cfdd13cb4a7c677a..87cf72190c3736ee5e3ca37cc983ce6f14b6e192 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -1643,7 +1643,7 @@ pub fn io(t: *Threaded) Io { .lockStderr = lockStderr, .tryLockStderr = tryLockStderr, .unlockStderr = unlockStderr, - .processCurrentDir = processCurrentDir, + .processCurrentPath = processCurrentPath, .processSetCurrentDir = processSetCurrentDir, .processReplace = processReplace, .processReplacePath = processReplacePath, @@ -1802,7 +1802,7 @@ pub fn ioBasic(t: *Threaded) Io { .lockStderr = lockStderr, .tryLockStderr = tryLockStderr, .unlockStderr = unlockStderr, - .processCurrentDir = processCurrentDir, + .processCurrentPath = processCurrentPath, .processSetCurrentDir = processSetCurrentDir, .processReplace = processReplace, .processReplacePath = processReplacePath, @@ -12606,7 +12606,7 @@ fn unlockStderr(userdata: ?*anyopaque) void { process.stderr_thread_mutex.unlock(); } -fn processCurrentDir(userdata: ?*anyopaque, buffer: []u8) process.CurrentDirError!usize { +fn processCurrentPath(userdata: ?*anyopaque, buffer: []u8) process.CurrentPathError!usize { const t: *Threaded = @ptrCast(@alignCast(userdata)); _ = t; if (is_windows) { @@ -12638,7 +12638,7 @@ fn processCurrentDir(userdata: ?*anyopaque, buffer: []u8) process.CurrentDirErro }; switch (err) { .SUCCESS => return std.mem.findScalar(u8, buffer, 0).?, - .NOENT => return error.CurrentWorkingDirectoryUnlinked, + .NOENT => return error.CurrentDirUnlinked, .RANGE => return error.NameTooLong, .FAULT => |e| return errnoBug(e), .INVAL => |e| return errnoBug(e), diff --git a/lib/std/fs/test.zig b/lib/std/fs/test.zig index be59393e3da32ee7e1ace2bab04d37fb1fc45b87..d0ec6b33e9cda1cbf6b5d27eeb1e9e6d1b831dda 100644 --- a/lib/std/fs/test.zig +++ b/lib/std/fs/test.zig @@ -1787,7 +1787,7 @@ test "open file with exclusive nonblocking lock twice (absolute paths)" { const gpa = testing.allocator; - const cwd = try std.process.currentDirAlloc(io, gpa); + const cwd = try std.process.currentPathAlloc(io, gpa); defer gpa.free(cwd); const filename = try Dir.path.resolve(gpa, &.{ cwd, sub_path }); diff --git a/lib/std/process.zig b/lib/std/process.zig index dd3e07c1eaf62dfd7011eeeaada3f8e44711b0bb..5bcacddc84a993d96586b861411af63148f361ac 100644 --- a/lib/std/process.zig +++ b/lib/std/process.zig @@ -63,22 +63,22 @@ pub const Init = struct { }; }; -pub const CurrentDirError = error{ +pub const CurrentPathError = error{ NameTooLong, /// Not possible on Windows. Always returned on WASI. - CurrentWorkingDirectoryUnlinked, + CurrentDirUnlinked, } || Io.UnexpectedError; /// On Windows, the result is encoded as [WTF-8](https://wtf-8.codeberg.page/). /// On other platforms, the result is an opaque sequence of bytes with no /// particular encoding. -pub fn currentDir(io: Io, buffer: []u8) CurrentDirError!usize { - return io.vtable.processCurrentDir(io.userdata, buffer); +pub fn currentPath(io: Io, buffer: []u8) CurrentPathError!usize { + return io.vtable.processCurrentPath(io.userdata, buffer); } -pub const CurrentDirAllocError = Allocator.Error || error{ +pub const CurrentPathAllocError = Allocator.Error || error{ /// Not possible on Windows. Always returned on WASI. - CurrentWorkingDirectoryUnlinked, + CurrentDirUnlinked, } || Io.UnexpectedError; /// On Windows, the result is encoded as [WTF-8](https://wtf-8.codeberg.page/). @@ -86,17 +86,17 @@ pub const CurrentDirAllocError = Allocator.Error || error{ /// particular encoding. /// /// Caller owns returned memory. -pub fn currentDirAlloc(io: Io, allocator: Allocator) CurrentDirAllocError![:0]u8 { +pub fn currentPathAlloc(io: Io, allocator: Allocator) CurrentPathAllocError![:0]u8 { var buffer: [max_path_bytes]u8 = undefined; - const n = currentDir(io, &buffer) catch |err| switch (err) { + const n = currentPath(io, &buffer) catch |err| switch (err) { error.NameTooLong => unreachable, else => |e| return e, }; return allocator.dupeZ(u8, buffer[0..n]); } -test currentDirAlloc { - const cwd = try currentDirAlloc(testing.io, testing.allocator); +test currentPathAlloc { + const cwd = try currentPathAlloc(testing.io, testing.allocator); testing.allocator.free(cwd); } @@ -453,7 +453,7 @@ pub fn spawnPath(io: Io, dir: Io.Dir, options: SpawnOptions) SpawnError!Child { return io.vtable.processSpawnPath(io.userdata, dir, options); } -pub const RunError = CurrentDirError || posix.ReadError || SpawnError || posix.PollError || error{ +pub const RunError = CurrentPathError || posix.ReadError || SpawnError || posix.PollError || error{ StdoutStreamTooLong, StderrStreamTooLong, }; diff --git a/src/introspect.zig b/src/introspect.zig index 36e94e979fc14e1ff92408d585544b2524901e7c..0cba9fdcc4d5d97f03559ab205d3710f54205176 100644 --- a/src/introspect.zig +++ b/src/introspect.zig @@ -51,23 +51,23 @@ pub fn findZigLibDir(gpa: Allocator, io: Io) !Cache.Directory { return findZigLibDirFromSelfExe(gpa, io, cwd_path, self_exe_path); } -/// Like `std.process.currentDirAlloc`, but also resolves the path with `Dir.path.resolve`. This +/// Like `std.process.currentPathAlloc`, but also resolves the path with `Dir.path.resolve`. This /// means the path has no repeated separators, no "." or ".." components, and no trailing separator. /// On WASI, "" is returned instead of ".". pub fn getResolvedCwd(io: Io, gpa: Allocator) error{ OutOfMemory, - CurrentWorkingDirectoryUnlinked, + CurrentDirUnlinked, Unexpected, }![]u8 { if (builtin.target.os.tag == .wasi) { if (std.debug.runtime_safety) { - const cwd = try std.process.currentDirAlloc(io, gpa); + const cwd = try std.process.currentPathAlloc(io, gpa); defer gpa.free(cwd); assert(mem.eql(u8, cwd, ".")); } return ""; } - const cwd = try std.process.currentDirAlloc(io, gpa); + const cwd = try std.process.currentPathAlloc(io, gpa); defer gpa.free(cwd); const resolved = try Dir.path.resolve(gpa, &.{cwd}); assert(Dir.path.isAbsolute(resolved)); diff --git a/test/standalone/child_process/main.zig b/test/standalone/child_process/main.zig index 159be62a9d1e6beb8495a628baabb18898e126ef..d252ee414d48a546654c939a411bddf99788f57c 100644 --- a/test/standalone/child_process/main.zig +++ b/test/standalone/child_process/main.zig @@ -16,7 +16,7 @@ pub fn main(init: std.process.Init.Minimal) !void { defer threaded.deinit(); const io = threaded.io(); - const process_cwd_path = try std.process.currentDirAlloc(io, gpa); + const process_cwd_path = try std.process.currentPathAlloc(io, gpa); defer gpa.free(process_cwd_path); var environ_map = try init.environ.createMap(gpa); diff --git a/test/standalone/posix/cwd.zig b/test/standalone/posix/cwd.zig index e5e376784a1d64dbb2323a10a70680d69edbe399..2588bde34f018f97b2ef799d2809ef7872a3b7df 100644 --- a/test/standalone/posix/cwd.zig +++ b/test/standalone/posix/cwd.zig @@ -28,13 +28,13 @@ pub fn main(init: std.process.Init) !void { // get current working directory and expect it to match given path fn expect_cwd(io: Io, expected_cwd: []const u8) !void { var cwd_buf: [path_max]u8 = undefined; - const actual_cwd = cwd_buf[0..try std.process.currentDir(io, &cwd_buf)]; + const actual_cwd = cwd_buf[0..try std.process.currentPath(io, &cwd_buf)]; try std.testing.expectEqualStrings(actual_cwd, expected_cwd); } fn test_chdir_self(io: Io) !void { var old_cwd_buf: [path_max]u8 = undefined; - const old_cwd = old_cwd_buf[0..try std.process.currentDir(io, &old_cwd_buf)]; + const old_cwd = old_cwd_buf[0..try std.process.currentPath(io, &old_cwd_buf)]; // Try changing to the current directory try std.Io.Threaded.chdir(old_cwd); @@ -43,7 +43,7 @@ fn test_chdir_self(io: Io) !void { fn test_chdir_absolute(io: Io) !void { var old_cwd_buf: [path_max]u8 = undefined; - const old_cwd = old_cwd_buf[0..try std.process.currentDir(io, &old_cwd_buf)]; + const old_cwd = old_cwd_buf[0..try std.process.currentPath(io, &old_cwd_buf)]; const parent = std.fs.path.dirname(old_cwd) orelse unreachable; // old_cwd should be absolute @@ -62,7 +62,7 @@ fn test_chdir_relative(gpa: Allocator, io: Io, tmp_dir: Io.Dir) !void { // Capture base working directory path, to build expected full path var base_cwd_buf: [path_max]u8 = undefined; - const base_cwd = base_cwd_buf[0..try std.process.currentDir(io, &base_cwd_buf)]; + const base_cwd = base_cwd_buf[0..try std.process.currentPath(io, &base_cwd_buf)]; const expected_path = try std.fs.path.resolve(gpa, &.{ base_cwd, subdir_path }); defer gpa.free(expected_path); @@ -71,7 +71,7 @@ fn test_chdir_relative(gpa: Allocator, io: Io, tmp_dir: Io.Dir) !void { try std.Io.Threaded.chdir(subdir_path); var new_cwd_buf: [path_max]u8 = undefined; - const new_cwd = new_cwd_buf[0..try std.process.currentDir(io, &new_cwd_buf)]; + const new_cwd = new_cwd_buf[0..try std.process.currentPath(io, &new_cwd_buf)]; // On Windows, fs.path.resolve returns an uppercase drive letter, but the drive letter returned by getcwd may be lowercase const resolved_cwd = try std.fs.path.resolve(gpa, &.{new_cwd}); diff --git a/test/standalone/self_exe_symlink/create-symlink.zig b/test/standalone/self_exe_symlink/create-symlink.zig index aaa015c4b0d4332c5677851072a204d6fcd94e90..36a0c09080a5d93876ed4fdb7768f406432e23b2 100644 --- a/test/standalone/self_exe_symlink/create-symlink.zig +++ b/test/standalone/self_exe_symlink/create-symlink.zig @@ -9,7 +9,7 @@ pub fn main(init: std.process.Init) !void { const exe_path = it.next() orelse unreachable; const symlink_path = it.next() orelse unreachable; - const cwd = try std.process.currentDirAlloc(io, init.arena.allocator()); + const cwd = try std.process.currentPathAlloc(io, init.arena.allocator()); // If `exe_path` is relative to our cwd, we need to convert it to be relative to the dirname of `symlink_path`. const exe_rel_path = try std.fs.path.relative(gpa, cwd, init.environ_map, std.fs.path.dirname(symlink_path) orelse ".", exe_path); diff --git a/test/standalone/windows_paths/relative.zig b/test/standalone/windows_paths/relative.zig index 7dcde62d50dbfc13663251ae4600e3340cd79e8d..6fd2fc188af1f77fd96da10e784d3874503fd562 100644 --- a/test/standalone/windows_paths/relative.zig +++ b/test/standalone/windows_paths/relative.zig @@ -4,7 +4,7 @@ pub fn main(init: std.process.Init) !void { const arena = init.arena.allocator(); const args = try init.minimal.args.toSlice(arena); const io = init.io; - const cwd_path = try std.process.currentDirAlloc(io, arena); + const cwd_path = try std.process.currentPathAlloc(io, arena); if (args.len < 3) return error.MissingArgs; diff --git a/test/standalone/windows_paths/test.zig b/test/standalone/windows_paths/test.zig index 47b13415a8396bb5f499966c9c42c4dbd2cc5665..1170de47ac7245480a48585a20089295d4cd82a4 100644 --- a/test/standalone/windows_paths/test.zig +++ b/test/standalone/windows_paths/test.zig @@ -10,7 +10,7 @@ pub fn main(init: std.process.Init) !void { const exe_path = args[1]; - const cwd_path = try std.process.currentDirAlloc(io, arena); + const cwd_path = try std.process.currentPathAlloc(io, arena); const parsed_cwd_path = std.fs.path.parsePathWindows(u8, cwd_path); if (parsed_cwd_path.kind == .drive_absolute and !std.ascii.isAlphabetic(cwd_path[0])) { diff --git a/test/standalone/windows_spawn/main.zig b/test/standalone/windows_spawn/main.zig index 0bb8e7fe16dfba1676fa08013dddcd857250e0e4..4b37e3cf6ee28066e2424725d5e0412ab5f6764d 100644 --- a/test/standalone/windows_spawn/main.zig +++ b/test/standalone/windows_spawn/main.zig @@ -8,7 +8,7 @@ const utf16Literal = std.unicode.utf8ToUtf16LeStringLiteral; pub fn main(init: std.process.Init) !void { const gpa = init.gpa; const io = init.io; - const process_cwd_path = try std.process.currentDirAlloc(io, init.arena.allocator()); + const process_cwd_path = try std.process.currentPathAlloc(io, init.arena.allocator()); var initial_process_cwd = try Io.Dir.cwd().openDir(io, ".", .{}); defer initial_process_cwd.close(io); diff --git a/tools/doctest.zig b/tools/doctest.zig index 5afec8e62c7313b577a5eeb9ab05d88a560f21b2..55b8ca7bfb87fae39b98550812b9dee165538f46 100644 --- a/tools/doctest.zig +++ b/tools/doctest.zig @@ -33,7 +33,7 @@ pub fn main(init: std.process.Init) !void { const arena = init.arena.allocator(); const io = init.io; const environ_map = init.environ_map; - const cwd_path = try std.process.currentDirAlloc(io, arena); + const cwd_path = try std.process.currentPathAlloc(io, arena); try environ_map.put("CLICOLOR_FORCE", "1"); diff --git a/tools/incr-check.zig b/tools/incr-check.zig index e15a91b548b4f3e74bcd3bd30b23ba56e7287935..c9564f85c25ac752e46f744a07fa519a83e54ac2 100644 --- a/tools/incr-check.zig +++ b/tools/incr-check.zig @@ -32,7 +32,7 @@ pub fn main(init: std.process.Init) !void { const arena = init.arena.allocator(); const io = init.io; const environ_map = init.environ_map; - const cwd_path = try std.process.currentDirAlloc(io, arena); + const cwd_path = try std.process.currentPathAlloc(io, arena); var opt_zig_exe: ?[]const u8 = null; var opt_input_file_name: ?[]const u8 = null; diff --git a/tools/process_headers.zig b/tools/process_headers.zig index 6e5ab387979566901bb7406dc87a9d330b989a2a..3be919b30358c24f46cbb8ef418ddd232c854168 100644 --- a/tools/process_headers.zig +++ b/tools/process_headers.zig @@ -145,7 +145,7 @@ pub fn main(init: std.process.Init) !void { const arena = init.arena.allocator(); const io = init.io; const args = try init.minimal.args.toSlice(arena); - const cwd_path = try std.process.currentDirAlloc(io, arena); + const cwd_path = try std.process.currentPathAlloc(io, arena); const environ_map = init.environ_map; var search_paths = std.array_list.Managed([]const u8).init(arena); diff --git a/tools/update-linux-headers.zig b/tools/update-linux-headers.zig index 649459a634b94cd73c6845d656b36e5c078dfbec..c5f556f53c9cd2fa595aa3db9fc23cea27910843 100644 --- a/tools/update-linux-headers.zig +++ b/tools/update-linux-headers.zig @@ -146,7 +146,7 @@ pub fn main(init: std.process.Init) !void { const io = init.io; const args = try init.minimal.args.toSlice(arena); const environ_map = init.environ_map; - const cwd = try std.process.currentDirAlloc(io, arena); + const cwd = try std.process.currentPathAlloc(io, arena); var search_paths = std.array_list.Managed([]const u8).init(arena); var opt_out_dir: ?[]const u8 = null; -- 2.54.0 From ecb9ddf2672fa1f067c240d914a5baf3a4d2d8a4 Mon Sep 17 00:00:00 2001 From: Brandon Black Date: Wed, 28 Jan 2026 16:54:04 -0600 Subject: [PATCH 078/499] Threaded.sleepPosix: fix libc error handling Confusingly, the POSIX spec for clock_nanosleep() says it returns *positive* error values directly and does not touch `errno`. Not detecting EINTR properly here was breaking the cancellation of threads blocked in this call when linking libc. --- lib/std/Io/Threaded.zig | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index c74850ca0a3b653f011cb30ab971f9e5a8c46958..b459c4e408695e184f227a22e7018ad009540a0f 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -10083,10 +10083,12 @@ fn sleepPosix(timeout: Io.Timeout) Io.SleepError!void { var timespec: posix.timespec = timestampToPosix(deadline_nanoseconds); const syscall: Syscall = try .start(); while (true) { - switch (posix.errno(posix.system.clock_nanosleep(clock_id, .{ .ABSTIME = switch (timeout) { + const rc = posix.system.clock_nanosleep(clock_id, .{ .ABSTIME = switch (timeout) { .none, .duration => false, .deadline => true, - } }, ×pec, ×pec))) { + } }, ×pec, ×pec); + // POSIX-standard libc clock_nanosleep() returns *positive* errno values directly + switch (if (builtin.link_libc) @as(posix.E, @enumFromInt(rc)) else posix.errno(rc)) { .SUCCESS => { syscall.finish(); return; -- 2.54.0 From ad0458f5826a9283df3e152fea24869d0914634d Mon Sep 17 00:00:00 2001 From: Pablo Alessandro Santos Hugen Date: Thu, 29 Jan 2026 19:24:39 -0300 Subject: [PATCH 079/499] std.Build: Fix wrong error enum Signed-off-by: Pablo Alessandro Santos Hugen --- lib/std/Build/Step/Run.zig | 13 ++++++------- 1 file changed, 6 insertions(+), 7 deletions(-) diff --git a/lib/std/Build/Step/Run.zig b/lib/std/Build/Step/Run.zig index d822697ae56fa6ac6ab29fa083a8ca9aa53de652..d025e01af40f8d3580e37ad830ee7515f0608cb4 100644 --- a/lib/std/Build/Step/Run.zig +++ b/lib/std/Build/Step/Run.zig @@ -1033,8 +1033,8 @@ fn make(step: *Step, options: Step.MakeOptions) !void { if (any_output) { const o_sub_path = "o" ++ Dir.path.sep_str ++ &digest; - b.cache_root.handle.rename(tmp_dir_path, b.cache_root.handle, o_sub_path, io) catch |err| { - if (err == error.PathAlreadyExists) { + b.cache_root.handle.rename(tmp_dir_path, b.cache_root.handle, o_sub_path, io) catch |err| switch (err) { + Dir.RenameError.DirNotEmpty => { b.cache_root.handle.deleteTree(io, o_sub_path) catch |del_err| { return step.fail("unable to remove dir '{f}'{s}: {t}", .{ b.cache_root, tmp_dir_path, del_err, @@ -1045,11 +1045,10 @@ fn make(step: *Step, options: Step.MakeOptions) !void { b.cache_root, tmp_dir_path, b.cache_root, o_sub_path, retry_err, }); }; - } else { - return step.fail("unable to rename dir '{f}{s}' to '{f}{s}': {t}", .{ - b.cache_root, tmp_dir_path, b.cache_root, o_sub_path, err, - }); - } + }, + else => return step.fail("unable to rename dir '{f}{s}' to '{f}{s}': {t}", .{ + b.cache_root, tmp_dir_path, b.cache_root, o_sub_path, err, + }), }; } -- 2.54.0 From ccd82ae7cc5ce3f708a919a070bffc7572416c62 Mon Sep 17 00:00:00 2001 From: lzm-build <3575188313@qq.com> Date: Fri, 30 Jan 2026 07:18:13 +0800 Subject: [PATCH 080/499] Add `f16`, `f80` and `f128` support for `atan` --- lib/c/math.zig | 67 ++- lib/libc/musl/src/math/atan.c | 116 ----- lib/libc/musl/src/math/atanf.c | 94 ---- lib/libc/musl/src/math/atanl.c | 184 ------- lib/libc/musl/src/math/i386/atan.s | 16 - lib/libc/musl/src/math/i386/atanf.s | 18 - lib/libc/musl/src/math/i386/atanl.s | 7 - lib/libc/musl/src/math/x32/atanl.s | 7 - lib/libc/musl/src/math/x86_64/atanl.s | 7 - lib/std/math/atan.zig | 673 +++++++++++++++++++------- src/libs/musl.zig | 8 - src/libs/wasi_libc.zig | 3 - 12 files changed, 544 insertions(+), 656 deletions(-) delete mode 100644 lib/libc/musl/src/math/atan.c delete mode 100644 lib/libc/musl/src/math/atanf.c delete mode 100644 lib/libc/musl/src/math/atanl.c delete mode 100644 lib/libc/musl/src/math/i386/atan.s delete mode 100644 lib/libc/musl/src/math/i386/atanf.s delete mode 100644 lib/libc/musl/src/math/i386/atanl.s delete mode 100644 lib/libc/musl/src/math/x32/atanl.s delete mode 100644 lib/libc/musl/src/math/x86_64/atanl.s diff --git a/lib/c/math.zig b/lib/c/math.zig index 8a6c91be2efc735326a5a56d1fe3452f8837bb2b..a9e7c808a80adab4758c439209d9c8470319d442 100644 --- a/lib/c/math.zig +++ b/lib/c/math.zig @@ -1,4 +1,5 @@ const std = @import("std"); +const math = std.math; const common = @import("common.zig"); const builtin = @import("builtin"); @@ -11,20 +12,20 @@ comptime { @export(&isnanl, .{ .name = "isnanl", .linkage = common.linkage, .visibility = common.visibility }); @export(&isnanl, .{ .name = "__isnanl", .linkage = common.linkage, .visibility = common.visibility }); - @export(&std.math.nan(f64), .{ .name = "__QNAN", .linkage = common.linkage, .visibility = common.visibility }); - @export(&std.math.snan(f64), .{ .name = "__SNAN", .linkage = common.linkage, .visibility = common.visibility }); - @export(&std.math.inf(f64), .{ .name = "__INF", .linkage = common.linkage, .visibility = common.visibility }); - @export(&std.math.floatTrueMin(f64), .{ .name = "__DENORM", .linkage = common.linkage, .visibility = common.visibility }); + @export(&math.nan(f64), .{ .name = "__QNAN", .linkage = common.linkage, .visibility = common.visibility }); + @export(&math.snan(f64), .{ .name = "__SNAN", .linkage = common.linkage, .visibility = common.visibility }); + @export(&math.inf(f64), .{ .name = "__INF", .linkage = common.linkage, .visibility = common.visibility }); + @export(&math.floatTrueMin(f64), .{ .name = "__DENORM", .linkage = common.linkage, .visibility = common.visibility }); - @export(&std.math.nan(f32), .{ .name = "__QNANF", .linkage = common.linkage, .visibility = common.visibility }); - @export(&std.math.snan(f32), .{ .name = "__SNANF", .linkage = common.linkage, .visibility = common.visibility }); - @export(&std.math.inf(f32), .{ .name = "__INFF", .linkage = common.linkage, .visibility = common.visibility }); - @export(&std.math.floatTrueMin(f32), .{ .name = "__DENORMF", .linkage = common.linkage, .visibility = common.visibility }); + @export(&math.nan(f32), .{ .name = "__QNANF", .linkage = common.linkage, .visibility = common.visibility }); + @export(&math.snan(f32), .{ .name = "__SNANF", .linkage = common.linkage, .visibility = common.visibility }); + @export(&math.inf(f32), .{ .name = "__INFF", .linkage = common.linkage, .visibility = common.visibility }); + @export(&math.floatTrueMin(f32), .{ .name = "__DENORMF", .linkage = common.linkage, .visibility = common.visibility }); - @export(&std.math.nan(c_longdouble), .{ .name = "__QNANL", .linkage = common.linkage, .visibility = common.visibility }); - @export(&std.math.snan(c_longdouble), .{ .name = "__SNANL", .linkage = common.linkage, .visibility = common.visibility }); - @export(&std.math.inf(c_longdouble), .{ .name = "__INFL", .linkage = common.linkage, .visibility = common.visibility }); - @export(&std.math.floatTrueMin(c_longdouble), .{ .name = "__DENORML", .linkage = common.linkage, .visibility = common.visibility }); + @export(&math.nan(c_longdouble), .{ .name = "__QNANL", .linkage = common.linkage, .visibility = common.visibility }); + @export(&math.snan(c_longdouble), .{ .name = "__SNANL", .linkage = common.linkage, .visibility = common.visibility }); + @export(&math.inf(c_longdouble), .{ .name = "__INFL", .linkage = common.linkage, .visibility = common.visibility }); + @export(&math.floatTrueMin(c_longdouble), .{ .name = "__DENORML", .linkage = common.linkage, .visibility = common.visibility }); } if (builtin.target.isMinGW() or builtin.target.isMuslLibC() or builtin.target.isWasiLibC()) { @@ -35,6 +36,9 @@ comptime { if (builtin.target.isMuslLibC() or builtin.target.isWasiLibC()) { @export(&acos, .{ .name = "acos", .linkage = common.linkage, .visibility = common.visibility }); + @export(&atanf, .{ .name = "atanf", .linkage = common.linkage, .visibility = common.visibility }); + @export(&atan, .{ .name = "atan", .linkage = common.linkage, .visibility = common.visibility }); + @export(&atanl, .{ .name = "atanl", .linkage = common.linkage, .visibility = common.visibility }); } if (builtin.target.isMuslLibC()) { @@ -45,41 +49,60 @@ comptime { } fn acos(x: f64) callconv(.c) f64 { - return std.math.acos(x); + return math.acos(x); +} + +fn atanf(x: f32) callconv(.c) f32 { + return math.atan(x); +} + +fn atan(x: f64) callconv(.c) f64 { + return math.atan(x); +} + +fn atanl(x: c_longdouble) callconv(.c) c_longdouble { + return switch (@typeInfo(@TypeOf(x)).float.bits) { + 16 => math.atan(@as(f16, @floatCast(x))), + 32 => math.atan(@as(f32, @floatCast(x))), + 64 => math.atan(@as(f64, @floatCast(x))), + 80 => math.atan(@as(f80, @floatCast(x))), + 128 => math.atan(@as(f128, @floatCast(x))), + else => unreachable, + }; } fn isnan(x: f64) callconv(.c) c_int { - return if (std.math.isNan(x)) 1 else 0; + return if (math.isNan(x)) 1 else 0; } fn isnanf(x: f32) callconv(.c) c_int { - return if (std.math.isNan(x)) 1 else 0; + return if (math.isNan(x)) 1 else 0; } fn isnanl(x: c_longdouble) callconv(.c) c_int { - return if (std.math.isNan(x)) 1 else 0; + return if (math.isNan(x)) 1 else 0; } fn nan(_: [*:0]const c_char) callconv(.c) f64 { - return std.math.nan(f64); + return math.nan(f64); } fn nanf(_: [*:0]const c_char) callconv(.c) f32 { - return std.math.nan(f32); + return math.nan(f32); } fn nanl(_: [*:0]const c_char) callconv(.c) c_longdouble { - return std.math.nan(c_longdouble); + return math.nan(c_longdouble); } fn copysignf(x: f32, y: f32) callconv(.c) f32 { - return std.math.copysign(x, y); + return math.copysign(x, y); } fn copysign(x: f64, y: f64) callconv(.c) f64 { - return std.math.copysign(x, y); + return math.copysign(x, y); } fn copysignl(x: c_longdouble, y: c_longdouble) callconv(.c) c_longdouble { - return std.math.copysign(x, y); + return math.copysign(x, y); } diff --git a/lib/libc/musl/src/math/atan.c b/lib/libc/musl/src/math/atan.c deleted file mode 100644 index 63b0ab25e3cf02ea81bab5a9ee4d99d6c40bb582..0000000000000000000000000000000000000000 --- a/lib/libc/musl/src/math/atan.c +++ /dev/null @@ -1,116 +0,0 @@ -/* origin: FreeBSD /usr/src/lib/msun/src/s_atan.c */ -/* - * ==================================================== - * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. - * - * Developed at SunPro, a Sun Microsystems, Inc. business. - * Permission to use, copy, modify, and distribute this - * software is freely granted, provided that this notice - * is preserved. - * ==================================================== - */ -/* atan(x) - * Method - * 1. Reduce x to positive by atan(x) = -atan(-x). - * 2. According to the integer k=4t+0.25 chopped, t=x, the argument - * is further reduced to one of the following intervals and the - * arctangent of t is evaluated by the corresponding formula: - * - * [0,7/16] atan(x) = t-t^3*(a1+t^2*(a2+...(a10+t^2*a11)...) - * [7/16,11/16] atan(x) = atan(1/2) + atan( (t-0.5)/(1+t/2) ) - * [11/16.19/16] atan(x) = atan( 1 ) + atan( (t-1)/(1+t) ) - * [19/16,39/16] atan(x) = atan(3/2) + atan( (t-1.5)/(1+1.5t) ) - * [39/16,INF] atan(x) = atan(INF) + atan( -1/t ) - * - * Constants: - * The hexadecimal values are the intended ones for the following - * constants. The decimal values may be used, provided that the - * compiler will convert from decimal to binary accurately enough - * to produce the hexadecimal values shown. - */ - - -#include "libm.h" - -static const double atanhi[] = { - 4.63647609000806093515e-01, /* atan(0.5)hi 0x3FDDAC67, 0x0561BB4F */ - 7.85398163397448278999e-01, /* atan(1.0)hi 0x3FE921FB, 0x54442D18 */ - 9.82793723247329054082e-01, /* atan(1.5)hi 0x3FEF730B, 0xD281F69B */ - 1.57079632679489655800e+00, /* atan(inf)hi 0x3FF921FB, 0x54442D18 */ -}; - -static const double atanlo[] = { - 2.26987774529616870924e-17, /* atan(0.5)lo 0x3C7A2B7F, 0x222F65E2 */ - 3.06161699786838301793e-17, /* atan(1.0)lo 0x3C81A626, 0x33145C07 */ - 1.39033110312309984516e-17, /* atan(1.5)lo 0x3C700788, 0x7AF0CBBD */ - 6.12323399573676603587e-17, /* atan(inf)lo 0x3C91A626, 0x33145C07 */ -}; - -static const double aT[] = { - 3.33333333333329318027e-01, /* 0x3FD55555, 0x5555550D */ - -1.99999999998764832476e-01, /* 0xBFC99999, 0x9998EBC4 */ - 1.42857142725034663711e-01, /* 0x3FC24924, 0x920083FF */ - -1.11111104054623557880e-01, /* 0xBFBC71C6, 0xFE231671 */ - 9.09088713343650656196e-02, /* 0x3FB745CD, 0xC54C206E */ - -7.69187620504482999495e-02, /* 0xBFB3B0F2, 0xAF749A6D */ - 6.66107313738753120669e-02, /* 0x3FB10D66, 0xA0D03D51 */ - -5.83357013379057348645e-02, /* 0xBFADDE2D, 0x52DEFD9A */ - 4.97687799461593236017e-02, /* 0x3FA97B4B, 0x24760DEB */ - -3.65315727442169155270e-02, /* 0xBFA2B444, 0x2C6A6C2F */ - 1.62858201153657823623e-02, /* 0x3F90AD3A, 0xE322DA11 */ -}; - -double atan(double x) -{ - double_t w,s1,s2,z; - uint32_t ix,sign; - int id; - - GET_HIGH_WORD(ix, x); - sign = ix >> 31; - ix &= 0x7fffffff; - if (ix >= 0x44100000) { /* if |x| >= 2^66 */ - if (isnan(x)) - return x; - z = atanhi[3] + 0x1p-120f; - return sign ? -z : z; - } - if (ix < 0x3fdc0000) { /* |x| < 0.4375 */ - if (ix < 0x3e400000) { /* |x| < 2^-27 */ - if (ix < 0x00100000) - /* raise underflow for subnormal x */ - FORCE_EVAL((float)x); - return x; - } - id = -1; - } else { - x = fabs(x); - if (ix < 0x3ff30000) { /* |x| < 1.1875 */ - if (ix < 0x3fe60000) { /* 7/16 <= |x| < 11/16 */ - id = 0; - x = (2.0*x-1.0)/(2.0+x); - } else { /* 11/16 <= |x| < 19/16 */ - id = 1; - x = (x-1.0)/(x+1.0); - } - } else { - if (ix < 0x40038000) { /* |x| < 2.4375 */ - id = 2; - x = (x-1.5)/(1.0+1.5*x); - } else { /* 2.4375 <= |x| < 2^66 */ - id = 3; - x = -1.0/x; - } - } - } - /* end of argument reduction */ - z = x*x; - w = z*z; - /* break sum from i=0 to 10 aT[i]z**(i+1) into odd and even poly */ - s1 = z*(aT[0]+w*(aT[2]+w*(aT[4]+w*(aT[6]+w*(aT[8]+w*aT[10]))))); - s2 = w*(aT[1]+w*(aT[3]+w*(aT[5]+w*(aT[7]+w*aT[9])))); - if (id < 0) - return x - x*(s1+s2); - z = atanhi[id] - (x*(s1+s2) - atanlo[id] - x); - return sign ? -z : z; -} diff --git a/lib/libc/musl/src/math/atanf.c b/lib/libc/musl/src/math/atanf.c deleted file mode 100644 index 178341b670fa249fa50157d878ac2a66bd7f1843..0000000000000000000000000000000000000000 --- a/lib/libc/musl/src/math/atanf.c +++ /dev/null @@ -1,94 +0,0 @@ -/* origin: FreeBSD /usr/src/lib/msun/src/s_atanf.c */ -/* - * Conversion to float by Ian Lance Taylor, Cygnus Support, ian@cygnus.com. - */ -/* - * ==================================================== - * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. - * - * Developed at SunPro, a Sun Microsystems, Inc. business. - * Permission to use, copy, modify, and distribute this - * software is freely granted, provided that this notice - * is preserved. - * ==================================================== - */ - - -#include "libm.h" - -static const float atanhi[] = { - 4.6364760399e-01, /* atan(0.5)hi 0x3eed6338 */ - 7.8539812565e-01, /* atan(1.0)hi 0x3f490fda */ - 9.8279368877e-01, /* atan(1.5)hi 0x3f7b985e */ - 1.5707962513e+00, /* atan(inf)hi 0x3fc90fda */ -}; - -static const float atanlo[] = { - 5.0121582440e-09, /* atan(0.5)lo 0x31ac3769 */ - 3.7748947079e-08, /* atan(1.0)lo 0x33222168 */ - 3.4473217170e-08, /* atan(1.5)lo 0x33140fb4 */ - 7.5497894159e-08, /* atan(inf)lo 0x33a22168 */ -}; - -static const float aT[] = { - 3.3333328366e-01, - -1.9999158382e-01, - 1.4253635705e-01, - -1.0648017377e-01, - 6.1687607318e-02, -}; - -float atanf(float x) -{ - float_t w,s1,s2,z; - uint32_t ix,sign; - int id; - - GET_FLOAT_WORD(ix, x); - sign = ix>>31; - ix &= 0x7fffffff; - if (ix >= 0x4c800000) { /* if |x| >= 2**26 */ - if (isnan(x)) - return x; - z = atanhi[3] + 0x1p-120f; - return sign ? -z : z; - } - if (ix < 0x3ee00000) { /* |x| < 0.4375 */ - if (ix < 0x39800000) { /* |x| < 2**-12 */ - if (ix < 0x00800000) - /* raise underflow for subnormal x */ - FORCE_EVAL(x*x); - return x; - } - id = -1; - } else { - x = fabsf(x); - if (ix < 0x3f980000) { /* |x| < 1.1875 */ - if (ix < 0x3f300000) { /* 7/16 <= |x| < 11/16 */ - id = 0; - x = (2.0f*x - 1.0f)/(2.0f + x); - } else { /* 11/16 <= |x| < 19/16 */ - id = 1; - x = (x - 1.0f)/(x + 1.0f); - } - } else { - if (ix < 0x401c0000) { /* |x| < 2.4375 */ - id = 2; - x = (x - 1.5f)/(1.0f + 1.5f*x); - } else { /* 2.4375 <= |x| < 2**26 */ - id = 3; - x = -1.0f/x; - } - } - } - /* end of argument reduction */ - z = x*x; - w = z*z; - /* break sum from i=0 to 10 aT[i]z**(i+1) into odd and even poly */ - s1 = z*(aT[0]+w*(aT[2]+w*aT[4])); - s2 = w*(aT[1]+w*aT[3]); - if (id < 0) - return x - x*(s1+s2); - z = atanhi[id] - ((x*(s1+s2) - atanlo[id]) - x); - return sign ? -z : z; -} diff --git a/lib/libc/musl/src/math/atanl.c b/lib/libc/musl/src/math/atanl.c deleted file mode 100644 index c3b0c9268db309060614a94e11d3755e2d26110a..0000000000000000000000000000000000000000 --- a/lib/libc/musl/src/math/atanl.c +++ /dev/null @@ -1,184 +0,0 @@ -/* origin: FreeBSD /usr/src/lib/msun/src/s_atanl.c */ -/* - * ==================================================== - * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. - * - * Developed at SunPro, a Sun Microsystems, Inc. business. - * Permission to use, copy, modify, and distribute this - * software is freely granted, provided that this notice - * is preserved. - * ==================================================== - */ -/* - * See comments in atan.c. - * Converted to long double by David Schultz . - */ - -#include "libm.h" - -#if LDBL_MANT_DIG == 53 && LDBL_MAX_EXP == 1024 -long double atanl(long double x) -{ - return atan(x); -} -#elif (LDBL_MANT_DIG == 64 || LDBL_MANT_DIG == 113) && LDBL_MAX_EXP == 16384 - -#if LDBL_MANT_DIG == 64 -#define EXPMAN(u) ((u.i.se & 0x7fff)<<8 | (u.i.m>>55 & 0xff)) - -static const long double atanhi[] = { - 4.63647609000806116202e-01L, - 7.85398163397448309628e-01L, - 9.82793723247329067960e-01L, - 1.57079632679489661926e+00L, -}; - -static const long double atanlo[] = { - 1.18469937025062860669e-20L, - -1.25413940316708300586e-20L, - 2.55232234165405176172e-20L, - -2.50827880633416601173e-20L, -}; - -static const long double aT[] = { - 3.33333333333333333017e-01L, - -1.99999999999999632011e-01L, - 1.42857142857046531280e-01L, - -1.11111111100562372733e-01L, - 9.09090902935647302252e-02L, - -7.69230552476207730353e-02L, - 6.66661718042406260546e-02L, - -5.88158892835030888692e-02L, - 5.25499891539726639379e-02L, - -4.70119845393155721494e-02L, - 4.03539201366454414072e-02L, - -2.91303858419364158725e-02L, - 1.24822046299269234080e-02L, -}; - -static long double T_even(long double x) -{ - return aT[0] + x * (aT[2] + x * (aT[4] + x * (aT[6] + - x * (aT[8] + x * (aT[10] + x * aT[12]))))); -} - -static long double T_odd(long double x) -{ - return aT[1] + x * (aT[3] + x * (aT[5] + x * (aT[7] + - x * (aT[9] + x * aT[11])))); -} -#elif LDBL_MANT_DIG == 113 -#define EXPMAN(u) ((u.i.se & 0x7fff)<<8 | u.i.top>>8) - -static const long double atanhi[] = { - 4.63647609000806116214256231461214397e-01L, - 7.85398163397448309615660845819875699e-01L, - 9.82793723247329067985710611014666038e-01L, - 1.57079632679489661923132169163975140e+00L, -}; - -static const long double atanlo[] = { - 4.89509642257333492668618435220297706e-36L, - 2.16795253253094525619926100651083806e-35L, - -2.31288434538183565909319952098066272e-35L, - 4.33590506506189051239852201302167613e-35L, -}; - -static const long double aT[] = { - 3.33333333333333333333333333333333125e-01L, - -1.99999999999999999999999999999180430e-01L, - 1.42857142857142857142857142125269827e-01L, - -1.11111111111111111111110834490810169e-01L, - 9.09090909090909090908522355708623681e-02L, - -7.69230769230769230696553844935357021e-02L, - 6.66666666666666660390096773046256096e-02L, - -5.88235294117646671706582985209643694e-02L, - 5.26315789473666478515847092020327506e-02L, - -4.76190476189855517021024424991436144e-02L, - 4.34782608678695085948531993458097026e-02L, - -3.99999999632663469330634215991142368e-02L, - 3.70370363987423702891250829918659723e-02L, - -3.44827496515048090726669907612335954e-02L, - 3.22579620681420149871973710852268528e-02L, - -3.03020767654269261041647570626778067e-02L, - 2.85641979882534783223403715930946138e-02L, - -2.69824879726738568189929461383741323e-02L, - 2.54194698498808542954187110873675769e-02L, - -2.35083879708189059926183138130183215e-02L, - 2.04832358998165364349957325067131428e-02L, - -1.54489555488544397858507248612362957e-02L, - 8.64492360989278761493037861575248038e-03L, - -2.58521121597609872727919154569765469e-03L, -}; - -static long double T_even(long double x) -{ - return (aT[0] + x * (aT[2] + x * (aT[4] + x * (aT[6] + x * (aT[8] + - x * (aT[10] + x * (aT[12] + x * (aT[14] + x * (aT[16] + - x * (aT[18] + x * (aT[20] + x * aT[22]))))))))))); -} - -static long double T_odd(long double x) -{ - return (aT[1] + x * (aT[3] + x * (aT[5] + x * (aT[7] + x * (aT[9] + - x * (aT[11] + x * (aT[13] + x * (aT[15] + x * (aT[17] + - x * (aT[19] + x * (aT[21] + x * aT[23]))))))))))); -} -#endif - -long double atanl(long double x) -{ - union ldshape u = {x}; - long double w, s1, s2, z; - int id; - unsigned e = u.i.se & 0x7fff; - unsigned sign = u.i.se >> 15; - unsigned expman; - - if (e >= 0x3fff + LDBL_MANT_DIG + 1) { /* if |x| is large, atan(x)~=pi/2 */ - if (isnan(x)) - return x; - return sign ? -atanhi[3] : atanhi[3]; - } - /* Extract the exponent and the first few bits of the mantissa. */ - expman = EXPMAN(u); - if (expman < ((0x3fff - 2) << 8) + 0xc0) { /* |x| < 0.4375 */ - if (e < 0x3fff - (LDBL_MANT_DIG+1)/2) { /* if |x| is small, atanl(x)~=x */ - /* raise underflow if subnormal */ - if (e == 0) - FORCE_EVAL((float)x); - return x; - } - id = -1; - } else { - x = fabsl(x); - if (expman < (0x3fff << 8) + 0x30) { /* |x| < 1.1875 */ - if (expman < ((0x3fff - 1) << 8) + 0x60) { /* 7/16 <= |x| < 11/16 */ - id = 0; - x = (2.0*x-1.0)/(2.0+x); - } else { /* 11/16 <= |x| < 19/16 */ - id = 1; - x = (x-1.0)/(x+1.0); - } - } else { - if (expman < ((0x3fff + 1) << 8) + 0x38) { /* |x| < 2.4375 */ - id = 2; - x = (x-1.5)/(1.0+1.5*x); - } else { /* 2.4375 <= |x| */ - id = 3; - x = -1.0/x; - } - } - } - /* end of argument reduction */ - z = x*x; - w = z*z; - /* break sum aT[i]z**(i+1) into odd and even poly */ - s1 = z*T_even(w); - s2 = w*T_odd(w); - if (id < 0) - return x - x*(s1+s2); - z = atanhi[id] - ((x*(s1+s2) - atanlo[id]) - x); - return sign ? -z : z; -} -#endif diff --git a/lib/libc/musl/src/math/i386/atan.s b/lib/libc/musl/src/math/i386/atan.s deleted file mode 100644 index 2c57f6b309d3d5e54430f17db69171e0ce5bc833..0000000000000000000000000000000000000000 --- a/lib/libc/musl/src/math/i386/atan.s +++ /dev/null @@ -1,16 +0,0 @@ -.global atan -.type atan,@function -atan: - fldl 4(%esp) - mov 8(%esp),%eax - add %eax,%eax - cmp $0x00200000,%eax - jb 1f - fld1 - fpatan - fstpl 4(%esp) - fldl 4(%esp) - ret - # subnormal x, return x with underflow -1: fsts 4(%esp) - ret diff --git a/lib/libc/musl/src/math/i386/atanf.s b/lib/libc/musl/src/math/i386/atanf.s deleted file mode 100644 index c2cbe2e0267f93272a195725489afcbbc24c8876..0000000000000000000000000000000000000000 --- a/lib/libc/musl/src/math/i386/atanf.s +++ /dev/null @@ -1,18 +0,0 @@ -.global atanf -.type atanf,@function -atanf: - flds 4(%esp) - mov 4(%esp),%eax - add %eax,%eax - cmp $0x01000000,%eax - jb 1f - fld1 - fpatan - fstps 4(%esp) - flds 4(%esp) - ret - # subnormal x, return x with underflow -1: fld %st(0) - fmul %st(1) - fstps 4(%esp) - ret diff --git a/lib/libc/musl/src/math/i386/atanl.s b/lib/libc/musl/src/math/i386/atanl.s deleted file mode 100644 index c508bc465b1a510efca440808bc88fe6c2a7d426..0000000000000000000000000000000000000000 --- a/lib/libc/musl/src/math/i386/atanl.s +++ /dev/null @@ -1,7 +0,0 @@ -.global atanl -.type atanl,@function -atanl: - fldt 4(%esp) - fld1 - fpatan - ret diff --git a/lib/libc/musl/src/math/x32/atanl.s b/lib/libc/musl/src/math/x32/atanl.s deleted file mode 100644 index f475fe0e9ee73ca2940fd47c9326323769207953..0000000000000000000000000000000000000000 --- a/lib/libc/musl/src/math/x32/atanl.s +++ /dev/null @@ -1,7 +0,0 @@ -.global atanl -.type atanl,@function -atanl: - fldt 8(%esp) - fld1 - fpatan - ret diff --git a/lib/libc/musl/src/math/x86_64/atanl.s b/lib/libc/musl/src/math/x86_64/atanl.s deleted file mode 100644 index df76de5de4f12834a4a3710a2e7370d10245e862..0000000000000000000000000000000000000000 --- a/lib/libc/musl/src/math/x86_64/atanl.s +++ /dev/null @@ -1,7 +0,0 @@ -.global atanl -.type atanl,@function -atanl: - fldt 8(%rsp) - fld1 - fpatan - ret diff --git a/lib/std/math/atan.zig b/lib/std/math/atan.zig index 377d96897ff7acad0e76aece1866fdbc4fd31d4e..8b783bf3474b7cf260c9ca4930e35200dd6704bd 100644 --- a/lib/std/math/atan.zig +++ b/lib/std/math/atan.zig @@ -3,11 +3,12 @@ // // https://git.musl-libc.org/cgit/musl/tree/src/math/atanf.c // https://git.musl-libc.org/cgit/musl/tree/src/math/atan.c +// https://git.musl-libc.org/cgit/musl/tree/src/math/atanl.c const std = @import("../std.zig"); const math = std.math; const mem = std.mem; -const expect = std.testing.expect; +const testing = std.testing; /// Returns the arc-tangent of x. /// @@ -17,28 +18,100 @@ const expect = std.testing.expect; pub fn atan(x: anytype) @TypeOf(x) { const T = @TypeOf(x); return switch (T) { - f32 => atan32(x), - f64 => atan64(x), + f16 => atanBinary16(x), + f32 => atanBinary32(x), + f64 => atanBinary64(x), + f80 => atanExtended80(x), + f128 => atanBinary128(x), else => @compileError("atan not implemented for " ++ @typeName(T)), }; } -fn atan32(x_: f32) f32 { - const atanhi = [_]f32{ - 4.6364760399e-01, // atan(0.5)hi - 7.8539812565e-01, // atan(1.0)hi - 9.8279368877e-01, // atan(1.5)hi - 1.5707962513e+00, // atan(inf)hi +fn atanBinary16(x: f16) f16 { + const atanhi: []const f32 = &.{ + 4.6364760399e-01, // atan(0.5)hi 0x3eed6338 + 7.8539812565e-01, // atan(1.0)hi 0x3f490fda + 9.8279368877e-01, // atan(1.5)hi 0x3f7b985e + 1.5707962513e+00, // atan(inf)hi 0x3fc90fda + }; + const aT: []const f32 = &.{ + 0x1.fffcccp-1, + -0x1.52e8ccp-2, + 0x1.522336p-3, }; - const atanlo = [_]f32{ - 5.0121582440e-09, // atan(0.5)lo - 3.7748947079e-08, // atan(1.0)lo - 3.4473217170e-08, // atan(1.5)lo - 7.5497894159e-08, // atan(inf)lo + const hx: u16 = @bitCast(x); + const ix = hx & 0x7fff; + const sign = (hx >> 15) != 0; + // if |x| >= 2^11 + if (ix >= 0x6800) { + if (math.isNan(x)) { + return x; + } + const z = atanhi[3] + 0x1p-120; + return @floatCast(if (sign) -z else z); + } + const x_: f32, const id: ?usize = blk: { + // |x| < 0.4375 + if (ix < 0x3700) { + // |x| < 2^(-6) + if (ix < 0x2400) { + if (ix < 0x400) { + // raise underflow for subnormal x + mem.doNotOptimizeAway(x * x); + } + return x; + } + break :blk .{ @floatCast(x), null }; + } else { + const x_: f32 = @floatCast(@abs(x)); + // |x| < 1.1875 + if (ix < 0x3cc0) { + // 7/16 <= |x| < 11/16 + if (ix < 0x3980) { + break :blk .{ (2.0 * x_ - 1.0) / (2.0 + x_), 0 }; + } + // 11/16 <= |x| < 19/16 + else { + break :blk .{ (x_ - 1.0) / (x_ + 1.0), 1 }; + } + } else { + // |x| < 2.4375 + if (ix < 0x40e0) { + break :blk .{ (x_ - 1.5) / (1.0 + 1.5 * x_), 2 }; + } + // 2.4375 <= |x| < 2^11 + else { + break :blk .{ -1.0 / x_, 3 }; + } + } + } }; + // end of argument reduction + const z = x_ * x_; + const s = aT[0] + z * (aT[1] + z * aT[2]); + if (id) |id_| { + const z_ = atanhi[id_] + x_ * s; + return @floatCast(if (sign) -z_ else z_); + } else { + return @floatCast(x_ * s); + } +} - const aT = [_]f32{ +fn atanBinary32(x: f32) f32 { + const atanhi: []const f32 = &.{ + 4.6364760399e-01, // atan(0.5)hi 0x3eed6338 + 7.8539812565e-01, // atan(1.0)hi 0x3f490fda + 9.8279368877e-01, // atan(1.5)hi 0x3f7b985e + 1.5707962513e+00, // atan(inf)hi 0x3fc90fda + }; + const atanlo: []const f32 = &.{ + 5.0121582440e-09, // atan(0.5)lo 0x31ac3769 + 3.7748947079e-08, // atan(1.0)lo 0x33222168 + 3.4473217170e-08, // atan(1.5)lo 0x33140fb4 + 7.5497894159e-08, // atan(inf)lo 0x33a22168 + }; + const aT: []const f32 = &.{ 3.3333328366e-01, -1.9999158382e-01, 1.4253635705e-01, @@ -46,211 +119,463 @@ fn atan32(x_: f32) f32 { 6.1687607318e-02, }; - var x = x_; - var ix: u32 = @as(u32, @bitCast(x)); - const sign = ix >> 31; - ix &= 0x7FFFFFFF; - - // |x| >= 2^26 - if (ix >= 0x4C800000) { + const hx: u32 = @bitCast(x); + const ix = hx & 0x7fff_ffff; + const sign = (hx >> 31) != 0; + // if |x| >= 2^26 + if (ix >= 0x4c80_0000) { if (math.isNan(x)) { return x; - } else { - const z = atanhi[3] + 0x1.0p-120; - return if (sign != 0) -z else z; } + const z = atanhi[3] + 0x1p-120; + return if (sign) -z else z; } - - var id: ?usize = undefined; - - // |x| < 0.4375 - if (ix < 0x3EE00000) { - // |x| < 2^(-12) - if (ix < 0x39800000) { - if (ix < 0x00800000) { - mem.doNotOptimizeAway(x * x); - } - return x; - } - id = null; - } else { - x = @abs(x); - // |x| < 1.1875 - if (ix < 0x3F980000) { - // 7/16 <= |x| < 11/16 - if (ix < 0x3F300000) { - id = 0; - x = (2.0 * x - 1.0) / (2.0 + x); - } - // 11/16 <= |x| < 19/16 - else { - id = 1; - x = (x - 1.0) / (x + 1.0); + const x_, const id: ?usize = blk: { + // |x| < 0.4375 + if (ix < 0x3ee00000) { + // |x| < 2^(-12) + if (ix < 0x39800000) { + if (ix < 0x00800000) { + // raise underflow for subnormal x + mem.doNotOptimizeAway(x * x); + } + return x; } + break :blk .{ x, null }; } else { - // |x| < 2.4375 - if (ix < 0x401C0000) { - id = 2; - x = (x - 1.5) / (1.0 + 1.5 * x); - } - // 2.4375 <= |x| < 2^26 - else { - id = 3; - x = -1.0 / x; + const x_ = @abs(x); + // |x| < 1.1875 + if (ix < 0x3f98_0000) { + // 7/16 <= |x| < 11/16 + if (ix < 0x3f30_0000) { + break :blk .{ (2.0 * x_ - 1.0) / (2.0 + x_), 0 }; + } + // 11/16 <= |x| < 19/16 + else { + break :blk .{ (x_ - 1.0) / (x_ + 1.0), 1 }; + } + } else { + // |x| < 2.4375 + if (ix < 0x401c_0000) { + break :blk .{ (x_ - 1.5) / (1.0 + 1.5 * x_), 2 }; + } + // 2.4375 <= |x| < 2^26 + else { + break :blk .{ -1.0 / x_, 3 }; + } } } - } - - const z = x * x; + }; + // end of argument reduction + const z = x_ * x_; const w = z * z; + // break sum from i=0 to 10 aT[i]z^(i+1) into odd and even poly const s1 = z * (aT[0] + w * (aT[2] + w * aT[4])); const s2 = w * (aT[1] + w * aT[3]); - - if (id) |id_value| { - const zz = atanhi[id_value] - ((x * (s1 + s2) - atanlo[id_value]) - x); - return if (sign != 0) -zz else zz; + if (id) |id_| { + const z_ = atanhi[id_] - ((x_ * (s1 + s2) - atanlo[id_]) - x_); + return if (sign) -z_ else z_; } else { - return x - x * (s1 + s2); + return x_ - x_ * (s1 + s2); } } -fn atan64(x_: f64) f64 { - const atanhi = [_]f64{ - 4.63647609000806093515e-01, // atan(0.5)hi - 7.85398163397448278999e-01, // atan(1.0)hi - 9.82793723247329054082e-01, // atan(1.5)hi - 1.57079632679489655800e+00, // atan(inf)hi +fn atanBinary64(x: f64) f64 { + const atanhi: []const f64 = &.{ + 4.63647609000806093515e-01, // atan(0.5)hi 0x3FDDAC67, 0x0561BB4F + 7.85398163397448278999e-01, // atan(1.0)hi 0x3FE921FB, 0x54442D18 + 9.82793723247329054082e-01, // atan(1.5)hi 0x3FEF730B, 0xD281F69B + 1.57079632679489655800e+00, // atan(inf)hi 0x3FF921FB, 0x54442D18 }; - - const atanlo = [_]f64{ - 2.26987774529616870924e-17, // atan(0.5)lo - 3.06161699786838301793e-17, // atan(1.0)lo - 1.39033110312309984516e-17, // atan(1.5)lo - 6.12323399573676603587e-17, // atan(inf)lo + const atanlo: []const f64 = &.{ + 2.26987774529616870924e-17, // atan(0.5)lo 0x3C7A2B7F, 0x222F65E2 + 3.06161699786838301793e-17, // atan(1.0)lo 0x3C81A626, 0x33145C07 + 1.39033110312309984516e-17, // atan(1.5)lo 0x3C700788, 0x7AF0CBBD + 6.12323399573676603587e-17, // atan(inf)lo 0x3C91A626, 0x33145C07 }; - - const aT = [_]f64{ - 3.33333333333329318027e-01, - -1.99999999998764832476e-01, - 1.42857142725034663711e-01, - -1.11111104054623557880e-01, - 9.09088713343650656196e-02, - -7.69187620504482999495e-02, - 6.66107313738753120669e-02, - -5.83357013379057348645e-02, - 4.97687799461593236017e-02, - -3.65315727442169155270e-02, - 1.62858201153657823623e-02, + const aT: []const f64 = &.{ + 3.33333333333329318027e-01, // 0x3FD55555, 0x5555550D + -1.99999999998764832476e-01, // 0xBFC99999, 0x9998EBC4 + 1.42857142725034663711e-01, // 0x3FC24924, 0x920083FF + -1.11111104054623557880e-01, // 0xBFBC71C6, 0xFE231671 + 9.09088713343650656196e-02, // 0x3FB745CD, 0xC54C206E + -7.69187620504482999495e-02, // 0xBFB3B0F2, 0xAF749A6D + 6.66107313738753120669e-02, // 0x3FB10D66, 0xA0D03D51 + -5.83357013379057348645e-02, // 0xBFADDE2D, 0x52DEFD9A + 4.97687799461593236017e-02, // 0x3FA97B4B, 0x24760DEB + -3.65315727442169155270e-02, // 0xBFA2B444, 0x2C6A6C2F + 1.62858201153657823623e-02, // 0x3F90AD3A, 0xE322DA11 }; - var x = x_; - const ux: u64 = @bitCast(x); - var ix: u32 = @intCast(ux >> 32); - const sign = ix >> 31; - ix &= 0x7FFFFFFF; - - // |x| >= 2^66 + const hx: u64 = @bitCast(x); + const ix: u32 = @truncate((hx >> 32) & 0x7fffffff); + const sign = (hx >> 63) != 0; + // if |x| >= 2^66 if (ix >= 0x44100000) { if (math.isNan(x)) { return x; - } else { - const z = atanhi[3] + 0x1.0p-120; - return if (sign != 0) -z else z; } + const z = atanhi[3] + 0x1p-120; + return if (sign) -z else z; } - - var id: ?usize = undefined; - - // |x| < 0.4375 - if (ix < 0x3FDC0000) { - // |x| < 2^(-27) - if (ix < 0x3E400000) { - if (ix < 0x00100000) { - mem.doNotOptimizeAway(@as(f32, @floatCast(x))); - } - return x; - } - id = null; - } else { - x = @abs(x); - // |x| < 1.1875 - if (ix < 0x3FF30000) { - // 7/16 <= |x| < 11/16 - if (ix < 0x3FE60000) { - id = 0; - x = (2.0 * x - 1.0) / (2.0 + x); - } - // 11/16 <= |x| < 19/16 - else { - id = 1; - x = (x - 1.0) / (x + 1.0); + const x_, const id: ?usize = blk: { + // |x| < 0.4375 + if (ix < 0x3fdc_0000) { + // |x| < 2^(-27) + if (ix < 0x3e40_0000) { + if (ix < 0x0010_0000) { + // raise underflow for subnormal x + mem.doNotOptimizeAway(@as(f32, @floatCast(x))); + } + return x; } + break :blk .{ x, null }; } else { - // |x| < 2.4375 - if (ix < 0x40038000) { - id = 2; - x = (x - 1.5) / (1.0 + 1.5 * x); - } - // 2.4375 <= |x| < 2^66 - else { - id = 3; - x = -1.0 / x; + const x_ = @abs(x); + // |x| < 1.1875 + if (ix < 0x3ff3_0000) { + // 7/16 <= |x| < 11/16 + if (ix < 0x3fe6_0000) { + break :blk .{ (2.0 * x_ - 1.0) / (2.0 + x_), 0 }; + } + // 11/16 <= |x| < 19/16 + else { + break :blk .{ (x_ - 1.0) / (x_ + 1.0), 1 }; + } + } else { + // |x| < 2.4375 + if (ix < 0x4003_8000) { + break :blk .{ (x_ - 1.5) / (1.0 + 1.5 * x_), 2 }; + } + // 2.4375 <= |x| < 2^66 + else { + break :blk .{ -1.0 / x_, 3 }; + } } } - } - - const z = x * x; + }; + // end of argument reduction + const z = x_ * x_; const w = z * z; + // break sum from i=0 to 10 aT[i]z^(i+1) into odd and even poly const s1 = z * (aT[0] + w * (aT[2] + w * (aT[4] + w * (aT[6] + w * (aT[8] + w * aT[10]))))); const s2 = w * (aT[1] + w * (aT[3] + w * (aT[5] + w * (aT[7] + w * aT[9])))); + if (id) |id_| { + const z_ = atanhi[id_] - (x_ * (s1 + s2) - atanlo[id_] - x_); + return if (sign) -z_ else z_; + } else { + return x_ - x_ * (s1 + s2); + } +} - if (id) |id_value| { - const zz = atanhi[id_value] - ((x * (s1 + s2) - atanlo[id_value]) - x); - return if (sign != 0) -zz else zz; +fn atanExtended80(x: f80) f80 { + const atanhi: []const f80 = &.{ + 4.63647609000806116202e-01, + 7.85398163397448309628e-01, + 9.82793723247329067960e-01, + 1.57079632679489661926e+00, + }; + const atanlo: []const f80 = &.{ + 1.18469937025062860669e-20, + -1.25413940316708300586e-20, + 2.55232234165405176172e-20, + -2.50827880633416601173e-20, + }; + const aT: []const f80 = &.{ + 3.33333333333333333017e-01, + -1.99999999999999632011e-01, + 1.42857142857046531280e-01, + -1.11111111100562372733e-01, + 9.09090902935647302252e-02, + -7.69230552476207730353e-02, + 6.66661718042406260546e-02, + -5.88158892835030888692e-02, + 5.25499891539726639379e-02, + -4.70119845393155721494e-02, + 4.03539201366454414072e-02, + -2.91303858419364158725e-02, + 1.24822046299269234080e-02, + }; + + const hx: u80 = @bitCast(x); + const se: u16 = @truncate(hx >> 64); + const e = se & 0x7fff; + const sign = se >> 15 != 0; + // if |x| is large, atan(x)~=pi/2 + if (e >= 0x3fff + math.floatMantissaBits(f80) + 1) { + if (math.isNan(x)) { + return x; + } + return if (sign) -atanhi[3] else atanhi[3]; + } + // Extract the exponent and the first few bits of the mantissa. + const m: u64 = @truncate(hx & 0x0000_ffff_ffff_ffff_ffff); + const expman = ((@as(u32, @intCast(se)) & 0x7fff) << 8) | (@as(u32, @truncate(m >> 55)) & 0xff); + const x_, const id: ?usize = blk: { + // |x| < 0.4375 + if (expman < ((0x3fff - 2) << 8) + 0xc0) { + // if |x| is small, atanl(x)~=x + if (e < 0x3fff - (math.floatMantissaBits(f80) + 1) / 2) { + // raise underflow if subnormal + if (e == 0) { + std.mem.doNotOptimizeAway(@as(f32, @floatCast(x))); + } + return x; + } + break :blk .{ x, null }; + } else { + const x_ = @abs(x); + // |x| < 1.1875 + if (expman < (0x3fff << 8) + 0x30) { + // 7/16 <= |x| < 11/16 + if (expman < ((0x3fff - 1) << 8) + 0x60) { + break :blk .{ (2.0 * x_ - 1.0) / (2.0 + x_), 0 }; + } + // 11/16 <= |x| < 19/16 + else { + break :blk .{ (x_ - 1.0) / (x_ + 1.0), 1 }; + } + } else { + // |x| < 2.4375 + if (expman < ((0x3fff + 1) << 8) + 0x38) { + break :blk .{ (x_ - 1.5) / (1.0 + 1.5 * x_), 2 }; + } + // 2.4375 <= |x| + else { + break :blk .{ -1.0 / x_, 3 }; + } + } + } + }; + // end of argument reduction + const z = x_ * x_; + const w = z * z; + // break sum aT[i]z^(i+1) into odd and even poly + const s1 = z * (aT[0] + w * (aT[2] + w * (aT[4] + w * (aT[6] + w * (aT[8] + w * (aT[10] + w * aT[12])))))); + const s2 = w * (aT[1] + w * (aT[3] + w * (aT[5] + w * (aT[7] + w * (aT[9] + w * aT[11]))))); + if (id) |id_| { + const z_ = atanhi[id_] - ((x_ * (s1 + s2) - atanlo[id_]) - x_); + return if (sign) -z_ else z_; } else { - return x - x * (s1 + s2); + return x_ - x_ * (s1 + s2); } } -test atan { - try expect(@as(u32, @bitCast(atan(@as(f32, 0.2)))) == @as(u32, @bitCast(atan32(0.2)))); - try expect(atan(@as(f64, 0.2)) == atan64(0.2)); +fn atanBinary128(x: f128) f128 { + const atanhi: []const f128 = &.{ + 4.63647609000806116214256231461214397e-01, + 7.85398163397448309615660845819875699e-01, + 9.82793723247329067985710611014666038e-01, + 1.57079632679489661923132169163975140e+00, + }; + const atanlo: []const f128 = &.{ + 4.89509642257333492668618435220297706e-36, + 2.16795253253094525619926100651083806e-35, + -2.31288434538183565909319952098066272e-35, + 4.33590506506189051239852201302167613e-35, + }; + const aT: []const f128 = &.{ + 3.33333333333333333333333333333333125e-01, + -1.99999999999999999999999999999180430e-01, + 1.42857142857142857142857142125269827e-01, + -1.11111111111111111111110834490810169e-01, + 9.09090909090909090908522355708623681e-02, + -7.69230769230769230696553844935357021e-02, + 6.66666666666666660390096773046256096e-02, + -5.88235294117646671706582985209643694e-02, + 5.26315789473666478515847092020327506e-02, + -4.76190476189855517021024424991436144e-02, + 4.34782608678695085948531993458097026e-02, + -3.99999999632663469330634215991142368e-02, + 3.70370363987423702891250829918659723e-02, + -3.44827496515048090726669907612335954e-02, + 3.22579620681420149871973710852268528e-02, + -3.03020767654269261041647570626778067e-02, + 2.85641979882534783223403715930946138e-02, + -2.69824879726738568189929461383741323e-02, + 2.54194698498808542954187110873675769e-02, + -2.35083879708189059926183138130183215e-02, + 2.04832358998165364349957325067131428e-02, + -1.54489555488544397858507248612362957e-02, + 8.64492360989278761493037861575248038e-03, + -2.58521121597609872727919154569765469e-03, + }; + + const hx: u128 = @bitCast(x); + const se: u16 = @truncate(hx >> 112); + const e = se & 0x7fff; + const sign = se >> 15 != 0; + // if |x| is large, atan(x)~=pi/2 + if (e >= 0x3fff + math.floatMantissaBits(f128) + 2) { + if (math.isNan(x)) { + return x; + } + return if (sign) -atanhi[3] else atanhi[3]; + } + // Extract the exponent and the first few bits of the mantissa. + const top: u16 = @truncate((hx >> 96) & 0x0000_ffff); + const expman = ((@as(u32, @intCast(se)) & 0x7fff) << 8) | (@as(u32, @intCast(top)) >> 8); + const x_, const id: ?usize = blk: { + // |x| < 0.4375 + if (expman < ((0x3fff - 2) << 8) + 0xc0) { + // if |x| is small, atanl(x)~=x + if (e < 0x3fff - (math.floatMantissaBits(f128) + 2) / 2) { + // raise underflow if subnormal + if (e == 0) { + mem.doNotOptimizeAway(@as(f32, @floatCast(x))); + } + return x; + } + break :blk .{ x, null }; + } else { + const x_ = @abs(x); + // |x| < 1.1875 + if (expman < (0x3fff << 8) + 0x30) { + // 7/16 <= |x| < 11/16 + if (expman < ((0x3fff - 1) << 8) + 0x60) { + break :blk .{ (2.0 * x_ - 1.0) / (2.0 + x_), 0 }; + } + // 11/16 <= |x| < 19/16 + else { + break :blk .{ (x_ - 1.0) / (x_ + 1.0), 1 }; + } + } else { + // |x| < 2.4375 + if (expman < ((0x3fff + 1) << 8) + 0x38) { + break :blk .{ (x_ - 1.5) / (1.0 + 1.5 * x_), 2 }; + } + // 2.4375 <= |x| + else { + break :blk .{ -1.0 / x_, 3 }; + } + } + } + }; + // end of argument reduction + const z = x_ * x_; + const w = z * z; + // break sum aT[i]z^(i+1) into odd and even poly + const s1 = z * (aT[0] + w * (aT[2] + w * (aT[4] + w * (aT[6] + w * (aT[8] + w * (aT[10] + w * (aT[12] + w * (aT[14] + w * (aT[16] + w * (aT[18] + w * (aT[20] + w * aT[22]))))))))))); + const s2 = w * (aT[1] + w * (aT[3] + w * (aT[5] + w * (aT[7] + w * (aT[9] + w * (aT[11] + w * (aT[13] + w * (aT[15] + w * (aT[17] + w * (aT[19] + w * (aT[21] + w * aT[23]))))))))))); + if (id) |id_| { + const z_ = atanhi[id_] - ((x_ * (s1 + s2) - atanlo[id_]) - x_); + return if (sign) -z_ else z_; + } else { + return x_ - x_ * (s1 + s2); + } } -test atan32 { - const epsilon = 0.000001; +test "atanBinary16.special" { + try testing.expectEqual(atanBinary16(0x0p+0), 0x0p+0); + try testing.expectEqual(atanBinary16(-0x0p+0), -0x0p+0); + try testing.expectApproxEqAbs(atanBinary16(0x1p+0), 0x1.92p-1, math.floatEpsAt(f16, 0x1.92p-1)); + try testing.expectApproxEqAbs(atanBinary16(-0x1p+0), -0x1.92p-1, math.floatEpsAt(f16, -0x1.92p-1)); + try testing.expectApproxEqAbs(atanBinary16(math.inf(f16)), 0x1.92p0, math.floatEpsAt(f16, 0x1.92p0)); + try testing.expectApproxEqAbs(atanBinary16(-math.inf(f16)), -0x1.92p0, math.floatEpsAt(f16, -0x1.92p0)); + try testing.expect(math.isNan(atanBinary16(math.nan(f16)))); +} + +test "atanBinary16" { + try testing.expectApproxEqAbs(atanBinary16(-0x1.864p-2), -0x1.74cp-2, math.floatEpsAt(f16, -0x1.74cp-2)); + try testing.expectApproxEqAbs(atanBinary16(-0x1.59cp1), -0x1.374p0, math.floatEpsAt(f16, -0x1.374p0)); + try testing.expectApproxEqAbs(atanBinary16(-0x1.d2cp0), -0x1.11cp0, math.floatEpsAt(f16, -0x1.11cp0)); + try testing.expectApproxEqAbs(atanBinary16(-0x1.5f4p-1), -0x1.33cp-1, math.floatEpsAt(f16, -0x1.33cp-1)); + try testing.expectApproxEqAbs(atanBinary16(0x1.588p1), 0x1.37p0, math.floatEpsAt(f16, 0x1.37p0)); + try testing.expectApproxEqAbs(atanBinary16(-0x1.b14p-2), -0x1.99cp-2, math.floatEpsAt(f16, -0x1.99cp-2)); + try testing.expectApproxEqAbs(atanBinary16(0x1.3ccp1), 0x1.2fcp0, math.floatEpsAt(f16, 0x1.2fcp0)); + try testing.expectApproxEqAbs(atanBinary16(-0x1.0ecp-2), -0x1.08cp-2, math.floatEpsAt(f16, -0x1.08cp-2)); + try testing.expectApproxEqAbs(atanBinary16(0x1.298p1), 0x1.2ap0, math.floatEpsAt(f16, 0x1.2ap0)); + try testing.expectApproxEqAbs(atanBinary16(-0x1.028p1), -0x1.1c8p0, math.floatEpsAt(f16, -0x1.1c8p0)); +} + +test "atanBinary32.special" { + try testing.expectEqual(atanBinary32(0x0p+0), 0x0p+0); + try testing.expectEqual(atanBinary32(-0x0p+0), -0x0p+0); + try testing.expectApproxEqAbs(atanBinary32(0x1p+0), 0x1.921fb6p-1, math.floatEpsAt(f32, 0x1.921fb6p-1)); + try testing.expectApproxEqAbs(atanBinary32(-0x1p+0), -0x1.921fb6p-1, math.floatEpsAt(f32, -0x1.921fb6p-1)); + try testing.expectApproxEqAbs(atanBinary32(math.inf(f32)), 0x1.921fb6p+0, math.floatEpsAt(f32, 0x1.921fb6p+0)); + try testing.expectApproxEqAbs(atanBinary32(-math.inf(f32)), -0x1.921fb6p+0, math.floatEpsAt(f32, -0x1.921fb6p+0)); + try testing.expect(math.isNan(atanBinary32(math.nan(f32)))); +} - try expect(math.approxEqAbs(f32, atan32(0.2), 0.197396, epsilon)); - try expect(math.approxEqAbs(f32, atan32(-0.2), -0.197396, epsilon)); - try expect(math.approxEqAbs(f32, atan32(0.3434), 0.330783, epsilon)); - try expect(math.approxEqAbs(f32, atan32(0.8923), 0.728545, epsilon)); - try expect(math.approxEqAbs(f32, atan32(1.5), 0.982794, epsilon)); +test "atanBinary32" { + try testing.expectApproxEqAbs(atanBinary32(-0x1.8629dp-2), -0x1.74c62p-2, math.floatEpsAt(f32, -0x1.74c62p-2)); + try testing.expectApproxEqAbs(atanBinary32(-0x1.59d42ep1), -0x1.375fd8p0, math.floatEpsAt(f32, -0x1.375fd8p0)); + try testing.expectApproxEqAbs(atanBinary32(-0x1.d2dbe2p0), -0x1.11b8aep0, math.floatEpsAt(f32, -0x1.11b8aep0)); + try testing.expectApproxEqAbs(atanBinary32(-0x1.5f314ep-1), -0x1.33d28cp-1, math.floatEpsAt(f32, -0x1.33d28cp-1)); + try testing.expectApproxEqAbs(atanBinary32(0x1.5869bp1), 0x1.37082ep0, math.floatEpsAt(f32, 0x1.37082ep0)); + try testing.expectApproxEqAbs(atanBinary32(-0x1.b13a06p-2), -0x1.99d7cap-2, math.floatEpsAt(f32, -0x1.99d7cap-2)); + try testing.expectApproxEqAbs(atanBinary32(0x1.3cb0f2p1), 0x1.2fcb12p0, math.floatEpsAt(f32, 0x1.2fcb12p0)); + try testing.expectApproxEqAbs(atanBinary32(-0x1.0ed746p-2), -0x1.08c71ap-2, math.floatEpsAt(f32, -0x1.08c71ap-2)); + try testing.expectApproxEqAbs(atanBinary32(0x1.299d54p1), 0x1.2a24e2p0, math.floatEpsAt(f32, 0x1.2a24e2p0)); + try testing.expectApproxEqAbs(atanBinary32(-0x1.0264fcp1), -0x1.1c6178p0, math.floatEpsAt(f32, -0x1.1c6178p0)); } -test atan64 { - const epsilon = 0.000001; +test "atanBinary64.special" { + try testing.expectEqual(atanBinary64(0x0p+0), 0x0p+0); + try testing.expectEqual(atanBinary64(-0x0p+0), -0x0p+0); + try testing.expectApproxEqAbs(atanBinary64(0x1p+0), 0x1.921fb54442d18p-1, math.floatEpsAt(f64, 0x1.921fb54442d18p-1)); + try testing.expectApproxEqAbs(atanBinary64(-0x1p+0), -0x1.921fb54442d18p-1, math.floatEpsAt(f64, -0x1.921fb54442d18p-1)); + try testing.expectApproxEqAbs(atanBinary64(math.inf(f64)), 0x1.921fb54442d18p+0, math.floatEpsAt(f64, 0x1.921fb54442d18p+0)); + try testing.expectApproxEqAbs(atanBinary64(-math.inf(f64)), -0x1.921fb54442d18p+0, math.floatEpsAt(f64, -0x1.921fb54442d18p+0)); + try testing.expect(math.isNan(atanBinary64(math.nan(f64)))); +} - try expect(math.approxEqAbs(f64, atan64(0.2), 0.197396, epsilon)); - try expect(math.approxEqAbs(f64, atan64(-0.2), -0.197396, epsilon)); - try expect(math.approxEqAbs(f64, atan64(0.3434), 0.330783, epsilon)); - try expect(math.approxEqAbs(f64, atan64(0.8923), 0.728545, epsilon)); - try expect(math.approxEqAbs(f64, atan64(1.5), 0.982794, epsilon)); +test "atanBinary64" { + try testing.expectApproxEqAbs(atanBinary64(-0x1.8629d0244cdccp-2), -0x1.74c61f4377016p-2, math.floatEpsAt(f64, -0x1.74c61f4377016p-2)); + try testing.expectApproxEqAbs(atanBinary64(-0x1.59d42d4659937p1), -0x1.375fd7987cc2p0, math.floatEpsAt(f64, -0x1.375fd7987cc2p0)); + try testing.expectApproxEqAbs(atanBinary64(-0x1.d2dbe23d04f06p0), -0x1.11b8adeba5616p0, math.floatEpsAt(f64, -0x1.11b8adeba5616p0)); + try testing.expectApproxEqAbs(atanBinary64(-0x1.5f314e72398e8p-1), -0x1.33d28ca762539p-1, math.floatEpsAt(f64, -0x1.33d28ca762539p-1)); + try testing.expectApproxEqAbs(atanBinary64(0x1.5869af37b7d08p1), 0x1.37082ce2dd03p0, math.floatEpsAt(f64, 0x1.37082ce2dd03p0)); + try testing.expectApproxEqAbs(atanBinary64(-0x1.b13a05a662618p-2), -0x1.99d7cac66dd44p-2, math.floatEpsAt(f64, -0x1.99d7cac66dd44p-2)); + try testing.expectApproxEqAbs(atanBinary64(0x1.3cb0f12f39d8ap1), 0x1.2fcb120468e8ep0, math.floatEpsAt(f64, 0x1.2fcb120468e8ep0)); + try testing.expectApproxEqAbs(atanBinary64(-0x1.0ed746b39cbb7p-2), -0x1.08c71aa0e509p-2, math.floatEpsAt(f64, -0x1.08c71aa0e509p-2)); + try testing.expectApproxEqAbs(atanBinary64(0x1.299d54ac7d6bp1), 0x1.2a24e22d861dfp0, math.floatEpsAt(f64, 0x1.2a24e22d861dfp0)); + try testing.expectApproxEqAbs(atanBinary64(-0x1.0264fb9f3d50ep1), -0x1.1c617825f9751p0, math.floatEpsAt(f64, -0x1.1c617825f9751p0)); } -test "atan32.special" { - const epsilon = 0.000001; +test "atanExtended80.special" { + try testing.expectEqual(atanExtended80(0x0p+0), 0x0p+0); + try testing.expectEqual(atanExtended80(-0x0p+0), -0x0p+0); + try testing.expectApproxEqAbs(atanExtended80(0x1p+0), 0x1.921fb54442d1846ap-1, math.floatEpsAt(f80, 0x1.921fb54442d1846ap-1)); + try testing.expectApproxEqAbs(atanExtended80(-0x1p+0), -0x1.921fb54442d1846ap-1, math.floatEpsAt(f80, -0x1.921fb54442d1846ap-1)); + try testing.expectApproxEqAbs(atanExtended80(math.inf(f80)), 0x1.921fb54442d1846ap0, math.floatEpsAt(f80, 0x1.921fb54442d1846ap0)); + try testing.expectApproxEqAbs(atanExtended80(-math.inf(f80)), -0x1.921fb54442d1846ap0, math.floatEpsAt(f80, -0x1.921fb54442d1846ap0)); + try testing.expect(math.isNan(atanExtended80(math.nan(f80)))); +} - try expect(math.isPositiveZero(atan32(0.0))); - try expect(math.isNegativeZero(atan32(-0.0))); - try expect(math.approxEqAbs(f32, atan32(math.inf(f32)), math.pi / 2.0, epsilon)); - try expect(math.approxEqAbs(f32, atan32(-math.inf(f32)), -math.pi / 2.0, epsilon)); +test "atanExtended80" { + try testing.expectApproxEqAbs(atanExtended80(-0x1.8629d0244cdcbed8p-2), -0x1.74c61f437701661p-2, math.floatEpsAt(f80, -0x1.74c61f437701661p-2)); + try testing.expectApproxEqAbs(atanExtended80(-0x1.59d42d4659936d9ep1), -0x1.375fd7987cc1fd02p0, math.floatEpsAt(f80, -0x1.375fd7987cc1fd02p0)); + try testing.expectApproxEqAbs(atanExtended80(-0x1.d2dbe23d04f067b4p0), -0x1.11b8adeba5615e04p0, math.floatEpsAt(f80, -0x1.11b8adeba5615e04p0)); + try testing.expectApproxEqAbs(atanExtended80(-0x1.5f314e72398e7dbcp-1), -0x1.33d28ca76253964cp-1, math.floatEpsAt(f80, -0x1.33d28ca76253964cp-1)); + try testing.expectApproxEqAbs(atanExtended80(0x1.5869af37b7d078cap1), 0x1.37082ce2dd03010cp0, math.floatEpsAt(f80, 0x1.37082ce2dd03010cp0)); + try testing.expectApproxEqAbs(atanExtended80(-0x1.b13a05a66261821ap-2), -0x1.99d7cac66dd4438p-2, math.floatEpsAt(f80, -0x1.99d7cac66dd4438p-2)); + try testing.expectApproxEqAbs(atanExtended80(0x1.3cb0f12f39d899cp1), 0x1.2fcb120468e8d9ecp0, math.floatEpsAt(f80, 0x1.2fcb120468e8d9ecp0)); + try testing.expectApproxEqAbs(atanExtended80(-0x1.0ed746b39cbb7614p-2), -0x1.08c71aa0e5090998p-2, math.floatEpsAt(f80, -0x1.08c71aa0e5090998p-2)); + try testing.expectApproxEqAbs(atanExtended80(0x1.299d54ac7d6afc52p1), 0x1.2a24e22d861debfep0, math.floatEpsAt(f80, 0x1.2a24e22d861debfep0)); + try testing.expectApproxEqAbs(atanExtended80(-0x1.0264fb9f3d50e4fp1), -0x1.1c617825f97512b8p0, math.floatEpsAt(f80, -0x1.1c617825f97512b8p0)); } -test "atan64.special" { - const epsilon = 0.000001; +test "atanBinary128.special" { + try testing.expectEqual(atanBinary128(0x0p+0), 0x0p+0); + try testing.expectEqual(atanBinary128(-0x0p+0), -0x0p+0); + try testing.expectApproxEqAbs(atanBinary128(0x1p+0), 0x1.921fb54442d18469898cc51701b8p-1, math.floatEpsAt(f128, 0x1.921fb54442d18469898cc51701b8p-1)); + try testing.expectApproxEqAbs(atanBinary128(-0x1p+0), -0x1.921fb54442d18469898cc51701b8p-1, math.floatEpsAt(f128, -0x1.921fb54442d18469898cc51701b8p-1)); + try testing.expectApproxEqAbs(atanBinary128(math.inf(f128)), 0x1.921fb54442d18469898cc51701b8p0, math.floatEpsAt(f128, 0x1.921fb54442d18469898cc51701b8p0)); + try testing.expectApproxEqAbs(atanBinary128(-math.inf(f128)), -0x1.921fb54442d18469898cc51701b8p0, math.floatEpsAt(f128, -0x1.921fb54442d18469898cc51701b8p0)); + try testing.expect(math.isNan(atanBinary128(math.nan(f128)))); +} - try expect(math.isPositiveZero(atan64(0.0))); - try expect(math.isNegativeZero(atan64(-0.0))); - try expect(math.approxEqAbs(f64, atan64(math.inf(f64)), math.pi / 2.0, epsilon)); - try expect(math.approxEqAbs(f64, atan64(-math.inf(f64)), -math.pi / 2.0, epsilon)); +test "atanBinary128" { + try testing.expectApproxEqAbs(atanBinary128(-0x1.8629d0244cdcbed71792ccdec26dp-2), -0x1.74c61f437701660ff76989d23707p-2, math.floatEpsAt(f128, -0x1.74c61f437701660ff76989d23707p-2)); + try testing.expectApproxEqAbs(atanBinary128(-0x1.59d42d4659936d9e22b5dea4faefp1), -0x1.375fd7987cc1fd0119cf0cc5b708p0, math.floatEpsAt(f128, -0x1.375fd7987cc1fd0119cf0cc5b708p0)); + try testing.expectApproxEqAbs(atanBinary128(-0x1.d2dbe23d04f067b42da3f8efdf57p0), -0x1.11b8adeba5615e0370722b511231p0, math.floatEpsAt(f128, -0x1.11b8adeba5615e0370722b511231p0)); + try testing.expectApproxEqAbs(atanBinary128(-0x1.5f314e72398e7dbbe70fb072983ep-1), -0x1.33d28ca76253964cb5d3581cdd88p-1, math.floatEpsAt(f128, -0x1.33d28ca76253964cb5d3581cdd88p-1)); + try testing.expectApproxEqAbs(atanBinary128(0x1.5869af37b7d078caa3456c44aecep1), 0x1.37082ce2dd03010bbea814dc5882p0, math.floatEpsAt(f128, 0x1.37082ce2dd03010bbea814dc5882p0)); + try testing.expectApproxEqAbs(atanBinary128(-0x1.b13a05a66261821a364ad8c6c999p-2), -0x1.99d7cac66dd4438077284b491a91p-2, math.floatEpsAt(f128, -0x1.99d7cac66dd4438077284b491a91p-2)); + try testing.expectApproxEqAbs(atanBinary128(0x1.3cb0f12f39d899c0d963ac413297p1), 0x1.2fcb120468e8d9ebdb74702314c8p0, math.floatEpsAt(f128, 0x1.2fcb120468e8d9ebdb74702314c8p0)); + try testing.expectApproxEqAbs(atanBinary128(-0x1.0ed746b39cbb7614d8735e8315a8p-2), -0x1.08c71aa0e5090998206fbbe2090fp-2, math.floatEpsAt(f128, -0x1.08c71aa0e5090998206fbbe2090fp-2)); + try testing.expectApproxEqAbs(atanBinary128(0x1.299d54ac7d6afc5154643b601519p1), 0x1.2a24e22d861debfd6f974500567fp0, math.floatEpsAt(f128, 0x1.2a24e22d861debfd6f974500567fp0)); + try testing.expectApproxEqAbs(atanBinary128(-0x1.0264fb9f3d50e4f0f966f0686064p1), -0x1.1c617825f97512b7f38656ab12cdp0, math.floatEpsAt(f128, -0x1.1c617825f97512b7f38656ab12cdp0)); } diff --git a/src/libs/musl.zig b/src/libs/musl.zig index 3c2c4dd25fed0d205bbbf9d9be0b9f3fb78467a3..28602cd6f6ec14f15f586c01bf857234bf1ce8b7 100644 --- a/src/libs/musl.zig +++ b/src/libs/musl.zig @@ -836,12 +836,9 @@ const src_files = [_][]const u8{ "musl/src/math/atan2.c", "musl/src/math/atan2f.c", "musl/src/math/atan2l.c", - "musl/src/math/atan.c", - "musl/src/math/atanf.c", "musl/src/math/atanh.c", "musl/src/math/atanhf.c", "musl/src/math/atanhl.c", - "musl/src/math/atanl.c", "musl/src/math/cbrt.c", "musl/src/math/cbrtf.c", "musl/src/math/cbrtl.c", @@ -892,9 +889,6 @@ const src_files = [_][]const u8{ "musl/src/math/i386/atan2f.s", "musl/src/math/i386/atan2l.s", "musl/src/math/i386/atan2.s", - "musl/src/math/i386/atanf.s", - "musl/src/math/i386/atanl.s", - "musl/src/math/i386/atan.s", "musl/src/math/i386/exp2l.s", "musl/src/math/i386/exp_ld.s", "musl/src/math/i386/expl.s", @@ -1076,7 +1070,6 @@ const src_files = [_][]const u8{ "musl/src/math/x32/acosl.s", "musl/src/math/x32/asinl.s", "musl/src/math/x32/atan2l.s", - "musl/src/math/x32/atanl.s", "musl/src/math/x32/exp2l.s", "musl/src/math/x32/expl.s", "musl/src/math/x32/expm1l.s", @@ -1098,7 +1091,6 @@ const src_files = [_][]const u8{ "musl/src/math/x86_64/acosl.s", "musl/src/math/x86_64/asinl.s", "musl/src/math/x86_64/atan2l.s", - "musl/src/math/x86_64/atanl.s", "musl/src/math/x86_64/exp2l.s", "musl/src/math/x86_64/expl.s", "musl/src/math/x86_64/expm1l.s", diff --git a/src/libs/wasi_libc.zig b/src/libs/wasi_libc.zig index 61d0f2cd0cb2d432d130aecd4b5f2b97fef1ad81..b1a1159778257d4b16297f1ce87f5431635dad23 100644 --- a/src/libs/wasi_libc.zig +++ b/src/libs/wasi_libc.zig @@ -698,12 +698,9 @@ const libc_top_half_src_files = [_][]const u8{ "musl/src/math/atan2.c", "musl/src/math/atan2f.c", "musl/src/math/atan2l.c", - "musl/src/math/atan.c", - "musl/src/math/atanf.c", "musl/src/math/atanh.c", "musl/src/math/atanhf.c", "musl/src/math/atanhl.c", - "musl/src/math/atanl.c", "musl/src/math/cbrt.c", "musl/src/math/cbrtf.c", "musl/src/math/cbrtl.c", -- 2.54.0 From b9819fce69e0f208e9e20071071a40863fbdb8a9 Mon Sep 17 00:00:00 2001 From: rpkak Date: Thu, 29 Jan 2026 23:28:20 +0100 Subject: [PATCH 081/499] Io.Threaded: limit copy_file_range len to prevent EOVERFLOW --- lib/std/Io/Threaded.zig | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 385bade400e548bdcb769a9c5fb78de2bd8ba0da..d265c5fdc94c681a24149d18ecb3cec891cefaf5 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -9675,10 +9675,12 @@ fn fileWriteFileStreaming( file_reader.interface.toss(n -| header.len); return n; } + var len: usize = @intFromEnum(limit); var off_in: i64 = undefined; const off_in_ptr: ?*i64 = switch (file_reader.mode) { .positional_simple, .streaming_simple => return error.Unimplemented, .positional => p: { + len = @min(len, std.math.maxInt(usize) - file_reader.pos); off_in = @intCast(file_reader.pos); break :p &off_in; }, @@ -9689,7 +9691,7 @@ fn fileWriteFileStreaming( .linux => n: { const syscall: Syscall = try .start(); while (true) { - const rc = linux_copy_file_range_sys.copy_file_range(in_fd, off_in_ptr, out_fd, null, @intFromEnum(limit), 0); + const rc = linux_copy_file_range_sys.copy_file_range(in_fd, off_in_ptr, out_fd, null, len, 0); switch (linux_copy_file_range_sys.errno(rc)) { .SUCCESS => { syscall.finish(); @@ -9849,10 +9851,12 @@ fn fileWriteFilePositional( file_reader.interface.toss(n -| header.len); return n; } + var len: usize = @min(@intFromEnum(limit), std.math.maxInt(usize) - offset); var off_in: i64 = undefined; const off_in_ptr: ?*i64 = switch (file_reader.mode) { .positional_simple, .streaming_simple => return error.Unimplemented, .positional => p: { + len = @min(len, std.math.maxInt(usize) - file_reader.pos); off_in = @intCast(file_reader.pos); break :p &off_in; }, @@ -9864,7 +9868,7 @@ fn fileWriteFilePositional( .linux => n: { const syscall: Syscall = try .start(); while (true) { - const rc = linux_copy_file_range_sys.copy_file_range(in_fd, off_in_ptr, out_fd, &off_out, @intFromEnum(limit), 0); + const rc = linux_copy_file_range_sys.copy_file_range(in_fd, off_in_ptr, out_fd, &off_out, len, 0); switch (linux_copy_file_range_sys.errno(rc)) { .SUCCESS => { syscall.finish(); @@ -9888,7 +9892,7 @@ fn fileWriteFilePositional( .IO => return error.InputOutput, .NOMEM => return error.SystemResources, .NOSPC => return error.NoSpaceLeft, - .OVERFLOW => return error.Unseekable, + .OVERFLOW => |err| errnoBug(err), // We avoid passing too large a count. .NXIO => return error.Unseekable, .SPIPE => return error.Unseekable, .PERM => return error.PermissionDenied, -- 2.54.0 From e7e168727e3c024869de9d114a7dd49d2a80de4d Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 29 Jan 2026 20:11:18 -0800 Subject: [PATCH 082/499] std.posix: goodbye connect, eventfd --- lib/std/os/linux/IoUring/test.zig | 17 +++++-- lib/std/posix.zig | 78 +++---------------------------- 2 files changed, 19 insertions(+), 76 deletions(-) diff --git a/lib/std/os/linux/IoUring/test.zig b/lib/std/os/linux/IoUring/test.zig index 2b152daf8e1e29b1b234b5cbbf38768ad6e90b45..ac2d5ddea54772253084d2699be4180cad7a794a 100644 --- a/lib/std/os/linux/IoUring/test.zig +++ b/lib/std/os/linux/IoUring/test.zig @@ -1755,7 +1755,7 @@ test "accept multishot" { // connect client const client = try socket(address.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0); errdefer posix.close(client); - try posix.connect(client, addrAny(&address), @sizeOf(linux.sockaddr.in)); + try connect(client, addrAny(&address), @sizeOf(linux.sockaddr.in)); // test accept completion var cqe = try ring.copy_cqe(); @@ -1865,7 +1865,7 @@ test "accept_direct" { // connect const client = try socket(address.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0); - try posix.connect(client, addrAny(&address), @sizeOf(linux.sockaddr.in)); + try connect(client, addrAny(&address), @sizeOf(linux.sockaddr.in)); defer posix.close(client); // accept completion @@ -1899,7 +1899,7 @@ test "accept_direct" { try testing.expectEqual(@as(u32, 1), try ring.submit()); // connect const client = try socket(address.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0); - try posix.connect(client, addrAny(&address), @sizeOf(linux.sockaddr.in)); + try connect(client, addrAny(&address), @sizeOf(linux.sockaddr.in)); defer posix.close(client); // completion with error const cqe_accept = try ring.copy_cqe(); @@ -1949,7 +1949,7 @@ test "accept_multishot_direct" { for (registered_fds) |_| { // connect const client = try socket(address.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0); - try posix.connect(client, addrAny(&address), @sizeOf(linux.sockaddr.in)); + try connect(client, addrAny(&address), @sizeOf(linux.sockaddr.in)); defer posix.close(client); // accept completion @@ -1964,7 +1964,7 @@ test "accept_multishot_direct" { { // connect const client = try socket(address.family, posix.SOCK.STREAM | posix.SOCK.CLOEXEC, 0); - try posix.connect(client, addrAny(&address), @sizeOf(linux.sockaddr.in)); + try connect(client, addrAny(&address), @sizeOf(linux.sockaddr.in)); defer posix.close(client); // completion with error const cqe_accept = try ring.copy_cqe(); @@ -2734,3 +2734,10 @@ fn send(sockfd: posix.socket_t, buf: []const u8, flags: u32) !usize { else => return error.SendFailed, } } + +fn connect(sock: posix.socket_t, sock_addr: *const posix.sockaddr, len: posix.socklen_t) !void { + switch (posix.errno(posix.system.connect(sock, sock_addr, len))) { + .SUCCESS => return, + else => return error.ConnectFailed, + } +} diff --git a/lib/std/posix.zig b/lib/std/posix.zig index 6bcb18155248f291d12848b93da4d6576f819b57..f5f3b73a594615de3b402588067afcafd839e945 100644 --- a/lib/std/posix.zig +++ b/lib/std/posix.zig @@ -1,27 +1,18 @@ //! POSIX API layer. //! //! This is more cross platform than using OS-specific APIs, however, it is -//! lower-level and less portable than other namespaces such as `std.fs` and +//! lower-level and less portable than other namespaces such as `std.Io` and //! `std.process`. //! //! These APIs are generally lowered to libc function calls if and only if libc //! is linked. Most operating systems other than Windows, Linux, and WASI //! require always linking libc because they use it as the stable syscall ABI. -//! -//! Operating systems that are not POSIX-compliant are sometimes supported by -//! this API layer; sometimes not. Generally, an implementation will be -//! provided only if such implementation is straightforward on that operating -//! system. Otherwise, programmers are expected to use OS-specific logic to -//! deal with the exception. - const builtin = @import("builtin"); const native_os = builtin.os.tag; const std = @import("std.zig"); const Io = std.Io; const mem = std.mem; -const fs = std.fs; -const max_path_bytes = std.fs.max_path_bytes; const maxInt = std.math.maxInt; const cast = std.math.cast; const assert = std.debug.assert; @@ -122,15 +113,14 @@ pub const STDIN_FILENO = system.STDIN_FILENO; pub const STDOUT_FILENO = system.STDOUT_FILENO; pub const SYS = system.SYS; pub const Sigaction = system.Sigaction; +/// Windows has no concept of `stat`. +/// +/// On Linux, the `stat` bits/wrappers are removed due to having to maintain +/// the different varying stat structs per target and libc, leading to runtime +/// errors. Users targeting Linux should add a comptime check and use statx, +/// similar to how `Io.File.stat` does. pub const Stat = switch (native_os) { - // Has no concept of `stat`. .windows => void, - // The `stat` bits/wrappers are removed due to having to maintain the - // different varying `struct stat`s per target and libc, leading to runtime - // errors. - // - // Users targeting linux should add a comptime check and use `statx`, - // similar to how `std.fs.File.stat` does. .linux => void, else => system.Stat, }; @@ -645,26 +635,6 @@ fn setSockFlags(sock: socket_t, flags: u32) !void { } } -pub const EventFdError = error{ - SystemResources, - ProcessFdQuotaExceeded, - SystemFdQuotaExceeded, -} || UnexpectedError; - -pub fn eventfd(initval: u32, flags: u32) EventFdError!i32 { - const rc = system.eventfd(initval, flags); - switch (errno(rc)) { - .SUCCESS => return @intCast(rc), - else => |err| return unexpectedErrno(err), - - .INVAL => unreachable, // invalid parameters - .MFILE => return error.ProcessFdQuotaExceeded, - .NFILE => return error.SystemFdQuotaExceeded, - .NODEV => return error.SystemResources, - .NOMEM => return error.SystemResources, - } -} - pub const GetSockNameError = error{ /// Insufficient resources were available in the system to perform the operation. SystemResources, @@ -707,40 +677,6 @@ pub fn getpeername(sock: socket_t, addr: *sockaddr, addrlen: *socklen_t) GetSock } } -pub const ConnectError = std.Io.net.IpAddress.ConnectError || std.Io.net.UnixAddress.ConnectError; - -pub fn connect(sock: socket_t, sock_addr: *const sockaddr, len: socklen_t) ConnectError!void { - if (native_os == .windows) { - @compileError("use std.Io instead"); - } - - while (true) { - switch (errno(system.connect(sock, sock_addr, len))) { - .SUCCESS => return, - .ACCES => return error.AccessDenied, - .PERM => return error.PermissionDenied, - .ADDRNOTAVAIL => return error.AddressUnavailable, - .AFNOSUPPORT => return error.AddressFamilyUnsupported, - .AGAIN, .INPROGRESS => return error.WouldBlock, - .ALREADY => return error.ConnectionPending, - .BADF => unreachable, // sockfd is not a valid open file descriptor. - .CONNREFUSED => return error.ConnectionRefused, - .CONNRESET => return error.ConnectionResetByPeer, - .FAULT => unreachable, // The socket structure address is outside the user's address space. - .INTR => continue, - .ISCONN => @panic("AlreadyConnected"), // The socket is already connected. - .HOSTUNREACH => return error.NetworkUnreachable, - .NETUNREACH => return error.NetworkUnreachable, - .NOTSOCK => unreachable, // The file descriptor sockfd does not refer to a socket. - .PROTOTYPE => unreachable, // The socket type does not support the requested communications protocol. - .TIMEDOUT => return error.Timeout, - .NOENT => return error.FileNotFound, // Returned when socket is AF.UNIX and the given path does not exist. - .CONNABORTED => unreachable, // Tried to reuse socket that previously received error.ConnectionRefused. - else => |err| return unexpectedErrno(err), - } - } -} - pub const FStatError = std.Io.File.StatError; /// Return information about a file descriptor. -- 2.54.0 From 36eb8dec98c743f369a2badf6a56599c736a45ae Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 29 Jan 2026 20:24:33 -0800 Subject: [PATCH 083/499] std.posix: goodbye to some functions - fstat - inotify_init1 - inotify_add_watch, inotify_add_watchZ - inotify_rm_watch - sysctlbynameZ --- lib/std/Thread.zig | 15 ++-- lib/std/posix.zig | 113 ---------------------------- lib/std/process.zig | 26 ++++--- lib/std/zig/system.zig | 14 ++-- lib/std/zig/system/darwin/macos.zig | 15 ++-- 5 files changed, 40 insertions(+), 143 deletions(-) diff --git a/lib/std/Thread.zig b/lib/std/Thread.zig index ad25b1728e88abe10f4fe14c36b53a66c15b4b6a..33fd4c0234cddd970e478caf40f43cc7d7edba2b 100644 --- a/lib/std/Thread.zig +++ b/lib/std/Thread.zig @@ -809,12 +809,15 @@ const PosixThreadImpl = struct { else => { var count: c_int = undefined; var count_len: usize = @sizeOf(c_int); - const name = if (comptime target.os.tag.isDarwin()) "hw.logicalcpu" else "hw.ncpu"; - posix.sysctlbynameZ(name, &count, &count_len, null, 0) catch |err| switch (err) { - error.UnknownName => unreachable, - else => |e| return e, - }; - return @as(usize, @intCast(count)); + const name = comptime if (target.os.tag.isDarwin()) "hw.logicalcpu" else "hw.ncpu"; + switch (posix.errno(posix.system.sysctlbyname(name, &count, &count_len, null, 0))) { + .SUCCESS => return @intCast(count), + .FAULT => unreachable, + .PERM => return error.PermissionDenied, + .NOMEM => return error.SystemResources, + .NOENT => unreachable, + else => |err| return posix.unexpectedErrno(err), + } }, } } diff --git a/lib/std/posix.zig b/lib/std/posix.zig index f5f3b73a594615de3b402588067afcafd839e945..6b5686b20a3531d04dfc975d5625022932b89782 100644 --- a/lib/std/posix.zig +++ b/lib/std/posix.zig @@ -677,89 +677,6 @@ pub fn getpeername(sock: socket_t, addr: *sockaddr, addrlen: *socklen_t) GetSock } } -pub const FStatError = std.Io.File.StatError; - -/// Return information about a file descriptor. -pub fn fstat(fd: fd_t) FStatError!Stat { - if (native_os == .wasi and !builtin.link_libc) { - @compileError("unsupported OS"); - } - - var stat = mem.zeroes(Stat); - switch (errno(system.fstat(fd, &stat))) { - .SUCCESS => return stat, - .INVAL => unreachable, - .BADF => unreachable, // Always a race condition. - .NOMEM => return error.SystemResources, - .ACCES => return error.AccessDenied, - else => |err| return unexpectedErrno(err), - } -} - -pub const INotifyInitError = error{ - ProcessFdQuotaExceeded, - SystemFdQuotaExceeded, - SystemResources, -} || UnexpectedError; - -/// initialize an inotify instance -pub fn inotify_init1(flags: u32) INotifyInitError!i32 { - const rc = system.inotify_init1(flags); - switch (errno(rc)) { - .SUCCESS => return @intCast(rc), - .INVAL => unreachable, - .MFILE => return error.ProcessFdQuotaExceeded, - .NFILE => return error.SystemFdQuotaExceeded, - .NOMEM => return error.SystemResources, - else => |err| return unexpectedErrno(err), - } -} - -pub const INotifyAddWatchError = error{ - AccessDenied, - NameTooLong, - FileNotFound, - SystemResources, - UserResourceLimitReached, - NotDir, - WatchAlreadyExists, -} || UnexpectedError; - -/// add a watch to an initialized inotify instance -pub fn inotify_add_watch(inotify_fd: i32, pathname: []const u8, mask: u32) INotifyAddWatchError!i32 { - const pathname_c = try toPosixPath(pathname); - return inotify_add_watchZ(inotify_fd, &pathname_c, mask); -} - -/// Same as `inotify_add_watch` except pathname is null-terminated. -pub fn inotify_add_watchZ(inotify_fd: i32, pathname: [*:0]const u8, mask: u32) INotifyAddWatchError!i32 { - const rc = system.inotify_add_watch(inotify_fd, pathname, mask); - switch (errno(rc)) { - .SUCCESS => return @intCast(rc), - .ACCES => return error.AccessDenied, - .BADF => unreachable, - .FAULT => unreachable, - .INVAL => unreachable, - .NAMETOOLONG => return error.NameTooLong, - .NOENT => return error.FileNotFound, - .NOMEM => return error.SystemResources, - .NOSPC => return error.UserResourceLimitReached, - .NOTDIR => return error.NotDir, - .EXIST => return error.WatchAlreadyExists, - else => |err| return unexpectedErrno(err), - } -} - -/// remove an existing watch from an inotify instance -pub fn inotify_rm_watch(inotify_fd: i32, wd: i32) void { - switch (errno(system.inotify_rm_watch(inotify_fd, wd))) { - .SUCCESS => return, - .BADF => unreachable, - .INVAL => unreachable, - else => unreachable, - } -} - pub const FanotifyInitError = error{ ProcessFdQuotaExceeded, SystemFdQuotaExceeded, @@ -996,36 +913,6 @@ pub fn sysctl( } } -pub const SysCtlByNameError = error{ - PermissionDenied, - SystemResources, - UnknownName, -} || UnexpectedError; - -pub fn sysctlbynameZ( - name: [*:0]const u8, - oldp: ?*anyopaque, - oldlenp: ?*usize, - newp: ?*anyopaque, - newlen: usize, -) SysCtlByNameError!void { - if (native_os == .wasi) { - @compileError("sysctl not supported on WASI"); - } - if (native_os == .haiku) { - @compileError("sysctl not supported on Haiku"); - } - - switch (errno(system.sysctlbyname(name, oldp, oldlenp, newp, newlen))) { - .SUCCESS => return, - .FAULT => unreachable, - .PERM => return error.PermissionDenied, - .NOMEM => return error.SystemResources, - .NOENT => return error.UnknownName, - else => |err| return unexpectedErrno(err), - } -} - pub fn gettimeofday(tv: ?*timeval, tz: ?*timezone) void { switch (errno(system.gettimeofday(tv, tz))) { .SUCCESS => return, diff --git a/lib/std/process.zig b/lib/std/process.zig index 5bcacddc84a993d96586b861411af63148f361ac..8395882c167cbe0149f081cd7fb4af413ec24e08 100644 --- a/lib/std/process.zig +++ b/lib/std/process.zig @@ -556,26 +556,28 @@ pub fn totalSystemMemory() TotalSystemMemoryError!u64 { const name = if (native_os == .netbsd) "hw.physmem64" else "hw.physmem"; var physmem: c_ulong = undefined; var len: usize = @sizeOf(c_ulong); - posix.sysctlbynameZ(name, &physmem, &len, null, 0) catch |err| switch (err) { - error.PermissionDenied => unreachable, // only when setting values, - error.SystemResources => unreachable, // memory already on the stack - error.UnknownName => unreachable, + switch (posix.errno(posix.system.sysctlbyname(name, &physmem, &len, null, 0))) { + .SUCCESS => return @intCast(physmem), + .FAULT => unreachable, + .PERM => unreachable, // only when setting values + .NOMEM => unreachable, // memory already on the stack + .NOENT => unreachable, else => return error.UnknownTotalSystemMemory, - }; - return @intCast(physmem); + } }, // whole Darwin family .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => { // "hw.memsize" returns uint64_t var physmem: u64 = undefined; var len: usize = @sizeOf(u64); - posix.sysctlbynameZ("hw.memsize", &physmem, &len, null, 0) catch |err| switch (err) { - error.PermissionDenied => unreachable, // only when setting values, - error.SystemResources => unreachable, // memory already on the stack - error.UnknownName => unreachable, // constant, known good value + switch (posix.errno(posix.system.sysctlbyname("hw.memsize", &physmem, &len, null, 0))) { + .SUCCESS => return physmem, + .FAULT => unreachable, + .PERM => unreachable, // only when setting values + .NOMEM => unreachable, // memory already on the stack + .NOENT => unreachable, // constant, known good value else => return error.UnknownTotalSystemMemory, - }; - return physmem; + } }, .openbsd => { const mib: [2]c_int = [_]c_int{ diff --git a/lib/std/zig/system.zig b/lib/std/zig/system.zig index efcf569de5a5ef12170c7c7a3ea82a9774569b1a..5046e2f51b081b77c95a9e08020eb24b688498a5 100644 --- a/lib/std/zig/system.zig +++ b/lib/std/zig/system.zig @@ -260,12 +260,14 @@ pub fn resolveTargetQuery(io: Io, query: Target.Query) DetectError!Target { var value: u32 = undefined; var len: usize = @sizeOf(@TypeOf(value)); - posix.sysctlbynameZ(key, &value, &len, null, 0) catch |err| switch (err) { - error.PermissionDenied => unreachable, // only when setting values, - error.SystemResources => unreachable, // memory already on the stack - error.UnknownName => unreachable, // constant, known good value - error.Unexpected => return error.OSVersionDetectionFail, - }; + switch (posix.errno(posix.system.sysctlbyname(key, &value, &len, null, 0))) { + .SUCCESS => {}, + .FAULT => unreachable, + .PERM => unreachable, // only when setting values, + .NOMEM => unreachable, // memory already on the stack + .NOENT => unreachable, // constant, known good value + else => return error.OSVersionDetectionFail, + } switch (builtin.target.os.tag) { .freebsd => { diff --git a/lib/std/zig/system/darwin/macos.zig b/lib/std/zig/system/darwin/macos.zig index 7d80c2b588e6a6587ae7437d454666a9d8d2fed4..c9dc8b57ce5928ba1e3eca5135f243d797643728 100644 --- a/lib/std/zig/system/darwin/macos.zig +++ b/lib/std/zig/system/darwin/macos.zig @@ -2,6 +2,7 @@ const builtin = @import("builtin"); const std = @import("std"); const Io = std.Io; +const posix = std.posix; const assert = std.debug.assert; const mem = std.mem; const testing = std.testing; @@ -399,12 +400,14 @@ test "detect" { pub fn detectNativeCpuAndFeatures() ?Target.Cpu { var cpu_family: std.c.CPUFAMILY = undefined; var len: usize = @sizeOf(std.c.CPUFAMILY); - std.posix.sysctlbynameZ("hw.cpufamily", &cpu_family, &len, null, 0) catch |err| switch (err) { - error.PermissionDenied => unreachable, // only when setting values, - error.SystemResources => unreachable, // memory already on the stack - error.UnknownName => unreachable, // constant, known good value - error.Unexpected => unreachable, // EFAULT: stack should be safe, EISDIR/ENOTDIR: constant, known good value - }; + switch (posix.errno(posix.system.sysctlbyname("hw.cpufamily", &cpu_family, &len, null, 0))) { + .SUCCESS => {}, + .FAULT => unreachable, // segmentation fault + .PERM => unreachable, // only when setting values, + .NOMEM => unreachable, // memory already on the stack + .NOENT => unreachable, // constant, known good value + else => unreachable, + } const current_arch = builtin.cpu.arch; switch (current_arch) { -- 2.54.0 From 0c67d9ebdec86a5faac9336c9ed912d798050e17 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 29 Jan 2026 20:27:01 -0800 Subject: [PATCH 084/499] std.posix: goodbye gettimeofday --- lib/std/posix.zig | 8 -------- 1 file changed, 8 deletions(-) diff --git a/lib/std/posix.zig b/lib/std/posix.zig index 6b5686b20a3531d04dfc975d5625022932b89782..660551fee40ff1b1d7c850412aabefa636fe192b 100644 --- a/lib/std/posix.zig +++ b/lib/std/posix.zig @@ -913,14 +913,6 @@ pub fn sysctl( } } -pub fn gettimeofday(tv: ?*timeval, tz: ?*timezone) void { - switch (errno(system.gettimeofday(tv, tz))) { - .SUCCESS => return, - .INVAL => unreachable, - else => unreachable, - } -} - pub const FcntlError = error{ PermissionDenied, FileBusy, -- 2.54.0 From 6a3226c43cd63fd331c3f4340d4331a8875138e3 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 29 Jan 2026 21:07:57 -0800 Subject: [PATCH 085/499] std.Io: add net.Socket.createPair and remove the following from std.posix: - socketpair - fcntl --- lib/std/Io.zig | 1 + lib/std/Io/Threaded.zig | 206 +++++++++++++++++++++++++--------------- lib/std/Io/net.zig | 29 ++++++ lib/std/posix.zig | 155 ------------------------------ lib/std/posix/test.zig | 10 +- 5 files changed, 162 insertions(+), 239 deletions(-) diff --git a/lib/std/Io.zig b/lib/std/Io.zig index 72df9e34f3ec9523577a921a43eb3086586fca4e..0c252492180404d6e41bd9b72d61421a5fda10e0 100644 --- a/lib/std/Io.zig +++ b/lib/std/Io.zig @@ -688,6 +688,7 @@ pub const VTable = struct { netConnectIp: *const fn (?*anyopaque, address: *const net.IpAddress, options: net.IpAddress.ConnectOptions) net.IpAddress.ConnectError!net.Stream, netListenUnix: *const fn (?*anyopaque, *const net.UnixAddress, net.UnixAddress.ListenOptions) net.UnixAddress.ListenError!net.Socket.Handle, netConnectUnix: *const fn (?*anyopaque, *const net.UnixAddress) net.UnixAddress.ConnectError!net.Socket.Handle, + netSocketCreatePair: *const fn (?*anyopaque, net.Socket.CreatePairOptions) net.Socket.CreatePairError![2]net.Socket, netSend: *const fn (?*anyopaque, net.Socket.Handle, []net.OutgoingMessage, net.SendFlags) struct { ?net.Socket.SendError, usize }, netReceive: *const fn (?*anyopaque, net.Socket.Handle, message_buffer: []net.IncomingMessage, data_buffer: []u8, net.ReceiveFlags, Timeout) struct { ?net.Socket.ReceiveTimeoutError, usize }, /// Returns 0 on end of stream. diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index d265c5fdc94c681a24149d18ecb3cec891cefaf5..f0562504fd5e4b833ed19ddea38638b0824bd5f1 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -1684,6 +1684,7 @@ pub fn io(t: *Threaded) Io { .windows => netConnectUnixWindows, else => netConnectUnixPosix, }, + .netSocketCreatePair = netSocketCreatePair, .netClose = netClose, .netShutdown = switch (native_os) { .windows => netShutdownWindows, @@ -1824,6 +1825,7 @@ pub fn ioBasic(t: *Threaded) Io { .netAccept = netAcceptUnavailable, .netBindIp = netBindIpUnavailable, .netConnectIp = netConnectIpUnavailable, + .netSocketCreatePair = netSocketCreatePairUnavailable, .netConnectUnix = netConnectUnixUnavailable, .netClose = netCloseUnavailable, .netShutdown = netShutdownUnavailable, @@ -10612,43 +10614,36 @@ fn posixConnect( addr_len: posix.socklen_t, ) !void { const syscall: Syscall = try .start(); - while (true) { - switch (posix.errno(posix.system.connect(socket_fd, addr, addr_len))) { - .SUCCESS => { - syscall.finish(); - return; - }, - .INTR => { - try syscall.checkCancel(); - continue; - }, - else => |e| { - syscall.finish(); - switch (e) { - .ADDRNOTAVAIL => return error.AddressUnavailable, - .AFNOSUPPORT => return error.AddressFamilyUnsupported, - .AGAIN, .INPROGRESS => return error.WouldBlock, - .ALREADY => return error.ConnectionPending, - .BADF => |err| return errnoBug(err), // File descriptor used after closed. - .CONNREFUSED => return error.ConnectionRefused, - .CONNRESET => return error.ConnectionResetByPeer, - .FAULT => |err| return errnoBug(err), - .ISCONN => |err| return errnoBug(err), - .HOSTUNREACH => return error.HostUnreachable, - .NETUNREACH => return error.NetworkUnreachable, - .NOTSOCK => |err| return errnoBug(err), - .PROTOTYPE => |err| return errnoBug(err), - .TIMEDOUT => return error.Timeout, - .CONNABORTED => |err| return errnoBug(err), - .ACCES => return error.AccessDenied, - .PERM => |err| return errnoBug(err), - .NOENT => |err| return errnoBug(err), - .NETDOWN => return error.NetworkDown, - else => |err| return posix.unexpectedErrno(err), - } - }, - } - } + while (true) switch (posix.errno(posix.system.connect(socket_fd, addr, addr_len))) { + .SUCCESS => { + syscall.finish(); + return; + }, + .INTR => { + try syscall.checkCancel(); + continue; + }, + .ADDRNOTAVAIL => return syscall.fail(error.AddressUnavailable), + .AFNOSUPPORT => return syscall.fail(error.AddressFamilyUnsupported), + .AGAIN, .INPROGRESS => return syscall.fail(error.WouldBlock), + .ALREADY => return syscall.fail(error.ConnectionPending), + .CONNREFUSED => return syscall.fail(error.ConnectionRefused), + .CONNRESET => return syscall.fail(error.ConnectionResetByPeer), + .HOSTUNREACH => return syscall.fail(error.HostUnreachable), + .NETUNREACH => return syscall.fail(error.NetworkUnreachable), + .TIMEDOUT => return syscall.fail(error.Timeout), + .ACCES => return syscall.fail(error.AccessDenied), + .NETDOWN => return syscall.fail(error.NetworkDown), + .BADF => |err| return syscall.errnoBug(err), // File descriptor used after closed. + .CONNABORTED => |err| return syscall.errnoBug(err), + .FAULT => |err| return syscall.errnoBug(err), + .ISCONN => |err| return syscall.errnoBug(err), + .NOENT => |err| return syscall.errnoBug(err), + .NOTSOCK => |err| return syscall.errnoBug(err), + .PERM => |err| return syscall.errnoBug(err), + .PROTOTYPE => |err| return syscall.errnoBug(err), + else => |err| return syscall.unexpectedErrno(err), + }; } fn posixConnectUnix( @@ -11106,46 +11101,31 @@ fn openSocketPosix( }!posix.socket_t { const mode = posixSocketMode(options.mode); const protocol = posixProtocol(options.protocol); + const flags: u32 = mode | if (socket_flags_unsupported) 0 else posix.SOCK.CLOEXEC; const syscall: Syscall = try .start(); const socket_fd = while (true) { - const flags: u32 = mode | if (socket_flags_unsupported) 0 else posix.SOCK.CLOEXEC; - const socket_rc = posix.system.socket(family, flags, protocol); - switch (posix.errno(socket_rc)) { + const rc = posix.system.socket(family, flags, protocol); + switch (posix.errno(rc)) { .SUCCESS => { - const fd: posix.fd_t = @intCast(socket_rc); + syscall.finish(); + const fd: posix.fd_t = @intCast(rc); errdefer posix.close(fd); - if (socket_flags_unsupported) while (true) { - try syscall.checkCancel(); - switch (posix.errno(posix.system.fcntl(fd, posix.F.SETFD, @as(usize, posix.FD_CLOEXEC)))) { - .SUCCESS => break, - .INTR => continue, - else => |err| { - syscall.finish(); - return posix.unexpectedErrno(err); - }, - } - }; - syscall.finish(); + if (socket_flags_unsupported) try setCloexec(fd); break fd; }, .INTR => { try syscall.checkCancel(); continue; }, - else => |e| { - syscall.finish(); - switch (e) { - .AFNOSUPPORT => return error.AddressFamilyUnsupported, - .INVAL => return error.ProtocolUnsupportedBySystem, - .MFILE => return error.ProcessFdQuotaExceeded, - .NFILE => return error.SystemFdQuotaExceeded, - .NOBUFS => return error.SystemResources, - .NOMEM => return error.SystemResources, - .PROTONOSUPPORT => return error.ProtocolUnsupportedByAddressFamily, - .PROTOTYPE => return error.SocketModeUnsupported, - else => |err| return posix.unexpectedErrno(err), - } - }, + .AFNOSUPPORT => return syscall.fail(error.AddressFamilyUnsupported), + .INVAL => return syscall.fail(error.ProtocolUnsupportedBySystem), + .MFILE => return syscall.fail(error.ProcessFdQuotaExceeded), + .NFILE => return syscall.fail(error.SystemFdQuotaExceeded), + .NOBUFS => return syscall.fail(error.SystemResources), + .NOMEM => return syscall.fail(error.SystemResources), + .PROTONOSUPPORT => return syscall.fail(error.ProtocolUnsupportedByAddressFamily), + .PROTOTYPE => return syscall.fail(error.SocketModeUnsupported), + else => |err| return syscall.unexpectedErrno(err), } }; errdefer posix.close(socket_fd); @@ -11158,6 +11138,84 @@ fn openSocketPosix( return socket_fd; } +fn setCloexec(fd: posix.fd_t) error{ Canceled, Unexpected }!void { + const syscall: Syscall = try .start(); + while (true) switch (posix.errno(posix.system.fcntl(fd, posix.F.SETFD, @as(usize, posix.FD_CLOEXEC)))) { + .SUCCESS => return syscall.finish(), + .INTR => { + try syscall.checkCancel(); + continue; + }, + else => |err| return syscall.unexpectedErrno(err), + }; +} + +fn netSocketCreatePair( + userdata: ?*anyopaque, + options: net.Socket.CreatePairOptions, +) net.Socket.CreatePairError![2]net.Socket { + const t: *Threaded = @ptrCast(@alignCast(userdata)); + _ = t; + if (!have_networking) return error.OperationUnsupported; + if (@TypeOf(posix.system.socketpair) == void) return error.OperationUnsupported; + if (native_os == .haiku) @panic("TODO"); + + const family: posix.sa_family_t = switch (options.family) { + .ip4 => posix.AF.INET, + .ip6 => posix.AF.INET6, + }; + const mode = posixSocketMode(options.mode); + const protocol = posixProtocol(options.protocol); + const flags: u32 = mode | if (socket_flags_unsupported) 0 else posix.SOCK.CLOEXEC; + + var sockets: [2]posix.socket_t = undefined; + const syscall: Syscall = try .start(); + while (true) switch (posix.errno(posix.system.socketpair(family, flags, protocol, &sockets))) { + .SUCCESS => { + syscall.finish(); + errdefer { + posix.close(sockets[0]); + posix.close(sockets[1]); + } + if (socket_flags_unsupported) { + try setCloexec(sockets[0]); + try setCloexec(sockets[1]); + } + var storages: [2]PosixAddress = undefined; + var addr_lens: [2]posix.socklen_t = .{ @sizeOf(PosixAddress), @sizeOf(PosixAddress) }; + try posixGetSockName(sockets[0], &storages[0].any, &addr_lens[0]); + try posixGetSockName(sockets[1], &storages[1].any, &addr_lens[1]); + return .{ + .{ .handle = sockets[0], .address = addressFromPosix(&storages[0]) }, + .{ .handle = sockets[1], .address = addressFromPosix(&storages[1]) }, + }; + }, + .INTR => { + try syscall.checkCancel(); + continue; + }, + .ACCES => return syscall.fail(error.AccessDenied), + .AFNOSUPPORT => return syscall.fail(error.AddressFamilyUnsupported), + .INVAL => return syscall.fail(error.ProtocolUnsupportedBySystem), + .MFILE => return syscall.fail(error.ProcessFdQuotaExceeded), + .NFILE => return syscall.fail(error.SystemFdQuotaExceeded), + .NOBUFS => return syscall.fail(error.SystemResources), + .NOMEM => return syscall.fail(error.SystemResources), + .PROTONOSUPPORT => return syscall.fail(error.ProtocolUnsupportedByAddressFamily), + .PROTOTYPE => return syscall.fail(error.SocketModeUnsupported), + else => |err| return syscall.unexpectedErrno(err), + }; +} + +fn netSocketCreatePairUnavailable( + userdata: ?*anyopaque, + options: net.Socket.CreatePairOptions, +) net.Socket.CreatePairError![2]net.Socket { + _ = userdata; + _ = options; + return error.OperationUnsupported; +} + fn openSocketWsa( t: *Threaded, family: posix.sa_family_t, @@ -11216,20 +11274,10 @@ fn netAcceptPosix(userdata: ?*anyopaque, listen_fd: net.Socket.Handle) net.Serve posix.system.accept(listen_fd, &storage.any, &addr_len); switch (posix.errno(rc)) { .SUCCESS => { + syscall.finish(); const fd: posix.fd_t = @intCast(rc); errdefer posix.close(fd); - if (!have_accept4) while (true) { - try syscall.checkCancel(); - switch (posix.errno(posix.system.fcntl(fd, posix.F.SETFD, @as(usize, posix.FD_CLOEXEC)))) { - .SUCCESS => break, - .INTR => continue, - else => |err| { - syscall.finish(); - return posix.unexpectedErrno(err); - }, - } - }; - syscall.finish(); + if (!have_accept4) try setCloexec(fd); break fd; }, .INTR => { diff --git a/lib/std/Io/net.zig b/lib/std/Io/net.zig index 76a581180d10ae2f256fe407413a2f15911e4f70..21bd13caf387e758d91333809879e87469250677 100644 --- a/lib/std/Io/net.zig +++ b/lib/std/Io/net.zig @@ -1187,6 +1187,35 @@ pub const Socket = struct { ) struct { ?ReceiveTimeoutError, usize } { return io.vtable.netReceive(io.userdata, s.handle, message_buffer, data_buffer, flags, timeout); } + + pub const CreatePairError = error{ + OperationUnsupported, + AccessDenied, + AddressFamilyUnsupported, + ProtocolUnsupportedBySystem, + /// The per-process limit on the number of open file descriptors has been reached. + ProcessFdQuotaExceeded, + /// The system-wide limit on the total number of open files has been reached. + SystemFdQuotaExceeded, + /// Insufficient memory is available. The socket cannot be created + /// until sufficient resources are freed. + SystemResources, + ProtocolUnsupportedByAddressFamily, + SocketModeUnsupported, + } || Io.UnexpectedError || Io.Cancelable; + + pub const CreatePairOptions = struct { + family: IpAddress.Family = .ip4, + mode: Mode = .stream, + protocol: ?Protocol = null, + }; + + /// Create a set of two sockets that are connected to each other. + /// + /// Also known as "socketpair". + pub fn createPair(io: Io, options: CreatePairOptions) CreatePairError![2]Socket { + return io.vtable.netSocketCreatePair(io.userdata, options); + } }; /// An open socket connection with a network protocol that guarantees diff --git a/lib/std/posix.zig b/lib/std/posix.zig index 660551fee40ff1b1d7c850412aabefa636fe192b..5e2cde9aa52421fc3f74a8b3baf92eb37c3cc562 100644 --- a/lib/std/posix.zig +++ b/lib/std/posix.zig @@ -509,132 +509,6 @@ pub fn getppid() pid_t { return system.getppid(); } -pub const SocketError = error{ - /// Permission to create a socket of the specified type and/or - /// pro‐tocol is denied. - AccessDenied, - - /// The implementation does not support the specified address family. - AddressFamilyUnsupported, - - /// Unknown protocol, or protocol family not available. - ProtocolFamilyNotAvailable, - - /// The per-process limit on the number of open file descriptors has been reached. - ProcessFdQuotaExceeded, - - /// The system-wide limit on the total number of open files has been reached. - SystemFdQuotaExceeded, - - /// Insufficient memory is available. The socket cannot be created until sufficient - /// resources are freed. - SystemResources, - - /// The protocol type or the specified protocol is not supported within this domain. - ProtocolNotSupported, - - /// The socket type is not supported by the protocol. - SocketTypeNotSupported, -} || UnexpectedError; - -pub fn socketpair(domain: u32, socket_type: u32, protocol: u32) SocketError![2]socket_t { - // Note to the future: we could provide a shim here for e.g. windows which - // creates a listening socket, then creates a second socket and connects it - // to the listening socket, and then returns the two. - if (@TypeOf(system.socketpair) == void) - @compileError("socketpair() not supported by this OS"); - - // I'm not really sure if haiku supports flags here. I'm following the - // existing filter here from pipe2(), because it sure seems like it - // supports flags there too, but haiku can be hard to understand. - const have_sock_flags = !builtin.target.os.tag.isDarwin() and native_os != .haiku; - const filtered_sock_type = if (!have_sock_flags) - socket_type & ~@as(u32, SOCK.NONBLOCK | SOCK.CLOEXEC) - else - socket_type; - var socks: [2]socket_t = undefined; - const rc = system.socketpair(domain, filtered_sock_type, protocol, &socks); - switch (errno(rc)) { - .SUCCESS => { - errdefer close(socks[0]); - errdefer close(socks[1]); - if (!have_sock_flags) { - try setSockFlags(socks[0], socket_type); - try setSockFlags(socks[1], socket_type); - } - return socks; - }, - .ACCES => return error.AccessDenied, - .AFNOSUPPORT => return error.AddressFamilyUnsupported, - .INVAL => return error.ProtocolFamilyNotAvailable, - .MFILE => return error.ProcessFdQuotaExceeded, - .NFILE => return error.SystemFdQuotaExceeded, - .NOBUFS => return error.SystemResources, - .NOMEM => return error.SystemResources, - .PROTONOSUPPORT => return error.ProtocolNotSupported, - .PROTOTYPE => return error.SocketTypeNotSupported, - else => |err| return unexpectedErrno(err), - } -} - -fn setSockFlags(sock: socket_t, flags: u32) !void { - if ((flags & SOCK.CLOEXEC) != 0) { - if (native_os == .windows) { - // TODO: Find out if this is supported for sockets - } else { - var fd_flags = fcntl(sock, F.GETFD, 0) catch |err| switch (err) { - error.FileBusy => unreachable, - error.Locked => unreachable, - error.PermissionDenied => unreachable, - error.DeadLock => unreachable, - error.LockedRegionLimitExceeded => unreachable, - else => |e| return e, - }; - fd_flags |= FD_CLOEXEC; - _ = fcntl(sock, F.SETFD, fd_flags) catch |err| switch (err) { - error.FileBusy => unreachable, - error.Locked => unreachable, - error.PermissionDenied => unreachable, - error.DeadLock => unreachable, - error.LockedRegionLimitExceeded => unreachable, - else => |e| return e, - }; - } - } - if ((flags & SOCK.NONBLOCK) != 0) { - if (native_os == .windows) { - var mode: c_ulong = 1; - if (windows.ws2_32.ioctlsocket(sock, windows.ws2_32.FIONBIO, &mode) == windows.ws2_32.SOCKET_ERROR) { - switch (windows.ws2_32.WSAGetLastError()) { - .NOTINITIALISED => unreachable, - .ENETDOWN => return error.NetworkDown, - .ENOTSOCK => return error.FileDescriptorNotASocket, - // TODO: handle more errors - else => |err| return windows.unexpectedWSAError(err), - } - } - } else { - var fl_flags = fcntl(sock, F.GETFL, 0) catch |err| switch (err) { - error.FileBusy => unreachable, - error.Locked => unreachable, - error.PermissionDenied => unreachable, - error.DeadLock => unreachable, - error.LockedRegionLimitExceeded => unreachable, - else => |e| return e, - }; - fl_flags |= 1 << @bitOffsetOf(O, "NONBLOCK"); - _ = fcntl(sock, F.SETFL, fl_flags) catch |err| switch (err) { - error.FileBusy => unreachable, - error.Locked => unreachable, - error.PermissionDenied => unreachable, - error.DeadLock => unreachable, - error.LockedRegionLimitExceeded => unreachable, - else => |e| return e, - }; - } - } -} - pub const GetSockNameError = error{ /// Insufficient resources were available in the system to perform the operation. SystemResources, @@ -913,35 +787,6 @@ pub fn sysctl( } } -pub const FcntlError = error{ - PermissionDenied, - FileBusy, - ProcessFdQuotaExceeded, - Locked, - DeadLock, - LockedRegionLimitExceeded, -} || UnexpectedError; - -pub fn fcntl(fd: fd_t, cmd: i32, arg: usize) FcntlError!usize { - while (true) { - const rc = system.fcntl(fd, cmd, arg); - switch (errno(rc)) { - .SUCCESS => return @intCast(rc), - .INTR => continue, - .AGAIN, .ACCES => return error.Locked, - .BADF => unreachable, - .BUSY => return error.FileBusy, - .INVAL => unreachable, // invalid parameters - .PERM => return error.PermissionDenied, - .MFILE => return error.ProcessFdQuotaExceeded, - .NOTDIR => unreachable, // invalid parameter - .DEADLK => return error.DeadLock, - .NOLCK => return error.LockedRegionLimitExceeded, - else => |err| return unexpectedErrno(err), - } - } -} - pub fn getSelfPhdrs() []std.elf.ElfN.Phdr { const getauxval = if (builtin.link_libc) std.c.getauxval else std.os.linux.getauxval; assert(getauxval(std.elf.AT_PHENT) == @sizeOf(std.elf.ElfN.Phdr)); diff --git a/lib/std/posix/test.zig b/lib/std/posix/test.zig index e720c4a4e6d23aee4573fa082582e5e0c4c066e8..5838595fcf50cec130595089b7d38c26b17f19b8 100644 --- a/lib/std/posix/test.zig +++ b/lib/std/posix/test.zig @@ -273,17 +273,17 @@ test "fcntl" { // Note: The test assumes createFile opens the file with CLOEXEC { - const flags = try posix.fcntl(file.handle, posix.F.GETFD, 0); + const flags = posix.system.fcntl(file.handle, posix.F.GETFD, @as(usize, 0)); try expect((flags & posix.FD_CLOEXEC) != 0); } { - _ = try posix.fcntl(file.handle, posix.F.SETFD, 0); - const flags = try posix.fcntl(file.handle, posix.F.GETFD, 0); + _ = posix.system.fcntl(file.handle, posix.F.SETFD, @as(usize, 0)); + const flags = posix.system.fcntl(file.handle, posix.F.GETFD, @as(usize, 0)); try expect((flags & posix.FD_CLOEXEC) == 0); } { - _ = try posix.fcntl(file.handle, posix.F.SETFD, posix.FD_CLOEXEC); - const flags = try posix.fcntl(file.handle, posix.F.GETFD, 0); + _ = posix.system.fcntl(file.handle, posix.F.SETFD, @as(usize, posix.FD_CLOEXEC)); + const flags = posix.system.fcntl(file.handle, posix.F.GETFD, @as(usize, 0)); try expect((flags & posix.FD_CLOEXEC) != 0); } } -- 2.54.0 From fa988e88ed21485830a70276b5c7567efb122f80 Mon Sep 17 00:00:00 2001 From: mercenary Date: Fri, 30 Jan 2026 20:19:19 +0100 Subject: [PATCH 086/499] zstd.Decompress: smarter rebase when discarding (#30891) The call to `rebase` in `discardIndirect` and `discardDirect` was inappropriate. As `rebase` expects the `capacity` parameter to exclude the sliding window, this call was asking for ANOTHER `d.window_len` bytes. This was impossible to fulfill with a buffer smaller than 2*`d.window_len`, and caused [#25764](https://github.com/ziglang/zig/issues/25764). This PR adds a basic test to do a discard (which does trigger [#25764](https://github.com/ziglang/zig/issues/25764)), and rebases only as much as is required to make the discard succeed ([or no rebase at all](https://github.com/ziglang/zig/issues/25764#issuecomment-3484716253)). That means: ideally rebase to fit `limit`, or if the buffer is too small, as much as possible. I must say, `discardDirect` does not make much sense to me, but I replaced it anyway. `rebaseForDiscard` works fine with `d.reader.buffer.len == 0`. Let me know if anything should be changed. Reviewed-on: https://codeberg.org/ziglang/zig/pulls/30891 Reviewed-by: Andrew Kelley Co-authored-by: mercenary Co-committed-by: mercenary --- lib/std/Io.zig | 8 ++++++++ lib/std/compress/zstd.zig | 13 +++++++++++++ lib/std/compress/zstd/Decompress.zig | 24 ++++++++++++++++++++++-- 3 files changed, 43 insertions(+), 2 deletions(-) diff --git a/lib/std/Io.zig b/lib/std/Io.zig index 0c252492180404d6e41bd9b72d61421a5fda10e0..6dc0e247315c4a69ed02332c9935131a5d6812fc 100644 --- a/lib/std/Io.zig +++ b/lib/std/Io.zig @@ -728,6 +728,14 @@ pub const Limit = enum(usize) { return @enumFromInt(@min(@intFromEnum(a), @intFromEnum(b))); } + pub fn max(a: Limit, b: Limit) Limit { + if (a == .unlimited or b == .unlimited) { + return .unlimited; + } + + return @enumFromInt(@max(@intFromEnum(a), @intFromEnum(b))); + } + pub fn minInt(l: Limit, n: usize) usize { return @min(n, @intFromEnum(l)); } diff --git a/lib/std/compress/zstd.zig b/lib/std/compress/zstd.zig index 39073b51c58b3b91a68f6db2adb5dfdd3ce8025b..51168889c6355b7952e9fe54576c7b74edc6d197 100644 --- a/lib/std/compress/zstd.zig +++ b/lib/std/compress/zstd.zig @@ -88,6 +88,17 @@ fn testDecompress(gpa: std.mem.Allocator, compressed: []const u8) ![]u8 { return out.toOwnedSlice(); } +/// Create a `Decompress` from `compressed` and immediately discard all output. Returns the number +/// of discarded bytes. +fn testDiscard(gpa: std.mem.Allocator, compressed: []const u8) !usize { + const buf: []u8 = try gpa.alloc(u8, default_window_len + block_size_max); + defer gpa.free(buf); + + var in: std.Io.Reader = .fixed(compressed); + var zstd_stream: Decompress = .init(&in, buf, .{}); + return try zstd_stream.reader.discardRemaining(); +} + fn testExpectDecompress(uncompressed: []const u8, compressed: []const u8) !void { const gpa = std.testing.allocator; const result = try testDecompress(gpa, compressed); @@ -117,6 +128,8 @@ test Decompress { try testExpectDecompress(uncompressed, compressed3); try testExpectDecompress(uncompressed, compressed19); + try std.testing.expectEqual(uncompressed.len, testDiscard(std.testing.allocator, compressed3)); + try std.testing.expectEqual(uncompressed.len, testDiscard(std.testing.allocator, compressed19)); } test "partial magic number" { diff --git a/lib/std/compress/zstd/Decompress.zig b/lib/std/compress/zstd/Decompress.zig index 0acef462e78d59485124ce975e94c8124dbd6b56..cab1ee99f4baf55169479eaf214363901ebd6791 100644 --- a/lib/std/compress/zstd/Decompress.zig +++ b/lib/std/compress/zstd/Decompress.zig @@ -123,9 +123,13 @@ fn rebaseFallible(r: *Reader, capacity: usize) Reader.RebaseError!void { rebase(r, capacity); } +// Rebase the buffer, keeping at least the sliding window (`d.window_len` bytes) buffered fn rebase(r: *Reader, capacity: usize) void { const d: *Decompress = @alignCast(@fieldParentPtr("reader", r)); + // `capacity` must fit in the buffer along with the required sliding window assert(capacity <= r.buffer.len - d.window_len); + // According to the vtable contract, this function will only be called if the free space in the + // buffer cannot already fit `capacity` bytes assert(r.end + capacity > r.buffer.len); const discard_n = @min(r.seek, r.end - d.window_len); const keep = r.buffer[discard_n..r.end]; @@ -134,11 +138,27 @@ fn rebase(r: *Reader, capacity: usize) void { r.seek -= discard_n; } +/// Rebase `d.reader.buffer` as much as needed for a discard limited by `limit` +fn rebaseForDiscard(d: *Decompress, limit: std.Io.Limit) void { + // Number of bytes desired to rebase, always rebase for at least block_size + const desire_n = limit.max(Limit.limited(zstd.block_size_max)); + // Maximum number of bytes possible to rebase + const max_n = d.reader.buffer.len -| d.window_len; + // Number of bytes to rebase + const n = desire_n.minInt(max_n); + + // Current buffer free space + const current_cap = d.reader.buffer.len - d.reader.end; + if (current_cap < n) { + rebase(&d.reader, n); + } +} + /// This could be improved so that when an amount is discarded that includes an /// entire frame, skip decoding that frame. fn discardDirect(r: *Reader, limit: std.Io.Limit) Reader.Error!usize { const d: *Decompress = @alignCast(@fieldParentPtr("reader", r)); - rebase(r, d.window_len); + rebaseForDiscard(d, limit); var writer: Writer = .{ .vtable = &.{ .drain = std.Io.Writer.Discarding.drain, @@ -162,7 +182,7 @@ fn discardDirect(r: *Reader, limit: std.Io.Limit) Reader.Error!usize { fn discardIndirect(r: *Reader, limit: std.Io.Limit) Reader.Error!usize { const d: *Decompress = @alignCast(@fieldParentPtr("reader", r)); - rebase(r, d.window_len); + rebaseForDiscard(d, limit); var writer: Writer = .{ .buffer = r.buffer, .end = r.end, -- 2.54.0 From c6538b70f5f0a5a1da8895c169c2cb9189148d85 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Fri, 30 Jan 2026 23:58:47 +0100 Subject: [PATCH 087/499] llvm: handle packed structs in C ABI integer promotion --- src/codegen/llvm.zig | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 358d4ac469df1db0a3c7c8b507635f696797725e..26369d2ce98193f191a1d27fd96d58a569cc4fd3 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -12608,8 +12608,7 @@ fn ccAbiPromoteInt(cc: std.builtin.CallingConvention, zcu: *Zcu, ty: Type) ?std. } const int_info = switch (ty.zigTypeTag(zcu)) { .bool => Type.u1.intInfo(zcu), - .int, .@"enum", .error_set => ty.intInfo(zcu), - else => return null, + else => if (ty.isAbiInt(zcu)) ty.intInfo(zcu) else return null, }; return switch (target.os.tag) { .driverkit, .ios, .maccatalyst, .macos, .watchos, .tvos, .visionos => switch (int_info.bits) { -- 2.54.0 From cbe38f771c0cb7d098878e8e703ed23620f868f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Fri, 30 Jan 2026 23:58:17 +0100 Subject: [PATCH 088/499] std.Io.Threaded: consider EOPNOTSUPP to be programmer error in createFileMap() Not doing so was hiding bugs (e.g. on s390x-linux). --- lib/std/Io/Threaded.zig | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index f0562504fd5e4b833ed19ddea38638b0824bd5f1..918b0f43f96ce4e64f9e10311b0e4c93a5de48f3 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -16796,6 +16796,7 @@ fn createFileMap( .OVERFLOW => return error.Unseekable, .BADF => return errnoBug(err), // Always a race condition. .INVAL => return errnoBug(err), // Invalid parameters to mmap() + .OPNOTSUPP => return errnoBug(err), // Bad flags with MAP.SHARED_VALIDATE on Linux. else => return posix.unexpectedErrno(err), } }; -- 2.54.0 From 7c68ab1d1085925ec33dc0f8a6695006b2d7a85d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Fri, 30 Jan 2026 23:47:42 +0100 Subject: [PATCH 089/499] std.os.linux: add MAP.DROPPABLE Introduced in Linux 6.11. --- lib/std/os/linux.zig | 1 + 1 file changed, 1 insertion(+) diff --git a/lib/std/os/linux.zig b/lib/std/os/linux.zig index f5868f71e9af718e2464e03a9e241f95f104ae89..501ba44557b4a817d7de516795bd4ee4d3361c24 100644 --- a/lib/std/os/linux.zig +++ b/lib/std/os/linux.zig @@ -146,6 +146,7 @@ pub const MAP_TYPE = enum(u4) { SHARED = 0x01, PRIVATE = 0x02, SHARED_VALIDATE = 0x03, + DROPPABLE = 0x08, }; pub const MAP = switch (native_arch) { -- 2.54.0 From c7c4e8d802c5c8ab2dc9d064c3985836e6677115 Mon Sep 17 00:00:00 2001 From: Justus Klausecker Date: Wed, 28 Jan 2026 11:27:10 +0100 Subject: [PATCH 090/499] Sema: harden `switch` logic against undef IB Most places where `undefined` was previously (intentionally) passed across function calls now use `Air.Inst.Ref.none` instead to ensure that these `undefined` references don't accidentally outlive the `switch` logic they belong to. --- src/Sema.zig | 82 ++++++++++++++++++++++++++++++---------------------- 1 file changed, 47 insertions(+), 35 deletions(-) diff --git a/src/Sema.zig b/src/Sema.zig index ea2890ee6a220a0cc013f6735057f8ee27e92ed3..fc71a58f776518edf6d0dd745469bb1c1f77e1ef 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -10761,7 +10761,7 @@ fn analyzeSwitchBlock( const val, const ref = if (operand_is_ref) .{ try sema.analyzeLoad(block, src, raw_operand, operand_src), raw_operand } else - .{ raw_operand, undefined }; + .{ raw_operand, .none }; const operand_ty = sema.typeOf(val); const maybe_operand_opv = try sema.typeHasOnePossibleValue(operand_ty); @@ -10785,7 +10785,7 @@ fn analyzeSwitchBlock( const operand_alloc = try block.addTy(.alloc, operand_ptr_ty); _ = try block.addBinOp(.store, operand_alloc, raw_operand); break :alloc operand_alloc; - } else undefined; + } else .none; break :operand .{ .{ .loop = .{ .operand_alloc = operand_alloc, .operand_is_ref = operand_is_ref, @@ -10857,7 +10857,7 @@ fn analyzeSwitchBlock( const new_val, const new_ref = if (operand_is_ref) .{ try sema.analyzeLoad(child_block, src, new_operand, new_operand_src), new_operand } else - .{ new_operand, undefined }; + .{ new_operand, .none }; const new_cond_ref = if (union_originally) try sema.unionToTag(child_block, item_ty, new_val, src) @@ -10953,7 +10953,7 @@ fn analyzeSwitchBlock( const by_val = try sema.analyzeLoad(block, src, loaded, src); break :load_operand .{ by_val, loaded }; } else { - break :load_operand .{ loaded, undefined }; + break :load_operand .{ loaded, .none }; } }, }; @@ -11393,33 +11393,31 @@ fn finishSwitchBr( var emit_bb = false; if (has_else and else_case.is_inline) { const else_prong_src = block.src(.{ .node_offset_switch_else_prong = src_node_offset }); - var error_names: InternPool.NullTerminatedString.Slice = undefined; - var min_int: Value = undefined; - check_enumerable: { + const error_names, const min_int = check_enumerable: { switch (item_ty.zigTypeTag(zcu)) { .@"union" => unreachable, .@"enum" => if (else_is_named_only or !item_ty.isNonexhaustiveEnum(zcu) or union_originally) { try branch_hints.ensureUnusedCapacity(gpa, @intCast(validated_switch.seen_enum_fields.len)); - break :check_enumerable; + break :check_enumerable .{ undefined, undefined }; }, .error_set => if (!operand_ty.isAnyError(zcu)) { - error_names = item_ty.errorSetNames(zcu); + const error_names = item_ty.errorSetNames(zcu); try branch_hints.ensureUnusedCapacity(gpa, error_names.len); - break :check_enumerable; + break :check_enumerable .{ error_names, undefined }; }, .int => { - min_int = try item_ty.minInt(pt, item_ty); - break :check_enumerable; + const min_int = try item_ty.minInt(pt, item_ty); + break :check_enumerable .{ undefined, min_int }; }, - .bool, .void => break :check_enumerable, + .bool, .void => break :check_enumerable .{ undefined, undefined }, else => {}, } return sema.fail(block, else_prong_src, "cannot enumerate values of type '{f}' for 'inline else'", .{ item_ty.fmt(pt), }); - } + }; var unhandled_it = validated_switch.iterateUnhandledItems(error_names, min_int); while (try unhandled_it.next(sema, item_ty)) |item_val| { cases_len += 1; @@ -11660,7 +11658,7 @@ fn fixupSwitchContinues( operand_is_ref: bool, item_ty: Type, mode: enum { normal, opv }, - any_non_inline_capture: bool, + any_maybe_runtime_capture: bool, merges: *const Block.Merges, ) CompileError!void { const pt = sema.pt; @@ -11686,7 +11684,7 @@ fn fixupSwitchContinues( assert(sema.air_instructions.items(.tag)[@intFromEnum(placeholder_inst)] == .br); const new_operand_maybe_ref = sema.air_instructions.items(.data)[@intFromEnum(placeholder_inst)].br.operand; - if (any_non_inline_capture and mode != .opv) { + if (any_maybe_runtime_capture and mode != .opv) { _ = try replacement_block.addBinOp(.store, operand.loop.operand_alloc, new_operand_maybe_ref); } @@ -12431,6 +12429,8 @@ fn resolveSwitchBlock( } } + assert(zir_switch.else_case != null or under_prong != null); // switch exhaustion check wrong + const else_case = validated_switch.else_case; const else_is_named_only = zir_switch.else_case != null and under_prong != null; @@ -12507,8 +12507,8 @@ const SwitchOperand = union(enum) { simple: struct { /// The raw switch operand value. Always defined. by_val: Air.Inst.Ref, - /// The switch operand *pointer*. Defined only if there is a prong - /// with a by-ref capture. + /// The switch operand *pointer*. `none` if there are no prongs with a + /// by-ref capture. by_ref: Air.Inst.Ref, /// The switch condition value. For unions, `operand` is the union /// and `cond` is its enum tag value. @@ -12519,7 +12519,7 @@ const SwitchOperand = union(enum) { loop: struct { /// The `alloc` containing the `switch` operand for the active dispatch. /// Each prong must load from this `alloc` to get captures. - /// If there are no captures, this may be undefined. + /// If there are no captures, this may be `none`. operand_alloc: Air.Inst.Ref, /// Whether `operand_alloc` contains a by-val operand or a by-ref /// operand. @@ -12665,19 +12665,31 @@ fn analyzeSwitchProng( } } - const operand_val, const operand_ptr = load_operand: { + const need_load: bool = need_load: { if (capture == .none and !has_tag_capture) { // No need to load the operand for this prong! - break :load_operand .{ undefined, undefined }; + break :need_load false; } - if (kind == .inline_ref and - !(capture != .none and operand_ty.zigTypeTag(zcu) == .@"union")) - { - // We only need to load the operand if there's a union payload capture - // since it's always runtime-known; only the tag is comptime-known here. - break :load_operand .{ undefined, undefined }; + if (capture != .none and operand_ty.zigTypeTag(zcu) == .@"union") { + // Non-OPV union payload captures are always runtime-known. + break :need_load true; + } + if (kind == .inline_ref) { + // `inline_ref` *is* the (comptime-known) capture. + break :need_load false; } assert(zir_switch.any_maybe_runtime_capture); // should have caught everything else by now + if (capture != .by_ref and + kind == .item_refs and kind.item_refs.len == 1) + { + // Capture is comptime-known because it's the only prong item + break :need_load false; + } + break :need_load true; + }; + + const operand_val: Air.Inst.Ref, const operand_ptr: Air.Inst.Ref = load_operand: { + if (!need_load) break :load_operand .{ .none, .none }; switch (operand) { .simple => |s| break :load_operand .{ s.by_val, s.by_ref }, .loop => |l| { @@ -12686,7 +12698,7 @@ fn analyzeSwitchProng( const by_val = try sema.analyzeLoad(case_block, operand_src, loaded, operand_src); break :load_operand .{ by_val, loaded }; } else { - break :load_operand .{ loaded, undefined }; + break :load_operand .{ loaded, .none }; } }, } @@ -12735,7 +12747,7 @@ fn analyzeSwitchProng( fn analyzeSwitchTagCapture( sema: *Sema, case_block: *Block, - /// May be `undefined` if `inline_case_capture` is not `.none`. + /// May be `none` if this is an inline capture or if `kind.item_refs.len == 1`. operand_val: Air.Inst.Ref, operand_ty: Type, capture_src: LazySrcLoc, @@ -12768,9 +12780,11 @@ fn analyzeSwitchPayloadCapture( sema: *Sema, case_block: *Block, operand: SwitchOperand, - /// May be `undefined` if this is an inline capture and operand is not a union. + /// Always has to be not-`none` if this is a union payload capture. + /// For non-union captures, this may be `none` if this is an inline capture + /// or if `kind.item_refs.len == 1` and capture is by val. operand_val: Air.Inst.Ref, - /// May be `undefined` if `capture_by_ref` is `false` or if `operand_val` is also `undefined`. + /// May be `none` if `capture_by_ref` is `false` or if `operand_val` is also `none`. operand_ptr: Air.Inst.Ref, operand_ty: Type, operand_src: LazySrcLoc, @@ -12816,8 +12830,6 @@ fn analyzeSwitchPayloadCapture( } } - const operand_ptr_ty = if (capture_by_ref) sema.typeOf(operand_ptr) else undefined; - if (kind == .special) { if (capture_by_ref) return operand_ptr; return switch (operand_ty.zigTypeTag(zcu)) { @@ -12894,7 +12906,7 @@ fn analyzeSwitchPayloadCapture( // By-reference captures have some further restrictions which make them easier to emit if (capture_by_ref) { - const operand_ptr_info = operand_ptr_ty.ptrInfo(zcu); + const operand_ptr_info = sema.typeOf(operand_ptr).ptrInfo(zcu); const capture_ptr_ty = resolve: { // By-ref captures of hetereogeneous types are only allowed if all field // pointer types are peer resolvable to each other. @@ -13136,7 +13148,7 @@ fn analyzeSwitchPayloadCapture( if (case_vals.len == 1) { const item_val = sema.resolveConstDefinedValue(case_block, .unneeded, case_vals[0], undefined) catch unreachable; const item_ty = try pt.singleErrorSetType(item_val.getErrorName(zcu).unwrap().?); - return sema.bitCast(case_block, item_ty, operand_val, operand_src, null); + return sema.bitCast(case_block, item_ty, .fromValue(item_val), operand_src, null); } var names: InferredErrorSet.NameMap = .{}; -- 2.54.0 From cb7be96644819e2e903c191e712d33b22f30a52a Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 20 Jan 2026 17:05:14 -0800 Subject: [PATCH 091/499] std.Io: give File a nonblocking bit on Windows This tracks whether it is a file opened in synchronous mode, or something that supports APC. This will be needed in order to know whether concurrent batch operations on the file should return error.ConcurrencyUnavailable, or use APC to complete the batch. This patch also switches to using NtCreateFile directly in std.Io.Threaded for dirCreateFile, as well as NtReadFile for fileReadStreaming, making it handle files opened in synchronous mode as well as files opened in asynchronous mode. --- lib/std/Io/File.zig | 15 +++++ lib/std/Io/Threaded.zig | 123 +++++++++++++++++++++++------------ lib/std/Progress.zig | 1 + lib/std/os/windows/ntdll.zig | 2 +- 4 files changed, 100 insertions(+), 41 deletions(-) diff --git a/lib/std/Io/File.zig b/lib/std/Io/File.zig index e537755a3365de8bab78d79fb55a40da0c33fe03..d0f487b911a4f37474d0dfc18925f6021ceda6c3 100644 --- a/lib/std/Io/File.zig +++ b/lib/std/Io/File.zig @@ -10,8 +10,20 @@ const assert = std.debug.assert; const Dir = std.Io.Dir; handle: Handle, +flags: Flags = .{}, pub const Handle = std.posix.fd_t; +pub const Flags = switch (native_os) { + .windows => packed struct(u1) { + /// * true: opened with MODE.IO.ASYNCHRONOUS + /// * false: opened with SYNCHRONOUS_ALERT or SYNCHRONOUS_NONALERT, or + /// not a file. + /// This is default-initialized to false as a workaround for + /// https://codeberg.org/ziglang/zig/issues/30842 + nonblocking: bool = false, + }, + else => packed struct(u0) {}, +}; pub const Reader = @import("File/Reader.zig"); pub const Writer = @import("File/Writer.zig"); @@ -77,6 +89,7 @@ pub fn stdout() File { return switch (native_os) { .windows => .{ .handle = std.os.windows.peb().ProcessParameters.hStdOutput, + .flags = .{ .nonblocking = false }, }, else => .{ .handle = std.posix.STDOUT_FILENO, @@ -88,6 +101,7 @@ pub fn stderr() File { return switch (native_os) { .windows => .{ .handle = std.os.windows.peb().ProcessParameters.hStdError, + .flags = .{ .nonblocking = false }, }, else => .{ .handle = std.posix.STDERR_FILENO, @@ -99,6 +113,7 @@ pub fn stdin() File { return switch (native_os) { .windows => .{ .handle = std.os.windows.peb().ProcessParameters.hStdInput, + .flags = .{ .nonblocking = false }, }, else => .{ .handle = std.posix.STDIN_FILENO, diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index f0562504fd5e4b833ed19ddea38638b0824bd5f1..cdbb0fd18291dd7ea75fbd229969573cd161eed0 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -1173,6 +1173,11 @@ const Syscall = struct { .blocked_canceling => return error.Canceled, // new status is `.canceled` } } + fn toApc(s: Syscall) Io.Cancelable!void { + // TODO set state to indicate instead of NtCancelSynchronousIoFile we + // need to use NtCancelIoFileEx + return s.checkCancel(); + } /// Marks this syscall as finished. fn finish(s: Syscall) void { const thread = s.thread orelse return; @@ -2759,7 +2764,12 @@ fn dirCreateDirPathOpenWasi( fn dirStat(userdata: ?*anyopaque, dir: Dir) Dir.StatError!Dir.Stat { const t: *Threaded = @ptrCast(@alignCast(userdata)); - const file: File = .{ .handle = dir.handle }; + const file: File = if (is_windows) .{ + .handle = dir.handle, + .flags = .{ .nonblocking = false }, + } else .{ + .handle = dir.handle, + }; return fileStat(t, file); } @@ -3682,7 +3692,10 @@ fn dirCreateFileWindows( errdefer windows.CloseHandle(handle); const exclusive = switch (flags.lock) { - .none => return .{ .handle = handle }, + .none => return .{ + .handle = handle, + .flags = .{ .nonblocking = false }, + }, .shared => false, .exclusive => true, }; @@ -3702,7 +3715,10 @@ fn dirCreateFileWindows( )) { .SUCCESS => { syscall.finish(); - return .{ .handle = handle }; + return .{ + .handle = handle, + .flags = .{ .nonblocking = false }, + }; }, .INSUFFICIENT_RESOURCES => return syscall.fail(error.SystemResources), .LOCK_NOT_GRANTED => return syscall.fail(error.WouldBlock), @@ -4273,7 +4289,10 @@ pub fn dirOpenFileWtf16( errdefer w.CloseHandle(handle); const exclusive = switch (flags.lock) { - .none => return .{ .handle = handle }, + .none => return .{ + .handle = handle, + .flags = .{ .nonblocking = false }, + }, .shared => false, .exclusive => true, }; @@ -4296,7 +4315,10 @@ pub fn dirOpenFileWtf16( .ACCESS_VIOLATION => |err| return syscall.ntstatusBug(err), // bad io_status_block pointer else => |status| return syscall.unexpectedNtstatus(status), }; - return .{ .handle = handle }; + return .{ + .handle = handle, + .flags = .{ .nonblocking = false }, + }; } fn dirOpenFileWasi( @@ -8365,46 +8387,66 @@ fn fileReadStreamingPosix(file: File, data: []const []u8) File.Reader.Error!usiz } fn fileReadStreamingWindows(file: File, data: []const []u8) File.Reader.Error!usize { - const DWORD = windows.DWORD; var index: usize = 0; while (index < data.len and data[index].len == 0) index += 1; if (index == data.len) return 0; const buffer = data[index]; - const want_read_count: DWORD = @min(std.math.maxInt(DWORD), buffer.len); - const syscall: Syscall = try .start(); - while (true) { - var n: DWORD = undefined; - if (windows.kernel32.ReadFile(file.handle, buffer.ptr, want_read_count, &n, null) != 0) { - syscall.finish(); - return n; + var io_status_block: windows.IO_STATUS_BLOCK = undefined; + + read: { + const syscall: Syscall = try .start(); + while (true) { + switch (windows.ntdll.NtReadFile( + file.handle, + null, // event + noopApc, // apc callback + null, // apc context + &io_status_block, + buffer.ptr, + @min(std.math.maxInt(u32), buffer.len), + null, // byte offset + null, // key + )) { + .SUCCESS => break :read syscall.finish(), + .PENDING => break, + .CANCELLED => { + try syscall.checkCancel(); + continue; + }, + .INVALID_PARAMETER => |err| return syscall.ntstatusBug(err), // wrong value for flags.nonblocking + else => |status| return syscall.unexpectedNtstatus(status), + } } - switch (windows.GetLastError()) { - .IO_PENDING => |err| { - syscall.finish(); - return windows.errorBug(err); - }, - .OPERATION_ABORTED => { - try syscall.checkCancel(); - continue; - }, - .BROKEN_PIPE, .HANDLE_EOF => { - syscall.finish(); - return 0; - }, - .NETNAME_DELETED => if (is_debug) unreachable else return error.Unexpected, - .LOCK_VIOLATION => return syscall.fail(error.LockViolation), - .ACCESS_DENIED => return syscall.fail(error.AccessDenied), - .INVALID_HANDLE => if (is_debug) unreachable else return error.Unexpected, - // TODO: Determine if INVALID_FUNCTION is possible in more scenarios than just passing - // a handle to a directory. - .INVALID_FUNCTION => return syscall.fail(error.IsDir), - else => |err| { - syscall.finish(); - return windows.unexpectedError(err); - }, + try syscall.toApc(); + while (true) { + switch (windows.ntdll.NtDelayExecution(1, null)) { + .USER_APC => break syscall.finish(), + .SUCCESS, .CANCELLED => { + try syscall.checkCancel(); + continue; + }, + else => |status| return syscall.unexpectedNtstatus(status), + } } } + + switch (io_status_block.u.Status) { + .SUCCESS, .END_OF_FILE, .PIPE_BROKEN => {}, + .ACCESS_DENIED => return error.AccessDenied, + else => |status| return windows.unexpectedStatus(status), + } + return io_status_block.Information; +} + +fn noopApc( + apc_context: ?*anyopaque, + io_status_block: *windows.IO_STATUS_BLOCK, + unused: windows.ULONG, +) callconv(.winapi) void { + _ = apc_context; + _ = io_status_block; + _ = unused; } fn fileReadPositionalPosix(file: File, data: []const []u8, offset: u64) File.ReadPositionalError!usize { @@ -14560,9 +14602,9 @@ fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) pro return .{ .id = piProcInfo.hProcess, .thread_handle = piProcInfo.hThread, - .stdin = if (g_hChildStd_IN_Wr) |h| .{ .handle = h } else null, - .stdout = if (g_hChildStd_OUT_Rd) |h| .{ .handle = h } else null, - .stderr = if (g_hChildStd_ERR_Rd) |h| .{ .handle = h } else null, + .stdin = if (g_hChildStd_IN_Wr) |h| .{ .handle = h, .flags = .{ .nonblocking = true } } else null, + .stdout = if (g_hChildStd_OUT_Rd) |h| .{ .handle = h, .flags = .{ .nonblocking = true } } else null, + .stderr = if (g_hChildStd_ERR_Rd) |h| .{ .handle = h, .flags = .{ .nonblocking = true } } else null, .request_resource_usage_statistics = options.request_resource_usage_statistics, }; } @@ -15696,6 +15738,7 @@ fn progressParentFile(userdata: ?*anyopaque) std.Progress.ParentFileError!File { .pointer => @ptrFromInt(int), else => return error.UnsupportedOperation, }, + .flags = if (is_windows) .{ .nonblocking = true } else .{}, }; } diff --git a/lib/std/Progress.zig b/lib/std/Progress.zig index 5ccc46778b43d0260ee1a6323e13eae20e31a2c9..5da61110796cd72016782b78ae7b83561ec0083d 100644 --- a/lib/std/Progress.zig +++ b/lib/std/Progress.zig @@ -979,6 +979,7 @@ fn serializeIpc(start_serialized_len: usize, serialized_buffer: *Serialized.Buff if (main_parent == .unused) continue; const file: Io.File = .{ .handle = main_storage.getIpcFd() orelse continue, + .flags = if (is_windows) .{ .nonblocking = true } else .{}, }; const opt_saved_metadata = findOld(file.handle, old_ipc_metadata_fds, old_ipc_metadata); var bytes_read: usize = 0; diff --git a/lib/std/os/windows/ntdll.zig b/lib/std/os/windows/ntdll.zig index f61cbbf5b8fe8bc468ad2f93638ca60503c54ed2..774ca28f19064bc66ad96840aa3114e9fb039cee 100644 --- a/lib/std/os/windows/ntdll.zig +++ b/lib/std/os/windows/ntdll.zig @@ -596,7 +596,7 @@ pub extern "ntdll" fn NtCancelSynchronousIoFile( pub extern "ntdll" fn NtDelayExecution( Alertable: BOOLEAN, - DelayInterval: *const LARGE_INTEGER, + DelayInterval: ?*const LARGE_INTEGER, ) callconv(.winapi) NTSTATUS; pub extern "ntdll" fn NtCancelIoFileEx( -- 2.54.0 From 8827488fcd556e6e95d97e838f781e6141ebde8c Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 21 Jan 2026 19:08:52 -0800 Subject: [PATCH 092/499] std: back out the flags field of Io.File For now, let us refrain from putting the sync mode into the Io.File struct, and document that to do concurrent batch operations, any Windows file handles must be in asynchronous mode. The consequences for violating this requirement is neither illegal behavior, nor an error, but that concurrency is lost. In other words, deadlock might occur. This prevents the addition of flags field. partial revert of 2faf14200f58ee72ec3a13e894d765f59e6483a9 --- lib/std/Io/File.zig | 15 --------------- lib/std/Io/Threaded.zig | 36 +++++++++--------------------------- lib/std/Progress.zig | 1 - 3 files changed, 9 insertions(+), 43 deletions(-) diff --git a/lib/std/Io/File.zig b/lib/std/Io/File.zig index d0f487b911a4f37474d0dfc18925f6021ceda6c3..e537755a3365de8bab78d79fb55a40da0c33fe03 100644 --- a/lib/std/Io/File.zig +++ b/lib/std/Io/File.zig @@ -10,20 +10,8 @@ const assert = std.debug.assert; const Dir = std.Io.Dir; handle: Handle, -flags: Flags = .{}, pub const Handle = std.posix.fd_t; -pub const Flags = switch (native_os) { - .windows => packed struct(u1) { - /// * true: opened with MODE.IO.ASYNCHRONOUS - /// * false: opened with SYNCHRONOUS_ALERT or SYNCHRONOUS_NONALERT, or - /// not a file. - /// This is default-initialized to false as a workaround for - /// https://codeberg.org/ziglang/zig/issues/30842 - nonblocking: bool = false, - }, - else => packed struct(u0) {}, -}; pub const Reader = @import("File/Reader.zig"); pub const Writer = @import("File/Writer.zig"); @@ -89,7 +77,6 @@ pub fn stdout() File { return switch (native_os) { .windows => .{ .handle = std.os.windows.peb().ProcessParameters.hStdOutput, - .flags = .{ .nonblocking = false }, }, else => .{ .handle = std.posix.STDOUT_FILENO, @@ -101,7 +88,6 @@ pub fn stderr() File { return switch (native_os) { .windows => .{ .handle = std.os.windows.peb().ProcessParameters.hStdError, - .flags = .{ .nonblocking = false }, }, else => .{ .handle = std.posix.STDERR_FILENO, @@ -113,7 +99,6 @@ pub fn stdin() File { return switch (native_os) { .windows => .{ .handle = std.os.windows.peb().ProcessParameters.hStdInput, - .flags = .{ .nonblocking = false }, }, else => .{ .handle = std.posix.STDIN_FILENO, diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index cdbb0fd18291dd7ea75fbd229969573cd161eed0..1a2ca61c00eea211c1f4998325d7f1c6d3b7cd2b 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -2764,12 +2764,7 @@ fn dirCreateDirPathOpenWasi( fn dirStat(userdata: ?*anyopaque, dir: Dir) Dir.StatError!Dir.Stat { const t: *Threaded = @ptrCast(@alignCast(userdata)); - const file: File = if (is_windows) .{ - .handle = dir.handle, - .flags = .{ .nonblocking = false }, - } else .{ - .handle = dir.handle, - }; + const file: File = .{ .handle = dir.handle }; return fileStat(t, file); } @@ -3692,10 +3687,7 @@ fn dirCreateFileWindows( errdefer windows.CloseHandle(handle); const exclusive = switch (flags.lock) { - .none => return .{ - .handle = handle, - .flags = .{ .nonblocking = false }, - }, + .none => return .{ .handle = handle }, .shared => false, .exclusive => true, }; @@ -3715,10 +3707,7 @@ fn dirCreateFileWindows( )) { .SUCCESS => { syscall.finish(); - return .{ - .handle = handle, - .flags = .{ .nonblocking = false }, - }; + return .{ .handle = handle }; }, .INSUFFICIENT_RESOURCES => return syscall.fail(error.SystemResources), .LOCK_NOT_GRANTED => return syscall.fail(error.WouldBlock), @@ -4289,10 +4278,7 @@ pub fn dirOpenFileWtf16( errdefer w.CloseHandle(handle); const exclusive = switch (flags.lock) { - .none => return .{ - .handle = handle, - .flags = .{ .nonblocking = false }, - }, + .none => return .{ .handle = handle }, .shared => false, .exclusive => true, }; @@ -4315,10 +4301,7 @@ pub fn dirOpenFileWtf16( .ACCESS_VIOLATION => |err| return syscall.ntstatusBug(err), // bad io_status_block pointer else => |status| return syscall.unexpectedNtstatus(status), }; - return .{ - .handle = handle, - .flags = .{ .nonblocking = false }, - }; + return .{ .handle = handle }; } fn dirOpenFileWasi( @@ -8414,7 +8397,7 @@ fn fileReadStreamingWindows(file: File, data: []const []u8) File.Reader.Error!us try syscall.checkCancel(); continue; }, - .INVALID_PARAMETER => |err| return syscall.ntstatusBug(err), // wrong value for flags.nonblocking + .INVALID_PARAMETER => |err| return syscall.ntstatusBug(err), // streaming read of async mode file else => |status| return syscall.unexpectedNtstatus(status), } } @@ -14602,9 +14585,9 @@ fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) pro return .{ .id = piProcInfo.hProcess, .thread_handle = piProcInfo.hThread, - .stdin = if (g_hChildStd_IN_Wr) |h| .{ .handle = h, .flags = .{ .nonblocking = true } } else null, - .stdout = if (g_hChildStd_OUT_Rd) |h| .{ .handle = h, .flags = .{ .nonblocking = true } } else null, - .stderr = if (g_hChildStd_ERR_Rd) |h| .{ .handle = h, .flags = .{ .nonblocking = true } } else null, + .stdin = if (g_hChildStd_IN_Wr) |h| .{ .handle = h } else null, + .stdout = if (g_hChildStd_OUT_Rd) |h| .{ .handle = h } else null, + .stderr = if (g_hChildStd_ERR_Rd) |h| .{ .handle = h } else null, .request_resource_usage_statistics = options.request_resource_usage_statistics, }; } @@ -15738,7 +15721,6 @@ fn progressParentFile(userdata: ?*anyopaque) std.Progress.ParentFileError!File { .pointer => @ptrFromInt(int), else => return error.UnsupportedOperation, }, - .flags = if (is_windows) .{ .nonblocking = true } else .{}, }; } diff --git a/lib/std/Progress.zig b/lib/std/Progress.zig index 5da61110796cd72016782b78ae7b83561ec0083d..5ccc46778b43d0260ee1a6323e13eae20e31a2c9 100644 --- a/lib/std/Progress.zig +++ b/lib/std/Progress.zig @@ -979,7 +979,6 @@ fn serializeIpc(start_serialized_len: usize, serialized_buffer: *Serialized.Buff if (main_parent == .unused) continue; const file: Io.File = .{ .handle = main_storage.getIpcFd() orelse continue, - .flags = if (is_windows) .{ .nonblocking = true } else .{}, }; const opt_saved_metadata = findOld(file.handle, old_ipc_metadata_fds, old_ipc_metadata); var bytes_read: usize = 0; -- 2.54.0 From 558025759632806c9d5b22b5a88d2cc4165c7fac Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 21 Jan 2026 19:16:35 -0800 Subject: [PATCH 093/499] std.Io.Threaded: add some temporary, choice panics --- lib/std/Io/Threaded.zig | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 1a2ca61c00eea211c1f4998325d7f1c6d3b7cd2b..7a04d23e5436289e2477a77d1b355db375a5325d 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -8398,7 +8398,8 @@ fn fileReadStreamingWindows(file: File, data: []const []u8) File.Reader.Error!us continue; }, .INVALID_PARAMETER => |err| return syscall.ntstatusBug(err), // streaming read of async mode file - else => |status| return syscall.unexpectedNtstatus(status), + else => |status| std.debug.panic("fileReadStreamingWindows NtReadFile returned {t}", .{status}), + //else => |status| return syscall.unexpectedNtstatus(status), } } try syscall.toApc(); @@ -8409,7 +8410,8 @@ fn fileReadStreamingWindows(file: File, data: []const []u8) File.Reader.Error!us try syscall.checkCancel(); continue; }, - else => |status| return syscall.unexpectedNtstatus(status), + else => |status| std.debug.panic("fileReadStreamingWindows NtDelayExecution returned {t}", .{status}), + //else => |status| return syscall.unexpectedNtstatus(status), } } } @@ -8417,7 +8419,8 @@ fn fileReadStreamingWindows(file: File, data: []const []u8) File.Reader.Error!us switch (io_status_block.u.Status) { .SUCCESS, .END_OF_FILE, .PIPE_BROKEN => {}, .ACCESS_DENIED => return error.AccessDenied, - else => |status| return windows.unexpectedStatus(status), + else => |status| std.debug.panic("fileReadStreamingWindows IO_STATUS_BLOCK returned {t}", .{status}), + //else => |status| return windows.unexpectedStatus(status), } return io_status_block.Information; } -- 2.54.0 From 1e3072ec4664200934c13adf8452de490130a00f Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 21 Jan 2026 21:05:15 -0800 Subject: [PATCH 094/499] std.Io.Threaded: introduce Thread.InterruptMethod implements APC cancelation except for the actual call to NtCancelIoFileEx --- lib/std/Io/Threaded.zig | 241 +++++++++++++++++++++++++--------------- 1 file changed, 149 insertions(+), 92 deletions(-) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 7a04d23e5436289e2477a77d1b355db375a5325d..a02ccb826811063d3ee34c2914644478422b3f69 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -339,8 +339,8 @@ const Group = struct { .canceled => true, .parked => unreachable, .blocked => unreachable, - .blocked_alertable => unreachable, - .blocked_alertable_canceling => unreachable, + .blocked_apc => unreachable, + .blocked_windows_dns => unreachable, .blocked_canceling => unreachable, }; if (result) { @@ -379,7 +379,10 @@ const Group = struct { while (it) |thread| : (it = thread.next) { // This non-mutating RMW exists for ordering reasons: see comment in `Group.Task.start` for reasons. _ = thread.status.fetchOr(.{ .cancelation = @enumFromInt(0), .awaitable = .null }, .release); - if (thread.cancelAwaitable(.fromGroup(g.ptr))) any_blocked = true; + if (thread.cancelAwaitable(.fromGroup(g.ptr))) |method| { + thread.interrupt_method = method; + any_blocked = true; + } } return any_blocked; } @@ -391,7 +394,7 @@ const Group = struct { var any_signaled = false; var it = t.worker_threads.load(.acquire); // acquire `Thread` values while (it) |thread| : (it = thread.next) { - if (thread.signalCanceledSyscall(t, .fromGroup(g.ptr))) any_signaled = true; + if (thread.signalCanceledSyscall(t, .fromGroup(g.ptr), thread.interrupt_method)) any_signaled = true; } return any_signaled; } @@ -543,8 +546,8 @@ const Future = struct { .canceled => true, .parked => unreachable, .blocked => unreachable, - .blocked_alertable => unreachable, - .blocked_alertable_canceling => unreachable, + .blocked_apc => unreachable, + .blocked_windows_dns => unreachable, .blocked_canceling => unreachable, }; thread.status.store(.{ .cancelation = .none, .awaitable = .null }, .monotonic); @@ -573,11 +576,15 @@ const Future = struct { num_completed: *std.atomic.Value(u32), thread: ?*Thread, ) void { - var need_signal: bool = if (thread) |th| th.cancelAwaitable(.fromFuture(future)) else false; + var interrupt_method: ?Thread.InterruptMethod = + if (thread) |th| th.cancelAwaitable(.fromFuture(future)) else null; var timeout_ns: u64 = 1 << 10; while (true) { - need_signal = need_signal and thread.?.signalCanceledSyscall(t, .fromFuture(future)); - Thread.futexWaitUncancelable(&num_completed.raw, 0, if (need_signal) timeout_ns else null); + if (interrupt_method) |method| { + if (!thread.?.signalCanceledSyscall(t, .fromFuture(future), method)) + interrupt_method = null; + } + Thread.futexWaitUncancelable(&num_completed.raw, 0, if (interrupt_method != null) timeout_ns else null); switch (num_completed.load(.acquire)) { // acquire task results 0 => {}, 1 => break, @@ -625,6 +632,9 @@ const Thread = struct { cancel_protection: Io.CancelProtection, /// Always released when `Status.cancelation` is set to `.parked`. futex_waiter: if (use_parking_futex) ?*parking_futex.Waiter else ?noreturn, + apc_context: if (is_windows) ?*anyopaque else void, + /// Used only by group cancelation code for temporary storage. + interrupt_method: InterruptMethod, csprng: Csprng, @@ -652,11 +662,13 @@ const Thread = struct { /// To request cancelation, set the status to `.blocked_canceling` and repeatedly interrupt the system call until the status changes. blocked = 0b011, - /// Windows-only: the thread is blocked in an alertable wait via - /// `NtDelayExecution`. To request cancelation, set the status to - /// `blocked_alertable_canceling` and repeatedly alert the thread - /// until the status changes. - blocked_alertable = 0b010, + /// Windows-only: the thread is blocked in a call to `NtDelayExecution`. + /// To request cancelation, set the status to `.canceling` and call `NtCancelIoFileEx`. + blocked_apc = 0b100, + + /// Windows-only: the thread is blocked in a call to `GetAddrInfoExW`. + /// To request cancelation, set the status to `.canceling` and call `GetAddrInfoExCancel`. + blocked_windows_dns = 0b010, /// The thread has an outstanding cancelation request but is not in a cancelable operation. /// When it acknowledges the cancelation, it will set the status to `.canceled`. @@ -705,8 +717,8 @@ const Thread = struct { switch (status.cancelation) { .parked => unreachable, .blocked => unreachable, - .blocked_alertable => unreachable, - .blocked_alertable_canceling => unreachable, + .blocked_apc => unreachable, + .blocked_windows_dns => unreachable, .blocked_canceling => unreachable, .none, .canceled => {}, .canceling => { @@ -986,17 +998,17 @@ const Thread = struct { /// It is possible that `thread` gets canceled by this function, but is blocked in a syscall. In /// that case, the thread may need to be sent a signal to interrupt the call. This function will /// return `true` to indicate this, in which case the caller must call `signalCanceledSyscall`. - fn cancelAwaitable(thread: *Thread, awaitable: AwaitableId) bool { + fn cancelAwaitable(thread: *Thread, awaitable: AwaitableId) ?InterruptMethod { var status = thread.status.load(.monotonic); while (true) { - if (status.awaitable != awaitable) return false; // thread is working on something else + if (status.awaitable != awaitable) return null; // thread is working on something else status = switch (status.cancelation) { .none => thread.status.cmpxchgWeak( .{ .cancelation = .none, .awaitable = awaitable }, .{ .cancelation = .canceling, .awaitable = awaitable }, .monotonic, .monotonic, - ) orelse return false, + ) orelse return null, .parked => thread.status.cmpxchgWeak( .{ .cancelation = .parked, .awaitable = awaitable }, @@ -1009,7 +1021,7 @@ const Thread = struct { parking_futex.removeCanceledWaiter(futex_waiter); } unpark(&.{thread.id}, null); - return false; + return null; }, .blocked => thread.status.cmpxchgWeak( @@ -1017,7 +1029,17 @@ const Thread = struct { .{ .cancelation = .blocked_canceling, .awaitable = awaitable }, .monotonic, .monotonic, - ) orelse return true, + ) orelse return .sync, + + .blocked_apc => thread.status.cmpxchgWeak( + .{ .cancelation = .blocked_apc, .awaitable = awaitable }, + .{ .cancelation = .canceling, .awaitable = awaitable }, + .monotonic, + .monotonic, + ) orelse { + if (!is_windows) unreachable; + return .apc; + }, .blocked_alertable => thread.status.cmpxchgWeak( .{ .cancelation = .blocked_alertable, .awaitable = awaitable }, @@ -1026,14 +1048,14 @@ const Thread = struct { .monotonic, ) orelse { if (!is_windows) unreachable; - return true; + return .dns; }, .canceling, .canceled => { // This can happen when the task start raced with the cancelation, so the thread // saw the cancelation on the future/group *and* we are trying to signal the // thread here. - return false; + return null; }, .blocked_canceling => unreachable, // `awaitable` has not been canceled before now @@ -1042,6 +1064,11 @@ const Thread = struct { } } + const InterruptMethod = switch (native_os) { + .windows => enum { sync, dns, apc }, + else => enum { sync }, + }; + /// Sends a signal to `thread` if it is still blocked in a syscall (i.e. has not yet observed /// the cancelation request from `cancelAwaitable`). /// @@ -1051,24 +1078,21 @@ const Thread = struct { /// the thread is still blocked. For the implementation, `Future.waitForCancelWithSignaling` and /// `Group.waitForCancelWithSignaling`: they use exponential backoff starting at a 1us delay and /// doubling each call. In practice, it is rare to send more than one signal. - fn signalCanceledSyscall(thread: *Thread, t: *Threaded, awaitable: AwaitableId) bool { - const status = thread.status.load(.monotonic); - if (status.awaitable != awaitable) { - // The thread has moved on and is working on something totally different. - return false; - } + fn signalCanceledSyscall(thread: *Thread, t: *Threaded, awaitable: AwaitableId, method: InterruptMethod) bool { + const bad_status: Status = .{ .cancelation = .blocked_canceling, .awaitable = awaitable }; + if (thread.status.load(.monotonic) != bad_status) return false; // The thread ID and/or handle can be read non-atomically because they never change and were // released by the store that made `thread` available to us. - switch (status.cancelation) { - .blocked_canceling => if (std.Thread.use_pthreads) { - return switch (std.c.pthread_kill(thread.handle, .IO)) { - 0 => true, - else => false, - }; - } else switch (native_os) { - .linux => { + if (std.Thread.use_pthreads) switch (method) { + .sync => return switch (std.c.pthread_kill(thread.handle, .IO)) { + 0 => true, + else => false, + }, + } else switch (native_os) { + .linux => switch (method) { + .sync => { const pid: posix.pid_t = pid: { const cached_pid = @atomicLoad(Pid, &t.pid, .monotonic); if (cached_pid != .unknown) break :pid @intFromEnum(cached_pid); @@ -1081,7 +1105,9 @@ const Thread = struct { else => false, }; }, - .windows => { + }, + .windows => switch (method) { + .sync => { var iosb: windows.IO_STATUS_BLOCK = undefined; return switch (windows.ntdll.NtCancelSynchronousIoFile(thread.handle, null, &iosb)) { .NOT_FOUND => true, // this might mean the operation hasn't started yet @@ -1089,15 +1115,8 @@ const Thread = struct { else => false, }; }, - else => return false, - }, - - .blocked_alertable_canceling => { - if (!is_windows) unreachable; - return switch (windows.ntdll.NtAlertThread(thread.handle)) { - .SUCCESS => true, - else => false, - }; + .dns => @panic("TODO call GetAddrInfoExCancel"), + .apc => @panic("TODO call NtCancelIoFileEx"), }, else => { @@ -1145,8 +1164,8 @@ const Syscall = struct { }, .monotonic).cancelation) { .parked => unreachable, .blocked => unreachable, - .blocked_alertable => unreachable, - .blocked_alertable_canceling => unreachable, + .blocked_apc => unreachable, + .blocked_windows_dns => unreachable, .blocked_canceling => unreachable, .none => return .{ .thread = thread }, // new status is `.blocked` .canceling => return error.Canceled, // new status is `.canceled` @@ -1165,19 +1184,14 @@ const Syscall = struct { }, .monotonic).cancelation) { .none => unreachable, .parked => unreachable, - .blocked_alertable => unreachable, - .blocked_alertable_canceling => unreachable, + .blocked_apc => unreachable, + .blocked_windows_dns => unreachable, .canceling => unreachable, .canceled => unreachable, .blocked => {}, // new status is `.blocked` (unchanged) .blocked_canceling => return error.Canceled, // new status is `.canceled` } } - fn toApc(s: Syscall) Io.Cancelable!void { - // TODO set state to indicate instead of NtCancelSynchronousIoFile we - // need to use NtCancelIoFileEx - return s.checkCancel(); - } /// Marks this syscall as finished. fn finish(s: Syscall) void { const thread = s.thread orelse return; @@ -1187,8 +1201,8 @@ const Syscall = struct { }, .monotonic).cancelation) { .none => unreachable, .parked => unreachable, - .blocked_alertable => unreachable, - .blocked_alertable_canceling => unreachable, + .blocked_apc => unreachable, + .blocked_windows_dns => unreachable, .canceling => unreachable, .canceled => unreachable, .blocked => {}, // new status is `.none` @@ -1196,25 +1210,25 @@ const Syscall = struct { } } /// Indicates instead of `NtCancelSynchronousIoFile` we need to use - /// `NtAlertThread` to interrupt the wait. + /// `NtCancelIoFileEx` to interrupt the wait. /// /// Windows only, called from blocked state only. - fn toAlertable(s: Syscall) Io.Cancelable!AlertableSyscall { - comptime assert(is_windows); - const thread = s.thread orelse return .{ .thread = null }; + fn toApc(s: Syscall, apc_context: ?*anyopaque) Io.Cancelable!void { + const thread = s.thread orelse return; + thread.apc_context = apc_context; var prev = thread.status.load(.monotonic); while (true) prev = switch (prev.cancelation) { .none => unreachable, .parked => unreachable, - .blocked_alertable => unreachable, - .blocked_alertable_canceling => unreachable, + .blocked_apc => unreachable, + .blocked_windows_dns => unreachable, .canceling => unreachable, .canceled => unreachable, .blocked => thread.status.cmpxchgWeak(prev, .{ - .cancelation = .blocked_alertable, + .cancelation = .blocked_apc, .awaitable = prev.awaitable, - }, .monotonic, .monotonic) orelse return .{ .thread = thread }, + }, .monotonic, .monotonic) orelse return, .blocked_canceling => thread.status.cmpxchgWeak(prev, .{ .cancelation = .canceled, @@ -1222,6 +1236,45 @@ const Syscall = struct { }, .monotonic, .monotonic) orelse return error.Canceled, }; } + /// Windows only, called from blocked_apc state only. + fn checkCancelApc(s: Syscall) Io.Cancelable!void { + const thread = s.thread orelse return; + var prev = thread.status.load(.monotonic); + while (true) prev = switch (prev.cancelation) { + .none => unreachable, + .parked => unreachable, + .blocked_windows_dns => unreachable, + .blocked => unreachable, + .canceling => unreachable, + .canceled => unreachable, + .blocked_apc => return, + .blocked_canceling => thread.status.cmpxchgWeak(prev, .{ + .cancelation = .canceled, + .awaitable = prev.awaitable, + }, .monotonic, .monotonic) orelse return error.Canceled, + }; + } + /// Windows only, called from blocked_apc state only. + fn finishApc(s: Syscall) void { + const thread = s.thread orelse return; + var prev = thread.status.load(.monotonic); + while (true) prev = switch (prev.cancelation) { + .none => unreachable, + .parked => unreachable, + .blocked_windows_dns => unreachable, + .blocked => unreachable, + .canceling => unreachable, + .canceled => unreachable, + .blocked_apc => thread.status.cmpxchgWeak(prev, .{ + .cancelation = .none, + .awaitable = prev.awaitable, + }, .monotonic, .monotonic) orelse return, + .blocked_canceling => thread.status.cmpxchgWeak(prev, .{ + .cancelation = .canceling, + .awaitable = prev.awaitable, + }, .monotonic, .monotonic) orelse return, + }; + } /// Convenience wrapper which calls `finish`, then returns `err`. fn fail(s: Syscall, err: anytype) @TypeOf(err) { s.finish(); @@ -1501,6 +1554,8 @@ fn worker(t: *Threaded) void { .cancel_protection = .unblocked, .futex_waiter = undefined, .csprng = .{}, + .apc_context = undefined, + .interrupt_method = undefined, }; Thread.current = &thread; @@ -2109,8 +2164,8 @@ fn groupAsyncEager( .canceled => true, .parked => unreachable, .blocked => unreachable, - .blocked_alertable => unreachable, - .blocked_alertable_canceling => unreachable, + .blocked_apc => unreachable, + .blocked_windows_dns => unreachable, .blocked_canceling => unreachable, }; } else false; @@ -2121,8 +2176,8 @@ fn groupAsyncEager( .canceled => true, .parked => unreachable, .blocked => unreachable, - .blocked_alertable => unreachable, - .blocked_alertable_canceling => unreachable, + .blocked_apc => unreachable, + .blocked_windows_dns => unreachable, .blocked_canceling => unreachable, }; } else false; @@ -2301,8 +2356,8 @@ fn recancelInner() void { .canceling => unreachable, // called `recancel` but cancelation was already pending .parked => unreachable, .blocked => unreachable, - .blocked_alertable => unreachable, - .blocked_alertable_canceling => unreachable, + .blocked_apc => unreachable, + .blocked_windows_dns => unreachable, .blocked_canceling => unreachable, } } @@ -8376,6 +8431,7 @@ fn fileReadStreamingWindows(file: File, data: []const []u8) File.Reader.Error!us const buffer = data[index]; var io_status_block: windows.IO_STATUS_BLOCK = undefined; + var done: bool = false; read: { const syscall: Syscall = try .start(); @@ -8383,8 +8439,8 @@ fn fileReadStreamingWindows(file: File, data: []const []u8) File.Reader.Error!us switch (windows.ntdll.NtReadFile( file.handle, null, // event - noopApc, // apc callback - null, // apc context + flagApc, // apc callback + &done, // apc context &io_status_block, buffer.ptr, @min(std.math.maxInt(u32), buffer.len), @@ -8402,12 +8458,12 @@ fn fileReadStreamingWindows(file: File, data: []const []u8) File.Reader.Error!us //else => |status| return syscall.unexpectedNtstatus(status), } } - try syscall.toApc(); + try syscall.toApc(&done); while (true) { switch (windows.ntdll.NtDelayExecution(1, null)) { - .USER_APC => break syscall.finish(), + .USER_APC => break syscall.finishApc(), .SUCCESS, .CANCELLED => { - try syscall.checkCancel(); + try syscall.checkCancelApc(); continue; }, else => |status| std.debug.panic("fileReadStreamingWindows NtDelayExecution returned {t}", .{status}), @@ -8425,12 +8481,13 @@ fn fileReadStreamingWindows(file: File, data: []const []u8) File.Reader.Error!us return io_status_block.Information; } -fn noopApc( +fn flagApc( apc_context: ?*anyopaque, io_status_block: *windows.IO_STATUS_BLOCK, unused: windows.ULONG, ) callconv(.winapi) void { - _ = apc_context; + const flag: *bool = @ptrCast(apc_context); + flag.* = true; _ = io_status_block; _ = unused; } @@ -12442,7 +12499,7 @@ fn netLookupFallible( var res: *ws2_32.ADDRINFOEXW = undefined; const timeout: ?*ws2_32.timeval = null; while (true) { - // TODO: hook this up to cancelation with `NtDelayExecution` and APC callbacks. + // TODO: hook this up to cancelation with `Thread.Status.cancelation.blocked_windows_dns`. try Thread.checkCancel(); // TODO make this append to the queue eagerly rather than blocking until the whole thing finishes const rc: ws2_32.WinsockError = @enumFromInt(ws2_32.GetAddrInfoExW(name_w, port_w, .DNS, null, &hints, &res, timeout, null, null, null)); @@ -16176,8 +16233,8 @@ const parking_futex = struct { .canceled => break :cancelable, // status is still `.canceled` .parked => unreachable, .blocked => unreachable, - .blocked_alertable => unreachable, - .blocked_alertable_canceling => unreachable, + .blocked_apc => unreachable, + .blocked_windows_dns => unreachable, .blocked_canceling => unreachable, } // We could now be unparked for a cancelation at any time! @@ -16228,8 +16285,8 @@ const parking_futex = struct { }, .canceled => unreachable, .blocked => unreachable, - .blocked_alertable => unreachable, - .blocked_alertable_canceling => unreachable, + .blocked_apc => unreachable, + .blocked_windows_dns => unreachable, .blocked_canceling => unreachable, }, } @@ -16270,8 +16327,8 @@ const parking_futex = struct { .canceling => continue, // race with a canceler who hasn't called `removeCanceledWaiter` yet .canceled => unreachable, .blocked => unreachable, - .blocked_alertable => unreachable, - .blocked_alertable_canceling => unreachable, + .blocked_apc => unreachable, + .blocked_windows_dns => unreachable, .blocked_canceling => unreachable, } // We're waking this waiter. Remove them from the bucket and add them to our local list. @@ -16337,8 +16394,8 @@ const parking_sleep = struct { .canceled => break :cancelable, // status is still `.canceled` .parked => unreachable, .blocked => unreachable, - .blocked_alertable => unreachable, - .blocked_alertable_canceling => unreachable, + .blocked_apc => unreachable, + .blocked_windows_dns => unreachable, .blocked_canceling => unreachable, } while (park(deadline, null)) { @@ -16356,8 +16413,8 @@ const parking_sleep = struct { .none => unreachable, .canceled => unreachable, .blocked => unreachable, - .blocked_alertable => unreachable, - .blocked_alertable_canceling => unreachable, + .blocked_apc => unreachable, + .blocked_windows_dns => unreachable, .blocked_canceling => unreachable, } } else |err| switch (err) { @@ -16376,8 +16433,8 @@ const parking_sleep = struct { .none => unreachable, .canceled => unreachable, .blocked => unreachable, - .blocked_alertable => unreachable, - .blocked_alertable_canceling => unreachable, + .blocked_apc => unreachable, + .blocked_windows_dns => unreachable, .blocked_canceling => unreachable, }, } -- 2.54.0 From e705ad83028b339314fa668ec04d57987ca73e87 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 21 Jan 2026 21:28:15 -0800 Subject: [PATCH 095/499] std.Io.Threaded: implement APC cancelation specifically the call to NtCancelIoFileEx --- lib/std/Io/Threaded.zig | 33 ++++++++++++++++++++++++++------- lib/std/os/windows/ntdll.zig | 6 +++--- 2 files changed, 29 insertions(+), 10 deletions(-) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index a02ccb826811063d3ee34c2914644478422b3f69..08d67a69975d0e63c3ff0ff9571fcc4d2d86d3ce 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -632,12 +632,17 @@ const Thread = struct { cancel_protection: Io.CancelProtection, /// Always released when `Status.cancelation` is set to `.parked`. futex_waiter: if (use_parking_futex) ?*parking_futex.Waiter else ?noreturn, - apc_context: if (is_windows) ?*anyopaque else void, + apc: Apc, /// Used only by group cancelation code for temporary storage. interrupt_method: InterruptMethod, csprng: Csprng, + const Apc = if (is_windows) struct { + handle: windows.HANDLE, + iosb: ?*windows.IO_STATUS_BLOCK, + } else void; + const Handle = Handle: { if (std.Thread.use_pthreads) break :Handle std.c.pthread_t; if (is_windows) break :Handle windows.HANDLE; @@ -1116,7 +1121,14 @@ const Thread = struct { }; }, .dns => @panic("TODO call GetAddrInfoExCancel"), - .apc => @panic("TODO call NtCancelIoFileEx"), + .apc => { + var iosb: windows.IO_STATUS_BLOCK = undefined; + return switch (windows.ntdll.NtCancelIoFileEx(thread.apc.handle, thread.apc.iosb, &iosb)) { + .NOT_FOUND => true, // this might mean the operation hasn't started yet + .SUCCESS => false, // the OS confirmed that our cancelation worked + else => false, + }; + }, }, else => { @@ -1213,9 +1225,9 @@ const Syscall = struct { /// `NtCancelIoFileEx` to interrupt the wait. /// /// Windows only, called from blocked state only. - fn toApc(s: Syscall, apc_context: ?*anyopaque) Io.Cancelable!void { + fn toApc(s: Syscall, apc: Thread.Apc) Io.Cancelable!void { const thread = s.thread orelse return; - thread.apc_context = apc_context; + thread.apc = apc; var prev = thread.status.load(.monotonic); while (true) prev = switch (prev.cancelation) { .none => unreachable, @@ -1554,7 +1566,7 @@ fn worker(t: *Threaded) void { .cancel_protection = .unblocked, .futex_waiter = undefined, .csprng = .{}, - .apc_context = undefined, + .apc = undefined, .interrupt_method = undefined, }; Thread.current = &thread; @@ -8458,10 +8470,17 @@ fn fileReadStreamingWindows(file: File, data: []const []u8) File.Reader.Error!us //else => |status| return syscall.unexpectedNtstatus(status), } } - try syscall.toApc(&done); + try syscall.toApc(.{ .handle = file.handle, .iosb = &io_status_block }); while (true) { switch (windows.ntdll.NtDelayExecution(1, null)) { - .USER_APC => break syscall.finishApc(), + .USER_APC => { + if (!done) { + // Other APC work was queued before calling into this function. + try syscall.checkCancelApc(); + continue; + } + break syscall.finishApc(); + }, .SUCCESS, .CANCELLED => { try syscall.checkCancelApc(); continue; diff --git a/lib/std/os/windows/ntdll.zig b/lib/std/os/windows/ntdll.zig index 774ca28f19064bc66ad96840aa3114e9fb039cee..a6d5b21322e29333aeaef3d64c7b20c3d74a9fc2 100644 --- a/lib/std/os/windows/ntdll.zig +++ b/lib/std/os/windows/ntdll.zig @@ -601,11 +601,11 @@ pub extern "ntdll" fn NtDelayExecution( pub extern "ntdll" fn NtCancelIoFileEx( FileHandle: HANDLE, - IoRequestToCancel: *const IO_STATUS_BLOCK, + IoRequestToCancel: ?*IO_STATUS_BLOCK, IoStatusBlock: *IO_STATUS_BLOCK, ) callconv(.winapi) NTSTATUS; pub extern "ntdll" fn NtCancelIoFile( - handle: HANDLE, - iosbToCancel: *const IO_STATUS_BLOCK, + FileHandle: HANDLE, + IoRequestToCancel: ?*IO_STATUS_BLOCK, ) callconv(.winapi) NTSTATUS; -- 2.54.0 From a933d7a6f88332a1ab7e1e6079e72a91320d0d4c Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 21 Jan 2026 21:42:23 -0800 Subject: [PATCH 096/499] std.Io.Threaded: don't pass null to NtDelayExecution Windows returns ACCESS_VIOLATION if you do that. --- lib/std/Io/Threaded.zig | 5 +++-- lib/std/os/windows/ntdll.zig | 2 +- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 08d67a69975d0e63c3ff0ff9571fcc4d2d86d3ce..a9a8586da682b3423b177509145a83a9e11405ee 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -8444,6 +8444,7 @@ fn fileReadStreamingWindows(file: File, data: []const []u8) File.Reader.Error!us var io_status_block: windows.IO_STATUS_BLOCK = undefined; var done: bool = false; + const infinite: windows.LARGE_INTEGER = windows.INFINITE; read: { const syscall: Syscall = try .start(); @@ -8472,7 +8473,7 @@ fn fileReadStreamingWindows(file: File, data: []const []u8) File.Reader.Error!us } try syscall.toApc(.{ .handle = file.handle, .iosb = &io_status_block }); while (true) { - switch (windows.ntdll.NtDelayExecution(1, null)) { + switch (windows.ntdll.NtDelayExecution(1, &infinite)) { .USER_APC => { if (!done) { // Other APC work was queued before calling into this function. @@ -8481,7 +8482,7 @@ fn fileReadStreamingWindows(file: File, data: []const []u8) File.Reader.Error!us } break syscall.finishApc(); }, - .SUCCESS, .CANCELLED => { + .SUCCESS, .CANCELLED, .TIMEOUT, .ALERTED => { try syscall.checkCancelApc(); continue; }, diff --git a/lib/std/os/windows/ntdll.zig b/lib/std/os/windows/ntdll.zig index a6d5b21322e29333aeaef3d64c7b20c3d74a9fc2..04b0a288debe648de202ed988570f1bb97bbaa6f 100644 --- a/lib/std/os/windows/ntdll.zig +++ b/lib/std/os/windows/ntdll.zig @@ -596,7 +596,7 @@ pub extern "ntdll" fn NtCancelSynchronousIoFile( pub extern "ntdll" fn NtDelayExecution( Alertable: BOOLEAN, - DelayInterval: ?*const LARGE_INTEGER, + DelayInterval: *const LARGE_INTEGER, ) callconv(.winapi) NTSTATUS; pub extern "ntdll" fn NtCancelIoFileEx( -- 2.54.0 From 6d9e6e2c385650521289088968e16e0b5a1eed60 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 22 Jan 2026 14:32:39 -0800 Subject: [PATCH 097/499] std.Io.Threaded: avoid extra fields of Thread As mlugg pointed out those race when a thread finishes an operation just after it is canceled and then that thread to picks up another task, resulting in these fields being potentially overwritten. This updates fileReadStreaming on Windows to handle being alerted, and then manage its own cancelation of the file I/O. --- lib/std/Io/Threaded.zig | 275 +++++++++++++---------------------- lib/std/os/windows/ntdll.zig | 2 +- 2 files changed, 103 insertions(+), 174 deletions(-) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index a9a8586da682b3423b177509145a83a9e11405ee..3d7520dbe375716629ad6447ba92c2959230289c 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -339,8 +339,8 @@ const Group = struct { .canceled => true, .parked => unreachable, .blocked => unreachable, - .blocked_apc => unreachable, - .blocked_windows_dns => unreachable, + .blocked_alertable => unreachable, + .blocked_alertable_canceling => unreachable, .blocked_canceling => unreachable, }; if (result) { @@ -379,10 +379,7 @@ const Group = struct { while (it) |thread| : (it = thread.next) { // This non-mutating RMW exists for ordering reasons: see comment in `Group.Task.start` for reasons. _ = thread.status.fetchOr(.{ .cancelation = @enumFromInt(0), .awaitable = .null }, .release); - if (thread.cancelAwaitable(.fromGroup(g.ptr))) |method| { - thread.interrupt_method = method; - any_blocked = true; - } + if (thread.cancelAwaitable(.fromGroup(g.ptr))) any_blocked = true; } return any_blocked; } @@ -394,7 +391,7 @@ const Group = struct { var any_signaled = false; var it = t.worker_threads.load(.acquire); // acquire `Thread` values while (it) |thread| : (it = thread.next) { - if (thread.signalCanceledSyscall(t, .fromGroup(g.ptr), thread.interrupt_method)) any_signaled = true; + if (thread.signalCanceledSyscall(t, .fromGroup(g.ptr))) any_signaled = true; } return any_signaled; } @@ -546,8 +543,8 @@ const Future = struct { .canceled => true, .parked => unreachable, .blocked => unreachable, - .blocked_apc => unreachable, - .blocked_windows_dns => unreachable, + .blocked_alertable => unreachable, + .blocked_alertable_canceling => unreachable, .blocked_canceling => unreachable, }; thread.status.store(.{ .cancelation = .none, .awaitable = .null }, .monotonic); @@ -576,15 +573,11 @@ const Future = struct { num_completed: *std.atomic.Value(u32), thread: ?*Thread, ) void { - var interrupt_method: ?Thread.InterruptMethod = - if (thread) |th| th.cancelAwaitable(.fromFuture(future)) else null; + var need_signal: bool = if (thread) |th| th.cancelAwaitable(.fromFuture(future)) else false; var timeout_ns: u64 = 1 << 10; while (true) { - if (interrupt_method) |method| { - if (!thread.?.signalCanceledSyscall(t, .fromFuture(future), method)) - interrupt_method = null; - } - Thread.futexWaitUncancelable(&num_completed.raw, 0, if (interrupt_method != null) timeout_ns else null); + need_signal = need_signal and thread.?.signalCanceledSyscall(t, .fromFuture(future)); + Thread.futexWaitUncancelable(&num_completed.raw, 0, if (need_signal) timeout_ns else null); switch (num_completed.load(.acquire)) { // acquire task results 0 => {}, 1 => break, @@ -632,17 +625,9 @@ const Thread = struct { cancel_protection: Io.CancelProtection, /// Always released when `Status.cancelation` is set to `.parked`. futex_waiter: if (use_parking_futex) ?*parking_futex.Waiter else ?noreturn, - apc: Apc, - /// Used only by group cancelation code for temporary storage. - interrupt_method: InterruptMethod, csprng: Csprng, - const Apc = if (is_windows) struct { - handle: windows.HANDLE, - iosb: ?*windows.IO_STATUS_BLOCK, - } else void; - const Handle = Handle: { if (std.Thread.use_pthreads) break :Handle std.c.pthread_t; if (is_windows) break :Handle windows.HANDLE; @@ -667,13 +652,11 @@ const Thread = struct { /// To request cancelation, set the status to `.blocked_canceling` and repeatedly interrupt the system call until the status changes. blocked = 0b011, - /// Windows-only: the thread is blocked in a call to `NtDelayExecution`. - /// To request cancelation, set the status to `.canceling` and call `NtCancelIoFileEx`. - blocked_apc = 0b100, - - /// Windows-only: the thread is blocked in a call to `GetAddrInfoExW`. - /// To request cancelation, set the status to `.canceling` and call `GetAddrInfoExCancel`. - blocked_windows_dns = 0b010, + /// Windows-only: the thread is blocked in an alertable wait via + /// `NtDelayExecution`. To request cancelation, set the status to + /// `blocked_alertable_canceling` and repeatedly alert the thread + /// until the status changes. + blocked_alertable = 0b010, /// The thread has an outstanding cancelation request but is not in a cancelable operation. /// When it acknowledges the cancelation, it will set the status to `.canceled`. @@ -722,8 +705,8 @@ const Thread = struct { switch (status.cancelation) { .parked => unreachable, .blocked => unreachable, - .blocked_apc => unreachable, - .blocked_windows_dns => unreachable, + .blocked_alertable => unreachable, + .blocked_alertable_canceling => unreachable, .blocked_canceling => unreachable, .none, .canceled => {}, .canceling => { @@ -1003,17 +986,17 @@ const Thread = struct { /// It is possible that `thread` gets canceled by this function, but is blocked in a syscall. In /// that case, the thread may need to be sent a signal to interrupt the call. This function will /// return `true` to indicate this, in which case the caller must call `signalCanceledSyscall`. - fn cancelAwaitable(thread: *Thread, awaitable: AwaitableId) ?InterruptMethod { + fn cancelAwaitable(thread: *Thread, awaitable: AwaitableId) bool { var status = thread.status.load(.monotonic); while (true) { - if (status.awaitable != awaitable) return null; // thread is working on something else + if (status.awaitable != awaitable) return false; // thread is working on something else status = switch (status.cancelation) { .none => thread.status.cmpxchgWeak( .{ .cancelation = .none, .awaitable = awaitable }, .{ .cancelation = .canceling, .awaitable = awaitable }, .monotonic, .monotonic, - ) orelse return null, + ) orelse return false, .parked => thread.status.cmpxchgWeak( .{ .cancelation = .parked, .awaitable = awaitable }, @@ -1026,7 +1009,7 @@ const Thread = struct { parking_futex.removeCanceledWaiter(futex_waiter); } unpark(&.{thread.id}, null); - return null; + return false; }, .blocked => thread.status.cmpxchgWeak( @@ -1034,17 +1017,7 @@ const Thread = struct { .{ .cancelation = .blocked_canceling, .awaitable = awaitable }, .monotonic, .monotonic, - ) orelse return .sync, - - .blocked_apc => thread.status.cmpxchgWeak( - .{ .cancelation = .blocked_apc, .awaitable = awaitable }, - .{ .cancelation = .canceling, .awaitable = awaitable }, - .monotonic, - .monotonic, - ) orelse { - if (!is_windows) unreachable; - return .apc; - }, + ) orelse return true, .blocked_alertable => thread.status.cmpxchgWeak( .{ .cancelation = .blocked_alertable, .awaitable = awaitable }, @@ -1053,14 +1026,14 @@ const Thread = struct { .monotonic, ) orelse { if (!is_windows) unreachable; - return .dns; + return true; }, .canceling, .canceled => { // This can happen when the task start raced with the cancelation, so the thread // saw the cancelation on the future/group *and* we are trying to signal the // thread here. - return null; + return false; }, .blocked_canceling => unreachable, // `awaitable` has not been canceled before now @@ -1069,11 +1042,6 @@ const Thread = struct { } } - const InterruptMethod = switch (native_os) { - .windows => enum { sync, dns, apc }, - else => enum { sync }, - }; - /// Sends a signal to `thread` if it is still blocked in a syscall (i.e. has not yet observed /// the cancelation request from `cancelAwaitable`). /// @@ -1083,21 +1051,24 @@ const Thread = struct { /// the thread is still blocked. For the implementation, `Future.waitForCancelWithSignaling` and /// `Group.waitForCancelWithSignaling`: they use exponential backoff starting at a 1us delay and /// doubling each call. In practice, it is rare to send more than one signal. - fn signalCanceledSyscall(thread: *Thread, t: *Threaded, awaitable: AwaitableId, method: InterruptMethod) bool { - const bad_status: Status = .{ .cancelation = .blocked_canceling, .awaitable = awaitable }; - if (thread.status.load(.monotonic) != bad_status) return false; + fn signalCanceledSyscall(thread: *Thread, t: *Threaded, awaitable: AwaitableId) bool { + const status = thread.status.load(.monotonic); + if (status.awaitable != awaitable) { + // The thread has moved on and is working on something totally different. + return false; + } // The thread ID and/or handle can be read non-atomically because they never change and were // released by the store that made `thread` available to us. - if (std.Thread.use_pthreads) switch (method) { - .sync => return switch (std.c.pthread_kill(thread.handle, .IO)) { - 0 => true, - else => false, - }, - } else switch (native_os) { - .linux => switch (method) { - .sync => { + switch (status.cancelation) { + .blocked_canceling => if (std.Thread.use_pthreads) { + return switch (std.c.pthread_kill(thread.handle, .IO)) { + 0 => true, + else => false, + }; + } else switch (native_os) { + .linux => { const pid: posix.pid_t = pid: { const cached_pid = @atomicLoad(Pid, &t.pid, .monotonic); if (cached_pid != .unknown) break :pid @intFromEnum(cached_pid); @@ -1110,9 +1081,7 @@ const Thread = struct { else => false, }; }, - }, - .windows => switch (method) { - .sync => { + .windows => { var iosb: windows.IO_STATUS_BLOCK = undefined; return switch (windows.ntdll.NtCancelSynchronousIoFile(thread.handle, null, &iosb)) { .NOT_FOUND => true, // this might mean the operation hasn't started yet @@ -1120,15 +1089,15 @@ const Thread = struct { else => false, }; }, - .dns => @panic("TODO call GetAddrInfoExCancel"), - .apc => { - var iosb: windows.IO_STATUS_BLOCK = undefined; - return switch (windows.ntdll.NtCancelIoFileEx(thread.apc.handle, thread.apc.iosb, &iosb)) { - .NOT_FOUND => true, // this might mean the operation hasn't started yet - .SUCCESS => false, // the OS confirmed that our cancelation worked - else => false, - }; - }, + else => return false, + }, + + .blocked_alertable_canceling => { + if (!is_windows) unreachable; + return switch (windows.ntdll.NtAlertThread(thread.handle)) { + .SUCCESS => true, + else => false, + }; }, else => { @@ -1176,8 +1145,8 @@ const Syscall = struct { }, .monotonic).cancelation) { .parked => unreachable, .blocked => unreachable, - .blocked_apc => unreachable, - .blocked_windows_dns => unreachable, + .blocked_alertable => unreachable, + .blocked_alertable_canceling => unreachable, .blocked_canceling => unreachable, .none => return .{ .thread = thread }, // new status is `.blocked` .canceling => return error.Canceled, // new status is `.canceled` @@ -1196,8 +1165,8 @@ const Syscall = struct { }, .monotonic).cancelation) { .none => unreachable, .parked => unreachable, - .blocked_apc => unreachable, - .blocked_windows_dns => unreachable, + .blocked_alertable => unreachable, + .blocked_alertable_canceling => unreachable, .canceling => unreachable, .canceled => unreachable, .blocked => {}, // new status is `.blocked` (unchanged) @@ -1213,8 +1182,8 @@ const Syscall = struct { }, .monotonic).cancelation) { .none => unreachable, .parked => unreachable, - .blocked_apc => unreachable, - .blocked_windows_dns => unreachable, + .blocked_alertable => unreachable, + .blocked_alertable_canceling => unreachable, .canceling => unreachable, .canceled => unreachable, .blocked => {}, // new status is `.none` @@ -1222,25 +1191,25 @@ const Syscall = struct { } } /// Indicates instead of `NtCancelSynchronousIoFile` we need to use - /// `NtCancelIoFileEx` to interrupt the wait. + /// `NtAlertThread` to interrupt the wait. /// /// Windows only, called from blocked state only. - fn toApc(s: Syscall, apc: Thread.Apc) Io.Cancelable!void { - const thread = s.thread orelse return; - thread.apc = apc; + fn toAlertable(s: Syscall) Io.Cancelable!AlertableSyscall { + comptime assert(is_windows); + const thread = s.thread orelse return .{ .thread = null }; var prev = thread.status.load(.monotonic); while (true) prev = switch (prev.cancelation) { .none => unreachable, .parked => unreachable, - .blocked_apc => unreachable, - .blocked_windows_dns => unreachable, + .blocked_alertable => unreachable, + .blocked_alertable_canceling => unreachable, .canceling => unreachable, .canceled => unreachable, .blocked => thread.status.cmpxchgWeak(prev, .{ - .cancelation = .blocked_apc, + .cancelation = .blocked_alertable, .awaitable = prev.awaitable, - }, .monotonic, .monotonic) orelse return, + }, .monotonic, .monotonic) orelse return .{ .thread = thread }, .blocked_canceling => thread.status.cmpxchgWeak(prev, .{ .cancelation = .canceled, @@ -1248,45 +1217,6 @@ const Syscall = struct { }, .monotonic, .monotonic) orelse return error.Canceled, }; } - /// Windows only, called from blocked_apc state only. - fn checkCancelApc(s: Syscall) Io.Cancelable!void { - const thread = s.thread orelse return; - var prev = thread.status.load(.monotonic); - while (true) prev = switch (prev.cancelation) { - .none => unreachable, - .parked => unreachable, - .blocked_windows_dns => unreachable, - .blocked => unreachable, - .canceling => unreachable, - .canceled => unreachable, - .blocked_apc => return, - .blocked_canceling => thread.status.cmpxchgWeak(prev, .{ - .cancelation = .canceled, - .awaitable = prev.awaitable, - }, .monotonic, .monotonic) orelse return error.Canceled, - }; - } - /// Windows only, called from blocked_apc state only. - fn finishApc(s: Syscall) void { - const thread = s.thread orelse return; - var prev = thread.status.load(.monotonic); - while (true) prev = switch (prev.cancelation) { - .none => unreachable, - .parked => unreachable, - .blocked_windows_dns => unreachable, - .blocked => unreachable, - .canceling => unreachable, - .canceled => unreachable, - .blocked_apc => thread.status.cmpxchgWeak(prev, .{ - .cancelation = .none, - .awaitable = prev.awaitable, - }, .monotonic, .monotonic) orelse return, - .blocked_canceling => thread.status.cmpxchgWeak(prev, .{ - .cancelation = .canceling, - .awaitable = prev.awaitable, - }, .monotonic, .monotonic) orelse return, - }; - } /// Convenience wrapper which calls `finish`, then returns `err`. fn fail(s: Syscall, err: anytype) @TypeOf(err) { s.finish(); @@ -1566,8 +1496,6 @@ fn worker(t: *Threaded) void { .cancel_protection = .unblocked, .futex_waiter = undefined, .csprng = .{}, - .apc = undefined, - .interrupt_method = undefined, }; Thread.current = &thread; @@ -2176,8 +2104,8 @@ fn groupAsyncEager( .canceled => true, .parked => unreachable, .blocked => unreachable, - .blocked_apc => unreachable, - .blocked_windows_dns => unreachable, + .blocked_alertable => unreachable, + .blocked_alertable_canceling => unreachable, .blocked_canceling => unreachable, }; } else false; @@ -2188,8 +2116,8 @@ fn groupAsyncEager( .canceled => true, .parked => unreachable, .blocked => unreachable, - .blocked_apc => unreachable, - .blocked_windows_dns => unreachable, + .blocked_alertable => unreachable, + .blocked_alertable_canceling => unreachable, .blocked_canceling => unreachable, }; } else false; @@ -2368,8 +2296,8 @@ fn recancelInner() void { .canceling => unreachable, // called `recancel` but cancelation was already pending .parked => unreachable, .blocked => unreachable, - .blocked_apc => unreachable, - .blocked_windows_dns => unreachable, + .blocked_alertable => unreachable, + .blocked_alertable_canceling => unreachable, .blocked_canceling => unreachable, } } @@ -8467,36 +8395,37 @@ fn fileReadStreamingWindows(file: File, data: []const []u8) File.Reader.Error!us continue; }, .INVALID_PARAMETER => |err| return syscall.ntstatusBug(err), // streaming read of async mode file - else => |status| std.debug.panic("fileReadStreamingWindows NtReadFile returned {t}", .{status}), - //else => |status| return syscall.unexpectedNtstatus(status), + else => |status| return syscall.unexpectedNtstatus(status), } } - try syscall.toApc(.{ .handle = file.handle, .iosb = &io_status_block }); - while (true) { - switch (windows.ntdll.NtDelayExecution(1, &infinite)) { - .USER_APC => { - if (!done) { - // Other APC work was queued before calling into this function. - try syscall.checkCancelApc(); - continue; - } - break syscall.finishApc(); + // Once we get here we received PENDING so we must not return from the + // function until the operation completes. + defer while (!done) { + _ = windows.ntdll.NtDelayExecution(1, &infinite); + }; + + const alertable_syscall = syscall.toAlertable() catch |err| switch (err) { + error.Canceled => |e| { + _ = windows.ntdll.NtCancelIoFile(file.handle, &io_status_block); + return e; + }, + }; + defer alertable_syscall.finish(); + while (!done) { + _ = windows.ntdll.NtDelayExecution(1, &infinite); + alertable_syscall.checkCancel() catch |err| switch (err) { + error.Canceled => |e| { + _ = windows.ntdll.NtCancelIoFile(file.handle, &io_status_block); + return e; }, - .SUCCESS, .CANCELLED, .TIMEOUT, .ALERTED => { - try syscall.checkCancelApc(); - continue; - }, - else => |status| std.debug.panic("fileReadStreamingWindows NtDelayExecution returned {t}", .{status}), - //else => |status| return syscall.unexpectedNtstatus(status), - } + }; } } switch (io_status_block.u.Status) { .SUCCESS, .END_OF_FILE, .PIPE_BROKEN => {}, .ACCESS_DENIED => return error.AccessDenied, - else => |status| std.debug.panic("fileReadStreamingWindows IO_STATUS_BLOCK returned {t}", .{status}), - //else => |status| return windows.unexpectedStatus(status), + else => |status| return windows.unexpectedStatus(status), } return io_status_block.Information; } @@ -12519,7 +12448,7 @@ fn netLookupFallible( var res: *ws2_32.ADDRINFOEXW = undefined; const timeout: ?*ws2_32.timeval = null; while (true) { - // TODO: hook this up to cancelation with `Thread.Status.cancelation.blocked_windows_dns`. + // TODO: hook this up to cancelation with `NtDelayExecution` and APC callbacks. try Thread.checkCancel(); // TODO make this append to the queue eagerly rather than blocking until the whole thing finishes const rc: ws2_32.WinsockError = @enumFromInt(ws2_32.GetAddrInfoExW(name_w, port_w, .DNS, null, &hints, &res, timeout, null, null, null)); @@ -16253,8 +16182,8 @@ const parking_futex = struct { .canceled => break :cancelable, // status is still `.canceled` .parked => unreachable, .blocked => unreachable, - .blocked_apc => unreachable, - .blocked_windows_dns => unreachable, + .blocked_alertable => unreachable, + .blocked_alertable_canceling => unreachable, .blocked_canceling => unreachable, } // We could now be unparked for a cancelation at any time! @@ -16305,8 +16234,8 @@ const parking_futex = struct { }, .canceled => unreachable, .blocked => unreachable, - .blocked_apc => unreachable, - .blocked_windows_dns => unreachable, + .blocked_alertable => unreachable, + .blocked_alertable_canceling => unreachable, .blocked_canceling => unreachable, }, } @@ -16347,8 +16276,8 @@ const parking_futex = struct { .canceling => continue, // race with a canceler who hasn't called `removeCanceledWaiter` yet .canceled => unreachable, .blocked => unreachable, - .blocked_apc => unreachable, - .blocked_windows_dns => unreachable, + .blocked_alertable => unreachable, + .blocked_alertable_canceling => unreachable, .blocked_canceling => unreachable, } // We're waking this waiter. Remove them from the bucket and add them to our local list. @@ -16414,8 +16343,8 @@ const parking_sleep = struct { .canceled => break :cancelable, // status is still `.canceled` .parked => unreachable, .blocked => unreachable, - .blocked_apc => unreachable, - .blocked_windows_dns => unreachable, + .blocked_alertable => unreachable, + .blocked_alertable_canceling => unreachable, .blocked_canceling => unreachable, } while (park(deadline, null)) { @@ -16433,8 +16362,8 @@ const parking_sleep = struct { .none => unreachable, .canceled => unreachable, .blocked => unreachable, - .blocked_apc => unreachable, - .blocked_windows_dns => unreachable, + .blocked_alertable => unreachable, + .blocked_alertable_canceling => unreachable, .blocked_canceling => unreachable, } } else |err| switch (err) { @@ -16453,8 +16382,8 @@ const parking_sleep = struct { .none => unreachable, .canceled => unreachable, .blocked => unreachable, - .blocked_apc => unreachable, - .blocked_windows_dns => unreachable, + .blocked_alertable => unreachable, + .blocked_alertable_canceling => unreachable, .blocked_canceling => unreachable, }, } diff --git a/lib/std/os/windows/ntdll.zig b/lib/std/os/windows/ntdll.zig index 04b0a288debe648de202ed988570f1bb97bbaa6f..d68cd1494b8392381187de4316a7155351687eae 100644 --- a/lib/std/os/windows/ntdll.zig +++ b/lib/std/os/windows/ntdll.zig @@ -601,7 +601,7 @@ pub extern "ntdll" fn NtDelayExecution( pub extern "ntdll" fn NtCancelIoFileEx( FileHandle: HANDLE, - IoRequestToCancel: ?*IO_STATUS_BLOCK, + IoRequestToCancel: *const IO_STATUS_BLOCK, IoStatusBlock: *IO_STATUS_BLOCK, ) callconv(.winapi) NTSTATUS; -- 2.54.0 From 11b0a504df219b09db1d7364cc8b6a12b631ac64 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 22 Jan 2026 16:54:32 -0800 Subject: [PATCH 098/499] std.Io.Threaded: handle some more error codes from NtReadFile --- lib/std/Io/Threaded.zig | 7 ++++++- 1 file changed, 6 insertions(+), 1 deletion(-) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 3d7520dbe375716629ad6447ba92c2959230289c..36fb28db04b7f85a8498fd9c8d5aa2923978a853 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -8388,12 +8388,15 @@ fn fileReadStreamingWindows(file: File, data: []const []u8) File.Reader.Error!us null, // byte offset null, // key )) { - .SUCCESS => break :read syscall.finish(), + .SUCCESS, .END_OF_FILE, .PIPE_BROKEN => break :read syscall.finish(), .PENDING => break, .CANCELLED => { try syscall.checkCancel(); continue; }, + .INVALID_DEVICE_REQUEST => return syscall.fail(error.IsDir), + .LOCK_NOT_GRANTED => return syscall.fail(error.LockViolation), + .ACCESS_DENIED => return syscall.fail(error.AccessDenied), .INVALID_PARAMETER => |err| return syscall.ntstatusBug(err), // streaming read of async mode file else => |status| return syscall.unexpectedNtstatus(status), } @@ -8424,6 +8427,8 @@ fn fileReadStreamingWindows(file: File, data: []const []u8) File.Reader.Error!us switch (io_status_block.u.Status) { .SUCCESS, .END_OF_FILE, .PIPE_BROKEN => {}, + .INVALID_DEVICE_REQUEST => return error.IsDir, + .LOCK_NOT_GRANTED => return error.LockViolation, .ACCESS_DENIED => return error.AccessDenied, else => |status| return windows.unexpectedStatus(status), } -- 2.54.0 From 9862518797f79c946ccfad3339260597ce37ca18 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 23 Jan 2026 00:03:37 -0800 Subject: [PATCH 099/499] std.Io.Threaded: fix NtDelayExecution delay interval --- lib/std/Io/Threaded.zig | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 36fb28db04b7f85a8498fd9c8d5aa2923978a853..9db284b8feb2b0bb48521a8f23e50f3e7568a64c 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -8372,7 +8372,7 @@ fn fileReadStreamingWindows(file: File, data: []const []u8) File.Reader.Error!us var io_status_block: windows.IO_STATUS_BLOCK = undefined; var done: bool = false; - const infinite: windows.LARGE_INTEGER = windows.INFINITE; + const max_delay_interval: windows.LARGE_INTEGER = std.math.minInt(i64); read: { const syscall: Syscall = try .start(); @@ -8404,7 +8404,7 @@ fn fileReadStreamingWindows(file: File, data: []const []u8) File.Reader.Error!us // Once we get here we received PENDING so we must not return from the // function until the operation completes. defer while (!done) { - _ = windows.ntdll.NtDelayExecution(1, &infinite); + _ = windows.ntdll.NtDelayExecution(1, &max_delay_interval); }; const alertable_syscall = syscall.toAlertable() catch |err| switch (err) { @@ -8415,7 +8415,7 @@ fn fileReadStreamingWindows(file: File, data: []const []u8) File.Reader.Error!us }; defer alertable_syscall.finish(); while (!done) { - _ = windows.ntdll.NtDelayExecution(1, &infinite); + _ = windows.ntdll.NtDelayExecution(1, &max_delay_interval); alertable_syscall.checkCancel() catch |err| switch (err) { error.Canceled => |e| { _ = windows.ntdll.NtCancelIoFile(file.handle, &io_status_block); -- 2.54.0 From 90890fcb5cf39e53dc470db8260964f95b607937 Mon Sep 17 00:00:00 2001 From: Jacob Young Date: Sat, 24 Jan 2026 03:37:43 -0500 Subject: [PATCH 100/499] Io.Threaded: fix UAF-induced crashes during asynchronous operations When `NtReadFile` returns `SUCCESS`, the APC routine still runs when next alertable, which was previously clobbering an out of scope `done`. Instead of adding an extra syscall to the success path, avoid all APC side effects, allowing instant completions to return immediately. --- lib/std/Io/Threaded.zig | 93 ++++++++++++++++++++--------------------- src/codegen/c/Type.zig | 4 +- src/link.zig | 11 ++++- 3 files changed, 56 insertions(+), 52 deletions(-) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 9db284b8feb2b0bb48521a8f23e50f3e7568a64c..4fd2ab170355613846117d199054457d52dffb27 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -1314,6 +1314,13 @@ const AlertableSyscall = struct { } }; +fn noopApc(_: ?*anyopaque, _: *windows.IO_STATUS_BLOCK, _: windows.ULONG) callconv(.winapi) void {} + +fn waitForApcOrAlert() void { + const infinite_timeout: windows.LARGE_INTEGER = std.math.minInt(windows.LARGE_INTEGER); + _ = windows.ntdll.NtDelayExecution(windows.TRUE, &infinite_timeout); +} + const max_iovecs_len = 8; const splat_buffer_size = 64; const default_PATH = "/usr/local/bin:/bin/:/usr/bin"; @@ -8371,40 +8378,41 @@ fn fileReadStreamingWindows(file: File, data: []const []u8) File.Reader.Error!us const buffer = data[index]; var io_status_block: windows.IO_STATUS_BLOCK = undefined; - var done: bool = false; - const max_delay_interval: windows.LARGE_INTEGER = std.math.minInt(i64); - - read: { - const syscall: Syscall = try .start(); - while (true) { - switch (windows.ntdll.NtReadFile( - file.handle, - null, // event - flagApc, // apc callback - &done, // apc context - &io_status_block, - buffer.ptr, - @min(std.math.maxInt(u32), buffer.len), - null, // byte offset - null, // key - )) { - .SUCCESS, .END_OF_FILE, .PIPE_BROKEN => break :read syscall.finish(), - .PENDING => break, - .CANCELLED => { - try syscall.checkCancel(); - continue; - }, - .INVALID_DEVICE_REQUEST => return syscall.fail(error.IsDir), - .LOCK_NOT_GRANTED => return syscall.fail(error.LockViolation), - .ACCESS_DENIED => return syscall.fail(error.AccessDenied), - .INVALID_PARAMETER => |err| return syscall.ntstatusBug(err), // streaming read of async mode file - else => |status| return syscall.unexpectedNtstatus(status), - } + const syscall: Syscall = try .start(); + while (true) { + io_status_block.u.Status = .PENDING; + switch (windows.ntdll.NtReadFile( + file.handle, + null, // event + noopApc, // apc callback + null, // apc context + &io_status_block, + buffer.ptr, + @min(std.math.maxInt(u32), buffer.len), + null, // byte offset + null, // key + )) { + .SUCCESS, .END_OF_FILE, .PIPE_BROKEN => { + syscall.finish(); + return io_status_block.Information; + }, + .PENDING => break, + .CANCELLED => { + try syscall.checkCancel(); + continue; + }, + .INVALID_DEVICE_REQUEST => return syscall.fail(error.IsDir), + .LOCK_NOT_GRANTED => return syscall.fail(error.LockViolation), + .ACCESS_DENIED => return syscall.fail(error.AccessDenied), + .INVALID_PARAMETER => |err| return syscall.ntstatusBug(err), // streaming read of async mode file + else => |status| return syscall.unexpectedNtstatus(status), } + } + { // Once we get here we received PENDING so we must not return from the // function until the operation completes. - defer while (!done) { - _ = windows.ntdll.NtDelayExecution(1, &max_delay_interval); + defer while (@atomicLoad(windows.NTSTATUS, &io_status_block.u.Status, .acquire) == .PENDING) { + waitForApcOrAlert(); }; const alertable_syscall = syscall.toAlertable() catch |err| switch (err) { @@ -8414,36 +8422,25 @@ fn fileReadStreamingWindows(file: File, data: []const []u8) File.Reader.Error!us }, }; defer alertable_syscall.finish(); - while (!done) { - _ = windows.ntdll.NtDelayExecution(1, &max_delay_interval); + waitForApcOrAlert(); + while (@atomicLoad(windows.NTSTATUS, &io_status_block.u.Status, .acquire) == .PENDING) { alertable_syscall.checkCancel() catch |err| switch (err) { error.Canceled => |e| { _ = windows.ntdll.NtCancelIoFile(file.handle, &io_status_block); return e; }, }; + waitForApcOrAlert(); } } - switch (io_status_block.u.Status) { - .SUCCESS, .END_OF_FILE, .PIPE_BROKEN => {}, + .SUCCESS, .END_OF_FILE, .PIPE_BROKEN => return io_status_block.Information, + .PENDING => unreachable, // cannot return until the operation completes .INVALID_DEVICE_REQUEST => return error.IsDir, .LOCK_NOT_GRANTED => return error.LockViolation, .ACCESS_DENIED => return error.AccessDenied, else => |status| return windows.unexpectedStatus(status), } - return io_status_block.Information; -} - -fn flagApc( - apc_context: ?*anyopaque, - io_status_block: *windows.IO_STATUS_BLOCK, - unused: windows.ULONG, -) callconv(.winapi) void { - const flag: *bool = @ptrCast(apc_context); - flag.* = true; - _ = io_status_block; - _ = unused; } fn fileReadPositionalPosix(file: File, data: []const []u8, offset: u64) File.ReadPositionalError!usize { @@ -14646,7 +14643,7 @@ fn getCngHandle(t: *Threaded) Io.RandomSecureError!windows.HANDLE { t.mutex.lock(); // Another thread might have won the race. defer t.mutex.unlock(); if (t.random_file.handle) |prev_handle| { - _ = windows.ntdll.NtClose(fresh_handle); + windows.CloseHandle(fresh_handle); return prev_handle; } else { t.random_file.handle = fresh_handle; diff --git a/src/codegen/c/Type.zig b/src/codegen/c/Type.zig index fb37b60580b3aa655f1a633f6ed49d87e5d5c590..0bcdb207fc693b3acb38cd2a2c458dee9d61dbe3 100644 --- a/src/codegen/c/Type.zig +++ b/src/codegen/c/Type.zig @@ -2389,7 +2389,7 @@ pub const Pool = struct { .nonstring = elem_ctype.isAnyChar() and switch (ptr_info.sentinel) { .none => true, .zero_u8 => false, - else => |sentinel| Value.fromInterned(sentinel).orderAgainstZero(zcu).compare(.neq), + else => |sentinel| !Value.fromInterned(sentinel).compareAllWithZero(.eq, zcu), }, }); }, @@ -2438,7 +2438,7 @@ pub const Pool = struct { .nonstring = elem_ctype.isAnyChar() and switch (array_info.sentinel) { .none => true, .zero_u8 => false, - else => |sentinel| Value.fromInterned(sentinel).orderAgainstZero(zcu).compare(.neq), + else => |sentinel| !Value.fromInterned(sentinel).compareAllWithZero(.eq, zcu), }, }); if (!kind.isParameter()) return array_ctype; diff --git a/src/link.zig b/src/link.zig index 6f19ec0e583010a18cc450f9ac22629bf09a141a..3af768a363733e5b2dc25e8f46412c601b2e05a0 100644 --- a/src/link.zig +++ b/src/link.zig @@ -605,8 +605,8 @@ pub const File = struct { switch (base.tag) { .lld => assert(base.file == null), .elf, .macho, .wasm => { - if (base.file != null) return; dev.checkAny(&.{ .coff_linker, .elf_linker, .macho_linker, .plan9_linker, .wasm_linker }); + if (base.file != null) return; const emit = base.emit; if (base.child_pid) |pid| { if (builtin.os.tag == .windows) { @@ -645,6 +645,7 @@ pub const File = struct { base.file = try emit.root_dir.handle.openFile(io, emit.sub_path, .{ .mode = .read_write }); }, .elf2, .coff2 => if (base.file == null) { + dev.checkAny(&.{ .elf2_linker, .coff2_linker }); const mf = if (base.cast(.elf2)) |elf| &elf.mf else if (base.cast(.coff2)) |coff| @@ -657,7 +658,13 @@ pub const File = struct { base.file = mf.memory_map.file; try mf.ensureTotalCapacity(@intCast(mf.nodes.items[0].location().resolve(mf)[1])); }, - .c, .spirv => dev.checkAny(&.{ .c_linker, .spirv_linker }), + .c => if (base.file == null) { + dev.check(.c_linker); + base.file = try base.emit.root_dir.handle.openFile(io, base.emit.sub_path, .{ + .mode = .write_only, + }); + }, + .spirv => dev.check(.spirv_linker), .plan9 => unreachable, } } -- 2.54.0 From bd4b6d8b14e24e0bc6c072e5fb1bba3c666564d2 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 5 Jan 2026 22:19:08 -0800 Subject: [PATCH 101/499] std.Io: delete the poll API --- lib/std/Io.zig | 466 +------------------------------------------------ 1 file changed, 8 insertions(+), 458 deletions(-) diff --git a/lib/std/Io.zig b/lib/std/Io.zig index 6dc0e247315c4a69ed02332c9935131a5d6812fc..506203418fe49264546d9d400912fafa220c923f 100644 --- a/lib/std/Io.zig +++ b/lib/std/Io.zig @@ -15,463 +15,13 @@ const Io = @This(); const builtin = @import("builtin"); -const is_windows = builtin.os.tag == .windows; const std = @import("std.zig"); -const windows = std.os.windows; -const posix = std.posix; const math = std.math; const assert = std.debug.assert; const Allocator = std.mem.Allocator; const Alignment = std.mem.Alignment; -pub fn poll( - gpa: Allocator, - comptime StreamEnum: type, - files: PollFiles(StreamEnum), -) Poller(StreamEnum) { - const enum_fields = @typeInfo(StreamEnum).@"enum".fields; - var result: Poller(StreamEnum) = .{ - .gpa = gpa, - .readers = @splat(.failing), - .poll_fds = undefined, - .windows = if (is_windows) .{ - .first_read_done = false, - .overlapped = [1]windows.OVERLAPPED{ - std.mem.zeroes(windows.OVERLAPPED), - } ** enum_fields.len, - .small_bufs = undefined, - .active = .{ - .count = 0, - .handles_buf = undefined, - .stream_map = undefined, - }, - } else {}, - }; - - inline for (enum_fields, 0..) |field, i| { - if (is_windows) { - result.windows.active.handles_buf[i] = @field(files, field.name).handle; - } else { - result.poll_fds[i] = .{ - .fd = @field(files, field.name).handle, - .events = posix.POLL.IN, - .revents = undefined, - }; - } - } - - return result; -} - -pub fn Poller(comptime StreamEnum: type) type { - return struct { - const enum_fields = @typeInfo(StreamEnum).@"enum".fields; - const PollFd = if (is_windows) void else posix.pollfd; - - gpa: Allocator, - readers: [enum_fields.len]Reader, - poll_fds: [enum_fields.len]PollFd, - windows: if (is_windows) struct { - first_read_done: bool, - overlapped: [enum_fields.len]windows.OVERLAPPED, - small_bufs: [enum_fields.len][128]u8, - active: struct { - count: math.IntFittingRange(0, enum_fields.len), - handles_buf: [enum_fields.len]windows.HANDLE, - stream_map: [enum_fields.len]StreamEnum, - - pub fn removeAt(self: *@This(), index: u32) void { - assert(index < self.count); - for (index + 1..self.count) |i| { - self.handles_buf[i - 1] = self.handles_buf[i]; - self.stream_map[i - 1] = self.stream_map[i]; - } - self.count -= 1; - } - }, - } else void, - - const Self = @This(); - - pub fn deinit(self: *Self) void { - const gpa = self.gpa; - if (is_windows) { - // cancel any pending IO to prevent clobbering OVERLAPPED value - for (self.windows.active.handles_buf[0..self.windows.active.count]) |h| { - _ = windows.kernel32.CancelIo(h); - } - } - inline for (&self.readers) |*r| gpa.free(r.buffer); - self.* = undefined; - } - - pub fn poll(self: *Self) !bool { - if (is_windows) { - return pollWindows(self, null); - } else { - return pollPosix(self, null); - } - } - - pub fn pollTimeout(self: *Self, nanoseconds: u64) !bool { - if (is_windows) { - return pollWindows(self, nanoseconds); - } else { - return pollPosix(self, nanoseconds); - } - } - - pub fn reader(self: *Self, which: StreamEnum) *Reader { - return &self.readers[@intFromEnum(which)]; - } - - pub fn toOwnedSlice(self: *Self, which: StreamEnum) error{OutOfMemory}![]u8 { - const gpa = self.gpa; - const r = reader(self, which); - if (r.seek == 0) { - const new = try gpa.realloc(r.buffer, r.end); - r.buffer = &.{}; - r.end = 0; - return new; - } - const new = try gpa.dupe(u8, r.buffered()); - gpa.free(r.buffer); - r.buffer = &.{}; - r.seek = 0; - r.end = 0; - return new; - } - - fn pollWindows(self: *Self, nanoseconds: ?u64) !bool { - const bump_amt = 512; - const gpa = self.gpa; - - if (!self.windows.first_read_done) { - var already_read_data = false; - for (0..enum_fields.len) |i| { - const handle = self.windows.active.handles_buf[i]; - switch (try windowsAsyncReadToFifoAndQueueSmallRead( - gpa, - handle, - &self.windows.overlapped[i], - &self.readers[i], - &self.windows.small_bufs[i], - bump_amt, - )) { - .populated, .empty => |state| { - if (state == .populated) already_read_data = true; - self.windows.active.handles_buf[self.windows.active.count] = handle; - self.windows.active.stream_map[self.windows.active.count] = @as(StreamEnum, @enumFromInt(i)); - self.windows.active.count += 1; - }, - .closed => {}, // don't add to the wait_objects list - .closed_populated => { - // don't add to the wait_objects list, but we did already get data - already_read_data = true; - }, - } - } - self.windows.first_read_done = true; - if (already_read_data) return true; - } - - while (true) { - if (self.windows.active.count == 0) return false; - - const status = windows.kernel32.WaitForMultipleObjects( - self.windows.active.count, - &self.windows.active.handles_buf, - 0, - if (nanoseconds) |ns| - @min(std.math.cast(u32, ns / std.time.ns_per_ms) orelse (windows.INFINITE - 1), windows.INFINITE - 1) - else - windows.INFINITE, - ); - if (status == windows.WAIT_FAILED) - return windows.unexpectedError(windows.GetLastError()); - if (status == windows.WAIT_TIMEOUT) - return true; - - if (status < windows.WAIT_OBJECT_0 or status > windows.WAIT_OBJECT_0 + enum_fields.len - 1) - unreachable; - - const active_idx = status - windows.WAIT_OBJECT_0; - - const stream_idx = @intFromEnum(self.windows.active.stream_map[active_idx]); - const handle = self.windows.active.handles_buf[active_idx]; - - const overlapped = &self.windows.overlapped[stream_idx]; - const stream_reader = &self.readers[stream_idx]; - const small_buf = &self.windows.small_bufs[stream_idx]; - - const num_bytes_read = switch (try windowsGetReadResult(handle, overlapped, false)) { - .success => |n| n, - .closed => { - self.windows.active.removeAt(active_idx); - continue; - }, - .aborted => unreachable, - }; - const buf = small_buf[0..num_bytes_read]; - const dest = try writableSliceGreedyAlloc(stream_reader, gpa, buf.len); - @memcpy(dest[0..buf.len], buf); - advanceBufferEnd(stream_reader, buf.len); - - switch (try windowsAsyncReadToFifoAndQueueSmallRead( - gpa, - handle, - overlapped, - stream_reader, - small_buf, - bump_amt, - )) { - .empty => {}, // irrelevant, we already got data from the small buffer - .populated => {}, - .closed, - .closed_populated, // identical, since we already got data from the small buffer - => self.windows.active.removeAt(active_idx), - } - return true; - } - } - - fn pollPosix(self: *Self, nanoseconds: ?u64) !bool { - const gpa = self.gpa; - // We ask for ensureUnusedCapacity with this much extra space. This - // has more of an effect on small reads because once the reads - // start to get larger the amount of space an ArrayList will - // allocate grows exponentially. - const bump_amt = 512; - - const err_mask = posix.POLL.ERR | posix.POLL.NVAL | posix.POLL.HUP; - - const events_len = try posix.poll(&self.poll_fds, if (nanoseconds) |ns| - std.math.cast(i32, ns / std.time.ns_per_ms) orelse std.math.maxInt(i32) - else - -1); - if (events_len == 0) { - for (self.poll_fds) |poll_fd| { - if (poll_fd.fd != -1) return true; - } else return false; - } - - var keep_polling = false; - for (&self.poll_fds, &self.readers) |*poll_fd, *r| { - // Try reading whatever is available before checking the error - // conditions. - // It's still possible to read after a POLL.HUP is received, - // always check if there's some data waiting to be read first. - if (poll_fd.revents & posix.POLL.IN != 0) { - const buf = try writableSliceGreedyAlloc(r, gpa, bump_amt); - const amt = posix.read(poll_fd.fd, buf) catch |err| switch (err) { - error.BrokenPipe => 0, // Handle the same as EOF. - else => |e| return e, - }; - advanceBufferEnd(r, amt); - if (amt == 0) { - // Remove the fd when the EOF condition is met. - poll_fd.fd = -1; - } else { - keep_polling = true; - } - } else if (poll_fd.revents & err_mask != 0) { - // Exclude the fds that signaled an error. - poll_fd.fd = -1; - } else if (poll_fd.fd != -1) { - keep_polling = true; - } - } - return keep_polling; - } - - /// Returns a slice into the unused capacity of `buffer` with at least - /// `min_len` bytes, extending `buffer` by resizing it with `gpa` as necessary. - /// - /// After calling this function, typically the caller will follow up with a - /// call to `advanceBufferEnd` to report the actual number of bytes buffered. - fn writableSliceGreedyAlloc(r: *Reader, allocator: Allocator, min_len: usize) Allocator.Error![]u8 { - { - const unused = r.buffer[r.end..]; - if (unused.len >= min_len) return unused; - } - if (r.seek > 0) { - const data = r.buffer[r.seek..r.end]; - @memmove(r.buffer[0..data.len], data); - r.seek = 0; - r.end = data.len; - } - { - var list: std.ArrayList(u8) = .{ - .items = r.buffer[0..r.end], - .capacity = r.buffer.len, - }; - defer r.buffer = list.allocatedSlice(); - try list.ensureUnusedCapacity(allocator, min_len); - } - const unused = r.buffer[r.end..]; - assert(unused.len >= min_len); - return unused; - } - - /// After writing directly into the unused capacity of `buffer`, this function - /// updates `end` so that users of `Reader` can receive the data. - fn advanceBufferEnd(r: *Reader, n: usize) void { - assert(n <= r.buffer.len - r.end); - r.end += n; - } - - /// The `ReadFile` docuementation states that `lpNumberOfBytesRead` does not have a meaningful - /// result when using overlapped I/O, but also that it cannot be `null` on Windows 7. For - /// compatibility, we point it to this dummy variables, which we never otherwise access. - /// See: https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-readfile - var win_dummy_bytes_read: u32 = undefined; - - /// Read as much data as possible from `handle` with `overlapped`, and write it to the FIFO. Before - /// returning, queue a read into `small_buf` so that `WaitForMultipleObjects` returns when more data - /// is available. `handle` must have no pending asynchronous operation. - fn windowsAsyncReadToFifoAndQueueSmallRead( - gpa: Allocator, - handle: windows.HANDLE, - overlapped: *windows.OVERLAPPED, - r: *Reader, - small_buf: *[128]u8, - bump_amt: usize, - ) !enum { empty, populated, closed_populated, closed } { - var read_any_data = false; - while (true) { - const fifo_read_pending = while (true) { - const buf = try writableSliceGreedyAlloc(r, gpa, bump_amt); - const buf_len = math.cast(u32, buf.len) orelse math.maxInt(u32); - - if (0 == windows.kernel32.ReadFile( - handle, - buf.ptr, - buf_len, - &win_dummy_bytes_read, - overlapped, - )) switch (windows.GetLastError()) { - .IO_PENDING => break true, - .BROKEN_PIPE => return if (read_any_data) .closed_populated else .closed, - else => |err| return windows.unexpectedError(err), - }; - - const num_bytes_read = switch (try windowsGetReadResult(handle, overlapped, false)) { - .success => |n| n, - .closed => return if (read_any_data) .closed_populated else .closed, - .aborted => unreachable, - }; - - read_any_data = true; - advanceBufferEnd(r, num_bytes_read); - - if (num_bytes_read == buf_len) { - // We filled the buffer, so there's probably more data available. - continue; - } else { - // We didn't fill the buffer, so assume we're out of data. - // There is no pending read. - break false; - } - }; - - if (fifo_read_pending) cancel_read: { - // Cancel the pending read into the FIFO. - _ = windows.kernel32.CancelIo(handle); - - // We have to wait for the handle to be signalled, i.e. for the cancelation to complete. - switch (windows.kernel32.WaitForSingleObject(handle, windows.INFINITE)) { - windows.WAIT_OBJECT_0 => {}, - windows.WAIT_FAILED => return windows.unexpectedError(windows.GetLastError()), - else => unreachable, - } - - // If it completed before we canceled, make sure to tell the FIFO! - const num_bytes_read = switch (try windowsGetReadResult(handle, overlapped, true)) { - .success => |n| n, - .closed => return if (read_any_data) .closed_populated else .closed, - .aborted => break :cancel_read, - }; - read_any_data = true; - advanceBufferEnd(r, num_bytes_read); - } - - // Try to queue the 1-byte read. - if (0 == windows.kernel32.ReadFile( - handle, - small_buf, - small_buf.len, - &win_dummy_bytes_read, - overlapped, - )) switch (windows.GetLastError()) { - .IO_PENDING => { - // 1-byte read pending as intended - return if (read_any_data) .populated else .empty; - }, - .BROKEN_PIPE => return if (read_any_data) .closed_populated else .closed, - else => |err| return windows.unexpectedError(err), - }; - - // We got data back this time. Write it to the FIFO and run the main loop again. - const num_bytes_read = switch (try windowsGetReadResult(handle, overlapped, false)) { - .success => |n| n, - .closed => return if (read_any_data) .closed_populated else .closed, - .aborted => unreachable, - }; - const buf = small_buf[0..num_bytes_read]; - const dest = try writableSliceGreedyAlloc(r, gpa, buf.len); - @memcpy(dest[0..buf.len], buf); - advanceBufferEnd(r, buf.len); - read_any_data = true; - } - } - - /// Simple wrapper around `GetOverlappedResult` to determine the result of a `ReadFile` operation. - /// If `!allow_aborted`, then `aborted` is never returned (`OPERATION_ABORTED` is considered unexpected). - /// - /// The `ReadFile` documentation states that the number of bytes read by an overlapped `ReadFile` must be determined using `GetOverlappedResult`, even if the - /// operation immediately returns data: - /// "Use NULL for [lpNumberOfBytesRead] if this is an asynchronous operation to avoid potentially - /// erroneous results." - /// "If `hFile` was opened with `FILE_FLAG_OVERLAPPED`, the following conditions are in effect: [...] - /// The lpNumberOfBytesRead parameter should be set to NULL. Use the GetOverlappedResult function to - /// get the actual number of bytes read." - /// See: https://learn.microsoft.com/en-us/windows/win32/api/fileapi/nf-fileapi-readfile - fn windowsGetReadResult( - handle: windows.HANDLE, - overlapped: *windows.OVERLAPPED, - allow_aborted: bool, - ) !union(enum) { - success: u32, - closed, - aborted, - } { - var num_bytes_read: u32 = undefined; - if (0 == windows.kernel32.GetOverlappedResult( - handle, - overlapped, - &num_bytes_read, - 0, - )) switch (windows.GetLastError()) { - .BROKEN_PIPE => return .closed, - .OPERATION_ABORTED => |err| if (allow_aborted) { - return .aborted; - } else { - return windows.unexpectedError(err); - }, - else => |err| return windows.unexpectedError(err), - }; - return .{ .success = num_bytes_read }; - } - }; -} - -/// Given an enum, returns a struct with fields of that enum, each field -/// representing an I/O stream for polling. -pub fn PollFiles(comptime StreamEnum: type) type { - return @Struct(.auto, null, std.meta.fieldNames(StreamEnum), &@splat(Io.File), &@splat(.{})); -} - userdata: ?*anyopaque, vtable: *const VTable, @@ -704,18 +254,18 @@ pub const VTable = struct { pub const Limit = enum(usize) { nothing = 0, - unlimited = std.math.maxInt(usize), + unlimited = math.maxInt(usize), _, - /// `std.math.maxInt(usize)` is interpreted to mean `.unlimited`. + /// `math.maxInt(usize)` is interpreted to mean `.unlimited`. pub fn limited(n: usize) Limit { return @enumFromInt(n); } - /// Any value grater than `std.math.maxInt(usize)` is interpreted to mean + /// Any value grater than `math.maxInt(usize)` is interpreted to mean /// `.unlimited`. pub fn limited64(n: u64) Limit { - return @enumFromInt(@min(n, std.math.maxInt(usize))); + return @enumFromInt(@min(n, math.maxInt(usize))); } pub fn countVec(data: []const []const u8) Limit { @@ -929,9 +479,9 @@ pub const Clock = enum { }; } - pub fn compare(lhs: Clock.Timestamp, op: std.math.CompareOperator, rhs: Clock.Timestamp) bool { + pub fn compare(lhs: Clock.Timestamp, op: math.CompareOperator, rhs: Clock.Timestamp) bool { assert(lhs.clock == rhs.clock); - return std.math.compare(lhs.raw.nanoseconds, op, rhs.raw.nanoseconds); + return math.compare(lhs.raw.nanoseconds, op, rhs.raw.nanoseconds); } }; @@ -996,7 +546,7 @@ pub const Duration = struct { nanoseconds: i96, pub const zero: Duration = .{ .nanoseconds = 0 }; - pub const max: Duration = .{ .nanoseconds = std.math.maxInt(i96) }; + pub const max: Duration = .{ .nanoseconds = math.maxInt(i96) }; pub fn fromNanoseconds(x: i96) Duration { return .{ .nanoseconds = x }; @@ -1652,7 +1202,7 @@ pub const Event = enum(u32) { pub fn set(e: *Event, io: Io) void { switch (@atomicRmw(Event, e, .Xchg, .is_set, .release)) { .unset, .is_set => {}, - .waiting => io.futexWake(Event, e, std.math.maxInt(u32)), + .waiting => io.futexWake(Event, e, math.maxInt(u32)), } } -- 2.54.0 From 0a0ecc4fb132d086c0f816a304a9fd8fde4a803e Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 7 Jan 2026 18:38:19 -0800 Subject: [PATCH 102/499] std.Io: proof-of-concept "operations" API This commit shows a proof-of-concept direction for std.Io.VTable to go, which is to have general support for batching, timeouts, and non-blocking. I'm not sure if this is a good idea or not so I'm putting it up for scrutiny. This commit introduces `std.Io.operate`, `std.Io.Operation`, and implements it experimentally for `FileReadStreaming`. In `std.Io.Threaded`, the implementation is based on poll(). This commit shows how it can be used in `std.process.run` to collect both stdout and stderr in a single-threaded program using `std.Threaded.Io`. It also demonstrates how to upgrade code that was previously using `std.Io.poll` (*not* integrated with the interface!) using concurrency. This may not be ideal since it makes the build runner no longer support single-threaded mode. There is still a needed abstraction for conveniently reading multiple File streams concurrently without io.concurrent, but this commit demonstrates that such an API can be built on top of the new `std.Io.operate` functionality. --- lib/std/Build/Step.zig | 47 ++++++++++----- lib/std/Io.zig | 36 ++++++++++- lib/std/Io/File.zig | 8 ++- lib/std/Io/File/Reader.zig | 4 +- lib/std/Io/Reader.zig | 21 +++++++ lib/std/Io/Threaded.zig | 87 ++++++++++++++++++++++++++- lib/std/process.zig | 23 ++++++-- lib/std/process/Child.zig | 118 ++++++++++++++++++++++--------------- 8 files changed, 272 insertions(+), 72 deletions(-) diff --git a/lib/std/Build/Step.zig b/lib/std/Build/Step.zig index 24e00bea5ed8629d0fbd2580169687ad0274ba10..bacc81cbfabe24938358d75653019443c892f03c 100644 --- a/lib/std/Build/Step.zig +++ b/lib/std/Build/Step.zig @@ -381,10 +381,15 @@ pub fn addError(step: *Step, comptime fmt: []const u8, args: anytype) error{OutO pub const ZigProcess = struct { child: std.process.Child, - poller: Io.Poller(StreamEnum), progress_ipc_fd: if (std.Progress.have_ipc) ?std.posix.fd_t else void, pub const StreamEnum = enum { stdout, stderr }; + + pub fn deinit(zp: *ZigProcess, gpa: Allocator, io: Io) void { + _ = gpa; + zp.child.kill(io); + zp.* = undefined; + } }; /// Assumes that argv contains `--listen=-` and that the process being spawned @@ -459,14 +464,10 @@ pub fn evalZigProcess( zp.* = .{ .child = zp.child, - .poller = Io.poll(gpa, ZigProcess.StreamEnum, .{ - .stdout = zp.child.stdout.?, - .stderr = zp.child.stderr.?, - }), .progress_ipc_fd = if (std.Progress.have_ipc) prog_node.getIpcFd() else {}, }; if (watch) s.setZigProcess(zp); - defer if (!watch) zp.poller.deinit(); + defer if (!watch) zp.deinit(gpa, io); const result = try zigProcessUpdate(s, zp, watch, web_server, gpa); @@ -526,6 +527,9 @@ fn zigProcessUpdate(s: *Step, zp: *ZigProcess, watch: bool, web_server: ?*Build. const arena = b.allocator; const io = b.graph.io; + var stderr_task = try io.concurrent(readStreamAlloc, .{ gpa, io, zp.child.stderr.?, .unlimited }); + defer if (stderr_task.cancel(io)) |slice| gpa.free(slice) else |_| {}; + var timer = try std.time.Timer.start(); try sendMessage(io, zp.child.stdin.?, .update); @@ -533,14 +537,18 @@ fn zigProcessUpdate(s: *Step, zp: *ZigProcess, watch: bool, web_server: ?*Build. var result: ?Path = null; - const stdout = zp.poller.reader(.stdout); + var stdout_buffer: [512]u8 = undefined; + var stdout_reader: Io.File.Reader = .initStreaming(zp.child.stdout.?, io, &stdout_buffer); + const stdout = &stdout_reader.interface; - poll: while (true) { + var body_buffer: std.ArrayList(u8) = .empty; + + while (true) { const Header = std.zig.Server.Message.Header; - while (stdout.buffered().len < @sizeOf(Header)) if (!try zp.poller.poll()) break :poll; - const header = stdout.takeStruct(Header, .little) catch unreachable; - while (stdout.buffered().len < header.bytes_len) if (!try zp.poller.poll()) break :poll; - const body = stdout.take(header.bytes_len) catch unreachable; + const header = try stdout.takeStruct(Header, .little); + body_buffer.clearRetainingCapacity(); + try stdout.appendExact(gpa, &body_buffer, header.bytes_len); + const body = body_buffer.items; switch (header.tag) { .zig_version => { if (!std.mem.eql(u8, builtin.zig_version_string, body)) { @@ -553,11 +561,11 @@ fn zigProcessUpdate(s: *Step, zp: *ZigProcess, watch: bool, web_server: ?*Build. .error_bundle => { s.result_error_bundle = try std.zig.Server.allocErrorBundle(gpa, body); // This message indicates the end of the update. - if (watch) break :poll; + if (watch) break; }, .emit_digest => { const EmitDigest = std.zig.Server.Message.EmitDigest; - const emit_digest = @as(*align(1) const EmitDigest, @ptrCast(body)); + const emit_digest: *align(1) const EmitDigest = @ptrCast(body); s.result_cached = emit_digest.flags.cache_hit; const digest = body[@sizeOf(EmitDigest)..][0..Cache.bin_digest_len]; result = .{ @@ -631,7 +639,8 @@ fn zigProcessUpdate(s: *Step, zp: *ZigProcess, watch: bool, web_server: ?*Build. s.result_duration_ns = timer.read(); - const stderr_contents = try zp.poller.toOwnedSlice(.stderr); + const stderr_contents = try stderr_task.await(io); + defer gpa.free(stderr_contents); if (stderr_contents.len > 0) { try s.result_error_msgs.append(arena, try arena.dupe(u8, stderr_contents)); } @@ -639,6 +648,14 @@ fn zigProcessUpdate(s: *Step, zp: *ZigProcess, watch: bool, web_server: ?*Build. return result; } +fn readStreamAlloc(gpa: Allocator, io: Io, file: Io.File, limit: Io.Limit) ![]u8 { + var file_reader: Io.File.Reader = .initStreaming(file, io, &.{}); + return file_reader.interface.allocRemaining(gpa, limit) catch |err| switch (err) { + error.ReadFailed => return file_reader.err.?, + else => |e| return e, + }; +} + pub fn getZigProcess(s: *Step) ?*ZigProcess { return switch (s.id) { .compile => s.cast(Compile).?.zig_process, diff --git a/lib/std/Io.zig b/lib/std/Io.zig index 506203418fe49264546d9d400912fafa220c923f..3663e9b8d7279747c022e291e08a908bd2cc9e5a 100644 --- a/lib/std/Io.zig +++ b/lib/std/Io.zig @@ -149,6 +149,8 @@ pub const VTable = struct { futexWaitUncancelable: *const fn (?*anyopaque, ptr: *const u32, expected: u32) void, futexWake: *const fn (?*anyopaque, ptr: *const u32, max_waiters: u32) void, + operate: *const fn (?*anyopaque, []Operation, n_wait: usize, Timeout) OperateError!void, + dirCreateDir: *const fn (?*anyopaque, Dir, []const u8, Dir.Permissions) Dir.CreateDirError!void, dirCreateDirPath: *const fn (?*anyopaque, Dir, []const u8, Dir.Permissions) Dir.CreateDirPathError!Dir.CreatePathStatus, dirCreateDirPathOpen: *const fn (?*anyopaque, Dir, []const u8, Dir.Permissions, Dir.OpenOptions) Dir.CreateDirPathOpenError!Dir, @@ -184,8 +186,6 @@ pub const VTable = struct { fileWriteFileStreaming: *const fn (?*anyopaque, File, header: []const u8, *Io.File.Reader, Io.Limit) File.Writer.WriteFileError!usize, fileWriteFilePositional: *const fn (?*anyopaque, File, header: []const u8, *Io.File.Reader, Io.Limit, offset: u64) File.WriteFilePositionalError!usize, /// Returns 0 on end of stream. - fileReadStreaming: *const fn (?*anyopaque, File, data: []const []u8) File.Reader.Error!usize, - /// Returns 0 on end of stream. fileReadPositional: *const fn (?*anyopaque, File, data: []const []u8, offset: u64) File.ReadPositionalError!usize, fileSeekBy: *const fn (?*anyopaque, File, relative_offset: i64) File.SeekError!void, fileSeekTo: *const fn (?*anyopaque, File, absolute_offset: u64) File.SeekError!void, @@ -252,6 +252,38 @@ pub const VTable = struct { netLookup: *const fn (?*anyopaque, net.HostName, *Queue(net.HostName.LookupResult), net.HostName.LookupOptions) net.HostName.LookupError!void, }; +pub const Operation = union(enum) { + noop, + file_read_streaming: FileReadStreaming, + + pub const FileReadStreaming = struct { + file: File, + data: []const []u8, + /// Causes `result` to return `error.WouldBlock` instead of blocking. + nonblocking: bool = false, + /// Returns 0 on end of stream. + result: File.Reader.Error!usize, + }; +}; + +pub const OperateError = error{ Canceled, Timeout }; + +/// Performs all `operations` in a non-deterministic order. Returns after all +/// `operations` have been attempted. The degree to which the operations are +/// performed concurrently is determined by the `Io` implementation. +/// +/// `n_wait` is an amount of operations between `0` and `operations.len` that +/// determines how many attempted operations must complete before `operate` +/// returns. Operation completion is defined by returning a value other than +/// `error.WouldBlock`. If the operation cannot return `error.WouldBlock`, it +/// always counts as completing. +/// +/// In the event `error.Canceled` is returned, any number of `operations` may +/// still have been completed successfully. +pub fn operate(io: Io, operations: []Operation, n_wait: usize, timeout: Timeout) OperateError!void { + return io.vtable.operate(io.userdata, operations, n_wait, timeout); +} + pub const Limit = enum(usize) { nothing = 0, unlimited = math.maxInt(usize), diff --git a/lib/std/Io/File.zig b/lib/std/Io/File.zig index e537755a3365de8bab78d79fb55a40da0c33fe03..303cb43908bf5d65c900c3601b51be0626e436f0 100644 --- a/lib/std/Io/File.zig +++ b/lib/std/Io/File.zig @@ -554,7 +554,13 @@ pub fn setTimestampsNow(file: File, io: Io) SetTimestampsError!void { /// See also: /// * `reader` pub fn readStreaming(file: File, io: Io, buffer: []const []u8) Reader.Error!usize { - return io.vtable.fileReadStreaming(io.userdata, file, buffer); + var operation: Io.Operation = .{ .file_read_streaming = .{ + .file = file, + .data = buffer, + .result = undefined, + } }; + io.vtable.operate(io.userdata, (&operation)[0..1], 1, .none) catch unreachable; + return operation.file_read_streaming.result; } pub const ReadPositionalError = error{ diff --git a/lib/std/Io/File/Reader.zig b/lib/std/Io/File/Reader.zig index 2e0e192cb2326bd62eecbfaac6d30ade3ba81f18..d3d1c05e3f30edf81614e0109239cb547668043a 100644 --- a/lib/std/Io/File/Reader.zig +++ b/lib/std/Io/File/Reader.zig @@ -300,7 +300,7 @@ fn readVecStreaming(r: *Reader, data: [][]u8) Io.Reader.Error!usize { const dest_n, const data_size = try r.interface.writableVector(&iovecs_buffer, data); const dest = iovecs_buffer[0..dest_n]; assert(dest[0].len > 0); - const n = io.vtable.fileReadStreaming(io.userdata, r.file, dest) catch |err| { + const n = r.file.readStreaming(io, dest) catch |err| { r.err = err; return error.ReadFailed; }; @@ -355,7 +355,7 @@ fn discard(io_reader: *Io.Reader, limit: Io.Limit) Io.Reader.Error!usize { const dest_n, const data_size = try r.interface.writableVector(&iovecs_buffer, &data); const dest = iovecs_buffer[0..dest_n]; assert(dest[0].len > 0); - const n = io.vtable.fileReadStreaming(io.userdata, file, dest) catch |err| { + const n = file.readStreaming(io, dest) catch |err| { r.err = err; return error.ReadFailed; }; diff --git a/lib/std/Io/Reader.zig b/lib/std/Io/Reader.zig index a2b70afc67db4edc9c1a3afed0eb0fe79737d471..9c5c762844c0f1e296fbd389ed016ebb991abfcf 100644 --- a/lib/std/Io/Reader.zig +++ b/lib/std/Io/Reader.zig @@ -315,6 +315,27 @@ pub fn allocRemainingAlignedSentinel( } } +pub const AppendExactError = Allocator.Error || Error; + +/// Transfers exactly `n` bytes from the reader to the `ArrayList`. +/// +/// See also: +/// * `appendRemaining` +pub fn appendExact( + r: *Reader, + gpa: Allocator, + list: *ArrayList(u8), + n: usize, +) AppendExactError!void { + try list.ensureUnusedCapacity(gpa, n); + var a = std.Io.Writer.Allocating.fromArrayList(gpa, list); + defer list.* = a.toArrayList(); + streamExact(r, &a.writer, n) catch |err| switch (err) { + error.ReadFailed, error.EndOfStream => |e| return e, + error.WriteFailed => unreachable, + }; +} + /// Transfers all bytes from the current position to the end of the stream, up /// to `limit`, appending them to `list`. /// diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 4fd2ab170355613846117d199054457d52dffb27..3710527f47cc14b5ff7f80dc7fb31c132f5082fb 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -1586,6 +1586,8 @@ pub fn io(t: *Threaded) Io { .futexWaitUncancelable = futexWaitUncancelable, .futexWake = futexWake, + .operate = operate, + .dirCreateDir = dirCreateDir, .dirCreateDirPath = dirCreateDirPath, .dirCreateDirPathOpen = dirCreateDirPathOpen, @@ -1620,7 +1622,6 @@ pub fn io(t: *Threaded) Io { .fileWritePositional = fileWritePositional, .fileWriteFileStreaming = fileWriteFileStreaming, .fileWriteFilePositional = fileWriteFilePositional, - .fileReadStreaming = fileReadStreaming, .fileReadPositional = fileReadPositional, .fileSeekBy = fileSeekBy, .fileSeekTo = fileSeekTo, @@ -1746,6 +1747,8 @@ pub fn ioBasic(t: *Threaded) Io { .futexWaitUncancelable = futexWaitUncancelable, .futexWake = futexWake, + .operate = operate, + .dirCreateDir = dirCreateDir, .dirCreateDirPath = dirCreateDirPath, .dirCreateDirPathOpen = dirCreateDirPathOpen, @@ -1780,7 +1783,6 @@ pub fn ioBasic(t: *Threaded) Io { .fileWritePositional = fileWritePositional, .fileWriteFileStreaming = fileWriteFileStreaming, .fileWriteFilePositional = fileWriteFilePositional, - .fileReadStreaming = fileReadStreaming, .fileReadPositional = fileReadPositional, .fileSeekBy = fileSeekBy, .fileSeekTo = fileSeekTo, @@ -2447,6 +2449,87 @@ fn futexWake(userdata: ?*anyopaque, ptr: *const u32, max_waiters: u32) void { Thread.futexWake(ptr, max_waiters); } +fn operate(userdata: ?*anyopaque, operations: []Io.Operation, n_wait: usize, timeout: Io.Timeout) Io.OperateError!void { + const t: *Threaded = @ptrCast(@alignCast(userdata)); + const t_io = ioBasic(t); + + if (is_windows) @panic("TODO"); + + const deadline = timeout.toDeadline(t_io) catch |err| switch (err) { + error.UnsupportedClock, error.Unexpected => null, + }; + + var poll_buffer: [100]posix.pollfd = undefined; + var map_buffer: [poll_buffer.len]u8 = undefined; // poll_buffer index to operations index + var poll_i: usize = 0; + var completed: usize = 0; + + // Put all the file reads with nonblocking enabled into the poll set. + if (operations.len > poll_buffer.len) @panic("TODO"); + + // TODO if any operation is canceled, cancel the rest + + for (operations, 0..) |*operation, operation_index| switch (operation.*) { + .noop => continue, + .file_read_streaming => |*o| { + if (o.nonblocking) { + o.result = error.WouldBlock; + poll_buffer[poll_i] = .{ + .fd = o.file.handle, + .events = posix.POLL.IN, + .revents = undefined, + }; + map_buffer[poll_i] = @intCast(operation_index); + poll_i += 1; + } else { + o.result = fileReadStreaming(o.file, o.data); + completed += 1; + } + }, + }; + + if (poll_i == 0) { + @branchHint(.likely); + return; + } + + const max_poll_ms = std.math.maxInt(i32); + + while (completed < n_wait) { + const timeout_ms: i32 = if (deadline) |d| t: { + const duration = d.durationFromNow(t_io) catch @panic("TODO make this unreachable"); + if (duration.raw.nanoseconds <= 0) return error.Timeout; + break :t @intCast(@min(max_poll_ms, duration.raw.toMilliseconds())); + } else -1; + const syscall = try Syscall.start(); + const poll_rc = posix.system.poll(&poll_buffer, poll_i, timeout_ms); + syscall.finish(); + switch (posix.errno(poll_rc)) { + .SUCCESS => { + if (poll_rc == 0) { + // Although spurious timeouts are OK, when no deadline + // is passed we must not return `error.Timeout`. + if (deadline == null) continue; + return error.Timeout; + } + for (poll_buffer[0..poll_i], map_buffer[0..poll_i]) |*poll_fd, operation_index| { + if (poll_fd.revents == 0) continue; + poll_fd.fd = -1; // Disarm this operation. + switch (operations[operation_index]) { + .noop => unreachable, + .file_read_streaming => |*o| { + o.result = fileReadStreaming(o.file, o.data); + completed += 1; + }, + } + } + }, + .INTR => continue, + else => @panic("TODO handle unexpected error from poll()"), + } + } +} + const dirCreateDir = switch (native_os) { .windows => dirCreateDirWindows, .wasi => dirCreateDirWasi, diff --git a/lib/std/process.zig b/lib/std/process.zig index 8395882c167cbe0149f081cd7fb4af413ec24e08..10bcc7649740b82baf6bd492ed99d93f38e35e37 100644 --- a/lib/std/process.zig +++ b/lib/std/process.zig @@ -454,13 +454,17 @@ pub fn spawnPath(io: Io, dir: Io.Dir, options: SpawnOptions) SpawnError!Child { } pub const RunError = CurrentPathError || posix.ReadError || SpawnError || posix.PollError || error{ - StdoutStreamTooLong, - StderrStreamTooLong, + StreamTooLong, }; pub const RunOptions = struct { argv: []const []const u8, - max_output_bytes: usize = 50 * 1024, + stderr_limit: Io.Limit = .unlimited, + stdout_limit: Io.Limit = .unlimited, + /// How many bytes to initially allocate for stderr. + stderr_reserve_amount: usize = 1, + /// How many bytes to initially allocate for stdout. + stdout_reserve_amount: usize = 1, /// Set to change the current working directory when spawning the child process. cwd: ?[]const u8 = null, @@ -486,6 +490,7 @@ pub const RunOptions = struct { create_no_window: bool = true, /// Darwin-only. Disable ASLR for the child process. disable_aslr: bool = false, + timeout: Io.Timeout = .none, }; pub const RunResult = struct { @@ -518,7 +523,17 @@ pub fn run(gpa: Allocator, io: Io, options: RunOptions) RunError!RunResult { var stderr: std.ArrayList(u8) = .empty; defer stderr.deinit(gpa); - try child.collectOutput(gpa, &stdout, &stderr, options.max_output_bytes); + try stdout.ensureUnusedCapacity(gpa, options.stdout_reserve_amount); + try stderr.ensureUnusedCapacity(gpa, options.stderr_reserve_amount); + + try child.collectOutput(io, .{ + .allocator = gpa, + .stdout = &stdout, + .stderr = &stderr, + .stdout_limit = options.stdout_limit, + .stderr_limit = options.stderr_limit, + .timeout = options.timeout, + }); const term = try child.wait(io); diff --git a/lib/std/process/Child.zig b/lib/std/process/Child.zig index 17e15f208da5fbb7155ada08fe2a9006127be258..6675c7bbe76e4df3712ba524f7919b0db2aa2e8a 100644 --- a/lib/std/process/Child.zig +++ b/lib/std/process/Child.zig @@ -9,7 +9,6 @@ const process = std.process; const File = std.Io.File; const assert = std.debug.assert; const Allocator = std.mem.Allocator; -const ArrayList = std.ArrayList; pub const Id = switch (native_os) { .windows => std.os.windows.HANDLE, @@ -126,53 +125,80 @@ pub fn wait(child: *Child, io: Io) WaitError!Term { return io.vtable.childWait(io.userdata, child); } -/// Collect the output from the process's stdout and stderr. Will return once all output -/// has been collected. This does not mean that the process has ended. `wait` should still -/// be called to wait for and clean up the process. +pub const CollectOutputError = error{ + Timeout, + StreamTooLong, +} || Allocator.Error || Io.File.Reader.Error; + +pub const CollectOutputOptions = struct { + stdout: *std.ArrayList(u8), + stderr: *std.ArrayList(u8), + /// Used for `stdout` and `stderr`. If not provided, only the existing + /// capacity will be used. + allocator: ?Allocator = null, + stdout_limit: Io.Limit = .unlimited, + stderr_limit: Io.Limit = .unlimited, + timeout: Io.Timeout = .none, +}; + +/// Collect the output from the process's stdout and stderr. Will return once +/// all output has been collected. This does not mean that the process has +/// ended. `wait` should still be called to wait for and clean up the process. /// /// The process must have been started with stdout and stderr set to /// `process.SpawnOptions.StdIo.pipe`. -pub fn collectOutput( - child: *const Child, - /// Used for `stdout` and `stderr`. - allocator: Allocator, - stdout: *ArrayList(u8), - stderr: *ArrayList(u8), - max_output_bytes: usize, -) !void { - var poller = std.Io.poll(allocator, enum { stdout, stderr }, .{ - .stdout = child.stdout.?, - .stderr = child.stderr.?, - }); - defer poller.deinit(); - - const stdout_r = poller.reader(.stdout); - stdout_r.buffer = stdout.allocatedSlice(); - stdout_r.seek = 0; - stdout_r.end = stdout.items.len; - - const stderr_r = poller.reader(.stderr); - stderr_r.buffer = stderr.allocatedSlice(); - stderr_r.seek = 0; - stderr_r.end = stderr.items.len; - - defer { - stdout.* = .{ - .items = stdout_r.buffer[0..stdout_r.end], - .capacity = stdout_r.buffer.len, - }; - stderr.* = .{ - .items = stderr_r.buffer[0..stderr_r.end], - .capacity = stderr_r.buffer.len, - }; - stdout_r.buffer = &.{}; - stderr_r.buffer = &.{}; - } - - while (try poller.poll()) { - if (stdout_r.bufferedLen() > max_output_bytes) - return error.StdoutStreamTooLong; - if (stderr_r.bufferedLen() > max_output_bytes) - return error.StderrStreamTooLong; +pub fn collectOutput(child: *const Child, io: Io, options: CollectOutputOptions) CollectOutputError!void { + const files: [2]Io.File = .{ child.stdout.?, child.stderr.? }; + const lists: [2]*std.ArrayList(u8) = .{ options.stdout, options.stderr }; + const limits: [2]Io.Limit = .{ options.stdout_limit, options.stderr_limit }; + var dones: [2]bool = .{ false, false }; + var reads: [2]Io.Operation = undefined; + var vecs: [2][1][]u8 = undefined; + while (true) { + for (&reads, &lists, &files, dones, &vecs) |*read, list, file, done, *vec| { + if (done) { + read.* = .noop; + continue; + } + if (options.allocator) |gpa| try list.ensureUnusedCapacity(gpa, 1); + const cap = list.unusedCapacitySlice(); + if (cap.len == 0) return error.StreamTooLong; + vec[0] = cap; + read.* = .{ .file_read_streaming = .{ + .file = file, + .data = vec, + .nonblocking = true, + .result = undefined, + } }; + } + var all_done = true; + var any_canceled = false; + var other_err: (error{StreamTooLong} || Io.File.Reader.Error)!void = {}; + const op_result = io.vtable.operate(io.userdata, &reads, 1, options.timeout); + for (&reads, &lists, &limits, &dones) |*read, list, limit, *done| { + if (done.*) continue; + const n = read.file_read_streaming.result catch |err| switch (err) { + error.Canceled => { + any_canceled = true; + continue; + }, + error.WouldBlock => continue, + else => |e| { + other_err = e; + continue; + }, + }; + if (n == 0) { + done.* = true; + } else { + all_done = false; + } + list.items.len += n; + if (list.items.len > @intFromEnum(limit)) other_err = error.StreamTooLong; + } + if (any_canceled) return error.Canceled; + try op_result; // could be error.Canceled + try other_err; + if (all_done) return; } } -- 2.54.0 From 05064e128137e6c7b1afe6d648b33ebfa60877f2 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 8 Jan 2026 12:55:38 -0800 Subject: [PATCH 103/499] std.Io: simplify operate function - no timeout - no n_wait - infallible --- lib/std/Io.zig | 19 +++--------- lib/std/Io/File.zig | 2 +- lib/std/Io/Threaded.zig | 63 ++++++++++++++++++--------------------- lib/std/process.zig | 2 -- lib/std/process/Child.zig | 4 +-- 5 files changed, 35 insertions(+), 55 deletions(-) diff --git a/lib/std/Io.zig b/lib/std/Io.zig index 3663e9b8d7279747c022e291e08a908bd2cc9e5a..c52b51e00a4dc996e9f59a04b499449cc732d96a 100644 --- a/lib/std/Io.zig +++ b/lib/std/Io.zig @@ -149,7 +149,7 @@ pub const VTable = struct { futexWaitUncancelable: *const fn (?*anyopaque, ptr: *const u32, expected: u32) void, futexWake: *const fn (?*anyopaque, ptr: *const u32, max_waiters: u32) void, - operate: *const fn (?*anyopaque, []Operation, n_wait: usize, Timeout) OperateError!void, + operate: *const fn (?*anyopaque, []Operation) void, dirCreateDir: *const fn (?*anyopaque, Dir, []const u8, Dir.Permissions) Dir.CreateDirError!void, dirCreateDirPath: *const fn (?*anyopaque, Dir, []const u8, Dir.Permissions) Dir.CreateDirPathError!Dir.CreatePathStatus, @@ -266,22 +266,11 @@ pub const Operation = union(enum) { }; }; -pub const OperateError = error{ Canceled, Timeout }; - /// Performs all `operations` in a non-deterministic order. Returns after all -/// `operations` have been attempted. The degree to which the operations are +/// `operations` have been completed. The degree to which the operations are /// performed concurrently is determined by the `Io` implementation. -/// -/// `n_wait` is an amount of operations between `0` and `operations.len` that -/// determines how many attempted operations must complete before `operate` -/// returns. Operation completion is defined by returning a value other than -/// `error.WouldBlock`. If the operation cannot return `error.WouldBlock`, it -/// always counts as completing. -/// -/// In the event `error.Canceled` is returned, any number of `operations` may -/// still have been completed successfully. -pub fn operate(io: Io, operations: []Operation, n_wait: usize, timeout: Timeout) OperateError!void { - return io.vtable.operate(io.userdata, operations, n_wait, timeout); +pub fn operate(io: Io, operations: []Operation) void { + return io.vtable.operate(io.userdata, operations); } pub const Limit = enum(usize) { diff --git a/lib/std/Io/File.zig b/lib/std/Io/File.zig index 303cb43908bf5d65c900c3601b51be0626e436f0..16663eb48488dc8d229a53ffee243e2d294e4b62 100644 --- a/lib/std/Io/File.zig +++ b/lib/std/Io/File.zig @@ -559,7 +559,7 @@ pub fn readStreaming(file: File, io: Io, buffer: []const []u8) Reader.Error!usiz .data = buffer, .result = undefined, } }; - io.vtable.operate(io.userdata, (&operation)[0..1], 1, .none) catch unreachable; + io.vtable.operate(io.userdata, (&operation)[0..1]); return operation.file_read_streaming.result; } diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 3710527f47cc14b5ff7f80dc7fb31c132f5082fb..8ee79a7ae360d18a5f9f57b672841bbe7cfee5e8 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -2449,20 +2449,15 @@ fn futexWake(userdata: ?*anyopaque, ptr: *const u32, max_waiters: u32) void { Thread.futexWake(ptr, max_waiters); } -fn operate(userdata: ?*anyopaque, operations: []Io.Operation, n_wait: usize, timeout: Io.Timeout) Io.OperateError!void { +fn operate(userdata: ?*anyopaque, operations: []Io.Operation) void { const t: *Threaded = @ptrCast(@alignCast(userdata)); - const t_io = ioBasic(t); + _ = t; if (is_windows) @panic("TODO"); - const deadline = timeout.toDeadline(t_io) catch |err| switch (err) { - error.UnsupportedClock, error.Unexpected => null, - }; - var poll_buffer: [100]posix.pollfd = undefined; var map_buffer: [poll_buffer.len]u8 = undefined; // poll_buffer index to operations index var poll_i: usize = 0; - var completed: usize = 0; // Put all the file reads with nonblocking enabled into the poll set. if (operations.len > poll_buffer.len) @panic("TODO"); @@ -2483,7 +2478,6 @@ fn operate(userdata: ?*anyopaque, operations: []Io.Operation, n_wait: usize, tim poll_i += 1; } else { o.result = fileReadStreaming(o.file, o.data); - completed += 1; } }, }; @@ -2493,41 +2487,42 @@ fn operate(userdata: ?*anyopaque, operations: []Io.Operation, n_wait: usize, tim return; } - const max_poll_ms = std.math.maxInt(i32); - - while (completed < n_wait) { - const timeout_ms: i32 = if (deadline) |d| t: { - const duration = d.durationFromNow(t_io) catch @panic("TODO make this unreachable"); - if (duration.raw.nanoseconds <= 0) return error.Timeout; - break :t @intCast(@min(max_poll_ms, duration.raw.toMilliseconds())); - } else -1; - const syscall = try Syscall.start(); - const poll_rc = posix.system.poll(&poll_buffer, poll_i, timeout_ms); - syscall.finish(); - switch (posix.errno(poll_rc)) { - .SUCCESS => { - if (poll_rc == 0) { - // Although spurious timeouts are OK, when no deadline - // is passed we must not return `error.Timeout`. - if (deadline == null) continue; - return error.Timeout; - } - for (poll_buffer[0..poll_i], map_buffer[0..poll_i]) |*poll_fd, operation_index| { - if (poll_fd.revents == 0) continue; - poll_fd.fd = -1; // Disarm this operation. + while (true) { + const syscall = Syscall.start() catch |err| switch (err) { + error.Canceled => { + for (map_buffer[0..poll_i]) |operation_index| { switch (operations[operation_index]) { .noop => unreachable, - .file_read_streaming => |*o| { - o.result = fileReadStreaming(o.file, o.data); - completed += 1; - }, + inline else => |*o| o.result = error.Canceled, } } + return; + }, + }; + const poll_rc = posix.system.poll(&poll_buffer, poll_i, -1); + syscall.finish(); + switch (posix.errno(poll_rc)) { + .SUCCESS => { + if (poll_rc == 0) { + // Spurious timeout; handle same as INTR. + continue; + } + break; }, .INTR => continue, else => @panic("TODO handle unexpected error from poll()"), } } + + for (poll_buffer[0..poll_i], map_buffer[0..poll_i]) |*poll_fd, operation_index| { + if (poll_fd.revents == 0) continue; + switch (operations[operation_index]) { + .noop => unreachable, + .file_read_streaming => |*o| { + o.result = fileReadStreaming(o.file, o.data); + }, + } + } } const dirCreateDir = switch (native_os) { diff --git a/lib/std/process.zig b/lib/std/process.zig index 10bcc7649740b82baf6bd492ed99d93f38e35e37..4a021879a55f257a8b453182972be038aff77138 100644 --- a/lib/std/process.zig +++ b/lib/std/process.zig @@ -490,7 +490,6 @@ pub const RunOptions = struct { create_no_window: bool = true, /// Darwin-only. Disable ASLR for the child process. disable_aslr: bool = false, - timeout: Io.Timeout = .none, }; pub const RunResult = struct { @@ -532,7 +531,6 @@ pub fn run(gpa: Allocator, io: Io, options: RunOptions) RunError!RunResult { .stderr = &stderr, .stdout_limit = options.stdout_limit, .stderr_limit = options.stderr_limit, - .timeout = options.timeout, }); const term = try child.wait(io); diff --git a/lib/std/process/Child.zig b/lib/std/process/Child.zig index 6675c7bbe76e4df3712ba524f7919b0db2aa2e8a..e541ca4e6534cadf6c5f1c68abd1564a21669ce3 100644 --- a/lib/std/process/Child.zig +++ b/lib/std/process/Child.zig @@ -138,7 +138,6 @@ pub const CollectOutputOptions = struct { allocator: ?Allocator = null, stdout_limit: Io.Limit = .unlimited, stderr_limit: Io.Limit = .unlimited, - timeout: Io.Timeout = .none, }; /// Collect the output from the process's stdout and stderr. Will return once @@ -174,7 +173,7 @@ pub fn collectOutput(child: *const Child, io: Io, options: CollectOutputOptions) var all_done = true; var any_canceled = false; var other_err: (error{StreamTooLong} || Io.File.Reader.Error)!void = {}; - const op_result = io.vtable.operate(io.userdata, &reads, 1, options.timeout); + io.vtable.operate(io.userdata, &reads); for (&reads, &lists, &limits, &dones) |*read, list, limit, *done| { if (done.*) continue; const n = read.file_read_streaming.result catch |err| switch (err) { @@ -197,7 +196,6 @@ pub fn collectOutput(child: *const Child, io: Io, options: CollectOutputOptions) if (list.items.len > @intFromEnum(limit)) other_err = error.StreamTooLong; } if (any_canceled) return error.Canceled; - try op_result; // could be error.Canceled try other_err; if (all_done) return; } -- 2.54.0 From 93f5c99149948b104ab504eff3a171b6c1bff065 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 8 Jan 2026 14:10:31 -0800 Subject: [PATCH 104/499] std.Io.Threaded.operate: handle cancelation and poll errors --- lib/std/Io/Threaded.zig | 49 +++++++++++++++++++++++++++++++---------- 1 file changed, 37 insertions(+), 12 deletions(-) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 8ee79a7ae360d18a5f9f57b672841bbe7cfee5e8..1f4bcf34787a144a8ead404fc7b64c85e92d5add 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -1323,6 +1323,7 @@ fn waitForApcOrAlert() void { const max_iovecs_len = 8; const splat_buffer_size = 64; +const poll_buffer_len = 100; const default_PATH = "/usr/local/bin:/bin/:/usr/bin"; comptime { @@ -2455,15 +2456,13 @@ fn operate(userdata: ?*anyopaque, operations: []Io.Operation) void { if (is_windows) @panic("TODO"); - var poll_buffer: [100]posix.pollfd = undefined; - var map_buffer: [poll_buffer.len]u8 = undefined; // poll_buffer index to operations index + var poll_buffer: [poll_buffer_len]posix.pollfd = undefined; + var map_buffer: [poll_buffer_len]u8 = undefined; // poll_buffer index to operations index var poll_i: usize = 0; // Put all the file reads with nonblocking enabled into the poll set. if (operations.len > poll_buffer.len) @panic("TODO"); - // TODO if any operation is canceled, cancel the rest - for (operations, 0..) |*operation, operation_index| switch (operation.*) { .noop => continue, .file_read_streaming => |*o| { @@ -2477,7 +2476,13 @@ fn operate(userdata: ?*anyopaque, operations: []Io.Operation) void { map_buffer[poll_i] = @intCast(operation_index); poll_i += 1; } else { - o.result = fileReadStreaming(o.file, o.data); + o.result = fileReadStreaming(o.file, o.data) catch |err| switch (err) { + error.Canceled => { + setOperationsCanceled(operations[operation_index..]); + return; + }, + else => err, + }; } }, }; @@ -2490,12 +2495,7 @@ fn operate(userdata: ?*anyopaque, operations: []Io.Operation) void { while (true) { const syscall = Syscall.start() catch |err| switch (err) { error.Canceled => { - for (map_buffer[0..poll_i]) |operation_index| { - switch (operations[operation_index]) { - .noop => unreachable, - inline else => |*o| o.result = error.Canceled, - } - } + setAllOperationsError(operations, map_buffer[0..poll_i], error.Canceled); return; }, }; @@ -2510,7 +2510,14 @@ fn operate(userdata: ?*anyopaque, operations: []Io.Operation) void { break; }, .INTR => continue, - else => @panic("TODO handle unexpected error from poll()"), + .NOMEM => { + setAllOperationsError(operations, map_buffer[0..poll_i], error.SystemResources); + return; + }, + else => { + setAllOperationsError(operations, map_buffer[0..poll_i], error.Unexpected); + return; + }, } } @@ -2525,6 +2532,24 @@ fn operate(userdata: ?*anyopaque, operations: []Io.Operation) void { } } +fn setAllOperationsError( + operations: []Io.Operation, + map: []const u8, + err: error{ Canceled, SystemResources, Unexpected }, +) void { + for (map) |operation_index| switch (operations[operation_index]) { + .noop => unreachable, + inline else => |*o| o.result = err, + }; +} + +fn setOperationsCanceled(operations: []Io.Operation) void { + for (operations) |*op| switch (op.*) { + .noop => unreachable, + inline else => |*o| o.result = error.Canceled, + }; +} + const dirCreateDir = switch (native_os) { .windows => dirCreateDirWindows, .wasi => dirCreateDirWasi, -- 2.54.0 From 6a7fe61d74f80456b32cb46ef21715bbffaf49a7 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 8 Jan 2026 15:07:03 -0800 Subject: [PATCH 105/499] std.Io.Threaded.operate: handle poll buffer exceeded --- lib/std/Io/Threaded.zig | 139 ++++++++++++++++++++-------------------- 1 file changed, 71 insertions(+), 68 deletions(-) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 1f4bcf34787a144a8ead404fc7b64c85e92d5add..e58d923793ef84b22e2132255604dd09633d5364 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -2458,81 +2458,84 @@ fn operate(userdata: ?*anyopaque, operations: []Io.Operation) void { var poll_buffer: [poll_buffer_len]posix.pollfd = undefined; var map_buffer: [poll_buffer_len]u8 = undefined; // poll_buffer index to operations index - var poll_i: usize = 0; + var operation_index: usize = 0; - // Put all the file reads with nonblocking enabled into the poll set. - if (operations.len > poll_buffer.len) @panic("TODO"); - - for (operations, 0..) |*operation, operation_index| switch (operation.*) { - .noop => continue, - .file_read_streaming => |*o| { - if (o.nonblocking) { - o.result = error.WouldBlock; - poll_buffer[poll_i] = .{ - .fd = o.file.handle, - .events = posix.POLL.IN, - .revents = undefined, - }; - map_buffer[poll_i] = @intCast(operation_index); - poll_i += 1; - } else { - o.result = fileReadStreaming(o.file, o.data) catch |err| switch (err) { - error.Canceled => { - setOperationsCanceled(operations[operation_index..]); - return; - }, - else => err, - }; + while (operation_index < operations.len) { + var poll_i: usize = 0; + while (operation_index < operations.len) : (operation_index += 1) { + switch (operations[operation_index]) { + .noop => continue, + .file_read_streaming => |*o| { + if (o.nonblocking) { + o.result = error.WouldBlock; + poll_buffer[poll_i] = .{ + .fd = o.file.handle, + .events = posix.POLL.IN, + .revents = 0, + }; + if (map_buffer.len - poll_i == 0) break; + map_buffer[poll_i] = @intCast(operation_index); + poll_i += 1; + } else { + o.result = fileReadStreaming(o.file, o.data) catch |err| switch (err) { + error.Canceled => { + setOperationsError(operations[operation_index..], error.Canceled); + return; + }, + else => err, + }; + } + }, } - }, - }; - - if (poll_i == 0) { - @branchHint(.likely); - return; - } + } - while (true) { - const syscall = Syscall.start() catch |err| switch (err) { - error.Canceled => { - setAllOperationsError(operations, map_buffer[0..poll_i], error.Canceled); - return; - }, - }; - const poll_rc = posix.system.poll(&poll_buffer, poll_i, -1); - syscall.finish(); - switch (posix.errno(poll_rc)) { - .SUCCESS => { - if (poll_rc == 0) { - // Spurious timeout; handle same as INTR. - continue; - } - break; - }, - .INTR => continue, - .NOMEM => { - setAllOperationsError(operations, map_buffer[0..poll_i], error.SystemResources); - return; - }, - else => { - setAllOperationsError(operations, map_buffer[0..poll_i], error.Unexpected); - return; - }, + if (poll_i == 0) { + @branchHint(.likely); + return; } - } - for (poll_buffer[0..poll_i], map_buffer[0..poll_i]) |*poll_fd, operation_index| { - if (poll_fd.revents == 0) continue; - switch (operations[operation_index]) { - .noop => unreachable, - .file_read_streaming => |*o| { - o.result = fileReadStreaming(o.file, o.data); - }, + while (true) { + const syscall = Syscall.start() catch |err| switch (err) { + error.Canceled => { + setPollOperationsError(operations, map_buffer[0..poll_i], error.Canceled); + setOperationsError(operations[operation_index..], error.Canceled); + return; + }, + }; + const poll_rc = posix.system.poll(&poll_buffer, poll_i, -1); + syscall.finish(); + switch (posix.errno(poll_rc)) { + .SUCCESS => { + if (poll_rc == 0) { + // Spurious timeout; handle same as INTR. + continue; + } + for (poll_buffer[0..poll_i], map_buffer[0..poll_i]) |*poll_fd, i| { + if (poll_fd.revents == 0) continue; + switch (operations[i]) { + .noop => unreachable, + .file_read_streaming => |*o| { + o.result = fileReadStreaming(o.file, o.data); + }, + } + } + break; + }, + .INTR => continue, + .NOMEM => { + setPollOperationsError(operations, map_buffer[0..poll_i], error.SystemResources); + break; + }, + else => { + setPollOperationsError(operations, map_buffer[0..poll_i], error.Unexpected); + break; + }, + } } } } -fn setAllOperationsError( +fn setPollOperationsError( operations: []Io.Operation, map: []const u8, err: error{ Canceled, SystemResources, Unexpected }, @@ -2543,10 +2546,10 @@ fn setAllOperationsError( }; } -fn setOperationsCanceled(operations: []Io.Operation) void { +fn setOperationsError(operations: []Io.Operation, err: error{ Canceled, SystemResources, Unexpected }) void { for (operations) |*op| switch (op.*) { .noop => unreachable, - inline else => |*o| o.result = error.Canceled, + inline else => |*o| o.result = err, }; } -- 2.54.0 From e0d06b40e3681cc9aad09764987d079fb0f9e0fa Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 8 Jan 2026 15:20:21 -0800 Subject: [PATCH 106/499] std.Io.Threaded: set poll_buffer_len to 32 reasoning is that polling with large amount of operations will be rarely done with std.Io.Threaded. However this still provides the opportunity to provide concurrency for any real world use cases that need it. --- lib/std/Io/Threaded.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index e58d923793ef84b22e2132255604dd09633d5364..55b3596ee79b47343ea391462488683aa734f418 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -1323,7 +1323,7 @@ fn waitForApcOrAlert() void { const max_iovecs_len = 8; const splat_buffer_size = 64; -const poll_buffer_len = 100; +const poll_buffer_len = 32; const default_PATH = "/usr/local/bin:/bin/:/usr/bin"; comptime { -- 2.54.0 From b996675dcf5533e506eee0ed44c64aebab69af6e Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 8 Jan 2026 16:36:18 -0800 Subject: [PATCH 107/499] fix error set --- lib/std/process/Child.zig | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/lib/std/process/Child.zig b/lib/std/process/Child.zig index e541ca4e6534cadf6c5f1c68abd1564a21669ce3..fc31014520a93b791f10048d17d2ef6046440c66 100644 --- a/lib/std/process/Child.zig +++ b/lib/std/process/Child.zig @@ -125,10 +125,7 @@ pub fn wait(child: *Child, io: Io) WaitError!Term { return io.vtable.childWait(io.userdata, child); } -pub const CollectOutputError = error{ - Timeout, - StreamTooLong, -} || Allocator.Error || Io.File.Reader.Error; +pub const CollectOutputError = error{StreamTooLong} || Allocator.Error || Io.File.Reader.Error; pub const CollectOutputOptions = struct { stdout: *std.ArrayList(u8), -- 2.54.0 From 87408f8addac76b8b4811fbfede30a1c0f637a8f Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 8 Jan 2026 16:55:26 -0800 Subject: [PATCH 108/499] std.process.Child: rewrite using concurrent I plan to immediately revert this, but here's a commit for posterity --- lib/std/process.zig | 1 + lib/std/process/Child.zig | 81 ++++++++++++--------------------------- 2 files changed, 26 insertions(+), 56 deletions(-) diff --git a/lib/std/process.zig b/lib/std/process.zig index 4a021879a55f257a8b453182972be038aff77138..b203838e3fc2aa4a9293cc54a6a192bdd9991fc6 100644 --- a/lib/std/process.zig +++ b/lib/std/process.zig @@ -455,6 +455,7 @@ pub fn spawnPath(io: Io, dir: Io.Dir, options: SpawnOptions) SpawnError!Child { pub const RunError = CurrentPathError || posix.ReadError || SpawnError || posix.PollError || error{ StreamTooLong, + ConcurrencyUnavailable, }; pub const RunOptions = struct { diff --git a/lib/std/process/Child.zig b/lib/std/process/Child.zig index fc31014520a93b791f10048d17d2ef6046440c66..e64f6106fa99fb6468aa4c0c398933f245f1c083 100644 --- a/lib/std/process/Child.zig +++ b/lib/std/process/Child.zig @@ -125,14 +125,15 @@ pub fn wait(child: *Child, io: Io) WaitError!Term { return io.vtable.childWait(io.userdata, child); } -pub const CollectOutputError = error{StreamTooLong} || Allocator.Error || Io.File.Reader.Error; +pub const CollectOutputError = error{ + StreamTooLong, + ConcurrencyUnavailable, +} || Allocator.Error || Io.File.Reader.Error; pub const CollectOutputOptions = struct { stdout: *std.ArrayList(u8), stderr: *std.ArrayList(u8), - /// Used for `stdout` and `stderr`. If not provided, only the existing - /// capacity will be used. - allocator: ?Allocator = null, + allocator: Allocator, stdout_limit: Io.Limit = .unlimited, stderr_limit: Io.Limit = .unlimited, }; @@ -144,56 +145,24 @@ pub const CollectOutputOptions = struct { /// The process must have been started with stdout and stderr set to /// `process.SpawnOptions.StdIo.pipe`. pub fn collectOutput(child: *const Child, io: Io, options: CollectOutputOptions) CollectOutputError!void { - const files: [2]Io.File = .{ child.stdout.?, child.stderr.? }; - const lists: [2]*std.ArrayList(u8) = .{ options.stdout, options.stderr }; - const limits: [2]Io.Limit = .{ options.stdout_limit, options.stderr_limit }; - var dones: [2]bool = .{ false, false }; - var reads: [2]Io.Operation = undefined; - var vecs: [2][1][]u8 = undefined; - while (true) { - for (&reads, &lists, &files, dones, &vecs) |*read, list, file, done, *vec| { - if (done) { - read.* = .noop; - continue; - } - if (options.allocator) |gpa| try list.ensureUnusedCapacity(gpa, 1); - const cap = list.unusedCapacitySlice(); - if (cap.len == 0) return error.StreamTooLong; - vec[0] = cap; - read.* = .{ .file_read_streaming = .{ - .file = file, - .data = vec, - .nonblocking = true, - .result = undefined, - } }; - } - var all_done = true; - var any_canceled = false; - var other_err: (error{StreamTooLong} || Io.File.Reader.Error)!void = {}; - io.vtable.operate(io.userdata, &reads); - for (&reads, &lists, &limits, &dones) |*read, list, limit, *done| { - if (done.*) continue; - const n = read.file_read_streaming.result catch |err| switch (err) { - error.Canceled => { - any_canceled = true; - continue; - }, - error.WouldBlock => continue, - else => |e| { - other_err = e; - continue; - }, - }; - if (n == 0) { - done.* = true; - } else { - all_done = false; - } - list.items.len += n; - if (list.items.len > @intFromEnum(limit)) other_err = error.StreamTooLong; - } - if (any_canceled) return error.Canceled; - try other_err; - if (all_done) return; - } + var stdout = try io.concurrent(collectStream, .{ + io, options.allocator, child.stdout.?, options.stdout, options.stdout_limit, + }); + defer stdout.cancel(io) catch {}; + + var stderr = try io.concurrent(collectStream, .{ + io, options.allocator, child.stderr.?, options.stderr, options.stderr_limit, + }); + defer stderr.cancel(io) catch {}; + + try stdout.await(io); + try stderr.await(io); +} + +fn collectStream(io: Io, gpa: Allocator, file: File, list: *std.ArrayList(u8), limit: Io.Limit) CollectOutputError!void { + var fr = file.readerStreaming(io, &.{}); + fr.interface.appendRemaining(gpa, list, limit) catch |err| switch (err) { + error.ReadFailed => return fr.err.?, + else => |e| return e, + }; } -- 2.54.0 From e2a266e744adaa7ee84a512fdf4cea44686ca0b6 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 8 Jan 2026 16:55:51 -0800 Subject: [PATCH 109/499] Revert "std.process.Child: rewrite using concurrent" This reverts commit 76e1ba8f490812c6e2ebf6f6becd89a71275d21e. --- lib/std/process.zig | 1 - lib/std/process/Child.zig | 81 +++++++++++++++++++++++++++------------ 2 files changed, 56 insertions(+), 26 deletions(-) diff --git a/lib/std/process.zig b/lib/std/process.zig index b203838e3fc2aa4a9293cc54a6a192bdd9991fc6..4a021879a55f257a8b453182972be038aff77138 100644 --- a/lib/std/process.zig +++ b/lib/std/process.zig @@ -455,7 +455,6 @@ pub fn spawnPath(io: Io, dir: Io.Dir, options: SpawnOptions) SpawnError!Child { pub const RunError = CurrentPathError || posix.ReadError || SpawnError || posix.PollError || error{ StreamTooLong, - ConcurrencyUnavailable, }; pub const RunOptions = struct { diff --git a/lib/std/process/Child.zig b/lib/std/process/Child.zig index e64f6106fa99fb6468aa4c0c398933f245f1c083..fc31014520a93b791f10048d17d2ef6046440c66 100644 --- a/lib/std/process/Child.zig +++ b/lib/std/process/Child.zig @@ -125,15 +125,14 @@ pub fn wait(child: *Child, io: Io) WaitError!Term { return io.vtable.childWait(io.userdata, child); } -pub const CollectOutputError = error{ - StreamTooLong, - ConcurrencyUnavailable, -} || Allocator.Error || Io.File.Reader.Error; +pub const CollectOutputError = error{StreamTooLong} || Allocator.Error || Io.File.Reader.Error; pub const CollectOutputOptions = struct { stdout: *std.ArrayList(u8), stderr: *std.ArrayList(u8), - allocator: Allocator, + /// Used for `stdout` and `stderr`. If not provided, only the existing + /// capacity will be used. + allocator: ?Allocator = null, stdout_limit: Io.Limit = .unlimited, stderr_limit: Io.Limit = .unlimited, }; @@ -145,24 +144,56 @@ pub const CollectOutputOptions = struct { /// The process must have been started with stdout and stderr set to /// `process.SpawnOptions.StdIo.pipe`. pub fn collectOutput(child: *const Child, io: Io, options: CollectOutputOptions) CollectOutputError!void { - var stdout = try io.concurrent(collectStream, .{ - io, options.allocator, child.stdout.?, options.stdout, options.stdout_limit, - }); - defer stdout.cancel(io) catch {}; - - var stderr = try io.concurrent(collectStream, .{ - io, options.allocator, child.stderr.?, options.stderr, options.stderr_limit, - }); - defer stderr.cancel(io) catch {}; - - try stdout.await(io); - try stderr.await(io); -} - -fn collectStream(io: Io, gpa: Allocator, file: File, list: *std.ArrayList(u8), limit: Io.Limit) CollectOutputError!void { - var fr = file.readerStreaming(io, &.{}); - fr.interface.appendRemaining(gpa, list, limit) catch |err| switch (err) { - error.ReadFailed => return fr.err.?, - else => |e| return e, - }; + const files: [2]Io.File = .{ child.stdout.?, child.stderr.? }; + const lists: [2]*std.ArrayList(u8) = .{ options.stdout, options.stderr }; + const limits: [2]Io.Limit = .{ options.stdout_limit, options.stderr_limit }; + var dones: [2]bool = .{ false, false }; + var reads: [2]Io.Operation = undefined; + var vecs: [2][1][]u8 = undefined; + while (true) { + for (&reads, &lists, &files, dones, &vecs) |*read, list, file, done, *vec| { + if (done) { + read.* = .noop; + continue; + } + if (options.allocator) |gpa| try list.ensureUnusedCapacity(gpa, 1); + const cap = list.unusedCapacitySlice(); + if (cap.len == 0) return error.StreamTooLong; + vec[0] = cap; + read.* = .{ .file_read_streaming = .{ + .file = file, + .data = vec, + .nonblocking = true, + .result = undefined, + } }; + } + var all_done = true; + var any_canceled = false; + var other_err: (error{StreamTooLong} || Io.File.Reader.Error)!void = {}; + io.vtable.operate(io.userdata, &reads); + for (&reads, &lists, &limits, &dones) |*read, list, limit, *done| { + if (done.*) continue; + const n = read.file_read_streaming.result catch |err| switch (err) { + error.Canceled => { + any_canceled = true; + continue; + }, + error.WouldBlock => continue, + else => |e| { + other_err = e; + continue; + }, + }; + if (n == 0) { + done.* = true; + } else { + all_done = false; + } + list.items.len += n; + if (list.items.len > @intFromEnum(limit)) other_err = error.StreamTooLong; + } + if (any_canceled) return error.Canceled; + try other_err; + if (all_done) return; + } } -- 2.54.0 From 642f329ac91d69d02588ba15714edafb09e709da Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 9 Jan 2026 15:06:50 -0800 Subject: [PATCH 110/499] std.Io: exploring a different batch API proposal --- lib/std/Io.zig | 95 ++++++++++++-- lib/std/Io/File.zig | 5 +- lib/std/Io/Threaded.zig | 256 +++++++++++++++++++++++++------------- lib/std/process.zig | 16 +-- lib/std/process/Child.zig | 92 +++++++------- 5 files changed, 312 insertions(+), 152 deletions(-) diff --git a/lib/std/Io.zig b/lib/std/Io.zig index c52b51e00a4dc996e9f59a04b499449cc732d96a..d916bb699582ccc976007a2952a76da5e7956b54 100644 --- a/lib/std/Io.zig +++ b/lib/std/Io.zig @@ -149,7 +149,10 @@ pub const VTable = struct { futexWaitUncancelable: *const fn (?*anyopaque, ptr: *const u32, expected: u32) void, futexWake: *const fn (?*anyopaque, ptr: *const u32, max_waiters: u32) void, - operate: *const fn (?*anyopaque, []Operation) void, + batch: *const fn (?*anyopaque, []Operation) ConcurrentError!void, + batchSubmit: *const fn (?*anyopaque, *Batch) void, + batchWait: *const fn (?*anyopaque, *Batch, resubmissions: []const usize, Timeout) Batch.WaitError!usize, + batchCancel: *const fn (?*anyopaque, *Batch) void, dirCreateDir: *const fn (?*anyopaque, Dir, []const u8, Dir.Permissions) Dir.CreateDirError!void, dirCreateDirPath: *const fn (?*anyopaque, Dir, []const u8, Dir.Permissions) Dir.CreateDirPathError!Dir.CreatePathStatus, @@ -253,26 +256,96 @@ pub const VTable = struct { }; pub const Operation = union(enum) { - noop, + noop: Noop, file_read_streaming: FileReadStreaming, + pub const Noop = struct { + reserved: [2]usize, + status: Status(void) = .{ .result = {} }, + }; + + /// Returns 0 on end of stream. pub const FileReadStreaming = struct { file: File, data: []const []u8, - /// Causes `result` to return `error.WouldBlock` instead of blocking. - nonblocking: bool = false, - /// Returns 0 on end of stream. - result: File.Reader.Error!usize, + status: Status(File.Reader.Error!usize) = .{ .unstarted = {} }, }; + + pub fn Status(Result: type) type { + return union { + unstarted: void, + pending: usize, + result: Result, + }; + } }; -/// Performs all `operations` in a non-deterministic order. Returns after all -/// `operations` have been completed. The degree to which the operations are -/// performed concurrently is determined by the `Io` implementation. -pub fn operate(io: Io, operations: []Operation) void { - return io.vtable.operate(io.userdata, operations); +/// Performs all `operations` in an unspecified order, concurrently. +/// +/// Returns after all `operations` have been completed. If the operations could +/// not be completed concurrently, returns `error.ConcurrencyUnavailable`. +/// +/// With this API, it is rare for concurrency to not be available. Even a +/// single-threaded `Io` implementation can, for example, take advantage of +/// poll() to implement this. Note that poll() is fallible however. +/// +/// If `operations.len` is one, `error.ConcurrencyUnavailable` is unreachable. +/// +/// On entry, all operations must already have `.status = .unstarted` except +/// noops must have `.status = .{ .result = {} }`, to safety check the state +/// transitions. +/// +/// On return, all operations have `.status = .{ .result = ... }`. +pub fn batch(io: Io, operations: []Operation) ConcurrentError!void { + return io.vtable.batch(io.userdata, operations); +} + +/// Performs one `Operation`. +pub fn operate(io: Io, operation: *Operation) void { + return io.vtable.batch(io.userdata, (operation)[0..1]) catch unreachable; } +/// Submits many operations together without waiting for all of them to +/// complete. +/// +/// This is a low-level abstraction based on `Operation`. For a higher +/// level API that operates on `Future`, see `Select`. +pub const Batch = struct { + operations: []Operation, + index: usize, + reserved: ?*anyopaque, + + pub fn init(operations: []Operation) Batch { + return .{ .operations = operations, .index = 0, .reserved = null }; + } + + /// Submits all non-noop `operations`. + pub fn submit(b: *Batch, io: Io) void { + return io.vtable.batchSubmit(io.userdata, b); + } + + pub const WaitError = ConcurrentError || Cancelable || Timeout.Error; + + /// Resubmits the previously completed or noop-initialized `operations` at + /// indexes given by `resubmissions`. This set of indexes typically will be empty + /// on the first call to `await` since all operations have already been + /// submitted via `async`. + /// + /// Returns the index of a completed `Operation`, or `operations.len` if + /// all operations are completed. + /// + /// When `error.Canceled` is returned, all operations have already completed. + pub fn wait(b: *Batch, io: Io, resubmissions: []const usize, timeout: Timeout) WaitError!usize { + return io.vtable.batchWait(io.userdata, b, resubmissions, timeout); + } + + /// Returns after all `operations` have completed. Each operation + /// independently may or may not have been canceled. + pub fn cancel(b: *Batch, io: Io) void { + return io.vtable.batchCancel(io.userdata, b); + } +}; + pub const Limit = enum(usize) { nothing = 0, unlimited = math.maxInt(usize), diff --git a/lib/std/Io/File.zig b/lib/std/Io/File.zig index 16663eb48488dc8d229a53ffee243e2d294e4b62..f27f249975ea85e9e2917fed2b0a2408406e9eaa 100644 --- a/lib/std/Io/File.zig +++ b/lib/std/Io/File.zig @@ -557,10 +557,9 @@ pub fn readStreaming(file: File, io: Io, buffer: []const []u8) Reader.Error!usiz var operation: Io.Operation = .{ .file_read_streaming = .{ .file = file, .data = buffer, - .result = undefined, } }; - io.vtable.operate(io.userdata, (&operation)[0..1]); - return operation.file_read_streaming.result; + io.operate(&operation); + return operation.file_read_streaming.status.result; } pub const ReadPositionalError = error{ diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 55b3596ee79b47343ea391462488683aa734f418..0eda4e8fdc73d10cc7dded447ecc506e6d7250d5 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -1587,7 +1587,10 @@ pub fn io(t: *Threaded) Io { .futexWaitUncancelable = futexWaitUncancelable, .futexWake = futexWake, - .operate = operate, + .batch = batch, + .batchSubmit = batchSubmit, + .batchWait = batchWait, + .batchCancel = batchCancel, .dirCreateDir = dirCreateDir, .dirCreateDirPath = dirCreateDirPath, @@ -1748,7 +1751,10 @@ pub fn ioBasic(t: *Threaded) Io { .futexWaitUncancelable = futexWaitUncancelable, .futexWake = futexWake, - .operate = operate, + .batch = batch, + .batchSubmit = batchSubmit, + .batchWait = batchWait, + .batchCancel = batchCancel, .dirCreateDir = dirCreateDir, .dirCreateDirPath = dirCreateDirPath, @@ -2450,107 +2456,187 @@ fn futexWake(userdata: ?*anyopaque, ptr: *const u32, max_waiters: u32) void { Thread.futexWake(ptr, max_waiters); } -fn operate(userdata: ?*anyopaque, operations: []Io.Operation) void { +fn batchSubmit(userdata: ?*anyopaque, b: *Io.Batch) void { const t: *Threaded = @ptrCast(@alignCast(userdata)); _ = t; + _ = b; + return; +} + +fn operate(op: *Io.Operation) void { + switch (op.*) { + .noop => {}, + .file_read_streaming => |*o| o.status = .{ .result = fileReadStreaming(o.file, o.data) }, + } +} +fn batchWait( + userdata: ?*anyopaque, + b: *Io.Batch, + resubmissions: []const usize, + timeout: Io.Timeout, +) Io.Batch.WaitError!usize { + _ = resubmissions; + const t: *Threaded = @ptrCast(@alignCast(userdata)); + const operations = b.operations; + if (operations.len == 1) { + operate(&operations[0]); + return b.operations.len; + } if (is_windows) @panic("TODO"); var poll_buffer: [poll_buffer_len]posix.pollfd = undefined; var map_buffer: [poll_buffer_len]u8 = undefined; // poll_buffer index to operations index - var operation_index: usize = 0; - - while (operation_index < operations.len) { - var poll_i: usize = 0; - while (operation_index < operations.len) : (operation_index += 1) { - switch (operations[operation_index]) { - .noop => continue, - .file_read_streaming => |*o| { - if (o.nonblocking) { - o.result = error.WouldBlock; - poll_buffer[poll_i] = .{ - .fd = o.file.handle, - .events = posix.POLL.IN, - .revents = 0, - }; - if (map_buffer.len - poll_i == 0) break; - map_buffer[poll_i] = @intCast(operation_index); - poll_i += 1; - } else { - o.result = fileReadStreaming(o.file, o.data) catch |err| switch (err) { - error.Canceled => { - setOperationsError(operations[operation_index..], error.Canceled); - return; - }, - else => err, - }; - } - }, - } - } + var poll_i: usize = 0; - if (poll_i == 0) { - @branchHint(.likely); - return; + for (operations, 0..) |*op, operation_index| switch (op.*) { + .noop => continue, + .file_read_streaming => |*o| { + if (poll_buffer.len - poll_i == 0) return error.ConcurrencyUnavailable; + poll_buffer[poll_i] = .{ + .fd = o.file.handle, + .events = posix.POLL.IN, + .revents = 0, + }; + map_buffer[poll_i] = @intCast(operation_index); + poll_i += 1; + }, + }; + + if (poll_i == 0) return operations.len; + + const t_io = ioBasic(t); + const deadline = timeout.toDeadline(t_io) catch return error.UnsupportedClock; + const max_poll_ms = std.math.maxInt(i32); + + while (true) { + const timeout_ms: i32 = if (deadline) |d| t: { + const duration = d.durationFromNow(t_io) catch return error.UnsupportedClock; + if (duration.raw.nanoseconds <= 0) return error.Timeout; + break :t @intCast(@min(max_poll_ms, duration.raw.toMilliseconds())); + } else -1; + const syscall = try Syscall.start(); + const rc = posix.system.poll(&poll_buffer, poll_i, timeout_ms); + syscall.finish(); + switch (posix.errno(rc)) { + .SUCCESS => { + if (rc == 0) { + // Although spurious timeouts are OK, when no deadline is + // passed we must not return `error.Timeout`. + if (deadline == null) continue; + return error.Timeout; + } + for (poll_buffer[0..poll_i], map_buffer[0..poll_i]) |*poll_fd, i| { + if (poll_fd.revents == 0) continue; + operate(&operations[i]); + return i; + } + }, + .INTR => continue, + else => return error.ConcurrencyUnavailable, } + } +} + +fn batchCancel(userdata: ?*anyopaque, b: *Io.Batch) void { + const t: *Threaded = @ptrCast(@alignCast(userdata)); + _ = t; + _ = b; + return; +} + +fn batch(userdata: ?*anyopaque, operations: []Io.Operation) Io.ConcurrentError!void { + const t: *Threaded = @ptrCast(@alignCast(userdata)); + _ = t; + + if (operations.len == 1) { + @branchHint(.likely); + return operate(&operations[0]); + } - while (true) { - const syscall = Syscall.start() catch |err| switch (err) { - error.Canceled => { - setPollOperationsError(operations, map_buffer[0..poll_i], error.Canceled); - setOperationsError(operations[operation_index..], error.Canceled); - return; - }, + if (is_windows) @panic("TODO"); + + var poll_buffer: [poll_buffer_len]posix.pollfd = undefined; + var map_buffer: [poll_buffer_len]u8 = undefined; // poll_buffer index to operations index + var poll_i: usize = 0; + + for (operations, 0..) |*op, operation_index| switch (op.*) { + .noop => continue, + .file_read_streaming => |*o| { + if (poll_buffer.len - poll_i == 0) return error.ConcurrencyUnavailable; + poll_buffer[poll_i] = .{ + .fd = o.file.handle, + .events = posix.POLL.IN, + .revents = 0, }; - const poll_rc = posix.system.poll(&poll_buffer, poll_i, -1); - syscall.finish(); - switch (posix.errno(poll_rc)) { - .SUCCESS => { - if (poll_rc == 0) { - // Spurious timeout; handle same as INTR. - continue; - } - for (poll_buffer[0..poll_i], map_buffer[0..poll_i]) |*poll_fd, i| { - if (poll_fd.revents == 0) continue; - switch (operations[i]) { - .noop => unreachable, - .file_read_streaming => |*o| { - o.result = fileReadStreaming(o.file, o.data); - }, - } - } - break; - }, - .INTR => continue, - .NOMEM => { - setPollOperationsError(operations, map_buffer[0..poll_i], error.SystemResources); - break; - }, - else => { - setPollOperationsError(operations, map_buffer[0..poll_i], error.Unexpected); - break; - }, - } + map_buffer[poll_i] = @intCast(operation_index); + poll_i += 1; + }, + }; + + const polls = poll_buffer[0..poll_i]; + const map = map_buffer[0..poll_i]; + + var pending = poll_i; + while (pending > 1) { + const syscall = Syscall.start() catch |err| switch (err) { + error.Canceled => { + if (!setOperationsError(operations, polls, map, error.Canceled)) + recancelInner(); + return; + }, + }; + const rc = posix.system.poll(polls.ptr, polls.len, -1); + syscall.finish(); + switch (posix.errno(rc)) { + .SUCCESS => { + if (rc == 0) { + // Spurious timeout; handle the same as INTR. + continue; + } + for (polls, map) |*poll_fd, i| { + if (poll_fd.revents == 0) continue; + poll_fd.fd = -1; + pending -= 1; + operate(&operations[i]); + } + }, + .INTR => continue, + .NOMEM => { + assert(setOperationsError(operations, polls, map, error.SystemResources)); + return; + }, + else => { + assert(setOperationsError(operations, polls, map, error.Unexpected)); + return; + }, } } + + if (pending == 1) for (poll_buffer[0..poll_i], map_buffer[0..poll_i]) |*poll_fd, i| { + if (poll_fd.fd == -1) continue; + operate(&operations[i]); + }; } -fn setPollOperationsError( +fn setOperationsError( operations: []Io.Operation, + polls: []const posix.pollfd, map: []const u8, err: error{ Canceled, SystemResources, Unexpected }, -) void { - for (map) |operation_index| switch (operations[operation_index]) { - .noop => unreachable, - inline else => |*o| o.result = err, - }; -} - -fn setOperationsError(operations: []Io.Operation, err: error{ Canceled, SystemResources, Unexpected }) void { - for (operations) |*op| switch (op.*) { - .noop => unreachable, - inline else => |*o| o.result = err, - }; +) bool { + var marked = false; + for (polls, map) |*poll_fd, i| { + if (poll_fd.fd == -1) continue; + switch (operations[i]) { + .noop => unreachable, + inline else => |*o| { + o.status = .{ .result = err }; + marked = true; + }, + } + } + return marked; } const dirCreateDir = switch (native_os) { diff --git a/lib/std/process.zig b/lib/std/process.zig index 4a021879a55f257a8b453182972be038aff77138..b5de41f5d88551df6a7c8a7af173b104838511d3 100644 --- a/lib/std/process.zig +++ b/lib/std/process.zig @@ -453,9 +453,7 @@ pub fn spawnPath(io: Io, dir: Io.Dir, options: SpawnOptions) SpawnError!Child { return io.vtable.processSpawnPath(io.userdata, dir, options); } -pub const RunError = CurrentPathError || posix.ReadError || SpawnError || posix.PollError || error{ - StreamTooLong, -}; +pub const RunError = SpawnError || Child.CollectOutputError; pub const RunOptions = struct { argv: []const []const u8, @@ -535,13 +533,15 @@ pub fn run(gpa: Allocator, io: Io, options: RunOptions) RunError!RunResult { const term = try child.wait(io); - const owned_stdout = try stdout.toOwnedSlice(gpa); - errdefer gpa.free(owned_stdout); - const owned_stderr = try stderr.toOwnedSlice(gpa); + const stdout_slice = try stdout.toOwnedSlice(gpa); + errdefer gpa.free(stdout_slice); + + const stderr_slice = try stderr.toOwnedSlice(gpa); + errdefer gpa.free(stderr_slice); return .{ - .stdout = owned_stdout, - .stderr = owned_stderr, + .stdout = stdout_slice, + .stderr = stderr_slice, .term = term, }; } diff --git a/lib/std/process/Child.zig b/lib/std/process/Child.zig index fc31014520a93b791f10048d17d2ef6046440c66..9d31b7708099cf2b2c5b2810f9ec107f8d13f098 100644 --- a/lib/std/process/Child.zig +++ b/lib/std/process/Child.zig @@ -125,7 +125,9 @@ pub fn wait(child: *Child, io: Io) WaitError!Term { return io.vtable.childWait(io.userdata, child); } -pub const CollectOutputError = error{StreamTooLong} || Allocator.Error || Io.File.Reader.Error; +pub const CollectOutputError = error{ + StreamTooLong, +} || Io.ConcurrentError || Allocator.Error || Io.File.Reader.Error || Io.Timeout.Error; pub const CollectOutputOptions = struct { stdout: *std.ArrayList(u8), @@ -135,6 +137,7 @@ pub const CollectOutputOptions = struct { allocator: ?Allocator = null, stdout_limit: Io.Limit = .unlimited, stderr_limit: Io.Limit = .unlimited, + timeout: Io.Timeout = .none, }; /// Collect the output from the process's stdout and stderr. Will return once @@ -144,56 +147,55 @@ pub const CollectOutputOptions = struct { /// The process must have been started with stdout and stderr set to /// `process.SpawnOptions.StdIo.pipe`. pub fn collectOutput(child: *const Child, io: Io, options: CollectOutputOptions) CollectOutputError!void { - const files: [2]Io.File = .{ child.stdout.?, child.stderr.? }; const lists: [2]*std.ArrayList(u8) = .{ options.stdout, options.stderr }; const limits: [2]Io.Limit = .{ options.stdout_limit, options.stderr_limit }; - var dones: [2]bool = .{ false, false }; - var reads: [2]Io.Operation = undefined; + + if (options.allocator) |gpa| { + for (lists) |list| try list.ensureUnusedCapacity(gpa, 1); + } else { + for (lists) |list| { + if (list.unusedCapacitySlice().len == 0) + return error.StreamTooLong; + } + } + var vecs: [2][1][]u8 = undefined; - while (true) { - for (&reads, &lists, &files, dones, &vecs) |*read, list, file, done, *vec| { - if (done) { - read.* = .noop; - continue; - } + for (lists, &vecs) |list, *vec| + vec[0] = list.unusedCapacitySlice(); + + var operations: [2]Io.Operation = .{ + .{ .file_read_streaming = .{ + .file = child.stdout.?, + .data = &vecs[0], + } }, + .{ .file_read_streaming = .{ + .file = child.stderr.?, + .data = &vecs[1], + } }, + }; + + var batch: Io.Batch = .init(&operations); + batch.submit(io); + defer batch.cancel(io); + + var pending = operations.len; + var retry_index: ?usize = null; + while (pending > 0) { + const resubmissions: []const usize = if (retry_index) |i| &.{i} else &.{}; + const index = try batch.wait(io, resubmissions, options.timeout); + const n = try operations[index].file_read_streaming.status.result; + if (n == 0) { + pending -= 1; + } else { + retry_index = index; + const list = lists[index]; + const limit = limits[index]; + list.items.len += n; + if (list.items.len >= @intFromEnum(limit)) return error.StreamTooLong; if (options.allocator) |gpa| try list.ensureUnusedCapacity(gpa, 1); const cap = list.unusedCapacitySlice(); if (cap.len == 0) return error.StreamTooLong; - vec[0] = cap; - read.* = .{ .file_read_streaming = .{ - .file = file, - .data = vec, - .nonblocking = true, - .result = undefined, - } }; + vecs[index][0] = cap; } - var all_done = true; - var any_canceled = false; - var other_err: (error{StreamTooLong} || Io.File.Reader.Error)!void = {}; - io.vtable.operate(io.userdata, &reads); - for (&reads, &lists, &limits, &dones) |*read, list, limit, *done| { - if (done.*) continue; - const n = read.file_read_streaming.result catch |err| switch (err) { - error.Canceled => { - any_canceled = true; - continue; - }, - error.WouldBlock => continue, - else => |e| { - other_err = e; - continue; - }, - }; - if (n == 0) { - done.* = true; - } else { - all_done = false; - } - list.items.len += n; - if (list.items.len > @intFromEnum(limit)) other_err = error.StreamTooLong; - } - if (any_canceled) return error.Canceled; - try other_err; - if (all_done) return; } } -- 2.54.0 From 23d25dbb9e4e469eb2e59d50bd888b7a61ffb876 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 9 Jan 2026 19:21:59 -0800 Subject: [PATCH 111/499] std.process.Child.collectOutput: change back to other impl this one avoids calling poll() more than necessary --- lib/std/Io.zig | 2 +- lib/std/process/Child.zig | 86 +++++++++++++++++++-------------------- 2 files changed, 43 insertions(+), 45 deletions(-) diff --git a/lib/std/Io.zig b/lib/std/Io.zig index d916bb699582ccc976007a2952a76da5e7956b54..2f34dc07e4c4100adc2a521d0f830fb56642882a 100644 --- a/lib/std/Io.zig +++ b/lib/std/Io.zig @@ -260,7 +260,7 @@ pub const Operation = union(enum) { file_read_streaming: FileReadStreaming, pub const Noop = struct { - reserved: [2]usize, + reserved: [2]usize = .{ 0, 0 }, status: Status(void) = .{ .result = {} }, }; diff --git a/lib/std/process/Child.zig b/lib/std/process/Child.zig index 9d31b7708099cf2b2c5b2810f9ec107f8d13f098..364c52446f8691a3e7505e89a40ca514125c5e59 100644 --- a/lib/std/process/Child.zig +++ b/lib/std/process/Child.zig @@ -137,7 +137,6 @@ pub const CollectOutputOptions = struct { allocator: ?Allocator = null, stdout_limit: Io.Limit = .unlimited, stderr_limit: Io.Limit = .unlimited, - timeout: Io.Timeout = .none, }; /// Collect the output from the process's stdout and stderr. Will return once @@ -147,55 +146,54 @@ pub const CollectOutputOptions = struct { /// The process must have been started with stdout and stderr set to /// `process.SpawnOptions.StdIo.pipe`. pub fn collectOutput(child: *const Child, io: Io, options: CollectOutputOptions) CollectOutputError!void { + const files: [2]Io.File = .{ child.stdout.?, child.stderr.? }; const lists: [2]*std.ArrayList(u8) = .{ options.stdout, options.stderr }; const limits: [2]Io.Limit = .{ options.stdout_limit, options.stderr_limit }; - - if (options.allocator) |gpa| { - for (lists) |list| try list.ensureUnusedCapacity(gpa, 1); - } else { - for (lists) |list| { - if (list.unusedCapacitySlice().len == 0) - return error.StreamTooLong; - } - } - + var dones: [2]bool = .{ false, false }; + var reads: [2]Io.Operation = undefined; var vecs: [2][1][]u8 = undefined; - for (lists, &vecs) |list, *vec| - vec[0] = list.unusedCapacitySlice(); - - var operations: [2]Io.Operation = .{ - .{ .file_read_streaming = .{ - .file = child.stdout.?, - .data = &vecs[0], - } }, - .{ .file_read_streaming = .{ - .file = child.stderr.?, - .data = &vecs[1], - } }, - }; - - var batch: Io.Batch = .init(&operations); - batch.submit(io); - defer batch.cancel(io); - - var pending = operations.len; - var retry_index: ?usize = null; - while (pending > 0) { - const resubmissions: []const usize = if (retry_index) |i| &.{i} else &.{}; - const index = try batch.wait(io, resubmissions, options.timeout); - const n = try operations[index].file_read_streaming.status.result; - if (n == 0) { - pending -= 1; - } else { - retry_index = index; - const list = lists[index]; - const limit = limits[index]; - list.items.len += n; - if (list.items.len >= @intFromEnum(limit)) return error.StreamTooLong; + while (true) { + for (&reads, &lists, &files, dones, &vecs) |*read, list, file, done, *vec| { + if (done) { + read.* = .{ .noop = .{} }; + continue; + } if (options.allocator) |gpa| try list.ensureUnusedCapacity(gpa, 1); const cap = list.unusedCapacitySlice(); if (cap.len == 0) return error.StreamTooLong; - vecs[index][0] = cap; + vec[0] = cap; + read.* = .{ .file_read_streaming = .{ + .file = file, + .data = vec, + } }; } + var all_done = true; + var any_canceled = false; + var other_err: (error{StreamTooLong} || Io.File.Reader.Error)!void = {}; + try io.vtable.batch(io.userdata, &reads); + for (&reads, &lists, &limits, &dones) |*read, list, limit, *done| { + if (done.*) continue; + const n = read.file_read_streaming.status.result catch |err| switch (err) { + error.Canceled => { + any_canceled = true; + continue; + }, + error.WouldBlock => continue, + else => |e| { + other_err = e; + continue; + }, + }; + if (n == 0) { + done.* = true; + } else { + all_done = false; + } + list.items.len += n; + if (list.items.len > @intFromEnum(limit)) other_err = error.StreamTooLong; + } + if (any_canceled) return error.Canceled; + try other_err; + if (all_done) return; } } -- 2.54.0 From 0a379513afdf807c357993ac4508449ec933c55b Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 9 Jan 2026 20:46:51 -0800 Subject: [PATCH 112/499] std.Io.Threaded: super broken Windows impl of batch this is a cry for help --- lib/std/Io/Threaded.zig | 83 ++++++++++++++++++++++++++++++++--------- 1 file changed, 66 insertions(+), 17 deletions(-) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 0eda4e8fdc73d10cc7dded447ecc506e6d7250d5..f7b4b73b33ae4150b612aba2b97cfc33230abc57 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -2547,14 +2547,13 @@ fn batchCancel(userdata: ?*anyopaque, b: *Io.Batch) void { fn batch(userdata: ?*anyopaque, operations: []Io.Operation) Io.ConcurrentError!void { const t: *Threaded = @ptrCast(@alignCast(userdata)); - _ = t; if (operations.len == 1) { @branchHint(.likely); return operate(&operations[0]); } - if (is_windows) @panic("TODO"); + if (is_windows) return batchWindows(t, operations); var poll_buffer: [poll_buffer_len]posix.pollfd = undefined; var map_buffer: [poll_buffer_len]u8 = undefined; // poll_buffer index to operations index @@ -2578,7 +2577,7 @@ fn batch(userdata: ?*anyopaque, operations: []Io.Operation) Io.ConcurrentError!v const map = map_buffer[0..poll_i]; var pending = poll_i; - while (pending > 1) { + while (pending > 0) { const syscall = Syscall.start() catch |err| switch (err) { error.Canceled => { if (!setOperationsError(operations, polls, map, error.Canceled)) @@ -2589,17 +2588,11 @@ fn batch(userdata: ?*anyopaque, operations: []Io.Operation) Io.ConcurrentError!v const rc = posix.system.poll(polls.ptr, polls.len, -1); syscall.finish(); switch (posix.errno(rc)) { - .SUCCESS => { - if (rc == 0) { - // Spurious timeout; handle the same as INTR. - continue; - } - for (polls, map) |*poll_fd, i| { - if (poll_fd.revents == 0) continue; - poll_fd.fd = -1; - pending -= 1; - operate(&operations[i]); - } + .SUCCESS => for (polls, map) |*poll_fd, i| { + if (poll_fd.revents == 0) continue; + poll_fd.fd = -1; + pending -= 1; + operate(&operations[i]); }, .INTR => continue, .NOMEM => { @@ -2612,11 +2605,67 @@ fn batch(userdata: ?*anyopaque, operations: []Io.Operation) Io.ConcurrentError!v }, } } +} - if (pending == 1) for (poll_buffer[0..poll_i], map_buffer[0..poll_i]) |*poll_fd, i| { - if (poll_fd.fd == -1) continue; - operate(&operations[i]); +fn batchWindows(t: *Threaded, operations: []Io.Operation) Io.ConcurrentError!void { + _ = t; + var overlapped_buffer: [poll_buffer_len]windows.OVERLAPPED = undefined; + var handles_buffer: [poll_buffer_len]windows.HANDLE = undefined; + var map_buffer: [poll_buffer_len]u8 = undefined; // handles_buffer index to operations index + var buffer_i: usize = 0; + + for (operations, 0..) |*op, operation_index| switch (op.*) { + .noop => continue, + .file_read_streaming => |*o| { + if (handles_buffer.len - buffer_i == 0) return error.ConcurrencyUnavailable; + + const overlapped = &overlapped_buffer[buffer_i]; + overlapped.* = .{ + .Internal = 0, + .InternalHigh = 0, + .DUMMYUNIONNAME = .{ + .DUMMYSTRUCTNAME = .{ + .Offset = 0, + .OffsetHigh = 0, + }, + .Pointer = null, + }, + .hEvent = null, + }; + var n: windows.DWORD = undefined; + const buf = o.data[0]; + if (windows.kernel32.ReadFile(o.file.handle, buf.ptr, buf.len, &n, overlapped) == 0) { + @panic("TODO"); + } + handles_buffer[buffer_i] = o.file.handle; + map_buffer[buffer_i] = @intCast(operation_index); + buffer_i += 1; + }, }; + + const handles = handles_buffer[0..buffer_i]; + const map = map_buffer[0..buffer_i]; + var pending = buffer_i; + + while (pending > 0) { + const syscall: Syscall = try .start(); + const index = windows.WaitForMultipleObjectsEx(handles, false, windows.INFINITE, true); + syscall.finish(); + var n: windows.DWORD = undefined; + if (0 == windows.kernel32.GetOverlappedResult(handles[index], overlapped_buffer[index], &n, 0)) { + switch (windows.GetLastError()) { + .BROKEN_PIPE => @panic("TODO"), + .OPERATION_ABORTED => @panic("TODO"), + else => @panic("TODO"), + } + } else switch (operations[map[index]]) { + .noop => unreachable, + .file_read_streaming => |*o| { + o.status = .{ .result = n }; + pending -= 1; + }, + } + } } fn setOperationsError( -- 2.54.0 From 8146ccfeccd17e6ee5adaa59112899a89ad409bd Mon Sep 17 00:00:00 2001 From: Jacob Young Date: Sat, 10 Jan 2026 15:34:36 -0500 Subject: [PATCH 113/499] Io: add ring to `Batch` API --- lib/std/Io.zig | 171 ++++++++++++++++++++++++++----------- lib/std/Io/File.zig | 2 +- lib/std/Io/File/Reader.zig | 22 +---- lib/std/Io/Threaded.zig | 151 +++++++++++++++++++++----------- lib/std/process/Child.zig | 72 +++++++--------- 5 files changed, 259 insertions(+), 159 deletions(-) diff --git a/lib/std/Io.zig b/lib/std/Io.zig index 2f34dc07e4c4100adc2a521d0f830fb56642882a..b503979fda68d5db200da632970f114f40de819d 100644 --- a/lib/std/Io.zig +++ b/lib/std/Io.zig @@ -149,9 +149,8 @@ pub const VTable = struct { futexWaitUncancelable: *const fn (?*anyopaque, ptr: *const u32, expected: u32) void, futexWake: *const fn (?*anyopaque, ptr: *const u32, max_waiters: u32) void, - batch: *const fn (?*anyopaque, []Operation) ConcurrentError!void, - batchSubmit: *const fn (?*anyopaque, *Batch) void, - batchWait: *const fn (?*anyopaque, *Batch, resubmissions: []const usize, Timeout) Batch.WaitError!usize, + operate: *const fn (?*anyopaque, *Operation) Cancelable!void, + batchWait: *const fn (?*anyopaque, *Batch, Timeout) Batch.WaitError!void, batchCancel: *const fn (?*anyopaque, *Batch) void, dirCreateDir: *const fn (?*anyopaque, Dir, []const u8, Dir.Permissions) Dir.CreateDirError!void, @@ -261,48 +260,50 @@ pub const Operation = union(enum) { pub const Noop = struct { reserved: [2]usize = .{ 0, 0 }, - status: Status(void) = .{ .result = {} }, + status: Status(void) = .{ .unstarted = {} }, }; /// Returns 0 on end of stream. pub const FileReadStreaming = struct { file: File, data: []const []u8, - status: Status(File.Reader.Error!usize) = .{ .unstarted = {} }, + status: Status(Error!usize) = .{ .unstarted = {} }, + + pub const Error = error{ + InputOutput, + SystemResources, + /// Trying to read a directory file descriptor as if it were a file. + IsDir, + BrokenPipe, + ConnectionResetByPeer, + /// File was not opened with read capability. + NotOpenForReading, + SocketUnconnected, + /// Non-blocking has been enabled, and reading from the file descriptor + /// would block. + WouldBlock, + /// In WASI, this error occurs when the file descriptor does + /// not hold the required rights to read from it. + AccessDenied, + /// Unable to read file due to lock. Depending on the `Io` implementation, + /// reading from a locked file may return this error, or may ignore the + /// lock. + LockViolation, + } || Io.UnexpectedError; }; pub fn Status(Result: type) type { return union { unstarted: void, - pending: usize, + pending: *Batch, result: Result, }; } }; -/// Performs all `operations` in an unspecified order, concurrently. -/// -/// Returns after all `operations` have been completed. If the operations could -/// not be completed concurrently, returns `error.ConcurrencyUnavailable`. -/// -/// With this API, it is rare for concurrency to not be available. Even a -/// single-threaded `Io` implementation can, for example, take advantage of -/// poll() to implement this. Note that poll() is fallible however. -/// -/// If `operations.len` is one, `error.ConcurrencyUnavailable` is unreachable. -/// -/// On entry, all operations must already have `.status = .unstarted` except -/// noops must have `.status = .{ .result = {} }`, to safety check the state -/// transitions. -/// -/// On return, all operations have `.status = .{ .result = ... }`. -pub fn batch(io: Io, operations: []Operation) ConcurrentError!void { - return io.vtable.batch(io.userdata, operations); -} - /// Performs one `Operation`. -pub fn operate(io: Io, operation: *Operation) void { - return io.vtable.batch(io.userdata, (operation)[0..1]) catch unreachable; +pub fn operate(io: Io, operation: *Operation) Cancelable!void { + return io.vtable.operate(io.userdata, operation) catch unreachable; } /// Submits many operations together without waiting for all of them to @@ -312,35 +313,107 @@ pub fn operate(io: Io, operation: *Operation) void { /// level API that operates on `Future`, see `Select`. pub const Batch = struct { operations: []Operation, - index: usize, - reserved: ?*anyopaque, + ring: [*]u32, + user: struct { + submit_tail: RingIndex, + complete_head: RingIndex, + complete_tail: RingIndex, + }, + impl: struct { + submit_head: RingIndex, + submit_tail: RingIndex, + complete_tail: RingIndex, + reserved: ?*anyopaque, + }, - pub fn init(operations: []Operation) Batch { - return .{ .operations = operations, .index = 0, .reserved = null }; - } + pub const RingIndex = enum(u32) { + _, - /// Submits all non-noop `operations`. - pub fn submit(b: *Batch, io: Io) void { - return io.vtable.batchSubmit(io.userdata, b); - } + pub fn index(ri: RingIndex, len: u31) u31 { + const i = @intFromEnum(ri); + assert(i < @as(u32, len) * 2); + return @intCast(if (i < len) i else i - len); + } + + pub fn prev(ri: RingIndex, len: u31) RingIndex { + const i = @intFromEnum(ri); + const double_len = @as(u32, len) * 2; + assert(i <= double_len); + return @enumFromInt((if (i > 0) i else double_len) - 1); + } + + pub fn next(ri: RingIndex, len: u31) RingIndex { + const i = @intFromEnum(ri) + 1; + const double_len = @as(u32, len) * 2; + assert(i <= double_len); + return @enumFromInt(if (i < double_len) i else 0); + } + }; pub const WaitError = ConcurrentError || Cancelable || Timeout.Error; - /// Resubmits the previously completed or noop-initialized `operations` at - /// indexes given by `resubmissions`. This set of indexes typically will be empty - /// on the first call to `await` since all operations have already been - /// submitted via `async`. - /// - /// Returns the index of a completed `Operation`, or `operations.len` if - /// all operations are completed. + pub fn init(operations: []Operation, ring: []u32) Batch { + const len: u31 = @intCast(operations.len); + assert(ring.len == len); + return .{ + .operations = operations, + .ring = ring.ptr, + .user = .{ + .submit_tail = @enumFromInt(0), + .complete_head = @enumFromInt(0), + .complete_tail = @enumFromInt(0), + }, + .impl = .{ + .submit_head = @enumFromInt(0), + .submit_tail = @enumFromInt(0), + .complete_tail = @enumFromInt(0), + .reserved = null, + }, + }; + } + + /// Adds `b.operations[operation]` to the list of submitted operations + /// that will be performed when `wait` is called. + pub fn add(b: *Batch, operation: usize) void { + const tail = b.user.submit_tail; + const len: u31 = @intCast(b.operations.len); + b.user.submit_tail = tail.next(len); + b.ring[0..len][tail.index(len)] = @intCast(operation); + } + + fn flush(b: *Batch) void { + @atomicStore(RingIndex, &b.impl.submit_tail, b.user.submit_tail, .release); + } + + /// Returns `operation` such that `b.operations[operation]` has completed. + /// Returns `null` when `wait` should be called. + pub fn next(b: *Batch) ?u32 { + const head = b.user.complete_head; + if (head == b.user.complete_tail) { + @branchHint(.unlikely); + b.flush(); + const tail = @atomicLoad(RingIndex, &b.impl.complete_tail, .acquire); + if (head == tail) { + @branchHint(.unlikely); + return null; + } + assert(head != tail); + b.user.complete_tail = tail; + } + const len: u31 = @intCast(b.operations.len); + b.user.complete_head = head.next(len); + return b.ring[0..len][head.index(len)]; + } + + /// Starts work on any submitted operations and returns when at least one has completeed. /// - /// When `error.Canceled` is returned, all operations have already completed. - pub fn wait(b: *Batch, io: Io, resubmissions: []const usize, timeout: Timeout) WaitError!usize { - return io.vtable.batchWait(io.userdata, b, resubmissions, timeout); + /// Returns `error.Timeout` if `timeout` expires first. + pub fn wait(b: *Batch, io: Io, timeout: Timeout) WaitError!void { + return io.vtable.batchWait(io.userdata, b, timeout); } - /// Returns after all `operations` have completed. Each operation - /// independently may or may not have been canceled. + /// Returns after all `operations` have completed. Operations which have not completed + /// after this function returns were successfully dropped and had no side effects. pub fn cancel(b: *Batch, io: Io) void { return io.vtable.batchCancel(io.userdata, b); } diff --git a/lib/std/Io/File.zig b/lib/std/Io/File.zig index f27f249975ea85e9e2917fed2b0a2408406e9eaa..cc7042d443d0ca250e3a853012d4241462e908b5 100644 --- a/lib/std/Io/File.zig +++ b/lib/std/Io/File.zig @@ -558,7 +558,7 @@ pub fn readStreaming(file: File, io: Io, buffer: []const []u8) Reader.Error!usiz .file = file, .data = buffer, } }; - io.operate(&operation); + try io.operate(&operation); return operation.file_read_streaming.status.result; } diff --git a/lib/std/Io/File/Reader.zig b/lib/std/Io/File/Reader.zig index d3d1c05e3f30edf81614e0109239cb547668043a..7703521d7ebb8b177d40335e06490af4a74cfd69 100644 --- a/lib/std/Io/File/Reader.zig +++ b/lib/std/Io/File/Reader.zig @@ -26,27 +26,7 @@ size_err: ?SizeError = null, seek_err: ?SeekError = null, interface: Io.Reader, -pub const Error = error{ - InputOutput, - SystemResources, - /// Trying to read a directory file descriptor as if it were a file. - IsDir, - BrokenPipe, - ConnectionResetByPeer, - /// File was not opened with read capability. - NotOpenForReading, - SocketUnconnected, - /// Non-blocking has been enabled, and reading from the file descriptor - /// would block. - WouldBlock, - /// In WASI, this error occurs when the file descriptor does - /// not hold the required rights to read from it. - AccessDenied, - /// Unable to read file due to lock. Depending on the `Io` implementation, - /// reading from a locked file may return this error, or may ignore the - /// lock. - LockViolation, -} || Io.Cancelable || Io.UnexpectedError; +pub const Error = Io.Operation.FileReadStreaming.Error || Io.Cancelable; pub const SizeError = File.StatError || error{ /// Occurs if, for example, the file handle is a network socket and therefore does not have a size. diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index f7b4b73b33ae4150b612aba2b97cfc33230abc57..fefa8fe84dcacd0a61007145e0a15d9fd0e67e82 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -1587,8 +1587,7 @@ pub fn io(t: *Threaded) Io { .futexWaitUncancelable = futexWaitUncancelable, .futexWake = futexWake, - .batch = batch, - .batchSubmit = batchSubmit, + .operate = operate, .batchWait = batchWait, .batchCancel = batchCancel, @@ -1751,8 +1750,7 @@ pub fn ioBasic(t: *Threaded) Io { .futexWaitUncancelable = futexWaitUncancelable, .futexWake = futexWake, - .batch = batch, - .batchSubmit = batchSubmit, + .operate = operate, .batchWait = batchWait, .batchCancel = batchCancel, @@ -2456,59 +2454,82 @@ fn futexWake(userdata: ?*anyopaque, ptr: *const u32, max_waiters: u32) void { Thread.futexWake(ptr, max_waiters); } -fn batchSubmit(userdata: ?*anyopaque, b: *Io.Batch) void { +fn operate(userdata: ?*anyopaque, op: *Io.Operation) Io.Cancelable!void { const t: *Threaded = @ptrCast(@alignCast(userdata)); _ = t; - _ = b; - return; -} - -fn operate(op: *Io.Operation) void { switch (op.*) { - .noop => {}, - .file_read_streaming => |*o| o.status = .{ .result = fileReadStreaming(o.file, o.data) }, + .noop => |*o| { + _ = o.status.unstarted; + o.status = .{ .result = {} }; + }, + .file_read_streaming => |*o| { + _ = o.status.unstarted; + o.status = .{ .result = fileReadStreaming(o.file, o.data) catch |err| switch (err) { + error.Canceled => return error.Canceled, + else => |e| e, + } }; + }, } } -fn batchWait( - userdata: ?*anyopaque, - b: *Io.Batch, - resubmissions: []const usize, - timeout: Io.Timeout, -) Io.Batch.WaitError!usize { - _ = resubmissions; +fn batchWait(userdata: ?*anyopaque, b: *Io.Batch, timeout: Io.Timeout) Io.Batch.WaitError!void { const t: *Threaded = @ptrCast(@alignCast(userdata)); const operations = b.operations; - if (operations.len == 1) { - operate(&operations[0]); - return b.operations.len; + const len: u31 = @intCast(operations.len); + const ring = b.ring[0..len]; + var submit_head = b.impl.submit_head; + const submit_tail = b.user.submit_tail; + b.impl.submit_tail = submit_tail; + var complete_tail = b.impl.complete_tail; + var map_buffer: [poll_buffer_len]u32 = undefined; // poll_buffer index to operations index + var poll_i: usize = 0; + defer { + for (map_buffer[0..poll_i]) |op| { + submit_head = submit_head.prev(len); + ring[submit_head.index(len)] = op; + } + b.impl.submit_head = submit_head; + b.impl.complete_tail = complete_tail; + b.user.complete_tail = complete_tail; } if (is_windows) @panic("TODO"); - var poll_buffer: [poll_buffer_len]posix.pollfd = undefined; - var map_buffer: [poll_buffer_len]u8 = undefined; // poll_buffer index to operations index - var poll_i: usize = 0; - - for (operations, 0..) |*op, operation_index| switch (op.*) { - .noop => continue, - .file_read_streaming => |*o| { - if (poll_buffer.len - poll_i == 0) return error.ConcurrencyUnavailable; - poll_buffer[poll_i] = .{ - .fd = o.file.handle, - .events = posix.POLL.IN, - .revents = 0, - }; - map_buffer[poll_i] = @intCast(operation_index); - poll_i += 1; + while (submit_head != submit_tail) : (submit_head = submit_head.next(len)) { + const op = ring[submit_head.index(len)]; + const operation = &operations[op]; + switch (operation.*) { + else => { + try operate(t, operation); + ring[complete_tail.index(len)] = op; + complete_tail = complete_tail.next(len); + }, + .file_read_streaming => |*o| { + _ = o.status.unstarted; + if (poll_buffer.len - poll_i == 0) return error.ConcurrencyUnavailable; + poll_buffer[poll_i] = .{ + .fd = o.file.handle, + .events = posix.POLL.IN, + .revents = 0, + }; + map_buffer[poll_i] = op; + poll_i += 1; + }, + } + } + switch (poll_i) { + 0 => return, + 1 => if (timeout == .none) { + const op = map_buffer[0]; + try operate(t, &operations[op]); + ring[complete_tail.index(len)] = op; + complete_tail = complete_tail.next(len); + return; }, - }; - - if (poll_i == 0) return operations.len; - + else => {}, + } const t_io = ioBasic(t); const deadline = timeout.toDeadline(t_io) catch return error.UnsupportedClock; const max_poll_ms = std.math.maxInt(i32); - while (true) { const timeout_ms: i32 = if (deadline) |d| t: { const duration = d.durationFromNow(t_io) catch return error.UnsupportedClock; @@ -2526,11 +2547,24 @@ fn batchWait( if (deadline == null) continue; return error.Timeout; } - for (poll_buffer[0..poll_i], map_buffer[0..poll_i]) |*poll_fd, i| { - if (poll_fd.revents == 0) continue; - operate(&operations[i]); - return i; + var canceled = false; + for (poll_buffer[0..poll_i], map_buffer[0..poll_i]) |*poll_fd, op| { + if (poll_fd.revents == 0) { + submit_head = submit_head.prev(len); + ring[submit_head.index(len)] = op; + } else { + operate(t, &operations[op]) catch |err| switch (err) { + error.Canceled => { + canceled = true; + continue; + }, + }; + ring[complete_tail.index(len)] = op; + complete_tail = complete_tail.next(len); + } } + poll_i = 0; + return if (canceled) error.Canceled; }, .INTR => continue, else => return error.ConcurrencyUnavailable, @@ -2540,9 +2574,27 @@ fn batchWait( fn batchCancel(userdata: ?*anyopaque, b: *Io.Batch) void { const t: *Threaded = @ptrCast(@alignCast(userdata)); - _ = t; - _ = b; - return; + const operations = b.operations; + const len: u31 = @intCast(operations.len); + const ring = b.ring[0..len]; + var submit_head = b.impl.submit_head; + const submit_tail = b.user.submit_tail; + b.impl.submit_tail = submit_tail; + var complete_tail = b.impl.complete_tail; + while (submit_head != submit_tail) : (submit_head = submit_head.next(len)) { + const op = ring[submit_head.index(len)]; + switch (operations[op]) { + .noop => { + operate(t, &operations[op]) catch unreachable; + ring[complete_tail.index(len)] = op; + complete_tail = complete_tail.next(len); + }, + .file_read_streaming => |*o| _ = o.status.unstarted, + } + } + b.impl.submit_head = submit_tail; + b.impl.complete_tail = complete_tail; + b.user.complete_tail = complete_tail; } fn batch(userdata: ?*anyopaque, operations: []Io.Operation) Io.ConcurrentError!void { @@ -10352,6 +10404,7 @@ fn nowWasi(clock: Io.Clock) Io.Clock.Error!Io.Timestamp { fn sleep(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void { const t: *Threaded = @ptrCast(@alignCast(userdata)); + if (timeout == .none) return; if (use_parking_sleep) return parking_sleep.sleep(try timeout.toDeadline(ioBasic(t))); if (native_os == .wasi) return sleepWasi(t, timeout); if (@TypeOf(posix.system.clock_nanosleep) != void) return sleepPosix(timeout); diff --git a/lib/std/process/Child.zig b/lib/std/process/Child.zig index 364c52446f8691a3e7505e89a40ca514125c5e59..19c974ff9f65c9beec210ea6498d76a65c28ac67 100644 --- a/lib/std/process/Child.zig +++ b/lib/std/process/Child.zig @@ -149,51 +149,45 @@ pub fn collectOutput(child: *const Child, io: Io, options: CollectOutputOptions) const files: [2]Io.File = .{ child.stdout.?, child.stderr.? }; const lists: [2]*std.ArrayList(u8) = .{ options.stdout, options.stderr }; const limits: [2]Io.Limit = .{ options.stdout_limit, options.stderr_limit }; - var dones: [2]bool = .{ false, false }; var reads: [2]Io.Operation = undefined; var vecs: [2][1][]u8 = undefined; - while (true) { - for (&reads, &lists, &files, dones, &vecs) |*read, list, file, done, *vec| { - if (done) { - read.* = .{ .noop = .{} }; - continue; - } - if (options.allocator) |gpa| try list.ensureUnusedCapacity(gpa, 1); - const cap = list.unusedCapacitySlice(); - if (cap.len == 0) return error.StreamTooLong; - vec[0] = cap; - read.* = .{ .file_read_streaming = .{ - .file = file, - .data = vec, - } }; + var ring: [2]u32 = undefined; + var batch: Io.Batch = .init(&reads, &ring); + defer { + batch.cancel(io); + while (batch.next()) |op| { + lists[op].items.len += reads[op].file_read_streaming.status.result catch continue; } - var all_done = true; - var any_canceled = false; - var other_err: (error{StreamTooLong} || Io.File.Reader.Error)!void = {}; - try io.vtable.batch(io.userdata, &reads); - for (&reads, &lists, &limits, &dones) |*read, list, limit, *done| { - if (done.*) continue; - const n = read.file_read_streaming.status.result catch |err| switch (err) { - error.Canceled => { - any_canceled = true; - continue; - }, - error.WouldBlock => continue, - else => |e| { - other_err = e; - continue; - }, - }; + } + var remaining: usize = 0; + for (0.., &reads, &lists, &files, &vecs) |op, *read, list, file, *vec| { + if (options.allocator) |gpa| try list.ensureUnusedCapacity(gpa, 1); + const cap = list.unusedCapacitySlice(); + if (cap.len == 0) return error.StreamTooLong; + vec[0] = cap; + read.* = .{ .file_read_streaming = .{ + .file = file, + .data = vec, + } }; + batch.add(op); + remaining += 1; + } + while (remaining > 0) { + try batch.wait(io, .none); + while (batch.next()) |op| { + const n = try reads[op].file_read_streaming.status.result; if (n == 0) { - done.* = true; + remaining -= 1; } else { - all_done = false; + lists[op].items.len += n; + if (lists[op].items.len > @intFromEnum(limits[op])) return error.StreamTooLong; + if (options.allocator) |gpa| try lists[op].ensureUnusedCapacity(gpa, 1); + const cap = lists[op].unusedCapacitySlice(); + if (cap.len == 0) return error.StreamTooLong; + vecs[op][0] = cap; + reads[op].file_read_streaming.status = .{ .unstarted = {} }; + batch.add(op); } - list.items.len += n; - if (list.items.len > @intFromEnum(limit)) other_err = error.StreamTooLong; } - if (any_canceled) return error.Canceled; - try other_err; - if (all_done) return; } } -- 2.54.0 From 78a1476475047d0ae591fd838ec55b6b87561dd3 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 12 Jan 2026 17:41:08 -0800 Subject: [PATCH 114/499] Build.WebServer: update concurrency API usage --- lib/std/Build/Step.zig | 1 + lib/std/Build/WebServer.zig | 36 +++++++++++++++++++++++------------- 2 files changed, 24 insertions(+), 13 deletions(-) diff --git a/lib/std/Build/Step.zig b/lib/std/Build/Step.zig index bacc81cbfabe24938358d75653019443c892f03c..e2c51cc6fe91216ba8f8188324a4b30c50d62443 100644 --- a/lib/std/Build/Step.zig +++ b/lib/std/Build/Step.zig @@ -542,6 +542,7 @@ fn zigProcessUpdate(s: *Step, zp: *ZigProcess, watch: bool, web_server: ?*Build. const stdout = &stdout_reader.interface; var body_buffer: std.ArrayList(u8) = .empty; + defer body_buffer.deinit(gpa); while (true) { const Header = std.zig.Server.Message.Header; diff --git a/lib/std/Build/WebServer.zig b/lib/std/Build/WebServer.zig index e1536fb8fa8bb2ad6e30a0e2a6fcaeda8d299c64..1f380b6c50d4f1aae6066362f37a4e2f6cd9c627 100644 --- a/lib/std/Build/WebServer.zig +++ b/lib/std/Build/WebServer.zig @@ -588,11 +588,12 @@ fn buildClientWasm(ws: *WebServer, arena: Allocator, optimize: std.builtin.Optim }); defer child.kill(io); - var poller = Io.poll(gpa, enum { stdout, stderr }, .{ - .stdout = child.stdout.?, - .stderr = child.stderr.?, - }); - defer poller.deinit(); + var stderr_task = try io.concurrent(readStreamAlloc, .{ gpa, io, child.stderr.?, .unlimited }); + defer if (stderr_task.cancel(io)) |slice| gpa.free(slice) else |_| {}; + + var stdout_buffer: [512]u8 = undefined; + var stdout_reader: Io.File.Reader = .initStreaming(child.stdout.?, io, &stdout_buffer); + const stdout = &stdout_reader.interface; try child.stdin.?.writeStreamingAll(io, @ptrCast(@as([]const std.zig.Client.Message.Header, &.{ .{ .tag = .update, .bytes_len = 0 }, @@ -600,16 +601,17 @@ fn buildClientWasm(ws: *WebServer, arena: Allocator, optimize: std.builtin.Optim }))); const Header = std.zig.Server.Message.Header; + var result: ?Cache.Path = null; var result_error_bundle = std.zig.ErrorBundle.empty; + var body_buffer: std.ArrayList(u8) = .empty; + defer body_buffer.deinit(gpa); - const stdout = poller.reader(.stdout); - - poll: while (true) { - while (stdout.buffered().len < @sizeOf(Header)) if (!(try poller.poll())) break :poll; - const header = stdout.takeStruct(Header, .little) catch unreachable; - while (stdout.buffered().len < header.bytes_len) if (!try poller.poll()) break :poll; - const body = stdout.take(header.bytes_len) catch unreachable; + while (true) { + const header = try stdout.takeStruct(Header, .little); + body_buffer.clearRetainingCapacity(); + try stdout.appendExact(gpa, &body_buffer, header.bytes_len); + const body = body_buffer.items; switch (header.tag) { .zig_version => { @@ -636,7 +638,7 @@ fn buildClientWasm(ws: *WebServer, arena: Allocator, optimize: std.builtin.Optim } } - const stderr_contents = try poller.toOwnedSlice(.stderr); + const stderr_contents = try stderr_task.await(io); if (stderr_contents.len > 0) { std.debug.print("{s}", .{stderr_contents}); } @@ -697,6 +699,14 @@ fn buildClientWasm(ws: *WebServer, arena: Allocator, optimize: std.builtin.Optim return base_path.join(arena, bin_name); } +fn readStreamAlloc(gpa: Allocator, io: Io, file: Io.File, limit: Io.Limit) ![]u8 { + var file_reader: Io.File.Reader = .initStreaming(file, io, &.{}); + return file_reader.interface.allocRemaining(gpa, limit) catch |err| switch (err) { + error.ReadFailed => return file_reader.err.?, + else => |e| return e, + }; +} + pub fn updateTimeReportCompile(ws: *WebServer, opts: struct { compile: *Build.Step.Compile, -- 2.54.0 From a0c2645948682b4bfdd2971f7512376605a45502 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 12 Jan 2026 18:04:56 -0800 Subject: [PATCH 115/499] std.Io.Threaded: delete dead code --- lib/std/Io/Threaded.zig | 209 +++++++++++++++------------------------- 1 file changed, 80 insertions(+), 129 deletions(-) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index fefa8fe84dcacd0a61007145e0a15d9fd0e67e82..2167baa5e48ced7aa41f4f9b6581b0e6e62c1c04 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -2474,6 +2474,7 @@ fn operate(userdata: ?*anyopaque, op: *Io.Operation) Io.Cancelable!void { fn batchWait(userdata: ?*anyopaque, b: *Io.Batch, timeout: Io.Timeout) Io.Batch.WaitError!void { const t: *Threaded = @ptrCast(@alignCast(userdata)); + if (is_windows) return batchWaitWindows(t, b, timeout); const operations = b.operations; const len: u31 = @intCast(operations.len); const ring = b.ring[0..len]; @@ -2492,13 +2493,12 @@ fn batchWait(userdata: ?*anyopaque, b: *Io.Batch, timeout: Io.Timeout) Io.Batch. b.impl.complete_tail = complete_tail; b.user.complete_tail = complete_tail; } - if (is_windows) @panic("TODO"); var poll_buffer: [poll_buffer_len]posix.pollfd = undefined; while (submit_head != submit_tail) : (submit_head = submit_head.next(len)) { const op = ring[submit_head.index(len)]; const operation = &operations[op]; switch (operation.*) { - else => { + .noop => { try operate(t, operation); ring[complete_tail.index(len)] = op; complete_tail = complete_tail.next(len); @@ -2597,149 +2597,100 @@ fn batchCancel(userdata: ?*anyopaque, b: *Io.Batch) void { b.user.complete_tail = complete_tail; } -fn batch(userdata: ?*anyopaque, operations: []Io.Operation) Io.ConcurrentError!void { - const t: *Threaded = @ptrCast(@alignCast(userdata)); +fn batchWaitWindows(t: *Threaded, b: *Io.Batch, timeout: Io.Timeout) Io.ConcurrentError!void { + const operations = b.operations; + const len: u31 = @intCast(operations.len); + const ring = b.ring[0..len]; + var submit_head = b.impl.submit_head; + const submit_tail = b.user.submit_tail; + b.impl.submit_tail = submit_tail; + var complete_tail = b.impl.complete_tail; - if (operations.len == 1) { - @branchHint(.likely); - return operate(&operations[0]); - } - - if (is_windows) return batchWindows(t, operations); - - var poll_buffer: [poll_buffer_len]posix.pollfd = undefined; - var map_buffer: [poll_buffer_len]u8 = undefined; // poll_buffer index to operations index - var poll_i: usize = 0; - - for (operations, 0..) |*op, operation_index| switch (op.*) { - .noop => continue, - .file_read_streaming => |*o| { - if (poll_buffer.len - poll_i == 0) return error.ConcurrencyUnavailable; - poll_buffer[poll_i] = .{ - .fd = o.file.handle, - .events = posix.POLL.IN, - .revents = 0, - }; - map_buffer[poll_i] = @intCast(operation_index); - poll_i += 1; - }, - }; - - const polls = poll_buffer[0..poll_i]; - const map = map_buffer[0..poll_i]; - - var pending = poll_i; - while (pending > 0) { - const syscall = Syscall.start() catch |err| switch (err) { - error.Canceled => { - if (!setOperationsError(operations, polls, map, error.Canceled)) - recancelInner(); - return; - }, - }; - const rc = posix.system.poll(polls.ptr, polls.len, -1); - syscall.finish(); - switch (posix.errno(rc)) { - .SUCCESS => for (polls, map) |*poll_fd, i| { - if (poll_fd.revents == 0) continue; - poll_fd.fd = -1; - pending -= 1; - operate(&operations[i]); - }, - .INTR => continue, - .NOMEM => { - assert(setOperationsError(operations, polls, map, error.SystemResources)); - return; - }, - else => { - assert(setOperationsError(operations, polls, map, error.Unexpected)); - return; - }, - } - } -} - -fn batchWindows(t: *Threaded, operations: []Io.Operation) Io.ConcurrentError!void { - _ = t; var overlapped_buffer: [poll_buffer_len]windows.OVERLAPPED = undefined; var handles_buffer: [poll_buffer_len]windows.HANDLE = undefined; - var map_buffer: [poll_buffer_len]u8 = undefined; // handles_buffer index to operations index + var map_buffer: [poll_buffer_len]u32 = undefined; // handles_buffer index to operations index var buffer_i: usize = 0; - for (operations, 0..) |*op, operation_index| switch (op.*) { - .noop => continue, - .file_read_streaming => |*o| { - if (handles_buffer.len - buffer_i == 0) return error.ConcurrencyUnavailable; + defer { + for (map_buffer[0..buffer_i]) |op| { + submit_head = submit_head.prev(len); + ring[submit_head.index(len)] = op; + } + b.impl.submit_head = submit_head; + b.impl.complete_tail = complete_tail; + b.user.complete_tail = complete_tail; + } - const overlapped = &overlapped_buffer[buffer_i]; - overlapped.* = .{ - .Internal = 0, - .InternalHigh = 0, - .DUMMYUNIONNAME = .{ - .DUMMYSTRUCTNAME = .{ - .Offset = 0, - .OffsetHigh = 0, + while (submit_head != submit_tail) : (submit_head = submit_head.next(len)) { + const op = ring[submit_head.index(len)]; + const operation = &operations[op]; + switch (operation.*) { + .noop => { + try operate(t, operation); + ring[complete_tail.index(len)] = op; + complete_tail = complete_tail.next(len); + }, + .file_read_streaming => |*o| { + _ = o.status.unstarted; + if (handles_buffer.len - buffer_i == 0) return error.ConcurrencyUnavailable; + const overlapped = &overlapped_buffer[buffer_i]; + overlapped.* = .{ + .Internal = 0, + .InternalHigh = 0, + .DUMMYUNIONNAME = .{ + .DUMMYSTRUCTNAME = .{ + .Offset = 0, + .OffsetHigh = 0, + }, + .Pointer = null, }, - .Pointer = null, - }, - .hEvent = null, - }; - var n: windows.DWORD = undefined; - const buf = o.data[0]; - if (windows.kernel32.ReadFile(o.file.handle, buf.ptr, buf.len, &n, overlapped) == 0) { - @panic("TODO"); - } - handles_buffer[buffer_i] = o.file.handle; - map_buffer[buffer_i] = @intCast(operation_index); - buffer_i += 1; + .hEvent = null, + }; + var n: windows.DWORD = undefined; + const buf = o.data[0]; + if (windows.kernel32.ReadFile(o.file.handle, buf.ptr, buf.len, &n, overlapped) == 0) { + @panic("TODO"); + } + handles_buffer[buffer_i] = o.file.handle; + map_buffer[buffer_i] = op; + buffer_i += 1; + }, + } + } + + switch (buffer_i) { + 0 => return, + 1 => if (timeout == .none) { + const op = map_buffer[0]; + try operate(t, &operations[op]); + ring[complete_tail.index(len)] = op; + complete_tail = complete_tail.next(len); + return; }, - }; + else => {}, + } const handles = handles_buffer[0..buffer_i]; const map = map_buffer[0..buffer_i]; - var pending = buffer_i; - while (pending > 0) { - const syscall: Syscall = try .start(); - const index = windows.WaitForMultipleObjectsEx(handles, false, windows.INFINITE, true); - syscall.finish(); - var n: windows.DWORD = undefined; - if (0 == windows.kernel32.GetOverlappedResult(handles[index], overlapped_buffer[index], &n, 0)) { - switch (windows.GetLastError()) { - .BROKEN_PIPE => @panic("TODO"), - .OPERATION_ABORTED => @panic("TODO"), - else => @panic("TODO"), - } - } else switch (operations[map[index]]) { - .noop => unreachable, - .file_read_streaming => |*o| { - o.status = .{ .result = n }; - pending -= 1; - }, + const syscall: Syscall = try .start(); + const index = windows.WaitForMultipleObjectsEx(handles, false, windows.INFINITE, true); + syscall.finish(); + var n: windows.DWORD = undefined; + if (0 == windows.kernel32.GetOverlappedResult(handles[index], overlapped_buffer[index], &n, 0)) { + switch (windows.GetLastError()) { + .BROKEN_PIPE => @panic("TODO"), + .OPERATION_ABORTED => @panic("TODO"), + else => @panic("TODO"), } + } else switch (operations[map[index]]) { + .noop => unreachable, + .file_read_streaming => |*o| { + o.status = .{ .result = n }; + }, } } -fn setOperationsError( - operations: []Io.Operation, - polls: []const posix.pollfd, - map: []const u8, - err: error{ Canceled, SystemResources, Unexpected }, -) bool { - var marked = false; - for (polls, map) |*poll_fd, i| { - if (poll_fd.fd == -1) continue; - switch (operations[i]) { - .noop => unreachable, - inline else => |*o| { - o.status = .{ .result = err }; - marked = true; - }, - } - } - return marked; -} - const dirCreateDir = switch (native_os) { .windows => dirCreateDirWindows, .wasi => dirCreateDirWasi, -- 2.54.0 From 20cadd60aa71d500913fdf03f50ba2de01cf918b Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 12 Jan 2026 23:21:55 -0800 Subject: [PATCH 116/499] std.Io.File: introduce MultiReader Concurrently read from multiple file streams, eliminating risk of deadlocking. --- lib/std/Build/Step.zig | 29 ++-- lib/std/Io.zig | 4 +- lib/std/Io/File.zig | 3 + lib/std/Io/File/MultiReader.zig | 240 ++++++++++++++++++++++++++++++++ lib/std/Io/Reader.zig | 8 +- 5 files changed, 256 insertions(+), 28 deletions(-) create mode 100644 lib/std/Io/File/MultiReader.zig diff --git a/lib/std/Build/Step.zig b/lib/std/Build/Step.zig index e2c51cc6fe91216ba8f8188324a4b30c50d62443..40845f75c31ccbfe5b931fabbb7f40c997212ffb 100644 --- a/lib/std/Build/Step.zig +++ b/lib/std/Build/Step.zig @@ -527,9 +527,6 @@ fn zigProcessUpdate(s: *Step, zp: *ZigProcess, watch: bool, web_server: ?*Build. const arena = b.allocator; const io = b.graph.io; - var stderr_task = try io.concurrent(readStreamAlloc, .{ gpa, io, zp.child.stderr.?, .unlimited }); - defer if (stderr_task.cancel(io)) |slice| gpa.free(slice) else |_| {}; - var timer = try std.time.Timer.start(); try sendMessage(io, zp.child.stdin.?, .update); @@ -537,19 +534,18 @@ fn zigProcessUpdate(s: *Step, zp: *ZigProcess, watch: bool, web_server: ?*Build. var result: ?Path = null; - var stdout_buffer: [512]u8 = undefined; - var stdout_reader: Io.File.Reader = .initStreaming(zp.child.stdout.?, io, &stdout_buffer); - const stdout = &stdout_reader.interface; + var multi_reader_buffer: Io.File.MultiReader.Buffer(2) = undefined; + var multi_reader: Io.File.MultiReader = undefined; + multi_reader.init(gpa, io, multi_reader_buffer.toStreams(), &.{ zp.child.stdout.?, zp.child.stderr.? }); + defer multi_reader.deinit(); - var body_buffer: std.ArrayList(u8) = .empty; - defer body_buffer.deinit(gpa); + const stdout = multi_reader.reader(0); + const stderr = multi_reader.reader(1); while (true) { const Header = std.zig.Server.Message.Header; const header = try stdout.takeStruct(Header, .little); - body_buffer.clearRetainingCapacity(); - try stdout.appendExact(gpa, &body_buffer, header.bytes_len); - const body = body_buffer.items; + const body = try stdout.take(header.bytes_len); switch (header.tag) { .zig_version => { if (!std.mem.eql(u8, builtin.zig_version_string, body)) { @@ -640,8 +636,7 @@ fn zigProcessUpdate(s: *Step, zp: *ZigProcess, watch: bool, web_server: ?*Build. s.result_duration_ns = timer.read(); - const stderr_contents = try stderr_task.await(io); - defer gpa.free(stderr_contents); + const stderr_contents = stderr.buffered(); if (stderr_contents.len > 0) { try s.result_error_msgs.append(arena, try arena.dupe(u8, stderr_contents)); } @@ -649,14 +644,6 @@ fn zigProcessUpdate(s: *Step, zp: *ZigProcess, watch: bool, web_server: ?*Build. return result; } -fn readStreamAlloc(gpa: Allocator, io: Io, file: Io.File, limit: Io.Limit) ![]u8 { - var file_reader: Io.File.Reader = .initStreaming(file, io, &.{}); - return file_reader.interface.allocRemaining(gpa, limit) catch |err| switch (err) { - error.ReadFailed => return file_reader.err.?, - else => |e| return e, - }; -} - pub fn getZigProcess(s: *Step) ?*ZigProcess { return switch (s.id) { .compile => s.cast(Compile).?.zig_process, diff --git a/lib/std/Io.zig b/lib/std/Io.zig index b503979fda68d5db200da632970f114f40de819d..980379b72b985fdd5107e522eda9bdbee475caf0 100644 --- a/lib/std/Io.zig +++ b/lib/std/Io.zig @@ -350,8 +350,6 @@ pub const Batch = struct { } }; - pub const WaitError = ConcurrentError || Cancelable || Timeout.Error; - pub fn init(operations: []Operation, ring: []u32) Batch { const len: u31 = @intCast(operations.len); assert(ring.len == len); @@ -405,6 +403,8 @@ pub const Batch = struct { return b.ring[0..len][head.index(len)]; } + pub const WaitError = ConcurrentError || Cancelable || Timeout.Error; + /// Starts work on any submitted operations and returns when at least one has completeed. /// /// Returns `error.Timeout` if `timeout` expires first. diff --git a/lib/std/Io/File.zig b/lib/std/Io/File.zig index cc7042d443d0ca250e3a853012d4241462e908b5..c545b6022278d5043d8890a39a91a7baae81d1dc 100644 --- a/lib/std/Io/File.zig +++ b/lib/std/Io/File.zig @@ -18,6 +18,9 @@ pub const Writer = @import("File/Writer.zig"); pub const Atomic = @import("File/Atomic.zig"); /// Memory intended to remain consistent with file contents. pub const MemoryMap = @import("File/MemoryMap.zig"); +/// Concurrently read from multiple file streams, eliminating risk of +/// deadlocking. +pub const MultiReader = @import("File/MultiReader.zig"); pub const INode = std.posix.ino_t; pub const NLink = std.posix.nlink_t; diff --git a/lib/std/Io/File/MultiReader.zig b/lib/std/Io/File/MultiReader.zig new file mode 100644 index 0000000000000000000000000000000000000000..1cf3f7b4042e01c2038575d69c583ea0afe23f7b --- /dev/null +++ b/lib/std/Io/File/MultiReader.zig @@ -0,0 +1,240 @@ +const MultiReader = @This(); + +const std = @import("../../std.zig"); +const Io = std.Io; +const File = Io.File; +const Allocator = std.mem.Allocator; +const assert = std.debug.assert; + +gpa: Allocator, +streams: *Streams, +batch: Io.Batch, + +pub const Context = struct { + mr: *MultiReader, + fr: File.Reader, + vec: [1][]u8, + err: ?Error, + eos: bool, +}; + +pub const Error = Allocator.Error || File.Reader.Error || Io.ConcurrentError; + +/// Trailing: +/// * `contexts: [len]Context` +/// * `ring: [len]u32` +/// * `operations: [len]Io.Operation` +pub const Streams = extern struct { + len: u32, + + pub fn contexts(s: *Streams) []Context { + _ = s; + @panic("TODO"); + } + + pub fn ring(s: *Streams) []u32 { + _ = s; + @panic("TODO"); + } + + pub fn operations(s: *Streams) []Io.Operation { + _ = s; + @panic("TODO"); + } +}; + +pub fn Buffer(comptime n: usize) type { + return extern struct { + len: u32, + contexts: [n][@sizeOf(Context)]u8 align(@alignOf(Context)), + ring: [n]u32, + operations: [n][@sizeOf(Io.Operation)]u8 align(@alignOf(Io.Operation)), + + pub fn toStreams(b: *@This()) *Streams { + return @ptrCast(b); + } + }; +} + +/// See `Streams.Buffer` for convenience API to obtain the `streams` parameter. +pub fn init(mr: *MultiReader, gpa: Allocator, io: Io, streams: *Streams, files: []const File) void { + const contexts = streams.contexts(); + for (contexts, files) |*context, file| context.* = .{ + .mr = mr, + .fr = .{ + .io = io, + .file = file, + .mode = .streaming, + .interface = .{ + .vtable = &.{ + .stream = stream, + .discard = discard, + .readVec = readVec, + .rebase = rebase, + }, + .buffer = &.{}, + .seek = 0, + .end = 0, + }, + }, + .vec = .{&.{}}, + .err = null, + .eos = false, + }; + const operations = streams.operations(); + const ring = streams.ring(); + mr.* = .{ + .gpa = gpa, + .streams = streams, + .batch = .init(operations, ring), + }; + for (operations, contexts, files, 0..) |*op, *context, file, i| { + const r = &context.fr.interface; + op.* = .{ .file_read_streaming = .{ + .file = file, + .data = &context.vec, + } }; + rebaseGrowing(mr, context, 1) catch |err| { + context.err = err; + continue; + }; + context.vec[0] = r.buffer; + mr.batch.add(i); + } +} + +pub fn deinit(mr: *MultiReader) void { + const gpa = mr.gpa; + const contexts = mr.streams.contexts(); + const io = contexts[0].fr.io; + mr.batch.cancel(io); + for (contexts) |*context| { + gpa.free(context.fr.interface.buffer); + } +} + +pub fn reader(mr: *MultiReader, index: usize) *Io.Reader { + return &mr.streams.contexts()[index].fr.interface; +} + +pub fn toOwnedSlice(mr: *MultiReader, index: usize) Allocator.Error![]u8 { + const gpa = mr.gpa; + const r: *Io.Reader = reader(mr, index); + if (r.seek == 0) { + const new = try gpa.realloc(r.buffer, r.end); + r.buffer = &.{}; + r.end = 0; + return new; + } + const new = try gpa.dupe(u8, r.buffered()); + gpa.free(r.buffer); + r.buffer = &.{}; + r.seek = 0; + r.end = 0; + return new; +} + +fn stream(r: *Io.Reader, w: *Io.Writer, limit: Io.Limit) Io.Reader.StreamError!usize { + _ = limit; + _ = w; + const fr: *File.Reader = @alignCast(@fieldParentPtr("interface", r)); + const context: *Context = @fieldParentPtr("fr", fr); + const mr = context.mr; + return fill(mr, context); +} + +fn discard(r: *Io.Reader, limit: Io.Limit) Io.Reader.Error!usize { + _ = limit; + const fr: *File.Reader = @alignCast(@fieldParentPtr("interface", r)); + const context: *Context = @fieldParentPtr("fr", fr); + const mr = context.mr; + return fill(mr, context); +} + +fn readVec(r: *Io.Reader, data: [][]u8) Io.Reader.Error!usize { + _ = data; + const fr: *File.Reader = @alignCast(@fieldParentPtr("interface", r)); + const context: *Context = @fieldParentPtr("fr", fr); + const mr = context.mr; + return fill(mr, context); +} + +fn rebase(r: *Io.Reader, capacity: usize) Io.Reader.RebaseError!void { + const fr: *File.Reader = @alignCast(@fieldParentPtr("interface", r)); + const context: *Context = @fieldParentPtr("fr", fr); + const mr = context.mr; + + return rebaseGrowing(mr, context, capacity) catch |err| { + context.err = err; + return error.ReadFailed; + }; +} + +fn rebaseGrowing(mr: *MultiReader, context: *Context, capacity: usize) Allocator.Error!void { + const gpa = mr.gpa; + const r = &context.fr.interface; + if (r.buffer.len >= capacity) { + const data = r.buffer[r.seek..r.end]; + @memmove(r.buffer[0..data.len], data); + r.seek = 0; + r.end = data.len; + } else { + const adjusted_capacity = std.ArrayList(u8).growCapacity(capacity); + + if (r.seek == 0) { + if (gpa.remap(r.buffer, adjusted_capacity)) |new_memory| { + r.buffer = new_memory; + return; + } + } + + const data = r.buffer[r.seek..r.end]; + const new = try gpa.alloc(u8, adjusted_capacity); + @memcpy(new[0..data.len], data); + r.seek = 0; + r.end = data.len; + } +} + +fn fill(mr: *MultiReader, original_context: *Context) Io.Reader.Error!usize { + const contexts = mr.streams.contexts(); + const operations = mr.streams.operations(); + const io = contexts[0].fr.io; + + mr.batch.wait(io, .none) catch |err| switch (err) { + error.Timeout, error.UnsupportedClock => unreachable, + else => |e| { + original_context.err = e; + return error.ReadFailed; + }, + }; + + while (mr.batch.next()) |i| { + const context = &contexts[i]; + const operation = &operations[i]; + const n = operation.file_read_streaming.status.result catch |err| { + context.err = err; + continue; + }; + if (n == 0) { + context.eos = true; + continue; + } + const r = &context.fr.interface; + r.end += n; + if (r.buffer.len - r.end == 0) { + rebaseGrowing(mr, context, r.bufferedLen() + 1) catch |err| { + context.err = err; + continue; + }; + assert(r.seek == 0); + context.vec[0] = r.buffer; + } + operation.file_read_streaming.status = .{ .unstarted = {} }; + mr.batch.add(i); + } + + if (original_context.err != null) return error.ReadFailed; + if (original_context.eos) return error.EndOfStream; + return 0; +} diff --git a/lib/std/Io/Reader.zig b/lib/std/Io/Reader.zig index 9c5c762844c0f1e296fbd389ed016ebb991abfcf..9ff025a637c87e15f852d09234fd8d6570ad2ee4 100644 --- a/lib/std/Io/Reader.zig +++ b/lib/std/Io/Reader.zig @@ -127,9 +127,7 @@ pub const ShortError = error{ ReadFailed, }; -pub const RebaseError = error{ - EndOfStream, -}; +pub const RebaseError = Error; pub const failing: Reader = .{ .vtable = &.{ @@ -1402,7 +1400,7 @@ pub fn takeLeb128(r: *Reader, comptime T: type) TakeLeb128Error!T { } /// Ensures `capacity` data can be buffered without rebasing. -pub fn rebase(r: *Reader, capacity: usize) RebaseError!void { +pub fn rebase(r: *Reader, capacity: usize) Error!void { if (r.buffer.len - r.seek >= capacity) { @branchHint(.likely); return; @@ -1410,7 +1408,7 @@ pub fn rebase(r: *Reader, capacity: usize) RebaseError!void { return r.vtable.rebase(r, capacity); } -pub fn defaultRebase(r: *Reader, capacity: usize) RebaseError!void { +pub fn defaultRebase(r: *Reader, capacity: usize) Error!void { assert(r.buffer.len - r.seek < capacity); const data = r.buffer[r.seek..r.end]; @memmove(r.buffer[0..data.len], data); -- 2.54.0 From 12cfc96e1b25d4c75fd08b9af72226502982195a Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 13 Jan 2026 18:42:00 -0800 Subject: [PATCH 117/499] std: update rest of build runner to new File.MultiReader API --- lib/std/Build/Step/Run.zig | 110 ++++++++++++++++++-------------- lib/std/Io/File/MultiReader.zig | 59 +++++++++++++---- 2 files changed, 109 insertions(+), 60 deletions(-) diff --git a/lib/std/Build/Step/Run.zig b/lib/std/Build/Step/Run.zig index d025e01af40f8d3580e37ad830ee7515f0608cb4..82c15531d00576c6d1aa0db121a36c521455dd4b 100644 --- a/lib/std/Build/Step/Run.zig +++ b/lib/std/Build/Step/Run.zig @@ -1669,39 +1669,44 @@ fn evalZigTest( while (true) { var child = try process.spawn(io, spawn_options); - var poller = std.Io.poll(gpa, StdioPollEnum, .{ - .stdout = child.stdout.?, - .stderr = child.stderr.?, - }); + var multi_reader_buffer: Io.File.MultiReader.Buffer(2) = undefined; + var multi_reader: Io.File.MultiReader = undefined; + multi_reader.init(gpa, io, multi_reader_buffer.toStreams(), &.{ child.stdout.?, child.stderr.? }); var child_killed = false; defer if (!child_killed) { child.kill(io); - poller.deinit(); + multi_reader.deinit(); run.step.result_peak_rss = @max( run.step.result_peak_rss, child.resource_usage_statistics.getMaxRss() orelse 0, ); }; - switch (try pollZigTest( + switch (try waitZigTest( run, &child, options, fuzz_context, - &poller, + &multi_reader, &test_metadata, &test_results, )) { .write_failed => |err| { // The runner unexpectedly closed a stdio pipe, which means a crash. Make sure we've captured // all available stderr to make our error output as useful as possible. - while (try poller.poll()) {} - run.step.result_stderr = try arena.dupe(u8, poller.reader(.stderr).buffered()); + const stderr_fr = multi_reader.fileReader(1); + while (true) { + stderr_fr.interface.fillMore() catch |e| switch (e) { + error.ReadFailed => return stderr_fr.err.?, + error.EndOfStream => break, + }; + } + run.step.result_stderr = try arena.dupe(u8, stderr_fr.interface.buffered()); // Clean up everything and wait for the child to exit. child.stdin.?.close(io); child.stdin = null; - poller.deinit(); + multi_reader.deinit(); child_killed = true; const term = try child.wait(io); run.step.result_peak_rss = @max( @@ -1716,13 +1721,14 @@ fn evalZigTest( .no_poll => |no_poll| { // This might be a success (we requested exit and the child dutifully closed stdout) or // a crash of some kind. Either way, the child will terminate by itself -- wait for it. - const stderr_owned = try arena.dupe(u8, poller.reader(.stderr).buffered()); - poller.reader(.stderr).tossBuffered(); + const stderr_reader = multi_reader.reader(1); + const stderr_owned = try arena.dupe(u8, stderr_reader.buffered()); + stderr_reader.tossBuffered(); // Clean up everything and wait for the child to exit. child.stdin.?.close(io); child.stdin = null; - poller.deinit(); + multi_reader.deinit(); child_killed = true; const term = try child.wait(io); run.step.result_peak_rss = @max( @@ -1770,8 +1776,9 @@ fn evalZigTest( return; }, .timeout => |timeout| { - const stderr = poller.reader(.stderr).buffered(); - poller.reader(.stderr).tossBuffered(); + const stderr_reader = multi_reader.reader(1); + const stderr = stderr_reader.buffered(); + stderr_reader.tossBuffered(); if (timeout.active_test_index) |test_index| { // A test was running. Report the timeout against that test, and continue on to // the next test. @@ -1796,16 +1803,16 @@ fn evalZigTest( } } -/// Polls stdout of a Zig test process until a termination condition is reached: +/// Reads stdout of a Zig test process until a termination condition is reached: /// * A write fails, indicating the child unexpectedly closed stdin /// * A test (or a response from the test runner) times out -/// * `poll` fails, indicating the child closed stdout and stderr -fn pollZigTest( +/// * The wait fails, indicating the child closed stdout and stderr +fn waitZigTest( run: *Run, child: *process.Child, options: Step.MakeOptions, fuzz_context: ?FuzzContext, - poller: *std.Io.Poller(StdioPollEnum), + multi_reader: *Io.File.MultiReader, opt_metadata: *?TestMetadata, results: *Step.TestResults, ) !union(enum) { @@ -1874,12 +1881,11 @@ fn pollZigTest( break :ns @max(options.unit_test_timeout_ns orelse 0, 60 * std.time.ns_per_s); }; - const stdout = poller.reader(.stdout); - const stderr = poller.reader(.stderr); + const stdout = multi_reader.reader(0); + const stderr = multi_reader.reader(1); + const Header = std.zig.Server.Message.Header; while (true) { - const Header = std.zig.Server.Message.Header; - // This block is exited when `stdout` contains enough bytes for a `Header`. header_ready: { if (stdout.buffered().len >= @sizeOf(Header)) { @@ -1894,18 +1900,22 @@ fn pollZigTest( break :ns options.unit_test_timeout_ns; }; - if (opt_timeout_ns) |timeout_ns| { - const remaining_ns = timeout_ns -| timer.?.read(); - if (!try poller.pollTimeout(remaining_ns)) return .{ .no_poll = .{ + const timeout: Io.Timeout = if (opt_timeout_ns) |timeout_ns| .{ .duration = .{ + .raw = .fromNanoseconds(timeout_ns -| timer.?.read()), + .clock = .awake, + } } else .none; + + multi_reader.fill(timeout) catch |err| switch (err) { + error.Timeout, error.EndOfStream => return .{ .no_poll = .{ .active_test_index = active_test_index, .ns_elapsed = if (timer) |*t| t.read() else 0, - } }; - } else { - if (!try poller.poll()) return .{ .no_poll = .{ - .active_test_index = active_test_index, - .ns_elapsed = if (timer) |*t| t.read() else 0, - } }; - } + } }, + error.UnsupportedClock => { + timer = null; + continue; + }, + else => |e| return e, + }; if (stdout.buffered().len >= @sizeOf(Header)) { // There wasn't a header before, but there is one after the `poll`. @@ -1923,11 +1933,8 @@ fn pollZigTest( } // There is definitely a header available now -- read it. const header = stdout.takeStruct(Header, .little) catch unreachable; + try stdout.fill(header.bytes_len); - while (stdout.buffered().len < header.bytes_len) if (!try poller.poll()) return .{ .no_poll = .{ - .active_test_index = active_test_index, - .ns_elapsed = if (timer) |*t| t.read() else 0, - } }; const body = stdout.take(header.bytes_len) catch unreachable; var body_r: std.Io.Reader = .fixed(body); switch (header.tag) { @@ -2164,6 +2171,7 @@ fn evalGeneric(run: *Run, spawn_options: process.SpawnOptions) !EvalGenericResul const b = run.step.owner; const io = b.graph.io; const arena = b.allocator; + const gpa = b.allocator; var child = try process.spawn(io, spawn_options); defer child.kill(io); @@ -2211,23 +2219,31 @@ fn evalGeneric(run: *Run, spawn_options: process.SpawnOptions) !EvalGenericResul if (child.stdout) |stdout| { if (child.stderr) |stderr| { - var poller = std.Io.poll(arena, enum { stdout, stderr }, .{ - .stdout = stdout, - .stderr = stderr, - }); - defer poller.deinit(); + var multi_reader_buffer: Io.File.MultiReader.Buffer(2) = undefined; + var multi_reader: Io.File.MultiReader = undefined; + multi_reader.init(gpa, io, multi_reader_buffer.toStreams(), &.{ stdout, stderr }); + defer multi_reader.deinit(); - while (try poller.poll()) { + const stdout_reader = multi_reader.reader(0); + const stderr_reader = multi_reader.reader(1); + + while (multi_reader.fill(.none)) |_| { if (run.stdio_limit.toInt()) |limit| { - if (poller.reader(.stderr).buffered().len > limit) + if (stdout_reader.buffered().len > limit) return error.StdoutStreamTooLong; - if (poller.reader(.stderr).buffered().len > limit) + if (stderr_reader.buffered().len > limit) return error.StderrStreamTooLong; } + } else |err| switch (err) { + error.UnsupportedClock, error.Timeout => unreachable, + error.EndOfStream => {}, + else => |e| return e, } - stdout_bytes = try poller.toOwnedSlice(.stdout); - stderr_bytes = try poller.toOwnedSlice(.stderr); + try multi_reader.checkAnyError(); + + stdout_bytes = try multi_reader.toOwnedSlice(0); + stderr_bytes = try multi_reader.toOwnedSlice(1); } else { var stdout_reader = stdout.readerStreaming(io, &.{}); stdout_bytes = stdout_reader.interface.allocRemaining(arena, run.stdio_limit) catch |err| switch (err) { diff --git a/lib/std/Io/File/MultiReader.zig b/lib/std/Io/File/MultiReader.zig index 1cf3f7b4042e01c2038575d69c583ea0afe23f7b..d4024ef914c17bfffcafac1ab2559828689abdf4 100644 --- a/lib/std/Io/File/MultiReader.zig +++ b/lib/std/Io/File/MultiReader.zig @@ -113,10 +113,28 @@ pub fn deinit(mr: *MultiReader) void { } } +pub fn fileReader(mr: *MultiReader, index: usize) *File.Reader { + return &mr.streams.contexts()[index].fr; +} + pub fn reader(mr: *MultiReader, index: usize) *Io.Reader { return &mr.streams.contexts()[index].fr.interface; } +/// Checks for errors in all streams, prioritizing `error.Canceled` if it +/// occurred anywhere. +pub fn checkAnyError(mr: *const MultiReader) Error!void { + const contexts = mr.streams.contexts(); + var other: Error!void = {}; + for (contexts) |*context| { + if (context.err) |err| switch (err) { + error.Canceled => |e| return e, + else => |e| other = e, + }; + } + return other; +} + pub fn toOwnedSlice(mr: *MultiReader, index: usize) Allocator.Error![]u8 { const gpa = mr.gpa; const r: *Io.Reader = reader(mr, index); @@ -140,7 +158,7 @@ fn stream(r: *Io.Reader, w: *Io.Writer, limit: Io.Limit) Io.Reader.StreamError!u const fr: *File.Reader = @alignCast(@fieldParentPtr("interface", r)); const context: *Context = @fieldParentPtr("fr", fr); const mr = context.mr; - return fill(mr, context); + return fillUntimed(mr, context); } fn discard(r: *Io.Reader, limit: Io.Limit) Io.Reader.Error!usize { @@ -148,7 +166,7 @@ fn discard(r: *Io.Reader, limit: Io.Limit) Io.Reader.Error!usize { const fr: *File.Reader = @alignCast(@fieldParentPtr("interface", r)); const context: *Context = @fieldParentPtr("fr", fr); const mr = context.mr; - return fill(mr, context); + return fillUntimed(mr, context); } fn readVec(r: *Io.Reader, data: [][]u8) Io.Reader.Error!usize { @@ -156,7 +174,7 @@ fn readVec(r: *Io.Reader, data: [][]u8) Io.Reader.Error!usize { const fr: *File.Reader = @alignCast(@fieldParentPtr("interface", r)); const context: *Context = @fieldParentPtr("fr", fr); const mr = context.mr; - return fill(mr, context); + return fillUntimed(mr, context); } fn rebase(r: *Io.Reader, capacity: usize) Io.Reader.RebaseError!void { @@ -196,20 +214,23 @@ fn rebaseGrowing(mr: *MultiReader, context: *Context, capacity: usize) Allocator } } -fn fill(mr: *MultiReader, original_context: *Context) Io.Reader.Error!usize { +pub const FillError = Io.Batch.WaitError || error{ + /// `fill` was called when all streams already have failed or reached the + /// end. + EndOfStream, +}; + +/// Wait until at least one stream receives more data. +pub fn fill(mr: *MultiReader, timeout: Io.Timeout) FillError!void { const contexts = mr.streams.contexts(); const operations = mr.streams.operations(); const io = contexts[0].fr.io; + var any_completed = false; - mr.batch.wait(io, .none) catch |err| switch (err) { - error.Timeout, error.UnsupportedClock => unreachable, - else => |e| { - original_context.err = e; - return error.ReadFailed; - }, - }; + try mr.batch.wait(io, timeout); while (mr.batch.next()) |i| { + any_completed = true; const context = &contexts[i]; const operation = &operations[i]; const n = operation.file_read_streaming.status.result catch |err| { @@ -234,7 +255,19 @@ fn fill(mr: *MultiReader, original_context: *Context) Io.Reader.Error!usize { mr.batch.add(i); } - if (original_context.err != null) return error.ReadFailed; - if (original_context.eos) return error.EndOfStream; + if (!any_completed) return error.EndOfStream; +} + +fn fillUntimed(mr: *MultiReader, context: *Context) Io.Reader.Error!usize { + fill(mr, .none) catch |err| switch (err) { + error.Timeout, error.UnsupportedClock => unreachable, + error.Canceled, error.ConcurrencyUnavailable => |e| { + context.err = e; + return error.ReadFailed; + }, + error.EndOfStream => |e| return e, + }; + if (context.err != null) return error.ReadFailed; + if (context.eos) return error.EndOfStream; return 0; } -- 2.54.0 From e56563ce3fb7ae2fb13f66ba6045ffb1f828ae08 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 13 Jan 2026 21:23:44 -0800 Subject: [PATCH 118/499] std.Io.File.MultiReader: implementation fixes --- lib/std/Build/Step.zig | 34 +++++---- lib/std/Build/Step/Run.zig | 12 ++-- lib/std/Io/File/MultiReader.zig | 118 ++++++++++++++++---------------- lib/std/crypto/tls/Client.zig | 3 +- 4 files changed, 87 insertions(+), 80 deletions(-) diff --git a/lib/std/Build/Step.zig b/lib/std/Build/Step.zig index 40845f75c31ccbfe5b931fabbb7f40c997212ffb..37fc2ca023f4ff9ebec6b2a88172b31e4f6182fe 100644 --- a/lib/std/Build/Step.zig +++ b/lib/std/Build/Step.zig @@ -381,13 +381,15 @@ pub fn addError(step: *Step, comptime fmt: []const u8, args: anytype) error{OutO pub const ZigProcess = struct { child: std.process.Child, + multi_reader_buffer: Io.File.MultiReader.Buffer(2), + multi_reader: Io.File.MultiReader, progress_ipc_fd: if (std.Progress.have_ipc) ?std.posix.fd_t else void, pub const StreamEnum = enum { stdout, stderr }; - pub fn deinit(zp: *ZigProcess, gpa: Allocator, io: Io) void { - _ = gpa; + pub fn deinit(zp: *ZigProcess, io: Io) void { zp.child.kill(io); + zp.multi_reader.deinit(); zp.* = undefined; } }; @@ -460,14 +462,18 @@ pub fn evalZigProcess( .request_resource_usage_statistics = true, .progress_node = prog_node, }) catch |err| return s.fail("failed to spawn zig compiler {s}: {t}", .{ argv[0], err }); - defer if (!watch) zp.child.kill(io); zp.* = .{ .child = zp.child, + .multi_reader_buffer = undefined, + .multi_reader = undefined, .progress_ipc_fd = if (std.Progress.have_ipc) prog_node.getIpcFd() else {}, }; + zp.multi_reader.init(gpa, io, zp.multi_reader_buffer.toStreams(), &.{ + zp.child.stdout.?, zp.child.stderr.?, + }); if (watch) s.setZigProcess(zp); - defer if (!watch) zp.deinit(gpa, io); + defer if (!watch) zp.deinit(io); const result = try zigProcessUpdate(s, zp, watch, web_server, gpa); @@ -534,18 +540,18 @@ fn zigProcessUpdate(s: *Step, zp: *ZigProcess, watch: bool, web_server: ?*Build. var result: ?Path = null; - var multi_reader_buffer: Io.File.MultiReader.Buffer(2) = undefined; - var multi_reader: Io.File.MultiReader = undefined; - multi_reader.init(gpa, io, multi_reader_buffer.toStreams(), &.{ zp.child.stdout.?, zp.child.stderr.? }); - defer multi_reader.deinit(); - - const stdout = multi_reader.reader(0); - const stderr = multi_reader.reader(1); + const stdout = zp.multi_reader.fileReader(0); while (true) { const Header = std.zig.Server.Message.Header; - const header = try stdout.takeStruct(Header, .little); - const body = try stdout.take(header.bytes_len); + const header = stdout.interface.takeStruct(Header, .little) catch |err| switch (err) { + error.EndOfStream => break, + error.ReadFailed => return stdout.err.?, + }; + const body = stdout.interface.take(header.bytes_len) catch |err| switch (err) { + error.EndOfStream => |e| return e, + error.ReadFailed => return stdout.err.?, + }; switch (header.tag) { .zig_version => { if (!std.mem.eql(u8, builtin.zig_version_string, body)) { @@ -636,7 +642,7 @@ fn zigProcessUpdate(s: *Step, zp: *ZigProcess, watch: bool, web_server: ?*Build. s.result_duration_ns = timer.read(); - const stderr_contents = stderr.buffered(); + const stderr_contents = zp.multi_reader.reader(1).buffered(); if (stderr_contents.len > 0) { try s.result_error_msgs.append(arena, try arena.dupe(u8, stderr_contents)); } diff --git a/lib/std/Build/Step/Run.zig b/lib/std/Build/Step/Run.zig index 82c15531d00576c6d1aa0db121a36c521455dd4b..c74286f61bfb6e6709223c40a12ed646ef46ef0b 100644 --- a/lib/std/Build/Step/Run.zig +++ b/lib/std/Build/Step/Run.zig @@ -1695,11 +1695,9 @@ fn evalZigTest( // The runner unexpectedly closed a stdio pipe, which means a crash. Make sure we've captured // all available stderr to make our error output as useful as possible. const stderr_fr = multi_reader.fileReader(1); - while (true) { - stderr_fr.interface.fillMore() catch |e| switch (e) { - error.ReadFailed => return stderr_fr.err.?, - error.EndOfStream => break, - }; + while (stderr_fr.interface.fillMore()) |_| {} else |e| switch (e) { + error.ReadFailed => return stderr_fr.err.?, + error.EndOfStream => {}, } run.step.result_stderr = try arena.dupe(u8, stderr_fr.interface.buffered()); @@ -1905,7 +1903,7 @@ fn waitZigTest( .clock = .awake, } } else .none; - multi_reader.fill(timeout) catch |err| switch (err) { + multi_reader.fill(64, timeout) catch |err| switch (err) { error.Timeout, error.EndOfStream => return .{ .no_poll = .{ .active_test_index = active_test_index, .ns_elapsed = if (timer) |*t| t.read() else 0, @@ -2227,7 +2225,7 @@ fn evalGeneric(run: *Run, spawn_options: process.SpawnOptions) !EvalGenericResul const stdout_reader = multi_reader.reader(0); const stderr_reader = multi_reader.reader(1); - while (multi_reader.fill(.none)) |_| { + while (multi_reader.fill(64, .none)) |_| { if (run.stdio_limit.toInt()) |limit| { if (stdout_reader.buffered().len > limit) return error.StdoutStreamTooLong; diff --git a/lib/std/Io/File/MultiReader.zig b/lib/std/Io/File/MultiReader.zig index d4024ef914c17bfffcafac1ab2559828689abdf4..a1ea42a7d8e98c261032357d482e41e199c80e74 100644 --- a/lib/std/Io/File/MultiReader.zig +++ b/lib/std/Io/File/MultiReader.zig @@ -28,18 +28,23 @@ pub const Streams = extern struct { len: u32, pub fn contexts(s: *Streams) []Context { - _ = s; - @panic("TODO"); + const base: usize = @intFromPtr(s); + const ptr: [*]Context = @ptrFromInt(std.mem.alignForward(usize, base + @sizeOf(Streams), @alignOf(Context))); + return ptr[0..s.len]; } pub fn ring(s: *Streams) []u32 { - _ = s; - @panic("TODO"); + const prev = contexts(s); + const end = prev.ptr + prev.len; + const ptr: [*]u32 = @ptrFromInt(std.mem.alignForward(usize, @intFromPtr(end), @alignOf(u32))); + return ptr[0..s.len]; } pub fn operations(s: *Streams) []Io.Operation { - _ = s; - @panic("TODO"); + const prev = ring(s); + const end = prev.ptr + prev.len; + const ptr: [*]Io.Operation = @ptrFromInt(std.mem.alignForward(usize, @intFromPtr(end), @alignOf(Io.Operation))); + return ptr[0..s.len]; } }; @@ -51,6 +56,7 @@ pub fn Buffer(comptime n: usize) type { operations: [n][@sizeOf(Io.Operation)]u8 align(@alignOf(Io.Operation)), pub fn toStreams(b: *@This()) *Streams { + b.len = n; return @ptrCast(b); } }; @@ -157,61 +163,43 @@ fn stream(r: *Io.Reader, w: *Io.Writer, limit: Io.Limit) Io.Reader.StreamError!u _ = w; const fr: *File.Reader = @alignCast(@fieldParentPtr("interface", r)); const context: *Context = @fieldParentPtr("fr", fr); - const mr = context.mr; - return fillUntimed(mr, context); + try fillUntimed(context, 1); + return 0; } fn discard(r: *Io.Reader, limit: Io.Limit) Io.Reader.Error!usize { _ = limit; const fr: *File.Reader = @alignCast(@fieldParentPtr("interface", r)); const context: *Context = @fieldParentPtr("fr", fr); - const mr = context.mr; - return fillUntimed(mr, context); + try fillUntimed(context, 1); + return 0; } fn readVec(r: *Io.Reader, data: [][]u8) Io.Reader.Error!usize { _ = data; const fr: *File.Reader = @alignCast(@fieldParentPtr("interface", r)); const context: *Context = @fieldParentPtr("fr", fr); - const mr = context.mr; - return fillUntimed(mr, context); + try fillUntimed(context, 1); + return 0; } fn rebase(r: *Io.Reader, capacity: usize) Io.Reader.RebaseError!void { const fr: *File.Reader = @alignCast(@fieldParentPtr("interface", r)); const context: *Context = @fieldParentPtr("fr", fr); - const mr = context.mr; - - return rebaseGrowing(mr, context, capacity) catch |err| { - context.err = err; - return error.ReadFailed; - }; + try fillUntimed(context, capacity); } -fn rebaseGrowing(mr: *MultiReader, context: *Context, capacity: usize) Allocator.Error!void { - const gpa = mr.gpa; - const r = &context.fr.interface; - if (r.buffer.len >= capacity) { - const data = r.buffer[r.seek..r.end]; - @memmove(r.buffer[0..data.len], data); - r.seek = 0; - r.end = data.len; - } else { - const adjusted_capacity = std.ArrayList(u8).growCapacity(capacity); - - if (r.seek == 0) { - if (gpa.remap(r.buffer, adjusted_capacity)) |new_memory| { - r.buffer = new_memory; - return; - } - } - - const data = r.buffer[r.seek..r.end]; - const new = try gpa.alloc(u8, adjusted_capacity); - @memcpy(new[0..data.len], data); - r.seek = 0; - r.end = data.len; - } +fn fillUntimed(context: *Context, capacity: usize) Io.Reader.Error!void { + fill(context.mr, capacity, .none) catch |err| switch (err) { + error.Timeout, error.UnsupportedClock => unreachable, + error.Canceled, error.ConcurrencyUnavailable => |e| { + context.err = e; + return error.ReadFailed; + }, + error.EndOfStream => |e| return e, + }; + if (context.err != null) return error.ReadFailed; + if (context.eos) return error.EndOfStream; } pub const FillError = Io.Batch.WaitError || error{ @@ -221,7 +209,7 @@ pub const FillError = Io.Batch.WaitError || error{ }; /// Wait until at least one stream receives more data. -pub fn fill(mr: *MultiReader, timeout: Io.Timeout) FillError!void { +pub fn fill(mr: *MultiReader, unused_capacity: usize, timeout: Io.Timeout) FillError!void { const contexts = mr.streams.contexts(); const operations = mr.streams.operations(); const io = contexts[0].fr.io; @@ -243,14 +231,14 @@ pub fn fill(mr: *MultiReader, timeout: Io.Timeout) FillError!void { } const r = &context.fr.interface; r.end += n; - if (r.buffer.len - r.end == 0) { - rebaseGrowing(mr, context, r.bufferedLen() + 1) catch |err| { + if (r.buffer.len - r.end < unused_capacity) { + rebaseGrowing(mr, context, r.bufferedLen() + unused_capacity) catch |err| { context.err = err; continue; }; assert(r.seek == 0); - context.vec[0] = r.buffer; } + context.vec[0] = r.buffer[r.end..]; operation.file_read_streaming.status = .{ .unstarted = {} }; mr.batch.add(i); } @@ -258,16 +246,30 @@ pub fn fill(mr: *MultiReader, timeout: Io.Timeout) FillError!void { if (!any_completed) return error.EndOfStream; } -fn fillUntimed(mr: *MultiReader, context: *Context) Io.Reader.Error!usize { - fill(mr, .none) catch |err| switch (err) { - error.Timeout, error.UnsupportedClock => unreachable, - error.Canceled, error.ConcurrencyUnavailable => |e| { - context.err = e; - return error.ReadFailed; - }, - error.EndOfStream => |e| return e, - }; - if (context.err != null) return error.ReadFailed; - if (context.eos) return error.EndOfStream; - return 0; +fn rebaseGrowing(mr: *MultiReader, context: *Context, capacity: usize) Allocator.Error!void { + const gpa = mr.gpa; + const r = &context.fr.interface; + if (r.buffer.len >= capacity) { + const data = r.buffer[r.seek..r.end]; + @memmove(r.buffer[0..data.len], data); + r.seek = 0; + r.end = data.len; + } else { + const adjusted_capacity = std.ArrayList(u8).growCapacity(capacity); + + if (r.seek == 0) { + if (gpa.remap(r.buffer, adjusted_capacity)) |new_memory| { + r.buffer = new_memory; + return; + } + } + + const data = r.buffer[r.seek..r.end]; + const new = try gpa.alloc(u8, adjusted_capacity); + @memcpy(new[0..data.len], data); + gpa.free(r.buffer); + r.buffer = new; + r.seek = 0; + r.end = data.len; + } } diff --git a/lib/std/crypto/tls/Client.zig b/lib/std/crypto/tls/Client.zig index 44a73c344a5703e1b9e7b1eb99f06e41bb2232bb..eeeb7d05373a527a8c1fbde2ff5bd8eb7aecc9a8 100644 --- a/lib/std/crypto/tls/Client.zig +++ b/lib/std/crypto/tls/Client.zig @@ -336,10 +336,11 @@ pub fn init(input: *Reader, output: *Writer, options: Options) InitError!Client // Ensure the input buffer pointer is stable in this scope. input.rebase(tls.max_ciphertext_record_len) catch |err| switch (err) { error.EndOfStream => {}, // We have assurance the remainder of stream can be buffered. + error.ReadFailed => |e| return e, }; const record_header = input.peek(tls.record_header_len) catch |err| switch (err) { error.EndOfStream => return error.TlsConnectionTruncated, - error.ReadFailed => return error.ReadFailed, + error.ReadFailed => |e| return e, }; const record_ct = input.takeEnumNonexhaustive(tls.ContentType, .big) catch unreachable; // already peeked input.toss(2); // legacy_version -- 2.54.0 From dd0153b91b55e3b32227562ed68fdaee31c5b844 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 14 Jan 2026 00:23:33 -0800 Subject: [PATCH 119/499] std.Io.operate: fix bogus catch this used to have a different error set. just goes to show you how useful switching on error set is even when there is only 1 prong --- lib/std/Io.zig | 2 +- lib/std/Io/Threaded.zig | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/std/Io.zig b/lib/std/Io.zig index 980379b72b985fdd5107e522eda9bdbee475caf0..a63a89e4ee020f91a959e62ee91782d120348c6b 100644 --- a/lib/std/Io.zig +++ b/lib/std/Io.zig @@ -303,7 +303,7 @@ pub const Operation = union(enum) { /// Performs one `Operation`. pub fn operate(io: Io, operation: *Operation) Cancelable!void { - return io.vtable.operate(io.userdata, operation) catch unreachable; + return io.vtable.operate(io.userdata, operation); } /// Submits many operations together without waiting for all of them to diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 2167baa5e48ced7aa41f4f9b6581b0e6e62c1c04..55a4c8aad73c59bb4c19bb4435877d33db033dac 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -2465,7 +2465,7 @@ fn operate(userdata: ?*anyopaque, op: *Io.Operation) Io.Cancelable!void { .file_read_streaming => |*o| { _ = o.status.unstarted; o.status = .{ .result = fileReadStreaming(o.file, o.data) catch |err| switch (err) { - error.Canceled => return error.Canceled, + error.Canceled => |e| return e, else => |e| e, } }; }, -- 2.54.0 From 372e8e54d3d7d09bc8805262c4034b7779467842 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 14 Jan 2026 00:56:00 -0800 Subject: [PATCH 120/499] compiler: update for std.Io.File.MultiReader API --- lib/std/Build/Step.zig | 11 ++++++- lib/std/Io/File/MultiReader.zig | 8 +++++ lib/std/process.zig | 2 ++ lib/std/process/Child.zig | 3 +- lib/std/zig/LibCInstallation.zig | 6 ++-- src/Compilation.zig | 51 +++++++++++++++++++++----------- 6 files changed, 59 insertions(+), 22 deletions(-) diff --git a/lib/std/Build/Step.zig b/lib/std/Build/Step.zig index 37fc2ca023f4ff9ebec6b2a88172b31e4f6182fe..0dd4b932800b3ee9c3e65dd2347381263f0eb34b 100644 --- a/lib/std/Build/Step.zig +++ b/lib/std/Build/Step.zig @@ -539,6 +539,7 @@ fn zigProcessUpdate(s: *Step, zp: *ZigProcess, watch: bool, web_server: ?*Build. if (!watch) try sendMessage(io, zp.child.stdin.?, .exit); var result: ?Path = null; + var eos_err: error{EndOfStream}!void = {}; const stdout = zp.multi_reader.fileReader(0); @@ -549,7 +550,13 @@ fn zigProcessUpdate(s: *Step, zp: *ZigProcess, watch: bool, web_server: ?*Build. error.ReadFailed => return stdout.err.?, }; const body = stdout.interface.take(header.bytes_len) catch |err| switch (err) { - error.EndOfStream => |e| return e, + error.EndOfStream => |e| { + // Better to report the crash with stderr below, but we set + // this in case the child exits successfully while violating + // this protocol. + eos_err = e; + break; + }, error.ReadFailed => return stdout.err.?, }; switch (header.tag) { @@ -647,6 +654,8 @@ fn zigProcessUpdate(s: *Step, zp: *ZigProcess, watch: bool, web_server: ?*Build. try s.result_error_msgs.append(arena, try arena.dupe(u8, stderr_contents)); } + try eos_err; + return result; } diff --git a/lib/std/Io/File/MultiReader.zig b/lib/std/Io/File/MultiReader.zig index a1ea42a7d8e98c261032357d482e41e199c80e74..0cfa777e9687c35b8651700ad831fd6b6b69b9a2 100644 --- a/lib/std/Io/File/MultiReader.zig +++ b/lib/std/Io/File/MultiReader.zig @@ -246,6 +246,14 @@ pub fn fill(mr: *MultiReader, unused_capacity: usize, timeout: Io.Timeout) FillE if (!any_completed) return error.EndOfStream; } +/// Wait until all streams fail or reach the end. +pub fn fillRemaining(mr: *MultiReader, timeout: Io.Timeout) Io.Batch.WaitError!void { + while (fill(mr, 1, timeout)) |_| {} else |err| switch (err) { + error.EndOfStream => return, + else => |e| return e, + } +} + fn rebaseGrowing(mr: *MultiReader, context: *Context, capacity: usize) Allocator.Error!void { const gpa = mr.gpa; const r = &context.fr.interface; diff --git a/lib/std/process.zig b/lib/std/process.zig index b5de41f5d88551df6a7c8a7af173b104838511d3..6f3c155f6d6712c71e582a047ab1c41ee160c4f7 100644 --- a/lib/std/process.zig +++ b/lib/std/process.zig @@ -488,6 +488,7 @@ pub const RunOptions = struct { create_no_window: bool = true, /// Darwin-only. Disable ASLR for the child process. disable_aslr: bool = false, + timeout: Io.Timeout = .none, }; pub const RunResult = struct { @@ -529,6 +530,7 @@ pub fn run(gpa: Allocator, io: Io, options: RunOptions) RunError!RunResult { .stderr = &stderr, .stdout_limit = options.stdout_limit, .stderr_limit = options.stderr_limit, + .timeout = options.timeout, }); const term = try child.wait(io); diff --git a/lib/std/process/Child.zig b/lib/std/process/Child.zig index 19c974ff9f65c9beec210ea6498d76a65c28ac67..fe6dfa389d92a302fb3667d696b1b8e771dab42e 100644 --- a/lib/std/process/Child.zig +++ b/lib/std/process/Child.zig @@ -137,6 +137,7 @@ pub const CollectOutputOptions = struct { allocator: ?Allocator = null, stdout_limit: Io.Limit = .unlimited, stderr_limit: Io.Limit = .unlimited, + timeout: Io.Timeout = .none, }; /// Collect the output from the process's stdout and stderr. Will return once @@ -173,7 +174,7 @@ pub fn collectOutput(child: *const Child, io: Io, options: CollectOutputOptions) remaining += 1; } while (remaining > 0) { - try batch.wait(io, .none); + try batch.wait(io, options.timeout); while (batch.next()) |op| { const n = try reads[op].file_read_streaming.status.result; if (n == 0) { diff --git a/lib/std/zig/LibCInstallation.zig b/lib/std/zig/LibCInstallation.zig index 02b3df54dce74ee89913d717747f3740d08c8d37..6a3f4b4813aa84dd89e2e214c36273442f9a024c 100644 --- a/lib/std/zig/LibCInstallation.zig +++ b/lib/std/zig/LibCInstallation.zig @@ -268,7 +268,8 @@ fn findNativeIncludeDirPosix(self: *LibCInstallation, gpa: Allocator, io: Io, ar }); const run_res = std.process.run(gpa, io, .{ - .max_output_bytes = 1024 * 1024, + .stdout_limit = .limited(1024 * 1024), + .stderr_limit = .limited(1024 * 1024), .argv = argv.items, .environ_map = &environ_map, // Some C compilers, such as Clang, are known to rely on argv[0] to find the path @@ -584,7 +585,8 @@ fn ccPrintFileName(gpa: Allocator, io: Io, args: CCPrintFileNameOptions) ![]u8 { try argv.append(arg1); const run_res = std.process.run(gpa, io, .{ - .max_output_bytes = 1024 * 1024, + .stdout_limit = .limited(1024 * 1024), + .stderr_limit = .limited(1024 * 1024), .argv = argv.items, .environ_map = &environ_map, // Some C compilers, such as Clang, are known to rely on argv[0] to find the path diff --git a/src/Compilation.zig b/src/Compilation.zig index 98c1b56e3855c919e2e0f80cb8af02e634a7154d..6b6021ab3c5e51351dd99a3e1e5b73a1ee18b33c 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -6873,6 +6873,7 @@ fn spawnZigRc( child_progress_node: std.Progress.Node, ) !void { const io = comp.io; + const gpa = comp.gpa; var node_name: std.ArrayList(u8) = .empty; defer node_name.deinit(arena); @@ -6887,55 +6888,69 @@ fn spawnZigRc( }); defer child.kill(io); - var poller = std.Io.poll(comp.gpa, enum { stdout, stderr }, .{ - .stdout = child.stdout.?, - .stderr = child.stderr.?, - }); - defer poller.deinit(); + var multi_reader_buffer: Io.File.MultiReader.Buffer(2) = undefined; + var multi_reader: Io.File.MultiReader = undefined; + multi_reader.init(gpa, io, multi_reader_buffer.toStreams(), &.{ child.stdout.?, child.stderr.? }); + defer multi_reader.deinit(); - const stdout = poller.reader(.stdout); + const stdout = multi_reader.fileReader(0); + const MessageHeader = std.zig.Server.Message.Header; - poll: while (true) { - const MessageHeader = std.zig.Server.Message.Header; - while (stdout.buffered().len < @sizeOf(MessageHeader)) if (!try poller.poll()) break :poll; - const header = stdout.takeStruct(MessageHeader, .little) catch unreachable; - while (stdout.buffered().len < header.bytes_len) if (!try poller.poll()) break :poll; - const body = stdout.take(header.bytes_len) catch unreachable; + var eos_err: error{EndOfStream}!void = {}; + while (true) { + const header = stdout.interface.takeStruct(MessageHeader, .little) catch |err| switch (err) { + error.EndOfStream => break, + error.ReadFailed => return stdout.err.?, + }; + const body = stdout.interface.take(header.bytes_len) catch |err| switch (err) { + error.EndOfStream => |e| { + // Better to report the crash with stderr below, but we set + // this in case the child exits successfully while violating + // this protocol. + eos_err = e; + break; + }, + error.ReadFailed => return stdout.err.?, + }; switch (header.tag) { // We expect exactly one ErrorBundle, and if any error_bundle header is // sent then it's a fatal error. .error_bundle => { - const error_bundle = try std.zig.Server.allocErrorBundle(comp.gpa, body); + const error_bundle = try std.zig.Server.allocErrorBundle(gpa, body); return comp.failWin32ResourceWithOwnedBundle(win32_resource, error_bundle); }, else => {}, // ignore other messages } } + try multi_reader.fillRemaining(.none); + // Just in case there's a failure that didn't send an ErrorBundle (e.g. an error return trace) - const stderr = poller.reader(.stderr); - const term = child.wait(io) catch |err| { return comp.failWin32Resource(win32_resource, "unable to wait for {s} rc: {t}", .{ argv[0], err }); }; + const stderr = multi_reader.reader(1).buffered(); + switch (term) { .exited => |code| { if (code != 0) { - log.err("zig rc failed with stderr:\n{s}", .{stderr.buffered()}); + log.err("zig rc failed with stderr:\n{s}", .{stderr}); return comp.failWin32Resource(win32_resource, "zig rc exited with code {d}", .{code}); } }, .signal => |sig| { - log.err("zig rc signaled {t} with stderr:\n{s}", .{ sig, stderr.buffered() }); + log.err("zig rc signaled {t} with stderr:\n{s}", .{ sig, stderr }); return comp.failWin32Resource(win32_resource, "zig rc terminated unexpectedly", .{}); }, else => { - log.err("zig rc terminated with stderr:\n{s}", .{stderr.buffered()}); + log.err("zig rc terminated with stderr:\n{s}", .{stderr}); return comp.failWin32Resource(win32_resource, "zig rc terminated unexpectedly", .{}); }, } + + try eos_err; } pub fn tmpFilePath(comp: Compilation, ally: Allocator, suffix: []const u8) error{OutOfMemory}![]const u8 { -- 2.54.0 From 68a34df0257b4baec32617bf8c70c223849b59e2 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 16 Jan 2026 21:07:59 -0800 Subject: [PATCH 121/499] std.Io.Threaded: fix error set --- lib/std/Io/Threaded.zig | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 55a4c8aad73c59bb4c19bb4435877d33db033dac..0bddff2fc2b8695bf26d2de3d22328d8d087f027 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -2475,6 +2475,7 @@ fn operate(userdata: ?*anyopaque, op: *Io.Operation) Io.Cancelable!void { fn batchWait(userdata: ?*anyopaque, b: *Io.Batch, timeout: Io.Timeout) Io.Batch.WaitError!void { const t: *Threaded = @ptrCast(@alignCast(userdata)); if (is_windows) return batchWaitWindows(t, b, timeout); + if (native_os == .wasi and !builtin.link_libc) @panic("TODO"); const operations = b.operations; const len: u31 = @intCast(operations.len); const ring = b.ring[0..len]; @@ -2597,7 +2598,7 @@ fn batchCancel(userdata: ?*anyopaque, b: *Io.Batch) void { b.user.complete_tail = complete_tail; } -fn batchWaitWindows(t: *Threaded, b: *Io.Batch, timeout: Io.Timeout) Io.ConcurrentError!void { +fn batchWaitWindows(t: *Threaded, b: *Io.Batch, timeout: Io.Timeout) Io.Batch.WaitError!void { const operations = b.operations; const len: u31 = @intCast(operations.len); const ring = b.ring[0..len]; -- 2.54.0 From 9134430387bb99504c5813abc8b566a97ba5b1f0 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 16 Jan 2026 21:21:56 -0800 Subject: [PATCH 122/499] std.Io.Threaded: fix batchWait impl --- lib/std/Io/Threaded.zig | 17 +++++++---------- 1 file changed, 7 insertions(+), 10 deletions(-) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 0bddff2fc2b8695bf26d2de3d22328d8d087f027..cfb690e634c1c13f812f22527abee1e97da594b5 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -2524,6 +2524,7 @@ fn batchWait(userdata: ?*anyopaque, b: *Io.Batch, timeout: Io.Timeout) Io.Batch. try operate(t, &operations[op]); ring[complete_tail.index(len)] = op; complete_tail = complete_tail.next(len); + poll_i = 0; return; }, else => {}, @@ -2548,24 +2549,20 @@ fn batchWait(userdata: ?*anyopaque, b: *Io.Batch, timeout: Io.Timeout) Io.Batch. if (deadline == null) continue; return error.Timeout; } - var canceled = false; - for (poll_buffer[0..poll_i], map_buffer[0..poll_i]) |*poll_fd, op| { + while (poll_i != 0) { + poll_i -= 1; + const poll_fd = &poll_buffer[poll_i]; + const op = map_buffer[poll_i]; if (poll_fd.revents == 0) { submit_head = submit_head.prev(len); ring[submit_head.index(len)] = op; } else { - operate(t, &operations[op]) catch |err| switch (err) { - error.Canceled => { - canceled = true; - continue; - }, - }; + try operate(t, &operations[op]); ring[complete_tail.index(len)] = op; complete_tail = complete_tail.next(len); } } - poll_i = 0; - return if (canceled) error.Canceled; + return; }, .INTR => continue, else => return error.ConcurrencyUnavailable, -- 2.54.0 From 54241bc770cbd610f48c365dce3c7ebf69336d73 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 16 Jan 2026 21:38:44 -0800 Subject: [PATCH 123/499] tools: update for std.process API changes --- tools/update_clang_options.zig | 1 - tools/update_cpu_features.zig | 1 - 2 files changed, 2 deletions(-) diff --git a/tools/update_clang_options.zig b/tools/update_clang_options.zig index b52267a3fbd2c7efe37b58efe19615c5c8a8c556..a073062d88c6885f3d4237eaa6cc158e1942e21f 100644 --- a/tools/update_clang_options.zig +++ b/tools/update_clang_options.zig @@ -676,7 +676,6 @@ pub fn main(init: std.process.Init) !void { const child_result = try std.process.run(arena, io, .{ .argv = &child_args, - .max_output_bytes = 100 * 1024 * 1024, }); std.debug.print("{s}\n", .{child_result.stderr}); diff --git a/tools/update_cpu_features.zig b/tools/update_cpu_features.zig index 3041ee6acc5129a3bfbe9b8d1295a94f69a529e7..eaa6a9afd262299842eeb842c3004b9c9a99bc7b 100644 --- a/tools/update_cpu_features.zig +++ b/tools/update_cpu_features.zig @@ -1987,7 +1987,6 @@ fn processOneTarget(io: Io, job: Job) void { const child_result = try std.process.run(arena, io, .{ .argv = &child_args, - .max_output_bytes = 500 * 1024 * 1024, }); tblgen_progress.end(); if (child_result.stderr.len != 0) { -- 2.54.0 From a4d438562d794023e64e50c6826eb20aeef20eab Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 16 Jan 2026 21:39:03 -0800 Subject: [PATCH 124/499] std.Io.Threaded: fix compilation failures on Windows it's still broken as hell tho --- lib/std/Io/Threaded.zig | 26 +++++++++++++++----------- 1 file changed, 15 insertions(+), 11 deletions(-) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index cfb690e634c1c13f812f22527abee1e97da594b5..0adaa60a81e563a7ea638d64894fb8b7abe24f71 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -1323,7 +1323,10 @@ fn waitForApcOrAlert() void { const max_iovecs_len = 8; const splat_buffer_size = 64; -const poll_buffer_len = 32; +/// Happens to be the same number that matches maximum number of handles that +/// NtWaitForMultipleObjects accepts. We use this value also for poll() on +/// posix systems. +const poll_buffer_len = 64; const default_PATH = "/usr/local/bin:/bin/:/usr/bin"; comptime { @@ -2635,18 +2638,13 @@ fn batchWaitWindows(t: *Threaded, b: *Io.Batch, timeout: Io.Timeout) Io.Batch.Wa overlapped.* = .{ .Internal = 0, .InternalHigh = 0, - .DUMMYUNIONNAME = .{ - .DUMMYSTRUCTNAME = .{ - .Offset = 0, - .OffsetHigh = 0, - }, - .Pointer = null, - }, + .DUMMYUNIONNAME = .{ .Pointer = null }, .hEvent = null, }; var n: windows.DWORD = undefined; const buf = o.data[0]; - if (windows.kernel32.ReadFile(o.file.handle, buf.ptr, buf.len, &n, overlapped) == 0) { + const buf_len = std.math.lossyCast(windows.DWORD, buf.len); + if (windows.kernel32.ReadFile(o.file.handle, buf.ptr, buf_len, &n, overlapped) == 0) { @panic("TODO"); } handles_buffer[buffer_i] = o.file.handle; @@ -2663,6 +2661,7 @@ fn batchWaitWindows(t: *Threaded, b: *Io.Batch, timeout: Io.Timeout) Io.Batch.Wa try operate(t, &operations[op]); ring[complete_tail.index(len)] = op; complete_tail = complete_tail.next(len); + buffer_i = 0; return; }, else => {}, @@ -2672,10 +2671,15 @@ fn batchWaitWindows(t: *Threaded, b: *Io.Batch, timeout: Io.Timeout) Io.Batch.Wa const map = map_buffer[0..buffer_i]; const syscall: Syscall = try .start(); - const index = windows.WaitForMultipleObjectsEx(handles, false, windows.INFINITE, true); + const index_result = windows.WaitForMultipleObjectsEx(handles, false, windows.INFINITE, true); syscall.finish(); + const index = index_result catch |err| switch (err) { + error.Unexpected => @panic("TODO"), + error.WaitAbandoned => @panic("TODO"), + error.WaitTimeOut => @panic("TODO"), + }; var n: windows.DWORD = undefined; - if (0 == windows.kernel32.GetOverlappedResult(handles[index], overlapped_buffer[index], &n, 0)) { + if (0 == windows.kernel32.GetOverlappedResult(handles[index], &overlapped_buffer[index], &n, 0)) { switch (windows.GetLastError()) { .BROKEN_PIPE => @panic("TODO"), .OPERATION_ABORTED => @panic("TODO"), -- 2.54.0 From 15ca46d1e70f24a1b37ac332630f6949552e4bda Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 16 Jan 2026 21:50:54 -0800 Subject: [PATCH 125/499] std.Io.Threaded: fix compilation error on some systems --- lib/std/Io/Threaded.zig | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 0adaa60a81e563a7ea638d64894fb8b7abe24f71..4024263f4fc89b34e2df96084ce2b824c6553f13 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -2486,8 +2486,8 @@ fn batchWait(userdata: ?*anyopaque, b: *Io.Batch, timeout: Io.Timeout) Io.Batch. const submit_tail = b.user.submit_tail; b.impl.submit_tail = submit_tail; var complete_tail = b.impl.complete_tail; - var map_buffer: [poll_buffer_len]u32 = undefined; // poll_buffer index to operations index - var poll_i: usize = 0; + var map_buffer: [poll_buffer_len]u8 = undefined; // poll_buffer index to operations index + var poll_i: u8 = 0; defer { for (map_buffer[0..poll_i]) |op| { submit_head = submit_head.prev(len); @@ -2515,7 +2515,7 @@ fn batchWait(userdata: ?*anyopaque, b: *Io.Batch, timeout: Io.Timeout) Io.Batch. .events = posix.POLL.IN, .revents = 0, }; - map_buffer[poll_i] = op; + map_buffer[poll_i] = @intCast(op); poll_i += 1; }, } -- 2.54.0 From a901ea23b0a53f6feba0111886156262fcff469d Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 16 Jan 2026 22:39:23 -0800 Subject: [PATCH 126/499] update doctest API usage --- tools/doctest.zig | 6 ------ 1 file changed, 6 deletions(-) diff --git a/tools/doctest.zig b/tools/doctest.zig index 55b8ca7bfb87fae39b98550812b9dee165538f46..97a9a0be3f27e079d5028403749ba68bb3b7240f 100644 --- a/tools/doctest.zig +++ b/tools/doctest.zig @@ -201,7 +201,6 @@ fn printOutput( .argv = build_args.items, .cwd = tmp_dir_path, .environ_map = environ_map, - .max_output_bytes = max_doc_file_size, }); switch (result.term) { .exited => |exit_code| { @@ -257,7 +256,6 @@ fn printOutput( .argv = run_args, .environ_map = environ_map, .cwd = tmp_dir_path, - .max_output_bytes = max_doc_file_size, }); switch (result.term) { .exited => |exit_code| { @@ -376,7 +374,6 @@ fn printOutput( .argv = test_args.items, .environ_map = environ_map, .cwd = tmp_dir_path, - .max_output_bytes = max_doc_file_size, }); switch (result.term) { .exited => |exit_code| { @@ -432,7 +429,6 @@ fn printOutput( .argv = test_args.items, .environ_map = environ_map, .cwd = tmp_dir_path, - .max_output_bytes = max_doc_file_size, }); switch (result.term) { .exited => |exit_code| { @@ -508,7 +504,6 @@ fn printOutput( .argv = build_args.items, .environ_map = environ_map, .cwd = tmp_dir_path, - .max_output_bytes = max_doc_file_size, }); switch (result.term) { .exited => |exit_code| { @@ -1132,7 +1127,6 @@ fn run( .argv = args, .environ_map = environ_map, .cwd = cwd, - .max_output_bytes = max_doc_file_size, }); switch (result.term) { .exited => |exit_code| { -- 2.54.0 From ec74d650fe5ba6b5fddd0277b06b43a94383a0e0 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 19 Jan 2026 15:14:43 -0800 Subject: [PATCH 127/499] incr-check: update to std.Io.File.MultiReader from std.Io.poll --- tools/incr-check.zig | 80 ++++++++++++++++++++++++-------------------- 1 file changed, 44 insertions(+), 36 deletions(-) diff --git a/tools/incr-check.zig b/tools/incr-check.zig index c9564f85c25ac752e46f744a07fa519a83e54ac2..840faf6f275aad2dc662183bd162698df443ee2d 100644 --- a/tools/incr-check.zig +++ b/tools/incr-check.zig @@ -28,6 +28,7 @@ fn logImpl( } pub fn main(init: std.process.Init) !void { + const gpa = init.gpa; const fatal = std.process.fatal; const arena = init.arena.allocator(); const io = init.io; @@ -224,11 +225,10 @@ pub fn main(init: std.process.Init) !void { .enable_darling = enable_darling, }; - var poller = Io.poll(arena, Eval.StreamEnum, .{ - .stdout = child.stdout.?, - .stderr = child.stderr.?, - }); - defer poller.deinit(); + var multi_reader_buffer: Io.File.MultiReader.Buffer(2) = undefined; + var multi_reader: Io.File.MultiReader = undefined; + multi_reader.init(gpa, io, multi_reader_buffer.toStreams(), &.{ child.stdout.?, child.stderr.? }); + defer multi_reader.deinit(); for (case.updates) |update| { var update_node = target_prog_node.start(update.name, 0); @@ -243,10 +243,10 @@ pub fn main(init: std.process.Init) !void { eval.write(update); try eval.requestUpdate(); - try eval.check(&poller, update, update_node); + try eval.check(&multi_reader, update, update_node); } - try eval.end(&poller); + try eval.end(&multi_reader); waitChild(&child, &eval); } @@ -272,9 +272,6 @@ const Eval = struct { enable_wasmtime: bool, enable_darling: bool, - const StreamEnum = enum { stdout, stderr }; - const Poller = Io.Poller(StreamEnum); - /// Currently this function assumes the previous updates have already been written. fn write(eval: *Eval, update: Case.Update) void { const io = eval.io; @@ -293,23 +290,29 @@ const Eval = struct { } } - fn check(eval: *Eval, poller: *Poller, update: Case.Update, prog_node: std.Progress.Node) !void { + fn check(eval: *Eval, mr: *Io.File.MultiReader, update: Case.Update, prog_node: std.Progress.Node) !void { const arena = eval.arena; - const stdout = poller.reader(.stdout); - const stderr = poller.reader(.stderr); + const stdout = mr.fileReader(0); + const stderr = &mr.fileReader(1).interface; + const Header = std.zig.Server.Message.Header; - poll: while (true) { - const Header = std.zig.Server.Message.Header; - while (stdout.buffered().len < @sizeOf(Header)) if (!try poller.poll()) break :poll; - const header = stdout.takeStruct(Header, .little) catch unreachable; - while (stdout.buffered().len < header.bytes_len) if (!try poller.poll()) break :poll; - const body = stdout.take(header.bytes_len) catch unreachable; + while (true) { + const header = stdout.interface.takeStruct(Header, .little) catch |err| switch (err) { + error.EndOfStream => break, + error.ReadFailed => return stdout.err.?, + }; + const body = stdout.interface.take(header.bytes_len) catch |err| switch (err) { + // If this panic triggers it might be helpful to rework this + // code to print the stderr from the abnormally terminated child. + error.EndOfStream => @panic("unexpected mid-message end of stream"), + error.ReadFailed => return stdout.err.?, + }; switch (header.tag) { .error_bundle => { const result_error_bundle = try std.zig.Server.allocErrorBundle(arena, body); if (stderr.bufferedLen() > 0) { - const stderr_data = try poller.toOwnedSlice(.stderr); + const stderr_data = try mr.toOwnedSlice(1); if (eval.allow_stderr) { std.log.info("error_bundle stderr:\n{s}", .{stderr_data}); } else { @@ -326,7 +329,7 @@ const Eval = struct { var r: std.Io.Reader = .fixed(body); _ = r.takeStruct(std.zig.Server.Message.EmitDigest, .little) catch unreachable; if (stderr.bufferedLen() > 0) { - const stderr_data = try poller.toOwnedSlice(.stderr); + const stderr_data = try mr.toOwnedSlice(1); if (eval.allow_stderr) { std.log.info("emit_digest stderr:\n{s}", .{stderr_data}); } else { @@ -358,11 +361,12 @@ const Eval = struct { } } - if (stderr.bufferedLen() > 0) { + const buffered_stderr = stderr.buffered(); + if (buffered_stderr.len > 0) { if (eval.allow_stderr) { - std.log.info("stderr:\n{s}", .{stderr.buffered()}); + std.log.info("stderr:\n{s}", .{buffered_stderr}); } else { - eval.fatal("unexpected stderr:\n{s}", .{stderr.buffered()}); + eval.fatal("unexpected stderr:\n{s}", .{buffered_stderr}); } } @@ -588,23 +592,27 @@ const Eval = struct { }; } - fn end(eval: *Eval, poller: *Poller) !void { + fn end(eval: *Eval, mr: *Io.File.MultiReader) !void { requestExit(eval.child, eval); - const stdout = poller.reader(.stdout); - const stderr = poller.reader(.stderr); + const stdout = mr.fileReader(0); + const Header = std.zig.Server.Message.Header; - poll: while (true) { - const Header = std.zig.Server.Message.Header; - while (stdout.buffered().len < @sizeOf(Header)) if (!try poller.poll()) break :poll; - const header = stdout.takeStruct(Header, .little) catch unreachable; - while (stdout.buffered().len < header.bytes_len) if (!try poller.poll()) break :poll; - stdout.toss(header.bytes_len); + while (true) { + const header = stdout.interface.takeStruct(Header, .little) catch |err| switch (err) { + error.EndOfStream => break, + error.ReadFailed => return stdout.err.?, + }; + stdout.interface.discardAll(header.bytes_len) catch |err| switch (err) { + error.ReadFailed => return stdout.err.?, + error.EndOfStream => |e| return e, + }; } - if (stderr.bufferedLen() > 0) { - eval.fatal("unexpected stderr:\n{s}", .{stderr.buffered()}); - } + try mr.fillRemaining(.none); + + const stderr = mr.reader(1).buffered(); + if (stderr.len > 0) eval.fatal("unexpected stderr:\n{s}", .{stderr}); } fn buildCOutput(eval: *Eval, c_path: []const u8, out_path: []const u8, prog_node: std.Progress.Node) !void { -- 2.54.0 From 276ca77bf04f574a730923aa77757ac0d2a464df Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 19 Jan 2026 15:16:51 -0800 Subject: [PATCH 128/499] build: adjust max_rss for behavior tests observed error: memory usage peaked at 0.70GB (699138048 bytes), exceeding the declared upper bound of 0.66GB (659809075 bytes) --- build.zig | 22 +--------------------- 1 file changed, 1 insertion(+), 21 deletions(-) diff --git a/build.zig b/build.zig index 9835714efd1dddf29af115c3c2a89dba528a6e06..aee378bdb2a0075c3d614b676d55631faacbd74c 100644 --- a/build.zig +++ b/build.zig @@ -472,27 +472,7 @@ pub fn build(b: *std.Build) !void { .skip_linux = skip_linux, .skip_llvm = skip_llvm, .skip_libc = skip_libc, - .max_rss = switch (b.graph.host.result.os.tag) { - .freebsd => 2_000_000_000, - .linux => switch (b.graph.host.result.cpu.arch) { - .aarch64 => 659_809_075, - .loongarch64 => 598_902_374, - .powerpc64le => 627_431_833, - .riscv64 => 827_043_430, - .s390x => 580_596_121, - .x86_64 => 3_290_894_745, - else => 3_300_000_000, - }, - .macos => switch (b.graph.host.result.cpu.arch) { - .aarch64 => 767_736_217, - else => 800_000_000, - }, - .windows => switch (b.graph.host.result.cpu.arch) { - .x86_64 => 603_070_054, - else => 700_000_000, - }, - else => 3_300_000_000, - }, + .max_rss = 3_300_000_000, })); test_modules_step.dependOn(tests.addModuleTests(b, .{ -- 2.54.0 From 37316a3cf61a0b193003a19b44116da346c6cd3e Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 26 Jan 2026 16:25:58 -0800 Subject: [PATCH 129/499] std.Io.Threaded: resolve merge conflicts --- lib/std/Io/Threaded.zig | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 4024263f4fc89b34e2df96084ce2b824c6553f13..e60959abd00f7f3dc107abae88534138e5d4d3a3 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -2459,7 +2459,6 @@ fn futexWake(userdata: ?*anyopaque, ptr: *const u32, max_waiters: u32) void { fn operate(userdata: ?*anyopaque, op: *Io.Operation) Io.Cancelable!void { const t: *Threaded = @ptrCast(@alignCast(userdata)); - _ = t; switch (op.*) { .noop => |*o| { _ = o.status.unstarted; @@ -2467,7 +2466,7 @@ fn operate(userdata: ?*anyopaque, op: *Io.Operation) Io.Cancelable!void { }, .file_read_streaming => |*o| { _ = o.status.unstarted; - o.status = .{ .result = fileReadStreaming(o.file, o.data) catch |err| switch (err) { + o.status = .{ .result = fileReadStreaming(t, o.file, o.data) catch |err| switch (err) { error.Canceled => |e| return e, else => |e| e, } }; -- 2.54.0 From 523aa213c9f7466bdff5c7030de7d221f1547621 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 26 Jan 2026 19:07:01 -0800 Subject: [PATCH 130/499] std.Io.Threaded: batchWait and batchCancel for Windows --- lib/std/Build/Step/Run.zig | 1 - lib/std/Io.zig | 8 + lib/std/Io/Threaded.zig | 350 +++++++++++++++++++++++-------------- 3 files changed, 226 insertions(+), 133 deletions(-) diff --git a/lib/std/Build/Step/Run.zig b/lib/std/Build/Step/Run.zig index c74286f61bfb6e6709223c40a12ed646ef46ef0b..a2d678275a40b49cd363686466067f5594774712 100644 --- a/lib/std/Build/Step/Run.zig +++ b/lib/std/Build/Step/Run.zig @@ -1721,7 +1721,6 @@ fn evalZigTest( // a crash of some kind. Either way, the child will terminate by itself -- wait for it. const stderr_reader = multi_reader.reader(1); const stderr_owned = try arena.dupe(u8, stderr_reader.buffered()); - stderr_reader.tossBuffered(); // Clean up everything and wait for the child to exit. child.stdin.?.close(io); diff --git a/lib/std/Io.zig b/lib/std/Io.zig index a63a89e4ee020f91a959e62ee91782d120348c6b..438fb0152957445d65bd3be978936ab0f5225448 100644 --- a/lib/std/Io.zig +++ b/lib/std/Io.zig @@ -350,6 +350,8 @@ pub const Batch = struct { } }; + /// After calling this, it is safe to unconditionally defer a call to + /// `cancel`. pub fn init(operations: []Operation, ring: []u32) Batch { const len: u31 = @intCast(operations.len); assert(ring.len == len); @@ -408,12 +410,18 @@ pub const Batch = struct { /// Starts work on any submitted operations and returns when at least one has completeed. /// /// Returns `error.Timeout` if `timeout` expires first. + /// + /// Depending on the `Io` implementation, may allocate resources that are + /// freed with `cancel`, even if an error is returned. pub fn wait(b: *Batch, io: Io, timeout: Timeout) WaitError!void { return io.vtable.batchWait(io.userdata, b, timeout); } /// Returns after all `operations` have completed. Operations which have not completed /// after this function returns were successfully dropped and had no side effects. + /// + /// This function is idempotent with respect to itself and `wait`. It is + /// safe to unconditionally `defer` a call to this function after `init`. pub fn cancel(b: *Batch, io: Io) void { return io.vtable.batchCancel(io.userdata, b); } diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index e60959abd00f7f3dc107abae88534138e5d4d3a3..0a17e58a67a14837aef66e20f4f7888540b84972 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -1255,6 +1255,32 @@ const AlertableSyscall = struct { assert(is_windows); } + fn start() Io.Cancelable!AlertableSyscall { + const thread = Thread.current orelse return .{ .thread = null }; + switch (thread.cancel_protection) { + .blocked => return .{ .thread = null }, + .unblocked => {}, + } + const old_status = thread.status.fetchOr(.{ + .cancelation = @enumFromInt(0b010), + .awaitable = .null, + }, .monotonic); + switch (old_status.cancelation) { + .parked => unreachable, + .blocked => unreachable, + .blocked_alertable => unreachable, + .blocked_canceling => unreachable, + .blocked_alertable_canceling => unreachable, + .none => return .{ .thread = thread }, // new status is `.blocked_alertable` + .canceling => { + // Status is unchanged (still `.canceling`)---change to `.canceled` before return. + thread.status.store(.{ .cancelation = .canceled, .awaitable = old_status.awaitable }, .monotonic); + return error.Canceled; + }, + .canceled => return .{ .thread = null }, // new status is `.canceled` (unchanged) + } + } + fn checkCancel(s: AlertableSyscall) Io.Cancelable!void { comptime assert(is_windows); const thread = s.thread orelse return; @@ -2501,10 +2527,10 @@ fn batchWait(userdata: ?*anyopaque, b: *Io.Batch, timeout: Io.Timeout) Io.Batch. const op = ring[submit_head.index(len)]; const operation = &operations[op]; switch (operation.*) { - .noop => { - try operate(t, operation); - ring[complete_tail.index(len)] = op; - complete_tail = complete_tail.next(len); + .noop => |*o| { + _ = o.status.unstarted; + o.status = .{ .result = {} }; + submitComplete(ring, &complete_tail, op); }, .file_read_streaming => |*o| { _ = o.status.unstarted; @@ -2524,8 +2550,7 @@ fn batchWait(userdata: ?*anyopaque, b: *Io.Batch, timeout: Io.Timeout) Io.Batch. 1 => if (timeout == .none) { const op = map_buffer[0]; try operate(t, &operations[op]); - ring[complete_tail.index(len)] = op; - complete_tail = complete_tail.next(len); + submitComplete(ring, &complete_tail, op); poll_i = 0; return; }, @@ -2560,8 +2585,7 @@ fn batchWait(userdata: ?*anyopaque, b: *Io.Batch, timeout: Io.Timeout) Io.Batch. ring[submit_head.index(len)] = op; } else { try operate(t, &operations[op]); - ring[complete_tail.index(len)] = op; - complete_tail = complete_tail.next(len); + submitComplete(ring, &complete_tail, op); } } return; @@ -2584,19 +2608,49 @@ fn batchCancel(userdata: ?*anyopaque, b: *Io.Batch) void { while (submit_head != submit_tail) : (submit_head = submit_head.next(len)) { const op = ring[submit_head.index(len)]; switch (operations[op]) { - .noop => { - operate(t, &operations[op]) catch unreachable; - ring[complete_tail.index(len)] = op; - complete_tail = complete_tail.next(len); + .noop => |*o| { + _ = o.status.unstarted; + o.status = .{ .result = {} }; + submitComplete(ring, &complete_tail, op); }, .file_read_streaming => |*o| _ = o.status.unstarted, } } + if (is_windows) { + // Iterate over pending and issue cancelations, then free the allocation for IO_STATUS_BLOCK + if (b.impl.reserved) |reserved| { + const gpa = t.allocator; + const metadatas_ptr: [*]WinOpMetadata = @ptrCast(@alignCast(reserved)); + const metadatas = metadatas_ptr[0..b.operations.len]; + for (metadatas, 0..) |*metadata, op| { + const done = @atomicLoad(windows.NTSTATUS, &metadata.iosb.u.Status, .acquire) != .PENDING; + if (done) continue; + switch (operations[op]) { + .noop => unreachable, + .file_read_streaming => |*o| { + _ = windows.ntdll.NtCancelIoFile(o.file.handle, &metadata.iosb); + }, + } + } + for (metadatas) |*metadata| { + while (@atomicLoad(windows.NTSTATUS, &metadata.iosb.u.Status, .acquire) == .PENDING) { + waitForApcOrAlert(); + } + } + gpa.free(metadatas); + b.impl.reserved = null; + } + } b.impl.submit_head = submit_tail; b.impl.complete_tail = complete_tail; b.user.complete_tail = complete_tail; } +const WinOpMetadata = struct { + iosb: windows.IO_STATUS_BLOCK, + pending: bool, +}; + fn batchWaitWindows(t: *Threaded, b: *Io.Batch, timeout: Io.Timeout) Io.Batch.WaitError!void { const operations = b.operations; const len: u31 = @intCast(operations.len); @@ -2606,16 +2660,16 @@ fn batchWaitWindows(t: *Threaded, b: *Io.Batch, timeout: Io.Timeout) Io.Batch.Wa b.impl.submit_tail = submit_tail; var complete_tail = b.impl.complete_tail; - var overlapped_buffer: [poll_buffer_len]windows.OVERLAPPED = undefined; - var handles_buffer: [poll_buffer_len]windows.HANDLE = undefined; - var map_buffer: [poll_buffer_len]u32 = undefined; // handles_buffer index to operations index - var buffer_i: usize = 0; + const metadatas_ptr: [*]WinOpMetadata = if (b.impl.reserved) |reserved| @ptrCast(@alignCast(reserved)) else a: { + const gpa = t.allocator; + const metadatas = gpa.alloc(WinOpMetadata, operations.len) catch return error.ConcurrencyUnavailable; + b.impl.reserved = metadatas.ptr; + @memset(metadatas, .{ .iosb = undefined, .pending = false }); + break :a metadatas.ptr; + }; + const metadatas = metadatas_ptr[0..operations.len]; defer { - for (map_buffer[0..buffer_i]) |op| { - submit_head = submit_head.prev(len); - ring[submit_head.index(len)] = op; - } b.impl.submit_head = submit_head; b.impl.complete_tail = complete_tail; b.user.complete_tail = complete_tail; @@ -2624,74 +2678,76 @@ fn batchWaitWindows(t: *Threaded, b: *Io.Batch, timeout: Io.Timeout) Io.Batch.Wa while (submit_head != submit_tail) : (submit_head = submit_head.next(len)) { const op = ring[submit_head.index(len)]; const operation = &operations[op]; + const metadata = &metadatas[op]; + metadata.* = .{ .iosb = undefined, .pending = false }; switch (operation.*) { - .noop => { - try operate(t, operation); - ring[complete_tail.index(len)] = op; - complete_tail = complete_tail.next(len); + .noop => |*o| { + _ = o.status.unstarted; + o.status = .{ .result = {} }; + submitComplete(ring, &complete_tail, op); }, .file_read_streaming => |*o| { _ = o.status.unstarted; - if (handles_buffer.len - buffer_i == 0) return error.ConcurrencyUnavailable; - const overlapped = &overlapped_buffer[buffer_i]; - overlapped.* = .{ - .Internal = 0, - .InternalHigh = 0, - .DUMMYUNIONNAME = .{ .Pointer = null }, - .hEvent = null, - }; - var n: windows.DWORD = undefined; - const buf = o.data[0]; - const buf_len = std.math.lossyCast(windows.DWORD, buf.len); - if (windows.kernel32.ReadFile(o.file.handle, buf.ptr, buf_len, &n, overlapped) == 0) { - @panic("TODO"); + switch (try ntReadFile(o.file.handle, o.data, &metadata.iosb)) { + .status => { + o.status = .{ .result = ntReadFileResult(&metadata.iosb) }; + submitComplete(ring, &complete_tail, op); + }, + .pending => { + o.status = .{ .pending = b }; + metadata.pending = true; + }, } - handles_buffer[buffer_i] = o.file.handle; - map_buffer[buffer_i] = op; - buffer_i += 1; }, } } - switch (buffer_i) { - 0 => return, - 1 => if (timeout == .none) { - const op = map_buffer[0]; - try operate(t, &operations[op]); - ring[complete_tail.index(len)] = op; - complete_tail = complete_tail.next(len); - buffer_i = 0; - return; - }, - else => {}, - } - - const handles = handles_buffer[0..buffer_i]; - const map = map_buffer[0..buffer_i]; + var delay_interval: windows.LARGE_INTEGER = timeoutToWindowsInterval(timeout); - const syscall: Syscall = try .start(); - const index_result = windows.WaitForMultipleObjectsEx(handles, false, windows.INFINITE, true); - syscall.finish(); - const index = index_result catch |err| switch (err) { - error.Unexpected => @panic("TODO"), - error.WaitAbandoned => @panic("TODO"), - error.WaitTimeOut => @panic("TODO"), - }; - var n: windows.DWORD = undefined; - if (0 == windows.kernel32.GetOverlappedResult(handles[index], &overlapped_buffer[index], &n, 0)) { - switch (windows.GetLastError()) { - .BROKEN_PIPE => @panic("TODO"), - .OPERATION_ABORTED => @panic("TODO"), - else => @panic("TODO"), + while (true) { + const alertable_syscall = try AlertableSyscall.start(); + const delay_rc = windows.ntdll.NtDelayExecution(windows.TRUE, &delay_interval); + alertable_syscall.finish(); + switch (delay_rc) { + .SUCCESS => { + // The thread woke due to the timeout. Although spurious + // timeouts are OK, when no deadline is passed we must not + // return `error.Timeout`. + if (timeout != .none) return error.Timeout; + }, + else => {}, } - } else switch (operations[map[index]]) { - .noop => unreachable, - .file_read_streaming => |*o| { - o.status = .{ .result = n }; - }, + var any_done = false; + var any_pending = false; + for (metadatas, 0..) |*metadata, op_usize| { + if (!metadata.pending) continue; + any_pending = true; + const op: u31 = @intCast(op_usize); + const done = @atomicLoad(windows.NTSTATUS, &metadata.iosb.u.Status, .acquire) != .PENDING; + switch (operations[op]) { + .noop => unreachable, + .file_read_streaming => |*o| { + assert(o.status.pending == b); + if (!done) continue; + o.status = .{ .result = ntReadFileResult(&metadata.iosb) }; + }, + } + any_done = true; + metadata.pending = false; + submitComplete(ring, &complete_tail, op); + } + if (any_done) return; + if (!any_pending) return; } } +fn submitComplete(ring: []u32, complete_tail: *Io.Batch.RingIndex, op: u32) void { + const ct = complete_tail.*; + const len: u31 = @intCast(ring.len); + ring[ct.index(len)] = op; + complete_tail.* = ct.next(len); +} + const dirCreateDir = switch (native_os) { .windows => dirCreateDirWindows, .wasi => dirCreateDirWasi, @@ -5529,7 +5585,7 @@ fn dirRealPathFileWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, fn realPathWindows(h_file: windows.HANDLE, out_buffer: []u8) File.RealPathError!usize { var wide_buf: [windows.PATH_MAX_WIDE]u16 = undefined; - // TODO move GetFinalPathNameByHandle logic into std.Io.Threaded and add cancel checks + // TODO move GetFinalPathNameByHandle logic into Io.Threaded and add cancel checks try Thread.checkCancel(); const wide_slice = try windows.GetFinalPathNameByHandle(h_file, .{}, &wide_buf); @@ -8617,70 +8673,42 @@ fn fileReadStreamingPosix(file: File, data: []const []u8) File.Reader.Error!usiz } fn fileReadStreamingWindows(file: File, data: []const []u8) File.Reader.Error!usize { - var index: usize = 0; - while (index < data.len and data[index].len == 0) index += 1; - if (index == data.len) return 0; - const buffer = data[index]; - var io_status_block: windows.IO_STATUS_BLOCK = undefined; - const syscall: Syscall = try .start(); - while (true) { - io_status_block.u.Status = .PENDING; - switch (windows.ntdll.NtReadFile( - file.handle, - null, // event - noopApc, // apc callback - null, // apc context - &io_status_block, - buffer.ptr, - @min(std.math.maxInt(u32), buffer.len), - null, // byte offset - null, // key - )) { - .SUCCESS, .END_OF_FILE, .PIPE_BROKEN => { - syscall.finish(); - return io_status_block.Information; - }, - .PENDING => break, - .CANCELLED => { - try syscall.checkCancel(); - continue; - }, - .INVALID_DEVICE_REQUEST => return syscall.fail(error.IsDir), - .LOCK_NOT_GRANTED => return syscall.fail(error.LockViolation), - .ACCESS_DENIED => return syscall.fail(error.AccessDenied), - .INVALID_PARAMETER => |err| return syscall.ntstatusBug(err), // streaming read of async mode file - else => |status| return syscall.unexpectedNtstatus(status), - } - } - { - // Once we get here we received PENDING so we must not return from the - // function until the operation completes. - defer while (@atomicLoad(windows.NTSTATUS, &io_status_block.u.Status, .acquire) == .PENDING) { - waitForApcOrAlert(); - }; + if (ntReadFile(file.handle, data, &io_status_block)) |result| switch (result) { + .status => return ntReadFileResult(&io_status_block), + .pending => { + // Once we get here we received PENDING so we must not return from the + // function until the operation completes. + defer while (@atomicLoad(windows.NTSTATUS, &io_status_block.u.Status, .acquire) == .PENDING) { + waitForApcOrAlert(); + }; - const alertable_syscall = syscall.toAlertable() catch |err| switch (err) { - error.Canceled => |e| { - _ = windows.ntdll.NtCancelIoFile(file.handle, &io_status_block); - return e; - }, - }; - defer alertable_syscall.finish(); - waitForApcOrAlert(); - while (@atomicLoad(windows.NTSTATUS, &io_status_block.u.Status, .acquire) == .PENDING) { - alertable_syscall.checkCancel() catch |err| switch (err) { + const alertable_syscall = AlertableSyscall.start() catch |err| switch (err) { error.Canceled => |e| { _ = windows.ntdll.NtCancelIoFile(file.handle, &io_status_block); return e; }, }; + defer alertable_syscall.finish(); waitForApcOrAlert(); - } - } + while (@atomicLoad(windows.NTSTATUS, &io_status_block.u.Status, .acquire) == .PENDING) { + alertable_syscall.checkCancel() catch |err| switch (err) { + error.Canceled => |e| { + _ = windows.ntdll.NtCancelIoFile(file.handle, &io_status_block); + return e; + }, + }; + waitForApcOrAlert(); + } + }, + } else |err| return err; + return ntReadFileResult(&io_status_block); +} + +fn ntReadFileResult(io_status_block: *windows.IO_STATUS_BLOCK) !usize { switch (io_status_block.u.Status) { .SUCCESS, .END_OF_FILE, .PIPE_BROKEN => return io_status_block.Information, - .PENDING => unreachable, // cannot return until the operation completes + .PENDING => unreachable, .INVALID_DEVICE_REQUEST => return error.IsDir, .LOCK_NOT_GRANTED => return error.LockViolation, .ACCESS_DENIED => return error.AccessDenied, @@ -8688,6 +8716,47 @@ fn fileReadStreamingWindows(file: File, data: []const []u8) File.Reader.Error!us } } +fn ntReadFile(handle: windows.HANDLE, data: []const []u8, iosb: *windows.IO_STATUS_BLOCK) Io.Cancelable!enum { status, pending } { + var index: usize = 0; + while (index < data.len and data[index].len == 0) index += 1; + if (index == data.len) { + iosb.u.Status = .SUCCESS; + iosb.Information = 0; + return .status; + } + const buffer = data[index]; + + const syscall: Syscall = try .start(); + while (true) { + iosb.u.Status = .PENDING; + switch (windows.ntdll.NtReadFile( + handle, + null, // event + noopApc, // apc callback + null, // apc context + iosb, + buffer.ptr, + @min(std.math.maxInt(u32), buffer.len), + null, // byte offset + null, // key + )) { + .PENDING => { + syscall.finish(); + return .pending; + }, + .CANCELLED => { + try syscall.checkCancel(); + continue; + }, + else => |status| { + syscall.finish(); + iosb.u.Status = status; + return .status; + }, + } + } +} + fn fileReadPositionalPosix(file: File, data: []const []u8, offset: u64) File.ReadPositionalError!usize { if (!have_preadv) @compileError("TODO implement fileReadPositionalPosix for cursed operating systems that don't support preadv (it's only Haiku)"); @@ -9318,7 +9387,7 @@ fn processExecutablePath(userdata: ?*anyopaque, out_buffer: []u8) process.Execut }; defer w.CloseHandle(h_file); - // TODO move GetFinalPathNameByHandle logic into std.Io.Threaded and add cancel checks + // TODO move GetFinalPathNameByHandle logic into Io.Threaded and add cancel checks try Thread.checkCancel(); const wide_slice = try w.GetFinalPathNameByHandle(h_file, .{}, &path_name_w_buf.data); @@ -12989,7 +13058,7 @@ fn processSetCurrentDir(userdata: ?*anyopaque, dir: Dir) process.SetCurrentDirEr if (is_windows) { var dir_path_buffer: [windows.PATH_MAX_WIDE]u16 = undefined; - // TODO move GetFinalPathNameByHandle logic into std.Io.Threaded and add cancel checks + // TODO move GetFinalPathNameByHandle logic into Io.Threaded and add cancel checks try Thread.checkCancel(); const dir_path = try windows.GetFinalPathNameByHandle(dir.handle, .{}, &dir_path_buffer); const path_len_bytes = std.math.cast(u16, dir_path.len * 2) orelse return error.NameTooLong; @@ -16657,7 +16726,7 @@ const parking_sleep = struct { /// Spurious wakeups are possible. /// /// `addr_hint` has no semantic effect, but may allow the OS to optimize this operation. -fn park(opt_deadline: ?std.Io.Clock.Timestamp, addr_hint: ?*const anyopaque) error{Timeout}!void { +fn park(opt_deadline: ?Io.Clock.Timestamp, addr_hint: ?*const anyopaque) error{Timeout}!void { comptime assert(use_parking_futex or use_parking_sleep); switch (native_os) { .windows => { @@ -16713,6 +16782,23 @@ fn park(opt_deadline: ?std.Io.Clock.Timestamp, addr_hint: ?*const anyopaque) err } } +fn timeoutToWindowsInterval(timeout: Io.Timeout) windows.LARGE_INTEGER { + switch (timeout) { + .none => { + return std.math.minInt(windows.LARGE_INTEGER); // infinite timeout + }, + .deadline => |deadline| { + const nanoseconds = deadline.raw.nanoseconds; + return @intCast(@divTrunc(nanoseconds, 100)); + }, + .duration => |duration| { + const now_timestamp = nowWindows(duration.clock) catch unreachable; + const deadline_ns = now_timestamp.nanoseconds + duration.raw.nanoseconds; + return @intCast(@divTrunc(deadline_ns, 100)); + }, + } +} + const UnparkTid = switch (native_os) { // `NtAlertMultipleThreadByThreadId` is weird and wants 64-bit thread handles? .windows => usize, -- 2.54.0 From efa502a1cd0333860848c2b6e15978f6551e02f3 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 27 Jan 2026 11:29:03 -0800 Subject: [PATCH 131/499] std.Build.Step.Run: gracefully handle test runner misbehavior specifically if it misbehaves after sending a message header but not the body --- lib/std/Build/Step/Run.zig | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/lib/std/Build/Step/Run.zig b/lib/std/Build/Step/Run.zig index a2d678275a40b49cd363686466067f5594774712..4d3cda54c948417c175caccdc0924da5cadfaf27 100644 --- a/lib/std/Build/Step/Run.zig +++ b/lib/std/Build/Step/Run.zig @@ -1930,7 +1930,28 @@ fn waitZigTest( } // There is definitely a header available now -- read it. const header = stdout.takeStruct(Header, .little) catch unreachable; - try stdout.fill(header.bytes_len); + + while (stdout.buffered().len < header.bytes_len) { + const timeout: Io.Timeout = t: { + const t = if (timer) |*t| t else break :t .none; + if (response_timeout_ns) |timeout_ns| break :t .{ .duration = .{ + .raw = .fromNanoseconds(timeout_ns -| t.read()), + .clock = .awake, + } }; + break :t .none; + }; + multi_reader.fill(64, timeout) catch |err| switch (err) { + error.Timeout, error.EndOfStream => return .{ .no_poll = .{ + .active_test_index = active_test_index, + .ns_elapsed = if (timer) |*t| t.read() else 0, + } }, + error.UnsupportedClock => { + timer = null; + continue; + }, + else => |e| return e, + }; + } const body = stdout.take(header.bytes_len) catch unreachable; var body_r: std.Io.Reader = .fixed(body); -- 2.54.0 From 2fb224cb845c18044186c1348ca9a1b2a3152948 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 27 Jan 2026 11:33:07 -0800 Subject: [PATCH 132/499] std.Io.Threaded: fix bad use of AlertableSyscall The defer would cause two problems: 1. keeping the state active during call to NtCancelIoFile 2. invalid state transition. after canceled is returned from checkCancel, new status is already canceled. calling finish after that is illegal. --- lib/std/Io/Threaded.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 0a17e58a67a14837aef66e20f4f7888540b84972..0f0b043ce03e64344da90862c34bb287cc4aeb2a 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -8689,7 +8689,6 @@ fn fileReadStreamingWindows(file: File, data: []const []u8) File.Reader.Error!us return e; }, }; - defer alertable_syscall.finish(); waitForApcOrAlert(); while (@atomicLoad(windows.NTSTATUS, &io_status_block.u.Status, .acquire) == .PENDING) { alertable_syscall.checkCancel() catch |err| switch (err) { @@ -8700,6 +8699,7 @@ fn fileReadStreamingWindows(file: File, data: []const []u8) File.Reader.Error!us }; waitForApcOrAlert(); } + alertable_syscall.finish(); }, } else |err| return err; return ntReadFileResult(&io_status_block); -- 2.54.0 From fdf1ee973e9f3cb01f6fec9e460950622cdf92e4 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 27 Jan 2026 13:24:27 -0800 Subject: [PATCH 133/499] std.Io.Threaded: move the NtDelayExecution later in batchWait also guard against receiving SUCCESS with 0 byte read ms docs say that pipes can do this if there is a 0 byte write --- lib/std/Io/Threaded.zig | 42 +++++++++++++++++++++++---------- lib/std/os/windows/kernel32.zig | 3 --- 2 files changed, 29 insertions(+), 16 deletions(-) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 0f0b043ce03e64344da90862c34bb287cc4aeb2a..00f14ed7412e851442dea2df6fec1a6b35f4df84 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -2705,18 +2705,6 @@ fn batchWaitWindows(t: *Threaded, b: *Io.Batch, timeout: Io.Timeout) Io.Batch.Wa var delay_interval: windows.LARGE_INTEGER = timeoutToWindowsInterval(timeout); while (true) { - const alertable_syscall = try AlertableSyscall.start(); - const delay_rc = windows.ntdll.NtDelayExecution(windows.TRUE, &delay_interval); - alertable_syscall.finish(); - switch (delay_rc) { - .SUCCESS => { - // The thread woke due to the timeout. Although spurious - // timeouts are OK, when no deadline is passed we must not - // return `error.Timeout`. - if (timeout != .none) return error.Timeout; - }, - else => {}, - } var any_done = false; var any_pending = false; for (metadatas, 0..) |*metadata, op_usize| { @@ -2738,6 +2726,18 @@ fn batchWaitWindows(t: *Threaded, b: *Io.Batch, timeout: Io.Timeout) Io.Batch.Wa } if (any_done) return; if (!any_pending) return; + const alertable_syscall = try AlertableSyscall.start(); + const delay_rc = windows.ntdll.NtDelayExecution(windows.TRUE, &delay_interval); + alertable_syscall.finish(); + switch (delay_rc) { + .SUCCESS => { + // The thread woke due to the timeout. Although spurious + // timeouts are OK, when no deadline is passed we must not + // return `error.Timeout`. + if (timeout != .none) return error.Timeout; + }, + else => {}, + } } } @@ -8707,7 +8707,11 @@ fn fileReadStreamingWindows(file: File, data: []const []u8) File.Reader.Error!us fn ntReadFileResult(io_status_block: *windows.IO_STATUS_BLOCK) !usize { switch (io_status_block.u.Status) { - .SUCCESS, .END_OF_FILE, .PIPE_BROKEN => return io_status_block.Information, + .SUCCESS => { + assert(io_status_block.Information != 0); + return io_status_block.Information; + }, + .END_OF_FILE, .PIPE_BROKEN => return 0, .PENDING => unreachable, .INVALID_DEVICE_REQUEST => return error.IsDir, .LOCK_NOT_GRANTED => return error.LockViolation, @@ -8744,6 +8748,17 @@ fn ntReadFile(handle: windows.HANDLE, data: []const []u8, iosb: *windows.IO_STAT syscall.finish(); return .pending; }, + .SUCCESS => { + // Only END_OF_FILE is the true end. + if (iosb.Information == 0) { + try syscall.checkCancel(); + continue; + } else { + syscall.finish(); + iosb.u.Status = .SUCCESS; + return .status; + } + }, .CANCELLED => { try syscall.checkCancel(); continue; @@ -9709,6 +9724,7 @@ fn writeFileStreamingWindows( handle: windows.HANDLE, bytes: []const u8, ) File.Writer.Error!usize { + assert(bytes.len != 0); var bytes_written: windows.DWORD = undefined; const adjusted_len = std.math.lossyCast(u32, bytes.len); const syscall: Syscall = try .start(); diff --git a/lib/std/os/windows/kernel32.zig b/lib/std/os/windows/kernel32.zig index fe28e40cbbedbd7381c2b787c27798589d75b7cc..b6785e4a33fb79eeed57e2786696f5d8ef49bb5e 100644 --- a/lib/std/os/windows/kernel32.zig +++ b/lib/std/os/windows/kernel32.zig @@ -188,9 +188,6 @@ pub extern "kernel32" fn PostQueuedCompletionStatus( lpOverlapped: ?*OVERLAPPED, ) callconv(.winapi) BOOL; -// TODO: -// GetOverlappedResultEx with bAlertable=false, which calls: GetStdHandle + WaitForSingleObjectEx. -// Uses the SwitchBack system to run implementations for older programs; Do we care about this? pub extern "kernel32" fn GetOverlappedResult( hFile: HANDLE, lpOverlapped: *OVERLAPPED, -- 2.54.0 From 8a80b5464022a9fb4320e37f9dfc2aa83539ff07 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 27 Jan 2026 15:31:23 -0800 Subject: [PATCH 134/499] std: remove error.BrokenPipe from file reads, add error.EndOfStream and make reading file streaming allowed to return 0 byte reads. According to Microsoft documentation, on Windows it is possible to get 0-byte reads from pipes when 0-byte writes are made. --- lib/std/Io.zig | 8 ++-- lib/std/Io/File.zig | 5 +- lib/std/Io/File/MultiReader.zig | 14 ++---- lib/std/Io/File/Reader.zig | 34 +++++++------ lib/std/Io/Threaded.zig | 85 ++++++++++++++------------------- lib/std/Progress.zig | 3 +- lib/std/process/Child.zig | 28 ++++++----- lib/std/zig/system.zig | 1 - 8 files changed, 82 insertions(+), 96 deletions(-) diff --git a/lib/std/Io.zig b/lib/std/Io.zig index 438fb0152957445d65bd3be978936ab0f5225448..c00e4619e830a2fcd5a7e87bfce67b4a0201fa70 100644 --- a/lib/std/Io.zig +++ b/lib/std/Io.zig @@ -187,7 +187,7 @@ pub const VTable = struct { fileWritePositional: *const fn (?*anyopaque, File, header: []const u8, data: []const []const u8, splat: usize, offset: u64) File.WritePositionalError!usize, fileWriteFileStreaming: *const fn (?*anyopaque, File, header: []const u8, *Io.File.Reader, Io.Limit) File.Writer.WriteFileError!usize, fileWriteFilePositional: *const fn (?*anyopaque, File, header: []const u8, *Io.File.Reader, Io.Limit, offset: u64) File.WriteFilePositionalError!usize, - /// Returns 0 on end of stream. + /// Returns 0 if reading at or past the end. fileReadPositional: *const fn (?*anyopaque, File, data: []const []u8, offset: u64) File.ReadPositionalError!usize, fileSeekBy: *const fn (?*anyopaque, File, relative_offset: i64) File.SeekError!void, fileSeekTo: *const fn (?*anyopaque, File, absolute_offset: u64) File.SeekError!void, @@ -263,18 +263,18 @@ pub const Operation = union(enum) { status: Status(void) = .{ .unstarted = {} }, }; - /// Returns 0 on end of stream. + /// May return 0 reads which is different than `error.EndOfStream`. pub const FileReadStreaming = struct { file: File, data: []const []u8, status: Status(Error!usize) = .{ .unstarted = {} }, - pub const Error = error{ + pub const Error = UnendingError || error{EndOfStream}; + pub const UnendingError = error{ InputOutput, SystemResources, /// Trying to read a directory file descriptor as if it were a file. IsDir, - BrokenPipe, ConnectionResetByPeer, /// File was not opened with read capability. NotOpenForReading, diff --git a/lib/std/Io/File.zig b/lib/std/Io/File.zig index c545b6022278d5043d8890a39a91a7baae81d1dc..df5b3b5a532239e1996287021d74d14ee37fd2ef 100644 --- a/lib/std/Io/File.zig +++ b/lib/std/Io/File.zig @@ -552,11 +552,13 @@ pub fn setTimestampsNow(file: File, io: Io) SetTimestampsError!void { }); } +pub const ReadStreamingError = error{EndOfStream} || Reader.Error; + /// Returns 0 on stream end or if `buffer` has no space available for data. /// /// See also: /// * `reader` -pub fn readStreaming(file: File, io: Io, buffer: []const []u8) Reader.Error!usize { +pub fn readStreaming(file: File, io: Io, buffer: []const []u8) ReadStreamingError!usize { var operation: Io.Operation = .{ .file_read_streaming = .{ .file = file, .data = buffer, @@ -570,7 +572,6 @@ pub const ReadPositionalError = error{ SystemResources, /// Trying to read a directory file descriptor as if it were a file. IsDir, - BrokenPipe, /// Non-blocking has been enabled, and reading from the file descriptor /// would block. WouldBlock, diff --git a/lib/std/Io/File/MultiReader.zig b/lib/std/Io/File/MultiReader.zig index 0cfa777e9687c35b8651700ad831fd6b6b69b9a2..08ad76000ca65a049e1537633b1d93ae18eef1f2 100644 --- a/lib/std/Io/File/MultiReader.zig +++ b/lib/std/Io/File/MultiReader.zig @@ -15,10 +15,9 @@ pub const Context = struct { fr: File.Reader, vec: [1][]u8, err: ?Error, - eos: bool, }; -pub const Error = Allocator.Error || File.Reader.Error || Io.ConcurrentError; +pub const Error = Allocator.Error || File.ReadStreamingError || Io.ConcurrentError; /// Trailing: /// * `contexts: [len]Context` @@ -85,7 +84,6 @@ pub fn init(mr: *MultiReader, gpa: Allocator, io: Io, streams: *Streams, files: }, .vec = .{&.{}}, .err = null, - .eos = false, }; const operations = streams.operations(); const ring = streams.ring(); @@ -198,8 +196,10 @@ fn fillUntimed(context: *Context, capacity: usize) Io.Reader.Error!void { }, error.EndOfStream => |e| return e, }; - if (context.err != null) return error.ReadFailed; - if (context.eos) return error.EndOfStream; + if (context.err) |err| switch (err) { + error.EndOfStream => |e| return e, + else => return error.ReadFailed, + }; } pub const FillError = Io.Batch.WaitError || error{ @@ -225,10 +225,6 @@ pub fn fill(mr: *MultiReader, unused_capacity: usize, timeout: Io.Timeout) FillE context.err = err; continue; }; - if (n == 0) { - context.eos = true; - continue; - } const r = &context.fr.interface; r.end += n; if (r.buffer.len - r.end < unused_capacity) { diff --git a/lib/std/Io/File/Reader.zig b/lib/std/Io/File/Reader.zig index 7703521d7ebb8b177d40335e06490af4a74cfd69..effd000df8d675e29368039c93f17fea053013f8 100644 --- a/lib/std/Io/File/Reader.zig +++ b/lib/std/Io/File/Reader.zig @@ -26,7 +26,7 @@ size_err: ?SizeError = null, seek_err: ?SeekError = null, interface: Io.Reader, -pub const Error = Io.Operation.FileReadStreaming.Error || Io.Cancelable; +pub const Error = Io.Operation.FileReadStreaming.UnendingError || Io.Cancelable; pub const SizeError = File.StatError || error{ /// Occurs if, for example, the file handle is a network socket and therefore does not have a size. @@ -280,14 +280,16 @@ fn readVecStreaming(r: *Reader, data: [][]u8) Io.Reader.Error!usize { const dest_n, const data_size = try r.interface.writableVector(&iovecs_buffer, data); const dest = iovecs_buffer[0..dest_n]; assert(dest[0].len > 0); - const n = r.file.readStreaming(io, dest) catch |err| { - r.err = err; - return error.ReadFailed; + const n = r.file.readStreaming(io, dest) catch |err| switch (err) { + error.EndOfStream => { + r.size = r.pos; + return error.EndOfStream; + }, + else => |e| { + r.err = e; + return error.ReadFailed; + }, }; - if (n == 0) { - r.size = r.pos; - return error.EndOfStream; - } r.pos += n; if (n > data_size) { r.interface.end += n - data_size; @@ -335,14 +337,16 @@ fn discard(io_reader: *Io.Reader, limit: Io.Limit) Io.Reader.Error!usize { const dest_n, const data_size = try r.interface.writableVector(&iovecs_buffer, &data); const dest = iovecs_buffer[0..dest_n]; assert(dest[0].len > 0); - const n = file.readStreaming(io, dest) catch |err| { - r.err = err; - return error.ReadFailed; + const n = file.readStreaming(io, dest) catch |err| switch (err) { + error.EndOfStream => { + r.size = r.pos; + return error.EndOfStream; + }, + else => |e| { + r.err = e; + return error.ReadFailed; + }, }; - if (n == 0) { - r.size = r.pos; - return error.EndOfStream; - } r.pos += n; if (n > data_size) { r.interface.end += n - data_size; diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 00f14ed7412e851442dea2df6fec1a6b35f4df84..c9c38c6b29a348b15112b9e3b82e884df416aefa 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -8583,14 +8583,14 @@ fn fileClose(userdata: ?*anyopaque, files: []const File) void { for (files) |file| posix.close(file.handle); } -fn fileReadStreaming(userdata: ?*anyopaque, file: File, data: []const []u8) File.Reader.Error!usize { +fn fileReadStreaming(userdata: ?*anyopaque, file: File, data: []const []u8) File.ReadStreamingError!usize { const t: *Threaded = @ptrCast(@alignCast(userdata)); _ = t; if (is_windows) return fileReadStreamingWindows(file, data); return fileReadStreamingPosix(file, data); } -fn fileReadStreamingPosix(file: File, data: []const []u8) File.Reader.Error!usize { +fn fileReadStreamingPosix(file: File, data: []const []u8) File.ReadStreamingError!usize { var iovecs_buffer: [max_iovecs_len]posix.iovec = undefined; var i: usize = 0; for (data) |buf| { @@ -8611,28 +8611,24 @@ fn fileReadStreamingPosix(file: File, data: []const []u8) File.Reader.Error!usiz switch (std.os.wasi.fd_read(file.handle, dest.ptr, dest.len, &nread)) { .SUCCESS => { syscall.finish(); + if (nread == 0) return error.EndOfStream; return nread; }, .INTR, .TIMEDOUT => { try syscall.checkCancel(); continue; }, - else => |e| { - syscall.finish(); - switch (e) { - .INVAL => |err| return errnoBug(err), - .FAULT => |err| return errnoBug(err), - .BADF => return error.IsDir, // File operation on directory. - .IO => return error.InputOutput, - .ISDIR => return error.IsDir, - .NOBUFS => return error.SystemResources, - .NOMEM => return error.SystemResources, - .NOTCONN => return error.SocketUnconnected, - .CONNRESET => return error.ConnectionResetByPeer, - .NOTCAPABLE => return error.AccessDenied, - else => |err| return posix.unexpectedErrno(err), - } - }, + .BADF => return syscall.fail(error.IsDir), // File operation on directory. + .IO => return syscall.fail(error.InputOutput), + .ISDIR => return syscall.fail(error.IsDir), + .NOBUFS => return syscall.fail(error.SystemResources), + .NOMEM => return syscall.fail(error.SystemResources), + .NOTCONN => return syscall.fail(error.SocketUnconnected), + .CONNRESET => return syscall.fail(error.ConnectionResetByPeer), + .NOTCAPABLE => return syscall.fail(error.AccessDenied), + .INVAL => |err| return syscall.errnoBug(err), + .FAULT => |err| return syscall.errnoBug(err), + else => |err| return syscall.unexpectedErrno(err), } } } @@ -8643,36 +8639,33 @@ fn fileReadStreamingPosix(file: File, data: []const []u8) File.Reader.Error!usiz switch (posix.errno(rc)) { .SUCCESS => { syscall.finish(); + if (rc == 0) return error.EndOfStream; return @intCast(rc); }, .INTR, .TIMEDOUT => { try syscall.checkCancel(); continue; }, - else => |e| { + .BADF => { syscall.finish(); - switch (e) { - .INVAL => |err| return errnoBug(err), - .FAULT => |err| return errnoBug(err), - .AGAIN => return error.WouldBlock, - .BADF => { - if (native_os == .wasi) return error.IsDir; // File operation on directory. - return error.NotOpenForReading; - }, - .IO => return error.InputOutput, - .ISDIR => return error.IsDir, - .NOBUFS => return error.SystemResources, - .NOMEM => return error.SystemResources, - .NOTCONN => return error.SocketUnconnected, - .CONNRESET => return error.ConnectionResetByPeer, - else => |err| return posix.unexpectedErrno(err), - } + if (native_os == .wasi) return error.IsDir; // File operation on directory. + return error.NotOpenForReading; }, + .AGAIN => return syscall.fail(error.WouldBlock), + .IO => return syscall.fail(error.InputOutput), + .ISDIR => return syscall.fail(error.IsDir), + .NOBUFS => return syscall.fail(error.SystemResources), + .NOMEM => return syscall.fail(error.SystemResources), + .NOTCONN => return syscall.fail(error.SocketUnconnected), + .CONNRESET => return syscall.fail(error.ConnectionResetByPeer), + .INVAL => |err| return syscall.errnoBug(err), + .FAULT => |err| return syscall.errnoBug(err), + else => |err| return syscall.unexpectedErrno(err), } } } -fn fileReadStreamingWindows(file: File, data: []const []u8) File.Reader.Error!usize { +fn fileReadStreamingWindows(file: File, data: []const []u8) File.ReadStreamingError!usize { var io_status_block: windows.IO_STATUS_BLOCK = undefined; if (ntReadFile(file.handle, data, &io_status_block)) |result| switch (result) { .status => return ntReadFileResult(&io_status_block), @@ -8707,11 +8700,9 @@ fn fileReadStreamingWindows(file: File, data: []const []u8) File.Reader.Error!us fn ntReadFileResult(io_status_block: *windows.IO_STATUS_BLOCK) !usize { switch (io_status_block.u.Status) { - .SUCCESS => { - assert(io_status_block.Information != 0); - return io_status_block.Information; - }, - .END_OF_FILE, .PIPE_BROKEN => return 0, + .SUCCESS => return io_status_block.Information, + .END_OF_FILE => return error.EndOfStream, + .PIPE_BROKEN => return error.EndOfStream, .PENDING => unreachable, .INVALID_DEVICE_REQUEST => return error.IsDir, .LOCK_NOT_GRANTED => return error.LockViolation, @@ -8749,15 +8740,9 @@ fn ntReadFile(handle: windows.HANDLE, data: []const []u8, iosb: *windows.IO_STAT return .pending; }, .SUCCESS => { - // Only END_OF_FILE is the true end. - if (iosb.Information == 0) { - try syscall.checkCancel(); - continue; - } else { - syscall.finish(); - iosb.u.Status = .SUCCESS; - return .status; - } + syscall.finish(); + iosb.u.Status = .SUCCESS; + return .status; }, .CANCELLED => { try syscall.checkCancel(); diff --git a/lib/std/Progress.zig b/lib/std/Progress.zig index 5ccc46778b43d0260ee1a6323e13eae20e31a2c9..d0ee9e556f5c384cf7eef6952900447d7c45a07b 100644 --- a/lib/std/Progress.zig +++ b/lib/std/Progress.zig @@ -984,7 +984,7 @@ fn serializeIpc(start_serialized_len: usize, serialized_buffer: *Serialized.Buff var bytes_read: usize = 0; while (true) { const n = file.readStreaming(io, &.{pipe_buf[bytes_read..]}) catch |err| switch (err) { - error.WouldBlock => break, + error.WouldBlock, error.EndOfStream => break, else => |e| { std.log.debug("failed to read child progress data: {t}", .{e}); main_storage.completed_count = 0; @@ -992,7 +992,6 @@ fn serializeIpc(start_serialized_len: usize, serialized_buffer: *Serialized.Buff continue :main_loop; }, }; - if (n == 0) break; if (opt_saved_metadata) |m| { if (m.remaining_read_trash_bytes > 0) { assert(bytes_read == 0); diff --git a/lib/std/process/Child.zig b/lib/std/process/Child.zig index fe6dfa389d92a302fb3667d696b1b8e771dab42e..e226fb7a9b27c4b48647798fb715a65401d964f1 100644 --- a/lib/std/process/Child.zig +++ b/lib/std/process/Child.zig @@ -176,19 +176,21 @@ pub fn collectOutput(child: *const Child, io: Io, options: CollectOutputOptions) while (remaining > 0) { try batch.wait(io, options.timeout); while (batch.next()) |op| { - const n = try reads[op].file_read_streaming.status.result; - if (n == 0) { - remaining -= 1; - } else { - lists[op].items.len += n; - if (lists[op].items.len > @intFromEnum(limits[op])) return error.StreamTooLong; - if (options.allocator) |gpa| try lists[op].ensureUnusedCapacity(gpa, 1); - const cap = lists[op].unusedCapacitySlice(); - if (cap.len == 0) return error.StreamTooLong; - vecs[op][0] = cap; - reads[op].file_read_streaming.status = .{ .unstarted = {} }; - batch.add(op); - } + const n = reads[op].file_read_streaming.status.result catch |err| switch (err) { + error.EndOfStream => { + remaining -= 1; + continue; + }, + else => |e| return e, + }; + lists[op].items.len += n; + if (lists[op].items.len > @intFromEnum(limits[op])) return error.StreamTooLong; + if (options.allocator) |gpa| try lists[op].ensureUnusedCapacity(gpa, 1); + const cap = lists[op].unusedCapacitySlice(); + if (cap.len == 0) return error.StreamTooLong; + vecs[op][0] = cap; + reads[op].file_read_streaming.status = .{ .unstarted = {} }; + batch.add(op); } } } diff --git a/lib/std/zig/system.zig b/lib/std/zig/system.zig index 5046e2f51b081b77c95a9e08020eb24b688498a5..b32b554dee373c371084740ae91294ef264c1206 100644 --- a/lib/std/zig/system.zig +++ b/lib/std/zig/system.zig @@ -420,7 +420,6 @@ pub fn resolveTargetQuery(io: Io, query: Target.Query) DetectError!Target { error.Canceled => |e| return e, error.Unexpected => |e| return e, error.WouldBlock => return error.Unexpected, - error.BrokenPipe => return error.Unexpected, error.ConnectionResetByPeer => return error.Unexpected, error.NotOpenForReading => return error.Unexpected, error.SocketUnconnected => return error.Unexpected, -- 2.54.0 From 6a1fd3c69db486df7a805d211e484c60efe294ae Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 28 Jan 2026 00:01:41 -0800 Subject: [PATCH 135/499] std.Io.File.MultiReader: make checkAnyError exclude EndOfStream --- lib/std/Build/Step/Run.zig | 6 ++---- lib/std/Io/File/MultiReader.zig | 10 ++++++---- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/lib/std/Build/Step/Run.zig b/lib/std/Build/Step/Run.zig index 4d3cda54c948417c175caccdc0924da5cadfaf27..55fee9a2868b74b0a6006cb67c7a366b7b477ed4 100644 --- a/lib/std/Build/Step/Run.zig +++ b/lib/std/Build/Step/Run.zig @@ -1385,14 +1385,12 @@ fn runCommand( break :term spawnChildAndCollect(run, interp_argv.items, &environ_map, has_side_effects, options, fuzz_context) catch |e| { if (!run.failing_to_execute_foreign_is_an_error) return error.MakeSkipped; if (e == error.MakeFailed) return error.MakeFailed; // error already reported - return step.fail("unable to spawn interpreter {s}: {s}", .{ - interp_argv.items[0], @errorName(e), - }); + return step.fail("unable to spawn interpreter {s}: {t}", .{ interp_argv.items[0], e }); }; } if (err == error.MakeFailed) return error.MakeFailed; // error already reported - return step.fail("failed to spawn and capture stdio from {s}: {s}", .{ argv[0], @errorName(err) }); + return step.fail("failed to spawn and capture stdio from {s}: {t}", .{ argv[0], err }); }; const generic_result = opt_generic_result orelse { diff --git a/lib/std/Io/File/MultiReader.zig b/lib/std/Io/File/MultiReader.zig index 08ad76000ca65a049e1537633b1d93ae18eef1f2..7a0f8de068d5912b8ba903665eba0b1c19fb7265 100644 --- a/lib/std/Io/File/MultiReader.zig +++ b/lib/std/Io/File/MultiReader.zig @@ -17,7 +17,8 @@ pub const Context = struct { err: ?Error, }; -pub const Error = Allocator.Error || File.ReadStreamingError || Io.ConcurrentError; +pub const Error = UnendingError || error{EndOfStream}; +pub const UnendingError = Allocator.Error || File.Reader.Error || Io.ConcurrentError; /// Trailing: /// * `contexts: [len]Context` @@ -126,13 +127,14 @@ pub fn reader(mr: *MultiReader, index: usize) *Io.Reader { } /// Checks for errors in all streams, prioritizing `error.Canceled` if it -/// occurred anywhere. -pub fn checkAnyError(mr: *const MultiReader) Error!void { +/// occurred anywhere, and ignoring `error.EndOfStream`. +pub fn checkAnyError(mr: *const MultiReader) UnendingError!void { const contexts = mr.streams.contexts(); - var other: Error!void = {}; + var other: UnendingError!void = {}; for (contexts) |*context| { if (context.err) |err| switch (err) { error.Canceled => |e| return e, + error.EndOfStream => continue, else => |e| other = e, }; } -- 2.54.0 From b2816f26980f7ac9f0d87c844320e29dfbb6269e Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 28 Jan 2026 02:27:20 -0800 Subject: [PATCH 136/499] build.zig: only-c implies no-lib --- build.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.zig b/build.zig index aee378bdb2a0075c3d614b676d55631faacbd74c..3ed237fa5ea851ee8412851715038400ec29e1b7 100644 --- a/build.zig +++ b/build.zig @@ -29,7 +29,7 @@ pub fn build(b: *std.Build) !void { const use_zig_libcxx = b.option(bool, "use-zig-libcxx", "If libc++ is needed, use zig's bundled version, don't try to integrate with the system") orelse false; const test_step = b.step("test", "Run all the tests"); - const skip_install_lib_files = b.option(bool, "no-lib", "skip copying of lib/ files and langref to installation prefix. Useful for development") orelse false; + const skip_install_lib_files = b.option(bool, "no-lib", "skip copying of lib/ files and langref to installation prefix. Useful for development") orelse only_c; const skip_install_langref = b.option(bool, "no-langref", "skip copying of langref to the installation prefix") orelse skip_install_lib_files; const std_docs = b.option(bool, "std-docs", "include standard library autodocs") orelse false; const no_bin = b.option(bool, "no-bin", "skip emitting compiler binary") orelse false; -- 2.54.0 From 687123a85eaac8b7c290b21c346e8aeb8470dfcb Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 28 Jan 2026 17:43:42 -0800 Subject: [PATCH 137/499] std.process.run: use Io.File.MultiReader and delete the special-cased function --- lib/std/process.zig | 49 +++++++++++++++------------ lib/std/process/Child.zig | 70 --------------------------------------- 2 files changed, 28 insertions(+), 91 deletions(-) diff --git a/lib/std/process.zig b/lib/std/process.zig index 6f3c155f6d6712c71e582a047ab1c41ee160c4f7..739027da0754f922f9418a7d1b124f08a26a408e 100644 --- a/lib/std/process.zig +++ b/lib/std/process.zig @@ -453,16 +453,16 @@ pub fn spawnPath(io: Io, dir: Io.Dir, options: SpawnOptions) SpawnError!Child { return io.vtable.processSpawnPath(io.userdata, dir, options); } -pub const RunError = SpawnError || Child.CollectOutputError; +pub const RunError = SpawnError || error{ + StreamTooLong, +} || Io.ConcurrentError || Allocator.Error || Io.File.Reader.Error || Io.Timeout.Error; pub const RunOptions = struct { argv: []const []const u8, stderr_limit: Io.Limit = .unlimited, stdout_limit: Io.Limit = .unlimited, - /// How many bytes to initially allocate for stderr. - stderr_reserve_amount: usize = 1, - /// How many bytes to initially allocate for stdout. - stdout_reserve_amount: usize = 1, + /// How many bytes to initially allocate for stderr and stdout. + reserve_amount: usize = 64, /// Set to change the current working directory when spawning the child process. cwd: ?[]const u8 = null, @@ -516,29 +516,36 @@ pub fn run(gpa: Allocator, io: Io, options: RunOptions) RunError!RunResult { }); defer child.kill(io); - var stdout: std.ArrayList(u8) = .empty; - defer stdout.deinit(gpa); - var stderr: std.ArrayList(u8) = .empty; - defer stderr.deinit(gpa); + var multi_reader_buffer: Io.File.MultiReader.Buffer(2) = undefined; + var multi_reader: Io.File.MultiReader = undefined; + multi_reader.init(gpa, io, multi_reader_buffer.toStreams(), &.{ child.stdout.?, child.stderr.? }); + defer multi_reader.deinit(); - try stdout.ensureUnusedCapacity(gpa, options.stdout_reserve_amount); - try stderr.ensureUnusedCapacity(gpa, options.stderr_reserve_amount); + const stdout_reader = multi_reader.reader(0); + const stderr_reader = multi_reader.reader(1); - try child.collectOutput(io, .{ - .allocator = gpa, - .stdout = &stdout, - .stderr = &stderr, - .stdout_limit = options.stdout_limit, - .stderr_limit = options.stderr_limit, - .timeout = options.timeout, - }); + while (multi_reader.fill(options.reserve_amount, options.timeout)) |_| { + if (options.stdout_limit.toInt()) |limit| { + if (stdout_reader.buffered().len > limit) + return error.StreamTooLong; + } + if (options.stderr_limit.toInt()) |limit| { + if (stderr_reader.buffered().len > limit) + return error.StreamTooLong; + } + } else |err| switch (err) { + error.EndOfStream => {}, + else => |e| return e, + } + + try multi_reader.checkAnyError(); const term = try child.wait(io); - const stdout_slice = try stdout.toOwnedSlice(gpa); + const stdout_slice = try multi_reader.toOwnedSlice(0); errdefer gpa.free(stdout_slice); - const stderr_slice = try stderr.toOwnedSlice(gpa); + const stderr_slice = try multi_reader.toOwnedSlice(1); errdefer gpa.free(stderr_slice); return .{ diff --git a/lib/std/process/Child.zig b/lib/std/process/Child.zig index e226fb7a9b27c4b48647798fb715a65401d964f1..c87d221a95c4ecdff81b467f8db72495a32a642f 100644 --- a/lib/std/process/Child.zig +++ b/lib/std/process/Child.zig @@ -124,73 +124,3 @@ pub fn wait(child: *Child, io: Io) WaitError!Term { assert(child.id != null); return io.vtable.childWait(io.userdata, child); } - -pub const CollectOutputError = error{ - StreamTooLong, -} || Io.ConcurrentError || Allocator.Error || Io.File.Reader.Error || Io.Timeout.Error; - -pub const CollectOutputOptions = struct { - stdout: *std.ArrayList(u8), - stderr: *std.ArrayList(u8), - /// Used for `stdout` and `stderr`. If not provided, only the existing - /// capacity will be used. - allocator: ?Allocator = null, - stdout_limit: Io.Limit = .unlimited, - stderr_limit: Io.Limit = .unlimited, - timeout: Io.Timeout = .none, -}; - -/// Collect the output from the process's stdout and stderr. Will return once -/// all output has been collected. This does not mean that the process has -/// ended. `wait` should still be called to wait for and clean up the process. -/// -/// The process must have been started with stdout and stderr set to -/// `process.SpawnOptions.StdIo.pipe`. -pub fn collectOutput(child: *const Child, io: Io, options: CollectOutputOptions) CollectOutputError!void { - const files: [2]Io.File = .{ child.stdout.?, child.stderr.? }; - const lists: [2]*std.ArrayList(u8) = .{ options.stdout, options.stderr }; - const limits: [2]Io.Limit = .{ options.stdout_limit, options.stderr_limit }; - var reads: [2]Io.Operation = undefined; - var vecs: [2][1][]u8 = undefined; - var ring: [2]u32 = undefined; - var batch: Io.Batch = .init(&reads, &ring); - defer { - batch.cancel(io); - while (batch.next()) |op| { - lists[op].items.len += reads[op].file_read_streaming.status.result catch continue; - } - } - var remaining: usize = 0; - for (0.., &reads, &lists, &files, &vecs) |op, *read, list, file, *vec| { - if (options.allocator) |gpa| try list.ensureUnusedCapacity(gpa, 1); - const cap = list.unusedCapacitySlice(); - if (cap.len == 0) return error.StreamTooLong; - vec[0] = cap; - read.* = .{ .file_read_streaming = .{ - .file = file, - .data = vec, - } }; - batch.add(op); - remaining += 1; - } - while (remaining > 0) { - try batch.wait(io, options.timeout); - while (batch.next()) |op| { - const n = reads[op].file_read_streaming.status.result catch |err| switch (err) { - error.EndOfStream => { - remaining -= 1; - continue; - }, - else => |e| return e, - }; - lists[op].items.len += n; - if (lists[op].items.len > @intFromEnum(limits[op])) return error.StreamTooLong; - if (options.allocator) |gpa| try lists[op].ensureUnusedCapacity(gpa, 1); - const cap = lists[op].unusedCapacitySlice(); - if (cap.len == 0) return error.StreamTooLong; - vecs[op][0] = cap; - reads[op].file_read_streaming.status = .{ .unstarted = {} }; - batch.add(op); - } - } -} -- 2.54.0 From 7a13d57916aae2840047cc3461aa44c3a72ca546 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 28 Jan 2026 17:59:46 -0800 Subject: [PATCH 138/499] std.Io.Threaded: add missing check for pending status in batchCancel --- lib/std/Io/Threaded.zig | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index c9c38c6b29a348b15112b9e3b82e884df416aefa..05ff37b76e40867ff7f0df62196aac07b3072353 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -2623,6 +2623,7 @@ fn batchCancel(userdata: ?*anyopaque, b: *Io.Batch) void { const metadatas_ptr: [*]WinOpMetadata = @ptrCast(@alignCast(reserved)); const metadatas = metadatas_ptr[0..b.operations.len]; for (metadatas, 0..) |*metadata, op| { + if (!metadata.pending) continue; const done = @atomicLoad(windows.NTSTATUS, &metadata.iosb.u.Status, .acquire) != .PENDING; if (done) continue; switch (operations[op]) { @@ -2633,6 +2634,7 @@ fn batchCancel(userdata: ?*anyopaque, b: *Io.Batch) void { } } for (metadatas) |*metadata| { + if (!metadata.pending) continue; while (@atomicLoad(windows.NTSTATUS, &metadata.iosb.u.Status, .acquire) == .PENDING) { waitForApcOrAlert(); } -- 2.54.0 From d770e14e001daaea9eb921c1630af69c518468a2 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 28 Jan 2026 18:19:12 -0800 Subject: [PATCH 139/499] std.Io.Threaded.batchWaitWindows: eager result sets any_done true Thanks jacobly for finding the bug --- lib/std/Io/Threaded.zig | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 05ff37b76e40867ff7f0df62196aac07b3072353..cfcb9f65dfb4aaba360f593f1f7442c545f302e1 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -2677,6 +2677,8 @@ fn batchWaitWindows(t: *Threaded, b: *Io.Batch, timeout: Io.Timeout) Io.Batch.Wa b.user.complete_tail = complete_tail; } + var any_done = false; + while (submit_head != submit_tail) : (submit_head = submit_head.next(len)) { const op = ring[submit_head.index(len)]; const operation = &operations[op]; @@ -2686,6 +2688,7 @@ fn batchWaitWindows(t: *Threaded, b: *Io.Batch, timeout: Io.Timeout) Io.Batch.Wa .noop => |*o| { _ = o.status.unstarted; o.status = .{ .result = {} }; + any_done = true; submitComplete(ring, &complete_tail, op); }, .file_read_streaming => |*o| { @@ -2693,6 +2696,7 @@ fn batchWaitWindows(t: *Threaded, b: *Io.Batch, timeout: Io.Timeout) Io.Batch.Wa switch (try ntReadFile(o.file.handle, o.data, &metadata.iosb)) { .status => { o.status = .{ .result = ntReadFileResult(&metadata.iosb) }; + any_done = true; submitComplete(ring, &complete_tail, op); }, .pending => { @@ -2707,7 +2711,6 @@ fn batchWaitWindows(t: *Threaded, b: *Io.Batch, timeout: Io.Timeout) Io.Batch.Wa var delay_interval: windows.LARGE_INTEGER = timeoutToWindowsInterval(timeout); while (true) { - var any_done = false; var any_pending = false; for (metadatas, 0..) |*metadata, op_usize| { if (!metadata.pending) continue; -- 2.54.0 From 3320e6a1ae453d40dd78ff3abf6c8543bec2555d Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 28 Jan 2026 18:40:48 -0800 Subject: [PATCH 140/499] std.Io.Threaded.batchWait better fix for any_done It is legal to call batchWait with already completed operations in the ring. In such case, we need to avoid waiting in the syscall. The any_done flag was a poor way of tracking state we already have: whether the completion queue is empty. This problem affects the posix poll implementation as well. Thanks again to jacobly for finding the problem. --- lib/std/Io/Threaded.zig | 25 +++++++++++++++++-------- 1 file changed, 17 insertions(+), 8 deletions(-) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index cfcb9f65dfb4aaba360f593f1f7442c545f302e1..29f578757444279ebe7070d6c249b15167ad4726 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -2560,17 +2560,31 @@ fn batchWait(userdata: ?*anyopaque, b: *Io.Batch, timeout: Io.Timeout) Io.Batch. const deadline = timeout.toDeadline(t_io) catch return error.UnsupportedClock; const max_poll_ms = std.math.maxInt(i32); while (true) { - const timeout_ms: i32 = if (deadline) |d| t: { + const timeout_ms: i32 = t: { + if (b.user.complete_head != complete_tail) { + // It is legal to call batchWait with already completed + // operations in the ring. In such case, we need to avoid + // blocking in the poll syscall, but we can still take this + // opportunity to find additional ready operations. + break :t 0; + } + const d = deadline orelse break :t -1; const duration = d.durationFromNow(t_io) catch return error.UnsupportedClock; if (duration.raw.nanoseconds <= 0) return error.Timeout; break :t @intCast(@min(max_poll_ms, duration.raw.toMilliseconds())); - } else -1; + }; const syscall = try Syscall.start(); const rc = posix.system.poll(&poll_buffer, poll_i, timeout_ms); syscall.finish(); switch (posix.errno(rc)) { .SUCCESS => { if (rc == 0) { + if (b.user.complete_head != complete_tail) { + // Since there are already completions available in the + // queue, this is neither a timeout nor a case for + // retrying. + return; + } // Although spurious timeouts are OK, when no deadline is // passed we must not return `error.Timeout`. if (deadline == null) continue; @@ -2677,8 +2691,6 @@ fn batchWaitWindows(t: *Threaded, b: *Io.Batch, timeout: Io.Timeout) Io.Batch.Wa b.user.complete_tail = complete_tail; } - var any_done = false; - while (submit_head != submit_tail) : (submit_head = submit_head.next(len)) { const op = ring[submit_head.index(len)]; const operation = &operations[op]; @@ -2688,7 +2700,6 @@ fn batchWaitWindows(t: *Threaded, b: *Io.Batch, timeout: Io.Timeout) Io.Batch.Wa .noop => |*o| { _ = o.status.unstarted; o.status = .{ .result = {} }; - any_done = true; submitComplete(ring, &complete_tail, op); }, .file_read_streaming => |*o| { @@ -2696,7 +2707,6 @@ fn batchWaitWindows(t: *Threaded, b: *Io.Batch, timeout: Io.Timeout) Io.Batch.Wa switch (try ntReadFile(o.file.handle, o.data, &metadata.iosb)) { .status => { o.status = .{ .result = ntReadFileResult(&metadata.iosb) }; - any_done = true; submitComplete(ring, &complete_tail, op); }, .pending => { @@ -2725,11 +2735,10 @@ fn batchWaitWindows(t: *Threaded, b: *Io.Batch, timeout: Io.Timeout) Io.Batch.Wa o.status = .{ .result = ntReadFileResult(&metadata.iosb) }; }, } - any_done = true; metadata.pending = false; submitComplete(ring, &complete_tail, op); } - if (any_done) return; + if (b.user.complete_head != complete_tail) return; if (!any_pending) return; const alertable_syscall = try AlertableSyscall.start(); const delay_rc = windows.ntdll.NtDelayExecution(windows.TRUE, &delay_interval); -- 2.54.0 From 4dd7fe90a2b541a44aafc3a9596445387a3aed49 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 28 Jan 2026 21:02:43 -0800 Subject: [PATCH 141/499] std.Io.Threaded: compress ntReadFile logic Just use the ntstatus field rather than an additional enum --- lib/std/Io/Threaded.zig | 137 ++++++++++++++++++---------------------- 1 file changed, 61 insertions(+), 76 deletions(-) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 29f578757444279ebe7070d6c249b15167ad4726..fbe12ada55850624b850c7c791c6d474d008b5d1 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -2695,7 +2695,10 @@ fn batchWaitWindows(t: *Threaded, b: *Io.Batch, timeout: Io.Timeout) Io.Batch.Wa const op = ring[submit_head.index(len)]; const operation = &operations[op]; const metadata = &metadatas[op]; - metadata.* = .{ .iosb = undefined, .pending = false }; + metadata.* = .{ .iosb = .{ + .u = .{ .Status = .PENDING }, + .Information = 0, + }, .pending = false }; switch (operation.*) { .noop => |*o| { _ = o.status.unstarted; @@ -2704,15 +2707,13 @@ fn batchWaitWindows(t: *Threaded, b: *Io.Batch, timeout: Io.Timeout) Io.Batch.Wa }, .file_read_streaming => |*o| { _ = o.status.unstarted; - switch (try ntReadFile(o.file.handle, o.data, &metadata.iosb)) { - .status => { - o.status = .{ .result = ntReadFileResult(&metadata.iosb) }; - submitComplete(ring, &complete_tail, op); - }, - .pending => { - o.status = .{ .pending = b }; - metadata.pending = true; - }, + try ntReadFile(o.file.handle, o.data, &metadata.iosb); + if (@atomicLoad(windows.NTSTATUS, &metadata.iosb.u.Status, .acquire) == .PENDING) { + o.status = .{ .pending = b }; + metadata.pending = true; + } else { + o.status = .{ .result = ntReadFileResult(&metadata.iosb) }; + submitComplete(ring, &complete_tail, op); } }, } @@ -8680,44 +8681,36 @@ fn fileReadStreamingPosix(file: File, data: []const []u8) File.ReadStreamingErro } fn fileReadStreamingWindows(file: File, data: []const []u8) File.ReadStreamingError!usize { - var io_status_block: windows.IO_STATUS_BLOCK = undefined; - if (ntReadFile(file.handle, data, &io_status_block)) |result| switch (result) { - .status => return ntReadFileResult(&io_status_block), - .pending => { - // Once we get here we received PENDING so we must not return from the - // function until the operation completes. - defer while (@atomicLoad(windows.NTSTATUS, &io_status_block.u.Status, .acquire) == .PENDING) { - waitForApcOrAlert(); - }; - - const alertable_syscall = AlertableSyscall.start() catch |err| switch (err) { - error.Canceled => |e| { - _ = windows.ntdll.NtCancelIoFile(file.handle, &io_status_block); - return e; - }, - }; - waitForApcOrAlert(); - while (@atomicLoad(windows.NTSTATUS, &io_status_block.u.Status, .acquire) == .PENDING) { - alertable_syscall.checkCancel() catch |err| switch (err) { - error.Canceled => |e| { - _ = windows.ntdll.NtCancelIoFile(file.handle, &io_status_block); - return e; - }, - }; - waitForApcOrAlert(); - } - alertable_syscall.finish(); - }, - } else |err| return err; + var io_status_block: windows.IO_STATUS_BLOCK = .{ + .u = .{ .Status = .PENDING }, + .Information = 0, + }; + try ntReadFile(file.handle, data, &io_status_block); + while (@atomicLoad(windows.NTSTATUS, &io_status_block.u.Status, .acquire) == .PENDING) { + // Once we get here we must not return from the function until the + // operation completes, thereby releasing reference to io_status_block. + const alertable_syscall = AlertableSyscall.start() catch |err| switch (err) { + error.Canceled => |e| { + _ = windows.ntdll.NtCancelIoFile(file.handle, &io_status_block); + while (@atomicLoad(windows.NTSTATUS, &io_status_block.u.Status, .acquire) == .PENDING) { + waitForApcOrAlert(); + } + return e; + }, + }; + waitForApcOrAlert(); + alertable_syscall.finish(); + } return ntReadFileResult(&io_status_block); } -fn ntReadFileResult(io_status_block: *windows.IO_STATUS_BLOCK) !usize { +fn ntReadFileResult(io_status_block: *const windows.IO_STATUS_BLOCK) !usize { switch (io_status_block.u.Status) { + .PENDING => unreachable, + .CANCELLED => unreachable, .SUCCESS => return io_status_block.Information, .END_OF_FILE => return error.EndOfStream, .PIPE_BROKEN => return error.EndOfStream, - .PENDING => unreachable, .INVALID_DEVICE_REQUEST => return error.IsDir, .LOCK_NOT_GRANTED => return error.LockViolation, .ACCESS_DENIED => return error.AccessDenied, @@ -8725,50 +8718,42 @@ fn ntReadFileResult(io_status_block: *windows.IO_STATUS_BLOCK) !usize { } } -fn ntReadFile(handle: windows.HANDLE, data: []const []u8, iosb: *windows.IO_STATUS_BLOCK) Io.Cancelable!enum { status, pending } { +fn ntReadFile(handle: windows.HANDLE, data: []const []u8, iosb: *windows.IO_STATUS_BLOCK) Io.Cancelable!void { var index: usize = 0; while (index < data.len and data[index].len == 0) index += 1; if (index == data.len) { iosb.u.Status = .SUCCESS; iosb.Information = 0; - return .status; + return; } const buffer = data[index]; const syscall: Syscall = try .start(); - while (true) { - iosb.u.Status = .PENDING; - switch (windows.ntdll.NtReadFile( - handle, - null, // event - noopApc, // apc callback - null, // apc context - iosb, - buffer.ptr, - @min(std.math.maxInt(u32), buffer.len), - null, // byte offset - null, // key - )) { - .PENDING => { - syscall.finish(); - return .pending; - }, - .SUCCESS => { - syscall.finish(); - iosb.u.Status = .SUCCESS; - return .status; - }, - .CANCELLED => { - try syscall.checkCancel(); - continue; - }, - else => |status| { - syscall.finish(); - iosb.u.Status = status; - return .status; - }, - } - } + while (true) switch (windows.ntdll.NtReadFile( + handle, + null, // event + noopApc, // apc callback + null, // apc context + iosb, + buffer.ptr, + @min(std.math.maxInt(u32), buffer.len), + null, // byte offset + null, // key + )) { + .PENDING => { + syscall.finish(); + return; + }, + .CANCELLED => { + try syscall.checkCancel(); + continue; + }, + else => |status| { + syscall.finish(); + iosb.u.Status = status; + return; + }, + }; } fn fileReadPositionalPosix(file: File, data: []const []u8, offset: u64) File.ReadPositionalError!usize { -- 2.54.0 From 8f8aa8346a8babe43fb85d7033db2a329d602366 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 29 Jan 2026 13:31:44 -0800 Subject: [PATCH 142/499] std.Io.Threaded: ntReadFileResult handles EOF + bytes available --- lib/std/Io/Threaded.zig | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index fbe12ada55850624b850c7c791c6d474d008b5d1..8e820f71d0c795f4a218509203ee79ff636a1620 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -8709,8 +8709,10 @@ fn ntReadFileResult(io_status_block: *const windows.IO_STATUS_BLOCK) !usize { .PENDING => unreachable, .CANCELLED => unreachable, .SUCCESS => return io_status_block.Information, - .END_OF_FILE => return error.EndOfStream, - .PIPE_BROKEN => return error.EndOfStream, + .END_OF_FILE, .PIPE_BROKEN => { + if (io_status_block.Information == 0) return error.EndOfStream; + return io_status_block.Information; + }, .INVALID_DEVICE_REQUEST => return error.IsDir, .LOCK_NOT_GRANTED => return error.LockViolation, .ACCESS_DENIED => return error.AccessDenied, -- 2.54.0 From c2679feaaab26cc76fc381c61d49488a96f1f63c Mon Sep 17 00:00:00 2001 From: Matthew Lugg Date: Fri, 30 Jan 2026 00:44:08 +0000 Subject: [PATCH 143/499] std.Io.Threaded: fix ntdll timeouts on Windows --- lib/std/Io/Threaded.zig | 41 ++++++++++++++++++++++++++++------------- 1 file changed, 28 insertions(+), 13 deletions(-) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 8e820f71d0c795f4a218509203ee79ff636a1620..287bed5d0994ff9aeed1b86b7eeb0bdef57dacd5 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -2719,7 +2719,13 @@ fn batchWaitWindows(t: *Threaded, b: *Io.Batch, timeout: Io.Timeout) Io.Batch.Wa } } - var delay_interval: windows.LARGE_INTEGER = timeoutToWindowsInterval(timeout); + const deadline: ?Io.Clock.Timestamp = timeout.toDeadline(ioBasic(t)) catch |err| switch (err) { + error.Unexpected => deadline: { + recoverableOsBugDetected(); + break :deadline .{ .raw = .{ .nanoseconds = 0 }, .clock = .awake }; + }, + error.UnsupportedClock => |e| return e, + }; while (true) { var any_pending = false; @@ -2741,6 +2747,16 @@ fn batchWaitWindows(t: *Threaded, b: *Io.Batch, timeout: Io.Timeout) Io.Batch.Wa } if (b.user.complete_head != complete_tail) return; if (!any_pending) return; + var delay_interval: windows.LARGE_INTEGER = interval: { + const d = deadline orelse break :interval std.math.minInt(windows.LARGE_INTEGER); + break :interval t.deadlineToWindowsInterval(d) catch |err| switch (err) { + error.UnsupportedClock => |e| return e, + error.Unexpected => { + recoverableOsBugDetected(); + break :interval -1; + }, + }; + }; const alertable_syscall = try AlertableSyscall.start(); const delay_rc = windows.ntdll.NtDelayExecution(windows.TRUE, &delay_interval); alertable_syscall.finish(); @@ -16784,19 +16800,18 @@ fn park(opt_deadline: ?Io.Clock.Timestamp, addr_hint: ?*const anyopaque) error{T } } -fn timeoutToWindowsInterval(timeout: Io.Timeout) windows.LARGE_INTEGER { - switch (timeout) { - .none => { - return std.math.minInt(windows.LARGE_INTEGER); // infinite timeout +fn deadlineToWindowsInterval(t: *Io.Threaded, deadline: Io.Clock.Timestamp) Io.Clock.Error!windows.LARGE_INTEGER { + // ntdll only supports two combinations: + // * real-time (`.real`) sleeps with absolute deadlines + // * monotonic (`.awake`/`.boot`) sleeps with relative durations + switch (deadline.clock) { + .cpu_process, .cpu_thread => unreachable, // cannot sleep for CPU time + .real => { + return @intCast(@max(@divTrunc(deadline.raw.nanoseconds, 100), 0)); }, - .deadline => |deadline| { - const nanoseconds = deadline.raw.nanoseconds; - return @intCast(@divTrunc(nanoseconds, 100)); - }, - .duration => |duration| { - const now_timestamp = nowWindows(duration.clock) catch unreachable; - const deadline_ns = now_timestamp.nanoseconds + duration.raw.nanoseconds; - return @intCast(@divTrunc(deadline_ns, 100)); + .awake, .boot => { + const duration = try deadline.durationFromNow(ioBasic(t)); + return @intCast(@min(@divTrunc(-duration.raw.nanoseconds, 100), -1)); }, } } -- 2.54.0 From f8828e543ab6331395ef4d9f9a2b53770028b6b2 Mon Sep 17 00:00:00 2001 From: Matthew Lugg Date: Fri, 30 Jan 2026 00:44:29 +0000 Subject: [PATCH 144/499] std.Build: fully upgrade Step.Run to std.Io timing (and fix a typo) --- lib/std/Build/Step/Run.zig | 108 +++++++++++++++++-------------------- 1 file changed, 48 insertions(+), 60 deletions(-) diff --git a/lib/std/Build/Step/Run.zig b/lib/std/Build/Step/Run.zig index 55fee9a2868b74b0a6006cb67c7a366b7b477ed4..68d0ec480c77e7099a8230dd422ad0b21f155bf9 100644 --- a/lib/std/Build/Step/Run.zig +++ b/lib/std/Build/Step/Run.zig @@ -1587,9 +1587,13 @@ fn spawnChildAndCollect( }; if (run.stdio == .zig_test) { - var timer = try std.time.Timer.start(); - defer run.step.result_duration_ns = timer.read(); - try evalZigTest(run, spawn_options, options, fuzz_context); + const started: Io.Clock.Timestamp = try .now(io, .awake); + const result = evalZigTest(run, spawn_options, options, fuzz_context) catch |err| switch (err) { + error.Canceled => |e| return e, + else => |e| e, + }; + run.step.result_duration_ns = @intCast((try started.untilNow(io)).raw.nanoseconds); + try result; return null; } else { const inherit = spawn_options.stdout == .inherit or spawn_options.stderr == .inherit; @@ -1602,10 +1606,14 @@ fn spawnChildAndCollect( } else .no_color; defer if (inherit) io.unlockStderr(); try setColorEnvironmentVariables(run, environ_map, terminal_mode); - var timer = try std.time.Timer.start(); - const res = try evalGeneric(run, spawn_options); - run.step.result_duration_ns = timer.read(); - return .{ .term = res.term, .stdout = res.stdout, .stderr = res.stderr }; + + const started: Io.Clock.Timestamp = try .now(io, .awake); + const result = evalGeneric(run, spawn_options) catch |err| switch (err) { + error.Canceled => |e| return e, + else => |e| e, + }; + run.step.result_duration_ns = @intCast((try started.untilNow(io)).raw.nanoseconds); + return try result; } } @@ -1861,9 +1869,7 @@ fn waitZigTest( var active_test_index: ?u32 = null; - // `null` means this host does not support `std.time.Timer`. This timer is `reset()` whenever we - // change `active_test_index`, i.e. whenever a test starts or finishes. - var timer: ?std.time.Timer = std.time.Timer.start() catch null; + var last_update: Io.Clock.Timestamp = try .now(io, .awake); var coverage_id: ?u64 = null; @@ -1871,16 +1877,27 @@ fn waitZigTest( // test. For instance, if the test runner leaves this much time between us requesting a test to // start and it acknowledging the test starting, we terminate the child and raise an error. This // *should* never happen, but could in theory be caused by some very unlucky IB in a test. - const response_timeout_ns: ?u64 = ns: { - if (fuzz_context != null) break :ns null; // don't timeout fuzz tests - break :ns @max(options.unit_test_timeout_ns orelse 0, 60 * std.time.ns_per_s); + const response_timeout: ?Io.Clock.Duration = t: { + if (fuzz_context != null) break :t null; // don't timeout fuzz tests + const ns = @max(options.unit_test_timeout_ns orelse 0, 60 * std.time.ns_per_s); + break :t .{ .clock = .awake, .raw = .fromNanoseconds(ns) }; }; + const test_timeout: ?Io.Clock.Duration = if (options.unit_test_timeout_ns) |ns| .{ + .clock = .awake, + .raw = .fromNanoseconds(ns), + } else null; const stdout = multi_reader.reader(0); const stderr = multi_reader.reader(1); const Header = std.zig.Server.Message.Header; while (true) { + const timeout: Io.Timeout = t: { + const opt_duration = if (active_test_index == null) response_timeout else test_timeout; + const duration = opt_duration orelse break :t .none; + break :t .{ .deadline = last_update.addDuration(duration) }; + }; + // This block is exited when `stdout` contains enough bytes for a `Header`. header_ready: { if (stdout.buffered().len >= @sizeOf(Header)) { @@ -1888,65 +1905,33 @@ fn waitZigTest( break :header_ready; } - // Always `null` if `timer` is `null`. - const opt_timeout_ns: ?u64 = ns: { - if (timer == null) break :ns null; - if (active_test_index == null) break :ns response_timeout_ns; - break :ns options.unit_test_timeout_ns; - }; - - const timeout: Io.Timeout = if (opt_timeout_ns) |timeout_ns| .{ .duration = .{ - .raw = .fromNanoseconds(timeout_ns -| timer.?.read()), - .clock = .awake, - } } else .none; - multi_reader.fill(64, timeout) catch |err| switch (err) { - error.Timeout, error.EndOfStream => return .{ .no_poll = .{ + error.Timeout => return .{ .timeout = .{ .active_test_index = active_test_index, - .ns_elapsed = if (timer) |*t| t.read() else 0, + .ns_elapsed = @intCast((try last_update.untilNow(io)).raw.nanoseconds), + } }, + error.EndOfStream => return .{ .no_poll = .{ + .active_test_index = active_test_index, + .ns_elapsed = @intCast((try last_update.untilNow(io)).raw.nanoseconds), } }, - error.UnsupportedClock => { - timer = null; - continue; - }, else => |e| return e, }; - if (stdout.buffered().len >= @sizeOf(Header)) { - // There wasn't a header before, but there is one after the `poll`. - break :header_ready; - } - - if (opt_timeout_ns) |timeout_ns| { - const cur_ns = timer.?.read(); - if (cur_ns >= timeout_ns) return .{ .timeout = .{ - .active_test_index = active_test_index, - .ns_elapsed = cur_ns, - } }; - } continue; } // There is definitely a header available now -- read it. const header = stdout.takeStruct(Header, .little) catch unreachable; while (stdout.buffered().len < header.bytes_len) { - const timeout: Io.Timeout = t: { - const t = if (timer) |*t| t else break :t .none; - if (response_timeout_ns) |timeout_ns| break :t .{ .duration = .{ - .raw = .fromNanoseconds(timeout_ns -| t.read()), - .clock = .awake, - } }; - break :t .none; - }; multi_reader.fill(64, timeout) catch |err| switch (err) { - error.Timeout, error.EndOfStream => return .{ .no_poll = .{ + error.Timeout => return .{ .timeout = .{ .active_test_index = active_test_index, - .ns_elapsed = if (timer) |*t| t.read() else 0, + .ns_elapsed = @intCast((try last_update.untilNow(io)).raw.nanoseconds), + } }, + error.EndOfStream => return .{ .no_poll = .{ + .active_test_index = active_test_index, + .ns_elapsed = @intCast((try last_update.untilNow(io)).raw.nanoseconds), } }, - error.UnsupportedClock => { - timer = null; - continue; - }, else => |e| return e, }; } @@ -1991,13 +1976,13 @@ fn waitZigTest( @memset(opt_metadata.*.?.ns_per_test, std.math.maxInt(u64)); active_test_index = null; - if (timer) |*t| t.reset(); + last_update = try .now(io, .awake); requestNextTest(io, child.stdin.?, &opt_metadata.*.?, &sub_prog_node) catch |err| return .{ .write_failed = err }; }, .test_started => { active_test_index = opt_metadata.*.?.next_index - 1; - if (timer) |*t| t.reset(); + last_update = try .now(io, .awake); }, .test_results => { assert(fuzz_context == null); @@ -2040,7 +2025,10 @@ fn waitZigTest( } active_test_index = null; - if (timer) |*t| md.ns_per_test[tr_hdr.index] = t.lap(); + + const now: Io.Clock.Timestamp = try .now(io, .awake); + md.ns_per_test[tr_hdr.index] = @intCast(last_update.durationTo(now).raw.nanoseconds); + last_update = now; requestNextTest(io, child.stdin.?, md, &sub_prog_node) catch |err| return .{ .write_failed = err }; }, -- 2.54.0 From 866ee4f1c52ae3e5a8ac95c9c5e61b019aa5eadb Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 29 Jan 2026 15:03:33 -0800 Subject: [PATCH 145/499] std.Io.Threaded: handle TIMEOUT from NtDelayExceution --- lib/std/Io/Threaded.zig | 2 +- lib/std/os/windows/ntdll.zig | 7 +++++++ 2 files changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 287bed5d0994ff9aeed1b86b7eeb0bdef57dacd5..bccaf24ecd9c969b55b60a94a90d8a064b5cac7b 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -2761,7 +2761,7 @@ fn batchWaitWindows(t: *Threaded, b: *Io.Batch, timeout: Io.Timeout) Io.Batch.Wa const delay_rc = windows.ntdll.NtDelayExecution(windows.TRUE, &delay_interval); alertable_syscall.finish(); switch (delay_rc) { - .SUCCESS => { + .SUCCESS, .TIMEOUT => { // The thread woke due to the timeout. Although spurious // timeouts are OK, when no deadline is passed we must not // return `error.Timeout`. diff --git a/lib/std/os/windows/ntdll.zig b/lib/std/os/windows/ntdll.zig index d68cd1494b8392381187de4316a7155351687eae..195a457d3b86167051c1a6f9d9eb4415673eb021 100644 --- a/lib/std/os/windows/ntdll.zig +++ b/lib/std/os/windows/ntdll.zig @@ -594,6 +594,13 @@ pub extern "ntdll" fn NtCancelSynchronousIoFile( IoStatusBlock: *IO_STATUS_BLOCK, ) callconv(.winapi) NTSTATUS; +/// This function has been observed to return SUCCESS on timeout on Windows 10 +/// and TIMEOUT on Wine 10.0. +/// +/// This function has been observed on Windows 11 such that positive interval +/// is real time, which can cause waits to be interrupted by changing system +/// time, however negative intervals are not affected by changes to system +/// time. pub extern "ntdll" fn NtDelayExecution( Alertable: BOOLEAN, DelayInterval: *const LARGE_INTEGER, -- 2.54.0 From a41ee5994d7f1f8dbecf54cdba0d56864d95ef99 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 29 Jan 2026 17:13:17 -0800 Subject: [PATCH 146/499] std.Build.Step: evalZigProcess handles EndOfStream and a happy little info log when the process needs to be restarted --- lib/std/Build/Step.zig | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/std/Build/Step.zig b/lib/std/Build/Step.zig index 0dd4b932800b3ee9c3e65dd2347381263f0eb34b..cfc263b7701e34f0ef762ae3f2524d0cac850f37 100644 --- a/lib/std/Build/Step.zig +++ b/lib/std/Build/Step.zig @@ -416,7 +416,8 @@ pub fn evalZigProcess( assert(watch); if (std.Progress.have_ipc) if (zp.progress_ipc_fd) |fd| prog_node.setIpcFd(fd); const result = zigProcessUpdate(s, zp, watch, web_server, gpa) catch |err| switch (err) { - error.BrokenPipe => { + error.BrokenPipe, error.EndOfStream => |reason| { + std.log.info("{s} restart required: {t}", .{ argv[0], reason }); // Process restart required. const term = zp.child.wait(io) catch |e| { return s.fail("unable to wait for {s}: {t}", .{ argv[0], e }); -- 2.54.0 From a520355e4ca8f190d5c470413308bb4c8cb8b526 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 29 Jan 2026 22:58:54 -0800 Subject: [PATCH 147/499] std.process: simplify RunError set --- lib/std/process.zig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/std/process.zig b/lib/std/process.zig index 739027da0754f922f9418a7d1b124f08a26a408e..3b5a0ecebd5e38ced3748bd0a012adc6d1d6d0a2 100644 --- a/lib/std/process.zig +++ b/lib/std/process.zig @@ -453,9 +453,9 @@ pub fn spawnPath(io: Io, dir: Io.Dir, options: SpawnOptions) SpawnError!Child { return io.vtable.processSpawnPath(io.userdata, dir, options); } -pub const RunError = SpawnError || error{ +pub const RunError = error{ StreamTooLong, -} || Io.ConcurrentError || Allocator.Error || Io.File.Reader.Error || Io.Timeout.Error; +} || SpawnError || Io.File.MultiReader.UnendingError || Io.Timeout.Error; pub const RunOptions = struct { argv: []const []const u8, -- 2.54.0 From 2674acdb77f8a144eaedbfa57a1b7fdc70dc1e60 Mon Sep 17 00:00:00 2001 From: Jacob Young Date: Fri, 30 Jan 2026 01:44:07 -0500 Subject: [PATCH 148/499] Io.Batch: implement alternate API --- lib/std/Io.zig | 241 ++++++++------ lib/std/Io/File.zig | 7 +- lib/std/Io/File/MultiReader.zig | 52 ++- lib/std/Io/Threaded.zig | 569 +++++++++++++++++++------------- 4 files changed, 505 insertions(+), 364 deletions(-) diff --git a/lib/std/Io.zig b/lib/std/Io.zig index c00e4619e830a2fcd5a7e87bfce67b4a0201fa70..ca77a9836ad4df4993f3f64ccbb3c2af6228bcc2 100644 --- a/lib/std/Io.zig +++ b/lib/std/Io.zig @@ -149,8 +149,9 @@ pub const VTable = struct { futexWaitUncancelable: *const fn (?*anyopaque, ptr: *const u32, expected: u32) void, futexWake: *const fn (?*anyopaque, ptr: *const u32, max_waiters: u32) void, - operate: *const fn (?*anyopaque, *Operation) Cancelable!void, - batchWait: *const fn (?*anyopaque, *Batch, Timeout) Batch.WaitError!void, + operate: *const fn (?*anyopaque, Operation) Cancelable!Operation.Result, + batchAwaitAsync: *const fn (?*anyopaque, *Batch) Batch.AwaitAsyncError!void, + batchAwaitConcurrent: *const fn (?*anyopaque, *Batch, Timeout) Batch.AwaitConcurrentError!void, batchCancel: *const fn (?*anyopaque, *Batch) void, dirCreateDir: *const fn (?*anyopaque, Dir, []const u8, Dir.Permissions) Dir.CreateDirError!void, @@ -255,19 +256,14 @@ pub const VTable = struct { }; pub const Operation = union(enum) { - noop: Noop, file_read_streaming: FileReadStreaming, - pub const Noop = struct { - reserved: [2]usize = .{ 0, 0 }, - status: Status(void) = .{ .unstarted = {} }, - }; + pub const Tag = @typeInfo(Operation).@"union".tag_type.?; /// May return 0 reads which is different than `error.EndOfStream`. pub const FileReadStreaming = struct { file: File, data: []const []u8, - status: Status(Error!usize) = .{ .unstarted = {} }, pub const Error = UnendingError || error{EndOfStream}; pub const UnendingError = error{ @@ -290,19 +286,72 @@ pub const Operation = union(enum) { /// lock. LockViolation, } || Io.UnexpectedError; + + pub const Result = usize; + }; + + pub const Result = Result: { + const operation_fields = @typeInfo(Operation).@"union".fields; + var field_names: [operation_fields.len][]const u8 = undefined; + var field_types: [operation_fields.len]type = undefined; + for (operation_fields, &field_names, &field_types) |field, *field_name, *field_type| { + field_name.* = field.name; + field_type.* = field.type.Error!field.type.Result; + } + break :Result @Union(.auto, Tag, &field_names, &field_types, &@splat(.{})); }; - pub fn Status(Result: type) type { - return union { - unstarted: void, - pending: *Batch, + pub const Storage = union { + unused: List.DoubleNode, + submission: Submission, + pending: Pending, + completion: Completion, + + pub const Submission = struct { + node: List.SingleNode, + operation: Operation, + }; + + pub const Pending = struct { + node: List.DoubleNode, + tag: Tag, + context: [3]usize, + }; + + pub const Completion = struct { + node: List.SingleNode, result: Result, }; - } + }; + + pub const OptionalIndex = enum(u32) { + none = std.math.maxInt(u32), + _, + + pub fn fromIndex(i: usize) OptionalIndex { + const oi: OptionalIndex = @enumFromInt(i); + assert(oi != .none); + return oi; + } + + pub fn toIndex(oi: OptionalIndex) u32 { + assert(oi != .none); + return @intFromEnum(oi); + } + }; + pub const List = struct { + head: OptionalIndex, + tail: OptionalIndex, + + pub const empty: List = .{ .head = .none, .tail = .none }; + + pub const SingleNode = struct { next: OptionalIndex }; + pub const DoubleNode = struct { prev: OptionalIndex, next: OptionalIndex }; + }; }; /// Performs one `Operation`. -pub fn operate(io: Io, operation: *Operation) Cancelable!void { +pub fn operate(io: Io, operation: Operation) Cancelable!Operation.Result { return io.vtable.operate(io.userdata, operation); } @@ -312,116 +361,96 @@ pub fn operate(io: Io, operation: *Operation) Cancelable!void { /// This is a low-level abstraction based on `Operation`. For a higher /// level API that operates on `Future`, see `Select`. pub const Batch = struct { - operations: []Operation, - ring: [*]u32, - user: struct { - submit_tail: RingIndex, - complete_head: RingIndex, - complete_tail: RingIndex, - }, - impl: struct { - submit_head: RingIndex, - submit_tail: RingIndex, - complete_tail: RingIndex, - reserved: ?*anyopaque, - }, - - pub const RingIndex = enum(u32) { - _, - - pub fn index(ri: RingIndex, len: u31) u31 { - const i = @intFromEnum(ri); - assert(i < @as(u32, len) * 2); - return @intCast(if (i < len) i else i - len); - } - - pub fn prev(ri: RingIndex, len: u31) RingIndex { - const i = @intFromEnum(ri); - const double_len = @as(u32, len) * 2; - assert(i <= double_len); - return @enumFromInt((if (i > 0) i else double_len) - 1); - } - - pub fn next(ri: RingIndex, len: u31) RingIndex { - const i = @intFromEnum(ri) + 1; - const double_len = @as(u32, len) * 2; - assert(i <= double_len); - return @enumFromInt(if (i < double_len) i else 0); - } - }; + storage: []Operation.Storage, + unused: Operation.List, + submissions: Operation.List, + pending: Operation.List, + completions: Operation.List, + context: ?*anyopaque, /// After calling this, it is safe to unconditionally defer a call to /// `cancel`. - pub fn init(operations: []Operation, ring: []u32) Batch { - const len: u31 = @intCast(operations.len); - assert(ring.len == len); + pub fn init(storage: []Operation.Storage) Batch { + var prev: Operation.OptionalIndex = .none; + for (storage, 0..) |*operation, index| { + operation.* = .{ .unused = .{ .prev = prev, .next = .fromIndex(index + 1) } }; + prev = .fromIndex(index); + } + storage[storage.len - 1].unused.next = .none; return .{ - .operations = operations, - .ring = ring.ptr, - .user = .{ - .submit_tail = @enumFromInt(0), - .complete_head = @enumFromInt(0), - .complete_tail = @enumFromInt(0), - }, - .impl = .{ - .submit_head = @enumFromInt(0), - .submit_tail = @enumFromInt(0), - .complete_tail = @enumFromInt(0), - .reserved = null, + .storage = storage, + .unused = .{ + .head = .fromIndex(0), + .tail = .fromIndex(storage.len - 1), }, + .submissions = .empty, + .pending = .empty, + .completions = .empty, + .context = null, }; } - /// Adds `b.operations[operation]` to the list of submitted operations - /// that will be performed when `wait` is called. - pub fn add(b: *Batch, operation: usize) void { - const tail = b.user.submit_tail; - const len: u31 = @intCast(b.operations.len); - b.user.submit_tail = tail.next(len); - b.ring[0..len][tail.index(len)] = @intCast(operation); + /// Adds an operation to be performed at the next await call. + /// Returns the index that will be returned by `next` after the operation completes. + /// Asserts that no more than `storage.len` operations are active at a time. + pub fn add(b: *Batch, operation: Operation) u32 { + const index = b.unused.next; + b.addAt(index.toIndex(), operation); + return index; } - fn flush(b: *Batch) void { - @atomicStore(RingIndex, &b.impl.submit_tail, b.user.submit_tail, .release); + /// Adds an operation to be performed at the next await call. + /// After the operation completes, `next` will return `index`. + /// Asserts that the operation at `index` is not active. + pub fn addAt(b: *Batch, index: u32, operation: Operation) void { + const storage = &b.storage[index]; + const unused = storage.unused; + switch (unused.prev) { + .none => b.unused.head = .none, + else => |prev_index| b.storage[prev_index.toIndex()].unused.next = unused.next, + } + switch (unused.next) { + .none => b.unused.tail = .none, + else => |next_index| b.storage[next_index.toIndex()].unused.prev = unused.prev, + } + + switch (b.submissions.tail) { + .none => b.submissions.head = .fromIndex(index), + else => |tail_index| b.storage[tail_index.toIndex()].submission.node.next = .fromIndex(index), + } + storage.* = .{ .submission = .{ .node = .{ .next = .none }, .operation = operation } }; + b.submissions.tail = .fromIndex(index); } - /// Returns `operation` such that `b.operations[operation]` has completed. - /// Returns `null` when `wait` should be called. - pub fn next(b: *Batch) ?u32 { - const head = b.user.complete_head; - if (head == b.user.complete_tail) { - @branchHint(.unlikely); - b.flush(); - const tail = @atomicLoad(RingIndex, &b.impl.complete_tail, .acquire); - if (head == tail) { - @branchHint(.unlikely); - return null; - } - assert(head != tail); - b.user.complete_tail = tail; + pub fn next(b: *Batch) ?struct { index: u32, result: Operation.Result } { + const index = b.completions.head; + if (index == .none) return null; + const storage = &b.storage[index.toIndex()]; + const completion = storage.completion; + const next_index = completion.node.next; + b.completions.head = next_index; + if (next_index == .none) b.completions.tail = .none; + + const tail_index = b.unused.tail; + switch (tail_index) { + .none => b.unused.head = index, + else => b.storage[tail_index.toIndex()].unused.next = index, } - const len: u31 = @intCast(b.operations.len); - b.user.complete_head = head.next(len); - return b.ring[0..len][head.index(len)]; + storage.* = .{ .unused = .{ .prev = tail_index, .next = .none } }; + b.unused.tail = index; + return .{ .index = index.toIndex(), .result = completion.result }; } - pub const WaitError = ConcurrentError || Cancelable || Timeout.Error; + pub const AwaitAsyncError = Cancelable; + pub fn awaitAsync(b: *Batch, io: Io) AwaitAsyncError!void { + return io.vtable.batchAwaitAsync(io.userdata, b); + } - /// Starts work on any submitted operations and returns when at least one has completeed. - /// - /// Returns `error.Timeout` if `timeout` expires first. - /// - /// Depending on the `Io` implementation, may allocate resources that are - /// freed with `cancel`, even if an error is returned. - pub fn wait(b: *Batch, io: Io, timeout: Timeout) WaitError!void { - return io.vtable.batchWait(io.userdata, b, timeout); + pub const AwaitConcurrentError = ConcurrentError || Cancelable || Timeout.Error; + pub fn awaitConcurrent(b: *Batch, io: Io, timeout: Timeout) AwaitConcurrentError!void { + return io.vtable.batchAwaitConcurrent(io.userdata, b, timeout); } - /// Returns after all `operations` have completed. Operations which have not completed - /// after this function returns were successfully dropped and had no side effects. - /// - /// This function is idempotent with respect to itself and `wait`. It is - /// safe to unconditionally `defer` a call to this function after `init`. pub fn cancel(b: *Batch, io: Io) void { return io.vtable.batchCancel(io.userdata, b); } diff --git a/lib/std/Io/File.zig b/lib/std/Io/File.zig index df5b3b5a532239e1996287021d74d14ee37fd2ef..5db6f81ac6f8e2b2ade7fcd49916af06b1317c7d 100644 --- a/lib/std/Io/File.zig +++ b/lib/std/Io/File.zig @@ -559,12 +559,11 @@ pub const ReadStreamingError = error{EndOfStream} || Reader.Error; /// See also: /// * `reader` pub fn readStreaming(file: File, io: Io, buffer: []const []u8) ReadStreamingError!usize { - var operation: Io.Operation = .{ .file_read_streaming = .{ + const result = try io.operate(.{ .file_read_streaming = .{ .file = file, .data = buffer, - } }; - try io.operate(&operation); - return operation.file_read_streaming.status.result; + } }); + return result.file_read_streaming; } pub const ReadPositionalError = error{ diff --git a/lib/std/Io/File/MultiReader.zig b/lib/std/Io/File/MultiReader.zig index 7a0f8de068d5912b8ba903665eba0b1c19fb7265..217215a3636e40b4fe4ad805be2df88fa2830947 100644 --- a/lib/std/Io/File/MultiReader.zig +++ b/lib/std/Io/File/MultiReader.zig @@ -22,8 +22,7 @@ pub const UnendingError = Allocator.Error || File.Reader.Error || Io.ConcurrentE /// Trailing: /// * `contexts: [len]Context` -/// * `ring: [len]u32` -/// * `operations: [len]Io.Operation` +/// * `storage: [len]Io.Operation.Storage` pub const Streams = extern struct { len: u32, @@ -33,17 +32,10 @@ pub const Streams = extern struct { return ptr[0..s.len]; } - pub fn ring(s: *Streams) []u32 { + pub fn storage(s: *Streams) []Io.Operation.Storage { const prev = contexts(s); const end = prev.ptr + prev.len; - const ptr: [*]u32 = @ptrFromInt(std.mem.alignForward(usize, @intFromPtr(end), @alignOf(u32))); - return ptr[0..s.len]; - } - - pub fn operations(s: *Streams) []Io.Operation { - const prev = ring(s); - const end = prev.ptr + prev.len; - const ptr: [*]Io.Operation = @ptrFromInt(std.mem.alignForward(usize, @intFromPtr(end), @alignOf(Io.Operation))); + const ptr: [*]Io.Operation.Storage = @ptrFromInt(std.mem.alignForward(usize, @intFromPtr(end), @alignOf(Io.Operation.Storage))); return ptr[0..s.len]; } }; @@ -52,8 +44,7 @@ pub fn Buffer(comptime n: usize) type { return extern struct { len: u32, contexts: [n][@sizeOf(Context)]u8 align(@alignOf(Context)), - ring: [n]u32, - operations: [n][@sizeOf(Io.Operation)]u8 align(@alignOf(Io.Operation)), + storage: [n][@sizeOf(Io.Operation.Storage)]u8 align(@alignOf(Io.Operation.Storage)), pub fn toStreams(b: *@This()) *Streams { b.len = n; @@ -86,25 +77,22 @@ pub fn init(mr: *MultiReader, gpa: Allocator, io: Io, streams: *Streams, files: .vec = .{&.{}}, .err = null, }; - const operations = streams.operations(); - const ring = streams.ring(); mr.* = .{ .gpa = gpa, .streams = streams, - .batch = .init(operations, ring), + .batch = .init(streams.storage()), }; - for (operations, contexts, files, 0..) |*op, *context, file, i| { + for (contexts, 0..) |*context, i| { const r = &context.fr.interface; - op.* = .{ .file_read_streaming = .{ - .file = file, - .data = &context.vec, - } }; rebaseGrowing(mr, context, 1) catch |err| { context.err = err; continue; }; context.vec[0] = r.buffer; - mr.batch.add(i); + mr.batch.addAt(@intCast(i), .{ .file_read_streaming = .{ + .file = context.fr.file, + .data = &context.vec, + } }); } } @@ -204,7 +192,7 @@ fn fillUntimed(context: *Context, capacity: usize) Io.Reader.Error!void { }; } -pub const FillError = Io.Batch.WaitError || error{ +pub const FillError = Io.Batch.AwaitConcurrentError || error{ /// `fill` was called when all streams already have failed or reached the /// end. EndOfStream, @@ -213,17 +201,15 @@ pub const FillError = Io.Batch.WaitError || error{ /// Wait until at least one stream receives more data. pub fn fill(mr: *MultiReader, unused_capacity: usize, timeout: Io.Timeout) FillError!void { const contexts = mr.streams.contexts(); - const operations = mr.streams.operations(); const io = contexts[0].fr.io; var any_completed = false; - try mr.batch.wait(io, timeout); + try mr.batch.awaitConcurrent(io, timeout); - while (mr.batch.next()) |i| { + while (mr.batch.next()) |operation| { any_completed = true; - const context = &contexts[i]; - const operation = &operations[i]; - const n = operation.file_read_streaming.status.result catch |err| { + const context = &contexts[operation.index]; + const n = operation.result.file_read_streaming catch |err| { context.err = err; continue; }; @@ -237,15 +223,17 @@ pub fn fill(mr: *MultiReader, unused_capacity: usize, timeout: Io.Timeout) FillE assert(r.seek == 0); } context.vec[0] = r.buffer[r.end..]; - operation.file_read_streaming.status = .{ .unstarted = {} }; - mr.batch.add(i); + mr.batch.addAt(operation.index, .{ .file_read_streaming = .{ + .file = context.fr.file, + .data = &context.vec, + } }); } if (!any_completed) return error.EndOfStream; } /// Wait until all streams fail or reach the end. -pub fn fillRemaining(mr: *MultiReader, timeout: Io.Timeout) Io.Batch.WaitError!void { +pub fn fillRemaining(mr: *MultiReader, timeout: Io.Timeout) Io.Batch.AwaitConcurrentError!void { while (fill(mr, 1, timeout)) |_| {} else |err| switch (err) { error.EndOfStream => return, else => |e| return e, diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index bccaf24ecd9c969b55b60a94a90d8a064b5cac7b..18f24eb40cefdabe8fca2820e239edac1046aafc 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -1617,7 +1617,8 @@ pub fn io(t: *Threaded) Io { .futexWake = futexWake, .operate = operate, - .batchWait = batchWait, + .batchAwaitAsync = batchAwaitAsync, + .batchAwaitConcurrent = batchAwaitConcurrent, .batchCancel = batchCancel, .dirCreateDir = dirCreateDir, @@ -1780,7 +1781,8 @@ pub fn ioBasic(t: *Threaded) Io { .futexWake = futexWake, .operate = operate, - .batchWait = batchWait, + .batchAwaitAsync = batchAwaitAsync, + .batchAwaitConcurrent = batchAwaitConcurrent, .batchCancel = batchCancel, .dirCreateDir = dirCreateDir, @@ -2483,85 +2485,227 @@ fn futexWake(userdata: ?*anyopaque, ptr: *const u32, max_waiters: u32) void { Thread.futexWake(ptr, max_waiters); } -fn operate(userdata: ?*anyopaque, op: *Io.Operation) Io.Cancelable!void { +fn operate(userdata: ?*anyopaque, operation: Io.Operation) Io.Cancelable!Io.Operation.Result { const t: *Threaded = @ptrCast(@alignCast(userdata)); - switch (op.*) { - .noop => |*o| { - _ = o.status.unstarted; - o.status = .{ .result = {} }; - }, - .file_read_streaming => |*o| { - _ = o.status.unstarted; - o.status = .{ .result = fileReadStreaming(t, o.file, o.data) catch |err| switch (err) { + switch (operation) { + .file_read_streaming => |o| return .{ + .file_read_streaming = fileReadStreaming(t, o.file, o.data) catch |err| switch (err) { error.Canceled => |e| return e, else => |e| e, - } }; + }, }, } } -fn batchWait(userdata: ?*anyopaque, b: *Io.Batch, timeout: Io.Timeout) Io.Batch.WaitError!void { +fn batchAwaitAsync(userdata: ?*anyopaque, b: *Io.Batch) Io.Batch.AwaitAsyncError!void { const t: *Threaded = @ptrCast(@alignCast(userdata)); - if (is_windows) return batchWaitWindows(t, b, timeout); + if (is_windows) { + try batchAwaitWindows(b); + const alertable_syscall = try AlertableSyscall.start(); + while (b.pending.head != .none and b.completions.head == .none) waitForApcOrAlert(); + alertable_syscall.finish(); + return; + } if (native_os == .wasi and !builtin.link_libc) @panic("TODO"); - const operations = b.operations; - const len: u31 = @intCast(operations.len); - const ring = b.ring[0..len]; - var submit_head = b.impl.submit_head; - const submit_tail = b.user.submit_tail; - b.impl.submit_tail = submit_tail; - var complete_tail = b.impl.complete_tail; - var map_buffer: [poll_buffer_len]u8 = undefined; // poll_buffer index to operations index - var poll_i: u8 = 0; - defer { - for (map_buffer[0..poll_i]) |op| { - submit_head = submit_head.prev(len); - ring[submit_head.index(len)] = op; + var poll_buffer: [poll_buffer_len]posix.pollfd = undefined; + var poll_len: u32 = 0; + { + var index = b.submissions.head; + while (index != .none and poll_len < poll_buffer_len) { + const submission = &b.storage[index.toIndex()].submission; + switch (submission.operation) { + .file_read_streaming => |o| { + poll_buffer[poll_len] = .{ .fd = o.file.handle, .events = posix.POLL.IN, .revents = 0 }; + poll_len += 1; + }, + } + index = submission.node.next; } - b.impl.submit_head = submit_head; - b.impl.complete_tail = complete_tail; - b.user.complete_tail = complete_tail; } - var poll_buffer: [poll_buffer_len]posix.pollfd = undefined; - while (submit_head != submit_tail) : (submit_head = submit_head.next(len)) { - const op = ring[submit_head.index(len)]; - const operation = &operations[op]; - switch (operation.*) { - .noop => |*o| { - _ = o.status.unstarted; - o.status = .{ .result = {} }; - submitComplete(ring, &complete_tail, op); + switch (poll_len) { + 0 => return, + 1 => {}, + else => while (true) { + const timeout_ms: i32 = t: { + if (b.completions.head != .none) { + // It is legal to call batchWait with already completed + // operations in the ring. In such case, we need to avoid + // blocking in the poll syscall, but we can still take this + // opportunity to find additional ready operations. + break :t 0; + } + const max_poll_ms = std.math.maxInt(i32); + break :t max_poll_ms; + }; + const syscall = try Syscall.start(); + const rc = posix.system.poll(&poll_buffer, poll_len, timeout_ms); + syscall.finish(); + switch (posix.errno(rc)) { + .SUCCESS => { + if (rc == 0) { + if (b.completions.head != .none) { + // Since there are already completions available in the + // queue, this is neither a timeout nor a case for + // retrying. + return; + } + continue; + } + var prev_index: Io.Operation.OptionalIndex = .none; + var index = b.submissions.head; + for (poll_buffer[0..poll_len]) |poll_entry| { + const storage = &b.storage[index.toIndex()]; + const submission = &storage.submission; + const next_index = submission.node.next; + if (poll_entry.revents != 0) { + const result = try operate(t, submission.operation); + + switch (prev_index) { + .none => b.submissions.head = next_index, + else => b.storage[prev_index.toIndex()].submission.node.next = next_index, + } + if (next_index == .none) b.submissions.tail = prev_index; + + switch (b.completions.tail) { + .none => b.completions.head = index, + else => |tail_index| b.storage[tail_index.toIndex()].completion.node.next = index, + } + storage.* = .{ .completion = .{ .node = .{ .next = .none }, .result = result } }; + b.completions.tail = index; + } else prev_index = index; + index = next_index; + } + assert(index == .none); + return; + }, + .INTR => continue, + else => break, + } + }, + } + { + var tail_index = b.completions.tail; + defer b.completions.tail = tail_index; + var index = b.submissions.head; + errdefer b.submissions.head = index; + while (index != .none) { + const storage = &b.storage[index.toIndex()]; + const submission = &storage.submission; + const next_index = submission.node.next; + const result = try operate(t, submission.operation); + + switch (tail_index) { + .none => b.completions.head = index, + else => b.storage[tail_index.toIndex()].completion.node.next = index, + } + storage.* = .{ .completion = .{ .node = .{ .next = .none }, .result = result } }; + tail_index = index; + index = next_index; + } + b.submissions = .{ .head = .none, .tail = .none }; + } +} + +fn batchAwaitConcurrent(userdata: ?*anyopaque, b: *Io.Batch, timeout: Io.Timeout) Io.Batch.AwaitConcurrentError!void { + const t: *Threaded = @ptrCast(@alignCast(userdata)); + if (is_windows) { + const deadline: ?Io.Clock.Timestamp = timeout.toDeadline(ioBasic(t)) catch |err| switch (err) { + error.Unexpected => deadline: { + recoverableOsBugDetected(); + break :deadline .{ .raw = .{ .nanoseconds = 0 }, .clock = .awake }; }, - .file_read_streaming => |*o| { - _ = o.status.unstarted; - if (poll_buffer.len - poll_i == 0) return error.ConcurrencyUnavailable; - poll_buffer[poll_i] = .{ - .fd = o.file.handle, - .events = posix.POLL.IN, - .revents = 0, + error.UnsupportedClock => |e| return e, + }; + try batchAwaitWindows(b); + while (b.pending.head != .none and b.completions.head == .none) { + var delay_interval: windows.LARGE_INTEGER = interval: { + const d = deadline orelse break :interval std.math.minInt(windows.LARGE_INTEGER); + break :interval t.deadlineToWindowsInterval(d) catch |err| switch (err) { + error.UnsupportedClock => |e| return e, + error.Unexpected => { + recoverableOsBugDetected(); + break :interval -1; + }, }; - map_buffer[poll_i] = @intCast(op); - poll_i += 1; - }, + }; + const alertable_syscall = try AlertableSyscall.start(); + const delay_rc = windows.ntdll.NtDelayExecution(windows.TRUE, &delay_interval); + alertable_syscall.finish(); + switch (delay_rc) { + .SUCCESS, .TIMEOUT => { + // The thread woke due to the timeout. Although spurious + // timeouts are OK, when no deadline is passed we must not + // return `error.Timeout`. + if (timeout != .none and b.completions.head == .none) return error.Timeout; + }, + else => {}, + } + } + return; + } + if (native_os == .wasi and !builtin.link_libc) @panic("TODO"); + var poll_buffer: [poll_buffer_len]posix.pollfd = undefined; + var poll_storage: struct { + gpa: std.mem.Allocator, + b: *Io.Batch, + slice: []posix.pollfd, + len: u32, + + fn add(storage: *@This(), file: Io.File, events: @FieldType(posix.pollfd, "events")) Io.ConcurrentError!void { + const len = storage.len; + if (len == poll_buffer_len) { + const slice: []posix.pollfd = if (storage.b.context) |context| + @as([*]posix.pollfd, @ptrCast(@alignCast(context)))[0..storage.b.storage.len] + else allocation: { + const allocation = storage.gpa.alloc(posix.pollfd, storage.b.storage.len) catch + return error.ConcurrencyUnavailable; + storage.b.context = allocation.ptr; + break :allocation allocation; + }; + @memcpy(slice[0..poll_buffer_len], storage.slice); + } + storage.slice[len] = .{ + .fd = file.handle, + .events = events, + .revents = 0, + }; + storage.len = len + 1; + } + } = .{ .gpa = t.allocator, .b = b, .slice = &poll_buffer, .len = 0 }; + { + var index = b.submissions.head; + while (index != .none) { + const submission = &b.storage[index.toIndex()].submission; + switch (submission.operation) { + .file_read_streaming => |o| try poll_storage.add(o.file, posix.POLL.IN), + } + index = submission.node.next; } } - switch (poll_i) { + switch (poll_storage.len) { 0 => return, 1 => if (timeout == .none) { - const op = map_buffer[0]; - try operate(t, &operations[op]); - submitComplete(ring, &complete_tail, op); - poll_i = 0; + const index = b.submissions.head; + const storage = &b.storage[index.toIndex()]; + const result = try operate(t, storage.submission.operation); + + b.submissions = .{ .head = .none, .tail = .none }; + + switch (b.completions.tail) { + .none => b.completions.head = index, + else => |tail_index| b.storage[tail_index.toIndex()].completion.node.next = index, + } + storage.* = .{ .completion = .{ .node = .{ .next = .none }, .result = result } }; + b.completions.tail = index; return; }, else => {}, } const t_io = ioBasic(t); const deadline = timeout.toDeadline(t_io) catch return error.UnsupportedClock; - const max_poll_ms = std.math.maxInt(i32); while (true) { const timeout_ms: i32 = t: { - if (b.user.complete_head != complete_tail) { + if (b.completions.head != .none) { // It is legal to call batchWait with already completed // operations in the ring. In such case, we need to avoid // blocking in the poll syscall, but we can still take this @@ -2571,15 +2715,16 @@ fn batchWait(userdata: ?*anyopaque, b: *Io.Batch, timeout: Io.Timeout) Io.Batch. const d = deadline orelse break :t -1; const duration = d.durationFromNow(t_io) catch return error.UnsupportedClock; if (duration.raw.nanoseconds <= 0) return error.Timeout; + const max_poll_ms = std.math.maxInt(i32); break :t @intCast(@min(max_poll_ms, duration.raw.toMilliseconds())); }; const syscall = try Syscall.start(); - const rc = posix.system.poll(&poll_buffer, poll_i, timeout_ms); + const rc = posix.system.poll(&poll_buffer, poll_storage.len, timeout_ms); syscall.finish(); switch (posix.errno(rc)) { .SUCCESS => { if (rc == 0) { - if (b.user.complete_head != complete_tail) { + if (b.completions.head != .none) { // Since there are already completions available in the // queue, this is neither a timeout nor a case for // retrying. @@ -2590,18 +2735,30 @@ fn batchWait(userdata: ?*anyopaque, b: *Io.Batch, timeout: Io.Timeout) Io.Batch. if (deadline == null) continue; return error.Timeout; } - while (poll_i != 0) { - poll_i -= 1; - const poll_fd = &poll_buffer[poll_i]; - const op = map_buffer[poll_i]; - if (poll_fd.revents == 0) { - submit_head = submit_head.prev(len); - ring[submit_head.index(len)] = op; - } else { - try operate(t, &operations[op]); - submitComplete(ring, &complete_tail, op); - } + var prev_index: Io.Operation.OptionalIndex = .none; + var index = b.submissions.head; + for (poll_storage.slice[0..poll_storage.len]) |poll_entry| { + const submission = &b.storage[index.toIndex()].submission; + const next_index = submission.node.next; + if (poll_entry.revents != 0) { + const result = try operate(t, submission.operation); + + switch (prev_index) { + .none => b.submissions.head = next_index, + else => b.storage[prev_index.toIndex()].submission.node.next = next_index, + } + if (next_index == .none) b.submissions.tail = prev_index; + + switch (b.completions.tail) { + .none => b.completions.head = index, + else => |tail_index| b.storage[tail_index.toIndex()].completion.node.next = index, + } + b.completions.tail = index; + b.storage[index.toIndex()] = .{ .completion = .{ .node = .{ .next = .none }, .result = result } }; + } else prev_index = index; + index = next_index; } + assert(index == .none); return; }, .INTR => continue, @@ -2610,166 +2767,126 @@ fn batchWait(userdata: ?*anyopaque, b: *Io.Batch, timeout: Io.Timeout) Io.Batch. } } -fn batchCancel(userdata: ?*anyopaque, b: *Io.Batch) void { - const t: *Threaded = @ptrCast(@alignCast(userdata)); - const operations = b.operations; - const len: u31 = @intCast(operations.len); - const ring = b.ring[0..len]; - var submit_head = b.impl.submit_head; - const submit_tail = b.user.submit_tail; - b.impl.submit_tail = submit_tail; - var complete_tail = b.impl.complete_tail; - while (submit_head != submit_tail) : (submit_head = submit_head.next(len)) { - const op = ring[submit_head.index(len)]; - switch (operations[op]) { - .noop => |*o| { - _ = o.status.unstarted; - o.status = .{ .result = {} }; - submitComplete(ring, &complete_tail, op); - }, - .file_read_streaming => |*o| _ = o.status.unstarted, - } - } - if (is_windows) { - // Iterate over pending and issue cancelations, then free the allocation for IO_STATUS_BLOCK - if (b.impl.reserved) |reserved| { - const gpa = t.allocator; - const metadatas_ptr: [*]WinOpMetadata = @ptrCast(@alignCast(reserved)); - const metadatas = metadatas_ptr[0..b.operations.len]; - for (metadatas, 0..) |*metadata, op| { - if (!metadata.pending) continue; - const done = @atomicLoad(windows.NTSTATUS, &metadata.iosb.u.Status, .acquire) != .PENDING; - if (done) continue; - switch (operations[op]) { - .noop => unreachable, - .file_read_streaming => |*o| { - _ = windows.ntdll.NtCancelIoFile(o.file.handle, &metadata.iosb); - }, - } - } - for (metadatas) |*metadata| { - if (!metadata.pending) continue; - while (@atomicLoad(windows.NTSTATUS, &metadata.iosb.u.Status, .acquire) == .PENDING) { - waitForApcOrAlert(); - } - } - gpa.free(metadatas); - b.impl.reserved = null; - } - } - b.impl.submit_head = submit_tail; - b.impl.complete_tail = complete_tail; - b.user.complete_tail = complete_tail; -} - -const WinOpMetadata = struct { +const WindowsBatchPendingOperationContext = extern struct { + file: windows.HANDLE, iosb: windows.IO_STATUS_BLOCK, - pending: bool, + + const Erased = [3]usize; + + comptime { + assert(@sizeOf(Erased) <= @sizeOf(WindowsBatchPendingOperationContext)); + } + + fn toErased(context: *WindowsBatchPendingOperationContext) *Erased { + return @ptrCast(context); + } + + fn fromErased(erased: *Erased) *WindowsBatchPendingOperationContext { + return @ptrCast(erased); + } }; -fn batchWaitWindows(t: *Threaded, b: *Io.Batch, timeout: Io.Timeout) Io.Batch.WaitError!void { - const operations = b.operations; - const len: u31 = @intCast(operations.len); - const ring = b.ring[0..len]; - var submit_head = b.impl.submit_head; - const submit_tail = b.user.submit_tail; - b.impl.submit_tail = submit_tail; - var complete_tail = b.impl.complete_tail; - - const metadatas_ptr: [*]WinOpMetadata = if (b.impl.reserved) |reserved| @ptrCast(@alignCast(reserved)) else a: { - const gpa = t.allocator; - const metadatas = gpa.alloc(WinOpMetadata, operations.len) catch return error.ConcurrencyUnavailable; - b.impl.reserved = metadatas.ptr; - @memset(metadatas, .{ .iosb = undefined, .pending = false }); - break :a metadatas.ptr; - }; - const metadatas = metadatas_ptr[0..operations.len]; - - defer { - b.impl.submit_head = submit_head; - b.impl.complete_tail = complete_tail; - b.user.complete_tail = complete_tail; +fn batchCancel(userdata: ?*anyopaque, b: *Io.Batch) void { + const t: *Threaded = @ptrCast(@alignCast(userdata)); + { + var tail_index = b.unused.tail; + defer b.unused.tail = tail_index; + var index = b.submissions.head; + errdefer b.submissions.head = index; + while (index != .none) { + const next_index = b.storage[index.toIndex()].submission.node.next; + switch (tail_index) { + .none => b.unused.head = index, + else => b.storage[tail_index.toIndex()].unused.next = index, + } + b.storage[index.toIndex()] = .{ .unused = .{ .prev = tail_index, .next = .none } }; + tail_index = index; + index = next_index; + } + b.submissions = .{ .head = .none, .tail = .none }; } - - while (submit_head != submit_tail) : (submit_head = submit_head.next(len)) { - const op = ring[submit_head.index(len)]; - const operation = &operations[op]; - const metadata = &metadatas[op]; - metadata.* = .{ .iosb = .{ - .u = .{ .Status = .PENDING }, - .Information = 0, - }, .pending = false }; - switch (operation.*) { - .noop => |*o| { - _ = o.status.unstarted; - o.status = .{ .result = {} }; - submitComplete(ring, &complete_tail, op); - }, - .file_read_streaming => |*o| { - _ = o.status.unstarted; - try ntReadFile(o.file.handle, o.data, &metadata.iosb); - if (@atomicLoad(windows.NTSTATUS, &metadata.iosb.u.Status, .acquire) == .PENDING) { - o.status = .{ .pending = b }; - metadata.pending = true; - } else { - o.status = .{ .result = ntReadFileResult(&metadata.iosb) }; - submitComplete(ring, &complete_tail, op); - } - }, + if (is_windows) { + var index = b.pending.head; + while (index != .none) { + const pending = &b.storage[index.toIndex()].pending; + const context: *WindowsBatchPendingOperationContext = .fromErased(&pending.context); + _ = windows.ntdll.NtCancelIoFile(context.file, &context.iosb); + index = pending.node.next; } + while (b.pending.head != .none) waitForApcOrAlert(); + } else if (b.context) |context| { + t.allocator.free(@as([*]posix.pollfd, @ptrCast(@alignCast(context)))[0..b.storage.len]); + b.context = null; } + assert(b.pending.head == .none); +} - const deadline: ?Io.Clock.Timestamp = timeout.toDeadline(ioBasic(t)) catch |err| switch (err) { - error.Unexpected => deadline: { - recoverableOsBugDetected(); - break :deadline .{ .raw = .{ .nanoseconds = 0 }, .clock = .awake }; +fn batchApc(apc_context: ?*anyopaque, iosb: *windows.IO_STATUS_BLOCK, _: windows.ULONG) callconv(.winapi) void { + const b: *Io.Batch = @ptrCast(@alignCast(apc_context)); + const context: *WindowsBatchPendingOperationContext = @fieldParentPtr("iosb", iosb); + const erased_context = context.toErased(); + const pending: *Io.Operation.Storage.Pending = @fieldParentPtr("context", erased_context); + switch (pending.node.prev) { + .none => b.pending.head = pending.node.next, + else => |prev_index| b.storage[prev_index.toIndex()].pending.node.next = pending.node.next, + } + switch (pending.node.next) { + .none => b.pending.tail = pending.node.prev, + else => |next_index| b.storage[next_index.toIndex()].pending.node.prev = pending.node.prev, + } + const storage: *Io.Operation.Storage = @fieldParentPtr("pending", pending); + const index = storage - b.storage.ptr; + switch (iosb.u.Status) { + .CANCELLED => { + const tail_index = b.unused.tail; + switch (tail_index) { + .none => b.unused.head = .fromIndex(index), + else => b.storage[tail_index.toIndex()].unused.next = .fromIndex(index), + } + storage.* = .{ .unused = .{ .prev = tail_index, .next = .none } }; + b.unused.tail = .fromIndex(index); }, - error.UnsupportedClock => |e| return e, - }; - - while (true) { - var any_pending = false; - for (metadatas, 0..) |*metadata, op_usize| { - if (!metadata.pending) continue; - any_pending = true; - const op: u31 = @intCast(op_usize); - const done = @atomicLoad(windows.NTSTATUS, &metadata.iosb.u.Status, .acquire) != .PENDING; - switch (operations[op]) { - .noop => unreachable, - .file_read_streaming => |*o| { - assert(o.status.pending == b); - if (!done) continue; - o.status = .{ .result = ntReadFileResult(&metadata.iosb) }; - }, + else => { + switch (b.completions.tail) { + .none => b.completions.head = .fromIndex(index), + else => |tail_index| b.storage[tail_index.toIndex()].completion.node.next = .fromIndex(index), } - metadata.pending = false; - submitComplete(ring, &complete_tail, op); - } - if (b.user.complete_head != complete_tail) return; - if (!any_pending) return; - var delay_interval: windows.LARGE_INTEGER = interval: { - const d = deadline orelse break :interval std.math.minInt(windows.LARGE_INTEGER); - break :interval t.deadlineToWindowsInterval(d) catch |err| switch (err) { - error.UnsupportedClock => |e| return e, - error.Unexpected => { - recoverableOsBugDetected(); - break :interval -1; - }, + b.completions.tail = .fromIndex(index); + const result: Io.Operation.Result = switch (pending.tag) { + .file_read_streaming => .{ .file_read_streaming = ntReadFileResult(iosb) }, }; - }; - const alertable_syscall = try AlertableSyscall.start(); - const delay_rc = windows.ntdll.NtDelayExecution(windows.TRUE, &delay_interval); - alertable_syscall.finish(); - switch (delay_rc) { - .SUCCESS, .TIMEOUT => { - // The thread woke due to the timeout. Although spurious - // timeouts are OK, when no deadline is passed we must not - // return `error.Timeout`. - if (timeout != .none) return error.Timeout; + storage.* = .{ .completion = .{ .node = .{ .next = .none }, .result = result } }; + }, + } +} + +fn batchAwaitWindows(b: *Io.Batch) Io.Cancelable!void { + var index = b.submissions.head; + errdefer b.submissions.head = index; + while (index != .none) { + const storage = &b.storage[index.toIndex()]; + const submission = storage.submission; + errdefer storage.* = .{ .submission = submission }; + storage.* = .{ .pending = .{ + .node = .{ .prev = b.pending.tail, .next = .none }, + .tag = submission.operation, + .context = undefined, + } }; + const context: *WindowsBatchPendingOperationContext = .fromErased(&storage.pending.context); + switch (submission.operation) { + .file_read_streaming => |o| { + context.file = o.file.handle; + try ntReadFile(o.file.handle, o.data, &batchApc, b, &context.iosb); }, - else => {}, } + switch (b.pending.tail) { + .none => b.pending.head = index, + else => |tail_index| b.storage[tail_index.toIndex()].pending.node.next = index, + } + b.pending.tail = index; + index = submission.node.next; } + b.submissions = .{ .head = .none, .tail = .none }; } fn submitComplete(ring: []u32, complete_tail: *Io.Batch.RingIndex, op: u32) void { @@ -8701,7 +8818,7 @@ fn fileReadStreamingWindows(file: File, data: []const []u8) File.ReadStreamingEr .u = .{ .Status = .PENDING }, .Information = 0, }; - try ntReadFile(file.handle, data, &io_status_block); + try ntReadFile(file.handle, data, &noopApc, null, &io_status_block); while (@atomicLoad(windows.NTSTATUS, &io_status_block.u.Status, .acquire) == .PENDING) { // Once we get here we must not return from the function until the // operation completes, thereby releasing reference to io_status_block. @@ -8736,12 +8853,20 @@ fn ntReadFileResult(io_status_block: *const windows.IO_STATUS_BLOCK) !usize { } } -fn ntReadFile(handle: windows.HANDLE, data: []const []u8, iosb: *windows.IO_STATUS_BLOCK) Io.Cancelable!void { +fn ntReadFile( + handle: windows.HANDLE, + data: []const []u8, + apcRoutine: ?*const windows.IO_APC_ROUTINE, + apc_context: ?*anyopaque, + iosb: *windows.IO_STATUS_BLOCK, +) Io.Cancelable!void { var index: usize = 0; while (index < data.len and data[index].len == 0) index += 1; if (index == data.len) { - iosb.u.Status = .SUCCESS; - iosb.Information = 0; + iosb.* = .{ .u = .{ .Status = .SUCCESS }, .Information = 0 }; + if (apcRoutine) |routine| if (routine != &noopApc) { + _ = windows.ntdll.NtQueueApcThread(windows.current_process, routine, apc_context, iosb, null); + }; return; } const buffer = data[index]; @@ -8750,8 +8875,8 @@ fn ntReadFile(handle: windows.HANDLE, data: []const []u8, iosb: *windows.IO_STAT while (true) switch (windows.ntdll.NtReadFile( handle, null, // event - noopApc, // apc callback - null, // apc context + apcRoutine, + apc_context, iosb, buffer.ptr, @min(std.math.maxInt(u32), buffer.len), -- 2.54.0 From 62c97b745d508a5ed7011bf3b8400fde4677b839 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 30 Jan 2026 12:27:27 -0800 Subject: [PATCH 149/499] std.Io.Threaded: stop checking bytes read with END_OF_FILE --- lib/std/Io/Threaded.zig | 8 +++----- 1 file changed, 3 insertions(+), 5 deletions(-) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 18f24eb40cefdabe8fca2820e239edac1046aafc..45184e6d595d9106176c0a7029fdb017dcc3ec49 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -8816,9 +8816,10 @@ fn fileReadStreamingPosix(file: File, data: []const []u8) File.ReadStreamingErro fn fileReadStreamingWindows(file: File, data: []const []u8) File.ReadStreamingError!usize { var io_status_block: windows.IO_STATUS_BLOCK = .{ .u = .{ .Status = .PENDING }, - .Information = 0, + .Information = undefined, }; try ntReadFile(file.handle, data, &noopApc, null, &io_status_block); + while (@atomicLoad(windows.NTSTATUS, &io_status_block.u.Status, .acquire) == .PENDING) { // Once we get here we must not return from the function until the // operation completes, thereby releasing reference to io_status_block. @@ -8842,10 +8843,7 @@ fn ntReadFileResult(io_status_block: *const windows.IO_STATUS_BLOCK) !usize { .PENDING => unreachable, .CANCELLED => unreachable, .SUCCESS => return io_status_block.Information, - .END_OF_FILE, .PIPE_BROKEN => { - if (io_status_block.Information == 0) return error.EndOfStream; - return io_status_block.Information; - }, + .END_OF_FILE, .PIPE_BROKEN => return error.EndOfStream, .INVALID_DEVICE_REQUEST => return error.IsDir, .LOCK_NOT_GRANTED => return error.LockViolation, .ACCESS_DENIED => return error.AccessDenied, -- 2.54.0 From 39a6d5d1c5db32e9648fba6a46f3fef4ef83974a Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 30 Jan 2026 17:59:48 -0800 Subject: [PATCH 150/499] std.Io.File: add non-blocking flag On Windows, we need to know ahead of time whether a file was opened in synchronous mode or asynchronous mode. There may be advantages to tracking this state for POSIX operating systems as well. --- lib/std/Io/File.zig | 18 +++++++++ lib/std/Io/Threaded.zig | 71 ++++++++++++++++++++++++++---------- lib/std/Io/Threaded/test.zig | 4 +- lib/std/Progress.zig | 1 + lib/std/posix/test.zig | 9 +++-- 5 files changed, 79 insertions(+), 24 deletions(-) diff --git a/lib/std/Io/File.zig b/lib/std/Io/File.zig index 5db6f81ac6f8e2b2ade7fcd49916af06b1317c7d..e0297e0573c06dc35cbc169bd822832b4cf80648 100644 --- a/lib/std/Io/File.zig +++ b/lib/std/Io/File.zig @@ -10,6 +10,18 @@ const assert = std.debug.assert; const Dir = std.Io.Dir; handle: Handle, +flags: Flags, + +pub const Flags = struct { + /// * true: + /// - windows: opened with MODE.IO.ASYNCHRONOUS + /// - POSIX: O_NONBLOCK is set + /// * false: + /// - windows: opened with SYNCHRONOUS_ALERT or SYNCHRONOUS_NONALERT, or + /// not a file. + /// - POSIX: O_NONBLOCK is unset + nonblocking: bool, +}; pub const Handle = std.posix.fd_t; @@ -80,9 +92,11 @@ pub fn stdout() File { return switch (native_os) { .windows => .{ .handle = std.os.windows.peb().ProcessParameters.hStdOutput, + .flags = .{ .nonblocking = false }, }, else => .{ .handle = std.posix.STDOUT_FILENO, + .flags = .{ .nonblocking = false }, }, }; } @@ -91,9 +105,11 @@ pub fn stderr() File { return switch (native_os) { .windows => .{ .handle = std.os.windows.peb().ProcessParameters.hStdError, + .flags = .{ .nonblocking = false }, }, else => .{ .handle = std.posix.STDERR_FILENO, + .flags = .{ .nonblocking = false }, }, }; } @@ -102,9 +118,11 @@ pub fn stdin() File { return switch (native_os) { .windows => .{ .handle = std.os.windows.peb().ProcessParameters.hStdInput, + .flags = .{ .nonblocking = false }, }, else => .{ .handle = std.posix.STDIN_FILENO, + .flags = .{ .nonblocking = false }, }, }; } diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 45184e6d595d9106176c0a7029fdb017dcc3ec49..8fdd2a58e9dd687e79e0a0457cfbc9342c0c5970 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -3215,8 +3215,10 @@ fn dirCreateDirPathOpenWasi( fn dirStat(userdata: ?*anyopaque, dir: Dir) Dir.StatError!Dir.Stat { const t: *Threaded = @ptrCast(@alignCast(userdata)); - const file: File = .{ .handle = dir.handle }; - return fileStat(t, file); + return fileStat(t, .{ + .handle = dir.handle, + .flags = .{ .nonblocking = false }, + }); } const dirStatFile = switch (native_os) { @@ -4008,7 +4010,10 @@ fn dirCreateFilePosix( } } - return .{ .handle = fd }; + return .{ + .handle = fd, + .flags = .{ .nonblocking = false }, + }; } fn dirCreateFileWindows( @@ -4138,7 +4143,10 @@ fn dirCreateFileWindows( errdefer windows.CloseHandle(handle); const exclusive = switch (flags.lock) { - .none => return .{ .handle = handle }, + .none => return .{ + .handle = handle, + .flags = .{ .nonblocking = false }, + }, .shared => false, .exclusive => true, }; @@ -4158,7 +4166,10 @@ fn dirCreateFileWindows( )) { .SUCCESS => { syscall.finish(); - return .{ .handle = handle }; + return .{ + .handle = handle, + .flags = .{ .nonblocking = false }, + }; }, .INSUFFICIENT_RESOURCES => return syscall.fail(error.SystemResources), .LOCK_NOT_GRANTED => return syscall.fail(error.WouldBlock), @@ -4207,7 +4218,10 @@ fn dirCreateFileWasi( switch (wasi.path_open(dir.handle, lookup_flags, sub_path.ptr, sub_path.len, oflags, base, inheriting, fdflags, &fd)) { .SUCCESS => { syscall.finish(); - return .{ .handle = fd }; + return .{ + .handle = fd, + .flags = .{ .nonblocking = false }, + }; }, .INTR => { try syscall.checkCancel(); @@ -4302,7 +4316,10 @@ fn dirCreateFileAtomic( .SUCCESS => { syscall.finish(); return .{ - .file = .{ .handle = @intCast(rc) }, + .file = .{ + .handle = @intCast(rc), + .flags = .{ .nonblocking = false }, + }, .file_basename_hex = 0, .dest_sub_path = dest_path, .file_open = true, @@ -4510,7 +4527,10 @@ fn dirOpenFilePosix( if (!flags.allow_directory) { const is_dir = is_dir: { - const stat = fileStat(t, .{ .handle = fd }) catch |err| switch (err) { + const stat = fileStat(t, .{ + .handle = fd, + .flags = .{ .nonblocking = false }, + }) catch |err| switch (err) { // The directory-ness is either unknown or unknowable error.Streaming => break :is_dir false, else => |e| return e, @@ -4596,7 +4616,10 @@ fn dirOpenFilePosix( } } - return .{ .handle = fd }; + return .{ + .handle = fd, + .flags = .{ .nonblocking = false }, + }; } fn dirOpenFileWindows( @@ -4729,7 +4752,10 @@ pub fn dirOpenFileWtf16( errdefer w.CloseHandle(handle); const exclusive = switch (flags.lock) { - .none => return .{ .handle = handle }, + .none => return .{ + .handle = handle, + .flags = .{ .nonblocking = false }, + }, .shared => false, .exclusive => true, }; @@ -4752,7 +4778,10 @@ pub fn dirOpenFileWtf16( .ACCESS_VIOLATION => |err| return syscall.ntstatusBug(err), // bad io_status_block pointer else => |status| return syscall.unexpectedNtstatus(status), }; - return .{ .handle = handle }; + return .{ + .handle = handle, + .flags = .{ .nonblocking = false }, + }; } fn dirOpenFileWasi( @@ -4834,7 +4863,7 @@ fn dirOpenFileWasi( if (!flags.allow_directory) { const is_dir = is_dir: { - const stat = fileStat(t, .{ .handle = fd }) catch |err| switch (err) { + const stat = fileStat(t, .{ .handle = fd, .flags = .{ .nonblocking = false } }) catch |err| switch (err) { // The directory-ness is either unknown or unknowable error.Streaming => break :is_dir false, else => |e| return e, @@ -4844,7 +4873,10 @@ fn dirOpenFileWasi( if (is_dir) return error.IsDir; } - return .{ .handle = fd }; + return .{ + .handle = fd, + .flags = .{ .nonblocking = false }, + }; } const dirOpenDir = switch (native_os) { @@ -14390,15 +14422,15 @@ fn spawnPosix(t: *Threaded, options: process.SpawnOptions) process.SpawnError!Sp .pid = pid, .err_fd = err_pipe[0], .stdin = switch (options.stdin) { - .pipe => .{ .handle = stdin_pipe[1] }, + .pipe => .{ .handle = stdin_pipe[1], .flags = .{ .nonblocking = false } }, else => null, }, .stdout = switch (options.stdout) { - .pipe => .{ .handle = stdout_pipe[0] }, + .pipe => .{ .handle = stdout_pipe[0], .flags = .{ .nonblocking = false } }, else => null, }, .stderr = switch (options.stderr) { - .pipe => .{ .handle = stderr_pipe[0] }, + .pipe => .{ .handle = stderr_pipe[0], .flags = .{ .nonblocking = false } }, else => null, }, }; @@ -15052,9 +15084,9 @@ fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) pro return .{ .id = piProcInfo.hProcess, .thread_handle = piProcInfo.hThread, - .stdin = if (g_hChildStd_IN_Wr) |h| .{ .handle = h } else null, - .stdout = if (g_hChildStd_OUT_Rd) |h| .{ .handle = h } else null, - .stderr = if (g_hChildStd_ERR_Rd) |h| .{ .handle = h } else null, + .stdin = if (g_hChildStd_IN_Wr) |h| .{ .handle = h, .flags = .{ .nonblocking = false } } else null, + .stdout = if (g_hChildStd_OUT_Rd) |h| .{ .handle = h, .flags = .{ .nonblocking = true } } else null, + .stderr = if (g_hChildStd_ERR_Rd) |h| .{ .handle = h, .flags = .{ .nonblocking = true } } else null, .request_resource_usage_statistics = options.request_resource_usage_statistics, }; } @@ -16188,6 +16220,7 @@ fn progressParentFile(userdata: ?*anyopaque) std.Progress.ParentFileError!File { .pointer => @ptrFromInt(int), else => return error.UnsupportedOperation, }, + .flags = .{ .nonblocking = false }, }; } diff --git a/lib/std/Io/Threaded/test.zig b/lib/std/Io/Threaded/test.zig index ffda1e7601c81b790c4bf9148a6835d4f448de88..593580d1f62511b7ce1d73248cc50a0bca5a45a2 100644 --- a/lib/std/Io/Threaded/test.zig +++ b/lib/std/Io/Threaded/test.zig @@ -188,8 +188,8 @@ test "cancel blocked read from pipe" { }), else => { const pipe = try std.Io.Threaded.pipe2(.{}); - read_end = .{ .handle = pipe[0] }; - write_end = .{ .handle = pipe[1] }; + read_end = .{ .handle = pipe[0], .flags = .{ .nonblocking = false } }; + write_end = .{ .handle = pipe[1], .flags = .{ .nonblocking = false } }; }, } defer { diff --git a/lib/std/Progress.zig b/lib/std/Progress.zig index d0ee9e556f5c384cf7eef6952900447d7c45a07b..0fefc77a32e788267a10f9fc2b613c96abeb5e19 100644 --- a/lib/std/Progress.zig +++ b/lib/std/Progress.zig @@ -979,6 +979,7 @@ fn serializeIpc(start_serialized_len: usize, serialized_buffer: *Serialized.Buff if (main_parent == .unused) continue; const file: Io.File = .{ .handle = main_storage.getIpcFd() orelse continue, + .flags = .{ .nonblocking = true }, }; const opt_saved_metadata = findOld(file.handle, old_ipc_metadata_fds, old_ipc_metadata); var bytes_read: usize = 0; diff --git a/lib/std/posix/test.zig b/lib/std/posix/test.zig index 5838595fcf50cec130595089b7d38c26b17f19b8..e2a52473f3862ea1332a82abd0d0ab98d428b153 100644 --- a/lib/std/posix/test.zig +++ b/lib/std/posix/test.zig @@ -126,8 +126,8 @@ test "pipe" { const io = testing.io; const fds = try std.Io.Threaded.pipe2(.{}); - const out: Io.File = .{ .handle = fds[0] }; - const in: Io.File = .{ .handle = fds[1] }; + const out: Io.File = .{ .handle = fds[0], .flags = .{ .nonblocking = false } }; + const in: Io.File = .{ .handle = fds[1], .flags = .{ .nonblocking = false } }; try in.writeStreamingAll(io, "hello"); var buf: [16]u8 = undefined; try expect((try out.readStreaming(io, &.{&buf})) == 5); @@ -150,7 +150,10 @@ test "memfd_create" { else => return error.SkipZigTest, } - const file: Io.File = .{ .handle = try posix.memfd_create("test", 0) }; + const file: Io.File = .{ + .handle = try posix.memfd_create("test", 0), + .flags = .{ .nonblocking = false }, + }; defer file.close(io); try file.writePositionalAll(io, "test", 0); -- 2.54.0 From 25aef0dd8786c5b5342eda167a609529efa09353 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 30 Jan 2026 19:10:44 -0800 Subject: [PATCH 151/499] std.Io.Threaded: rework file reading to observe nonblocking flag - batchAwaitAsync does blocking reads with NtReadFile (no APC, no event) when the nonblocking flag is unset, but still takes advantage of APCs when nonblocking flag is set. - batchAwaitConcurrent returns error.ConcurrencyUnavailable when it encounters a file_read_streaming operation on a file in blocking mode. - fileReadStreaming avoids pointlessly checking sync cancelation status when nonblocking flag is set, uses an APC with a done flag, and waits on that value to change in NtDelayExecution before returning. - fix incorrect use of NtCancelIoFile (ntdll function prototype was wrong, leading to misuse) --- lib/std/Io/Threaded.zig | 224 +++++++++++++++++++++++------------ lib/std/os/windows/ntdll.zig | 2 +- 2 files changed, 147 insertions(+), 79 deletions(-) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 8fdd2a58e9dd687e79e0a0457cfbc9342c0c5970..e9e5ece521e9276c11a0523ae53eac44ce548ef4 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -1340,8 +1340,6 @@ const AlertableSyscall = struct { } }; -fn noopApc(_: ?*anyopaque, _: *windows.IO_STATUS_BLOCK, _: windows.ULONG) callconv(.winapi) void {} - fn waitForApcOrAlert() void { const infinite_timeout: windows.LARGE_INTEGER = std.math.minInt(windows.LARGE_INTEGER); _ = windows.ntdll.NtDelayExecution(windows.TRUE, &infinite_timeout); @@ -2500,7 +2498,10 @@ fn operate(userdata: ?*anyopaque, operation: Io.Operation) Io.Cancelable!Io.Oper fn batchAwaitAsync(userdata: ?*anyopaque, b: *Io.Batch) Io.Batch.AwaitAsyncError!void { const t: *Threaded = @ptrCast(@alignCast(userdata)); if (is_windows) { - try batchAwaitWindows(b); + batchAwaitWindows(b, false) catch |err| switch (err) { + error.ConcurrencyUnavailable => unreachable, // passed concurrency=false + else => |e| return e, + }; const alertable_syscall = try AlertableSyscall.start(); while (b.pending.head != .none and b.completions.head == .none) waitForApcOrAlert(); alertable_syscall.finish(); @@ -2616,7 +2617,7 @@ fn batchAwaitConcurrent(userdata: ?*anyopaque, b: *Io.Batch, timeout: Io.Timeout }, error.UnsupportedClock => |e| return e, }; - try batchAwaitWindows(b); + try batchAwaitWindows(b, true); while (b.pending.head != .none and b.completions.head == .none) { var delay_interval: windows.LARGE_INTEGER = interval: { const d = deadline orelse break :interval std.math.minInt(windows.LARGE_INTEGER); @@ -2810,7 +2811,8 @@ fn batchCancel(userdata: ?*anyopaque, b: *Io.Batch) void { while (index != .none) { const pending = &b.storage[index.toIndex()].pending; const context: *WindowsBatchPendingOperationContext = .fromErased(&pending.context); - _ = windows.ntdll.NtCancelIoFile(context.file, &context.iosb); + var cancel_iosb: windows.IO_STATUS_BLOCK = undefined; + _ = windows.ntdll.NtCancelIoFileEx(context.file, &context.iosb, &cancel_iosb); index = pending.node.next; } while (b.pending.head != .none) waitForApcOrAlert(); @@ -2860,30 +2862,94 @@ fn batchApc(apc_context: ?*anyopaque, iosb: *windows.IO_STATUS_BLOCK, _: windows } } -fn batchAwaitWindows(b: *Io.Batch) Io.Cancelable!void { +/// If `concurrency` is false, `error.ConcurrencyUnavailable` is unreachable. +fn batchAwaitWindows(b: *Io.Batch, concurrency: bool) error{ Canceled, ConcurrencyUnavailable }!void { var index = b.submissions.head; errdefer b.submissions.head = index; while (index != .none) { const storage = &b.storage[index.toIndex()]; const submission = storage.submission; - errdefer storage.* = .{ .submission = submission }; storage.* = .{ .pending = .{ .node = .{ .prev = b.pending.tail, .next = .none }, .tag = submission.operation, .context = undefined, } }; - const context: *WindowsBatchPendingOperationContext = .fromErased(&storage.pending.context); - switch (submission.operation) { - .file_read_streaming => |o| { - context.file = o.file.handle; - try ntReadFile(o.file.handle, o.data, &batchApc, b, &context.iosb); - }, - } switch (b.pending.tail) { .none => b.pending.head = index, else => |tail_index| b.storage[tail_index.toIndex()].pending.node.next = index, } b.pending.tail = index; + const context: *WindowsBatchPendingOperationContext = .fromErased(&storage.pending.context); + errdefer { + context.iosb.u.Status = .CANCELLED; + batchApc(b, &context.iosb, 0); + } + switch (submission.operation) { + .file_read_streaming => |o| o: { + var data_index: usize = 0; + while (o.data.len - data_index != 0 and o.data[data_index].len == 0) data_index += 1; + if (o.data.len - data_index == 0) { + context.iosb = .{ + .u = .{ .Status = .SUCCESS }, + .Information = 0, + }; + batchApc(b, &context.iosb, 0); + break :o; + } + const buffer = o.data[data_index]; + const short_buffer_len = @min(std.math.maxInt(u32), buffer.len); + + if (o.file.flags.nonblocking) { + context.file = o.file.handle; + switch (windows.ntdll.NtReadFile( + o.file.handle, + null, // event + &batchApc, + b, + &context.iosb, + buffer.ptr, + short_buffer_len, + null, // byte offset + null, // key + )) { + .PENDING, .SUCCESS => {}, + .CANCELLED => unreachable, + else => |status| { + context.iosb.u.Status = status; + batchApc(b, &context.iosb, 0); + }, + } + } else { + if (concurrency) return error.ConcurrencyUnavailable; + + const syscall: Syscall = try .start(); + while (true) switch (windows.ntdll.NtReadFile( + o.file.handle, + null, // event + null, // APC routine + null, // APC context + &context.iosb, + buffer.ptr, + short_buffer_len, + null, // byte offset + null, // key + )) { + .PENDING => unreachable, // unrecoverable: wrong File nonblocking flag + .CANCELLED => { + try syscall.checkCancel(); + continue; + }, + else => |status| { + syscall.finish(); + + context.iosb.u.Status = status; + batchApc(b, &context.iosb, 0); + break; + }, + }; + } + }, + } index = submission.node.next; } b.submissions = .{ .head = .none, .tail = .none }; @@ -8846,28 +8912,76 @@ fn fileReadStreamingPosix(file: File, data: []const []u8) File.ReadStreamingErro } fn fileReadStreamingWindows(file: File, data: []const []u8) File.ReadStreamingError!usize { - var io_status_block: windows.IO_STATUS_BLOCK = .{ - .u = .{ .Status = .PENDING }, - .Information = undefined, - }; - try ntReadFile(file.handle, data, &noopApc, null, &io_status_block); + var index: usize = 0; + while (data.len - index != 0 and data[index].len == 0) index += 1; + if (data.len - index == 0) return 0; + const buffer = data[index]; + const short_buffer_len = @min(std.math.maxInt(u32), buffer.len); - while (@atomicLoad(windows.NTSTATUS, &io_status_block.u.Status, .acquire) == .PENDING) { - // Once we get here we must not return from the function until the - // operation completes, thereby releasing reference to io_status_block. - const alertable_syscall = AlertableSyscall.start() catch |err| switch (err) { - error.Canceled => |e| { - _ = windows.ntdll.NtCancelIoFile(file.handle, &io_status_block); - while (@atomicLoad(windows.NTSTATUS, &io_status_block.u.Status, .acquire) == .PENDING) { - waitForApcOrAlert(); - } - return e; + var iosb: windows.IO_STATUS_BLOCK = undefined; + + if (!file.flags.nonblocking) { + const syscall: Syscall = try .start(); + while (true) switch (windows.ntdll.NtReadFile( + file.handle, + null, // event + null, // APC routine + null, // APC context + &iosb, + buffer.ptr, + short_buffer_len, + null, // byte offset + null, // key + )) { + .PENDING => unreachable, // unrecoverable: wrong File nonblocking flag + .CANCELLED => { + try syscall.checkCancel(); + continue; + }, + else => |status| { + syscall.finish(); + iosb.u.Status = status; + return ntReadFileResult(&iosb); }, }; - waitForApcOrAlert(); - alertable_syscall.finish(); } - return ntReadFileResult(&io_status_block); + + var done: bool = false; + + switch (windows.ntdll.NtReadFile( + file.handle, + null, // event + flagApc, + &done, // APC context + &iosb, + buffer.ptr, + short_buffer_len, + null, // byte offset + null, // key + )) { + // We must wait for the APC routine. + .PENDING, .SUCCESS => while (!done) { + // Once we get here we must not return from the function until the + // operation completes, thereby releasing reference to io_status_block. + const alertable_syscall = AlertableSyscall.start() catch |err| switch (err) { + error.Canceled => |e| { + var cancel_iosb: windows.IO_STATUS_BLOCK = undefined; + _ = windows.ntdll.NtCancelIoFileEx(file.handle, &iosb, &cancel_iosb); + while (!done) waitForApcOrAlert(); + return e; + }, + }; + waitForApcOrAlert(); + alertable_syscall.finish(); + }, + else => |status| iosb.u.Status = status, + } + return ntReadFileResult(&iosb); +} + +fn flagApc(userdata: ?*anyopaque, _: *windows.IO_STATUS_BLOCK, _: windows.ULONG) callconv(.winapi) void { + const flag: *bool = @ptrCast(userdata); + flag.* = true; } fn ntReadFileResult(io_status_block: *const windows.IO_STATUS_BLOCK) !usize { @@ -8883,52 +8997,6 @@ fn ntReadFileResult(io_status_block: *const windows.IO_STATUS_BLOCK) !usize { } } -fn ntReadFile( - handle: windows.HANDLE, - data: []const []u8, - apcRoutine: ?*const windows.IO_APC_ROUTINE, - apc_context: ?*anyopaque, - iosb: *windows.IO_STATUS_BLOCK, -) Io.Cancelable!void { - var index: usize = 0; - while (index < data.len and data[index].len == 0) index += 1; - if (index == data.len) { - iosb.* = .{ .u = .{ .Status = .SUCCESS }, .Information = 0 }; - if (apcRoutine) |routine| if (routine != &noopApc) { - _ = windows.ntdll.NtQueueApcThread(windows.current_process, routine, apc_context, iosb, null); - }; - return; - } - const buffer = data[index]; - - const syscall: Syscall = try .start(); - while (true) switch (windows.ntdll.NtReadFile( - handle, - null, // event - apcRoutine, - apc_context, - iosb, - buffer.ptr, - @min(std.math.maxInt(u32), buffer.len), - null, // byte offset - null, // key - )) { - .PENDING => { - syscall.finish(); - return; - }, - .CANCELLED => { - try syscall.checkCancel(); - continue; - }, - else => |status| { - syscall.finish(); - iosb.u.Status = status; - return; - }, - }; -} - fn fileReadPositionalPosix(file: File, data: []const []u8, offset: u64) File.ReadPositionalError!usize { if (!have_preadv) @compileError("TODO implement fileReadPositionalPosix for cursed operating systems that don't support preadv (it's only Haiku)"); diff --git a/lib/std/os/windows/ntdll.zig b/lib/std/os/windows/ntdll.zig index 195a457d3b86167051c1a6f9d9eb4415673eb021..d9e68e54f9f275fbf254098e8b7716aad2cead5e 100644 --- a/lib/std/os/windows/ntdll.zig +++ b/lib/std/os/windows/ntdll.zig @@ -614,5 +614,5 @@ pub extern "ntdll" fn NtCancelIoFileEx( pub extern "ntdll" fn NtCancelIoFile( FileHandle: HANDLE, - IoRequestToCancel: ?*IO_STATUS_BLOCK, + IoStatusBlock: *IO_STATUS_BLOCK, ) callconv(.winapi) NTSTATUS; -- 2.54.0 From b6f4bb91c41bb78276ad54725ae5c61a160defd6 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 30 Jan 2026 11:45:08 -0800 Subject: [PATCH 152/499] std.Io: add documentation to Batch --- lib/std/Io.zig | 34 ++++++++++++++++++++++++++++++---- lib/std/Io/Threaded.zig | 2 +- 2 files changed, 31 insertions(+), 5 deletions(-) diff --git a/lib/std/Io.zig b/lib/std/Io.zig index ca77a9836ad4df4993f3f64ccbb3c2af6228bcc2..c3ba3575e477ddce5c2cf5fb9ed7bbe5b6a02acb 100644 --- a/lib/std/Io.zig +++ b/lib/std/Io.zig @@ -150,7 +150,7 @@ pub const VTable = struct { futexWake: *const fn (?*anyopaque, ptr: *const u32, max_waiters: u32) void, operate: *const fn (?*anyopaque, Operation) Cancelable!Operation.Result, - batchAwaitAsync: *const fn (?*anyopaque, *Batch) Batch.AwaitAsyncError!void, + batchAwaitAsync: *const fn (?*anyopaque, *Batch) Cancelable!void, batchAwaitConcurrent: *const fn (?*anyopaque, *Batch, Timeout) Batch.AwaitConcurrentError!void, batchCancel: *const fn (?*anyopaque, *Batch) void, @@ -359,7 +359,7 @@ pub fn operate(io: Io, operation: Operation) Cancelable!Operation.Result { /// complete. /// /// This is a low-level abstraction based on `Operation`. For a higher -/// level API that operates on `Future`, see `Select`. +/// level API that operates on `Future`, see `Select` and `Group`. pub const Batch = struct { storage: []Operation.Storage, unused: Operation.List, @@ -422,6 +422,11 @@ pub const Batch = struct { b.submissions.tail = .fromIndex(index); } + /// After calling `awaitAsync`, `awaitConcurrent`, or `cancel`, this + /// function iterates over the completed operations. + /// + /// Each completion returned from this function dequeues from the `Batch`. + /// It is not required to dequeue all completions before awaiting again. pub fn next(b: *Batch) ?struct { index: u32, result: Operation.Result } { const index = b.completions.head; if (index == .none) return null; @@ -441,16 +446,37 @@ pub const Batch = struct { return .{ .index = index.toIndex(), .result = completion.result }; } - pub const AwaitAsyncError = Cancelable; - pub fn awaitAsync(b: *Batch, io: Io) AwaitAsyncError!void { + /// Waits for at least one of the submitted operations to complete. After + /// this function returns the completed operations can be iterated with + /// `next`. + /// + /// This function provides opportunity for the implementation to introduce + /// concurrency into the batched operations, but unlike `awaitConcurrent`, + /// does not require it, and therefore cannot fail with + /// `error.ConcurrencyUnavailable`. + pub fn awaitAsync(b: *Batch, io: Io) Cancelable!void { return io.vtable.batchAwaitAsync(io.userdata, b); } pub const AwaitConcurrentError = ConcurrentError || Cancelable || Timeout.Error; + + /// Waits for at least one of the submitted operations to complete. After + /// this function returns the completed operations can be iterated with + /// `next`. + /// + /// Unlike `awaitAsync`, this function requires the implementation to + /// perform the operations concurrently and therefore can fail with + /// `error.ConcurrencyUnavailable`. pub fn awaitConcurrent(b: *Batch, io: Io, timeout: Timeout) AwaitConcurrentError!void { return io.vtable.batchAwaitConcurrent(io.userdata, b, timeout); } + /// Requests all pending operations to be interrupted, then waits for all + /// pending operations to complete. After this returns, the `Batch` is in a + /// well-defined state, ready to be iterated with `next`. Successfully + /// canceled operations will be absent from the iteration. Some operations + /// may have successfully completed regardless of the cancel request and + /// will appear in the iteration. pub fn cancel(b: *Batch, io: Io) void { return io.vtable.batchCancel(io.userdata, b); } diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index e9e5ece521e9276c11a0523ae53eac44ce548ef4..f9002567a2cc0fbb9a3db8dfed8dcfe6211ffa37 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -2495,7 +2495,7 @@ fn operate(userdata: ?*anyopaque, operation: Io.Operation) Io.Cancelable!Io.Oper } } -fn batchAwaitAsync(userdata: ?*anyopaque, b: *Io.Batch) Io.Batch.AwaitAsyncError!void { +fn batchAwaitAsync(userdata: ?*anyopaque, b: *Io.Batch) Io.Cancelable!void { const t: *Threaded = @ptrCast(@alignCast(userdata)); if (is_windows) { batchAwaitWindows(b, false) catch |err| switch (err) { -- 2.54.0 From 43866f743978ef6cf4755760cc96d4c00665fde3 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 30 Jan 2026 21:57:51 -0800 Subject: [PATCH 153/499] build.zig: bump max_rss encountered error: memory usage peaked at 0.66GB (656060416 bytes), exceeding the declared upper bound of 0.64GB (639565414 bytes) --- build.zig | 18 +----------------- 1 file changed, 1 insertion(+), 17 deletions(-) diff --git a/build.zig b/build.zig index 3ed237fa5ea851ee8412851715038400ec29e1b7..84cbba38bdb6026dc3691463ac40d1fcad33419e 100644 --- a/build.zig +++ b/build.zig @@ -498,23 +498,7 @@ pub fn build(b: *std.Build) !void { .skip_llvm = skip_llvm, .skip_libc = true, .no_builtin = true, - .max_rss = switch (b.graph.host.result.os.tag) { - .freebsd => 800_000_000, - .linux => switch (b.graph.host.result.cpu.arch) { - .aarch64 => 639_565_414, - .loongarch64 => 598_884_352, - .powerpc64le => 597_897_625, - .riscv64 => 636_429_516, - .s390x => 574_166_630, - .x86_64 => 978_463_129, - else => 900_000_000, - }, - .macos => switch (b.graph.host.result.cpu.arch) { - .aarch64 => 701_413_785, - else => 800_000_000, - }, - else => 900_000_000, - }, + .max_rss = 900_000_000, })); test_modules_step.dependOn(tests.addModuleTests(b, .{ -- 2.54.0 From 14e1e5f6d872a7a8a8d98fa48cc6f41f002a7d1f Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 30 Jan 2026 22:00:02 -0800 Subject: [PATCH 154/499] std: IoUring test handles EINTR --- lib/std/os/linux/IoUring/test.zig | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/std/os/linux/IoUring/test.zig b/lib/std/os/linux/IoUring/test.zig index ac2d5ddea54772253084d2699be4180cad7a794a..644b9b7c77c2666a0493a84eab6cc6b8065b4e2d 100644 --- a/lib/std/os/linux/IoUring/test.zig +++ b/lib/std/os/linux/IoUring/test.zig @@ -2736,8 +2736,9 @@ fn send(sockfd: posix.socket_t, buf: []const u8, flags: u32) !usize { } fn connect(sock: posix.socket_t, sock_addr: *const posix.sockaddr, len: posix.socklen_t) !void { - switch (posix.errno(posix.system.connect(sock, sock_addr, len))) { + while (true) switch (posix.errno(posix.system.connect(sock, sock_addr, len))) { .SUCCESS => return, + .INTR => continue, else => return error.ConnectFailed, - } + }; } -- 2.54.0 From 9646801bed8f0f36b59deecff32ef02868ed72f2 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 30 Jan 2026 22:06:33 -0800 Subject: [PATCH 155/499] std: fix Preopens compilation error --- lib/std/process/Preopens.zig | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/lib/std/process/Preopens.zig b/lib/std/process/Preopens.zig index 8223c29f83fadce644c49c874e0039d9e090ed96..3baf696ee9a5d95dbf2bed352ea4867e8c3fde8c 100644 --- a/lib/std/process/Preopens.zig +++ b/lib/std/process/Preopens.zig @@ -29,7 +29,10 @@ pub fn get(p: *const Preopens, name: []const u8) ?Resource { switch (native_os) { .wasi => { const index = p.map.getIndex(name) orelse return null; - if (index <= 2) return .{ .file = .{ .handle = @intCast(index) } }; + if (index <= 2) return .{ .file = .{ + .handle = @intCast(index), + .flags = .{ .nonblocking = false }, + } }; return .{ .dir = .{ .handle = @intCast(index) } }; }, else => { -- 2.54.0 From 5ccc2ea85d5d4c23daae8a3afe6b7784071597ac Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 30 Jan 2026 22:00:02 -0800 Subject: [PATCH 156/499] std: IoUring test handles EINTR --- lib/std/os/linux/IoUring/test.zig | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/lib/std/os/linux/IoUring/test.zig b/lib/std/os/linux/IoUring/test.zig index ac2d5ddea54772253084d2699be4180cad7a794a..644b9b7c77c2666a0493a84eab6cc6b8065b4e2d 100644 --- a/lib/std/os/linux/IoUring/test.zig +++ b/lib/std/os/linux/IoUring/test.zig @@ -2736,8 +2736,9 @@ fn send(sockfd: posix.socket_t, buf: []const u8, flags: u32) !usize { } fn connect(sock: posix.socket_t, sock_addr: *const posix.sockaddr, len: posix.socklen_t) !void { - switch (posix.errno(posix.system.connect(sock, sock_addr, len))) { + while (true) switch (posix.errno(posix.system.connect(sock, sock_addr, len))) { .SUCCESS => return, + .INTR => continue, else => return error.ConnectFailed, - } + }; } -- 2.54.0 From e60ba21114bc4514acc535be7106ce6c16e9bf04 Mon Sep 17 00:00:00 2001 From: "kj4tmp@gmail.com" Date: Thu, 29 Jan 2026 21:07:12 -0800 Subject: [PATCH 157/499] libzigc: roundl --- lib/libc/mingw/math/roundl.c | 26 -------------------------- lib/libc/musl/src/math/s390x/roundl.c | 15 --------------- src/libs/mingw.zig | 1 - src/libs/musl.zig | 2 -- src/libs/wasi_libc.zig | 1 - test/libc.zig | 2 +- 6 files changed, 1 insertion(+), 46 deletions(-) delete mode 100644 lib/libc/mingw/math/roundl.c delete mode 100644 lib/libc/musl/src/math/s390x/roundl.c diff --git a/lib/libc/mingw/math/roundl.c b/lib/libc/mingw/math/roundl.c deleted file mode 100644 index 9879a82cc2a3ad98f89c263efec3236c5387d1c7..0000000000000000000000000000000000000000 --- a/lib/libc/mingw/math/roundl.c +++ /dev/null @@ -1,26 +0,0 @@ -/** - * This file has no copyright assigned and is placed in the Public Domain. - * This file is part of the mingw-w64 runtime package. - * No warranty is given; refer to the file DISCLAIMER.PD within this package. - */ -#include - -long double -roundl (long double x) -{ - long double res = 0.0L; - if (x >= 0.0L) - { - res = ceill (x); - if (res - x > 0.5L) - res -= 1.0L; - } - else - { - res = ceill (-x); - if (res + x > 0.5L) - res -= 1.0L; - res = -res; - } - return res; -} diff --git a/lib/libc/musl/src/math/s390x/roundl.c b/lib/libc/musl/src/math/s390x/roundl.c deleted file mode 100644 index ce644ddd7953eaa5975bfdb06d875375e4f7f226..0000000000000000000000000000000000000000 --- a/lib/libc/musl/src/math/s390x/roundl.c +++ /dev/null @@ -1,15 +0,0 @@ -#include - -#if defined(__HTM__) || __ARCH__ >= 9 - -long double roundl(long double x) -{ - __asm__ ("fixbra %0, 1, %1, 4" : "=f"(x) : "f"(x)); - return x; -} - -#else - -#include "../roundl.c" - -#endif diff --git a/src/libs/mingw.zig b/src/libs/mingw.zig index 8f20b8cd0baf737631f88e813b216feab99436a9..568dd945512c23023823b9eced149bc2004832f0 100644 --- a/src/libs/mingw.zig +++ b/src/libs/mingw.zig @@ -936,7 +936,6 @@ const mingw32_x86_src = [_][]const u8{ "math" ++ path.sep_str ++ "lrintl.c", "math" ++ path.sep_str ++ "lroundl.c", "math" ++ path.sep_str ++ "rintl.c", - "math" ++ path.sep_str ++ "roundl.c", "math" ++ path.sep_str ++ "tgammal.c", "math" ++ path.sep_str ++ "x86" ++ path.sep_str ++ "_chgsignl.S", "math" ++ path.sep_str ++ "x86" ++ path.sep_str ++ "acoshl.c", diff --git a/src/libs/musl.zig b/src/libs/musl.zig index 28602cd6f6ec14f15f586c01bf857234bf1ce8b7..c6bdac314c2c4f9dbf114e9c031d2a867b43ee15 100644 --- a/src/libs/musl.zig +++ b/src/libs/musl.zig @@ -1023,7 +1023,6 @@ const src_files = [_][]const u8{ "musl/src/math/riscv64/fmaf.c", "musl/src/math/round.c", "musl/src/math/roundf.c", - "musl/src/math/roundl.c", "musl/src/math/s390x/fma.c", "musl/src/math/s390x/fmaf.c", "musl/src/math/s390x/nearbyint.c", @@ -1034,7 +1033,6 @@ const src_files = [_][]const u8{ "musl/src/math/s390x/rintl.c", "musl/src/math/s390x/round.c", "musl/src/math/s390x/roundf.c", - "musl/src/math/s390x/roundl.c", "musl/src/math/scalb.c", "musl/src/math/scalbf.c", "musl/src/math/scalbln.c", diff --git a/src/libs/wasi_libc.zig b/src/libs/wasi_libc.zig index b1a1159778257d4b16297f1ce87f5431635dad23..19b820eef9bd305b4a0b45bf9a54b5e12d6acaa3 100644 --- a/src/libs/wasi_libc.zig +++ b/src/libs/wasi_libc.zig @@ -810,7 +810,6 @@ const libc_top_half_src_files = [_][]const u8{ "musl/src/math/rintl.c", "musl/src/math/round.c", "musl/src/math/roundf.c", - "musl/src/math/roundl.c", "musl/src/math/scalb.c", "musl/src/math/scalbf.c", "musl/src/math/scalbln.c", diff --git a/test/libc.zig b/test/libc.zig index e1d866eb3d22293999b1307e55904147242167b4..ee7343493051d79533d75df6e314843e1bce2ab3 100644 --- a/test/libc.zig +++ b/test/libc.zig @@ -298,7 +298,7 @@ pub fn addCases(cases: *tests.LibcContext) void { // cases.addLibcTestCase("math/rintl.c", true, .{}); // cases.addLibcTestCase("math/round.c", true, .{}); // cases.addLibcTestCase("math/roundf.c", true, .{}); - // cases.addLibcTestCase("math/roundl.c", true, .{}); + cases.addLibcTestCase("math/roundl.c", true, .{}); cases.addLibcTestCase("math/scalb.c", true, .{}); cases.addLibcTestCase("math/scalbf.c", true, .{}); cases.addLibcTestCase("math/scalbln.c", true, .{}); -- 2.54.0 From 69a95571ed6e0f2c4562b70dc222f427b3366858 Mon Sep 17 00:00:00 2001 From: Jeff Anderson Date: Sat, 31 Jan 2026 17:15:00 -0800 Subject: [PATCH 158/499] libzigc: round --- lib/libc/musl/src/math/aarch64/round.c | 7 ----- lib/libc/musl/src/math/powerpc64/round.c | 15 ---------- lib/libc/musl/src/math/round.c | 35 ------------------------ lib/libc/musl/src/math/s390x/round.c | 15 ---------- src/libs/musl.zig | 4 --- src/libs/wasi_libc.zig | 1 - test/libc.zig | 2 +- 7 files changed, 1 insertion(+), 78 deletions(-) delete mode 100644 lib/libc/musl/src/math/aarch64/round.c delete mode 100644 lib/libc/musl/src/math/powerpc64/round.c delete mode 100644 lib/libc/musl/src/math/round.c delete mode 100644 lib/libc/musl/src/math/s390x/round.c diff --git a/lib/libc/musl/src/math/aarch64/round.c b/lib/libc/musl/src/math/aarch64/round.c deleted file mode 100644 index 897a84cc2a043b2e8fdaef03fc2bbd665bde5366..0000000000000000000000000000000000000000 --- a/lib/libc/musl/src/math/aarch64/round.c +++ /dev/null @@ -1,7 +0,0 @@ -#include - -double round(double x) -{ - __asm__ ("frinta %d0, %d1" : "=w"(x) : "w"(x)); - return x; -} diff --git a/lib/libc/musl/src/math/powerpc64/round.c b/lib/libc/musl/src/math/powerpc64/round.c deleted file mode 100644 index 4b9318e09fa70fe0c8193c10bfbd1f761fd15654..0000000000000000000000000000000000000000 --- a/lib/libc/musl/src/math/powerpc64/round.c +++ /dev/null @@ -1,15 +0,0 @@ -#include - -#ifdef _ARCH_PWR5X - -double round(double x) -{ - __asm__ ("frin %0, %1" : "=d"(x) : "d"(x)); - return x; -} - -#else - -#include "../round.c" - -#endif diff --git a/lib/libc/musl/src/math/round.c b/lib/libc/musl/src/math/round.c deleted file mode 100644 index 130d58d2571e77cc15a67b355010e11c5ebebe91..0000000000000000000000000000000000000000 --- a/lib/libc/musl/src/math/round.c +++ /dev/null @@ -1,35 +0,0 @@ -#include "libm.h" - -#if FLT_EVAL_METHOD==0 || FLT_EVAL_METHOD==1 -#define EPS DBL_EPSILON -#elif FLT_EVAL_METHOD==2 -#define EPS LDBL_EPSILON -#endif -static const double_t toint = 1/EPS; - -double round(double x) -{ - union {double f; uint64_t i;} u = {x}; - int e = u.i >> 52 & 0x7ff; - double_t y; - - if (e >= 0x3ff+52) - return x; - if (u.i >> 63) - x = -x; - if (e < 0x3ff-1) { - /* raise inexact if x!=0 */ - FORCE_EVAL(x + toint); - return 0*u.f; - } - y = x + toint - toint - x; - if (y > 0.5) - y = y + x - 1; - else if (y <= -0.5) - y = y + x + 1; - else - y = y + x; - if (u.i >> 63) - y = -y; - return y; -} diff --git a/lib/libc/musl/src/math/s390x/round.c b/lib/libc/musl/src/math/s390x/round.c deleted file mode 100644 index 71f80251184bf1430f3a134dfd081568346af31e..0000000000000000000000000000000000000000 --- a/lib/libc/musl/src/math/s390x/round.c +++ /dev/null @@ -1,15 +0,0 @@ -#include - -#if defined(__HTM__) || __ARCH__ >= 9 - -double round(double x) -{ - __asm__ ("fidbra %0, 1, %1, 4" : "=f"(x) : "f"(x)); - return x; -} - -#else - -#include "../round.c" - -#endif diff --git a/src/libs/musl.zig b/src/libs/musl.zig index c6bdac314c2c4f9dbf114e9c031d2a867b43ee15..97f1d4e2f0de422ea79ff53cb9627b38ee364576 100644 --- a/src/libs/musl.zig +++ b/src/libs/musl.zig @@ -818,7 +818,6 @@ const src_files = [_][]const u8{ "musl/src/math/aarch64/nearbyintf.c", "musl/src/math/aarch64/rint.c", "musl/src/math/aarch64/rintf.c", - "musl/src/math/aarch64/round.c", "musl/src/math/aarch64/roundf.c", "musl/src/math/acosf.c", "musl/src/math/acosh.c", @@ -997,7 +996,6 @@ const src_files = [_][]const u8{ "musl/src/math/powerpc64/lrintf.c", "musl/src/math/powerpc64/lround.c", "musl/src/math/powerpc64/lroundf.c", - "musl/src/math/powerpc64/round.c", "musl/src/math/powerpc64/roundf.c", "musl/src/math/powerpc/fma.c", "musl/src/math/powerpc/fmaf.c", @@ -1021,7 +1019,6 @@ const src_files = [_][]const u8{ "musl/src/math/riscv32/fmaf.c", "musl/src/math/riscv64/fma.c", "musl/src/math/riscv64/fmaf.c", - "musl/src/math/round.c", "musl/src/math/roundf.c", "musl/src/math/s390x/fma.c", "musl/src/math/s390x/fmaf.c", @@ -1031,7 +1028,6 @@ const src_files = [_][]const u8{ "musl/src/math/s390x/rint.c", "musl/src/math/s390x/rintf.c", "musl/src/math/s390x/rintl.c", - "musl/src/math/s390x/round.c", "musl/src/math/s390x/roundf.c", "musl/src/math/scalb.c", "musl/src/math/scalbf.c", diff --git a/src/libs/wasi_libc.zig b/src/libs/wasi_libc.zig index 19b820eef9bd305b4a0b45bf9a54b5e12d6acaa3..743812e93c05efe1c4e33b18e13ad2b5ad2325e7 100644 --- a/src/libs/wasi_libc.zig +++ b/src/libs/wasi_libc.zig @@ -808,7 +808,6 @@ const libc_top_half_src_files = [_][]const u8{ "musl/src/math/remquof.c", "musl/src/math/remquol.c", "musl/src/math/rintl.c", - "musl/src/math/round.c", "musl/src/math/roundf.c", "musl/src/math/scalb.c", "musl/src/math/scalbf.c", diff --git a/test/libc.zig b/test/libc.zig index ee7343493051d79533d75df6e314843e1bce2ab3..cf61d1ba4d06dcccc34b752c432e2ae310bdd960 100644 --- a/test/libc.zig +++ b/test/libc.zig @@ -296,7 +296,7 @@ pub fn addCases(cases: *tests.LibcContext) void { // cases.addLibcTestCase("math/rint.c", true, .{}); cases.addLibcTestCase("math/rintf.c", true, .{}); // cases.addLibcTestCase("math/rintl.c", true, .{}); - // cases.addLibcTestCase("math/round.c", true, .{}); + cases.addLibcTestCase("math/round.c", true, .{}); // cases.addLibcTestCase("math/roundf.c", true, .{}); cases.addLibcTestCase("math/roundl.c", true, .{}); cases.addLibcTestCase("math/scalb.c", true, .{}); -- 2.54.0 From 3abc96a601d2349cc1743774f0cebb2eb0ea0c61 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sat, 31 Jan 2026 14:58:02 -0800 Subject: [PATCH 159/499] std.Io: add test for batchAwaitAsync and make it always work for all targets including WASI This function guarantees no additional failure modes introduced. --- lib/std/Io.zig | 14 ++- lib/std/Io/Threaded.zig | 188 +++++++++++++++++++++------------------- lib/std/Io/test.zig | 60 +++++++++++++ 3 files changed, 170 insertions(+), 92 deletions(-) diff --git a/lib/std/Io.zig b/lib/std/Io.zig index c3ba3575e477ddce5c2cf5fb9ed7bbe5b6a02acb..16d056ed0f18ec12cb0843d6d0c5d34e7ad70400 100644 --- a/lib/std/Io.zig +++ b/lib/std/Io.zig @@ -369,7 +369,9 @@ pub const Batch = struct { context: ?*anyopaque, /// After calling this, it is safe to unconditionally defer a call to - /// `cancel`. + /// `cancel`. `storage` is a pre-allocated buffer of undefined memory that + /// determines the maximum number of active operations that can be + /// submitted via `add` and `addAt`. pub fn init(storage: []Operation.Storage) Batch { var prev: Operation.OptionalIndex = .none; for (storage, 0..) |*operation, index| { @@ -422,12 +424,20 @@ pub const Batch = struct { b.submissions.tail = .fromIndex(index); } + pub const Completion = struct { + /// The element within the provided operation storage that completed. + /// `addAt` can be used to re-arm the `Batch` using this `index`. + index: u32, + /// The return value of the operation. + result: Operation.Result, + }; + /// After calling `awaitAsync`, `awaitConcurrent`, or `cancel`, this /// function iterates over the completed operations. /// /// Each completion returned from this function dequeues from the `Batch`. /// It is not required to dequeue all completions before awaiting again. - pub fn next(b: *Batch) ?struct { index: u32, result: Operation.Result } { + pub fn next(b: *Batch) ?Completion { const index = b.completions.head; if (index == .none) return null; const storage = &b.storage[index.toIndex()]; diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index e9b62ca5f5c325a70f06f27cb3d6d79f35125bff..12521ea1af03a29dfb73672de61e52b69723ec26 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -1938,6 +1938,10 @@ const have_mmap = switch (native_os) { .wasi, .windows => false, else => true, }; +const have_poll = switch (native_os) { + .wasi, .windows => false, + else => true, +}; const open_sym = if (posix.lfs64_abi) posix.system.open64 else posix.system.open; const openat_sym = if (posix.lfs64_abi) posix.system.openat64 else posix.system.openat; @@ -2507,104 +2511,104 @@ fn batchAwaitAsync(userdata: ?*anyopaque, b: *Io.Batch) Io.Cancelable!void { alertable_syscall.finish(); return; } - if (native_os == .wasi and !builtin.link_libc) @panic("TODO"); - var poll_buffer: [poll_buffer_len]posix.pollfd = undefined; - var poll_len: u32 = 0; - { - var index = b.submissions.head; - while (index != .none and poll_len < poll_buffer_len) { - const submission = &b.storage[index.toIndex()].submission; - switch (submission.operation) { - .file_read_streaming => |o| { - poll_buffer[poll_len] = .{ .fd = o.file.handle, .events = posix.POLL.IN, .revents = 0 }; - poll_len += 1; - }, + if (have_poll) { + var poll_buffer: [poll_buffer_len]posix.pollfd = undefined; + var poll_len: u32 = 0; + { + var index = b.submissions.head; + while (index != .none and poll_len < poll_buffer_len) { + const submission = &b.storage[index.toIndex()].submission; + switch (submission.operation) { + .file_read_streaming => |o| { + poll_buffer[poll_len] = .{ .fd = o.file.handle, .events = posix.POLL.IN, .revents = 0 }; + poll_len += 1; + }, + } + index = submission.node.next; } - index = submission.node.next; } - } - switch (poll_len) { - 0 => return, - 1 => {}, - else => while (true) { - const timeout_ms: i32 = t: { - if (b.completions.head != .none) { - // It is legal to call batchWait with already completed - // operations in the ring. In such case, we need to avoid - // blocking in the poll syscall, but we can still take this - // opportunity to find additional ready operations. - break :t 0; - } - const max_poll_ms = std.math.maxInt(i32); - break :t max_poll_ms; - }; - const syscall = try Syscall.start(); - const rc = posix.system.poll(&poll_buffer, poll_len, timeout_ms); - syscall.finish(); - switch (posix.errno(rc)) { - .SUCCESS => { - if (rc == 0) { - if (b.completions.head != .none) { - // Since there are already completions available in the - // queue, this is neither a timeout nor a case for - // retrying. - return; - } - continue; + switch (poll_len) { + 0 => return, + 1 => {}, + else => while (true) { + const timeout_ms: i32 = t: { + if (b.completions.head != .none) { + // It is legal to call batchWait with already completed + // operations in the ring. In such case, we need to avoid + // blocking in the poll syscall, but we can still take this + // opportunity to find additional ready operations. + break :t 0; } - var prev_index: Io.Operation.OptionalIndex = .none; - var index = b.submissions.head; - for (poll_buffer[0..poll_len]) |poll_entry| { - const storage = &b.storage[index.toIndex()]; - const submission = &storage.submission; - const next_index = submission.node.next; - if (poll_entry.revents != 0) { - const result = try operate(t, submission.operation); - - switch (prev_index) { - .none => b.submissions.head = next_index, - else => b.storage[prev_index.toIndex()].submission.node.next = next_index, + const max_poll_ms = std.math.maxInt(i32); + break :t max_poll_ms; + }; + const syscall = try Syscall.start(); + const rc = posix.system.poll(&poll_buffer, poll_len, timeout_ms); + syscall.finish(); + switch (posix.errno(rc)) { + .SUCCESS => { + if (rc == 0) { + if (b.completions.head != .none) { + // Since there are already completions available in the + // queue, this is neither a timeout nor a case for + // retrying. + return; } - if (next_index == .none) b.submissions.tail = prev_index; + continue; + } + var prev_index: Io.Operation.OptionalIndex = .none; + var index = b.submissions.head; + for (poll_buffer[0..poll_len]) |poll_entry| { + const storage = &b.storage[index.toIndex()]; + const submission = &storage.submission; + const next_index = submission.node.next; + if (poll_entry.revents != 0) { + const result = try operate(t, submission.operation); - switch (b.completions.tail) { - .none => b.completions.head = index, - else => |tail_index| b.storage[tail_index.toIndex()].completion.node.next = index, - } - storage.* = .{ .completion = .{ .node = .{ .next = .none }, .result = result } }; - b.completions.tail = index; - } else prev_index = index; - index = next_index; - } - assert(index == .none); - return; - }, - .INTR => continue, - else => break, - } - }, + switch (prev_index) { + .none => b.submissions.head = next_index, + else => b.storage[prev_index.toIndex()].submission.node.next = next_index, + } + if (next_index == .none) b.submissions.tail = prev_index; + + switch (b.completions.tail) { + .none => b.completions.head = index, + else => |tail_index| b.storage[tail_index.toIndex()].completion.node.next = index, + } + storage.* = .{ .completion = .{ .node = .{ .next = .none }, .result = result } }; + b.completions.tail = index; + } else prev_index = index; + index = next_index; + } + assert(index == .none); + return; + }, + .INTR => continue, + else => break, + } + }, + } } - { - var tail_index = b.completions.tail; - defer b.completions.tail = tail_index; - var index = b.submissions.head; - errdefer b.submissions.head = index; - while (index != .none) { - const storage = &b.storage[index.toIndex()]; - const submission = &storage.submission; - const next_index = submission.node.next; - const result = try operate(t, submission.operation); - switch (tail_index) { - .none => b.completions.head = index, - else => b.storage[tail_index.toIndex()].completion.node.next = index, - } - storage.* = .{ .completion = .{ .node = .{ .next = .none }, .result = result } }; - tail_index = index; - index = next_index; + var tail_index = b.completions.tail; + defer b.completions.tail = tail_index; + var index = b.submissions.head; + errdefer b.submissions.head = index; + while (index != .none) { + const storage = &b.storage[index.toIndex()]; + const submission = &storage.submission; + const next_index = submission.node.next; + const result = try operate(t, submission.operation); + + switch (tail_index) { + .none => b.completions.head = index, + else => b.storage[tail_index.toIndex()].completion.node.next = index, } - b.submissions = .{ .head = .none, .tail = .none }; + storage.* = .{ .completion = .{ .node = .{ .next = .none }, .result = result } }; + tail_index = index; + index = next_index; } + b.submissions = .{ .head = .none, .tail = .none }; } fn batchAwaitConcurrent(userdata: ?*anyopaque, b: *Io.Batch, timeout: Io.Timeout) Io.Batch.AwaitConcurrentError!void { @@ -2644,7 +2648,11 @@ fn batchAwaitConcurrent(userdata: ?*anyopaque, b: *Io.Batch, timeout: Io.Timeout } return; } - if (native_os == .wasi and !builtin.link_libc) @panic("TODO"); + if (native_os == .wasi) { + // TODO call poll_oneoff + return error.ConcurrencyUnavailable; + } + if (!have_poll) return error.ConcurrencyUnavailable; var poll_buffer: [poll_buffer_len]posix.pollfd = undefined; var poll_storage: struct { gpa: std.mem.Allocator, diff --git a/lib/std/Io/test.zig b/lib/std/Io/test.zig index b022b10e6e64c91d358aaba4dfff6bf70c986837..930a176b015c9ee966bab3e643b37f14d5648f4f 100644 --- a/lib/std/Io/test.zig +++ b/lib/std/Io/test.zig @@ -656,3 +656,63 @@ test "memory mapping" { try expectEqualStrings("this9is9my data123\x00\x00", mm.memory[0.."this9is9my data123\x00\x00".len]); } } + +test "read from a file using Batch.awaitAsync API" { + const io = testing.io; + + var tmp = tmpDir(.{}); + defer tmp.cleanup(); + + try tmp.dir.writeFile(io, .{ + .sub_path = "eyes.txt", + .data = "Heaven's been cheating the Hell out of me", + }); + try tmp.dir.writeFile(io, .{ + .sub_path = "saviour.txt", + .data = "Burn your thoughts, erase your will / to gods of suffering and tears", + }); + + var eyes_file = try tmp.dir.openFile(io, "eyes.txt", .{}); + defer eyes_file.close(io); + + var saviour_file = try tmp.dir.openFile(io, "saviour.txt", .{}); + defer saviour_file.close(io); + + var eyes_buf: [100]u8 = undefined; + var saviour_buf: [100]u8 = undefined; + var storage: [2]Io.Operation.Storage = undefined; + var batch: Io.Batch = .init(&storage); + + batch.addAt(0, .{ .file_read_streaming = .{ + .file = eyes_file, + .data = &.{&eyes_buf}, + } }); + batch.addAt(1, .{ .file_read_streaming = .{ + .file = saviour_file, + .data = &.{&saviour_buf}, + } }); + + // This API is supposed to *always* work even if the target has no + // concurrency primitives available. + try batch.awaitAsync(io); + + while (batch.next()) |completion| { + switch (completion.index) { + 0 => { + const n = try completion.result.file_read_streaming; + try expectEqualStrings( + "Heaven's been cheating the Hell out of me"[0..n], + eyes_buf[0..n], + ); + }, + 1 => { + const n = try completion.result.file_read_streaming; + try expectEqualStrings( + "Burn your thoughts, erase your will / to gods of suffering and tears"[0..n], + saviour_buf[0..n], + ); + }, + else => return error.TestFailure, + } + } +} -- 2.54.0 From 379d128cba904a7599ebb191afa0873ef1a633cc Mon Sep 17 00:00:00 2001 From: Jeff Anderson Date: Sat, 31 Jan 2026 17:18:42 -0800 Subject: [PATCH 160/499] libzigc: roundf --- lib/libc/musl/src/math/aarch64/roundf.c | 7 ----- lib/libc/musl/src/math/powerpc64/roundf.c | 15 ---------- lib/libc/musl/src/math/roundf.c | 36 ----------------------- lib/libc/musl/src/math/s390x/roundf.c | 15 ---------- src/libs/musl.zig | 4 --- src/libs/wasi_libc.zig | 1 - test/libc.zig | 2 +- 7 files changed, 1 insertion(+), 79 deletions(-) delete mode 100644 lib/libc/musl/src/math/aarch64/roundf.c delete mode 100644 lib/libc/musl/src/math/powerpc64/roundf.c delete mode 100644 lib/libc/musl/src/math/roundf.c delete mode 100644 lib/libc/musl/src/math/s390x/roundf.c diff --git a/lib/libc/musl/src/math/aarch64/roundf.c b/lib/libc/musl/src/math/aarch64/roundf.c deleted file mode 100644 index 91637eaa1204ad92c451722b87c972231ecf55af..0000000000000000000000000000000000000000 --- a/lib/libc/musl/src/math/aarch64/roundf.c +++ /dev/null @@ -1,7 +0,0 @@ -#include - -float roundf(float x) -{ - __asm__ ("frinta %s0, %s1" : "=w"(x) : "w"(x)); - return x; -} diff --git a/lib/libc/musl/src/math/powerpc64/roundf.c b/lib/libc/musl/src/math/powerpc64/roundf.c deleted file mode 100644 index ae93f999abb868de89898337d849ffb928436e47..0000000000000000000000000000000000000000 --- a/lib/libc/musl/src/math/powerpc64/roundf.c +++ /dev/null @@ -1,15 +0,0 @@ -#include - -#ifdef _ARCH_PWR5X - -float roundf(float x) -{ - __asm__ ("frin %0, %1" : "=f"(x) : "f"(x)); - return x; -} - -#else - -#include "../roundf.c" - -#endif diff --git a/lib/libc/musl/src/math/roundf.c b/lib/libc/musl/src/math/roundf.c deleted file mode 100644 index e8210af5621aa392543616d308200ddb22523281..0000000000000000000000000000000000000000 --- a/lib/libc/musl/src/math/roundf.c +++ /dev/null @@ -1,36 +0,0 @@ -#include "libm.h" - -#if FLT_EVAL_METHOD==0 -#define EPS FLT_EPSILON -#elif FLT_EVAL_METHOD==1 -#define EPS DBL_EPSILON -#elif FLT_EVAL_METHOD==2 -#define EPS LDBL_EPSILON -#endif -static const float_t toint = 1/EPS; - -float roundf(float x) -{ - union {float f; uint32_t i;} u = {x}; - int e = u.i >> 23 & 0xff; - float_t y; - - if (e >= 0x7f+23) - return x; - if (u.i >> 31) - x = -x; - if (e < 0x7f-1) { - FORCE_EVAL(x + toint); - return 0*u.f; - } - y = x + toint - toint - x; - if (y > 0.5f) - y = y + x - 1; - else if (y <= -0.5f) - y = y + x + 1; - else - y = y + x; - if (u.i >> 31) - y = -y; - return y; -} diff --git a/lib/libc/musl/src/math/s390x/roundf.c b/lib/libc/musl/src/math/s390x/roundf.c deleted file mode 100644 index 46d2e10c8766787e91838fec4d0d7c5176eb0131..0000000000000000000000000000000000000000 --- a/lib/libc/musl/src/math/s390x/roundf.c +++ /dev/null @@ -1,15 +0,0 @@ -#include - -#if defined(__HTM__) || __ARCH__ >= 9 - -float roundf(float x) -{ - __asm__ ("fiebra %0, 1, %1, 4" : "=f"(x) : "f"(x)); - return x; -} - -#else - -#include "../roundf.c" - -#endif diff --git a/src/libs/musl.zig b/src/libs/musl.zig index 97f1d4e2f0de422ea79ff53cb9627b38ee364576..b8f7068e5c77b9678e4a14f748b66c255a2aaffd 100644 --- a/src/libs/musl.zig +++ b/src/libs/musl.zig @@ -818,7 +818,6 @@ const src_files = [_][]const u8{ "musl/src/math/aarch64/nearbyintf.c", "musl/src/math/aarch64/rint.c", "musl/src/math/aarch64/rintf.c", - "musl/src/math/aarch64/roundf.c", "musl/src/math/acosf.c", "musl/src/math/acosh.c", "musl/src/math/acoshf.c", @@ -996,7 +995,6 @@ const src_files = [_][]const u8{ "musl/src/math/powerpc64/lrintf.c", "musl/src/math/powerpc64/lround.c", "musl/src/math/powerpc64/lroundf.c", - "musl/src/math/powerpc64/roundf.c", "musl/src/math/powerpc/fma.c", "musl/src/math/powerpc/fmaf.c", "musl/src/math/powf.c", @@ -1019,7 +1017,6 @@ const src_files = [_][]const u8{ "musl/src/math/riscv32/fmaf.c", "musl/src/math/riscv64/fma.c", "musl/src/math/riscv64/fmaf.c", - "musl/src/math/roundf.c", "musl/src/math/s390x/fma.c", "musl/src/math/s390x/fmaf.c", "musl/src/math/s390x/nearbyint.c", @@ -1028,7 +1025,6 @@ const src_files = [_][]const u8{ "musl/src/math/s390x/rint.c", "musl/src/math/s390x/rintf.c", "musl/src/math/s390x/rintl.c", - "musl/src/math/s390x/roundf.c", "musl/src/math/scalb.c", "musl/src/math/scalbf.c", "musl/src/math/scalbln.c", diff --git a/src/libs/wasi_libc.zig b/src/libs/wasi_libc.zig index 743812e93c05efe1c4e33b18e13ad2b5ad2325e7..239f8b477704b3ac2f6d891214aae6a7056916a1 100644 --- a/src/libs/wasi_libc.zig +++ b/src/libs/wasi_libc.zig @@ -808,7 +808,6 @@ const libc_top_half_src_files = [_][]const u8{ "musl/src/math/remquof.c", "musl/src/math/remquol.c", "musl/src/math/rintl.c", - "musl/src/math/roundf.c", "musl/src/math/scalb.c", "musl/src/math/scalbf.c", "musl/src/math/scalbln.c", diff --git a/test/libc.zig b/test/libc.zig index cf61d1ba4d06dcccc34b752c432e2ae310bdd960..57581ca32e9e51710a300588a12d61a3a44caba2 100644 --- a/test/libc.zig +++ b/test/libc.zig @@ -297,7 +297,7 @@ pub fn addCases(cases: *tests.LibcContext) void { cases.addLibcTestCase("math/rintf.c", true, .{}); // cases.addLibcTestCase("math/rintl.c", true, .{}); cases.addLibcTestCase("math/round.c", true, .{}); - // cases.addLibcTestCase("math/roundf.c", true, .{}); + cases.addLibcTestCase("math/roundf.c", true, .{}); cases.addLibcTestCase("math/roundl.c", true, .{}); cases.addLibcTestCase("math/scalb.c", true, .{}); cases.addLibcTestCase("math/scalbf.c", true, .{}); -- 2.54.0 From 9bd648bd4099d4ba14785bea4d7e0aa9e81fb105 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 30 Jan 2026 20:54:06 -0800 Subject: [PATCH 161/499] std.Io.Operation: support non-fallible results --- lib/std/Io.zig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/std/Io.zig b/lib/std/Io.zig index 16d056ed0f18ec12cb0843d6d0c5d34e7ad70400..74ddd1dccf72863945c4d0025d1b19e9522a49f1 100644 --- a/lib/std/Io.zig +++ b/lib/std/Io.zig @@ -287,7 +287,7 @@ pub const Operation = union(enum) { LockViolation, } || Io.UnexpectedError; - pub const Result = usize; + pub const Result = Error!usize; }; pub const Result = Result: { @@ -296,7 +296,7 @@ pub const Operation = union(enum) { var field_types: [operation_fields.len]type = undefined; for (operation_fields, &field_names, &field_types) |field, *field_name, *field_type| { field_name.* = field.name; - field_type.* = field.type.Error!field.type.Result; + field_type.* = field.type.Result; } break :Result @Union(.auto, Tag, &field_names, &field_types, &@splat(.{})); }; -- 2.54.0 From cc442d24ab172a6a2e5ee5210eca3e2f822629f8 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 30 Jan 2026 22:00:45 -0800 Subject: [PATCH 162/499] std.Io: move fileWriteStreaming to Operation This serves as an example to contributors of how to move VTable functions to becoming an Operation, thereby enabling Batch API and timeouts. --- lib/std/Io.zig | 36 ++++++- lib/std/Io/File.zig | 21 +++- lib/std/Io/File/Writer.zig | 27 +---- lib/std/Io/Threaded.zig | 200 ++++++++++++++++++++++++++++++------- lib/std/Progress.zig | 4 +- 5 files changed, 217 insertions(+), 71 deletions(-) diff --git a/lib/std/Io.zig b/lib/std/Io.zig index 74ddd1dccf72863945c4d0025d1b19e9522a49f1..a892c0123c7179d1f203ba231e087ec7bd92a105 100644 --- a/lib/std/Io.zig +++ b/lib/std/Io.zig @@ -184,7 +184,6 @@ pub const VTable = struct { fileStat: *const fn (?*anyopaque, File) File.StatError!File.Stat, fileLength: *const fn (?*anyopaque, File) File.LengthError!u64, fileClose: *const fn (?*anyopaque, []const File) void, - fileWriteStreaming: *const fn (?*anyopaque, File, header: []const u8, data: []const []const u8, splat: usize) File.Writer.Error!usize, fileWritePositional: *const fn (?*anyopaque, File, header: []const u8, data: []const []const u8, splat: usize, offset: u64) File.WritePositionalError!usize, fileWriteFileStreaming: *const fn (?*anyopaque, File, header: []const u8, *Io.File.Reader, Io.Limit) File.Writer.WriteFileError!usize, fileWriteFilePositional: *const fn (?*anyopaque, File, header: []const u8, *Io.File.Reader, Io.Limit, offset: u64) File.WriteFilePositionalError!usize, @@ -257,6 +256,7 @@ pub const VTable = struct { pub const Operation = union(enum) { file_read_streaming: FileReadStreaming, + file_write_streaming: FileWriteStreaming, pub const Tag = @typeInfo(Operation).@"union".tag_type.?; @@ -290,6 +290,40 @@ pub const Operation = union(enum) { pub const Result = Error!usize; }; + pub const FileWriteStreaming = struct { + file: File, + header: []const u8 = &.{}, + data: []const []const u8, + splat: usize = 1, + + pub const Error = error{ + DiskQuota, + FileTooBig, + InputOutput, + NoSpaceLeft, + DeviceBusy, + /// File descriptor does not hold the required rights to write to it. + AccessDenied, + PermissionDenied, + /// File is an unconnected socket, or closed its read end. + BrokenPipe, + /// Insufficient kernel memory to read from in_fd. + SystemResources, + NotOpenForWriting, + /// The process cannot access the file because another process has locked + /// a portion of the file. Windows-only. + LockViolation, + /// Non-blocking has been enabled and this operation would block. + WouldBlock, + /// This error occurs when a device gets disconnected before or mid-flush + /// while it's being written to - errno(6): No such device or address. + NoDevice, + FileBusy, + } || Io.UnexpectedError; + + pub const Result = Error!usize; + }; + pub const Result = Result: { const operation_fields = @typeInfo(Operation).@"union".fields; var field_names: [operation_fields.len][]const u8 = undefined; diff --git a/lib/std/Io/File.zig b/lib/std/Io/File.zig index e0297e0573c06dc35cbc169bd822832b4cf80648..ba7f5d01e051c25436bbbe11f06951aae89b314c 100644 --- a/lib/std/Io/File.zig +++ b/lib/std/Io/File.zig @@ -572,16 +572,16 @@ pub fn setTimestampsNow(file: File, io: Io) SetTimestampsError!void { pub const ReadStreamingError = error{EndOfStream} || Reader.Error; -/// Returns 0 on stream end or if `buffer` has no space available for data. +/// May return fewer bytes than buffer space available, including 0. +/// End-of-stream is indicated by `error.EndOfStream`. /// /// See also: /// * `reader` pub fn readStreaming(file: File, io: Io, buffer: []const []u8) ReadStreamingError!usize { - const result = try io.operate(.{ .file_read_streaming = .{ + return (try io.operate(.{ .file_read_streaming = .{ .file = file, .data = buffer, - } }); - return result.file_read_streaming; + } })).file_read_streaming; } pub const ReadPositionalError = error{ @@ -714,11 +714,22 @@ pub fn writerStreaming(file: File, io: Io, buffer: []u8) Writer { return .initStreaming(file, io, buffer); } +/// This is a low-level API that calls the `Io` interface function directly. +/// For a higher level API, see `writerStreaming`. +pub fn writeStreaming(file: File, io: Io, header: []const u8, data: []const []const u8, splat: usize) Writer.Error!usize { + return (try io.operate(.{ .file_write_streaming = .{ + .file = file, + .header = header, + .data = data, + .splat = splat, + } })).file_write_streaming; +} + /// Equivalent to creating a streaming writer, writing `bytes`, and then flushing. pub fn writeStreamingAll(file: File, io: Io, bytes: []const u8) Writer.Error!void { var index: usize = 0; while (index < bytes.len) { - index += try io.vtable.fileWriteStreaming(io.userdata, file, &.{}, &.{bytes[index..]}, 1); + index += try writeStreaming(file, io, &.{}, &.{bytes[index..]}, 1); } } diff --git a/lib/std/Io/File/Writer.zig b/lib/std/Io/File/Writer.zig index 68a68e28ec706bf9b1863fd3b329c78f4255ad9a..83f99d2ffcd062336b300817572e01d657d4c15e 100644 --- a/lib/std/Io/File/Writer.zig +++ b/lib/std/Io/File/Writer.zig @@ -20,30 +20,7 @@ interface: Io.Writer, pub const Mode = File.Reader.Mode; -pub const Error = error{ - DiskQuota, - FileTooBig, - InputOutput, - NoSpaceLeft, - DeviceBusy, - /// File descriptor does not hold the required rights to write to it. - AccessDenied, - PermissionDenied, - /// File is an unconnected socket, or closed its read end. - BrokenPipe, - /// Insufficient kernel memory to read from in_fd. - SystemResources, - NotOpenForWriting, - /// The process cannot access the file because another process has locked - /// a portion of the file. Windows-only. - LockViolation, - /// Non-blocking has been enabled and this operation would block. - WouldBlock, - /// This error occurs when a device gets disconnected before or mid-flush - /// while it's being written to - errno(6): No such device or address. - NoDevice, - FileBusy, -} || Io.Cancelable || Io.UnexpectedError; +pub const Error = Io.Operation.FileWriteStreaming.Error || Io.Cancelable; pub const WriteFileError = Error || error{ /// Descriptor is not valid or locked, or an mmap(2)-like operation is not available for in_fd. @@ -146,7 +123,7 @@ fn drainPositional(w: *Writer, data: []const []const u8, splat: usize) Io.Writer fn drainStreaming(w: *Writer, data: []const []const u8, splat: usize) Io.Writer.Error!usize { const io = w.io; const header = w.interface.buffered(); - const n = io.vtable.fileWriteStreaming(io.userdata, w.file, header, data, splat) catch |err| { + const n = w.file.writeStreaming(io, header, data, splat) catch |err| { w.err = err; return error.WriteFailed; }; diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 12521ea1af03a29dfb73672de61e52b69723ec26..8de5e2692a89a3da983db7a76f9033a3822d084b 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -1649,7 +1649,6 @@ pub fn io(t: *Threaded) Io { .fileStat = fileStat, .fileLength = fileLength, .fileClose = fileClose, - .fileWriteStreaming = fileWriteStreaming, .fileWritePositional = fileWritePositional, .fileWriteFileStreaming = fileWriteFileStreaming, .fileWriteFilePositional = fileWriteFilePositional, @@ -1813,7 +1812,6 @@ pub fn ioBasic(t: *Threaded) Io { .fileStat = fileStat, .fileLength = fileLength, .fileClose = fileClose, - .fileWriteStreaming = fileWriteStreaming, .fileWritePositional = fileWritePositional, .fileWriteFileStreaming = fileWriteFileStreaming, .fileWriteFilePositional = fileWriteFilePositional, @@ -2496,6 +2494,12 @@ fn operate(userdata: ?*anyopaque, operation: Io.Operation) Io.Cancelable!Io.Oper else => |e| e, }, }, + .file_write_streaming => |o| return .{ + .file_write_streaming = fileWriteStreaming(t, o.file, o.header, o.data, o.splat) catch |err| switch (err) { + error.Canceled => |e| return e, + else => |e| e, + }, + }, } } @@ -2523,6 +2527,10 @@ fn batchAwaitAsync(userdata: ?*anyopaque, b: *Io.Batch) Io.Cancelable!void { poll_buffer[poll_len] = .{ .fd = o.file.handle, .events = posix.POLL.IN, .revents = 0 }; poll_len += 1; }, + .file_write_streaming => |o| { + poll_buffer[poll_len] = .{ .fd = o.file.handle, .events = posix.POLL.OUT, .revents = 0 }; + poll_len += 1; + }, } index = submission.node.next; } @@ -2687,6 +2695,7 @@ fn batchAwaitConcurrent(userdata: ?*anyopaque, b: *Io.Batch, timeout: Io.Timeout const submission = &b.storage[index.toIndex()].submission; switch (submission.operation) { .file_read_streaming => |o| try poll_storage.add(o.file, posix.POLL.IN), + .file_write_streaming => |o| try poll_storage.add(o.file, posix.POLL.OUT), } index = submission.node.next; } @@ -2864,6 +2873,7 @@ fn batchApc(apc_context: ?*anyopaque, iosb: *windows.IO_STATUS_BLOCK, _: windows b.completions.tail = .fromIndex(index); const result: Io.Operation.Result = switch (pending.tag) { .file_read_streaming => .{ .file_read_streaming = ntReadFileResult(iosb) }, + .file_write_streaming => .{ .file_write_streaming = ntWriteFileResult(iosb) }, }; storage.* = .{ .completion = .{ .node = .{ .next = .none }, .result = result } }; }, @@ -2950,6 +2960,66 @@ fn batchAwaitWindows(b: *Io.Batch, concurrency: bool) error{ Canceled, Concurren else => |status| { syscall.finish(); + context.iosb.u.Status = status; + batchApc(b, &context.iosb, 0); + break; + }, + }; + } + }, + .file_write_streaming => |o| o: { + const buffer = windowsWriteBuffer(o.header, o.data, o.splat); + if (buffer.len == 0) { + context.iosb = .{ + .u = .{ .Status = .SUCCESS }, + .Information = 0, + }; + batchApc(b, &context.iosb, 0); + break :o; + } + if (o.file.flags.nonblocking) { + context.file = o.file.handle; + switch (windows.ntdll.NtWriteFile( + o.file.handle, + null, // event + &batchApc, + b, + &context.iosb, + buffer.ptr, + @intCast(buffer.len), + null, // byte offset + null, // key + )) { + .PENDING, .SUCCESS => {}, + .CANCELLED => unreachable, + else => |status| { + context.iosb.u.Status = status; + batchApc(b, &context.iosb, 0); + }, + } + } else { + if (concurrency) return error.ConcurrencyUnavailable; + + const syscall: Syscall = try .start(); + while (true) switch (windows.ntdll.NtWriteFile( + o.file.handle, + null, // event + null, // APC routine + null, // APC context + &context.iosb, + buffer.ptr, + @intCast(buffer.len), + null, // byte offset + null, // key + )) { + .PENDING => unreachable, // unrecoverable: wrong File nonblocking flag + .CANCELLED => { + try syscall.checkCancel(); + continue; + }, + else => |status| { + syscall.finish(); + context.iosb.u.Status = status; batchApc(b, &context.iosb, 0); break; @@ -2963,6 +3033,21 @@ fn batchAwaitWindows(b: *Io.Batch, concurrency: bool) error{ Canceled, Concurren b.submissions = .{ .head = .none, .tail = .none }; } +/// Since Windows only supports writing one contiguous buffer, returns the +/// first one, while also limiting it to a length representable by 32-bit +/// unsigned integer. +fn windowsWriteBuffer(header: []const u8, data: []const []const u8, splat: usize) []const u8 { + const buffer = b: { + if (header.len != 0) break :b header; + for (data[0 .. data.len - 1]) |buffer| { + if (buffer.len != 0) break :b buffer; + } + if (splat == 0) return &.{}; + break :b data[data.len - 1]; + }; + return buffer[0..@min(buffer.len, std.math.maxInt(u32))]; +} + fn submitComplete(ring: []u32, complete_tail: *Io.Batch.RingIndex, op: u32) void { const ct = complete_tail.*; const len: u31 = @intCast(ring.len); @@ -9005,6 +9090,24 @@ fn ntReadFileResult(io_status_block: *const windows.IO_STATUS_BLOCK) !usize { } } +fn ntWriteFileResult(io_status_block: *const windows.IO_STATUS_BLOCK) !usize { + switch (io_status_block.u.Status) { + .PENDING => unreachable, + .CANCELLED => unreachable, + .SUCCESS => return io_status_block.Information, + .INVALID_USER_BUFFER => return error.SystemResources, + .NO_MEMORY => return error.SystemResources, + .QUOTA_EXCEEDED => return error.SystemResources, + .PIPE_BROKEN => return error.BrokenPipe, + .INVALID_HANDLE => return error.NotOpenForWriting, + .LOCK_NOT_GRANTED => return error.LockViolation, + .ACCESS_DENIED => return error.AccessDenied, + .WORKING_SET_QUOTA => return error.SystemResources, + .DISK_FULL => return error.NoSpaceLeft, + else => |status| return windows.unexpectedStatus(status), + } +} + fn fileReadPositionalPosix(file: File, data: []const []u8, offset: u64) File.ReadPositionalError!usize { if (!have_preadv) @compileError("TODO implement fileReadPositionalPosix for cursed operating systems that don't support preadv (it's only Haiku)"); @@ -9837,16 +9940,9 @@ fn fileWriteStreaming( _ = t; if (is_windows) { - if (header.len != 0) { - return writeFileStreamingWindows(file.handle, header); - } - for (data[0 .. data.len - 1]) |buf| { - if (buf.len == 0) continue; - return writeFileStreamingWindows(file.handle, buf); - } - const pattern = data[data.len - 1]; - if (pattern.len == 0 or splat == 0) return 0; - return writeFileStreamingWindows(file.handle, pattern); + const buffer = windowsWriteBuffer(header, data, splat); + if (buffer.len == 0) return 0; + return fileWriteStreamingWindows(file, buffer); } var iovecs: [max_iovecs_len]posix.iovec_const = undefined; @@ -9953,38 +10049,66 @@ fn fileWriteStreaming( } } -fn writeFileStreamingWindows( - handle: windows.HANDLE, - bytes: []const u8, -) File.Writer.Error!usize { - assert(bytes.len != 0); - var bytes_written: windows.DWORD = undefined; - const adjusted_len = std.math.lossyCast(u32, bytes.len); - const syscall: Syscall = try .start(); - while (true) { - if (windows.kernel32.WriteFile(handle, bytes.ptr, adjusted_len, &bytes_written, null) != 0) { - syscall.finish(); - return bytes_written; +fn fileWriteStreamingWindows(file: File, buffer: []const u8) File.Writer.Error!usize { + assert(buffer.len != 0); + + var iosb: windows.IO_STATUS_BLOCK = undefined; + + if (file.flags.nonblocking) { + var done: bool = false; + switch (windows.ntdll.NtWriteFile( + file.handle, + null, // event + flagApc, + &done, // APC context + &iosb, + buffer.ptr, + @intCast(buffer.len), + null, // byte offset + null, // key + )) { + // We must wait for the APC routine. + .PENDING, .SUCCESS => while (!done) { + // Once we get here we must not return from the function until the + // operation completes, thereby releasing reference to io_status_block. + const alertable_syscall = AlertableSyscall.start() catch |err| switch (err) { + error.Canceled => |e| { + var cancel_iosb: windows.IO_STATUS_BLOCK = undefined; + _ = windows.ntdll.NtCancelIoFileEx(file.handle, &iosb, &cancel_iosb); + while (!done) waitForApcOrAlert(); + return e; + }, + }; + waitForApcOrAlert(); + alertable_syscall.finish(); + }, + else => |status| iosb.u.Status = status, } - switch (windows.GetLastError()) { - .OPERATION_ABORTED => { + return ntWriteFileResult(&iosb); + } else { + const syscall: Syscall = try .start(); + while (true) switch (windows.ntdll.NtWriteFile( + file.handle, + null, // event + null, // APC routine + null, // APC context + &iosb, + buffer.ptr, + @intCast(buffer.len), + null, // byte offset + null, // key + )) { + .PENDING => unreachable, // unrecoverable: wrong File nonblocking flag + .CANCELLED => { try syscall.checkCancel(); continue; }, - .INVALID_USER_BUFFER => return syscall.fail(error.SystemResources), - .NOT_ENOUGH_MEMORY => return syscall.fail(error.SystemResources), - .NOT_ENOUGH_QUOTA => return syscall.fail(error.SystemResources), - .NO_DATA => return syscall.fail(error.BrokenPipe), - .INVALID_HANDLE => return syscall.fail(error.NotOpenForWriting), - .LOCK_VIOLATION => return syscall.fail(error.LockViolation), - .ACCESS_DENIED => return syscall.fail(error.AccessDenied), - .WORKING_SET_QUOTA => return syscall.fail(error.SystemResources), - .DISK_FULL => return syscall.fail(error.NoSpaceLeft), - else => |err| { + else => |status| { syscall.finish(); - return windows.unexpectedError(err); + iosb.u.Status = status; + return ntWriteFileResult(&iosb); }, - } + }; } } diff --git a/lib/std/Progress.zig b/lib/std/Progress.zig index 0fefc77a32e788267a10f9fc2b613c96abeb5e19..ee2a993a4de04b472db56faed611e6d598fcf103 100644 --- a/lib/std/Progress.zig +++ b/lib/std/Progress.zig @@ -1437,7 +1437,7 @@ fn writeIpc(io: Io, file: Io.File, serialized: Serialized) error{BrokenPipe}!voi // We do this in a separate write call to give a better chance for the // writev below to be in a single packet. const n = @min(parents.len, remaining_write_trash_bytes); - if (io.vtable.fileWriteStreaming(io.userdata, file, &.{}, &.{parents[0..n]}, 1)) |written| { + if (file.writeStreaming(io, &.{}, &.{parents[0..n]}, 1)) |written| { remaining_write_trash_bytes -= written; continue; } else |err| switch (err) { @@ -1478,7 +1478,7 @@ fn writevNonblock(io: Io, file: Io.File, iov: [][]const u8) Io.File.Writer.Error return total_written) : (iov_index += 1) written -= iov[iov_index].len; iov[iov_index].ptr += written; iov[iov_index].len -= written; - written = try io.vtable.fileWriteStreaming(io.userdata, file, &.{}, iov, 1); + written = try file.writeStreaming(io, &.{}, iov, 1); if (written == 0) return total_written; total_written += written; } -- 2.54.0 From 0aae9768aab8e1b534eb9a7682ad36ab4ef0487d Mon Sep 17 00:00:00 2001 From: Jeff Anderson Date: Sat, 31 Jan 2026 21:54:25 -0800 Subject: [PATCH 163/499] libzigc: cbrt --- lib/c/math.zig | 5 ++ lib/libc/musl/src/math/cbrt.c | 103 ---------------------------------- src/libs/musl.zig | 1 - src/libs/wasi_libc.zig | 1 - 4 files changed, 5 insertions(+), 105 deletions(-) delete mode 100644 lib/libc/musl/src/math/cbrt.c diff --git a/lib/c/math.zig b/lib/c/math.zig index a9e7c808a80adab4758c439209d9c8470319d442..bb0a424b7db08f54aed34e034dd52eed9007b28f 100644 --- a/lib/c/math.zig +++ b/lib/c/math.zig @@ -39,6 +39,7 @@ comptime { @export(&atanf, .{ .name = "atanf", .linkage = common.linkage, .visibility = common.visibility }); @export(&atan, .{ .name = "atan", .linkage = common.linkage, .visibility = common.visibility }); @export(&atanl, .{ .name = "atanl", .linkage = common.linkage, .visibility = common.visibility }); + @export(&cbrt, .{ .name = "cbrt", .linkage = common.linkage, .visibility = common.visibility }); } if (builtin.target.isMuslLibC()) { @@ -106,3 +107,7 @@ fn copysign(x: f64, y: f64) callconv(.c) f64 { fn copysignl(x: c_longdouble, y: c_longdouble) callconv(.c) c_longdouble { return math.copysign(x, y); } + +fn cbrt(x: f64) callconv(.c) f64 { + return math.cbrt(x); +} diff --git a/lib/libc/musl/src/math/cbrt.c b/lib/libc/musl/src/math/cbrt.c deleted file mode 100644 index 7599d3e37d2f6f81f21321b62f1e97aae5e34167..0000000000000000000000000000000000000000 --- a/lib/libc/musl/src/math/cbrt.c +++ /dev/null @@ -1,103 +0,0 @@ -/* origin: FreeBSD /usr/src/lib/msun/src/s_cbrt.c */ -/* - * ==================================================== - * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. - * - * Developed at SunPro, a Sun Microsystems, Inc. business. - * Permission to use, copy, modify, and distribute this - * software is freely granted, provided that this notice - * is preserved. - * ==================================================== - * - * Optimized by Bruce D. Evans. - */ -/* cbrt(x) - * Return cube root of x - */ - -#include -#include - -static const uint32_t -B1 = 715094163, /* B1 = (1023-1023/3-0.03306235651)*2**20 */ -B2 = 696219795; /* B2 = (1023-1023/3-54/3-0.03306235651)*2**20 */ - -/* |1/cbrt(x) - p(x)| < 2**-23.5 (~[-7.93e-8, 7.929e-8]). */ -static const double -P0 = 1.87595182427177009643, /* 0x3ffe03e6, 0x0f61e692 */ -P1 = -1.88497979543377169875, /* 0xbffe28e0, 0x92f02420 */ -P2 = 1.621429720105354466140, /* 0x3ff9f160, 0x4a49d6c2 */ -P3 = -0.758397934778766047437, /* 0xbfe844cb, 0xbee751d9 */ -P4 = 0.145996192886612446982; /* 0x3fc2b000, 0xd4e4edd7 */ - -double cbrt(double x) -{ - union {double f; uint64_t i;} u = {x}; - double_t r,s,t,w; - uint32_t hx = u.i>>32 & 0x7fffffff; - - if (hx >= 0x7ff00000) /* cbrt(NaN,INF) is itself */ - return x+x; - - /* - * Rough cbrt to 5 bits: - * cbrt(2**e*(1+m) ~= 2**(e/3)*(1+(e%3+m)/3) - * where e is integral and >= 0, m is real and in [0, 1), and "/" and - * "%" are integer division and modulus with rounding towards minus - * infinity. The RHS is always >= the LHS and has a maximum relative - * error of about 1 in 16. Adding a bias of -0.03306235651 to the - * (e%3+m)/3 term reduces the error to about 1 in 32. With the IEEE - * floating point representation, for finite positive normal values, - * ordinary integer divison of the value in bits magically gives - * almost exactly the RHS of the above provided we first subtract the - * exponent bias (1023 for doubles) and later add it back. We do the - * subtraction virtually to keep e >= 0 so that ordinary integer - * division rounds towards minus infinity; this is also efficient. - */ - if (hx < 0x00100000) { /* zero or subnormal? */ - u.f = x*0x1p54; - hx = u.i>>32 & 0x7fffffff; - if (hx == 0) - return x; /* cbrt(0) is itself */ - hx = hx/3 + B2; - } else - hx = hx/3 + B1; - u.i &= 1ULL<<63; - u.i |= (uint64_t)hx << 32; - t = u.f; - - /* - * New cbrt to 23 bits: - * cbrt(x) = t*cbrt(x/t**3) ~= t*P(t**3/x) - * where P(r) is a polynomial of degree 4 that approximates 1/cbrt(r) - * to within 2**-23.5 when |r - 1| < 1/10. The rough approximation - * has produced t such than |t/cbrt(x) - 1| ~< 1/32, and cubing this - * gives us bounds for r = t**3/x. - * - * Try to optimize for parallel evaluation as in __tanf.c. - */ - r = (t*t)*(t/x); - t = t*((P0+r*(P1+r*P2))+((r*r)*r)*(P3+r*P4)); - - /* - * Round t away from zero to 23 bits (sloppily except for ensuring that - * the result is larger in magnitude than cbrt(x) but not much more than - * 2 23-bit ulps larger). With rounding towards zero, the error bound - * would be ~5/6 instead of ~4/6. With a maximum error of 2 23-bit ulps - * in the rounded t, the infinite-precision error in the Newton - * approximation barely affects third digit in the final error - * 0.667; the error in the rounded t can be up to about 3 23-bit ulps - * before the final error is larger than 0.667 ulps. - */ - u.f = t; - u.i = (u.i + 0x80000000) & 0xffffffffc0000000ULL; - t = u.f; - - /* one step Newton iteration to 53 bits with error < 0.667 ulps */ - s = t*t; /* t*t is exact */ - r = x/s; /* error <= 0.5 ulps; |r| < |t| */ - w = t+t; /* t+t is exact */ - r = (r-t)/(w+r); /* r-t is exact; w+r ~= 3*t */ - t = t+t*r; /* error <= 0.5 + 0.5/3 + epsilon */ - return t; -} diff --git a/src/libs/musl.zig b/src/libs/musl.zig index c6bdac314c2c4f9dbf114e9c031d2a867b43ee15..e42c12b7f01a5bfd2e112fc8af754c2b4f572f90 100644 --- a/src/libs/musl.zig +++ b/src/libs/musl.zig @@ -839,7 +839,6 @@ const src_files = [_][]const u8{ "musl/src/math/atanh.c", "musl/src/math/atanhf.c", "musl/src/math/atanhl.c", - "musl/src/math/cbrt.c", "musl/src/math/cbrtf.c", "musl/src/math/cbrtl.c", "musl/src/math/__cos.c", diff --git a/src/libs/wasi_libc.zig b/src/libs/wasi_libc.zig index 19b820eef9bd305b4a0b45bf9a54b5e12d6acaa3..ea113d3b2e94f1c9db90629406a8f61786a52ab6 100644 --- a/src/libs/wasi_libc.zig +++ b/src/libs/wasi_libc.zig @@ -701,7 +701,6 @@ const libc_top_half_src_files = [_][]const u8{ "musl/src/math/atanh.c", "musl/src/math/atanhf.c", "musl/src/math/atanhl.c", - "musl/src/math/cbrt.c", "musl/src/math/cbrtf.c", "musl/src/math/cbrtl.c", "musl/src/math/__cos.c", -- 2.54.0 From 4aadb5e4a5e09d6b2018fecb364c0fabec028c9a Mon Sep 17 00:00:00 2001 From: Jeff Anderson Date: Sat, 31 Jan 2026 22:55:11 -0800 Subject: [PATCH 164/499] libzigc: cbrtf --- lib/c/math.zig | 5 +++ lib/libc/musl/src/math/cbrtf.c | 66 ---------------------------------- src/libs/musl.zig | 1 - src/libs/wasi_libc.zig | 1 - 4 files changed, 5 insertions(+), 68 deletions(-) delete mode 100644 lib/libc/musl/src/math/cbrtf.c diff --git a/lib/c/math.zig b/lib/c/math.zig index bb0a424b7db08f54aed34e034dd52eed9007b28f..3811142769073deb3c2b9b1deba3c3f583b25a1f 100644 --- a/lib/c/math.zig +++ b/lib/c/math.zig @@ -40,6 +40,7 @@ comptime { @export(&atan, .{ .name = "atan", .linkage = common.linkage, .visibility = common.visibility }); @export(&atanl, .{ .name = "atanl", .linkage = common.linkage, .visibility = common.visibility }); @export(&cbrt, .{ .name = "cbrt", .linkage = common.linkage, .visibility = common.visibility }); + @export(&cbrtf, .{ .name = "cbrtf", .linkage = common.linkage, .visibility = common.visibility }); } if (builtin.target.isMuslLibC()) { @@ -111,3 +112,7 @@ fn copysignl(x: c_longdouble, y: c_longdouble) callconv(.c) c_longdouble { fn cbrt(x: f64) callconv(.c) f64 { return math.cbrt(x); } + +fn cbrtf(x: f32) callconv(.c) f32 { + return math.cbrt(x); +} diff --git a/lib/libc/musl/src/math/cbrtf.c b/lib/libc/musl/src/math/cbrtf.c deleted file mode 100644 index 89c2c8655da46a37a737dfed63ae8c619f9c7350..0000000000000000000000000000000000000000 --- a/lib/libc/musl/src/math/cbrtf.c +++ /dev/null @@ -1,66 +0,0 @@ -/* origin: FreeBSD /usr/src/lib/msun/src/s_cbrtf.c */ -/* - * Conversion to float by Ian Lance Taylor, Cygnus Support, ian@cygnus.com. - * Debugged and optimized by Bruce D. Evans. - */ -/* - * ==================================================== - * Copyright (C) 1993 by Sun Microsystems, Inc. All rights reserved. - * - * Developed at SunPro, a Sun Microsystems, Inc. business. - * Permission to use, copy, modify, and distribute this - * software is freely granted, provided that this notice - * is preserved. - * ==================================================== - */ -/* cbrtf(x) - * Return cube root of x - */ - -#include -#include - -static const unsigned -B1 = 709958130, /* B1 = (127-127.0/3-0.03306235651)*2**23 */ -B2 = 642849266; /* B2 = (127-127.0/3-24/3-0.03306235651)*2**23 */ - -float cbrtf(float x) -{ - double_t r,T; - union {float f; uint32_t i;} u = {x}; - uint32_t hx = u.i & 0x7fffffff; - - if (hx >= 0x7f800000) /* cbrt(NaN,INF) is itself */ - return x + x; - - /* rough cbrt to 5 bits */ - if (hx < 0x00800000) { /* zero or subnormal? */ - if (hx == 0) - return x; /* cbrt(+-0) is itself */ - u.f = x*0x1p24f; - hx = u.i & 0x7fffffff; - hx = hx/3 + B2; - } else - hx = hx/3 + B1; - u.i &= 0x80000000; - u.i |= hx; - - /* - * First step Newton iteration (solving t*t-x/t == 0) to 16 bits. In - * double precision so that its terms can be arranged for efficiency - * without causing overflow or underflow. - */ - T = u.f; - r = T*T*T; - T = T*((double_t)x+x+r)/(x+r+r); - - /* - * Second step Newton iteration to 47 bits. In double precision for - * efficiency and accuracy. - */ - r = T*T*T; - T = T*((double_t)x+x+r)/(x+r+r); - - /* rounding to 24 bits is perfect in round-to-nearest mode */ - return T; -} diff --git a/src/libs/musl.zig b/src/libs/musl.zig index e42c12b7f01a5bfd2e112fc8af754c2b4f572f90..763869a0bdbf9ba9a60db5cd35f623d1edadb258 100644 --- a/src/libs/musl.zig +++ b/src/libs/musl.zig @@ -839,7 +839,6 @@ const src_files = [_][]const u8{ "musl/src/math/atanh.c", "musl/src/math/atanhf.c", "musl/src/math/atanhl.c", - "musl/src/math/cbrtf.c", "musl/src/math/cbrtl.c", "musl/src/math/__cos.c", "musl/src/math/__cosdf.c", diff --git a/src/libs/wasi_libc.zig b/src/libs/wasi_libc.zig index ea113d3b2e94f1c9db90629406a8f61786a52ab6..3f069de0c20e55f381c57445306cc328c6d90fd1 100644 --- a/src/libs/wasi_libc.zig +++ b/src/libs/wasi_libc.zig @@ -701,7 +701,6 @@ const libc_top_half_src_files = [_][]const u8{ "musl/src/math/atanh.c", "musl/src/math/atanhf.c", "musl/src/math/atanhl.c", - "musl/src/math/cbrtf.c", "musl/src/math/cbrtl.c", "musl/src/math/__cos.c", "musl/src/math/__cosdf.c", -- 2.54.0 From 59073484baf89072aa02f23fe9a09662e5def100 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sun, 1 Feb 2026 01:08:01 -0800 Subject: [PATCH 165/499] std.Io: add ioctl / DeviceIoControlFile API --- lib/std/Io.zig | 29 ++++++- lib/std/Io/Threaded.zig | 188 ++++++++++++++++++++++++++++++++++------ lib/std/Progress.zig | 23 +++-- lib/std/c.zig | 7 +- lib/std/posix.zig | 22 ----- 5 files changed, 211 insertions(+), 58 deletions(-) diff --git a/lib/std/Io.zig b/lib/std/Io.zig index a892c0123c7179d1f203ba231e087ec7bd92a105..d2b9648245d822660b871aaabe1d29f496edd573 100644 --- a/lib/std/Io.zig +++ b/lib/std/Io.zig @@ -257,6 +257,9 @@ pub const VTable = struct { pub const Operation = union(enum) { file_read_streaming: FileReadStreaming, file_write_streaming: FileWriteStreaming, + /// On Windows this is NtDeviceIoControlFile. On POSIX this is ioctl. On + /// other systems this tag is unreachable. + device_io_control: DeviceIoControl, pub const Tag = @typeInfo(Operation).@"union".tag_type.?; @@ -324,13 +327,37 @@ pub const Operation = union(enum) { pub const Result = Error!usize; }; + pub const DeviceIoControl = switch (builtin.os.tag) { + .wasi => noreturn, + .windows => struct { + file: File, + IoControlCode: std.os.windows.CTL_CODE, + InputBuffer: ?*const anyopaque, + InputBufferLength: u32, + OutputBuffer: ?*anyopaque, + OutputBufferLength: u32, + + pub const Result = std.os.windows.IO_STATUS_BLOCK; + }, + else => struct { + file: File, + /// Device-dependent operation code. + code: u32, + arg: ?*anyopaque, + + /// Device and operation dependent result. Negative values are + /// negative errno. + pub const Result = i32; + }, + }; + pub const Result = Result: { const operation_fields = @typeInfo(Operation).@"union".fields; var field_names: [operation_fields.len][]const u8 = undefined; var field_types: [operation_fields.len]type = undefined; for (operation_fields, &field_names, &field_types) |field, *field_name, *field_type| { field_name.* = field.name; - field_type.* = field.type.Result; + field_type.* = if (field.type == noreturn) noreturn else field.type.Result; } break :Result @Union(.auto, Tag, &field_names, &field_types, &@splat(.{})); }; diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 8de5e2692a89a3da983db7a76f9033a3822d084b..0feec928fca854237041a140b644a38e0bd1c1e4 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -2500,6 +2500,9 @@ fn operate(userdata: ?*anyopaque, operation: Io.Operation) Io.Cancelable!Io.Oper else => |e| e, }, }, + .device_io_control => |*o| return .{ + .device_io_control = try deviceIoControl(t, o), + }, } } @@ -2531,6 +2534,14 @@ fn batchAwaitAsync(userdata: ?*anyopaque, b: *Io.Batch) Io.Cancelable!void { poll_buffer[poll_len] = .{ .fd = o.file.handle, .events = posix.POLL.OUT, .revents = 0 }; poll_len += 1; }, + .device_io_control => |o| { + poll_buffer[poll_len] = .{ + .fd = o.file.handle, + .events = posix.POLL.OUT | posix.POLL.IN | posix.POLL.ERR, + .revents = 0, + }; + poll_len += 1; + }, } index = submission.node.next; } @@ -2696,6 +2707,7 @@ fn batchAwaitConcurrent(userdata: ?*anyopaque, b: *Io.Batch, timeout: Io.Timeout switch (submission.operation) { .file_read_streaming => |o| try poll_storage.add(o.file, posix.POLL.IN), .file_write_streaming => |o| try poll_storage.add(o.file, posix.POLL.OUT), + .device_io_control => |o| try poll_storage.add(o.file, posix.POLL.IN | posix.POLL.OUT | posix.POLL.ERR), } index = submission.node.next; } @@ -2874,6 +2886,7 @@ fn batchApc(apc_context: ?*anyopaque, iosb: *windows.IO_STATUS_BLOCK, _: windows const result: Io.Operation.Result = switch (pending.tag) { .file_read_streaming => .{ .file_read_streaming = ntReadFileResult(iosb) }, .file_write_streaming => .{ .file_write_streaming = ntWriteFileResult(iosb) }, + .device_io_control => .{ .device_io_control = iosb.* }, }; storage.* = .{ .completion = .{ .node = .{ .next = .none }, .result = result } }; }, @@ -3020,6 +3033,59 @@ fn batchAwaitWindows(b: *Io.Batch, concurrency: bool) error{ Canceled, Concurren else => |status| { syscall.finish(); + context.iosb.u.Status = status; + batchApc(b, &context.iosb, 0); + break; + }, + }; + } + }, + .device_io_control => |o| { + if (o.file.flags.nonblocking) { + context.file = o.file.handle; + switch (windows.ntdll.NtDeviceIoControlFile( + o.file.handle, + null, // event + &batchApc, + b, + &context.iosb, + o.IoControlCode, + o.InputBuffer, + o.InputBufferLength, + o.OutputBuffer, + o.OutputBufferLength, + )) { + .PENDING, .SUCCESS => {}, + .CANCELLED => unreachable, + else => |status| { + context.iosb.u.Status = status; + batchApc(b, &context.iosb, 0); + }, + } + } else { + if (concurrency) return error.ConcurrencyUnavailable; + + const syscall: Syscall = try .start(); + while (true) switch (windows.ntdll.NtDeviceIoControlFile( + o.file.handle, + null, // event + null, // APC routine + null, // APC context + &context.iosb, + o.IoControlCode, + o.InputBuffer, + o.InputBufferLength, + o.OutputBuffer, + o.OutputBufferLength, + )) { + .PENDING => unreachable, // unrecoverable: wrong File nonblocking flag + .CANCELLED => { + try syscall.checkCancel(); + continue; + }, + else => |status| { + syscall.finish(); + context.iosb.u.Status = status; batchApc(b, &context.iosb, 0); break; @@ -12986,31 +13052,18 @@ fn netInterfaceNameResolve( }; const syscall: Syscall = try .start(); - while (true) { - switch (posix.errno(posix.system.ioctl(sock_fd, posix.SIOCGIFINDEX, @intFromPtr(&ifr)))) { - .SUCCESS => { - syscall.finish(); - return .{ .index = @bitCast(ifr.ifru.ivalue) }; - }, - .INTR => { - try syscall.checkCancel(); - continue; - }, - else => |e| { - syscall.finish(); - switch (e) { - .INVAL => |err| return errnoBug(err), // Bad parameters. - .NOTTY => |err| return errnoBug(err), - .NXIO => |err| return errnoBug(err), - .BADF => |err| return errnoBug(err), // File descriptor used after closed. - .FAULT => |err| return errnoBug(err), // Bad pointer parameter. - .IO => |err| return errnoBug(err), // sock_fd is not a file descriptor - .NODEV => return error.InterfaceNotFound, - else => |err| return posix.unexpectedErrno(err), - } - }, - } - } + while (true) switch (posix.errno(posix.system.ioctl(sock_fd, posix.SIOCGIFINDEX, @intFromPtr(&ifr)))) { + .SUCCESS => { + syscall.finish(); + return .{ .index = @bitCast(ifr.ifru.ivalue) }; + }, + .INTR => { + try syscall.checkCancel(); + continue; + }, + .NODEV => return syscall.fail(error.InterfaceNotFound), + else => |err| return syscall.unexpectedErrno(err), + }; } if (is_windows) { @@ -17849,3 +17902,88 @@ fn mmSyncWrite(file: File, memory: []u8, offset: u64) File.WritePositionalError! } } } + +fn deviceIoControl(t: *Threaded, o: *const Io.Operation.DeviceIoControl) Io.Cancelable!Io.Operation.DeviceIoControl.Result { + _ = t; + if (is_windows) { + var iosb: windows.IO_STATUS_BLOCK = undefined; + if (o.file.flags.nonblocking) { + var done: bool = false; + switch (windows.ntdll.NtDeviceIoControlFile( + o.file.handle, + null, // event + flagApc, + &done, // APC context + &iosb, + o.IoControlCode, + o.InputBuffer, + o.InputBufferLength, + o.OutputBuffer, + o.OutputBufferLength, + )) { + // We must wait for the APC routine. + .PENDING, .SUCCESS => while (!done) { + // Once we get here we must not return from the function until the + // operation completes, thereby releasing reference to io_status_block. + const alertable_syscall = AlertableSyscall.start() catch |err| switch (err) { + error.Canceled => |e| { + var cancel_iosb: windows.IO_STATUS_BLOCK = undefined; + _ = windows.ntdll.NtCancelIoFileEx(o.file.handle, &iosb, &cancel_iosb); + while (!done) waitForApcOrAlert(); + return e; + }, + }; + waitForApcOrAlert(); + alertable_syscall.finish(); + }, + else => |status| iosb.u.Status = status, + } + } else { + const syscall: Syscall = try .start(); + while (true) switch (windows.ntdll.NtDeviceIoControlFile( + o.file.handle, + null, // event + null, // APC routine + null, // APC context + &iosb, + o.IoControlCode, + o.InputBuffer, + o.InputBufferLength, + o.OutputBuffer, + o.OutputBufferLength, + )) { + .PENDING => unreachable, // unrecoverable: wrong asynchronous flag + .CANCELLED => { + try syscall.checkCancel(); + continue; + }, + else => |status| { + syscall.finish(); + iosb.u.Status = status; + break; + }, + }; + } + return iosb; + } else { + const syscall: Syscall = try .start(); + while (true) { + const rc = posix.system.ioctl(o.file.handle, @bitCast(o.code), @intFromPtr(o.arg)); + switch (posix.errno(rc)) { + .SUCCESS => { + syscall.finish(); + if (@TypeOf(rc) == usize) return @bitCast(@as(u32, @truncate(rc))); + return rc; + }, + .INTR => { + try syscall.checkCancel(); + continue; + }, + else => |err| { + syscall.finish(); + return -@as(i32, @intFromEnum(err)); + }, + } + } + } +} diff --git a/lib/std/Progress.zig b/lib/std/Progress.zig index ee2a993a4de04b472db56faed611e6d598fcf103..e2f3ed52322ef30a89747bbbd0c394c4bf27389d 100644 --- a/lib/std/Progress.zig +++ b/lib/std/Progress.zig @@ -573,7 +573,7 @@ fn updateTask(io: Io) void { { const resize_flag = wait(io, global_progress.initial_delay_ns); if (@atomicLoad(bool, &global_progress.done, .monotonic)) return; - maybeUpdateSize(resize_flag); + maybeUpdateSize(io, resize_flag) catch return; const buffer, _ = computeRedraw(&serialized_buffer); if (io.vtable.tryLockStderr(io.userdata, null) catch return) |locked_stderr| { @@ -592,7 +592,7 @@ fn updateTask(io: Io) void { return clearWrittenWithEscapeCodes(stderr.file_writer) catch {}; } - maybeUpdateSize(resize_flag); + maybeUpdateSize(io, resize_flag) catch return; const buffer, _ = computeRedraw(&serialized_buffer); if (io.vtable.tryLockStderr(io.userdata, null) catch return) |locked_stderr| { @@ -622,7 +622,7 @@ fn windowsApiUpdateTask(io: Io) void { { const resize_flag = wait(io, global_progress.initial_delay_ns); if (@atomicLoad(bool, &global_progress.done, .monotonic)) return; - maybeUpdateSize(resize_flag); + maybeUpdateSize(io, resize_flag) catch return; const buffer, const nl_n = computeRedraw(&serialized_buffer); if (io.vtable.tryLockStderr(io.userdata, null) catch return) |locked_stderr| { @@ -643,7 +643,7 @@ fn windowsApiUpdateTask(io: Io) void { return clearWrittenWindowsApi() catch {}; } - maybeUpdateSize(resize_flag); + maybeUpdateSize(io, resize_flag) catch return; const buffer, const nl_n = computeRedraw(&serialized_buffer); if (io.vtable.tryLockStderr(io.userdata, null) catch return) |locked_stderr| { @@ -1484,15 +1484,15 @@ fn writevNonblock(io: Io, file: Io.File, iov: [][]const u8) Io.File.Writer.Error } } -fn maybeUpdateSize(resize_flag: bool) void { +fn maybeUpdateSize(io: Io, resize_flag: bool) !void { if (!resize_flag) return; - const fd = global_progress.terminal.handle; + const file = global_progress.terminal; if (is_windows) { var info: windows.CONSOLE_SCREEN_BUFFER_INFO = undefined; - if (windows.kernel32.GetConsoleScreenBufferInfo(fd, &info) != windows.FALSE) { + if (windows.kernel32.GetConsoleScreenBufferInfo(file.handle, &info) != windows.FALSE) { // In the old Windows console, dwSize.Y is the line count of the // entire scrollback buffer, so we use this instead so that we // always get the size of the screen. @@ -1512,8 +1512,13 @@ fn maybeUpdateSize(resize_flag: bool) void { .ypixel = 0, }; - const err = posix.system.ioctl(fd, posix.T.IOCGWINSZ, @intFromPtr(&winsize)); - if (posix.errno(err) == .SUCCESS) { + const err = (try io.operate(.{ .device_io_control = .{ + .file = file, + .code = posix.T.IOCGWINSZ, + .arg = &winsize, + } })).device_io_control; + + if (err >= 0) { global_progress.rows = winsize.row; global_progress.cols = winsize.col; } else { diff --git a/lib/std/c.zig b/lib/std/c.zig index e68d5a71c51397b3028da96ee1c2d4f6e6f2a847..809ce29a472e61529c8c079ac24d8c71d5745ca2 100644 --- a/lib/std/c.zig +++ b/lib/std/c.zig @@ -10731,7 +10731,6 @@ pub extern "c" fn sysctlnametomib(name: [*:0]const u8, mibp: ?*c_int, sizep: ?*u pub extern "c" fn tcgetattr(fd: fd_t, termios_p: *termios) c_int; pub extern "c" fn tcsetattr(fd: fd_t, optional_action: TCSA, termios_p: *const termios) c_int; pub extern "c" fn fcntl(fd: fd_t, cmd: c_int, ...) c_int; -pub extern "c" fn ioctl(fd: fd_t, request: c_int, ...) c_int; pub extern "c" fn uname(buf: *utsname) c_int; pub extern "c" fn gethostname(name: [*]u8, len: usize) c_int; @@ -11108,6 +11107,11 @@ pub const clock_nanosleep = switch (native_os) { else => {}, }; +pub const ioctl = switch (native_os) { + .windows, .wasi => {}, + else => private.ioctl, +}; + // OS-specific bits. These are protected from being used on the wrong OS by // comptime assertions inside each OS-specific file. @@ -11495,6 +11499,7 @@ const private = struct { }; extern "c" fn getrusage(who: c_int, usage: *rusage) c_int; extern "c" fn gettimeofday(noalias tv: ?*timeval, noalias tz: ?*timezone) c_int; + extern "c" fn ioctl(fd: fd_t, request: c_int, ...) c_int; extern "c" fn msync(addr: *align(page_size) const anyopaque, len: usize, flags: c_int) c_int; extern "c" fn nanosleep(rqtp: *const timespec, rmtp: ?*timespec) c_int; extern "c" fn clock_nanosleep(clockid: clockid_t, flags: TIMER, t: *const timespec, remain: ?*timespec) c_int; diff --git a/lib/std/posix.zig b/lib/std/posix.zig index 5e2cde9aa52421fc3f74a8b3baf92eb37c3cc562..7c03e7953b5be7a4eba881adb33fd6a71310fa01 100644 --- a/lib/std/posix.zig +++ b/lib/std/posix.zig @@ -1800,28 +1800,6 @@ pub fn name_to_handle_atZ( } } -pub const IoCtl_SIOCGIFINDEX_Error = error{ - FileSystem, - InterfaceNotFound, -} || UnexpectedError; - -pub fn ioctl_SIOCGIFINDEX(fd: fd_t, ifr: *ifreq) IoCtl_SIOCGIFINDEX_Error!void { - while (true) { - switch (errno(system.ioctl(fd, SIOCGIFINDEX, @intFromPtr(ifr)))) { - .SUCCESS => return, - .INVAL => unreachable, // Bad parameters. - .NOTTY => unreachable, - .NXIO => unreachable, - .BADF => unreachable, // Always a race condition. - .FAULT => unreachable, // Bad pointer parameter. - .INTR => continue, - .IO => return error.FileSystem, - .NODEV => return error.InterfaceNotFound, - else => |err| return unexpectedErrno(err), - } - } -} - pub const lfs64_abi = native_os == .linux and builtin.link_libc and (builtin.abi.isGnu() or builtin.abi.isAndroid()); /// Whether or not `error.Unexpected` will print its value and a stack trace. -- 2.54.0 From aacf8ce03d2b52e52830873c40eabb1cb1eb8272 Mon Sep 17 00:00:00 2001 From: Jake Greenfield Date: Sun, 1 Feb 2026 22:04:20 -0500 Subject: [PATCH 166/499] Use MultiReader in `zig std` --- lib/compiler/std-docs.zig | 42 +++++++++++++++++++++++++-------------- 1 file changed, 27 insertions(+), 15 deletions(-) diff --git a/lib/compiler/std-docs.zig b/lib/compiler/std-docs.zig index 55f49c44e27d2aef6d0a7d513317454f1a3a44d2..72a8c74ff2ce89925f087a32517453b612226b43 100644 --- a/lib/compiler/std-docs.zig +++ b/lib/compiler/std-docs.zig @@ -324,11 +324,10 @@ fn buildWasmBinary( .stderr = .pipe, }); - var poller = Io.poll(gpa, enum { stdout, stderr }, .{ - .stdout = child.stdout.?, - .stderr = child.stderr.?, - }); - defer poller.deinit(); + var multi_reader_buffer: Io.File.MultiReader.Buffer(2) = undefined; + var multi_reader: Io.File.MultiReader = undefined; + multi_reader.init(gpa, io, multi_reader_buffer.toStreams(), &.{ child.stdout.?, child.stderr.? }); + defer multi_reader.deinit(); try sendMessage(io, child.stdin.?, .update); try sendMessage(io, child.stdin.?, .exit); @@ -336,14 +335,23 @@ fn buildWasmBinary( var result: ?Cache.Path = null; var result_error_bundle = std.zig.ErrorBundle.empty; - const stdout = poller.reader(.stdout); + const stdout = multi_reader.fileReader(0); + const MessageHeader = std.zig.Server.Message.Header; - poll: while (true) { - const Header = std.zig.Server.Message.Header; - while (stdout.buffered().len < @sizeOf(Header)) if (!try poller.poll()) break :poll; - const header = stdout.takeStruct(Header, .little) catch unreachable; - while (stdout.buffered().len < header.bytes_len) if (!try poller.poll()) break :poll; - const body = stdout.take(header.bytes_len) catch unreachable; + var eos_err: error{EndOfStream}!void = {}; + + while (true) { + const header = stdout.interface.takeStruct(MessageHeader, .little) catch |err| switch (err) { + error.EndOfStream => break, + error.ReadFailed => return stdout.err.?, + }; + const body = stdout.interface.take(header.bytes_len) catch |err| switch (err) { + error.EndOfStream => |e| { + eos_err = e; + break; + }, + error.ReadFailed => return stdout.err.?, + }; switch (header.tag) { .zig_version => { @@ -372,11 +380,15 @@ fn buildWasmBinary( } } - const stderr = poller.reader(.stderr); - if (stderr.bufferedLen() > 0) { - std.debug.print("{s}", .{stderr.buffered()}); + try multi_reader.fillRemaining(.none); + const stderr = multi_reader.reader(1).buffered(); + + if (stderr.len > 0) { + std.debug.print("{s}", .{stderr}); } + try eos_err; + // Send EOF to stdin. child.stdin.?.close(io); child.stdin = null; -- 2.54.0 From 60ac4e78ebb6de925b7bd1dc8571af55d096f964 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sun, 1 Feb 2026 18:44:08 -0800 Subject: [PATCH 167/499] std.Io.Mutex: fix tryLock `@cmpxchgWeak` can return the expected value sometimes. --- lib/std/Io.zig | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/lib/std/Io.zig b/lib/std/Io.zig index d2b9648245d822660b871aaabe1d29f496edd573..bb9610c0904b670b1eff8933efac16bd3138eca2 100644 --- a/lib/std/Io.zig +++ b/lib/std/Io.zig @@ -1236,15 +1236,7 @@ pub const Mutex = extern struct { }; pub fn tryLock(m: *Mutex) bool { - switch (m.state.cmpxchgWeak( - .unlocked, - .locked_once, - .acquire, - .monotonic, - ) orelse return true) { - .unlocked => unreachable, - .locked_once, .contended => return false, - } + return m.state.cmpxchgWeak(.unlocked, .locked_once, .acquire, .monotonic) == null; } pub fn lock(m: *Mutex, io: Io) Cancelable!void { -- 2.54.0 From b191e50be58cde6589a70410fb2f40ec43d0ffad Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sun, 1 Feb 2026 14:42:12 -0800 Subject: [PATCH 168/499] std.Thread: remove ResetEvent and WaitGroup * std.Thread.ResetEvent -> Io.Event * std.Thread.WaitGroup -> Io.Group --- CMakeLists.txt | 1 - lib/std/Io/Threaded.zig | 61 +++++++- lib/std/Io/test.zig | 133 +++++++++++++++++ lib/std/Thread.zig | 276 +++-------------------------------- lib/std/Thread/WaitGroup.zig | 87 ----------- lib/std/fs/test.zig | 19 ++- 6 files changed, 224 insertions(+), 353 deletions(-) delete mode 100644 lib/std/Thread/WaitGroup.zig diff --git a/CMakeLists.txt b/CMakeLists.txt index fe8660559c39614153329c69f0e74f48041a9e38..05fb9c4805fdb0b8fadcc4bd05167e70ed9668e7 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -410,7 +410,6 @@ set(ZIG_STAGE2_SOURCES lib/std/Thread.zig lib/std/Thread/Futex.zig lib/std/Thread/Mutex.zig - lib/std/Thread/WaitGroup.zig lib/std/array_hash_map.zig lib/std/array_list.zig lib/std/ascii.zig diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 0feec928fca854237041a140b644a38e0bd1c1e4..46ff3a7f38df43edaa06cca3fad6b043d913b95f 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -35,7 +35,7 @@ run_queue: std.SinglyLinkedList = .{}, join_requested: bool = false, stack_size: usize, /// All threads are spawned detached; this is how we wait until they all exit. -wait_group: std.Thread.WaitGroup = .{}, +wait_group: WaitGroup = .init, async_limit: Io.Limit, concurrent_limit: Io.Limit = .unlimited, /// Error from calling `std.Thread.getCpuCount` in `init`. @@ -17987,3 +17987,62 @@ fn deviceIoControl(t: *Threaded, o: *const Io.Operation.DeviceIoControl) Io.Canc } } } + +const WaitGroup = struct { + state: std.atomic.Value(usize), + event: Io.Event, + + const init: WaitGroup = .{ .state = .{ .raw = 0 }, .event = .unset }; + + const is_waiting: usize = 1 << 0; + const one_pending: usize = 1 << 1; + + fn start(wg: *WaitGroup) void { + const prev_state = wg.state.fetchAdd(one_pending, .monotonic); + assert((prev_state / one_pending) < (std.math.maxInt(usize) / one_pending)); + } + + fn value(wg: *WaitGroup) usize { + return wg.state.load(.monotonic) / one_pending; + } + + fn wait(wg: *WaitGroup) void { + const prev_state = wg.state.fetchAdd(is_waiting, .acquire); + assert(prev_state & is_waiting == 0); + if ((prev_state / one_pending) > 0) eventWait(&wg.event); + } + + fn finish(wg: *WaitGroup) void { + const state = wg.state.fetchSub(one_pending, .acq_rel); + assert((state / one_pending) > 0); + + if (state == (one_pending | is_waiting)) { + eventSet(&wg.event); + } + } +}; + +/// Same as `Io.Event.wait` but avoids the VTable. +fn eventWait(event: *Io.Event) void { + if (@cmpxchgStrong(Io.Event, event, .unset, .waiting, .acquire, .acquire)) |prev| switch (prev) { + .unset => unreachable, + .waiting => {}, + .is_set => return, + }; + while (true) { + Thread.futexWaitUncancelable(@ptrCast(event), @intFromEnum(Io.Event.waiting), null); + switch (@atomicLoad(Io.Event, event, .acquire)) { + .unset => unreachable, // `reset` called before pending `wait` returned + .waiting => continue, + .is_set => return, + } + } +} + +/// Same as `Io.Event.set` but avoids the VTable. +fn eventSet(event: *Io.Event) void { + switch (@atomicRmw(Io.Event, event, .Xchg, .is_set, .release)) { + .unset, .is_set => {}, + .waiting => Thread.futexWake(@ptrCast(event), std.math.maxInt(u32)), + } +} diff --git a/lib/std/Io/test.zig b/lib/std/Io/test.zig index 930a176b015c9ee966bab3e643b37f14d5648f4f..a9e2eb28d4125826143239ccc3d6ff5ceef70ffc 100644 --- a/lib/std/Io/test.zig +++ b/lib/std/Io/test.zig @@ -716,3 +716,136 @@ test "read from a file using Batch.awaitAsync API" { } } } + +test "Event smoke test" { + const io = testing.io; + + var event: Io.Event = .unset; + try testing.expectEqual(false, event.isSet()); + + // make sure the event gets set + event.set(io); + try testing.expectEqual(true, event.isSet()); + + // make sure the event gets unset again + event.reset(); + try testing.expectEqual(false, event.isSet()); + + // waits should timeout as there's no other thread to set the event + try testing.expectError(error.Timeout, event.waitTimeout(io, .{ .duration = .{ + .raw = .zero, + .clock = .awake, + } })); + try testing.expectError(error.Timeout, event.waitTimeout(io, .{ .duration = .{ + .raw = .fromMilliseconds(1), + .clock = .awake, + } })); + + // set the event again and make sure waits complete + event.set(io); + try event.wait(io); + try event.waitTimeout(io, .{ .duration = .{ .raw = .fromMilliseconds(1), .clock = .awake } }); + try testing.expectEqual(true, event.isSet()); +} + +test "Event signaling" { + if (builtin.single_threaded) { + // This test requires spawning threads. + return error.SkipZigTest; + } + + const io = testing.io; + + const Context = struct { + in: Io.Event = .unset, + out: Io.Event = .unset, + value: usize = 0, + + fn input(self: *@This()) !void { + // wait for the value to become 1 + try self.in.wait(io); + self.in.reset(); + try testing.expectEqual(self.value, 1); + + // bump the value and wake up output() + self.value = 2; + self.out.set(io); + + // wait for output to receive 2, bump the value and wake us up with 3 + try self.in.wait(io); + self.in.reset(); + try testing.expectEqual(self.value, 3); + + // bump the value and wake up output() for it to see 4 + self.value = 4; + self.out.set(io); + } + + fn output(self: *@This()) !void { + // start with 0 and bump the value for input to see 1 + try testing.expectEqual(self.value, 0); + self.value = 1; + self.in.set(io); + + // wait for input to receive 1, bump the value to 2 and wake us up + try self.out.wait(io); + self.out.reset(); + try testing.expectEqual(self.value, 2); + + // bump the value to 3 for input to see (rhymes) + self.value = 3; + self.in.set(io); + + // wait for input to bump the value to 4 and receive no more (rhymes) + try self.out.wait(io); + self.out.reset(); + try testing.expectEqual(self.value, 4); + } + }; + + var ctx = Context{}; + + const thread = try std.Thread.spawn(.{}, Context.output, .{&ctx}); + defer thread.join(); + + try ctx.input(); +} + +test "Event broadcast" { + if (builtin.single_threaded) { + // This test requires spawning threads. + return error.SkipZigTest; + } + + const io = testing.io; + + const num_threads = 10; + const Barrier = struct { + event: Io.Event = .unset, + counter: std.atomic.Value(usize) = std.atomic.Value(usize).init(num_threads), + + fn wait(self: *@This()) void { + if (self.counter.fetchSub(1, .acq_rel) == 1) { + self.event.set(io); + } + } + }; + + const Context = struct { + start_barrier: Barrier = .{}, + finish_barrier: Barrier = .{}, + + fn run(self: *@This()) void { + self.start_barrier.wait(); + self.finish_barrier.wait(); + } + }; + + var ctx = Context{}; + var threads: [num_threads - 1]std.Thread = undefined; + + for (&threads) |*t| t.* = try std.Thread.spawn(.{}, Context.run, .{&ctx}); + defer for (threads) |t| t.join(); + + ctx.run(); +} diff --git a/lib/std/Thread.zig b/lib/std/Thread.zig index 33fd4c0234cddd970e478caf40f43cc7d7edba2b..ef35422e1d2c7013d739dc6a05791de888292b9c 100644 --- a/lib/std/Thread.zig +++ b/lib/std/Thread.zig @@ -19,129 +19,11 @@ pub const Mutex = @import("Thread/Mutex.zig"); pub const Semaphore = @import("Thread/Semaphore.zig"); pub const Condition = @import("Thread/Condition.zig"); pub const RwLock = @import("Thread/RwLock.zig"); -pub const WaitGroup = @import("Thread/WaitGroup.zig"); pub const Pool = @compileError("deprecated; consider using 'std.Io.Group' with 'std.Io.Threaded'"); pub const use_pthreads = native_os != .windows and native_os != .wasi and builtin.link_libc; -/// A thread-safe logical boolean value which can be `set` and `unset`. -/// -/// It can also block threads until the value is set with cancelation via timed -/// waits. Statically initializable; four bytes on all targets. -pub const ResetEvent = enum(u32) { - unset = 0, - waiting = 1, - is_set = 2, - - /// Returns whether the logical boolean is `set`. - /// - /// Once `reset` is called, this returns false until the next `set`. - /// - /// The memory accesses before the `set` can be said to happen before - /// `isSet` returns true. - pub fn isSet(re: *const ResetEvent) bool { - if (builtin.single_threaded) return switch (re.*) { - .unset => false, - .waiting => unreachable, - .is_set => true, - }; - // Acquire barrier ensures memory accesses before `set` happen before - // returning true. - return @atomicLoad(ResetEvent, re, .acquire) == .is_set; - } - - /// Blocks the calling thread until `set` is called. - /// - /// This is effectively a more efficient version of `while (!isSet()) {}`. - /// - /// The memory accesses before the `set` can be said to happen before `wait` returns. - pub fn wait(re: *ResetEvent) void { - if (builtin.single_threaded) switch (re.*) { - .unset => unreachable, // Deadlock, no other threads to wake us up. - .waiting => unreachable, // Invalid state. - .is_set => return, - }; - if (!re.isSet()) return timedWaitInner(re, null) catch |err| switch (err) { - error.Timeout => unreachable, // No timeout specified. - }; - } - - /// Blocks the calling thread until `set` is called, or until the - /// corresponding timeout expires, returning `error.Timeout`. - /// - /// This is effectively a more efficient version of `while (!isSet()) {}`. - /// - /// The memory accesses before the set() can be said to happen before - /// timedWait() returns without error. - pub fn timedWait(re: *ResetEvent, timeout_ns: u64) error{Timeout}!void { - if (builtin.single_threaded) switch (re.*) { - .unset => return error.Timeout, - .waiting => unreachable, // Invalid state. - .is_set => return, - }; - if (!re.isSet()) return timedWaitInner(re, timeout_ns); - } - - fn timedWaitInner(re: *ResetEvent, timeout: ?u64) error{Timeout}!void { - @branchHint(.cold); - - // Try to set the state from `unset` to `waiting` to indicate to the - // `set` thread that others are blocked on the ResetEvent. Avoid using - // any strict barriers until we know the ResetEvent is set. - var state = @atomicLoad(ResetEvent, re, .acquire); - if (state == .unset) { - state = @cmpxchgStrong(ResetEvent, re, state, .waiting, .acquire, .acquire) orelse .waiting; - } - - // Wait until the ResetEvent is set since the state is waiting. - if (state == .waiting) { - var futex_deadline = Futex.Deadline.init(timeout); - while (true) { - const wait_result = futex_deadline.wait(@ptrCast(re), @intFromEnum(ResetEvent.waiting)); - - // Check if the ResetEvent was set before possibly reporting error.Timeout below. - state = @atomicLoad(ResetEvent, re, .acquire); - if (state != .waiting) break; - - try wait_result; - } - } - - assert(state == .is_set); - } - - /// Marks the logical boolean as `set` and unblocks any threads in `wait` - /// or `timedWait` to observe the new state. - /// - /// The logical boolean stays `set` until `reset` is called, making future - /// `set` calls do nothing semantically. - /// - /// The memory accesses before `set` can be said to happen before `isSet` - /// returns true or `wait`/`timedWait` return successfully. - pub fn set(re: *ResetEvent) void { - if (builtin.single_threaded) { - re.* = .is_set; - return; - } - if (@atomicRmw(ResetEvent, re, .Xchg, .is_set, .release) == .waiting) { - Futex.wake(@ptrCast(re), std.math.maxInt(u32)); - } - } - - /// Unmarks the ResetEvent as if `set` was never called. - /// - /// Assumes no threads are blocked in `wait` or `timedWait`. Concurrent - /// calls to `set`, `isSet` and `reset` are allowed. - pub fn reset(re: *ResetEvent) void { - if (builtin.single_threaded) { - re.* = .unset; - return; - } - @atomicStore(ResetEvent, re, .unset, .monotonic); - } -}; - const Thread = @This(); const Impl = if (native_os == .windows) WindowsThreadImpl @@ -1676,16 +1558,16 @@ test "setName, getName" { const io = testing.io; const Context = struct { - start_wait_event: ResetEvent = .unset, - test_done_event: ResetEvent = .unset, - thread_done_event: ResetEvent = .unset, + start_wait_event: Io.Event = .unset, + test_done_event: Io.Event = .unset, + thread_done_event: Io.Event = .unset, done: std.atomic.Value(bool) = std.atomic.Value(bool).init(false), thread: Thread = undefined, pub fn run(ctx: *@This()) !void { // Wait for the main thread to have set the thread field in the context. - ctx.start_wait_event.wait(); + try ctx.start_wait_event.wait(io); switch (native_os) { .windows => testThreadName(io, &ctx.thread) catch |err| switch (err) { @@ -1696,10 +1578,10 @@ test "setName, getName" { } // Signal our test is done - ctx.test_done_event.set(); + ctx.test_done_event.set(io); // wait for the thread to property exit - ctx.thread_done_event.wait(); + try ctx.thread_done_event.wait(io); } }; @@ -1707,8 +1589,8 @@ test "setName, getName" { var thread = try spawn(.{}, Context.run, .{&context}); context.thread = thread; - context.start_wait_event.set(); - context.test_done_event.wait(); + context.start_wait_event.set(io); + try context.test_done_event.wait(io); switch (native_os) { .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => { @@ -1722,31 +1604,32 @@ test "setName, getName" { else => try testThreadName(io, &thread), } - context.thread_done_event.set(); + context.thread_done_event.set(io); thread.join(); } test { _ = Futex; - _ = ResetEvent; _ = Mutex; _ = Semaphore; _ = Condition; _ = RwLock; } -fn testIncrementNotify(value: *usize, event: *ResetEvent) void { +fn testIncrementNotify(io: Io, value: *usize, event: *Io.Event) void { value.* += 1; - event.set(); + event.set(io); } test join { if (builtin.single_threaded) return error.SkipZigTest; + const io = testing.io; + var value: usize = 0; - var event: ResetEvent = .unset; + var event: Io.Event = .unset; - const thread = try Thread.spawn(.{}, testIncrementNotify, .{ &value, &event }); + const thread = try Thread.spawn(.{}, testIncrementNotify, .{ io, &value, &event }); thread.join(); try std.testing.expectEqual(value, 1); @@ -1755,13 +1638,15 @@ test join { test detach { if (builtin.single_threaded) return error.SkipZigTest; + const io = testing.io; + var value: usize = 0; - var event: ResetEvent = .unset; + var event: Io.Event = .unset; - const thread = try Thread.spawn(.{}, testIncrementNotify, .{ &value, &event }); + const thread = try Thread.spawn(.{}, testIncrementNotify, .{ io, &value, &event }); thread.detach(); - event.wait(); + try event.wait(io); try std.testing.expectEqual(value, 1); } @@ -1803,127 +1688,6 @@ fn testTls() !void { if (x != 1235) return error.TlsBadEndValue; } -test "ResetEvent smoke test" { - var event: ResetEvent = .unset; - try testing.expectEqual(false, event.isSet()); - - // make sure the event gets set - event.set(); - try testing.expectEqual(true, event.isSet()); - - // make sure the event gets unset again - event.reset(); - try testing.expectEqual(false, event.isSet()); - - // waits should timeout as there's no other thread to set the event - try testing.expectError(error.Timeout, event.timedWait(0)); - try testing.expectError(error.Timeout, event.timedWait(std.time.ns_per_ms)); - - // set the event again and make sure waits complete - event.set(); - event.wait(); - try event.timedWait(std.time.ns_per_ms); - try testing.expectEqual(true, event.isSet()); -} - -test "ResetEvent signaling" { - // This test requires spawning threads - if (builtin.single_threaded) { - return error.SkipZigTest; - } - - const Context = struct { - in: ResetEvent = .unset, - out: ResetEvent = .unset, - value: usize = 0, - - fn input(self: *@This()) !void { - // wait for the value to become 1 - self.in.wait(); - self.in.reset(); - try testing.expectEqual(self.value, 1); - - // bump the value and wake up output() - self.value = 2; - self.out.set(); - - // wait for output to receive 2, bump the value and wake us up with 3 - self.in.wait(); - self.in.reset(); - try testing.expectEqual(self.value, 3); - - // bump the value and wake up output() for it to see 4 - self.value = 4; - self.out.set(); - } - - fn output(self: *@This()) !void { - // start with 0 and bump the value for input to see 1 - try testing.expectEqual(self.value, 0); - self.value = 1; - self.in.set(); - - // wait for input to receive 1, bump the value to 2 and wake us up - self.out.wait(); - self.out.reset(); - try testing.expectEqual(self.value, 2); - - // bump the value to 3 for input to see (rhymes) - self.value = 3; - self.in.set(); - - // wait for input to bump the value to 4 and receive no more (rhymes) - self.out.wait(); - self.out.reset(); - try testing.expectEqual(self.value, 4); - } - }; - - var ctx = Context{}; - - const thread = try std.Thread.spawn(.{}, Context.output, .{&ctx}); - defer thread.join(); - - try ctx.input(); -} - -test "ResetEvent broadcast" { - // This test requires spawning threads - if (builtin.single_threaded) { - return error.SkipZigTest; - } - - const num_threads = 10; - const Barrier = struct { - event: ResetEvent = .unset, - counter: std.atomic.Value(usize) = std.atomic.Value(usize).init(num_threads), - - fn wait(self: *@This()) void { - if (self.counter.fetchSub(1, .acq_rel) == 1) { - self.event.set(); - } - } - }; - - const Context = struct { - start_barrier: Barrier = .{}, - finish_barrier: Barrier = .{}, - - fn run(self: *@This()) void { - self.start_barrier.wait(); - self.finish_barrier.wait(); - } - }; - - var ctx = Context{}; - var threads: [num_threads - 1]std.Thread = undefined; - - for (&threads) |*t| t.* = try std.Thread.spawn(.{}, Context.run, .{&ctx}); - defer for (threads) |t| t.join(); - - ctx.run(); -} - /// Configures the per-thread alternative signal stack requested by `std.options.signal_stack_size`. pub fn maybeAttachSignalStack() void { const size = std.options.signal_stack_size orelse return; diff --git a/lib/std/Thread/WaitGroup.zig b/lib/std/Thread/WaitGroup.zig deleted file mode 100644 index 8a9107192dac15e3541e0daf410d284e860f6e91..0000000000000000000000000000000000000000 --- a/lib/std/Thread/WaitGroup.zig +++ /dev/null @@ -1,87 +0,0 @@ -const builtin = @import("builtin"); -const std = @import("std"); -const assert = std.debug.assert; -const WaitGroup = @This(); - -const is_waiting: usize = 1 << 0; -const one_pending: usize = 1 << 1; - -state: std.atomic.Value(usize) = std.atomic.Value(usize).init(0), -event: std.Thread.ResetEvent = .unset, - -pub fn start(self: *WaitGroup) void { - return startStateless(&self.state); -} - -pub fn startStateless(state: *std.atomic.Value(usize)) void { - const prev_state = state.fetchAdd(one_pending, .monotonic); - assert((prev_state / one_pending) < (std.math.maxInt(usize) / one_pending)); -} - -pub fn startMany(self: *WaitGroup, n: usize) void { - const state = self.state.fetchAdd(one_pending * n, .monotonic); - assert((state / one_pending) < (std.math.maxInt(usize) / one_pending)); -} - -pub fn finish(self: *WaitGroup) void { - const state = self.state.fetchSub(one_pending, .acq_rel); - assert((state / one_pending) > 0); - - if (state == (one_pending | is_waiting)) { - self.event.set(); - } -} - -pub fn finishStateless(state: *std.atomic.Value(usize), event: *std.Thread.ResetEvent) void { - const prev_state = state.fetchSub(one_pending, .acq_rel); - assert((prev_state / one_pending) > 0); - if (prev_state == (one_pending | is_waiting)) event.set(); -} - -pub fn wait(wg: *WaitGroup) void { - return waitStateless(&wg.state, &wg.event); -} - -pub fn waitStateless(state: *std.atomic.Value(usize), event: *std.Thread.ResetEvent) void { - const prev_state = state.fetchAdd(is_waiting, .acquire); - assert(prev_state & is_waiting == 0); - if ((prev_state / one_pending) > 0) event.wait(); -} - -pub fn reset(self: *WaitGroup) void { - self.state.store(0, .monotonic); - self.event.reset(); -} - -pub fn isDone(wg: *WaitGroup) bool { - const state = wg.state.load(.acquire); - assert(state & is_waiting == 0); - - return (state / one_pending) == 0; -} - -pub fn value(wg: *WaitGroup) usize { - return wg.state.load(.monotonic) / one_pending; -} - -// Spawns a new thread for the task. This is appropriate when the callee -// delegates all work. -pub fn spawnManager( - wg: *WaitGroup, - comptime func: anytype, - args: anytype, -) void { - if (builtin.single_threaded) { - @call(.auto, func, args); - return; - } - const Manager = struct { - fn run(wg_inner: *WaitGroup, args_inner: @TypeOf(args)) void { - defer wg_inner.finish(); - @call(.auto, func, args_inner); - } - }; - wg.start(); - const t = std.Thread.spawn(.{}, Manager.run, .{ wg, args }) catch return Manager.run(wg, args); - t.detach(); -} diff --git a/lib/std/fs/test.zig b/lib/std/fs/test.zig index d0ec6b33e9cda1cbf6b5d27eeb1e9e6d1b831dda..977a285fb4cc122d6cc82da157844df143973494 100644 --- a/lib/std/fs/test.zig +++ b/lib/std/fs/test.zig @@ -1745,29 +1745,32 @@ test "open file with exclusive lock twice, make sure second lock waits" { errdefer file.close(io); const S = struct { - fn checkFn(inner_ctx: *TestContext, path: []const u8, started: *std.Thread.ResetEvent, locked: *std.Thread.ResetEvent) !void { - started.set(); + fn checkFn(inner_ctx: *TestContext, path: []const u8, started: *Io.Event, locked: *Io.Event) !void { + started.set(inner_ctx.io); const file1 = try inner_ctx.dir.createFile(inner_ctx.io, path, .{ .lock = .exclusive }); - locked.set(); + locked.set(inner_ctx.io); file1.close(inner_ctx.io); } }; - var started: std.Thread.ResetEvent = .unset; - var locked: std.Thread.ResetEvent = .unset; + var started: Io.Event = .unset; + var locked: Io.Event = .unset; const t = try std.Thread.spawn(.{}, S.checkFn, .{ ctx, filename, &started, &locked }); defer t.join(); // Wait for the spawned thread to start trying to acquire the exclusive file lock. // Then wait a bit to make sure that can't acquire it since we currently hold the file lock. - started.wait(); - try expectError(error.Timeout, locked.timedWait(10 * std.time.ns_per_ms)); + try started.wait(io); + try expectError(error.Timeout, locked.waitTimeout(io, .{ .duration = .{ + .raw = .fromMilliseconds(10), + .clock = .awake, + } })); // Release the file lock which should unlock the thread to lock it and set the locked event. file.close(io); - locked.wait(); + try locked.wait(io); } }.impl); } -- 2.54.0 From 83abd73801a6ab3125c1b0b8ba432be188c9e7ca Mon Sep 17 00:00:00 2001 From: Tom Winter Date: Wed, 3 Dec 2025 07:06:16 +0000 Subject: [PATCH 169/499] Windows: Support directory handle for cwd instead of string for Child.process This implementation is a bit of a hacky workaround, as we use a ntdll API to grab the full path of the directory handle. As far as I can tell this might be the only solution to the problem, as kernel32.CreateProcessW takes a directory path as a string only. I might be wrong though as haven't researched the problem thoroughly. --- lib/std/Io/Threaded.zig | 28 ++++++++++++++++++++++------ lib/std/process.zig | 3 +++ 2 files changed, 25 insertions(+), 6 deletions(-) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 0feec928fca854237041a140b644a38e0bd1c1e4..60be4a8d4c8211d9045e20ee291fab0541f441bb 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -15193,7 +15193,26 @@ fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) pro defer arena_allocator.deinit(); const arena = arena_allocator.allocator(); - const cwd_w = if (options.cwd) |cwd| try std.unicode.wtf8ToWtf16LeAllocZ(arena, cwd) else null; + const cwd_w = cwd_w: { + if (options.cwd_dir) |cwd_dir| { + var dir_path_buffer = try arena.alloc(u16, windows.PATH_MAX_WIDE + 1); + // TODO move GetFinalPathNameByHandle logic into std.Io.Threaded and add cancel checks + try Thread.checkCancel(); + const dir_path = try windows.GetFinalPathNameByHandle( + cwd_dir.handle, + .{}, + dir_path_buffer[0..windows.PATH_MAX_WIDE], + ); + dir_path_buffer[dir_path.len] = 0; + // Shrink the allocation down to just the path buffer + sentinel + dir_path_buffer = try arena.realloc(dir_path_buffer, dir_path.len + 1); + break :cwd_w dir_path_buffer[0..dir_path.len :0]; + } else if (options.cwd) |cwd| { + break :cwd_w try std.unicode.wtf8ToWtf16LeAllocZ(arena, cwd); + } else { + break :cwd_w null; + } + }; const cwd_w_ptr = if (cwd_w) |cwd| cwd.ptr else null; const maybe_envp_buf = if (options.environ_map) |environ_map| try environ_map.createBlockWindows(arena) else null; @@ -15204,16 +15223,13 @@ fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) pro // The cwd provided by options is in effect when choosing the executable // path to match POSIX semantics. - var cwd_path_w_needs_free = false; const cwd_path_w = x: { // If the app name is absolute, then we need to use its dirname as the cwd if (app_name_is_absolute) { - cwd_path_w_needs_free = true; const dir = Dir.path.dirname(app_name_wtf8).?; break :x try std.unicode.wtf8ToWtf16LeAllocZ(arena, dir); - } else if (options.cwd) |cwd| { - cwd_path_w_needs_free = true; - break :x try std.unicode.wtf8ToWtf16LeAllocZ(arena, cwd); + } else if (cwd_w) |cwd| { + break :x cwd; } else { break :x &[_:0]u16{}; // empty for cwd } diff --git a/lib/std/process.zig b/lib/std/process.zig index 3b5a0ecebd5e38ced3748bd0a012adc6d1d6d0a2..d4e5e689316db4d356c25362bfae6b0aea844f18 100644 --- a/lib/std/process.zig +++ b/lib/std/process.zig @@ -359,6 +359,9 @@ pub const SpawnError = error{ /// children of the calling process and the child had already performed an /// image replacement. ProcessAlreadyExec, + /// On Windows, the volume does not contain a recognized file system. File + /// system drivers might not be loaded, or the volume may be corrupt. + UnrecognizedVolume, } || Io.Dir.PathNameError || Io.Cancelable || Io.UnexpectedError; pub const SpawnOptions = struct { -- 2.54.0 From 05346e123bc5dbc20a7a60655b06b55030a84f47 Mon Sep 17 00:00:00 2001 From: Ryan Liptak Date: Sun, 7 Dec 2025 02:56:41 -0800 Subject: [PATCH 170/499] Add process.Child.Cwd, use it for cwd and remove cwd_dir field The user must now explicitly choose between inheriting the current CWD, passing a path for the CWD, or passing a Dir for the CWD. --- lib/compiler/std-docs.zig | 10 +++--- lib/std/Build.zig | 4 +-- lib/std/Build/Step.zig | 28 ++++++++------- lib/std/Build/Step/InstallArtifact.zig | 2 +- lib/std/Build/Step/Run.zig | 4 +-- lib/std/Build/WebServer.zig | 10 +++--- lib/std/Io/Threaded.zig | 48 +++++++++++++++----------- lib/std/process.zig | 13 ++----- lib/std/process/Child.zig | 11 ++++++ test/standalone/windows_paths/test.zig | 42 +++++++++++----------- test/standalone/windows_spawn/main.zig | 14 ++++++-- tools/doctest.zig | 12 +++---- tools/incr-check.zig | 9 ++--- 13 files changed, 112 insertions(+), 95 deletions(-) diff --git a/lib/compiler/std-docs.zig b/lib/compiler/std-docs.zig index 72a8c74ff2ce89925f087a32517453b612226b43..c9ce1700519bcb31692cd4623ce81801d7559a83 100644 --- a/lib/compiler/std-docs.zig +++ b/lib/compiler/std-docs.zig @@ -398,7 +398,7 @@ fn buildWasmBinary( if (code != 0) { std.log.err( "the following command exited with error code {d}:\n{s}", - .{ code, try std.Build.Step.allocPrintCmd(arena, null, null, argv.items) }, + .{ code, try std.Build.Step.allocPrintCmd(arena, .inherit, null, argv.items) }, ); return error.WasmCompilationFailed; } @@ -406,14 +406,14 @@ fn buildWasmBinary( .signal => |sig| { std.log.err( "the following command terminated with signal {t}:\n{s}", - .{ sig, try std.Build.Step.allocPrintCmd(arena, null, null, argv.items) }, + .{ sig, try std.Build.Step.allocPrintCmd(arena, .inherit, null, argv.items) }, ); return error.WasmCompilationFailed; }, .stopped, .unknown => { std.log.err( "the following command terminated unexpectedly:\n{s}", - .{try std.Build.Step.allocPrintCmd(arena, null, null, argv.items)}, + .{try std.Build.Step.allocPrintCmd(arena, .inherit, null, argv.items)}, ); return error.WasmCompilationFailed; }, @@ -423,14 +423,14 @@ fn buildWasmBinary( try result_error_bundle.renderToStderr(io, .{}, .auto); std.log.err("the following command failed with {d} compilation errors:\n{s}", .{ result_error_bundle.errorMessageCount(), - try std.Build.Step.allocPrintCmd(arena, null, null, argv.items), + try std.Build.Step.allocPrintCmd(arena, .inherit, null, argv.items), }); return error.WasmCompilationFailed; } return result orelse { std.log.err("child process failed to report result\n{s}", .{ - try std.Build.Step.allocPrintCmd(arena, null, null, argv.items), + try std.Build.Step.allocPrintCmd(arena, .inherit, null, argv.items), }); return error.WasmCompilationFailed; }; diff --git a/lib/std/Build.zig b/lib/std/Build.zig index 242949605cb5af1d4eb35e99506d13a620e73514..4517ce75b04fb4e2fec2d101bbfccdee92109835 100644 --- a/lib/std/Build.zig +++ b/lib/std/Build.zig @@ -1868,7 +1868,7 @@ pub fn runAllowFail( const io = graph.io; const max_output_size = 400 * 1024; - try Step.handleVerbose2(b, null, &graph.environ_map, argv); + try Step.handleVerbose2(b, .inherit, &graph.environ_map, argv); var child = try std.process.spawn(io, .{ .argv = argv, @@ -1911,7 +1911,7 @@ pub fn run(b: *Build, argv: []const []const u8) []u8 { var code: u8 = undefined; return b.runAllowFail(argv, &code, .inherit) catch |err| process.fatal( "the following command failed with {t}:\n{s}", - .{ err, Step.allocPrintCmd(b.allocator, null, null, argv) catch @panic("OOM") }, + .{ err, Step.allocPrintCmd(b.allocator, .inherit, null, argv) catch @panic("OOM") }, ); } diff --git a/lib/std/Build/Step.zig b/lib/std/Build/Step.zig index cfc263b7701e34f0ef762ae3f2524d0cac850f37..2f5e4316d47cb32e1dee00c79bee977333b3bc79 100644 --- a/lib/std/Build/Step.zig +++ b/lib/std/Build/Step.zig @@ -350,10 +350,10 @@ pub fn captureChildProcess( // If an error occurs, it's happened in this command: assert(s.result_failed_command == null); - s.result_failed_command = try allocPrintCmd(gpa, null, null, argv); + s.result_failed_command = try allocPrintCmd(gpa, .inherit, null, argv); try handleChildProcUnsupported(s); - try handleVerbose(s.owner, null, argv); + try handleVerbose(s.owner, .inherit, argv); const result = std.process.run(arena, io, .{ .argv = argv, @@ -410,7 +410,7 @@ pub fn evalZigProcess( // If an error occurs, it's happened in this command: assert(s.result_failed_command == null); - s.result_failed_command = try allocPrintCmd(gpa, null, null, argv); + s.result_failed_command = try allocPrintCmd(gpa, .inherit, null, argv); if (s.getZigProcess()) |zp| update: { assert(watch); @@ -449,7 +449,7 @@ pub fn evalZigProcess( assert(argv.len != 0); try handleChildProcUnsupported(s); - try handleVerbose(s.owner, null, argv); + try handleVerbose(s.owner, .inherit, argv); const zp = try gpa.create(ZigProcess); defer if (!watch) gpa.destroy(zp); @@ -515,7 +515,7 @@ pub fn installFile(s: *Step, src_lazy_path: Build.LazyPath, dest_path: []const u const b = s.owner; const io = b.graph.io; const src_path = src_lazy_path.getPath3(b, s); - try handleVerbose(b, null, &.{ "install", "-C", b.fmt("{f}", .{src_path}), dest_path }); + try handleVerbose(b, .inherit, &.{ "install", "-C", b.fmt("{f}", .{src_path}), dest_path }); return Io.Dir.updateFile(src_path.root_dir.handle, io, src_path.sub_path, .cwd(), dest_path, .{}) catch |err| return s.fail("unable to update file from '{f}' to '{s}': {t}", .{ src_path, dest_path, err }); } @@ -524,7 +524,7 @@ pub fn installFile(s: *Step, src_lazy_path: Build.LazyPath, dest_path: []const u pub fn installDir(s: *Step, dest_path: []const u8) !Io.Dir.CreatePathStatus { const b = s.owner; const io = b.graph.io; - try handleVerbose(b, null, &.{ "install", "-d", dest_path }); + try handleVerbose(b, .inherit, &.{ "install", "-d", dest_path }); return Io.Dir.cwd().createDirPathStatus(io, dest_path, .default_dir) catch |err| return s.fail("unable to create dir '{s}': {t}", .{ dest_path, err }); } @@ -700,15 +700,15 @@ fn sendMessage(io: Io, file: Io.File, tag: std.zig.Client.Message.Tag) !void { pub fn handleVerbose( b: *Build, - opt_cwd: ?[]const u8, + cwd: std.process.Child.Cwd, argv: []const []const u8, ) error{OutOfMemory}!void { - return handleVerbose2(b, opt_cwd, null, argv); + return handleVerbose2(b, cwd, null, argv); } pub fn handleVerbose2( b: *Build, - opt_cwd: ?[]const u8, + cwd: std.process.Child.Cwd, opt_env: ?*const std.process.Environ.Map, argv: []const []const u8, ) error{OutOfMemory}!void { @@ -716,7 +716,7 @@ pub fn handleVerbose2( const graph = b.graph; // Intention of verbose is to print all sub-process command lines to // stderr before spawning them. - const text = try allocPrintCmd(b.allocator, opt_cwd, if (opt_env) |env| .{ + const text = try allocPrintCmd(b.allocator, cwd, if (opt_env) |env| .{ .child = env, .parent = &graph.environ_map, } else null, argv); @@ -751,7 +751,7 @@ pub fn handleChildProcessTerm(s: *Step, term: std.process.Child.Term) error{ Mak pub fn allocPrintCmd( gpa: Allocator, - opt_cwd: ?[]const u8, + cwd: std.process.Child.Cwd, opt_env: ?struct { child: *const std.process.Environ.Map, parent: *const std.process.Environ.Map, @@ -796,7 +796,11 @@ pub fn allocPrintCmd( var aw: Io.Writer.Allocating = .init(gpa); defer aw.deinit(); const writer = &aw.writer; - if (opt_cwd) |cwd| writer.print("cd {s} && ", .{cwd}) catch return error.OutOfMemory; + switch (cwd) { + .inherit => {}, + .path => |path| writer.print("cd {s} && ", .{path}) catch return error.OutOfMemory, + .dir => @panic("TODO"), + } if (opt_env) |env| { var it = env.child.iterator(); while (it.next()) |entry| { diff --git a/lib/std/Build/Step/InstallArtifact.zig b/lib/std/Build/Step/InstallArtifact.zig index 019d465f01d6b2784e337ed410bd2b073bffb210..c3c9d6c85395358a91eddfc31b9f0b0872b77fd7 100644 --- a/lib/std/Build/Step/InstallArtifact.zig +++ b/lib/std/Build/Step/InstallArtifact.zig @@ -187,7 +187,7 @@ fn make(step: *Step, options: Step.MakeOptions) !void { const full_dest_path = b.pathJoin(&.{ full_h_prefix, entry.path }); switch (entry.kind) { .directory => { - try Step.handleVerbose(b, null, &.{ "install", "-d", full_dest_path }); + try Step.handleVerbose(b, .inherit, &.{ "install", "-d", full_dest_path }); const p = try step.installDir(full_dest_path); all_cached = all_cached and p == .existed; }, diff --git a/lib/std/Build/Step/Run.zig b/lib/std/Build/Step/Run.zig index 68d0ec480c77e7099a8230dd422ad0b21f155bf9..3f5df9f2ae351db03282648d3ce38c488f8b3288 100644 --- a/lib/std/Build/Step/Run.zig +++ b/lib/std/Build/Step/Run.zig @@ -1227,7 +1227,7 @@ fn runCommand( const gpa = options.gpa; const io = b.graph.io; - const cwd: ?[]const u8 = if (run.cwd) |lazy_cwd| lazy_cwd.getPath2(b, step) else null; + const cwd: process.Child.Cwd = if (run.cwd) |lazy_cwd| .{ .path = lazy_cwd.getPath2(b, step) } else .inherit; try step.handleChildProcUnsupported(); try Step.handleVerbose2(step.owner, cwd, run.environ_map, argv); @@ -1549,7 +1549,7 @@ fn spawnChildAndCollect( assert(run.stdio == .zig_test); } - const child_cwd = if (run.cwd) |lazy_cwd| lazy_cwd.getPath2(b, &run.step) else null; + const child_cwd: process.Child.Cwd = if (run.cwd) |lazy_cwd| .{ .path = lazy_cwd.getPath2(b, &run.step) } else .inherit; // If an error occurs, it's caused by this command: assert(run.step.result_failed_command == null); diff --git a/lib/std/Build/WebServer.zig b/lib/std/Build/WebServer.zig index 1f380b6c50d4f1aae6066362f37a4e2f6cd9c627..7205291400ab81d6a693f6361ebc6c2975ce9a40 100644 --- a/lib/std/Build/WebServer.zig +++ b/lib/std/Build/WebServer.zig @@ -652,7 +652,7 @@ fn buildClientWasm(ws: *WebServer, arena: Allocator, optimize: std.builtin.Optim if (code != 0) { log.err( "the following command exited with error code {d}:\n{s}", - .{ code, try Build.Step.allocPrintCmd(arena, null, null, argv.items) }, + .{ code, try Build.Step.allocPrintCmd(arena, .inherit, null, argv.items) }, ); return error.WasmCompilationFailed; } @@ -660,14 +660,14 @@ fn buildClientWasm(ws: *WebServer, arena: Allocator, optimize: std.builtin.Optim .signal => |sig| { log.err( "the following command terminated with signal {t}:\n{s}", - .{ sig, try Build.Step.allocPrintCmd(arena, null, null, argv.items) }, + .{ sig, try Build.Step.allocPrintCmd(arena, .inherit, null, argv.items) }, ); return error.WasmCompilationFailed; }, .stopped, .unknown => { log.err( "the following command terminated unexpectedly:\n{s}", - .{try Build.Step.allocPrintCmd(arena, null, null, argv.items)}, + .{try Build.Step.allocPrintCmd(arena, .inherit, null, argv.items)}, ); return error.WasmCompilationFailed; }, @@ -677,14 +677,14 @@ fn buildClientWasm(ws: *WebServer, arena: Allocator, optimize: std.builtin.Optim try result_error_bundle.renderToStderr(io, .{}, .auto); log.err("the following command failed with {d} compilation errors:\n{s}", .{ result_error_bundle.errorMessageCount(), - try Build.Step.allocPrintCmd(arena, null, null, argv.items), + try Build.Step.allocPrintCmd(arena, .inherit, null, argv.items), }); return error.WasmCompilationFailed; } const base_path = result orelse { log.err("child process failed to report result\n{s}", .{ - try Build.Step.allocPrintCmd(arena, null, null, argv.items), + try Build.Step.allocPrintCmd(arena, .inherit, null, argv.items), }); return error.WasmCompilationFailed; }; diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 60be4a8d4c8211d9045e20ee291fab0541f441bb..298c3c167f6e6c044d41cf1e83e03f3664e54df6 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -14606,10 +14606,14 @@ fn spawnPosix(t: *Threaded, options: process.SpawnOptions) process.SpawnError!Sp setUpChildIo(options.stdout, stdout_pipe[1], posix.STDOUT_FILENO, dev_null_fd) catch |err| forkBail(ep1, err); setUpChildIo(options.stderr, stderr_pipe[1], posix.STDERR_FILENO, dev_null_fd) catch |err| forkBail(ep1, err); - if (options.cwd_dir) |cwd| { - fchdir(cwd.handle) catch |err| forkBail(ep1, err); - } else if (options.cwd) |cwd| { - chdir(cwd) catch |err| forkBail(ep1, err); + switch (options.cwd) { + .inherit => {}, + .dir => |cwd| { + fchdir(cwd.handle) catch |err| forkBail(ep1, err); + }, + .path => |cwd| { + chdir(cwd) catch |err| forkBail(ep1, err); + }, } // Must happen after fchdir above, the cwd file descriptor might be @@ -15194,23 +15198,25 @@ fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) pro const arena = arena_allocator.allocator(); const cwd_w = cwd_w: { - if (options.cwd_dir) |cwd_dir| { - var dir_path_buffer = try arena.alloc(u16, windows.PATH_MAX_WIDE + 1); - // TODO move GetFinalPathNameByHandle logic into std.Io.Threaded and add cancel checks - try Thread.checkCancel(); - const dir_path = try windows.GetFinalPathNameByHandle( - cwd_dir.handle, - .{}, - dir_path_buffer[0..windows.PATH_MAX_WIDE], - ); - dir_path_buffer[dir_path.len] = 0; - // Shrink the allocation down to just the path buffer + sentinel - dir_path_buffer = try arena.realloc(dir_path_buffer, dir_path.len + 1); - break :cwd_w dir_path_buffer[0..dir_path.len :0]; - } else if (options.cwd) |cwd| { - break :cwd_w try std.unicode.wtf8ToWtf16LeAllocZ(arena, cwd); - } else { - break :cwd_w null; + switch (options.cwd) { + .inherit => break :cwd_w null, + .dir => |cwd_dir| { + var dir_path_buffer = try arena.alloc(u16, windows.PATH_MAX_WIDE + 1); + // TODO move GetFinalPathNameByHandle logic into std.Io.Threaded and add cancel checks + try Thread.checkCancel(); + const dir_path = try windows.GetFinalPathNameByHandle( + cwd_dir.handle, + .{}, + dir_path_buffer[0..windows.PATH_MAX_WIDE], + ); + dir_path_buffer[dir_path.len] = 0; + // Shrink the allocation down to just the path buffer + sentinel + dir_path_buffer = try arena.realloc(dir_path_buffer, dir_path.len + 1); + break :cwd_w dir_path_buffer[0..dir_path.len :0]; + }, + .path => |cwd| { + break :cwd_w try std.unicode.wtf8ToWtf16LeAllocZ(arena, cwd); + }, } }; const cwd_w_ptr = if (cwd_w) |cwd| cwd.ptr else null; diff --git a/lib/std/process.zig b/lib/std/process.zig index d4e5e689316db4d356c25362bfae6b0aea844f18..d09239dbc2e0de28b0fd9f51a886b241fdbf4b21 100644 --- a/lib/std/process.zig +++ b/lib/std/process.zig @@ -368,11 +368,7 @@ pub const SpawnOptions = struct { argv: []const []const u8, /// Set to change the current working directory when spawning the child process. - cwd: ?[]const u8 = null, - /// Set to change the current working directory when spawning the child process. - /// This is not yet implemented for Windows. See https://github.com/ziglang/zig/issues/5190 - /// Once that is done, `cwd` will be deprecated in favor of this field. - cwd_dir: ?Io.Dir = null, + cwd: Child.Cwd = .inherit, /// Replaces the child environment when provided. The PATH value from here /// is not used to resolve `argv[0]`; that resolution always uses parent /// environment. @@ -468,11 +464,7 @@ pub const RunOptions = struct { reserve_amount: usize = 64, /// Set to change the current working directory when spawning the child process. - cwd: ?[]const u8 = null, - /// Set to change the current working directory when spawning the child process. - /// This is not yet implemented for Windows. See https://github.com/ziglang/zig/issues/5190 - /// Once that is done, `cwd` will be deprecated in favor of this field. - cwd_dir: ?Io.Dir = null, + cwd: Child.Cwd = .inherit, /// Replaces the child environment when provided. The PATH value from here /// is not used to resolve `argv[0]`; that resolution always uses parent /// environment. @@ -506,7 +498,6 @@ pub fn run(gpa: Allocator, io: Io, options: RunOptions) RunError!RunResult { var child = try spawn(io, .{ .argv = options.argv, .cwd = options.cwd, - .cwd_dir = options.cwd_dir, .environ_map = options.environ_map, .expand_arg0 = options.expand_arg0, .progress_node = options.progress_node, diff --git a/lib/std/process/Child.zig b/lib/std/process/Child.zig index c87d221a95c4ecdff81b467f8db72495a32a642f..7ce3143b362de6f5f837f7e513304d54b47fea4b 100644 --- a/lib/std/process/Child.zig +++ b/lib/std/process/Child.zig @@ -98,6 +98,17 @@ pub const Term = union(enum) { unknown: u32, }; +pub const Cwd = union(enum) { + /// CWD of the child is the same as the current CWD. + inherit, + /// On POSIX systems, `fchdir` is called after `fork` using this handle. + /// On Windows, the path is inferred from the provided handle and that path is used when calling `CreateProcessW`. + dir: Io.Dir, + /// On POSIX systems, `chdir` is called after `fork` using this path. + /// On Windows, this path is used when calling `CreateProcessW`. + path: []const u8, +}; + /// Requests for the operating system to forcibly terminate the child process, /// then blocks until it terminates, then cleans up all resources. /// diff --git a/test/standalone/windows_paths/test.zig b/test/standalone/windows_paths/test.zig index 1170de47ac7245480a48585a20089295d4cd82a4..36f2a8be0e7659d783db4fb64ea79ed3c87c6bed 100644 --- a/test/standalone/windows_paths/test.zig +++ b/test/standalone/windows_paths/test.zig @@ -32,39 +32,39 @@ pub fn main(init: std.process.Init) !void { // With the special =X: environment variable set, drive-relative paths that // don't match the CWD's drive letter are resolved against that env var. - try checkRelative(arena, io, "..\\..\\bar", &.{ exe_path, drive_rel, drive_abs }, null, &alt_drive_env_map); - try checkRelative(arena, io, "..\\baz\\foo", &.{ exe_path, drive_abs, drive_rel }, null, &alt_drive_env_map); + try checkRelative(arena, io, "..\\..\\bar", &.{ exe_path, drive_rel, drive_abs }, &alt_drive_env_map); + try checkRelative(arena, io, "..\\baz\\foo", &.{ exe_path, drive_abs, drive_rel }, &alt_drive_env_map); // Without that environment variable set, drive-relative paths that don't match the // CWD's drive letter are resolved against the root of the drive. - try checkRelative(arena, io, "..\\bar", &.{ exe_path, drive_rel, drive_abs }, null, &empty_env); - try checkRelative(arena, io, "..\\foo", &.{ exe_path, drive_abs, drive_rel }, null, &empty_env); + try checkRelative(arena, io, "..\\bar", &.{ exe_path, drive_rel, drive_abs }, &empty_env); + try checkRelative(arena, io, "..\\foo", &.{ exe_path, drive_abs, drive_rel }, &empty_env); // Bare drive-relative path with no components - try checkRelative(arena, io, "bar", &.{ exe_path, drive_rel[0..2], drive_abs }, null, &empty_env); - try checkRelative(arena, io, "..", &.{ exe_path, drive_abs, drive_rel[0..2] }, null, &empty_env); + try checkRelative(arena, io, "bar", &.{ exe_path, drive_rel[0..2], drive_abs }, &empty_env); + try checkRelative(arena, io, "..", &.{ exe_path, drive_abs, drive_rel[0..2] }, &empty_env); // Bare drive-relative path with no components, drive-CWD set - try checkRelative(arena, io, "..\\bar", &.{ exe_path, drive_rel[0..2], drive_abs }, null, &alt_drive_env_map); - try checkRelative(arena, io, "..\\baz", &.{ exe_path, drive_abs, drive_rel[0..2] }, null, &alt_drive_env_map); + try checkRelative(arena, io, "..\\bar", &.{ exe_path, drive_rel[0..2], drive_abs }, &alt_drive_env_map); + try checkRelative(arena, io, "..\\baz", &.{ exe_path, drive_abs, drive_rel[0..2] }, &alt_drive_env_map); // Bare drive-relative path relative to the CWD should be equivalent if drive-CWD is set - try checkRelative(arena, io, "", &.{ exe_path, alt_drive_cwd, drive_rel[0..2] }, null, &alt_drive_env_map); - try checkRelative(arena, io, "", &.{ exe_path, drive_rel[0..2], alt_drive_cwd }, null, &alt_drive_env_map); + try checkRelative(arena, io, "", &.{ exe_path, alt_drive_cwd, drive_rel[0..2] }, &alt_drive_env_map); + try checkRelative(arena, io, "", &.{ exe_path, drive_rel[0..2], alt_drive_cwd }, &alt_drive_env_map); // Bare drive-relative should always be equivalent to itself - try checkRelative(arena, io, "", &.{ exe_path, drive_rel[0..2], drive_rel[0..2] }, null, &alt_drive_env_map); - try checkRelative(arena, io, "", &.{ exe_path, drive_rel[0..2], drive_rel[0..2] }, null, &alt_drive_env_map); - try checkRelative(arena, io, "", &.{ exe_path, drive_rel[0..2], drive_rel[0..2] }, null, &empty_env); - try checkRelative(arena, io, "", &.{ exe_path, drive_rel[0..2], drive_rel[0..2] }, null, &empty_env); + try checkRelative(arena, io, "", &.{ exe_path, drive_rel[0..2], drive_rel[0..2] }, &alt_drive_env_map); + try checkRelative(arena, io, "", &.{ exe_path, drive_rel[0..2], drive_rel[0..2] }, &alt_drive_env_map); + try checkRelative(arena, io, "", &.{ exe_path, drive_rel[0..2], drive_rel[0..2] }, &empty_env); + try checkRelative(arena, io, "", &.{ exe_path, drive_rel[0..2], drive_rel[0..2] }, &empty_env); } if (parsed_cwd_path.kind == .unc_absolute) { const drive_abs_path = try std.fmt.allocPrint(arena, "{c}:\\foo\\bar", .{alt_drive_letter}); { - try checkRelative(arena, io, drive_abs_path, &.{ exe_path, cwd_path, drive_abs_path }, null, &empty_env); - try checkRelative(arena, io, cwd_path, &.{ exe_path, drive_abs_path, cwd_path }, null, &empty_env); + try checkRelative(arena, io, drive_abs_path, &.{ exe_path, cwd_path, drive_abs_path }, &empty_env); + try checkRelative(arena, io, cwd_path, &.{ exe_path, drive_abs_path, cwd_path }, &empty_env); } } else if (parsed_cwd_path.kind == .drive_absolute) { const cur_drive_letter = parsed_cwd_path.root[0]; @@ -72,14 +72,14 @@ pub fn main(init: std.process.Init) !void { const unc_cwd = try std.fmt.allocPrint(arena, "\\\\127.0.0.1\\{c}$\\{s}", .{ cur_drive_letter, path_beyond_root }); { - try checkRelative(arena, io, cwd_path, &.{ exe_path, unc_cwd, cwd_path }, null, &empty_env); - try checkRelative(arena, io, unc_cwd, &.{ exe_path, cwd_path, unc_cwd }, null, &empty_env); + try checkRelative(arena, io, cwd_path, &.{ exe_path, unc_cwd, cwd_path }, &empty_env); + try checkRelative(arena, io, unc_cwd, &.{ exe_path, cwd_path, unc_cwd }, &empty_env); } { const drive_abs = cwd_path; const drive_rel = parsed_cwd_path.root[0..2]; - try checkRelative(arena, io, "", &.{ exe_path, drive_abs, drive_rel }, null, &empty_env); - try checkRelative(arena, io, "", &.{ exe_path, drive_rel, drive_abs }, null, &empty_env); + try checkRelative(arena, io, "", &.{ exe_path, drive_abs, drive_rel }, &empty_env); + try checkRelative(arena, io, "", &.{ exe_path, drive_rel, drive_abs }, &empty_env); } } else { return error.UnexpectedPathType; @@ -91,12 +91,10 @@ fn checkRelative( io: Io, expected_stdout: []const u8, argv: []const []const u8, - cwd: ?[]const u8, environ_map: ?*const std.process.Environ.Map, ) !void { const result = try std.process.run(allocator, io, .{ .argv = argv, - .cwd = cwd, .environ_map = environ_map, }); defer allocator.free(result.stdout); diff --git a/test/standalone/windows_spawn/main.zig b/test/standalone/windows_spawn/main.zig index 4b37e3cf6ee28066e2424725d5e0412ab5f6764d..18c9a68c57b9194e72c8f8b29859dd382307cb65 100644 --- a/test/standalone/windows_spawn/main.zig +++ b/test/standalone/windows_spawn/main.zig @@ -207,10 +207,20 @@ fn testExecError(err: anyerror, gpa: Allocator, io: Io, command: []const u8) !vo } fn testExec(gpa: Allocator, io: Io, command: []const u8, expected_stdout: []const u8) !void { - return testExecWithCwd(gpa, io, command, null, expected_stdout); + return testExecWithCwdInner(gpa, io, command, .inherit, expected_stdout); } -fn testExecWithCwd(gpa: Allocator, io: Io, command: []const u8, cwd: ?[]const u8, expected_stdout: []const u8) !void { +fn testExecWithCwd(gpa: Allocator, io: Io, command: []const u8, cwd: []const u8, expected_stdout: []const u8) !void { + // Test by passing CWD as both a path and a Dir + try testExecWithCwdInner(gpa, io, command, .{ .path = cwd }, expected_stdout); + + var cwd_dir = try Io.Dir.cwd().openDir(io, cwd, .{}); + defer cwd_dir.close(io); + + try testExecWithCwdInner(gpa, io, command, .{ .dir = cwd_dir }, expected_stdout); +} + +fn testExecWithCwdInner(gpa: Allocator, io: Io, command: []const u8, cwd: std.process.Child.Cwd, expected_stdout: []const u8) !void { const result = try std.process.run(gpa, io, .{ .argv = &[_][]const u8{command}, .cwd = cwd, diff --git a/tools/doctest.zig b/tools/doctest.zig index 97a9a0be3f27e079d5028403749ba68bb3b7240f..0e29b2b983f6f5915fae5f9ae0fc759ba1abcdfa 100644 --- a/tools/doctest.zig +++ b/tools/doctest.zig @@ -199,7 +199,7 @@ fn printOutput( if (expected_outcome == .build_fail) { const result = try process.run(arena, io, .{ .argv = build_args.items, - .cwd = tmp_dir_path, + .cwd = .{ .path = tmp_dir_path }, .environ_map = environ_map, }); switch (result.term) { @@ -255,7 +255,7 @@ fn printOutput( const result = try process.run(arena, io, .{ .argv = run_args, .environ_map = environ_map, - .cwd = tmp_dir_path, + .cwd = .{ .path = tmp_dir_path }, }); switch (result.term) { .exited => |exit_code| { @@ -373,7 +373,7 @@ fn printOutput( const result = try process.run(arena, io, .{ .argv = test_args.items, .environ_map = environ_map, - .cwd = tmp_dir_path, + .cwd = .{ .path = tmp_dir_path }, }); switch (result.term) { .exited => |exit_code| { @@ -428,7 +428,7 @@ fn printOutput( const result = try process.run(arena, io, .{ .argv = test_args.items, .environ_map = environ_map, - .cwd = tmp_dir_path, + .cwd = .{ .path = tmp_dir_path }, }); switch (result.term) { .exited => |exit_code| { @@ -503,7 +503,7 @@ fn printOutput( const result = try process.run(arena, io, .{ .argv = build_args.items, .environ_map = environ_map, - .cwd = tmp_dir_path, + .cwd = .{ .path = tmp_dir_path }, }); switch (result.term) { .exited => |exit_code| { @@ -1126,7 +1126,7 @@ fn run( const result = try process.run(allocator, io, .{ .argv = args, .environ_map = environ_map, - .cwd = cwd, + .cwd = .{ .path = cwd }, }); switch (result.term) { .exited => |exit_code| { diff --git a/tools/incr-check.zig b/tools/incr-check.zig index 840faf6f275aad2dc662183bd162698df443ee2d..171af570361c4986b3ddd75ac909d2ba6b1a7551 100644 --- a/tools/incr-check.zig +++ b/tools/incr-check.zig @@ -202,8 +202,7 @@ pub fn main(init: std.process.Init) !void { .stdout = .pipe, .stderr = .pipe, .progress_node = zig_prog_node, - .cwd_dir = tmp_dir, - .cwd = tmp_dir_path, + .cwd = .{ .path = tmp_dir_path }, }); defer child.kill(io); @@ -533,8 +532,7 @@ const Eval = struct { const result = std.process.run(eval.arena, io, .{ .argv = argv, - .cwd_dir = eval.tmp_dir, - .cwd = eval.tmp_dir_path, + .cwd = .{ .path = eval.tmp_dir_path }, }) catch |err| { if (is_foreign) { // Chances are the foreign executor isn't available. Skip this evaluation. @@ -626,8 +624,7 @@ const Eval = struct { const result = std.process.run(eval.arena, eval.io, .{ .argv = eval.cc_child_args.items, - .cwd_dir = eval.tmp_dir, - .cwd = eval.tmp_dir_path, + .cwd = .{ .path = eval.tmp_dir_path }, .progress_node = child_prog_node, }) catch |err| { eval.fatal("failed to spawn zig cc for '{s}': {t}", .{ c_path, err }); -- 2.54.0 From 633eb247ab819d7120fea2e0178100cc8fca8ac3 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sun, 1 Feb 2026 14:57:27 -0800 Subject: [PATCH 171/499] std.Io.Event: fix single-threaded implementation --- lib/std/Io.zig | 2 +- lib/std/Io/Threaded.zig | 7 +++++-- 2 files changed, 6 insertions(+), 3 deletions(-) diff --git a/lib/std/Io.zig b/lib/std/Io.zig index bb9610c0904b670b1eff8933efac16bd3138eca2..ea182c680390e17133749ec28ddd520c32b5c21d 100644 --- a/lib/std/Io.zig +++ b/lib/std/Io.zig @@ -1467,7 +1467,7 @@ pub const Event = enum(u32) { pub fn waitTimeout(event: *Event, io: Io, timeout: Timeout) WaitTimeoutError!void { if (@cmpxchgStrong(Event, event, .unset, .waiting, .acquire, .acquire)) |prev| switch (prev) { .unset => unreachable, - .waiting => assert(!builtin.single_threaded), // invalid state + .waiting => {}, .is_set => return, }; errdefer { diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 46ff3a7f38df43edaa06cca3fad6b043d913b95f..ca7127128766286a4e9b0dc9bae758239b8a4da0 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -2461,7 +2461,10 @@ fn cancel( } fn futexWait(userdata: ?*anyopaque, ptr: *const u32, expected: u32, timeout: Io.Timeout) Io.Cancelable!void { - if (builtin.single_threaded) unreachable; // Deadlock. + if (builtin.single_threaded) { + assert(timeout != .none); // Deadlock. + return; + } const t: *Threaded = @ptrCast(@alignCast(userdata)); const t_io = ioBasic(t); const timeout_ns: ?u64 = ns: { @@ -2479,7 +2482,7 @@ fn futexWaitUncancelable(userdata: ?*anyopaque, ptr: *const u32, expected: u32) } fn futexWake(userdata: ?*anyopaque, ptr: *const u32, max_waiters: u32) void { - if (builtin.single_threaded) unreachable; // Nothing to wake up. + if (builtin.single_threaded) return; // Nothing to wake up. const t: *Threaded = @ptrCast(@alignCast(userdata)); _ = t; Thread.futexWake(ptr, max_waiters); -- 2.54.0 From 1cd3af43fd74a8481b35d58c351ae056c1cba362 Mon Sep 17 00:00:00 2001 From: Krzysztof Antonowski Date: Mon, 2 Feb 2026 22:19:19 +0100 Subject: [PATCH 172/499] std.Io.Threaded: implement CPU-based clocks on Windows (#31093) Reviewed-on: https://codeberg.org/ziglang/zig/pulls/31093 Co-authored-by: Krzysztof Antonowski Co-committed-by: Krzysztof Antonowski --- lib/std/Io/Threaded.zig | 37 ++++++++++++++++++++++++++++++++++--- lib/std/os/windows.zig | 8 ++++++++ 2 files changed, 42 insertions(+), 3 deletions(-) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 298c3c167f6e6c044d41cf1e83e03f3664e54df6..3f4affcc5ca17b83d5cb99b398b735c363cd4919 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -10849,9 +10849,40 @@ fn nowWindows(clock: Io.Clock) Io.Clock.Error!Io.Timestamp { const result = (@as(u96, qpc) * scale) >> 32; return .{ .nanoseconds = @intCast(result) }; }, - .cpu_process, - .cpu_thread, - => return error.UnsupportedClock, + .cpu_process => { + const handle = windows.GetCurrentProcess(); + var times: windows.KERNEL_USER_TIMES = undefined; + + // https://github.com/reactos/reactos/blob/master/ntoskrnl/ps/query.c#L442-L485 + if (windows.ntdll.NtQueryInformationProcess( + handle, + windows.PROCESSINFOCLASS.Times, + ×, + @sizeOf(windows.KERNEL_USER_TIMES), + null, + ) != .SUCCESS) + return error.Unexpected; + + const sum = @as(i96, times.UserTime) + @as(i96, times.KernelTime); + return .{ .nanoseconds = sum * 100 }; + }, + .cpu_thread => { + const handle = windows.GetCurrentThread(); + var times: windows.KERNEL_USER_TIMES = undefined; + + // https://github.com/reactos/reactos/blob/master/ntoskrnl/ps/query.c#L2971-L3019 + if (windows.ntdll.NtQueryInformationThread( + handle, + windows.THREADINFOCLASS.Times, + ×, + @sizeOf(windows.KERNEL_USER_TIMES), + null, + ) != .SUCCESS) + return error.Unexpected; + + const sum = @as(i96, times.UserTime) + @as(i96, times.KernelTime); + return .{ .nanoseconds = sum * 100 }; + }, } } diff --git a/lib/std/os/windows.zig b/lib/std/os/windows.zig index b9653b3ac9979d9eb5144822fb7ed27f802a55a7..0f57eed766eba130ece4bafc3b76e5684de2239b 100644 --- a/lib/std/os/windows.zig +++ b/lib/std/os/windows.zig @@ -6191,6 +6191,14 @@ pub const PROCESS_BASIC_INFORMATION = extern struct { InheritedFromUniqueProcessId: ULONG_PTR, }; +// https://github.com/reactos/reactos/blob/master/sdk/include/ndk/pstypes.h#L977-L983 +pub const KERNEL_USER_TIMES = extern struct { + CreationTime: LARGE_INTEGER, + ExitTime: LARGE_INTEGER, + KernelTime: LARGE_INTEGER, + UserTime: LARGE_INTEGER, +}; + pub const ReadMemoryError = error{ Unexpected, }; -- 2.54.0 From eb74e23e7ba4f2000bbe4e908bf74c5c9dd7adcc Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sun, 1 Feb 2026 16:13:51 -0800 Subject: [PATCH 173/499] std.Io.Threaded: sever dependency on std.Thread Mutex and Condition --- lib/std/Io/Threaded.zig | 253 +++++++++++++++++++++++++++++----------- 1 file changed, 186 insertions(+), 67 deletions(-) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 4b1ebbc59b26339ae5783ee14fe478b9a682f256..11708af071376e65f1a120789fb6020271958ae9 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -29,8 +29,8 @@ const ws2_32 = std.os.windows.ws2_32; /// * scanning environment variables on some targets /// * memory-mapping when mmap or equivalent is not available allocator: Allocator, -mutex: std.Thread.Mutex = .{}, -cond: std.Thread.Condition = .{}, +mutex: Io.Mutex = .init, +cond: Io.Condition = .init, run_queue: std.SinglyLinkedList = .{}, join_requested: bool = false, stack_size: usize, @@ -1486,8 +1486,8 @@ var global_single_threaded_instance: Threaded = .init_single_threaded; pub const global_single_threaded: *Threaded = &global_single_threaded_instance; pub fn setAsyncLimit(t: *Threaded, new_limit: Io.Limit) void { - t.mutex.lock(); - defer t.mutex.unlock(); + mutexLockUncancelable(&t.mutex); + defer mutexUnlock(&t.mutex); t.async_limit = new_limit; } @@ -1508,11 +1508,11 @@ pub fn deinit(t: *Threaded) void { fn join(t: *Threaded) void { if (builtin.single_threaded) return; { - t.mutex.lock(); - defer t.mutex.unlock(); + mutexLockUncancelable(&t.mutex); + defer mutexUnlock(&t.mutex); t.join_requested = true; } - t.cond.broadcast(); + condBroadcast(&t.cond); t.wait_group.wait(); } @@ -1574,20 +1574,20 @@ fn worker(t: *Threaded) void { defer t.wait_group.finish(); - t.mutex.lock(); - defer t.mutex.unlock(); + mutexLockUncancelable(&t.mutex); + defer mutexUnlock(&t.mutex); while (true) { while (t.run_queue.popFirst()) |runnable_node| { - t.mutex.unlock(); + mutexUnlock(&t.mutex); thread.cancel_protection = .unblocked; const runnable: *Runnable = @fieldParentPtr("node", runnable_node); runnable.startFn(runnable, &thread, t); - t.mutex.lock(); + mutexLockUncancelable(&t.mutex); t.busy_count -= 1; } if (t.join_requested) break; - t.cond.wait(&t.mutex); + condWait(&t.cond, &t.mutex); } } @@ -2004,12 +2004,12 @@ fn async( }, }; - t.mutex.lock(); + mutexLockUncancelable(&t.mutex); const busy_count = t.busy_count; if (busy_count >= @intFromEnum(t.async_limit)) { - t.mutex.unlock(); + mutexUnlock(&t.mutex); future.destroy(gpa); start(context.ptr, result.ptr); return null; @@ -2023,7 +2023,7 @@ fn async( const thread = std.Thread.spawn(.{ .stack_size = t.stack_size }, worker, .{t}) catch { t.wait_group.finish(); t.busy_count = busy_count; - t.mutex.unlock(); + mutexUnlock(&t.mutex); future.destroy(gpa); start(context.ptr, result.ptr); return null; @@ -2033,8 +2033,8 @@ fn async( t.run_queue.prepend(&future.runnable.node); - t.mutex.unlock(); - t.cond.signal(); + mutexUnlock(&t.mutex); + condSignal(&t.cond); return @ptrCast(future); } @@ -2056,8 +2056,8 @@ fn concurrent( }; errdefer future.destroy(gpa); - t.mutex.lock(); - defer t.mutex.unlock(); + mutexLockUncancelable(&t.mutex); + defer mutexUnlock(&t.mutex); const busy_count = t.busy_count; @@ -2080,7 +2080,7 @@ fn concurrent( t.run_queue.prepend(&future.runnable.node); - t.cond.signal(); + condSignal(&t.cond); return @ptrCast(future); } @@ -2101,12 +2101,12 @@ fn groupAsync( error.OutOfMemory => return groupAsyncEager(start, context.ptr), }; - t.mutex.lock(); + mutexLockUncancelable(&t.mutex); const busy_count = t.busy_count; if (busy_count >= @intFromEnum(t.async_limit)) { - t.mutex.unlock(); + mutexUnlock(&t.mutex); task.destroy(gpa); return groupAsyncEager(start, context.ptr); } @@ -2119,7 +2119,7 @@ fn groupAsync( const thread = std.Thread.spawn(.{ .stack_size = t.stack_size }, worker, .{t}) catch { t.wait_group.finish(); t.busy_count = busy_count; - t.mutex.unlock(); + mutexUnlock(&t.mutex); task.destroy(gpa); return groupAsyncEager(start, context.ptr); }; @@ -2136,8 +2136,8 @@ fn groupAsync( }, .monotonic); t.run_queue.prepend(&task.runnable.node); - t.mutex.unlock(); - t.cond.signal(); + mutexUnlock(&t.mutex); + condSignal(&t.cond); } fn groupAsyncEager( start: *const fn (context: *const anyopaque) Io.Cancelable!void, @@ -2201,8 +2201,8 @@ fn groupConcurrent( }; errdefer task.destroy(gpa); - t.mutex.lock(); - defer t.mutex.unlock(); + mutexLockUncancelable(&t.mutex); + defer mutexUnlock(&t.mutex); const busy_count = t.busy_count; @@ -2233,7 +2233,7 @@ fn groupConcurrent( }, .monotonic); t.run_queue.prepend(&task.runnable.node); - t.cond.signal(); + condSignal(&t.cond); } fn groupAwait(userdata: ?*anyopaque, type_erased: *Io.Group, initial_token: *anyopaque) Io.Cancelable!void { @@ -3838,8 +3838,8 @@ fn fileStatWindows(userdata: ?*anyopaque, file: File) File.StatError!File.Stat { fn systemBasicInformation(t: *Threaded) ?*const windows.SYSTEM_BASIC_INFORMATION { if (!t.system_basic_information.initialized.load(.acquire)) { - t.mutex.lock(); - defer t.mutex.unlock(); + mutexLockUncancelable(&t.mutex); + defer mutexUnlock(&t.mutex); switch (windows.ntdll.NtQuerySystemInformation( .SystemBasicInformation, @@ -14299,10 +14299,9 @@ const Wsa = struct { }; fn initializeWsa(t: *Threaded) error{ NetworkDown, Canceled }!void { - const t_io = io(t); const wsa = &t.wsa; - try wsa.mutex.lock(t_io); - defer wsa.mutex.unlock(t_io); + try mutexLock(&wsa.mutex); + defer mutexUnlock(&wsa.mutex); switch (wsa.status) { .uninitialized => { var wsa_data: ws2_32.WSADATA = undefined; @@ -14373,8 +14372,8 @@ const WindowsEnvironStrings = struct { }; fn scanEnviron(t: *Threaded) void { - t.mutex.lock(); - defer t.mutex.unlock(); + mutexLockUncancelable(&t.mutex); + defer mutexUnlock(&t.mutex); if (t.environ.initialized) return; t.environ.initialized = true; @@ -14729,8 +14728,8 @@ fn spawnPosix(t: *Threaded, options: process.SpawnOptions) process.SpawnError!Sp fn getDevNullFd(t: *Threaded) !posix.fd_t { { - t.mutex.lock(); - defer t.mutex.unlock(); + mutexLockUncancelable(&t.mutex); + defer mutexUnlock(&t.mutex); if (t.null_file.fd != -1) return t.null_file.fd; } const mode: u32 = 0; @@ -14741,8 +14740,8 @@ fn getDevNullFd(t: *Threaded) !posix.fd_t { .SUCCESS => { syscall.finish(); const fresh_fd: posix.fd_t = @intCast(rc); - t.mutex.lock(); // Another thread might have won the race. - defer t.mutex.unlock(); + mutexLockUncancelable(&t.mutex); // Another thread might have won the race. + defer mutexUnlock(&t.mutex); if (t.null_file.fd != -1) { posix.close(fresh_fd); return t.null_file.fd; @@ -15402,8 +15401,8 @@ fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) pro fn getCngHandle(t: *Threaded) Io.RandomSecureError!windows.HANDLE { { - t.mutex.lock(); - defer t.mutex.unlock(); + mutexLockUncancelable(&t.mutex); + defer mutexUnlock(&t.mutex); if (t.random_file.handle) |handle| return handle; } @@ -15437,8 +15436,8 @@ fn getCngHandle(t: *Threaded) Io.RandomSecureError!windows.HANDLE { )) { .SUCCESS => { syscall.finish(); - t.mutex.lock(); // Another thread might have won the race. - defer t.mutex.unlock(); + mutexLockUncancelable(&t.mutex); // Another thread might have won the race. + defer mutexUnlock(&t.mutex); if (t.random_file.handle) |prev_handle| { windows.CloseHandle(fresh_handle); return prev_handle; @@ -15458,8 +15457,8 @@ fn getCngHandle(t: *Threaded) Io.RandomSecureError!windows.HANDLE { fn getNulHandle(t: *Threaded) !windows.HANDLE { { - t.mutex.lock(); - defer t.mutex.unlock(); + mutexLockUncancelable(&t.mutex); + defer mutexUnlock(&t.mutex); if (t.null_file.handle) |handle| return handle; } @@ -15505,8 +15504,8 @@ fn getNulHandle(t: *Threaded) !windows.HANDLE { )) { .SUCCESS => { syscall.finish(); - t.mutex.lock(); // Another thread might have won the race. - defer t.mutex.unlock(); + mutexLockUncancelable(&t.mutex); // Another thread might have won the race. + defer mutexUnlock(&t.mutex); if (t.null_file.handle) |prev_handle| { windows.CloseHandle(fresh_handle); return prev_handle; @@ -16551,15 +16550,15 @@ fn random(userdata: ?*anyopaque, buffer: []u8) void { } fn randomMainThread(t: *Threaded, buffer: []u8) void { - t.mutex.lock(); - defer t.mutex.unlock(); + mutexLockUncancelable(&t.mutex); + defer mutexUnlock(&t.mutex); if (!t.csprng.isInitialized()) { @branchHint(.unlikely); var seed: [Csprng.seed_len]u8 = undefined; { - t.mutex.unlock(); - defer t.mutex.lock(); + mutexUnlock(&t.mutex); + defer mutexLockUncancelable(&t.mutex); const prev = swapCancelProtection(t, .blocked); defer _ = swapCancelProtection(t, prev); @@ -16744,8 +16743,8 @@ fn randomSecure(userdata: ?*anyopaque, buffer: []u8) Io.RandomSecureError!void { fn getRandomFd(t: *Threaded) Io.RandomSecureError!posix.fd_t { { - t.mutex.lock(); - defer t.mutex.unlock(); + mutexLockUncancelable(&t.mutex); + defer mutexUnlock(&t.mutex); if (t.random_file.fd == -2) return error.EntropyUnavailable; if (t.random_file.fd != -1) return t.random_file.fd; @@ -16785,8 +16784,8 @@ fn getRandomFd(t: *Threaded) Io.RandomSecureError!posix.fd_t { .SUCCESS => { syscall.finish(); if (!statx.mask.TYPE) return error.EntropyUnavailable; - t.mutex.lock(); // Another thread might have won the race. - defer t.mutex.unlock(); + mutexLockUncancelable(&t.mutex); // Another thread might have won the race. + defer mutexUnlock(&t.mutex); if (t.random_file.fd >= 0) { posix.close(fd); return t.random_file.fd; @@ -16813,8 +16812,8 @@ fn getRandomFd(t: *Threaded) Io.RandomSecureError!posix.fd_t { switch (posix.errno(fstat_sym(fd, &stat))) { .SUCCESS => { syscall.finish(); - t.mutex.lock(); // Another thread might have won the race. - defer t.mutex.unlock(); + mutexLockUncancelable(&t.mutex); // Another thread might have won the race. + defer mutexUnlock(&t.mutex); if (t.random_file.fd >= 0) { posix.close(fd); return t.random_file.fd; @@ -16878,13 +16877,13 @@ const parking_futex = struct { /// avoid a race. num_waiters: std.atomic.Value(u32), /// Protects `waiters`. - mutex: std.Thread.Mutex, + mutex: Io.Mutex, waiters: std.DoublyLinkedList, /// Prevent false sharing between buckets. _: void align(std.atomic.cache_line) = {}, - const init: Bucket = .{ .num_waiters = .init(0), .mutex = .{}, .waiters = .{} }; + const init: Bucket = .{ .num_waiters = .init(0), .mutex = .init, .waiters = .{} }; }; const Waiter = struct { @@ -16947,8 +16946,8 @@ const parking_futex = struct { var status_buf: std.atomic.Value(Thread.Status) = undefined; { - bucket.mutex.lock(); - defer bucket.mutex.unlock(); + mutexLockUncancelable(&bucket.mutex); + defer mutexUnlock(&bucket.mutex); _ = bucket.num_waiters.fetchAdd(1, .acquire); @@ -17017,8 +17016,8 @@ const parking_futex = struct { .parked => { // We saw a timeout and updated our own status from `.parked` to `.none`. It is // our responsibility to remove `waiter` from `bucket`. - bucket.mutex.lock(); - defer bucket.mutex.unlock(); + mutexLockUncancelable(&bucket.mutex); + defer mutexUnlock(&bucket.mutex); bucket.waiters.remove(&waiter.node); assert(bucket.num_waiters.fetchSub(1, .monotonic) > 0); }, @@ -17057,8 +17056,8 @@ const parking_futex = struct { // of the critical section. This forms a singly-linked list of waiters using `Waiter.node.next`. var waking_head: ?*std.DoublyLinkedList.Node = null; { - bucket.mutex.lock(); - defer bucket.mutex.unlock(); + mutexLockUncancelable(&bucket.mutex); + defer mutexUnlock(&bucket.mutex); var num_removed: u32 = 0; var it = bucket.waiters.first; @@ -17113,8 +17112,8 @@ const parking_futex = struct { fn removeCanceledWaiter(waiter: *Waiter) void { const bucket = bucketForAddress(waiter.address); - bucket.mutex.lock(); - defer bucket.mutex.unlock(); + mutexLockUncancelable(&bucket.mutex); + defer mutexUnlock(&bucket.mutex); bucket.waiters.remove(&waiter.node); assert(bucket.num_waiters.fetchSub(1, .monotonic) > 0); waiter.done.store(true, .release); // potentially invalidates `waiter.*` @@ -18102,3 +18101,123 @@ fn eventSet(event: *Io.Event) void { .waiting => Thread.futexWake(@ptrCast(event), std.math.maxInt(u32)), } } + +/// Same as `Io.Condition.broadcast` but avoids the VTable. +fn condBroadcast(cond: *Io.Condition) void { + var prev_state = cond.state.load(.monotonic); + while (prev_state.waiters > prev_state.signals) { + @branchHint(.unlikely); + prev_state = cond.state.cmpxchgWeak(prev_state, .{ + .waiters = prev_state.waiters, + .signals = prev_state.waiters, + }, .release, .monotonic) orelse { + // Update the epoch to tell the waiting threads that there are new signals for them. + // Note that a waiting thread could miss a take if *exactly* (1<<32)-1 wakes happen + // between it observing the epoch and sleeping on it, but this is extraordinarily + // unlikely due to the precise number of calls required. + _ = cond.epoch.fetchAdd(1, .release); // `.release` to ensure ordered after `state` update + Thread.futexWake(&cond.epoch.raw, prev_state.waiters - prev_state.signals); + return; + }; + } +} + +/// Same as `Io.Condition.signal` but avoids the VTable. +fn condSignal(cond: *Io.Condition) void { + var prev_state = cond.state.load(.monotonic); + while (prev_state.waiters > prev_state.signals) { + @branchHint(.unlikely); + prev_state = cond.state.cmpxchgWeak(prev_state, .{ + .waiters = prev_state.waiters, + .signals = prev_state.signals + 1, + }, .release, .monotonic) orelse { + // Update the epoch to tell the waiting threads that there are new signals for them. + // Note that a waiting thread could miss a take if *exactly* (1<<32)-1 wakes happen + // between it observing the epoch and sleeping on it, but this is extraordinarily + // unlikely due to the precise number of calls required. + _ = cond.epoch.fetchAdd(1, .release); // `.release` to ensure ordered after `state` update + Thread.futexWake(&cond.epoch.raw, 1); + return; + }; + } +} + +/// Same as `Io.Condition.waitUncancelable` but avoids the VTable. +fn condWait(cond: *Io.Condition, mutex: *Io.Mutex) void { + var epoch = cond.epoch.load(.acquire); // `.acquire` to ensure ordered before state load + + { + const prev_state = cond.state.fetchAdd(.{ .waiters = 1, .signals = 0 }, .monotonic); + assert(prev_state.waiters < std.math.maxInt(u16)); // overflow caused by too many waiters + } + + mutexUnlock(mutex); + defer mutexLockUncancelable(mutex); + + while (true) { + Thread.futexWaitUncancelable(&cond.epoch.raw, epoch, null); + + epoch = cond.epoch.load(.acquire); // `.acquire` to ensure ordered before `state` laod + + var prev_state = cond.state.load(.monotonic); + while (prev_state.signals > 0) { + prev_state = cond.state.cmpxchgWeak(prev_state, .{ + .waiters = prev_state.waiters - 1, + .signals = prev_state.signals - 1, + }, .acquire, .monotonic) orelse { + // We successfully consumed a signal. + return; + }; + } + } +} + +/// Same as `Io.Mutex.lockUncancelable` but avoids the VTable. +fn mutexLock(m: *Io.Mutex) Io.Cancelable!void { + const initial_state = m.state.cmpxchgWeak( + .unlocked, + .locked_once, + .acquire, + .monotonic, + ) orelse { + @branchHint(.likely); + return; + }; + if (initial_state == .contended) { + try Thread.futexWait(@ptrCast(&m.state.raw), @intFromEnum(Io.Mutex.State.contended), null); + } + while (m.state.swap(.contended, .acquire) != .unlocked) { + try Thread.futexWait(@ptrCast(&m.state.raw), @intFromEnum(Io.Mutex.State.contended), null); + } +} + +/// Same as `Io.Mutex.lockUncancelable` but avoids the VTable. +fn mutexLockUncancelable(m: *Io.Mutex) void { + const initial_state = m.state.cmpxchgWeak( + .unlocked, + .locked_once, + .acquire, + .monotonic, + ) orelse { + @branchHint(.likely); + return; + }; + if (initial_state == .contended) { + Thread.futexWaitUncancelable(@ptrCast(&m.state.raw), @intFromEnum(Io.Mutex.State.contended), null); + } + while (m.state.swap(.contended, .acquire) != .unlocked) { + Thread.futexWaitUncancelable(@ptrCast(&m.state.raw), @intFromEnum(Io.Mutex.State.contended), null); + } +} + +/// Same as `Io.Mutex.unlock` but avoids the VTable. +fn mutexUnlock(m: *Io.Mutex) void { + switch (m.state.swap(.unlocked, .release)) { + .unlocked => unreachable, + .locked_once => {}, + .contended => { + @branchHint(.unlikely); + Thread.futexWake(@ptrCast(&m.state.raw), 1); + }, + } +} -- 2.54.0 From 5312063138e787a09493e5f5affb5c8652b66dbc Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 2 Feb 2026 14:25:27 -0800 Subject: [PATCH 174/499] std.Io.Threaded: work around parking futex bug This commit should be reverted - it's testing a hypothesis that Windows is deadlocking due to bug in the implementation of std.Io.Threaded.parking_futex --- lib/std/Io/Threaded.zig | 39 +++++++++++++++++++++++++++++---------- 1 file changed, 29 insertions(+), 10 deletions(-) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 11708af071376e65f1a120789fb6020271958ae9..60df1bdd81eef7dd7885e315dbc80ee188681144 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -29,8 +29,8 @@ const ws2_32 = std.os.windows.ws2_32; /// * scanning environment variables on some targets /// * memory-mapping when mmap or equivalent is not available allocator: Allocator, -mutex: Io.Mutex = .init, -cond: Io.Condition = .init, +mutex: Mutex = .init, +cond: Condition = .init, run_queue: std.SinglyLinkedList = .{}, join_requested: bool = false, stack_size: usize, @@ -14299,9 +14299,10 @@ const Wsa = struct { }; fn initializeWsa(t: *Threaded) error{ NetworkDown, Canceled }!void { + const t_io = io(t); const wsa = &t.wsa; - try mutexLock(&wsa.mutex); - defer mutexUnlock(&wsa.mutex); + try wsa.mutex.lock(t_io); + defer wsa.mutex.unlock(t_io); switch (wsa.status) { .uninitialized => { var wsa_data: ws2_32.WSADATA = undefined; @@ -16877,7 +16878,7 @@ const parking_futex = struct { /// avoid a race. num_waiters: std.atomic.Value(u32), /// Protects `waiters`. - mutex: Io.Mutex, + mutex: Mutex, waiters: std.DoublyLinkedList, /// Prevent false sharing between buckets. @@ -18102,8 +18103,14 @@ fn eventSet(event: *Io.Event) void { } } +const Condition = if (!is_windows) Io.Condition else struct { + condition: windows.CONDITION_VARIABLE, + const init: @This() = .{ .condition = .{} }; +}; + /// Same as `Io.Condition.broadcast` but avoids the VTable. -fn condBroadcast(cond: *Io.Condition) void { +fn condBroadcast(cond: *Condition) void { + if (is_windows) return windows.ntdll.RtlWakeAllConditionVariable(&cond.condition); var prev_state = cond.state.load(.monotonic); while (prev_state.waiters > prev_state.signals) { @branchHint(.unlikely); @@ -18123,7 +18130,8 @@ fn condBroadcast(cond: *Io.Condition) void { } /// Same as `Io.Condition.signal` but avoids the VTable. -fn condSignal(cond: *Io.Condition) void { +fn condSignal(cond: *Condition) void { + if (is_windows) return windows.ntdll.RtlWakeConditionVariable(&cond.condition); var prev_state = cond.state.load(.monotonic); while (prev_state.waiters > prev_state.signals) { @branchHint(.unlikely); @@ -18143,7 +18151,11 @@ fn condSignal(cond: *Io.Condition) void { } /// Same as `Io.Condition.waitUncancelable` but avoids the VTable. -fn condWait(cond: *Io.Condition, mutex: *Io.Mutex) void { +fn condWait(cond: *Condition, mutex: *Mutex) void { + if (is_windows) { + _ = windows.kernel32.SleepConditionVariableSRW(&cond.condition, &mutex.srwlock, windows.INFINITE, 0); + return; + } var epoch = cond.epoch.load(.acquire); // `.acquire` to ensure ordered before state load { @@ -18172,6 +18184,11 @@ fn condWait(cond: *Io.Condition, mutex: *Io.Mutex) void { } } +const Mutex = if (!is_windows) Io.Mutex else struct { + srwlock: windows.SRWLOCK, + const init: @This() = .{ .srwlock = .{} }; +}; + /// Same as `Io.Mutex.lockUncancelable` but avoids the VTable. fn mutexLock(m: *Io.Mutex) Io.Cancelable!void { const initial_state = m.state.cmpxchgWeak( @@ -18192,7 +18209,8 @@ fn mutexLock(m: *Io.Mutex) Io.Cancelable!void { } /// Same as `Io.Mutex.lockUncancelable` but avoids the VTable. -fn mutexLockUncancelable(m: *Io.Mutex) void { +fn mutexLockUncancelable(m: *Mutex) void { + if (is_windows) return windows.ntdll.RtlAcquireSRWLockExclusive(&m.srwlock); const initial_state = m.state.cmpxchgWeak( .unlocked, .locked_once, @@ -18211,7 +18229,8 @@ fn mutexLockUncancelable(m: *Io.Mutex) void { } /// Same as `Io.Mutex.unlock` but avoids the VTable. -fn mutexUnlock(m: *Io.Mutex) void { +fn mutexUnlock(m: *Mutex) void { + if (is_windows) return windows.ntdll.RtlReleaseSRWLockExclusive(&m.srwlock); switch (m.state.swap(.unlocked, .release)) { .unlocked => unreachable, .locked_once => {}, -- 2.54.0 From 255aeb57b24bc24b604744460a61ebf7c44e42ea Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sun, 1 Feb 2026 14:57:27 -0800 Subject: [PATCH 175/499] std: introduce atomic.Mutex and use it in heap.SmpAllocator This allocator implementation uses only lock-free operations. --- lib/std/atomic.zig | 25 +++++++++++++++++++++---- lib/std/heap/SmpAllocator.zig | 2 +- 2 files changed, 22 insertions(+), 5 deletions(-) diff --git a/lib/std/atomic.zig b/lib/std/atomic.zig index 0b35b61e9babe01ab206756ce129451ee95806e9..0040dbf735eeaabad5c5d532663757bfb5de5491 100644 --- a/lib/std/atomic.zig +++ b/lib/std/atomic.zig @@ -1,3 +1,10 @@ +const builtin = @import("builtin"); + +const std = @import("std.zig"); +const AtomicOrder = std.builtin.AtomicOrder; +const testing = std.testing; +const assert = std.debug.assert; + /// This is a thin wrapper around a primitive value to prevent accidental data races. pub fn Value(comptime T: type) type { return extern struct { @@ -496,7 +503,17 @@ test "current CPU has a cache line size" { _ = cache_line; } -const std = @import("std.zig"); -const builtin = @import("builtin"); -const AtomicOrder = std.builtin.AtomicOrder; -const testing = std.testing; +/// A lock-free single-owner resource. +pub const Mutex = enum(u8) { + unlocked, + locked, + + pub fn tryLock(m: *Mutex) bool { + return @cmpxchgWeak(Mutex, m, .unlocked, .locked, .acquire, .monotonic) == null; + } + + pub fn unlock(m: *Mutex) void { + assert(m.* == .locked); + @atomicStore(Mutex, m, .unlocked, .release); + } +}; diff --git a/lib/std/heap/SmpAllocator.zig b/lib/std/heap/SmpAllocator.zig index f9637c70a29f0ae7a25388143403a59cb13edf69..e51e4975b68dce33c3c9d2e78691f8b9bec32159 100644 --- a/lib/std/heap/SmpAllocator.zig +++ b/lib/std/heap/SmpAllocator.zig @@ -62,7 +62,7 @@ const Thread = struct { /// /// Threads lock this before accessing their own state in order /// to support freelist reclamation. - mutex: std.Thread.Mutex = .{}, + mutex: std.atomic.Mutex = .unlocked, /// For each size class, tracks the next address to be returned from /// `alloc` when the freelist is empty. -- 2.54.0 From 550da1b676d059ae39a629d60da0f9cd155a5e89 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sun, 1 Feb 2026 17:33:49 -0800 Subject: [PATCH 176/499] std: migrate remaining sync primitives to Io - delete std.Thread.Futex - delete std.Thread.Mutex - delete std.Thread.Semaphore - delete std.Thread.Condition - delete std.Thread.RwLock - delete std.once std.Thread.Mutex.Recursive remains... for now. it will be replaced with a special purpose mechanism used only by panic logic. std.Io.Threaded exposes mutexLock and mutexUnlock for the advanced case when you need to call them directly. --- CMakeLists.txt | 2 - lib/compiler/build_runner.zig | 19 +- lib/compiler_rt/emutls.zig | 9 +- lib/fuzzer.zig | 2 +- lib/std/Io/Threaded.zig | 166 ++-- lib/std/Thread.zig | 14 +- lib/std/Thread/Condition.zig | 683 ----------------- lib/std/Thread/Futex.zig | 1063 -------------------------- lib/std/Thread/Mutex.zig | 367 --------- lib/std/Thread/Mutex/Recursive.zig | 14 +- lib/std/Thread/RwLock.zig | 386 ---------- lib/std/Thread/Semaphore.zig | 111 --- lib/std/debug.zig | 9 +- lib/std/debug/Coverage.zig | 20 +- lib/std/debug/Info.zig | 4 +- lib/std/debug/SelfInfo/Elf.zig | 50 +- lib/std/debug/SelfInfo/MachO.zig | 32 +- lib/std/debug/SelfInfo/Windows.zig | 20 +- lib/std/heap/ThreadSafeAllocator.zig | 35 +- lib/std/heap/debug_allocator.zig | 51 +- lib/std/heap/sbrk_allocator.zig | 18 +- lib/std/http/Client.zig | 67 +- lib/std/once.zig | 71 -- lib/std/std.zig | 1 - 24 files changed, 260 insertions(+), 2954 deletions(-) delete mode 100644 lib/std/Thread/Condition.zig delete mode 100644 lib/std/Thread/Futex.zig delete mode 100644 lib/std/Thread/Mutex.zig delete mode 100644 lib/std/Thread/RwLock.zig delete mode 100644 lib/std/Thread/Semaphore.zig delete mode 100644 lib/std/once.zig diff --git a/CMakeLists.txt b/CMakeLists.txt index 05fb9c4805fdb0b8fadcc4bd05167e70ed9668e7..38dbd6ca6afae4f08df1cac61913794890e393de 100644 --- a/CMakeLists.txt +++ b/CMakeLists.txt @@ -408,8 +408,6 @@ set(ZIG_STAGE2_SOURCES lib/std/Target/wasm.zig lib/std/Target/x86.zig lib/std/Thread.zig - lib/std/Thread/Futex.zig - lib/std/Thread/Mutex.zig lib/std/array_hash_map.zig lib/std/array_list.zig lib/std/ascii.zig diff --git a/lib/compiler/build_runner.zig b/lib/compiler/build_runner.zig index 5ef74adabab9e651d9cc65730b43c4fd783a2aa0..04ceb6212c84ce9c91633fa3ad10c316702a1855 100644 --- a/lib/compiler/build_runner.zig +++ b/lib/compiler/build_runner.zig @@ -30,14 +30,6 @@ pub fn main(init: process.Init.Minimal) !void { defer _ = debug_gpa_state.deinit(); const gpa = debug_gpa_state.allocator(); - // ...but we'll back our arena by `std.heap.page_allocator` for efficiency. - var single_threaded_arena: std.heap.ArenaAllocator = .init(std.heap.page_allocator); - defer single_threaded_arena.deinit(); - var thread_safe_arena: std.heap.ThreadSafeAllocator = .{ .child_allocator = single_threaded_arena.allocator() }; - const arena = thread_safe_arena.allocator(); - - const args = try init.args.toSlice(arena); - var threaded: std.Io.Threaded = .init(gpa, .{ .environ = init.environ, .argv0 = .init(init.args), @@ -45,6 +37,17 @@ pub fn main(init: process.Init.Minimal) !void { defer threaded.deinit(); const io = threaded.io(); + // ...but we'll back our arena by `std.heap.page_allocator` for efficiency. + var single_threaded_arena: std.heap.ArenaAllocator = .init(std.heap.page_allocator); + defer single_threaded_arena.deinit(); + var thread_safe_arena: std.heap.ThreadSafeAllocator = .{ + .child_allocator = single_threaded_arena.allocator(), + .io = io, + }; + const arena = thread_safe_arena.allocator(); + + const args = try init.args.toSlice(arena); + // skip my own exe name var arg_idx: usize = 1; diff --git a/lib/compiler_rt/emutls.zig b/lib/compiler_rt/emutls.zig index c52ce020edddb269e4f0f52e789e6381092384cd..d702597cd24ec3189b5296d5610aefcc57c10fce 100644 --- a/lib/compiler_rt/emutls.zig +++ b/lib/compiler_rt/emutls.zig @@ -147,7 +147,8 @@ const ObjectArray = struct { // It provides thread-safety for on-demand storage of Thread Objects. const current_thread_storage = struct { var key: std.c.pthread_key_t = undefined; - var init_once = std.once(current_thread_storage.init); + var init_mutex: std.c.pthread_mutex_t = std.c.PTHREAD_MUTEX_INITIALIZER; + var init_done: bool = false; /// Return a per thread ObjectArray with at least the expected index. pub fn getArray(index: usize) *ObjectArray { @@ -183,9 +184,13 @@ const current_thread_storage = struct { /// Initialize pthread_key_t. fn init() void { + if (@atomicLoad(bool, &init_done, .monotonic)) return; + _ = std.c.pthread_mutex_lock(&init_mutex); if (std.c.pthread_key_create(¤t_thread_storage.key, current_thread_storage.deinit) != .SUCCESS) { abort(); } + @atomicStore(bool, &init_done, true, .release); + _ = std.c.pthread_mutex_unlock(&init_mutex); } /// Invoked by pthread specific destructor. the passed argument is the ObjectArray pointer. @@ -283,7 +288,7 @@ const emutls_control = extern struct { /// Get the pointer on allocated storage for emutls variable. pub fn getPointer(self: *emutls_control) *anyopaque { // ensure current_thread_storage initialization is done - current_thread_storage.init_once.call(); + current_thread_storage.init(); const index = self.getIndex(); var array = current_thread_storage.getArray(index); diff --git a/lib/fuzzer.zig b/lib/fuzzer.zig index 1030128d48d5de94f11fd9b036a9f77078c93b13..37ca752d1fa962d0b97d9035fb5823c6e0b61507 100644 --- a/lib/fuzzer.zig +++ b/lib/fuzzer.zig @@ -632,7 +632,7 @@ export fn fuzzer_main(limit_kind: abi.LimitKind, amount: u64) void { export fn fuzzer_unslide_address(addr: usize) usize { const si = std.debug.getSelfDebugInfo() catch @compileError("unsupported"); - const slide = si.getModuleSlide(std.debug.getDebugInfoAllocator(), addr) catch |err| { + const slide = si.getModuleSlide(std.debug.getDebugInfoAllocator(), io, addr) catch |err| { std.debug.panic("failed to find virtual address slide: {t}", .{err}); }; return addr - slide; diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 60df1bdd81eef7dd7885e315dbc80ee188681144..b2ff7533849f1b3d8e41b7bfb5a2c2920bc6ee35 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -1126,6 +1126,25 @@ const Thread = struct { return @ptrFromInt(@as(usize, @bitCast(split))); } }; + + /// Same as `Io.Mutex.lock` but avoids the VTable. + fn mutexLock(m: *Io.Mutex) Io.Cancelable!void { + const initial_state = m.state.cmpxchgWeak( + .unlocked, + .locked_once, + .acquire, + .monotonic, + ) orelse { + @branchHint(.likely); + return; + }; + if (initial_state == .contended) { + try Thread.futexWait(@ptrCast(&m.state.raw), @intFromEnum(Io.Mutex.State.contended), null); + } + while (m.state.swap(.contended, .acquire) != .unlocked) { + try Thread.futexWait(@ptrCast(&m.state.raw), @intFromEnum(Io.Mutex.State.contended), null); + } + } }; const Syscall = struct { @@ -1486,8 +1505,8 @@ var global_single_threaded_instance: Threaded = .init_single_threaded; pub const global_single_threaded: *Threaded = &global_single_threaded_instance; pub fn setAsyncLimit(t: *Threaded, new_limit: Io.Limit) void { - mutexLockUncancelable(&t.mutex); - defer mutexUnlock(&t.mutex); + mutexLockInternal(&t.mutex); + defer mutexUnlockInternal(&t.mutex); t.async_limit = new_limit; } @@ -1508,8 +1527,8 @@ pub fn deinit(t: *Threaded) void { fn join(t: *Threaded) void { if (builtin.single_threaded) return; { - mutexLockUncancelable(&t.mutex); - defer mutexUnlock(&t.mutex); + mutexLockInternal(&t.mutex); + defer mutexUnlockInternal(&t.mutex); t.join_requested = true; } condBroadcast(&t.cond); @@ -1574,16 +1593,16 @@ fn worker(t: *Threaded) void { defer t.wait_group.finish(); - mutexLockUncancelable(&t.mutex); - defer mutexUnlock(&t.mutex); + mutexLockInternal(&t.mutex); + defer mutexUnlockInternal(&t.mutex); while (true) { while (t.run_queue.popFirst()) |runnable_node| { - mutexUnlock(&t.mutex); + mutexUnlockInternal(&t.mutex); thread.cancel_protection = .unblocked; const runnable: *Runnable = @fieldParentPtr("node", runnable_node); runnable.startFn(runnable, &thread, t); - mutexLockUncancelable(&t.mutex); + mutexLockInternal(&t.mutex); t.busy_count -= 1; } if (t.join_requested) break; @@ -2004,12 +2023,12 @@ fn async( }, }; - mutexLockUncancelable(&t.mutex); + mutexLockInternal(&t.mutex); const busy_count = t.busy_count; if (busy_count >= @intFromEnum(t.async_limit)) { - mutexUnlock(&t.mutex); + mutexUnlockInternal(&t.mutex); future.destroy(gpa); start(context.ptr, result.ptr); return null; @@ -2023,7 +2042,7 @@ fn async( const thread = std.Thread.spawn(.{ .stack_size = t.stack_size }, worker, .{t}) catch { t.wait_group.finish(); t.busy_count = busy_count; - mutexUnlock(&t.mutex); + mutexUnlockInternal(&t.mutex); future.destroy(gpa); start(context.ptr, result.ptr); return null; @@ -2033,7 +2052,7 @@ fn async( t.run_queue.prepend(&future.runnable.node); - mutexUnlock(&t.mutex); + mutexUnlockInternal(&t.mutex); condSignal(&t.cond); return @ptrCast(future); } @@ -2056,8 +2075,8 @@ fn concurrent( }; errdefer future.destroy(gpa); - mutexLockUncancelable(&t.mutex); - defer mutexUnlock(&t.mutex); + mutexLockInternal(&t.mutex); + defer mutexUnlockInternal(&t.mutex); const busy_count = t.busy_count; @@ -2101,12 +2120,12 @@ fn groupAsync( error.OutOfMemory => return groupAsyncEager(start, context.ptr), }; - mutexLockUncancelable(&t.mutex); + mutexLockInternal(&t.mutex); const busy_count = t.busy_count; if (busy_count >= @intFromEnum(t.async_limit)) { - mutexUnlock(&t.mutex); + mutexUnlockInternal(&t.mutex); task.destroy(gpa); return groupAsyncEager(start, context.ptr); } @@ -2119,7 +2138,7 @@ fn groupAsync( const thread = std.Thread.spawn(.{ .stack_size = t.stack_size }, worker, .{t}) catch { t.wait_group.finish(); t.busy_count = busy_count; - mutexUnlock(&t.mutex); + mutexUnlockInternal(&t.mutex); task.destroy(gpa); return groupAsyncEager(start, context.ptr); }; @@ -2136,7 +2155,7 @@ fn groupAsync( }, .monotonic); t.run_queue.prepend(&task.runnable.node); - mutexUnlock(&t.mutex); + mutexUnlockInternal(&t.mutex); condSignal(&t.cond); } fn groupAsyncEager( @@ -2201,8 +2220,8 @@ fn groupConcurrent( }; errdefer task.destroy(gpa); - mutexLockUncancelable(&t.mutex); - defer mutexUnlock(&t.mutex); + mutexLockInternal(&t.mutex); + defer mutexUnlockInternal(&t.mutex); const busy_count = t.busy_count; @@ -3838,8 +3857,8 @@ fn fileStatWindows(userdata: ?*anyopaque, file: File) File.StatError!File.Stat { fn systemBasicInformation(t: *Threaded) ?*const windows.SYSTEM_BASIC_INFORMATION { if (!t.system_basic_information.initialized.load(.acquire)) { - mutexLockUncancelable(&t.mutex); - defer mutexUnlock(&t.mutex); + mutexLockInternal(&t.mutex); + defer mutexUnlockInternal(&t.mutex); switch (windows.ntdll.NtQuerySystemInformation( .SystemBasicInformation, @@ -14373,8 +14392,8 @@ const WindowsEnvironStrings = struct { }; fn scanEnviron(t: *Threaded) void { - mutexLockUncancelable(&t.mutex); - defer mutexUnlock(&t.mutex); + mutexLockInternal(&t.mutex); + defer mutexUnlockInternal(&t.mutex); if (t.environ.initialized) return; t.environ.initialized = true; @@ -14729,8 +14748,8 @@ fn spawnPosix(t: *Threaded, options: process.SpawnOptions) process.SpawnError!Sp fn getDevNullFd(t: *Threaded) !posix.fd_t { { - mutexLockUncancelable(&t.mutex); - defer mutexUnlock(&t.mutex); + mutexLockInternal(&t.mutex); + defer mutexUnlockInternal(&t.mutex); if (t.null_file.fd != -1) return t.null_file.fd; } const mode: u32 = 0; @@ -14741,8 +14760,8 @@ fn getDevNullFd(t: *Threaded) !posix.fd_t { .SUCCESS => { syscall.finish(); const fresh_fd: posix.fd_t = @intCast(rc); - mutexLockUncancelable(&t.mutex); // Another thread might have won the race. - defer mutexUnlock(&t.mutex); + mutexLockInternal(&t.mutex); // Another thread might have won the race. + defer mutexUnlockInternal(&t.mutex); if (t.null_file.fd != -1) { posix.close(fresh_fd); return t.null_file.fd; @@ -15402,8 +15421,8 @@ fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) pro fn getCngHandle(t: *Threaded) Io.RandomSecureError!windows.HANDLE { { - mutexLockUncancelable(&t.mutex); - defer mutexUnlock(&t.mutex); + mutexLockInternal(&t.mutex); + defer mutexUnlockInternal(&t.mutex); if (t.random_file.handle) |handle| return handle; } @@ -15437,8 +15456,8 @@ fn getCngHandle(t: *Threaded) Io.RandomSecureError!windows.HANDLE { )) { .SUCCESS => { syscall.finish(); - mutexLockUncancelable(&t.mutex); // Another thread might have won the race. - defer mutexUnlock(&t.mutex); + mutexLockInternal(&t.mutex); // Another thread might have won the race. + defer mutexUnlockInternal(&t.mutex); if (t.random_file.handle) |prev_handle| { windows.CloseHandle(fresh_handle); return prev_handle; @@ -15458,8 +15477,8 @@ fn getCngHandle(t: *Threaded) Io.RandomSecureError!windows.HANDLE { fn getNulHandle(t: *Threaded) !windows.HANDLE { { - mutexLockUncancelable(&t.mutex); - defer mutexUnlock(&t.mutex); + mutexLockInternal(&t.mutex); + defer mutexUnlockInternal(&t.mutex); if (t.null_file.handle) |handle| return handle; } @@ -15505,8 +15524,8 @@ fn getNulHandle(t: *Threaded) !windows.HANDLE { )) { .SUCCESS => { syscall.finish(); - mutexLockUncancelable(&t.mutex); // Another thread might have won the race. - defer mutexUnlock(&t.mutex); + mutexLockInternal(&t.mutex); // Another thread might have won the race. + defer mutexUnlockInternal(&t.mutex); if (t.null_file.handle) |prev_handle| { windows.CloseHandle(fresh_handle); return prev_handle; @@ -16551,15 +16570,15 @@ fn random(userdata: ?*anyopaque, buffer: []u8) void { } fn randomMainThread(t: *Threaded, buffer: []u8) void { - mutexLockUncancelable(&t.mutex); - defer mutexUnlock(&t.mutex); + mutexLockInternal(&t.mutex); + defer mutexUnlockInternal(&t.mutex); if (!t.csprng.isInitialized()) { @branchHint(.unlikely); var seed: [Csprng.seed_len]u8 = undefined; { - mutexUnlock(&t.mutex); - defer mutexLockUncancelable(&t.mutex); + mutexUnlockInternal(&t.mutex); + defer mutexLockInternal(&t.mutex); const prev = swapCancelProtection(t, .blocked); defer _ = swapCancelProtection(t, prev); @@ -16744,8 +16763,8 @@ fn randomSecure(userdata: ?*anyopaque, buffer: []u8) Io.RandomSecureError!void { fn getRandomFd(t: *Threaded) Io.RandomSecureError!posix.fd_t { { - mutexLockUncancelable(&t.mutex); - defer mutexUnlock(&t.mutex); + mutexLockInternal(&t.mutex); + defer mutexUnlockInternal(&t.mutex); if (t.random_file.fd == -2) return error.EntropyUnavailable; if (t.random_file.fd != -1) return t.random_file.fd; @@ -16785,8 +16804,8 @@ fn getRandomFd(t: *Threaded) Io.RandomSecureError!posix.fd_t { .SUCCESS => { syscall.finish(); if (!statx.mask.TYPE) return error.EntropyUnavailable; - mutexLockUncancelable(&t.mutex); // Another thread might have won the race. - defer mutexUnlock(&t.mutex); + mutexLockInternal(&t.mutex); // Another thread might have won the race. + defer mutexUnlockInternal(&t.mutex); if (t.random_file.fd >= 0) { posix.close(fd); return t.random_file.fd; @@ -16813,8 +16832,8 @@ fn getRandomFd(t: *Threaded) Io.RandomSecureError!posix.fd_t { switch (posix.errno(fstat_sym(fd, &stat))) { .SUCCESS => { syscall.finish(); - mutexLockUncancelable(&t.mutex); // Another thread might have won the race. - defer mutexUnlock(&t.mutex); + mutexLockInternal(&t.mutex); // Another thread might have won the race. + defer mutexUnlockInternal(&t.mutex); if (t.random_file.fd >= 0) { posix.close(fd); return t.random_file.fd; @@ -16947,8 +16966,8 @@ const parking_futex = struct { var status_buf: std.atomic.Value(Thread.Status) = undefined; { - mutexLockUncancelable(&bucket.mutex); - defer mutexUnlock(&bucket.mutex); + mutexLockInternal(&bucket.mutex); + defer mutexUnlockInternal(&bucket.mutex); _ = bucket.num_waiters.fetchAdd(1, .acquire); @@ -17017,8 +17036,8 @@ const parking_futex = struct { .parked => { // We saw a timeout and updated our own status from `.parked` to `.none`. It is // our responsibility to remove `waiter` from `bucket`. - mutexLockUncancelable(&bucket.mutex); - defer mutexUnlock(&bucket.mutex); + mutexLockInternal(&bucket.mutex); + defer mutexUnlockInternal(&bucket.mutex); bucket.waiters.remove(&waiter.node); assert(bucket.num_waiters.fetchSub(1, .monotonic) > 0); }, @@ -17057,8 +17076,8 @@ const parking_futex = struct { // of the critical section. This forms a singly-linked list of waiters using `Waiter.node.next`. var waking_head: ?*std.DoublyLinkedList.Node = null; { - mutexLockUncancelable(&bucket.mutex); - defer mutexUnlock(&bucket.mutex); + mutexLockInternal(&bucket.mutex); + defer mutexUnlockInternal(&bucket.mutex); var num_removed: u32 = 0; var it = bucket.waiters.first; @@ -17113,8 +17132,8 @@ const parking_futex = struct { fn removeCanceledWaiter(waiter: *Waiter) void { const bucket = bucketForAddress(waiter.address); - mutexLockUncancelable(&bucket.mutex); - defer mutexUnlock(&bucket.mutex); + mutexLockInternal(&bucket.mutex); + defer mutexUnlockInternal(&bucket.mutex); bucket.waiters.remove(&waiter.node); assert(bucket.num_waiters.fetchSub(1, .monotonic) > 0); waiter.done.store(true, .release); // potentially invalidates `waiter.*` @@ -18163,8 +18182,8 @@ fn condWait(cond: *Condition, mutex: *Mutex) void { assert(prev_state.waiters < std.math.maxInt(u16)); // overflow caused by too many waiters } - mutexUnlock(mutex); - defer mutexLockUncancelable(mutex); + mutexUnlockInternal(mutex); + defer mutexLockInternal(mutex); while (true) { Thread.futexWaitUncancelable(&cond.epoch.raw, epoch, null); @@ -18189,28 +18208,13 @@ const Mutex = if (!is_windows) Io.Mutex else struct { const init: @This() = .{ .srwlock = .{} }; }; -/// Same as `Io.Mutex.lockUncancelable` but avoids the VTable. -fn mutexLock(m: *Io.Mutex) Io.Cancelable!void { - const initial_state = m.state.cmpxchgWeak( - .unlocked, - .locked_once, - .acquire, - .monotonic, - ) orelse { - @branchHint(.likely); - return; - }; - if (initial_state == .contended) { - try Thread.futexWait(@ptrCast(&m.state.raw), @intFromEnum(Io.Mutex.State.contended), null); - } - while (m.state.swap(.contended, .acquire) != .unlocked) { - try Thread.futexWait(@ptrCast(&m.state.raw), @intFromEnum(Io.Mutex.State.contended), null); - } -} - -/// Same as `Io.Mutex.lockUncancelable` but avoids the VTable. -fn mutexLockUncancelable(m: *Mutex) void { +fn mutexLockInternal(m: *Mutex) void { if (is_windows) return windows.ntdll.RtlAcquireSRWLockExclusive(&m.srwlock); + return mutexLock(m); +} + +/// Same as `Io.Mutex.lockUncancelable` but avoids the VTable. +pub fn mutexLock(m: *Io.Mutex) void { const initial_state = m.state.cmpxchgWeak( .unlocked, .locked_once, @@ -18228,9 +18232,13 @@ fn mutexLockUncancelable(m: *Mutex) void { } } -/// Same as `Io.Mutex.unlock` but avoids the VTable. -fn mutexUnlock(m: *Mutex) void { +fn mutexUnlockInternal(m: *Mutex) void { if (is_windows) return windows.ntdll.RtlReleaseSRWLockExclusive(&m.srwlock); + return mutexUnlock(m); +} + +/// Same as `Io.Mutex.unlock` but avoids the VTable. +pub fn mutexUnlock(m: *Io.Mutex) void { switch (m.state.swap(.unlocked, .release)) { .unlocked => unreachable, .locked_once => {}, diff --git a/lib/std/Thread.zig b/lib/std/Thread.zig index ef35422e1d2c7013d739dc6a05791de888292b9c..3191dd26ce4563cb10ee2803edf49c9ae59c3e1d 100644 --- a/lib/std/Thread.zig +++ b/lib/std/Thread.zig @@ -14,13 +14,9 @@ const posix = std.posix; const windows = std.os.windows; const testing = std.testing; -pub const Futex = @import("Thread/Futex.zig"); -pub const Mutex = @import("Thread/Mutex.zig"); -pub const Semaphore = @import("Thread/Semaphore.zig"); -pub const Condition = @import("Thread/Condition.zig"); -pub const RwLock = @import("Thread/RwLock.zig"); - -pub const Pool = @compileError("deprecated; consider using 'std.Io.Group' with 'std.Io.Threaded'"); +pub const Mutex = struct { + pub const Recursive = @import("Thread/Mutex/Recursive.zig"); +}; pub const use_pthreads = native_os != .windows and native_os != .wasi and builtin.link_libc; @@ -1609,11 +1605,7 @@ test "setName, getName" { } test { - _ = Futex; _ = Mutex; - _ = Semaphore; - _ = Condition; - _ = RwLock; } fn testIncrementNotify(io: Io, value: *usize, event: *Io.Event) void { diff --git a/lib/std/Thread/Condition.zig b/lib/std/Thread/Condition.zig deleted file mode 100644 index 8917e07a4fee3191c18ffa2c22ca36b0347fb3a8..0000000000000000000000000000000000000000 --- a/lib/std/Thread/Condition.zig +++ /dev/null @@ -1,683 +0,0 @@ -//! Condition variables are used with a Mutex to efficiently wait for an arbitrary condition to occur. -//! It does this by atomically unlocking the mutex, blocking the thread until notified, and finally re-locking the mutex. -//! Condition can be statically initialized and is at most `@sizeOf(u64)` large. -//! -//! Example: -//! ``` -//! var m = Mutex{}; -//! var c = Condition{}; -//! var predicate = false; -//! -//! fn consumer() void { -//! m.lock(); -//! defer m.unlock(); -//! -//! while (!predicate) { -//! c.wait(&m); -//! } -//! } -//! -//! fn producer() void { -//! { -//! m.lock(); -//! defer m.unlock(); -//! predicate = true; -//! } -//! c.signal(); -//! } -//! -//! const thread = try std.Thread.spawn(.{}, producer, .{}); -//! consumer(); -//! thread.join(); -//! ``` -//! -//! Note that condition variables can only reliably unblock threads that are sequenced before them using the same Mutex. -//! This means that the following is allowed to deadlock: -//! ``` -//! thread-1: mutex.lock() -//! thread-1: condition.wait(&mutex) -//! -//! thread-2: // mutex.lock() (without this, the following signal may not see the waiting thread-1) -//! thread-2: // mutex.unlock() (this is optional for correctness once locked above, as signal can be called while holding the mutex) -//! thread-2: condition.signal() -//! ``` - -const std = @import("../std.zig"); -const builtin = @import("builtin"); -const Condition = @This(); -const Mutex = std.Thread.Mutex; - -const os = std.os; -const assert = std.debug.assert; -const testing = std.testing; -const Futex = std.Thread.Futex; - -impl: Impl = .{}, - -/// Atomically releases the Mutex, blocks the caller thread, then re-acquires the Mutex on return. -/// "Atomically" here refers to accesses done on the Condition after acquiring the Mutex. -/// -/// The Mutex must be locked by the caller's thread when this function is called. -/// A Mutex can have multiple Conditions waiting with it concurrently, but not the opposite. -/// It is undefined behavior for multiple threads to wait ith different mutexes using the same Condition concurrently. -/// Once threads have finished waiting with one Mutex, the Condition can be used to wait with another Mutex. -/// -/// A blocking call to wait() is unblocked from one of the following conditions: -/// - a spurious ("at random") wake up occurs -/// - a future call to `signal()` or `broadcast()` which has acquired the Mutex and is sequenced after this `wait()`. -/// -/// Given wait() can be interrupted spuriously, the blocking condition should be checked continuously -/// irrespective of any notifications from `signal()` or `broadcast()`. -pub fn wait(self: *Condition, mutex: *Mutex) void { - self.impl.wait(mutex, null) catch |err| switch (err) { - error.Timeout => unreachable, // no timeout provided so we shouldn't have timed-out - }; -} - -/// Atomically releases the Mutex, blocks the caller thread, then re-acquires the Mutex on return. -/// "Atomically" here refers to accesses done on the Condition after acquiring the Mutex. -/// -/// The Mutex must be locked by the caller's thread when this function is called. -/// A Mutex can have multiple Conditions waiting with it concurrently, but not the opposite. -/// It is undefined behavior for multiple threads to wait ith different mutexes using the same Condition concurrently. -/// Once threads have finished waiting with one Mutex, the Condition can be used to wait with another Mutex. -/// -/// A blocking call to `timedWait()` is unblocked from one of the following conditions: -/// - a spurious ("at random") wake occurs -/// - the caller was blocked for around `timeout_ns` nanoseconds, in which `error.Timeout` is returned. -/// - a future call to `signal()` or `broadcast()` which has acquired the Mutex and is sequenced after this `timedWait()`. -/// -/// Given `timedWait()` can be interrupted spuriously, the blocking condition should be checked continuously -/// irrespective of any notifications from `signal()` or `broadcast()`. -pub fn timedWait(self: *Condition, mutex: *Mutex, timeout_ns: u64) error{Timeout}!void { - return self.impl.wait(mutex, timeout_ns); -} - -/// Unblocks at least one thread blocked in a call to `wait()` or `timedWait()` with a given Mutex. -/// The blocked thread must be sequenced before this call with respect to acquiring the same Mutex in order to be observable for unblocking. -/// `signal()` can be called with or without the relevant Mutex being acquired and have no "effect" if there's no observable blocked threads. -pub fn signal(self: *Condition) void { - self.impl.wake(.one); -} - -/// Unblocks all threads currently blocked in a call to `wait()` or `timedWait()` with a given Mutex. -/// The blocked threads must be sequenced before this call with respect to acquiring the same Mutex in order to be observable for unblocking. -/// `broadcast()` can be called with or without the relevant Mutex being acquired and have no "effect" if there's no observable blocked threads. -pub fn broadcast(self: *Condition) void { - self.impl.wake(.all); -} - -const Impl = Impl: { - if (builtin.single_threaded) break :Impl SingleThreadedImpl; - if (builtin.os.tag == .windows) break :Impl WindowsImpl; - - if (builtin.os.tag.isDarwin() or - builtin.target.os.tag == .linux or - builtin.target.os.tag == .freebsd or - builtin.target.os.tag == .openbsd or - builtin.target.os.tag == .dragonfly or - builtin.target.cpu.arch.isWasm()) - { - // Futex is the system's synchronization primitive; use that. - break :Impl FutexImpl; - } - - if (std.Thread.use_pthreads) { - // This system doesn't have a futex primitive, so `std.Thread.Futex` is using `PosixImpl`, - // which implements futex *on top of* pthread mutexes and conditions. Therefore, instead - // of going through that long inefficient path, just use pthread condition variable directly. - break :Impl PosixImpl; - } - - break :Impl FutexImpl; -}; - -const Notify = enum { - one, // wake up only one thread - all, // wake up all threads -}; - -const SingleThreadedImpl = struct { - fn wait(self: *Impl, mutex: *Mutex, timeout: ?u64) error{Timeout}!void { - _ = self; - _ = mutex; - // There are no other threads to wake us up. - // So if we wait without a timeout we would never wake up. - assert(timeout != null); // Deadlock detected. - return error.Timeout; - } - - fn wake(self: *Impl, comptime notify: Notify) void { - // There are no other threads to wake up. - _ = self; - _ = notify; - } -}; - -const WindowsImpl = struct { - condition: os.windows.CONDITION_VARIABLE = .{}, - - fn wait(self: *Impl, mutex: *Mutex, timeout: ?u64) error{Timeout}!void { - var timeout_overflowed = false; - var timeout_ms: os.windows.DWORD = os.windows.INFINITE; - - if (timeout) |timeout_ns| { - // Round the nanoseconds to the nearest millisecond, - // then saturating cast it to windows DWORD for use in kernel32 call. - const ms = (timeout_ns +| (std.time.ns_per_ms / 2)) / std.time.ns_per_ms; - timeout_ms = std.math.cast(os.windows.DWORD, ms) orelse std.math.maxInt(os.windows.DWORD); - - // Track if the timeout overflowed into INFINITE and make sure not to wait forever. - if (timeout_ms == os.windows.INFINITE) { - timeout_overflowed = true; - timeout_ms -= 1; - } - } - - if (builtin.mode == .Debug) { - // The internal state of the DebugMutex needs to be handled here as well. - mutex.impl.locking_thread.store(0, .unordered); - } - const rc = os.windows.kernel32.SleepConditionVariableSRW( - &self.condition, - if (builtin.mode == .Debug) &mutex.impl.impl.srwlock else &mutex.impl.srwlock, - timeout_ms, - 0, // the srwlock was assumed to acquired in exclusive mode not shared - ); - if (builtin.mode == .Debug) { - // The internal state of the DebugMutex needs to be handled here as well. - mutex.impl.locking_thread.store(std.Thread.getCurrentId(), .unordered); - } - - // Return error.Timeout if we know the timeout elapsed correctly. - if (rc == os.windows.FALSE) { - assert(os.windows.GetLastError() == .TIMEOUT); - if (!timeout_overflowed) return error.Timeout; - } - } - - fn wake(self: *Impl, comptime notify: Notify) void { - switch (notify) { - .one => os.windows.ntdll.RtlWakeConditionVariable(&self.condition), - .all => os.windows.ntdll.RtlWakeAllConditionVariable(&self.condition), - } - } -}; - -const FutexImpl = struct { - state: std.atomic.Value(u32) = std.atomic.Value(u32).init(0), - epoch: std.atomic.Value(u32) = std.atomic.Value(u32).init(0), - - const one_waiter = 1; - const waiter_mask = 0xffff; - - const one_signal = 1 << 16; - const signal_mask = 0xffff << 16; - - fn wait(self: *Impl, mutex: *Mutex, timeout: ?u64) error{Timeout}!void { - // Observe the epoch, then check the state again to see if we should wake up. - // The epoch must be observed before we check the state or we could potentially miss a wake() and deadlock: - // - // - T1: s = LOAD(&state) - // - T2: UPDATE(&s, signal) - // - T2: UPDATE(&epoch, 1) + FUTEX_WAKE(&epoch) - // - T1: e = LOAD(&epoch) (was reordered after the state load) - // - T1: s & signals == 0 -> FUTEX_WAIT(&epoch, e) (missed the state update + the epoch change) - // - // Acquire barrier to ensure the epoch load happens before the state load. - var epoch = self.epoch.load(.acquire); - var state = self.state.fetchAdd(one_waiter, .monotonic); - assert(state & waiter_mask != waiter_mask); - state += one_waiter; - - mutex.unlock(); - defer mutex.lock(); - - var futex_deadline = Futex.Deadline.init(timeout); - - while (true) { - futex_deadline.wait(&self.epoch, epoch) catch |err| switch (err) { - // On timeout, we must decrement the waiter we added above. - error.Timeout => { - while (true) { - // If there's a signal when we're timing out, consume it and report being woken up instead. - // Acquire barrier ensures code before the wake() which added the signal happens before we decrement it and return. - while (state & signal_mask != 0) { - const new_state = state - one_waiter - one_signal; - state = self.state.cmpxchgWeak(state, new_state, .acquire, .monotonic) orelse return; - } - - // Remove the waiter we added and officially return timed out. - const new_state = state - one_waiter; - state = self.state.cmpxchgWeak(state, new_state, .monotonic, .monotonic) orelse return err; - } - }, - }; - - epoch = self.epoch.load(.acquire); - state = self.state.load(.monotonic); - - // Try to wake up by consuming a signal and decremented the waiter we added previously. - // Acquire barrier ensures code before the wake() which added the signal happens before we decrement it and return. - while (state & signal_mask != 0) { - const new_state = state - one_waiter - one_signal; - state = self.state.cmpxchgWeak(state, new_state, .acquire, .monotonic) orelse return; - } - } - } - - fn wake(self: *Impl, comptime notify: Notify) void { - var state = self.state.load(.monotonic); - while (true) { - const waiters = (state & waiter_mask) / one_waiter; - const signals = (state & signal_mask) / one_signal; - - // Reserves which waiters to wake up by incrementing the signals count. - // Therefore, the signals count is always less than or equal to the waiters count. - // We don't need to Futex.wake if there's nothing to wake up or if other wake() threads have reserved to wake up the current waiters. - const wakeable = waiters - signals; - if (wakeable == 0) { - return; - } - - const to_wake = switch (notify) { - .one => 1, - .all => wakeable, - }; - - // Reserve the amount of waiters to wake by incrementing the signals count. - // Release barrier ensures code before the wake() happens before the signal it posted and consumed by the wait() threads. - const new_state = state + (one_signal * to_wake); - state = self.state.cmpxchgWeak(state, new_state, .release, .monotonic) orelse { - // Wake up the waiting threads we reserved above by changing the epoch value. - // NOTE: a waiting thread could miss a wake up if *exactly* ((1<<32)-1) wake()s happen between it observing the epoch and sleeping on it. - // This is very unlikely due to how many precise amount of Futex.wake() calls that would be between the waiting thread's potential preemption. - // - // Release barrier ensures the signal being added to the state happens before the epoch is changed. - // If not, the waiting thread could potentially deadlock from missing both the state and epoch change: - // - // - T2: UPDATE(&epoch, 1) (reordered before the state change) - // - T1: e = LOAD(&epoch) - // - T1: s = LOAD(&state) - // - T2: UPDATE(&state, signal) + FUTEX_WAKE(&epoch) - // - T1: s & signals == 0 -> FUTEX_WAIT(&epoch, e) (missed both epoch change and state change) - _ = self.epoch.fetchAdd(1, .release); - Futex.wake(&self.epoch, to_wake); - return; - }; - } - } -}; - -const PosixImpl = struct { - cond: std.c.pthread_cond_t = .{}, - - fn wait(self: *Impl, mutex: *Mutex, timeout: ?u64) error{Timeout}!void { - if (builtin.mode == .Debug) { - mutex.impl.locking_thread.store(0, .unordered); - } - defer if (builtin.mode == .Debug) { - mutex.impl.locking_thread.store(std.Thread.getCurrentId(), .unordered); - }; - - const mtx = if (builtin.mode == .Debug) &mutex.impl.impl.mutex else &mutex.impl.mutex; - - if (timeout) |t| { - switch (std.c.pthread_cond_timedwait(&self.cond, mtx, &.{ - .sec = @intCast(@divFloor(t, std.time.ns_per_s)), - .nsec = @intCast(@mod(t, std.time.ns_per_s)), - })) { - .SUCCESS => return, - .TIMEDOUT => return error.Timeout, - else => unreachable, - } - } - - assert(std.c.pthread_cond_wait(&self.cond, mtx) == .SUCCESS); - } - - fn wake(self: *Impl, comptime notify: Notify) void { - assert(switch (notify) { - .one => std.c.pthread_cond_signal(&self.cond), - .all => std.c.pthread_cond_broadcast(&self.cond), - } == .SUCCESS); - } -}; - -test "smoke test" { - var mutex = Mutex{}; - var cond = Condition{}; - - // Try to wake outside the mutex - defer cond.signal(); - defer cond.broadcast(); - - mutex.lock(); - defer mutex.unlock(); - - // Try to wait with a timeout (should not deadlock) - try testing.expectError(error.Timeout, cond.timedWait(&mutex, 0)); - try testing.expectError(error.Timeout, cond.timedWait(&mutex, std.time.ns_per_ms)); - - // Try to wake inside the mutex. - cond.signal(); - cond.broadcast(); -} - -// Inspired from: https://github.com/Amanieu/parking_lot/pull/129 -test "wait and signal" { - // This test requires spawning threads - if (builtin.single_threaded) { - return error.SkipZigTest; - } - - const io = testing.io; - - const num_threads = 4; - - const MultiWait = struct { - mutex: Mutex = .{}, - cond: Condition = .{}, - threads: [num_threads]std.Thread = undefined, - spawn_count: std.math.IntFittingRange(0, num_threads) = 0, - - fn run(self: *@This()) void { - self.mutex.lock(); - defer self.mutex.unlock(); - self.spawn_count += 1; - - self.cond.wait(&self.mutex); - self.cond.timedWait(&self.mutex, std.time.ns_per_ms) catch {}; - self.cond.signal(); - } - }; - - var multi_wait = MultiWait{}; - for (&multi_wait.threads) |*t| { - t.* = try std.Thread.spawn(.{}, MultiWait.run, .{&multi_wait}); - } - - while (true) { - try std.Io.Clock.Duration.sleep(.{ .clock = .awake, .raw = .fromMilliseconds(100) }, io); - - multi_wait.mutex.lock(); - defer multi_wait.mutex.unlock(); - // Make sure all of the threads have finished spawning to avoid a deadlock. - if (multi_wait.spawn_count == num_threads) break; - } - - multi_wait.cond.signal(); - for (multi_wait.threads) |t| { - t.join(); - } -} - -test signal { - // This test requires spawning threads - if (builtin.single_threaded) { - return error.SkipZigTest; - } - - const io = testing.io; - - const num_threads = 4; - - const SignalTest = struct { - mutex: Mutex = .{}, - cond: Condition = .{}, - notified: bool = false, - threads: [num_threads]std.Thread = undefined, - spawn_count: std.math.IntFittingRange(0, num_threads) = 0, - - fn run(self: *@This()) void { - self.mutex.lock(); - defer self.mutex.unlock(); - self.spawn_count += 1; - - // Use timedWait() a few times before using wait() - // to test multiple threads timing out frequently. - var i: usize = 0; - while (!self.notified) : (i +%= 1) { - if (i < 5) { - self.cond.timedWait(&self.mutex, 1) catch {}; - } else { - self.cond.wait(&self.mutex); - } - } - - // Once we received the signal, notify another thread (inside the lock). - assert(self.notified); - self.cond.signal(); - } - }; - - var signal_test = SignalTest{}; - for (&signal_test.threads) |*t| { - t.* = try std.Thread.spawn(.{}, SignalTest.run, .{&signal_test}); - } - - while (true) { - try std.Io.Clock.Duration.sleep(.{ .clock = .awake, .raw = .fromMilliseconds(10) }, io); - - signal_test.mutex.lock(); - defer signal_test.mutex.unlock(); - // Make sure at least one thread has finished spawning to avoid testing nothing. - if (signal_test.spawn_count > 0) break; - } - - { - // Wake up one of them (outside the lock) after setting notified=true. - defer signal_test.cond.signal(); - - signal_test.mutex.lock(); - defer signal_test.mutex.unlock(); - - try testing.expect(!signal_test.notified); - signal_test.notified = true; - } - - for (signal_test.threads) |t| { - t.join(); - } -} - -test "multi signal" { - // This test requires spawning threads - if (builtin.single_threaded) { - return error.SkipZigTest; - } - - const num_threads = 4; - const num_iterations = 4; - - const Paddle = struct { - mutex: Mutex = .{}, - cond: Condition = .{}, - value: u32 = 0, - - fn hit(self: *@This()) void { - defer self.cond.signal(); - - self.mutex.lock(); - defer self.mutex.unlock(); - - self.value += 1; - } - - fn run(self: *@This(), hit_to: *@This()) !void { - self.mutex.lock(); - defer self.mutex.unlock(); - - var current: u32 = 0; - while (current < num_iterations) : (current += 1) { - // Wait for the value to change from hit() - while (self.value == current) { - self.cond.wait(&self.mutex); - } - - // hit the next paddle - try testing.expectEqual(self.value, current + 1); - hit_to.hit(); - } - } - }; - - var paddles = [_]Paddle{.{}} ** num_threads; - var threads = [_]std.Thread{undefined} ** num_threads; - - // Create a circle of paddles which hit each other - for (&threads, 0..) |*t, i| { - const paddle = &paddles[i]; - const hit_to = &paddles[(i + 1) % paddles.len]; - t.* = try std.Thread.spawn(.{}, Paddle.run, .{ paddle, hit_to }); - } - - // Hit the first paddle and wait for them all to complete by hitting each other for num_iterations. - paddles[0].hit(); - for (threads) |t| t.join(); - - // The first paddle will be hit one last time by the last paddle. - for (paddles, 0..) |p, i| { - const expected = @as(u32, num_iterations) + @intFromBool(i == 0); - try testing.expectEqual(p.value, expected); - } -} - -test broadcast { - // This test requires spawning threads - if (builtin.single_threaded) { - return error.SkipZigTest; - } - - const num_threads = 10; - - const BroadcastTest = struct { - mutex: Mutex = .{}, - cond: Condition = .{}, - completed: Condition = .{}, - count: usize = 0, - threads: [num_threads]std.Thread = undefined, - - fn run(self: *@This()) void { - self.mutex.lock(); - defer self.mutex.unlock(); - - // The last broadcast thread to start tells the main test thread it's completed. - self.count += 1; - if (self.count == num_threads) { - self.completed.signal(); - } - - // Waits for the count to reach zero after the main test thread observes it at num_threads. - // Tries to use timedWait() a bit before falling back to wait() to test multiple threads timing out. - var i: usize = 0; - while (self.count != 0) : (i +%= 1) { - if (i < 10) { - self.cond.timedWait(&self.mutex, 1) catch {}; - } else { - self.cond.wait(&self.mutex); - } - } - } - }; - - var broadcast_test = BroadcastTest{}; - for (&broadcast_test.threads) |*t| { - t.* = try std.Thread.spawn(.{}, BroadcastTest.run, .{&broadcast_test}); - } - - { - broadcast_test.mutex.lock(); - defer broadcast_test.mutex.unlock(); - - // Wait for all the broadcast threads to spawn. - // timedWait() to detect any potential deadlocks. - while (broadcast_test.count != num_threads) { - broadcast_test.completed.timedWait( - &broadcast_test.mutex, - 1 * std.time.ns_per_s, - ) catch {}; - } - - // Reset the counter and wake all the threads to exit. - broadcast_test.count = 0; - broadcast_test.cond.broadcast(); - } - - for (broadcast_test.threads) |t| { - t.join(); - } -} - -test "broadcasting - wake all threads" { - // Tests issue #12877 - // This test requires spawning threads - if (builtin.single_threaded) { - return error.SkipZigTest; - } - - var num_runs: usize = 1; - const num_threads = 10; - - while (num_runs > 0) : (num_runs -= 1) { - const BroadcastTest = struct { - mutex: Mutex = .{}, - cond: Condition = .{}, - completed: Condition = .{}, - count: usize = 0, - thread_id_to_wake: usize = 0, - threads: [num_threads]std.Thread = undefined, - wakeups: usize = 0, - - fn run(self: *@This(), thread_id: usize) void { - self.mutex.lock(); - defer self.mutex.unlock(); - - // The last broadcast thread to start tells the main test thread it's completed. - self.count += 1; - if (self.count == num_threads) { - self.completed.signal(); - } - - while (self.thread_id_to_wake != thread_id) { - self.cond.timedWait(&self.mutex, 1 * std.time.ns_per_s) catch {}; - self.wakeups += 1; - } - if (self.thread_id_to_wake <= num_threads) { - // Signal next thread to wake up. - self.thread_id_to_wake += 1; - self.cond.broadcast(); - } - } - }; - - var broadcast_test = BroadcastTest{}; - var thread_id: usize = 1; - for (&broadcast_test.threads) |*t| { - t.* = try std.Thread.spawn(.{}, BroadcastTest.run, .{ &broadcast_test, thread_id }); - thread_id += 1; - } - - { - broadcast_test.mutex.lock(); - defer broadcast_test.mutex.unlock(); - - // Wait for all the broadcast threads to spawn. - // timedWait() to detect any potential deadlocks. - while (broadcast_test.count != num_threads) { - broadcast_test.completed.timedWait( - &broadcast_test.mutex, - 1 * std.time.ns_per_s, - ) catch {}; - } - - // Signal thread 1 to wake up - broadcast_test.thread_id_to_wake = 1; - broadcast_test.cond.broadcast(); - } - - for (broadcast_test.threads) |t| { - t.join(); - } - } -} diff --git a/lib/std/Thread/Futex.zig b/lib/std/Thread/Futex.zig deleted file mode 100644 index 7c44621d7221797a14e6163ce92c6b9e019c8cfd..0000000000000000000000000000000000000000 --- a/lib/std/Thread/Futex.zig +++ /dev/null @@ -1,1063 +0,0 @@ -//! A mechanism used to block (`wait`) and unblock (`wake`) threads using a -//! 32bit memory address as hints. -//! -//! Blocking a thread is acknowledged only if the 32bit memory address is equal -//! to a given value. This check helps avoid block/unblock deadlocks which -//! occur if a `wake()` happens before a `wait()`. -//! -//! Using Futex, other Thread synchronization primitives can be built which -//! efficiently wait for cross-thread events or signals. - -const std = @import("../std.zig"); -const builtin = @import("builtin"); -const Futex = @This(); -const windows = std.os.windows; -const linux = std.os.linux; -const c = std.c; - -const assert = std.debug.assert; -const testing = std.testing; -const atomic = std.atomic; - -/// Checks if `ptr` still contains the value `expect` and, if so, blocks the caller until either: -/// - The value at `ptr` is no longer equal to `expect` and `wake()` is called on the same address. -/// - The caller is unblocked spuriously ("at random"). -/// -/// The checking of `ptr` and `expect`, along with blocking the caller, is done atomically -/// and totally ordered (sequentially consistent) with respect to other wait()/wake() calls on the same `ptr`. -pub fn wait(ptr: *const atomic.Value(u32), expect: u32) void { - @branchHint(.cold); - - Impl.wait(ptr, expect, null) catch |err| switch (err) { - error.Timeout => unreachable, // null timeout meant to wait forever - }; -} - -/// Checks if `ptr` still contains the value `expect` and, if so, blocks the caller until either: -/// - The value at `ptr` is no longer equal to `expect`. -/// - The caller is unblocked by a matching `wake()`. -/// - The caller is unblocked spuriously ("at random"). -/// - The caller blocks for longer than the given timeout. In which case, `error.Timeout` is returned. -/// -/// The checking of `ptr` and `expect`, along with blocking the caller, is done atomically -/// and totally ordered (sequentially consistent) with respect to other wait()/wake() calls on the same `ptr`. -pub fn timedWait(ptr: *const atomic.Value(u32), expect: u32, timeout_ns: u64) error{Timeout}!void { - @branchHint(.cold); - - // Avoid calling into the OS for no-op timeouts. - if (timeout_ns == 0) { - if (ptr.load(.seq_cst) != expect) return; - return error.Timeout; - } - - return Impl.wait(ptr, expect, timeout_ns); -} - -/// Unblocks at most `max_waiters` callers blocked in a `wait()` call on `ptr`. -pub fn wake(ptr: *const atomic.Value(u32), max_waiters: u32) void { - @branchHint(.cold); - - // Avoid calling into the OS if there's nothing to wake up. - if (max_waiters == 0) { - return; - } - - Impl.wake(ptr, max_waiters); -} - -const Impl = if (builtin.single_threaded) - SingleThreadedImpl -else if (builtin.os.tag == .windows) - WindowsImpl -else if (builtin.os.tag.isDarwin()) - DarwinImpl -else if (builtin.os.tag == .linux) - LinuxImpl -else if (builtin.os.tag == .freebsd) - FreebsdImpl -else if (builtin.os.tag == .openbsd) - OpenbsdImpl -else if (builtin.os.tag == .dragonfly) - DragonflyImpl -else if (builtin.target.cpu.arch.isWasm()) - WasmImpl -else if (std.Thread.use_pthreads) - PosixImpl -else - UnsupportedImpl; - -/// We can't do @compileError() in the `Impl` switch statement above as its eagerly evaluated. -/// So instead, we @compileError() on the methods themselves for platforms which don't support futex. -const UnsupportedImpl = struct { - fn wait(ptr: *const atomic.Value(u32), expect: u32, timeout: ?u64) error{Timeout}!void { - return unsupported(.{ ptr, expect, timeout }); - } - - fn wake(ptr: *const atomic.Value(u32), max_waiters: u32) void { - return unsupported(.{ ptr, max_waiters }); - } - - fn unsupported(unused: anytype) noreturn { - _ = unused; - @compileError("Unsupported operating system " ++ @tagName(builtin.target.os.tag)); - } -}; - -const SingleThreadedImpl = struct { - fn wait(ptr: *const atomic.Value(u32), expect: u32, timeout: ?u64) error{Timeout}!void { - if (ptr.raw != expect) { - return; - } - - // There are no threads to wake us up. - // So if we wait without a timeout we would never wake up. - const delay = timeout orelse { - unreachable; // deadlock detected - }; - - _ = delay; - return error.Timeout; - } - - fn wake(ptr: *const atomic.Value(u32), max_waiters: u32) void { - // There are no other threads to possibly wake up - _ = ptr; - _ = max_waiters; - } -}; - -// We use WaitOnAddress through NtDll instead of API-MS-Win-Core-Synch-l1-2-0.dll -// as it's generally already a linked target and is autoloaded into all processes anyway. -const WindowsImpl = struct { - fn wait(ptr: *const atomic.Value(u32), expect: u32, timeout: ?u64) error{Timeout}!void { - var timeout_value: windows.LARGE_INTEGER = undefined; - var timeout_ptr: ?*const windows.LARGE_INTEGER = null; - - // NTDLL functions work with time in units of 100 nanoseconds. - // Positive values are absolute deadlines while negative values are relative durations. - if (timeout) |delay| { - timeout_value = @as(windows.LARGE_INTEGER, @intCast(delay / 100)); - timeout_value = -timeout_value; - timeout_ptr = &timeout_value; - } - - const rc = windows.ntdll.RtlWaitOnAddress( - ptr, - &expect, - @sizeOf(@TypeOf(expect)), - timeout_ptr, - ); - - switch (rc) { - .SUCCESS => {}, - .TIMEOUT => { - assert(timeout != null); - return error.Timeout; - }, - else => unreachable, - } - } - - fn wake(ptr: *const atomic.Value(u32), max_waiters: u32) void { - const address: ?*const anyopaque = ptr; - assert(max_waiters != 0); - - switch (max_waiters) { - 1 => windows.ntdll.RtlWakeAddressSingle(address), - else => windows.ntdll.RtlWakeAddressAll(address), - } - } -}; - -const DarwinImpl = struct { - fn wait(ptr: *const atomic.Value(u32), expect: u32, timeout: ?u64) error{Timeout}!void { - // Darwin XNU 7195.50.7.100.1 introduced __ulock_wait2 and migrated code paths (notably pthread_cond_t) towards it: - // https://github.com/apple/darwin-xnu/commit/d4061fb0260b3ed486147341b72468f836ed6c8f#diff-08f993cc40af475663274687b7c326cc6c3031e0db3ac8de7b24624610616be6 - // - // This XNU version appears to correspond to 11.0.1: - // https://kernelshaman.blogspot.com/2021/01/building-xnu-for-macos-big-sur-1101.html - // - // ulock_wait() uses 32-bit micro-second timeouts where 0 = INFINITE or no-timeout - // ulock_wait2() uses 64-bit nano-second timeouts (with the same convention) - const supports_ulock_wait2 = builtin.target.os.version_range.semver.min.major >= 11; - - var timeout_ns: u64 = 0; - if (timeout) |delay| { - assert(delay != 0); // handled by timedWait() - timeout_ns = delay; - } - - // If we're using `__ulock_wait` and `timeout` is too big to fit inside a `u32` count of - // micro-seconds (around 70min), we'll request a shorter timeout. This is fine (users - // should handle spurious wakeups), but we need to remember that we did so, so that - // we don't return `Timeout` incorrectly. If that happens, we set this variable to - // true so that we we know to ignore the ETIMEDOUT result. - var timeout_overflowed = false; - - const addr: *const anyopaque = ptr; - const flags: c.UL = .{ - .op = .COMPARE_AND_WAIT, - .NO_ERRNO = true, - }; - const status = blk: { - if (supports_ulock_wait2) { - break :blk c.__ulock_wait2(flags, addr, expect, timeout_ns, 0); - } - - const timeout_us = std.math.cast(u32, timeout_ns / std.time.ns_per_us) orelse overflow: { - timeout_overflowed = true; - break :overflow std.math.maxInt(u32); - }; - - break :blk c.__ulock_wait(flags, addr, expect, timeout_us); - }; - - if (status >= 0) return; - switch (@as(c.E, @enumFromInt(-status))) { - // Wait was interrupted by the OS or other spurious signalling. - .INTR => {}, - // Address of the futex was paged out. This is unlikely, but possible in theory, and - // pthread/libdispatch on darwin bother to handle it. In this case we'll return - // without waiting, but the caller should retry anyway. - .FAULT => {}, - // Only report Timeout if we didn't have to cap the timeout - .TIMEDOUT => { - assert(timeout != null); - if (!timeout_overflowed) return error.Timeout; - }, - else => unreachable, - } - } - - fn wake(ptr: *const atomic.Value(u32), max_waiters: u32) void { - const flags: c.UL = .{ - .op = .COMPARE_AND_WAIT, - .NO_ERRNO = true, - .WAKE_ALL = max_waiters > 1, - }; - - while (true) { - const addr: *const anyopaque = ptr; - const status = c.__ulock_wake(flags, addr, 0); - - if (status >= 0) return; - switch (@as(c.E, @enumFromInt(-status))) { - .INTR => continue, // spurious wake() - .FAULT => unreachable, // __ulock_wake doesn't generate EFAULT according to darwin pthread_cond_t - .NOENT => return, // nothing was woken up - .ALREADY => unreachable, // only for UL.Op.WAKE_THREAD - else => unreachable, - } - } - } -}; - -// https://man7.org/linux/man-pages/man2/futex.2.html -const LinuxImpl = struct { - fn wait(ptr: *const atomic.Value(u32), expect: u32, timeout: ?u64) error{Timeout}!void { - var ts: linux.timespec = undefined; - if (timeout) |timeout_ns| { - ts.sec = @as(@TypeOf(ts.sec), @intCast(timeout_ns / std.time.ns_per_s)); - ts.nsec = @as(@TypeOf(ts.nsec), @intCast(timeout_ns % std.time.ns_per_s)); - } - - const rc = linux.futex_4arg( - &ptr.raw, - .{ .cmd = .WAIT, .private = true }, - expect, - if (timeout != null) &ts else null, - ); - - switch (linux.errno(rc)) { - .SUCCESS => {}, // notified by `wake()` - .INTR => {}, // spurious wakeup - .AGAIN => {}, // ptr.* != expect - .TIMEDOUT => { - assert(timeout != null); - return error.Timeout; - }, - .INVAL => {}, // possibly timeout overflow - .FAULT => unreachable, // ptr was invalid - else => unreachable, - } - } - - fn wake(ptr: *const atomic.Value(u32), max_waiters: u32) void { - const rc = linux.futex_3arg( - &ptr.raw, - .{ .cmd = .WAKE, .private = true }, - @min(max_waiters, std.math.maxInt(i32)), - ); - - switch (linux.errno(rc)) { - .SUCCESS => {}, // successful wake up - .INVAL => {}, // invalid futex_wait() on ptr done elsewhere - .FAULT => {}, // pointer became invalid while doing the wake - else => unreachable, - } - } -}; - -// https://www.freebsd.org/cgi/man.cgi?query=_umtx_op&sektion=2&n=1 -const FreebsdImpl = struct { - fn wait(ptr: *const atomic.Value(u32), expect: u32, timeout: ?u64) error{Timeout}!void { - var tm_size: usize = 0; - var tm: c._umtx_time = undefined; - var tm_ptr: ?*const c._umtx_time = null; - - if (timeout) |timeout_ns| { - tm_ptr = &tm; - tm_size = @sizeOf(@TypeOf(tm)); - - tm.flags = 0; // use relative time not UMTX_ABSTIME - tm.clockid = .MONOTONIC; - tm.timeout.sec = @as(@TypeOf(tm.timeout.sec), @intCast(timeout_ns / std.time.ns_per_s)); - tm.timeout.nsec = @as(@TypeOf(tm.timeout.nsec), @intCast(timeout_ns % std.time.ns_per_s)); - } - - const rc = c._umtx_op( - @intFromPtr(&ptr.raw), - @intFromEnum(c.UMTX_OP.WAIT_UINT_PRIVATE), - @as(c_ulong, expect), - tm_size, - @intFromPtr(tm_ptr), - ); - - switch (std.posix.errno(rc)) { - .SUCCESS => {}, - .FAULT => unreachable, // one of the args points to invalid memory - .INVAL => unreachable, // arguments should be correct - .TIMEDOUT => { - assert(timeout != null); - return error.Timeout; - }, - .INTR => {}, // spurious wake - else => unreachable, - } - } - - fn wake(ptr: *const atomic.Value(u32), max_waiters: u32) void { - const rc = c._umtx_op( - @intFromPtr(&ptr.raw), - @intFromEnum(c.UMTX_OP.WAKE_PRIVATE), - @as(c_ulong, max_waiters), - 0, // there is no timeout struct - 0, // there is no timeout struct pointer - ); - - switch (std.posix.errno(rc)) { - .SUCCESS => {}, - .FAULT => {}, // it's ok if the ptr doesn't point to valid memory - .INVAL => unreachable, // arguments should be correct - else => unreachable, - } - } -}; - -// https://man.openbsd.org/futex.2 -const OpenbsdImpl = struct { - fn wait(ptr: *const atomic.Value(u32), expect: u32, timeout: ?u64) error{Timeout}!void { - var ts: c.timespec = undefined; - if (timeout) |timeout_ns| { - ts.sec = @as(@TypeOf(ts.sec), @intCast(timeout_ns / std.time.ns_per_s)); - ts.nsec = @as(@TypeOf(ts.nsec), @intCast(timeout_ns % std.time.ns_per_s)); - } - - const rc = c.futex( - @as(*const volatile u32, @ptrCast(&ptr.raw)), - c.FUTEX.WAIT | c.FUTEX.PRIVATE_FLAG, - @as(c_int, @bitCast(expect)), - if (timeout != null) &ts else null, - null, // FUTEX.WAIT takes no requeue address - ); - - switch (std.posix.errno(rc)) { - .SUCCESS => {}, // woken up by wake - .NOSYS => unreachable, // the futex operation shouldn't be invalid - .FAULT => unreachable, // ptr was invalid - .AGAIN => {}, // ptr != expect - .INVAL => unreachable, // invalid timeout - .TIMEDOUT => { - assert(timeout != null); - return error.Timeout; - }, - .INTR => {}, // spurious wake from signal - .CANCELED => {}, // spurious wake from signal with SA_RESTART - else => unreachable, - } - } - - fn wake(ptr: *const atomic.Value(u32), max_waiters: u32) void { - const rc = c.futex( - @as(*const volatile u32, @ptrCast(&ptr.raw)), - c.FUTEX.WAKE | c.FUTEX.PRIVATE_FLAG, - std.math.cast(c_int, max_waiters) orelse std.math.maxInt(c_int), - null, // FUTEX.WAKE takes no timeout ptr - null, // FUTEX.WAKE takes no requeue address - ); - - // returns number of threads woken up. - assert(rc >= 0); - } -}; - -// https://man.dragonflybsd.org/?command=umtx§ion=2 -const DragonflyImpl = struct { - fn wait(ptr: *const atomic.Value(u32), expect: u32, timeout: ?u64) error{Timeout}!void { - // Dragonfly uses a scheme where 0 timeout means wait until signaled or spurious wake. - // It's reporting of timeout's is also unrealiable so we use an external timing source (Timer) instead. - var timeout_us: c_int = 0; - var timeout_overflowed = false; - var sleep_timer: std.time.Timer = undefined; - - if (timeout) |delay| { - assert(delay != 0); // handled by timedWait(). - timeout_us = std.math.cast(c_int, delay / std.time.ns_per_us) orelse blk: { - timeout_overflowed = true; - break :blk std.math.maxInt(c_int); - }; - - // Only need to record the start time if we can provide somewhat accurate error.Timeout's - if (!timeout_overflowed) { - sleep_timer = std.time.Timer.start() catch unreachable; - } - } - - const value = @as(c_int, @bitCast(expect)); - const addr = @as(*const volatile c_int, @ptrCast(&ptr.raw)); - const rc = c.umtx_sleep(addr, value, timeout_us); - - switch (std.posix.errno(rc)) { - .SUCCESS => {}, - .BUSY => {}, // ptr != expect - .AGAIN => { // maybe timed out, or paged out, or hit 2s kernel refresh - if (timeout) |timeout_ns| { - // Report error.Timeout only if we know the timeout duration has passed. - // If not, there's not much choice other than treating it as a spurious wake. - if (!timeout_overflowed and sleep_timer.read() >= timeout_ns) { - return error.Timeout; - } - } - }, - .INTR => {}, // spurious wake - .INVAL => unreachable, // invalid timeout - else => unreachable, - } - } - - fn wake(ptr: *const atomic.Value(u32), max_waiters: u32) void { - // A count of zero means wake all waiters. - assert(max_waiters != 0); - const to_wake = std.math.cast(c_int, max_waiters) orelse 0; - - // https://man.dragonflybsd.org/?command=umtx§ion=2 - // > umtx_wakeup() will generally return 0 unless the address is bad. - // We are fine with the address being bad (e.g. for Semaphore.post() where Semaphore.wait() frees the Semaphore) - const addr = @as(*const volatile c_int, @ptrCast(&ptr.raw)); - _ = c.umtx_wakeup(addr, to_wake); - } -}; - -const WasmImpl = struct { - fn wait(ptr: *const atomic.Value(u32), expect: u32, timeout: ?u64) error{Timeout}!void { - if (!comptime builtin.cpu.has(.wasm, .atomics)) @compileError("WASI target missing cpu feature 'atomics'"); - - const to: i64 = if (timeout) |to| @intCast(to) else -1; - const result = asm volatile ( - \\local.get %[ptr] - \\local.get %[expected] - \\local.get %[timeout] - \\memory.atomic.wait32 0 - \\local.set %[ret] - : [ret] "=r" (-> u32), - : [ptr] "r" (&ptr.raw), - [expected] "r" (@as(i32, @bitCast(expect))), - [timeout] "r" (to), - ); - switch (result) { - 0 => {}, // ok - 1 => {}, // expected =! loaded - 2 => return error.Timeout, - else => unreachable, - } - } - - fn wake(ptr: *const atomic.Value(u32), max_waiters: u32) void { - if (!comptime builtin.cpu.has(.wasm, .atomics)) @compileError("WASI target missing cpu feature 'atomics'"); - - assert(max_waiters != 0); - const woken_count = asm volatile ( - \\local.get %[ptr] - \\local.get %[waiters] - \\memory.atomic.notify 0 - \\local.set %[ret] - : [ret] "=r" (-> u32), - : [ptr] "r" (&ptr.raw), - [waiters] "r" (max_waiters), - ); - _ = woken_count; // can be 0 when linker flag 'shared-memory' is not enabled - } -}; - -/// Modified version of linux's futex and Go's sema to implement userspace wait queues with pthread: -/// https://code.woboq.org/linux/linux/kernel/futex.c.html -/// https://go.dev/src/runtime/sema.go -const PosixImpl = struct { - const Event = struct { - cond: c.pthread_cond_t, - mutex: c.pthread_mutex_t, - state: enum { empty, waiting, notified }, - - fn init(self: *Event) void { - // Use static init instead of pthread_cond/mutex_init() since this is generally faster. - self.cond = .{}; - self.mutex = .{}; - self.state = .empty; - } - - fn deinit(self: *Event) void { - // Some platforms reportedly give EINVAL for statically initialized pthread types. - const rc = c.pthread_cond_destroy(&self.cond); - assert(rc == .SUCCESS or rc == .INVAL); - - const rm = c.pthread_mutex_destroy(&self.mutex); - assert(rm == .SUCCESS or rm == .INVAL); - - self.* = undefined; - } - - fn wait(self: *Event, timeout: ?u64) error{Timeout}!void { - assert(c.pthread_mutex_lock(&self.mutex) == .SUCCESS); - defer assert(c.pthread_mutex_unlock(&self.mutex) == .SUCCESS); - - // Early return if the event was already set. - if (self.state == .notified) { - return; - } - - // Compute the absolute timeout if one was specified. - // POSIX requires that REALTIME is used by default for the pthread timedwait functions. - // This can be changed with pthread_condattr_setclock, but it's an extension and may not be available everywhere. - var ts: c.timespec = undefined; - if (timeout) |timeout_ns| { - ts = std.posix.clock_gettime(c.CLOCK.REALTIME) catch unreachable; - ts.sec +|= @as(@TypeOf(ts.sec), @intCast(timeout_ns / std.time.ns_per_s)); - ts.nsec += @as(@TypeOf(ts.nsec), @intCast(timeout_ns % std.time.ns_per_s)); - - if (ts.nsec >= std.time.ns_per_s) { - ts.sec +|= 1; - ts.nsec -= std.time.ns_per_s; - } - } - - // Start waiting on the event - there can be only one thread waiting. - assert(self.state == .empty); - self.state = .waiting; - - while (true) { - // Block using either pthread_cond_wait or pthread_cond_timewait if there's an absolute timeout. - const rc = blk: { - if (timeout == null) break :blk c.pthread_cond_wait(&self.cond, &self.mutex); - break :blk c.pthread_cond_timedwait(&self.cond, &self.mutex, &ts); - }; - - // After waking up, check if the event was set. - if (self.state == .notified) { - return; - } - - assert(self.state == .waiting); - switch (rc) { - .SUCCESS => {}, - .TIMEDOUT => { - // If timed out, reset the event to avoid the set() thread doing an unnecessary signal(). - self.state = .empty; - return error.Timeout; - }, - .INVAL => unreachable, // cond, mutex, and potentially ts should all be valid - .PERM => unreachable, // mutex is locked when cond_*wait() functions are called - else => unreachable, - } - } - } - - fn set(self: *Event) void { - assert(c.pthread_mutex_lock(&self.mutex) == .SUCCESS); - defer assert(c.pthread_mutex_unlock(&self.mutex) == .SUCCESS); - - // Make sure that multiple calls to set() were not done on the same Event. - const old_state = self.state; - assert(old_state != .notified); - - // Mark the event as set and wake up the waiting thread if there was one. - // This must be done while the mutex as the wait() thread could deallocate - // the condition variable once it observes the new state, potentially causing a UAF if done unlocked. - self.state = .notified; - if (old_state == .waiting) { - assert(c.pthread_cond_signal(&self.cond) == .SUCCESS); - } - } - }; - - const Treap = std.Treap(usize, std.math.order); - const Waiter = struct { - node: Treap.Node, - prev: ?*Waiter, - next: ?*Waiter, - tail: ?*Waiter, - is_queued: bool, - event: Event, - }; - - // An unordered set of Waiters - const WaitList = struct { - top: ?*Waiter = null, - len: usize = 0, - - fn push(self: *WaitList, waiter: *Waiter) void { - waiter.next = self.top; - self.top = waiter; - self.len += 1; - } - - fn pop(self: *WaitList) ?*Waiter { - const waiter = self.top orelse return null; - self.top = waiter.next; - self.len -= 1; - return waiter; - } - }; - - const WaitQueue = struct { - fn insert(treap: *Treap, address: usize, waiter: *Waiter) void { - // prepare the waiter to be inserted. - waiter.next = null; - waiter.is_queued = true; - - // Find the wait queue entry associated with the address. - // If there isn't a wait queue on the address, this waiter creates the queue. - var entry = treap.getEntryFor(address); - const entry_node = entry.node orelse { - waiter.prev = null; - waiter.tail = waiter; - entry.set(&waiter.node); - return; - }; - - // There's a wait queue on the address; get the queue head and tail. - const head: *Waiter = @fieldParentPtr("node", entry_node); - const tail = head.tail orelse unreachable; - - // Push the waiter to the tail by replacing it and linking to the previous tail. - head.tail = waiter; - tail.next = waiter; - waiter.prev = tail; - } - - fn remove(treap: *Treap, address: usize, max_waiters: usize) WaitList { - // Find the wait queue associated with this address and get the head/tail if any. - var entry = treap.getEntryFor(address); - var queue_head: ?*Waiter = if (entry.node) |node| @fieldParentPtr("node", node) else null; - const queue_tail = if (queue_head) |head| head.tail else null; - - // Once we're done updating the head, fix it's tail pointer and update the treap's queue head as well. - defer entry.set(blk: { - const new_head = queue_head orelse break :blk null; - new_head.tail = queue_tail; - break :blk &new_head.node; - }); - - var removed = WaitList{}; - while (removed.len < max_waiters) { - // dequeue and collect waiters from their wait queue. - const waiter = queue_head orelse break; - queue_head = waiter.next; - removed.push(waiter); - - // When dequeueing, we must mark is_queued as false. - // This ensures that a waiter which calls tryRemove() returns false. - assert(waiter.is_queued); - waiter.is_queued = false; - } - - return removed; - } - - fn tryRemove(treap: *Treap, address: usize, waiter: *Waiter) bool { - if (!waiter.is_queued) { - return false; - } - - queue_remove: { - // Find the wait queue associated with the address. - var entry = blk: { - // A waiter without a previous link means it's the queue head that's in the treap so we can avoid lookup. - if (waiter.prev == null) { - assert(waiter.node.key == address); - break :blk treap.getEntryForExisting(&waiter.node); - } - break :blk treap.getEntryFor(address); - }; - - // The queue head and tail must exist if we're removing a queued waiter. - const head: *Waiter = @fieldParentPtr("node", entry.node orelse unreachable); - const tail = head.tail orelse unreachable; - - // A waiter with a previous link is never the head of the queue. - if (waiter.prev) |prev| { - assert(waiter != head); - prev.next = waiter.next; - - // A waiter with both a previous and next link is in the middle. - // We only need to update the surrounding waiter's links to remove it. - if (waiter.next) |next| { - assert(waiter != tail); - next.prev = waiter.prev; - break :queue_remove; - } - - // A waiter with a previous but no next link means it's the tail of the queue. - // In that case, we need to update the head's tail reference. - assert(waiter == tail); - head.tail = waiter.prev; - break :queue_remove; - } - - // A waiter with no previous link means it's the queue head of queue. - // We must replace (or remove) the head waiter reference in the treap. - assert(waiter == head); - entry.set(blk: { - const new_head = waiter.next orelse break :blk null; - new_head.tail = head.tail; - break :blk &new_head.node; - }); - } - - // Mark the waiter as successfully removed. - waiter.is_queued = false; - return true; - } - }; - - const Bucket = struct { - mutex: c.pthread_mutex_t align(atomic.cache_line) = .{}, - pending: atomic.Value(usize) = atomic.Value(usize).init(0), - treap: Treap = .{}, - - // Global array of buckets that addresses map to. - // Bucket array size is pretty much arbitrary here, but it must be a power of two for fibonacci hashing. - var buckets = [_]Bucket{.{}} ** @bitSizeOf(usize); - - // https://github.com/Amanieu/parking_lot/blob/1cf12744d097233316afa6c8b7d37389e4211756/core/src/parking_lot.rs#L343-L353 - fn from(address: usize) *Bucket { - // The upper `@bitSizeOf(usize)` bits of the fibonacci golden ratio. - // Hashing this via (h * k) >> (64 - b) where k=golden-ration and b=bitsize-of-array - // evenly lays out h=hash values over the bit range even when the hash has poor entropy (identity-hash for pointers). - const max_multiplier_bits = @bitSizeOf(usize); - const fibonacci_multiplier = 0x9E3779B97F4A7C15 >> (64 - max_multiplier_bits); - - const max_bucket_bits = @ctz(buckets.len); - comptime assert(std.math.isPowerOfTwo(buckets.len)); - - const index = (address *% fibonacci_multiplier) >> (max_multiplier_bits - max_bucket_bits); - return &buckets[index]; - } - }; - - const Address = struct { - fn from(ptr: *const atomic.Value(u32)) usize { - // Get the alignment of the pointer. - const alignment = @alignOf(atomic.Value(u32)); - comptime assert(std.math.isPowerOfTwo(alignment)); - - // Make sure the pointer is aligned, - // then cut off the zero bits from the alignment to get the unique address. - const addr = @intFromPtr(ptr); - assert(addr & (alignment - 1) == 0); - return addr >> @ctz(@as(usize, alignment)); - } - }; - - fn wait(ptr: *const atomic.Value(u32), expect: u32, timeout: ?u64) error{Timeout}!void { - const address = Address.from(ptr); - const bucket = Bucket.from(address); - - // Announce that there's a waiter in the bucket before checking the ptr/expect condition. - // If the announcement is reordered after the ptr check, the waiter could deadlock: - // - // - T1: checks ptr == expect which is true - // - T2: updates ptr to != expect - // - T2: does Futex.wake(), sees no pending waiters, exits - // - T1: bumps pending waiters (was reordered after the ptr == expect check) - // - T1: goes to sleep and misses both the ptr change and T2's wake up - // - // acquire barrier to ensure the announcement happens before the ptr check below. - var pending = bucket.pending.fetchAdd(1, .acquire); - assert(pending < std.math.maxInt(usize)); - - // If the wait gets canceled, remove the pending count we previously added. - // This is done outside the mutex lock to keep the critical section short in case of contention. - var canceled = false; - defer if (canceled) { - pending = bucket.pending.fetchSub(1, .monotonic); - assert(pending > 0); - }; - - var waiter: Waiter = undefined; - { - assert(c.pthread_mutex_lock(&bucket.mutex) == .SUCCESS); - defer assert(c.pthread_mutex_unlock(&bucket.mutex) == .SUCCESS); - - canceled = ptr.load(.monotonic) != expect; - if (canceled) { - return; - } - - waiter.event.init(); - WaitQueue.insert(&bucket.treap, address, &waiter); - } - - defer { - assert(!waiter.is_queued); - waiter.event.deinit(); - } - - waiter.event.wait(timeout) catch { - // If we fail to cancel after a timeout, it means a wake() thread dequeued us and will wake us up. - // We must wait until the event is set as that's a signal that the wake() thread won't access the waiter memory anymore. - // If we return early without waiting, the waiter on the stack would be invalidated and the wake() thread risks a UAF. - defer if (!canceled) waiter.event.wait(null) catch unreachable; - - assert(c.pthread_mutex_lock(&bucket.mutex) == .SUCCESS); - defer assert(c.pthread_mutex_unlock(&bucket.mutex) == .SUCCESS); - - canceled = WaitQueue.tryRemove(&bucket.treap, address, &waiter); - if (canceled) { - return error.Timeout; - } - }; - } - - fn wake(ptr: *const atomic.Value(u32), max_waiters: u32) void { - const address = Address.from(ptr); - const bucket = Bucket.from(address); - - // Quick check if there's even anything to wake up. - // The change to the ptr's value must happen before we check for pending waiters. - // If not, the wake() thread could miss a sleeping waiter and have it deadlock: - // - // - T2: p = has pending waiters (reordered before the ptr update) - // - T1: bump pending waiters - // - T1: if ptr == expected: sleep() - // - T2: update ptr != expected - // - T2: p is false from earlier so doesn't wake (T1 missed ptr update and T2 missed T1 sleeping) - // - // What we really want here is a Release load, but that doesn't exist under the C11 memory model. - // We could instead do `bucket.pending.fetchAdd(0, Release) == 0` which achieves effectively the same thing, - // LLVM lowers the fetchAdd(0, .release) into an mfence+load which avoids gaining ownership of the cache-line. - if (bucket.pending.fetchAdd(0, .release) == 0) { - return; - } - - // Keep a list of all the waiters notified and wake then up outside the mutex critical section. - var notified = WaitList{}; - defer if (notified.len > 0) { - const pending = bucket.pending.fetchSub(notified.len, .monotonic); - assert(pending >= notified.len); - - while (notified.pop()) |waiter| { - assert(!waiter.is_queued); - waiter.event.set(); - } - }; - - assert(c.pthread_mutex_lock(&bucket.mutex) == .SUCCESS); - defer assert(c.pthread_mutex_unlock(&bucket.mutex) == .SUCCESS); - - // Another pending check again to avoid the WaitQueue lookup if not necessary. - if (bucket.pending.load(.monotonic) > 0) { - notified = WaitQueue.remove(&bucket.treap, address, max_waiters); - } - } -}; - -test "smoke test" { - var value = atomic.Value(u32).init(0); - - // Try waits with invalid values. - Futex.wait(&value, 0xdeadbeef); - Futex.timedWait(&value, 0xdeadbeef, 0) catch {}; - - // Try timeout waits. - try testing.expectError(error.Timeout, Futex.timedWait(&value, 0, 0)); - try testing.expectError(error.Timeout, Futex.timedWait(&value, 0, std.time.ns_per_ms)); - - // Try wakes - Futex.wake(&value, 0); - Futex.wake(&value, 1); - Futex.wake(&value, std.math.maxInt(u32)); -} - -test "signaling" { - // This test requires spawning threads - if (builtin.single_threaded) { - return error.SkipZigTest; - } - - const num_threads = 4; - const num_iterations = 4; - - const Paddle = struct { - value: atomic.Value(u32) = atomic.Value(u32).init(0), - current: u32 = 0, - - fn hit(self: *@This()) void { - _ = self.value.fetchAdd(1, .release); - Futex.wake(&self.value, 1); - } - - fn run(self: *@This(), hit_to: *@This()) !void { - while (self.current < num_iterations) { - // Wait for the value to change from hit() - var new_value: u32 = undefined; - while (true) { - new_value = self.value.load(.acquire); - if (new_value != self.current) break; - Futex.wait(&self.value, self.current); - } - - // change the internal "current" value - try testing.expectEqual(new_value, self.current + 1); - self.current = new_value; - - // hit the next paddle - hit_to.hit(); - } - } - }; - - var paddles = [_]Paddle{.{}} ** num_threads; - var threads = [_]std.Thread{undefined} ** num_threads; - - // Create a circle of paddles which hit each other - for (&threads, 0..) |*t, i| { - const paddle = &paddles[i]; - const hit_to = &paddles[(i + 1) % paddles.len]; - t.* = try std.Thread.spawn(.{}, Paddle.run, .{ paddle, hit_to }); - } - - // Hit the first paddle and wait for them all to complete by hitting each other for num_iterations. - paddles[0].hit(); - for (threads) |t| t.join(); - for (paddles) |p| try testing.expectEqual(p.current, num_iterations); -} - -test "broadcasting" { - // This test requires spawning threads - if (builtin.single_threaded) { - return error.SkipZigTest; - } - - const num_threads = 4; - const num_iterations = 4; - - const Barrier = struct { - count: atomic.Value(u32) = atomic.Value(u32).init(num_threads), - futex: atomic.Value(u32) = atomic.Value(u32).init(0), - - fn wait(self: *@This()) !void { - // Decrement the counter. - // Release ensures stuff before this barrier.wait() happens before the last one. - // Acquire for the last counter ensures stuff before previous barrier.wait()s happened before it. - const count = self.count.fetchSub(1, .acq_rel); - try testing.expect(count <= num_threads); - try testing.expect(count > 0); - - // First counter to reach zero wakes all other threads. - // Release on futex update ensures stuff before all barrier.wait()'s happens before they all return. - if (count - 1 == 0) { - self.futex.store(1, .release); - Futex.wake(&self.futex, num_threads - 1); - return; - } - - // Other threads wait until last counter wakes them up. - // Acquire on futex synchronizes with last barrier count to ensure stuff before all barrier.wait()'s happen before us. - while (self.futex.load(.acquire) == 0) { - Futex.wait(&self.futex, 0); - } - } - }; - - const Broadcast = struct { - barriers: [num_iterations]Barrier = [_]Barrier{.{}} ** num_iterations, - threads: [num_threads]std.Thread = undefined, - - fn run(self: *@This()) !void { - for (&self.barriers) |*barrier| { - try barrier.wait(); - } - } - }; - - var broadcast = Broadcast{}; - for (&broadcast.threads) |*t| t.* = try std.Thread.spawn(.{}, Broadcast.run, .{&broadcast}); - for (broadcast.threads) |t| t.join(); -} - -/// Deadline is used to wait efficiently for a pointer's value to change using Futex and a fixed timeout. -/// -/// Futex's timedWait() api uses a relative duration which suffers from over-waiting -/// when used in a loop which is often required due to the possibility of spurious wakeups. -/// -/// Deadline instead converts the relative timeout to an absolute one so that multiple calls -/// to Futex timedWait() can block for and report more accurate error.Timeouts. -pub const Deadline = struct { - timeout: ?u64, - started: std.time.Timer, - - /// Create the deadline to expire after the given amount of time in nanoseconds passes. - /// Pass in `null` to have the deadline call `Futex.wait()` and never expire. - pub fn init(expires_in_ns: ?u64) Deadline { - var deadline: Deadline = undefined; - deadline.timeout = expires_in_ns; - - // std.time.Timer is required to be supported for somewhat accurate reportings of error.Timeout. - if (deadline.timeout != null) { - deadline.started = std.time.Timer.start() catch unreachable; - } - - return deadline; - } - - /// Wait until either: - /// - the `ptr`'s value changes from `expect`. - /// - `Futex.wake()` is called on the `ptr`. - /// - A spurious wake occurs. - /// - The deadline expires; In which case `error.Timeout` is returned. - pub fn wait(self: *Deadline, ptr: *const atomic.Value(u32), expect: u32) error{Timeout}!void { - @branchHint(.cold); - - // Check if we actually have a timeout to wait until. - // If not just wait "forever". - const timeout_ns = self.timeout orelse { - return Futex.wait(ptr, expect); - }; - - // Get how much time has passed since we started waiting - // then subtract that from the init() timeout to get how much longer to wait. - // Use overflow to detect when we've been waiting longer than the init() timeout. - const elapsed_ns = self.started.read(); - const until_timeout_ns = std.math.sub(u64, timeout_ns, elapsed_ns) catch 0; - return Futex.timedWait(ptr, expect, until_timeout_ns); - } -}; - -test "Deadline" { - var deadline = Deadline.init(100 * std.time.ns_per_ms); - var futex_word = atomic.Value(u32).init(0); - - while (true) { - deadline.wait(&futex_word, 0) catch break; - } -} diff --git a/lib/std/Thread/Mutex.zig b/lib/std/Thread/Mutex.zig deleted file mode 100644 index eb0dd42ceb6bb7e024e3fdbec12d1e4802d1863b..0000000000000000000000000000000000000000 --- a/lib/std/Thread/Mutex.zig +++ /dev/null @@ -1,367 +0,0 @@ -//! Mutex is a synchronization primitive which enforces atomic access to a -//! shared region of code known as the "critical section". -//! -//! It does this by blocking ensuring only one thread is in the critical -//! section at any given point in time by blocking the others. -//! -//! Mutex can be statically initialized and is at most `@sizeOf(u64)` large. -//! Use `lock()` or `tryLock()` to enter the critical section and `unlock()` to leave it. - -const std = @import("../std.zig"); -const builtin = @import("builtin"); -const Mutex = @This(); - -const assert = std.debug.assert; -const testing = std.testing; -const Thread = std.Thread; -const Futex = Thread.Futex; - -impl: Impl = .{}, - -pub const Recursive = @import("Mutex/Recursive.zig"); - -/// Tries to acquire the mutex without blocking the caller's thread. -/// Returns `false` if the calling thread would have to block to acquire it. -/// Otherwise, returns `true` and the caller should `unlock()` the Mutex to release it. -pub fn tryLock(self: *Mutex) bool { - return self.impl.tryLock(); -} - -/// Acquires the mutex, blocking the caller's thread until it can. -/// It is undefined behavior if the mutex is already held by the caller's thread. -/// Once acquired, call `unlock()` on the Mutex to release it. -pub fn lock(self: *Mutex) void { - self.impl.lock(); -} - -/// Releases the mutex which was previously acquired with `lock()` or `tryLock()`. -/// It is undefined behavior if the mutex is unlocked from a different thread that it was locked from. -pub fn unlock(self: *Mutex) void { - self.impl.unlock(); -} - -const Impl = if (builtin.mode == .Debug and !builtin.single_threaded) - DebugImpl -else - ReleaseImpl; - -const ReleaseImpl = Impl: { - if (builtin.single_threaded) break :Impl SingleThreadedImpl; - if (builtin.os.tag == .windows) break :Impl WindowsImpl; - if (builtin.os.tag.isDarwin()) break :Impl DarwinImpl; - - if (builtin.target.os.tag == .linux or - builtin.target.os.tag == .freebsd or - builtin.target.os.tag == .openbsd or - builtin.target.os.tag == .dragonfly or - builtin.target.cpu.arch.isWasm()) - { - // Futex is the system's synchronization primitive; use that. - break :Impl FutexImpl; - } - - if (std.Thread.use_pthreads) { - // This system doesn't have a futex primitive, so `std.Thread.Futex` is using `PosixImpl`, - // which implements futex *on top of* pthread mutexes and conditions. Therefore, instead - // of going through that long inefficient path, just use pthread mutex directly. - break :Impl PosixImpl; - } - - break :Impl FutexImpl; -}; - -const DebugImpl = struct { - locking_thread: std.atomic.Value(Thread.Id) = std.atomic.Value(Thread.Id).init(0), // 0 means it's not locked. - impl: ReleaseImpl = .{}, - - inline fn tryLock(self: *@This()) bool { - const locking = self.impl.tryLock(); - if (locking) { - self.locking_thread.store(Thread.getCurrentId(), .unordered); - } - return locking; - } - - inline fn lock(self: *@This()) void { - const current_id = Thread.getCurrentId(); - if (self.locking_thread.load(.unordered) == current_id and current_id != 0) { - @panic("Deadlock detected"); - } - self.impl.lock(); - self.locking_thread.store(current_id, .unordered); - } - - inline fn unlock(self: *@This()) void { - assert(self.locking_thread.load(.unordered) == Thread.getCurrentId()); - self.locking_thread.store(0, .unordered); - self.impl.unlock(); - } -}; - -const SingleThreadedImpl = struct { - is_locked: bool = false, - - fn tryLock(self: *@This()) bool { - if (self.is_locked) return false; - self.is_locked = true; - return true; - } - - fn lock(self: *@This()) void { - if (!self.tryLock()) { - unreachable; // deadlock detected - } - } - - fn unlock(self: *@This()) void { - assert(self.is_locked); - self.is_locked = false; - } -}; - -/// SRWLOCK on windows is almost always faster than Futex solution. -/// It also implements an efficient Condition with requeue support for us. -const WindowsImpl = struct { - srwlock: windows.SRWLOCK = .{}, - - fn tryLock(self: *@This()) bool { - return windows.ntdll.RtlTryAcquireSRWLockExclusive(&self.srwlock) != windows.FALSE; - } - - fn lock(self: *@This()) void { - windows.ntdll.RtlAcquireSRWLockExclusive(&self.srwlock); - } - - fn unlock(self: *@This()) void { - windows.ntdll.RtlReleaseSRWLockExclusive(&self.srwlock); - } - - const windows = std.os.windows; -}; - -/// os_unfair_lock on darwin supports priority inheritance and is generally faster than Futex solutions. -const DarwinImpl = struct { - oul: c.os_unfair_lock = .{}, - - fn tryLock(self: *@This()) bool { - return c.os_unfair_lock_trylock(&self.oul); - } - - fn lock(self: *@This()) void { - c.os_unfair_lock_lock(&self.oul); - } - - fn unlock(self: *@This()) void { - c.os_unfair_lock_unlock(&self.oul); - } - - const c = std.c; -}; - -const FutexImpl = struct { - state: std.atomic.Value(u32) = std.atomic.Value(u32).init(unlocked), - - const unlocked: u32 = 0b00; - const locked: u32 = 0b01; - const contended: u32 = 0b11; // must contain the `locked` bit for x86 optimization below - - fn lock(self: *@This()) void { - if (!self.tryLock()) - self.lockSlow(); - } - - fn tryLock(self: *@This()) bool { - // On x86, use `lock bts` instead of `lock cmpxchg` as: - // - they both seem to mark the cache-line as modified regardless: https://stackoverflow.com/a/63350048 - // - `lock bts` is smaller instruction-wise which makes it better for inlining - if (builtin.target.cpu.arch.isX86()) { - const locked_bit = @ctz(locked); - return self.state.bitSet(locked_bit, .acquire) == 0; - } - - // Acquire barrier ensures grabbing the lock happens before the critical section - // and that the previous lock holder's critical section happens before we grab the lock. - return self.state.cmpxchgWeak(unlocked, locked, .acquire, .monotonic) == null; - } - - fn lockSlow(self: *@This()) void { - @branchHint(.cold); - - // Avoid doing an atomic swap below if we already know the state is contended. - // An atomic swap unconditionally stores which marks the cache-line as modified unnecessarily. - if (self.state.load(.monotonic) == contended) { - Futex.wait(&self.state, contended); - } - - // Try to acquire the lock while also telling the existing lock holder that there are threads waiting. - // - // Once we sleep on the Futex, we must acquire the mutex using `contended` rather than `locked`. - // If not, threads sleeping on the Futex wouldn't see the state change in unlock and potentially deadlock. - // The downside is that the last mutex unlocker will see `contended` and do an unnecessary Futex wake - // but this is better than having to wake all waiting threads on mutex unlock. - // - // Acquire barrier ensures grabbing the lock happens before the critical section - // and that the previous lock holder's critical section happens before we grab the lock. - while (self.state.swap(contended, .acquire) != unlocked) { - Futex.wait(&self.state, contended); - } - } - - fn unlock(self: *@This()) void { - // Unlock the mutex and wake up a waiting thread if any. - // - // A waiting thread will acquire with `contended` instead of `locked` - // which ensures that it wakes up another thread on the next unlock(). - // - // Release barrier ensures the critical section happens before we let go of the lock - // and that our critical section happens before the next lock holder grabs the lock. - const state = self.state.swap(unlocked, .release); - assert(state != unlocked); - - if (state == contended) { - Futex.wake(&self.state, 1); - } - } -}; - -const PosixImpl = struct { - mutex: std.c.pthread_mutex_t = .{}, - - fn tryLock(impl: *PosixImpl) bool { - switch (std.c.pthread_mutex_trylock(&impl.mutex)) { - .SUCCESS => return true, - .BUSY => return false, - .INVAL => unreachable, // mutex is initialized correctly - else => unreachable, - } - } - - fn lock(impl: *PosixImpl) void { - switch (std.c.pthread_mutex_lock(&impl.mutex)) { - .SUCCESS => return, - .INVAL => unreachable, // mutex is initialized correctly - .DEADLK => unreachable, // not an error checking mutex - else => unreachable, - } - } - - fn unlock(impl: *PosixImpl) void { - switch (std.c.pthread_mutex_unlock(&impl.mutex)) { - .SUCCESS => return, - .INVAL => unreachable, // mutex is initialized correctly - .PERM => unreachable, // not an error checking mutex - else => unreachable, - } - } -}; - -test "smoke test" { - var mutex = Mutex{}; - - try testing.expect(mutex.tryLock()); - try testing.expect(!mutex.tryLock()); - mutex.unlock(); - - mutex.lock(); - try testing.expect(!mutex.tryLock()); - mutex.unlock(); -} - -// A counter which is incremented without atomic instructions -const NonAtomicCounter = struct { - // direct u128 could maybe use xmm ops on x86 which are atomic - value: [2]u64 = [_]u64{ 0, 0 }, - - fn get(self: NonAtomicCounter) u128 { - return @as(u128, @bitCast(self.value)); - } - - fn inc(self: *NonAtomicCounter) void { - for (@as([2]u64, @bitCast(self.get() + 1)), 0..) |v, i| { - @as(*volatile u64, @ptrCast(&self.value[i])).* = v; - } - } -}; - -test "many uncontended" { - // This test requires spawning threads. - if (builtin.single_threaded) { - return error.SkipZigTest; - } - - const num_threads = 4; - const num_increments = 1000; - - const Runner = struct { - mutex: Mutex = .{}, - thread: Thread = undefined, - counter: NonAtomicCounter = .{}, - - fn run(self: *@This()) void { - var i: usize = num_increments; - while (i > 0) : (i -= 1) { - self.mutex.lock(); - defer self.mutex.unlock(); - - self.counter.inc(); - } - } - }; - - var runners = [_]Runner{.{}} ** num_threads; - for (&runners) |*r| r.thread = try Thread.spawn(.{}, Runner.run, .{r}); - for (runners) |r| r.thread.join(); - for (runners) |r| try testing.expectEqual(r.counter.get(), num_increments); -} - -test "many contended" { - // This test requires spawning threads. - if (builtin.single_threaded) { - return error.SkipZigTest; - } - - const num_threads = 4; - const num_increments = 1000; - - const Runner = struct { - mutex: Mutex = .{}, - counter: NonAtomicCounter = .{}, - - fn run(self: *@This()) void { - var i: usize = num_increments; - while (i > 0) : (i -= 1) { - // Occasionally hint to let another thread run. - defer if (i % 100 == 0) Thread.yield() catch {}; - - self.mutex.lock(); - defer self.mutex.unlock(); - - self.counter.inc(); - } - } - }; - - var runner = Runner{}; - - var threads: [num_threads]Thread = undefined; - for (&threads) |*t| t.* = try Thread.spawn(.{}, Runner.run, .{&runner}); - for (threads) |t| t.join(); - - try testing.expectEqual(runner.counter.get(), num_increments * num_threads); -} - -// https://github.com/ziglang/zig/issues/19295 -//test @This() { -// var m: Mutex = .{}; -// -// { -// m.lock(); -// defer m.unlock(); -// // ... critical section code -// } -// -// if (m.tryLock()) { -// defer m.unlock(); -// // ... critical section code -// } -//} diff --git a/lib/std/Thread/Mutex/Recursive.zig b/lib/std/Thread/Mutex/Recursive.zig index d6d90ed648429d52df84ad8e0cc49402002bc131..8fa0563fb36b1eeaa0edd9c7d9b9d864b379db73 100644 --- a/lib/std/Thread/Mutex/Recursive.zig +++ b/lib/std/Thread/Mutex/Recursive.zig @@ -7,18 +7,18 @@ //! A recursive mutex is an abstraction layer on top of a regular mutex; //! therefore it is recommended to use instead `std.Mutex` unless there is a //! specific reason a recursive mutex is warranted. - -const std = @import("../../std.zig"); const Recursive = @This(); -const Mutex = std.Thread.Mutex; + +const std = @import("../../std.zig"); +const Io = std.Io; const assert = std.debug.assert; -mutex: Mutex, +mutex: Io.Mutex, thread_id: std.Thread.Id, lock_count: usize, pub const init: Recursive = .{ - .mutex = .{}, + .mutex = .init, .thread_id = invalid_thread_id, .lock_count = 0, }; @@ -49,7 +49,7 @@ pub fn tryLock(r: *Recursive) bool { pub fn lock(r: *Recursive) void { const current_thread_id = std.Thread.getCurrentId(); if (@atomicLoad(std.Thread.Id, &r.thread_id, .unordered) != current_thread_id) { - r.mutex.lock(); + Io.Threaded.mutexLock(&r.mutex); assert(r.lock_count == 0); @atomicStore(std.Thread.Id, &r.thread_id, current_thread_id, .unordered); } @@ -64,7 +64,7 @@ pub fn unlock(r: *Recursive) void { r.lock_count -= 1; if (r.lock_count == 0) { @atomicStore(std.Thread.Id, &r.thread_id, invalid_thread_id, .unordered); - r.mutex.unlock(); + Io.Threaded.mutexUnlock(&r.mutex); } } diff --git a/lib/std/Thread/RwLock.zig b/lib/std/Thread/RwLock.zig deleted file mode 100644 index 3e032c7c00cc85335df4d5172c89afb0bb5a8c51..0000000000000000000000000000000000000000 --- a/lib/std/Thread/RwLock.zig +++ /dev/null @@ -1,386 +0,0 @@ -//! A lock that supports one writer or many readers. -//! This API is for kernel threads, not evented I/O. -//! This API requires being initialized at runtime, and initialization -//! can fail. Once initialized, the core operations cannot fail. - -impl: Impl = .{}, - -const RwLock = @This(); -const std = @import("../std.zig"); -const builtin = @import("builtin"); -const assert = std.debug.assert; -const testing = std.testing; - -pub const Impl = if (builtin.single_threaded) - SingleThreadedRwLock -else if (std.Thread.use_pthreads) - PthreadRwLock -else - DefaultRwLock; - -/// Attempts to obtain exclusive lock ownership. -/// Returns `true` if the lock is obtained, `false` otherwise. -pub fn tryLock(rwl: *RwLock) bool { - return rwl.impl.tryLock(); -} - -/// Blocks until exclusive lock ownership is acquired. -pub fn lock(rwl: *RwLock) void { - return rwl.impl.lock(); -} - -/// Releases a held exclusive lock. -/// Asserts the lock is held exclusively. -pub fn unlock(rwl: *RwLock) void { - return rwl.impl.unlock(); -} - -/// Attempts to obtain shared lock ownership. -/// Returns `true` if the lock is obtained, `false` otherwise. -pub fn tryLockShared(rwl: *RwLock) bool { - return rwl.impl.tryLockShared(); -} - -/// Obtains shared lock ownership. -/// Blocks if another thread has exclusive ownership. -/// May block if another thread is attempting to get exclusive ownership. -pub fn lockShared(rwl: *RwLock) void { - return rwl.impl.lockShared(); -} - -/// Releases a held shared lock. -pub fn unlockShared(rwl: *RwLock) void { - return rwl.impl.unlockShared(); -} - -/// Single-threaded applications use this for deadlock checks in -/// debug mode, and no-ops in release modes. -pub const SingleThreadedRwLock = struct { - state: enum { unlocked, locked_exclusive, locked_shared } = .unlocked, - shared_count: usize = 0, - - /// Attempts to obtain exclusive lock ownership. - /// Returns `true` if the lock is obtained, `false` otherwise. - pub fn tryLock(rwl: *SingleThreadedRwLock) bool { - switch (rwl.state) { - .unlocked => { - assert(rwl.shared_count == 0); - rwl.state = .locked_exclusive; - return true; - }, - .locked_exclusive, .locked_shared => return false, - } - } - - /// Blocks until exclusive lock ownership is acquired. - pub fn lock(rwl: *SingleThreadedRwLock) void { - assert(rwl.state == .unlocked); // deadlock detected - assert(rwl.shared_count == 0); // corrupted state detected - rwl.state = .locked_exclusive; - } - - /// Releases a held exclusive lock. - /// Asserts the lock is held exclusively. - pub fn unlock(rwl: *SingleThreadedRwLock) void { - assert(rwl.state == .locked_exclusive); - assert(rwl.shared_count == 0); // corrupted state detected - rwl.state = .unlocked; - } - - /// Attempts to obtain shared lock ownership. - /// Returns `true` if the lock is obtained, `false` otherwise. - pub fn tryLockShared(rwl: *SingleThreadedRwLock) bool { - switch (rwl.state) { - .unlocked => { - rwl.state = .locked_shared; - assert(rwl.shared_count == 0); - rwl.shared_count = 1; - return true; - }, - .locked_shared => { - rwl.shared_count += 1; - return true; - }, - .locked_exclusive => return false, - } - } - - /// Blocks until shared lock ownership is acquired. - pub fn lockShared(rwl: *SingleThreadedRwLock) void { - switch (rwl.state) { - .unlocked => { - rwl.state = .locked_shared; - assert(rwl.shared_count == 0); - rwl.shared_count = 1; - }, - .locked_shared => { - rwl.shared_count += 1; - }, - .locked_exclusive => unreachable, // deadlock detected - } - } - - /// Releases a held shared lock. - pub fn unlockShared(rwl: *SingleThreadedRwLock) void { - switch (rwl.state) { - .unlocked => unreachable, // too many calls to `unlockShared` - .locked_exclusive => unreachable, // exclusively held lock - .locked_shared => { - rwl.shared_count -= 1; - if (rwl.shared_count == 0) { - rwl.state = .unlocked; - } - }, - } - } -}; - -pub const PthreadRwLock = struct { - rwlock: std.c.pthread_rwlock_t = .{}, - - pub fn tryLock(rwl: *PthreadRwLock) bool { - return std.c.pthread_rwlock_trywrlock(&rwl.rwlock) == .SUCCESS; - } - - pub fn lock(rwl: *PthreadRwLock) void { - const rc = std.c.pthread_rwlock_wrlock(&rwl.rwlock); - assert(rc == .SUCCESS); - } - - pub fn unlock(rwl: *PthreadRwLock) void { - const rc = std.c.pthread_rwlock_unlock(&rwl.rwlock); - assert(rc == .SUCCESS); - } - - pub fn tryLockShared(rwl: *PthreadRwLock) bool { - return std.c.pthread_rwlock_tryrdlock(&rwl.rwlock) == .SUCCESS; - } - - pub fn lockShared(rwl: *PthreadRwLock) void { - const rc = std.c.pthread_rwlock_rdlock(&rwl.rwlock); - assert(rc == .SUCCESS); - } - - pub fn unlockShared(rwl: *PthreadRwLock) void { - const rc = std.c.pthread_rwlock_unlock(&rwl.rwlock); - assert(rc == .SUCCESS); - } -}; - -pub const DefaultRwLock = struct { - state: usize = 0, - mutex: std.Thread.Mutex = .{}, - semaphore: std.Thread.Semaphore = .{}, - - const IS_WRITING: usize = 1; - const WRITER: usize = 1 << 1; - const READER: usize = 1 << (1 + @bitSizeOf(Count)); - const WRITER_MASK: usize = std.math.maxInt(Count) << @ctz(WRITER); - const READER_MASK: usize = std.math.maxInt(Count) << @ctz(READER); - const Count = std.meta.Int(.unsigned, @divFloor(@bitSizeOf(usize) - 1, 2)); - - pub fn tryLock(rwl: *DefaultRwLock) bool { - if (rwl.mutex.tryLock()) { - const state = @atomicLoad(usize, &rwl.state, .seq_cst); - if (state & READER_MASK == 0) { - _ = @atomicRmw(usize, &rwl.state, .Or, IS_WRITING, .seq_cst); - return true; - } - - rwl.mutex.unlock(); - } - - return false; - } - - pub fn lock(rwl: *DefaultRwLock) void { - _ = @atomicRmw(usize, &rwl.state, .Add, WRITER, .seq_cst); - rwl.mutex.lock(); - - const state = @atomicRmw(usize, &rwl.state, .Add, IS_WRITING -% WRITER, .seq_cst); - if (state & READER_MASK != 0) - rwl.semaphore.wait(); - } - - pub fn unlock(rwl: *DefaultRwLock) void { - _ = @atomicRmw(usize, &rwl.state, .And, ~IS_WRITING, .seq_cst); - rwl.mutex.unlock(); - } - - pub fn tryLockShared(rwl: *DefaultRwLock) bool { - const state = @atomicLoad(usize, &rwl.state, .seq_cst); - if (state & (IS_WRITING | WRITER_MASK) == 0) { - _ = @cmpxchgStrong( - usize, - &rwl.state, - state, - state + READER, - .seq_cst, - .seq_cst, - ) orelse return true; - } - - if (rwl.mutex.tryLock()) { - _ = @atomicRmw(usize, &rwl.state, .Add, READER, .seq_cst); - rwl.mutex.unlock(); - return true; - } - - return false; - } - - pub fn lockShared(rwl: *DefaultRwLock) void { - var state = @atomicLoad(usize, &rwl.state, .seq_cst); - while (state & (IS_WRITING | WRITER_MASK) == 0) { - state = @cmpxchgWeak( - usize, - &rwl.state, - state, - state + READER, - .seq_cst, - .seq_cst, - ) orelse return; - } - - rwl.mutex.lock(); - _ = @atomicRmw(usize, &rwl.state, .Add, READER, .seq_cst); - rwl.mutex.unlock(); - } - - pub fn unlockShared(rwl: *DefaultRwLock) void { - const state = @atomicRmw(usize, &rwl.state, .Sub, READER, .seq_cst); - - if ((state & READER_MASK == READER) and (state & IS_WRITING != 0)) - rwl.semaphore.post(); - } -}; - -test "DefaultRwLock - internal state" { - var rwl = DefaultRwLock{}; - - // The following failed prior to the fix for Issue #13163, - // where the WRITER flag was subtracted by the lock method. - - rwl.lock(); - rwl.unlock(); - try testing.expectEqual(rwl, DefaultRwLock{}); -} - -test "smoke test" { - var rwl = RwLock{}; - - rwl.lock(); - try testing.expect(!rwl.tryLock()); - try testing.expect(!rwl.tryLockShared()); - rwl.unlock(); - - try testing.expect(rwl.tryLock()); - try testing.expect(!rwl.tryLock()); - try testing.expect(!rwl.tryLockShared()); - rwl.unlock(); - - rwl.lockShared(); - try testing.expect(!rwl.tryLock()); - try testing.expect(rwl.tryLockShared()); - rwl.unlockShared(); - rwl.unlockShared(); - - try testing.expect(rwl.tryLockShared()); - try testing.expect(!rwl.tryLock()); - try testing.expect(rwl.tryLockShared()); - rwl.unlockShared(); - rwl.unlockShared(); - - rwl.lock(); - rwl.unlock(); -} - -test "concurrent access" { - if (builtin.single_threaded) - return; - - const num_writers: usize = 2; - const num_readers: usize = 4; - const num_writes: usize = 1000; - const num_reads: usize = 2000; - - const Runner = struct { - const Runner = @This(); - - rwl: RwLock, - writes: usize, - reads: std.atomic.Value(usize), - - val_a: usize, - val_b: usize, - - fn reader(run: *Runner, thread_idx: usize) !void { - var prng = std.Random.DefaultPrng.init(thread_idx); - const rnd = prng.random(); - while (true) { - run.rwl.lockShared(); - defer run.rwl.unlockShared(); - - try testing.expect(run.writes <= num_writes); - if (run.reads.fetchAdd(1, .monotonic) >= num_reads) break; - - // We use `volatile` accesses so that we can make sure the memory is accessed either - // side of a yield, maximising chances of a race. - const a_ptr: *const volatile usize = &run.val_a; - const b_ptr: *const volatile usize = &run.val_b; - - const old_a = a_ptr.*; - if (rnd.boolean()) try std.Thread.yield(); - const old_b = b_ptr.*; - try testing.expect(old_a == old_b); - } - } - - fn writer(run: *Runner, thread_idx: usize) !void { - var prng = std.Random.DefaultPrng.init(thread_idx); - const rnd = prng.random(); - while (true) { - run.rwl.lock(); - defer run.rwl.unlock(); - - try testing.expect(run.writes <= num_writes); - if (run.writes == num_writes) break; - - // We use `volatile` accesses so that we can make sure the memory is accessed either - // side of a yield, maximising chances of a race. - const a_ptr: *volatile usize = &run.val_a; - const b_ptr: *volatile usize = &run.val_b; - - const new_val = rnd.int(usize); - - const old_a = a_ptr.*; - a_ptr.* = new_val; - if (rnd.boolean()) try std.Thread.yield(); - const old_b = b_ptr.*; - b_ptr.* = new_val; - try testing.expect(old_a == old_b); - - run.writes += 1; - } - } - }; - - var run: Runner = .{ - .rwl = .{}, - .writes = 0, - .reads = .init(0), - .val_a = 0, - .val_b = 0, - }; - var write_threads: [num_writers]std.Thread = undefined; - var read_threads: [num_readers]std.Thread = undefined; - - for (&write_threads, 0..) |*t, i| t.* = try .spawn(.{}, Runner.writer, .{ &run, i }); - for (&read_threads, num_writers..) |*t, i| t.* = try .spawn(.{}, Runner.reader, .{ &run, i }); - - for (write_threads) |t| t.join(); - for (read_threads) |t| t.join(); - - try testing.expect(run.writes == num_writes); - try testing.expect(run.reads.raw >= num_reads); -} diff --git a/lib/std/Thread/Semaphore.zig b/lib/std/Thread/Semaphore.zig deleted file mode 100644 index a82ae3d002b1fb4f0147be5e2747958ad8b93dc2..0000000000000000000000000000000000000000 --- a/lib/std/Thread/Semaphore.zig +++ /dev/null @@ -1,111 +0,0 @@ -//! A semaphore is an unsigned integer that blocks the kernel thread if -//! the number would become negative. -//! This API supports static initialization and does not require deinitialization. -//! -//! Example: -//! ``` -//! var s = Semaphore{}; -//! -//! fn consumer() void { -//! s.wait(); -//! } -//! -//! fn producer() void { -//! s.post(); -//! } -//! -//! const thread = try std.Thread.spawn(.{}, producer, .{}); -//! consumer(); -//! thread.join(); -//! ``` - -mutex: Mutex = .{}, -cond: Condition = .{}, -/// It is OK to initialize this field to any value. -permits: usize = 0, - -const Semaphore = @This(); -const std = @import("../std.zig"); -const Mutex = std.Thread.Mutex; -const Condition = std.Thread.Condition; -const builtin = @import("builtin"); -const testing = std.testing; - -pub fn wait(sem: *Semaphore) void { - sem.mutex.lock(); - defer sem.mutex.unlock(); - - while (sem.permits == 0) - sem.cond.wait(&sem.mutex); - - sem.permits -= 1; - if (sem.permits > 0) - sem.cond.signal(); -} - -pub fn timedWait(sem: *Semaphore, timeout_ns: u64) error{Timeout}!void { - var timeout_timer = std.time.Timer.start() catch unreachable; - - sem.mutex.lock(); - defer sem.mutex.unlock(); - - while (sem.permits == 0) { - const elapsed = timeout_timer.read(); - if (elapsed > timeout_ns) - return error.Timeout; - - const local_timeout_ns = timeout_ns - elapsed; - try sem.cond.timedWait(&sem.mutex, local_timeout_ns); - } - - sem.permits -= 1; - if (sem.permits > 0) - sem.cond.signal(); -} - -pub fn post(sem: *Semaphore) void { - sem.mutex.lock(); - defer sem.mutex.unlock(); - - sem.permits += 1; - sem.cond.signal(); -} - -test Semaphore { - if (builtin.single_threaded) { - return error.SkipZigTest; - } - - const TestContext = struct { - sem: *Semaphore, - n: *i32, - fn worker(ctx: *@This()) void { - ctx.sem.wait(); - ctx.n.* += 1; - ctx.sem.post(); - } - }; - const num_threads = 3; - var sem = Semaphore{ .permits = 1 }; - var threads: [num_threads]std.Thread = undefined; - var n: i32 = 0; - var ctx = TestContext{ .sem = &sem, .n = &n }; - - for (&threads) |*t| t.* = try std.Thread.spawn(.{}, TestContext.worker, .{&ctx}); - for (threads) |t| t.join(); - sem.wait(); - try testing.expect(n == num_threads); -} - -test timedWait { - var sem = Semaphore{}; - try testing.expectEqual(0, sem.permits); - - try testing.expectError(error.Timeout, sem.timedWait(1)); - - sem.post(); - try testing.expectEqual(1, sem.permits); - - try sem.timedWait(1); - try testing.expectEqual(0, sem.permits); -} diff --git a/lib/std/debug.zig b/lib/std/debug.zig index 4cf8cc5dc626128112bc8f190cfadcdf18026299..8f83bf19c8704aa41e2dbfae6429ed961cd12bbf 100644 --- a/lib/std/debug.zig +++ b/lib/std/debug.zig @@ -696,7 +696,7 @@ pub noinline fn writeCurrentStackTrace(options: StackUnwindOptions, t: Io.Termin .useless, .unsafe => {}, .safe, .ideal => continue, // no need to even warn } - const module_name = di.getModuleName(di_gpa, unwind_error.address) catch "???"; + const module_name = di.getModuleName(di_gpa, io, unwind_error.address) catch "???"; const caption: []const u8 = switch (unwind_error.err) { error.MissingDebugInfo => "unwind info unavailable", error.InvalidDebugInfo => "unwind info invalid", @@ -1141,7 +1141,7 @@ fn printSourceAtAddress( symbol.source_location, address, symbol.name orelse "???", - symbol.compile_unit_name orelse debug_info.getModuleName(gpa, address) catch "???", + symbol.compile_unit_name orelse debug_info.getModuleName(gpa, io, address) catch "???", ); } fn printLineInfo( @@ -1356,7 +1356,10 @@ pub fn getDebugInfoAllocator() Allocator { // Otherwise, use a global arena backed by the page allocator const S = struct { var arena: std.heap.ArenaAllocator = .init(std.heap.page_allocator); - var ts_arena: std.heap.ThreadSafeAllocator = .{ .child_allocator = arena.allocator() }; + var ts_arena: std.heap.ThreadSafeAllocator = .{ + .child_allocator = arena.allocator(), + .io = std.Options.debug_io, + }; }; return S.ts_arena.allocator(); } diff --git a/lib/std/debug/Coverage.zig b/lib/std/debug/Coverage.zig index 749b992a025683d4770b8a4c315d5d190735d287..81dfce853e0e66c706be73f09407de80d37f339f 100644 --- a/lib/std/debug/Coverage.zig +++ b/lib/std/debug/Coverage.zig @@ -1,11 +1,12 @@ +const Coverage = @This(); + const std = @import("../std.zig"); +const Io = std.Io; const Allocator = std.mem.Allocator; const Hash = std.hash.Wyhash; const Dwarf = std.debug.Dwarf; const assert = std.debug.assert; -const Coverage = @This(); - /// Provides a globally-scoped integer index for directories. /// /// As opposed to, for example, a directory index that is compilation-unit @@ -23,12 +24,12 @@ directories: std.ArrayHashMapUnmanaged(String, void, String.MapContext, false), files: std.ArrayHashMapUnmanaged(File, void, File.MapContext, false), string_bytes: std.ArrayList(u8), /// Protects the other fields. -mutex: std.Thread.Mutex, +mutex: Io.Mutex, pub const init: Coverage = .{ .directories = .{}, .files = .{}, - .mutex = .{}, + .mutex = .init, .string_bytes = .{}, }; @@ -140,11 +141,12 @@ pub fn stringAt(cov: *Coverage, index: String) [:0]const u8 { return span(cov.string_bytes.items[@intFromEnum(index)..]); } -pub const ResolveAddressesDwarfError = Dwarf.ScanError; +pub const ResolveAddressesDwarfError = Dwarf.ScanError || Io.Cancelable; pub fn resolveAddressesDwarf( cov: *Coverage, gpa: Allocator, + io: Io, endian: std.builtin.Endian, /// Asserts the addresses are in ascending order. sorted_pc_addrs: []const u64, @@ -161,8 +163,8 @@ pub fn resolveAddressesDwarf( var prev_pc: u64 = 0; var prev_cu: ?*std.debug.Dwarf.CompileUnit = null; // Protects directories and files tables from other threads. - cov.mutex.lock(); - defer cov.mutex.unlock(); + try cov.mutex.lock(io); + defer cov.mutex.unlock(io); next_pc: for (sorted_pc_addrs, output) |pc, *out| { assert(pc >= prev_pc); prev_pc = pc; @@ -183,8 +185,8 @@ pub fn resolveAddressesDwarf( if (cu != prev_cu) { prev_cu = cu; if (cu.src_loc_cache == null) { - cov.mutex.unlock(); - defer cov.mutex.lock(); + cov.mutex.unlock(io); + defer cov.mutex.lockUncancelable(io); d.populateSrcLocCache(gpa, endian, cu) catch |err| switch (err) { error.MissingDebugInfo, error.InvalidDebugInfo => { out.* = SourceLocation.invalid; diff --git a/lib/std/debug/Info.zig b/lib/std/debug/Info.zig index 34e79227d1375c621648d3c0eb74e9fb92dc736d..d16db5d69544c7591bc4230e60a2061cc5121400 100644 --- a/lib/std/debug/Info.zig +++ b/lib/std/debug/Info.zig @@ -93,7 +93,7 @@ pub fn resolveAddresses( ) ResolveAddressesError!void { assert(sorted_pc_addrs.len == output.len); switch (info.impl) { - .elf => |*ef| return info.coverage.resolveAddressesDwarf(gpa, ef.endian, sorted_pc_addrs, output, &ef.dwarf.?), + .elf => |*ef| return info.coverage.resolveAddressesDwarf(gpa, io, ef.endian, sorted_pc_addrs, output, &ef.dwarf.?), .macho => |*mf| { // Resolving all of the addresses at once unfortunately isn't so easy in Mach-O binaries // due to split debug information. For now, we'll just resolve the addreses one by one. @@ -112,7 +112,7 @@ pub fn resolveAddresses( else => |e| return e, }; } - try info.coverage.resolveAddressesDwarf(gpa, .little, &.{dwarf_pc_addr}, src_loc[0..1], dwarf); + try info.coverage.resolveAddressesDwarf(gpa, io, .little, &.{dwarf_pc_addr}, src_loc[0..1], dwarf); } }, } diff --git a/lib/std/debug/SelfInfo/Elf.zig b/lib/std/debug/SelfInfo/Elf.zig index ffcb4dfd26fdb9b1a80d030221d83b87283ef499..a2868fcee1429b0ab4757d4cb32603c27904250f 100644 --- a/lib/std/debug/SelfInfo/Elf.zig +++ b/lib/std/debug/SelfInfo/Elf.zig @@ -1,4 +1,4 @@ -rwlock: std.Thread.RwLock, +mutex: Io.Mutex, modules: std.ArrayList(Module), ranges: std.ArrayList(Module.Range), @@ -6,7 +6,7 @@ ranges: std.ArrayList(Module.Range), unwind_cache: if (can_unwind) ?[]Dwarf.SelfUnwinder.CacheEntry else ?noreturn, pub const init: SelfInfo = .{ - .rwlock = .{}, + .mutex = .init, .modules = .empty, .ranges = .empty, .unwind_cache = null, @@ -29,8 +29,8 @@ pub fn deinit(si: *SelfInfo, gpa: Allocator) void { } pub fn getSymbol(si: *SelfInfo, gpa: Allocator, io: Io, address: usize) Error!std.debug.Symbol { - const module = try si.findModule(gpa, address, .exclusive); - defer si.rwlock.unlock(); + const module = try si.findModule(gpa, io, address, .exclusive); + defer si.mutex.unlock(io); const vaddr = address - module.load_offset; @@ -73,15 +73,15 @@ pub fn getSymbol(si: *SelfInfo, gpa: Allocator, io: Io, address: usize) Error!st error.OutOfMemory => |e| return e, }; } -pub fn getModuleName(si: *SelfInfo, gpa: Allocator, address: usize) Error![]const u8 { - const module = try si.findModule(gpa, address, .shared); - defer si.rwlock.unlockShared(); +pub fn getModuleName(si: *SelfInfo, gpa: Allocator, io: Io, address: usize) Error![]const u8 { + const module = try si.findModule(gpa, io, address, .shared); + defer si.mutex.unlock(io); if (module.name.len == 0) return error.MissingDebugInfo; return module.name; } -pub fn getModuleSlide(si: *SelfInfo, gpa: Allocator, address: usize) Error!usize { - const module = try si.findModule(gpa, address, .shared); - defer si.rwlock.unlockShared(); +pub fn getModuleSlide(si: *SelfInfo, gpa: Allocator, io: Io, address: usize) Error!usize { + const module = try si.findModule(gpa, io, address, .shared); + defer si.mutex.unlock(io); return module.load_offset; } @@ -183,8 +183,8 @@ pub fn unwindFrame(si: *SelfInfo, gpa: Allocator, io: Io, context: *UnwindContex comptime assert(can_unwind); { - si.rwlock.lockShared(); - defer si.rwlock.unlockShared(); + try si.mutex.lock(io); + defer si.mutex.unlock(io); if (si.unwind_cache) |cache| { if (Dwarf.SelfUnwinder.CacheEntry.find(cache, context.pc)) |entry| { return context.next(gpa, entry); @@ -192,8 +192,8 @@ pub fn unwindFrame(si: *SelfInfo, gpa: Allocator, io: Io, context: *UnwindContex } } - const module = try si.findModule(gpa, context.pc, .exclusive); - defer si.rwlock.unlock(); + const module = try si.findModule(gpa, io, context.pc, .exclusive); + defer si.mutex.unlock(io); if (si.unwind_cache == null) { si.unwind_cache = try gpa.alloc(Dwarf.SelfUnwinder.CacheEntry, 2048); @@ -375,11 +375,11 @@ const Module = struct { } }; -fn findModule(si: *SelfInfo, gpa: Allocator, address: usize, lock: enum { shared, exclusive }) Error!*Module { +fn findModule(si: *SelfInfo, gpa: Allocator, io: Io, address: usize, lock: enum { shared, exclusive }) Error!*Module { // With the requested lock, scan the module ranges looking for `address`. switch (lock) { - .shared => si.rwlock.lockShared(), - .exclusive => si.rwlock.lock(), + .shared => try si.mutex.lock(io), + .exclusive => try si.mutex.lock(io), } for (si.ranges.items) |*range| { if (address >= range.start and address < range.start + range.len) { @@ -389,15 +389,12 @@ fn findModule(si: *SelfInfo, gpa: Allocator, address: usize, lock: enum { shared // The address wasn't in a known range. We will rebuild the module/range lists, since it's possible // a new module was loaded. Upgrade to an exclusive lock if necessary. switch (lock) { - .shared => { - si.rwlock.unlockShared(); - si.rwlock.lock(); - }, + .shared => {}, .exclusive => {}, } // Rebuild module list with the exclusive lock. { - errdefer si.rwlock.unlock(); + errdefer si.mutex.unlock(io); for (si.modules.items) |*mod| { unwind: { const u = &(mod.unwind orelse break :unwind catch break :unwind); @@ -415,10 +412,7 @@ fn findModule(si: *SelfInfo, gpa: Allocator, address: usize, lock: enum { shared } // Downgrade the lock back to shared if necessary. switch (lock) { - .shared => { - si.rwlock.unlock(); - si.rwlock.lockShared(); - }, + .shared => {}, .exclusive => {}, } // Scan the newly rebuilt module ranges. @@ -429,8 +423,8 @@ fn findModule(si: *SelfInfo, gpa: Allocator, address: usize, lock: enum { shared } // Still nothing; unlock and error. switch (lock) { - .shared => si.rwlock.unlockShared(), - .exclusive => si.rwlock.unlock(), + .shared => si.mutex.unlock(io), + .exclusive => si.mutex.unlock(io), } return error.MissingDebugInfo; } diff --git a/lib/std/debug/SelfInfo/MachO.zig b/lib/std/debug/SelfInfo/MachO.zig index 774ee5815ab6c89be015ecdf941ea79b16e021fc..1cc2ebed53939959c2e1712d7afdf58dfe1178e5 100644 --- a/lib/std/debug/SelfInfo/MachO.zig +++ b/lib/std/debug/SelfInfo/MachO.zig @@ -1,9 +1,9 @@ -mutex: std.Thread.Mutex, +mutex: Io.Mutex, /// Accessed through `Module.Adapter`. modules: std.ArrayHashMapUnmanaged(Module, void, Module.Context, false), pub const init: SelfInfo = .{ - .mutex = .{}, + .mutex = .init, .modules = .empty, }; pub fn deinit(si: *SelfInfo, gpa: Allocator) void { @@ -21,8 +21,8 @@ pub fn deinit(si: *SelfInfo, gpa: Allocator) void { } pub fn getSymbol(si: *SelfInfo, gpa: Allocator, io: Io, address: usize) Error!std.debug.Symbol { - const module = try si.findModule(gpa, address); - defer si.mutex.unlock(); + const module = try si.findModule(gpa, io, address); + defer si.mutex.unlock(io); const file = try module.getFile(gpa, io); @@ -76,9 +76,10 @@ pub fn getSymbol(si: *SelfInfo, gpa: Allocator, io: Io, address: usize) Error!st ) catch null, }; } -pub fn getModuleName(si: *SelfInfo, gpa: Allocator, address: usize) Error![]const u8 { +pub fn getModuleName(si: *SelfInfo, gpa: Allocator, io: Io, address: usize) Error![]const u8 { _ = si; _ = gpa; + _ = io; // This function is marked as deprecated; however, it is significantly more // performant than `dladdr` (since the latter also does a very slow symbol // lookup), so let's use it since it's still available. @@ -86,9 +87,9 @@ pub fn getModuleName(si: *SelfInfo, gpa: Allocator, address: usize) Error![]cons @ptrFromInt(address), ) orelse return error.MissingDebugInfo); } -pub fn getModuleSlide(si: *SelfInfo, gpa: Allocator, address: usize) Error!usize { - const module = try si.findModule(gpa, address); - defer si.mutex.unlock(); +pub fn getModuleSlide(si: *SelfInfo, gpa: Allocator, io: Io, address: usize) Error!usize { + const module = try si.findModule(gpa, io, address); + defer si.mutex.unlock(io); const header: *std.macho.mach_header_64 = @ptrFromInt(module.text_base); const raw_macho: [*]u8 = @ptrCast(header); var it = macho.LoadCommandIterator.init(header, raw_macho[@sizeOf(macho.mach_header_64)..][0..header.sizeofcmds]) catch unreachable; @@ -107,8 +108,7 @@ pub const UnwindContext = std.debug.Dwarf.SelfUnwinder; /// If the compact encoding can't encode a way to unwind a frame, it will /// defer unwinding to DWARF, in which case `__eh_frame` will be used if available. pub fn unwindFrame(si: *SelfInfo, gpa: Allocator, io: Io, context: *UnwindContext) Error!usize { - _ = io; - return unwindFrameInner(si, gpa, context) catch |err| switch (err) { + return unwindFrameInner(si, gpa, io, context) catch |err| switch (err) { error.InvalidDebugInfo, error.MissingDebugInfo, error.UnsupportedDebugInfo, @@ -134,9 +134,9 @@ pub fn unwindFrame(si: *SelfInfo, gpa: Allocator, io: Io, context: *UnwindContex => return error.InvalidDebugInfo, }; } -fn unwindFrameInner(si: *SelfInfo, gpa: Allocator, context: *UnwindContext) !usize { - const module = try si.findModule(gpa, context.pc); - defer si.mutex.unlock(); +fn unwindFrameInner(si: *SelfInfo, gpa: Allocator, io: Io, context: *UnwindContext) !usize { + const module = try si.findModule(gpa, io, context.pc); + defer si.mutex.unlock(io); const unwind: *Module.Unwind = try module.getUnwindInfo(gpa); @@ -430,15 +430,15 @@ fn unwindFrameInner(si: *SelfInfo, gpa: Allocator, context: *UnwindContext) !usi } /// Acquires the mutex on success. -fn findModule(si: *SelfInfo, gpa: Allocator, address: usize) Error!*Module { +fn findModule(si: *SelfInfo, gpa: Allocator, io: Io, address: usize) Error!*Module { // This function is marked as deprecated; however, it is significantly more // performant than `dladdr` (since the latter also does a very slow symbol // lookup), so let's use it since it's still available. const text_base = std.c._dyld_get_image_header_containing_address( @ptrFromInt(address), ) orelse return error.MissingDebugInfo; - si.mutex.lock(); - errdefer si.mutex.unlock(); + try si.mutex.lock(io); + errdefer si.mutex.unlock(io); const gop = try si.modules.getOrPutAdapted(gpa, @intFromPtr(text_base), Module.Adapter{}); errdefer comptime unreachable; if (!gop.found_existing) gop.key_ptr.* = .{ diff --git a/lib/std/debug/SelfInfo/Windows.zig b/lib/std/debug/SelfInfo/Windows.zig index b26883778dcc803bd3daaf0ce2acae82b55333ef..75cc329c3b0606846302234a8e93273e8e58b484 100644 --- a/lib/std/debug/SelfInfo/Windows.zig +++ b/lib/std/debug/SelfInfo/Windows.zig @@ -1,9 +1,9 @@ -mutex: std.Thread.Mutex, +mutex: Io.Mutex, modules: std.ArrayList(Module), module_name_arena: std.heap.ArenaAllocator.State, pub const init: SelfInfo = .{ - .mutex = .{}, + .mutex = .init, .modules = .empty, .module_name_arena = .{}, }; @@ -21,21 +21,21 @@ pub fn deinit(si: *SelfInfo, gpa: Allocator) void { } pub fn getSymbol(si: *SelfInfo, gpa: Allocator, io: Io, address: usize) Error!std.debug.Symbol { - si.mutex.lock(); - defer si.mutex.unlock(); + try si.mutex.lock(io); + defer si.mutex.unlock(io); const module = try si.findModule(gpa, address); const di = try module.getDebugInfo(gpa, io); return di.getSymbol(gpa, address - module.base_address); } -pub fn getModuleName(si: *SelfInfo, gpa: Allocator, address: usize) Error![]const u8 { - si.mutex.lock(); - defer si.mutex.unlock(); +pub fn getModuleName(si: *SelfInfo, gpa: Allocator, io: Io, address: usize) Error![]const u8 { + try si.mutex.lock(io); + defer si.mutex.unlock(io); const module = try si.findModule(gpa, address); return module.name; } -pub fn getModuleSlide(si: *SelfInfo, gpa: Allocator, address: usize) Error!usize { - si.mutex.lock(); - defer si.mutex.unlock(); +pub fn getModuleSlide(si: *SelfInfo, gpa: Allocator, io: Io, address: usize) Error!usize { + try si.mutex.lock(io); + defer si.mutex.unlock(io); const module = try si.findModule(gpa, address); return module.base_address; } diff --git a/lib/std/heap/ThreadSafeAllocator.zig b/lib/std/heap/ThreadSafeAllocator.zig index dc8bf89017b726da122683a5cfd2fb9fc15ba192..6999213937a95fa1792b964c0237e46e5b8c0ba7 100644 --- a/lib/std/heap/ThreadSafeAllocator.zig +++ b/lib/std/heap/ThreadSafeAllocator.zig @@ -1,7 +1,14 @@ -//! Wraps a non-thread-safe allocator and makes it thread-safe. +//! Deprecated. Thread safety should be built into each Allocator instance +//! directly rather than trying to do this "composable allocators" thing. +const ThreadSafeAllocator = @This(); + +const std = @import("../std.zig"); +const Io = std.Io; +const Allocator = std.mem.Allocator; child_allocator: Allocator, -mutex: std.Thread.Mutex = .{}, +io: Io, +mutex: Io.Mutex = .init, pub fn allocator(self: *ThreadSafeAllocator) Allocator { return .{ @@ -17,39 +24,39 @@ pub fn allocator(self: *ThreadSafeAllocator) Allocator { fn alloc(ctx: *anyopaque, n: usize, alignment: std.mem.Alignment, ra: usize) ?[*]u8 { const self: *ThreadSafeAllocator = @ptrCast(@alignCast(ctx)); - self.mutex.lock(); - defer self.mutex.unlock(); + const io = self.io; + self.mutex.lockUncancelable(io); + defer self.mutex.unlock(io); return self.child_allocator.rawAlloc(n, alignment, ra); } fn resize(ctx: *anyopaque, buf: []u8, alignment: std.mem.Alignment, new_len: usize, ret_addr: usize) bool { const self: *ThreadSafeAllocator = @ptrCast(@alignCast(ctx)); + const io = self.io; - self.mutex.lock(); - defer self.mutex.unlock(); + self.mutex.lockUncancelable(io); + defer self.mutex.unlock(io); return self.child_allocator.rawResize(buf, alignment, new_len, ret_addr); } fn remap(context: *anyopaque, memory: []u8, alignment: std.mem.Alignment, new_len: usize, return_address: usize) ?[*]u8 { const self: *ThreadSafeAllocator = @ptrCast(@alignCast(context)); + const io = self.io; - self.mutex.lock(); - defer self.mutex.unlock(); + self.mutex.lockUncancelable(io); + defer self.mutex.unlock(io); return self.child_allocator.rawRemap(memory, alignment, new_len, return_address); } fn free(ctx: *anyopaque, buf: []u8, alignment: std.mem.Alignment, ret_addr: usize) void { const self: *ThreadSafeAllocator = @ptrCast(@alignCast(ctx)); + const io = self.io; - self.mutex.lock(); - defer self.mutex.unlock(); + self.mutex.lockUncancelable(io); + defer self.mutex.unlock(io); return self.child_allocator.rawFree(buf, alignment, ret_addr); } - -const std = @import("../std.zig"); -const ThreadSafeAllocator = @This(); -const Allocator = std.mem.Allocator; diff --git a/lib/std/heap/debug_allocator.zig b/lib/std/heap/debug_allocator.zig index 3ea4a28f96f97a34292ba1461b56b0dc3903316b..d150bb09279c48da6f2c48ba6cee97a54bb07c9e 100644 --- a/lib/std/heap/debug_allocator.zig +++ b/lib/std/heap/debug_allocator.zig @@ -126,16 +126,6 @@ pub const Config = struct { /// Whether the allocator may be used simultaneously from multiple threads. thread_safe: bool = !builtin.single_threaded, - /// What type of mutex you'd like to use, for thread safety. - /// when specified, the mutex type must have the same shape as `std.Thread.Mutex` and - /// `DummyMutex`, and have no required fields. Specifying this field causes - /// the `thread_safe` field to be ignored. - /// - /// when null (default): - /// * the mutex type defaults to `std.Thread.Mutex` when thread_safe is enabled. - /// * the mutex type defaults to `DummyMutex` otherwise. - MutexType: ?type = null, - /// This is a temporary debugging trick you can use to turn segfaults into more helpful /// logged error messages with stack trace details. The downside is that every allocation /// will be leaked, unless used with retain_metadata! @@ -204,17 +194,8 @@ pub fn DebugAllocator(comptime config: Config) type { const total_requested_bytes_init = if (config.enable_memory_limit) @as(usize, 0) else {}; const requested_memory_limit_init = if (config.enable_memory_limit) @as(usize, math.maxInt(usize)) else {}; - const mutex_init = if (config.MutexType) |T| - T{} - else if (config.thread_safe) - std.Thread.Mutex{} - else - DummyMutex{}; - - const DummyMutex = struct { - inline fn lock(_: DummyMutex) void {} - inline fn unlock(_: DummyMutex) void {} - }; + const have_mutex = config.thread_safe; + const mutex_init = if (have_mutex) std.Io.Mutex.init else {}; const stack_n = config.stack_trace_frames; const one_trace_size = @sizeOf(usize) * stack_n; @@ -737,8 +718,8 @@ pub fn DebugAllocator(comptime config: Config) type { fn alloc(context: *anyopaque, len: usize, alignment: mem.Alignment, ret_addr: usize) ?[*]u8 { const self: *Self = @ptrCast(@alignCast(context)); - self.mutex.lock(); - defer self.mutex.unlock(); + if (have_mutex) std.Io.Threaded.mutexLock(&self.mutex); + defer if (have_mutex) std.Io.Threaded.mutexUnlock(&self.mutex); if (config.enable_memory_limit) { const new_req_bytes = self.total_requested_bytes + len; @@ -850,8 +831,8 @@ pub fn DebugAllocator(comptime config: Config) type { return_address: usize, ) bool { const self: *Self = @ptrCast(@alignCast(context)); - self.mutex.lock(); - defer self.mutex.unlock(); + if (have_mutex) std.Io.Threaded.mutexLock(&self.mutex); + defer if (have_mutex) std.Io.Threaded.mutexUnlock(&self.mutex); const size_class_index: usize = @max(@bitSizeOf(usize) - @clz(memory.len - 1), @intFromEnum(alignment)); if (size_class_index >= self.buckets.len) { @@ -869,8 +850,8 @@ pub fn DebugAllocator(comptime config: Config) type { return_address: usize, ) ?[*]u8 { const self: *Self = @ptrCast(@alignCast(context)); - self.mutex.lock(); - defer self.mutex.unlock(); + if (have_mutex) std.Io.Threaded.mutexLock(&self.mutex); + defer if (have_mutex) std.Io.Threaded.mutexUnlock(&self.mutex); const size_class_index: usize = @max(@bitSizeOf(usize) - @clz(memory.len - 1), @intFromEnum(alignment)); if (size_class_index >= self.buckets.len) { @@ -887,8 +868,8 @@ pub fn DebugAllocator(comptime config: Config) type { return_address: usize, ) void { const self: *Self = @ptrCast(@alignCast(context)); - self.mutex.lock(); - defer self.mutex.unlock(); + if (have_mutex) std.Io.Threaded.mutexLock(&self.mutex); + defer if (have_mutex) std.Io.Threaded.mutexUnlock(&self.mutex); const size_class_index: usize = @max(@bitSizeOf(usize) - @clz(old_memory.len - 1), @intFromEnum(alignment)); if (size_class_index >= self.buckets.len) { @@ -1331,18 +1312,6 @@ test "realloc large object to small object" { try std.testing.expect(slice[16] == 0x34); } -test "overridable mutexes" { - var gpa = DebugAllocator(.{ .MutexType = std.Thread.Mutex }){ - .backing_allocator = std.testing.allocator, - .mutex = std.Thread.Mutex{}, - }; - defer std.testing.expect(gpa.deinit() == .ok) catch @panic("leak"); - const allocator = gpa.allocator(); - - const ptr = try allocator.create(i32); - defer allocator.destroy(ptr); -} - test "non-page-allocator backing allocator" { var gpa: DebugAllocator(.{ .backing_allocator_zeroes = false, diff --git a/lib/std/heap/sbrk_allocator.zig b/lib/std/heap/sbrk_allocator.zig index a67cd517cfffdf42301883ee3e2f4dd8bb754cba..c4006d332239c38bc9fbeb7f770f81fd5f77bb22 100644 --- a/lib/std/heap/sbrk_allocator.zig +++ b/lib/std/heap/sbrk_allocator.zig @@ -1,5 +1,7 @@ -const std = @import("../std.zig"); const builtin = @import("builtin"); + +const std = @import("../std.zig"); +const Io = std.Io; const math = std.math; const Allocator = std.mem.Allocator; const mem = std.mem; @@ -39,12 +41,12 @@ pub fn SbrkAllocator(comptime sbrk: *const fn (n: usize) usize) type { var big_frees = [1]usize{0} ** big_size_class_count; // TODO don't do the naive locking strategy - var lock: std.Thread.Mutex = .{}; + var mutex: Io.Mutex = .{}; fn alloc(ctx: *anyopaque, len: usize, alignment: mem.Alignment, return_address: usize) ?[*]u8 { _ = ctx; _ = return_address; - lock.lock(); - defer lock.unlock(); + Io.Threaded.mutexLock(&mutex); + defer Io.Threaded.mutexUnlock(&mutex); // Make room for the freelist next pointer. const actual_len = @max(len +| @sizeOf(usize), alignment.toByteUnits()); const slot_size = math.ceilPowerOfTwo(usize, actual_len) catch return null; @@ -88,8 +90,8 @@ pub fn SbrkAllocator(comptime sbrk: *const fn (n: usize) usize) type { ) bool { _ = ctx; _ = return_address; - lock.lock(); - defer lock.unlock(); + Io.Threaded.mutexLock(&mutex); + defer Io.Threaded.mutexUnlock(&mutex); // We don't want to move anything from one size class to another, but we // can recover bytes in between powers of two. const buf_align = alignment.toByteUnits(); @@ -127,8 +129,8 @@ pub fn SbrkAllocator(comptime sbrk: *const fn (n: usize) usize) type { ) void { _ = ctx; _ = return_address; - lock.lock(); - defer lock.unlock(); + Io.Threaded.mutexLock(&mutex); + defer Io.Threaded.mutexUnlock(&mutex); const buf_align = alignment.toByteUnits(); const actual_len = @max(buf.len + @sizeOf(usize), buf_align); const slot_size = math.ceilPowerOfTwoAssert(usize, actual_len); diff --git a/lib/std/http/Client.zig b/lib/std/http/Client.zig index ddfcd96b6ab374045897dd86a834f12be331c897..a46e12ecf4846dd56c31de2444ebd3c1700f85bc 100644 --- a/lib/std/http/Client.zig +++ b/lib/std/http/Client.zig @@ -3,22 +3,25 @@ //! Connections are opened in a thread-safe manner, but individual Requests are not. //! //! TLS support may be disabled via `std.options.http_disable_tls`. +//! +//! TODO all the lockUncancelable in this file should be changed to regular lock and +//! `error.Canceled` added to more error sets. +const Client = @This(); -const std = @import("../std.zig"); const builtin = @import("builtin"); + +const std = @import("../std.zig"); +const Io = std.Io; const testing = std.testing; const http = std.http; const mem = std.mem; const Uri = std.Uri; -const Allocator = mem.Allocator; +const Allocator = std.mem.Allocator; const assert = std.debug.assert; -const Io = std.Io; const Writer = std.Io.Writer; const Reader = std.Io.Reader; const HostName = std.Io.net.HostName; -const Client = @This(); - pub const disable_tls = std.options.http_disable_tls; /// Used for all client allocations. Must be thread-safe. @@ -27,7 +30,7 @@ allocator: Allocator, io: Io, ca_bundle: if (disable_tls) void else std.crypto.Certificate.Bundle = if (disable_tls) {} else .{}, -ca_bundle_mutex: std.Thread.Mutex = .{}, +ca_bundle_mutex: Io.Mutex = .init, /// Used both for the reader and writer buffers. tls_buffer_size: if (disable_tls) u0 else usize = if (disable_tls) 0 else std.crypto.tls.Client.min_buffer_len, /// If non-null, ssl secrets are logged to a stream. Creating such a stream @@ -62,7 +65,7 @@ https_proxy: ?*Proxy = null, /// A Least-Recently-Used cache of open connections to be reused. pub const ConnectionPool = struct { - mutex: std.Thread.Mutex = .{}, + mutex: Io.Mutex = .init, /// Open connections that are currently in use. used: std.DoublyLinkedList = .{}, /// Open connections that are not currently in use. @@ -81,9 +84,9 @@ pub const ConnectionPool = struct { /// If no connection is found, null is returned. /// /// Threadsafe. - pub fn findConnection(pool: *ConnectionPool, criteria: Criteria) ?*Connection { - pool.mutex.lock(); - defer pool.mutex.unlock(); + pub fn findConnection(pool: *ConnectionPool, io: Io, criteria: Criteria) ?*Connection { + pool.mutex.lockUncancelable(io); + defer pool.mutex.unlock(io); var next = pool.free.last; while (next) |node| : (next = node.prev) { @@ -110,9 +113,9 @@ pub const ConnectionPool = struct { } /// Acquires an existing connection from the connection pool. This function is threadsafe. - pub fn acquire(pool: *ConnectionPool, connection: *Connection) void { - pool.mutex.lock(); - defer pool.mutex.unlock(); + pub fn acquire(pool: *ConnectionPool, io: Io, connection: *Connection) void { + pool.mutex.lockUncancelable(io); + defer pool.mutex.unlock(io); return pool.acquireUnsafe(connection); } @@ -122,8 +125,8 @@ pub const ConnectionPool = struct { /// /// Threadsafe. pub fn release(pool: *ConnectionPool, connection: *Connection, io: Io) void { - pool.mutex.lock(); - defer pool.mutex.unlock(); + pool.mutex.lockUncancelable(io); + defer pool.mutex.unlock(io); pool.used.remove(&connection.pool_node); @@ -147,9 +150,9 @@ pub const ConnectionPool = struct { } /// Adds a newly created node to the pool of used connections. This function is threadsafe. - pub fn addUsed(pool: *ConnectionPool, connection: *Connection) void { - pool.mutex.lock(); - defer pool.mutex.unlock(); + pub fn addUsed(pool: *ConnectionPool, io: Io, connection: *Connection) void { + pool.mutex.lockUncancelable(io); + defer pool.mutex.unlock(io); pool.used.append(&connection.pool_node); } @@ -159,9 +162,9 @@ pub const ConnectionPool = struct { /// If the new size is smaller than the current size, then idle connections will be closed until the pool is the new size. /// /// Threadsafe. - pub fn resize(pool: *ConnectionPool, allocator: Allocator, new_size: usize) void { - pool.mutex.lock(); - defer pool.mutex.unlock(); + pub fn resize(pool: *ConnectionPool, io: Io, allocator: Allocator, new_size: usize) void { + pool.mutex.lockUncancelable(io); + defer pool.mutex.unlock(io); const next = pool.free.first; _ = next; @@ -182,7 +185,7 @@ pub const ConnectionPool = struct { /// /// Threadsafe. pub fn deinit(pool: *ConnectionPool, io: Io) void { - pool.mutex.lock(); + pool.mutex.lockUncancelable(io); var next = pool.free.first; while (next) |node| { @@ -1308,9 +1311,11 @@ pub fn deinit(client: *Client) void { /// Uses `arena` for a few small allocations that must outlive the client, or /// at least until those fields are set to different values. pub fn initDefaultProxies(client: *Client, arena: Allocator, environ_map: *std.process.Environ.Map) !void { + const io = client.io; + // Prevent any new connections from being created. - client.connection_pool.mutex.lock(); - defer client.connection_pool.mutex.unlock(); + client.connection_pool.mutex.lockUncancelable(io); + defer client.connection_pool.mutex.unlock(io); assert(client.connection_pool.used.first == null); // There are active requests. @@ -1437,7 +1442,7 @@ pub fn connectTcpOptions(client: *Client, options: ConnectTcpOptions) ConnectTcp const proxied_host = options.proxied_host orelse host; const proxied_port = options.proxied_port orelse port; - if (client.connection_pool.findConnection(.{ + if (client.connection_pool.findConnection(io, .{ .host = proxied_host, .port = proxied_port, .protocol = protocol, @@ -1455,12 +1460,12 @@ pub fn connectTcpOptions(client: *Client, options: ConnectTcpOptions) ConnectTcp error.Canceled => |e| return e, else => return error.TlsInitializationFailed, }; - client.connection_pool.addUsed(&tc.connection); + client.connection_pool.addUsed(io, &tc.connection); return &tc.connection; }, .plain => { const pc = try Connection.Plain.create(client, proxied_host, proxied_port, stream); - client.connection_pool.addUsed(&pc.connection); + client.connection_pool.addUsed(io, &pc.connection); return &pc.connection; }, } @@ -1474,7 +1479,7 @@ pub const ConnectUnixError = Allocator.Error || std.posix.SocketError || error{N pub fn connectUnix(client: *Client, path: []const u8) ConnectUnixError!*Connection { const io = client.io; - if (client.connection_pool.findConnection(.{ + if (client.connection_pool.findConnection(io, .{ .host = path, .port = 0, .protocol = .plain, @@ -1516,7 +1521,7 @@ pub fn connectProxied( const io = client.io; if (!proxy.supports_connect) return error.TunnelNotSupported; - if (client.connection_pool.findConnection(.{ + if (client.connection_pool.findConnection(io, .{ .host = proxied_host, .port = proxied_port, .protocol = proxy.protocol, @@ -1691,8 +1696,8 @@ pub fn request( if (protocol == .tls) { if (disable_tls) unreachable; { - client.ca_bundle_mutex.lock(); - defer client.ca_bundle_mutex.unlock(); + client.ca_bundle_mutex.lockUncancelable(io); + defer client.ca_bundle_mutex.unlock(io); if (client.now == null) { const now = try Io.Clock.real.now(io); diff --git a/lib/std/once.zig b/lib/std/once.zig deleted file mode 100644 index 326487df076dcc242e25cec9d7c40c6fdb87da84..0000000000000000000000000000000000000000 --- a/lib/std/once.zig +++ /dev/null @@ -1,71 +0,0 @@ -const std = @import("std.zig"); -const builtin = @import("builtin"); -const testing = std.testing; - -pub fn once(comptime f: fn () void) Once(f) { - return Once(f){}; -} - -/// An object that executes the function `f` just once. -/// It is undefined behavior if `f` re-enters the same Once instance. -pub fn Once(comptime f: fn () void) type { - return struct { - done: bool = false, - mutex: std.Thread.Mutex = std.Thread.Mutex{}, - - /// Call the function `f`. - /// If `call` is invoked multiple times `f` will be executed only the - /// first time. - /// The invocations are thread-safe. - pub fn call(self: *@This()) void { - if (@atomicLoad(bool, &self.done, .acquire)) - return; - - return self.callSlow(); - } - - fn callSlow(self: *@This()) void { - @branchHint(.cold); - - self.mutex.lock(); - defer self.mutex.unlock(); - - // The first thread to acquire the mutex gets to run the initializer - if (!self.done) { - f(); - @atomicStore(bool, &self.done, true, .release); - } - } - }; -} - -var global_number: i32 = 0; -var global_once = once(incr); - -fn incr() void { - global_number += 1; -} - -test "Once executes its function just once" { - if (builtin.single_threaded) { - global_once.call(); - global_once.call(); - } else { - var threads: [10]std.Thread = undefined; - var thread_count: usize = 0; - defer for (threads[0..thread_count]) |handle| handle.join(); - - for (&threads) |*handle| { - handle.* = try std.Thread.spawn(.{}, struct { - fn thread_fn(x: u8) void { - _ = x; - global_once.call(); - if (global_number != 1) @panic("memory ordering bug"); - } - }.thread_fn, .{0}); - thread_count += 1; - } - } - - try testing.expectEqual(@as(i32, 1), global_number); -} diff --git a/lib/std/std.zig b/lib/std/std.zig index 81ccf6a309b52a226e0e9582ff8eca15b591201b..3998b3247a53c6a8373fa7521e9f57164769dce2 100644 --- a/lib/std/std.zig +++ b/lib/std/std.zig @@ -86,7 +86,6 @@ pub const math = @import("math.zig"); pub const mem = @import("mem.zig"); pub const meta = @import("meta.zig"); pub const os = @import("os.zig"); -pub const once = @import("once.zig").once; pub const pdb = @import("pdb.zig"); pub const pie = @import("pie.zig"); pub const posix = @import("posix.zig"); -- 2.54.0 From 4c4e9d054e37afe82a856f5845c548f177996bd4 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 2 Feb 2026 20:18:14 -0800 Subject: [PATCH 177/499] std.Io: add RwLock and Semaphore sync primitives and restore usage by std.debug.SelfInfo.Elf --- lib/std/Io.zig | 15 ++- lib/std/Io/RwLock.zig | 238 +++++++++++++++++++++++++++++++++ lib/std/Io/Semaphore.zig | 65 +++++++++ lib/std/Io/Threaded.zig | 8 +- lib/std/debug/SelfInfo/Elf.zig | 36 ++--- 5 files changed, 342 insertions(+), 20 deletions(-) create mode 100644 lib/std/Io/RwLock.zig create mode 100644 lib/std/Io/Semaphore.zig diff --git a/lib/std/Io.zig b/lib/std/Io.zig index ea182c680390e17133749ec28ddd520c32b5c21d..22ca78fd2b1a8552e0e6fb599c299638add1046e 100644 --- a/lib/std/Io.zig +++ b/lib/std/Io.zig @@ -47,6 +47,9 @@ pub const Dir = @import("Io/Dir.zig"); pub const File = @import("Io/File.zig"); pub const Terminal = @import("Io/Terminal.zig"); +pub const RwLock = @import("Io/RwLock.zig"); +pub const Semaphore = @import("Io/Semaphore.zig"); + pub const VTable = struct { /// If it returns `null` it means `result` has been already populated and /// `await` will be a no-op. @@ -882,7 +885,7 @@ pub const Timeout = union(enum) { pub const Error = error{ Timeout, UnsupportedClock }; - pub fn toDeadline(t: Timeout, io: Io) Clock.Error!?Clock.Timestamp { + pub fn toTimestamp(t: Timeout, io: Io) Clock.Error!?Clock.Timestamp { return switch (t) { .none => null, .duration => |d| try .fromNow(io, d), @@ -890,6 +893,14 @@ pub const Timeout = union(enum) { }; } + pub fn toDeadline(t: Timeout, io: Io) Timeout { + return switch (t) { + .none => .none, + .duration => |d| .{ .deadline = Clock.Timestamp.fromNow(io, d) catch @panic("TODO") }, + .deadline => |d| .{ .deadline = d }, + }; + } + pub fn toDurationFromNow(t: Timeout, io: Io) Clock.Error!?Clock.Duration { return switch (t) { .none => null, @@ -2153,5 +2164,7 @@ test { _ = Writer; _ = Evented; _ = Threaded; + _ = RwLock; + _ = Semaphore; _ = @import("Io/test.zig"); } diff --git a/lib/std/Io/RwLock.zig b/lib/std/Io/RwLock.zig new file mode 100644 index 0000000000000000000000000000000000000000..7be4f026e16c150feef443286315567add6bf69c --- /dev/null +++ b/lib/std/Io/RwLock.zig @@ -0,0 +1,238 @@ +//! A lock that supports one writer or many readers. +const RwLock = @This(); + +const builtin = @import("builtin"); + +const std = @import("../std.zig"); +const Io = std.Io; +const assert = std.debug.assert; +const testing = std.testing; + +state: usize, +mutex: Io.Mutex, +semaphore: Io.Semaphore, + +pub const init: RwLock = .{ + .state = 0, + .mutex = .init, + .semaphore = .{}, +}; + +const is_writing: usize = 1; +const writer: usize = 1 << 1; +const reader: usize = 1 << (1 + @bitSizeOf(Count)); +const writer_mask: usize = std.math.maxInt(Count) << @ctz(writer); +const reader_mask: usize = std.math.maxInt(Count) << @ctz(reader); +const Count = @Int(.unsigned, @divFloor(@bitSizeOf(usize) - 1, 2)); + +pub fn tryLock(rl: *RwLock, io: Io) bool { + if (rl.mutex.tryLock()) { + const state = @atomicLoad(usize, &rl.state, .seq_cst); + if (state & reader_mask == 0) { + _ = @atomicRmw(usize, &rl.state, .Or, is_writing, .seq_cst); + return true; + } + + rl.mutex.unlock(io); + } + + return false; +} + +pub fn lockUncancelable(rl: *RwLock, io: Io) void { + _ = @atomicRmw(usize, &rl.state, .Add, writer, .seq_cst); + rl.mutex.lockUncancelable(io); + + const state = @atomicRmw(usize, &rl.state, .Add, is_writing -% writer, .seq_cst); + if (state & reader_mask != 0) + rl.semaphore.waitUncancelable(io); +} + +pub fn unlock(rl: *RwLock, io: Io) void { + _ = @atomicRmw(usize, &rl.state, .And, ~is_writing, .seq_cst); + rl.mutex.unlock(io); +} + +pub fn tryLockShared(rl: *RwLock, io: Io) bool { + const state = @atomicLoad(usize, &rl.state, .seq_cst); + if (state & (is_writing | writer_mask) == 0) { + _ = @cmpxchgStrong( + usize, + &rl.state, + state, + state + reader, + .seq_cst, + .seq_cst, + ) orelse return true; + } + + if (rl.mutex.tryLock()) { + _ = @atomicRmw(usize, &rl.state, .Add, reader, .seq_cst); + rl.mutex.unlock(io); + return true; + } + + return false; +} + +pub fn lockSharedUncancelable(rl: *RwLock, io: Io) void { + var state = @atomicLoad(usize, &rl.state, .seq_cst); + while (state & (is_writing | writer_mask) == 0) { + state = @cmpxchgWeak( + usize, + &rl.state, + state, + state + reader, + .seq_cst, + .seq_cst, + ) orelse return; + } + + rl.mutex.lockUncancelable(io); + _ = @atomicRmw(usize, &rl.state, .Add, reader, .seq_cst); + rl.mutex.unlock(io); +} + +pub fn unlockShared(rl: *RwLock, io: Io) void { + const state = @atomicRmw(usize, &rl.state, .Sub, reader, .seq_cst); + + if ((state & reader_mask == reader) and (state & is_writing != 0)) + rl.semaphore.post(io); +} + +test "internal state" { + const io = testing.io; + + var rl: Io.RwLock = .init; + + // The following failed prior to the fix for Issue #13163, + // where the WRITER flag was subtracted by the lock method. + + rl.lockUncancelable(io); + rl.unlock(io); + try testing.expectEqual(rl, Io.RwLock.init); +} + +test "smoke test" { + const io = testing.io; + + var rl: Io.RwLock = .init; + + rl.lockUncancelable(io); + try testing.expect(!rl.tryLock(io)); + try testing.expect(!rl.tryLockShared(io)); + rl.unlock(io); + + try testing.expect(rl.tryLock(io)); + try testing.expect(!rl.tryLock(io)); + try testing.expect(!rl.tryLockShared(io)); + rl.unlock(io); + + rl.lockSharedUncancelable(io); + try testing.expect(!rl.tryLock(io)); + try testing.expect(rl.tryLockShared(io)); + rl.unlockShared(io); + rl.unlockShared(io); + + try testing.expect(rl.tryLockShared(io)); + try testing.expect(!rl.tryLock(io)); + try testing.expect(rl.tryLockShared(io)); + rl.unlockShared(io); + rl.unlockShared(io); + + rl.lockUncancelable(io); + rl.unlock(io); +} + +test "concurrent access" { + if (builtin.single_threaded) return; + + const io = testing.io; + const num_writers: usize = 2; + const num_readers: usize = 4; + const num_writes: usize = 1000; + const num_reads: usize = 2000; + + const Runner = struct { + const Runner = @This(); + + io: Io, + + rl: Io.RwLock, + writes: usize, + reads: std.atomic.Value(usize), + + val_a: usize, + val_b: usize, + + fn reader(run: *Runner, thread_idx: usize) !void { + var prng = std.Random.DefaultPrng.init(thread_idx); + const rnd = prng.random(); + while (true) { + run.rl.lockSharedUncancelable(run.io); + defer run.rl.unlockShared(run.io); + + try testing.expect(run.writes <= num_writes); + if (run.reads.fetchAdd(1, .monotonic) >= num_reads) break; + + // We use `volatile` accesses so that we can make sure the memory is accessed either + // side of a yield, maximising chances of a race. + const a_ptr: *const volatile usize = &run.val_a; + const b_ptr: *const volatile usize = &run.val_b; + + const old_a = a_ptr.*; + if (rnd.boolean()) try std.Thread.yield(); + const old_b = b_ptr.*; + try testing.expect(old_a == old_b); + } + } + + fn writer(run: *Runner, thread_idx: usize) !void { + var prng = std.Random.DefaultPrng.init(thread_idx); + const rnd = prng.random(); + while (true) { + run.rl.lockUncancelable(run.io); + defer run.rl.unlock(run.io); + + try testing.expect(run.writes <= num_writes); + if (run.writes == num_writes) break; + + // We use `volatile` accesses so that we can make sure the memory is accessed either + // side of a yield, maximising chances of a race. + const a_ptr: *volatile usize = &run.val_a; + const b_ptr: *volatile usize = &run.val_b; + + const new_val = rnd.int(usize); + + const old_a = a_ptr.*; + a_ptr.* = new_val; + if (rnd.boolean()) try std.Thread.yield(); + const old_b = b_ptr.*; + b_ptr.* = new_val; + try testing.expect(old_a == old_b); + + run.writes += 1; + } + } + }; + + var run: Runner = .{ + .io = io, + .rl = .init, + .writes = 0, + .reads = .init(0), + .val_a = 0, + .val_b = 0, + }; + var write_threads: [num_writers]std.Thread = undefined; + var read_threads: [num_readers]std.Thread = undefined; + + for (&write_threads, 0..) |*t, i| t.* = try .spawn(.{}, Runner.writer, .{ &run, i }); + for (&read_threads, num_writers..) |*t, i| t.* = try .spawn(.{}, Runner.reader, .{ &run, i }); + + for (write_threads) |t| t.join(); + for (read_threads) |t| t.join(); + + try testing.expect(run.writes == num_writes); + try testing.expect(run.reads.raw >= num_reads); +} diff --git a/lib/std/Io/Semaphore.zig b/lib/std/Io/Semaphore.zig new file mode 100644 index 0000000000000000000000000000000000000000..248e6ab4d071dc82a5cd626765628de299b142b8 --- /dev/null +++ b/lib/std/Io/Semaphore.zig @@ -0,0 +1,65 @@ +//! An unsigned integer that blocks the kernel thread if the number would +//! become negative. +//! +//! This API supports static initialization and does not require deinitialization. +const Semaphore = @This(); + +const builtin = @import("builtin"); + +const std = @import("../std.zig"); +const Io = std.Io; +const testing = std.testing; + +mutex: Io.Mutex = .init, +cond: Io.Condition = .init, +/// It is OK to initialize this field to any value. +permits: usize = 0, + +pub fn wait(s: *Semaphore, io: Io) Io.Cancelable!void { + try s.mutex.lock(io); + defer s.mutex.unlock(io); + while (s.permits == 0) try s.cond.wait(io, &s.mutex); + s.permits -= 1; + if (s.permits > 0) s.cond.signal(io); +} + +pub fn waitUncancelable(s: *Semaphore, io: Io) void { + s.mutex.lockUncancelable(io); + defer s.mutex.unlock(io); + while (s.permits == 0) s.cond.waitUncancelable(io, &s.mutex); + s.permits -= 1; + if (s.permits > 0) s.cond.signal(io); +} + +pub fn post(s: *Semaphore, io: Io) void { + s.mutex.lockUncancelable(io); + defer s.mutex.unlock(io); + + s.permits += 1; + s.cond.signal(io); +} + +test Semaphore { + if (builtin.single_threaded) return error.SkipZigTest; + const io = testing.io; + + const TestContext = struct { + sem: *Semaphore, + n: *i32, + fn worker(ctx: *@This()) !void { + try ctx.sem.wait(io); + ctx.n.* += 1; + ctx.sem.post(io); + } + }; + const num_threads = 3; + var sem: Semaphore = .{ .permits = 1 }; + var threads: [num_threads]std.Thread = undefined; + var n: i32 = 0; + var ctx = TestContext{ .sem = &sem, .n = &n }; + + for (&threads) |*t| t.* = try std.Thread.spawn(.{}, TestContext.worker, .{&ctx}); + for (threads) |t| t.join(); + try sem.wait(io); + try testing.expect(n == num_threads); +} diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index b2ff7533849f1b3d8e41b7bfb5a2c2920bc6ee35..053a87b27c8809bfdb0aca8efd2629e4afd14879 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -2655,7 +2655,7 @@ fn batchAwaitAsync(userdata: ?*anyopaque, b: *Io.Batch) Io.Cancelable!void { fn batchAwaitConcurrent(userdata: ?*anyopaque, b: *Io.Batch, timeout: Io.Timeout) Io.Batch.AwaitConcurrentError!void { const t: *Threaded = @ptrCast(@alignCast(userdata)); if (is_windows) { - const deadline: ?Io.Clock.Timestamp = timeout.toDeadline(ioBasic(t)) catch |err| switch (err) { + const deadline: ?Io.Clock.Timestamp = timeout.toTimestamp(ioBasic(t)) catch |err| switch (err) { error.Unexpected => deadline: { recoverableOsBugDetected(); break :deadline .{ .raw = .{ .nanoseconds = 0 }, .clock = .awake }; @@ -2754,7 +2754,7 @@ fn batchAwaitConcurrent(userdata: ?*anyopaque, b: *Io.Batch, timeout: Io.Timeout else => {}, } const t_io = ioBasic(t); - const deadline = timeout.toDeadline(t_io) catch return error.UnsupportedClock; + const deadline = timeout.toTimestamp(t_io) catch return error.UnsupportedClock; while (true) { const timeout_ms: i32 = t: { if (b.completions.head != .none) { @@ -10918,7 +10918,7 @@ fn nowWasi(clock: Io.Clock) Io.Clock.Error!Io.Timestamp { fn sleep(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void { const t: *Threaded = @ptrCast(@alignCast(userdata)); if (timeout == .none) return; - if (use_parking_sleep) return parking_sleep.sleep(try timeout.toDeadline(ioBasic(t))); + if (use_parking_sleep) return parking_sleep.sleep(try timeout.toTimestamp(ioBasic(t))); if (native_os == .wasi) return sleepWasi(t, timeout); if (@TypeOf(posix.system.clock_nanosleep) != void) return sleepPosix(timeout); return sleepNanosleep(t, timeout); @@ -12630,7 +12630,7 @@ fn netReceivePosix( var message_i: usize = 0; var data_i: usize = 0; - const deadline = timeout.toDeadline(t_io) catch |err| return .{ err, message_i }; + const deadline = timeout.toTimestamp(t_io) catch |err| return .{ err, message_i }; recv: while (true) { if (message_buffer.len - message_i == 0) return .{ null, message_i }; diff --git a/lib/std/debug/SelfInfo/Elf.zig b/lib/std/debug/SelfInfo/Elf.zig index a2868fcee1429b0ab4757d4cb32603c27904250f..40841bac2c351d824604556c7c67f1548aa964a0 100644 --- a/lib/std/debug/SelfInfo/Elf.zig +++ b/lib/std/debug/SelfInfo/Elf.zig @@ -1,4 +1,4 @@ -mutex: Io.Mutex, +rwlock: Io.RwLock, modules: std.ArrayList(Module), ranges: std.ArrayList(Module.Range), @@ -6,7 +6,7 @@ ranges: std.ArrayList(Module.Range), unwind_cache: if (can_unwind) ?[]Dwarf.SelfUnwinder.CacheEntry else ?noreturn, pub const init: SelfInfo = .{ - .mutex = .init, + .rwlock = .init, .modules = .empty, .ranges = .empty, .unwind_cache = null, @@ -30,7 +30,7 @@ pub fn deinit(si: *SelfInfo, gpa: Allocator) void { pub fn getSymbol(si: *SelfInfo, gpa: Allocator, io: Io, address: usize) Error!std.debug.Symbol { const module = try si.findModule(gpa, io, address, .exclusive); - defer si.mutex.unlock(io); + defer si.rwlock.unlock(io); const vaddr = address - module.load_offset; @@ -75,13 +75,13 @@ pub fn getSymbol(si: *SelfInfo, gpa: Allocator, io: Io, address: usize) Error!st } pub fn getModuleName(si: *SelfInfo, gpa: Allocator, io: Io, address: usize) Error![]const u8 { const module = try si.findModule(gpa, io, address, .shared); - defer si.mutex.unlock(io); + defer si.rwlock.unlockShared(io); if (module.name.len == 0) return error.MissingDebugInfo; return module.name; } pub fn getModuleSlide(si: *SelfInfo, gpa: Allocator, io: Io, address: usize) Error!usize { const module = try si.findModule(gpa, io, address, .shared); - defer si.mutex.unlock(io); + defer si.rwlock.unlockShared(io); return module.load_offset; } @@ -183,8 +183,8 @@ pub fn unwindFrame(si: *SelfInfo, gpa: Allocator, io: Io, context: *UnwindContex comptime assert(can_unwind); { - try si.mutex.lock(io); - defer si.mutex.unlock(io); + si.rwlock.lockSharedUncancelable(io); + defer si.rwlock.unlockShared(io); if (si.unwind_cache) |cache| { if (Dwarf.SelfUnwinder.CacheEntry.find(cache, context.pc)) |entry| { return context.next(gpa, entry); @@ -193,7 +193,7 @@ pub fn unwindFrame(si: *SelfInfo, gpa: Allocator, io: Io, context: *UnwindContex } const module = try si.findModule(gpa, io, context.pc, .exclusive); - defer si.mutex.unlock(io); + defer si.rwlock.unlock(io); if (si.unwind_cache == null) { si.unwind_cache = try gpa.alloc(Dwarf.SelfUnwinder.CacheEntry, 2048); @@ -378,8 +378,8 @@ const Module = struct { fn findModule(si: *SelfInfo, gpa: Allocator, io: Io, address: usize, lock: enum { shared, exclusive }) Error!*Module { // With the requested lock, scan the module ranges looking for `address`. switch (lock) { - .shared => try si.mutex.lock(io), - .exclusive => try si.mutex.lock(io), + .shared => si.rwlock.lockSharedUncancelable(io), + .exclusive => si.rwlock.lockUncancelable(io), } for (si.ranges.items) |*range| { if (address >= range.start and address < range.start + range.len) { @@ -389,12 +389,15 @@ fn findModule(si: *SelfInfo, gpa: Allocator, io: Io, address: usize, lock: enum // The address wasn't in a known range. We will rebuild the module/range lists, since it's possible // a new module was loaded. Upgrade to an exclusive lock if necessary. switch (lock) { - .shared => {}, + .shared => { + si.rwlock.unlockShared(io); + si.rwlock.lockUncancelable(io); + }, .exclusive => {}, } // Rebuild module list with the exclusive lock. { - errdefer si.mutex.unlock(io); + errdefer si.rwlock.unlock(io); for (si.modules.items) |*mod| { unwind: { const u = &(mod.unwind orelse break :unwind catch break :unwind); @@ -412,7 +415,10 @@ fn findModule(si: *SelfInfo, gpa: Allocator, io: Io, address: usize, lock: enum } // Downgrade the lock back to shared if necessary. switch (lock) { - .shared => {}, + .shared => { + si.rwlock.unlock(io); + si.rwlock.lockSharedUncancelable(io); + }, .exclusive => {}, } // Scan the newly rebuilt module ranges. @@ -423,8 +429,8 @@ fn findModule(si: *SelfInfo, gpa: Allocator, io: Io, address: usize, lock: enum } // Still nothing; unlock and error. switch (lock) { - .shared => si.mutex.unlock(io), - .exclusive => si.mutex.unlock(io), + .shared => si.rwlock.unlockShared(io), + .exclusive => si.rwlock.unlock(io), } return error.MissingDebugInfo; } -- 2.54.0 From 922ab8b8bc3b6dc14da9393b65ca2601f9a82728 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sun, 1 Feb 2026 18:09:39 -0800 Subject: [PATCH 178/499] std: finish moving time to Io interface Importantly, adds ability to get Clock resolution, which may be zero. This allows error.Unexpected and error.ClockUnsupported to be removed from timeout and clock reading error sets. --- lib/compiler/aro/aro/Compilation.zig | 2 +- lib/compiler/aro/aro/Preprocessor.zig | 2 +- lib/compiler/build_runner.zig | 2 +- lib/std/Build/Step.zig | 19 +-- lib/std/Build/Step/Run.zig | 26 ++-- lib/std/Build/WebServer.zig | 6 +- lib/std/Io.zig | 122 ++++++++++++----- lib/std/Io/File/MultiReader.zig | 2 +- lib/std/Io/Threaded.zig | 160 +++++++++++++--------- lib/std/Io/net.zig | 2 +- lib/std/Io/net/HostName.zig | 2 +- lib/std/Io/test.zig | 6 - lib/std/crypto/Certificate/Bundle.zig | 4 +- lib/std/http/Client.zig | 2 +- lib/std/os/linux.zig | 10 +- lib/std/os/linux/IoUring/test.zig | 4 +- lib/std/posix.zig | 52 -------- lib/std/time.zig | 182 -------------------------- src/Compilation.zig | 35 +++-- src/Zcu.zig | 10 +- src/Zcu/PerThread.zig | 6 +- src/link.zig | 8 +- 22 files changed, 258 insertions(+), 406 deletions(-) diff --git a/lib/compiler/aro/aro/Compilation.zig b/lib/compiler/aro/aro/Compilation.zig index c5f400f1d9f1cbf6e18daafa5eaa9dbb3f2d92ef..44e0248b8b519799b647078cbcf8c369e04b705a 100644 --- a/lib/compiler/aro/aro/Compilation.zig +++ b/lib/compiler/aro/aro/Compilation.zig @@ -107,7 +107,7 @@ pub const Environment = struct { if (parsed > max_timestamp) return error.InvalidEpoch; return .{ .provided = parsed }; } else { - const timestamp = try Io.Clock.real.now(io); + const timestamp = Io.Clock.real.now(io); const seconds = std.math.cast(u64, timestamp.toSeconds()) orelse return error.InvalidEpoch; return .{ .system = std.math.clamp(seconds, 0, max_timestamp) }; } diff --git a/lib/compiler/aro/aro/Preprocessor.zig b/lib/compiler/aro/aro/Preprocessor.zig index 6e36703df14a100feca2b200e1998962d2d7c99f..854c3afbf38b25e2b622da064e99e1033c819385 100644 --- a/lib/compiler/aro/aro/Preprocessor.zig +++ b/lib/compiler/aro/aro/Preprocessor.zig @@ -301,7 +301,7 @@ pub fn init(comp: *Compilation, source_epoch: SourceEpoch) Preprocessor { /// Initialize Preprocessor with builtin macros. pub fn initDefault(comp: *Compilation) !Preprocessor { const source_epoch: SourceEpoch = comp.environment.sourceEpoch(comp.io) catch |er| switch (er) { - error.InvalidEpoch, error.UnsupportedClock, error.Unexpected => blk: { + error.InvalidEpoch => blk: { const diagnostic: Diagnostic = .invalid_source_epoch; try comp.diagnostics.add(.{ .text = diagnostic.fmt, .kind = diagnostic.kind, .opt = diagnostic.opt, .location = null }); break :blk .default; diff --git a/lib/compiler/build_runner.zig b/lib/compiler/build_runner.zig index 04ceb6212c84ce9c91633fa3ad10c316702a1855..2dd18d4f0dcaa426988c8747fa03c49859830d72 100644 --- a/lib/compiler/build_runner.zig +++ b/lib/compiler/build_runner.zig @@ -548,7 +548,7 @@ pub fn main(init: process.Init.Minimal) !void { break :w try .init(graph.cache.cwd); }; - const now = Io.Clock.Timestamp.now(io, .awake) catch |err| fatal("failed to collect timestamp: {t}", .{err}); + const now = Io.Clock.Timestamp.now(io, .awake); run.web_server = if (webui_listen) |listen_address| ws: { if (builtin.single_threaded) unreachable; // `fatal` above diff --git a/lib/std/Build/Step.zig b/lib/std/Build/Step.zig index 2f5e4316d47cb32e1dee00c79bee977333b3bc79..b9581196c0ec59e125a8d0da6f607264f2afddb0 100644 --- a/lib/std/Build/Step.zig +++ b/lib/std/Build/Step.zig @@ -266,16 +266,19 @@ pub fn init(options: StepOptions) Step { /// here. pub fn make(s: *Step, options: MakeOptions) error{ MakeFailed, MakeSkipped }!void { const arena = s.owner.allocator; + const graph = s.owner.graph; + const io = graph.io; - var timer: ?std.time.Timer = t: { - if (!s.owner.graph.time_report) break :t null; + var start_ts: ?Io.Timestamp = t: { + if (!graph.time_report) break :t null; if (s.id == .compile) break :t null; if (s.id == .run and s.cast(Run).?.stdio == .zig_test) break :t null; - break :t std.time.Timer.start() catch @panic("--time-report not supported on this host"); + break :t Io.Clock.awake.now(io); }; const make_result = s.makeFn(s, options); - if (timer) |*t| { - options.web_server.?.updateTimeReportGeneric(s, t.read()); + if (start_ts) |*ts| { + const duration = ts.untilNow(io, .awake); + options.web_server.?.updateTimeReportGeneric(s, duration); } make_result catch |err| switch (err) { @@ -534,7 +537,7 @@ fn zigProcessUpdate(s: *Step, zp: *ZigProcess, watch: bool, web_server: ?*Build. const arena = b.allocator; const io = b.graph.io; - var timer = try std.time.Timer.start(); + const start_ts = Io.Clock.awake.now(io); try sendMessage(io, zp.child.stdin.?, .update); if (!watch) try sendMessage(io, zp.child.stdin.?, .exit); @@ -637,7 +640,7 @@ fn zigProcessUpdate(s: *Step, zp: *ZigProcess, watch: bool, web_server: ?*Build. .compile = s.cast(Step.Compile).?, .use_llvm = tr.flags.use_llvm, .stats = tr.stats, - .ns_total = timer.read(), + .ns_total = @intCast(start_ts.untilNow(io, .awake).toNanoseconds()), .llvm_pass_timings_len = tr.llvm_pass_timings_len, .files_len = tr.files_len, .decls_len = tr.decls_len, @@ -648,7 +651,7 @@ fn zigProcessUpdate(s: *Step, zp: *ZigProcess, watch: bool, web_server: ?*Build. } } - s.result_duration_ns = timer.read(); + s.result_duration_ns = @intCast(start_ts.untilNow(io, .awake).toNanoseconds()); const stderr_contents = zp.multi_reader.reader(1).buffered(); if (stderr_contents.len > 0) { diff --git a/lib/std/Build/Step/Run.zig b/lib/std/Build/Step/Run.zig index 3f5df9f2ae351db03282648d3ce38c488f8b3288..e80f57f0bc33db759b32ef03b37c5a355e680f84 100644 --- a/lib/std/Build/Step/Run.zig +++ b/lib/std/Build/Step/Run.zig @@ -1587,12 +1587,12 @@ fn spawnChildAndCollect( }; if (run.stdio == .zig_test) { - const started: Io.Clock.Timestamp = try .now(io, .awake); + const started: Io.Clock.Timestamp = .now(io, .awake); const result = evalZigTest(run, spawn_options, options, fuzz_context) catch |err| switch (err) { error.Canceled => |e| return e, else => |e| e, }; - run.step.result_duration_ns = @intCast((try started.untilNow(io)).raw.nanoseconds); + run.step.result_duration_ns = @intCast(started.untilNow(io).raw.nanoseconds); try result; return null; } else { @@ -1607,12 +1607,12 @@ fn spawnChildAndCollect( defer if (inherit) io.unlockStderr(); try setColorEnvironmentVariables(run, environ_map, terminal_mode); - const started: Io.Clock.Timestamp = try .now(io, .awake); + const started: Io.Clock.Timestamp = .now(io, .awake); const result = evalGeneric(run, spawn_options) catch |err| switch (err) { error.Canceled => |e| return e, else => |e| e, }; - run.step.result_duration_ns = @intCast((try started.untilNow(io)).raw.nanoseconds); + run.step.result_duration_ns = @intCast(started.untilNow(io).raw.nanoseconds); return try result; } } @@ -1869,7 +1869,7 @@ fn waitZigTest( var active_test_index: ?u32 = null; - var last_update: Io.Clock.Timestamp = try .now(io, .awake); + var last_update: Io.Clock.Timestamp = .now(io, .awake); var coverage_id: ?u64 = null; @@ -1908,11 +1908,11 @@ fn waitZigTest( multi_reader.fill(64, timeout) catch |err| switch (err) { error.Timeout => return .{ .timeout = .{ .active_test_index = active_test_index, - .ns_elapsed = @intCast((try last_update.untilNow(io)).raw.nanoseconds), + .ns_elapsed = @intCast(last_update.untilNow(io).raw.nanoseconds), } }, error.EndOfStream => return .{ .no_poll = .{ .active_test_index = active_test_index, - .ns_elapsed = @intCast((try last_update.untilNow(io)).raw.nanoseconds), + .ns_elapsed = @intCast(last_update.untilNow(io).raw.nanoseconds), } }, else => |e| return e, }; @@ -1926,11 +1926,11 @@ fn waitZigTest( multi_reader.fill(64, timeout) catch |err| switch (err) { error.Timeout => return .{ .timeout = .{ .active_test_index = active_test_index, - .ns_elapsed = @intCast((try last_update.untilNow(io)).raw.nanoseconds), + .ns_elapsed = @intCast(last_update.untilNow(io).raw.nanoseconds), } }, error.EndOfStream => return .{ .no_poll = .{ .active_test_index = active_test_index, - .ns_elapsed = @intCast((try last_update.untilNow(io)).raw.nanoseconds), + .ns_elapsed = @intCast(last_update.untilNow(io).raw.nanoseconds), } }, else => |e| return e, }; @@ -1976,13 +1976,13 @@ fn waitZigTest( @memset(opt_metadata.*.?.ns_per_test, std.math.maxInt(u64)); active_test_index = null; - last_update = try .now(io, .awake); + last_update = .now(io, .awake); requestNextTest(io, child.stdin.?, &opt_metadata.*.?, &sub_prog_node) catch |err| return .{ .write_failed = err }; }, .test_started => { active_test_index = opt_metadata.*.?.next_index - 1; - last_update = try .now(io, .awake); + last_update = .now(io, .awake); }, .test_results => { assert(fuzz_context == null); @@ -2026,7 +2026,7 @@ fn waitZigTest( active_test_index = null; - const now: Io.Clock.Timestamp = try .now(io, .awake); + const now: Io.Clock.Timestamp = .now(io, .awake); md.ns_per_test[tr_hdr.index] = @intCast(last_update.durationTo(now).raw.nanoseconds); last_update = now; @@ -2239,7 +2239,7 @@ fn evalGeneric(run: *Run, spawn_options: process.SpawnOptions) !EvalGenericResul return error.StderrStreamTooLong; } } else |err| switch (err) { - error.UnsupportedClock, error.Timeout => unreachable, + error.Timeout => unreachable, error.EndOfStream => {}, else => |e| return e, } diff --git a/lib/std/Build/WebServer.zig b/lib/std/Build/WebServer.zig index 7205291400ab81d6a693f6361ebc6c2975ce9a40..08bc584522683423c04ee33fe6b03f96f68b64e8 100644 --- a/lib/std/Build/WebServer.zig +++ b/lib/std/Build/WebServer.zig @@ -243,7 +243,7 @@ pub fn finishBuild(ws: *WebServer, opts: struct { pub fn now(s: *const WebServer) i64 { const io = s.graph.io; - const ts = base_clock.now(io) catch s.base_timestamp; + const ts = base_clock.now(io); return @intCast(s.base_timestamp.durationTo(ts).toNanoseconds()); } @@ -761,7 +761,7 @@ pub fn updateTimeReportCompile(ws: *WebServer, opts: struct { ws.notifyUpdate(); } -pub fn updateTimeReportGeneric(ws: *WebServer, step: *Build.Step, ns_total: u64) void { +pub fn updateTimeReportGeneric(ws: *WebServer, step: *Build.Step, duration: Io.Duration) void { const gpa = ws.gpa; const io = ws.graph.io; @@ -780,7 +780,7 @@ pub fn updateTimeReportGeneric(ws: *WebServer, step: *Build.Step, ns_total: u64) const out: *align(1) abi.time_report.GenericResult = @ptrCast(buf); out.* = .{ .step_idx = step_idx, - .ns_total = ns_total, + .ns_total = @intCast(duration.toNanoseconds()), }; { ws.time_report_mutex.lock(io) catch return; diff --git a/lib/std/Io.zig b/lib/std/Io.zig index 22ca78fd2b1a8552e0e6fb599c299638add1046e..09da9b533acdcfa58bc443992015c646660ee141 100644 --- a/lib/std/Io.zig +++ b/lib/std/Io.zig @@ -231,8 +231,9 @@ pub const VTable = struct { progressParentFile: *const fn (?*anyopaque) std.Progress.ParentFileError!File, - now: *const fn (?*anyopaque, Clock) Clock.Error!Timestamp, - sleep: *const fn (?*anyopaque, Timeout) SleepError!void, + now: *const fn (?*anyopaque, Clock) Timestamp, + clockResolution: *const fn (?*anyopaque, Clock) Duration, + sleep: *const fn (?*anyopaque, Timeout) Cancelable!void, random: *const fn (?*anyopaque, buffer: []u8) void, randomSecure: *const fn (?*anyopaque, buffer: []u8) RandomSecureError!void, @@ -701,30 +702,48 @@ pub const Clock = enum { /// thread. cpu_thread, - pub const Error = error{UnsupportedClock} || UnexpectedError; - - /// This function is not cancelable because first of all it does not block, - /// but more importantly, the cancelation logic itself may want to check - /// the time. - pub fn now(clock: Clock, io: Io) Error!Io.Timestamp { + /// This function is not cancelable because it does not block. + /// + /// Resolution is determined by `resolution` which may be 0 if the + /// clock is unsupported. + /// + /// See also: + /// * `Clock.Timestamp.now` + pub fn now(clock: Clock, io: Io) Io.Timestamp { return io.vtable.now(io.userdata, clock); } + /// Reveals the granularity of `clock`. May be zero, indicating + /// unsupported clock. + pub fn resolution(clock: Clock, io: Io) Io.Duration { + return io.vtable.clockResolution(io.userdata, clock); + } + pub const Timestamp = struct { raw: Io.Timestamp, clock: Clock, - /// This function is not cancelable because first of all it does not block, - /// but more importantly, the cancelation logic itself may want to check - /// the time. - pub fn now(io: Io, clock: Clock) Error!Clock.Timestamp { + /// This function is not cancelable because it does not block. + /// + /// Resolution is determined by `resolution` which may be 0 if + /// the clock is unsupported. + /// + /// See also: + /// * `Clock.now` + pub fn now(io: Io, clock: Clock) Clock.Timestamp { return .{ - .raw = try io.vtable.now(io.userdata, clock), + .raw = io.vtable.now(io.userdata, clock), .clock = clock, }; } - pub fn wait(t: Clock.Timestamp, io: Io) SleepError!void { + /// Sleeps until the timestamp arrives. + /// + /// See also: + /// * `Io.sleep` + /// * `Clock.Duration.sleep` + /// * `Timeout.sleep` + pub fn wait(t: Clock.Timestamp, io: Io) Cancelable!void { return io.vtable.sleep(io.userdata, .{ .deadline = t }); } @@ -752,30 +771,38 @@ pub const Clock = enum { }; } - pub fn fromNow(io: Io, duration: Clock.Duration) Error!Clock.Timestamp { + /// Resolution is determined by `resolution` which may be 0 if + /// the clock is unsupported. + pub fn fromNow(io: Io, duration: Clock.Duration) Clock.Timestamp { return .{ .clock = duration.clock, - .raw = (try duration.clock.now(io)).addDuration(duration.raw), + .raw = duration.clock.now(io).addDuration(duration.raw), }; } - pub fn untilNow(timestamp: Clock.Timestamp, io: Io) Error!Clock.Duration { - const now_ts = try Clock.Timestamp.now(io, timestamp.clock); + /// Resolution is determined by `resolution` which may be 0 if + /// the clock is unsupported. + pub fn untilNow(timestamp: Clock.Timestamp, io: Io) Clock.Duration { + const now_ts = Clock.Timestamp.now(io, timestamp.clock); return timestamp.durationTo(now_ts); } - pub fn durationFromNow(timestamp: Clock.Timestamp, io: Io) Error!Clock.Duration { - const now_ts = try timestamp.clock.now(io); + /// Resolution is determined by `resolution` which may be 0 if + /// the clock is unsupported. + pub fn durationFromNow(timestamp: Clock.Timestamp, io: Io) Clock.Duration { + const now_ts = timestamp.clock.now(io); return .{ .clock = timestamp.clock, .raw = now_ts.durationTo(timestamp.raw), }; } - pub fn toClock(t: Clock.Timestamp, io: Io, clock: Clock) Error!Clock.Timestamp { + /// Resolution is determined by `resolution` which may be 0 if + /// the clock is unsupported. + pub fn toClock(t: Clock.Timestamp, io: Io, clock: Clock) Clock.Timestamp { if (t.clock == clock) return t; - const now_old = try t.clock.now(io); - const now_new = try clock.now(io); + const now_old = t.clock.now(io); + const now_new = clock.now(io); const duration = now_old.durationTo(t); return .{ .clock = clock, @@ -793,7 +820,13 @@ pub const Clock = enum { raw: Io.Duration, clock: Clock, - pub fn sleep(duration: Clock.Duration, io: Io) SleepError!void { + /// Waits until a specified amount of time has passed on `clock`. + /// + /// See also: + /// * `Io.sleep` + /// * `Clock.Timestamp.wait` + /// * `Timeout.sleep` + pub fn sleep(duration: Clock.Duration, io: Io) Cancelable!void { return io.vtable.sleep(io.userdata, .{ .duration = duration }); } }; @@ -802,6 +835,10 @@ pub const Clock = enum { pub const Timestamp = struct { nanoseconds: i96, + pub fn now(io: Io, clock: Clock) Io.Timestamp { + return io.vtable.now(io.userdata, clock); + } + pub const zero: Timestamp = .{ .nanoseconds = 0 }; pub fn durationTo(from: Timestamp, to: Timestamp) Duration { @@ -844,6 +881,13 @@ pub const Timestamp = struct { .fill = n.fill, }); } + + /// Resolution is determined by `Clock.resolution` which may be 0 if + /// the clock is unsupported. + pub fn untilNow(t: Timestamp, io: Io, clock: Clock) Duration { + const now_ts = clock.now(io); + return t.durationTo(now_ts); + } }; pub const Duration = struct { @@ -883,12 +927,12 @@ pub const Timeout = union(enum) { duration: Clock.Duration, deadline: Clock.Timestamp, - pub const Error = error{ Timeout, UnsupportedClock }; + pub const Error = error{Timeout}; - pub fn toTimestamp(t: Timeout, io: Io) Clock.Error!?Clock.Timestamp { + pub fn toTimestamp(t: Timeout, io: Io) ?Clock.Timestamp { return switch (t) { .none => null, - .duration => |d| try .fromNow(io, d), + .duration => |d| .fromNow(io, d), .deadline => |d| d, }; } @@ -896,20 +940,26 @@ pub const Timeout = union(enum) { pub fn toDeadline(t: Timeout, io: Io) Timeout { return switch (t) { .none => .none, - .duration => |d| .{ .deadline = Clock.Timestamp.fromNow(io, d) catch @panic("TODO") }, + .duration => |d| .{ .deadline = .fromNow(io, d) }, .deadline => |d| .{ .deadline = d }, }; } - pub fn toDurationFromNow(t: Timeout, io: Io) Clock.Error!?Clock.Duration { + pub fn toDurationFromNow(t: Timeout, io: Io) ?Clock.Duration { return switch (t) { .none => null, .duration => |d| d, - .deadline => |d| try d.durationFromNow(io), + .deadline => |d| d.durationFromNow(io), }; } - pub fn sleep(timeout: Timeout, io: Io) SleepError!void { + /// Waits until the timeout has passed. + /// + /// See also: + /// * `Io.sleep` + /// * `Clock.Duration.sleep` + /// * `Clock.Timestamp.wait` + pub fn sleep(timeout: Timeout, io: Io) Cancelable!void { return io.vtable.sleep(io.userdata, timeout); } }; @@ -2027,9 +2077,13 @@ pub fn concurrent( return future; } -pub const SleepError = error{UnsupportedClock} || UnexpectedError || Cancelable; - -pub fn sleep(io: Io, duration: Duration, clock: Clock) SleepError!void { +/// Waits until a specified amount of time has passed on `clock`. +/// +/// See also: +/// * `Clock.Duration.sleep` +/// * `Clock.Timestamp.wait` +/// * `Timeout.sleep` +pub fn sleep(io: Io, duration: Duration, clock: Clock) Cancelable!void { return io.vtable.sleep(io.userdata, .{ .duration = .{ .raw = duration, .clock = clock, diff --git a/lib/std/Io/File/MultiReader.zig b/lib/std/Io/File/MultiReader.zig index 217215a3636e40b4fe4ad805be2df88fa2830947..85841d4a976d90a396685be689e8ea04cc8e4e24 100644 --- a/lib/std/Io/File/MultiReader.zig +++ b/lib/std/Io/File/MultiReader.zig @@ -179,7 +179,7 @@ fn rebase(r: *Io.Reader, capacity: usize) Io.Reader.RebaseError!void { fn fillUntimed(context: *Context, capacity: usize) Io.Reader.Error!void { fill(context.mr, capacity, .none) catch |err| switch (err) { - error.Timeout, error.UnsupportedClock => unreachable, + error.Timeout => unreachable, error.Canceled, error.ConcurrencyUnavailable => |e| { context.err = e; return error.ReadFailed; diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 053a87b27c8809bfdb0aca8efd2629e4afd14879..2cd350f58c9efcddba6f9bb9704fbaa21de0644e 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -1712,6 +1712,7 @@ pub fn io(t: *Threaded) Io { .progressParentFile = progressParentFile, .now = now, + .clockResolution = clockResolution, .sleep = sleep, .random = random, @@ -1875,6 +1876,7 @@ pub fn ioBasic(t: *Threaded) Io { .progressParentFile = progressParentFile, .now = now, + .clockResolution = clockResolution, .sleep = sleep, .random = random, @@ -2487,7 +2489,7 @@ fn futexWait(userdata: ?*anyopaque, ptr: *const u32, expected: u32, timeout: Io. const t: *Threaded = @ptrCast(@alignCast(userdata)); const t_io = ioBasic(t); const timeout_ns: ?u64 = ns: { - const d = (timeout.toDurationFromNow(t_io) catch break :ns 10) orelse break :ns null; + const d = timeout.toDurationFromNow(t_io) orelse break :ns null; break :ns std.math.lossyCast(u64, d.raw.toNanoseconds()); }; return Thread.futexWait(ptr, expected, timeout_ns); @@ -2655,24 +2657,12 @@ fn batchAwaitAsync(userdata: ?*anyopaque, b: *Io.Batch) Io.Cancelable!void { fn batchAwaitConcurrent(userdata: ?*anyopaque, b: *Io.Batch, timeout: Io.Timeout) Io.Batch.AwaitConcurrentError!void { const t: *Threaded = @ptrCast(@alignCast(userdata)); if (is_windows) { - const deadline: ?Io.Clock.Timestamp = timeout.toTimestamp(ioBasic(t)) catch |err| switch (err) { - error.Unexpected => deadline: { - recoverableOsBugDetected(); - break :deadline .{ .raw = .{ .nanoseconds = 0 }, .clock = .awake }; - }, - error.UnsupportedClock => |e| return e, - }; + const deadline: ?Io.Clock.Timestamp = timeout.toTimestamp(ioBasic(t)); try batchAwaitWindows(b, true); while (b.pending.head != .none and b.completions.head == .none) { var delay_interval: windows.LARGE_INTEGER = interval: { const d = deadline orelse break :interval std.math.minInt(windows.LARGE_INTEGER); - break :interval t.deadlineToWindowsInterval(d) catch |err| switch (err) { - error.UnsupportedClock => |e| return e, - error.Unexpected => { - recoverableOsBugDetected(); - break :interval -1; - }, - }; + break :interval t.deadlineToWindowsInterval(d); }; const alertable_syscall = try AlertableSyscall.start(); const delay_rc = windows.ntdll.NtDelayExecution(windows.TRUE, &delay_interval); @@ -2754,7 +2744,7 @@ fn batchAwaitConcurrent(userdata: ?*anyopaque, b: *Io.Batch, timeout: Io.Timeout else => {}, } const t_io = ioBasic(t); - const deadline = timeout.toTimestamp(t_io) catch return error.UnsupportedClock; + const deadline = timeout.toTimestamp(t_io); while (true) { const timeout_ms: i32 = t: { if (b.completions.head != .none) { @@ -2765,7 +2755,7 @@ fn batchAwaitConcurrent(userdata: ?*anyopaque, b: *Io.Batch, timeout: Io.Timeout break :t 0; } const d = deadline orelse break :t -1; - const duration = d.durationFromNow(t_io) catch return error.UnsupportedClock; + const duration = d.durationFromNow(t_io); if (duration.raw.nanoseconds <= 0) return error.Timeout; const max_poll_ms = std.math.maxInt(i32); break :t @intCast(@min(max_poll_ms, duration.raw.toMilliseconds())); @@ -10821,22 +10811,21 @@ fn fileWriteFilePositional( return error.Unimplemented; } -fn nowPosix(clock: Io.Clock) Io.Clock.Error!Io.Timestamp { +fn nowPosix(clock: Io.Clock) Io.Timestamp { const clock_id: posix.clockid_t = clockToPosix(clock); - var tp: posix.timespec = undefined; - switch (posix.errno(posix.system.clock_gettime(clock_id, &tp))) { - .SUCCESS => return timestampFromPosix(&tp), - .INVAL => return error.UnsupportedClock, - else => |err| return posix.unexpectedErrno(err), + var timespec: posix.timespec = undefined; + switch (posix.errno(posix.system.clock_gettime(clock_id, ×pec))) { + .SUCCESS => return timestampFromPosix(×pec), + else => return .zero, } } -fn now(userdata: ?*anyopaque, clock: Io.Clock) Io.Clock.Error!Io.Timestamp { +fn now(userdata: ?*anyopaque, clock: Io.Clock) Io.Timestamp { const t: *Threaded = @ptrCast(@alignCast(userdata)); _ = t; return nowInner(clock); } -fn nowInner(clock: Io.Clock) Io.Clock.Error!Io.Timestamp { +fn nowInner(clock: Io.Clock) Io.Timestamp { return switch (native_os) { .windows => nowWindows(clock), .wasi => nowWasi(clock), @@ -10844,7 +10833,55 @@ fn nowInner(clock: Io.Clock) Io.Clock.Error!Io.Timestamp { }; } -fn nowWindows(clock: Io.Clock) Io.Clock.Error!Io.Timestamp { +fn clockResolution(userdata: ?*anyopaque, clock: Io.Clock) Io.Duration { + const t: *Threaded = @ptrCast(@alignCast(userdata)); + _ = t; + return switch (native_os) { + .windows => switch (clock) { + .awake, .boot, .real => { + // We don't need to cache QPF as it's internally just a memory read to KUSER_SHARED_DATA + // (a read-only page of info updated and mapped by the kernel to all processes): + // https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/ntddk/ns-ntddk-kuser_shared_data + // https://www.geoffchappell.com/studies/windows/km/ntoskrnl/inc/api/ntexapi_x/kuser_shared_data/index.htm + var qpf: windows.LARGE_INTEGER = undefined; + if (windows.ntdll.RtlQueryPerformanceFrequency(&qpf) != 0) { + recoverableOsBugDetected(); + return .zero; + } + // 10Mhz (1 qpc tick every 100ns) is a common enough QPF value that we can optimize on it. + // https://github.com/microsoft/STL/blob/785143a0c73f030238ef618890fd4d6ae2b3a3a0/stl/inc/chrono#L694-L701 + const common_qpf = 10_000_000; + if (qpf == common_qpf) return .fromNanoseconds(std.time.ns_per_s / common_qpf); + + // Convert to ns using fixed point. + const scale = @as(u64, std.time.ns_per_s << 32) / @as(u32, @intCast(qpf)); + const result = scale >> 32; + return .fromNanoseconds(result); + }, + .cpu_process, .cpu_thread => return .zero, + }, + .wasi => { + if (builtin.link_libc) return clockResolutionPosix(clock); + var ns: std.os.wasi.timestamp_t = undefined; + return switch (std.os.wasi.clock_res_get(clockToWasi(clock), &ns)) { + .SUCCESS => .fromNanoseconds(ns), + else => .zero, + }; + }, + else => return clockResolutionPosix(clock), + }; +} + +fn clockResolutionPosix(clock: Io.Clock) Io.Duration { + const clock_id: posix.clockid_t = clockToPosix(clock); + var timespec: posix.timespec = undefined; + return switch (posix.errno(posix.system.clock_getres(clock_id, ×pec))) { + .SUCCESS => .fromNanoseconds(nanosecondsFromPosix(×pec)), + else => .zero, + }; +} + +fn nowWindows(clock: Io.Clock) Io.Timestamp { switch (clock) { .real => { // RtlGetSystemTimePrecise() has a granularity of 100 nanoseconds @@ -10882,8 +10919,7 @@ fn nowWindows(clock: Io.Clock) Io.Clock.Error!Io.Timestamp { ×, @sizeOf(windows.KERNEL_USER_TIMES), null, - ) != .SUCCESS) - return error.Unexpected; + ) != .SUCCESS) return .zero; const sum = @as(i96, times.UserTime) + @as(i96, times.KernelTime); return .{ .nanoseconds = sum * 100 }; @@ -10899,8 +10935,7 @@ fn nowWindows(clock: Io.Clock) Io.Clock.Error!Io.Timestamp { ×, @sizeOf(windows.KERNEL_USER_TIMES), null, - ) != .SUCCESS) - return error.Unexpected; + ) != .SUCCESS) return .zero; const sum = @as(i96, times.UserTime) + @as(i96, times.KernelTime); return .{ .nanoseconds = sum * 100 }; @@ -10908,23 +10943,23 @@ fn nowWindows(clock: Io.Clock) Io.Clock.Error!Io.Timestamp { } } -fn nowWasi(clock: Io.Clock) Io.Clock.Error!Io.Timestamp { +fn nowWasi(clock: Io.Clock) Io.Timestamp { var ns: std.os.wasi.timestamp_t = undefined; const err = std.os.wasi.clock_time_get(clockToWasi(clock), 1, &ns); - if (err != .SUCCESS) return error.Unexpected; + if (err != .SUCCESS) return .zero; return .fromNanoseconds(ns); } -fn sleep(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void { +fn sleep(userdata: ?*anyopaque, timeout: Io.Timeout) Io.Cancelable!void { const t: *Threaded = @ptrCast(@alignCast(userdata)); if (timeout == .none) return; - if (use_parking_sleep) return parking_sleep.sleep(try timeout.toTimestamp(ioBasic(t))); + if (use_parking_sleep) return parking_sleep.sleep(timeout.toTimestamp(ioBasic(t))); if (native_os == .wasi) return sleepWasi(t, timeout); if (@TypeOf(posix.system.clock_nanosleep) != void) return sleepPosix(timeout); return sleepNanosleep(t, timeout); } -fn sleepPosix(timeout: Io.Timeout) Io.SleepError!void { +fn sleepPosix(timeout: Io.Timeout) Io.Cancelable!void { const clock_id: posix.clockid_t = clockToPosix(switch (timeout) { .none => .awake, .duration => |d| d.clock, @@ -10944,25 +10979,27 @@ fn sleepPosix(timeout: Io.Timeout) Io.SleepError!void { } }, ×pec, ×pec); // POSIX-standard libc clock_nanosleep() returns *positive* errno values directly switch (if (builtin.link_libc) @as(posix.E, @enumFromInt(rc)) else posix.errno(rc)) { - .SUCCESS => { - syscall.finish(); - return; - }, .INTR => { try syscall.checkCancel(); continue; }, - .INVAL => return syscall.fail(error.UnsupportedClock), - else => |err| return syscall.unexpectedErrno(err), + // Handles SUCCESS as well as clock not available and unexpected + // errors. The user had a chance to check clock resolution before + // getting here, which would have reported 0, making this a legal + // amount of time to sleep. + else => { + syscall.finish(); + return; + }, } } } -fn sleepWasi(t: *Threaded, timeout: Io.Timeout) Io.SleepError!void { +fn sleepWasi(t: *Threaded, timeout: Io.Timeout) Io.Cancelable!void { const t_io = ioBasic(t); const w = std.os.wasi; - const clock: w.subscription_clock_t = if (try timeout.toDurationFromNow(t_io)) |d| .{ + const clock: w.subscription_clock_t = if (timeout.toDurationFromNow(t_io)) |d| .{ .id = clockToWasi(d.clock), .timeout = std.math.lossyCast(u64, d.raw.nanoseconds), .precision = 0, @@ -10987,13 +11024,13 @@ fn sleepWasi(t: *Threaded, timeout: Io.Timeout) Io.SleepError!void { syscall.finish(); } -fn sleepNanosleep(t: *Threaded, timeout: Io.Timeout) Io.SleepError!void { +fn sleepNanosleep(t: *Threaded, timeout: Io.Timeout) Io.Cancelable!void { const t_io = ioBasic(t); const sec_type = @typeInfo(posix.timespec).@"struct".fields[0].type; const nsec_type = @typeInfo(posix.timespec).@"struct".fields[1].type; var timespec: posix.timespec = t: { - const d = (try timeout.toDurationFromNow(t_io)) orelse break :t .{ + const d = timeout.toDurationFromNow(t_io) orelse break :t .{ .sec = std.math.maxInt(sec_type), .nsec = std.math.maxInt(nsec_type), }; @@ -12630,7 +12667,7 @@ fn netReceivePosix( var message_i: usize = 0; var data_i: usize = 0; - const deadline = timeout.toTimestamp(t_io) catch |err| return .{ err, message_i }; + const deadline = timeout.toTimestamp(t_io); recv: while (true) { if (message_buffer.len - message_i == 0) return .{ null, message_i }; @@ -12678,7 +12715,7 @@ fn netReceivePosix( const max_poll_ms = std.math.maxInt(u31); const timeout_ms: u31 = if (deadline) |d| t: { - const duration = d.durationFromNow(t_io) catch |err| return .{ err, message_i }; + const duration = d.durationFromNow(t_io); if (duration.raw.nanoseconds <= 0) return .{ error.Timeout, message_i }; break :t @intCast(@min(max_poll_ms, duration.raw.toMilliseconds())); } else max_poll_ms; @@ -13875,7 +13912,11 @@ fn statFromWasi(st: *const std.os.wasi.filestat_t) File.Stat { } fn timestampFromPosix(timespec: *const posix.timespec) Io.Timestamp { - return .{ .nanoseconds = @intCast(@as(i128, timespec.sec) * std.time.ns_per_s + timespec.nsec) }; + return .{ .nanoseconds = nanosecondsFromPosix(timespec) }; +} + +fn nanosecondsFromPosix(timespec: *const posix.timespec) i96 { + return @intCast(@as(i128, timespec.sec) * std.time.ns_per_s + timespec.nsec); } fn timestampToPosix(nanoseconds: i96) posix.timespec { @@ -14013,13 +14054,13 @@ fn lookupDns( // boot clock is chosen because time the computer is suspended should count // against time spent waiting for external messages to arrive. const clock: Io.Clock = .boot; - var now_ts = try clock.now(t_io); + var now_ts = clock.now(t_io); const final_ts = now_ts.addDuration(.fromSeconds(rc.timeout_seconds)); const attempt_duration: Io.Duration = .{ .nanoseconds = (std.time.ns_per_s / rc.attempts) * @as(i96, rc.timeout_seconds), }; - send: while (now_ts.nanoseconds < final_ts.nanoseconds) : (now_ts = try clock.now(t_io)) { + send: while (now_ts.nanoseconds < final_ts.nanoseconds) : (now_ts = clock.now(t_io)) { const max_messages = queries_buffer.len * HostName.ResolvConf.max_nameservers; { var message_buffer: [max_messages]Io.net.OutgoingMessage = undefined; @@ -17021,7 +17062,7 @@ const parking_futex = struct { const deadline: ?Io.Clock.Timestamp = switch (timeout) { .none => null, .duration => |d| .{ - .raw = (nowInner(d.clock) catch unreachable).addDuration(d.raw), + .raw = nowInner(d.clock).addDuration(d.raw), .clock = d.clock, }, .deadline => |d| d, @@ -17143,7 +17184,7 @@ const parking_sleep = struct { comptime { assert(use_parking_sleep); } - fn sleep(deadline: ?Io.Clock.Timestamp) Io.SleepError!void { + fn sleep(deadline: ?Io.Clock.Timestamp) Io.Cancelable!void { const opt_thread = Thread.current; cancelable: { const thread = opt_thread orelse break :cancelable; @@ -17216,12 +17257,9 @@ const parking_sleep = struct { } /// Sleep for approximately `ms` awake milliseconds in an attempt to work around Windows kernel bugs. fn windowsRetrySleep(ms: u32) (Io.Cancelable || Io.UnexpectedError)!void { - const now_timestamp = nowWindows(.awake) catch unreachable; // '.awake' is supported on Windows + const now_timestamp = nowWindows(.awake); // '.awake' is supported on Windows const deadline = now_timestamp.addDuration(.fromMilliseconds(ms)); - parking_sleep.sleep(.{ .raw = deadline, .clock = .awake }) catch |err| switch (err) { - error.UnsupportedClock => unreachable, - else => |e| return e, - }; + try parking_sleep.sleep(.{ .raw = deadline, .clock = .awake }); } }; @@ -17234,7 +17272,7 @@ fn park(opt_deadline: ?Io.Clock.Timestamp, addr_hint: ?*const anyopaque) error{T .windows => { var timeout_buf: windows.LARGE_INTEGER = undefined; const raw_timeout: ?*windows.LARGE_INTEGER = if (opt_deadline) |deadline| timeout: { - const now_timestamp = nowWindows(deadline.clock) catch unreachable; + const now_timestamp = nowWindows(deadline.clock); const nanoseconds = now_timestamp.durationTo(deadline.raw).nanoseconds; timeout_buf = @intCast(@divTrunc(-nanoseconds, 100)); break :timeout &timeout_buf; @@ -17284,17 +17322,17 @@ fn park(opt_deadline: ?Io.Clock.Timestamp, addr_hint: ?*const anyopaque) error{T } } -fn deadlineToWindowsInterval(t: *Io.Threaded, deadline: Io.Clock.Timestamp) Io.Clock.Error!windows.LARGE_INTEGER { +fn deadlineToWindowsInterval(t: *Io.Threaded, deadline: Io.Clock.Timestamp) windows.LARGE_INTEGER { // ntdll only supports two combinations: // * real-time (`.real`) sleeps with absolute deadlines // * monotonic (`.awake`/`.boot`) sleeps with relative durations switch (deadline.clock) { - .cpu_process, .cpu_thread => unreachable, // cannot sleep for CPU time + .cpu_process, .cpu_thread => return 0, .real => { return @intCast(@max(@divTrunc(deadline.raw.nanoseconds, 100), 0)); }, .awake, .boot => { - const duration = try deadline.durationFromNow(ioBasic(t)); + const duration = deadline.durationFromNow(ioBasic(t)); return @intCast(@min(@divTrunc(-duration.raw.nanoseconds, 100), -1)); }, } diff --git a/lib/std/Io/net.zig b/lib/std/Io/net.zig index 21bd13caf387e758d91333809879e87469250677..72daee6f94625f9ce88fb2e71b7802a0b3bfbbd4 100644 --- a/lib/std/Io/net.zig +++ b/lib/std/Io/net.zig @@ -1137,7 +1137,7 @@ pub const Socket = struct { const maybe_err, const count = io.vtable.netReceive(io.userdata, s.handle, (&message)[0..1], buffer, .{}, .none); if (maybe_err) |err| switch (err) { // No timeout is passed to `netReceieve`, so it must not return timeout related errors. - error.Timeout, error.UnsupportedClock => unreachable, + error.Timeout => unreachable, else => |e| return e, }; assert(1 == count); diff --git a/lib/std/Io/net/HostName.zig b/lib/std/Io/net/HostName.zig index 66b5b648a665e05c26bf93f469d3a1a114d2faca..db669c958b910f9c568fa1bd1251a916e85de14b 100644 --- a/lib/std/Io/net/HostName.zig +++ b/lib/std/Io/net/HostName.zig @@ -145,7 +145,7 @@ pub const LookupError = error{ NoAddressReturned, /// Failed to open or read "/etc/hosts" or "/etc/resolv.conf". DetectingNetworkConfigurationFailed, -} || Io.Clock.Error || IpAddress.BindError || Io.Cancelable; +} || IpAddress.BindError || Io.Cancelable; pub const LookupResult = union(enum) { address: IpAddress, diff --git a/lib/std/Io/test.zig b/lib/std/Io/test.zig index a9e2eb28d4125826143239ccc3d6ff5ceef70ffc..70af7a25ee2c909492ff53fb56ecfdfb337e5ac7 100644 --- a/lib/std/Io/test.zig +++ b/lib/std/Io/test.zig @@ -216,14 +216,12 @@ test "Group.cancel" { defer result.* = 1; io.sleep(.fromSeconds(100_000), .awake) catch |err| switch (err) { error.Canceled => |e| return e, - else => {}, }; } fn sleepRecancel(io: Io, result: *usize) void { io.sleep(.fromSeconds(100_000), .awake) catch |err| switch (err) { error.Canceled => io.recancel(), - else => {}, }; result.* = 1; } @@ -523,8 +521,6 @@ test "cancel sleep" { fn blockUntilCanceled(io: Io) void { while (true) io.sleep(.fromSeconds(100_000), .awake) catch |err| switch (err) { error.Canceled => return, - error.UnsupportedClock => @panic("unsupported clock"), - error.Unexpected => @panic("unexpected"), }; } }; @@ -552,8 +548,6 @@ test "tasks spawned in group after Group.cancel are canceled" { fn blockUntilCanceled(io: Io) Io.Cancelable!void { while (true) io.sleep(.fromSeconds(100_000), .awake) catch |err| switch (err) { error.Canceled => |e| return e, - error.UnsupportedClock => @panic("unsupported clock"), - error.Unexpected => @panic("unexpected"), }; } }; diff --git a/lib/std/crypto/Certificate/Bundle.zig b/lib/std/crypto/Certificate/Bundle.zig index 385ef23c9c7ee205a24263472b08ee107ed95234..5a07ff8a390b5a3402d61660059bcee3265ef031 100644 --- a/lib/std/crypto/Certificate/Bundle.zig +++ b/lib/std/crypto/Certificate/Bundle.zig @@ -212,7 +212,7 @@ pub fn addCertsFromDir(cb: *Bundle, gpa: Allocator, io: Io, now: Io.Timestamp, i } } -pub const AddCertsFromFilePathError = Io.File.OpenError || AddCertsFromFileError || Io.Clock.Error; +pub const AddCertsFromFilePathError = Io.File.OpenError || AddCertsFromFileError; pub fn addCertsFromFilePathAbsolute( cb: *Bundle, @@ -338,7 +338,7 @@ test "scan for OS-provided certificates" { var bundle: Bundle = .{}; defer bundle.deinit(gpa); - const now = try Io.Clock.real.now(io); + const now = Io.Clock.real.now(io); try bundle.rescan(gpa, io, now); } diff --git a/lib/std/http/Client.zig b/lib/std/http/Client.zig index a46e12ecf4846dd56c31de2444ebd3c1700f85bc..bc0e3ec0ff10c80d090ac1361f7e999684bed60e 100644 --- a/lib/std/http/Client.zig +++ b/lib/std/http/Client.zig @@ -1700,7 +1700,7 @@ pub fn request( defer client.ca_bundle_mutex.unlock(io); if (client.now == null) { - const now = try Io.Clock.real.now(io); + const now = Io.Clock.real.now(io); client.now = now; client.ca_bundle.rescan(client.allocator, io, now) catch return error.CertificateBundleLoadFailure; diff --git a/lib/std/os/linux.zig b/lib/std/os/linux.zig index 501ba44557b4a817d7de516795bd4ee4d3361c24..4539aaff4e3aaf9df611d7a7da04b5557438394e 100644 --- a/lib/std/os/linux.zig +++ b/lib/std/os/linux.zig @@ -1914,21 +1914,21 @@ fn init_vdso_clock_gettime(clk: clockid_t, ts: *timespec) callconv(.c) usize { @atomicStore(?VdsoClockGettime, &vdso_clock_gettime, ptr, .monotonic); // Call into the VDSO if available if (ptr) |f| return f(clk, ts); - return @as(usize, @bitCast(-@as(isize, @intFromEnum(E.NOSYS)))); + return @bitCast(-@as(isize, @intFromEnum(E.NOSYS))); } -pub fn clock_getres(clk_id: i32, tp: *timespec) usize { +pub fn clock_getres(clk_id: clockid_t, tp: *timespec) usize { return syscall2( if (@hasField(SYS, "clock_getres") and native_arch != .hexagon) .clock_getres else .clock_getres_time64, - @as(usize, @bitCast(@as(isize, clk_id))), + @as(usize, @intFromEnum(clk_id)), @intFromPtr(tp), ); } -pub fn clock_settime(clk_id: i32, tp: *const timespec) usize { +pub fn clock_settime(clk_id: clockid_t, tp: *const timespec) usize { return syscall2( if (@hasField(SYS, "clock_settime") and native_arch != .hexagon) .clock_settime else .clock_settime64, - @as(usize, @bitCast(@as(isize, clk_id))), + @as(usize, @intFromEnum(clk_id)), @intFromPtr(tp), ); } diff --git a/lib/std/os/linux/IoUring/test.zig b/lib/std/os/linux/IoUring/test.zig index 644b9b7c77c2666a0493a84eab6cc6b8065b4e2d..240a60c2e7a59b555bd2954e2c6b07e1239db4e9 100644 --- a/lib/std/os/linux/IoUring/test.zig +++ b/lib/std/os/linux/IoUring/test.zig @@ -620,12 +620,12 @@ test "timeout (after a relative time)" { const margin = 5; const ts: linux.kernel_timespec = .{ .sec = 0, .nsec = ms * 1000000 }; - const started = try std.Io.Clock.awake.now(io); + const started = std.Io.Clock.awake.now(io); const sqe = try ring.timeout(0x55555555, &ts, 0, 0); try testing.expectEqual(linux.IORING_OP.TIMEOUT, sqe.opcode); try testing.expectEqual(@as(u32, 1), try ring.submit()); const cqe = try ring.copy_cqe(); - const stopped = try std.Io.Clock.awake.now(io); + const stopped = std.Io.Clock.awake.now(io); try testing.expectEqual(linux.io_uring_cqe{ .user_data = 0x55555555, diff --git a/lib/std/posix.zig b/lib/std/posix.zig index 7c03e7953b5be7a4eba881adb33fd6a71310fa01..1742325026ed269ef38c78ddb61d80ec750e8390 100644 --- a/lib/std/posix.zig +++ b/lib/std/posix.zig @@ -864,58 +864,6 @@ pub fn dl_iterate_phdr( } } -pub const ClockGetTimeError = error{UnsupportedClock} || UnexpectedError; - -pub fn clock_gettime(clock_id: clockid_t) ClockGetTimeError!timespec { - var tp: timespec = undefined; - - if (native_os == .windows) { - @compileError("Windows does not support POSIX; use Windows-specific API or cross-platform std.time API"); - } else if (native_os == .wasi and !builtin.link_libc) { - var ts: timestamp_t = undefined; - switch (system.clock_time_get(clock_id, 1, &ts)) { - .SUCCESS => { - tp = .{ - .sec = @intCast(ts / std.time.ns_per_s), - .nsec = @intCast(ts % std.time.ns_per_s), - }; - }, - .INVAL => return error.UnsupportedClock, - else => |err| return unexpectedErrno(err), - } - return tp; - } - - switch (errno(system.clock_gettime(clock_id, &tp))) { - .SUCCESS => return tp, - .FAULT => unreachable, - .INVAL => return error.UnsupportedClock, - else => |err| return unexpectedErrno(err), - } -} - -pub fn clock_getres(clock_id: clockid_t, res: *timespec) ClockGetTimeError!void { - if (native_os == .wasi and !builtin.link_libc) { - var ts: timestamp_t = undefined; - switch (system.clock_res_get(@bitCast(clock_id), &ts)) { - .SUCCESS => res.* = .{ - .sec = @intCast(ts / std.time.ns_per_s), - .nsec = @intCast(ts % std.time.ns_per_s), - }, - .INVAL => return error.UnsupportedClock, - else => |err| return unexpectedErrno(err), - } - return; - } - - switch (errno(system.clock_getres(clock_id, res))) { - .SUCCESS => return, - .FAULT => unreachable, - .INVAL => return error.UnsupportedClock, - else => |err| return unexpectedErrno(err), - } -} - pub const SchedGetAffinityError = error{PermissionDenied} || UnexpectedError; pub fn sched_getaffinity(pid: pid_t) SchedGetAffinityError!cpu_set_t { diff --git a/lib/std/time.zig b/lib/std/time.zig index c47a3121a7bbff32875e274abf7d23510d32b482..562d2042c7a7afc26cf4441600ca49abe183371b 100644 --- a/lib/std/time.zig +++ b/lib/std/time.zig @@ -1,11 +1,3 @@ -const std = @import("std.zig"); -const builtin = @import("builtin"); -const assert = std.debug.assert; -const testing = std.testing; -const math = std.math; -const windows = std.os.windows; -const posix = std.posix; - pub const epoch = @import("time/epoch.zig"); // Divisions of a nanosecond. @@ -38,180 +30,6 @@ pub const s_per_hour = s_per_min * 60; pub const s_per_day = s_per_hour * 24; pub const s_per_week = s_per_day * 7; -/// An Instant represents a timestamp with respect to the currently -/// executing program that ticks during suspend and can be used to -/// record elapsed time unlike `nanoTimestamp`. -/// -/// It tries to sample the system's fastest and most precise timer available. -/// It also tries to be monotonic, but this is not a guarantee due to OS/hardware bugs. -/// If you need monotonic readings for elapsed time, consider `Timer` instead. -pub const Instant = struct { - timestamp: if (is_posix) posix.timespec else u64, - - // true if we should use clock_gettime() - const is_posix = switch (builtin.os.tag) { - .windows, .uefi, .wasi => false, - else => true, - }; - - /// Queries the system for the current moment of time as an Instant. - /// This is not guaranteed to be monotonic or steadily increasing, but for - /// most implementations it is. - /// Returns `error.Unsupported` when a suitable clock is not detected. - pub fn now() error{Unsupported}!Instant { - const clock_id = switch (builtin.os.tag) { - .windows => { - // QPC on windows doesn't fail on >= XP/2000 and includes time suspended. - return .{ .timestamp = windows.QueryPerformanceCounter() }; - }, - .wasi => { - var ns: std.os.wasi.timestamp_t = undefined; - const rc = std.os.wasi.clock_time_get(.MONOTONIC, 1, &ns); - if (rc != .SUCCESS) return error.Unsupported; - return .{ .timestamp = ns }; - }, - .uefi => { - const value, _ = std.os.uefi.system_table.runtime_services.getTime() catch return error.Unsupported; - return .{ .timestamp = value.toEpoch() }; - }, - // On darwin, use UPTIME_RAW instead of MONOTONIC as it ticks while - // suspended. - .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => posix.CLOCK.UPTIME_RAW, - // On freebsd derivatives, use MONOTONIC_FAST as currently there's - // no precision tradeoff. - .freebsd, .dragonfly => posix.CLOCK.MONOTONIC_FAST, - // On linux, use BOOTTIME instead of MONOTONIC as it ticks while - // suspended. - .linux => posix.CLOCK.BOOTTIME, - // On other posix systems, MONOTONIC is generally the fastest and - // ticks while suspended. - else => posix.CLOCK.MONOTONIC, - }; - - const ts = posix.clock_gettime(clock_id) catch return error.Unsupported; - return .{ .timestamp = ts }; - } - - /// Quickly compares two instances between each other. - pub fn order(self: Instant, other: Instant) std.math.Order { - // windows and wasi timestamps are in u64 which is easily comparible - if (!is_posix) { - return std.math.order(self.timestamp, other.timestamp); - } - - var ord = std.math.order(self.timestamp.sec, other.timestamp.sec); - if (ord == .eq) { - ord = std.math.order(self.timestamp.nsec, other.timestamp.nsec); - } - return ord; - } - - /// Returns elapsed time in nanoseconds since the `earlier` Instant. - /// This assumes that the `earlier` Instant represents a moment in time before or equal to `self`. - /// This also assumes that the time that has passed between both Instants fits inside a u64 (~585 yrs). - pub fn since(self: Instant, earlier: Instant) u64 { - switch (builtin.os.tag) { - .windows => { - // We don't need to cache QPF as it's internally just a memory read to KUSER_SHARED_DATA - // (a read-only page of info updated and mapped by the kernel to all processes): - // https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/ntddk/ns-ntddk-kuser_shared_data - // https://www.geoffchappell.com/studies/windows/km/ntoskrnl/inc/api/ntexapi_x/kuser_shared_data/index.htm - const qpc = self.timestamp - earlier.timestamp; - const qpf = windows.QueryPerformanceFrequency(); - - // 10Mhz (1 qpc tick every 100ns) is a common enough QPF value that we can optimize on it. - // https://github.com/microsoft/STL/blob/785143a0c73f030238ef618890fd4d6ae2b3a3a0/stl/inc/chrono#L694-L701 - const common_qpf = 10_000_000; - if (qpf == common_qpf) { - return qpc * (ns_per_s / common_qpf); - } - - // Convert to ns using fixed point. - const scale = @as(u64, std.time.ns_per_s << 32) / @as(u32, @intCast(qpf)); - const result = (@as(u96, qpc) * scale) >> 32; - return @as(u64, @truncate(result)); - }, - .uefi, .wasi => { - // UEFI and WASI timestamps are directly in nanoseconds - return self.timestamp - earlier.timestamp; - }, - else => { - // Convert timespec diff to ns - const seconds = @as(u64, @intCast(self.timestamp.sec - earlier.timestamp.sec)); - const elapsed = (seconds * ns_per_s) + @as(u32, @intCast(self.timestamp.nsec)); - return elapsed - @as(u32, @intCast(earlier.timestamp.nsec)); - }, - } - } -}; - -/// A monotonic, high performance timer. -/// -/// Timer.start() is used to initialize the timer -/// and gives the caller an opportunity to check for the existence of a supported clock. -/// Once a supported clock is discovered, -/// it is assumed that it will be available for the duration of the Timer's use. -/// -/// Monotonicity is ensured by saturating on the most previous sample. -/// This means that while timings reported are monotonic, -/// they're not guaranteed to tick at a steady rate as this is up to the underlying system. -pub const Timer = struct { - started: Instant, - previous: Instant, - - pub const Error = error{TimerUnsupported}; - - /// Initialize the timer by querying for a supported clock. - /// Returns `error.TimerUnsupported` when such a clock is unavailable. - /// This should only fail in hostile environments such as linux seccomp misuse. - pub fn start() Error!Timer { - const current = Instant.now() catch return error.TimerUnsupported; - return Timer{ .started = current, .previous = current }; - } - - /// Reads the timer value since start or the last reset in nanoseconds. - pub fn read(self: *Timer) u64 { - const current = self.sample(); - return current.since(self.started); - } - - /// Resets the timer value to 0/now. - pub fn reset(self: *Timer) void { - const current = self.sample(); - self.started = current; - } - - /// Returns the current value of the timer in nanoseconds, then resets it. - pub fn lap(self: *Timer) u64 { - const current = self.sample(); - defer self.started = current; - return current.since(self.started); - } - - /// Returns an Instant sampled at the callsite that is - /// guaranteed to be monotonic with respect to the timer's starting point. - fn sample(self: *Timer) Instant { - const current = Instant.now() catch unreachable; - if (current.order(self.previous) == .gt) { - self.previous = current; - } - return self.previous; - } -}; - -test Timer { - const io = std.testing.io; - - var timer = try Timer.start(); - - try std.Io.Clock.Duration.sleep(.{ .clock = .awake, .raw = .fromMilliseconds(10) }, io); - const time_0 = timer.read(); - try testing.expect(time_0 > 0); - - const time_1 = timer.lap(); - try testing.expect(time_1 >= time_0); -} - test { _ = epoch; } diff --git a/src/Compilation.zig b/src/Compilation.zig index 6b6021ab3c5e51351dd99a3e1e5b73a1ee18b33c..4f671b71b944886940d74f1dd6639b76b28e1aaf 100644 --- a/src/Compilation.zig +++ b/src/Compilation.zig @@ -331,48 +331,42 @@ const QueuedJobs = struct { pub const Timer = union(enum) { unused, active: struct { - start: std.time.Instant, + start: Io.Timestamp, saved_ns: u64, }, paused: u64, stopped, - pub fn pause(t: *Timer) void { + pub fn pause(t: *Timer, io: Io) void { switch (t.*) { .unused => return, .active => |a| { - const current = std.time.Instant.now() catch unreachable; - const new_ns = switch (current.order(a.start)) { - .lt, .eq => 0, - .gt => current.since(a.start), - }; + const current: Io.Timestamp = .now(io, .awake); + const new_ns: u64 = @intCast(current.nanoseconds -| a.start.nanoseconds); t.* = .{ .paused = a.saved_ns + new_ns }; }, .paused => unreachable, .stopped => unreachable, } } - pub fn @"resume"(t: *Timer) void { + pub fn @"resume"(t: *Timer, io: Io) void { switch (t.*) { .unused => return, .active => unreachable, .paused => |saved_ns| t.* = .{ .active = .{ - .start = std.time.Instant.now() catch unreachable, + .start = .now(io, .awake), .saved_ns = saved_ns, } }, .stopped => unreachable, } } - pub fn finish(t: *Timer) ?u64 { + pub fn finish(t: *Timer, io: Io) ?u64 { defer t.* = .stopped; switch (t.*) { .unused => return null, .active => |a| { - const current = std.time.Instant.now() catch unreachable; - const new_ns = switch (current.order(a.start)) { - .lt, .eq => 0, - .gt => current.since(a.start), - }; + const current: Io.Timestamp = .now(io, .awake); + const new_ns: u64 = @intCast(current.nanoseconds -| a.start.nanoseconds); return a.saved_ns + new_ns; }, .paused => |ns| return ns, @@ -387,7 +381,8 @@ pub const Timer = union(enum) { /// is set. pub fn startTimer(comp: *Compilation) Timer { if (comp.time_report == null) return .unused; - const now = std.time.Instant.now() catch @panic("std.time.Timer unsupported; cannot emit time report"); + const io = comp.io; + const now: Io.Timestamp = .now(io, .awake); return .{ .active = .{ .start = now, .saved_ns = 0, @@ -3408,7 +3403,7 @@ fn flush(comp: *Compilation, arena: Allocator, tid: Zcu.PerThread.Id) (Io.Cancel defer sub_prog_node.end(); var timer = comp.startTimer(); - defer if (timer.finish()) |ns| { + defer if (timer.finish(io)) |ns| { comp.mutex.lockUncancelable(io); defer comp.mutex.unlock(io); comp.time_report.?.stats.real_ns_llvm_emit = ns; @@ -3453,7 +3448,7 @@ fn flush(comp: *Compilation, arena: Allocator, tid: Zcu.PerThread.Id) (Io.Cancel } if (comp.bin_file) |lf| { var timer = comp.startTimer(); - defer if (timer.finish()) |ns| { + defer if (timer.finish(io)) |ns| { comp.mutex.lockUncancelable(io); defer comp.mutex.unlock(io); comp.time_report.?.stats.real_ns_link_flush = ns; @@ -4686,7 +4681,7 @@ fn performAllTheWork( var decl_work_timer: ?Timer = null; defer commit_timer: { const t = &(decl_work_timer orelse break :commit_timer); - const ns = t.finish() orelse break :commit_timer; + const ns = t.finish(io) orelse break :commit_timer; comp.mutex.lockUncancelable(io); defer comp.mutex.unlock(io); comp.time_report.?.stats.real_ns_decls = ns; @@ -4719,7 +4714,7 @@ fn performAllTheWork( defer zir_prog_node.end(); var timer = comp.startTimer(); - defer if (timer.finish()) |ns| { + defer if (timer.finish(io)) |ns| { comp.mutex.lockUncancelable(io); defer comp.mutex.unlock(io); comp.time_report.?.stats.real_ns_files = ns; diff --git a/src/Zcu.zig b/src/Zcu.zig index ceccc2c192cd8a8fef9248179b82535476d34cf3..6a2872eae8fa3c0d3ce1beed7a29c1b080c4d1b2 100644 --- a/src/Zcu.zig +++ b/src/Zcu.zig @@ -4754,6 +4754,7 @@ const TrackedUnitSema = struct { analysis_timer_decl: ?InternPool.TrackedInst.Index, pub fn end(tus: TrackedUnitSema, zcu: *Zcu) void { const comp = zcu.comp; + const io = comp.io; if (tus.old_name) |old_name| { zcu.sema_prog_node.completeOne(); // we're just renaming, but it's effectively completion zcu.cur_sema_prog_node.setName(&old_name); @@ -4762,9 +4763,8 @@ const TrackedUnitSema = struct { zcu.cur_sema_prog_node = .none; } report_time: { - const sema_ns = zcu.cur_analysis_timer.?.finish() orelse break :report_time; + const sema_ns = zcu.cur_analysis_timer.?.finish(io) orelse break :report_time; const zir_decl = tus.analysis_timer_decl orelse break :report_time; - const io = comp.io; comp.mutex.lockUncancelable(io); defer comp.mutex.unlock(io); comp.time_report.?.stats.cpu_ns_sema += sema_ns; @@ -4779,11 +4779,13 @@ const TrackedUnitSema = struct { gop.value_ptr.count += 1; } zcu.cur_analysis_timer = tus.old_analysis_timer; - if (zcu.cur_analysis_timer) |*t| t.@"resume"(); + if (zcu.cur_analysis_timer) |*t| t.@"resume"(io); } }; pub fn trackUnitSema(zcu: *Zcu, name: []const u8, zir_inst: ?InternPool.TrackedInst.Index) TrackedUnitSema { - if (zcu.cur_analysis_timer) |*t| t.pause(); + const comp = zcu.comp; + const io = comp.io; + if (zcu.cur_analysis_timer) |*t| t.pause(io); const old_analysis_timer = zcu.cur_analysis_timer; zcu.cur_analysis_timer = zcu.comp.startTimer(); const old_name: ?[std.Progress.Node.max_name_len]u8 = old_name: { diff --git a/src/Zcu/PerThread.zig b/src/Zcu/PerThread.zig index 950a3e5a19f5c08524df7087aa201f54e4f05400..56b09d9da1110eaf2491bff1ff03e7b5e6b50c53 100644 --- a/src/Zcu/PerThread.zig +++ b/src/Zcu/PerThread.zig @@ -263,7 +263,7 @@ pub fn updateFile( var timer = comp.startTimer(); // Any potential AST errors are converted to ZIR errors when we run AstGen/ZonGen. file.tree = try Ast.parse(gpa, source, file.getMode()); - if (timer.finish()) |ns_parse| { + if (timer.finish(io)) |ns_parse| { comp.mutex.lockUncancelable(io); defer comp.mutex.unlock(io); comp.time_report.?.stats.cpu_ns_parse += ns_parse; @@ -295,7 +295,7 @@ pub fn updateFile( else => |e| return e, }; - if (timer.finish()) |ns_astgen| { + if (timer.finish(io)) |ns_astgen| { comp.mutex.lockUncancelable(io); defer comp.mutex.unlock(io); comp.time_report.?.stats.cpu_ns_astgen += ns_astgen; @@ -4485,7 +4485,7 @@ pub fn runCodegen(pt: Zcu.PerThread, func_index: InternPool.Index, air: *Air) Ru const codegen_result = runCodegenInner(pt, func_index, air); - if (timer.finish()) |ns_codegen| report_time: { + if (timer.finish(io)) |ns_codegen| report_time: { const ip = &zcu.intern_pool; const nav = ip.indexToKey(func_index).func.owner_nav; const zir_decl = ip.getNav(nav).srcInst(ip); diff --git a/src/link.zig b/src/link.zig index 3af768a363733e5b2dc25e8f46412c601b2e05a0..c8e46540bac608301ee75b5c315b5c9b2ac05b72 100644 --- a/src/link.zig +++ b/src/link.zig @@ -1388,7 +1388,7 @@ pub fn doPrelinkTask(comp: *Compilation, task: PrelinkTask) void { }; var timer = comp.startTimer(); - defer if (timer.finish()) |ns| { + defer if (timer.finish(io)) |ns| { comp.mutex.lockUncancelable(io); defer comp.mutex.unlock(io); comp.time_report.?.stats.cpu_ns_link += ns; @@ -1535,12 +1535,12 @@ pub fn doZcuTask(comp: *Compilation, tid: usize, task: ZcuTask) void { break :nav nav_index; }, .link_func => |codegen_task| nav: { - timer.pause(); + timer.pause(io); const func, var mir = codegen_task.wait(&zcu.codegen_task_pool, io) catch |err| switch (err) { error.Canceled, error.AlreadyReported => return, }; defer mir.deinit(zcu); - timer.@"resume"(); + timer.@"resume"(io); const nav = zcu.funcInfo(func).owner_nav; const fqn_slice = ip.getNav(nav).fqn.toSlice(ip); @@ -1592,7 +1592,7 @@ pub fn doZcuTask(comp: *Compilation, tid: usize, task: ZcuTask) void { }, }; - if (timer.finish()) |ns_link| report_time: { + if (timer.finish(io)) |ns_link| report_time: { comp.mutex.lockUncancelable(io); defer comp.mutex.unlock(io); const tr = &zcu.comp.time_report.?; -- 2.54.0 From 11476d83c9218ad2b22ab59b2fee3a4a4bf36367 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sun, 1 Feb 2026 22:32:48 -0800 Subject: [PATCH 179/499] stage1: add wasi_snapshot_preview1_clock_res_get to wasi.c --- stage1/wasi.c | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/stage1/wasi.c b/stage1/wasi.c index e4772735d9ada66cc977d7c681a4930faebc203e..3be4bd00248a7da45f643c070fa3576970024798 100644 --- a/stage1/wasi.c +++ b/stage1/wasi.c @@ -924,6 +924,15 @@ uint32_t wasi_snapshot_preview1_clock_time_get(uint32_t id, uint64_t precision, return wasi_errno_success; } +uint32_t wasi_snapshot_preview1_clock_res_get(uint32_t id, uint32_t res_timestamp) { + uint8_t *const m = *wasm_memory; + uint64_t *res_timestamp_ptr = (uint64_t *)&m[res_timestamp]; +#if LOG_TRACE + fprintf(stderr, "wasi_snapshot_preview1_clock_res_get(%u, %llu)\n", id, (unsigned long long)res_timestamp); +#endif + return wasi_errno_notcapable; +} + uint32_t wasi_snapshot_preview1_path_remove_directory(uint32_t fd, uint32_t path, uint32_t path_len) { uint8_t *const m = *wasm_memory; const char *path_ptr = (const char *)&m[path]; -- 2.54.0 From fe5da36aa3b1dbeb02276c6628640c68a5922191 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 3 Feb 2026 01:02:48 -0800 Subject: [PATCH 180/499] std.Io make Clock.resolution fallible --- lib/std/Io.zig | 9 +++++++-- lib/std/Io/Threaded.zig | 4 ++-- 2 files changed, 9 insertions(+), 4 deletions(-) diff --git a/lib/std/Io.zig b/lib/std/Io.zig index 09da9b533acdcfa58bc443992015c646660ee141..92327e0f5d16f0e83cd3cfabe0c86f463a68f03c 100644 --- a/lib/std/Io.zig +++ b/lib/std/Io.zig @@ -232,7 +232,7 @@ pub const VTable = struct { progressParentFile: *const fn (?*anyopaque) std.Progress.ParentFileError!File, now: *const fn (?*anyopaque, Clock) Timestamp, - clockResolution: *const fn (?*anyopaque, Clock) Duration, + clockResolution: *const fn (?*anyopaque, Clock) Clock.ResolutionError!Duration, sleep: *const fn (?*anyopaque, Timeout) Cancelable!void, random: *const fn (?*anyopaque, buffer: []u8) void, @@ -713,9 +713,14 @@ pub const Clock = enum { return io.vtable.now(io.userdata, clock); } + pub const ResolutionError = error{ + ClockUnavailable, + Unexpected, + }; + /// Reveals the granularity of `clock`. May be zero, indicating /// unsupported clock. - pub fn resolution(clock: Clock, io: Io) Io.Duration { + pub fn resolution(clock: Clock, io: Io) ResolutionError!Io.Duration { return io.vtable.clockResolution(io.userdata, clock); } diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 2cd350f58c9efcddba6f9bb9704fbaa21de0644e..6255d96978405cc0d1b1fc4908644facea0ef4b0 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -10833,7 +10833,7 @@ fn nowInner(clock: Io.Clock) Io.Timestamp { }; } -fn clockResolution(userdata: ?*anyopaque, clock: Io.Clock) Io.Duration { +fn clockResolution(userdata: ?*anyopaque, clock: Io.Clock) Io.Clock.ResolutionError!Io.Duration { const t: *Threaded = @ptrCast(@alignCast(userdata)); _ = t; return switch (native_os) { @@ -10872,7 +10872,7 @@ fn clockResolution(userdata: ?*anyopaque, clock: Io.Clock) Io.Duration { }; } -fn clockResolutionPosix(clock: Io.Clock) Io.Duration { +fn clockResolutionPosix(clock: Io.Clock) Io.Clock.ResolutionError!Io.Duration { const clock_id: posix.clockid_t = clockToPosix(clock); var timespec: posix.timespec = undefined; return switch (posix.errno(posix.system.clock_getres(clock_id, ×pec))) { -- 2.54.0 From 02599bccb5c0b75e6dcdf950fa2dc1ece40c19f3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Tue, 3 Feb 2026 13:31:24 +0100 Subject: [PATCH 181/499] build: bump test-libc max_rss to 3_500_000_000 error: memory usage peaked at 3.11GB (3105054720 bytes), exceeding the declared upper bound of 2.90GB (2900000000 bytes) --- build.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.zig b/build.zig index 84cbba38bdb6026dc3691463ac40d1fcad33419e..8006715735ab97eb3d2b216c5688e6e8a85de356 100644 --- a/build.zig +++ b/build.zig @@ -681,7 +681,7 @@ pub fn build(b: *std.Build) !void { .test_filters = test_filters, .test_target_filters = test_target_filters, .skip_wasm = skip_wasm, - .max_rss = 2_496_066_355, + .max_rss = 3_500_000_000, })) |test_libc_step| test_step.dependOn(test_libc_step); } -- 2.54.0 From 6ce0dd1a81d52e5e2e331e88628c89e989dc15ee Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Thu, 18 Dec 2025 14:26:11 +0100 Subject: [PATCH 182/499] ci: run test-libc on x86_64-linux-debug-llvm and x86_64-linux-release --- ci/x86_64-linux-debug-llvm.sh | 1 + ci/x86_64-linux-release.sh | 1 + 2 files changed, 2 insertions(+) diff --git a/ci/x86_64-linux-debug-llvm.sh b/ci/x86_64-linux-debug-llvm.sh index 6ef1b5a00939d8adfb1e2c52f555ce6593c03a25..26d3911c135b0bdbf91ae0004788349e1cdc998b 100755 --- a/ci/x86_64-linux-debug-llvm.sh +++ b/ci/x86_64-linux-debug-llvm.sh @@ -54,6 +54,7 @@ stage3-debug/bin/zig build \ stage3-debug/bin/zig build test docs \ --maxrss ${ZSF_MAX_RSS:-0} \ -Dlldb=$HOME/deps/lldb-zig/Debug-e0a42bb34/bin/lldb \ + -Dlibc-test-path=$HOME/deps/libc-test-f2bac77 \ -fqemu \ -fwasmtime \ -Dstatic-llvm \ diff --git a/ci/x86_64-linux-release.sh b/ci/x86_64-linux-release.sh index 99781406e273ffd10dccbe3e2f20d3fdda08b55c..94e6e93bbdc0f9fd5146c300624efb3f16a222c0 100755 --- a/ci/x86_64-linux-release.sh +++ b/ci/x86_64-linux-release.sh @@ -59,6 +59,7 @@ stage3-release/bin/zig build \ stage3-release/bin/zig build test docs \ --maxrss ${ZSF_MAX_RSS:-0} \ -Dlldb=$HOME/deps/lldb-zig/Release-e0a42bb34/bin/lldb \ + -Dlibc-test-path=$HOME/deps/libc-test-f2bac77 \ -fqemu \ -fwasmtime \ -Dstatic-llvm \ -- 2.54.0 From 7aae7dd3f4d4b85837369fa591e566ae812f87dc Mon Sep 17 00:00:00 2001 From: Ivel Date: Tue, 3 Feb 2026 00:53:58 -0300 Subject: [PATCH 183/499] libzigc: pow --- lib/c/math.zig | 5 + lib/libc/musl/src/math/pow.c | 343 ----------------------------------- src/libs/musl.zig | 1 - src/libs/wasi_libc.zig | 1 - 4 files changed, 5 insertions(+), 345 deletions(-) delete mode 100644 lib/libc/musl/src/math/pow.c diff --git a/lib/c/math.zig b/lib/c/math.zig index 3811142769073deb3c2b9b1deba3c3f583b25a1f..8fc7c0322878b7b6862392abe3158a05b7adda11 100644 --- a/lib/c/math.zig +++ b/lib/c/math.zig @@ -41,6 +41,7 @@ comptime { @export(&atanl, .{ .name = "atanl", .linkage = common.linkage, .visibility = common.visibility }); @export(&cbrt, .{ .name = "cbrt", .linkage = common.linkage, .visibility = common.visibility }); @export(&cbrtf, .{ .name = "cbrtf", .linkage = common.linkage, .visibility = common.visibility }); + @export(&pow, .{ .name = "pow", .linkage = common.linkage, .visibility = common.visibility }); } if (builtin.target.isMuslLibC()) { @@ -116,3 +117,7 @@ fn cbrt(x: f64) callconv(.c) f64 { fn cbrtf(x: f32) callconv(.c) f32 { return math.cbrt(x); } + +fn pow(x: f64, y: f64) callconv(.c) f64 { + return math.pow(f64, x, y); +} diff --git a/lib/libc/musl/src/math/pow.c b/lib/libc/musl/src/math/pow.c deleted file mode 100644 index 694c2ef64d008cf78696a6d660353e83792d22f3..0000000000000000000000000000000000000000 --- a/lib/libc/musl/src/math/pow.c +++ /dev/null @@ -1,343 +0,0 @@ -/* - * Double-precision x^y function. - * - * Copyright (c) 2018, Arm Limited. - * SPDX-License-Identifier: MIT - */ - -#include -#include -#include "libm.h" -#include "exp_data.h" -#include "pow_data.h" - -/* -Worst-case error: 0.54 ULP (~= ulperr_exp + 1024*Ln2*relerr_log*2^53) -relerr_log: 1.3 * 2^-68 (Relative error of log, 1.5 * 2^-68 without fma) -ulperr_exp: 0.509 ULP (ULP error of exp, 0.511 ULP without fma) -*/ - -#define T __pow_log_data.tab -#define A __pow_log_data.poly -#define Ln2hi __pow_log_data.ln2hi -#define Ln2lo __pow_log_data.ln2lo -#define N (1 << POW_LOG_TABLE_BITS) -#define OFF 0x3fe6955500000000 - -/* Top 12 bits of a double (sign and exponent bits). */ -static inline uint32_t top12(double x) -{ - return asuint64(x) >> 52; -} - -/* Compute y+TAIL = log(x) where the rounded result is y and TAIL has about - additional 15 bits precision. IX is the bit representation of x, but - normalized in the subnormal range using the sign bit for the exponent. */ -static inline double_t log_inline(uint64_t ix, double_t *tail) -{ - /* double_t for better performance on targets with FLT_EVAL_METHOD==2. */ - double_t z, r, y, invc, logc, logctail, kd, hi, t1, t2, lo, lo1, lo2, p; - uint64_t iz, tmp; - int k, i; - - /* x = 2^k z; where z is in range [OFF,2*OFF) and exact. - The range is split into N subintervals. - The ith subinterval contains z and c is near its center. */ - tmp = ix - OFF; - i = (tmp >> (52 - POW_LOG_TABLE_BITS)) % N; - k = (int64_t)tmp >> 52; /* arithmetic shift */ - iz = ix - (tmp & 0xfffULL << 52); - z = asdouble(iz); - kd = (double_t)k; - - /* log(x) = k*Ln2 + log(c) + log1p(z/c-1). */ - invc = T[i].invc; - logc = T[i].logc; - logctail = T[i].logctail; - - /* Note: 1/c is j/N or j/N/2 where j is an integer in [N,2N) and - |z/c - 1| < 1/N, so r = z/c - 1 is exactly representible. */ -#if __FP_FAST_FMA - r = __builtin_fma(z, invc, -1.0); -#else - /* Split z such that rhi, rlo and rhi*rhi are exact and |rlo| <= |r|. */ - double_t zhi = asdouble((iz + (1ULL << 31)) & (-1ULL << 32)); - double_t zlo = z - zhi; - double_t rhi = zhi * invc - 1.0; - double_t rlo = zlo * invc; - r = rhi + rlo; -#endif - - /* k*Ln2 + log(c) + r. */ - t1 = kd * Ln2hi + logc; - t2 = t1 + r; - lo1 = kd * Ln2lo + logctail; - lo2 = t1 - t2 + r; - - /* Evaluation is optimized assuming superscalar pipelined execution. */ - double_t ar, ar2, ar3, lo3, lo4; - ar = A[0] * r; /* A[0] = -0.5. */ - ar2 = r * ar; - ar3 = r * ar2; - /* k*Ln2 + log(c) + r + A[0]*r*r. */ -#if __FP_FAST_FMA - hi = t2 + ar2; - lo3 = __builtin_fma(ar, r, -ar2); - lo4 = t2 - hi + ar2; -#else - double_t arhi = A[0] * rhi; - double_t arhi2 = rhi * arhi; - hi = t2 + arhi2; - lo3 = rlo * (ar + arhi); - lo4 = t2 - hi + arhi2; -#endif - /* p = log1p(r) - r - A[0]*r*r. */ - p = (ar3 * (A[1] + r * A[2] + - ar2 * (A[3] + r * A[4] + ar2 * (A[5] + r * A[6])))); - lo = lo1 + lo2 + lo3 + lo4 + p; - y = hi + lo; - *tail = hi - y + lo; - return y; -} - -#undef N -#undef T -#define N (1 << EXP_TABLE_BITS) -#define InvLn2N __exp_data.invln2N -#define NegLn2hiN __exp_data.negln2hiN -#define NegLn2loN __exp_data.negln2loN -#define Shift __exp_data.shift -#define T __exp_data.tab -#define C2 __exp_data.poly[5 - EXP_POLY_ORDER] -#define C3 __exp_data.poly[6 - EXP_POLY_ORDER] -#define C4 __exp_data.poly[7 - EXP_POLY_ORDER] -#define C5 __exp_data.poly[8 - EXP_POLY_ORDER] -#define C6 __exp_data.poly[9 - EXP_POLY_ORDER] - -/* Handle cases that may overflow or underflow when computing the result that - is scale*(1+TMP) without intermediate rounding. The bit representation of - scale is in SBITS, however it has a computed exponent that may have - overflown into the sign bit so that needs to be adjusted before using it as - a double. (int32_t)KI is the k used in the argument reduction and exponent - adjustment of scale, positive k here means the result may overflow and - negative k means the result may underflow. */ -static inline double specialcase(double_t tmp, uint64_t sbits, uint64_t ki) -{ - double_t scale, y; - - if ((ki & 0x80000000) == 0) { - /* k > 0, the exponent of scale might have overflowed by <= 460. */ - sbits -= 1009ull << 52; - scale = asdouble(sbits); - y = 0x1p1009 * (scale + scale * tmp); - return eval_as_double(y); - } - /* k < 0, need special care in the subnormal range. */ - sbits += 1022ull << 52; - /* Note: sbits is signed scale. */ - scale = asdouble(sbits); - y = scale + scale * tmp; - if (fabs(y) < 1.0) { - /* Round y to the right precision before scaling it into the subnormal - range to avoid double rounding that can cause 0.5+E/2 ulp error where - E is the worst-case ulp error outside the subnormal range. So this - is only useful if the goal is better than 1 ulp worst-case error. */ - double_t hi, lo, one = 1.0; - if (y < 0.0) - one = -1.0; - lo = scale - y + scale * tmp; - hi = one + y; - lo = one - hi + y + lo; - y = eval_as_double(hi + lo) - one; - /* Fix the sign of 0. */ - if (y == 0.0) - y = asdouble(sbits & 0x8000000000000000); - /* The underflow exception needs to be signaled explicitly. */ - fp_force_eval(fp_barrier(0x1p-1022) * 0x1p-1022); - } - y = 0x1p-1022 * y; - return eval_as_double(y); -} - -#define SIGN_BIAS (0x800 << EXP_TABLE_BITS) - -/* Computes sign*exp(x+xtail) where |xtail| < 2^-8/N and |xtail| <= |x|. - The sign_bias argument is SIGN_BIAS or 0 and sets the sign to -1 or 1. */ -static inline double exp_inline(double_t x, double_t xtail, uint32_t sign_bias) -{ - uint32_t abstop; - uint64_t ki, idx, top, sbits; - /* double_t for better performance on targets with FLT_EVAL_METHOD==2. */ - double_t kd, z, r, r2, scale, tail, tmp; - - abstop = top12(x) & 0x7ff; - if (predict_false(abstop - top12(0x1p-54) >= - top12(512.0) - top12(0x1p-54))) { - if (abstop - top12(0x1p-54) >= 0x80000000) { - /* Avoid spurious underflow for tiny x. */ - /* Note: 0 is common input. */ - double_t one = WANT_ROUNDING ? 1.0 + x : 1.0; - return sign_bias ? -one : one; - } - if (abstop >= top12(1024.0)) { - /* Note: inf and nan are already handled. */ - if (asuint64(x) >> 63) - return __math_uflow(sign_bias); - else - return __math_oflow(sign_bias); - } - /* Large x is special cased below. */ - abstop = 0; - } - - /* exp(x) = 2^(k/N) * exp(r), with exp(r) in [2^(-1/2N),2^(1/2N)]. */ - /* x = ln2/N*k + r, with int k and r in [-ln2/2N, ln2/2N]. */ - z = InvLn2N * x; -#if TOINT_INTRINSICS - kd = roundtoint(z); - ki = converttoint(z); -#elif EXP_USE_TOINT_NARROW - /* z - kd is in [-0.5-2^-16, 0.5] in all rounding modes. */ - kd = eval_as_double(z + Shift); - ki = asuint64(kd) >> 16; - kd = (double_t)(int32_t)ki; -#else - /* z - kd is in [-1, 1] in non-nearest rounding modes. */ - kd = eval_as_double(z + Shift); - ki = asuint64(kd); - kd -= Shift; -#endif - r = x + kd * NegLn2hiN + kd * NegLn2loN; - /* The code assumes 2^-200 < |xtail| < 2^-8/N. */ - r += xtail; - /* 2^(k/N) ~= scale * (1 + tail). */ - idx = 2 * (ki % N); - top = (ki + sign_bias) << (52 - EXP_TABLE_BITS); - tail = asdouble(T[idx]); - /* This is only a valid scale when -1023*N < k < 1024*N. */ - sbits = T[idx + 1] + top; - /* exp(x) = 2^(k/N) * exp(r) ~= scale + scale * (tail + exp(r) - 1). */ - /* Evaluation is optimized assuming superscalar pipelined execution. */ - r2 = r * r; - /* Without fma the worst case error is 0.25/N ulp larger. */ - /* Worst case error is less than 0.5+1.11/N+(abs poly error * 2^53) ulp. */ - tmp = tail + r + r2 * (C2 + r * C3) + r2 * r2 * (C4 + r * C5); - if (predict_false(abstop == 0)) - return specialcase(tmp, sbits, ki); - scale = asdouble(sbits); - /* Note: tmp == 0 or |tmp| > 2^-200 and scale > 2^-739, so there - is no spurious underflow here even without fma. */ - return eval_as_double(scale + scale * tmp); -} - -/* Returns 0 if not int, 1 if odd int, 2 if even int. The argument is - the bit representation of a non-zero finite floating-point value. */ -static inline int checkint(uint64_t iy) -{ - int e = iy >> 52 & 0x7ff; - if (e < 0x3ff) - return 0; - if (e > 0x3ff + 52) - return 2; - if (iy & ((1ULL << (0x3ff + 52 - e)) - 1)) - return 0; - if (iy & (1ULL << (0x3ff + 52 - e))) - return 1; - return 2; -} - -/* Returns 1 if input is the bit representation of 0, infinity or nan. */ -static inline int zeroinfnan(uint64_t i) -{ - return 2 * i - 1 >= 2 * asuint64(INFINITY) - 1; -} - -double pow(double x, double y) -{ - uint32_t sign_bias = 0; - uint64_t ix, iy; - uint32_t topx, topy; - - ix = asuint64(x); - iy = asuint64(y); - topx = top12(x); - topy = top12(y); - if (predict_false(topx - 0x001 >= 0x7ff - 0x001 || - (topy & 0x7ff) - 0x3be >= 0x43e - 0x3be)) { - /* Note: if |y| > 1075 * ln2 * 2^53 ~= 0x1.749p62 then pow(x,y) = inf/0 - and if |y| < 2^-54 / 1075 ~= 0x1.e7b6p-65 then pow(x,y) = +-1. */ - /* Special cases: (x < 0x1p-126 or inf or nan) or - (|y| < 0x1p-65 or |y| >= 0x1p63 or nan). */ - if (predict_false(zeroinfnan(iy))) { - if (2 * iy == 0) - return issignaling_inline(x) ? x + y : 1.0; - if (ix == asuint64(1.0)) - return issignaling_inline(y) ? x + y : 1.0; - if (2 * ix > 2 * asuint64(INFINITY) || - 2 * iy > 2 * asuint64(INFINITY)) - return x + y; - if (2 * ix == 2 * asuint64(1.0)) - return 1.0; - if ((2 * ix < 2 * asuint64(1.0)) == !(iy >> 63)) - return 0.0; /* |x|<1 && y==inf or |x|>1 && y==-inf. */ - return y * y; - } - if (predict_false(zeroinfnan(ix))) { - double_t x2 = x * x; - if (ix >> 63 && checkint(iy) == 1) - x2 = -x2; - /* Without the barrier some versions of clang hoist the 1/x2 and - thus division by zero exception can be signaled spuriously. */ - return iy >> 63 ? fp_barrier(1 / x2) : x2; - } - /* Here x and y are non-zero finite. */ - if (ix >> 63) { - /* Finite x < 0. */ - int yint = checkint(iy); - if (yint == 0) - return __math_invalid(x); - if (yint == 1) - sign_bias = SIGN_BIAS; - ix &= 0x7fffffffffffffff; - topx &= 0x7ff; - } - if ((topy & 0x7ff) - 0x3be >= 0x43e - 0x3be) { - /* Note: sign_bias == 0 here because y is not odd. */ - if (ix == asuint64(1.0)) - return 1.0; - if ((topy & 0x7ff) < 0x3be) { - /* |y| < 2^-65, x^y ~= 1 + y*log(x). */ - if (WANT_ROUNDING) - return ix > asuint64(1.0) ? 1.0 + y : - 1.0 - y; - else - return 1.0; - } - return (ix > asuint64(1.0)) == (topy < 0x800) ? - __math_oflow(0) : - __math_uflow(0); - } - if (topx == 0) { - /* Normalize subnormal x so exponent becomes negative. */ - ix = asuint64(x * 0x1p52); - ix &= 0x7fffffffffffffff; - ix -= 52ULL << 52; - } - } - - double_t lo; - double_t hi = log_inline(ix, &lo); - double_t ehi, elo; -#if __FP_FAST_FMA - ehi = y * hi; - elo = y * lo + __builtin_fma(y, hi, -ehi); -#else - double_t yhi = asdouble(iy & -1ULL << 27); - double_t ylo = y - yhi; - double_t lhi = asdouble(asuint64(hi) & -1ULL << 27); - double_t llo = hi - lhi + lo; - ehi = yhi * lhi; - elo = ylo * lhi + y * llo; /* |elo| < |ehi| * 2^-25. */ -#endif - return exp_inline(ehi, elo, sign_bias); -} diff --git a/src/libs/musl.zig b/src/libs/musl.zig index 553be9b070a7240992df02c5dfff9b2514870e51..1974e0e4ea3765ca3de2c8f7adaedc817280dc0d 100644 --- a/src/libs/musl.zig +++ b/src/libs/musl.zig @@ -985,7 +985,6 @@ const src_files = [_][]const u8{ "musl/src/math/nexttowardf.c", "musl/src/math/nexttowardl.c", "musl/src/math/__polevll.c", - "musl/src/math/pow.c", "musl/src/math/pow_data.c", "musl/src/math/powerpc64/fma.c", "musl/src/math/powerpc64/fmaf.c", diff --git a/src/libs/wasi_libc.zig b/src/libs/wasi_libc.zig index 5cae320e9c905dfa7bb569f0e5904b5b62f0a40c..8e0ade2a0d0f678c6a028c324f46611a644ccf92 100644 --- a/src/libs/wasi_libc.zig +++ b/src/libs/wasi_libc.zig @@ -794,7 +794,6 @@ const libc_top_half_src_files = [_][]const u8{ "musl/src/math/nexttowardf.c", "musl/src/math/nexttowardl.c", "musl/src/math/__polevll.c", - "musl/src/math/pow.c", "musl/src/math/pow_data.c", "musl/src/math/powf.c", "musl/src/math/powf_data.c", -- 2.54.0 From 2fce12b42a3662e018ad30ca874f4e7cfcdf33d2 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Mon, 2 Feb 2026 11:24:42 +0100 Subject: [PATCH 184/499] test: improve logic for generating stack trace test combinations --- test/src/StackTrace.zig | 50 +++++++++++++++++++++++++++++++++++------ 1 file changed, 43 insertions(+), 7 deletions(-) diff --git a/test/src/StackTrace.zig b/test/src/StackTrace.zig index 4e5c946682681ad47f458e67de7b7fd7c8a3dc24..ff0b145ff88e1591744b0d6de1fb75e945127d44 100644 --- a/test/src/StackTrace.zig +++ b/test/src/StackTrace.zig @@ -55,7 +55,7 @@ fn addCaseTarget( }; }; const both_pie = switch (target.result.os.tag) { - .fuchsia, .openbsd => false, + .fuchsia => false, else => true, }; const both_libc = switch (target.result.os.tag) { @@ -63,8 +63,30 @@ fn addCaseTarget( else => !target.result.requiresLibC(), }; - // On aarch64-macos, FP unwinding is blessed by Apple to always be reliable, and std.debug knows this. - const fp_unwind_is_safe = target.result.cpu.arch == .aarch64 and target.result.os.tag.isDarwin(); + // See `std.debug.StackIterator.fp_usability` logic. + const fp_usability: enum { useless, unsafe, safe, ideal } = switch (target.result.cpu.arch) { + .alpha, + .csky, + .microblaze, + .microblazeel, + .mips, + .mipsel, + .mips64, + .mips64el, + .sh, + .sheb, + => .useless, + .hexagon, + .powerpc, + .powerpcle, + .powerpc64, + .powerpc64le, + .sparc, + .sparc64, + => .ideal, + .aarch64 => if (target.result.os.tag.isDarwin()) .safe else .unsafe, + else => .unsafe, + }; const supports_unwind_tables = switch (target.result.os.tag) { // x86-windows just has no way to do stack unwinding other then using frame pointers. .windows => target.result.cpu.arch != .x86, @@ -86,10 +108,24 @@ fn addCaseTarget( const only_fp: @This() = .{ .tables = false, .fp = true }; }; const unwind_info_vals: []const UnwindInfo = switch (config.unwind) { - .none => &.{.none}, - .any => &.{ .only_tables, .only_fp, .both }, - .safe => if (fp_unwind_is_safe) &.{ .only_tables, .only_fp, .both } else &.{ .only_tables, .both }, - .no_safe => if (fp_unwind_is_safe) &.{.none} else &.{ .none, .only_fp }, + .none => switch (fp_usability) { + .useless => &.{ .none, .only_fp }, + .unsafe, .safe => &.{.none}, + .ideal => &.{}, + }, + .any => switch (fp_usability) { + .useless => &.{ .only_tables, .both }, + .unsafe, .safe, .ideal => &.{ .only_tables, .only_fp, .both }, + }, + .safe => switch (fp_usability) { + .useless, .unsafe => &.{ .only_tables, .both }, + .safe, .ideal => &.{ .only_tables, .only_fp, .both }, + }, + .no_safe => switch (fp_usability) { + .useless, .unsafe => &.{ .none, .only_fp }, + .safe => &.{.none}, + .ideal => &.{}, + }, }; for (use_llvm_vals) |use_llvm| { -- 2.54.0 From e9b442db5a2b03c09f8286cab94d9ce4976bc077 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Sun, 1 Feb 2026 06:35:13 +0100 Subject: [PATCH 185/499] llvm: fix C ABI integer promotion for more targets Also stop pretending that this function handles _BitInt types correctly. Handling 32-bit integers on MIPS properly is blocked on: https://github.com/llvm/llvm-project/issues/179088 --- src/codegen/llvm.zig | 82 ++++++++++++++++++++++++++++++-------------- 1 file changed, 56 insertions(+), 26 deletions(-) diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index 26369d2ce98193f191a1d27fd96d58a569cc4fd3..a178604a6874b68dc30e69f00b7d50bc0e0bfa86 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -12600,46 +12600,76 @@ fn iterateParamTypes(object: *Object, pt: Zcu.PerThread, fn_info: InternPool.Key }; } +/// This function deliberately does not handle `_BitInt` because it typically +/// has different ABI than regular integer types, and there is no currently no +/// way to determine whether a Zig integer type is meant to represent e.g. `int` +/// or `_BitInt(32)`. fn ccAbiPromoteInt(cc: std.builtin.CallingConvention, zcu: *Zcu, ty: Type) ?std.builtin.Signedness { - const target = zcu.getTarget(); switch (cc) { .auto, .@"inline", .async => return null, else => {}, } + const int_info = switch (ty.zigTypeTag(zcu)) { .bool => Type.u1.intInfo(zcu), else => if (ty.isAbiInt(zcu)) ty.intInfo(zcu) else return null, }; - return switch (target.os.tag) { - .driverkit, .ios, .maccatalyst, .macos, .watchos, .tvos, .visionos => switch (int_info.bits) { - 0...16 => int_info.signedness, - else => null, - }, - else => switch (target.cpu.arch) { - .loongarch64, .riscv64, .riscv64be => switch (int_info.bits) { - 0...16 => int_info.signedness, - 32 => .signed, // LLVM always signextends 32 bit ints, unsure if bug. - 17...31, 33...63 => int_info.signedness, - else => null, - }, + assert(int_info.bits >= 0); - .sparc64, - .powerpc64, - .powerpc64le, - .s390x, - => switch (int_info.bits) { - 0...63 => int_info.signedness, + const target = zcu.getTarget(); + return switch (target.cpu.arch) { + .aarch64, + .aarch64_be, + => switch (target.os.tag) { + .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => switch (int_info.bits) { + 8, 16 => int_info.signedness, else => null, }, + else => null, + }, - .aarch64, - .aarch64_be, - => null, + .avr, + => switch (int_info.bits) { + 8 => int_info.signedness, + else => null, + }, - else => switch (int_info.bits) { - 0...16 => int_info.signedness, - else => null, - }, + .lanai, + => null, + + .loongarch64, + .riscv64, + .riscv64be, + => switch (int_info.bits) { + 8, 16 => int_info.signedness, + 32 => .signed, + else => null, + }, + + .mips, + .mipsel, + .mips64, + .mips64el, + => switch (int_info.bits) { + 8, 16, 64 => int_info.signedness, + // https://github.com/llvm/llvm-project/issues/179088 + // 32 => .signed, + else => null, + }, + + .powerpc64, + .powerpc64le, + .s390x, + .sparc64, + .ve, + => switch (int_info.bits) { + 8, 16, 32 => int_info.signedness, + else => null, + }, + + else => switch (int_info.bits) { + 8, 16 => int_info.signedness, + else => null, }, }; } -- 2.54.0 From 184c8f9545944333bf122203c8d813785b43df8a Mon Sep 17 00:00:00 2001 From: rpkak Date: Sun, 1 Feb 2026 11:21:16 +0100 Subject: [PATCH 186/499] std.heap.PageAllocator: align hint --- lib/std/heap/PageAllocator.zig | 16 +++++++++------- 1 file changed, 9 insertions(+), 7 deletions(-) diff --git a/lib/std/heap/PageAllocator.zig b/lib/std/heap/PageAllocator.zig index d62432905bd7a14fe82a0053cddf128f43e13618..4ca3ec944fc5f47ebb61dba0360afdf21c0b76ce 100644 --- a/lib/std/heap/PageAllocator.zig +++ b/lib/std/heap/PageAllocator.zig @@ -87,12 +87,14 @@ pub fn map(n: usize, alignment: mem.Alignment) ?[*]u8 { } const aligned_len = mem.alignForward(usize, n, page_size); - const max_drop_len = alignment_bytes - @min(alignment_bytes, page_size); - const overalloc_len = if (max_drop_len <= aligned_len - n) - aligned_len - else - mem.alignForward(usize, aligned_len + max_drop_len, page_size); - const hint = @atomicLoad(@TypeOf(std.heap.next_mmap_addr_hint), &std.heap.next_mmap_addr_hint, .unordered); + const max_drop_len = alignment_bytes -| page_size; + const overalloc_len = aligned_len + max_drop_len; + const maybe_unaligned_hint = @atomicLoad(@TypeOf(std.heap.next_mmap_addr_hint), &std.heap.next_mmap_addr_hint, .unordered); + + // Aligning hint does not use mem.alignPointer, because it is slow. + // Aligning hint does not use mem.alignForward, because it asserts that there will be no overflow. + const hint: ?[*]align(page_size_min) u8 = @ptrFromInt(((@intFromPtr(maybe_unaligned_hint)) +% (alignment_bytes - 1)) & ~(alignment_bytes - 1)); + const slice = posix.mmap( hint, overalloc_len, @@ -110,7 +112,7 @@ pub fn map(n: usize, alignment: mem.Alignment) ?[*]u8 { const remaining_len = overalloc_len - drop_len; if (remaining_len > aligned_len) posix.munmap(@alignCast(result_ptr[aligned_len..remaining_len])); const new_hint: [*]align(page_size_min) u8 = @alignCast(result_ptr + aligned_len); - _ = @cmpxchgStrong(@TypeOf(std.heap.next_mmap_addr_hint), &std.heap.next_mmap_addr_hint, hint, new_hint, .monotonic, .monotonic); + _ = @cmpxchgStrong(@TypeOf(std.heap.next_mmap_addr_hint), &std.heap.next_mmap_addr_hint, maybe_unaligned_hint, new_hint, .monotonic, .monotonic); return result_ptr; } -- 2.54.0 From 1ab5a58474d69ba8b9cc63a7ce3eb9b5338c5d2d Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 3 Feb 2026 12:00:14 -0800 Subject: [PATCH 187/499] std.Io.Threaded: handle errors from clockResolution --- lib/std/Io/Threaded.zig | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 6255d96978405cc0d1b1fc4908644facea0ef4b0..621dfb28d8c4be0c8538c2f4b418900eda41fde9 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -10858,14 +10858,15 @@ fn clockResolution(userdata: ?*anyopaque, clock: Io.Clock) Io.Clock.ResolutionEr const result = scale >> 32; return .fromNanoseconds(result); }, - .cpu_process, .cpu_thread => return .zero, + .cpu_process, .cpu_thread => return error.ClockUnavailable, }, .wasi => { if (builtin.link_libc) return clockResolutionPosix(clock); var ns: std.os.wasi.timestamp_t = undefined; return switch (std.os.wasi.clock_res_get(clockToWasi(clock), &ns)) { .SUCCESS => .fromNanoseconds(ns), - else => .zero, + .INVAL => return error.ClockUnavailable, + else => |err| return posix.unexpectedErrno(err), }; }, else => return clockResolutionPosix(clock), @@ -10877,7 +10878,8 @@ fn clockResolutionPosix(clock: Io.Clock) Io.Clock.ResolutionError!Io.Duration { var timespec: posix.timespec = undefined; return switch (posix.errno(posix.system.clock_getres(clock_id, ×pec))) { .SUCCESS => .fromNanoseconds(nanosecondsFromPosix(×pec)), - else => .zero, + .INVAL => return error.ClockUnavailable, + else => |err| return posix.unexpectedErrno(err), }; } -- 2.54.0 From 56a43fb86f5127171709033fb43e19bc3b8a1cdc Mon Sep 17 00:00:00 2001 From: Matthew Lugg Date: Tue, 3 Feb 2026 19:42:54 +0000 Subject: [PATCH 188/499] Revert "std.Io.Threaded: work around parking futex bug" This reverts commit 5312063138e787a09493e5f5affb5c8652b66dbc. --- lib/std/Io/Threaded.zig | 155 +++++++++++++++++----------------------- 1 file changed, 64 insertions(+), 91 deletions(-) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 6255d96978405cc0d1b1fc4908644facea0ef4b0..5464330bd0095ddcfb8d638ab82aec09fc3d48d2 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -29,8 +29,8 @@ const ws2_32 = std.os.windows.ws2_32; /// * scanning environment variables on some targets /// * memory-mapping when mmap or equivalent is not available allocator: Allocator, -mutex: Mutex = .init, -cond: Condition = .init, +mutex: Io.Mutex = .init, +cond: Io.Condition = .init, run_queue: std.SinglyLinkedList = .{}, join_requested: bool = false, stack_size: usize, @@ -1505,8 +1505,8 @@ var global_single_threaded_instance: Threaded = .init_single_threaded; pub const global_single_threaded: *Threaded = &global_single_threaded_instance; pub fn setAsyncLimit(t: *Threaded, new_limit: Io.Limit) void { - mutexLockInternal(&t.mutex); - defer mutexUnlockInternal(&t.mutex); + mutexLock(&t.mutex); + defer mutexUnlock(&t.mutex); t.async_limit = new_limit; } @@ -1527,8 +1527,8 @@ pub fn deinit(t: *Threaded) void { fn join(t: *Threaded) void { if (builtin.single_threaded) return; { - mutexLockInternal(&t.mutex); - defer mutexUnlockInternal(&t.mutex); + mutexLock(&t.mutex); + defer mutexUnlock(&t.mutex); t.join_requested = true; } condBroadcast(&t.cond); @@ -1593,16 +1593,16 @@ fn worker(t: *Threaded) void { defer t.wait_group.finish(); - mutexLockInternal(&t.mutex); - defer mutexUnlockInternal(&t.mutex); + mutexLock(&t.mutex); + defer mutexUnlock(&t.mutex); while (true) { while (t.run_queue.popFirst()) |runnable_node| { - mutexUnlockInternal(&t.mutex); + mutexUnlock(&t.mutex); thread.cancel_protection = .unblocked; const runnable: *Runnable = @fieldParentPtr("node", runnable_node); runnable.startFn(runnable, &thread, t); - mutexLockInternal(&t.mutex); + mutexLock(&t.mutex); t.busy_count -= 1; } if (t.join_requested) break; @@ -2025,12 +2025,12 @@ fn async( }, }; - mutexLockInternal(&t.mutex); + mutexLock(&t.mutex); const busy_count = t.busy_count; if (busy_count >= @intFromEnum(t.async_limit)) { - mutexUnlockInternal(&t.mutex); + mutexUnlock(&t.mutex); future.destroy(gpa); start(context.ptr, result.ptr); return null; @@ -2044,7 +2044,7 @@ fn async( const thread = std.Thread.spawn(.{ .stack_size = t.stack_size }, worker, .{t}) catch { t.wait_group.finish(); t.busy_count = busy_count; - mutexUnlockInternal(&t.mutex); + mutexUnlock(&t.mutex); future.destroy(gpa); start(context.ptr, result.ptr); return null; @@ -2054,7 +2054,7 @@ fn async( t.run_queue.prepend(&future.runnable.node); - mutexUnlockInternal(&t.mutex); + mutexUnlock(&t.mutex); condSignal(&t.cond); return @ptrCast(future); } @@ -2077,8 +2077,8 @@ fn concurrent( }; errdefer future.destroy(gpa); - mutexLockInternal(&t.mutex); - defer mutexUnlockInternal(&t.mutex); + mutexLock(&t.mutex); + defer mutexUnlock(&t.mutex); const busy_count = t.busy_count; @@ -2122,12 +2122,12 @@ fn groupAsync( error.OutOfMemory => return groupAsyncEager(start, context.ptr), }; - mutexLockInternal(&t.mutex); + mutexLock(&t.mutex); const busy_count = t.busy_count; if (busy_count >= @intFromEnum(t.async_limit)) { - mutexUnlockInternal(&t.mutex); + mutexUnlock(&t.mutex); task.destroy(gpa); return groupAsyncEager(start, context.ptr); } @@ -2140,7 +2140,7 @@ fn groupAsync( const thread = std.Thread.spawn(.{ .stack_size = t.stack_size }, worker, .{t}) catch { t.wait_group.finish(); t.busy_count = busy_count; - mutexUnlockInternal(&t.mutex); + mutexUnlock(&t.mutex); task.destroy(gpa); return groupAsyncEager(start, context.ptr); }; @@ -2157,7 +2157,7 @@ fn groupAsync( }, .monotonic); t.run_queue.prepend(&task.runnable.node); - mutexUnlockInternal(&t.mutex); + mutexUnlock(&t.mutex); condSignal(&t.cond); } fn groupAsyncEager( @@ -2222,8 +2222,8 @@ fn groupConcurrent( }; errdefer task.destroy(gpa); - mutexLockInternal(&t.mutex); - defer mutexUnlockInternal(&t.mutex); + mutexLock(&t.mutex); + defer mutexUnlock(&t.mutex); const busy_count = t.busy_count; @@ -3847,8 +3847,8 @@ fn fileStatWindows(userdata: ?*anyopaque, file: File) File.StatError!File.Stat { fn systemBasicInformation(t: *Threaded) ?*const windows.SYSTEM_BASIC_INFORMATION { if (!t.system_basic_information.initialized.load(.acquire)) { - mutexLockInternal(&t.mutex); - defer mutexUnlockInternal(&t.mutex); + mutexLock(&t.mutex); + defer mutexUnlock(&t.mutex); switch (windows.ntdll.NtQuerySystemInformation( .SystemBasicInformation, @@ -14359,10 +14359,9 @@ const Wsa = struct { }; fn initializeWsa(t: *Threaded) error{ NetworkDown, Canceled }!void { - const t_io = io(t); const wsa = &t.wsa; - try wsa.mutex.lock(t_io); - defer wsa.mutex.unlock(t_io); + try mutexLock(&wsa.mutex); + defer mutexUnlock(&wsa.mutex); switch (wsa.status) { .uninitialized => { var wsa_data: ws2_32.WSADATA = undefined; @@ -14433,8 +14432,8 @@ const WindowsEnvironStrings = struct { }; fn scanEnviron(t: *Threaded) void { - mutexLockInternal(&t.mutex); - defer mutexUnlockInternal(&t.mutex); + mutexLock(&t.mutex); + defer mutexUnlock(&t.mutex); if (t.environ.initialized) return; t.environ.initialized = true; @@ -14789,8 +14788,8 @@ fn spawnPosix(t: *Threaded, options: process.SpawnOptions) process.SpawnError!Sp fn getDevNullFd(t: *Threaded) !posix.fd_t { { - mutexLockInternal(&t.mutex); - defer mutexUnlockInternal(&t.mutex); + mutexLock(&t.mutex); + defer mutexUnlock(&t.mutex); if (t.null_file.fd != -1) return t.null_file.fd; } const mode: u32 = 0; @@ -14801,8 +14800,8 @@ fn getDevNullFd(t: *Threaded) !posix.fd_t { .SUCCESS => { syscall.finish(); const fresh_fd: posix.fd_t = @intCast(rc); - mutexLockInternal(&t.mutex); // Another thread might have won the race. - defer mutexUnlockInternal(&t.mutex); + mutexLock(&t.mutex); // Another thread might have won the race. + defer mutexUnlock(&t.mutex); if (t.null_file.fd != -1) { posix.close(fresh_fd); return t.null_file.fd; @@ -15462,8 +15461,8 @@ fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) pro fn getCngHandle(t: *Threaded) Io.RandomSecureError!windows.HANDLE { { - mutexLockInternal(&t.mutex); - defer mutexUnlockInternal(&t.mutex); + mutexLock(&t.mutex); + defer mutexUnlock(&t.mutex); if (t.random_file.handle) |handle| return handle; } @@ -15497,8 +15496,8 @@ fn getCngHandle(t: *Threaded) Io.RandomSecureError!windows.HANDLE { )) { .SUCCESS => { syscall.finish(); - mutexLockInternal(&t.mutex); // Another thread might have won the race. - defer mutexUnlockInternal(&t.mutex); + mutexLock(&t.mutex); // Another thread might have won the race. + defer mutexUnlock(&t.mutex); if (t.random_file.handle) |prev_handle| { windows.CloseHandle(fresh_handle); return prev_handle; @@ -15518,8 +15517,8 @@ fn getCngHandle(t: *Threaded) Io.RandomSecureError!windows.HANDLE { fn getNulHandle(t: *Threaded) !windows.HANDLE { { - mutexLockInternal(&t.mutex); - defer mutexUnlockInternal(&t.mutex); + mutexLock(&t.mutex); + defer mutexUnlock(&t.mutex); if (t.null_file.handle) |handle| return handle; } @@ -15565,8 +15564,8 @@ fn getNulHandle(t: *Threaded) !windows.HANDLE { )) { .SUCCESS => { syscall.finish(); - mutexLockInternal(&t.mutex); // Another thread might have won the race. - defer mutexUnlockInternal(&t.mutex); + mutexLock(&t.mutex); // Another thread might have won the race. + defer mutexUnlock(&t.mutex); if (t.null_file.handle) |prev_handle| { windows.CloseHandle(fresh_handle); return prev_handle; @@ -16611,15 +16610,15 @@ fn random(userdata: ?*anyopaque, buffer: []u8) void { } fn randomMainThread(t: *Threaded, buffer: []u8) void { - mutexLockInternal(&t.mutex); - defer mutexUnlockInternal(&t.mutex); + mutexLock(&t.mutex); + defer mutexUnlock(&t.mutex); if (!t.csprng.isInitialized()) { @branchHint(.unlikely); var seed: [Csprng.seed_len]u8 = undefined; { - mutexUnlockInternal(&t.mutex); - defer mutexLockInternal(&t.mutex); + mutexUnlock(&t.mutex); + defer mutexLock(&t.mutex); const prev = swapCancelProtection(t, .blocked); defer _ = swapCancelProtection(t, prev); @@ -16804,8 +16803,8 @@ fn randomSecure(userdata: ?*anyopaque, buffer: []u8) Io.RandomSecureError!void { fn getRandomFd(t: *Threaded) Io.RandomSecureError!posix.fd_t { { - mutexLockInternal(&t.mutex); - defer mutexUnlockInternal(&t.mutex); + mutexLock(&t.mutex); + defer mutexUnlock(&t.mutex); if (t.random_file.fd == -2) return error.EntropyUnavailable; if (t.random_file.fd != -1) return t.random_file.fd; @@ -16845,8 +16844,8 @@ fn getRandomFd(t: *Threaded) Io.RandomSecureError!posix.fd_t { .SUCCESS => { syscall.finish(); if (!statx.mask.TYPE) return error.EntropyUnavailable; - mutexLockInternal(&t.mutex); // Another thread might have won the race. - defer mutexUnlockInternal(&t.mutex); + mutexLock(&t.mutex); // Another thread might have won the race. + defer mutexUnlock(&t.mutex); if (t.random_file.fd >= 0) { posix.close(fd); return t.random_file.fd; @@ -16873,8 +16872,8 @@ fn getRandomFd(t: *Threaded) Io.RandomSecureError!posix.fd_t { switch (posix.errno(fstat_sym(fd, &stat))) { .SUCCESS => { syscall.finish(); - mutexLockInternal(&t.mutex); // Another thread might have won the race. - defer mutexUnlockInternal(&t.mutex); + mutexLock(&t.mutex); // Another thread might have won the race. + defer mutexUnlock(&t.mutex); if (t.random_file.fd >= 0) { posix.close(fd); return t.random_file.fd; @@ -16938,7 +16937,7 @@ const parking_futex = struct { /// avoid a race. num_waiters: std.atomic.Value(u32), /// Protects `waiters`. - mutex: Mutex, + mutex: Io.Mutex, waiters: std.DoublyLinkedList, /// Prevent false sharing between buckets. @@ -17007,8 +17006,8 @@ const parking_futex = struct { var status_buf: std.atomic.Value(Thread.Status) = undefined; { - mutexLockInternal(&bucket.mutex); - defer mutexUnlockInternal(&bucket.mutex); + mutexLock(&bucket.mutex); + defer mutexUnlock(&bucket.mutex); _ = bucket.num_waiters.fetchAdd(1, .acquire); @@ -17077,8 +17076,8 @@ const parking_futex = struct { .parked => { // We saw a timeout and updated our own status from `.parked` to `.none`. It is // our responsibility to remove `waiter` from `bucket`. - mutexLockInternal(&bucket.mutex); - defer mutexUnlockInternal(&bucket.mutex); + mutexLock(&bucket.mutex); + defer mutexUnlock(&bucket.mutex); bucket.waiters.remove(&waiter.node); assert(bucket.num_waiters.fetchSub(1, .monotonic) > 0); }, @@ -17117,8 +17116,8 @@ const parking_futex = struct { // of the critical section. This forms a singly-linked list of waiters using `Waiter.node.next`. var waking_head: ?*std.DoublyLinkedList.Node = null; { - mutexLockInternal(&bucket.mutex); - defer mutexUnlockInternal(&bucket.mutex); + mutexLock(&bucket.mutex); + defer mutexUnlock(&bucket.mutex); var num_removed: u32 = 0; var it = bucket.waiters.first; @@ -17173,8 +17172,8 @@ const parking_futex = struct { fn removeCanceledWaiter(waiter: *Waiter) void { const bucket = bucketForAddress(waiter.address); - mutexLockInternal(&bucket.mutex); - defer mutexUnlockInternal(&bucket.mutex); + mutexLock(&bucket.mutex); + defer mutexUnlock(&bucket.mutex); bucket.waiters.remove(&waiter.node); assert(bucket.num_waiters.fetchSub(1, .monotonic) > 0); waiter.done.store(true, .release); // potentially invalidates `waiter.*` @@ -18160,14 +18159,8 @@ fn eventSet(event: *Io.Event) void { } } -const Condition = if (!is_windows) Io.Condition else struct { - condition: windows.CONDITION_VARIABLE, - const init: @This() = .{ .condition = .{} }; -}; - /// Same as `Io.Condition.broadcast` but avoids the VTable. -fn condBroadcast(cond: *Condition) void { - if (is_windows) return windows.ntdll.RtlWakeAllConditionVariable(&cond.condition); +fn condBroadcast(cond: *Io.Condition) void { var prev_state = cond.state.load(.monotonic); while (prev_state.waiters > prev_state.signals) { @branchHint(.unlikely); @@ -18187,8 +18180,7 @@ fn condBroadcast(cond: *Condition) void { } /// Same as `Io.Condition.signal` but avoids the VTable. -fn condSignal(cond: *Condition) void { - if (is_windows) return windows.ntdll.RtlWakeConditionVariable(&cond.condition); +fn condSignal(cond: *Io.Condition) void { var prev_state = cond.state.load(.monotonic); while (prev_state.waiters > prev_state.signals) { @branchHint(.unlikely); @@ -18208,11 +18200,7 @@ fn condSignal(cond: *Condition) void { } /// Same as `Io.Condition.waitUncancelable` but avoids the VTable. -fn condWait(cond: *Condition, mutex: *Mutex) void { - if (is_windows) { - _ = windows.kernel32.SleepConditionVariableSRW(&cond.condition, &mutex.srwlock, windows.INFINITE, 0); - return; - } +fn condWait(cond: *Io.Condition, mutex: *Io.Mutex) void { var epoch = cond.epoch.load(.acquire); // `.acquire` to ensure ordered before state load { @@ -18220,8 +18208,8 @@ fn condWait(cond: *Condition, mutex: *Mutex) void { assert(prev_state.waiters < std.math.maxInt(u16)); // overflow caused by too many waiters } - mutexUnlockInternal(mutex); - defer mutexLockInternal(mutex); + mutexUnlock(mutex); + defer mutexLock(mutex); while (true) { Thread.futexWaitUncancelable(&cond.epoch.raw, epoch, null); @@ -18241,16 +18229,6 @@ fn condWait(cond: *Condition, mutex: *Mutex) void { } } -const Mutex = if (!is_windows) Io.Mutex else struct { - srwlock: windows.SRWLOCK, - const init: @This() = .{ .srwlock = .{} }; -}; - -fn mutexLockInternal(m: *Mutex) void { - if (is_windows) return windows.ntdll.RtlAcquireSRWLockExclusive(&m.srwlock); - return mutexLock(m); -} - /// Same as `Io.Mutex.lockUncancelable` but avoids the VTable. pub fn mutexLock(m: *Io.Mutex) void { const initial_state = m.state.cmpxchgWeak( @@ -18270,11 +18248,6 @@ pub fn mutexLock(m: *Io.Mutex) void { } } -fn mutexUnlockInternal(m: *Mutex) void { - if (is_windows) return windows.ntdll.RtlReleaseSRWLockExclusive(&m.srwlock); - return mutexUnlock(m); -} - /// Same as `Io.Mutex.unlock` but avoids the VTable. pub fn mutexUnlock(m: *Io.Mutex) void { switch (m.state.swap(.unlocked, .release)) { -- 2.54.0 From 7c08f77efa6052750118881b506f7b59918993e0 Mon Sep 17 00:00:00 2001 From: Matthew Lugg Date: Tue, 3 Feb 2026 19:50:31 +0000 Subject: [PATCH 189/499] Revert "std.Io.Threaded: spurious unparks are possible" It turns out that at least on Windows, spurious unparks are *not* possible, and in fact triggering them breaks some RTL synchronization primitives. For instance, if you have a pending unpark going into a contended `RtlEnterCriticalSection` call, it will never unblock. In other words, the Windows API worked exactly how I thought it did, and it's only the NetBSD/Illumos one which is dumb. This is actually exactly why Windows 8 introduced the parking API despite alertable sleeps being a thing! This commit doesn't yet deal with making NetBSD work, nor does it even compile I imagine. The next commit will fix everything back up. This reverts commit c518593e9793a2aed4e0173348aff2cbef58b717. --- lib/std/Io/Threaded.zig | 244 ++++++++++++++++++++-------------------- 1 file changed, 123 insertions(+), 121 deletions(-) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 5464330bd0095ddcfb8d638ab82aec09fc3d48d2..558263a0102d755cc75e9dce78249428e8f8f3ec 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -4955,7 +4955,10 @@ pub fn dirOpenFileWtf16( // kernel bug with retry attempts. syscall.finish(); if (max_attempts - attempt == 0) return error.FileBusy; - try parking_sleep.windowsRetrySleep((@as(u32, 1) << attempt) >> 1); + try parking_sleep.sleep(.{ .duration = .{ + .raw = .fromMilliseconds((@as(u32, 1) << attempt) >> 1), + .clock = .awake, + } }); attempt += 1; syscall = try .start(); continue; @@ -4977,7 +4980,10 @@ pub fn dirOpenFileWtf16( // fixed by sleeping and retrying until the error goes away. syscall.finish(); if (max_attempts - attempt == 0) return error.FileBusy; - try parking_sleep.windowsRetrySleep((@as(u32, 1) << attempt) >> 1); + try parking_sleep.sleep(.{ .duration = .{ + .raw = .fromMilliseconds((@as(u32, 1) << attempt) >> 1), + .clock = .awake, + } }); attempt += 1; syscall = try .start(); continue; @@ -10823,9 +10829,6 @@ fn nowPosix(clock: Io.Clock) Io.Timestamp { fn now(userdata: ?*anyopaque, clock: Io.Clock) Io.Timestamp { const t: *Threaded = @ptrCast(@alignCast(userdata)); _ = t; - return nowInner(clock); -} -fn nowInner(clock: Io.Clock) Io.Timestamp { return switch (native_os) { .windows => nowWindows(clock), .wasi => nowWasi(clock), @@ -15582,7 +15585,10 @@ fn getNulHandle(t: *Threaded) !windows.HANDLE { // this other than retrying the creation after the OS finishes // the deletion. syscall.finish(); - try parking_sleep.windowsRetrySleep(1); + try parking_sleep.sleep(.{ .duration = .{ + .raw = .fromMilliseconds(1), + .clock = .awake, + } }); syscall = try .start(); continue; }, @@ -16955,13 +16961,9 @@ const parking_futex = struct { /// /// * Removing the `Waiter` from `Bucket.waiters` /// * Decrementing `Bucket.num_waiters` - /// * Atomically setting `done` (after this, the `Waiter` may go out of scope at any time, - /// so must not be referenced again) - /// * Unparking the thread (last, so that the unparked thread definitely sees `done`) + /// * Unparking the thread (*after* the above, so that the `Waiter` does not go out of scope + /// while it is still in the `Bucket`). thread_status: *std.atomic.Value(Thread.Status), - /// Initially `false`. Whoever updates `thread_status` to `.none`/`.canceling` will update - /// this to `true` once they are done with the `Waiter`, just before unparking `tid`. - done: std.atomic.Value(bool), }; fn bucketForAddress(address: usize) *Bucket { @@ -17000,7 +17002,6 @@ const parking_futex = struct { .address = @intFromPtr(ptr), .tid = self_tid, .thread_status = undefined, // populated in critical section - .done = .init(false), }; var status_buf: std.atomic.Value(Thread.Status) = undefined; @@ -17058,44 +17059,41 @@ const parking_futex = struct { bucket.waiters.append(&waiter.node); } - const deadline: ?Io.Clock.Timestamp = switch (timeout) { - .none => null, - .duration => |d| .{ - .raw = nowInner(d.clock).addDuration(d.raw), - .clock = d.clock, - }, - .deadline => |d| d, - }; - while (park(deadline, ptr)) { - if (waiter.done.load(.acquire)) return; // all done! + if (park(timeout, ptr)) { + // We were unparked by either `wake` or cancelation, so our current status is either + // `.none` or `.canceling`. In either case, they've already removed `waiter` from + // `bucket`, so we have nothing more to do! } else |err| switch (err) { - error.Timeout => switch (waiter.thread_status.fetchAnd( - .{ .cancelation = @enumFromInt(0b110), .awaitable = .all_ones }, - .monotonic, - ).cancelation) { - .parked => { - // We saw a timeout and updated our own status from `.parked` to `.none`. It is - // our responsibility to remove `waiter` from `bucket`. - mutexLock(&bucket.mutex); - defer mutexUnlock(&bucket.mutex); - bucket.waiters.remove(&waiter.node); - assert(bucket.num_waiters.fetchSub(1, .monotonic) > 0); - }, - .none, .canceling => { - // Race condition: the timeout was reached, then `wake` or a cancelation tried - // to update our status. They won the race, so wait for them to do the cleanup. - // They'll tell us by setting `waiter.done` and unparking us. - while (!waiter.done.load(.acquire)) { - park(null, ptr) catch |e| switch (e) { + error.Timeout => { + // We're not out of the woods yet: an unpark could race with the timeout. + const old_status = waiter.thread_status.fetchAnd( + .{ .cancelation = @enumFromInt(0b110), .awaitable = .all_ones }, + .monotonic, + ); + switch (old_status.cancelation) { + .parked => { + // No race. It is our responsibility to remove `waiter` from `bucket`. + // New status is `.none`. + bucket.mutex.lock(); + defer bucket.mutex.unlock(); + bucket.waiters.remove(&waiter.node); + assert(bucket.num_waiters.fetchSub(1, .monotonic) > 0); + }, + .none, .canceling => { + // Race condition: the timeout was reached, then `wake` or a canceler tried + // to unpark us. Whoever did that will remove us from `bucket`. Wait for + // that (and drop the unpark request in doing so). + // New status is `.none` or `.canceling` respectively. + park(.none, ptr) catch |e| switch (e) { error.Timeout => unreachable, }; - } - }, - .canceled => unreachable, - .blocked => unreachable, - .blocked_alertable => unreachable, - .blocked_alertable_canceling => unreachable, - .blocked_canceling => unreachable, + }, + .canceled => unreachable, + .blocked => unreachable, + .blocked_alertable => unreachable, + .blocked_canceling => unreachable, + .blocked_alertable_canceling => unreachable, + } }, } } @@ -17144,6 +17142,9 @@ const parking_futex = struct { waiter.node.next = waking_head; waking_head = &waiter.node; num_removed += 1; + // Signal to `waiter` that they're about to be unparked, in case we're racing with their + // timeout. See corresponding logic in `wake`. + waiter.address = 0; } _ = bucket.num_waiters.fetchSub(num_removed, .monotonic); @@ -17158,8 +17159,6 @@ const parking_futex = struct { const waiter: *Waiter = @fieldParentPtr("node", node); unpark_buf[unpark_len] = waiter.tid; unpark_len += 1; - waiter.done.store(true, .release); - // `waiter.*` is now potentially invalid so must not be referenced again. if (unpark_len == unpark_buf.len) { unpark(&unpark_buf, ptr); unpark_len = 0; @@ -17176,14 +17175,13 @@ const parking_futex = struct { defer mutexUnlock(&bucket.mutex); bucket.waiters.remove(&waiter.node); assert(bucket.num_waiters.fetchSub(1, .monotonic) > 0); - waiter.done.store(true, .release); // potentially invalidates `waiter.*` } }; const parking_sleep = struct { comptime { assert(use_parking_sleep); } - fn sleep(deadline: ?Io.Clock.Timestamp) Io.Cancelable!void { + fn sleep(timeout: Io.Timeout) Io.Cancelable!void { const opt_thread = Thread.current; cancelable: { const thread = opt_thread orelse break :cancelable; @@ -17192,90 +17190,87 @@ const parking_sleep = struct { .unblocked => {}, } thread.futex_waiter = null; - const orig_status = thread.status.fetchOr( - .{ .cancelation = @enumFromInt(0b001), .awaitable = .null }, - .release, // release `thread.futex_waiter` - ); - switch (orig_status.cancelation) { - .none => {}, // status is now `.parked` - .canceling => return error.Canceled, // status is now `.canceled` - .canceled => break :cancelable, // status is still `.canceled` - .parked => unreachable, - .blocked => unreachable, - .blocked_alertable => unreachable, - .blocked_alertable_canceling => unreachable, - .blocked_canceling => unreachable, - } - while (park(deadline, null)) { - // Either a cancelation or a spurious unpark; let's see which! - switch (thread.status.load(.monotonic).cancelation) { - .parked => continue, // spurious unpark; keep sleeping - .canceling => { - // We got canceled; update our state and return. - thread.status.store( - .{ .cancelation = .canceled, .awaitable = orig_status.awaitable }, - .monotonic, - ); - return error.Canceled; - }, - .none => unreachable, - .canceled => unreachable, + { + const old_status = thread.status.fetchOr( + .{ .cancelation = @enumFromInt(0b001), .awaitable = .null }, + .release, // release `thread.futex_waiter` + ); + switch (old_status.cancelation) { + .none => {}, // status is now `.parked` + .canceling => return error.Canceled, // status is now `.canceled` + .canceled => break :cancelable, // status is still `.canceled` + .parked => unreachable, .blocked => unreachable, .blocked_alertable => unreachable, .blocked_alertable_canceling => unreachable, .blocked_canceling => unreachable, } - } else |err| switch (err) { - error.Timeout => switch (thread.status.fetchAnd( - .{ .cancelation = @enumFromInt(0b110), .awaitable = .all_ones }, + } + if (park(timeout, null)) { + // The only reason this could possibly happen is cancelation. + const old_status = thread.status.load(.monotonic); + assert(old_status.cancelation == .canceling); + thread.status.store( + .{ .cancelation = .canceled, .awaitable = old_status.awaitable }, .monotonic, - ).cancelation) { - // We updated our own status from `.parked` to `.none`. - .parked => return, // new status is `.none` - .canceling => { - // Timeout raced with a cancelation. We don't need to do anything, but - // the next `park` on this thread will see a spurious unpark. - // Status is still `.canceling`. - return; - }, - .none => unreachable, - .canceled => unreachable, - .blocked => unreachable, - .blocked_alertable => unreachable, - .blocked_alertable_canceling => unreachable, - .blocked_canceling => unreachable, + ); + return error.Canceled; + } else |err| switch (err) { + error.Timeout => { + // We're not out of the woods yet: an unpark could race with the timeout. + const old_status = thread.status.fetchAnd( + .{ .cancelation = @enumFromInt(0b110), .awaitable = .all_ones }, + .monotonic, + ); + switch (old_status.cancelation) { + .parked => return, // No race; new status is `.none` + .canceling => { + // Race condition: the timeout was reached, then someone tried to unpark + // us for a cancelation. Whoever did that will have called `unpark`, so + // drop that unpark request by waiting for it. + // Status is still `.canceling`. + park(.none, null) catch |e| switch (e) { + error.Timeout => unreachable, + }; + return; + }, + .none => unreachable, + .canceled => unreachable, + .blocked => unreachable, + .blocked_alertable => unreachable, + .blocked_canceling => unreachable, + .blocked_alertable_canceling => unreachable, + } }, } } - // Uncancelable sleep; this case is very simple. - while (park(deadline, null)) { - // Definitely spurious; nothing to do. + // Uncancelable sleep; we expect not to be manually unparked. + if (park(timeout, null)) { + unreachable; // unexpected unpark } else |err| switch (err) { error.Timeout => return, } } - /// Sleep for approximately `ms` awake milliseconds in an attempt to work around Windows kernel bugs. - fn windowsRetrySleep(ms: u32) (Io.Cancelable || Io.UnexpectedError)!void { - const now_timestamp = nowWindows(.awake); // '.awake' is supported on Windows - const deadline = now_timestamp.addDuration(.fromMilliseconds(ms)); - try parking_sleep.sleep(.{ .raw = deadline, .clock = .awake }); - } }; -/// Spurious wakeups are possible. -/// /// `addr_hint` has no semantic effect, but may allow the OS to optimize this operation. -fn park(opt_deadline: ?Io.Clock.Timestamp, addr_hint: ?*const anyopaque) error{Timeout}!void { +fn park(timeout: Io.Timeout, addr_hint: ?*const anyopaque) error{Timeout}!void { comptime assert(use_parking_futex or use_parking_sleep); switch (native_os) { .windows => { var timeout_buf: windows.LARGE_INTEGER = undefined; - const raw_timeout: ?*windows.LARGE_INTEGER = if (opt_deadline) |deadline| timeout: { - const now_timestamp = nowWindows(deadline.clock); - const nanoseconds = now_timestamp.durationTo(deadline.raw).nanoseconds; - timeout_buf = @intCast(@divTrunc(-nanoseconds, 100)); - break :timeout &timeout_buf; - } else null; + const raw_timeout: ?*windows.LARGE_INTEGER = timeout: switch (timeout) { + .none => null, + .deadline => |timestamp| continue :timeout .{ .duration = .{ + .clock = timestamp.clock, + .raw = (nowWindows(timestamp.clock) catch unreachable).durationTo(timestamp.raw), + } }, + .duration => |duration| { + _ = duration.clock; // Windows only supports monotonic + timeout_buf = @intCast(@divTrunc(-duration.raw.nanoseconds, 100)); + break :timeout &timeout_buf; + }, + }; // `RtlWaitOnAddress` passes the futex address in as the first argument to this call, // but it's unclear what that actually does, especially since `NtAlertThreadByThreadId` // does *not* accept the address so the kernel can't really be using it as a hint. An @@ -17297,13 +17292,20 @@ fn park(opt_deadline: ?Io.Clock.Timestamp, addr_hint: ?*const anyopaque) error{T }, .netbsd => { var ts_buf: posix.timespec = undefined; - const ts: ?*posix.timespec, const clock_real: bool = if (opt_deadline) |deadline| timeout: { - ts_buf = timestampToPosix(deadline.raw.nanoseconds); - break :timeout .{ &ts_buf, deadline.clock == .real }; - } else .{ null, true }; + const ts: ?*posix.timespec, const abstime: bool, const clock_real: bool = switch (timeout) { + .none => .{ null, false, false }, + .deadline => |timestamp| timeout: { + ts_buf = timestampToPosix(timestamp.raw.nanoseconds); + break :timeout .{ &ts_buf, true, timestamp.clock == .real }; + }, + .duration => |duration| timeout: { + ts_buf = timestampToPosix(duration.raw.nanoseconds); + break :timeout .{ &ts_buf, false, duration.clock == .real }; + }, + }; switch (posix.errno(std.c._lwp_park( if (clock_real) .REALTIME else .MONOTONIC, - .{ .ABSTIME = true }, + .{ .ABSTIME = abstime }, ts, 0, addr_hint, -- 2.54.0 From 6d6532dd9eb862dfd6e59ceed5c762342d2cc0d5 Mon Sep 17 00:00:00 2001 From: Matthew Lugg Date: Tue, 3 Feb 2026 22:14:42 +0000 Subject: [PATCH 190/499] Io.Threaded: add ParkingMutex, and deal with spurious unparks on NetBSD We can't use Io.Mutex in parking_futex; instead, we need a simple parking-based mutex implementation. That's fairly simple to do. Also deal with spurious unparks on NetBSD, where they *can* happen (as opposed to Windows, where they cannot). --- lib/std/Io/Threaded.zig | 284 +++++++++++++++++++++++++++++++--------- 1 file changed, 219 insertions(+), 65 deletions(-) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 558263a0102d755cc75e9dce78249428e8f8f3ec..59d816f62ceac38c2bcc385695c2f4616119842d 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -2662,7 +2662,7 @@ fn batchAwaitConcurrent(userdata: ?*anyopaque, b: *Io.Batch, timeout: Io.Timeout while (b.pending.head != .none and b.completions.head == .none) { var delay_interval: windows.LARGE_INTEGER = interval: { const d = deadline orelse break :interval std.math.minInt(windows.LARGE_INTEGER); - break :interval t.deadlineToWindowsInterval(d); + break :interval timeoutToWindowsInterval(.{ .deadline = d }).?; }; const alertable_syscall = try AlertableSyscall.start(); const delay_rc = windows.ntdll.NtDelayExecution(windows.TRUE, &delay_interval); @@ -4339,7 +4339,10 @@ fn dirCreateFileWindows( // kernel bug with retry attempts. syscall.finish(); if (max_attempts - attempt == 0) return error.FileBusy; - try parking_sleep.windowsRetrySleep((@as(u32, 1) << attempt) >> 1); + try parking_sleep.sleep(.{ .duration = .{ + .raw = .fromMilliseconds((@as(u32, 1) << attempt) >> 1), + .clock = .awake, + } }); attempt += 1; syscall = try .start(); continue; @@ -4352,7 +4355,10 @@ fn dirCreateFileWindows( // fixed by sleeping and retrying until the error goes away. syscall.finish(); if (max_attempts - attempt == 0) return error.FileBusy; - try parking_sleep.windowsRetrySleep((@as(u32, 1) << attempt) >> 1); + try parking_sleep.sleep(.{ .duration = .{ + .raw = .fromMilliseconds((@as(u32, 1) << attempt) >> 1), + .clock = .awake, + } }); attempt += 1; syscall = try .start(); continue; @@ -7382,7 +7388,10 @@ fn dirReadLinkWindows(dir: Dir, sub_path: []const u8, buffer: []u8) Dir.ReadLink // kernel bug with retry attempts. syscall.finish(); if (max_attempts - attempt == 0) return error.FileBusy; - try parking_sleep.windowsRetrySleep((@as(u32, 1) << attempt) >> 1); + try parking_sleep.sleep(.{ .duration = .{ + .raw = .fromMilliseconds((@as(u32, 1) << attempt) >> 1), + .clock = .awake, + } }); attempt += 1; syscall = try .start(); continue; @@ -7395,7 +7404,10 @@ fn dirReadLinkWindows(dir: Dir, sub_path: []const u8, buffer: []u8) Dir.ReadLink // fixed by sleeping and retrying until the error goes away. syscall.finish(); if (max_attempts - attempt == 0) return error.FileBusy; - try parking_sleep.windowsRetrySleep((@as(u32, 1) << attempt) >> 1); + try parking_sleep.sleep(.{ .duration = .{ + .raw = .fromMilliseconds((@as(u32, 1) << attempt) >> 1), + .clock = .awake, + } }); attempt += 1; syscall = try .start(); continue; @@ -10956,7 +10968,7 @@ fn nowWasi(clock: Io.Clock) Io.Timestamp { fn sleep(userdata: ?*anyopaque, timeout: Io.Timeout) Io.Cancelable!void { const t: *Threaded = @ptrCast(@alignCast(userdata)); if (timeout == .none) return; - if (use_parking_sleep) return parking_sleep.sleep(timeout.toTimestamp(ioBasic(t))); + if (use_parking_sleep) return parking_sleep.sleep(timeout); if (native_os == .wasi) return sleepWasi(t, timeout); if (@TypeOf(posix.system.clock_nanosleep) != void) return sleepPosix(timeout); return sleepNanosleep(t, timeout); @@ -14363,7 +14375,7 @@ const Wsa = struct { fn initializeWsa(t: *Threaded) error{ NetworkDown, Canceled }!void { const wsa = &t.wsa; - try mutexLock(&wsa.mutex); + mutexLock(&wsa.mutex); defer mutexUnlock(&wsa.mutex); switch (wsa.status) { .uninitialized => { @@ -16943,7 +16955,7 @@ const parking_futex = struct { /// avoid a race. num_waiters: std.atomic.Value(u32), /// Protects `waiters`. - mutex: Io.Mutex, + mutex: ParkingMutex, waiters: std.DoublyLinkedList, /// Prevent false sharing between buckets. @@ -17007,8 +17019,8 @@ const parking_futex = struct { var status_buf: std.atomic.Value(Thread.Status) = undefined; { - mutexLock(&bucket.mutex); - defer mutexUnlock(&bucket.mutex); + bucket.mutex.lock(); + defer bucket.mutex.unlock(); _ = bucket.num_waiters.fetchAdd(1, .acquire); @@ -17059,7 +17071,7 @@ const parking_futex = struct { bucket.waiters.append(&waiter.node); } - if (park(timeout, ptr)) { + if (park(timeout, ptr, waiter.thread_status)) { // We were unparked by either `wake` or cancelation, so our current status is either // `.none` or `.canceling`. In either case, they've already removed `waiter` from // `bucket`, so we have nothing more to do! @@ -17084,7 +17096,7 @@ const parking_futex = struct { // to unpark us. Whoever did that will remove us from `bucket`. Wait for // that (and drop the unpark request in doing so). // New status is `.none` or `.canceling` respectively. - park(.none, ptr) catch |e| switch (e) { + park(.none, ptr, waiter.thread_status) catch |e| switch (e) { error.Timeout => unreachable, }; }, @@ -17114,8 +17126,8 @@ const parking_futex = struct { // of the critical section. This forms a singly-linked list of waiters using `Waiter.node.next`. var waking_head: ?*std.DoublyLinkedList.Node = null; { - mutexLock(&bucket.mutex); - defer mutexUnlock(&bucket.mutex); + bucket.mutex.lock(); + defer bucket.mutex.unlock(); var num_removed: u32 = 0; var it = bucket.waiters.first; @@ -17171,8 +17183,8 @@ const parking_futex = struct { fn removeCanceledWaiter(waiter: *Waiter) void { const bucket = bucketForAddress(waiter.address); - mutexLock(&bucket.mutex); - defer mutexUnlock(&bucket.mutex); + bucket.mutex.lock(); + defer bucket.mutex.unlock(); bucket.waiters.remove(&waiter.node); assert(bucket.num_waiters.fetchSub(1, .monotonic) > 0); } @@ -17206,7 +17218,7 @@ const parking_sleep = struct { .blocked_canceling => unreachable, } } - if (park(timeout, null)) { + if (park(timeout, null, &thread.status)) { // The only reason this could possibly happen is cancelation. const old_status = thread.status.load(.monotonic); assert(old_status.cancelation == .canceling); @@ -17229,7 +17241,7 @@ const parking_sleep = struct { // us for a cancelation. Whoever did that will have called `unpark`, so // drop that unpark request by waiting for it. // Status is still `.canceling`. - park(.none, null) catch |e| switch (e) { + park(.none, null, &thread.status) catch |e| switch (e) { error.Timeout => unreachable, }; return; @@ -17245,32 +17257,183 @@ const parking_sleep = struct { } } // Uncancelable sleep; we expect not to be manually unparked. - if (park(timeout, null)) { + var dummy_status: std.atomic.Value(Thread.Status) = .init(.{ .cancelation = .parked, .awaitable = .null }); + if (park(timeout, null, &dummy_status)) { unreachable; // unexpected unpark } else |err| switch (err) { error.Timeout => return, } } }; +const ParkingMutex = struct { + state: std.atomic.Value(State), -/// `addr_hint` has no semantic effect, but may allow the OS to optimize this operation. -fn park(timeout: Io.Timeout, addr_hint: ?*const anyopaque) error{Timeout}!void { + const init: ParkingMutex = .{ .state = .init(.unlocked) }; + + comptime { + assert(use_parking_futex); + } + + const State = enum(usize) { + unlocked = 1, + /// This value is intentionally 0 so that `waiter` returns `null`. + locked_once = 0, + /// Contended; value is a `*Waiter`. + _, + /// Returns the head of the waiter list. Illegal to call if `s == .unlocked`. + fn waiter(s: State) ?*Waiter { + return @ptrFromInt(@intFromEnum(s)); + } + /// Returns a locked state where `w` is contending the lock. + /// If `w` is `null`, returns `.locked_once`. + fn fromWaiter(w: ?*Waiter) State { + return @enumFromInt(@intFromPtr(w)); + } + }; + const Waiter = struct { + status: std.atomic.Value(Thread.Status), + /// Never modified once the `Waiter` is in the linked list. + next: ?*Waiter, + /// Never modified once the `Waiter` is in the linked list. + tid: std.Thread.Id, + }; + fn lock(m: *ParkingMutex) void { + state: switch (State.unlocked) { // assume 'unlocked' to optimize for uncontended case + .unlocked => continue :state m.state.cmpxchgWeak( + .unlocked, + .locked_once, + .acquire, // acquire lock + .monotonic, + ) orelse { + @branchHint(.likely); + return; + }, + + .locked_once, _ => |last_state| { + const old_waiter = last_state.waiter(); + const self_tid = if (Thread.current) |t| t.id else std.Thread.getCurrentId(); + var waiter: Waiter = .{ + .next = old_waiter, + .status = .init(.{ .cancelation = .parked, .awaitable = .null }), + .tid = self_tid, + }; + if (m.state.cmpxchgWeak( + .fromWaiter(old_waiter), + .fromWaiter(&waiter), + .release, // release `waiter` + .monotonic, + )) |new_state| { + continue :state new_state; + } + // We're now in the list of waiters---park until we're given the lock. + park(.none, m, &waiter.status) catch |err| switch (err) { + error.Timeout => unreachable, + }; + // We now hold the lock. + assert(waiter.status.load(.monotonic).cancelation == .none); + return; + }, + } + } + fn unlock(m: *ParkingMutex) void { + state: switch (State.locked_once) { // assume 'locked_once' to optimize for uncontended case + .unlocked => unreachable, // we hold the lock + + .locked_once => continue :state m.state.cmpxchgWeak( + .locked_once, + .unlocked, + .release, // release lock + .acquire, // acquire any `Waiter` memory + ) orelse { + @branchHint(.likely); + return; + }, + + _ => |last_state| { + // The logic here does not have ABA problems, and does some accesses non-atomically, + // because `Waiter.next` is owned by the lock holder (that's us!) once the waiter is + // in the linked list, up until we set `Waiter.status` to `.none`. + + // Run through the waiter list to the end to ensure fairness. This is obviously not + // ideal, but it shouldn't be a big deal in practice provided the critical section + // is fairly small (so we won't get too many threads contending the mutex at once). + // There's a *chance* we could get away with a LIFO queue for our use case, but I + // don't wanna risk that. + var parent: ?*Waiter = null; + var waiter: *Waiter = last_state.waiter().?; + while (waiter.next) |next| { + parent = waiter; + waiter = next; + } + // `waiter` is next in line for the lock. Remove them from the list. + if (parent) |p| { + assert(p.next == waiter); + p.next = null; + } else { + // We're waking the last waiter, so clear the list head. + if (m.state.cmpxchgWeak( + .fromWaiter(last_state.waiter().?), + .locked_once, + .acquire, + .acquire, // acquire any new `Waiter` memory + )) |new_state| { + continue :state new_state; + } + } + // Now we're ready to actually hand the lock over to them. + const tid = waiter.tid; // load this before the store below potentially invalidates `waiter` + waiter.status.store(.{ .cancelation = .none, .awaitable = .null }, .release); // release lock + unpark(&.{tid}, m); + return; + }, + } + } +}; + +fn timeoutToWindowsInterval(timeout: Io.Timeout) ?windows.LARGE_INTEGER { + // ntdll only supports two combinations: + // * real-time (`.real`) sleeps with absolute deadlines + // * monotonic (`.awake`/`.boot`) sleeps with relative durations + const clock = switch (timeout) { + .none => return null, + .duration => |d| d.clock, + .deadline => |d| d.clock, + }; + switch (clock) { + .cpu_process, .cpu_thread => unreachable, // cannot sleep for CPU time + .real => { + const deadline = switch (timeout) { + .none => unreachable, + .duration => |d| nowWindows(clock).addDuration(d.raw), + .deadline => |d| d.raw, + }; + return @intCast(@max(@divTrunc(deadline.nanoseconds, 100), 0)); + }, + .awake, .boot => { + const duration = switch (timeout) { + .none => unreachable, + .duration => |d| d.raw, + .deadline => |d| nowWindows(clock).durationTo(d.raw), + }; + return @intCast(@min(@divTrunc(-duration.nanoseconds, 100), -1)); + }, + } +} + +fn park( + timeout: Io.Timeout, + /// This value has no semantic effect, but may allow the OS to optimize the operation. + addr_hint: ?*const anyopaque, + /// The API on NetBSD and Illumos sucks and can unpark spuriously (well, it *can't*, but signals + /// cause an indistinguishable unblock, and libpthread really likes to leave unparks pending). + /// As such, on these targets only, this `status` is checked to determine if an unpark is real. + /// no way to differentiate + status: *std.atomic.Value(Thread.Status), +) error{Timeout}!void { comptime assert(use_parking_futex or use_parking_sleep); switch (native_os) { .windows => { - var timeout_buf: windows.LARGE_INTEGER = undefined; - const raw_timeout: ?*windows.LARGE_INTEGER = timeout: switch (timeout) { - .none => null, - .deadline => |timestamp| continue :timeout .{ .duration = .{ - .clock = timestamp.clock, - .raw = (nowWindows(timestamp.clock) catch unreachable).durationTo(timestamp.raw), - } }, - .duration => |duration| { - _ = duration.clock; // Windows only supports monotonic - timeout_buf = @intCast(@divTrunc(-duration.raw.nanoseconds, 100)); - break :timeout &timeout_buf; - }, - }; + const raw_timeout = timeoutToWindowsInterval(timeout); // `RtlWaitOnAddress` passes the futex address in as the first argument to this call, // but it's unclear what that actually does, especially since `NtAlertThreadByThreadId` // does *not* accept the address so the kernel can't really be using it as a hint. An @@ -17284,7 +17447,10 @@ fn park(timeout: Io.Timeout, addr_hint: ?*const anyopaque) error{Timeout}!void { // this parameter). However, to err on the side of caution, let's match the behavior of // `RtlWaitOnAddress` and pass the pointer, in case the kernel ever does something // stupid such as trying to dereference it. - switch (windows.ntdll.NtWaitForAlertByThreadId(addr_hint, raw_timeout)) { + switch (windows.ntdll.NtWaitForAlertByThreadId( + addr_hint, + if (raw_timeout) |*t| t else null, + )) { .ALERTED => return, .TIMEOUT => return error.Timeout, else => unreachable, @@ -17303,19 +17469,23 @@ fn park(timeout: Io.Timeout, addr_hint: ?*const anyopaque) error{Timeout}!void { break :timeout .{ &ts_buf, false, duration.clock == .real }; }, }; - switch (posix.errno(std.c._lwp_park( - if (clock_real) .REALTIME else .MONOTONIC, - .{ .ABSTIME = abstime }, - ts, - 0, - addr_hint, - null, - ))) { - .SUCCESS, .ALREADY, .INTR => return, - .TIMEDOUT => return error.Timeout, - .INVAL => unreachable, - .SRCH => unreachable, - else => unreachable, + // It's okay to pass the same timeout in a loop. If it's a duration, the OS actually + // writes the remaining time into the buffer when the syscall returns. + while (status.load(.monotonic).cancelation == .parked) { + switch (posix.errno(std.c._lwp_park( + if (clock_real) .REALTIME else .MONOTONIC, + .{ .ABSTIME = abstime }, + ts, + 0, + addr_hint, + null, + ))) { + .SUCCESS, .ALREADY, .INTR => {}, + .TIMEDOUT => return error.Timeout, + .INVAL => unreachable, + .SRCH => unreachable, + else => unreachable, + } } }, .illumos => @panic("TODO: illumos lwp_park"), @@ -17323,24 +17493,8 @@ fn park(timeout: Io.Timeout, addr_hint: ?*const anyopaque) error{Timeout}!void { } } -fn deadlineToWindowsInterval(t: *Io.Threaded, deadline: Io.Clock.Timestamp) windows.LARGE_INTEGER { - // ntdll only supports two combinations: - // * real-time (`.real`) sleeps with absolute deadlines - // * monotonic (`.awake`/`.boot`) sleeps with relative durations - switch (deadline.clock) { - .cpu_process, .cpu_thread => return 0, - .real => { - return @intCast(@max(@divTrunc(deadline.raw.nanoseconds, 100), 0)); - }, - .awake, .boot => { - const duration = deadline.durationFromNow(ioBasic(t)); - return @intCast(@min(@divTrunc(-duration.raw.nanoseconds, 100), -1)); - }, - } -} - const UnparkTid = switch (native_os) { - // `NtAlertMultipleThreadByThreadId` is weird and wants 64-bit thread handles? + // `NtAlertMultipleThreadByThreadId` is weird and wants 64-bit thread IDs? .windows => usize, else => std.Thread.Id, }; -- 2.54.0 From d45f9aca14bd36a710293b3d0092039fd91a571b Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 3 Feb 2026 20:06:50 -0800 Subject: [PATCH 191/499] std.Thread: delete Mutex.Recursive Replaced by the lockStderr functions of std.Io. Trying to make `std.process.stderr_thread_mutex` be a bridge across different Io implementations didn't work in practice. --- lib/std/Io.zig | 3 +- lib/std/Io/IoUring.zig | 2 +- lib/std/Io/Kqueue.zig | 2 +- lib/std/Io/Threaded.zig | 39 +++++++++++++--- lib/std/Thread.zig | 14 +----- lib/std/Thread/Mutex/Recursive.zig | 72 ------------------------------ lib/std/process.zig | 7 --- 7 files changed, 39 insertions(+), 100 deletions(-) delete mode 100644 lib/std/Thread/Mutex/Recursive.zig diff --git a/lib/std/Io.zig b/lib/std/Io.zig index 92327e0f5d16f0e83cd3cfabe0c86f463a68f03c..0eb1df2d1a7decb3787d74c6baeb7c49a507a4fe 100644 --- a/lib/std/Io.zig +++ b/lib/std/Io.zig @@ -2160,8 +2160,7 @@ pub const LockedStderr = struct { /// For doing application-level writes to the standard error stream. /// Coordinates also with debug-level writes that are ignorant of Io interface -/// and implementations. When this returns, `std.process.stderr_thread_mutex` -/// will be locked. +/// and implementations. /// /// See also: /// * `tryLockStderr` diff --git a/lib/std/Io/IoUring.zig b/lib/std/Io/IoUring.zig index 81cdc242018d6129a52afd0b4232006f52811d77..8ff3ae22ef230ab9a503c6d9867347e961c65008 100644 --- a/lib/std/Io/IoUring.zig +++ b/lib/std/Io/IoUring.zig @@ -10,7 +10,7 @@ const IoUring = std.os.linux.IoUring; /// Must be a thread-safe allocator. gpa: Allocator, -mutex: std.Thread.Mutex, +mutex: Io.Mutex, main_fiber_buffer: [@sizeOf(Fiber) + Fiber.max_result_size]u8 align(@alignOf(Fiber)), threads: Thread.List, diff --git a/lib/std/Io/Kqueue.zig b/lib/std/Io/Kqueue.zig index f998d5cef8b41461ad47cbcbe31595acbc557927..23cd1b39286ac6f4a0c5041b5b94b9c2896121b1 100644 --- a/lib/std/Io/Kqueue.zig +++ b/lib/std/Io/Kqueue.zig @@ -15,7 +15,7 @@ const posix = std.posix; /// Must be a thread-safe allocator. gpa: Allocator, -mutex: std.Thread.Mutex, +mutex: Io.Mutex, main_fiber_buffer: [@sizeOf(Fiber) + Fiber.max_result_size]u8 align(@alignOf(Fiber)), threads: Thread.List, diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 4a84a782b641b13244f0ea3d170b7128c0e56c15..c1c63067817b99ea620173ce6807e86ce279cd9e 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -67,6 +67,9 @@ stderr_writer: File.Writer = .{ }, stderr_mode: Io.Terminal.Mode = .no_color, stderr_writer_initialized: bool = false, +stderr_mutex: Io.Mutex = .init, +stderr_mutex_locker: std.Thread.Id = Thread.invalid_id, +stderr_mutex_lock_count: usize = 0, argv0: Argv0, environ: Environ, @@ -689,6 +692,13 @@ const Thread = struct { threadlocal var current: ?*Thread = null; + /// A value that does not alias any other thread id. + const invalid_id: std.Thread.Id = std.math.maxInt(std.Thread.Id); + + fn currentId() std.Thread.Id { + return if (current) |t| t.id else std.Thread.getCurrentId(); + } + /// The thread is neither in a syscall nor entering one, but we want to check for cancelation /// anyway. If there is a pending cancel request, acknowledge it and return `error.Canceled`. fn checkCancel() Io.Cancelable!void { @@ -13502,15 +13512,29 @@ fn netLookupFallible( fn lockStderr(userdata: ?*anyopaque, terminal_mode: ?Io.Terminal.Mode) Io.Cancelable!Io.LockedStderr { const t: *Threaded = @ptrCast(@alignCast(userdata)); - // Only global mutex since this is Threaded. - process.stderr_thread_mutex.lock(); + const current_thread_id = Thread.currentId(); + + if (@atomicLoad(std.Thread.Id, &t.stderr_mutex_locker, .unordered) != current_thread_id) { + mutexLock(&t.stderr_mutex); + assert(t.stderr_mutex_lock_count == 0); + @atomicStore(std.Thread.Id, &t.stderr_mutex_locker, current_thread_id, .unordered); + } + t.stderr_mutex_lock_count += 1; + return initLockedStderr(t, terminal_mode); } fn tryLockStderr(userdata: ?*anyopaque, terminal_mode: ?Io.Terminal.Mode) Io.Cancelable!?Io.LockedStderr { const t: *Threaded = @ptrCast(@alignCast(userdata)); - // Only global mutex since this is Threaded. - if (!process.stderr_thread_mutex.tryLock()) return null; + const current_thread_id = Thread.currentId(); + + if (@atomicLoad(std.Thread.Id, &t.stderr_mutex_locker, .unordered) != current_thread_id) { + if (!t.stderr_mutex.tryLock()) return null; + assert(t.stderr_mutex_lock_count == 0); + @atomicStore(std.Thread.Id, &t.stderr_mutex_locker, current_thread_id, .unordered); + } + t.stderr_mutex_lock_count += 1; + return try initLockedStderr(t, terminal_mode); } @@ -13541,7 +13565,12 @@ fn unlockStderr(userdata: ?*anyopaque) void { }; t.stderr_writer.interface.end = 0; t.stderr_writer.interface.buffer = &.{}; - process.stderr_thread_mutex.unlock(); + + t.stderr_mutex_lock_count -= 1; + if (t.stderr_mutex_lock_count == 0) { + @atomicStore(std.Thread.Id, &t.stderr_mutex_locker, Thread.invalid_id, .unordered); + mutexUnlock(&t.stderr_mutex); + } } fn processCurrentPath(userdata: ?*anyopaque, buffer: []u8) process.CurrentPathError!usize { diff --git a/lib/std/Thread.zig b/lib/std/Thread.zig index 3191dd26ce4563cb10ee2803edf49c9ae59c3e1d..4d0e992e975b847ae0332172a1505f2924a9d906 100644 --- a/lib/std/Thread.zig +++ b/lib/std/Thread.zig @@ -1,6 +1,5 @@ -//! This struct represents a kernel thread, and acts as a namespace for -//! concurrency primitives that operate on kernel threads. For concurrency -//! primitives that interact with the I/O interface, see `std.Io`. +//! This struct represents a kernel thread. +const Thread = @This(); const builtin = @import("builtin"); const target = builtin.target; @@ -14,13 +13,8 @@ const posix = std.posix; const windows = std.os.windows; const testing = std.testing; -pub const Mutex = struct { - pub const Recursive = @import("Thread/Mutex/Recursive.zig"); -}; - pub const use_pthreads = native_os != .windows and native_os != .wasi and builtin.link_libc; -const Thread = @This(); const Impl = if (native_os == .windows) WindowsThreadImpl else if (use_pthreads) @@ -1604,10 +1598,6 @@ test "setName, getName" { thread.join(); } -test { - _ = Mutex; -} - fn testIncrementNotify(io: Io, value: *usize, event: *Io.Event) void { value.* += 1; event.set(io); diff --git a/lib/std/Thread/Mutex/Recursive.zig b/lib/std/Thread/Mutex/Recursive.zig deleted file mode 100644 index 8fa0563fb36b1eeaa0edd9c7d9b9d864b379db73..0000000000000000000000000000000000000000 --- a/lib/std/Thread/Mutex/Recursive.zig +++ /dev/null @@ -1,72 +0,0 @@ -//! A synchronization primitive enforcing atomic access to a shared region of -//! code known as the "critical section". -//! -//! Equivalent to `std.Mutex` except it allows the same thread to obtain the -//! lock multiple times. -//! -//! A recursive mutex is an abstraction layer on top of a regular mutex; -//! therefore it is recommended to use instead `std.Mutex` unless there is a -//! specific reason a recursive mutex is warranted. -const Recursive = @This(); - -const std = @import("../../std.zig"); -const Io = std.Io; -const assert = std.debug.assert; - -mutex: Io.Mutex, -thread_id: std.Thread.Id, -lock_count: usize, - -pub const init: Recursive = .{ - .mutex = .init, - .thread_id = invalid_thread_id, - .lock_count = 0, -}; - -/// Acquires the `Mutex` without blocking the caller's thread. -/// -/// Returns `false` if the calling thread would have to block to acquire it. -/// -/// Otherwise, returns `true` and the caller should `unlock()` the Mutex to release it. -pub fn tryLock(r: *Recursive) bool { - const current_thread_id = std.Thread.getCurrentId(); - if (@atomicLoad(std.Thread.Id, &r.thread_id, .unordered) != current_thread_id) { - if (!r.mutex.tryLock()) return false; - assert(r.lock_count == 0); - @atomicStore(std.Thread.Id, &r.thread_id, current_thread_id, .unordered); - } - r.lock_count += 1; - return true; -} - -/// Acquires the `Mutex`, blocking the current thread while the mutex is -/// already held by another thread. -/// -/// The `Mutex` can be held multiple times by the same thread. -/// -/// Once acquired, call `unlock` on the `Mutex` to release it, regardless -/// of whether the lock was already held by the same thread. -pub fn lock(r: *Recursive) void { - const current_thread_id = std.Thread.getCurrentId(); - if (@atomicLoad(std.Thread.Id, &r.thread_id, .unordered) != current_thread_id) { - Io.Threaded.mutexLock(&r.mutex); - assert(r.lock_count == 0); - @atomicStore(std.Thread.Id, &r.thread_id, current_thread_id, .unordered); - } - r.lock_count += 1; -} - -/// Releases the `Mutex` which was previously acquired with `lock` or `tryLock`. -/// -/// It is undefined behavior to unlock from a different thread that it was -/// locked from. -pub fn unlock(r: *Recursive) void { - r.lock_count -= 1; - if (r.lock_count == 0) { - @atomicStore(std.Thread.Id, &r.thread_id, invalid_thread_id, .unordered); - Io.Threaded.mutexUnlock(&r.mutex); - } -} - -/// A value that does not alias any other thread id. -const invalid_thread_id: std.Thread.Id = std.math.maxInt(std.Thread.Id); diff --git a/lib/std/process.zig b/lib/std/process.zig index d09239dbc2e0de28b0fd9f51a886b241fdbf4b21..618d7f8f5bc3cbc1b62e05693d5d550fdcbb5eb1 100644 --- a/lib/std/process.zig +++ b/lib/std/process.zig @@ -20,13 +20,6 @@ pub const Args = @import("process/Args.zig"); pub const Environ = @import("process/Environ.zig"); pub const Preopens = @import("process/Preopens.zig"); -/// This is the global, process-wide protection to coordinate stderr writes. -/// -/// The primary motivation for recursive mutex here is so that a panic while -/// stderr mutex is held still dumps the stack trace and other debug -/// information. -pub var stderr_thread_mutex: std.Thread.Mutex.Recursive = .init; - /// A standard set of pre-initialized useful APIs for programs to take /// advantage of. This is the type of the first parameter of the main function. /// Applications wanting more flexibility can accept `Init.Minimal` instead. -- 2.54.0 From fce7878a9149caa80433e6d650e0bd7f60d345fd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Wed, 4 Feb 2026 11:11:39 +0100 Subject: [PATCH 192/499] test: disable hexagon-linux-musl C ABI tests for now https://gitlab.com/qemu-project/qemu/-/issues/3291 --- test/tests.zig | 15 ++++++++------- 1 file changed, 8 insertions(+), 7 deletions(-) diff --git a/test/tests.zig b/test/tests.zig index ede30a131d804934f8c0b63355965b80c168c35f..81fc9ae7d074971c31f80d11042e66ed5dc55a95 100644 --- a/test/tests.zig +++ b/test/tests.zig @@ -1714,13 +1714,14 @@ const c_abi_targets = blk: { }, }, - .{ - .target = .{ - .cpu_arch = .hexagon, - .os_tag = .linux, - .abi = .musl, - }, - }, + // https://gitlab.com/qemu-project/qemu/-/issues/3291 + // .{ + // .target = .{ + // .cpu_arch = .hexagon, + // .os_tag = .linux, + // .abi = .musl, + // }, + // }, .{ .target = .{ -- 2.54.0 From ffc6da29e3fa53a9c81bcb8af7467cdd6345538f Mon Sep 17 00:00:00 2001 From: Jacob Young Date: Fri, 30 Jan 2026 13:07:41 -0500 Subject: [PATCH 193/499] std.Io.Threaded: implement and cleanup windows codepaths --- build.zig | 1 + lib/std/Build/Watch.zig | 18 +- lib/std/Io/Threaded.zig | 716 +++++++++++++------------ lib/std/Io/Threaded/test.zig | 16 +- lib/std/Progress.zig | 37 +- lib/std/Thread.zig | 6 +- lib/std/mem/Allocator.zig | 15 +- lib/std/os/windows.zig | 269 +--------- lib/std/os/windows/kernel32.zig | 108 ---- lib/std/os/windows/ntdll.zig | 17 +- lib/std/process/Environ.zig | 706 ++++++++++++++---------- lib/std/start.zig | 17 +- test/standalone/env_vars/main.zig | 23 - test/standalone/windows_argv/fuzz.zig | 9 +- test/standalone/windows_spawn/main.zig | 7 +- 15 files changed, 920 insertions(+), 1045 deletions(-) diff --git a/build.zig b/build.zig index 84cbba38bdb6026dc3691463ac40d1fcad33419e..3df6969ddad159576ac3cb06c39c25c6586aa17c 100644 --- a/build.zig +++ b/build.zig @@ -1498,6 +1498,7 @@ fn generateLangRef(b: *std.Build) std.Build.LazyPath { defer dir.close(io); var wf = b.addWriteFiles(); + b.step("test-docs", "Test code snippets from the docs").dependOn(&wf.step); var it = dir.iterateAssumeFirstIteration(); while (it.next(io) catch @panic("failed to read dir")) |entry| { diff --git a/lib/std/Build/Watch.zig b/lib/std/Build/Watch.zig index c4ac62b216604a89ed3a91c078bbe17bca8826ce..5920a227cb192f75c7e8224e211240d814acaf0b 100644 --- a/lib/std/Build/Watch.zig +++ b/lib/std/Build/Watch.zig @@ -366,15 +366,7 @@ const Os = switch (builtin.os.tag) { .MaximumLength = @intCast(path_len_bytes), .Buffer = @constCast(sub_path_w.span().ptr), }; - var attr = windows.OBJECT_ATTRIBUTES{ - .Length = @sizeOf(windows.OBJECT_ATTRIBUTES), - .RootDirectory = if (std.fs.path.isAbsoluteWindowsW(sub_path_w.span())) null else root_fd, - .Attributes = .{}, - .ObjectName = &nt_name, - .SecurityDescriptor = null, - .SecurityQualityOfService = null, - }; - var io: windows.IO_STATUS_BLOCK = undefined; + var iosb: windows.IO_STATUS_BLOCK = undefined; switch (windows.ntdll.NtCreateFile( &dir_handle, @@ -385,14 +377,18 @@ const Os = switch (builtin.os.tag) { .STANDARD = .{ .SYNCHRONIZE = true }, .GENERIC = .{ .READ = true }, }, - &attr, - &io, + &.{ + .RootDirectory = if (std.fs.path.isAbsoluteWindowsW(sub_path_w.span())) null else root_fd, + .ObjectName = &nt_name, + }, + &iosb, null, .{}, .VALID_FLAGS, .OPEN, .{ .DIRECTORY_FILE = true, + .IO = .ASYNCHRONOUS, .OPEN_FOR_BACKUP_INTENT = true, }, null, diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index c1c63067817b99ea620173ce6807e86ce279cd9e..49132e79189842591b6b0a443bcd6cecd5a4fc85 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -76,6 +76,7 @@ environ: Environ, null_file: NullFile = .{}, random_file: RandomFile = .{}, +pipe_file: PipeFile = .{}, csprng: Csprng = .{}, @@ -121,7 +122,7 @@ pub const Argv0 = switch (native_os) { const Environ = struct { /// Unmodified data directly from the OS. - process_environ: process.Environ = .empty, + process_environ: process.Environ, /// Protected by `mutex`. Determines whether the other fields have been /// memoized based on `process_environ`. initialized: bool = false, @@ -131,13 +132,15 @@ const Environ = struct { /// Protected by `mutex`. Memoized based on `process_environ`. string: String = .{}, /// ZIG_PROGRESS - zig_progress_handle: std.Progress.ParentFileError!u31 = error.EnvironmentVariableMissing, + zig_progress_file: std.Progress.ParentFileError!File = error.EnvironmentVariableMissing, /// Protected by `mutex`. Tracks the problem, if any, that occurred when /// trying to scan environment variables. /// /// Errors are only possible on WASI. err: ?Error = null, + pub const empty: Environ = .{ .process_environ = .empty }; + pub const Error = Allocator.Error || Io.UnexpectedError; pub const Exist = struct { @@ -193,6 +196,24 @@ pub const RandomFile = switch (native_os) { }, }; +pub const PipeFile = switch (native_os) { + .windows => struct { + handle: ?windows.HANDLE = null, + + fn deinit(this: *@This()) void { + if (this.handle) |handle| { + windows.CloseHandle(handle); + this.handle = null; + } + } + }, + else => struct { + fn deinit(this: @This()) void { + _ = this; + } + }, +}; + pub const Pid = if (native_os == .linux) enum(posix.pid_t) { unknown = 0, _, @@ -1496,7 +1517,9 @@ pub const init_single_threaded: Threaded = .{ .old_sig_pipe = undefined, .have_signal_handler = false, .argv0 = .empty, - .environ = .{}, + .environ = .{ .process_environ = .{ + .block = if (process.Environ.Block == process.Environ.GlobalBlock) .global else .empty, + } }, .worker_threads = .init(null), .disable_memory_mapping = false, }; @@ -1531,6 +1554,7 @@ pub fn deinit(t: *Threaded) void { } t.null_file.deinit(); t.random_file.deinit(); + t.pipe_file.deinit(); t.* = undefined; } @@ -1573,14 +1597,7 @@ fn worker(t: *Threaded) void { }, }, }, - &.{ - .Length = @sizeOf(windows.OBJECT_ATTRIBUTES), - .RootDirectory = null, - .ObjectName = null, - .Attributes = .{}, - .SecurityDescriptor = null, - .SecurityQualityOfService = null, - }, + &.{ .ObjectName = null }, &windows.teb().ClientId, ) == .SUCCESS); } @@ -3376,12 +3393,8 @@ fn dirCreateDirPathOpenWindows( }, }, &.{ - .Length = @sizeOf(w.OBJECT_ATTRIBUTES), .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle, - .Attributes = .{}, .ObjectName = &nt_name, - .SecurityDescriptor = null, - .SecurityQualityOfService = null, }, &io_status_block, null, @@ -4063,13 +4076,9 @@ fn dirAccessWindows( .MaximumLength = path_len_bytes, .Buffer = @constCast(sub_path_w.ptr), }; - var attr: windows.OBJECT_ATTRIBUTES = .{ - .Length = @sizeOf(windows.OBJECT_ATTRIBUTES), + const attr: windows.OBJECT_ATTRIBUTES = .{ .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle, - .Attributes = .{}, .ObjectName = &nt_name, - .SecurityDescriptor = null, - .SecurityQualityOfService = null, }; var basic_info: windows.FILE.BASIC_INFORMATION = undefined; const syscall: Syscall = try .start(); @@ -4285,14 +4294,8 @@ fn dirCreateFileWindows( .Buffer = @constCast(sub_path_w.ptr), }; const attr: windows.OBJECT_ATTRIBUTES = .{ - .Length = @sizeOf(windows.OBJECT_ATTRIBUTES), .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle, - .Attributes = .{ - .INHERIT = false, - }, .ObjectName = &nt_name, - .SecurityDescriptor = null, - .SecurityQualityOfService = null, }; const create_disposition: windows.FILE.CREATE_DISPOSITION = if (flags.exclusive) .CREATE @@ -4905,17 +4908,6 @@ pub fn dirOpenFileWtf16( .MaximumLength = path_len_bytes, .Buffer = @constCast(sub_path_w.ptr), }; - var attr: w.OBJECT_ATTRIBUTES = .{ - .Length = @sizeOf(w.OBJECT_ATTRIBUTES), - .RootDirectory = dir_handle, - .Attributes = .{ - // TODO should we set INHERIT=false? - //.INHERIT = false, - }, - .ObjectName = &nt_name, - .SecurityDescriptor = null, - .SecurityQualityOfService = null, - }; var io_status_block: w.IO_STATUS_BLOCK = undefined; // There are multiple kernel bugs being worked around with retries. @@ -4934,7 +4926,10 @@ pub fn dirOpenFileWtf16( .WRITE = flags.isWrite(), }, }, - &attr, + &.{ + .RootDirectory = dir_handle, + .ObjectName = &nt_name, + }, &io_status_block, null, .{ .NORMAL = true }, @@ -5302,12 +5297,8 @@ pub fn dirOpenDirWindows( }, }, &.{ - .Length = @sizeOf(w.OBJECT_ATTRIBUTES), .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle, - .Attributes = .{}, .ObjectName = &nt_name, - .SecurityDescriptor = null, - .SecurityQualityOfService = null, }, &io_status_block, null, @@ -6517,12 +6508,8 @@ fn dirDeleteWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, remov .SYNCHRONIZE = true, } }, &.{ - .Length = @sizeOf(w.OBJECT_ATTRIBUTES), .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle, - .Attributes = .{}, .ObjectName = &nt_name, - .SecurityDescriptor = null, - .SecurityQualityOfService = null, }, &io_status_block, null, @@ -6531,6 +6518,7 @@ fn dirDeleteWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, remov .OPEN, .{ .DIRECTORY_FILE = remove_dir, + .IO = .SYNCHRONOUS_NONALERT, .NON_DIRECTORY_FILE = !remove_dir, .OPEN_REPARSE_POINT = true, // would we ever want to delete the target instead? }, @@ -7342,14 +7330,8 @@ fn dirReadLinkWindows(dir: Dir, sub_path: []const u8, buffer: []u8) Dir.ReadLink .Buffer = @constCast(sub_path_w.ptr), }; const attr: windows.OBJECT_ATTRIBUTES = .{ - .Length = @sizeOf(windows.OBJECT_ATTRIBUTES), .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle, - .Attributes = .{ - .INHERIT = false, - }, .ObjectName = &nt_name, - .SecurityDescriptor = null, - .SecurityQualityOfService = null, }; var io_status_block: windows.IO_STATUS_BLOCK = undefined; var result_handle: windows.HANDLE = undefined; @@ -7906,24 +7888,19 @@ fn fileSyncWindows(userdata: ?*anyopaque, file: File) File.SyncError!void { const t: *Threaded = @ptrCast(@alignCast(userdata)); _ = t; + var io_status_block: windows.IO_STATUS_BLOCK = undefined; const syscall: Syscall = try .start(); while (true) { - if (windows.kernel32.FlushFileBuffers(file.handle) != 0) { - return syscall.finish(); - } - switch (windows.GetLastError()) { - .SUCCESS => unreachable, // `FlushFileBuffers` returned nonzero + switch (windows.ntdll.NtFlushBuffersFile(file.handle, &io_status_block)) { + .SUCCESS => break syscall.finish(), + .CANCELLED => { + try syscall.checkCancel(); + continue; + }, .INVALID_HANDLE => unreachable, .ACCESS_DENIED => return syscall.fail(error.AccessDenied), // a sync was performed but the system couldn't update the access time - .UNEXP_NET_ERR => return syscall.fail(error.InputOutput), - .OPERATION_ABORTED => { - try syscall.checkCancel(); - continue; - }, - else => |err| { - syscall.finish(); - return windows.unexpectedError(err); - }, + .UNEXPECTED_NETWORK_ERROR => return syscall.fail(error.InputOutput), + else => |status| return syscall.unexpectedNtstatus(status), } } } @@ -14556,22 +14533,39 @@ fn scanEnviron(t: *Threaded) void { comptime assert(@sizeOf(Environ.String) == 0); } } else { - for (t.environ.process_environ.block) |opt_line| { - const line = opt_line.?; - var line_i: usize = 0; - while (line[line_i] != 0 and line[line_i] != '=') : (line_i += 1) {} - const key = line[0..line_i]; + for (t.environ.process_environ.block.slice) |opt_entry| { + const entry = opt_entry.?; + var entry_i: usize = 0; + while (entry[entry_i] != 0 and entry[entry_i] != '=') : (entry_i += 1) {} + const key = entry[0..entry_i]; - var end_i: usize = line_i; - while (line[end_i] != 0) : (end_i += 1) {} - const value = line[line_i + 1 .. end_i :0]; + var end_i: usize = entry_i; + while (entry[end_i] != 0) : (end_i += 1) {} + const value = entry[entry_i + 1 .. end_i :0]; if (std.mem.eql(u8, key, "NO_COLOR")) { t.environ.exist.NO_COLOR = true; } else if (std.mem.eql(u8, key, "CLICOLOR_FORCE")) { t.environ.exist.CLICOLOR_FORCE = true; } else if (std.mem.eql(u8, key, "ZIG_PROGRESS")) { - t.environ.zig_progress_handle = std.fmt.parseInt(u31, value, 10) catch error.UnrecognizedFormat; + t.environ.zig_progress_file = file: { + const int = std.fmt.parseInt(switch (@typeInfo(File.Handle)) { + .int => |int_info| @Int( + .unsigned, + int_info.bits - @intFromBool(int_info.signedness == .signed), + ), + .pointer => usize, + else => break :file error.UnsupportedOperation, + }, value, 10) catch break :file error.UnrecognizedFormat; + break :file .{ + .handle = switch (@typeInfo(File.Handle)) { + .int => int, + .pointer => @ptrFromInt(int), + else => comptime unreachable, + }, + .flags = .{ .nonblocking = true }, + }; + }; } else inline for (@typeInfo(Environ.String).@"struct".fields) |field| { if (std.mem.eql(u8, key, field.name)) @field(t.environ.string, field.name) = value; } @@ -14594,19 +14588,17 @@ fn processReplace(userdata: ?*anyopaque, options: process.ReplaceOptions) proces const argv_buf = try arena.allocSentinel(?[*:0]const u8, options.argv.len, null); for (options.argv, 0..) |arg, i| argv_buf[i] = (try arena.dupeZ(u8, arg)).ptr; - const envp: [*:null]const ?[*:0]const u8 = m: { + const env_block = env_block: { const prog_fd: i32 = -1; - if (options.environ_map) |environ_map| { - break :m (try environ_map.createBlockPosix(arena, .{ - .zig_progress_fd = prog_fd, - })).ptr; - } - break :m (try process.Environ.createBlockPosix(t.environ.process_environ, arena, .{ + if (options.environ_map) |environ_map| break :env_block try environ_map.createPosixBlock(arena, .{ .zig_progress_fd = prog_fd, - })).ptr; + }); + break :env_block try t.environ.process_environ.createPosixBlock(arena, .{ + .zig_progress_fd = prog_fd, + }); }; - return posixExecv(options.expand_arg0, argv_buf.ptr[0].?, argv_buf.ptr, envp, PATH); + return posixExecv(options.expand_arg0, argv_buf.ptr[0].?, argv_buf.ptr, env_block, PATH); } fn processReplacePath(userdata: ?*anyopaque, dir: Dir, options: process.ReplaceOptions) process.ReplaceError { @@ -14705,16 +14697,14 @@ fn spawnPosix(t: *Threaded, options: process.SpawnOptions) process.SpawnError!Sp const prog_fileno = 3; comptime assert(@max(posix.STDIN_FILENO, posix.STDOUT_FILENO, posix.STDERR_FILENO) + 1 == prog_fileno); - const envp: [*:null]const ?[*:0]const u8 = m: { + const env_block = env_block: { const prog_fd: i32 = if (prog_pipe[1] == -1) -1 else prog_fileno; - if (options.environ_map) |environ_map| { - break :m (try environ_map.createBlockPosix(arena, .{ - .zig_progress_fd = prog_fd, - })).ptr; - } - break :m (try process.Environ.createBlockPosix(t.environ.process_environ, arena, .{ + if (options.environ_map) |environ_map| break :env_block try environ_map.createPosixBlock(arena, .{ .zig_progress_fd = prog_fd, - })).ptr; + }); + break :env_block try t.environ.process_environ.createPosixBlock(arena, .{ + .zig_progress_fd = prog_fd, + }); }; // This pipe communicates to the parent errors in the child between `fork` and `execvpe`. @@ -14797,7 +14787,7 @@ fn spawnPosix(t: *Threaded, options: process.SpawnOptions) process.SpawnError!Sp } } - const err = posixExecv(options.expand_arg0, argv_buf.ptr[0].?, argv_buf.ptr, envp, PATH); + const err = posixExecv(options.expand_arg0, argv_buf.ptr[0].?, argv_buf.ptr, env_block, PATH); forkBail(ep1, err); } @@ -14811,7 +14801,6 @@ fn spawnPosix(t: *Threaded, options: process.SpawnOptions) process.SpawnError!Sp if (options.stderr == .pipe) posix.close(stderr_pipe[1]); if (prog_pipe[1] != -1) posix.close(prog_pipe[1]); - options.progress_node.setIpcFd(prog_pipe[0]); return .{ @@ -14935,42 +14924,44 @@ fn childKillWindows(t: *Threaded, child: *process.Child, exit_code: windows.UINT // some rare edge cases where our process handle no longer has the // PROCESS_TERMINATE access right, so let's do another check to make // sure the process is really no longer running: - windows.WaitForSingleObjectEx(handle, 0, false) catch return error.AccessDenied; - return error.AlreadyTerminated; + const minimal_timeout: windows.LARGE_INTEGER = -1; + switch (windows.ntdll.NtWaitForSingleObject(handle, windows.FALSE, &minimal_timeout)) { + .SUCCESS => return error.AlreadyTerminated, + else => return error.AccessDenied, + } }, else => |err| return windows.unexpectedError(err), } } - _ = windows.kernel32.WaitForSingleObjectEx(handle, windows.INFINITE, windows.FALSE); + const infinite_timeout: windows.LARGE_INTEGER = std.math.minInt(windows.LARGE_INTEGER); + _ = windows.ntdll.NtWaitForSingleObject(handle, windows.FALSE, &infinite_timeout); childCleanupWindows(child); } fn childWaitWindows(child: *process.Child) process.Child.WaitError!process.Child.Term { const handle = child.id.?; - const syscall: Syscall = try .start(); - while (true) switch (windows.kernel32.WaitForSingleObjectEx(handle, windows.INFINITE, windows.FALSE)) { - windows.WAIT_OBJECT_0 => break syscall.finish(), - windows.WAIT_ABANDONED, windows.WAIT_TIMEOUT => { - try syscall.checkCancel(); + const alertable_syscall: AlertableSyscall = try .start(); + const infinite_timeout: windows.LARGE_INTEGER = std.math.minInt(windows.LARGE_INTEGER); + while (true) switch (windows.ntdll.NtWaitForSingleObject(handle, windows.TRUE, &infinite_timeout)) { + windows.NTSTATUS.WAIT_0 => break alertable_syscall.finish(), + .USER_APC, .ALERTED, .TIMEOUT => { + try alertable_syscall.checkCancel(); continue; }, - windows.WAIT_FAILED => { - syscall.finish(); - switch (windows.GetLastError()) { - else => |err| return windows.unexpectedError(err), - } - }, - else => return syscall.fail(error.Unexpected), + else => |status| return alertable_syscall.unexpectedNtstatus(status), }; - const term: process.Child.Term = x: { - var exit_code: windows.DWORD = undefined; - if (windows.kernel32.GetExitCodeProcess(handle, &exit_code) == 0) { - break :x .{ .unknown = 0 }; - } else { - break :x .{ .exited = @as(u8, @truncate(exit_code)) }; - } + var info: windows.PROCESS_BASIC_INFORMATION = undefined; + const term: process.Child.Term = switch (windows.ntdll.NtQueryInformationProcess( + handle, + .BasicInformation, + &info, + @sizeOf(windows.PROCESS_BASIC_INFORMATION), + null, + )) { + .SUCCESS => .{ .exited = @as(u8, @truncate(@intFromEnum(info.ExitStatus))) }, + else => .{ .unknown = 0 }, }; childCleanupWindows(child); @@ -15233,88 +15224,70 @@ fn setUpChildIo(stdio: process.SpawnOptions.StdIo, pipe_fd: i32, std_fileno: i32 fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) process.SpawnError!process.Child { const t: *Threaded = @ptrCast(@alignCast(userdata)); - var saAttr: windows.SECURITY_ATTRIBUTES = .{ - .nLength = @sizeOf(windows.SECURITY_ATTRIBUTES), - .bInheritHandle = windows.TRUE, - .lpSecurityDescriptor = null, - }; - const any_ignore = options.stdin == .ignore or options.stdout == .ignore or options.stderr == .ignore; + const nul_handle = if (any_ignore) try getNulDevice(t) else undefined; - const nul_handle = if (any_ignore) try getNulHandle(t) else undefined; + const any_inherit = + options.stdin == .inherit or + options.stdout == .inherit or + options.stderr == .inherit; + const peb = if (any_inherit) windows.peb() else undefined; - var g_hChildStd_IN_Rd: ?windows.HANDLE = null; - var g_hChildStd_IN_Wr: ?windows.HANDLE = null; - switch (options.stdin) { - .pipe => { - try windowsMakePipeIn(&g_hChildStd_IN_Rd, &g_hChildStd_IN_Wr, &saAttr); - }, - .ignore => { - g_hChildStd_IN_Rd = nul_handle; - }, - .inherit => { - g_hChildStd_IN_Rd = windows.GetStdHandle(windows.STD_INPUT_HANDLE) catch null; - }, - .close => { - g_hChildStd_IN_Rd = null; - }, - .file => @panic("TODO implement passing file stdio in processSpawnWindows"), - } - errdefer if (options.stdin == .pipe) { - windowsDestroyPipe(g_hChildStd_IN_Rd, g_hChildStd_IN_Wr); - }; + const stdin_pipe = if (options.stdin == .pipe) try t.windowsCreatePipe(.{ + .server = .{ .attributes = .{ .INHERIT = false }, .mode = .{ .IO = .SYNCHRONOUS_NONALERT } }, + .client = .{ .attributes = .{ .INHERIT = true }, .mode = .{ .IO = .SYNCHRONOUS_NONALERT } }, + .outbound = true, + }) else undefined; + errdefer if (options.stdin == .pipe) for (stdin_pipe) |handle| windows.CloseHandle(handle); - var g_hChildStd_OUT_Rd: ?windows.HANDLE = null; - var g_hChildStd_OUT_Wr: ?windows.HANDLE = null; - switch (options.stdout) { - .pipe => { - try windowsMakeAsyncPipe(&g_hChildStd_OUT_Rd, &g_hChildStd_OUT_Wr, &saAttr); - }, - .ignore => { - g_hChildStd_OUT_Wr = nul_handle; - }, - .inherit => { - g_hChildStd_OUT_Wr = windows.GetStdHandle(windows.STD_OUTPUT_HANDLE) catch null; - }, - .close => { - g_hChildStd_OUT_Wr = null; - }, - .file => @panic("TODO implement passing file stdio in processSpawnWindows"), - } - errdefer if (options.stdout == .pipe) { - windowsDestroyPipe(g_hChildStd_OUT_Rd, g_hChildStd_OUT_Wr); - }; + const stdout_pipe = if (options.stdout == .pipe) try t.windowsCreatePipe(.{ + .server = .{ .attributes = .{ .INHERIT = false }, .mode = .{ .IO = .ASYNCHRONOUS } }, + .client = .{ .attributes = .{ .INHERIT = true }, .mode = .{ .IO = .SYNCHRONOUS_NONALERT } }, + .inbound = true, + }) else undefined; + errdefer if (options.stdout == .pipe) for (stdout_pipe) |handle| windows.CloseHandle(handle); - var g_hChildStd_ERR_Rd: ?windows.HANDLE = null; - var g_hChildStd_ERR_Wr: ?windows.HANDLE = null; - switch (options.stderr) { - .pipe => { - try windowsMakeAsyncPipe(&g_hChildStd_ERR_Rd, &g_hChildStd_ERR_Wr, &saAttr); - }, - .ignore => { - g_hChildStd_ERR_Wr = nul_handle; - }, - .inherit => { - g_hChildStd_ERR_Wr = windows.GetStdHandle(windows.STD_ERROR_HANDLE) catch null; - }, - .close => { - g_hChildStd_ERR_Wr = null; - }, - .file => @panic("TODO implement passing file stdio in processSpawnWindows"), - } - errdefer if (options.stderr == .pipe) { - windowsDestroyPipe(g_hChildStd_ERR_Rd, g_hChildStd_ERR_Wr); - }; + const stderr_pipe = if (options.stderr == .pipe) try t.windowsCreatePipe(.{ + .server = .{ .attributes = .{ .INHERIT = false }, .mode = .{ .IO = .ASYNCHRONOUS } }, + .client = .{ .attributes = .{ .INHERIT = true }, .mode = .{ .IO = .SYNCHRONOUS_NONALERT } }, + .inbound = true, + }) else undefined; + errdefer if (options.stderr == .pipe) for (stderr_pipe) |handle| windows.CloseHandle(handle); + + const prog_pipe = if (options.progress_node.index != .none) try t.windowsCreatePipe(.{ + .server = .{ .attributes = .{ .INHERIT = false }, .mode = .{ .IO = .ASYNCHRONOUS } }, + .client = .{ .attributes = .{ .INHERIT = true }, .mode = .{ .IO = .SYNCHRONOUS_NONALERT } }, + .inbound = true, + }) else undefined; + errdefer if (options.progress_node.index != .none) for (prog_pipe) |handle| windows.CloseHandle(handle); var siStartInfo: windows.STARTUPINFOW = .{ .cb = @sizeOf(windows.STARTUPINFOW), - .hStdError = g_hChildStd_ERR_Wr, - .hStdOutput = g_hChildStd_OUT_Wr, - .hStdInput = g_hChildStd_IN_Rd, .dwFlags = windows.STARTF_USESTDHANDLES, + .hStdInput = switch (options.stdin) { + .inherit => peb.ProcessParameters.hStdInput, + .file => |file| file.handle, + .ignore => nul_handle, + .pipe => stdin_pipe[1], + .close => null, + }, + .hStdOutput = switch (options.stdout) { + .inherit => peb.ProcessParameters.hStdOutput, + .file => |file| file.handle, + .ignore => nul_handle, + .pipe => stdout_pipe[1], + .close => null, + }, + .hStdError = switch (options.stderr) { + .inherit => peb.ProcessParameters.hStdError, + .file => |file| file.handle, + .ignore => nul_handle, + .pipe => stderr_pipe[1], + .close => null, + }, .lpReserved = null, .lpDesktop = null, @@ -15360,8 +15333,18 @@ fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) pro }; const cwd_w_ptr = if (cwd_w) |cwd| cwd.ptr else null; - const maybe_envp_buf = if (options.environ_map) |environ_map| try environ_map.createBlockWindows(arena) else null; - const envp_ptr = if (maybe_envp_buf) |envp_buf| envp_buf.ptr else null; + const env_block = env_block: { + const prog_handle = if (options.progress_node.index != .none) + prog_pipe[1] + else + windows.INVALID_HANDLE_VALUE; + if (options.environ_map) |environ_map| break :env_block try environ_map.createWindowsBlock(arena, .{ + .zig_progress_handle = prog_handle, + }); + break :env_block try t.environ.process_environ.createWindowsBlock(arena, .{ + .zig_progress_handle = if (options.progress_node.index != .none) prog_pipe[1] else windows.INVALID_HANDLE_VALUE, + }); + }; const app_name_wtf8 = options.argv[0]; const app_name_is_absolute = Dir.path.isAbsolute(app_name_wtf8); @@ -15436,7 +15419,7 @@ fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) pro &app_buf, PATHEXT, &cmd_line_cache, - envp_ptr, + env_block, cwd_w_ptr, flags, &siStartInfo, @@ -15471,7 +15454,7 @@ fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) pro &app_buf, PATHEXT, &cmd_line_cache, - envp_ptr, + env_block, cwd_w_ptr, flags, &siStartInfo, @@ -15491,21 +15474,40 @@ fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) pro }; } - if (options.stdin == .pipe) windows.CloseHandle(g_hChildStd_IN_Rd.?); - if (options.stderr == .pipe) windows.CloseHandle(g_hChildStd_ERR_Wr.?); - if (options.stdout == .pipe) windows.CloseHandle(g_hChildStd_OUT_Wr.?); + if (options.progress_node.index != .none) { + windows.CloseHandle(prog_pipe[1]); + options.progress_node.setIpcFd(prog_pipe[0]); + } return .{ .id = piProcInfo.hProcess, .thread_handle = piProcInfo.hThread, - .stdin = if (g_hChildStd_IN_Wr) |h| .{ .handle = h, .flags = .{ .nonblocking = false } } else null, - .stdout = if (g_hChildStd_OUT_Rd) |h| .{ .handle = h, .flags = .{ .nonblocking = true } } else null, - .stderr = if (g_hChildStd_ERR_Rd) |h| .{ .handle = h, .flags = .{ .nonblocking = true } } else null, + .stdin = stdin: switch (options.stdin) { + .pipe => { + windows.CloseHandle(stdin_pipe[1]); + break :stdin .{ .handle = stdin_pipe[0], .flags = .{ .nonblocking = false } }; + }, + else => null, + }, + .stdout = stdout: switch (options.stdout) { + .pipe => { + windows.CloseHandle(stdout_pipe[1]); + break :stdout .{ .handle = stdout_pipe[0], .flags = .{ .nonblocking = true } }; + }, + else => null, + }, + .stderr = stderr: switch (options.stderr) { + .pipe => { + windows.CloseHandle(stderr_pipe[1]); + break :stderr .{ .handle = stderr_pipe[0], .flags = .{ .nonblocking = true } }; + }, + else => null, + }, .request_resource_usage_statistics = options.request_resource_usage_statistics, }; } -fn getCngHandle(t: *Threaded) Io.RandomSecureError!windows.HANDLE { +fn getCngDevice(t: *Threaded) Io.RandomSecureError!windows.HANDLE { { mutexLock(&t.mutex); defer mutexUnlock(&t.mutex); @@ -15513,12 +15515,6 @@ fn getCngHandle(t: *Threaded) Io.RandomSecureError!windows.HANDLE { } const device_path = [_]u16{ '\\', 'D', 'e', 'v', 'i', 'c', 'e', '\\', 'C', 'N', 'G' }; - - var nt_name: windows.UNICODE_STRING = .{ - .Length = device_path.len * 2, - .MaximumLength = 0, - .Buffer = @constCast(&device_path), - }; var fresh_handle: windows.HANDLE = undefined; var io_status_block: windows.IO_STATUS_BLOCK = undefined; var syscall: Syscall = try .start(); @@ -15529,12 +15525,11 @@ fn getCngHandle(t: *Threaded) Io.RandomSecureError!windows.HANDLE { .SPECIFIC = .{ .FILE = .{ .READ_DATA = true } }, }, &.{ - .Length = @sizeOf(windows.OBJECT_ATTRIBUTES), - .RootDirectory = null, - .ObjectName = &nt_name, - .Attributes = .{}, - .SecurityDescriptor = null, - .SecurityQualityOfService = null, + .ObjectName = @constCast(&windows.UNICODE_STRING{ + .Length = @sizeOf(@TypeOf(device_path)), + .MaximumLength = 0, + .Buffer = @constCast(&device_path), + }), }, &io_status_block, .VALID_FLAGS, @@ -15561,7 +15556,7 @@ fn getCngHandle(t: *Threaded) Io.RandomSecureError!windows.HANDLE { }; } -fn getNulHandle(t: *Threaded) !windows.HANDLE { +fn getNulDevice(t: *Threaded) !windows.HANDLE { { mutexLock(&t.mutex); defer mutexUnlock(&t.mutex); @@ -15569,44 +15564,26 @@ fn getNulHandle(t: *Threaded) !windows.HANDLE { } const device_path = [_]u16{ '\\', 'D', 'e', 'v', 'i', 'c', 'e', '\\', 'N', 'u', 'l', 'l' }; - var nt_name: windows.UNICODE_STRING = .{ - .Length = device_path.len * 2, - .MaximumLength = 0, - .Buffer = @constCast(&device_path), - }; - const attr: windows.OBJECT_ATTRIBUTES = .{ - .Length = @sizeOf(windows.OBJECT_ATTRIBUTES), - .RootDirectory = null, - .Attributes = .{ - .INHERIT = true, - }, - .ObjectName = &nt_name, - .SecurityDescriptor = null, - .SecurityQualityOfService = null, - }; - var io_status_block: windows.IO_STATUS_BLOCK = undefined; var fresh_handle: windows.HANDLE = undefined; + var io_status_block: windows.IO_STATUS_BLOCK = undefined; var syscall: Syscall = try .start(); - while (true) switch (windows.ntdll.NtCreateFile( + while (true) switch (windows.ntdll.NtOpenFile( &fresh_handle, .{ .STANDARD = .{ .SYNCHRONIZE = true }, - .GENERIC = .{ .WRITE = true, .READ = true }, + .SPECIFIC = .{ .FILE = .{ .READ_DATA = true, .WRITE_DATA = true } }, + }, + &.{ + .Attributes = .{ .INHERIT = true }, + .ObjectName = @constCast(&windows.UNICODE_STRING{ + .Length = @sizeOf(@TypeOf(device_path)), + .MaximumLength = 0, + .Buffer = @constCast(&device_path), + }), }, - &attr, &io_status_block, - null, - .{ .NORMAL = true }, .VALID_FLAGS, - .OPEN, - .{ - .DIRECTORY_FILE = false, - .NON_DIRECTORY_FILE = true, - .IO = .SYNCHRONOUS_NONALERT, - .OPEN_REPARSE_POINT = false, - }, - null, - 0, + .{ .IO = .SYNCHRONOUS_NONALERT }, )) { .SUCCESS => { syscall.finish(); @@ -15620,6 +15597,64 @@ fn getNulHandle(t: *Threaded) !windows.HANDLE { return fresh_handle; } }, + .CANCELLED => { + try syscall.checkCancel(); + continue; + }, + .INVALID_PARAMETER => |status| return syscall.ntstatusBug(status), + .OBJECT_PATH_SYNTAX_BAD => |status| return syscall.ntstatusBug(status), + .INVALID_HANDLE => |status| return syscall.ntstatusBug(status), + .OBJECT_NAME_INVALID => return syscall.fail(error.BadPathName), + .OBJECT_NAME_NOT_FOUND => return syscall.fail(error.FileNotFound), + .OBJECT_PATH_NOT_FOUND => return syscall.fail(error.FileNotFound), + .NO_MEDIA_IN_DEVICE => return syscall.fail(error.NoDevice), + .SHARING_VIOLATION => return syscall.fail(error.AccessDenied), + .ACCESS_DENIED => return syscall.fail(error.AccessDenied), + .PIPE_NOT_AVAILABLE => return syscall.fail(error.NoDevice), + .FILE_IS_A_DIRECTORY => return syscall.fail(error.IsDir), + .NOT_A_DIRECTORY => return syscall.fail(error.NotDir), + .USER_MAPPED_FILE => return syscall.fail(error.AccessDenied), + else => |status| return syscall.unexpectedNtstatus(status), + }; +} + +fn getNamedPipeDevice(t: *Threaded) !windows.HANDLE { + { + mutexLock(&t.mutex); + defer mutexUnlock(&t.mutex); + if (t.pipe_file.handle) |handle| return handle; + } + + const device_path = [_]u16{ '\\', 'D', 'e', 'v', 'i', 'c', 'e', '\\', 'N', 'a', 'm', 'e', 'd', 'P', 'i', 'p', 'e', '\\' }; + var fresh_handle: windows.HANDLE = undefined; + var io_status_block: windows.IO_STATUS_BLOCK = undefined; + var syscall: Syscall = try .start(); + while (true) switch (windows.ntdll.NtOpenFile( + &fresh_handle, + .{ .STANDARD = .{ .SYNCHRONIZE = true } }, + &.{ + .ObjectName = @constCast(&windows.UNICODE_STRING{ + .Length = @sizeOf(@TypeOf(device_path)), + .MaximumLength = 0, + .Buffer = @constCast(&device_path), + }), + }, + &io_status_block, + .VALID_FLAGS, + .{ .IO = .SYNCHRONOUS_NONALERT }, + )) { + .SUCCESS => { + syscall.finish(); + mutexLock(&t.mutex); // Another thread might have won the race. + defer mutexUnlock(&t.mutex); + if (t.pipe_file.handle) |prev_handle| { + windows.CloseHandle(fresh_handle); + return prev_handle; + } else { + t.pipe_file.handle = fresh_handle; + return fresh_handle; + } + }, .DELETE_PENDING => { // This error means that there *was* a file in this location on // the file system, but it was deleted. However, the OS is not @@ -15666,7 +15701,7 @@ fn windowsCreateProcessPathExt( app_buf: *std.ArrayList(u16), pathext: [:0]const u16, cmd_line_cache: *WindowsCommandLineCache, - envp_ptr: ?[*:0]const u16, + env_block: ?process.Environ.WindowsBlock, cwd_ptr: ?[*:0]u16, flags: windows.CreateProcessFlags, lpStartupInfo: *windows.STARTUPINFOW, @@ -15843,7 +15878,7 @@ fn windowsCreateProcessPathExt( if (windowsCreateProcess( app_name_w.ptr, cmd_line_w.ptr, - envp_ptr, + env_block, cwd_ptr, flags, lpStartupInfo, @@ -15903,7 +15938,7 @@ fn windowsCreateProcessPathExt( else full_app_name; - if (windowsCreateProcess(app_name_w.ptr, cmd_line_w.ptr, envp_ptr, cwd_ptr, flags, lpStartupInfo, lpProcessInformation)) |_| { + if (windowsCreateProcess(app_name_w.ptr, cmd_line_w.ptr, env_block, cwd_ptr, flags, lpStartupInfo, lpProcessInformation)) |_| { return; } else |err| switch (err) { error.FileNotFound => continue, @@ -15927,7 +15962,7 @@ fn windowsCreateProcessPathExt( fn windowsCreateProcess( app_name: [*:0]u16, cmd_line: [*:0]u16, - env_ptr: ?[*:0]const u16, + env_block: ?process.Environ.WindowsBlock, cwd_ptr: ?[*:0]u16, flags: windows.CreateProcessFlags, lpStartupInfo: *windows.STARTUPINFOW, @@ -15942,7 +15977,7 @@ fn windowsCreateProcess( null, windows.TRUE, flags, - env_ptr, + if (env_block) |block| block.slice.ptr else null, cwd_ptr, lpStartupInfo, lpProcessInformation, @@ -16463,11 +16498,11 @@ fn posixExecv( arg0_expand: process.ArgExpansion, file: [*:0]const u8, child_argv: [*:null]?[*:0]const u8, - envp: [*:null]const ?[*:0]const u8, + env_block: process.Environ.PosixBlock, PATH: []const u8, ) process.ReplaceError { const file_slice = std.mem.sliceTo(file, 0); - if (std.mem.findScalar(u8, file_slice, '/') != null) return posixExecvPath(file, child_argv, envp); + if (std.mem.findScalar(u8, file_slice, '/') != null) return posixExecvPath(file, child_argv, env_block); // Use of PATH_MAX here is valid as the path_buf will be passed // directly to the operating system in posixExecvPath. @@ -16495,7 +16530,7 @@ fn posixExecv( .expand => child_argv[0] = full_path, .no_expand => {}, } - err = posixExecvPath(full_path, child_argv, envp); + err = posixExecvPath(full_path, child_argv, env_block); switch (err) { error.AccessDenied => seen_eacces = true, error.FileNotFound, error.NotDir => {}, @@ -16510,10 +16545,10 @@ fn posixExecv( pub fn posixExecvPath( path: [*:0]const u8, child_argv: [*:null]const ?[*:0]const u8, - envp: [*:null]const ?[*:0]const u8, + env_block: process.Environ.PosixBlock, ) process.ReplaceError { try Thread.checkCancel(); - switch (posix.errno(posix.system.execve(path, child_argv, envp))) { + switch (posix.errno(posix.system.execve(path, child_argv, env_block.slice.ptr))) { .FAULT => |err| return errnoBug(err), // Bad pointer parameter. .@"2BIG" => return error.SystemResources, .MFILE => return error.ProcessFdQuotaExceeded, @@ -16545,100 +16580,105 @@ pub fn posixExecvPath( } } -fn windowsMakePipeIn(rd: *?windows.HANDLE, wr: *?windows.HANDLE, sattr: *const windows.SECURITY_ATTRIBUTES) !void { - var rd_h: windows.HANDLE = undefined; - var wr_h: windows.HANDLE = undefined; - try windows.CreatePipe(&rd_h, &wr_h, sattr); - errdefer windowsDestroyPipe(rd_h, wr_h); - try windows.SetHandleInformation(wr_h, windows.HANDLE_FLAG_INHERIT, 0); - rd.* = rd_h; - wr.* = wr_h; -} - -fn windowsDestroyPipe(rd: ?windows.HANDLE, wr: ?windows.HANDLE) void { - if (rd) |h| posix.close(h); - if (wr) |h| posix.close(h); -} +pub const CreatePipeOptions = struct { + server: End, + client: End, + inbound: bool = false, + outbound: bool = false, + maximum_instances: u32 = 1, + quota: u32 = 4096, + default_timeout: windows.LARGE_INTEGER = -120 * std.time.ns_per_s / 100, -fn windowsMakeAsyncPipe(rd: *?windows.HANDLE, wr: *?windows.HANDLE, sattr: *const windows.SECURITY_ATTRIBUTES) !void { - var tmp_bufw: [128]u16 = undefined; - - // Anonymous pipes are built upon Named pipes. - // https://docs.microsoft.com/en-us/windows/win32/api/namedpipeapi/nf-namedpipeapi-createpipe - // Asynchronous (overlapped) read and write operations are not supported by anonymous pipes. - // https://docs.microsoft.com/en-us/windows/win32/ipc/anonymous-pipe-operations - const pipe_path = blk: { - var tmp_buf: [128]u8 = undefined; - // Forge a random path for the pipe. - const pipe_path = std.fmt.bufPrintSentinel( - &tmp_buf, - "\\\\.\\pipe\\zig-childprocess-{d}-{d}", - .{ windows.GetCurrentProcessId(), pipe_name_counter.fetchAdd(1, .monotonic) }, - 0, - ) catch unreachable; - const len = std.unicode.wtf8ToWtf16Le(&tmp_bufw, pipe_path) catch unreachable; - tmp_bufw[len] = 0; - break :blk tmp_bufw[0..len :0]; + pub const End = struct { + attributes: windows.OBJECT_ATTRIBUTES.ATTRIBUTES = .{}, + mode: windows.FILE.MODE, }; - - // Create the read handle that can be used with overlapped IO ops. - const read_handle = windows.kernel32.CreateNamedPipeW( - pipe_path.ptr, - windows.PIPE_ACCESS_INBOUND | windows.FILE_FLAG_OVERLAPPED, - windows.PIPE_TYPE_BYTE, - 1, - 4096, - 4096, - 0, - sattr, - ); - if (read_handle == windows.INVALID_HANDLE_VALUE) { - switch (windows.GetLastError()) { - else => |err| return windows.unexpectedError(err), - } - } - errdefer posix.close(read_handle); - - var sattr_copy = sattr.*; - const write_handle = windows.kernel32.CreateFileW( - pipe_path.ptr, - .{ .GENERIC = .{ .WRITE = true } }, - 0, - &sattr_copy, - windows.OPEN_EXISTING, - @bitCast(windows.FILE.ATTRIBUTE{ .NORMAL = true }), - null, - ); - if (write_handle == windows.INVALID_HANDLE_VALUE) { - switch (windows.GetLastError()) { - else => |err| return windows.unexpectedError(err), - } - } - errdefer posix.close(write_handle); - - try windows.SetHandleInformation(read_handle, windows.HANDLE_FLAG_INHERIT, 0); - - rd.* = read_handle; - wr.* = write_handle; +}; +pub fn windowsCreatePipe(t: *Threaded, options: CreatePipeOptions) ![2]windows.HANDLE { + const named_pipe_device = try t.getNamedPipeDevice(); + const server_handle = server_handle: { + var handle: windows.HANDLE = undefined; + var io_status_block: windows.IO_STATUS_BLOCK = undefined; + const syscall: Syscall = try .start(); + while (true) switch (windows.ntdll.NtCreateNamedPipeFile( + &handle, + .{ + .SPECIFIC = .{ .FILE_PIPE = .{ + .READ_DATA = options.inbound, + .WRITE_DATA = options.outbound, + .WRITE_ATTRIBUTES = true, + } }, + .STANDARD = .{ .SYNCHRONIZE = true }, + }, + &.{ + .RootDirectory = named_pipe_device, + .Attributes = options.server.attributes, + }, + &io_status_block, + .{ .READ = true, .WRITE = true }, + .CREATE, + options.server.mode, + .{ .TYPE = .BYTE_STREAM }, + .{ .MODE = .BYTE_STREAM }, + .{ .OPERATION = .QUEUE }, + options.maximum_instances, + if (options.inbound) options.quota else 0, + if (options.outbound) options.quota else 0, + &options.default_timeout, + )) { + .SUCCESS => break syscall.finish(), + .CANCELLED => { + try syscall.checkCancel(); + continue; + }, + .INVALID_PARAMETER => |status| return syscall.ntstatusBug(status), + .INSUFFICIENT_RESOURCES => return syscall.fail(error.SystemResources), + else => |status| return syscall.unexpectedNtstatus(status), + }; + break :server_handle handle; + }; + errdefer windows.CloseHandle(server_handle); + const client_handle = client_handle: { + var handle: windows.HANDLE = undefined; + var io_status_block: windows.IO_STATUS_BLOCK = undefined; + const syscall: Syscall = try .start(); + while (true) switch (windows.ntdll.NtOpenFile( + &handle, + .{ + .SPECIFIC = .{ .FILE_PIPE = .{ + .READ_DATA = options.outbound, + .WRITE_DATA = options.inbound, + .WRITE_ATTRIBUTES = true, + } }, + .STANDARD = .{ .SYNCHRONIZE = true }, + }, + &.{ + .RootDirectory = server_handle, + .Attributes = options.client.attributes, + }, + &io_status_block, + .{ .READ = true, .WRITE = true }, + options.client.mode, + )) { + .SUCCESS => break syscall.finish(), + .CANCELLED => { + try syscall.checkCancel(); + continue; + }, + .INVALID_PARAMETER => |status| return syscall.ntstatusBug(status), + .INSUFFICIENT_RESOURCES => return syscall.fail(error.SystemResources), + else => |status| return syscall.unexpectedNtstatus(status), + }; + break :client_handle handle; + }; + errdefer windows.CloseHandle(client_handle); + return .{ server_handle, client_handle }; } -var pipe_name_counter = std.atomic.Value(u32).init(1); - fn progressParentFile(userdata: ?*anyopaque) std.Progress.ParentFileError!File { const t: *Threaded = @ptrCast(@alignCast(userdata)); - t.scanEnviron(); - - const int = try t.environ.zig_progress_handle; - - return .{ - .handle = switch (@typeInfo(Io.File.Handle)) { - .int => int, - .pointer => @ptrFromInt(int), - else => return error.UnsupportedOperation, - }, - .flags = .{ .nonblocking = false }, - }; + return t.environ.zig_progress_file; } pub fn environString(t: *Threaded, comptime name: []const u8) ?[:0]const u8 { @@ -16734,7 +16774,7 @@ fn randomSecure(userdata: ?*anyopaque, buffer: []u8) Io.RandomSecureError!void { // despite the function being documented to always return TRUE // * reads from "\\Device\\CNG" which then seeds a per-CPU AES CSPRNG // Therefore, that function is avoided in favor of using the device directly. - const cng_device = try getCngHandle(t); + const cng_device = try getCngDevice(t); var io_status_block: windows.IO_STATUS_BLOCK = undefined; var i: usize = 0; const syscall: Syscall = try .start(); diff --git a/lib/std/Io/Threaded/test.zig b/lib/std/Io/Threaded/test.zig index 593580d1f62511b7ce1d73248cc50a0bca5a45a2..81c7be9170e8479c231e5f30931b38de39723f20 100644 --- a/lib/std/Io/Threaded/test.zig +++ b/lib/std/Io/Threaded/test.zig @@ -181,13 +181,17 @@ test "cancel blocked read from pipe" { var write_end: Io.File = undefined; switch (builtin.target.os.tag) { .wasi => return error.SkipZigTest, - .windows => try std.os.windows.CreatePipe(&read_end.handle, &write_end.handle, &.{ - .nLength = @sizeOf(std.os.windows.SECURITY_ATTRIBUTES), - .lpSecurityDescriptor = null, - .bInheritHandle = std.os.windows.FALSE, - }), + .windows => { + const pipe = try threaded.windowsCreatePipe(.{ + .server = .{ .mode = .{ .IO = .SYNCHRONOUS_NONALERT } }, + .client = .{ .mode = .{ .IO = .SYNCHRONOUS_NONALERT } }, + .inbound = true, + }); + read_end = .{ .handle = pipe[0], .flags = .{ .nonblocking = false } }; + write_end = .{ .handle = pipe[1], .flags = .{ .nonblocking = false } }; + }, else => { - const pipe = try std.Io.Threaded.pipe2(.{}); + const pipe = try std.Io.Threaded.pipe2(.{ .CLOEXEC = true }); read_end = .{ .handle = pipe[0], .flags = .{ .nonblocking = false } }; write_end = .{ .handle = pipe[1], .flags = .{ .nonblocking = false } }; }, diff --git a/lib/std/Progress.zig b/lib/std/Progress.zig index e2f3ed52322ef30a89747bbbd0c394c4bf27389d..780f27ec75a89b11c414a2463c3ff68fb9041b57 100644 --- a/lib/std/Progress.zig +++ b/lib/std/Progress.zig @@ -139,7 +139,7 @@ pub const Node = struct { fn setIpcFd(s: *Storage, fd: Io.File.Handle) void { const integer: u32 = switch (@typeInfo(Io.File.Handle)) { .int => @bitCast(fd), - .pointer => @intFromPtr(fd), + .pointer => @intCast(@intFromPtr(fd)), else => @compileError("unsupported fd_t of " ++ @typeName(Io.File.Handle)), }; // `estimated_total_count` max int indicates the special state that @@ -342,10 +342,18 @@ pub const Node = struct { /// Posix-only. Used by `std.process.Child`. Thread-safe. pub fn setIpcFd(node: Node, fd: Io.File.Handle) void { const index = node.index.unwrap() orelse return; - assert(fd >= 0); - assert(fd != posix.STDOUT_FILENO); - assert(fd != posix.STDIN_FILENO); - assert(fd != posix.STDERR_FILENO); + switch (@typeInfo(Io.File.Handle)) { + .int => { + assert(fd >= 0); + assert(fd != posix.STDOUT_FILENO); + assert(fd != posix.STDIN_FILENO); + assert(fd != posix.STDERR_FILENO); + }, + .pointer => { + assert(fd != windows.INVALID_HANDLE_VALUE); + }, + else => @compileError("unsupported fd_t of " ++ @typeName(Io.File.Handle)), + } storageByIndex(index).setIpcFd(fd); } @@ -477,21 +485,18 @@ pub fn start(io: Io, options: Options) Node { global_progress.refresh_rate_ns = @intCast(options.refresh_rate_ns.toNanoseconds()); global_progress.initial_delay_ns = @intCast(options.initial_delay_ns.toNanoseconds()); - if (noop_impl) - return Node.none; + if (noop_impl) return .none; global_progress.io = io; if (io.vtable.progressParentFile(io.userdata)) |ipc_file| { global_progress.update_worker = io.concurrent(ipcThreadRun, .{ io, ipc_file }) catch |err| { global_progress.start_failure = .{ .spawn_ipc_worker = err }; - return Node.none; + return .none; }; } else |env_err| switch (env_err) { error.EnvironmentVariableMissing => { - if (options.disable_printing) { - return Node.none; - } + if (options.disable_printing) return .none; const stderr: Io.File = .stderr(); global_progress.terminal = stderr; if (stderr.enableAnsiEscapeCodes(io)) |_| { @@ -504,14 +509,12 @@ pub fn start(io: Io, options: Options) Node { } else |err| switch (err) { error.Canceled => { io.recancel(); - return Node.none; + return .none; }, } } - if (global_progress.terminal_mode == .off) { - return Node.none; - } + if (global_progress.terminal_mode == .off) return .none; if (have_sigwinch) { const act: posix.Sigaction = .{ @@ -530,12 +533,12 @@ pub fn start(io: Io, options: Options) Node { global_progress.update_worker = future; } else |err| { global_progress.start_failure = .{ .spawn_update_worker = err }; - return Node.none; + return .none; } }, else => |e| { global_progress.start_failure = .{ .parent_ipc = e }; - return Node.none; + return .none; }, } diff --git a/lib/std/Thread.zig b/lib/std/Thread.zig index 4d0e992e975b847ae0332172a1505f2924a9d906..131f57dcf860d59d89b5cec88aa491bbf06fc719 100644 --- a/lib/std/Thread.zig +++ b/lib/std/Thread.zig @@ -598,7 +598,11 @@ const WindowsThreadImpl = struct { } fn join(self: Impl) void { - windows.WaitForSingleObjectEx(self.thread.thread_handle, windows.INFINITE, false) catch unreachable; + const infinite_timeout: windows.LARGE_INTEGER = std.math.minInt(windows.LARGE_INTEGER); + switch (windows.ntdll.NtWaitForSingleObject(self.thread.thread_handle, windows.FALSE, &infinite_timeout)) { + windows.NTSTATUS.WAIT_0 => {}, + else => |status| windows.unexpectedStatus(status) catch unreachable, + } windows.CloseHandle(self.thread.thread_handle); assert(self.thread.completion.load(.seq_cst) == .completed); self.thread.free(); diff --git a/lib/std/mem/Allocator.zig b/lib/std/mem/Allocator.zig index 72581236e6dffbcb3104024d714af5ec03a2a672..db1ea978eaf21d31fa846db8dc591fb9749f746d 100644 --- a/lib/std/mem/Allocator.zig +++ b/lib/std/mem/Allocator.zig @@ -452,12 +452,23 @@ pub fn dupe(allocator: Allocator, comptime T: type, m: []const T) Error![]T { return new_buf; } +/// Deprecated in favor of `dupeSentinel` /// Copies `m` to newly allocated memory, with a null-terminated element. Caller owns the memory. pub fn dupeZ(allocator: Allocator, comptime T: type, m: []const T) Error![:0]T { + return allocator.dupeSentinel(T, m, 0); +} + +/// Copies `m` to newly allocated memory, with a null-terminated element. Caller owns the memory. +pub fn dupeSentinel( + allocator: Allocator, + comptime T: type, + m: []const T, + comptime sentinel: T, +) Error![:sentinel]T { const new_buf = try allocator.alloc(T, m.len + 1); @memcpy(new_buf[0..m.len], m); - new_buf[m.len] = 0; - return new_buf[0..m.len :0]; + new_buf[m.len] = sentinel; + return new_buf[0..m.len :sentinel]; } /// An allocator that always fails to allocate. diff --git a/lib/std/os/windows.zig b/lib/std/os/windows.zig index 0f57eed766eba130ece4bafc3b76e5684de2239b..dcb9087f136c664b9133e03c0b71458ddd8ed9c3 100644 --- a/lib/std/os/windows.zig +++ b/lib/std/os/windows.zig @@ -521,7 +521,7 @@ pub const FILE = struct { _, pub const VALID_FLAGS: @This() = @enumFromInt(0b11); - } = .ASYNCHRONOUS, + }, /// The file being opened must not be a directory file or this call /// fails. The file object being opened can represent a data file, a /// logical, virtual, or physical device, or a volume. @@ -2324,12 +2324,12 @@ pub fn GetProcessHeap() ?*HEAP { // ref: um/winternl.h pub const OBJECT_ATTRIBUTES = extern struct { - Length: ULONG, - RootDirectory: ?HANDLE, - ObjectName: ?*UNICODE_STRING, - Attributes: ATTRIBUTES, - SecurityDescriptor: ?*anyopaque, - SecurityQualityOfService: ?*anyopaque, + Length: ULONG = @sizeOf(OBJECT_ATTRIBUTES), + RootDirectory: ?HANDLE = null, + ObjectName: ?*UNICODE_STRING = @constCast(&UNICODE_STRING.empty), + Attributes: ATTRIBUTES = .{}, + SecurityDescriptor: ?*anyopaque = null, + SecurityQualityOfService: ?*anyopaque = null, // Valid values for the Attributes field pub const ATTRIBUTES = packed struct(ULONG) { @@ -2420,14 +2420,10 @@ pub fn OpenFile(sub_path_w: []const u16, options: OpenFileOptions) OpenError!HAN .Buffer = @constCast(sub_path_w.ptr), }; const attr: OBJECT_ATTRIBUTES = .{ - .Length = @sizeOf(OBJECT_ATTRIBUTES), .RootDirectory = if (std.fs.path.isAbsoluteWindowsWtf16(sub_path_w)) null else options.dir, - .Attributes = .{ - .INHERIT = if (options.sa) |sa| sa.bInheritHandle != FALSE else false, - }, + .Attributes = .{ .INHERIT = if (options.sa) |sa| sa.bInheritHandle != FALSE else false }, .ObjectName = &nt_name, .SecurityDescriptor = if (options.sa) |ptr| ptr.lpSecurityDescriptor else null, - .SecurityQualityOfService = null, }; var io: IO_STATUS_BLOCK = undefined; while (true) { @@ -2475,7 +2471,8 @@ pub fn OpenFile(sub_path_w: []const u16, options: OpenFileOptions) OpenError!HAN // call has failed. There is not really a sane way to handle // this other than retrying the creation after the OS finishes // the deletion. - _ = kernel32.SleepEx(1, TRUE); + const delay_one_ms: LARGE_INTEGER = -(std.time.ns_per_ms / 100); + _ = ntdll.NtDelayExecution(TRUE, &delay_one_ms); continue; }, .VIRUS_INFECTED, .VIRUS_DELETED => return error.AntivirusInterference, @@ -2506,151 +2503,6 @@ pub fn GetCurrentThreadId() DWORD { pub fn GetLastError() Win32Error { return @enumFromInt(teb().LastErrorValue); } - -pub const CreatePipeError = error{ Unexpected, SystemResources }; - -var npfs: ?HANDLE = null; - -/// A Zig wrapper around `NtCreateNamedPipeFile` and `NtCreateFile` syscalls. -/// It implements similar behavior to `CreatePipe` and is meant to serve -/// as a direct substitute for that call. -pub fn CreatePipe(rd: *HANDLE, wr: *HANDLE, sattr: *const SECURITY_ATTRIBUTES) CreatePipeError!void { - // Up to NT 5.2 (Windows XP/Server 2003), `CreatePipe` would generate a pipe similar to: - // - // \??\pipe\Win32Pipes.{pid}.{count} - // - // where `pid` is the process id and count is a incrementing counter. - // The implementation was changed after NT 6.0 (Vista) to open a handle to the Named Pipe File System - // and use that as the root directory for `NtCreateNamedPipeFile`. - // This object is visible under the NPFS but has no filename attached to it. - // - // This implementation replicates how `CreatePipe` works in modern Windows versions. - const opt_dev_handle = @atomicLoad(?HANDLE, &npfs, .seq_cst); - const dev_handle = opt_dev_handle orelse blk: { - const str = std.unicode.utf8ToUtf16LeStringLiteral("\\Device\\NamedPipe\\"); - const len: u16 = @truncate(str.len * @sizeOf(u16)); - const name: UNICODE_STRING = .{ - .Length = len, - .MaximumLength = len, - .Buffer = @ptrCast(@constCast(str)), - }; - const attrs: OBJECT_ATTRIBUTES = .{ - .ObjectName = @constCast(&name), - .Length = @sizeOf(OBJECT_ATTRIBUTES), - .RootDirectory = null, - .Attributes = .{}, - .SecurityDescriptor = null, - .SecurityQualityOfService = null, - }; - - var iosb: IO_STATUS_BLOCK = undefined; - var handle: HANDLE = undefined; - switch (ntdll.NtCreateFile( - &handle, - .{ - .STANDARD = .{ .SYNCHRONIZE = true }, - .GENERIC = .{ .READ = true }, - }, - @constCast(&attrs), - &iosb, - null, - .{}, - .VALID_FLAGS, - .OPEN, - .{ .IO = .SYNCHRONOUS_NONALERT }, - null, - 0, - )) { - .SUCCESS => {}, - // Judging from the ReactOS sources this is technically possible. - .INSUFFICIENT_RESOURCES => return error.SystemResources, - .INVALID_PARAMETER => unreachable, - else => |e| return unexpectedStatus(e), - } - if (@cmpxchgStrong(?HANDLE, &npfs, null, handle, .seq_cst, .seq_cst)) |xchg| { - CloseHandle(handle); - break :blk xchg.?; - } else break :blk handle; - }; - - const name: UNICODE_STRING = .{ .Buffer = null, .Length = 0, .MaximumLength = 0 }; - var attrs: OBJECT_ATTRIBUTES = .{ - .ObjectName = @constCast(&name), - .Length = @sizeOf(OBJECT_ATTRIBUTES), - .RootDirectory = dev_handle, - .Attributes = .{ .INHERIT = sattr.bInheritHandle != FALSE }, - .SecurityDescriptor = sattr.lpSecurityDescriptor, - .SecurityQualityOfService = null, - }; - - // 120 second relative timeout in 100ns units. - const default_timeout: LARGE_INTEGER = (-120 * std.time.ns_per_s) / 100; - var iosb: IO_STATUS_BLOCK = undefined; - var read: HANDLE = undefined; - switch (ntdll.NtCreateNamedPipeFile( - &read, - .{ - .SPECIFIC = .{ .FILE_PIPE = .{ - .WRITE_ATTRIBUTES = true, - } }, - .STANDARD = .{ .SYNCHRONIZE = true }, - .GENERIC = .{ .READ = true }, - }, - &attrs, - &iosb, - .{ .READ = true, .WRITE = true }, - .CREATE, - .{ .IO = .SYNCHRONOUS_NONALERT }, - .{ .TYPE = .BYTE_STREAM }, - .{ .MODE = .BYTE_STREAM }, - .{ .OPERATION = .QUEUE }, - 1, - 4096, - 4096, - @constCast(&default_timeout), - )) { - .SUCCESS => {}, - .INVALID_PARAMETER => unreachable, - .INSUFFICIENT_RESOURCES => return error.SystemResources, - else => |e| return unexpectedStatus(e), - } - errdefer CloseHandle(read); - - attrs.RootDirectory = read; - - var write: HANDLE = undefined; - switch (ntdll.NtCreateFile( - &write, - .{ - .SPECIFIC = .{ .FILE_PIPE = .{ - .READ_ATTRIBUTES = true, - } }, - .STANDARD = .{ .SYNCHRONIZE = true }, - .GENERIC = .{ .WRITE = true }, - }, - &attrs, - &iosb, - null, - .{}, - .VALID_FLAGS, - .OPEN, - .{ - .IO = .SYNCHRONOUS_NONALERT, - .NON_DIRECTORY_FILE = true, - }, - null, - 0, - )) { - .SUCCESS => {}, - .INVALID_PARAMETER => unreachable, - .INSUFFICIENT_RESOURCES => return error.SystemResources, - else => |e| return unexpectedStatus(e), - } - - rd.* = read; - wr.* = write; -} - /// A Zig wrapper around `NtDeviceIoControlFile` and `NtFsControlFile` syscalls. /// It implements similar behavior to `DeviceIoControl` and is meant to serve /// as a direct substitute for that call. @@ -2707,66 +2559,6 @@ pub fn GetOverlappedResult(h: HANDLE, overlapped: *OVERLAPPED, wait: bool) !DWOR return bytes; } -pub const SetHandleInformationError = error{Unexpected}; - -pub fn SetHandleInformation(h: HANDLE, mask: DWORD, flags: DWORD) SetHandleInformationError!void { - if (kernel32.SetHandleInformation(h, mask, flags) == 0) { - switch (GetLastError()) { - else => |err| return unexpectedError(err), - } - } -} - -pub const WaitForSingleObjectError = error{ - WaitAbandoned, - WaitTimeOut, - Unexpected, -}; - -pub fn WaitForSingleObject(handle: HANDLE, milliseconds: DWORD) WaitForSingleObjectError!void { - return WaitForSingleObjectEx(handle, milliseconds, false); -} - -pub fn WaitForSingleObjectEx(handle: HANDLE, milliseconds: DWORD, alertable: bool) WaitForSingleObjectError!void { - switch (kernel32.WaitForSingleObjectEx(handle, milliseconds, @intFromBool(alertable))) { - WAIT_ABANDONED => return error.WaitAbandoned, - WAIT_OBJECT_0 => return, - WAIT_TIMEOUT => return error.WaitTimeOut, - WAIT_FAILED => switch (GetLastError()) { - else => |err| return unexpectedError(err), - }, - else => return error.Unexpected, - } -} - -pub fn WaitForMultipleObjectsEx(handles: []const HANDLE, waitAll: bool, milliseconds: DWORD, alertable: bool) !u32 { - assert(handles.len > 0 and handles.len <= MAXIMUM_WAIT_OBJECTS); - const nCount: DWORD = @as(DWORD, @intCast(handles.len)); - switch (kernel32.WaitForMultipleObjectsEx( - nCount, - handles.ptr, - @intFromBool(waitAll), - milliseconds, - @intFromBool(alertable), - )) { - WAIT_OBJECT_0...WAIT_OBJECT_0 + MAXIMUM_WAIT_OBJECTS => |n| { - const handle_index = n - WAIT_OBJECT_0; - assert(handle_index < nCount); - return handle_index; - }, - WAIT_ABANDONED_0...WAIT_ABANDONED_0 + MAXIMUM_WAIT_OBJECTS => |n| { - const handle_index = n - WAIT_ABANDONED_0; - assert(handle_index < nCount); - return error.WaitAbandoned; - }, - WAIT_TIMEOUT => return error.WaitTimeOut, - WAIT_FAILED => switch (GetLastError()) { - else => |err| return unexpectedError(err), - }, - else => return error.Unexpected, - } -} - pub const CreateIoCompletionPortError = error{Unexpected}; pub fn CreateIoCompletionPort( @@ -2878,21 +2670,6 @@ pub fn CloseHandle(hObject: HANDLE) void { assert(ntdll.NtClose(hObject) == .SUCCESS); } -pub const GetStdHandleError = error{ - NoStandardHandleAttached, - Unexpected, -}; - -pub fn GetStdHandle(handle_id: DWORD) GetStdHandleError!HANDLE { - const handle = kernel32.GetStdHandle(handle_id) orelse return error.NoStandardHandleAttached; - if (handle == INVALID_HANDLE_VALUE) { - switch (GetLastError()) { - else => |err| return unexpectedError(err), - } - } - return handle; -} - pub const QueryObjectNameError = error{ AccessDenied, InvalidHandle, @@ -3545,6 +3322,12 @@ pub fn nanoSecondsToFileTime(ns: Io.Timestamp) FILETIME { }; } +/// Use RtlUpcaseUnicodeChar on Windows when not in comptime to avoid including a +/// redundant copy of the uppercase data. +pub inline fn toUpperWtf16(c: u16) u16 { + return (if (builtin.os.tag != .windows or @inComptime()) nls.upcaseW else ntdll.RtlUpcaseUnicodeChar)(c); +} + /// Compares two WTF16 strings using the equivalent functionality of /// `RtlEqualUnicodeString` (with case insensitive comparison enabled). /// This function can be called on any target. @@ -3598,19 +3381,12 @@ pub fn eqlIgnoreCaseWtf8(a: []const u8, b: []const u8) bool { var a_wtf8_it = std.unicode.Wtf8View.initUnchecked(a).iterator(); var b_wtf8_it = std.unicode.Wtf8View.initUnchecked(b).iterator(); - // Use RtlUpcaseUnicodeChar on Windows when not in comptime to avoid including a - // redundant copy of the uppercase data. - const upcaseImpl = switch (builtin.os.tag) { - .windows => if (@inComptime()) nls.upcaseW else ntdll.RtlUpcaseUnicodeChar, - else => nls.upcaseW, - }; - while (true) { const a_cp = a_wtf8_it.nextCodepoint() orelse break; const b_cp = b_wtf8_it.nextCodepoint() orelse return false; if (a_cp <= maxInt(u16) and b_cp <= maxInt(u16)) { - if (a_cp != b_cp and upcaseImpl(@intCast(a_cp)) != upcaseImpl(@intCast(b_cp))) { + if (a_cp != b_cp and toUpperWtf16(@intCast(a_cp)) != toUpperWtf16(@intCast(b_cp))) { return false; } } else if (a_cp != b_cp) { @@ -4098,15 +3874,6 @@ pub const Win32Error = @import("windows/win32error.zig").Win32Error; pub const LANG = @import("windows/lang.zig"); pub const SUBLANG = @import("windows/sublang.zig"); -/// The standard input device. Initially, this is the console input buffer, CONIN$. -pub const STD_INPUT_HANDLE = maxInt(DWORD) - 10 + 1; - -/// The standard output device. Initially, this is the active console screen buffer, CONOUT$. -pub const STD_OUTPUT_HANDLE = maxInt(DWORD) - 11 + 1; - -/// The standard error device. Initially, this is the active console screen buffer, CONOUT$. -pub const STD_ERROR_HANDLE = maxInt(DWORD) - 12 + 1; - pub const BOOL = c_int; pub const BOOLEAN = BYTE; pub const BYTE = u8; @@ -5244,6 +5011,8 @@ pub const UNICODE_STRING = extern struct { Length: c_ushort, MaximumLength: c_ushort, Buffer: ?[*]WCHAR, + + pub const empty: UNICODE_STRING = .{ .Length = 0, .MaximumLength = 0, .Buffer = null }; }; pub const ACTIVATION_CONTEXT_DATA = opaque {}; diff --git a/lib/std/os/windows/kernel32.zig b/lib/std/os/windows/kernel32.zig index b6785e4a33fb79eeed57e2786696f5d8ef49bb5e..d6af93cfc3add89352135762670270333ef7147c 100644 --- a/lib/std/os/windows/kernel32.zig +++ b/lib/std/os/windows/kernel32.zig @@ -12,8 +12,6 @@ const FILETIME = windows.FILETIME; const HANDLE = windows.HANDLE; const HANDLER_ROUTINE = windows.HANDLER_ROUTINE; const HMODULE = windows.HMODULE; -const INIT_ONCE = windows.INIT_ONCE; -const INIT_ONCE_FN = windows.INIT_ONCE_FN; const LARGE_INTEGER = windows.LARGE_INTEGER; const LPCSTR = windows.LPCSTR; const LPCVOID = windows.LPCVOID; @@ -24,7 +22,6 @@ const LPWSTR = windows.LPWSTR; const MODULEENTRY32 = windows.MODULEENTRY32; const OVERLAPPED = windows.OVERLAPPED; const OVERLAPPED_ENTRY = windows.OVERLAPPED_ENTRY; -const PMEMORY_BASIC_INFORMATION = windows.PMEMORY_BASIC_INFORMATION; const PROCESS_INFORMATION = windows.PROCESS_INFORMATION; const SECURITY_ATTRIBUTES = windows.SECURITY_ATTRIBUTES; const SIZE_T = windows.SIZE_T; @@ -37,7 +34,6 @@ const ULONG = windows.ULONG; const ULONG_PTR = windows.ULONG_PTR; const va_list = windows.va_list; const WCHAR = windows.WCHAR; -const WIN32_FIND_DATAW = windows.WIN32_FIND_DATAW; const Win32Error = windows.Win32Error; const WORD = windows.WORD; @@ -59,39 +55,6 @@ pub extern "kernel32" fn CancelIo( hFile: HANDLE, ) callconv(.winapi) BOOL; -// TODO: Wrapper around NtCancelIoFileEx. -pub extern "kernel32" fn CancelIoEx( - hFile: HANDLE, - lpOverlapped: ?*OVERLAPPED, -) callconv(.winapi) BOOL; - -pub extern "kernel32" fn CreateFileW( - lpFileName: LPCWSTR, - dwDesiredAccess: ACCESS_MASK, - dwShareMode: DWORD, - lpSecurityAttributes: ?*SECURITY_ATTRIBUTES, - dwCreationDisposition: DWORD, - dwFlagsAndAttributes: DWORD, - hTemplateFile: ?HANDLE, -) callconv(.winapi) HANDLE; - -// TODO A bunch of logic around NtCreateNamedPipe -pub extern "kernel32" fn CreateNamedPipeW( - lpName: LPCWSTR, - dwOpenMode: DWORD, - dwPipeMode: DWORD, - nMaxInstances: DWORD, - nOutBufferSize: DWORD, - nInBufferSize: DWORD, - nDefaultTimeOut: DWORD, - lpSecurityAttributes: ?*const SECURITY_ATTRIBUTES, -) callconv(.winapi) HANDLE; - -// TODO: Matches `STD_*_HANDLE` to peb().ProcessParameters.Standard* -pub extern "kernel32" fn GetStdHandle( - nStdHandle: DWORD, -) callconv(.winapi) ?HANDLE; - // TODO: Wrapper around NtSetInformationFile + `FILE_POSITION_INFORMATION`. // `FILE_STANDARD_INFORMATION` is also used if dwMoveMethod is `FILE_END` pub extern "kernel32" fn SetFilePointerEx( @@ -117,11 +80,6 @@ pub extern "kernel32" fn WriteFile( in_out_lpOverlapped: ?*OVERLAPPED, ) callconv(.winapi) BOOL; -// TODO: Wrapper around GetStdHandle + NtFlushBuffersFile. -pub extern "kernel32" fn FlushFileBuffers( - hFile: HANDLE, -) callconv(.winapi) BOOL; - // TODO: Wrapper around NtSetInformationFile + `FILE_IO_COMPLETION_NOTIFICATION_INFORMATION`. pub extern "kernel32" fn SetFileCompletionNotificationModes( FileHandle: HANDLE, @@ -143,24 +101,6 @@ pub extern "kernel32" fn GetSystemDirectoryW( // I/O - Kernel Objects -// TODO: Wrapper around GetStdHandle + NtDuplicateObject. -pub extern "kernel32" fn DuplicateHandle( - hSourceProcessHandle: HANDLE, - hSourceHandle: HANDLE, - hTargetProcessHandle: HANDLE, - lpTargetHandle: *HANDLE, - dwDesiredAccess: ACCESS_MASK, - bInheritHandle: BOOL, - dwOptions: DWORD, -) callconv(.winapi) BOOL; - -// TODO: Wrapper around GetStdHandle + NtQueryObject + NtSetInformationObject with .ObjectHandleFlagInformation. -pub extern "kernel32" fn SetHandleInformation( - hObject: HANDLE, - dwMask: DWORD, - dwFlags: DWORD, -) callconv(.winapi) BOOL; - // TODO: Wrapper around NtRemoveIoCompletion. pub extern "kernel32" fn GetQueuedCompletionStatus( CompletionPort: HANDLE, @@ -210,37 +150,6 @@ pub extern "kernel32" fn TerminateProcess( uExitCode: UINT, ) callconv(.winapi) BOOL; -// TODO: WaitForSingleObjectEx with bAlertable=false. -pub extern "kernel32" fn WaitForSingleObject( - hHandle: HANDLE, - dwMilliseconds: DWORD, -) callconv(.winapi) DWORD; - -// TODO: Wrapper for GetStdHandle + NtWaitForSingleObject. -// Sets up an activation context before calling NtWaitForSingleObject. -pub extern "kernel32" fn WaitForSingleObjectEx( - hHandle: HANDLE, - dwMilliseconds: DWORD, - bAlertable: BOOL, -) callconv(.winapi) DWORD; - -// TODO: WaitForMultipleObjectsEx with alertable=false -pub extern "kernel32" fn WaitForMultipleObjects( - nCount: DWORD, - lpHandle: [*]const HANDLE, - bWaitAll: BOOL, - dwMilliseconds: DWORD, -) callconv(.winapi) DWORD; - -// TODO: Wrapper around NtWaitForMultipleObjects. -pub extern "kernel32" fn WaitForMultipleObjectsEx( - nCount: DWORD, - lpHandle: [*]const HANDLE, - bWaitAll: BOOL, - dwMilliseconds: DWORD, - bAlertable: BOOL, -) callconv(.winapi) DWORD; - // Process Management pub extern "kernel32" fn CreateProcessW( @@ -256,12 +165,6 @@ pub extern "kernel32" fn CreateProcessW( lpProcessInformation: *PROCESS_INFORMATION, ) callconv(.winapi) BOOL; -// TODO: implement via ntdll instead -pub extern "kernel32" fn SleepEx( - dwMilliseconds: DWORD, - bAlertable: BOOL, -) callconv(.winapi) DWORD; - // TODO: Wrapper around NtQueryInformationProcess with `PROCESS_BASIC_INFORMATION`. pub extern "kernel32" fn GetExitCodeProcess( hProcess: HANDLE, @@ -436,14 +339,3 @@ pub extern "kernel32" fn FormatMessageW( // TODO: Getter for teb().LastErrorValue. pub extern "kernel32" fn GetLastError() callconv(.winapi) Win32Error; - -// TODO: Wrapper around RtlSetLastWin32Error. -pub extern "kernel32" fn SetLastError( - dwErrCode: Win32Error, -) callconv(.winapi) void; - -// Everything Else - -pub extern "kernel32" fn GetSystemInfo( - lpSystemInfo: *SYSTEM_INFO, -) callconv(.winapi) void; diff --git a/lib/std/os/windows/ntdll.zig b/lib/std/os/windows/ntdll.zig index d9e68e54f9f275fbf254098e8b7716aad2cead5e..bda9fee828b6dc33c44b4a40b090c1d0f6761d84 100644 --- a/lib/std/os/windows/ntdll.zig +++ b/lib/std/os/windows/ntdll.zig @@ -407,6 +407,11 @@ pub extern "ntdll" fn NtCreateNamedPipeFile( DefaultTimeout: ?*const LARGE_INTEGER, ) callconv(.winapi) NTSTATUS; +pub extern "ntdll" fn NtFlushBuffersFile( + FileHandle: HANDLE, + IoStatusBlock: *IO_STATUS_BLOCK, +) callconv(.winapi) NTSTATUS; + pub extern "ntdll" fn NtMapViewOfSection( SectionHandle: HANDLE, ProcessHandle: HANDLE, @@ -590,7 +595,7 @@ pub extern "ntdll" fn NtOpenThread( pub extern "ntdll" fn NtCancelSynchronousIoFile( ThreadHandle: HANDLE, - RequestToCancel: ?*IO_STATUS_BLOCK, + IoRequestToCancel: ?*IO_STATUS_BLOCK, IoStatusBlock: *IO_STATUS_BLOCK, ) callconv(.winapi) NTSTATUS; @@ -606,13 +611,13 @@ pub extern "ntdll" fn NtDelayExecution( DelayInterval: *const LARGE_INTEGER, ) callconv(.winapi) NTSTATUS; +pub extern "ntdll" fn NtCancelIoFile( + FileHandle: HANDLE, + IoStatusBlock: *IO_STATUS_BLOCK, +) callconv(.winapi) NTSTATUS; + pub extern "ntdll" fn NtCancelIoFileEx( FileHandle: HANDLE, IoRequestToCancel: *const IO_STATUS_BLOCK, IoStatusBlock: *IO_STATUS_BLOCK, ) callconv(.winapi) NTSTATUS; - -pub extern "ntdll" fn NtCancelIoFile( - FileHandle: HANDLE, - IoStatusBlock: *IO_STATUS_BLOCK, -) callconv(.winapi) NTSTATUS; diff --git a/lib/std/process/Environ.zig b/lib/std/process/Environ.zig index 5255e1af16ab86f4e1e29158cbfda3c8c8830c66..e33024de3525fcabfaa08a5be5fc8e80dd4dd026 100644 --- a/lib/std/process/Environ.zig +++ b/lib/std/process/Environ.zig @@ -4,7 +4,7 @@ const builtin = @import("builtin"); const native_os = builtin.os.tag; const std = @import("../std.zig"); -const Allocator = std.mem.Allocator; +const Allocator = mem.Allocator; const assert = std.debug.assert; const testing = std.testing; const unicode = std.unicode; @@ -14,12 +14,7 @@ const mem = std.mem; /// Unmodified, unprocessed data provided by the operating system. block: Block, -pub const empty: Environ = .{ - .block = switch (Block) { - void => {}, - else => &.{}, - }, -}; +pub const empty: Environ = .{ .block = .empty }; /// On WASI without libc, this is `void` because the environment has to be /// queried and heap-allocated at runtime. @@ -28,13 +23,65 @@ pub const empty: Environ = .{ /// is modified, so a long-lived pointer cannot be used. Therefore, on this /// operating system `void` is also used. pub const Block = switch (native_os) { - .windows => void, + .windows => GlobalBlock, .wasi => switch (builtin.link_libc) { - false => void, - true => [:null]const ?[*:0]const u8, + false => GlobalBlock, + true => PosixBlock, }, - .freestanding, .other => void, - else => [:null]const ?[*:0]const u8, + .freestanding, .other => GlobalBlock, + else => PosixBlock, +}; + +pub const GlobalBlock = struct { + use_global: bool, + + pub const empty: GlobalBlock = .{ .use_global = false }; + pub const global: GlobalBlock = .{ .use_global = true }; + + pub fn deinit(_: GlobalBlock, _: Allocator) void {} +}; + +pub const PosixBlock = struct { + slice: [:null]const ?[*:0]const u8, + + pub const empty: PosixBlock = .{ .slice = &.{} }; + + pub fn deinit(block: PosixBlock, gpa: Allocator) void { + for (block.slice) |entry| gpa.free(mem.span(entry.?)); + gpa.free(block.slice); + } + + pub const View = struct { + slice: []const [*:0]const u8, + + pub fn isEmpty(v: View) bool { + return v.slice.len == 0; + } + }; + pub fn view(block: PosixBlock) View { + return .{ .slice = @ptrCast(block.slice) }; + } +}; + +pub const WindowsBlock = struct { + slice: [:0]const u16, + + pub const empty: WindowsBlock = .{ .slice = &.{0} }; + + pub fn deinit(block: WindowsBlock, gpa: Allocator) void { + gpa.free(block.slice); + } + + pub const View = struct { + ptr: [*:0]const u16, + + pub fn isEmpty(v: View) bool { + return v.ptr[0] == 0; + } + }; + pub fn view(block: WindowsBlock) View { + return .{ .ptr = block.slice.ptr }; + } }; pub const Map = struct { @@ -46,47 +93,60 @@ pub const Map = struct { pub const Size = usize; pub const EnvNameHashContext = struct { - fn upcase(c: u21) u21 { - if (c <= std.math.maxInt(u16)) - return std.os.windows.ntdll.RtlUpcaseUnicodeChar(@as(u16, @intCast(c))); - return c; - } - pub fn hash(self: @This(), s: []const u8) u32 { _ = self; - if (native_os == .windows) { - var h = std.hash.Wyhash.init(0); - var it = unicode.Wtf8View.initUnchecked(s).iterator(); - while (it.nextCodepoint()) |cp| { - const cp_upper = upcase(cp); - h.update(&[_]u8{ - @as(u8, @intCast((cp_upper >> 16) & 0xff)), - @as(u8, @intCast((cp_upper >> 8) & 0xff)), - @as(u8, @intCast((cp_upper >> 0) & 0xff)), - }); - } - return @truncate(h.final()); + switch (native_os) { + else => return std.array_hash_map.hashString(s), + .windows => { + var h = std.hash.Wyhash.init(0); + var it = unicode.Wtf8View.initUnchecked(s).iterator(); + while (it.nextCodepoint()) |cp| { + const cp_upper = if (std.math.cast(u16, cp)) |wtf16| + std.os.windows.toUpperWtf16(wtf16) + else + cp; + h.update(&[_]u8{ + @truncate(cp_upper >> 0), + @truncate(cp_upper >> 8), + @truncate(cp_upper >> 16), + }); + } + return @truncate(h.final()); + }, } - return std.array_hash_map.hashString(s); } pub fn eql(self: @This(), a: []const u8, b: []const u8, b_index: usize) bool { _ = self; _ = b_index; - if (native_os == .windows) { - var it_a = unicode.Wtf8View.initUnchecked(a).iterator(); - var it_b = unicode.Wtf8View.initUnchecked(b).iterator(); - while (true) { - const c_a = it_a.nextCodepoint() orelse break; - const c_b = it_b.nextCodepoint() orelse return false; - if (upcase(c_a) != upcase(c_b)) - return false; - } - return if (it_b.nextCodepoint()) |_| false else true; - } - return std.array_hash_map.eqlString(a, b); + return eqlKeys(a, b); } }; + fn eqlKeys(a: []const u8, b: []const u8) bool { + return switch (native_os) { + else => std.array_hash_map.eqlString(a, b), + .windows => std.os.windows.eqlIgnoreCaseWtf8(a, b), + }; + } + + pub fn validateKey(key: []const u8) bool { + switch (native_os) { + else => return key.len > 0 and mem.findAny(u8, key, &.{ 0, '=' }) == null, + .windows => { + if (!unicode.wtf8ValidateSlice(key)) return false; + var it = unicode.Wtf8View.initUnchecked(key).iterator(); + switch (it.nextCodepoint() orelse return false) { + 0 => return false, + else => {}, + } + while (it.nextCodepoint()) |cp| switch (cp) { + 0, '=' => return false, + else => {}, + }; + return true; + }, + } + } /// Create a Map backed by a specific allocator. /// That allocator will be used for both backing allocations @@ -99,30 +159,71 @@ pub const Map = struct { /// of the stored keys and values. pub fn deinit(self: *Map) void { const gpa = self.allocator; - var it = self.array_hash_map.iterator(); - while (it.next()) |entry| { - gpa.free(entry.key_ptr.*); - gpa.free(entry.value_ptr.*); - } + for (self.keys()) |key| gpa.free(key); + for (self.values()) |value| gpa.free(value); self.array_hash_map.deinit(gpa); self.* = undefined; } - pub fn keys(m: *const Map) [][]const u8 { - return m.array_hash_map.keys(); + pub fn keys(map: *const Map) [][]const u8 { + return map.array_hash_map.keys(); } - pub fn values(m: *const Map) [][]const u8 { - return m.array_hash_map.values(); + pub fn values(map: *const Map) [][]const u8 { + return map.array_hash_map.values(); + } + + pub fn putPosixBlock(map: *Map, view: PosixBlock.View) Allocator.Error!void { + for (view.slice) |entry| { + var entry_i: usize = 0; + while (entry[entry_i] != 0 and entry[entry_i] != '=') : (entry_i += 1) {} + const key = entry[0..entry_i]; + + var end_i: usize = entry_i; + while (entry[end_i] != 0) : (end_i += 1) {} + const value = entry[entry_i + 1 .. end_i]; + + try map.put(key, value); + } + } + + pub fn putWindowsBlock(map: *Map, view: WindowsBlock.View) Allocator.Error!void { + var i: usize = 0; + while (view.ptr[i] != 0) { + const key_start = i; + + // There are some special environment variables that start with =, + // so we need a special case to not treat = as a key/value separator + // if it's the first character. + // https://devblogs.microsoft.com/oldnewthing/20100506-00/?p=14133 + if (view.ptr[key_start] == '=') i += 1; + + while (view.ptr[i] != 0 and view.ptr[i] != '=') : (i += 1) {} + const key_w = view.ptr[key_start..i]; + const key = try unicode.wtf16LeToWtf8Alloc(map.allocator, key_w); + errdefer map.allocator.free(key); + + if (view.ptr[i] == '=') i += 1; + + const value_start = i; + while (view.ptr[i] != 0) : (i += 1) {} + const value_w = view.ptr[value_start..i]; + const value = try unicode.wtf16LeToWtf8Alloc(map.allocator, value_w); + errdefer map.allocator.free(value); + + i += 1; // skip over null byte + + try map.putMove(key, value); + } } /// Same as `put` but the key and value become owned by the Map rather /// than being copied. /// If `putMove` fails, the ownership of key and value does not transfer. /// On Windows `key` must be a valid [WTF-8](https://wtf-8.codeberg.page/) string. - pub fn putMove(self: *Map, key: []u8, value: []u8) !void { + pub fn putMove(self: *Map, key: []u8, value: []u8) Allocator.Error!void { + assert(validateKey(key)); const gpa = self.allocator; - assert(unicode.wtf8ValidateSlice(key)); const get_or_put = try self.array_hash_map.getOrPut(gpa, key); if (get_or_put.found_existing) { gpa.free(get_or_put.key_ptr.*); @@ -134,8 +235,8 @@ pub const Map = struct { /// `key` and `value` are copied into the Map. /// On Windows `key` must be a valid [WTF-8](https://wtf-8.codeberg.page/) string. - pub fn put(self: *Map, key: []const u8, value: []const u8) !void { - assert(unicode.wtf8ValidateSlice(key)); + pub fn put(self: *Map, key: []const u8, value: []const u8) Allocator.Error!void { + assert(validateKey(key)); const gpa = self.allocator; const value_copy = try gpa.dupe(u8, value); errdefer gpa.free(value_copy); @@ -155,7 +256,7 @@ pub const Map = struct { /// The returned pointer is invalidated if the map resizes. /// On Windows `key` must be a valid [WTF-8](https://wtf-8.codeberg.page/) string. pub fn getPtr(self: Map, key: []const u8) ?*[]const u8 { - assert(unicode.wtf8ValidateSlice(key)); + assert(validateKey(key)); return self.array_hash_map.getPtr(key); } @@ -164,11 +265,12 @@ pub const Map = struct { /// key is removed from the map. /// On Windows `key` must be a valid [WTF-8](https://wtf-8.codeberg.page/) string. pub fn get(self: Map, key: []const u8) ?[]const u8 { - assert(unicode.wtf8ValidateSlice(key)); + assert(validateKey(key)); return self.array_hash_map.get(key); } pub fn contains(m: *const Map, key: []const u8) bool { + assert(validateKey(key)); return m.array_hash_map.contains(key); } @@ -181,7 +283,7 @@ pub const Map = struct { /// This invalidates the value returned by get() for this key. /// On Windows `key` must be a valid [WTF-8](https://wtf-8.codeberg.page/) string. pub fn swapRemove(self: *Map, key: []const u8) bool { - assert(unicode.wtf8ValidateSlice(key)); + assert(validateKey(key)); const kv = self.array_hash_map.fetchSwapRemove(key) orelse return false; const gpa = self.allocator; gpa.free(kv.key); @@ -198,7 +300,7 @@ pub const Map = struct { /// This invalidates the value returned by get() for this key. /// On Windows `key` must be a valid [WTF-8](https://wtf-8.codeberg.page/) string. pub fn orderedRemove(self: *Map, key: []const u8) bool { - assert(unicode.wtf8ValidateSlice(key)); + assert(validateKey(key)); const kv = self.array_hash_map.fetchOrderedRemove(key) orelse return false; const gpa = self.allocator; gpa.free(kv.key); @@ -233,105 +335,120 @@ pub const Map = struct { /// Creates a null-delimited environment variable block in the format /// expected by POSIX, from a hash map plus options. - pub fn createBlockPosix( + pub fn createPosixBlock( map: *const Map, - arena: Allocator, - options: CreateBlockPosixOptions, - ) Allocator.Error![:null]?[*:0]u8 { + gpa: Allocator, + options: CreatePosixBlockOptions, + ) Allocator.Error!PosixBlock { const ZigProgressAction = enum { nothing, edit, delete, add }; - const zig_progress_action: ZigProgressAction = a: { - const fd = options.zig_progress_fd orelse break :a .nothing; - const exists = map.get("ZIG_PROGRESS") != null; + const zig_progress_action: ZigProgressAction = action: { + const fd = options.zig_progress_fd orelse break :action .nothing; + const exists = map.contains("ZIG_PROGRESS"); if (fd >= 0) { - break :a if (exists) .edit else .add; + break :action if (exists) .edit else .add; } else { - if (exists) break :a .delete; + if (exists) break :action .delete; } - break :a .nothing; + break :action .nothing; }; - const envp_count: usize = c: { - var c: usize = map.count(); + const envp = try gpa.allocSentinel(?[*:0]u8, len: { + var len: usize = map.count(); switch (zig_progress_action) { - .add => c += 1, - .delete => c -= 1, + .add => len += 1, + .delete => len -= 1, .nothing, .edit => {}, } - break :c c; - }; - - const envp_buf = try arena.allocSentinel(?[*:0]u8, envp_count, null); - var i: usize = 0; + break :len len; + }, null); + var envp_len: usize = 0; + errdefer { + envp[envp_len] = null; + PosixBlock.deinit(.{ .slice = envp[0..envp_len :null] }, gpa); + } if (zig_progress_action == .add) { - envp_buf[i] = try std.fmt.allocPrintSentinel(arena, "ZIG_PROGRESS={d}", .{options.zig_progress_fd.?}, 0); - i += 1; + envp[envp_len] = try std.fmt.allocPrintSentinel(gpa, "ZIG_PROGRESS={d}", .{options.zig_progress_fd.?}, 0); + envp_len += 1; } - { - var it = map.iterator(); - while (it.next()) |pair| { - if (mem.eql(u8, pair.key_ptr.*, "ZIG_PROGRESS")) switch (zig_progress_action) { - .add => unreachable, - .delete => continue, - .edit => { - envp_buf[i] = try std.fmt.allocPrintSentinel(arena, "{s}={d}", .{ - pair.key_ptr.*, options.zig_progress_fd.?, - }, 0); - i += 1; - continue; - }, - .nothing => {}, - }; + for (map.keys(), map.values()) |key, value| { + if (mem.eql(u8, key, "ZIG_PROGRESS")) switch (zig_progress_action) { + .add => unreachable, + .delete => continue, + .edit => { + envp[envp_len] = try std.fmt.allocPrintSentinel(gpa, "{s}={d}", .{ + key, options.zig_progress_fd.?, + }, 0); + envp_len += 1; + continue; + }, + .nothing => {}, + }; - envp_buf[i] = try std.fmt.allocPrintSentinel(arena, "{s}={s}", .{ pair.key_ptr.*, pair.value_ptr.* }, 0); - i += 1; - } + envp[envp_len] = try std.fmt.allocPrintSentinel(gpa, "{s}={s}", .{ key, value }, 0); + envp_len += 1; } - assert(i == envp_count); - return envp_buf; + assert(envp_len == envp.len); + return .{ .slice = envp }; } /// Caller owns result. - pub fn createBlockWindows(map: *const Map, gpa: Allocator) error{ OutOfMemory, InvalidWtf8 }![:0]u16 { + pub fn createWindowsBlock( + map: *const Map, + gpa: Allocator, + options: CreateWindowsBlockOptions, + ) error{ OutOfMemory, InvalidWtf8 }!WindowsBlock { // count bytes needed - const max_chars_needed = x: { - // Only need 2 trailing NUL code units for an empty environment - var max_chars_needed: usize = if (map.count() == 0) 2 else 1; - var it = map.iterator(); - while (it.next()) |pair| { - // +1 for '=' - // +1 for null byte - max_chars_needed += pair.key_ptr.len + pair.value_ptr.len + 2; + const max_chars_needed = max_chars_needed: { + var max_chars_needed: usize = "\x00".len; + if (options.zig_progress_handle) |handle| if (handle != std.os.windows.INVALID_HANDLE_VALUE) { + max_chars_needed += std.fmt.count("ZIG_PROGRESS={d}\x00", .{@intFromPtr(handle)}); + }; + for (map.keys(), map.values()) |key, value| { + if (options.zig_progress_handle != null and eqlKeys(key, "ZIG_PROGRESS")) continue; + max_chars_needed += key.len + "=".len + value.len + "\x00".len; } - break :x max_chars_needed; + break :max_chars_needed @max("\x00\x00".len, max_chars_needed); }; - const result = try gpa.alloc(u16, max_chars_needed); - errdefer gpa.free(result); + const block = try gpa.alloc(u16, max_chars_needed); + errdefer gpa.free(block); - var it = map.iterator(); var i: usize = 0; - while (it.next()) |pair| { - i += try unicode.wtf8ToWtf16Le(result[i..], pair.key_ptr.*); - result[i] = '='; + if (options.zig_progress_handle) |handle| if (handle != std.os.windows.INVALID_HANDLE_VALUE) { + @memcpy( + block[i..][0.."ZIG_PROGRESS=".len], + &[_]u16{ 'Z', 'I', 'G', '_', 'P', 'R', 'O', 'G', 'R', 'E', 'S', 'S', '=' }, + ); + i += "ZIG_PROGRESS=".len; + var value_buf: [std.fmt.count("{d}", .{std.math.maxInt(usize)})]u8 = undefined; + const value = std.fmt.bufPrint(&value_buf, "{d}", .{@intFromPtr(handle)}) catch unreachable; + for (block[i..][0..value.len], value) |*r, v| r.* = v; + i += value.len; + block[i] = 0; i += 1; - i += try unicode.wtf8ToWtf16Le(result[i..], pair.value_ptr.*); - result[i] = 0; + }; + for (map.keys(), map.values()) |key, value| { + if (options.zig_progress_handle != null and eqlKeys(key, "ZIG_PROGRESS")) continue; + i += try unicode.wtf8ToWtf16Le(block[i..], key); + block[i] = '='; + i += 1; + i += try unicode.wtf8ToWtf16Le(block[i..], value); + block[i] = 0; i += 1; } - result[i] = 0; - i += 1; // An empty environment is a special case that requires a redundant // NUL terminator. CreateProcess will read the second code unit even // though theoretically the first should be enough to recognize that the // environment is empty (see https://nullprogram.com/blog/2023/08/23/) - if (map.count() == 0) { - result[i] = 0; + for (0..2) |_| { + block[i] = 0; i += 1; - } - const reallocated = try gpa.realloc(result, i); - return reallocated[0 .. i - 1 :0]; + if (i >= 2) break; + } else unreachable; + const reallocated = try gpa.realloc(block, i); + return .{ .slice = reallocated[0 .. i - 1 :0] }; } }; @@ -344,13 +461,18 @@ pub const CreateMapError = error{ /// Allocates a `Map` and copies environment block into it. pub fn createMap(env: Environ, allocator: Allocator) CreateMapError!Map { - if (native_os == .windows) - return createMapWide(std.os.windows.peb().ProcessParameters.Environment, allocator); + var map = Map.init(allocator); + errdefer map.deinit(); + if (native_os == .windows) empty: { + if (!env.block.use_global) break :empty; - var result = Map.init(allocator); - errdefer result.deinit(); + const peb = std.os.windows.peb(); + assert(std.os.windows.ntdll.RtlEnterCriticalSection(peb.FastPebLock) == .SUCCESS); + defer assert(std.os.windows.ntdll.RtlLeaveCriticalSection(peb.FastPebLock) == .SUCCESS); + try map.putWindowsBlock(.{ .ptr = peb.ProcessParameters.Environment }); + } else if (native_os == .wasi and !builtin.link_libc) empty: { + if (!env.block.use_global) break :empty; - if (native_os == .wasi and !builtin.link_libc) { var environ_count: usize = undefined; var environ_buf_size: usize = undefined; @@ -360,7 +482,7 @@ pub fn createMap(env: Environ, allocator: Allocator) CreateMapError!Map { } if (environ_count == 0) { - return result; + return map; } const environ = try allocator.alloc([*:0]u8, environ_count); @@ -373,63 +495,9 @@ pub fn createMap(env: Environ, allocator: Allocator) CreateMapError!Map { return posix.unexpectedErrno(environ_get_ret); } - for (environ) |line| { - const pair = mem.sliceTo(line, 0); - var parts = mem.splitScalar(u8, pair, '='); - const key = parts.first(); - const value = parts.rest(); - try result.put(key, value); - } - return result; - } else { - for (env.block) |opt_line| { - const line = opt_line.?; - var line_i: usize = 0; - while (line[line_i] != 0 and line[line_i] != '=') : (line_i += 1) {} - const key = line[0..line_i]; - - var end_i: usize = line_i; - while (line[end_i] != 0) : (end_i += 1) {} - const value = line[line_i + 1 .. end_i]; - - try result.put(key, value); - } - return result; - } -} - -pub fn createMapWide(ptr: [*:0]u16, gpa: Allocator) CreateMapError!Map { - var result = Map.init(gpa); - errdefer result.deinit(); - - var i: usize = 0; - while (ptr[i] != 0) { - const key_start = i; - - // There are some special environment variables that start with =, - // so we need a special case to not treat = as a key/value separator - // if it's the first character. - // https://devblogs.microsoft.com/oldnewthing/20100506-00/?p=14133 - if (ptr[key_start] == '=') i += 1; - - while (ptr[i] != 0 and ptr[i] != '=') : (i += 1) {} - const key_w = ptr[key_start..i]; - const key = try unicode.wtf16LeToWtf8Alloc(gpa, key_w); - errdefer gpa.free(key); - - if (ptr[i] == '=') i += 1; - - const value_start = i; - while (ptr[i] != 0) : (i += 1) {} - const value_w = ptr[value_start..i]; - const value = try unicode.wtf16LeToWtf8Alloc(gpa, value_w); - errdefer gpa.free(value); - - i += 1; // skip over null byte - - try result.putMove(key, value); - } - return result; + try map.putPosixBlock(.{ .slice = environ }); + } else try map.putPosixBlock(env.block.view()); + return map; } pub const ContainsError = error{ @@ -451,6 +519,7 @@ pub const ContainsError = error{ /// * `containsConstant` /// * `containsUnempty` pub fn contains(environ: Environ, gpa: Allocator, key: []const u8) ContainsError!bool { + if (native_os == .windows and !unicode.wtf8ValidateSlice(key)) return error.InvalidWtf8; var map = try createMap(environ, gpa); defer map.deinit(); return map.contains(key); @@ -464,6 +533,7 @@ pub fn contains(environ: Environ, gpa: Allocator, key: []const u8) ContainsError /// * `containsUnemptyConstant` /// * `contains` pub fn containsUnempty(environ: Environ, gpa: Allocator, key: []const u8) ContainsError!bool { + if (native_os == .windows and !unicode.wtf8ValidateSlice(key)) return error.InvalidWtf8; var map = try createMap(environ, gpa); defer map.deinit(); const value = map.get(key) orelse return false; @@ -516,16 +586,15 @@ pub inline fn containsUnemptyConstant(environ: Environ, comptime key: []const u8 /// * `createMap` pub fn getPosix(environ: Environ, key: []const u8) ?[:0]const u8 { if (mem.findScalar(u8, key, '=') != null) return null; - for (environ.block) |opt_line| { - const line = opt_line.?; - var line_i: usize = 0; - while (line[line_i] != 0) : (line_i += 1) { - if (line_i == key.len) break; - if (line[line_i] != key[line_i]) break; + for (environ.block.view().slice) |entry| { + var entry_i: usize = 0; + while (entry[entry_i] != 0) : (entry_i += 1) { + if (entry_i == key.len) break; + if (entry[entry_i] != key[entry_i]) break; } - if ((line_i != key.len) or (line[line_i] != '=')) continue; + if ((entry_i != key.len) or (entry[entry_i] != '=')) continue; - return mem.sliceTo(line + line_i + 1, 0); + return mem.sliceTo(entry + entry_i + 1, 0); } return null; } @@ -541,14 +610,16 @@ pub fn getPosix(environ: Environ, key: []const u8) ?[:0]const u8 { /// * `containsConstant` /// * `contains` pub fn getWindows(environ: Environ, key: [*:0]const u16) ?[:0]const u16 { - comptime assert(native_os == .windows); - comptime assert(@TypeOf(environ.block) == void); - // '=' anywhere but the start makes this an invalid environment variable name. const key_slice = mem.sliceTo(key, 0); - if (key_slice.len > 0 and mem.findScalar(u16, key_slice[1..], '=') != null) return null; + assert(key_slice.len > 0 and mem.findScalar(u16, key_slice[1..], '=') == null); - const ptr = std.os.windows.peb().ProcessParameters.Environment; + if (!environ.block.use_global) return null; + + const peb = std.os.windows.peb(); + assert(std.os.windows.ntdll.RtlEnterCriticalSection(peb.FastPebLock) == .SUCCESS); + defer assert(std.os.windows.ntdll.RtlLeaveCriticalSection(peb.FastPebLock) == .SUCCESS); + const ptr = peb.ProcessParameters.Environment; var i: usize = 0; while (ptr[i] != 0) { @@ -558,8 +629,7 @@ pub fn getWindows(environ: Environ, key: [*:0]const u16) ?[:0]const u16 { // so we need a special case to not treat = as a key/value separator // if it's the first character. // https://devblogs.microsoft.com/oldnewthing/20100506-00/?p=14133 - const equal_search_start: usize = if (key_value[0] == '=') 1 else 0; - const equal_index = mem.findScalarPos(u16, key_value, equal_search_start, '=') orelse { + const equal_index = mem.findScalarPos(u16, key_value, 1, '=') orelse { // This is enforced by CreateProcess. // If violated, CreateProcess will fail with INVALID_PARAMETER. unreachable; // must contain a = @@ -598,13 +668,14 @@ pub const GetAllocError = error{ /// See also: /// * `createMap` pub fn getAlloc(environ: Environ, gpa: Allocator, key: []const u8) GetAllocError![]u8 { + if (native_os == .windows and !unicode.wtf8ValidateSlice(key)) return error.InvalidWtf8; var map = createMap(environ, gpa) catch return error.OutOfMemory; defer map.deinit(); const val = map.get(key) orelse return error.EnvironmentVariableMissing; return gpa.dupe(u8, val); } -pub const CreateBlockPosixOptions = struct { +pub const CreatePosixBlockOptions = struct { /// `null` means to leave the `ZIG_PROGRESS` environment variable unmodified. /// If non-null, negative means to remove the environment variable, and >= 0 /// means to provide it with the given integer. @@ -613,67 +684,147 @@ pub const CreateBlockPosixOptions = struct { /// Creates a null-delimited environment variable block in the format expected /// by POSIX, from a different one. -pub fn createBlockPosix( +pub fn createPosixBlock( existing: Environ, - arena: Allocator, - options: CreateBlockPosixOptions, -) Allocator.Error![:null]?[*:0]u8 { - const contains_zig_progress = for (existing.block) |opt_line| { - if (mem.eql(u8, mem.sliceTo(opt_line.?, '='), "ZIG_PROGRESS")) break true; + gpa: Allocator, + options: CreatePosixBlockOptions, +) Allocator.Error!PosixBlock { + const contains_zig_progress = for (existing.block.view().slice) |entry| { + if (mem.eql(u8, mem.sliceTo(entry, '='), "ZIG_PROGRESS")) break true; } else false; const ZigProgressAction = enum { nothing, edit, delete, add }; - const zig_progress_action: ZigProgressAction = a: { - const fd = options.zig_progress_fd orelse break :a .nothing; + const zig_progress_action: ZigProgressAction = action: { + const fd = options.zig_progress_fd orelse break :action .nothing; if (fd >= 0) { - break :a if (contains_zig_progress) .edit else .add; + break :action if (contains_zig_progress) .edit else .add; } else { - if (contains_zig_progress) break :a .delete; + if (contains_zig_progress) break :action .delete; } - break :a .nothing; + break :action .nothing; }; - const envp_count: usize = c: { - var count: usize = existing.block.len; + const envp = try gpa.allocSentinel(?[*:0]u8, len: { + var len: usize = existing.block.slice.len; switch (zig_progress_action) { - .add => count += 1, - .delete => count -= 1, + .add => len += 1, + .delete => len -= 1, .nothing, .edit => {}, } - break :c count; - }; - - const envp_buf = try arena.allocSentinel(?[*:0]u8, envp_count, null); - var i: usize = 0; - var existing_index: usize = 0; - + break :len len; + }, null); + var envp_len: usize = 0; + errdefer { + envp[envp_len] = null; + PosixBlock.deinit(.{ .slice = envp[0..envp_len :null] }, gpa); + } if (zig_progress_action == .add) { - envp_buf[i] = try std.fmt.allocPrintSentinel(arena, "ZIG_PROGRESS={d}", .{options.zig_progress_fd.?}, 0); - i += 1; + envp[envp_len] = try std.fmt.allocPrintSentinel(gpa, "ZIG_PROGRESS={d}", .{options.zig_progress_fd.?}, 0); + envp_len += 1; } - while (existing.block[existing_index]) |line| : (existing_index += 1) { - if (mem.eql(u8, mem.sliceTo(line, '='), "ZIG_PROGRESS")) switch (zig_progress_action) { + var existing_index: usize = 0; + while (existing.block.slice[existing_index]) |entry| : (existing_index += 1) { + if (mem.eql(u8, mem.sliceTo(entry, '='), "ZIG_PROGRESS")) switch (zig_progress_action) { .add => unreachable, .delete => continue, .edit => { - envp_buf[i] = try std.fmt.allocPrintSentinel(arena, "ZIG_PROGRESS={d}", .{options.zig_progress_fd.?}, 0); - i += 1; + envp[envp_len] = try std.fmt.allocPrintSentinel(gpa, "ZIG_PROGRESS={d}", .{options.zig_progress_fd.?}, 0); + envp_len += 1; continue; }, .nothing => {}, }; - envp_buf[i] = try arena.dupeZ(u8, mem.span(line)); - i += 1; + envp[envp_len] = try gpa.dupeZ(u8, mem.span(entry)); + envp_len += 1; } - assert(i == envp_count); - return envp_buf; + assert(envp_len == envp.len); + return .{ .slice = envp }; +} + +pub const CreateWindowsBlockOptions = struct { + /// `null` means to leave the `ZIG_PROGRESS` environment variable unmodified. + /// If non-null, `std.os.windows.INVALID_HANDLE_VALUE` means to remove the + /// environment variable, otherwise provide it with the given handle as an integer. + zig_progress_handle: ?std.os.windows.HANDLE = null, +}; + +/// Creates a null-delimited environment variable block in the format expected +/// by POSIX, from a different one. +pub fn createWindowsBlock( + existing: Environ, + gpa: Allocator, + options: CreateWindowsBlockOptions, +) Allocator.Error!WindowsBlock { + if (!existing.block.use_global) return .{ + .slice = try gpa.dupeSentinel(u16, WindowsBlock.empty.slice, 0), + }; + const peb = std.os.windows.peb(); + assert(std.os.windows.ntdll.RtlEnterCriticalSection(peb.FastPebLock) == .SUCCESS); + defer assert(std.os.windows.ntdll.RtlLeaveCriticalSection(peb.FastPebLock) == .SUCCESS); + const existing_block = peb.ProcessParameters.Environment; + var ranges: [2]struct { start: usize, end: usize } = undefined; + var ranges_len: usize = 0; + ranges[ranges_len].start = 0; + const zig_progress_key = [_]u16{ 'Z', 'I', 'G', '_', 'P', 'R', 'O', 'G', 'R', 'E', 'S', 'S', '=' }; + const needed_len = needed_len: { + var needed_len: usize = "\x00".len; + if (options.zig_progress_handle) |handle| if (handle != std.os.windows.INVALID_HANDLE_VALUE) { + needed_len += std.fmt.count("ZIG_PROGRESS={d}\x00", .{@intFromPtr(handle)}); + }; + var i: usize = 0; + while (existing_block[i] != 0) { + const start = i; + const entry = mem.sliceTo(existing_block[start..], 0); + i += entry.len + "\x00".len; + if (options.zig_progress_handle != null and entry.len >= zig_progress_key.len and + std.os.windows.eqlIgnoreCaseWtf16(entry[0..zig_progress_key.len], &zig_progress_key)) + { + ranges[ranges_len].end = start; + ranges_len += 1; + ranges[ranges_len].start = i; + } else needed_len += entry.len + "\x00".len; + } + ranges[ranges_len].end = i; + ranges_len += 1; + break :needed_len @max("\x00\x00".len, needed_len); + }; + const block = try gpa.alloc(u16, needed_len); + errdefer gpa.free(block); + var i: usize = 0; + if (options.zig_progress_handle) |handle| if (handle != std.os.windows.INVALID_HANDLE_VALUE) { + @memcpy(block[i..][0..zig_progress_key.len], &zig_progress_key); + i += zig_progress_key.len; + var value_buf: [std.fmt.count("{d}", .{std.math.maxInt(usize)})]u8 = undefined; + const value = std.fmt.bufPrint(&value_buf, "{d}", .{@intFromPtr(handle)}) catch unreachable; + for (block[i..][0..value.len], value) |*r, v| r.* = v; + i += value.len; + block[i] = 0; + i += 1; + }; + for (ranges[0..ranges_len]) |range| { + const range_len = range.end - range.start; + @memcpy(block[i..][0..range_len], existing_block[range.start..range.end]); + i += range_len; + } + // An empty environment is a special case that requires a redundant + // NUL terminator. CreateProcess will read the second code unit even + // though theoretically the first should be enough to recognize that the + // environment is empty (see https://nullprogram.com/blog/2023/08/23/) + for (0..2) |_| { + block[i] = 0; + i += 1; + if (i >= 2) break; + } else unreachable; + assert(i == block.len); + return .{ .slice = block[0 .. i - 1 :0] }; } -test "Map.createBlock" { - const allocator = testing.allocator; - var envmap = Map.init(allocator); +test "Map.createPosixBlock" { + const gpa = testing.allocator; + + var envmap = Map.init(gpa); defer envmap.deinit(); try envmap.put("HOME", "/home/ifreund"); @@ -682,29 +833,24 @@ test "Map.createBlock" { try envmap.put("DEBUGINFOD_URLS", " "); try envmap.put("XCURSOR_SIZE", "24"); - var arena = std.heap.ArenaAllocator.init(allocator); - defer arena.deinit(); - const environ = try envmap.createBlockPosix(arena.allocator(), .{}); + const block = try envmap.createPosixBlock(gpa, .{}); + defer block.deinit(gpa); - try testing.expectEqual(@as(usize, 5), environ.len); + try testing.expectEqual(@as(usize, 5), block.slice.len); - inline for (.{ + for (&[_][]const u8{ "HOME=/home/ifreund", "WAYLAND_DISPLAY=wayland-1", "DISPLAY=:1", "DEBUGINFOD_URLS= ", "XCURSOR_SIZE=24", - }) |target| { - for (environ) |variable| { - if (mem.eql(u8, mem.span(variable orelse continue), target)) break; - } else { - try testing.expect(false); // Environment variable not found - } - } + }, block.slice) |expected, actual| try testing.expectEqualStrings(expected, mem.span(actual.?)); } test Map { - var env = Map.init(testing.allocator); + const gpa = testing.allocator; + + var env: Map = .init(gpa); defer env.deinit(); try env.put("SOMETHING_NEW", "hello"); @@ -740,6 +886,7 @@ test Map { try testing.expect(env.swapRemove("SOMETHING_NEW")); try testing.expect(!env.swapRemove("SOMETHING_NEW")); try testing.expect(env.get("SOMETHING_NEW") == null); + try testing.expect(!env.contains("SOMETHING_NEW")); try testing.expectEqual(@as(Map.Size, 1), env.count()); @@ -749,10 +896,10 @@ test Map { try testing.expectEqualStrings("something else", env.get("кириллица").?); // and WTF-8 that's not valid UTF-8 - const wtf8_with_surrogate_pair = try unicode.wtf16LeToWtf8Alloc(testing.allocator, &[_]u16{ + const wtf8_with_surrogate_pair = try unicode.wtf16LeToWtf8Alloc(gpa, &[_]u16{ mem.nativeToLittle(u16, 0xD83D), // unpaired high surrogate }); - defer testing.allocator.free(wtf8_with_surrogate_pair); + defer gpa.free(wtf8_with_surrogate_pair); try env.put(wtf8_with_surrogate_pair, wtf8_with_surrogate_pair); try testing.expectEqualSlices(u8, wtf8_with_surrogate_pair, env.get(wtf8_with_surrogate_pair).?); @@ -769,13 +916,9 @@ test "convert from Environ to Map and back again" { defer map.deinit(); try map.put("FOO", "BAR"); try map.put("A", ""); - try map.put("", "B"); - var arena_allocator = std.heap.ArenaAllocator.init(gpa); - defer arena_allocator.deinit(); - const arena = arena_allocator.allocator(); - - const environ: Environ = .{ .block = try map.createBlockPosix(arena, .{}) }; + const environ: Environ = .{ .block = try map.createPosixBlock(gpa, .{}) }; + defer environ.block.deinit(gpa); try testing.expectEqual(true, environ.contains(gpa, "FOO")); try testing.expectEqual(false, environ.contains(gpa, "BAR")); @@ -783,7 +926,6 @@ test "convert from Environ to Map and back again" { try testing.expectEqual(true, environ.containsConstant("A")); try testing.expectEqual(false, environ.containsUnempty(gpa, "A")); try testing.expectEqual(false, environ.containsUnemptyConstant("A")); - try testing.expectEqual(true, environ.contains(gpa, "")); try testing.expectEqual(false, environ.contains(gpa, "B")); try testing.expectError(error.EnvironmentVariableMissing, environ.getAlloc(gpa, "BOGUS")); @@ -800,23 +942,47 @@ test "convert from Environ to Map and back again" { try testing.expectEqualDeep(map.values(), map2.values()); } -test createMapWide { - if (builtin.cpu.arch.endian() == .big) return error.SkipZigTest; // TODO +test "Map.putPosixBlock" { + const gpa = testing.allocator; + + var map: Map = .init(gpa); + defer map.deinit(); + + try map.put("FOO", "BAR"); + try map.put("A", ""); + try map.put("ZIG_PROGRESS", "unchanged"); + + const block = try map.createPosixBlock(gpa, .{}); + defer block.deinit(gpa); + + var map2: Map = .init(gpa); + defer map2.deinit(); + try map2.putPosixBlock(block.view()); + + try testing.expectEqualDeep(&[_][]const u8{ "FOO", "A", "ZIG_PROGRESS" }, map2.keys()); + try testing.expectEqualDeep(&[_][]const u8{ "BAR", "", "unchanged" }, map2.values()); +} + +test "Map.putWindowsBlock" { + if (native_os != .windows) return; const gpa = testing.allocator; var map: Map = .init(gpa); defer map.deinit(); + try map.put("FOO", "BAR"); try map.put("A", ""); - try map.put("", "B"); + try map.put("=B", ""); + try map.put("ZIG_PROGRESS", "unchanged"); - const environ: [:0]u16 = try map.createBlockWindows(gpa); - defer gpa.free(environ); + const block = try map.createWindowsBlock(gpa, .{}); + defer block.deinit(gpa); - var map2 = try createMapWide(environ, gpa); + var map2: Map = .init(gpa); defer map2.deinit(); + try map2.putWindowsBlock(block.view()); - try testing.expectEqualDeep(&[_][]const u8{ "FOO", "A", "=B" }, map2.keys()); - try testing.expectEqualDeep(&[_][]const u8{ "BAR", "", "" }, map2.values()); + try testing.expectEqualDeep(&[_][]const u8{ "FOO", "A", "=B", "ZIG_PROGRESS" }, map2.keys()); + try testing.expectEqualDeep(&[_][]const u8{ "BAR", "", "", "unchanged" }, map2.values()); } diff --git a/lib/std/start.zig b/lib/std/start.zig index a8c281ae160f75bdf5c63d1d6c55484ed9a29f7e..e39465fe9b395b0cc4a4395eb589d98b42ac3232 100644 --- a/lib/std/start.zig +++ b/lib/std/start.zig @@ -90,15 +90,15 @@ fn _DllMainCRTStartup( fn wasm_freestanding_start() callconv(.c) void { // This is marked inline because for some reason LLVM in // release mode fails to inline it, and we want fewer call frames in stack traces. - _ = @call(.always_inline, callMain, .{ {}, {} }); + _ = @call(.always_inline, callMain, .{ {}, std.process.Environ.Block.global }); } fn startWasi() callconv(.c) void { // The function call is marked inline because for some reason LLVM in // release mode fails to inline it, and we want fewer call frames in stack traces. switch (builtin.wasi_exec_model) { - .reactor => _ = @call(.always_inline, callMain, .{ {}, {} }), - .command => std.os.wasi.proc_exit(@call(.always_inline, callMain, .{ {}, {} })), + .reactor => _ = @call(.always_inline, callMain, .{ {}, std.process.Environ.Block.global }), + .command => std.os.wasi.proc_exit(@call(.always_inline, callMain, .{ {}, std.process.Environ.Block.global })), } } @@ -476,7 +476,7 @@ fn WinStartup() callconv(.withStackAlign(.c, 1)) noreturn { const cmd_line = std.os.windows.peb().ProcessParameters.CommandLine; const cmd_line_w = cmd_line.Buffer.?[0..@divExact(cmd_line.Length, 2)]; - std.os.windows.ntdll.RtlExitUserProcess(callMain(cmd_line_w, {})); + std.os.windows.ntdll.RtlExitUserProcess(callMain(cmd_line_w, .global)); } fn wWinMainCRTStartup() callconv(.withStackAlign(.c, 1)) noreturn { @@ -620,13 +620,14 @@ fn expandStackSize(phdrs: []elf.Phdr) void { } inline fn callMainWithArgs(argc: usize, argv: [*][*:0]u8, envp: [:null]?[*:0]u8) u8 { + const env_block: std.process.Environ.Block = .{ .slice = envp }; if (std.Options.debug_threaded_io) |t| { if (@sizeOf(std.Io.Threaded.Argv0) != 0) t.argv0.value = argv[0]; - t.environ = .{ .process_environ = .{ .block = envp } }; + t.environ = .{ .process_environ = .{ .block = env_block } }; } std.Thread.maybeAttachSignalStack(); std.debug.maybeEnableSegfaultHandler(); - return callMain(argv[0..argc], envp); + return callMain(argv[0..argc], env_block); } fn main(c_argc: c_int, c_argv: [*][*:0]c_char, c_envp: [*:null]?[*:0]c_char) callconv(.c) c_int { @@ -648,7 +649,7 @@ fn main(c_argc: c_int, c_argv: [*][*:0]c_char, c_envp: [*:null]?[*:0]c_char) cal std.debug.maybeEnableSegfaultHandler(); const cmd_line = std.os.windows.peb().ProcessParameters.CommandLine; const cmd_line_w = cmd_line.Buffer.?[0..@divExact(cmd_line.Length, 2)]; - return callMain(cmd_line_w, {}); + return callMain(cmd_line_w, .global); }, else => {}, } @@ -661,7 +662,7 @@ fn mainWithoutEnv(c_argc: c_int, c_argv: [*][*:0]c_char) callconv(.c) c_int { if (@sizeOf(std.Io.Threaded.Argv0) != 0) { if (std.Options.debug_threaded_io) |t| t.argv0.value = argv[0]; } - return callMain(argv, &.{}); + return callMain(argv, .empty); } /// General error message for a malformed return type diff --git a/test/standalone/env_vars/main.zig b/test/standalone/env_vars/main.zig index 09167f285fe9960fbedb8f9fcb179bc354ca15f4..6ffcae81aecb867afbcf91cdbf92b2678ff575ac 100644 --- a/test/standalone/env_vars/main.zig +++ b/test/standalone/env_vars/main.zig @@ -12,14 +12,10 @@ pub fn main(init: std.process.Init) !void { // containsUnempty { try std.testing.expect(try environ.containsUnempty(allocator, "FOO")); - try std.testing.expect(!(try environ.containsUnempty(allocator, "FOO="))); - try std.testing.expect(!(try environ.containsUnempty(allocator, "FO"))); - try std.testing.expect(!(try environ.containsUnempty(allocator, "FOOO"))); if (builtin.os.tag == .windows) { try std.testing.expect(try environ.containsUnempty(allocator, "foo")); } try std.testing.expect(try environ.containsUnempty(allocator, "EQUALS")); - try std.testing.expect(!(try environ.containsUnempty(allocator, "EQUALS=ABC"))); try std.testing.expect(try environ.containsUnempty(allocator, "КИРиллИЦА")); if (builtin.os.tag == .windows) { try std.testing.expect(try environ.containsUnempty(allocator, "кирИЛЛица")); @@ -35,14 +31,10 @@ pub fn main(init: std.process.Init) !void { // containsUnemptyConstant { try std.testing.expect(environ.containsUnemptyConstant("FOO")); - try std.testing.expect(!environ.containsUnemptyConstant("FOO=")); - try std.testing.expect(!environ.containsUnemptyConstant("FO")); - try std.testing.expect(!environ.containsUnemptyConstant("FOOO")); if (builtin.os.tag == .windows) { try std.testing.expect(environ.containsUnemptyConstant("foo")); } try std.testing.expect(environ.containsUnemptyConstant("EQUALS")); - try std.testing.expect(!environ.containsUnemptyConstant("EQUALS=ABC")); try std.testing.expect(environ.containsUnemptyConstant("КИРиллИЦА")); if (builtin.os.tag == .windows) { try std.testing.expect(environ.containsUnemptyConstant("кирИЛЛица")); @@ -58,14 +50,10 @@ pub fn main(init: std.process.Init) !void { // contains { try std.testing.expect(try environ.contains(allocator, "FOO")); - try std.testing.expect(!(try environ.contains(allocator, "FOO="))); - try std.testing.expect(!(try environ.contains(allocator, "FO"))); - try std.testing.expect(!(try environ.contains(allocator, "FOOO"))); if (builtin.os.tag == .windows) { try std.testing.expect(try environ.contains(allocator, "foo")); } try std.testing.expect(try environ.contains(allocator, "EQUALS")); - try std.testing.expect(!(try environ.contains(allocator, "EQUALS=ABC"))); try std.testing.expect(try environ.contains(allocator, "КИРиллИЦА")); if (builtin.os.tag == .windows) { try std.testing.expect(try environ.contains(allocator, "кирИЛЛица")); @@ -81,14 +69,10 @@ pub fn main(init: std.process.Init) !void { // containsConstant { try std.testing.expect(environ.containsConstant("FOO")); - try std.testing.expect(!environ.containsConstant("FOO=")); - try std.testing.expect(!environ.containsConstant("FO")); - try std.testing.expect(!environ.containsConstant("FOOO")); if (builtin.os.tag == .windows) { try std.testing.expect(environ.containsConstant("foo")); } try std.testing.expect(environ.containsConstant("EQUALS")); - try std.testing.expect(!environ.containsConstant("EQUALS=ABC")); try std.testing.expect(environ.containsConstant("КИРиллИЦА")); if (builtin.os.tag == .windows) { try std.testing.expect(environ.containsConstant("кирИЛЛица")); @@ -104,14 +88,10 @@ pub fn main(init: std.process.Init) !void { // getAlloc { try std.testing.expectEqualSlices(u8, "123", try environ.getAlloc(arena, "FOO")); - try std.testing.expectError(error.EnvironmentVariableMissing, environ.getAlloc(arena, "FOO=")); - try std.testing.expectError(error.EnvironmentVariableMissing, environ.getAlloc(arena, "FO")); - try std.testing.expectError(error.EnvironmentVariableMissing, environ.getAlloc(arena, "FOOO")); if (builtin.os.tag == .windows) { try std.testing.expectEqualSlices(u8, "123", try environ.getAlloc(arena, "foo")); } try std.testing.expectEqualSlices(u8, "ABC=123", try environ.getAlloc(arena, "EQUALS")); - try std.testing.expectError(error.EnvironmentVariableMissing, environ.getAlloc(arena, "EQUALS=ABC")); try std.testing.expectEqualSlices(u8, "non-ascii አማርኛ \u{10FFFF}", try environ.getAlloc(arena, "КИРиллИЦА")); if (builtin.os.tag == .windows) { try std.testing.expectEqualSlices(u8, "non-ascii አማርኛ \u{10FFFF}", try environ.getAlloc(arena, "кирИЛЛица")); @@ -130,13 +110,10 @@ pub fn main(init: std.process.Init) !void { defer environ_map.deinit(); try std.testing.expectEqualSlices(u8, "123", environ_map.get("FOO").?); - try std.testing.expectEqual(null, environ_map.get("FO")); - try std.testing.expectEqual(null, environ_map.get("FOOO")); if (builtin.os.tag == .windows) { try std.testing.expectEqualSlices(u8, "123", environ_map.get("foo").?); } try std.testing.expectEqualSlices(u8, "ABC=123", environ_map.get("EQUALS").?); - try std.testing.expectEqual(null, environ_map.get("EQUALS=ABC")); try std.testing.expectEqualSlices(u8, "non-ascii አማርኛ \u{10FFFF}", environ_map.get("КИРиллИЦА").?); if (builtin.os.tag == .windows) { try std.testing.expectEqualSlices(u8, "non-ascii አማርኛ \u{10FFFF}", environ_map.get("кирИЛЛица").?); diff --git a/test/standalone/windows_argv/fuzz.zig b/test/standalone/windows_argv/fuzz.zig index b955697d3867afe69bf1396699ce96c41c7a7e15..9227bb6f893306686dc0ad08de2cfca89410b815 100644 --- a/test/standalone/windows_argv/fuzz.zig +++ b/test/standalone/windows_argv/fuzz.zig @@ -125,7 +125,7 @@ fn spawnVerify(verify_path: [:0]const u16, cmd_line: [:0]const u16) !windows.DWO .lpReserved2 = null, .hStdInput = null, .hStdOutput = null, - .hStdError = windows.GetStdHandle(windows.STD_ERROR_HANDLE) catch null, + .hStdError = windows.peb().ProcessParameters.hStdError, }; var proc_info: windows.PROCESS_INFORMATION = undefined; @@ -149,7 +149,12 @@ fn spawnVerify(verify_path: [:0]const u16, cmd_line: [:0]const u16) !windows.DWO break :spawn proc_info.hProcess; }; defer windows.CloseHandle(child_proc); - try windows.WaitForSingleObjectEx(child_proc, windows.INFINITE, false); + const infinite_timeout: windows.LARGE_INTEGER = std.math.minInt(windows.LARGE_INTEGER); + switch (windows.ntdll.NtWaitForSingleObject(child_proc, windows.FALSE, &infinite_timeout)) { + windows.NTSTATUS.WAIT_0 => {}, + .TIMEOUT => return error.WaitTimeOut, + else => |status| return windows.unexpectedStatus(status), + } var exit_code: windows.DWORD = undefined; if (windows.kernel32.GetExitCodeProcess(child_proc, &exit_code) == 0) { diff --git a/test/standalone/windows_spawn/main.zig b/test/standalone/windows_spawn/main.zig index 18c9a68c57b9194e72c8f8b29859dd382307cb65..ea28900dac55e76b1c971a55cc9206383a902817 100644 --- a/test/standalone/windows_spawn/main.zig +++ b/test/standalone/windows_spawn/main.zig @@ -233,12 +233,13 @@ fn testExecWithCwdInner(gpa: Allocator, io: Io, command: []const u8, cwd: std.pr } fn renameExe(dir: Io.Dir, io: Io, old_sub_path: []const u8, new_sub_path: []const u8) !void { - var attempt: u5 = 0; + var attempt: u5 = 10; while (true) break dir.rename(old_sub_path, dir, new_sub_path, io) catch |err| switch (err) { error.AccessDenied => { - if (attempt == 13) return error.AccessDenied; + if (attempt == 26) return error.AccessDenied; // give the kernel a chance to finish closing the executable handle - _ = std.os.windows.kernel32.SleepEx(@as(u32, 1) << attempt >> 1, std.os.windows.FALSE); + const interval = @as(std.os.windows.LARGE_INTEGER, -1) << attempt; + _ = std.os.windows.ntdll.NtDelayExecution(std.os.windows.FALSE, &interval); attempt += 1; continue; }, -- 2.54.0 From fcdde3e4c796fcaf160b6161040df8d458523e95 Mon Sep 17 00:00:00 2001 From: Matthew Lugg Date: Wed, 4 Feb 2026 15:36:25 +0100 Subject: [PATCH 194/499] std.Io.Threaded: gracefully handle race leading to ESRCH in unpark() --- lib/std/Io/Threaded.zig | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index c1c63067817b99ea620173ce6807e86ce279cd9e..129ad59543d5a6bd59f441c681dbabdd89d7fdd8 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -17548,8 +17548,8 @@ fn unpark(tids: []const UnparkTid, addr_hint: ?*const anyopaque) void { switch (posix.errno(std.c._lwp_unpark_all(@ptrCast(tids.ptr), tids.len, addr_hint))) { .SUCCESS => return, // For errors, fall through to a loop over `tids`, though this is only expected to - // be possible for ENOMEM (and even that is questionable). - .SRCH => recoverableOsBugDetected(), + // be possible for ENOMEM (even that is questionable) and ESRCH (see comment below). + .SRCH => {}, .FAULT => recoverableOsBugDetected(), .INVAL => recoverableOsBugDetected(), .NOMEM => {}, @@ -17558,7 +17558,11 @@ fn unpark(tids: []const UnparkTid, addr_hint: ?*const anyopaque) void { for (tids) |tid| { switch (posix.errno(std.c._lwp_unpark(@bitCast(tid), addr_hint))) { .SUCCESS => {}, - .SRCH => recoverableOsBugDetected(), + .SRCH => { + // This can happen in a rare race: the thread might have been spuriously + // unparked, so already observed the changing status, and from there have + // exited. That's okay, because the thread has woken up like we wanted. + }, else => recoverableOsBugDetected(), } } -- 2.54.0 From a816f9e245b7f0db96fddc727e2c7004bdb88056 Mon Sep 17 00:00:00 2001 From: Matthew Lugg Date: Wed, 4 Feb 2026 17:59:21 +0000 Subject: [PATCH 195/499] std.Io.Threaded: use _lwp_park correctly for real this time? --- lib/std/Io/Threaded.zig | 76 +++++++++++++++++++++++++---------------- 1 file changed, 47 insertions(+), 29 deletions(-) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 129ad59543d5a6bd59f441c681dbabdd89d7fdd8..be0bb284c3bda515d55ca6a39060dccdfa38bf18 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -628,6 +628,7 @@ const Thread = struct { cancel_protection: Io.CancelProtection, /// Always released when `Status.cancelation` is set to `.parked`. futex_waiter: if (use_parking_futex) ?*parking_futex.Waiter else ?noreturn, + unpark_flag: UnparkFlag, csprng: Csprng, @@ -1018,6 +1019,7 @@ const Thread = struct { if (thread.futex_waiter) |futex_waiter| { parking_futex.removeCanceledWaiter(futex_waiter); } + if (need_unpark_flag) setUnparkFlag(&thread.unpark_flag); unpark(&.{thread.id}, null); return false; }, @@ -1559,6 +1561,7 @@ fn worker(t: *Threaded) void { }), .cancel_protection = .unblocked, .futex_waiter = undefined, + .unpark_flag = unpark_flag_init, .csprng = .{}, }; Thread.current = &thread; @@ -17007,6 +17010,7 @@ const parking_futex = struct { /// * Unparking the thread (*after* the above, so that the `Waiter` does not go out of scope /// while it is still in the `Bucket`). thread_status: *std.atomic.Value(Thread.Status), + unpark_flag: if (need_unpark_flag) *UnparkFlag else void, }; fn bucketForAddress(address: usize) *Bucket { @@ -17045,9 +17049,11 @@ const parking_futex = struct { .address = @intFromPtr(ptr), .tid = self_tid, .thread_status = undefined, // populated in critical section + .unpark_flag = undefined, // populated in critical section }; var status_buf: std.atomic.Value(Thread.Status) = undefined; + var unpark_flag_buf: UnparkFlag = unpark_flag_init; { bucket.mutex.lock(); @@ -17062,7 +17068,7 @@ const parking_futex = struct { // This is in the critical section to avoid marking the thread as parked until we're // certain that we're actually going to park. - waiter.thread_status = status: { + waiter.thread_status, waiter.unpark_flag = status: { cancelable: { if (uncancelable) break :cancelable; const thread = opt_thread orelse break :cancelable; @@ -17090,19 +17096,19 @@ const parking_futex = struct { .blocked_canceling => unreachable, } // We could now be unparked for a cancelation at any time! - break :status &thread.status; + break :status .{ &thread.status, if (need_unpark_flag) &thread.unpark_flag }; } // This is an uncancelable wait, so just use `status_buf`. Note that the value of // `status_buf.awaitable` is irrelevant because this is only visible to futex code, // while only cancelation cares about `awaitable`. status_buf.raw = .{ .cancelation = .parked, .awaitable = .null }; - break :status &status_buf; + break :status .{ &status_buf, if (need_unpark_flag) &unpark_flag_buf }; }; bucket.waiters.append(&waiter.node); } - if (park(timeout, ptr, waiter.thread_status)) { + if (park(timeout, ptr, waiter.unpark_flag)) { // We were unparked by either `wake` or cancelation, so our current status is either // `.none` or `.canceling`. In either case, they've already removed `waiter` from // `bucket`, so we have nothing more to do! @@ -17127,7 +17133,7 @@ const parking_futex = struct { // to unpark us. Whoever did that will remove us from `bucket`. Wait for // that (and drop the unpark request in doing so). // New status is `.none` or `.canceling` respectively. - park(.none, ptr, waiter.thread_status) catch |e| switch (e) { + park(.none, ptr, waiter.unpark_flag) catch |e| switch (e) { error.Timeout => unreachable, }; }, @@ -17201,6 +17207,7 @@ const parking_futex = struct { waking_head = node.next; const waiter: *Waiter = @fieldParentPtr("node", node); unpark_buf[unpark_len] = waiter.tid; + if (need_unpark_flag) setUnparkFlag(waiter.unpark_flag); unpark_len += 1; if (unpark_len == unpark_buf.len) { unpark(&unpark_buf, ptr); @@ -17249,7 +17256,7 @@ const parking_sleep = struct { .blocked_canceling => unreachable, } } - if (park(timeout, null, &thread.status)) { + if (park(timeout, null, if (need_unpark_flag) &thread.unpark_flag)) { // The only reason this could possibly happen is cancelation. const old_status = thread.status.load(.monotonic); assert(old_status.cancelation == .canceling); @@ -17272,7 +17279,7 @@ const parking_sleep = struct { // us for a cancelation. Whoever did that will have called `unpark`, so // drop that unpark request by waiting for it. // Status is still `.canceling`. - park(.none, null, &thread.status) catch |e| switch (e) { + park(.none, null, if (need_unpark_flag) &thread.unpark_flag) catch |e| switch (e) { error.Timeout => unreachable, }; return; @@ -17288,8 +17295,8 @@ const parking_sleep = struct { } } // Uncancelable sleep; we expect not to be manually unparked. - var dummy_status: std.atomic.Value(Thread.Status) = .init(.{ .cancelation = .parked, .awaitable = .null }); - if (park(timeout, null, &dummy_status)) { + var dummy_flag: UnparkFlag = unpark_flag_init; + if (park(timeout, null, if (need_unpark_flag) &dummy_flag)) { unreachable; // unexpected unpark } else |err| switch (err) { error.Timeout => return, @@ -17322,7 +17329,7 @@ const ParkingMutex = struct { } }; const Waiter = struct { - status: std.atomic.Value(Thread.Status), + unpark_flag: UnparkFlag, /// Never modified once the `Waiter` is in the linked list. next: ?*Waiter, /// Never modified once the `Waiter` is in the linked list. @@ -17345,7 +17352,7 @@ const ParkingMutex = struct { const self_tid = if (Thread.current) |t| t.id else std.Thread.getCurrentId(); var waiter: Waiter = .{ .next = old_waiter, - .status = .init(.{ .cancelation = .parked, .awaitable = .null }), + .unpark_flag = unpark_flag_init, .tid = self_tid, }; if (m.state.cmpxchgWeak( @@ -17357,11 +17364,9 @@ const ParkingMutex = struct { continue :state new_state; } // We're now in the list of waiters---park until we're given the lock. - park(.none, m, &waiter.status) catch |err| switch (err) { + park(.none, m, if (need_unpark_flag) &waiter.unpark_flag) catch |err| switch (err) { error.Timeout => unreachable, }; - // We now hold the lock. - assert(waiter.status.load(.monotonic).cancelation == .none); return; }, } @@ -17383,7 +17388,7 @@ const ParkingMutex = struct { _ => |last_state| { // The logic here does not have ABA problems, and does some accesses non-atomically, // because `Waiter.next` is owned by the lock holder (that's us!) once the waiter is - // in the linked list, up until we set `Waiter.status` to `.none`. + // in the linked list, up until we unpark the waiter. // Run through the waiter list to the end to ensure fairness. This is obviously not // ideal, but it shouldn't be a big deal in practice provided the critical section @@ -17412,8 +17417,8 @@ const ParkingMutex = struct { } } // Now we're ready to actually hand the lock over to them. - const tid = waiter.tid; // load this before the store below potentially invalidates `waiter` - waiter.status.store(.{ .cancelation = .none, .awaitable = .null }, .release); // release lock + const tid = waiter.tid; // load before the unpark below potentially invalidates `waiter` + if (need_unpark_flag) setUnparkFlag(&waiter.unpark_flag); unpark(&.{tid}, m); return; }, @@ -17451,15 +17456,34 @@ fn timeoutToWindowsInterval(timeout: Io.Timeout) ?windows.LARGE_INTEGER { } } +/// The API on NetBSD and Illumos sucks and can unpark spuriously (well, it *can't*, but signals +/// cause an indistinguishable unblock, and libpthread really likes to leave unparks pending). +/// As such, on these targets only, we need to pass around a flag to track whether a thread is +/// "actually" being unparked. +const need_unpark_flag = switch (native_os) { + .netbsd, .illumos => true, + else => false, +}; +const UnparkFlag = if (need_unpark_flag) std.atomic.Value(bool) else void; +const unpark_flag_init: UnparkFlag = if (need_unpark_flag) .init(false); +/// Must be called before `unpark`. After this function is called, the thread may be unparked at any +/// time, so the caller must not reference values on its stack. +fn setUnparkFlag(f: *UnparkFlag) void { + f.store(true, .release); +} + +/// The type passed into `unpark` for the thread ID. You'd think this was just a `std.Thread.Id`, +/// but it seems that someone at Microsoft forgot how big their TIDs are supposed to be. +const UnparkTid = switch (native_os) { + .windows => usize, + else => std.Thread.Id, +}; + fn park( timeout: Io.Timeout, /// This value has no semantic effect, but may allow the OS to optimize the operation. addr_hint: ?*const anyopaque, - /// The API on NetBSD and Illumos sucks and can unpark spuriously (well, it *can't*, but signals - /// cause an indistinguishable unblock, and libpthread really likes to leave unparks pending). - /// As such, on these targets only, this `status` is checked to determine if an unpark is real. - /// no way to differentiate - status: *std.atomic.Value(Thread.Status), + unpark_flag: if (need_unpark_flag) *UnparkFlag else void, ) error{Timeout}!void { comptime assert(use_parking_futex or use_parking_sleep); switch (native_os) { @@ -17502,7 +17526,7 @@ fn park( }; // It's okay to pass the same timeout in a loop. If it's a duration, the OS actually // writes the remaining time into the buffer when the syscall returns. - while (status.load(.monotonic).cancelation == .parked) { + while (!unpark_flag.swap(false, .acquire)) { switch (posix.errno(std.c._lwp_park( if (clock_real) .REALTIME else .MONOTONIC, .{ .ABSTIME = abstime }, @@ -17523,12 +17547,6 @@ fn park( else => comptime unreachable, } } - -const UnparkTid = switch (native_os) { - // `NtAlertMultipleThreadByThreadId` is weird and wants 64-bit thread IDs? - .windows => usize, - else => std.Thread.Id, -}; /// `addr_hint` has no semantic effect, but may allow the OS to optimize this operation. fn unpark(tids: []const UnparkTid, addr_hint: ?*const anyopaque) void { comptime assert(use_parking_futex or use_parking_sleep); -- 2.54.0 From 06879041acfe73e62f549a412d9f012a190113a3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Wed, 4 Feb 2026 00:00:06 +0100 Subject: [PATCH 196/499] link.Lld: disable parallel linking on NetBSD host To work around NetBSD 10.1 malloc bugs. --- src/link/Lld.zig | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/src/link/Lld.zig b/src/link/Lld.zig index fa94e534593a81c853c85911bf7054af03b65eab..5c7ba84623bde145063b4ff87b08434840ec43d9 100644 --- a/src/link/Lld.zig +++ b/src/link/Lld.zig @@ -356,6 +356,16 @@ fn linkAsArchive(lld: *Lld, arena: Allocator) !void { if (bad) return error.UnableToWriteArchive; } +fn addCommonArgs(argv: *std.array_list.Managed([]const u8), coff: bool) !void { + if (builtin.os.tag == .netbsd) { + // NetBSD 10.1's `malloc` appears to have some nasty bugs that occur + // when doing parallel linking in LLD, manifesting as input and/or + // output section memory randomly being unmapped. So just don't do + // parallel linking for now. + try argv.append(if (coff) "-threads:1" else "--threads=1"); + } +} + fn coffLink(lld: *Lld, arena: Allocator) !void { const comp = lld.base.comp; const gpa = comp.gpa; @@ -418,6 +428,7 @@ fn coffLink(lld: *Lld, arena: Allocator) !void { // it calls exit() and does not reset all global data between invocations. const linker_command = "lld-link"; try argv.appendSlice(&[_][]const u8{ comp.self_exe_path.?, linker_command }); + try addCommonArgs(&argv, true); if (target.isMinGW()) { try argv.append("-lldmingw"); @@ -836,6 +847,8 @@ fn elfLink(lld: *Lld, arena: Allocator) !void { // it calls exit() and does not reset all global data between invocations. const linker_command = "ld.lld"; try argv.appendSlice(&[_][]const u8{ comp.self_exe_path.?, linker_command }); + try addCommonArgs(&argv, false); + if (is_obj) { try argv.append("-r"); } @@ -1401,6 +1414,8 @@ fn wasmLink(lld: *Lld, arena: Allocator) !void { // it calls exit() and does not reset all global data between invocations. const linker_command = "wasm-ld"; try argv.appendSlice(&[_][]const u8{ comp.self_exe_path.?, linker_command }); + try addCommonArgs(&argv, false); + try argv.append("--error-limit=0"); if (comp.config.lto != .none) { @@ -1724,6 +1739,7 @@ fn spawnLld(comp: *Compilation, arena: Allocator, argv: []const []const u8) !voi if (stderr.len > 0) log.warn("unexpected LLD stderr:\n{s}", .{stderr}); } +const builtin = @import("builtin"); const std = @import("std"); const Io = std.Io; const Allocator = std.mem.Allocator; -- 2.54.0 From 012be3efd779764de8cb910f3d07a25c13861c48 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Tue, 3 Feb 2026 23:45:46 +0100 Subject: [PATCH 197/499] Revert "ci: temporarily disable x86_64-netbsd while I investigate failures" This reverts commit 99ec1ee3536b577bd1d14facde523c503108886d. --- .forgejo/workflows/ci.yaml | 40 +++++++++++++++++++------------------- 1 file changed, 20 insertions(+), 20 deletions(-) diff --git a/.forgejo/workflows/ci.yaml b/.forgejo/workflows/ci.yaml index 7e758bb67db1b935fd7fb4efeb7174a8a2f92ba1..48813b9001d0463d39b7ce8e2566ae5dad555b87 100644 --- a/.forgejo/workflows/ci.yaml +++ b/.forgejo/workflows/ci.yaml @@ -197,26 +197,26 @@ jobs: run: sh ci/x86_64-linux-release.sh timeout-minutes: 360 - #x86_64-netbsd-debug: - # runs-on: [self-hosted, x86_64-netbsd] - # steps: - # - name: Checkout - # uses: https://codeberg.org/ziglang/checkout@19af6bac491e2534a4687a50ee84fa7f13258d28 - # with: - # fetch-depth: 0 - # - name: Build and Test - # run: sh ci/x86_64-netbsd-debug.sh - # timeout-minutes: 120 - #x86_64-netbsd-release: - # runs-on: [self-hosted, x86_64-netbsd] - # steps: - # - name: Checkout - # uses: https://codeberg.org/ziglang/checkout@19af6bac491e2534a4687a50ee84fa7f13258d28 - # with: - # fetch-depth: 0 - # - name: Build and Test - # run: sh ci/x86_64-netbsd-release.sh - # timeout-minutes: 120 + x86_64-netbsd-debug: + runs-on: [self-hosted, x86_64-netbsd] + steps: + - name: Checkout + uses: https://codeberg.org/ziglang/checkout@19af6bac491e2534a4687a50ee84fa7f13258d28 + with: + fetch-depth: 0 + - name: Build and Test + run: sh ci/x86_64-netbsd-debug.sh + timeout-minutes: 120 + x86_64-netbsd-release: + runs-on: [self-hosted, x86_64-netbsd] + steps: + - name: Checkout + uses: https://codeberg.org/ziglang/checkout@19af6bac491e2534a4687a50ee84fa7f13258d28 + with: + fetch-depth: 0 + - name: Build and Test + run: sh ci/x86_64-netbsd-release.sh + timeout-minutes: 120 x86_64-openbsd-debug: runs-on: [self-hosted, x86_64-openbsd] -- 2.54.0 From 71156aff806856d5d48e72cd8aeb9315b9ae0b62 Mon Sep 17 00:00:00 2001 From: Jacob Young Date: Sat, 31 Jan 2026 20:22:53 -0500 Subject: [PATCH 198/499] std.Progress: implement ipc resource cleanup --- lib/std/Build/Step.zig | 52 +-- lib/std/Io/Threaded.zig | 93 ++-- lib/std/Progress.zig | 938 ++++++++++++++++++++-------------------- lib/std/os/linux.zig | 18 + 4 files changed, 557 insertions(+), 544 deletions(-) diff --git a/lib/std/Build/Step.zig b/lib/std/Build/Step.zig index b9581196c0ec59e125a8d0da6f607264f2afddb0..b518826843caad753b23051f92840af7b6143a22 100644 --- a/lib/std/Build/Step.zig +++ b/lib/std/Build/Step.zig @@ -386,10 +386,14 @@ pub const ZigProcess = struct { child: std.process.Child, multi_reader_buffer: Io.File.MultiReader.Buffer(2), multi_reader: Io.File.MultiReader, - progress_ipc_fd: if (std.Progress.have_ipc) ?std.posix.fd_t else void, + progress_ipc_index: ?if (std.Progress.have_ipc) std.Progress.Ipc.Index else noreturn, pub const StreamEnum = enum { stdout, stderr }; + pub fn saveState(zp: *ZigProcess, prog_node: std.Progress.Node) void { + zp.progress_ipc_index = if (std.Progress.have_ipc) prog_node.takeIpcIndex() else null; + } + pub fn deinit(zp: *ZigProcess, io: Io) void { zp.child.kill(io); zp.multi_reader.deinit(); @@ -417,7 +421,14 @@ pub fn evalZigProcess( if (s.getZigProcess()) |zp| update: { assert(watch); - if (std.Progress.have_ipc) if (zp.progress_ipc_fd) |fd| prog_node.setIpcFd(fd); + if (zp.progress_ipc_index) |ipc_index| prog_node.setIpcIndex(ipc_index); + zp.progress_ipc_index = null; + var exited = false; + defer if (exited) { + s.cast(Compile).?.zig_process = null; + zp.deinit(io); + gpa.destroy(zp); + } else zp.saveState(prog_node); const result = zigProcessUpdate(s, zp, watch, web_server, gpa) catch |err| switch (err) { error.BrokenPipe, error.EndOfStream => |reason| { std.log.info("{s} restart required: {t}", .{ argv[0], reason }); @@ -426,7 +437,7 @@ pub fn evalZigProcess( return s.fail("unable to wait for {s}: {t}", .{ argv[0], e }); }; _ = term; - s.clearZigProcess(gpa); + exited = true; break :update; }, else => |e| return e, @@ -442,7 +453,7 @@ pub fn evalZigProcess( return s.fail("unable to wait for {s}: {t}", .{ argv[0], e }); }; s.result_peak_rss = zp.child.resource_usage_statistics.getMaxRss() orelse 0; - s.clearZigProcess(gpa); + exited = true; try handleChildProcessTerm(s, term); return error.MakeFailed; } @@ -467,19 +478,16 @@ pub fn evalZigProcess( .progress_node = prog_node, }) catch |err| return s.fail("failed to spawn zig compiler {s}: {t}", .{ argv[0], err }); - zp.* = .{ - .child = zp.child, - .multi_reader_buffer = undefined, - .multi_reader = undefined, - .progress_ipc_fd = if (std.Progress.have_ipc) prog_node.getIpcFd() else {}, - }; zp.multi_reader.init(gpa, io, zp.multi_reader_buffer.toStreams(), &.{ zp.child.stdout.?, zp.child.stderr.?, }); - if (watch) s.setZigProcess(zp); + if (watch) s.cast(Compile).?.zig_process = zp; defer if (!watch) zp.deinit(io); - const result = try zigProcessUpdate(s, zp, watch, web_server, gpa); + const result = result: { + defer if (watch) zp.saveState(prog_node); + break :result try zigProcessUpdate(s, zp, watch, web_server, gpa); + }; if (!watch) { // Send EOF to stdin. @@ -670,26 +678,6 @@ pub fn getZigProcess(s: *Step) ?*ZigProcess { }; } -fn setZigProcess(s: *Step, zp: *ZigProcess) void { - switch (s.id) { - .compile => s.cast(Compile).?.zig_process = zp, - else => unreachable, - } -} - -fn clearZigProcess(s: *Step, gpa: Allocator) void { - switch (s.id) { - .compile => { - const compile = s.cast(Compile).?; - if (compile.zig_process) |zp| { - gpa.destroy(zp); - compile.zig_process = null; - } - }, - else => unreachable, - } -} - fn sendMessage(io: Io, file: Io.File, tag: std.zig.Client.Message.Tag) !void { const header: std.zig.Client.Message.Header = .{ .tag = tag, diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 49132e79189842591b6b0a443bcd6cecd5a4fc85..197cc6c6574027cec15af7ede2edc49e265dee7e 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -19,7 +19,7 @@ const Alignment = std.mem.Alignment; const assert = std.debug.assert; const posix = std.posix; const windows = std.os.windows; -const ws2_32 = std.os.windows.ws2_32; +const ws2_32 = windows.ws2_32; /// Thread-safe. /// @@ -2609,8 +2609,7 @@ fn batchAwaitAsync(userdata: ?*anyopaque, b: *Io.Batch) Io.Cancelable!void { // opportunity to find additional ready operations. break :t 0; } - const max_poll_ms = std.math.maxInt(i32); - break :t max_poll_ms; + break :t std.math.maxInt(i32); }; const syscall = try Syscall.start(); const rc = posix.system.poll(&poll_buffer, poll_len, timeout_ms); @@ -2730,6 +2729,7 @@ fn batchAwaitConcurrent(userdata: ?*anyopaque, b: *Io.Batch, timeout: Io.Timeout break :allocation allocation; }; @memcpy(slice[0..poll_buffer_len], storage.slice); + storage.slice = slice; } storage.slice[len] = .{ .fd = file.handle, @@ -2783,9 +2783,7 @@ fn batchAwaitConcurrent(userdata: ?*anyopaque, b: *Io.Batch, timeout: Io.Timeout } const d = deadline orelse break :t -1; const duration = d.durationFromNow(t_io); - if (duration.raw.nanoseconds <= 0) return error.Timeout; - const max_poll_ms = std.math.maxInt(i32); - break :t @intCast(@min(max_poll_ms, duration.raw.toMilliseconds())); + break :t @min(@max(0, duration.raw.toMilliseconds()), std.math.maxInt(i32)); }; const syscall = try Syscall.start(); const rc = posix.system.poll(&poll_buffer, poll_storage.len, timeout_ms); @@ -14420,7 +14418,10 @@ const WindowsEnvironStrings = struct { PATHEXT: ?[:0]const u16 = null, fn scan() WindowsEnvironStrings { - const ptr = windows.peb().ProcessParameters.Environment; + const peb = windows.peb(); + assert(windows.ntdll.RtlEnterCriticalSection(peb.FastPebLock) == .SUCCESS); + defer assert(windows.ntdll.RtlLeaveCriticalSection(peb.FastPebLock) == .SUCCESS); + const ptr = peb.ProcessParameters.Environment; var result: WindowsEnvironStrings = .{}; var i: usize = 0; @@ -14446,7 +14447,7 @@ const WindowsEnvironStrings = struct { inline for (@typeInfo(WindowsEnvironStrings).@"struct".fields) |field| { const field_name_w = comptime std.unicode.wtf8ToWtf16LeStringLiteral(field.name); - if (std.os.windows.eqlIgnoreCaseWtf16(key_w, field_name_w)) @field(result, field.name) = value_w; + if (windows.eqlIgnoreCaseWtf16(key_w, field_name_w)) @field(result, field.name) = value_w; } } @@ -14465,29 +14466,46 @@ fn scanEnviron(t: *Threaded) void { // This value expires with any call that modifies the environment, // which is outside of this Io implementation's control, so references // must be short-lived. - const ptr = windows.peb().ProcessParameters.Environment; + const peb = windows.peb(); + assert(windows.ntdll.RtlEnterCriticalSection(peb.FastPebLock) == .SUCCESS); + defer assert(windows.ntdll.RtlLeaveCriticalSection(peb.FastPebLock) == .SUCCESS); + const ptr = peb.ProcessParameters.Environment; var i: usize = 0; while (ptr[i] != 0) { - const key_start = i; // There are some special environment variables that start with =, // so we need a special case to not treat = as a key/value separator // if it's the first character. // https://devblogs.microsoft.com/oldnewthing/20100506-00/?p=14133 - if (ptr[key_start] == '=') i += 1; - + const key_start = i; + if (ptr[i] == '=') i += 1; while (ptr[i] != 0 and ptr[i] != '=') : (i += 1) {} const key_w = ptr[key_start..i]; - if (std.mem.eql(u16, key_w, &.{ 'N', 'O', '_', 'C', 'O', 'L', 'O', 'R' })) { - t.environ.exist.NO_COLOR = true; - } else if (std.mem.eql(u16, key_w, &.{ 'C', 'L', 'I', 'C', 'O', 'L', 'O', 'R', '_', 'F', 'O', 'R', 'C', 'E' })) { - t.environ.exist.CLICOLOR_FORCE = true; - } - comptime assert(@sizeOf(Environ.String) == 0); + const value_start = i + 1; while (ptr[i] != 0) : (i += 1) {} // skip over '=' and value + const value_w = ptr[value_start..i]; i += 1; // skip over null byte + + if (windows.eqlIgnoreCaseWtf16(key_w, &.{ 'N', 'O', '_', 'C', 'O', 'L', 'O', 'R' })) { + t.environ.exist.NO_COLOR = true; + } else if (windows.eqlIgnoreCaseWtf16(key_w, &.{ 'C', 'L', 'I', 'C', 'O', 'L', 'O', 'R', '_', 'F', 'O', 'R', 'C', 'E' })) { + t.environ.exist.CLICOLOR_FORCE = true; + } else if (windows.eqlIgnoreCaseWtf16(key_w, &.{ 'Z', 'I', 'G', '_', 'P', 'R', 'O', 'G', 'R', 'E', 'S', 'S' })) { + t.environ.zig_progress_file = file: { + var value_buf: [std.fmt.count("{d}", .{std.math.maxInt(usize)})]u8 = undefined; + const len = std.unicode.calcWtf8Len(value_w); + if (len > value_buf.len) break :file error.UnrecognizedFormat; + assert(std.unicode.wtf16LeToWtf8(&value_buf, value_w) == len); + break :file .{ + .handle = @ptrFromInt(std.fmt.parseInt(usize, value_buf[0..len], 10) catch + break :file error.UnrecognizedFormat), + .flags = .{ .nonblocking = true }, + }; + }; + } + comptime assert(@sizeOf(Environ.String) == 0); } } else if (native_os == .wasi and !builtin.link_libc) { var environ_count: usize = undefined; @@ -14549,20 +14567,9 @@ fn scanEnviron(t: *Threaded) void { t.environ.exist.CLICOLOR_FORCE = true; } else if (std.mem.eql(u8, key, "ZIG_PROGRESS")) { t.environ.zig_progress_file = file: { - const int = std.fmt.parseInt(switch (@typeInfo(File.Handle)) { - .int => |int_info| @Int( - .unsigned, - int_info.bits - @intFromBool(int_info.signedness == .signed), - ), - .pointer => usize, - else => break :file error.UnsupportedOperation, - }, value, 10) catch break :file error.UnrecognizedFormat; break :file .{ - .handle = switch (@typeInfo(File.Handle)) { - .int => int, - .pointer => @ptrFromInt(int), - else => comptime unreachable, - }, + .handle = std.fmt.parseInt(u31, value, 10) catch + break :file error.UnrecognizedFormat, .flags = .{ .nonblocking = true }, }; }; @@ -14668,16 +14675,17 @@ fn spawnPosix(t: *Threaded, options: process.SpawnOptions) process.SpawnError!Sp const any_ignore = (options.stdin == .ignore or options.stdout == .ignore or options.stderr == .ignore); const dev_null_fd = if (any_ignore) try getDevNullFd(t) else undefined; - const prog_pipe: [2]posix.fd_t = p: { - if (options.progress_node.index == .none) { - break :p .{ -1, -1 }; - } else { - // We use CLOEXEC for the same reason as in `pipe_flags`. - break :p try pipe2(.{ .NONBLOCK = true, .CLOEXEC = true }); - } - }; + const prog_pipe: [2]posix.fd_t = if (options.progress_node.index != .none) + // We use CLOEXEC for the same reason as in `pipe_flags`. + try pipe2(.{ .NONBLOCK = true, .CLOEXEC = true }) + else + .{ -1, -1 }; errdefer destroyPipe(prog_pipe); + if (native_os == .linux and prog_pipe[0] != -1) { + _ = posix.system.fcntl(prog_pipe[0], posix.F.SETPIPE_SZ, @as(u32, std.Progress.max_packet_len * 2)); + } + var arena_allocator = std.heap.ArenaAllocator.init(t.allocator); defer arena_allocator.deinit(); const arena = arena_allocator.allocator(); @@ -14801,7 +14809,7 @@ fn spawnPosix(t: *Threaded, options: process.SpawnOptions) process.SpawnError!Sp if (options.stderr == .pipe) posix.close(stderr_pipe[1]); if (prog_pipe[1] != -1) posix.close(prog_pipe[1]); - options.progress_node.setIpcFd(prog_pipe[0]); + options.progress_node.setIpcFile(t, .{ .handle = prog_pipe[0], .flags = .{ .nonblocking = true } }); return .{ .pid = pid, @@ -15259,8 +15267,9 @@ fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) pro const prog_pipe = if (options.progress_node.index != .none) try t.windowsCreatePipe(.{ .server = .{ .attributes = .{ .INHERIT = false }, .mode = .{ .IO = .ASYNCHRONOUS } }, - .client = .{ .attributes = .{ .INHERIT = true }, .mode = .{ .IO = .SYNCHRONOUS_NONALERT } }, + .client = .{ .attributes = .{ .INHERIT = true }, .mode = .{ .IO = .ASYNCHRONOUS } }, .inbound = true, + .quota = std.Progress.max_packet_len * 2, }) else undefined; errdefer if (options.progress_node.index != .none) for (prog_pipe) |handle| windows.CloseHandle(handle); @@ -15476,7 +15485,7 @@ fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) pro if (options.progress_node.index != .none) { windows.CloseHandle(prog_pipe[1]); - options.progress_node.setIpcFd(prog_pipe[0]); + options.progress_node.setIpcFile(t, .{ .handle = prog_pipe[0], .flags = .{ .nonblocking = true } }); } return .{ diff --git a/lib/std/Progress.zig b/lib/std/Progress.zig index 780f27ec75a89b11c414a2463c3ff68fb9041b57..f17fed0a5bc20a5810ceec5853e478a4bf11321a 100644 --- a/lib/std/Progress.zig +++ b/lib/std/Progress.zig @@ -11,7 +11,7 @@ const windows = std.os.windows; const testing = std.testing; const assert = std.debug.assert; const posix = std.posix; -const Writer = std.Io.Writer; +const Writer = Io.Writer; /// Currently this API only supports this value being set to stderr, which /// happens automatically inside `start`. @@ -21,13 +21,10 @@ io: Io, terminal_mode: TerminalMode, -update_worker: ?Io.Future(void), +update_worker: ?Io.Future(WorkerError!void), /// Atomically set by SIGWINCH as well as the root done() function. redraw_event: Io.Event, -/// Indicates a request to shut down and reset global state. -/// Accessed atomically. -done: bool, need_clear: bool, status: Status, @@ -43,15 +40,19 @@ draw_buffer: []u8, /// This is in a separate array from `node_storage` but with the same length so /// that it can be iterated over efficiently without trashing too much of the /// CPU cache. -node_parents: []Node.Parent, -node_storage: []Node.Storage, -node_freelist_next: []Node.OptionalIndex, +node_parents: [node_storage_buffer_len]Node.Parent, +node_storage: [node_storage_buffer_len]Node.Storage, +node_freelist_next: [node_storage_buffer_len]Node.OptionalIndex, node_freelist: Freelist, /// This is the number of elements in node arrays which have been used so far. Nodes before this /// index are either active, or on the freelist. The remaining nodes are implicitly free. This /// value may at times temporarily exceed the node count. node_end_index: u32, +ipc_next: Ipc.SlotAtomic, +ipc: [ipc_storage_buffer_len]Ipc, +ipc_files: [ipc_storage_buffer_len]Io.File, + start_failure: StartFailure, pub const Status = enum { @@ -77,6 +78,80 @@ const Freelist = packed struct(u32) { generation: u24, }; +pub const Ipc = packed struct(u32) { + /// mutex protecting `file` use, only locked by `serializeIpc` + locked: bool, + /// when unlocked: whether `file` is defined + /// when locked: whether `file` does not need to be closed + valid: bool, + unused: @Int(.unsigned, 32 - 2 - @bitSizeOf(Generation)) = 0, + generation: Generation, + + pub const Slot = std.math.IntFittingRange(0, ipc_storage_buffer_len - 1); + pub const Generation = @Int(.unsigned, 32 - @bitSizeOf(Slot)); + + const SlotAtomic = @Int(.unsigned, std.math.ceilPowerOfTwoAssert(usize, @min(@bitSizeOf(Slot), 8))); + + pub const Index = packed struct(u32) { + slot: Slot, + generation: Generation, + }; + + const Data = struct { + state: State, + bytes_read: u16, + main_index: u8, + start_index: u8, + nodes_len: u8, + + const State = enum { unused, pending, ready }; + + /// No operations have been started on this file. + const unused: Data = .{ + .state = .unused, + .bytes_read = 0, + .main_index = 0, + .start_index = 0, + .nodes_len = 0, + }; + + fn findLastPacket(data: *const Data, buffer: *const [max_packet_len]u8) struct { u16, u16 } { + assert(data.state == .ready); + var packet_start: u16 = 0; + var packet_end: u16 = 0; + const bytes_read = data.bytes_read; + while (bytes_read - packet_end >= 1) { + const nodes_len: u16 = buffer[packet_end]; + const packet_len = 1 + nodes_len * (@sizeOf(Node.Storage) + @sizeOf(Node.Parent)); + if (packet_end + packet_len > bytes_read) break; + packet_start = packet_end; + packet_end += packet_len; + } + return .{ packet_start, packet_end }; + } + + fn rebase( + data: *Data, + buffer: *[max_packet_len]u8, + vec: *[1][]u8, + batch: *std.Io.Batch, + slot: Slot, + packet_end: u16, + ) void { + assert(data.state == .ready); + const remaining = buffer[packet_end..data.bytes_read]; + @memmove(buffer[0..remaining.len], remaining); + vec.* = .{buffer[remaining.len..]}; + batch.addAt(slot, .{ .file_read_streaming = .{ + .file = global_progress.ipc_files[slot], + .data = vec, + } }); + data.state = .pending; + data.bytes_read = @intCast(remaining.len); + } + }; +}; + pub const TerminalMode = union(enum) { off, ansi_escape_codes, @@ -116,7 +191,7 @@ pub const Node = struct { pub const none: Node = .{ .index = .none }; - pub const max_name_len = 40; + pub const max_name_len = 120; const Storage = extern struct { /// Little endian. @@ -127,25 +202,16 @@ pub const Node = struct { name: [max_name_len]u8 align(@alignOf(usize)), /// Not thread-safe. - fn getIpcFd(s: Storage) ?Io.File.Handle { - return if (s.estimated_total_count == std.math.maxInt(u32)) switch (@typeInfo(Io.File.Handle)) { - .int => @bitCast(s.completed_count), - .pointer => @ptrFromInt(s.completed_count), - else => @compileError("unsupported fd_t of " ++ @typeName(Io.File.Handle)), - } else null; + fn getIpcIndex(s: Storage) ?Ipc.Index { + return if (s.estimated_total_count == std.math.maxInt(u32)) @bitCast(s.completed_count) else null; } /// Thread-safe. - fn setIpcFd(s: *Storage, fd: Io.File.Handle) void { - const integer: u32 = switch (@typeInfo(Io.File.Handle)) { - .int => @bitCast(fd), - .pointer => @intCast(@intFromPtr(fd)), - else => @compileError("unsupported fd_t of " ++ @typeName(Io.File.Handle)), - }; + fn setIpcIndex(s: *Storage, ipc_index: Ipc.Index) void { // `estimated_total_count` max int indicates the special state that // causes `completed_count` to be treated as a file descriptor, so // the order here matters. - @atomicStore(u32, &s.completed_count, integer, .monotonic); + @atomicStore(u32, &s.completed_count, @bitCast(ipc_index), .monotonic); @atomicStore(u32, &s.estimated_total_count, std.math.maxInt(u32), .release); // synchronizes with acquire in `serialize` } @@ -155,6 +221,14 @@ pub const Node = struct { s.estimated_total_count = @byteSwap(s.estimated_total_count); } + fn copyRoot(dest: *Node.Storage, src: *align(1) const Node.Storage) void { + dest.* = .{ + .completed_count = src.completed_count, + .estimated_total_count = src.estimated_total_count, + .name = if (src.name[0] == 0) dest.name else src.name, + }; + } + comptime { assert((@sizeOf(Storage) % 4) == 0); } @@ -242,7 +316,7 @@ pub const Node = struct { } const free_index = @atomicRmw(u32, &global_progress.node_end_index, .Add, 1, .monotonic); - if (free_index >= global_progress.node_storage.len) { + if (free_index >= node_storage_buffer_len) { // Ran out of node storage memory. Progress for this node will not be tracked. _ = @atomicRmw(u32, &global_progress.node_end_index, .Sub, 1, .monotonic); return Node.none; @@ -292,15 +366,17 @@ pub const Node = struct { const index = n.index.unwrap() orelse return; const storage = storageByIndex(index); // Avoid u32 max int which is used to indicate a special state. - const saturated = @min(std.math.maxInt(u32) - 1, count); - @atomicStore(u32, &storage.estimated_total_count, saturated, .monotonic); + const saturated_total_count = @min(std.math.maxInt(u32) - 1, count); + @atomicStore(u32, &storage.estimated_total_count, saturated_total_count, .monotonic); } /// Thread-safe. pub fn increaseEstimatedTotalItems(n: Node, count: usize) void { const index = n.index.unwrap() orelse return; const storage = storageByIndex(index); - _ = @atomicRmw(u32, &storage.estimated_total_count, .Add, std.math.lossyCast(u32, count), .monotonic); + // Avoid u32 max int which is used to indicate a special state. + const saturated_total_count = @min(std.math.maxInt(u32) - 1, count); + _ = @atomicRmw(u32, &storage.estimated_total_count, .Add, saturated_total_count, .monotonic); } /// Finish a started `Node`. Thread-safe. @@ -310,11 +386,25 @@ pub const Node = struct { return; } const index = n.index.unwrap() orelse return; + const io = global_progress.io; const parent_ptr = parentByIndex(index); if (@atomicLoad(Node.Parent, parent_ptr, .monotonic).unwrap()) |parent_index| { _ = @atomicRmw(u32, &storageByIndex(parent_index).completed_count, .Add, 1, .monotonic); @atomicStore(Node.Parent, parent_ptr, .unused, .monotonic); + if (storageByIndex(index).getIpcIndex()) |ipc_index| { + const file = global_progress.ipc_files[ipc_index.slot]; + const ipc = @atomicRmw( + Ipc, + &global_progress.ipc[ipc_index.slot], + .And, + .{ .locked = true, .valid = false, .generation = std.math.maxInt(Ipc.Generation) }, + .release, + ); + assert(ipc.valid and ipc.generation == ipc_index.generation); + if (!ipc.locked) file.close(io); + } + const freelist = &global_progress.node_freelist; var old_freelist = @atomicLoad(Freelist, freelist, .monotonic); while (true) { @@ -332,42 +422,52 @@ pub const Node = struct { }; } } else { - @atomicStore(bool, &global_progress.done, true, .monotonic); - const io = global_progress.io; - global_progress.redraw_event.set(io); - if (global_progress.update_worker) |*worker| worker.await(io); + if (global_progress.update_worker) |*worker| worker.cancel(io) catch {}; + for (&global_progress.ipc, &global_progress.ipc_files) |ipc, ipc_file| { + assert(!ipc.locked or !ipc.valid); // missing call to end() + if (ipc.locked or ipc.valid) ipc_file.close(io); + } } } - /// Posix-only. Used by `std.process.Child`. Thread-safe. - pub fn setIpcFd(node: Node, fd: Io.File.Handle) void { + /// Used by `std.process.Child`. Thread-safe. + pub fn setIpcFile(node: Node, expected_io_userdata: ?*anyopaque, file: Io.File) void { const index = node.index.unwrap() orelse return; - switch (@typeInfo(Io.File.Handle)) { - .int => { - assert(fd >= 0); - assert(fd != posix.STDOUT_FILENO); - assert(fd != posix.STDIN_FILENO); - assert(fd != posix.STDERR_FILENO); - }, - .pointer => { - assert(fd != windows.INVALID_HANDLE_VALUE); - }, - else => @compileError("unsupported fd_t of " ++ @typeName(Io.File.Handle)), - } - storageByIndex(index).setIpcFd(fd); + const io = global_progress.io; + assert(io.userdata == expected_io_userdata); + for (0..ipc_storage_buffer_len) |_| { + const slot: Ipc.Slot = @truncate( + @atomicRmw(Ipc.SlotAtomic, &global_progress.ipc_next, .Add, 1, .monotonic), + ); + if (slot >= ipc_storage_buffer_len) continue; + const ipc_ptr = &global_progress.ipc[slot]; + const ipc = @atomicLoad(Ipc, ipc_ptr, .monotonic); + if (ipc.locked or ipc.valid) continue; + const generation = ipc.generation +% 1; + if (@cmpxchgWeak( + Ipc, + ipc_ptr, + ipc, + .{ .locked = false, .valid = true, .generation = generation }, + .acquire, + .monotonic, + )) |_| continue; + global_progress.ipc_files[slot] = file; + storageByIndex(index).setIpcIndex(.{ .slot = slot, .generation = generation }); + break; + } else file.close(io); } - /// Posix-only. Thread-safe. Assumes the node is storing an IPC file - /// descriptor. - pub fn getIpcFd(node: Node) ?Io.File.Handle { - const index = node.index.unwrap() orelse return null; - const storage = storageByIndex(index); - const int = @atomicLoad(u32, &storage.completed_count, .monotonic); - return switch (@typeInfo(Io.File.Handle)) { - .int => @bitCast(int), - .pointer => @ptrFromInt(int), - else => @compileError("unsupported fd_t of " ++ @typeName(Io.File.Handle)), - }; + pub fn setIpcIndex(node: Node, ipc_index: Ipc.Index) void { + storageByIndex(node.index.unwrap() orelse return).setIpcIndex(ipc_index); + } + + /// Not thread-safe. + pub fn takeIpcIndex(node: Node) ?Ipc.Index { + const storage = storageByIndex(node.index.unwrap() orelse return null); + assert(storage.estimated_total_count == std.math.maxInt(u32)); + @atomicStore(u32, &storage.estimated_total_count, 0, .monotonic); + return @bitCast(storage.completed_count); } fn storageByIndex(index: Node.Index) *Node.Storage { @@ -387,7 +487,9 @@ pub const Node = struct { const storage = storageByIndex(free_index); @atomicStore(u32, &storage.completed_count, 0, .monotonic); - @atomicStore(u32, &storage.estimated_total_count, std.math.lossyCast(u32, estimated_total_items), .monotonic); + // Avoid u32 max int which is used to indicate a special state. + const saturated_total_count = @min(std.math.maxInt(u32) - 1, estimated_total_items); + @atomicStore(u32, &storage.estimated_total_count, saturated_total_count, .monotonic); const name_len = @min(max_name_len, name.len); copyAtomicStore(storage.name[0..name_len], name[0..name_len]); if (name_len < storage.name.len) @@ -414,16 +516,20 @@ var global_progress: Progress = .{ .rows = 0, .cols = 0, .draw_buffer = undefined, - .done = false, .need_clear = false, .status = .working, - .start_failure = .unstarted, - .node_parents = &node_parents_buffer, - .node_storage = &node_storage_buffer, - .node_freelist_next = &node_freelist_next_buffer, + .node_parents = undefined, + .node_storage = undefined, + .node_freelist_next = undefined, .node_freelist = .{ .head = .none, .generation = 0 }, .node_end_index = 0, + + .ipc_next = 0, + .ipc = undefined, + .ipc_files = undefined, + + .start_failure = .unstarted, }; pub const StartFailure = union(enum) { @@ -433,17 +539,23 @@ pub const StartFailure = union(enum) { parent_ipc: error{ UnsupportedOperation, UnrecognizedFormat }, }; -const node_storage_buffer_len = 83; -var node_parents_buffer: [node_storage_buffer_len]Node.Parent = undefined; -var node_storage_buffer: [node_storage_buffer_len]Node.Storage = undefined; -var node_freelist_next_buffer: [node_storage_buffer_len]Node.OptionalIndex = undefined; +/// One less than a power of two ensures `max_packet_len` is already a power of two. +const node_storage_buffer_len = ipc_storage_buffer_len - 1; + +/// Power of two to avoid wasted `ipc_next` increments. +const ipc_storage_buffer_len = 128; + +pub const max_packet_len = std.math.ceilPowerOfTwoAssert( + usize, + 1 + node_storage_buffer_len * (@sizeOf(Node.Storage) + @sizeOf(Node.OptionalIndex)), +); var default_draw_buffer: [4096]u8 = undefined; var debug_start_trace = std.debug.Trace.init; pub const have_ipc = switch (builtin.os.tag) { - .wasi, .freestanding, .windows => false, + .wasi, .freestanding => false, else => true, }; @@ -475,9 +587,9 @@ pub fn start(io: Io, options: Options) Node { } debug_start_trace.add("first initialized here"); - @memset(global_progress.node_parents, .unused); + @memset(&global_progress.node_parents, .unused); + @memset(&global_progress.ipc, .{ .locked = false, .valid = false, .generation = 0 }); const root_node = Node.init(@enumFromInt(0), .none, options.root_name, options.estimated_total_items); - global_progress.done = false; global_progress.node_end_index = 1; assert(options.draw_buffer.len >= 200); @@ -551,58 +663,55 @@ pub fn setStatus(new_status: Status) void { } /// Returns whether a resize is needed to learn the terminal size. -fn wait(io: Io, timeout_ns: u64) bool { +fn wait(io: Io, timeout_ns: u64) Io.Cancelable!bool { const timeout: Io.Timeout = .{ .duration = .{ .clock = .awake, .raw = .fromNanoseconds(timeout_ns), } }; const resize_flag = if (global_progress.redraw_event.waitTimeout(io, timeout)) |_| true else |err| switch (err) { - error.Timeout, error.Canceled => false, + error.Timeout => false, + error.Canceled => |e| return e, }; global_progress.redraw_event.reset(); return resize_flag or (global_progress.cols == 0); } -fn updateTask(io: Io) void { +const WorkerError = error{WindowTooSmall} || Io.ConcurrentError || Io.Cancelable || + Io.File.Writer.Error || Io.Operation.FileReadStreaming.Error; + +fn updateTask(io: Io) WorkerError!void { // Store this data in the thread so that it does not need to be part of the // linker data of the main executable. var serialized_buffer: Serialized.Buffer = undefined; + serialized_buffer.init(); + defer serialized_buffer.batch.cancel(io); // In this function we bypass the wrapper code inside `Io.lockStderr` / // `Io.tryLockStderr` in order to avoid clearing the terminal twice. // We still want to go through the `Io` instance however in case it uses a // task-switching mutex. - { - const resize_flag = wait(io, global_progress.initial_delay_ns); - if (@atomicLoad(bool, &global_progress.done, .monotonic)) return; - maybeUpdateSize(io, resize_flag) catch return; - - const buffer, _ = computeRedraw(&serialized_buffer); - if (io.vtable.tryLockStderr(io.userdata, null) catch return) |locked_stderr| { - defer io.unlockStderr(); - global_progress.need_clear = true; - locked_stderr.file_writer.interface.writeAll(buffer) catch return; - } + try maybeUpdateSize(io, try wait(io, global_progress.initial_delay_ns)); + errdefer { + const cancel_protection = io.swapCancelProtection(.blocked); + defer _ = io.swapCancelProtection(cancel_protection); + const stderr = io.vtable.lockStderr(io.userdata, null) catch |err| switch (err) { + error.Canceled => unreachable, // blocked + }; + defer io.unlockStderr(); + clearWrittenWithEscapeCodes(stderr.file_writer) catch {}; } - while (true) { - const resize_flag = wait(io, global_progress.refresh_rate_ns); - - if (@atomicLoad(bool, &global_progress.done, .monotonic)) { - const stderr = io.vtable.lockStderr(io.userdata, null) catch return; - defer io.unlockStderr(); - return clearWrittenWithEscapeCodes(stderr.file_writer) catch {}; - } - - maybeUpdateSize(io, resize_flag) catch return; - - const buffer, _ = computeRedraw(&serialized_buffer); - if (io.vtable.tryLockStderr(io.userdata, null) catch return) |locked_stderr| { + const buffer, _ = try computeRedraw(io, &serialized_buffer); + if (try io.vtable.tryLockStderr(io.userdata, null)) |locked_stderr| { defer io.unlockStderr(); global_progress.need_clear = true; - locked_stderr.file_writer.interface.writeAll(buffer) catch return; + locked_stderr.file_writer.interface.writeAll(buffer) catch |err| switch (err) { + error.WriteFailed => return locked_stderr.file_writer.err.?, + }; } + + try maybeUpdateSize(io, try wait(io, global_progress.refresh_rate_ns)); } } @@ -614,79 +723,60 @@ fn windowsApiWriteMarker() void { _ = windows.kernel32.WriteConsoleW(handle, &[_]u16{windows_api_start_marker}, 1, &num_chars_written, null); } -fn windowsApiUpdateTask(io: Io) void { +fn windowsApiUpdateTask(io: Io) WorkerError!void { + // Store this data in the thread so that it does not need to be part of the + // linker data of the main executable. var serialized_buffer: Serialized.Buffer = undefined; + serialized_buffer.init(); + defer serialized_buffer.batch.cancel(io); // In this function we bypass the wrapper code inside `Io.lockStderr` / // `Io.tryLockStderr` in order to avoid clearing the terminal twice. // We still want to go through the `Io` instance however in case it uses a // task-switching mutex. - { - const resize_flag = wait(io, global_progress.initial_delay_ns); - if (@atomicLoad(bool, &global_progress.done, .monotonic)) return; - maybeUpdateSize(io, resize_flag) catch return; - - const buffer, const nl_n = computeRedraw(&serialized_buffer); - if (io.vtable.tryLockStderr(io.userdata, null) catch return) |locked_stderr| { - defer io.unlockStderr(); - windowsApiWriteMarker(); - global_progress.need_clear = true; - locked_stderr.file_writer.interface.writeAll(buffer) catch return; - windowsApiMoveToMarker(nl_n) catch return; - } + try maybeUpdateSize(io, try wait(io, global_progress.initial_delay_ns)); + errdefer { + const cancel_protection = io.swapCancelProtection(.blocked); + defer _ = io.swapCancelProtection(cancel_protection); + _ = io.vtable.lockStderr(io.userdata, null) catch |err| switch (err) { + error.Canceled => unreachable, // blocked + }; + defer io.unlockStderr(); + clearWrittenWindowsApi() catch {}; } - while (true) { - const resize_flag = wait(io, global_progress.refresh_rate_ns); - - if (@atomicLoad(bool, &global_progress.done, .monotonic)) { - _ = io.vtable.lockStderr(io.userdata, null) catch return; - defer io.unlockStderr(); - return clearWrittenWindowsApi() catch {}; - } - - maybeUpdateSize(io, resize_flag) catch return; - - const buffer, const nl_n = computeRedraw(&serialized_buffer); + const buffer, const nl_n = try computeRedraw(io, &serialized_buffer); if (io.vtable.tryLockStderr(io.userdata, null) catch return) |locked_stderr| { defer io.unlockStderr(); - clearWrittenWindowsApi() catch return; + try clearWrittenWindowsApi(); windowsApiWriteMarker(); global_progress.need_clear = true; - locked_stderr.file_writer.interface.writeAll(buffer) catch return; + locked_stderr.file_writer.interface.writeAll(buffer) catch |err| switch (err) { + error.WriteFailed => return locked_stderr.file_writer.err.?, + }; windowsApiMoveToMarker(nl_n) catch return; } + + try maybeUpdateSize(io, try wait(io, global_progress.refresh_rate_ns)); } } -fn ipcThreadRun(io: Io, file: Io.File) void { +fn ipcThreadRun(io: Io, file: Io.File) WorkerError!void { // Store this data in the thread so that it does not need to be part of the // linker data of the main executable. var serialized_buffer: Serialized.Buffer = undefined; + serialized_buffer.init(); + defer serialized_buffer.batch.cancel(io); + var fw = file.writerStreaming(io, &.{}); - { - _ = wait(io, global_progress.initial_delay_ns); - - if (@atomicLoad(bool, &global_progress.done, .monotonic)) - return; - - const serialized = serialize(&serialized_buffer); - writeIpc(io, file, serialized) catch |err| switch (err) { - error.BrokenPipe => return, - }; - } - + _ = try io.sleep(.fromNanoseconds(global_progress.initial_delay_ns), .awake); while (true) { - _ = wait(io, global_progress.refresh_rate_ns); - - if (@atomicLoad(bool, &global_progress.done, .monotonic)) - return; - - const serialized = serialize(&serialized_buffer); - writeIpc(io, file, serialized) catch |err| switch (err) { - error.BrokenPipe => return, + writeIpc(&fw.interface, try serialize(io, &serialized_buffer)) catch |err| switch (err) { + error.WriteFailed => return fw.err.?, }; + + _ = try io.sleep(.fromNanoseconds(global_progress.refresh_rate_ns), .awake); } } @@ -865,31 +955,49 @@ const Serialized = struct { const Buffer = struct { parents: [node_storage_buffer_len]Node.Parent, storage: [node_storage_buffer_len]Node.Storage, - map: [node_storage_buffer_len]Node.OptionalIndex, - parents_copy: [node_storage_buffer_len]Node.Parent, - storage_copy: [node_storage_buffer_len]Node.Storage, - ipc_metadata_fds_copy: [node_storage_buffer_len]Fd, - ipc_metadata_copy: [node_storage_buffer_len]SavedMetadata, + ipc_start: u8, + ipc_end: u8, + ipc_data: [ipc_storage_buffer_len]Ipc.Data, + ipc_buffers: [ipc_storage_buffer_len][max_packet_len]u8, + ipc_vecs: [ipc_storage_buffer_len][1][]u8, + batch_storage: [ipc_storage_buffer_len]Io.Operation.Storage, + batch: Io.Batch, - ipc_metadata_fds: [node_storage_buffer_len]Fd, - ipc_metadata: [node_storage_buffer_len]SavedMetadata, + fn init(buffer: *Buffer) void { + buffer.ipc_start = 0; + buffer.ipc_end = 0; + @memset(&buffer.ipc_data, .unused); + buffer.batch = .init(&buffer.batch_storage); + } }; }; -fn serialize(serialized_buffer: *Serialized.Buffer) Serialized { - var serialized_len: usize = 0; - var any_ipc = false; +fn serialize(io: Io, serialized_buffer: *Serialized.Buffer) !Serialized { + var prev_parents: [node_storage_buffer_len]Node.Parent = undefined; + var prev_storage: [node_storage_buffer_len]Node.Storage = undefined; + { + const ipc_start = serialized_buffer.ipc_start; + const ipc_end = serialized_buffer.ipc_end; + @memcpy(prev_parents[ipc_start..ipc_end], serialized_buffer.parents[ipc_start..ipc_end]); + @memcpy(prev_storage[ipc_start..ipc_end], serialized_buffer.storage[ipc_start..ipc_end]); + } // Iterate all of the nodes and construct a serializable copy of the state that can be examined // without atomics. The `@min` call is here because `node_end_index` might briefly exceed the // node count sometimes. - const end_index = @min(@atomicLoad(u32, &global_progress.node_end_index, .monotonic), global_progress.node_storage.len); + const end_index = @min( + @atomicLoad(u32, &global_progress.node_end_index, .monotonic), + node_storage_buffer_len, + ); + var map: [node_storage_buffer_len]Node.OptionalIndex = undefined; + var serialized_len: u8 = 0; + var maybe_ipc_start: ?u8 = null; for ( global_progress.node_parents[0..end_index], global_progress.node_storage[0..end_index], - serialized_buffer.map[0..end_index], - ) |*parent_ptr, *storage_ptr, *map| { + map[0..end_index], + ) |*parent_ptr, *storage_ptr, *map_entry| { const parent = @atomicLoad(Node.Parent, parent_ptr, .monotonic); if (parent == .unused) { // We might read "mixed" node data in this loop, due to weird atomic things @@ -903,17 +1011,17 @@ fn serialize(serialized_buffer: *Serialized.Buffer) Serialized { // parent, it will just not be printed at all. The general idea here is that performance // is more important than 100% correct output every frame, given that this API is likely // to be used in hot paths! - map.* = .none; + map_entry.* = .none; continue; } const dest_storage = &serialized_buffer.storage[serialized_len]; copyAtomicLoad(&dest_storage.name, &storage_ptr.name); - dest_storage.estimated_total_count = @atomicLoad(u32, &storage_ptr.estimated_total_count, .acquire); // sychronizes with release in `setIpcFd` + dest_storage.estimated_total_count = @atomicLoad(u32, &storage_ptr.estimated_total_count, .acquire); // sychronizes with release in `setIpcIndex` dest_storage.completed_count = @atomicLoad(u32, &storage_ptr.completed_count, .monotonic); - any_ipc = any_ipc or (dest_storage.getIpcFd() != null); serialized_buffer.parents[serialized_len] = parent; - map.* = @enumFromInt(serialized_len); + map_entry.* = @enumFromInt(serialized_len); + if (maybe_ipc_start == null and dest_storage.getIpcIndex() != null) maybe_ipc_start = serialized_len; serialized_len += 1; } @@ -922,13 +1030,201 @@ fn serialize(serialized_buffer: *Serialized.Buffer) Serialized { parent.* = switch (parent.*) { .unused => unreachable, .none => .none, - _ => |p| serialized_buffer.map[@intFromEnum(p)].toParent(), + _ => |p| map[@intFromEnum(p)].toParent(), }; } + // Fill pipe buffers. + const batch = &serialized_buffer.batch; + batch.awaitConcurrent(io, .{ + .duration = .{ .raw = .zero, .clock = .awake }, + }) catch |err| switch (err) { + error.Timeout => {}, + else => |e| return e, + }; + var ready_len: u8 = 0; + while (batch.next()) |operation| switch (operation.index) { + 0...ipc_storage_buffer_len - 1 => { + const ipc_data = &serialized_buffer.ipc_data[operation.index]; + ipc_data.bytes_read += @intCast( + operation.result.file_read_streaming catch |err| switch (err) { + error.EndOfStream => { + const file = global_progress.ipc_files[operation.index]; + const ipc = @atomicRmw( + Ipc, + &global_progress.ipc[operation.index], + .And, + .{ + .locked = false, + .valid = true, + .generation = std.math.maxInt(Ipc.Generation), + }, + .release, + ); + assert(ipc.locked); + if (!ipc.valid) file.close(io); + ipc_data.* = .unused; + continue; + }, + else => |e| return e, + }, + ); + assert(ipc_data.state == .pending); + ipc_data.state = .ready; + ready_len += 1; + }, + else => unreachable, + }; + // Find nodes which correspond to child processes. - if (any_ipc) - serialized_len = serializeIpc(serialized_len, serialized_buffer); + const ipc_start = maybe_ipc_start orelse serialized_len; + serialized_buffer.ipc_start = ipc_start; + for ( + serialized_buffer.parents[ipc_start..serialized_len], + serialized_buffer.storage[ipc_start..serialized_len], + ipc_start.., + ) |main_parent, *main_storage, main_index| { + if (main_parent == .unused) continue; + const ipc_index = main_storage.getIpcIndex() orelse continue; + const ipc = &global_progress.ipc[ipc_index.slot]; + const ipc_data = &serialized_buffer.ipc_data[ipc_index.slot]; + state: switch (ipc_data.state) { + .unused => { + if (@cmpxchgWeak( + Ipc, + ipc, + .{ .locked = false, .valid = true, .generation = ipc_index.generation }, + .{ .locked = true, .valid = true, .generation = ipc_index.generation }, + .acquire, + .monotonic, + )) |_| continue; + + const ipc_vec = &serialized_buffer.ipc_vecs[ipc_index.slot]; + ipc_vec.* = .{&serialized_buffer.ipc_buffers[ipc_index.slot]}; + batch.addAt(ipc_index.slot, .{ .file_read_streaming = .{ + .file = global_progress.ipc_files[ipc_index.slot], + .data = ipc_vec, + } }); + + ipc_data.* = .{ + .state = .pending, + .bytes_read = 0, + .main_index = @intCast(main_index), + .start_index = serialized_len, + .nodes_len = 0, + }; + main_storage.completed_count = 0; + main_storage.estimated_total_count = 0; + }, + .pending => { + const start_index = ipc_data.start_index; + const nodes_len = @min(ipc_data.nodes_len, node_storage_buffer_len - serialized_len); + + main_storage.copyRoot(&prev_storage[ipc_data.main_index]); + @memcpy( + serialized_buffer.storage[serialized_len..][0..nodes_len], + prev_storage[start_index..][0..nodes_len], + ); + for ( + serialized_buffer.parents[serialized_len..][0..nodes_len], + prev_parents[serialized_len..][0..nodes_len], + ) |*parent, prev_parent| parent.* = switch (prev_parent) { + .none, .unused => .none, + _ => if (@intFromEnum(prev_parent) == ipc_data.main_index) + @enumFromInt(main_index) + else if (@intFromEnum(prev_parent) >= start_index and + @intFromEnum(prev_parent) < start_index + nodes_len) + @enumFromInt(@intFromEnum(prev_parent) - start_index + serialized_len) + else + .none, + }; + + ipc_data.main_index = @intCast(main_index); + ipc_data.start_index = serialized_len; + ipc_data.nodes_len = nodes_len; + serialized_len += nodes_len; + }, + .ready => { + const ipc_buffer = &serialized_buffer.ipc_buffers[ipc_index.slot]; + const packet_start, const packet_end = ipc_data.findLastPacket(ipc_buffer); + const packet_is_empty = packet_end - packet_start <= 1; + if (!packet_is_empty) { + const storage, const parents, const nodes_len = packet_contents: { + var packet_index: usize = packet_start; + const nodes_len: u16 = ipc_buffer[packet_index]; + packet_index += 1; + const storage_bytes = + ipc_buffer[packet_index..][0 .. nodes_len * @sizeOf(Node.Storage)]; + packet_index += storage_bytes.len; + const parents_bytes = + ipc_buffer[packet_index..][0 .. nodes_len * @sizeOf(Node.Parent)]; + packet_index += parents_bytes.len; + assert(packet_index == packet_end); + const storage: []align(1) const Node.Storage = @ptrCast(storage_bytes); + const parents: []align(1) const Node.Parent = @ptrCast(parents_bytes); + const children_nodes_len = + @min(nodes_len - 1, node_storage_buffer_len - serialized_len); + break :packet_contents .{ storage, parents, children_nodes_len }; + }; + + // Mount the root here. + main_storage.copyRoot(&storage[0]); + if (is_big_endian) main_storage.byteSwap(); + + // Copy the rest of the tree to the end. + const serialized_storage = + serialized_buffer.storage[serialized_len..][0..nodes_len]; + @memcpy(serialized_storage, storage[1..][0..nodes_len]); + if (is_big_endian) for (serialized_storage) |*s| s.byteSwap(); + + // Patch up parent pointers taking into account how the subtree is mounted. + for ( + serialized_buffer.parents[serialized_len..][0..nodes_len], + parents[1..][0..nodes_len], + ) |*parent, prev_parent| parent.* = switch (prev_parent) { + // Fix bad data so the rest of the code does not see `unused`. + .none, .unused => .none, + // Root node is being mounted here. + @as(Node.Parent, @enumFromInt(0)) => @enumFromInt(main_index), + // Other nodes mounted at the end. + // Don't trust child data; if the data is outside the expected range, + // ignore the data. This also handles the case when data was truncated. + _ => if (@intFromEnum(prev_parent) <= nodes_len) + @enumFromInt(@intFromEnum(prev_parent) - 1 + serialized_len) + else + .none, + }; + + ipc_data.main_index = @intCast(main_index); + ipc_data.start_index = serialized_len; + ipc_data.nodes_len = nodes_len; + serialized_len += nodes_len; + } + const ipc_vec = &serialized_buffer.ipc_vecs[ipc_index.slot]; + ipc_data.rebase(ipc_buffer, ipc_vec, batch, ipc_index.slot, packet_end); + ready_len -= 1; + if (packet_is_empty) continue :state .pending; + }, + } + } + serialized_buffer.ipc_end = serialized_len; + + // Ignore data from unused pipes. This ensures that if a child process exists we will + // eventually see `EndOfStream` and close the pipe. + if (ready_len > 0) for ( + &serialized_buffer.ipc_data, + &serialized_buffer.ipc_buffers, + &serialized_buffer.ipc_vecs, + 0.., + ) |*ipc_data, *ipc_buffer, *ipc_vec, ipc_slot| switch (ipc_data.state) { + .unused, .pending => {}, + .ready => { + _, const packet_end = ipc_data.findLastPacket(ipc_buffer); + ipc_data.rebase(ipc_buffer, ipc_vec, batch, @intCast(ipc_slot), packet_end); + ready_len -= 1; + }, + }; + assert(ready_len == 0); return .{ .parents = serialized_buffer.parents[0..serialized_len], @@ -936,252 +1232,10 @@ fn serialize(serialized_buffer: *Serialized.Buffer) Serialized { }; } -const SavedMetadata = struct { - remaining_read_trash_bytes: u16, - main_index: u8, - start_index: u8, - nodes_len: u8, -}; +fn computeRedraw(io: Io, serialized_buffer: *Serialized.Buffer) !struct { []u8, usize } { + if (global_progress.rows == 0 or global_progress.cols == 0) return error.WindowTooSmall; -const Fd = enum(i32) { - _, - - fn init(fd: Io.File.Handle) Fd { - return @enumFromInt(if (is_windows) @as(isize, @bitCast(@intFromPtr(fd))) else fd); - } - - fn get(fd: Fd) Io.File.Handle { - return if (is_windows) - @ptrFromInt(@as(usize, @bitCast(@as(isize, @intFromEnum(fd))))) - else - @intFromEnum(fd); - } -}; - -var ipc_metadata_len: u8 = 0; - -fn serializeIpc(start_serialized_len: usize, serialized_buffer: *Serialized.Buffer) usize { - const io = global_progress.io; - const ipc_metadata_fds_copy = &serialized_buffer.ipc_metadata_fds_copy; - const ipc_metadata_copy = &serialized_buffer.ipc_metadata_copy; - const ipc_metadata_fds = &serialized_buffer.ipc_metadata_fds; - const ipc_metadata = &serialized_buffer.ipc_metadata; - - var serialized_len = start_serialized_len; - var pipe_buf: [2 * 4096]u8 = undefined; - - const old_ipc_metadata_fds = ipc_metadata_fds_copy[0..ipc_metadata_len]; - const old_ipc_metadata = ipc_metadata_copy[0..ipc_metadata_len]; - ipc_metadata_len = 0; - - main_loop: for ( - serialized_buffer.parents[0..serialized_len], - serialized_buffer.storage[0..serialized_len], - 0.., - ) |main_parent, *main_storage, main_index| { - if (main_parent == .unused) continue; - const file: Io.File = .{ - .handle = main_storage.getIpcFd() orelse continue, - .flags = .{ .nonblocking = true }, - }; - const opt_saved_metadata = findOld(file.handle, old_ipc_metadata_fds, old_ipc_metadata); - var bytes_read: usize = 0; - while (true) { - const n = file.readStreaming(io, &.{pipe_buf[bytes_read..]}) catch |err| switch (err) { - error.WouldBlock, error.EndOfStream => break, - else => |e| { - std.log.debug("failed to read child progress data: {t}", .{e}); - main_storage.completed_count = 0; - main_storage.estimated_total_count = 0; - continue :main_loop; - }, - }; - if (opt_saved_metadata) |m| { - if (m.remaining_read_trash_bytes > 0) { - assert(bytes_read == 0); - if (m.remaining_read_trash_bytes >= n) { - m.remaining_read_trash_bytes = @intCast(m.remaining_read_trash_bytes - n); - continue; - } - const src = pipe_buf[m.remaining_read_trash_bytes..n]; - @memmove(pipe_buf[0..src.len], src); - m.remaining_read_trash_bytes = 0; - bytes_read = src.len; - continue; - } - } - bytes_read += n; - } - // Ignore all but the last message on the pipe. - var input: []u8 = pipe_buf[0..bytes_read]; - if (input.len == 0) { - serialized_len = useSavedIpcData(serialized_len, serialized_buffer, main_storage, main_index, opt_saved_metadata, 0, file.handle); - continue; - } - - const storage, const parents = while (true) { - const subtree_len: usize = input[0]; - const expected_bytes = 1 + subtree_len * (@sizeOf(Node.Storage) + @sizeOf(Node.Parent)); - if (input.len < expected_bytes) { - // Ignore short reads. We'll handle the next full message when it comes instead. - const remaining_read_trash_bytes: u16 = @intCast(expected_bytes - input.len); - serialized_len = useSavedIpcData(serialized_len, serialized_buffer, main_storage, main_index, opt_saved_metadata, remaining_read_trash_bytes, file.handle); - continue :main_loop; - } - if (input.len > expected_bytes) { - input = input[expected_bytes..]; - continue; - } - const storage_bytes = input[1..][0 .. subtree_len * @sizeOf(Node.Storage)]; - const parents_bytes = input[1 + storage_bytes.len ..][0 .. subtree_len * @sizeOf(Node.Parent)]; - break .{ - std.mem.bytesAsSlice(Node.Storage, storage_bytes), - std.mem.bytesAsSlice(Node.Parent, parents_bytes), - }; - }; - - const nodes_len: u8 = @intCast(@min(parents.len - 1, serialized_buffer.storage.len - serialized_len)); - - // Remember in case the pipe is empty on next update. - ipc_metadata_fds[ipc_metadata_len] = Fd.init(file.handle); - ipc_metadata[ipc_metadata_len] = .{ - .remaining_read_trash_bytes = 0, - .start_index = @intCast(serialized_len), - .nodes_len = nodes_len, - .main_index = @intCast(main_index), - }; - ipc_metadata_len += 1; - - // Mount the root here. - copyRoot(main_storage, &storage[0]); - if (is_big_endian) main_storage.byteSwap(); - - // Copy the rest of the tree to the end. - const storage_dest = serialized_buffer.storage[serialized_len..][0..nodes_len]; - @memcpy(storage_dest, storage[1..][0..nodes_len]); - - // Always little-endian over the pipe. - if (is_big_endian) for (storage_dest) |*s| s.byteSwap(); - - // Patch up parent pointers taking into account how the subtree is mounted. - for (serialized_buffer.parents[serialized_len..][0..nodes_len], parents[1..][0..nodes_len]) |*dest, p| { - dest.* = switch (p) { - // Fix bad data so the rest of the code does not see `unused`. - .none, .unused => .none, - // Root node is being mounted here. - @as(Node.Parent, @enumFromInt(0)) => @enumFromInt(main_index), - // Other nodes mounted at the end. - // Don't trust child data; if the data is outside the expected range, ignore the data. - // This also handles the case when data was truncated. - _ => |off| if (@intFromEnum(off) > nodes_len) - .none - else - @enumFromInt(serialized_len + @intFromEnum(off) - 1), - }; - } - - serialized_len += nodes_len; - } - - // Save a copy in case any pipes are empty on the next update. - @memcpy(serialized_buffer.parents_copy[0..serialized_len], serialized_buffer.parents[0..serialized_len]); - @memcpy(serialized_buffer.storage_copy[0..serialized_len], serialized_buffer.storage[0..serialized_len]); - @memcpy(ipc_metadata_fds_copy[0..ipc_metadata_len], ipc_metadata_fds[0..ipc_metadata_len]); - @memcpy(ipc_metadata_copy[0..ipc_metadata_len], ipc_metadata[0..ipc_metadata_len]); - - return serialized_len; -} - -fn copyRoot(dest: *Node.Storage, src: *align(1) Node.Storage) void { - dest.* = .{ - .completed_count = src.completed_count, - .estimated_total_count = src.estimated_total_count, - .name = if (src.name[0] == 0) dest.name else src.name, - }; -} - -fn findOld( - ipc_fd: Io.File.Handle, - old_metadata_fds: []Fd, - old_metadata: []SavedMetadata, -) ?*SavedMetadata { - for (old_metadata_fds, old_metadata) |fd, *m| { - if (fd.get() == ipc_fd) - return m; - } - return null; -} - -fn useSavedIpcData( - start_serialized_len: usize, - serialized_buffer: *Serialized.Buffer, - main_storage: *Node.Storage, - main_index: usize, - opt_saved_metadata: ?*SavedMetadata, - remaining_read_trash_bytes: u16, - fd: Io.File.Handle, -) usize { - const parents_copy = &serialized_buffer.parents_copy; - const storage_copy = &serialized_buffer.storage_copy; - const ipc_metadata_fds = &serialized_buffer.ipc_metadata_fds; - const ipc_metadata = &serialized_buffer.ipc_metadata; - - const saved_metadata = opt_saved_metadata orelse { - main_storage.completed_count = 0; - main_storage.estimated_total_count = 0; - if (remaining_read_trash_bytes > 0) { - ipc_metadata_fds[ipc_metadata_len] = Fd.init(fd); - ipc_metadata[ipc_metadata_len] = .{ - .remaining_read_trash_bytes = remaining_read_trash_bytes, - .start_index = @intCast(start_serialized_len), - .nodes_len = 0, - .main_index = @intCast(main_index), - }; - ipc_metadata_len += 1; - } - return start_serialized_len; - }; - - const start_index = saved_metadata.start_index; - const nodes_len = @min(saved_metadata.nodes_len, serialized_buffer.storage.len - start_serialized_len); - const old_main_index = saved_metadata.main_index; - - ipc_metadata_fds[ipc_metadata_len] = Fd.init(fd); - ipc_metadata[ipc_metadata_len] = .{ - .remaining_read_trash_bytes = remaining_read_trash_bytes, - .start_index = @intCast(start_serialized_len), - .nodes_len = nodes_len, - .main_index = @intCast(main_index), - }; - ipc_metadata_len += 1; - - const parents = parents_copy[start_index..][0..nodes_len]; - const storage = storage_copy[start_index..][0..nodes_len]; - - copyRoot(main_storage, &storage_copy[old_main_index]); - - @memcpy(serialized_buffer.storage[start_serialized_len..][0..storage.len], storage); - - for (serialized_buffer.parents[start_serialized_len..][0..parents.len], parents) |*dest, p| { - dest.* = switch (p) { - .none, .unused => .none, - _ => |prev| d: { - if (@intFromEnum(prev) == old_main_index) { - break :d @enumFromInt(main_index); - } else if (@intFromEnum(prev) > nodes_len) { - break :d .none; - } else { - break :d @enumFromInt(@intFromEnum(prev) - start_index + start_serialized_len); - } - }, - }; - } - - return start_serialized_len + storage.len; -} - -fn computeRedraw(serialized_buffer: *Serialized.Buffer) struct { []u8, usize } { - const serialized = serialize(serialized_buffer); + const serialized = try serialize(io, serialized_buffer); // Now we can analyze our copy of the graph without atomics, reconstructing // children lists which do not exist in the canonical data. These are @@ -1416,9 +1470,7 @@ fn withinRowLimit(p: *Progress, nl_n: usize) bool { return nl_n + 2 < p.rows; } -var remaining_write_trash_bytes: usize = 0; - -fn writeIpc(io: Io, file: Io.File, serialized: Serialized) error{BrokenPipe}!void { +fn writeIpc(writer: *Io.Writer, serialized: Serialized) Io.Writer.Error!void { // Byteswap if necessary to ensure little endian over the pipe. This is // needed because the parent or child process might be running in qemu. if (is_big_endian) for (serialized.storage) |*s| s.byteSwap(); @@ -1429,62 +1481,8 @@ fn writeIpc(io: Io, file: Io.File, serialized: Serialized) error{BrokenPipe}!voi const storage = std.mem.sliceAsBytes(serialized.storage); const parents = std.mem.sliceAsBytes(serialized.parents); - var vecs: [3][]const u8 = .{ header, storage, parents }; - - // Ensures the packet can fit in the pipe buffer. - const upper_bound_msg_len = 1 + node_storage_buffer_len * @sizeOf(Node.Storage) + - node_storage_buffer_len * @sizeOf(Node.OptionalIndex); - comptime assert(upper_bound_msg_len <= 4096); - - while (remaining_write_trash_bytes > 0) { - // We do this in a separate write call to give a better chance for the - // writev below to be in a single packet. - const n = @min(parents.len, remaining_write_trash_bytes); - if (file.writeStreaming(io, &.{}, &.{parents[0..n]}, 1)) |written| { - remaining_write_trash_bytes -= written; - continue; - } else |err| switch (err) { - error.WouldBlock => return, - error.BrokenPipe => return error.BrokenPipe, - else => |e| { - std.log.debug("failed to send progress to parent process: {t}", .{e}); - return error.BrokenPipe; - }, - } - } - - // If this write would block we do not want to keep trying, but we need to - // know if a partial message was written. - if (writevNonblock(io, file, &vecs)) |written| { - const total = header.len + storage.len + parents.len; - if (written < total) { - remaining_write_trash_bytes = total - written; - } - } else |err| switch (err) { - error.WouldBlock => {}, - error.BrokenPipe => return error.BrokenPipe, - else => |e| { - std.log.debug("failed to send progress to parent process: {t}", .{e}); - return error.BrokenPipe; - }, - } -} - -fn writevNonblock(io: Io, file: Io.File, iov: [][]const u8) Io.File.Writer.Error!usize { - var iov_index: usize = 0; - var written: usize = 0; - var total_written: usize = 0; - while (true) { - while (if (iov_index < iov.len) - written >= iov[iov_index].len - else - return total_written) : (iov_index += 1) written -= iov[iov_index].len; - iov[iov_index].ptr += written; - iov[iov_index].len -= written; - written = try file.writeStreaming(io, &.{}, iov, 1); - if (written == 0) return total_written; - total_written += written; - } + var vec = [3][]const u8{ header, storage, parents }; + try writer.writeVecAll(&vec); } fn maybeUpdateSize(io: Io, resize_flag: bool) !void { diff --git a/lib/std/os/linux.zig b/lib/std/os/linux.zig index 4539aaff4e3aaf9df611d7a7da04b5557438394e..ef4616b111062dd8f5b1bd34794a9fbd15d8d7a6 100644 --- a/lib/std/os/linux.zig +++ b/lib/std/os/linux.zig @@ -1848,6 +1848,24 @@ pub const F = struct { pub const RDLCK = if (is_sparc) 1 else 0; pub const WRLCK = if (is_sparc) 2 else 1; pub const UNLCK = if (is_sparc) 3 else 2; + + pub const LINUX_SPECIFIC_BASE = 1024; + + pub const SETLEASE = LINUX_SPECIFIC_BASE + 0; + pub const GETLEASE = LINUX_SPECIFIC_BASE + 1; + pub const NOTIFY = LINUX_SPECIFIC_BASE + 2; + pub const DUPFD_QUERY = LINUX_SPECIFIC_BASE + 3; + pub const CREATED_QUERY = LINUX_SPECIFIC_BASE + 4; + pub const CANCELLK = LINUX_SPECIFIC_BASE + 5; + pub const DUPFD_CLOEXEC = LINUX_SPECIFIC_BASE + 6; + pub const SETPIPE_SZ = LINUX_SPECIFIC_BASE + 7; + pub const GETPIPE_SZ = LINUX_SPECIFIC_BASE + 8; + pub const ADD_SEALS = LINUX_SPECIFIC_BASE + 9; + pub const GET_SEALS = LINUX_SPECIFIC_BASE + 10; + pub const GET_RW_HINT = LINUX_SPECIFIC_BASE + 11; + pub const SET_RW_HINT = LINUX_SPECIFIC_BASE + 12; + pub const GET_FILE_RW_HINT = LINUX_SPECIFIC_BASE + 13; + pub const SET_FILE_RW_HINT = LINUX_SPECIFIC_BASE + 14; }; pub const F_OWNER = enum(i32) { -- 2.54.0 From b49dc5eb70b804a2354a01bd60c686f0f2279f2d Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 4 Feb 2026 10:54:02 -0800 Subject: [PATCH 199/499] build: bump max_rss for C ABI tests on Windows --- build.zig | 5 +---- 1 file changed, 1 insertion(+), 4 deletions(-) diff --git a/build.zig b/build.zig index 3df6969ddad159576ac3cb06c39c25c6586aa17c..964c180d757d74f39d7a2ba0f86dd27583ded0cc 100644 --- a/build.zig +++ b/build.zig @@ -625,10 +625,7 @@ pub fn build(b: *std.Build) !void { .aarch64 => 1_813_612_134, else => 1_900_000_000, }, - .windows => switch (b.graph.host.result.cpu.arch) { - .x86_64 => 386_287_616, - else => 400_000_000, - }, + .windows => 400_000_000, else => 2_200_000_000, }, })); -- 2.54.0 From 2a193a39871ad098931cecc1154cc42278c28a2b Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 3 Feb 2026 21:08:42 -0800 Subject: [PATCH 200/499] std: move GetFinalPathNameByHandle to Io.Threaded unfortunately this function calls NtCreateFile so it has to participate in cancelation --- lib/std/Build/Watch.zig | 2 +- lib/std/Io/Threaded.zig | 598 +++++++++++++++++- lib/std/Io/Threaded/test.zig | 335 ++++++++++ lib/std/dynamic_library.zig | 71 +-- lib/std/os/windows.zig | 565 ----------------- lib/std/os/windows/test.zig | 339 ---------- lib/std/zig/parser_test.zig | 2 +- .../standalone/load_dynamic_library/build.zig | 1 + 8 files changed, 907 insertions(+), 1006 deletions(-) delete mode 100644 lib/std/os/windows/test.zig diff --git a/lib/std/Build/Watch.zig b/lib/std/Build/Watch.zig index 5920a227cb192f75c7e8224e211240d814acaf0b..15ccfcfdf9cde96d937b3fde8424c2e1bfbf200d 100644 --- a/lib/std/Build/Watch.zig +++ b/lib/std/Build/Watch.zig @@ -358,7 +358,7 @@ const Os = switch (builtin.os.tag) { var dir_handle: windows.HANDLE = undefined; const root_fd = path.root_dir.handle.handle; const sub_path = path.subPathOrDot(); - const sub_path_w = try windows.sliceToPrefixedFileW(root_fd, sub_path); + const sub_path_w = try std.Io.Threaded.sliceToPrefixedFileW(root_fd, sub_path); // TODO eliminate this call const path_len_bytes = std.math.cast(u16, sub_path_w.len * 2) orelse return error.NameTooLong; var nt_name = windows.UNICODE_STRING{ diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index fdef4ef47e5a33c9770fae9967bba34821946678..1c171d7321b8575d01a82fcbe712078a662c8ee6 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -3257,7 +3257,7 @@ fn dirCreateDirWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, pe const t: *Threaded = @ptrCast(@alignCast(userdata)); _ = t; - const sub_path_w = try windows.sliceToPrefixedFileW(dir.handle, sub_path); + const sub_path_w = try sliceToPrefixedFileW(dir.handle, sub_path); _ = permissions; // TODO use this value const syscall: Syscall = try .start(); @@ -3363,7 +3363,7 @@ fn dirCreateDirPathOpenWindows( }; components: while (true) { - const sub_path_w_array = try w.sliceToPrefixedFileW(dir.handle, component.path); + const sub_path_w_array = try sliceToPrefixedFileW(dir.handle, component.path); const sub_path_w = sub_path_w_array.span(); const is_last = it.peekNext() == null; const create_disposition: w.FILE.CREATE_DISPOSITION = if (is_last) .OPEN_IF else .CREATE; @@ -4064,7 +4064,7 @@ fn dirAccessWindows( _ = options; // TODO - const sub_path_w_array = try windows.sliceToPrefixedFileW(dir.handle, sub_path); + const sub_path_w_array = try sliceToPrefixedFileW(dir.handle, sub_path); const sub_path_w = sub_path_w_array.span(); if (sub_path_w[0] == '.' and sub_path_w[1] == 0) return; @@ -4285,7 +4285,7 @@ fn dirCreateFileWindows( if (std.mem.eql(u8, sub_path, ".")) return error.IsDir; if (std.mem.eql(u8, sub_path, "..")) return error.IsDir; - const sub_path_w_array = try windows.sliceToPrefixedFileW(dir.handle, sub_path); + const sub_path_w_array = try sliceToPrefixedFileW(dir.handle, sub_path); const sub_path_w = sub_path_w_array.span(); const path_len_bytes = std.math.cast(u16, sub_path_w.len * 2) orelse return error.NameTooLong; @@ -4887,7 +4887,7 @@ fn dirOpenFileWindows( ) File.OpenError!File { const t: *Threaded = @ptrCast(@alignCast(userdata)); _ = t; - const sub_path_w_array = try windows.sliceToPrefixedFileW(dir.handle, sub_path); + const sub_path_w_array = try sliceToPrefixedFileW(dir.handle, sub_path); const sub_path_w = sub_path_w_array.span(); const dir_handle = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle; return dirOpenFileWtf16(dir_handle, sub_path_w, flags); @@ -5151,7 +5151,7 @@ fn dirOpenDirPosix( _ = t; if (is_windows) { - const sub_path_w = try windows.sliceToPrefixedFileW(dir.handle, sub_path); + const sub_path_w = try sliceToPrefixedFileW(dir.handle, sub_path); return dirOpenDirWindows(dir, sub_path_w.span(), options); } @@ -5984,7 +5984,7 @@ fn dirRealPathFileWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, const t: *Threaded = @ptrCast(@alignCast(userdata)); _ = t; - var path_name_w = try windows.sliceToPrefixedFileW(dir.handle, sub_path); + var path_name_w = try sliceToPrefixedFileW(dir.handle, sub_path); const h_file = handle: { const syscall: Syscall = try .start(); @@ -6016,9 +6016,7 @@ fn dirRealPathFileWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, fn realPathWindows(h_file: windows.HANDLE, out_buffer: []u8) File.RealPathError!usize { var wide_buf: [windows.PATH_MAX_WIDE]u16 = undefined; - // TODO move GetFinalPathNameByHandle logic into Io.Threaded and add cancel checks - try Thread.checkCancel(); - const wide_slice = try windows.GetFinalPathNameByHandle(h_file, .{}, &wide_buf); + const wide_slice = try GetFinalPathNameByHandle(h_file, .{}, &wide_buf); const len = std.unicode.calcWtf8Len(wide_slice); if (len > out_buffer.len) @@ -6027,6 +6025,552 @@ fn realPathWindows(h_file: windows.HANDLE, out_buffer: []u8) File.RealPathError! return std.unicode.wtf16LeToWtf8(out_buffer, wide_slice); } +/// Specifies how to format volume path in the result of `GetFinalPathNameByHandle`. +/// Defaults to DOS volume names. +pub const GetFinalPathNameByHandleFormat = struct { + volume_name: enum { + /// Format as DOS volume name + Dos, + /// Format as NT volume name + Nt, + } = .Dos, +}; + +pub const GetFinalPathNameByHandleError = error{ + AccessDenied, + FileNotFound, + NameTooLong, + /// The volume does not contain a recognized file system. File system + /// drivers might not be loaded, or the volume may be corrupt. + UnrecognizedVolume, +} || Io.Cancelable || Io.UnexpectedError; + +/// Returns canonical (normalized) path of handle. +/// Use `GetFinalPathNameByHandleFormat` to specify whether the path is meant to include +/// NT or DOS volume name (e.g., `\Device\HarddiskVolume0\foo.txt` versus `C:\foo.txt`). +/// If DOS volume name format is selected, note that this function does *not* prepend +/// `\\?\` prefix to the resultant path. +pub fn GetFinalPathNameByHandle( + hFile: windows.HANDLE, + fmt: GetFinalPathNameByHandleFormat, + out_buffer: []u16, +) GetFinalPathNameByHandleError![]u16 { + const final_path = QueryObjectName(hFile, out_buffer) catch |err| switch (err) { + // we assume InvalidHandle is close enough to FileNotFound in semantics + // to not further complicate the error set + error.InvalidHandle => return error.FileNotFound, + else => |e| return e, + }; + + switch (fmt.volume_name) { + .Nt => { + // the returned path is already in .Nt format + return final_path; + }, + .Dos => { + // parse the string to separate volume path from file path + const device_prefix = std.unicode.utf8ToUtf16LeStringLiteral("\\Device\\"); + + // We aren't entirely sure of the structure of the path returned by + // QueryObjectName in all contexts/environments. + // This code is written to cover the various cases that have + // been encountered and solved appropriately. But note that there's + // no easy way to verify that they have all been tackled! + // (Unless you, the reader knows of one then please do action that!) + if (!std.mem.startsWith(u16, final_path, device_prefix)) { + // Wine seems to return NT namespaced paths starting with \??\ from QueryObjectName + // (e.g. `\??\Z:\some\path\to\a\file.txt`), in which case we can just strip the + // prefix to turn it into an absolute path. + // https://github.com/ziglang/zig/issues/26029 + // https://bugs.winehq.org/show_bug.cgi?id=39569 + return windows.ntToWin32Namespace(final_path, out_buffer) catch |err| switch (err) { + error.NotNtPath => return error.Unexpected, + error.NameTooLong => |e| return e, + }; + } + + const file_path_begin_index = std.mem.findPos(u16, final_path, device_prefix.len, &[_]u16{'\\'}) orelse unreachable; + const volume_name_u16 = final_path[0..file_path_begin_index]; + const device_name_u16 = volume_name_u16[device_prefix.len..]; + const file_name_u16 = final_path[file_path_begin_index..]; + + // MUP is Multiple UNC Provider, and indicates that the path is a UNC + // path. In this case, the canonical UNC path can be gotten by just + // dropping the \Device\Mup\ and making sure the path begins with \\ + if (std.mem.eql(u16, device_name_u16, std.unicode.utf8ToUtf16LeStringLiteral("Mup"))) { + out_buffer[0] = '\\'; + @memmove(out_buffer[1..][0..file_name_u16.len], file_name_u16); + return out_buffer[0 .. 1 + file_name_u16.len]; + } + + // Get DOS volume name. DOS volume names are actually symbolic link objects to the + // actual NT volume. For example: + // (NT) \Device\HarddiskVolume4 => (DOS) \DosDevices\C: == (DOS) C: + const MIN_SIZE = @sizeOf(windows.MOUNTMGR_MOUNT_POINT) + windows.MAX_PATH; + // We initialize the input buffer to all zeros for convenience since + // `DeviceIoControl` with `IOCTL_MOUNTMGR_QUERY_POINTS` expects this. + var input_buf: [MIN_SIZE]u8 align(@alignOf(windows.MOUNTMGR_MOUNT_POINT)) = [_]u8{0} ** MIN_SIZE; + var output_buf: [MIN_SIZE * 4]u8 align(@alignOf(windows.MOUNTMGR_MOUNT_POINTS)) = undefined; + + // This surprising path is a filesystem path to the mount manager on Windows. + // Source: https://stackoverflow.com/questions/3012828/using-ioctl-mountmgr-query-points + // This is the NT namespaced version of \\.\MountPointManager + const mgmt_path_u16 = std.unicode.utf8ToUtf16LeStringLiteral("\\??\\MountPointManager"); + const mgmt_handle = windows.OpenFile(mgmt_path_u16, .{ + .access_mask = .{ .STANDARD = .{ .SYNCHRONIZE = true } }, + .creation = .OPEN, + }) catch |err| switch (err) { + error.IsDir => return error.Unexpected, + error.NotDir => return error.Unexpected, + error.NoDevice => return error.Unexpected, + error.AccessDenied => return error.Unexpected, + error.PipeBusy => return error.Unexpected, + error.PathAlreadyExists => return error.Unexpected, + error.WouldBlock => return error.Unexpected, + error.NetworkNotFound => return error.Unexpected, + error.AntivirusInterference => return error.Unexpected, + error.BadPathName => return error.Unexpected, + error.OperationCanceled => @panic("TODO: better integrate cancelation"), + else => |e| return e, + }; + defer windows.CloseHandle(mgmt_handle); + + var input_struct: *windows.MOUNTMGR_MOUNT_POINT = @ptrCast(&input_buf[0]); + input_struct.DeviceNameOffset = @sizeOf(windows.MOUNTMGR_MOUNT_POINT); + input_struct.DeviceNameLength = @intCast(volume_name_u16.len * 2); + @memcpy(input_buf[@sizeOf(windows.MOUNTMGR_MOUNT_POINT)..][0 .. volume_name_u16.len * 2], @as([*]const u8, @ptrCast(volume_name_u16.ptr))); + + { + const rc = windows.DeviceIoControl(mgmt_handle, windows.IOCTL.MOUNTMGR.QUERY_POINTS, .{ .in = &input_buf, .out = &output_buf }); + switch (rc) { + .SUCCESS => {}, + .OBJECT_NAME_NOT_FOUND => return error.FileNotFound, + else => return windows.unexpectedStatus(rc), + } + } + const mount_points_struct: *const windows.MOUNTMGR_MOUNT_POINTS = @ptrCast(&output_buf[0]); + + const mount_points = @as( + [*]const windows.MOUNTMGR_MOUNT_POINT, + @ptrCast(&mount_points_struct.MountPoints[0]), + )[0..mount_points_struct.NumberOfMountPoints]; + + for (mount_points) |mount_point| { + const symlink = @as( + [*]const u16, + @ptrCast(@alignCast(&output_buf[mount_point.SymbolicLinkNameOffset])), + )[0 .. mount_point.SymbolicLinkNameLength / 2]; + + // Look for `\DosDevices\` prefix. We don't really care if there are more than one symlinks + // with traditional DOS drive letters, so pick the first one available. + var prefix_buf = std.unicode.utf8ToUtf16LeStringLiteral("\\DosDevices\\"); + const prefix = prefix_buf[0..prefix_buf.len]; + + if (std.mem.startsWith(u16, symlink, prefix)) { + const drive_letter = symlink[prefix.len..]; + + if (out_buffer.len < drive_letter.len + file_name_u16.len) return error.NameTooLong; + + @memcpy(out_buffer[0..drive_letter.len], drive_letter); + @memmove(out_buffer[drive_letter.len..][0..file_name_u16.len], file_name_u16); + const total_len = drive_letter.len + file_name_u16.len; + + // Validate that DOS does not contain any spurious nul bytes. + assert(std.mem.findScalar(u16, out_buffer[0..total_len], 0) == null); + + return out_buffer[0..total_len]; + } else if (mountmgrIsVolumeName(symlink)) { + // If the symlink is a volume GUID like \??\Volume{383da0b0-717f-41b6-8c36-00500992b58d}, + // then it is a volume mounted as a path rather than a drive letter. We need to + // query the mount manager again to get the DOS path for the volume. + + // 49 is the maximum length accepted by mountmgrIsVolumeName + const vol_input_size = @sizeOf(windows.MOUNTMGR_TARGET_NAME) + (49 * 2); + var vol_input_buf: [vol_input_size]u8 align(@alignOf(windows.MOUNTMGR_TARGET_NAME)) = [_]u8{0} ** vol_input_size; + // Note: If the path exceeds MAX_PATH, the Disk Management GUI doesn't accept the full path, + // and instead if must be specified using a shortened form (e.g. C:\FOO~1\BAR~1\<...>). + // However, just to be sure we can handle any path length, we use PATH_MAX_WIDE here. + const min_output_size = @sizeOf(windows.MOUNTMGR_VOLUME_PATHS) + (windows.PATH_MAX_WIDE * 2); + var vol_output_buf: [min_output_size]u8 align(@alignOf(windows.MOUNTMGR_VOLUME_PATHS)) = undefined; + + var vol_input_struct: *windows.MOUNTMGR_TARGET_NAME = @ptrCast(&vol_input_buf[0]); + vol_input_struct.DeviceNameLength = @intCast(symlink.len * 2); + @memcpy(@as([*]windows.WCHAR, &vol_input_struct.DeviceName)[0..symlink.len], symlink); + + const rc = windows.DeviceIoControl(mgmt_handle, windows.IOCTL.MOUNTMGR.QUERY_DOS_VOLUME_PATH, .{ .in = &vol_input_buf, .out = &vol_output_buf }); + switch (rc) { + .SUCCESS => {}, + .UNRECOGNIZED_VOLUME => return error.UnrecognizedVolume, + else => return windows.unexpectedStatus(rc), + } + const volume_paths_struct: *const windows.MOUNTMGR_VOLUME_PATHS = @ptrCast(&vol_output_buf[0]); + const volume_path = std.mem.sliceTo(@as( + [*]const u16, + &volume_paths_struct.MultiSz, + )[0 .. volume_paths_struct.MultiSzLength / 2], 0); + + if (out_buffer.len < volume_path.len + file_name_u16.len) return error.NameTooLong; + + // `out_buffer` currently contains the memory of `file_name_u16`, so it can overlap with where + // we want to place the filename before returning. Here are the possible overlapping cases: + // + // out_buffer: [filename] + // dest: [___(a)___] [___(b)___] + // + // In the case of (a), we need to copy forwards, and in the case of (b) we need + // to copy backwards. We also need to do this before copying the volume path because + // it could overwrite the file_name_u16 memory. + const file_name_dest = out_buffer[volume_path.len..][0..file_name_u16.len]; + @memmove(file_name_dest, file_name_u16); + @memcpy(out_buffer[0..volume_path.len], volume_path); + const total_len = volume_path.len + file_name_u16.len; + + // Validate that DOS does not contain any spurious nul bytes. + assert(std.mem.findScalar(u16, out_buffer[0..total_len], 0) == null); + + return out_buffer[0..total_len]; + } + } + + // If we've ended up here, then something went wrong/is corrupted in the OS, + // so error out! + return error.FileNotFound; + }, + } +} + +test GetFinalPathNameByHandle { + if (builtin.os.tag != .windows) + return; + + //any file will do + var tmp = std.testing.tmpDir(.{}); + defer tmp.cleanup(); + const handle = tmp.dir.handle; + var buffer: [windows.PATH_MAX_WIDE]u16 = undefined; + + //check with sufficient size + const nt_path = try GetFinalPathNameByHandle(handle, .{ .volume_name = .Nt }, &buffer); + _ = try GetFinalPathNameByHandle(handle, .{ .volume_name = .Dos }, &buffer); + + const required_len_in_u16 = nt_path.len + @divExact(@intFromPtr(nt_path.ptr) - @intFromPtr(&buffer), 2) + 1; + //check with insufficient size + try std.testing.expectError(error.NameTooLong, GetFinalPathNameByHandle(handle, .{ .volume_name = .Nt }, buffer[0 .. required_len_in_u16 - 1])); + try std.testing.expectError(error.NameTooLong, GetFinalPathNameByHandle(handle, .{ .volume_name = .Dos }, buffer[0 .. required_len_in_u16 - 1])); + + //check with exactly-sufficient size + _ = try GetFinalPathNameByHandle(handle, .{ .volume_name = .Nt }, buffer[0..required_len_in_u16]); + _ = try GetFinalPathNameByHandle(handle, .{ .volume_name = .Dos }, buffer[0..required_len_in_u16]); +} + +/// Equivalent to the MOUNTMGR_IS_VOLUME_NAME macro in mountmgr.h +fn mountmgrIsVolumeName(name: []const u16) bool { + return (name.len == 48 or (name.len == 49 and name[48] == std.mem.nativeToLittle(u16, '\\'))) and + name[0] == std.mem.nativeToLittle(u16, '\\') and + (name[1] == std.mem.nativeToLittle(u16, '?') or name[1] == std.mem.nativeToLittle(u16, '\\')) and + name[2] == std.mem.nativeToLittle(u16, '?') and + name[3] == std.mem.nativeToLittle(u16, '\\') and + std.mem.startsWith(u16, name[4..], std.unicode.utf8ToUtf16LeStringLiteral("Volume{")) and + name[19] == std.mem.nativeToLittle(u16, '-') and + name[24] == std.mem.nativeToLittle(u16, '-') and + name[29] == std.mem.nativeToLittle(u16, '-') and + name[34] == std.mem.nativeToLittle(u16, '-') and + name[47] == std.mem.nativeToLittle(u16, '}'); +} + +test mountmgrIsVolumeName { + @setEvalBranchQuota(2000); + const L = std.unicode.utf8ToUtf16LeStringLiteral; + try std.testing.expect(mountmgrIsVolumeName(L("\\\\?\\Volume{383da0b0-717f-41b6-8c36-00500992b58d}"))); + try std.testing.expect(mountmgrIsVolumeName(L("\\??\\Volume{383da0b0-717f-41b6-8c36-00500992b58d}"))); + try std.testing.expect(mountmgrIsVolumeName(L("\\\\?\\Volume{383da0b0-717f-41b6-8c36-00500992b58d}\\"))); + try std.testing.expect(mountmgrIsVolumeName(L("\\??\\Volume{383da0b0-717f-41b6-8c36-00500992b58d}\\"))); + try std.testing.expect(!mountmgrIsVolumeName(L("\\\\.\\Volume{383da0b0-717f-41b6-8c36-00500992b58d}"))); + try std.testing.expect(!mountmgrIsVolumeName(L("\\??\\Volume{383da0b0-717f-41b6-8c36-00500992b58d}\\foo"))); + try std.testing.expect(!mountmgrIsVolumeName(L("\\??\\Volume{383da0b0-717f-41b6-8c36-00500992b58}"))); +} + +pub const QueryObjectNameError = error{ + AccessDenied, + InvalidHandle, + NameTooLong, + Unexpected, +}; + +pub fn QueryObjectName(handle: windows.HANDLE, out_buffer: []u16) QueryObjectNameError![]u16 { + const out_buffer_aligned = std.mem.alignInSlice(out_buffer, @alignOf(windows.OBJECT_NAME_INFORMATION)) orelse return error.NameTooLong; + + const info: *windows.OBJECT_NAME_INFORMATION = @ptrCast(out_buffer_aligned); + // buffer size is specified in bytes + const out_buffer_len = std.math.cast(windows.ULONG, out_buffer_aligned.len * 2) orelse std.math.maxInt(windows.ULONG); + // last argument would return the length required for full_buffer, not exposed here + return switch (windows.ntdll.NtQueryObject(handle, .ObjectNameInformation, info, out_buffer_len, null)) { + .SUCCESS => blk: { + // info.Name.Buffer from ObQueryNameString is documented to be null (and MaximumLength == 0) + // if the object was "unnamed", not sure if this can happen for file handles + if (info.Name.MaximumLength == 0) break :blk error.Unexpected; + // resulting string length is specified in bytes + const path_length_unterminated = @divExact(info.Name.Length, 2); + break :blk info.Name.Buffer.?[0..path_length_unterminated]; + }, + .ACCESS_DENIED => error.AccessDenied, + .INVALID_HANDLE => error.InvalidHandle, + // triggered when the buffer is too small for the OBJECT_NAME_INFORMATION object (.INFO_LENGTH_MISMATCH), + // or if the buffer is too small for the file path returned (.BUFFER_OVERFLOW, .BUFFER_TOO_SMALL) + .INFO_LENGTH_MISMATCH, .BUFFER_OVERFLOW, .BUFFER_TOO_SMALL => error.NameTooLong, + else => |e| windows.unexpectedStatus(e), + }; +} + +test QueryObjectName { + if (builtin.os.tag != .windows) + return; + + //any file will do; canonicalization works on NTFS junctions and symlinks, hardlinks remain separate paths. + var tmp = std.testing.tmpDir(.{}); + defer tmp.cleanup(); + const handle = tmp.dir.handle; + var out_buffer: [windows.PATH_MAX_WIDE]u16 = undefined; + + const result_path = try QueryObjectName(handle, &out_buffer); + const required_len_in_u16 = result_path.len + @divExact(@intFromPtr(result_path.ptr) - @intFromPtr(&out_buffer), 2) + 1; + //insufficient size + try std.testing.expectError(error.NameTooLong, QueryObjectName(handle, out_buffer[0 .. required_len_in_u16 - 1])); + //exactly-sufficient size + _ = try QueryObjectName(handle, out_buffer[0..required_len_in_u16]); +} + +const Wtf16ToPrefixedFileWError = error{ + AccessDenied, + FileNotFound, +} || Dir.PathNameError || Io.Cancelable || Io.UnexpectedError; + +/// Converts the `path` to WTF16, null-terminated. If the path contains any +/// namespace prefix, or is anything but a relative path (rooted, drive relative, +/// etc) the result will have the NT-style prefix `\??\`. +/// +/// Similar to RtlDosPathNameToNtPathName_U with a few differences: +/// - Does not allocate on the heap. +/// - Relative paths are kept as relative unless they contain too many .. +/// components, in which case they are resolved against the `dir` if it +/// is non-null, or the CWD if it is null. +/// - Special case device names like COM1, NUL, etc are not handled specially (TODO) +/// - . and space are not stripped from the end of relative paths (potential TODO) +pub fn wToPrefixedFileW(dir: ?windows.HANDLE, path: [:0]const u16) Wtf16ToPrefixedFileWError!WindowsPathSpace { + const nt_prefix = [_]u16{ '\\', '?', '?', '\\' }; + if (windows.hasCommonNtPrefix(u16, path)) { + // TODO: Figure out a way to design an API that can avoid the copy for NT, + // since it is always returned fully unmodified. + var path_space: WindowsPathSpace = undefined; + path_space.data[0..nt_prefix.len].* = nt_prefix; + const len_after_prefix = path.len - nt_prefix.len; + @memcpy(path_space.data[nt_prefix.len..][0..len_after_prefix], path[nt_prefix.len..]); + path_space.len = path.len; + path_space.data[path_space.len] = 0; + return path_space; + } else { + const path_type = Dir.path.getWin32PathType(u16, path); + var path_space: WindowsPathSpace = undefined; + if (path_type == .local_device) { + switch (getLocalDevicePathType(u16, path)) { + .verbatim => { + path_space.data[0..nt_prefix.len].* = nt_prefix; + const len_after_prefix = path.len - nt_prefix.len; + @memcpy(path_space.data[nt_prefix.len..][0..len_after_prefix], path[nt_prefix.len..]); + path_space.len = path.len; + path_space.data[path_space.len] = 0; + return path_space; + }, + .local_device, .fake_verbatim => { + const path_byte_len = windows.ntdll.RtlGetFullPathName_U( + path.ptr, + path_space.data.len * 2, + &path_space.data, + null, + ); + if (path_byte_len == 0) { + // TODO: This may not be the right error + return error.BadPathName; + } else if (path_byte_len / 2 > path_space.data.len) { + return error.NameTooLong; + } + path_space.len = path_byte_len / 2; + // Both prefixes will be normalized but retained, so all + // we need to do now is replace them with the NT prefix + path_space.data[0..nt_prefix.len].* = nt_prefix; + return path_space; + }, + } + } + relative: { + if (path_type == .relative) { + // TODO: Handle special case device names like COM1, AUX, NUL, CONIN$, CONOUT$, etc. + // See https://googleprojectzero.blogspot.com/2016/02/the-definitive-guide-on-win32-to-nt.html + + // TODO: Potentially strip all trailing . and space characters from the + // end of the path. This is something that both RtlDosPathNameToNtPathName_U + // and RtlGetFullPathName_U do. Technically, trailing . and spaces + // are allowed, but such paths may not interact well with Windows (i.e. + // files with these paths can't be deleted from explorer.exe, etc). + // This could be something that normalizePath may want to do. + + @memcpy(path_space.data[0..path.len], path); + // Try to normalize, but if we get too many parent directories, + // then we need to start over and use RtlGetFullPathName_U instead. + path_space.len = windows.normalizePath(u16, path_space.data[0..path.len]) catch |err| switch (err) { + error.TooManyParentDirs => break :relative, + }; + path_space.data[path_space.len] = 0; + return path_space; + } + } + // We now know we are going to return an absolute NT path, so + // we can unconditionally prefix it with the NT prefix. + path_space.data[0..nt_prefix.len].* = nt_prefix; + if (path_type == .root_local_device) { + // `\\.` and `\\?` always get converted to `\??\` exactly, so + // we can just stop here + path_space.len = nt_prefix.len; + path_space.data[path_space.len] = 0; + return path_space; + } + const path_buf_offset = switch (path_type) { + // UNC paths will always start with `\\`. However, we want to + // end up with something like `\??\UNC\server\share`, so to get + // RtlGetFullPathName to write into the spot we want the `server` + // part to end up, we need to provide an offset such that + // the `\\` part gets written where the `C\` of `UNC\` will be + // in the final NT path. + .unc_absolute => nt_prefix.len + 2, + else => nt_prefix.len, + }; + const buf_len: u32 = @intCast(path_space.data.len - path_buf_offset); + const path_to_get: [:0]const u16 = path_to_get: { + // If dir is null, then we don't need to bother with GetFinalPathNameByHandle because + // RtlGetFullPathName_U will resolve relative paths against the CWD for us. + if (path_type != .relative or dir == null) { + break :path_to_get path; + } + // We can also skip GetFinalPathNameByHandle if the handle matches + // the handle returned by Io.Dir.cwd() + if (dir.? == Io.Dir.cwd().handle) { + break :path_to_get path; + } + // At this point, we know we have a relative path that had too many + // `..` components to be resolved by normalizePath, so we need to + // convert it into an absolute path and let RtlGetFullPathName_U + // canonicalize it. We do this by getting the path of the `dir` + // and appending the relative path to it. + var dir_path_buf: [windows.PATH_MAX_WIDE:0]u16 = undefined; + const dir_path = GetFinalPathNameByHandle(dir.?, .{}, &dir_path_buf) catch |err| switch (err) { + // This mapping is not correct; it is actually expected + // that calling GetFinalPathNameByHandle might return + // error.UnrecognizedVolume, and in fact has been observed + // in the wild. The problem is that wToPrefixedFileW was + // never intended to make *any* OS syscall APIs. It's only + // supposed to convert a string to one that is eligible to + // be used in the ntdll syscalls. + // + // To solve this, this function needs to no longer call + // GetFinalPathNameByHandle under any conditions, or the + // calling function needs to get reworked to not need to + // call this function. + // + // This may involve making breaking API changes. + error.UnrecognizedVolume => return error.Unexpected, + else => |e| return e, + }; + if (dir_path.len + 1 + path.len > windows.PATH_MAX_WIDE) { + return error.NameTooLong; + } + // We don't have to worry about potentially doubling up path separators + // here since RtlGetFullPathName_U will handle canonicalizing it. + dir_path_buf[dir_path.len] = '\\'; + @memcpy(dir_path_buf[dir_path.len + 1 ..][0..path.len], path); + const full_len = dir_path.len + 1 + path.len; + dir_path_buf[full_len] = 0; + break :path_to_get dir_path_buf[0..full_len :0]; + }; + const path_byte_len = windows.ntdll.RtlGetFullPathName_U( + path_to_get.ptr, + buf_len * 2, + path_space.data[path_buf_offset..].ptr, + null, + ); + if (path_byte_len == 0) { + // TODO: This may not be the right error + return error.BadPathName; + } else if (path_byte_len / 2 > buf_len) { + return error.NameTooLong; + } + path_space.len = path_buf_offset + (path_byte_len / 2); + if (path_type == .unc_absolute) { + // Now add in the UNC, the `C` should overwrite the first `\` of the + // FullPathName, ultimately resulting in `\??\UNC\` + assert(path_space.data[path_buf_offset] == '\\'); + assert(path_space.data[path_buf_offset + 1] == '\\'); + const unc = [_]u16{ 'U', 'N', 'C' }; + path_space.data[nt_prefix.len..][0..unc.len].* = unc; + } + return path_space; + } +} + +const LocalDevicePathType = enum { + /// `\\.\` (path separators can be `\` or `/`) + local_device, + /// `\\?\` + /// When converted to an NT path, everything past the prefix is left + /// untouched and `\\?\` is replaced by `\??\`. + verbatim, + /// `\\?\` without all path separators being `\`. + /// This seems to be recognized as a prefix, but the 'verbatim' aspect + /// is not respected (i.e. if `//?/C:/foo` is converted to an NT path, + /// it will become `\??\C:\foo` [it will be canonicalized and the //?/ won't + /// be treated as part of the final path]) + fake_verbatim, +}; + +/// Only relevant for Win32 -> NT path conversion. +/// Asserts `path` is of type `Dir.path.Win32PathType.local_device`. +fn getLocalDevicePathType(comptime T: type, path: []const T) LocalDevicePathType { + if (std.debug.runtime_safety) { + assert(Dir.path.getWin32PathType(T, path) == .local_device); + } + + const backslash = std.mem.nativeToLittle(T, '\\'); + const all_backslash = path[0] == backslash and + path[1] == backslash and + path[3] == backslash; + return switch (path[2]) { + std.mem.nativeToLittle(T, '?') => if (all_backslash) .verbatim else .fake_verbatim, + std.mem.nativeToLittle(T, '.') => .local_device, + else => unreachable, + }; +} + +pub const Wtf8ToPrefixedFileWError = Wtf16ToPrefixedFileWError; + +/// Same as `wToPrefixedFileW` but accepts a WTF-8 encoded path. +/// https://wtf-8.codeberg.page/ +pub fn sliceToPrefixedFileW(dir: ?windows.HANDLE, path: []const u8) Wtf8ToPrefixedFileWError!WindowsPathSpace { + var temp_path: WindowsPathSpace = undefined; + temp_path.len = std.unicode.wtf8ToWtf16Le(&temp_path.data, path) catch |err| switch (err) { + error.InvalidWtf8 => return error.BadPathName, + }; + temp_path.data[temp_path.len] = 0; + return wToPrefixedFileW(dir, temp_path.span()); +} + +pub const WindowsPathSpace = struct { + data: [windows.PATH_MAX_WIDE:0]u16, + len: usize, + + pub fn span(self: *const WindowsPathSpace) [:0]const u16 { + return self.data[0..self.len :0]; + } +}; + fn dirRealPathFilePosix(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, out_buffer: []u8) Dir.RealPathFileError!usize { if (native_os == .wasi) return error.OperationUnsupported; @@ -6478,7 +7022,7 @@ fn dirDeleteWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, remov _ = t; const w = windows; - const sub_path_w_buf = try w.sliceToPrefixedFileW(dir.handle, sub_path); + const sub_path_w_buf = try sliceToPrefixedFileW(dir.handle, sub_path); const sub_path_w = sub_path_w_buf.span(); const path_len_bytes = @as(u16, @intCast(sub_path_w.len * 2)); @@ -6759,9 +7303,9 @@ fn dirRenameWindowsInner( replace_if_exists: bool, ) Dir.RenamePreserveError!void { const w = windows; - const old_path_w_buf = try windows.sliceToPrefixedFileW(old_dir.handle, old_sub_path); + const old_path_w_buf = try sliceToPrefixedFileW(old_dir.handle, old_sub_path); const old_path_w = old_path_w_buf.span(); - const new_path_w_buf = try windows.sliceToPrefixedFileW(new_dir.handle, new_sub_path); + const new_path_w_buf = try sliceToPrefixedFileW(new_dir.handle, new_sub_path); const new_path_w = new_path_w_buf.span(); const src_fd = src_fd: { @@ -7092,7 +7636,7 @@ fn dirSymLinkWindows( // Target path does not use sliceToPrefixedFileW because certain paths // are handled differently when creating a symlink than they would be // when converting to an NT namespaced path. - var target_path_w: w.PathSpace = undefined; + var target_path_w: WindowsPathSpace = undefined; target_path_w.len = try w.wtf8ToWtf16Le(&target_path_w.data, target_path); target_path_w.data[target_path_w.len] = 0; // However, we need to canonicalize any path separators to `\`, since if @@ -7104,7 +7648,7 @@ fn dirSymLinkWindows( std.mem.nativeToLittle(u16, '\\'), ); - const sym_link_path_w = try w.sliceToPrefixedFileW(dir.handle, sym_link_path); + const sym_link_path_w = try sliceToPrefixedFileW(dir.handle, sym_link_path); const SYMLINK_DATA = extern struct { ReparseTag: w.IO_REPARSE_TAG, @@ -7158,7 +7702,7 @@ fn dirSymLinkWindows( // https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createsymboliclinkw var is_target_absolute = false; const final_target_path = target_path: { - if (w.hasCommonNtPrefix(u16, target_path_w.span())) { + if (windows.hasCommonNtPrefix(u16, target_path_w.span())) { // Already an NT path, no need to do anything to it break :target_path target_path_w.span(); } else { @@ -7176,7 +7720,7 @@ fn dirSymLinkWindows( break :target_path target_path_w.span(), } } - var prefixed_target_path = try w.wToPrefixedFileW(dir.handle, target_path_w.span()); + var prefixed_target_path = try wToPrefixedFileW(dir.handle, target_path_w.span()); // We do this after prefixing to ensure that drive-relative paths are treated as absolute is_target_absolute = Dir.path.isAbsoluteWindowsWtf16(prefixed_target_path.span()); break :target_path prefixed_target_path.span(); @@ -7322,7 +7866,7 @@ fn dirReadLink(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, buffer: [] fn dirReadLinkWindows(dir: Dir, sub_path: []const u8, buffer: []u8) Dir.ReadLinkError!usize { // This gets used once for `sub_path` and then reused again temporarily // before converting back to `buffer`. - var sub_path_w_buf = try windows.sliceToPrefixedFileW(dir.handle, sub_path); + var sub_path_w_buf = try sliceToPrefixedFileW(dir.handle, sub_path); const sub_path_w = sub_path_w_buf.span(); const path_len_bytes = std.math.cast(u16, sub_path_w.len * 2) orelse return error.NameTooLong; var nt_name: windows.UNICODE_STRING = .{ @@ -9586,7 +10130,7 @@ fn processExecutableOpen(userdata: ?*anyopaque, flags: File.OpenFlags) process.O // the file, we can let the openFileW call follow the symlink for us. const image_path_unicode_string = &windows.peb().ProcessParameters.ImagePathName; const image_path_name = image_path_unicode_string.Buffer.?[0 .. image_path_unicode_string.Length / 2 :0]; - const prefixed_path_w = try windows.wToPrefixedFileW(null, image_path_name); + const prefixed_path_w = try wToPrefixedFileW(null, image_path_name); return dirOpenFileWtf16(null, prefixed_path_w.span(), flags); }, .driverkit, @@ -9794,7 +10338,7 @@ fn processExecutablePath(userdata: ?*anyopaque, out_buffer: []u8) process.Execut // If ImagePathName is a symlink, then it will contain the path of the // symlink, not the path that the symlink points to. We want the path // that the symlink points to, though, so we need to get the realpath. - var path_name_w_buf = try w.wToPrefixedFileW(null, image_path_name); + var path_name_w_buf = try wToPrefixedFileW(null, image_path_name); const h_file = handle: { const syscall: Syscall = try .start(); @@ -9822,9 +10366,7 @@ fn processExecutablePath(userdata: ?*anyopaque, out_buffer: []u8) process.Execut }; defer w.CloseHandle(h_file); - // TODO move GetFinalPathNameByHandle logic into Io.Threaded and add cancel checks - try Thread.checkCancel(); - const wide_slice = try w.GetFinalPathNameByHandle(h_file, .{}, &path_name_w_buf.data); + const wide_slice = try GetFinalPathNameByHandle(h_file, .{}, &path_name_w_buf.data); const len = std.unicode.calcWtf8Len(wide_slice); if (len > out_buffer.len) @@ -13598,9 +14140,7 @@ fn processSetCurrentDir(userdata: ?*anyopaque, dir: Dir) process.SetCurrentDirEr if (is_windows) { var dir_path_buffer: [windows.PATH_MAX_WIDE]u16 = undefined; - // TODO move GetFinalPathNameByHandle logic into Io.Threaded and add cancel checks - try Thread.checkCancel(); - const dir_path = try windows.GetFinalPathNameByHandle(dir.handle, .{}, &dir_path_buffer); + const dir_path = try GetFinalPathNameByHandle(dir.handle, .{}, &dir_path_buffer); const path_len_bytes = std.math.cast(u16, dir_path.len * 2) orelse return error.NameTooLong; var nt_name: windows.UNICODE_STRING = .{ .Length = path_len_bytes, @@ -15326,9 +15866,7 @@ fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) pro .inherit => break :cwd_w null, .dir => |cwd_dir| { var dir_path_buffer = try arena.alloc(u16, windows.PATH_MAX_WIDE + 1); - // TODO move GetFinalPathNameByHandle logic into std.Io.Threaded and add cancel checks - try Thread.checkCancel(); - const dir_path = try windows.GetFinalPathNameByHandle( + const dir_path = try GetFinalPathNameByHandle( cwd_dir.handle, .{}, dir_path_buffer[0..windows.PATH_MAX_WIDE], @@ -15752,7 +16290,7 @@ fn windowsCreateProcessPathExt( try dir_buf.append(arena, 0); defer dir_buf.shrinkRetainingCapacity(dir_path_len); const dir_path_z = dir_buf.items[0 .. dir_buf.items.len - 1 :0]; - const prefixed_path = try windows.wToPrefixedFileW(null, dir_path_z); + const prefixed_path = try wToPrefixedFileW(null, dir_path_z); break :dir dirOpenDirWindows(.cwd(), prefixed_path.span(), .{ .iterate = true, }) catch |err| switch (err) { diff --git a/lib/std/Io/Threaded/test.zig b/lib/std/Io/Threaded/test.zig index 81c7be9170e8479c231e5f30931b38de39723f20..1c9b188584b588a44c225e51450390787eb81557 100644 --- a/lib/std/Io/Threaded/test.zig +++ b/lib/std/Io/Threaded/test.zig @@ -6,6 +6,7 @@ const std = @import("std"); const Io = std.Io; const testing = std.testing; const assert = std.debug.assert; +const windows = std.os.windows; test "concurrent vs main prevents deadlock via oversubscription" { if (true) { @@ -277,3 +278,337 @@ test "memory mapping fallback" { try testing.expectEqualStrings("this9is9my data123", mm.memory); } } + +/// Wrapper around RtlDosPathNameToNtPathName_U for use in comparing +/// the behavior of RtlDosPathNameToNtPathName_U with wToPrefixedFileW +/// Note: RtlDosPathNameToNtPathName_U is not used in the Zig implementation +// because it allocates. +fn RtlDosPathNameToNtPathName_U(path: [:0]const u16) !Io.Threaded.WindowsPathSpace { + var out: windows.UNICODE_STRING = undefined; + const rc = windows.ntdll.RtlDosPathNameToNtPathName_U(path, &out, null, null); + if (rc != windows.TRUE) return error.BadPathName; + defer windows.ntdll.RtlFreeUnicodeString(&out); + + var path_space: Io.Threaded.WindowsPathSpace = undefined; + const out_path = out.Buffer.?[0 .. out.Length / 2]; + @memcpy(path_space.data[0..out_path.len], out_path); + path_space.len = out.Length / 2; + path_space.data[path_space.len] = 0; + + return path_space; +} + +/// Test that the Zig conversion matches the expected_path (for instances where +/// the Zig implementation intentionally diverges from what RtlDosPathNameToNtPathName_U does). +fn testToPrefixedFileNoOracle(comptime path: []const u8, comptime expected_path: []const u8) !void { + const path_utf16 = std.unicode.utf8ToUtf16LeStringLiteral(path); + const expected_path_utf16 = std.unicode.utf8ToUtf16LeStringLiteral(expected_path); + const actual_path = try Io.Threaded.wToPrefixedFileW(null, path_utf16); + std.testing.expectEqualSlices(u16, expected_path_utf16, actual_path.span()) catch |e| { + std.debug.print("got '{f}', expected '{f}'\n", .{ std.unicode.fmtUtf16Le(actual_path.span()), std.unicode.fmtUtf16Le(expected_path_utf16) }); + return e; + }; +} + +/// Test that the Zig conversion matches the expected_path and that the +/// expected_path matches the conversion that RtlDosPathNameToNtPathName_U does. +fn testToPrefixedFileWithOracle(comptime path: []const u8, comptime expected_path: []const u8) !void { + try testToPrefixedFileNoOracle(path, expected_path); + try testToPrefixedFileOnlyOracle(path); +} + +/// Test that the Zig conversion matches the conversion that RtlDosPathNameToNtPathName_U does. +fn testToPrefixedFileOnlyOracle(comptime path: []const u8) !void { + const path_utf16 = std.unicode.utf8ToUtf16LeStringLiteral(path); + const zig_result = try Io.Threaded.wToPrefixedFileW(null, path_utf16); + const win32_api_result = try RtlDosPathNameToNtPathName_U(path_utf16); + std.testing.expectEqualSlices(u16, win32_api_result.span(), zig_result.span()) catch |e| { + std.debug.print("got '{f}', expected '{f}'\n", .{ std.unicode.fmtUtf16Le(zig_result.span()), std.unicode.fmtUtf16Le(win32_api_result.span()) }); + return e; + }; +} + +test "toPrefixedFileW" { + if (builtin.os.tag != .windows) return error.SkipZigTest; + + // Most test cases come from https://googleprojectzero.blogspot.com/2016/02/the-definitive-guide-on-win32-to-nt.html + // Note that these tests do not actually touch the filesystem or care about whether or not + // any of the paths actually exist or are otherwise valid. + + // Drive Absolute + try testToPrefixedFileWithOracle("X:\\ABC\\DEF", "\\??\\X:\\ABC\\DEF"); + try testToPrefixedFileWithOracle("X:\\", "\\??\\X:\\"); + try testToPrefixedFileWithOracle("X:\\ABC\\", "\\??\\X:\\ABC\\"); + // Trailing . and space characters are stripped + try testToPrefixedFileWithOracle("X:\\ABC\\DEF. .", "\\??\\X:\\ABC\\DEF"); + try testToPrefixedFileWithOracle("X:/ABC/DEF", "\\??\\X:\\ABC\\DEF"); + try testToPrefixedFileWithOracle("X:\\ABC\\..\\XYZ", "\\??\\X:\\XYZ"); + try testToPrefixedFileWithOracle("X:\\ABC\\..\\..\\..", "\\??\\X:\\"); + // Drive letter casing is unchanged + try testToPrefixedFileWithOracle("x:\\", "\\??\\x:\\"); + + // Drive Relative + // These tests depend on the CWD of the specified drive letter which can vary, + // so instead we just test that the Zig implementation matches the result of + // RtlDosPathNameToNtPathName_U. + // TODO: Setting the =X: environment variable didn't seem to affect + // RtlDosPathNameToNtPathName_U, not sure why that is but getting that + // to work could be an avenue to making these cases environment-independent. + // All -> are examples of the result if the X drive's cwd was X:\ABC + try testToPrefixedFileOnlyOracle("X:DEF\\GHI"); // -> \??\X:\ABC\DEF\GHI + try testToPrefixedFileOnlyOracle("X:"); // -> \??\X:\ABC + try testToPrefixedFileOnlyOracle("X:DEF. ."); // -> \??\X:\ABC\DEF + try testToPrefixedFileOnlyOracle("X:ABC\\..\\XYZ"); // -> \??\X:\ABC\XYZ + try testToPrefixedFileOnlyOracle("X:ABC\\..\\..\\.."); // -> \??\X:\ + try testToPrefixedFileOnlyOracle("x:"); // -> \??\X:\ABC + + // Rooted + // These tests depend on the drive letter of the CWD which can vary, so + // instead we just test that the Zig implementation matches the result of + // RtlDosPathNameToNtPathName_U. + // TODO: Getting the CWD path, getting the drive letter from it, and using it to + // construct the expected NT paths could be an avenue to making these cases + // environment-independent and therefore able to use testToPrefixedFileWithOracle. + // All -> are examples of the result if the CWD's drive letter was X + try testToPrefixedFileOnlyOracle("\\ABC\\DEF"); // -> \??\X:\ABC\DEF + try testToPrefixedFileOnlyOracle("\\"); // -> \??\X:\ + try testToPrefixedFileOnlyOracle("\\ABC\\DEF. ."); // -> \??\X:\ABC\DEF + try testToPrefixedFileOnlyOracle("/ABC/DEF"); // -> \??\X:\ABC\DEF + try testToPrefixedFileOnlyOracle("\\ABC\\..\\XYZ"); // -> \??\X:\XYZ + try testToPrefixedFileOnlyOracle("\\ABC\\..\\..\\.."); // -> \??\X:\ + + // Relative + // These cases differ in functionality to RtlDosPathNameToNtPathName_U. + // Relative paths remain relative if they don't have enough .. components + // to error with TooManyParentDirs + try testToPrefixedFileNoOracle("ABC\\DEF", "ABC\\DEF"); + // TODO: enable this if trailing . and spaces are stripped from relative paths + //try testToPrefixedFileNoOracle("ABC\\DEF. .", "ABC\\DEF"); + try testToPrefixedFileNoOracle("ABC/DEF", "ABC\\DEF"); + try testToPrefixedFileNoOracle("./ABC/.././DEF", "DEF"); + // TooManyParentDirs, so resolved relative to the CWD + // All -> are examples of the result if the CWD was X:\ABC\DEF + try testToPrefixedFileOnlyOracle("..\\GHI"); // -> \??\X:\ABC\GHI + try testToPrefixedFileOnlyOracle("GHI\\..\\..\\.."); // -> \??\X:\ + + // UNC Absolute + try testToPrefixedFileWithOracle("\\\\server\\share\\ABC\\DEF", "\\??\\UNC\\server\\share\\ABC\\DEF"); + try testToPrefixedFileWithOracle("\\\\server", "\\??\\UNC\\server"); + try testToPrefixedFileWithOracle("\\\\server\\share", "\\??\\UNC\\server\\share"); + try testToPrefixedFileWithOracle("\\\\server\\share\\ABC. .", "\\??\\UNC\\server\\share\\ABC"); + try testToPrefixedFileWithOracle("//server/share/ABC/DEF", "\\??\\UNC\\server\\share\\ABC\\DEF"); + try testToPrefixedFileWithOracle("\\\\server\\share\\ABC\\..\\XYZ", "\\??\\UNC\\server\\share\\XYZ"); + try testToPrefixedFileWithOracle("\\\\server\\share\\ABC\\..\\..\\..", "\\??\\UNC\\server\\share"); + + // Local Device + try testToPrefixedFileWithOracle("\\\\.\\COM20", "\\??\\COM20"); + try testToPrefixedFileWithOracle("\\\\.\\pipe\\mypipe", "\\??\\pipe\\mypipe"); + try testToPrefixedFileWithOracle("\\\\.\\X:\\ABC\\DEF. .", "\\??\\X:\\ABC\\DEF"); + try testToPrefixedFileWithOracle("\\\\.\\X:/ABC/DEF", "\\??\\X:\\ABC\\DEF"); + try testToPrefixedFileWithOracle("\\\\.\\X:\\ABC\\..\\XYZ", "\\??\\X:\\XYZ"); + // Can replace the first component of the path (contrary to drive absolute and UNC absolute paths) + try testToPrefixedFileWithOracle("\\\\.\\X:\\ABC\\..\\..\\C:\\", "\\??\\C:\\"); + try testToPrefixedFileWithOracle("\\\\.\\pipe\\mypipe\\..\\notmine", "\\??\\pipe\\notmine"); + + // Special-case device names + // TODO: Enable once these are supported + // more cases to test here: https://googleprojectzero.blogspot.com/2016/02/the-definitive-guide-on-win32-to-nt.html + //try testToPrefixedFileWithOracle("COM1", "\\??\\COM1"); + // Sometimes the special-cased device names are not respected + try testToPrefixedFileWithOracle("\\\\.\\X:\\COM1", "\\??\\X:\\COM1"); + try testToPrefixedFileWithOracle("\\\\abc\\xyz\\COM1", "\\??\\UNC\\abc\\xyz\\COM1"); + + // Verbatim + // Left untouched except \\?\ is replaced by \??\ + try testToPrefixedFileWithOracle("\\\\?\\X:", "\\??\\X:"); + try testToPrefixedFileWithOracle("\\\\?\\X:\\COM1", "\\??\\X:\\COM1"); + try testToPrefixedFileWithOracle("\\\\?\\X:/ABC/DEF. .", "\\??\\X:/ABC/DEF. ."); + try testToPrefixedFileWithOracle("\\\\?\\X:\\ABC\\..\\..\\..", "\\??\\X:\\ABC\\..\\..\\.."); + // NT Namespace + // Fully unmodified + try testToPrefixedFileWithOracle("\\??\\X:", "\\??\\X:"); + try testToPrefixedFileWithOracle("\\??\\X:\\COM1", "\\??\\X:\\COM1"); + try testToPrefixedFileWithOracle("\\??\\X:/ABC/DEF. .", "\\??\\X:/ABC/DEF. ."); + try testToPrefixedFileWithOracle("\\??\\X:\\ABC\\..\\..\\..", "\\??\\X:\\ABC\\..\\..\\.."); + + // 'Fake' Verbatim + // If the prefix looks like the verbatim prefix but not all path separators in the + // prefix are backslashes, then it gets canonicalized and the prefix is dropped in favor + // of the NT prefix. + try testToPrefixedFileWithOracle("//?/C:/ABC", "\\??\\C:\\ABC"); + // 'Fake' NT + // If the prefix looks like the NT prefix but not all path separators in the prefix + // are backslashes, then it gets canonicalized and the /??/ is not dropped but + // rather treated as part of the path. In other words, the path is treated + // as a rooted path, so the final path is resolved relative to the CWD's + // drive letter. + // The -> shows an example of the result if the CWD's drive letter was X + try testToPrefixedFileOnlyOracle("/??/C:/ABC"); // -> \??\X:\??\C:\ABC + + // Root Local Device + // \\. and \\? always get converted to \??\ + try testToPrefixedFileWithOracle("\\\\.", "\\??\\"); + try testToPrefixedFileWithOracle("\\\\?", "\\??\\"); + try testToPrefixedFileWithOracle("//?", "\\??\\"); + try testToPrefixedFileWithOracle("//.", "\\??\\"); +} + +fn testRemoveDotDirs(str: []const u8, expected: []const u8) !void { + const mutable = try testing.allocator.dupe(u8, str); + defer testing.allocator.free(mutable); + const actual = mutable[0..try windows.removeDotDirsSanitized(u8, mutable)]; + try testing.expect(std.mem.eql(u8, actual, expected)); +} +fn testRemoveDotDirsError(err: anyerror, str: []const u8) !void { + const mutable = try testing.allocator.dupe(u8, str); + defer testing.allocator.free(mutable); + try testing.expectError(err, windows.removeDotDirsSanitized(u8, mutable)); +} +test "removeDotDirs" { + try testRemoveDotDirs("", ""); + try testRemoveDotDirs(".", ""); + try testRemoveDotDirs(".\\", ""); + try testRemoveDotDirs(".\\.", ""); + try testRemoveDotDirs(".\\.\\", ""); + try testRemoveDotDirs(".\\.\\.", ""); + + try testRemoveDotDirs("a", "a"); + try testRemoveDotDirs("a\\", "a\\"); + try testRemoveDotDirs("a\\b", "a\\b"); + try testRemoveDotDirs("a\\.", "a\\"); + try testRemoveDotDirs("a\\b\\.", "a\\b\\"); + try testRemoveDotDirs("a\\.\\b", "a\\b"); + + try testRemoveDotDirs(".a", ".a"); + try testRemoveDotDirs(".a\\", ".a\\"); + try testRemoveDotDirs(".a\\.b", ".a\\.b"); + try testRemoveDotDirs(".a\\.", ".a\\"); + try testRemoveDotDirs(".a\\.\\.", ".a\\"); + try testRemoveDotDirs(".a\\.\\.\\.b", ".a\\.b"); + try testRemoveDotDirs(".a\\.\\.\\.b\\", ".a\\.b\\"); + + try testRemoveDotDirsError(error.TooManyParentDirs, ".."); + try testRemoveDotDirsError(error.TooManyParentDirs, "..\\"); + try testRemoveDotDirsError(error.TooManyParentDirs, ".\\..\\"); + try testRemoveDotDirsError(error.TooManyParentDirs, ".\\.\\..\\"); + + try testRemoveDotDirs("a\\..", ""); + try testRemoveDotDirs("a\\..\\", ""); + try testRemoveDotDirs("a\\..\\.", ""); + try testRemoveDotDirs("a\\..\\.\\", ""); + try testRemoveDotDirs("a\\..\\.\\.", ""); + try testRemoveDotDirsError(error.TooManyParentDirs, "a\\..\\.\\.\\.."); + + try testRemoveDotDirs("a\\..\\.\\.\\b", "b"); + try testRemoveDotDirs("a\\..\\.\\.\\b\\", "b\\"); + try testRemoveDotDirs("a\\..\\.\\.\\b\\.", "b\\"); + try testRemoveDotDirs("a\\..\\.\\.\\b\\.\\", "b\\"); + try testRemoveDotDirs("a\\..\\.\\.\\b\\.\\..", ""); + try testRemoveDotDirs("a\\..\\.\\.\\b\\.\\..\\", ""); + try testRemoveDotDirs("a\\..\\.\\.\\b\\.\\..\\.", ""); + try testRemoveDotDirsError(error.TooManyParentDirs, "a\\..\\.\\.\\b\\.\\..\\.\\.."); + + try testRemoveDotDirs("a\\b\\..\\", "a\\"); + try testRemoveDotDirs("a\\b\\..\\c", "a\\c"); +} + +const RTL_PATH_TYPE = enum(c_int) { + Unknown, + UncAbsolute, + DriveAbsolute, + DriveRelative, + Rooted, + Relative, + LocalDevice, + RootLocalDevice, +}; + +pub extern "ntdll" fn RtlDetermineDosPathNameType_U( + Path: [*:0]const u16, +) callconv(.winapi) RTL_PATH_TYPE; + +test "getWin32PathType vs RtlDetermineDosPathNameType_U" { + if (builtin.os.tag != .windows) return error.SkipZigTest; + + var buf: std.ArrayList(u16) = .empty; + defer buf.deinit(std.testing.allocator); + + var wtf8_buf: std.ArrayList(u8) = .empty; + defer wtf8_buf.deinit(std.testing.allocator); + + var random = std.Random.DefaultPrng.init(std.testing.random_seed); + const rand = random.random(); + + for (0..1000) |_| { + buf.clearRetainingCapacity(); + const path = try getRandomWtf16Path(std.testing.allocator, &buf, rand); + wtf8_buf.clearRetainingCapacity(); + const wtf8_len = std.unicode.calcWtf8Len(path); + try wtf8_buf.ensureTotalCapacity(std.testing.allocator, wtf8_len); + wtf8_buf.items.len = wtf8_len; + std.debug.assert(std.unicode.wtf16LeToWtf8(wtf8_buf.items, path) == wtf8_len); + + const windows_type = RtlDetermineDosPathNameType_U(path); + const wtf16_type = std.fs.path.getWin32PathType(u16, path); + const wtf8_type = std.fs.path.getWin32PathType(u8, wtf8_buf.items); + + checkPathType(windows_type, wtf16_type) catch |err| { + std.debug.print("expected type {}, got {} for path: {f}\n", .{ windows_type, wtf16_type, std.unicode.fmtUtf16Le(path) }); + std.debug.print("path bytes:\n", .{}); + std.debug.dumpHex(std.mem.sliceAsBytes(path)); + return err; + }; + + if (wtf16_type != wtf8_type) { + std.debug.print("type mismatch between wtf8: {} and wtf16: {} for path: {f}\n", .{ wtf8_type, wtf16_type, std.unicode.fmtUtf16Le(path) }); + std.debug.print("wtf-16 path bytes:\n", .{}); + std.debug.dumpHex(std.mem.sliceAsBytes(path)); + std.debug.print("wtf-8 path bytes:\n", .{}); + std.debug.dumpHex(std.mem.sliceAsBytes(wtf8_buf.items)); + return error.Wtf8Wtf16Mismatch; + } + } +} + +fn checkPathType(windows_type: RTL_PATH_TYPE, zig_type: std.fs.path.Win32PathType) !void { + const expected_windows_type: RTL_PATH_TYPE = switch (zig_type) { + .unc_absolute => .UncAbsolute, + .drive_absolute => .DriveAbsolute, + .drive_relative => .DriveRelative, + .rooted => .Rooted, + .relative => .Relative, + .local_device => .LocalDevice, + .root_local_device => .RootLocalDevice, + }; + if (windows_type != expected_windows_type) return error.PathTypeMismatch; +} + +fn getRandomWtf16Path(allocator: std.mem.Allocator, buf: *std.ArrayList(u16), rand: std.Random) ![:0]const u16 { + const Choice = enum { + backslash, + slash, + control, + printable, + non_ascii, + }; + + const choices = rand.uintAtMostBiased(u16, 32); + + for (0..choices) |_| { + const choice = rand.enumValue(Choice); + const code_unit = switch (choice) { + .backslash => '\\', + .slash => '/', + .control => switch (rand.uintAtMostBiased(u8, 0x20)) { + 0x20 => '\x7F', + else => |b| b + 1, // no NUL + }, + .printable => '!' + rand.uintAtMostBiased(u8, '~' - '!'), + .non_ascii => rand.intRangeAtMostBiased(u16, 0x80, 0xFFFF), + }; + try buf.append(allocator, std.mem.nativeToLittle(u16, code_unit)); + } + + try buf.append(allocator, 0); + return buf.items[0 .. buf.items.len - 1 :0]; +} diff --git a/lib/std/dynamic_library.zig b/lib/std/dynamic_library.zig index 16a82c874bd05fdabf0c462f592e4e6d6930a4a9..d7cea3b3cd3699e4e0a4d5700b241fdc4656ac4e 100644 --- a/lib/std/dynamic_library.zig +++ b/lib/std/dynamic_library.zig @@ -17,7 +17,6 @@ pub const DynLib = struct { ElfDynLib else DlDynLib, - .windows => WindowsDynLib, .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos, .freebsd, .netbsd, .openbsd, .dragonfly, .illumos => DlDynLib, else => struct { const open = @compileError("unsupported platform"); @@ -27,7 +26,7 @@ pub const DynLib = struct { inner: InnerType, - pub const Error = ElfDynLibError || DlDynLibError || WindowsDynLibError; + pub const Error = ElfDynLibError || DlDynLibError; /// Trusts the file. Malicious file will be able to execute arbitrary code. pub fn open(path: []const u8) Error!DynLib { @@ -558,73 +557,6 @@ test "ElfDynLib" { try testing.expectError(error.FileNotFound, ElfDynLib.openZ("invalid_so.so", null)); } -/// Separated to avoid referencing `WindowsDynLib`, because its field types may not -/// be valid on other targets. -const WindowsDynLibError = error{ - FileNotFound, - InvalidPath, -} || windows.LoadLibraryError; - -pub const WindowsDynLib = struct { - pub const Error = WindowsDynLibError; - - dll: windows.HMODULE, - - pub fn open(path: []const u8) Error!WindowsDynLib { - return openEx(path, .none); - } - - /// WindowsDynLib specific - /// Opens dynamic library with specified library loading flags. - pub fn openEx(path: []const u8, flags: windows.LoadLibraryFlags) Error!WindowsDynLib { - const path_w = windows.sliceToPrefixedFileW(null, path) catch return error.InvalidPath; - return openExW(path_w.span().ptr, flags); - } - - pub fn openZ(path_c: [*:0]const u8) Error!WindowsDynLib { - return openExZ(path_c, .none); - } - - /// WindowsDynLib specific - /// Opens dynamic library with specified library loading flags. - pub fn openExZ(path_c: [*:0]const u8, flags: windows.LoadLibraryFlags) Error!WindowsDynLib { - const path_w = windows.cStrToPrefixedFileW(null, path_c) catch return error.InvalidPath; - return openExW(path_w.span().ptr, flags); - } - - /// WindowsDynLib specific - pub fn openW(path_w: [*:0]const u16) Error!WindowsDynLib { - return openExW(path_w, .none); - } - - /// WindowsDynLib specific - /// Opens dynamic library with specified library loading flags. - pub fn openExW(path_w: [*:0]const u16, flags: windows.LoadLibraryFlags) Error!WindowsDynLib { - var offset: usize = 0; - if (path_w[0] == '\\' and path_w[1] == '?' and path_w[2] == '?' and path_w[3] == '\\') { - // + 4 to skip over the \??\ - offset = 4; - } - - return .{ - .dll = try windows.LoadLibraryExW(path_w + offset, flags), - }; - } - - pub fn close(self: *WindowsDynLib) void { - windows.FreeLibrary(self.dll); - self.* = undefined; - } - - pub fn lookup(self: *WindowsDynLib, comptime T: type, name: [:0]const u8) ?T { - if (windows.kernel32.GetProcAddress(self.dll, name.ptr)) |addr| { - return @as(T, @ptrCast(@alignCast(addr))); - } else { - return null; - } - } -}; - /// Separated to avoid referencing `DlDynLib`, because its field types may not /// be valid on other targets. const DlDynLibError = error{ FileNotFound, NameTooLong }; @@ -676,7 +608,6 @@ pub const DlDynLib = struct { test "dynamic_library" { const libname = switch (native_os) { .linux, .freebsd, .openbsd, .illumos => "invalid_so.so", - .windows => "invalid_dll.dll", .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => "invalid_dylib.dylib", else => return error.SkipZigTest, }; diff --git a/lib/std/os/windows.zig b/lib/std/os/windows.zig index dcb9087f136c664b9133e03c0b71458ddd8ed9c3..ac36e77ac35d75e52fe356ba6139e2c864160039 100644 --- a/lib/std/os/windows.zig +++ b/lib/std/os/windows.zig @@ -15,12 +15,6 @@ const math = std.math; const maxInt = std.math.maxInt; const UnexpectedError = std.posix.UnexpectedError; -test { - if (builtin.os.tag == .windows) { - _ = @import("windows/test.zig"); - } -} - pub const advapi32 = @import("windows/advapi32.zig"); pub const kernel32 = @import("windows/kernel32.zig"); pub const ntdll = @import("windows/ntdll.zig"); @@ -2670,324 +2664,6 @@ pub fn CloseHandle(hObject: HANDLE) void { assert(ntdll.NtClose(hObject) == .SUCCESS); } -pub const QueryObjectNameError = error{ - AccessDenied, - InvalidHandle, - NameTooLong, - Unexpected, -}; - -pub fn QueryObjectName(handle: HANDLE, out_buffer: []u16) QueryObjectNameError![]u16 { - const out_buffer_aligned = mem.alignInSlice(out_buffer, @alignOf(OBJECT_NAME_INFORMATION)) orelse return error.NameTooLong; - - const info = @as(*OBJECT_NAME_INFORMATION, @ptrCast(out_buffer_aligned)); - // buffer size is specified in bytes - const out_buffer_len = std.math.cast(ULONG, out_buffer_aligned.len * 2) orelse maxInt(ULONG); - // last argument would return the length required for full_buffer, not exposed here - return switch (ntdll.NtQueryObject(handle, .ObjectNameInformation, info, out_buffer_len, null)) { - .SUCCESS => blk: { - // info.Name.Buffer from ObQueryNameString is documented to be null (and MaximumLength == 0) - // if the object was "unnamed", not sure if this can happen for file handles - if (info.Name.MaximumLength == 0) break :blk error.Unexpected; - // resulting string length is specified in bytes - const path_length_unterminated = @divExact(info.Name.Length, 2); - break :blk info.Name.Buffer.?[0..path_length_unterminated]; - }, - .ACCESS_DENIED => error.AccessDenied, - .INVALID_HANDLE => error.InvalidHandle, - // triggered when the buffer is too small for the OBJECT_NAME_INFORMATION object (.INFO_LENGTH_MISMATCH), - // or if the buffer is too small for the file path returned (.BUFFER_OVERFLOW, .BUFFER_TOO_SMALL) - .INFO_LENGTH_MISMATCH, .BUFFER_OVERFLOW, .BUFFER_TOO_SMALL => error.NameTooLong, - else => |e| unexpectedStatus(e), - }; -} - -test QueryObjectName { - if (builtin.os.tag != .windows) - return; - - //any file will do; canonicalization works on NTFS junctions and symlinks, hardlinks remain separate paths. - var tmp = std.testing.tmpDir(.{}); - defer tmp.cleanup(); - const handle = tmp.dir.handle; - var out_buffer: [PATH_MAX_WIDE]u16 = undefined; - - const result_path = try QueryObjectName(handle, &out_buffer); - const required_len_in_u16 = result_path.len + @divExact(@intFromPtr(result_path.ptr) - @intFromPtr(&out_buffer), 2) + 1; - //insufficient size - try std.testing.expectError(error.NameTooLong, QueryObjectName(handle, out_buffer[0 .. required_len_in_u16 - 1])); - //exactly-sufficient size - _ = try QueryObjectName(handle, out_buffer[0..required_len_in_u16]); -} - -pub const GetFinalPathNameByHandleError = error{ - AccessDenied, - FileNotFound, - NameTooLong, - /// The volume does not contain a recognized file system. File system - /// drivers might not be loaded, or the volume may be corrupt. - UnrecognizedVolume, - Unexpected, -}; - -/// Specifies how to format volume path in the result of `GetFinalPathNameByHandle`. -/// Defaults to DOS volume names. -pub const GetFinalPathNameByHandleFormat = struct { - volume_name: enum { - /// Format as DOS volume name - Dos, - /// Format as NT volume name - Nt, - } = .Dos, -}; - -/// Returns canonical (normalized) path of handle. -/// Use `GetFinalPathNameByHandleFormat` to specify whether the path is meant to include -/// NT or DOS volume name (e.g., `\Device\HarddiskVolume0\foo.txt` versus `C:\foo.txt`). -/// If DOS volume name format is selected, note that this function does *not* prepend -/// `\\?\` prefix to the resultant path. -/// -/// TODO move this function into std.Io.Threaded and add cancelation checks -pub fn GetFinalPathNameByHandle( - hFile: HANDLE, - fmt: GetFinalPathNameByHandleFormat, - out_buffer: []u16, -) GetFinalPathNameByHandleError![]u16 { - const final_path = QueryObjectName(hFile, out_buffer) catch |err| switch (err) { - // we assume InvalidHandle is close enough to FileNotFound in semantics - // to not further complicate the error set - error.InvalidHandle => return error.FileNotFound, - else => |e| return e, - }; - - switch (fmt.volume_name) { - .Nt => { - // the returned path is already in .Nt format - return final_path; - }, - .Dos => { - // parse the string to separate volume path from file path - const device_prefix = std.unicode.utf8ToUtf16LeStringLiteral("\\Device\\"); - - // We aren't entirely sure of the structure of the path returned by - // QueryObjectName in all contexts/environments. - // This code is written to cover the various cases that have - // been encountered and solved appropriately. But note that there's - // no easy way to verify that they have all been tackled! - // (Unless you, the reader knows of one then please do action that!) - if (!mem.startsWith(u16, final_path, device_prefix)) { - // Wine seems to return NT namespaced paths starting with \??\ from QueryObjectName - // (e.g. `\??\Z:\some\path\to\a\file.txt`), in which case we can just strip the - // prefix to turn it into an absolute path. - // https://github.com/ziglang/zig/issues/26029 - // https://bugs.winehq.org/show_bug.cgi?id=39569 - return ntToWin32Namespace(final_path, out_buffer) catch |err| switch (err) { - error.NotNtPath => return error.Unexpected, - error.NameTooLong => |e| return e, - }; - } - - const file_path_begin_index = mem.findPos(u16, final_path, device_prefix.len, &[_]u16{'\\'}) orelse unreachable; - const volume_name_u16 = final_path[0..file_path_begin_index]; - const device_name_u16 = volume_name_u16[device_prefix.len..]; - const file_name_u16 = final_path[file_path_begin_index..]; - - // MUP is Multiple UNC Provider, and indicates that the path is a UNC - // path. In this case, the canonical UNC path can be gotten by just - // dropping the \Device\Mup\ and making sure the path begins with \\ - if (mem.eql(u16, device_name_u16, std.unicode.utf8ToUtf16LeStringLiteral("Mup"))) { - out_buffer[0] = '\\'; - @memmove(out_buffer[1..][0..file_name_u16.len], file_name_u16); - return out_buffer[0 .. 1 + file_name_u16.len]; - } - - // Get DOS volume name. DOS volume names are actually symbolic link objects to the - // actual NT volume. For example: - // (NT) \Device\HarddiskVolume4 => (DOS) \DosDevices\C: == (DOS) C: - const MIN_SIZE = @sizeOf(MOUNTMGR_MOUNT_POINT) + MAX_PATH; - // We initialize the input buffer to all zeros for convenience since - // `DeviceIoControl` with `IOCTL_MOUNTMGR_QUERY_POINTS` expects this. - var input_buf: [MIN_SIZE]u8 align(@alignOf(MOUNTMGR_MOUNT_POINT)) = [_]u8{0} ** MIN_SIZE; - var output_buf: [MIN_SIZE * 4]u8 align(@alignOf(MOUNTMGR_MOUNT_POINTS)) = undefined; - - // This surprising path is a filesystem path to the mount manager on Windows. - // Source: https://stackoverflow.com/questions/3012828/using-ioctl-mountmgr-query-points - // This is the NT namespaced version of \\.\MountPointManager - const mgmt_path_u16 = std.unicode.utf8ToUtf16LeStringLiteral("\\??\\MountPointManager"); - const mgmt_handle = OpenFile(mgmt_path_u16, .{ - .access_mask = .{ .STANDARD = .{ .SYNCHRONIZE = true } }, - .creation = .OPEN, - }) catch |err| switch (err) { - error.IsDir => return error.Unexpected, - error.NotDir => return error.Unexpected, - error.NoDevice => return error.Unexpected, - error.AccessDenied => return error.Unexpected, - error.PipeBusy => return error.Unexpected, - error.PathAlreadyExists => return error.Unexpected, - error.WouldBlock => return error.Unexpected, - error.NetworkNotFound => return error.Unexpected, - error.AntivirusInterference => return error.Unexpected, - error.BadPathName => return error.Unexpected, - error.OperationCanceled => @panic("TODO: better integrate cancelation"), - else => |e| return e, - }; - defer CloseHandle(mgmt_handle); - - var input_struct: *MOUNTMGR_MOUNT_POINT = @ptrCast(&input_buf[0]); - input_struct.DeviceNameOffset = @sizeOf(MOUNTMGR_MOUNT_POINT); - input_struct.DeviceNameLength = @intCast(volume_name_u16.len * 2); - @memcpy(input_buf[@sizeOf(MOUNTMGR_MOUNT_POINT)..][0 .. volume_name_u16.len * 2], @as([*]const u8, @ptrCast(volume_name_u16.ptr))); - - { - const rc = DeviceIoControl(mgmt_handle, IOCTL.MOUNTMGR.QUERY_POINTS, .{ .in = &input_buf, .out = &output_buf }); - switch (rc) { - .SUCCESS => {}, - .OBJECT_NAME_NOT_FOUND => return error.FileNotFound, - else => return unexpectedStatus(rc), - } - } - const mount_points_struct: *const MOUNTMGR_MOUNT_POINTS = @ptrCast(&output_buf[0]); - - const mount_points = @as( - [*]const MOUNTMGR_MOUNT_POINT, - @ptrCast(&mount_points_struct.MountPoints[0]), - )[0..mount_points_struct.NumberOfMountPoints]; - - for (mount_points) |mount_point| { - const symlink = @as( - [*]const u16, - @ptrCast(@alignCast(&output_buf[mount_point.SymbolicLinkNameOffset])), - )[0 .. mount_point.SymbolicLinkNameLength / 2]; - - // Look for `\DosDevices\` prefix. We don't really care if there are more than one symlinks - // with traditional DOS drive letters, so pick the first one available. - var prefix_buf = std.unicode.utf8ToUtf16LeStringLiteral("\\DosDevices\\"); - const prefix = prefix_buf[0..prefix_buf.len]; - - if (mem.startsWith(u16, symlink, prefix)) { - const drive_letter = symlink[prefix.len..]; - - if (out_buffer.len < drive_letter.len + file_name_u16.len) return error.NameTooLong; - - @memcpy(out_buffer[0..drive_letter.len], drive_letter); - @memmove(out_buffer[drive_letter.len..][0..file_name_u16.len], file_name_u16); - const total_len = drive_letter.len + file_name_u16.len; - - // Validate that DOS does not contain any spurious nul bytes. - assert(mem.findScalar(u16, out_buffer[0..total_len], 0) == null); - - return out_buffer[0..total_len]; - } else if (mountmgrIsVolumeName(symlink)) { - // If the symlink is a volume GUID like \??\Volume{383da0b0-717f-41b6-8c36-00500992b58d}, - // then it is a volume mounted as a path rather than a drive letter. We need to - // query the mount manager again to get the DOS path for the volume. - - // 49 is the maximum length accepted by mountmgrIsVolumeName - const vol_input_size = @sizeOf(MOUNTMGR_TARGET_NAME) + (49 * 2); - var vol_input_buf: [vol_input_size]u8 align(@alignOf(MOUNTMGR_TARGET_NAME)) = [_]u8{0} ** vol_input_size; - // Note: If the path exceeds MAX_PATH, the Disk Management GUI doesn't accept the full path, - // and instead if must be specified using a shortened form (e.g. C:\FOO~1\BAR~1\<...>). - // However, just to be sure we can handle any path length, we use PATH_MAX_WIDE here. - const min_output_size = @sizeOf(MOUNTMGR_VOLUME_PATHS) + (PATH_MAX_WIDE * 2); - var vol_output_buf: [min_output_size]u8 align(@alignOf(MOUNTMGR_VOLUME_PATHS)) = undefined; - - var vol_input_struct: *MOUNTMGR_TARGET_NAME = @ptrCast(&vol_input_buf[0]); - vol_input_struct.DeviceNameLength = @intCast(symlink.len * 2); - @memcpy(@as([*]WCHAR, &vol_input_struct.DeviceName)[0..symlink.len], symlink); - - const rc = DeviceIoControl(mgmt_handle, IOCTL.MOUNTMGR.QUERY_DOS_VOLUME_PATH, .{ .in = &vol_input_buf, .out = &vol_output_buf }); - switch (rc) { - .SUCCESS => {}, - .UNRECOGNIZED_VOLUME => return error.UnrecognizedVolume, - else => return unexpectedStatus(rc), - } - const volume_paths_struct: *const MOUNTMGR_VOLUME_PATHS = @ptrCast(&vol_output_buf[0]); - const volume_path = std.mem.sliceTo(@as( - [*]const u16, - &volume_paths_struct.MultiSz, - )[0 .. volume_paths_struct.MultiSzLength / 2], 0); - - if (out_buffer.len < volume_path.len + file_name_u16.len) return error.NameTooLong; - - // `out_buffer` currently contains the memory of `file_name_u16`, so it can overlap with where - // we want to place the filename before returning. Here are the possible overlapping cases: - // - // out_buffer: [filename] - // dest: [___(a)___] [___(b)___] - // - // In the case of (a), we need to copy forwards, and in the case of (b) we need - // to copy backwards. We also need to do this before copying the volume path because - // it could overwrite the file_name_u16 memory. - const file_name_dest = out_buffer[volume_path.len..][0..file_name_u16.len]; - @memmove(file_name_dest, file_name_u16); - @memcpy(out_buffer[0..volume_path.len], volume_path); - const total_len = volume_path.len + file_name_u16.len; - - // Validate that DOS does not contain any spurious nul bytes. - assert(mem.findScalar(u16, out_buffer[0..total_len], 0) == null); - - return out_buffer[0..total_len]; - } - } - - // If we've ended up here, then something went wrong/is corrupted in the OS, - // so error out! - return error.FileNotFound; - }, - } -} - -/// Equivalent to the MOUNTMGR_IS_VOLUME_NAME macro in mountmgr.h -fn mountmgrIsVolumeName(name: []const u16) bool { - return (name.len == 48 or (name.len == 49 and name[48] == mem.nativeToLittle(u16, '\\'))) and - name[0] == mem.nativeToLittle(u16, '\\') and - (name[1] == mem.nativeToLittle(u16, '?') or name[1] == mem.nativeToLittle(u16, '\\')) and - name[2] == mem.nativeToLittle(u16, '?') and - name[3] == mem.nativeToLittle(u16, '\\') and - mem.startsWith(u16, name[4..], std.unicode.utf8ToUtf16LeStringLiteral("Volume{")) and - name[19] == mem.nativeToLittle(u16, '-') and - name[24] == mem.nativeToLittle(u16, '-') and - name[29] == mem.nativeToLittle(u16, '-') and - name[34] == mem.nativeToLittle(u16, '-') and - name[47] == mem.nativeToLittle(u16, '}'); -} - -test mountmgrIsVolumeName { - @setEvalBranchQuota(2000); - const L = std.unicode.utf8ToUtf16LeStringLiteral; - try std.testing.expect(mountmgrIsVolumeName(L("\\\\?\\Volume{383da0b0-717f-41b6-8c36-00500992b58d}"))); - try std.testing.expect(mountmgrIsVolumeName(L("\\??\\Volume{383da0b0-717f-41b6-8c36-00500992b58d}"))); - try std.testing.expect(mountmgrIsVolumeName(L("\\\\?\\Volume{383da0b0-717f-41b6-8c36-00500992b58d}\\"))); - try std.testing.expect(mountmgrIsVolumeName(L("\\??\\Volume{383da0b0-717f-41b6-8c36-00500992b58d}\\"))); - try std.testing.expect(!mountmgrIsVolumeName(L("\\\\.\\Volume{383da0b0-717f-41b6-8c36-00500992b58d}"))); - try std.testing.expect(!mountmgrIsVolumeName(L("\\??\\Volume{383da0b0-717f-41b6-8c36-00500992b58d}\\foo"))); - try std.testing.expect(!mountmgrIsVolumeName(L("\\??\\Volume{383da0b0-717f-41b6-8c36-00500992b58}"))); -} - -test GetFinalPathNameByHandle { - if (builtin.os.tag != .windows) - return; - - //any file will do - var tmp = std.testing.tmpDir(.{}); - defer tmp.cleanup(); - const handle = tmp.dir.handle; - var buffer: [PATH_MAX_WIDE]u16 = undefined; - - //check with sufficient size - const nt_path = try GetFinalPathNameByHandle(handle, .{ .volume_name = .Nt }, &buffer); - _ = try GetFinalPathNameByHandle(handle, .{ .volume_name = .Dos }, &buffer); - - const required_len_in_u16 = nt_path.len + @divExact(@intFromPtr(nt_path.ptr) - @intFromPtr(&buffer), 2) + 1; - //check with insufficient size - try std.testing.expectError(error.NameTooLong, GetFinalPathNameByHandle(handle, .{ .volume_name = .Nt }, buffer[0 .. required_len_in_u16 - 1])); - try std.testing.expectError(error.NameTooLong, GetFinalPathNameByHandle(handle, .{ .volume_name = .Dos }, buffer[0 .. required_len_in_u16 - 1])); - - //check with exactly-sufficient size - _ = try GetFinalPathNameByHandle(handle, .{ .volume_name = .Nt }, buffer[0..required_len_in_u16]); - _ = try GetFinalPathNameByHandle(handle, .{ .volume_name = .Dos }, buffer[0..required_len_in_u16]); -} - pub fn getpeername(s: ws2_32.SOCKET, name: *ws2_32.sockaddr, namelen: *ws2_32.socklen_t) i32 { return ws2_32.getpeername(s, name, @as(*i32, @ptrCast(namelen))); } @@ -3419,15 +3095,6 @@ test "eqlIgnoreCaseWtf16/Wtf8" { try testEqlIgnoreCase(false, "𐓏", "𐓷"); } -pub const PathSpace = struct { - data: [PATH_MAX_WIDE:0]u16, - len: usize, - - pub fn span(self: *const PathSpace) [:0]const u16 { - return self.data[0..self.len :0]; - } -}; - /// The error type for `removeDotDirsSanitized` pub const RemoveDotDirsError = error{TooManyParentDirs}; @@ -3503,205 +3170,6 @@ pub fn normalizePath(comptime T: type, path: []T) RemoveDotDirsError!usize { return prefix_len + try removeDotDirsSanitized(T, path[prefix_len..new_len]); } -pub const Wtf8ToPrefixedFileWError = Wtf16ToPrefixedFileWError; - -/// Same as `sliceToPrefixedFileW` but accepts a pointer -/// to a null-terminated WTF-8 encoded path. -/// https://wtf-8.codeberg.page/ -pub fn cStrToPrefixedFileW(dir: ?HANDLE, s: [*:0]const u8) Wtf8ToPrefixedFileWError!PathSpace { - return sliceToPrefixedFileW(dir, mem.sliceTo(s, 0)); -} - -/// Same as `wToPrefixedFileW` but accepts a WTF-8 encoded path. -/// https://wtf-8.codeberg.page/ -pub fn sliceToPrefixedFileW(dir: ?HANDLE, path: []const u8) Wtf8ToPrefixedFileWError!PathSpace { - var temp_path: PathSpace = undefined; - temp_path.len = std.unicode.wtf8ToWtf16Le(&temp_path.data, path) catch |err| switch (err) { - error.InvalidWtf8 => return error.BadPathName, - }; - temp_path.data[temp_path.len] = 0; - return wToPrefixedFileW(dir, temp_path.span()); -} - -pub const Wtf16ToPrefixedFileWError = error{ - AccessDenied, - BadPathName, - FileNotFound, - NameTooLong, - Unexpected, -}; - -/// Converts the `path` to WTF16, null-terminated. If the path contains any -/// namespace prefix, or is anything but a relative path (rooted, drive relative, -/// etc) the result will have the NT-style prefix `\??\`. -/// -/// Similar to RtlDosPathNameToNtPathName_U with a few differences: -/// - Does not allocate on the heap. -/// - Relative paths are kept as relative unless they contain too many .. -/// components, in which case they are resolved against the `dir` if it -/// is non-null, or the CWD if it is null. -/// - Special case device names like COM1, NUL, etc are not handled specially (TODO) -/// - . and space are not stripped from the end of relative paths (potential TODO) -pub fn wToPrefixedFileW(dir: ?HANDLE, path: [:0]const u16) Wtf16ToPrefixedFileWError!PathSpace { - const nt_prefix = [_]u16{ '\\', '?', '?', '\\' }; - if (hasCommonNtPrefix(u16, path)) { - // TODO: Figure out a way to design an API that can avoid the copy for NT, - // since it is always returned fully unmodified. - var path_space: PathSpace = undefined; - path_space.data[0..nt_prefix.len].* = nt_prefix; - const len_after_prefix = path.len - nt_prefix.len; - @memcpy(path_space.data[nt_prefix.len..][0..len_after_prefix], path[nt_prefix.len..]); - path_space.len = path.len; - path_space.data[path_space.len] = 0; - return path_space; - } else { - const path_type = std.fs.path.getWin32PathType(u16, path); - var path_space: PathSpace = undefined; - if (path_type == .local_device) { - switch (getLocalDevicePathType(u16, path)) { - .verbatim => { - path_space.data[0..nt_prefix.len].* = nt_prefix; - const len_after_prefix = path.len - nt_prefix.len; - @memcpy(path_space.data[nt_prefix.len..][0..len_after_prefix], path[nt_prefix.len..]); - path_space.len = path.len; - path_space.data[path_space.len] = 0; - return path_space; - }, - .local_device, .fake_verbatim => { - const path_byte_len = ntdll.RtlGetFullPathName_U( - path.ptr, - path_space.data.len * 2, - &path_space.data, - null, - ); - if (path_byte_len == 0) { - // TODO: This may not be the right error - return error.BadPathName; - } else if (path_byte_len / 2 > path_space.data.len) { - return error.NameTooLong; - } - path_space.len = path_byte_len / 2; - // Both prefixes will be normalized but retained, so all - // we need to do now is replace them with the NT prefix - path_space.data[0..nt_prefix.len].* = nt_prefix; - return path_space; - }, - } - } - relative: { - if (path_type == .relative) { - // TODO: Handle special case device names like COM1, AUX, NUL, CONIN$, CONOUT$, etc. - // See https://googleprojectzero.blogspot.com/2016/02/the-definitive-guide-on-win32-to-nt.html - - // TODO: Potentially strip all trailing . and space characters from the - // end of the path. This is something that both RtlDosPathNameToNtPathName_U - // and RtlGetFullPathName_U do. Technically, trailing . and spaces - // are allowed, but such paths may not interact well with Windows (i.e. - // files with these paths can't be deleted from explorer.exe, etc). - // This could be something that normalizePath may want to do. - - @memcpy(path_space.data[0..path.len], path); - // Try to normalize, but if we get too many parent directories, - // then we need to start over and use RtlGetFullPathName_U instead. - path_space.len = normalizePath(u16, path_space.data[0..path.len]) catch |err| switch (err) { - error.TooManyParentDirs => break :relative, - }; - path_space.data[path_space.len] = 0; - return path_space; - } - } - // We now know we are going to return an absolute NT path, so - // we can unconditionally prefix it with the NT prefix. - path_space.data[0..nt_prefix.len].* = nt_prefix; - if (path_type == .root_local_device) { - // `\\.` and `\\?` always get converted to `\??\` exactly, so - // we can just stop here - path_space.len = nt_prefix.len; - path_space.data[path_space.len] = 0; - return path_space; - } - const path_buf_offset = switch (path_type) { - // UNC paths will always start with `\\`. However, we want to - // end up with something like `\??\UNC\server\share`, so to get - // RtlGetFullPathName to write into the spot we want the `server` - // part to end up, we need to provide an offset such that - // the `\\` part gets written where the `C\` of `UNC\` will be - // in the final NT path. - .unc_absolute => nt_prefix.len + 2, - else => nt_prefix.len, - }; - const buf_len: u32 = @intCast(path_space.data.len - path_buf_offset); - const path_to_get: [:0]const u16 = path_to_get: { - // If dir is null, then we don't need to bother with GetFinalPathNameByHandle because - // RtlGetFullPathName_U will resolve relative paths against the CWD for us. - if (path_type != .relative or dir == null) { - break :path_to_get path; - } - // We can also skip GetFinalPathNameByHandle if the handle matches - // the handle returned by Io.Dir.cwd() - if (dir.? == Io.Dir.cwd().handle) { - break :path_to_get path; - } - // At this point, we know we have a relative path that had too many - // `..` components to be resolved by normalizePath, so we need to - // convert it into an absolute path and let RtlGetFullPathName_U - // canonicalize it. We do this by getting the path of the `dir` - // and appending the relative path to it. - var dir_path_buf: [PATH_MAX_WIDE:0]u16 = undefined; - const dir_path = GetFinalPathNameByHandle(dir.?, .{}, &dir_path_buf) catch |err| switch (err) { - // This mapping is not correct; it is actually expected - // that calling GetFinalPathNameByHandle might return - // error.UnrecognizedVolume, and in fact has been observed - // in the wild. The problem is that wToPrefixedFileW was - // never intended to make *any* OS syscall APIs. It's only - // supposed to convert a string to one that is eligible to - // be used in the ntdll syscalls. - // - // To solve this, this function needs to no longer call - // GetFinalPathNameByHandle under any conditions, or the - // calling function needs to get reworked to not need to - // call this function. - // - // This may involve making breaking API changes. - error.UnrecognizedVolume => return error.Unexpected, - else => |e| return e, - }; - if (dir_path.len + 1 + path.len > PATH_MAX_WIDE) { - return error.NameTooLong; - } - // We don't have to worry about potentially doubling up path separators - // here since RtlGetFullPathName_U will handle canonicalizing it. - dir_path_buf[dir_path.len] = '\\'; - @memcpy(dir_path_buf[dir_path.len + 1 ..][0..path.len], path); - const full_len = dir_path.len + 1 + path.len; - dir_path_buf[full_len] = 0; - break :path_to_get dir_path_buf[0..full_len :0]; - }; - const path_byte_len = ntdll.RtlGetFullPathName_U( - path_to_get.ptr, - buf_len * 2, - path_space.data[path_buf_offset..].ptr, - null, - ); - if (path_byte_len == 0) { - // TODO: This may not be the right error - return error.BadPathName; - } else if (path_byte_len / 2 > buf_len) { - return error.NameTooLong; - } - path_space.len = path_buf_offset + (path_byte_len / 2); - if (path_type == .unc_absolute) { - // Now add in the UNC, the `C` should overwrite the first `\` of the - // FullPathName, ultimately resulting in `\??\UNC\` - assert(path_space.data[path_buf_offset] == '\\'); - assert(path_space.data[path_buf_offset + 1] == '\\'); - const unc = [_]u16{ 'U', 'N', 'C' }; - path_space.data[nt_prefix.len..][0..unc.len].* = unc; - } - return path_space; - } -} - /// Returns true if the path starts with `\??\`, which is indicative of an NT path /// but is not enough to fully distinguish between NT paths and Win32 paths, as /// `\??\` is not actually a distinct prefix but rather the path to a special virtual @@ -3725,39 +3193,6 @@ pub fn hasCommonNtPrefix(comptime T: type, path: []const T) bool { return mem.startsWith(T, path, expected_prefix); } -const LocalDevicePathType = enum { - /// `\\.\` (path separators can be `\` or `/`) - local_device, - /// `\\?\` - /// When converted to an NT path, everything past the prefix is left - /// untouched and `\\?\` is replaced by `\??\`. - verbatim, - /// `\\?\` without all path separators being `\`. - /// This seems to be recognized as a prefix, but the 'verbatim' aspect - /// is not respected (i.e. if `//?/C:/foo` is converted to an NT path, - /// it will become `\??\C:\foo` [it will be canonicalized and the //?/ won't - /// be treated as part of the final path]) - fake_verbatim, -}; - -/// Only relevant for Win32 -> NT path conversion. -/// Asserts `path` is of type `std.fs.path.Win32PathType.local_device`. -fn getLocalDevicePathType(comptime T: type, path: []const T) LocalDevicePathType { - if (std.debug.runtime_safety) { - assert(std.fs.path.getWin32PathType(T, path) == .local_device); - } - - const backslash = mem.nativeToLittle(T, '\\'); - const all_backslash = path[0] == backslash and - path[1] == backslash and - path[3] == backslash; - return switch (path[2]) { - mem.nativeToLittle(T, '?') => if (all_backslash) .verbatim else .fake_verbatim, - mem.nativeToLittle(T, '.') => .local_device, - else => unreachable, - }; -} - /// Similar to `RtlNtPathNameToDosPathName` but does not do any heap allocation. /// The possible transformations are: /// \??\C:\Some\Path -> C:\Some\Path diff --git a/lib/std/os/windows/test.zig b/lib/std/os/windows/test.zig deleted file mode 100644 index 58eb4e0b97645bf59699d0b3939a4deb1bda0d73..0000000000000000000000000000000000000000 --- a/lib/std/os/windows/test.zig +++ /dev/null @@ -1,339 +0,0 @@ -const std = @import("../../std.zig"); -const builtin = @import("builtin"); -const windows = std.os.windows; -const mem = std.mem; -const testing = std.testing; - -/// Wrapper around RtlDosPathNameToNtPathName_U for use in comparing -/// the behavior of RtlDosPathNameToNtPathName_U with wToPrefixedFileW -/// Note: RtlDosPathNameToNtPathName_U is not used in the Zig implementation -// because it allocates. -fn RtlDosPathNameToNtPathName_U(path: [:0]const u16) !windows.PathSpace { - var out: windows.UNICODE_STRING = undefined; - const rc = windows.ntdll.RtlDosPathNameToNtPathName_U(path, &out, null, null); - if (rc != windows.TRUE) return error.BadPathName; - defer windows.ntdll.RtlFreeUnicodeString(&out); - - var path_space: windows.PathSpace = undefined; - const out_path = out.Buffer.?[0 .. out.Length / 2]; - @memcpy(path_space.data[0..out_path.len], out_path); - path_space.len = out.Length / 2; - path_space.data[path_space.len] = 0; - - return path_space; -} - -/// Test that the Zig conversion matches the expected_path (for instances where -/// the Zig implementation intentionally diverges from what RtlDosPathNameToNtPathName_U does). -fn testToPrefixedFileNoOracle(comptime path: []const u8, comptime expected_path: []const u8) !void { - const path_utf16 = std.unicode.utf8ToUtf16LeStringLiteral(path); - const expected_path_utf16 = std.unicode.utf8ToUtf16LeStringLiteral(expected_path); - const actual_path = try windows.wToPrefixedFileW(null, path_utf16); - std.testing.expectEqualSlices(u16, expected_path_utf16, actual_path.span()) catch |e| { - std.debug.print("got '{f}', expected '{f}'\n", .{ std.unicode.fmtUtf16Le(actual_path.span()), std.unicode.fmtUtf16Le(expected_path_utf16) }); - return e; - }; -} - -/// Test that the Zig conversion matches the expected_path and that the -/// expected_path matches the conversion that RtlDosPathNameToNtPathName_U does. -fn testToPrefixedFileWithOracle(comptime path: []const u8, comptime expected_path: []const u8) !void { - try testToPrefixedFileNoOracle(path, expected_path); - try testToPrefixedFileOnlyOracle(path); -} - -/// Test that the Zig conversion matches the conversion that RtlDosPathNameToNtPathName_U does. -fn testToPrefixedFileOnlyOracle(comptime path: []const u8) !void { - const path_utf16 = std.unicode.utf8ToUtf16LeStringLiteral(path); - const zig_result = try windows.wToPrefixedFileW(null, path_utf16); - const win32_api_result = try RtlDosPathNameToNtPathName_U(path_utf16); - std.testing.expectEqualSlices(u16, win32_api_result.span(), zig_result.span()) catch |e| { - std.debug.print("got '{f}', expected '{f}'\n", .{ std.unicode.fmtUtf16Le(zig_result.span()), std.unicode.fmtUtf16Le(win32_api_result.span()) }); - return e; - }; -} - -test "toPrefixedFileW" { - if (builtin.os.tag != .windows) return error.SkipZigTest; - - // Most test cases come from https://googleprojectzero.blogspot.com/2016/02/the-definitive-guide-on-win32-to-nt.html - // Note that these tests do not actually touch the filesystem or care about whether or not - // any of the paths actually exist or are otherwise valid. - - // Drive Absolute - try testToPrefixedFileWithOracle("X:\\ABC\\DEF", "\\??\\X:\\ABC\\DEF"); - try testToPrefixedFileWithOracle("X:\\", "\\??\\X:\\"); - try testToPrefixedFileWithOracle("X:\\ABC\\", "\\??\\X:\\ABC\\"); - // Trailing . and space characters are stripped - try testToPrefixedFileWithOracle("X:\\ABC\\DEF. .", "\\??\\X:\\ABC\\DEF"); - try testToPrefixedFileWithOracle("X:/ABC/DEF", "\\??\\X:\\ABC\\DEF"); - try testToPrefixedFileWithOracle("X:\\ABC\\..\\XYZ", "\\??\\X:\\XYZ"); - try testToPrefixedFileWithOracle("X:\\ABC\\..\\..\\..", "\\??\\X:\\"); - // Drive letter casing is unchanged - try testToPrefixedFileWithOracle("x:\\", "\\??\\x:\\"); - - // Drive Relative - // These tests depend on the CWD of the specified drive letter which can vary, - // so instead we just test that the Zig implementation matches the result of - // RtlDosPathNameToNtPathName_U. - // TODO: Setting the =X: environment variable didn't seem to affect - // RtlDosPathNameToNtPathName_U, not sure why that is but getting that - // to work could be an avenue to making these cases environment-independent. - // All -> are examples of the result if the X drive's cwd was X:\ABC - try testToPrefixedFileOnlyOracle("X:DEF\\GHI"); // -> \??\X:\ABC\DEF\GHI - try testToPrefixedFileOnlyOracle("X:"); // -> \??\X:\ABC - try testToPrefixedFileOnlyOracle("X:DEF. ."); // -> \??\X:\ABC\DEF - try testToPrefixedFileOnlyOracle("X:ABC\\..\\XYZ"); // -> \??\X:\ABC\XYZ - try testToPrefixedFileOnlyOracle("X:ABC\\..\\..\\.."); // -> \??\X:\ - try testToPrefixedFileOnlyOracle("x:"); // -> \??\X:\ABC - - // Rooted - // These tests depend on the drive letter of the CWD which can vary, so - // instead we just test that the Zig implementation matches the result of - // RtlDosPathNameToNtPathName_U. - // TODO: Getting the CWD path, getting the drive letter from it, and using it to - // construct the expected NT paths could be an avenue to making these cases - // environment-independent and therefore able to use testToPrefixedFileWithOracle. - // All -> are examples of the result if the CWD's drive letter was X - try testToPrefixedFileOnlyOracle("\\ABC\\DEF"); // -> \??\X:\ABC\DEF - try testToPrefixedFileOnlyOracle("\\"); // -> \??\X:\ - try testToPrefixedFileOnlyOracle("\\ABC\\DEF. ."); // -> \??\X:\ABC\DEF - try testToPrefixedFileOnlyOracle("/ABC/DEF"); // -> \??\X:\ABC\DEF - try testToPrefixedFileOnlyOracle("\\ABC\\..\\XYZ"); // -> \??\X:\XYZ - try testToPrefixedFileOnlyOracle("\\ABC\\..\\..\\.."); // -> \??\X:\ - - // Relative - // These cases differ in functionality to RtlDosPathNameToNtPathName_U. - // Relative paths remain relative if they don't have enough .. components - // to error with TooManyParentDirs - try testToPrefixedFileNoOracle("ABC\\DEF", "ABC\\DEF"); - // TODO: enable this if trailing . and spaces are stripped from relative paths - //try testToPrefixedFileNoOracle("ABC\\DEF. .", "ABC\\DEF"); - try testToPrefixedFileNoOracle("ABC/DEF", "ABC\\DEF"); - try testToPrefixedFileNoOracle("./ABC/.././DEF", "DEF"); - // TooManyParentDirs, so resolved relative to the CWD - // All -> are examples of the result if the CWD was X:\ABC\DEF - try testToPrefixedFileOnlyOracle("..\\GHI"); // -> \??\X:\ABC\GHI - try testToPrefixedFileOnlyOracle("GHI\\..\\..\\.."); // -> \??\X:\ - - // UNC Absolute - try testToPrefixedFileWithOracle("\\\\server\\share\\ABC\\DEF", "\\??\\UNC\\server\\share\\ABC\\DEF"); - try testToPrefixedFileWithOracle("\\\\server", "\\??\\UNC\\server"); - try testToPrefixedFileWithOracle("\\\\server\\share", "\\??\\UNC\\server\\share"); - try testToPrefixedFileWithOracle("\\\\server\\share\\ABC. .", "\\??\\UNC\\server\\share\\ABC"); - try testToPrefixedFileWithOracle("//server/share/ABC/DEF", "\\??\\UNC\\server\\share\\ABC\\DEF"); - try testToPrefixedFileWithOracle("\\\\server\\share\\ABC\\..\\XYZ", "\\??\\UNC\\server\\share\\XYZ"); - try testToPrefixedFileWithOracle("\\\\server\\share\\ABC\\..\\..\\..", "\\??\\UNC\\server\\share"); - - // Local Device - try testToPrefixedFileWithOracle("\\\\.\\COM20", "\\??\\COM20"); - try testToPrefixedFileWithOracle("\\\\.\\pipe\\mypipe", "\\??\\pipe\\mypipe"); - try testToPrefixedFileWithOracle("\\\\.\\X:\\ABC\\DEF. .", "\\??\\X:\\ABC\\DEF"); - try testToPrefixedFileWithOracle("\\\\.\\X:/ABC/DEF", "\\??\\X:\\ABC\\DEF"); - try testToPrefixedFileWithOracle("\\\\.\\X:\\ABC\\..\\XYZ", "\\??\\X:\\XYZ"); - // Can replace the first component of the path (contrary to drive absolute and UNC absolute paths) - try testToPrefixedFileWithOracle("\\\\.\\X:\\ABC\\..\\..\\C:\\", "\\??\\C:\\"); - try testToPrefixedFileWithOracle("\\\\.\\pipe\\mypipe\\..\\notmine", "\\??\\pipe\\notmine"); - - // Special-case device names - // TODO: Enable once these are supported - // more cases to test here: https://googleprojectzero.blogspot.com/2016/02/the-definitive-guide-on-win32-to-nt.html - //try testToPrefixedFileWithOracle("COM1", "\\??\\COM1"); - // Sometimes the special-cased device names are not respected - try testToPrefixedFileWithOracle("\\\\.\\X:\\COM1", "\\??\\X:\\COM1"); - try testToPrefixedFileWithOracle("\\\\abc\\xyz\\COM1", "\\??\\UNC\\abc\\xyz\\COM1"); - - // Verbatim - // Left untouched except \\?\ is replaced by \??\ - try testToPrefixedFileWithOracle("\\\\?\\X:", "\\??\\X:"); - try testToPrefixedFileWithOracle("\\\\?\\X:\\COM1", "\\??\\X:\\COM1"); - try testToPrefixedFileWithOracle("\\\\?\\X:/ABC/DEF. .", "\\??\\X:/ABC/DEF. ."); - try testToPrefixedFileWithOracle("\\\\?\\X:\\ABC\\..\\..\\..", "\\??\\X:\\ABC\\..\\..\\.."); - // NT Namespace - // Fully unmodified - try testToPrefixedFileWithOracle("\\??\\X:", "\\??\\X:"); - try testToPrefixedFileWithOracle("\\??\\X:\\COM1", "\\??\\X:\\COM1"); - try testToPrefixedFileWithOracle("\\??\\X:/ABC/DEF. .", "\\??\\X:/ABC/DEF. ."); - try testToPrefixedFileWithOracle("\\??\\X:\\ABC\\..\\..\\..", "\\??\\X:\\ABC\\..\\..\\.."); - - // 'Fake' Verbatim - // If the prefix looks like the verbatim prefix but not all path separators in the - // prefix are backslashes, then it gets canonicalized and the prefix is dropped in favor - // of the NT prefix. - try testToPrefixedFileWithOracle("//?/C:/ABC", "\\??\\C:\\ABC"); - // 'Fake' NT - // If the prefix looks like the NT prefix but not all path separators in the prefix - // are backslashes, then it gets canonicalized and the /??/ is not dropped but - // rather treated as part of the path. In other words, the path is treated - // as a rooted path, so the final path is resolved relative to the CWD's - // drive letter. - // The -> shows an example of the result if the CWD's drive letter was X - try testToPrefixedFileOnlyOracle("/??/C:/ABC"); // -> \??\X:\??\C:\ABC - - // Root Local Device - // \\. and \\? always get converted to \??\ - try testToPrefixedFileWithOracle("\\\\.", "\\??\\"); - try testToPrefixedFileWithOracle("\\\\?", "\\??\\"); - try testToPrefixedFileWithOracle("//?", "\\??\\"); - try testToPrefixedFileWithOracle("//.", "\\??\\"); -} - -fn testRemoveDotDirs(str: []const u8, expected: []const u8) !void { - const mutable = try testing.allocator.dupe(u8, str); - defer testing.allocator.free(mutable); - const actual = mutable[0..try windows.removeDotDirsSanitized(u8, mutable)]; - try testing.expect(mem.eql(u8, actual, expected)); -} -fn testRemoveDotDirsError(err: anyerror, str: []const u8) !void { - const mutable = try testing.allocator.dupe(u8, str); - defer testing.allocator.free(mutable); - try testing.expectError(err, windows.removeDotDirsSanitized(u8, mutable)); -} -test "removeDotDirs" { - try testRemoveDotDirs("", ""); - try testRemoveDotDirs(".", ""); - try testRemoveDotDirs(".\\", ""); - try testRemoveDotDirs(".\\.", ""); - try testRemoveDotDirs(".\\.\\", ""); - try testRemoveDotDirs(".\\.\\.", ""); - - try testRemoveDotDirs("a", "a"); - try testRemoveDotDirs("a\\", "a\\"); - try testRemoveDotDirs("a\\b", "a\\b"); - try testRemoveDotDirs("a\\.", "a\\"); - try testRemoveDotDirs("a\\b\\.", "a\\b\\"); - try testRemoveDotDirs("a\\.\\b", "a\\b"); - - try testRemoveDotDirs(".a", ".a"); - try testRemoveDotDirs(".a\\", ".a\\"); - try testRemoveDotDirs(".a\\.b", ".a\\.b"); - try testRemoveDotDirs(".a\\.", ".a\\"); - try testRemoveDotDirs(".a\\.\\.", ".a\\"); - try testRemoveDotDirs(".a\\.\\.\\.b", ".a\\.b"); - try testRemoveDotDirs(".a\\.\\.\\.b\\", ".a\\.b\\"); - - try testRemoveDotDirsError(error.TooManyParentDirs, ".."); - try testRemoveDotDirsError(error.TooManyParentDirs, "..\\"); - try testRemoveDotDirsError(error.TooManyParentDirs, ".\\..\\"); - try testRemoveDotDirsError(error.TooManyParentDirs, ".\\.\\..\\"); - - try testRemoveDotDirs("a\\..", ""); - try testRemoveDotDirs("a\\..\\", ""); - try testRemoveDotDirs("a\\..\\.", ""); - try testRemoveDotDirs("a\\..\\.\\", ""); - try testRemoveDotDirs("a\\..\\.\\.", ""); - try testRemoveDotDirsError(error.TooManyParentDirs, "a\\..\\.\\.\\.."); - - try testRemoveDotDirs("a\\..\\.\\.\\b", "b"); - try testRemoveDotDirs("a\\..\\.\\.\\b\\", "b\\"); - try testRemoveDotDirs("a\\..\\.\\.\\b\\.", "b\\"); - try testRemoveDotDirs("a\\..\\.\\.\\b\\.\\", "b\\"); - try testRemoveDotDirs("a\\..\\.\\.\\b\\.\\..", ""); - try testRemoveDotDirs("a\\..\\.\\.\\b\\.\\..\\", ""); - try testRemoveDotDirs("a\\..\\.\\.\\b\\.\\..\\.", ""); - try testRemoveDotDirsError(error.TooManyParentDirs, "a\\..\\.\\.\\b\\.\\..\\.\\.."); - - try testRemoveDotDirs("a\\b\\..\\", "a\\"); - try testRemoveDotDirs("a\\b\\..\\c", "a\\c"); -} - -const RTL_PATH_TYPE = enum(c_int) { - Unknown, - UncAbsolute, - DriveAbsolute, - DriveRelative, - Rooted, - Relative, - LocalDevice, - RootLocalDevice, -}; - -pub extern "ntdll" fn RtlDetermineDosPathNameType_U( - Path: [*:0]const u16, -) callconv(.winapi) RTL_PATH_TYPE; - -test "getWin32PathType vs RtlDetermineDosPathNameType_U" { - if (builtin.os.tag != .windows) return error.SkipZigTest; - - var buf: std.ArrayList(u16) = .empty; - defer buf.deinit(std.testing.allocator); - - var wtf8_buf: std.ArrayList(u8) = .empty; - defer wtf8_buf.deinit(std.testing.allocator); - - var random = std.Random.DefaultPrng.init(std.testing.random_seed); - const rand = random.random(); - - for (0..1000) |_| { - buf.clearRetainingCapacity(); - const path = try getRandomWtf16Path(std.testing.allocator, &buf, rand); - wtf8_buf.clearRetainingCapacity(); - const wtf8_len = std.unicode.calcWtf8Len(path); - try wtf8_buf.ensureTotalCapacity(std.testing.allocator, wtf8_len); - wtf8_buf.items.len = wtf8_len; - std.debug.assert(std.unicode.wtf16LeToWtf8(wtf8_buf.items, path) == wtf8_len); - - const windows_type = RtlDetermineDosPathNameType_U(path); - const wtf16_type = std.fs.path.getWin32PathType(u16, path); - const wtf8_type = std.fs.path.getWin32PathType(u8, wtf8_buf.items); - - checkPathType(windows_type, wtf16_type) catch |err| { - std.debug.print("expected type {}, got {} for path: {f}\n", .{ windows_type, wtf16_type, std.unicode.fmtUtf16Le(path) }); - std.debug.print("path bytes:\n", .{}); - std.debug.dumpHex(std.mem.sliceAsBytes(path)); - return err; - }; - - if (wtf16_type != wtf8_type) { - std.debug.print("type mismatch between wtf8: {} and wtf16: {} for path: {f}\n", .{ wtf8_type, wtf16_type, std.unicode.fmtUtf16Le(path) }); - std.debug.print("wtf-16 path bytes:\n", .{}); - std.debug.dumpHex(std.mem.sliceAsBytes(path)); - std.debug.print("wtf-8 path bytes:\n", .{}); - std.debug.dumpHex(std.mem.sliceAsBytes(wtf8_buf.items)); - return error.Wtf8Wtf16Mismatch; - } - } -} - -fn checkPathType(windows_type: RTL_PATH_TYPE, zig_type: std.fs.path.Win32PathType) !void { - const expected_windows_type: RTL_PATH_TYPE = switch (zig_type) { - .unc_absolute => .UncAbsolute, - .drive_absolute => .DriveAbsolute, - .drive_relative => .DriveRelative, - .rooted => .Rooted, - .relative => .Relative, - .local_device => .LocalDevice, - .root_local_device => .RootLocalDevice, - }; - if (windows_type != expected_windows_type) return error.PathTypeMismatch; -} - -fn getRandomWtf16Path(allocator: std.mem.Allocator, buf: *std.ArrayList(u16), rand: std.Random) ![:0]const u16 { - const Choice = enum { - backslash, - slash, - control, - printable, - non_ascii, - }; - - const choices = rand.uintAtMostBiased(u16, 32); - - for (0..choices) |_| { - const choice = rand.enumValue(Choice); - const code_unit = switch (choice) { - .backslash => '\\', - .slash => '/', - .control => switch (rand.uintAtMostBiased(u8, 0x20)) { - 0x20 => '\x7F', - else => |b| b + 1, // no NUL - }, - .printable => '!' + rand.uintAtMostBiased(u8, '~' - '!'), - .non_ascii => rand.intRangeAtMostBiased(u16, 0x80, 0xFFFF), - }; - try buf.append(allocator, std.mem.nativeToLittle(u16, code_unit)); - } - - try buf.append(allocator, 0); - return buf.items[0 .. buf.items.len - 1 :0]; -} diff --git a/lib/std/zig/parser_test.zig b/lib/std/zig/parser_test.zig index 1f1195633006e461cb352b161f0dcebf12f700ad..46aa82f10fb5b7fe42f13b6c7b3c31a4cbf5dd79 100644 --- a/lib/std/zig/parser_test.zig +++ b/lib/std/zig/parser_test.zig @@ -639,7 +639,7 @@ test "zig fmt: array types last token" { test "zig fmt: sentinel-terminated array type" { try testCanonical( - \\pub fn cStrToPrefixedFileW(s: [*:0]const u8) ![PATH_MAX_WIDE:0]u16 { + \\pub fn foobar(s: [*:0]const u8) ![PATH_MAX_WIDE:0]u16 { \\ return sliceToPrefixedFileW(mem.toSliceConst(u8, s)); \\} \\ diff --git a/test/standalone/load_dynamic_library/build.zig b/test/standalone/load_dynamic_library/build.zig index 305ffde56506cf97cd0465d1af85f8680b7445b7..2160b2b3e474a558725d9f2c39eb98f7a1be9d8a 100644 --- a/test/standalone/load_dynamic_library/build.zig +++ b/test/standalone/load_dynamic_library/build.zig @@ -9,6 +9,7 @@ pub fn build(b: *std.Build) void { const target = b.graph.host; if (builtin.os.tag == .wasi) return; + if (builtin.os.tag == .windows) return; const lib = b.addLibrary(.{ .linkage = .dynamic, -- 2.54.0 From 3a5fff45ec04f4b713acae51a38510c316e42445 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Tue, 3 Feb 2026 21:27:27 -0800 Subject: [PATCH 201/499] std: move os.windows.OpenFile into Io.Threaded it needs cancelation integration --- lib/std/Io/Threaded.zig | 345 ++++++++++++++++++++++++++-------------- lib/std/os/windows.zig | 117 -------------- 2 files changed, 228 insertions(+), 234 deletions(-) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 1c171d7321b8575d01a82fcbe712078a662c8ee6..ac9b4fd7f46e17c10d4ec9e0b18fd79e7dd08739 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -3260,30 +3260,23 @@ fn dirCreateDirWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, pe const sub_path_w = try sliceToPrefixedFileW(dir.handle, sub_path); _ = permissions; // TODO use this value - const syscall: Syscall = try .start(); - const sub_dir_handle = while (true) { - break windows.OpenFile(sub_path_w.span(), .{ - .dir = dir.handle, - .access_mask = .{ - .GENERIC = .{ .READ = true }, - .STANDARD = .{ .SYNCHRONIZE = true }, - }, - .creation = .CREATE, - .filter = .dir_only, - }) catch |err| switch (err) { - error.IsDir => return syscall.fail(error.Unexpected), - error.PipeBusy => return syscall.fail(error.Unexpected), - error.NoDevice => return syscall.fail(error.Unexpected), - error.WouldBlock => return syscall.fail(error.Unexpected), - error.AntivirusInterference => return syscall.fail(error.Unexpected), - error.OperationCanceled => { - try syscall.checkCancel(); - continue; - }, - else => |e| return syscall.fail(e), - }; + const sub_dir_handle = OpenFile(sub_path_w.span(), .{ + .dir = dir.handle, + .access_mask = .{ + .GENERIC = .{ .READ = true }, + .STANDARD = .{ .SYNCHRONIZE = true }, + }, + .creation = .CREATE, + .filter = .dir_only, + }) catch |err| switch (err) { + error.IsDir => return error.Unexpected, + error.PipeBusy => return error.Unexpected, + error.FileBusy => return error.Unexpected, + error.NoDevice => return error.Unexpected, + error.WouldBlock => return error.Unexpected, + error.AntivirusInterference => return error.Unexpected, + else => |e| return e, }; - syscall.finish(); windows.CloseHandle(sub_dir_handle); } @@ -5987,27 +5980,19 @@ fn dirRealPathFileWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, var path_name_w = try sliceToPrefixedFileW(dir.handle, sub_path); const h_file = handle: { - const syscall: Syscall = try .start(); - while (true) { - if (windows.OpenFile(path_name_w.span(), .{ - .dir = dir.handle, - .access_mask = .{ - .GENERIC = .{ .READ = true }, - .STANDARD = .{ .SYNCHRONIZE = true }, - }, - .creation = .OPEN, - .filter = .any, - })) |handle| { - syscall.finish(); - break :handle handle; - } else |err| switch (err) { - error.WouldBlock => unreachable, - error.OperationCanceled => { - try syscall.checkCancel(); - continue; - }, - else => |e| return syscall.fail(e), - } + if (OpenFile(path_name_w.span(), .{ + .dir = dir.handle, + .access_mask = .{ + .GENERIC = .{ .READ = true }, + .STANDARD = .{ .SYNCHRONIZE = true }, + }, + .creation = .OPEN, + .filter = .any, + })) |handle| { + break :handle handle; + } else |err| switch (err) { + error.WouldBlock => unreachable, + else => |e| return e, } }; defer windows.CloseHandle(h_file); @@ -6116,7 +6101,7 @@ pub fn GetFinalPathNameByHandle( // Source: https://stackoverflow.com/questions/3012828/using-ioctl-mountmgr-query-points // This is the NT namespaced version of \\.\MountPointManager const mgmt_path_u16 = std.unicode.utf8ToUtf16LeStringLiteral("\\??\\MountPointManager"); - const mgmt_handle = windows.OpenFile(mgmt_path_u16, .{ + const mgmt_handle = OpenFile(mgmt_path_u16, .{ .access_mask = .{ .STANDARD = .{ .SYNCHRONIZE = true } }, .creation = .OPEN, }) catch |err| switch (err) { @@ -6125,12 +6110,12 @@ pub fn GetFinalPathNameByHandle( error.NoDevice => return error.Unexpected, error.AccessDenied => return error.Unexpected, error.PipeBusy => return error.Unexpected, + error.FileBusy => return error.Unexpected, error.PathAlreadyExists => return error.Unexpected, error.WouldBlock => return error.Unexpected, error.NetworkNotFound => return error.Unexpected, error.AntivirusInterference => return error.Unexpected, error.BadPathName => return error.Unexpected, - error.OperationCanceled => @panic("TODO: better integrate cancelation"), else => |e| return e, }; defer windows.CloseHandle(mgmt_handle); @@ -7309,31 +7294,23 @@ fn dirRenameWindowsInner( const new_path_w = new_path_w_buf.span(); const src_fd = src_fd: { - const syscall: Syscall = try .start(); - while (true) { - if (w.OpenFile(old_path_w, .{ - .dir = old_dir.handle, - .access_mask = .{ - .GENERIC = .{ .WRITE = true }, - .STANDARD = .{ - .RIGHTS = .{ .DELETE = true }, - .SYNCHRONIZE = true, - }, + if (OpenFile(old_path_w, .{ + .dir = old_dir.handle, + .access_mask = .{ + .GENERIC = .{ .WRITE = true }, + .STANDARD = .{ + .RIGHTS = .{ .DELETE = true }, + .SYNCHRONIZE = true, }, - .creation = .OPEN, - .filter = .any, // This function is supposed to rename both files and directories. - .follow_symlinks = false, - })) |handle| { - syscall.finish(); - break :src_fd handle; - } else |err| switch (err) { - error.WouldBlock => unreachable, // Not possible without `.share_access_nonblocking = true`. - error.OperationCanceled => { - try syscall.checkCancel(); - continue; - }, - else => |e| return e, - } + }, + .creation = .OPEN, + .filter = .any, // This function is supposed to rename both files and directories. + .follow_symlinks = false, + })) |handle| { + break :src_fd handle; + } else |err| switch (err) { + error.WouldBlock => unreachable, // Not possible without `.share_access_nonblocking = true`. + else => |e| return e, } }; defer w.CloseHandle(src_fd); @@ -7662,32 +7639,25 @@ fn dirSymLinkWindows( }; const symlink_handle = handle: { - const syscall: Syscall = try .start(); - while (true) { - if (w.OpenFile(sym_link_path_w.span(), .{ - .access_mask = .{ - .GENERIC = .{ .READ = true, .WRITE = true }, - .STANDARD = .{ .SYNCHRONIZE = true }, - }, - .dir = dir.handle, - .creation = .CREATE, - .filter = if (flags.is_directory) .dir_only else .non_directory_only, - })) |handle| { - syscall.finish(); - break :handle handle; - } else |err| switch (err) { - error.IsDir => return syscall.fail(error.PathAlreadyExists), - error.NotDir => return syscall.fail(error.Unexpected), - error.WouldBlock => return syscall.fail(error.Unexpected), - error.PipeBusy => return syscall.fail(error.Unexpected), - error.NoDevice => return syscall.fail(error.Unexpected), - error.AntivirusInterference => return syscall.fail(error.Unexpected), - error.OperationCanceled => { - try syscall.checkCancel(); - continue; - }, - else => |e| return e, - } + if (OpenFile(sym_link_path_w.span(), .{ + .access_mask = .{ + .GENERIC = .{ .READ = true, .WRITE = true }, + .STANDARD = .{ .SYNCHRONIZE = true }, + }, + .dir = dir.handle, + .creation = .CREATE, + .filter = if (flags.is_directory) .dir_only else .non_directory_only, + })) |handle| { + break :handle handle; + } else |err| switch (err) { + error.IsDir => return error.PathAlreadyExists, + error.NotDir => return error.Unexpected, + error.WouldBlock => return error.Unexpected, + error.PipeBusy => return error.Unexpected, + error.FileBusy => return error.Unexpected, + error.NoDevice => return error.Unexpected, + error.AntivirusInterference => return error.Unexpected, + else => |e| return e, } }; defer w.CloseHandle(symlink_handle); @@ -10341,27 +10311,20 @@ fn processExecutablePath(userdata: ?*anyopaque, out_buffer: []u8) process.Execut var path_name_w_buf = try wToPrefixedFileW(null, image_path_name); const h_file = handle: { - const syscall: Syscall = try .start(); - while (true) { - if (w.OpenFile(path_name_w_buf.span(), .{ - .dir = null, - .access_mask = .{ - .GENERIC = .{ .READ = true }, - .STANDARD = .{ .SYNCHRONIZE = true }, - }, - .creation = .OPEN, - .filter = .any, - })) |handle| { - syscall.finish(); - break :handle handle; - } else |err| switch (err) { - error.WouldBlock => unreachable, - error.OperationCanceled => { - try syscall.checkCancel(); - continue; - }, - else => |e| return e, - } + if (OpenFile(path_name_w_buf.span(), .{ + .dir = null, + .access_mask = .{ + .GENERIC = .{ .READ = true }, + .STANDARD = .{ .SYNCHRONIZE = true }, + }, + .creation = .OPEN, + .filter = .any, + })) |handle| { + break :handle handle; + } else |err| switch (err) { + error.WouldBlock => unreachable, + error.FileBusy => unreachable, + else => |e| return e, } }; defer w.CloseHandle(h_file); @@ -19055,3 +19018,151 @@ pub fn mutexUnlock(m: *Io.Mutex) void { }, } } + +const OpenError = error{ + IsDir, + NotDir, + FileNotFound, + NoDevice, + AccessDenied, + PipeBusy, + PathAlreadyExists, + WouldBlock, + NetworkNotFound, + AntivirusInterference, + FileBusy, +} || Dir.PathNameError || Io.Cancelable || Io.UnexpectedError; + +const OpenFileOptions = struct { + access_mask: windows.ACCESS_MASK, + dir: ?windows.HANDLE = null, + sa: ?*windows.SECURITY_ATTRIBUTES = null, + share_access: windows.FILE.SHARE = .VALID_FLAGS, + creation: windows.FILE.CREATE_DISPOSITION, + filter: Filter = .non_directory_only, + /// If false, tries to open path as a reparse point without dereferencing it. + /// Defaults to true. + follow_symlinks: bool = true, + + pub const Filter = enum { + /// Causes `OpenFile` to return `error.IsDir` if the opened handle would be a directory. + non_directory_only, + /// Causes `OpenFile` to return `error.NotDir` if the opened handle is not a directory. + dir_only, + /// `OpenFile` does not discriminate between opening files and directories. + any, + }; +}; + +/// TODO: inline this logic everywhere and delete this function +fn OpenFile(sub_path_w: []const u16, options: OpenFileOptions) OpenError!windows.HANDLE { + if (std.mem.eql(u16, sub_path_w, &[_]u16{'.'}) and options.filter == .non_directory_only) { + return error.IsDir; + } + if (std.mem.eql(u16, sub_path_w, &[_]u16{ '.', '.' }) and options.filter == .non_directory_only) { + return error.IsDir; + } + + var result: windows.HANDLE = undefined; + + const path_len_bytes = std.math.cast(u16, sub_path_w.len * 2) orelse return error.NameTooLong; + var nt_name: windows.UNICODE_STRING = .{ + .Length = path_len_bytes, + .MaximumLength = path_len_bytes, + .Buffer = @constCast(sub_path_w.ptr), + }; + const attr: windows.OBJECT_ATTRIBUTES = .{ + .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w)) null else options.dir, + .Attributes = .{ .INHERIT = if (options.sa) |sa| sa.bInheritHandle != windows.FALSE else false }, + .ObjectName = &nt_name, + .SecurityDescriptor = if (options.sa) |ptr| ptr.lpSecurityDescriptor else null, + }; + + var iosb: windows.IO_STATUS_BLOCK = undefined; + + // There are multiple kernel bugs being worked around with retries. + const max_attempts = 13; + var attempt: u5 = 0; + + var syscall: Syscall = try .start(); + while (true) { + switch (windows.ntdll.NtCreateFile( + &result, + options.access_mask, + &attr, + &iosb, + null, + .{ .NORMAL = true }, + options.share_access, + options.creation, + .{ + .DIRECTORY_FILE = options.filter == .dir_only, + .NON_DIRECTORY_FILE = options.filter == .non_directory_only, + .IO = if (options.follow_symlinks) .SYNCHRONOUS_NONALERT else .ASYNCHRONOUS, + .OPEN_REPARSE_POINT = !options.follow_symlinks, + }, + null, + 0, + )) { + .SUCCESS => { + syscall.finish(); + return result; + }, + .CANCELLED => { + try syscall.checkCancel(); + continue; + }, + .SHARING_VIOLATION => { + // This occurs if the file attempting to be opened is a running + // executable. However, there's a kernel bug: the error may be + // incorrectly returned for an indeterminate amount of time + // after an executable file is closed. Here we work around the + // kernel bug with retry attempts. + syscall.finish(); + if (max_attempts - attempt == 0) return error.FileBusy; + try parking_sleep.sleep(.{ .duration = .{ + .raw = .fromMilliseconds((@as(u32, 1) << attempt) >> 1), + .clock = .awake, + } }); + attempt += 1; + syscall = try .start(); + continue; + }, + .DELETE_PENDING => { + // This error means that there *was* a file in this location on + // the file system, but it was deleted. However, the OS is not + // finished with the deletion operation, and so this CreateFile + // call has failed. There is not really a sane way to handle + // this other than retrying the creation after the OS finishes + // the deletion. + syscall.finish(); + if (max_attempts - attempt == 0) return error.FileBusy; + try parking_sleep.sleep(.{ .duration = .{ + .raw = .fromMilliseconds((@as(u32, 1) << attempt) >> 1), + .clock = .awake, + } }); + attempt += 1; + syscall = try .start(); + continue; + }, + .OBJECT_NAME_INVALID => return syscall.fail(error.BadPathName), + .OBJECT_NAME_NOT_FOUND => return syscall.fail(error.FileNotFound), + .OBJECT_PATH_NOT_FOUND => return syscall.fail(error.FileNotFound), + .BAD_NETWORK_PATH => return syscall.fail(error.NetworkNotFound), // \\server was not found + .BAD_NETWORK_NAME => return syscall.fail(error.NetworkNotFound), // \\server was found but \\server\share wasn't + .NO_MEDIA_IN_DEVICE => return syscall.fail(error.NoDevice), + .ACCESS_DENIED => return syscall.fail(error.AccessDenied), + .PIPE_BUSY => return syscall.fail(error.PipeBusy), + .PIPE_NOT_AVAILABLE => return syscall.fail(error.NoDevice), + .OBJECT_NAME_COLLISION => return syscall.fail(error.PathAlreadyExists), + .FILE_IS_A_DIRECTORY => return syscall.fail(error.IsDir), + .NOT_A_DIRECTORY => return syscall.fail(error.NotDir), + .USER_MAPPED_FILE => return syscall.fail(error.AccessDenied), + .VIRUS_INFECTED, .VIRUS_DELETED => return syscall.fail(error.AntivirusInterference), + .INVALID_PARAMETER => |status| return syscall.ntstatusBug(status), + .OBJECT_PATH_SYNTAX_BAD => |status| return syscall.ntstatusBug(status), + .INVALID_HANDLE => |status| return syscall.ntstatusBug(status), + else => |status| return syscall.unexpectedNtstatus(status), + } + } +} diff --git a/lib/std/os/windows.zig b/lib/std/os/windows.zig index ac36e77ac35d75e52fe356ba6139e2c864160039..0180138a0fbb4c7258181ed1d7ee6a572eb40db6 100644 --- a/lib/std/os/windows.zig +++ b/lib/std/os/windows.zig @@ -2359,123 +2359,6 @@ pub const OBJECT_ATTRIBUTES = extern struct { // ref none -pub const OpenError = error{ - IsDir, - NotDir, - FileNotFound, - NoDevice, - AccessDenied, - PipeBusy, - PathAlreadyExists, - Unexpected, - NameTooLong, - WouldBlock, - NetworkNotFound, - AntivirusInterference, - BadPathName, - OperationCanceled, -}; - -pub const OpenFileOptions = struct { - access_mask: ACCESS_MASK, - dir: ?HANDLE = null, - sa: ?*SECURITY_ATTRIBUTES = null, - share_access: FILE.SHARE = .VALID_FLAGS, - creation: FILE.CREATE_DISPOSITION, - filter: Filter = .non_directory_only, - /// If false, tries to open path as a reparse point without dereferencing it. - /// Defaults to true. - follow_symlinks: bool = true, - - pub const Filter = enum { - /// Causes `OpenFile` to return `error.IsDir` if the opened handle would be a directory. - non_directory_only, - /// Causes `OpenFile` to return `error.NotDir` if the opened handle is not a directory. - dir_only, - /// `OpenFile` does not discriminate between opening files and directories. - any, - }; -}; - -pub fn OpenFile(sub_path_w: []const u16, options: OpenFileOptions) OpenError!HANDLE { - if (mem.eql(u16, sub_path_w, &[_]u16{'.'}) and options.filter == .non_directory_only) { - return error.IsDir; - } - if (mem.eql(u16, sub_path_w, &[_]u16{ '.', '.' }) and options.filter == .non_directory_only) { - return error.IsDir; - } - - var result: HANDLE = undefined; - - const path_len_bytes = math.cast(u16, sub_path_w.len * 2) orelse return error.NameTooLong; - var nt_name: UNICODE_STRING = .{ - .Length = path_len_bytes, - .MaximumLength = path_len_bytes, - .Buffer = @constCast(sub_path_w.ptr), - }; - const attr: OBJECT_ATTRIBUTES = .{ - .RootDirectory = if (std.fs.path.isAbsoluteWindowsWtf16(sub_path_w)) null else options.dir, - .Attributes = .{ .INHERIT = if (options.sa) |sa| sa.bInheritHandle != FALSE else false }, - .ObjectName = &nt_name, - .SecurityDescriptor = if (options.sa) |ptr| ptr.lpSecurityDescriptor else null, - }; - var io: IO_STATUS_BLOCK = undefined; - while (true) { - const rc = ntdll.NtCreateFile( - &result, - options.access_mask, - &attr, - &io, - null, - .{ .NORMAL = true }, - options.share_access, - options.creation, - .{ - .DIRECTORY_FILE = options.filter == .dir_only, - .NON_DIRECTORY_FILE = options.filter == .non_directory_only, - .IO = if (options.follow_symlinks) .SYNCHRONOUS_NONALERT else .ASYNCHRONOUS, - .OPEN_REPARSE_POINT = !options.follow_symlinks, - }, - null, - 0, - ); - switch (rc) { - .SUCCESS => return result, - .OBJECT_NAME_INVALID => return error.BadPathName, - .OBJECT_NAME_NOT_FOUND => return error.FileNotFound, - .OBJECT_PATH_NOT_FOUND => return error.FileNotFound, - .BAD_NETWORK_PATH => return error.NetworkNotFound, // \\server was not found - .BAD_NETWORK_NAME => return error.NetworkNotFound, // \\server was found but \\server\share wasn't - .NO_MEDIA_IN_DEVICE => return error.NoDevice, - .INVALID_PARAMETER => unreachable, - .SHARING_VIOLATION => return error.AccessDenied, - .ACCESS_DENIED => return error.AccessDenied, - .PIPE_BUSY => return error.PipeBusy, - .PIPE_NOT_AVAILABLE => return error.NoDevice, - .OBJECT_PATH_SYNTAX_BAD => unreachable, - .OBJECT_NAME_COLLISION => return error.PathAlreadyExists, - .FILE_IS_A_DIRECTORY => return error.IsDir, - .NOT_A_DIRECTORY => return error.NotDir, - .USER_MAPPED_FILE => return error.AccessDenied, - .INVALID_HANDLE => unreachable, - .DELETE_PENDING => { - // This error means that there *was* a file in this location on - // the file system, but it was deleted. However, the OS is not - // finished with the deletion operation, and so this CreateFile - // call has failed. There is not really a sane way to handle - // this other than retrying the creation after the OS finishes - // the deletion. - const delay_one_ms: LARGE_INTEGER = -(std.time.ns_per_ms / 100); - _ = ntdll.NtDelayExecution(TRUE, &delay_one_ms); - continue; - }, - .VIRUS_INFECTED, .VIRUS_DELETED => return error.AntivirusInterference, - .CANCELLED => return error.OperationCanceled, - else => return unexpectedStatus(rc), - } - } -} - pub fn GetCurrentProcess() HANDLE { const process_pseudo_handle: usize = @bitCast(@as(isize, -1)); return @ptrFromInt(process_pseudo_handle); -- 2.54.0 From 0e7d00776e5468b53c2ba07ebfe9fef5abda310a Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 4 Feb 2026 12:44:11 -0800 Subject: [PATCH 202/499] std.Io.Threaded: inline OpenFile into dirCreateDirWindows --- lib/std/Io/Threaded.zig | 152 +++++++++++++++++++++++++++++----------- 1 file changed, 112 insertions(+), 40 deletions(-) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index ac9b4fd7f46e17c10d4ec9e0b18fd79e7dd08739..5db2d6cfd0ac2dc72999547ec8f5b05ded309650 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -1404,6 +1404,8 @@ const splat_buffer_size = 64; /// posix systems. const poll_buffer_len = 64; const default_PATH = "/usr/local/bin:/bin/:/usr/bin"; +/// There are multiple kernel bugs being worked around with retries. +const max_windows_kernel_bug_retries = 13; comptime { if (@TypeOf(posix.IOV_MAX) != void) assert(max_iovecs_len <= posix.IOV_MAX); @@ -3256,28 +3258,114 @@ fn dirCreateDirWasi(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, permi fn dirCreateDirWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, permissions: Dir.Permissions) Dir.CreateDirError!void { const t: *Threaded = @ptrCast(@alignCast(userdata)); _ = t; - - const sub_path_w = try sliceToPrefixedFileW(dir.handle, sub_path); _ = permissions; // TODO use this value - const sub_dir_handle = OpenFile(sub_path_w.span(), .{ - .dir = dir.handle, - .access_mask = .{ + const sub_path_w_array = try sliceToPrefixedFileW(dir.handle, sub_path); + const sub_path_w = sub_path_w_array.span(); + const path_len_bytes = std.math.cast(u16, sub_path_w.len * 2) orelse return error.NameTooLong; + + var nt_name: windows.UNICODE_STRING = .{ + .Length = path_len_bytes, + .MaximumLength = path_len_bytes, + .Buffer = @constCast(sub_path_w.ptr), + }; + const attr: windows.OBJECT_ATTRIBUTES = .{ + .Length = @sizeOf(windows.OBJECT_ATTRIBUTES), + .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle, + .Attributes = .{ + .INHERIT = false, + }, + .ObjectName = &nt_name, + .SecurityDescriptor = null, + .SecurityQualityOfService = null, + }; + + var sub_dir_handle: windows.HANDLE = undefined; + var io_status_block: windows.IO_STATUS_BLOCK = undefined; + var attempt: u5 = 0; + var syscall: Syscall = try .start(); + while (true) switch (windows.ntdll.NtCreateFile( + &sub_dir_handle, + .{ .GENERIC = .{ .READ = true }, .STANDARD = .{ .SYNCHRONIZE = true }, }, - .creation = .CREATE, - .filter = .dir_only, - }) catch |err| switch (err) { - error.IsDir => return error.Unexpected, - error.PipeBusy => return error.Unexpected, - error.FileBusy => return error.Unexpected, - error.NoDevice => return error.Unexpected, - error.WouldBlock => return error.Unexpected, - error.AntivirusInterference => return error.Unexpected, - else => |e| return e, + &attr, + &io_status_block, + null, + .{ .NORMAL = true }, + .VALID_FLAGS, + .CREATE, + .{ + .DIRECTORY_FILE = true, + .NON_DIRECTORY_FILE = false, + .IO = .SYNCHRONOUS_NONALERT, + .OPEN_REPARSE_POINT = false, + }, + null, + 0, + )) { + .SUCCESS => { + syscall.finish(); + windows.CloseHandle(sub_dir_handle); + return; + }, + .CANCELLED => { + try syscall.checkCancel(); + continue; + }, + .SHARING_VIOLATION => { + // This occurs if the file attempting to be opened is a running + // executable. However, there's a kernel bug: the error may be + // incorrectly returned for an indeterminate amount of time + // after an executable file is closed. Here we work around the + // kernel bug with retry attempts. + syscall.finish(); + if (max_windows_kernel_bug_retries - attempt == 0) return error.FileBusy; + try parking_sleep.sleep(.{ .duration = .{ + .raw = .fromMilliseconds((@as(u32, 1) << attempt) >> 1), + .clock = .awake, + } }); + attempt += 1; + syscall = try .start(); + continue; + }, + .DELETE_PENDING => { + // This error means that there *was* a file in this location on + // the file system, but it was deleted. However, the OS is not + // finished with the deletion operation, and so this CreateFile + // call has failed. There is not really a sane way to handle + // this other than retrying the creation after the OS finishes + // the deletion. + syscall.finish(); + if (max_windows_kernel_bug_retries - attempt == 0) return error.FileBusy; + try parking_sleep.sleep(.{ .duration = .{ + .raw = .fromMilliseconds((@as(u32, 1) << attempt) >> 1), + .clock = .awake, + } }); + attempt += 1; + syscall = try .start(); + continue; + }, + .OBJECT_NAME_INVALID => return syscall.fail(error.BadPathName), + .OBJECT_NAME_NOT_FOUND => return syscall.fail(error.FileNotFound), + .OBJECT_PATH_NOT_FOUND => return syscall.fail(error.FileNotFound), + .BAD_NETWORK_PATH => return syscall.fail(error.NetworkNotFound), // \\server was not found + .BAD_NETWORK_NAME => return syscall.fail(error.NetworkNotFound), // \\server was found but \\server\share wasn't + .NO_MEDIA_IN_DEVICE => return syscall.fail(error.NoDevice), + .ACCESS_DENIED => return syscall.fail(error.AccessDenied), + .PIPE_BUSY => return syscall.fail(error.PipeBusy), + .PIPE_NOT_AVAILABLE => return syscall.fail(error.NoDevice), + .OBJECT_NAME_COLLISION => return syscall.fail(error.PathAlreadyExists), + .FILE_IS_A_DIRECTORY => return syscall.fail(error.IsDir), + .NOT_A_DIRECTORY => return syscall.fail(error.NotDir), + .USER_MAPPED_FILE => return syscall.fail(error.AccessDenied), + .VIRUS_INFECTED, .VIRUS_DELETED => return syscall.fail(error.AntivirusInterference), + .INVALID_PARAMETER => |status| return syscall.ntstatusBug(status), + .OBJECT_PATH_SYNTAX_BAD => |status| return syscall.ntstatusBug(status), + .INVALID_HANDLE => |status| return syscall.ntstatusBug(status), + else => |status| return syscall.unexpectedNtstatus(status), }; - windows.CloseHandle(sub_dir_handle); } fn dirCreateDirPath( @@ -4307,11 +4395,7 @@ fn dirCreateFileWindows( }; var io_status_block: windows.IO_STATUS_BLOCK = undefined; - - // There are multiple kernel bugs being worked around with retries. - const max_attempts = 13; var attempt: u5 = 0; - var handle: windows.HANDLE = undefined; var syscall: Syscall = try .start(); while (true) switch (windows.ntdll.NtCreateFile( @@ -4345,7 +4429,7 @@ fn dirCreateFileWindows( // after an executable file is closed. Here we work around the // kernel bug with retry attempts. syscall.finish(); - if (max_attempts - attempt == 0) return error.FileBusy; + if (max_windows_kernel_bug_retries - attempt == 0) return error.FileBusy; try parking_sleep.sleep(.{ .duration = .{ .raw = .fromMilliseconds((@as(u32, 1) << attempt) >> 1), .clock = .awake, @@ -4361,7 +4445,7 @@ fn dirCreateFileWindows( // call has failed. Here, we simulate the kernel bug being // fixed by sleeping and retrying until the error goes away. syscall.finish(); - if (max_attempts - attempt == 0) return error.FileBusy; + if (max_windows_kernel_bug_retries - attempt == 0) return error.FileBusy; try parking_sleep.sleep(.{ .duration = .{ .raw = .fromMilliseconds((@as(u32, 1) << attempt) >> 1), .clock = .awake, @@ -4903,11 +4987,7 @@ pub fn dirOpenFileWtf16( .Buffer = @constCast(sub_path_w.ptr), }; var io_status_block: w.IO_STATUS_BLOCK = undefined; - - // There are multiple kernel bugs being worked around with retries. - const max_attempts = 13; var attempt: u5 = 0; - var syscall: Syscall = try .start(); const handle = while (true) { var result: w.HANDLE = undefined; @@ -4959,7 +5039,7 @@ pub fn dirOpenFileWtf16( // after an executable file is closed. Here we work around the // kernel bug with retry attempts. syscall.finish(); - if (max_attempts - attempt == 0) return error.FileBusy; + if (max_windows_kernel_bug_retries - attempt == 0) return error.FileBusy; try parking_sleep.sleep(.{ .duration = .{ .raw = .fromMilliseconds((@as(u32, 1) << attempt) >> 1), .clock = .awake, @@ -4984,7 +5064,7 @@ pub fn dirOpenFileWtf16( // call has failed. Here, we simulate the kernel bug being // fixed by sleeping and retrying until the error goes away. syscall.finish(); - if (max_attempts - attempt == 0) return error.FileBusy; + if (max_windows_kernel_bug_retries - attempt == 0) return error.FileBusy; try parking_sleep.sleep(.{ .duration = .{ .raw = .fromMilliseconds((@as(u32, 1) << attempt) >> 1), .clock = .awake, @@ -7850,11 +7930,7 @@ fn dirReadLinkWindows(dir: Dir, sub_path: []const u8, buffer: []u8) Dir.ReadLink }; var io_status_block: windows.IO_STATUS_BLOCK = undefined; var result_handle: windows.HANDLE = undefined; - - // There are multiple kernel bugs being worked around with retries. - const max_attempts = 13; var attempt: u5 = 0; - var syscall: Syscall = try .start(); while (true) switch (windows.ntdll.NtCreateFile( &result_handle, @@ -7894,7 +7970,7 @@ fn dirReadLinkWindows(dir: Dir, sub_path: []const u8, buffer: []u8) Dir.ReadLink // after an executable file is closed. Here we work around the // kernel bug with retry attempts. syscall.finish(); - if (max_attempts - attempt == 0) return error.FileBusy; + if (max_windows_kernel_bug_retries - attempt == 0) return error.FileBusy; try parking_sleep.sleep(.{ .duration = .{ .raw = .fromMilliseconds((@as(u32, 1) << attempt) >> 1), .clock = .awake, @@ -7910,7 +7986,7 @@ fn dirReadLinkWindows(dir: Dir, sub_path: []const u8, buffer: []u8) Dir.ReadLink // call has failed. Here, we simulate the kernel bug being // fixed by sleeping and retrying until the error goes away. syscall.finish(); - if (max_attempts - attempt == 0) return error.FileBusy; + if (max_windows_kernel_bug_retries - attempt == 0) return error.FileBusy; try parking_sleep.sleep(.{ .duration = .{ .raw = .fromMilliseconds((@as(u32, 1) << attempt) >> 1), .clock = .awake, @@ -19079,11 +19155,7 @@ fn OpenFile(sub_path_w: []const u16, options: OpenFileOptions) OpenError!windows }; var iosb: windows.IO_STATUS_BLOCK = undefined; - - // There are multiple kernel bugs being worked around with retries. - const max_attempts = 13; var attempt: u5 = 0; - var syscall: Syscall = try .start(); while (true) { switch (windows.ntdll.NtCreateFile( @@ -19119,7 +19191,7 @@ fn OpenFile(sub_path_w: []const u16, options: OpenFileOptions) OpenError!windows // after an executable file is closed. Here we work around the // kernel bug with retry attempts. syscall.finish(); - if (max_attempts - attempt == 0) return error.FileBusy; + if (max_windows_kernel_bug_retries - attempt == 0) return error.FileBusy; try parking_sleep.sleep(.{ .duration = .{ .raw = .fromMilliseconds((@as(u32, 1) << attempt) >> 1), .clock = .awake, @@ -19136,7 +19208,7 @@ fn OpenFile(sub_path_w: []const u16, options: OpenFileOptions) OpenError!windows // this other than retrying the creation after the OS finishes // the deletion. syscall.finish(); - if (max_attempts - attempt == 0) return error.FileBusy; + if (max_windows_kernel_bug_retries - attempt == 0) return error.FileBusy; try parking_sleep.sleep(.{ .duration = .{ .raw = .fromMilliseconds((@as(u32, 1) << attempt) >> 1), .clock = .awake, -- 2.54.0 From 3078a3197bce73f6ce70117989ea09f991470f50 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 4 Feb 2026 12:46:56 -0800 Subject: [PATCH 203/499] std.Io.Threaded.dirCreateDirWindows: remove unexpected error handling --- lib/std/Io/Threaded.zig | 9 ++------- 1 file changed, 2 insertions(+), 7 deletions(-) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 5db2d6cfd0ac2dc72999547ec8f5b05ded309650..c467615f9cb2579c6e0ac539eb735515d3e723ab 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -3321,7 +3321,7 @@ fn dirCreateDirWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, pe // after an executable file is closed. Here we work around the // kernel bug with retry attempts. syscall.finish(); - if (max_windows_kernel_bug_retries - attempt == 0) return error.FileBusy; + if (max_windows_kernel_bug_retries - attempt == 0) return error.Unexpected; try parking_sleep.sleep(.{ .duration = .{ .raw = .fromMilliseconds((@as(u32, 1) << attempt) >> 1), .clock = .awake, @@ -3338,7 +3338,7 @@ fn dirCreateDirWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, pe // this other than retrying the creation after the OS finishes // the deletion. syscall.finish(); - if (max_windows_kernel_bug_retries - attempt == 0) return error.FileBusy; + if (max_windows_kernel_bug_retries - attempt == 0) return error.Unexpected; try parking_sleep.sleep(.{ .duration = .{ .raw = .fromMilliseconds((@as(u32, 1) << attempt) >> 1), .clock = .awake, @@ -3352,15 +3352,10 @@ fn dirCreateDirWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, pe .OBJECT_PATH_NOT_FOUND => return syscall.fail(error.FileNotFound), .BAD_NETWORK_PATH => return syscall.fail(error.NetworkNotFound), // \\server was not found .BAD_NETWORK_NAME => return syscall.fail(error.NetworkNotFound), // \\server was found but \\server\share wasn't - .NO_MEDIA_IN_DEVICE => return syscall.fail(error.NoDevice), .ACCESS_DENIED => return syscall.fail(error.AccessDenied), - .PIPE_BUSY => return syscall.fail(error.PipeBusy), - .PIPE_NOT_AVAILABLE => return syscall.fail(error.NoDevice), .OBJECT_NAME_COLLISION => return syscall.fail(error.PathAlreadyExists), - .FILE_IS_A_DIRECTORY => return syscall.fail(error.IsDir), .NOT_A_DIRECTORY => return syscall.fail(error.NotDir), .USER_MAPPED_FILE => return syscall.fail(error.AccessDenied), - .VIRUS_INFECTED, .VIRUS_DELETED => return syscall.fail(error.AntivirusInterference), .INVALID_PARAMETER => |status| return syscall.ntstatusBug(status), .OBJECT_PATH_SYNTAX_BAD => |status| return syscall.ntstatusBug(status), .INVALID_HANDLE => |status| return syscall.ntstatusBug(status), -- 2.54.0 From c77e7146f5fa8e83c06cd6612b7298df06912974 Mon Sep 17 00:00:00 2001 From: Jacob Young Date: Wed, 4 Feb 2026 18:12:29 -0500 Subject: [PATCH 204/499] std.Threaded: replace console kernel32 functions with ntdll --- lib/std/Io.zig | 8 +- lib/std/Io/Terminal.zig | 26 +-- lib/std/Io/Threaded.zig | 208 ++++++++++------------- lib/std/Progress.zig | 171 +++++++++++-------- lib/std/log.zig | 5 +- lib/std/os/windows.zig | 289 ++++++++++++++++++++++++++++---- lib/std/os/windows/kernel32.zig | 84 ---------- lib/std/std.zig | 6 +- src/libs/mingw.zig | 2 +- 9 files changed, 477 insertions(+), 322 deletions(-) diff --git a/lib/std/Io.zig b/lib/std/Io.zig index 0eb1df2d1a7decb3787d74c6baeb7c49a507a4fe..47ba7c2072c7bc1d954bdc21c85577cb71703bbe 100644 --- a/lib/std/Io.zig +++ b/lib/std/Io.zig @@ -335,11 +335,9 @@ pub const Operation = union(enum) { .wasi => noreturn, .windows => struct { file: File, - IoControlCode: std.os.windows.CTL_CODE, - InputBuffer: ?*const anyopaque, - InputBufferLength: u32, - OutputBuffer: ?*anyopaque, - OutputBufferLength: u32, + code: std.os.windows.CTL_CODE, + in: []const u8 = &.{}, + out: []u8 = &.{}, pub const Result = std.os.windows.IO_STATUS_BLOCK; }, diff --git a/lib/std/Io/Terminal.zig b/lib/std/Io/Terminal.zig index beacc4d301c782144c58cccffe52df0ec058ab3d..27805f5e4d5f88ab7eaa7ebdb6326a248d54afd6 100644 --- a/lib/std/Io/Terminal.zig +++ b/lib/std/Io/Terminal.zig @@ -40,7 +40,8 @@ pub const Mode = union(enum) { windows_api: WindowsApi, pub const WindowsApi = if (!is_windows) noreturn else struct { - handle: File.Handle, + io: Io, + file: File, reset_attributes: u16, }; @@ -65,20 +66,21 @@ pub const Mode = union(enum) { } if (is_windows and try file.isTty(io)) { - const windows = std.os.windows; - var info: windows.CONSOLE_SCREEN_BUFFER_INFO = undefined; - if (windows.kernel32.GetConsoleScreenBufferInfo(file.handle, &info) != 0) { - return .{ .windows_api = .{ - .handle = file.handle, - .reset_attributes = info.wAttributes, - } }; + var get_console_info = std.os.windows.CONSOLE.USER_IO.GET_SCREEN_BUFFER_INFO; + switch (try get_console_info.operate(io, file)) { + .SUCCESS => return .{ .windows_api = .{ + .io = io, + .file = file, + .reset_attributes = get_console_info.Data.wAttributes, + } }, + else => {}, } } return if (force_color == true) .escape_codes else .no_color; } }; -pub const SetColorError = std.os.windows.SetConsoleTextAttributeError || Io.Writer.Error; +pub const SetColorError = Io.Cancelable || Io.UnexpectedError || Io.Writer.Error; pub fn setColor(t: Terminal, color: Color) SetColorError!void { switch (t.mode) { @@ -132,7 +134,11 @@ pub fn setColor(t: Terminal, color: Color) SetColorError!void { .reset => wa.reset_attributes, }; try t.writer.flush(); - try windows.SetConsoleTextAttribute(wa.handle, attributes); + var set_text_attribute = windows.CONSOLE.USER_IO.SET_TEXT_ATTRIBUTE(attributes); + switch (try set_text_attribute.operate(wa.io, wa.file)) { + .SUCCESS => {}, + else => |status| return windows.unexpectedStatus(status), + } }, } } diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index c467615f9cb2579c6e0ac539eb735515d3e723ab..ab2e9af75765de1f30b6fa6c2f087921ffa54e9b 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -3083,19 +3083,23 @@ fn batchAwaitWindows(b: *Io.Batch, concurrency: bool) error{ Canceled, Concurren } }, .device_io_control => |o| { + const NtControlFile = switch (o.code.DeviceType) { + .FILE_SYSTEM, .NAMED_PIPE => &windows.ntdll.NtFsControlFile, + else => &windows.ntdll.NtDeviceIoControlFile, + }; if (o.file.flags.nonblocking) { context.file = o.file.handle; - switch (windows.ntdll.NtDeviceIoControlFile( + switch (NtControlFile( o.file.handle, null, // event &batchApc, b, &context.iosb, - o.IoControlCode, - o.InputBuffer, - o.InputBufferLength, - o.OutputBuffer, - o.OutputBufferLength, + o.code, + if (o.in.len > 0) o.in.ptr else null, + @intCast(o.in.len), + if (o.out.len > 0) o.out.ptr else null, + @intCast(o.out.len), )) { .PENDING, .SUCCESS => {}, .CANCELLED => unreachable, @@ -3108,17 +3112,17 @@ fn batchAwaitWindows(b: *Io.Batch, concurrency: bool) error{ Canceled, Concurren if (concurrency) return error.ConcurrencyUnavailable; const syscall: Syscall = try .start(); - while (true) switch (windows.ntdll.NtDeviceIoControlFile( + while (true) switch (NtControlFile( o.file.handle, null, // event null, // APC routine null, // APC context &context.iosb, - o.IoControlCode, - o.InputBuffer, - o.InputBufferLength, - o.OutputBuffer, - o.OutputBufferLength, + o.code, + if (o.in.len > 0) o.in.ptr else null, + @intCast(o.in.len), + if (o.out.len > 0) o.out.ptr else null, + @intCast(o.out.len), )) { .PENDING => unreachable, // unrecoverable: wrong File nonblocking flag .CANCELLED => { @@ -8547,29 +8551,24 @@ fn fileSyncWasi(userdata: ?*anyopaque, file: File) File.SyncError!void { fn fileIsTty(userdata: ?*anyopaque, file: File) Io.Cancelable!bool { const t: *Threaded = @ptrCast(@alignCast(userdata)); - _ = t; - return isTty(file); + return t.isTty(file); } -fn isTty(file: File) Io.Cancelable!bool { +fn isTty(t: *Threaded, file: File) Io.Cancelable!bool { if (is_windows) { - if (try isCygwinPty(file)) return true; - var out: windows.DWORD = undefined; - const syscall: Syscall = try .start(); - while (windows.kernel32.GetConsoleMode(file.handle, &out) == 0) { - switch (windows.GetLastError()) { - .OPERATION_ABORTED => { - try syscall.checkCancel(); - continue; - }, - else => { - syscall.finish(); - return false; - }, - } + var get_console_mode = windows.CONSOLE.USER_IO.GET_MODE; + switch ((try t.deviceIoControl(&.{ + .file = .{ + .handle = windows.peb().ProcessParameters.ConsoleHandle, + .flags = .{ .nonblocking = false }, + }, + .code = windows.IOCTL.CONDRV.ISSUE_USER_IO, + .in = @ptrCast(&get_console_mode.request(file, 0, .{}, 0, .{})), + })).u.Status) { + .SUCCESS => return true, + .INVALID_HANDLE => return isCygwinPty(file), + else => return false, } - syscall.finish(); - return true; } if (builtin.link_libc) { @@ -8637,35 +8636,26 @@ fn isTty(file: File) Io.Cancelable!bool { fn fileEnableAnsiEscapeCodes(userdata: ?*anyopaque, file: File) File.EnableAnsiEscapeCodesError!void { const t: *Threaded = @ptrCast(@alignCast(userdata)); - _ = t; - if (!is_windows) { - if (try supportsAnsiEscapeCodes(file)) return; - return error.NotTerminalDevice; - } + if (!is_windows) return if (!try t.supportsAnsiEscapeCodes(file)) error.NotTerminalDevice; // For Windows Terminal, VT Sequences processing is enabled by default. - var original_console_mode: windows.DWORD = 0; - - { - const syscall: Syscall = try .start(); - while (windows.kernel32.GetConsoleMode(file.handle, &original_console_mode) == 0) { - switch (windows.GetLastError()) { - .OPERATION_ABORTED => { - try syscall.checkCancel(); - continue; - }, - else => { - syscall.finish(); - if (try isCygwinPty(file)) return; - return error.NotTerminalDevice; - }, - } - } - syscall.finish(); + const console: File = .{ + .handle = windows.peb().ProcessParameters.ConsoleHandle, + .flags = .{ .nonblocking = false }, + }; + var get_console_mode = windows.CONSOLE.USER_IO.GET_MODE; + switch ((try t.deviceIoControl(&.{ + .file = console, + .code = windows.IOCTL.CONDRV.ISSUE_USER_IO, + .in = @ptrCast(&get_console_mode.request(file, 0, .{}, 0, .{})), + })).u.Status) { + .SUCCESS => {}, + .INVALID_HANDLE => return if (!try isCygwinPty(file)) error.NotTerminalDevice, + else => return error.NotTerminalDevice, } - if (original_console_mode & windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING != 0) return; + if (get_console_mode.Data & windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING != 0) return; // For Windows Console, VT Sequences processing support was added in Windows 10 build 14361, but disabled by default. // https://devblogs.microsoft.com/commandline/tmux-support-arrives-for-bash-on-ubuntu-on-windows/ @@ -8678,58 +8668,40 @@ fn fileEnableAnsiEscapeCodes(userdata: ?*anyopaque, file: File) File.EnableAnsiE // Additionally, the default console mode in Windows Terminal does not have // `DISABLE_NEWLINE_AUTO_RETURN` set, so by only enabling `ENABLE_VIRTUAL_TERMINAL_PROCESSING` // we end up matching the mode of Windows Terminal. - const requested_console_modes = windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING; - const console_mode = original_console_mode | requested_console_modes; - - { - const syscall: Syscall = try .start(); - while (windows.kernel32.SetConsoleMode(file.handle, console_mode) == 0) { - switch (windows.GetLastError()) { - .OPERATION_ABORTED => { - try syscall.checkCancel(); - continue; - }, - else => { - syscall.finish(); - if (try isCygwinPty(file)) return; - return error.NotTerminalDevice; - }, - } - } - syscall.finish(); + var set_console_mode = windows.CONSOLE.USER_IO.SET_MODE( + get_console_mode.Data | windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING, + ); + switch ((try t.deviceIoControl(&.{ + .file = console, + .code = windows.IOCTL.CONDRV.ISSUE_USER_IO, + .in = @ptrCast(&set_console_mode.request(file, 0, .{}, 0, .{})), + })).u.Status) { + .SUCCESS => {}, + else => |status| return windows.unexpectedStatus(status), } } fn fileSupportsAnsiEscapeCodes(userdata: ?*anyopaque, file: File) Io.Cancelable!bool { const t: *Threaded = @ptrCast(@alignCast(userdata)); - _ = t; - return supportsAnsiEscapeCodes(file); + return t.supportsAnsiEscapeCodes(file); } -fn supportsAnsiEscapeCodes(file: File) Io.Cancelable!bool { +fn supportsAnsiEscapeCodes(t: *Threaded, file: File) Io.Cancelable!bool { if (is_windows) { - var console_mode: windows.DWORD = 0; - - const syscall: Syscall = try .start(); - while (windows.kernel32.GetConsoleMode(file.handle, &console_mode) == 0) { - switch (windows.GetLastError()) { - .OPERATION_ABORTED => { - try syscall.checkCancel(); - continue; - }, - else => { - syscall.finish(); - break; - }, - } - } else { - syscall.finish(); - if (console_mode & windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING != 0) { - return true; - } + var get_console_mode = windows.CONSOLE.USER_IO.GET_MODE; + switch ((try t.deviceIoControl(&.{ + .file = .{ + .handle = windows.peb().ProcessParameters.ConsoleHandle, + .flags = .{ .nonblocking = false }, + }, + .code = windows.IOCTL.CONDRV.ISSUE_USER_IO, + .in = @ptrCast(&get_console_mode.request(file, 0, .{}, 0, .{})), + })).u.Status) { + .SUCCESS => if (get_console_mode.Data & windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING != 0) + return true, + .INVALID_HANDLE => return isCygwinPty(file), + else => return false, } - - return isCygwinPty(file); } if (native_os == .wasi) { @@ -8739,7 +8711,7 @@ fn supportsAnsiEscapeCodes(file: File) Io.Cancelable!bool { return false; } - if (try isTty(file)) return true; + if (try t.isTty(file)) return true; return false; } @@ -14111,12 +14083,14 @@ fn initLockedStderr(t: *Threaded, terminal_mode: ?Io.Terminal.Mode) Io.Cancelabl fn unlockStderr(userdata: ?*anyopaque) void { const t: *Threaded = @ptrCast(@alignCast(userdata)); - t.stderr_writer.interface.flush() catch |err| switch (err) { - error.WriteFailed => switch (t.stderr_writer.err.?) { + if (t.stderr_writer.err == null) t.stderr_writer.interface.flush() catch {}; + if (t.stderr_writer.err) |err| { + switch (err) { error.Canceled => recancelInner(), else => {}, - }, - }; + } + t.stderr_writer.err = null; + } t.stderr_writer.interface.end = 0; t.stderr_writer.interface.buffer = &.{}; @@ -18848,20 +18822,24 @@ fn mmSyncWrite(file: File, memory: []u8, offset: u64) File.WritePositionalError! fn deviceIoControl(t: *Threaded, o: *const Io.Operation.DeviceIoControl) Io.Cancelable!Io.Operation.DeviceIoControl.Result { _ = t; if (is_windows) { + const NtControlFile = switch (o.code.DeviceType) { + .FILE_SYSTEM, .NAMED_PIPE => &windows.ntdll.NtFsControlFile, + else => &windows.ntdll.NtDeviceIoControlFile, + }; var iosb: windows.IO_STATUS_BLOCK = undefined; if (o.file.flags.nonblocking) { var done: bool = false; - switch (windows.ntdll.NtDeviceIoControlFile( + switch (NtControlFile( o.file.handle, null, // event flagApc, &done, // APC context &iosb, - o.IoControlCode, - o.InputBuffer, - o.InputBufferLength, - o.OutputBuffer, - o.OutputBufferLength, + o.code, + if (o.in.len > 0) o.in.ptr else null, + @intCast(o.in.len), + if (o.out.len > 0) o.out.ptr else null, + @intCast(o.out.len), )) { // We must wait for the APC routine. .PENDING, .SUCCESS => while (!done) { @@ -18882,17 +18860,17 @@ fn deviceIoControl(t: *Threaded, o: *const Io.Operation.DeviceIoControl) Io.Canc } } else { const syscall: Syscall = try .start(); - while (true) switch (windows.ntdll.NtDeviceIoControlFile( + while (true) switch (NtControlFile( o.file.handle, null, // event null, // APC routine null, // APC context &iosb, - o.IoControlCode, - o.InputBuffer, - o.InputBufferLength, - o.OutputBuffer, - o.OutputBufferLength, + o.code, + if (o.in.len > 0) o.in.ptr else null, + @intCast(o.in.len), + if (o.out.len > 0) o.out.ptr else null, + @intCast(o.out.len), )) { .PENDING => unreachable, // unrecoverable: wrong asynchronous flag .CANCELLED => { diff --git a/lib/std/Progress.zig b/lib/std/Progress.zig index f17fed0a5bc20a5810ceec5853e478a4bf11321a..2240f95fddd65b97681d6803f4e9ffef0d0c8369 100644 --- a/lib/std/Progress.zig +++ b/lib/std/Progress.zig @@ -157,7 +157,7 @@ pub const TerminalMode = union(enum) { ansi_escape_codes, /// This is not the same as being run on windows because other terminals /// exist like MSYS/git-bash. - windows_api: if (is_windows) WindowsApi else void, + windows_api: if (is_windows) WindowsApi else noreturn, pub const WindowsApi = struct { /// The output code page of the console. @@ -614,33 +614,39 @@ pub fn start(io: Io, options: Options) Node { if (stderr.enableAnsiEscapeCodes(io)) |_| { global_progress.terminal_mode = .ansi_escape_codes; } else |_| if (is_windows) { - if (stderr.isTty(io)) |is_tty| { - if (is_tty) global_progress.terminal_mode = TerminalMode{ .windows_api = .{ - .code_page = windows.kernel32.GetConsoleOutputCP(), - } }; - } else |err| switch (err) { + var get_console_cp = windows.CONSOLE.USER_IO.GET_CP(.Output); + // Normally, we would pass `null` to `operate` here as the kernel32 + // function does not accept a handle, however, if we pass one anyway, + // then we will get an error if the handle is not associated with + // this process's console, effectively combining an `isTty` check + // into the same syscall. + switch (get_console_cp.operate(io, stderr) catch |err| switch (err) { error.Canceled => { io.recancel(); return .none; }, + }) { + .SUCCESS => global_progress.terminal_mode = .{ .windows_api = .{ + .code_page = get_console_cp.Data.CodePage, + } }, + .INVALID_HANDLE => {}, + else => {}, } } - - if (global_progress.terminal_mode == .off) return .none; - - if (have_sigwinch) { - const act: posix.Sigaction = .{ - .handler = .{ .sigaction = handleSigWinch }, - .mask = posix.sigemptyset(), - .flags = (posix.SA.SIGINFO | posix.SA.RESTART), - }; - posix.sigaction(.WINCH, &act, null); - } - - if (switch (global_progress.terminal_mode) { - .off => unreachable, // handled a few lines above - .ansi_escape_codes => io.concurrent(updateTask, .{io}), - .windows_api => if (is_windows) io.concurrent(windowsApiUpdateTask, .{io}) else unreachable, + if (future: switch (global_progress.terminal_mode) { + .off => return .none, + .ansi_escape_codes => { + if (have_sigwinch) { + const act: posix.Sigaction = .{ + .handler = .{ .sigaction = handleSigWinch }, + .mask = posix.sigemptyset(), + .flags = (posix.SA.SIGINFO | posix.SA.RESTART), + }; + posix.sigaction(.WINCH, &act, null); + } + break :future io.concurrent(updateTask, .{io}); + }, + .windows_api => io.concurrent(windowsApiUpdateTask, .{io}), }) |future| { global_progress.update_worker = future; } else |err| { @@ -715,12 +721,24 @@ fn updateTask(io: Io) WorkerError!void { } } -fn windowsApiWriteMarker() void { +const WindowsApiError = Io.Cancelable || Io.UnexpectedError; + +fn windowsApiWriteMarker(io: Io) WindowsApiError!void { // Write the marker that we will use to find the beginning of the progress when clearing. // Note: This doesn't have to use WriteConsoleW, but doing so avoids dealing with the code page. - var num_chars_written: windows.DWORD = undefined; - const handle = global_progress.terminal.handle; - _ = windows.kernel32.WriteConsoleW(handle, &[_]u16{windows_api_start_marker}, 1, &num_chars_written, null); + const terminal = global_progress.terminal; + var write_console = windows.CONSOLE.USER_IO.WRITE(.WideCharacter); + const buffer = [1]windows.WCHAR{windows_api_start_marker}; + switch ((try io.operate(.{ .device_io_control = .{ + .file = terminal, + .code = windows.IOCTL.CONDRV.ISSUE_USER_IO, + .in = @ptrCast(&write_console.request(null, 1, .{ + .{ .Size = @sizeOf(@TypeOf(buffer)), .Pointer = &buffer }, + }, 0, .{})), + } })).device_io_control.u.Status) { + .SUCCESS => {}, + else => |status| return windows.unexpectedStatus(status), + } } fn windowsApiUpdateTask(io: Io) WorkerError!void { @@ -743,19 +761,19 @@ fn windowsApiUpdateTask(io: Io) WorkerError!void { error.Canceled => unreachable, // blocked }; defer io.unlockStderr(); - clearWrittenWindowsApi() catch {}; + clearWrittenWindowsApi(io) catch {}; } while (true) { const buffer, const nl_n = try computeRedraw(io, &serialized_buffer); if (io.vtable.tryLockStderr(io.userdata, null) catch return) |locked_stderr| { defer io.unlockStderr(); - try clearWrittenWindowsApi(); - windowsApiWriteMarker(); + try clearWrittenWindowsApi(io); + try windowsApiWriteMarker(io); global_progress.need_clear = true; locked_stderr.file_writer.interface.writeAll(buffer) catch |err| switch (err) { error.WriteFailed => return locked_stderr.file_writer.err.?, }; - windowsApiMoveToMarker(nl_n) catch return; + windowsApiMoveToMarker(io, nl_n) catch return; } try maybeUpdateSize(io, try wait(io, global_progress.refresh_rate_ns)); @@ -859,7 +877,7 @@ fn appendTreeSymbol(symbol: TreeSymbol, buf: []u8, start_i: usize) usize { return start_i + bytes.len; }, .windows_api => |windows_api| { - const bytes = if (!is_windows) unreachable else switch (windows_api.code_page) { + const bytes = switch (windows_api.code_page) { // Code page 437 is the default code page and contains the box drawing symbols 437 => symbol.bytes(.code_page_437), // UTF-8 @@ -882,7 +900,7 @@ pub fn clearWrittenWithEscapeCodes(file_writer: *Io.File.Writer) Io.Writer.Error /// U+25BA or ► const windows_api_start_marker = 0x25BA; -fn clearWrittenWindowsApi() error{Unexpected}!void { +fn clearWrittenWindowsApi(io: Io) WindowsApiError!void { // This uses a 'marker' strategy. The idea is: // - Always write a marker (in this case U+25BA or ►) at the beginning of the progress // - Get the current cursor position (at the end of the progress) @@ -903,43 +921,60 @@ fn clearWrittenWindowsApi() error{Unexpected}!void { // character in order to be readable via ReadConsoleOutputAttribute. It doesn't seem // like any of the available attributes are invisible/benign. if (!global_progress.need_clear) return; - const handle = global_progress.terminal.handle; + const terminal = global_progress.terminal; const screen_area = @as(windows.DWORD, global_progress.cols) * global_progress.rows; - var console_info: windows.CONSOLE_SCREEN_BUFFER_INFO = undefined; - if (windows.kernel32.GetConsoleScreenBufferInfo(handle, &console_info) == 0) { - return error.Unexpected; + var get_console_info = windows.CONSOLE.USER_IO.GET_SCREEN_BUFFER_INFO; + switch (try get_console_info.operate(io, terminal)) { + .SUCCESS => {}, + else => |status| return windows.unexpectedStatus(status), } - var num_chars_written: windows.DWORD = undefined; - if (windows.kernel32.FillConsoleOutputCharacterW(handle, ' ', screen_area, console_info.dwCursorPosition, &num_chars_written) == 0) { - return error.Unexpected; + var fill_spaces = windows.CONSOLE.USER_IO.FILL( + .{ .WideCharacter = ' ' }, + screen_area, + get_console_info.Data.dwCursorPosition, + ); + switch (try fill_spaces.operate(io, terminal)) { + .SUCCESS => {}, + else => |status| return windows.unexpectedStatus(status), } } -fn windowsApiMoveToMarker(nl_n: usize) error{Unexpected}!void { - const handle = global_progress.terminal.handle; - var console_info: windows.CONSOLE_SCREEN_BUFFER_INFO = undefined; - if (windows.kernel32.GetConsoleScreenBufferInfo(handle, &console_info) == 0) { - return error.Unexpected; +fn windowsApiMoveToMarker(io: Io, nl_n: usize) WindowsApiError!void { + const terminal = global_progress.terminal; + var get_console_info = windows.CONSOLE.USER_IO.GET_SCREEN_BUFFER_INFO; + switch (try get_console_info.operate(io, terminal)) { + .SUCCESS => {}, + else => |status| return windows.unexpectedStatus(status), } - const cursor_pos = console_info.dwCursorPosition; + const cursor_pos = get_console_info.Data.dwCursorPosition; const expected_y = cursor_pos.Y - @as(i16, @intCast(nl_n)); var start_pos: windows.COORD = .{ .X = 0, .Y = expected_y }; - while (start_pos.Y >= 0) { - var wchar: [1]u16 = undefined; - var num_console_chars_read: windows.DWORD = undefined; - if (windows.kernel32.ReadConsoleOutputCharacterW(handle, &wchar, wchar.len, start_pos, &num_console_chars_read) == 0) { - return error.Unexpected; + while (start_pos.Y >= 0) : (start_pos.Y -= 1) { + var read_output_char = windows.CONSOLE.USER_IO.READ_OUTPUT_CHARACTER(start_pos, .WideCharacter); + var buffer: [1]windows.WCHAR = undefined; + switch ((try io.operate(.{ .device_io_control = .{ + .file = .{ + .handle = windows.peb().ProcessParameters.ConsoleHandle, + .flags = .{ .nonblocking = false }, + }, + .code = windows.IOCTL.CONDRV.ISSUE_USER_IO, + .in = @ptrCast(&read_output_char.request(terminal, 0, .{}, 1, .{ + .{ .Size = @sizeOf(@TypeOf(buffer)), .Pointer = &buffer }, + })), + } })).device_io_control.u.Status) { + .SUCCESS => {}, + else => |status| return windows.unexpectedStatus(status), } - - if (wchar[0] == windows_api_start_marker) break; - start_pos.Y -= 1; + if (read_output_char.Data.nLength >= 1 and buffer[0] == windows_api_start_marker) break; } else { // If we couldn't find the marker, then just assume that no lines wrapped start_pos = .{ .X = 0, .Y = expected_y }; } - if (windows.kernel32.SetConsoleCursorPosition(handle, start_pos) == 0) { - return error.Unexpected; + var set_cursor_position = windows.CONSOLE.USER_IO.SET_CURSOR_POSITION(start_pos); + switch (try set_cursor_position.operate(io, terminal)) { + .SUCCESS => {}, + else => |status| return windows.unexpectedStatus(status), } } @@ -1279,7 +1314,7 @@ fn computeRedraw(io: Io, serialized_buffer: *Serialized.Buffer) !struct { []u8, buf[i..][0..clear.len].* = clear.*; i += clear.len; }, - .windows_api => if (!is_windows) unreachable, + .windows_api => {}, } const root_node_index: Node.Index = @enumFromInt(0); @@ -1491,19 +1526,17 @@ fn maybeUpdateSize(io: Io, resize_flag: bool) !void { const file = global_progress.terminal; if (is_windows) { - var info: windows.CONSOLE_SCREEN_BUFFER_INFO = undefined; - - if (windows.kernel32.GetConsoleScreenBufferInfo(file.handle, &info) != windows.FALSE) { - // In the old Windows console, dwSize.Y is the line count of the - // entire scrollback buffer, so we use this instead so that we - // always get the size of the screen. - const screen_height = info.srWindow.Bottom - info.srWindow.Top; - global_progress.rows = @intCast(screen_height); - global_progress.cols = @intCast(info.dwSize.X); - } else { - std.log.debug("failed to determine terminal size; using conservative guess 80x25", .{}); - global_progress.rows = 25; - global_progress.cols = 80; + var get_console_info = windows.CONSOLE.USER_IO.GET_SCREEN_BUFFER_INFO; + switch (try get_console_info.operate(io, file)) { + .SUCCESS => { + global_progress.rows = @intCast(get_console_info.Data.dwWindowSize.Y); + global_progress.cols = @intCast(get_console_info.Data.dwWindowSize.X); + }, + else => { + std.log.debug("failed to determine terminal size; using conservative guess 80x25", .{}); + global_progress.rows = 25; + global_progress.cols = 80; + }, } } else { var winsize: posix.winsize = .{ diff --git a/lib/std/log.zig b/lib/std/log.zig index df11fe205b4cefce36bc9d666e4d3718416bccf8..f66cb4e04d1550a7f6825cfcbc03f61ee2438cb3 100644 --- a/lib/std/log.zig +++ b/lib/std/log.zig @@ -80,7 +80,7 @@ pub fn logEnabled(comptime level: Level, comptime scope: @EnumLiteral()) bool { return @intFromEnum(level) <= @intFromEnum(std.options.log_level); } -pub const terminalMode = std.options.logTerminalMode; +pub const terminalMode = std.Options.logTerminalMode; pub fn defaultTerminalMode() std.Io.Terminal.Mode { const stderr = std.debug.lockStderr(&.{}).terminal(); @@ -99,6 +99,9 @@ pub fn defaultLog( comptime format: []const u8, args: anytype, ) void { + const io = std.Options.debug_io; + const prev = io.swapCancelProtection(.blocked); + defer _ = io.swapCancelProtection(prev); var buffer: [64]u8 = undefined; const stderr = std.debug.lockStderr(&buffer).terminal(); defer std.debug.unlockStderr(); diff --git a/lib/std/os/windows.zig b/lib/std/os/windows.zig index 0180138a0fbb4c7258181ed1d7ee6a572eb40db6..86d4ce0efd3b8170ef819544d41f2ccc16351be3 100644 --- a/lib/std/os/windows.zig +++ b/lib/std/os/windows.zig @@ -649,6 +649,235 @@ pub const FILE = struct { }; }; +pub const CONSOLE = struct { + pub const USER_IO = struct { + pub const INFO = struct { + pub const CP = extern struct { + /// GetCP: output + /// SetCP: input + CodePage: UINT, + /// input + Mode: MODE, + + pub const MODE = enum(BOOLEAN) { + Input = FALSE, + Output = TRUE, + }; + }; + + pub const WRITE = extern struct { + /// output, in bytes + Size: DWORD, + /// input + Mode: MODE, + + pub const MODE = enum(BOOLEAN) { + Character = FALSE, + WideCharacter = TRUE, + }; + }; + + pub const FILL = extern struct { + /// input + dwWriteCoord: COORD, + /// input + Tag: WITH.Tag, + /// input + With: WITH.Payload, + /// input/output, in characters + nLength: DWORD, + + pub const WITH = union(enum(DWORD)) { + Character: CHAR = 1, + WideCharacter: WCHAR = 2, + Attribute: WORD = 3, + + pub const Tag = @typeInfo(WITH).@"union".tag_type.?; + pub const Payload = PAYLOAD: { + const with_fields = @typeInfo(WITH).@"union".fields; + var field_names: [with_fields.len][]const u8 = undefined; + var field_types: [with_fields.len]type = undefined; + for (with_fields, &field_names, &field_types) |field, *field_name, *field_type| { + field_name.* = field.name; + field_type.* = field.type; + } + break :PAYLOAD @Union(.@"extern", null, &field_names, &field_types, &@splat(.{})); + }; + }; + }; + + /// all output + pub const SCREEN_BUFFER = extern struct { + dwSize: COORD, + dwCursorPosition: COORD, + dwWindowPosition: COORD, + wAttributes: WORD, + dwWindowSize: COORD, + dwMaximumWindowSize: COORD, + wPopupAttributes: WORD, + bFullscreenSupported: BOOL, + ColorTable: [16]COLORREF, + }; + + pub const READ_OUTPUT_CHARACTER = extern struct { + /// input + dwReadCoord: COORD, + Mode: MODE, + /// output, in characters + nLength: DWORD, + + pub const MODE = enum(DWORD) { + Character = 1, + WideCharacter = 2, + }; + }; + }; + + pub fn GET_CP(mode: INFO.CP.MODE) Header.With(INFO.CP) { + return .init(.GetCP, .{ .CodePage = undefined, .Mode = mode }); + } + pub const GET_MODE: Header.With(DWORD) = .init(.GetMode, undefined); + pub fn SET_MODE(mode: DWORD) Header.With(DWORD) { + return .init(.SetMode, mode); + } + pub fn WRITE(mode: INFO.WRITE.MODE) Header.With(INFO.WRITE) { + return .init(.Write, .{ .Size = undefined, .Mode = mode }); + } + pub fn FILL(with: INFO.FILL.WITH, len: DWORD, coord: COORD) Header.With(INFO.FILL) { + return .init(.Fill, .{ + .dwWriteCoord = coord, + .Tag = with, + .With = switch (with) { + inline else => |payload, tag| @unionInit( + INFO.FILL.WITH.Payload, + @tagName(tag), + payload, + ), + }, + .nLength = len, + }); + } + pub fn SET_CP(mode: INFO.CP.MODE, cp: UINT) Header.With(INFO.CP) { + return .init(.SetCP, .{ .CodePage = cp, .Mode = mode }); + } + pub const GET_SCREEN_BUFFER_INFO: Header.With(INFO.SCREEN_BUFFER) = + .init(.GetScreenBufferInfo, undefined); + pub fn SET_CURSOR_POSITION(coord: COORD) Header.With(COORD) { + return .init(.SetCursorPosition, coord); + } + pub fn SET_TEXT_ATTRIBUTE(attribute: WORD) Header.With(WORD) { + return .init(.SetTextAttribute, attribute); + } + pub fn READ_OUTPUT_CHARACTER( + coord: COORD, + mode: INFO.READ_OUTPUT_CHARACTER.MODE, + ) Header.With(INFO.READ_OUTPUT_CHARACTER) { + return .init(.ReadOutputCharacter, .{ + .dwReadCoord = coord, + .Mode = mode, + .nLength = undefined, + }); + } + + pub const InputBuffer = extern struct { + Size: u32, + Pointer: *const anyopaque, + }; + + pub const OutputBuffer = extern struct { + Size: u32, + Pointer: *anyopaque, + }; + + pub fn Request(comptime in_len: u32, comptime out_len: u32) type { + return extern struct { + Handle: ?HANDLE, + InputBuffersLength: u32, + OutputBuffersLength: u32, + InputBuffers: [in_len]InputBuffer, + OutputBuffers: [out_len]OutputBuffer, + + pub fn init( + handle: ?HANDLE, + in: [in_len]InputBuffer, + out: [out_len]OutputBuffer, + ) @This() { + return .{ + .Handle = handle, + .InputBuffersLength = in_len, + .OutputBuffersLength = out_len, + .InputBuffers = in, + .OutputBuffers = out, + }; + } + }; + } + + pub const Header = extern struct { + Operation: Operation, + Size: u32, + + pub fn With(comptime Data: type) type { + return extern struct { + Header: Header, + Data: Data, + + pub fn init(operation: Operation, data: Data) @This() { + return .{ + .Header = .{ .Operation = operation, .Size = @sizeOf(Data) }, + .Data = data, + }; + } + + pub fn request( + with: *@This(), + file: ?Io.File, + comptime in_len: u32, + in: [in_len]InputBuffer, + comptime out_len: u32, + out: [out_len]OutputBuffer, + ) Request(1 + in_len, 1 + out_len) { + return .init( + if (file) |f| f.handle else null, + [1]InputBuffer{.{ + .Size = @offsetOf(@This(), "Data") + @sizeOf(Data), + .Pointer = with, + }} ++ in, + [1]OutputBuffer{.{ .Size = @sizeOf(Data), .Pointer = &with.Data }} ++ out, + ); + } + + pub fn operate(with: *@This(), io: Io, file: ?Io.File) Io.Cancelable!NTSTATUS { + return (try io.operate(.{ .device_io_control = .{ + .file = .{ + .handle = peb().ProcessParameters.ConsoleHandle, + .flags = .{ .nonblocking = false }, + }, + .code = IOCTL.CONDRV.ISSUE_USER_IO, + .in = @ptrCast(&with.request(file, 0, .{}, 0, .{})), + } })).device_io_control.u.Status; + } + }; + } + }; + + pub const Operation = enum(u32) { + GetCP = 0x1000000, + GetMode = 0x1000001, + SetMode = 0x1000002, + Read = 0x1000005, + Write = 0x1000006, + Fill = 0x2000000, + SetCP = 0x2000004, + GetScreenBufferInfo = 0x2000007, + SetCursorPosition = 0x200000a, + SetTextAttribute = 0x200000d, + ReadOutputCharacter = 0x200000f, + _, + }; + }; +}; + // ref: km/ntddk.h pub const PROCESSINFOCLASS = enum(c_int) { @@ -1160,6 +1389,22 @@ pub const CTL_CODE = packed struct(ULONG) { }; pub const IOCTL = struct { + pub const CONDRV = struct { + pub const READ_IO: CTL_CODE = .{ .DeviceType = .CONSOLE, .Function = 1, .Method = .OUT_DIRECT, .Access = .ANY }; + pub const COMPLETE_IO: CTL_CODE = .{ .DeviceType = .CONSOLE, .Function = 2, .Method = .NEITHER, .Access = .ANY }; + pub const READ_INPUT: CTL_CODE = .{ .DeviceType = .CONSOLE, .Function = 3, .Method = .NEITHER, .Access = .ANY }; + pub const WRITE_OUTPUT: CTL_CODE = .{ .DeviceType = .CONSOLE, .Function = 4, .Method = .NEITHER, .Access = .ANY }; + pub const ISSUE_USER_IO: CTL_CODE = .{ .DeviceType = .CONSOLE, .Function = 5, .Method = .OUT_DIRECT, .Access = .ANY }; + pub const DISCONNECT_PIPE: CTL_CODE = .{ .DeviceType = .CONSOLE, .Function = 6, .Method = .NEITHER, .Access = .ANY }; + pub const SET_SERVER_INFORMATION: CTL_CODE = .{ .DeviceType = .CONSOLE, .Function = 7, .Method = .NEITHER, .Access = .ANY }; + pub const GET_SERVER_PID: CTL_CODE = .{ .DeviceType = .CONSOLE, .Function = 8, .Method = .NEITHER, .Access = .ANY }; + pub const GET_DISPLAY_SIZE: CTL_CODE = .{ .DeviceType = .CONSOLE, .Function = 9, .Method = .NEITHER, .Access = .ANY }; + pub const UPDATE_DISPLAY: CTL_CODE = .{ .DeviceType = .CONSOLE, .Function = 10, .Method = .NEITHER, .Access = .ANY }; + pub const SET_CURSOR: CTL_CODE = .{ .DeviceType = .CONSOLE, .Function = 11, .Method = .NEITHER, .Access = .ANY }; + pub const ALLOW_VIA_UIACCESS: CTL_CODE = .{ .DeviceType = .CONSOLE, .Function = 12, .Method = .NEITHER, .Access = .ANY }; + pub const LAUNCH_SERVER: CTL_CODE = .{ .DeviceType = .CONSOLE, .Function = 13, .Method = .NEITHER, .Access = .ANY }; + pub const GET_FONT_SIZE: CTL_CODE = .{ .DeviceType = .CONSOLE, .Function = 14, .Method = .NEITHER, .Access = .ANY }; + }; pub const KSEC = struct { pub const GEN_RANDOM: CTL_CODE = .{ .DeviceType = .KSEC, .Function = 2, .Method = .BUFFERED, .Access = .ANY }; }; @@ -2663,29 +2908,6 @@ pub fn NtFreeVirtualMemory(hProcess: HANDLE, addr: ?*PVOID, size: *SIZE_T, free_ }; } -pub const SetConsoleTextAttributeError = error{Unexpected}; - -pub fn SetConsoleTextAttribute(hConsoleOutput: HANDLE, wAttributes: WORD) SetConsoleTextAttributeError!void { - if (kernel32.SetConsoleTextAttribute(hConsoleOutput, wAttributes) == 0) { - switch (GetLastError()) { - else => |err| return unexpectedError(err), - } - } -} - -pub fn SetConsoleCtrlHandler(handler_routine: ?HANDLER_ROUTINE, add: bool) !void { - const success = kernel32.SetConsoleCtrlHandler( - handler_routine, - if (add) TRUE else FALSE, - ); - - if (success == FALSE) { - return switch (GetLastError()) { - else => |err| unexpectedError(err), - }; - } -} - pub fn SetFileCompletionNotificationModes(handle: HANDLE, flags: UCHAR) !void { const success = kernel32.SetFileCompletionNotificationModes(handle, flags); if (success == FALSE) { @@ -3244,6 +3466,7 @@ pub const ULONGLONG = u64; pub const LONGLONG = i64; pub const HLOCAL = HANDLE; pub const LANGID = c_ushort; +pub const COLORREF = DWORD; pub const WPARAM = usize; pub const LPARAM = LONG_PTR; @@ -3784,21 +4007,17 @@ pub const FileNotifyChangeFilter = packed struct(DWORD) { _pad: u20 = 0, }; -pub const CONSOLE_SCREEN_BUFFER_INFO = extern struct { - dwSize: COORD, - dwCursorPosition: COORD, - wAttributes: WORD, - srWindow: SMALL_RECT, - dwMaximumWindowSize: COORD, -}; - pub const ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x4; pub const DISABLE_NEWLINE_AUTO_RETURN = 0x8; -pub const FOREGROUND_BLUE = 1; -pub const FOREGROUND_GREEN = 2; -pub const FOREGROUND_RED = 4; -pub const FOREGROUND_INTENSITY = 8; +pub const FOREGROUND_BLUE = 0x0001; +pub const FOREGROUND_GREEN = 0x0002; +pub const FOREGROUND_RED = 0x0004; +pub const FOREGROUND_INTENSITY = 0x0008; +pub const BACKGROUND_BLUE = 0x0010; +pub const BACKGROUND_GREEN = 0x0020; +pub const BACKGROUND_RED = 0x0040; +pub const BACKGROUND_INTENSITY = 0x0080; pub const LIST_ENTRY = extern struct { Flink: *LIST_ENTRY, diff --git a/lib/std/os/windows/kernel32.zig b/lib/std/os/windows/kernel32.zig index d6af93cfc3add89352135762670270333ef7147c..ed9af392a3a09b5ba649f965f6ff1798eada944d 100644 --- a/lib/std/os/windows/kernel32.zig +++ b/lib/std/os/windows/kernel32.zig @@ -4,7 +4,6 @@ const windows = std.os.windows; const ACCESS_MASK = windows.ACCESS_MASK; const BOOL = windows.BOOL; const CONDITION_VARIABLE = windows.CONDITION_VARIABLE; -const CONSOLE_SCREEN_BUFFER_INFO = windows.CONSOLE_SCREEN_BUFFER_INFO; const COORD = windows.COORD; const DWORD = windows.DWORD; const FARPROC = windows.FARPROC; @@ -191,89 +190,6 @@ pub extern "kernel32" fn CreateThread( lpThreadId: ?*DWORD, ) callconv(.winapi) ?HANDLE; -// Locks, critical sections, initializers - -// TODO: -// - dwMilliseconds -> LARGE_INTEGER. -// - RtlSleepConditionVariableSRW -// - return rc != .TIMEOUT -pub extern "kernel32" fn SleepConditionVariableSRW( - ConditionVariable: *CONDITION_VARIABLE, - SRWLock: *SRWLOCK, - dwMilliseconds: DWORD, - Flags: ULONG, -) callconv(.winapi) BOOL; - -// Console management - -pub extern "kernel32" fn GetConsoleMode( - hConsoleHandle: HANDLE, - lpMode: *DWORD, -) callconv(.winapi) BOOL; - -pub extern "kernel32" fn SetConsoleMode( - hConsoleHandle: HANDLE, - dwMode: DWORD, -) callconv(.winapi) BOOL; - -pub extern "kernel32" fn GetConsoleScreenBufferInfo( - hConsoleOutput: HANDLE, - lpConsoleScreenBufferInfo: *CONSOLE_SCREEN_BUFFER_INFO, -) callconv(.winapi) BOOL; - -pub extern "kernel32" fn SetConsoleTextAttribute( - hConsoleOutput: HANDLE, - wAttributes: WORD, -) callconv(.winapi) BOOL; - -pub extern "kernel32" fn SetConsoleCtrlHandler( - HandlerRoutine: ?HANDLER_ROUTINE, - Add: BOOL, -) callconv(.winapi) BOOL; - -pub extern "kernel32" fn SetConsoleOutputCP( - wCodePageID: UINT, -) callconv(.winapi) BOOL; - -pub extern "kernel32" fn GetConsoleOutputCP() callconv(.winapi) UINT; - -pub extern "kernel32" fn FillConsoleOutputAttribute( - hConsoleOutput: HANDLE, - wAttribute: WORD, - nLength: DWORD, - dwWriteCoord: COORD, - lpNumberOfAttrsWritten: *DWORD, -) callconv(.winapi) BOOL; - -pub extern "kernel32" fn FillConsoleOutputCharacterW( - hConsoleOutput: HANDLE, - cCharacter: WCHAR, - nLength: DWORD, - dwWriteCoord: COORD, - lpNumberOfCharsWritten: *DWORD, -) callconv(.winapi) BOOL; - -pub extern "kernel32" fn SetConsoleCursorPosition( - hConsoleOutput: HANDLE, - dwCursorPosition: COORD, -) callconv(.winapi) BOOL; - -pub extern "kernel32" fn WriteConsoleW( - hConsoleOutput: HANDLE, - lpBuffer: [*]const u16, - nNumberOfCharsToWrite: DWORD, - lpNumberOfCharsWritten: ?*DWORD, - lpReserved: ?LPVOID, -) callconv(.winapi) BOOL; - -pub extern "kernel32" fn ReadConsoleOutputCharacterW( - hConsoleOutput: HANDLE, - lpCharacter: [*]u16, - nLength: DWORD, - dwReadCoord: COORD, - lpNumberOfCharsRead: *DWORD, -) callconv(.winapi) BOOL; - // Code Libraries/Modules // TODO: Wrapper around LdrGetDllFullName. diff --git a/lib/std/std.zig b/lib/std/std.zig index 3998b3247a53c6a8373fa7521e9f57164769dce2..d563cdfae7aac1003fb7d91472cd6653f78a5cee 100644 --- a/lib/std/std.zig +++ b/lib/std/std.zig @@ -135,8 +135,6 @@ pub const Options = struct { args: anytype, ) void = log.defaultLog, - logTerminalMode: fn () Io.Terminal.Mode = log.defaultTerminalMode, - /// Overrides `std.heap.page_size_min`. page_size_min: ?usize = null, /// Overrides `std.heap.page_size_max`. @@ -176,6 +174,10 @@ pub const Options = struct { /// stack traces will just print an error to the relevant `Io.Writer` and return. allow_stack_tracing: bool = !@import("builtin").strip_debug_info, + /// TODO This is a separate decl instead of a field as a workaround around + /// compilation errors due to zig not being lazy enough. + pub const logTerminalMode: fn () Io.Terminal.Mode = log.defaultTerminalMode; + /// TODO This is a separate decl instead of a field as a workaround around /// compilation errors due to zig not being lazy enough. pub const elf_debug_info_search_paths: ?fn (exe_path: []const u8) switch (@import("builtin").object_format) { diff --git a/src/libs/mingw.zig b/src/libs/mingw.zig index 568dd945512c23023823b9eced149bc2004832f0..a863f56913530548a5a83df8877d6a1db2467b2b 100644 --- a/src/libs/mingw.zig +++ b/src/libs/mingw.zig @@ -347,7 +347,7 @@ pub fn buildImportLib(comp: *Compilation, lib_name: []const u8) !void { if (msg.kind == .@"fatal error" or msg.kind == .@"error") { msg.write(stderr.terminal(), true) catch |err| switch (err) { error.WriteFailed => return stderr.file_writer.err.?, - error.Unexpected => |e| return e, + error.Canceled, error.Unexpected => |e| return e, }; return error.AroPreprocessorFailed; } -- 2.54.0 From fa3228ae42d3bc92ad66fe91e108511583129ffd Mon Sep 17 00:00:00 2001 From: Ivel Date: Thu, 5 Feb 2026 20:21:41 +0100 Subject: [PATCH 205/499] libc: reimplement swab in Zig (#31130) This PR replaces the bundled musl swab() implementation with zig's one. Contributes towards #30978. It looks like there are not test cases for swab() in test-libc. Reviewed-on: https://codeberg.org/ziglang/zig/pulls/31130 Reviewed-by: Andrew Kelley Co-authored-by: Ivel Co-committed-by: Ivel --- lib/c/unistd.zig | 42 +++++++++++++++++++++++++++++++++ lib/libc/musl/src/string/swab.c | 13 ---------- src/libs/musl.zig | 1 - src/libs/wasi_libc.zig | 1 - 4 files changed, 42 insertions(+), 15 deletions(-) delete mode 100644 lib/libc/musl/src/string/swab.c diff --git a/lib/c/unistd.zig b/lib/c/unistd.zig index f903ef78a8d16a280b959e48a54b91a7635a5e00..98290f0e04a0621d43ff521ecebde45beafdf965 100644 --- a/lib/c/unistd.zig +++ b/lib/c/unistd.zig @@ -43,6 +43,9 @@ comptime { @export(&execveLinux, .{ .name = "execve", .linkage = common.linkage, .visibility = common.visibility }); } + if (builtin.target.isMuslLibC() or builtin.target.isWasiLibC()) { + @export(&swab, .{ .name = "swab", .linkage = common.linkage, .visibility = common.visibility }); + } } fn _exit(exit_code: c_int) callconv(.c) noreturn { @@ -181,3 +184,42 @@ fn unlinkatLinux(fd: c_int, path: [*:0]const c_char, flags: c_int) callconv(.c) fn execveLinux(path: [*:0]const c_char, argv: [*:null]const ?[*:0]c_char, envp: [*:null]const ?[*:0]c_char) callconv(.c) c_int { return common.errno(linux.execve(@ptrCast(path), @ptrCast(argv), @ptrCast(envp))); } + +fn swab(noalias src_ptr: *const anyopaque, noalias dest_ptr: *anyopaque, n: isize) callconv(.c) void { + var src: [*]const u8 = @ptrCast(src_ptr); + var dest: [*]u8 = @ptrCast(dest_ptr); + var i = n; + + while (i > 1) : (i -= 2) { + dest[0] = src[1]; + dest[1] = src[0]; + dest += 2; + src += 2; + } +} + +test swab { + var a: [4]u8 = undefined; + @memset(a[0..], '\x00'); + swab("abcd", &a, 4); + try std.testing.expectEqualSlices(u8, "badc", &a); + + // Partial copy + @memset(a[0..], '\x00'); + swab("abcd", &a, 2); + try std.testing.expectEqualSlices(u8, "ba\x00\x00", &a); + + // n < 1 + @memset(a[0..], '\x00'); + swab("abcd", &a, 0); + try std.testing.expectEqualSlices(u8, "\x00" ** 4, &a); + swab("abcd", &a, -1); + try std.testing.expectEqualSlices(u8, "\x00" ** 4, &a); + + // Odd n + @memset(a[0..], '\x00'); + swab("abcd", &a, 1); + try std.testing.expectEqualSlices(u8, "\x00" ** 4, &a); + swab("abcd", &a, 3); + try std.testing.expectEqualSlices(u8, "ba\x00\x00", &a); +} diff --git a/lib/libc/musl/src/string/swab.c b/lib/libc/musl/src/string/swab.c deleted file mode 100644 index ace0f4666dd613d58bcf2c93cfd70b37d3f3ae28..0000000000000000000000000000000000000000 --- a/lib/libc/musl/src/string/swab.c +++ /dev/null @@ -1,13 +0,0 @@ -#include - -void swab(const void *restrict _src, void *restrict _dest, ssize_t n) -{ - const char *src = _src; - char *dest = _dest; - for (; n>1; n-=2) { - dest[0] = src[1]; - dest[1] = src[0]; - dest += 2; - src += 2; - } -} diff --git a/src/libs/musl.zig b/src/libs/musl.zig index 1974e0e4ea3765ca3de2c8f7adaedc817280dc0d..ebbd28566529c41fc3529ff361bc4228f7c9299a 100644 --- a/src/libs/musl.zig +++ b/src/libs/musl.zig @@ -1579,7 +1579,6 @@ const src_files = [_][]const u8{ "musl/src/string/strndup.c", "musl/src/string/strsignal.c", "musl/src/string/strverscmp.c", - "musl/src/string/swab.c", "musl/src/string/wcscasecmp.c", "musl/src/string/wcscasecmp_l.c", "musl/src/string/wcsdup.c", diff --git a/src/libs/wasi_libc.zig b/src/libs/wasi_libc.zig index 8e0ade2a0d0f678c6a028c324f46611a644ccf92..24a0ead5988a761b1a86b16e38cd438177cceb89 100644 --- a/src/libs/wasi_libc.zig +++ b/src/libs/wasi_libc.zig @@ -957,7 +957,6 @@ const libc_top_half_src_files = [_][]const u8{ "musl/src/string/strerror_r.c", "musl/src/string/strndup.c", "musl/src/string/strverscmp.c", - "musl/src/string/swab.c", "musl/src/string/wcscasecmp.c", "musl/src/string/wcscasecmp_l.c", "musl/src/string/wcsdup.c", -- 2.54.0 From bcb5218a2b2bce189831e68b3396cfd6f246caa2 Mon Sep 17 00:00:00 2001 From: Ryan Liptak Date: Wed, 4 Feb 2026 18:12:06 -0800 Subject: [PATCH 206/499] Environ: reinstate `null` return on `=` in environment variable keys Changes an assert back into a conditional to match the behavior of `getPosix`, see https://codeberg.org/ziglang/zig/pulls/31113#issuecomment-10371698 and https://github.com/ziglang/zig/issues/23331. Note: the conditional has been updated to also return null early on 0-length key lookups, since there's no need to iterate the block in that case. For `Environ.Map`, validation of keys has been split into two categories: 'put' and 'fetch', each of which are tailored to the constraints that the implementation actually relies upon. Specifically: - Hashing (fetching) requires the keys to be valid WTF-8 on Windows, but does not rely on any other properties of the keys (attempting to fetch `F\x00=` is not a problem, it just won't be found) - `create{Posix,Windows}Block` relies on the Map to always have fully valid keys (no NUL, no `=` in an invalid location, no zero-length keys), which means that the 'put' APIs need to validate that incoming keys adhere to those properties. The relevant assertions are now documented on each of the Map functions. Also reinstates some test cases in the `env_vars` standalone test. Some of the reinstated tests are effectively just testing the Environ.Map implementation due to how `Environ.contains`, `Environ.getAlloc`, etc are implemented, but that is not inherent to those functions so the tests are still potentially relevant if e.g. `contains` is implemented in terms of `getPosix`/`getWindows` in the future (which is totally possible and maybe a good idea since constructing the whole map is not necessary for looking up one key). --- lib/std/process/Environ.zig | 57 +++++++++++++++++-------------- test/standalone/env_vars/main.zig | 23 +++++++++++++ 2 files changed, 55 insertions(+), 25 deletions(-) diff --git a/lib/std/process/Environ.zig b/lib/std/process/Environ.zig index e33024de3525fcabfaa08a5be5fc8e80dd4dd026..45e2385c99a6d56317714b40f2b1161f1613caea 100644 --- a/lib/std/process/Environ.zig +++ b/lib/std/process/Environ.zig @@ -129,25 +129,21 @@ pub const Map = struct { }; } - pub fn validateKey(key: []const u8) bool { + pub fn validateKeyForPut(key: []const u8) bool { switch (native_os) { else => return key.len > 0 and mem.findAny(u8, key, &.{ 0, '=' }) == null, .windows => { if (!unicode.wtf8ValidateSlice(key)) return false; - var it = unicode.Wtf8View.initUnchecked(key).iterator(); - switch (it.nextCodepoint() orelse return false) { - 0 => return false, - else => {}, - } - while (it.nextCodepoint()) |cp| switch (cp) { - 0, '=' => return false, - else => {}, - }; - return true; + return key.len > 0 and key[0] != 0 and mem.findAnyPos(u8, key, 1, &.{ 0, '=' }) == null; }, } } + pub fn validateKeyForFetch(key: []const u8) bool { + if (native_os == .windows and !unicode.wtf8ValidateSlice(key)) return false; + return true; + } + /// Create a Map backed by a specific allocator. /// That allocator will be used for both backing allocations /// and string deduplication. @@ -220,9 +216,14 @@ pub const Map = struct { /// Same as `put` but the key and value become owned by the Map rather /// than being copied. /// If `putMove` fails, the ownership of key and value does not transfer. - /// On Windows `key` must be a valid [WTF-8](https://wtf-8.codeberg.page/) string. + /// + /// Asserts that `key` is valid: + /// - It cannot contain a NUL (`'\x00') byte. + /// - It must have a length > 0. + /// - It cannot contain `=`, except on Windows where only the first code point is allowed to be `=`. + /// - On Windows, it must be valid [WTF-8](https://wtf-8.codeberg.page/). pub fn putMove(self: *Map, key: []u8, value: []u8) Allocator.Error!void { - assert(validateKey(key)); + assert(validateKeyForPut(key)); const gpa = self.allocator; const get_or_put = try self.array_hash_map.getOrPut(gpa, key); if (get_or_put.found_existing) { @@ -234,9 +235,14 @@ pub const Map = struct { } /// `key` and `value` are copied into the Map. - /// On Windows `key` must be a valid [WTF-8](https://wtf-8.codeberg.page/) string. + /// + /// Asserts that `key` is valid: + /// - It cannot contain a NUL (`'\x00') byte. + /// - It must have a length > 0. + /// - It cannot contain `=`, except on Windows where only the first code point is allowed to be `=`. + /// - On Windows, it must be valid [WTF-8](https://wtf-8.codeberg.page/). pub fn put(self: *Map, key: []const u8, value: []const u8) Allocator.Error!void { - assert(validateKey(key)); + assert(validateKeyForPut(key)); const gpa = self.allocator; const value_copy = try gpa.dupe(u8, value); errdefer gpa.free(value_copy); @@ -254,23 +260,24 @@ pub const Map = struct { /// Find the address of the value associated with a key. /// The returned pointer is invalidated if the map resizes. - /// On Windows `key` must be a valid [WTF-8](https://wtf-8.codeberg.page/) string. + /// On Windows, asserts that `key` is valid [WTF-8](https://wtf-8.codeberg.page/). pub fn getPtr(self: Map, key: []const u8) ?*[]const u8 { - assert(validateKey(key)); + assert(validateKeyForFetch(key)); return self.array_hash_map.getPtr(key); } /// Return the map's copy of the value associated with /// a key. The returned string is invalidated if this /// key is removed from the map. - /// On Windows `key` must be a valid [WTF-8](https://wtf-8.codeberg.page/) string. + /// On Windows, asserts that `key` is valid [WTF-8](https://wtf-8.codeberg.page/). pub fn get(self: Map, key: []const u8) ?[]const u8 { - assert(validateKey(key)); + assert(validateKeyForFetch(key)); return self.array_hash_map.get(key); } + /// On Windows, asserts that `key` is valid [WTF-8](https://wtf-8.codeberg.page/). pub fn contains(m: *const Map, key: []const u8) bool { - assert(validateKey(key)); + assert(validateKeyForFetch(key)); return m.array_hash_map.contains(key); } @@ -281,9 +288,9 @@ pub const Map = struct { /// Returns true if an entry was removed, false otherwise. /// /// This invalidates the value returned by get() for this key. - /// On Windows `key` must be a valid [WTF-8](https://wtf-8.codeberg.page/) string. + /// On Windows, asserts that `key` is valid [WTF-8](https://wtf-8.codeberg.page/). pub fn swapRemove(self: *Map, key: []const u8) bool { - assert(validateKey(key)); + assert(validateKeyForFetch(key)); const kv = self.array_hash_map.fetchSwapRemove(key) orelse return false; const gpa = self.allocator; gpa.free(kv.key); @@ -298,9 +305,9 @@ pub const Map = struct { /// Returns true if an entry was removed, false otherwise. /// /// This invalidates the value returned by get() for this key. - /// On Windows `key` must be a valid [WTF-8](https://wtf-8.codeberg.page/) string. + /// On Windows, asserts that `key` is valid [WTF-8](https://wtf-8.codeberg.page/). pub fn orderedRemove(self: *Map, key: []const u8) bool { - assert(validateKey(key)); + assert(validateKeyForFetch(key)); const kv = self.array_hash_map.fetchOrderedRemove(key) orelse return false; const gpa = self.allocator; gpa.free(kv.key); @@ -612,7 +619,7 @@ pub fn getPosix(environ: Environ, key: []const u8) ?[:0]const u8 { pub fn getWindows(environ: Environ, key: [*:0]const u16) ?[:0]const u16 { // '=' anywhere but the start makes this an invalid environment variable name. const key_slice = mem.sliceTo(key, 0); - assert(key_slice.len > 0 and mem.findScalar(u16, key_slice[1..], '=') == null); + if (key_slice.len == 0 or mem.findScalar(u16, key_slice[1..], '=') != null) return null; if (!environ.block.use_global) return null; diff --git a/test/standalone/env_vars/main.zig b/test/standalone/env_vars/main.zig index 6ffcae81aecb867afbcf91cdbf92b2678ff575ac..09167f285fe9960fbedb8f9fcb179bc354ca15f4 100644 --- a/test/standalone/env_vars/main.zig +++ b/test/standalone/env_vars/main.zig @@ -12,10 +12,14 @@ pub fn main(init: std.process.Init) !void { // containsUnempty { try std.testing.expect(try environ.containsUnempty(allocator, "FOO")); + try std.testing.expect(!(try environ.containsUnempty(allocator, "FOO="))); + try std.testing.expect(!(try environ.containsUnempty(allocator, "FO"))); + try std.testing.expect(!(try environ.containsUnempty(allocator, "FOOO"))); if (builtin.os.tag == .windows) { try std.testing.expect(try environ.containsUnempty(allocator, "foo")); } try std.testing.expect(try environ.containsUnempty(allocator, "EQUALS")); + try std.testing.expect(!(try environ.containsUnempty(allocator, "EQUALS=ABC"))); try std.testing.expect(try environ.containsUnempty(allocator, "КИРиллИЦА")); if (builtin.os.tag == .windows) { try std.testing.expect(try environ.containsUnempty(allocator, "кирИЛЛица")); @@ -31,10 +35,14 @@ pub fn main(init: std.process.Init) !void { // containsUnemptyConstant { try std.testing.expect(environ.containsUnemptyConstant("FOO")); + try std.testing.expect(!environ.containsUnemptyConstant("FOO=")); + try std.testing.expect(!environ.containsUnemptyConstant("FO")); + try std.testing.expect(!environ.containsUnemptyConstant("FOOO")); if (builtin.os.tag == .windows) { try std.testing.expect(environ.containsUnemptyConstant("foo")); } try std.testing.expect(environ.containsUnemptyConstant("EQUALS")); + try std.testing.expect(!environ.containsUnemptyConstant("EQUALS=ABC")); try std.testing.expect(environ.containsUnemptyConstant("КИРиллИЦА")); if (builtin.os.tag == .windows) { try std.testing.expect(environ.containsUnemptyConstant("кирИЛЛица")); @@ -50,10 +58,14 @@ pub fn main(init: std.process.Init) !void { // contains { try std.testing.expect(try environ.contains(allocator, "FOO")); + try std.testing.expect(!(try environ.contains(allocator, "FOO="))); + try std.testing.expect(!(try environ.contains(allocator, "FO"))); + try std.testing.expect(!(try environ.contains(allocator, "FOOO"))); if (builtin.os.tag == .windows) { try std.testing.expect(try environ.contains(allocator, "foo")); } try std.testing.expect(try environ.contains(allocator, "EQUALS")); + try std.testing.expect(!(try environ.contains(allocator, "EQUALS=ABC"))); try std.testing.expect(try environ.contains(allocator, "КИРиллИЦА")); if (builtin.os.tag == .windows) { try std.testing.expect(try environ.contains(allocator, "кирИЛЛица")); @@ -69,10 +81,14 @@ pub fn main(init: std.process.Init) !void { // containsConstant { try std.testing.expect(environ.containsConstant("FOO")); + try std.testing.expect(!environ.containsConstant("FOO=")); + try std.testing.expect(!environ.containsConstant("FO")); + try std.testing.expect(!environ.containsConstant("FOOO")); if (builtin.os.tag == .windows) { try std.testing.expect(environ.containsConstant("foo")); } try std.testing.expect(environ.containsConstant("EQUALS")); + try std.testing.expect(!environ.containsConstant("EQUALS=ABC")); try std.testing.expect(environ.containsConstant("КИРиллИЦА")); if (builtin.os.tag == .windows) { try std.testing.expect(environ.containsConstant("кирИЛЛица")); @@ -88,10 +104,14 @@ pub fn main(init: std.process.Init) !void { // getAlloc { try std.testing.expectEqualSlices(u8, "123", try environ.getAlloc(arena, "FOO")); + try std.testing.expectError(error.EnvironmentVariableMissing, environ.getAlloc(arena, "FOO=")); + try std.testing.expectError(error.EnvironmentVariableMissing, environ.getAlloc(arena, "FO")); + try std.testing.expectError(error.EnvironmentVariableMissing, environ.getAlloc(arena, "FOOO")); if (builtin.os.tag == .windows) { try std.testing.expectEqualSlices(u8, "123", try environ.getAlloc(arena, "foo")); } try std.testing.expectEqualSlices(u8, "ABC=123", try environ.getAlloc(arena, "EQUALS")); + try std.testing.expectError(error.EnvironmentVariableMissing, environ.getAlloc(arena, "EQUALS=ABC")); try std.testing.expectEqualSlices(u8, "non-ascii አማርኛ \u{10FFFF}", try environ.getAlloc(arena, "КИРиллИЦА")); if (builtin.os.tag == .windows) { try std.testing.expectEqualSlices(u8, "non-ascii አማርኛ \u{10FFFF}", try environ.getAlloc(arena, "кирИЛЛица")); @@ -110,10 +130,13 @@ pub fn main(init: std.process.Init) !void { defer environ_map.deinit(); try std.testing.expectEqualSlices(u8, "123", environ_map.get("FOO").?); + try std.testing.expectEqual(null, environ_map.get("FO")); + try std.testing.expectEqual(null, environ_map.get("FOOO")); if (builtin.os.tag == .windows) { try std.testing.expectEqualSlices(u8, "123", environ_map.get("foo").?); } try std.testing.expectEqualSlices(u8, "ABC=123", environ_map.get("EQUALS").?); + try std.testing.expectEqual(null, environ_map.get("EQUALS=ABC")); try std.testing.expectEqualSlices(u8, "non-ascii አማርኛ \u{10FFFF}", environ_map.get("КИРиллИЦА").?); if (builtin.os.tag == .windows) { try std.testing.expectEqualSlices(u8, "non-ascii አማርኛ \u{10FFFF}", environ_map.get("кирИЛЛица").?); -- 2.54.0 From c38f9336a3157ae863a795b005db670c6cc04906 Mon Sep 17 00:00:00 2001 From: brickmonster <92665597+brickmonster@users.noreply.github.com> Date: Wed, 4 Feb 2026 23:50:42 +0000 Subject: [PATCH 207/499] std.os.linux: fix test not building --- lib/std/os/linux/bpf.zig | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/lib/std/os/linux/bpf.zig b/lib/std/os/linux/bpf.zig index b7c5c7d4e9b81a4cc8627743ea64913d958bf3b4..85f7d677e8f335871f0b751c5b5634bcf4205533 100644 --- a/lib/std/os/linux/bpf.zig +++ b/lib/std/os/linux/bpf.zig @@ -1555,7 +1555,7 @@ pub fn map_create(map_type: MapType, key_size: u32, value_size: u32, max_entries test "map_create" { const map = try map_create(.hash, 4, 4, 32); - defer std.os.close(map); + defer _ = std.os.linux.close(map); } pub fn map_lookup_elem(fd: fd_t, key: []const u8, value: []u8) !void { @@ -1647,7 +1647,7 @@ test "map lookup, update, and delete" { const key_size = 4; const value_size = 4; const map = try map_create(.hash, key_size, value_size, 1); - defer std.os.close(map); + defer _ = std.os.linux.close(map); const key = std.mem.zeroes([key_size]u8); var value = std.mem.zeroes([value_size]u8); @@ -1729,7 +1729,7 @@ test "prog_load" { }; const prog = try prog_load(.socket_filter, &good_prog, null, "MIT", 0, 0); - defer std.os.close(prog); + defer _ = std.os.linux.close(prog); try expectError(error.UnsafeProgram, prog_load(.socket_filter, &bad_prog, null, "MIT", 0, 0)); } -- 2.54.0 From d0b39c7f2b95522c262a92b97bf5654721ace1c7 Mon Sep 17 00:00:00 2001 From: Pivok Date: Thu, 5 Feb 2026 21:57:32 +0100 Subject: [PATCH 208/499] libzigc: hypot (#31104) First time contribution. Implements hypot for libzigc #30978. Commands i run: ``` $ stage3/bin/zig build -p stage4 -Denable-llvm -Dno-lib $ stage4/bin/zig build test-libc -Dlibc-test-path=../../libc-test -Dtest-filter=hypot --summary line -fqemu -fwasmtime Build Summary: 725/737 steps succeeded (12 skipped) ``` I also changed std.math.hypot becuase some libc-tests raised fp exceptions. Example: ``` ../../libc-test/src/math/special/hypot.h:8: bad fp exception: RN hypot(0x1p-1074,0x0p+0)=0x1p-1074, want 0 got INEXACT|UNDERFLOW ../../libc-test/src/math/special/hypot.h:9: bad fp exception: RN hypot(0x1p-1074,-0x0p+0)=0x1p-1074, want 0 got INEXACT|UNDERFLOW ``` I also run this command as a quick sanity check: ``` $ stage4/bin/zig build test-std -Dtest-filter=hypot -Dtest-target-filter=x86_64-linux-musl --summary line Build Summary: 5/5 steps succeeded; 136/136 tests passed ``` Reviewed-on: https://codeberg.org/ziglang/zig/pulls/31104 Reviewed-by: Andrew Kelley Co-authored-by: Pivok Co-committed-by: Pivok --- lib/c/math.zig | 5 +++ lib/libc/musl/src/math/hypot.c | 67 ----------------------------- lib/libc/musl/src/math/i386/hypot.s | 45 ------------------- lib/std/math/hypot.zig | 8 ++-- src/libs/musl.zig | 2 - src/libs/wasi_libc.zig | 1 - 6 files changed, 9 insertions(+), 119 deletions(-) delete mode 100644 lib/libc/musl/src/math/hypot.c delete mode 100644 lib/libc/musl/src/math/i386/hypot.s diff --git a/lib/c/math.zig b/lib/c/math.zig index 8fc7c0322878b7b6862392abe3158a05b7adda11..403e8f9d471fefee67c667c7afa73c443ccd34c3 100644 --- a/lib/c/math.zig +++ b/lib/c/math.zig @@ -41,6 +41,7 @@ comptime { @export(&atanl, .{ .name = "atanl", .linkage = common.linkage, .visibility = common.visibility }); @export(&cbrt, .{ .name = "cbrt", .linkage = common.linkage, .visibility = common.visibility }); @export(&cbrtf, .{ .name = "cbrtf", .linkage = common.linkage, .visibility = common.visibility }); + @export(&hypot, .{ .name = "hypot", .linkage = common.linkage, .visibility = common.visibility }); @export(&pow, .{ .name = "pow", .linkage = common.linkage, .visibility = common.visibility }); } @@ -118,6 +119,10 @@ fn cbrtf(x: f32) callconv(.c) f32 { return math.cbrt(x); } +fn hypot(x: f64, y: f64) callconv(.c) f64 { + return math.hypot(x, y); +} + fn pow(x: f64, y: f64) callconv(.c) f64 { return math.pow(f64, x, y); } diff --git a/lib/libc/musl/src/math/hypot.c b/lib/libc/musl/src/math/hypot.c deleted file mode 100644 index 6071bf1e284f376f417dbab5bc306964a7491f3f..0000000000000000000000000000000000000000 --- a/lib/libc/musl/src/math/hypot.c +++ /dev/null @@ -1,67 +0,0 @@ -#include -#include -#include - -#if FLT_EVAL_METHOD > 1U && LDBL_MANT_DIG == 64 -#define SPLIT (0x1p32 + 1) -#else -#define SPLIT (0x1p27 + 1) -#endif - -static void sq(double_t *hi, double_t *lo, double x) -{ - double_t xh, xl, xc; - - xc = (double_t)x*SPLIT; - xh = x - xc + xc; - xl = x - xh; - *hi = (double_t)x*x; - *lo = xh*xh - *hi + 2*xh*xl + xl*xl; -} - -double hypot(double x, double y) -{ - union {double f; uint64_t i;} ux = {x}, uy = {y}, ut; - int ex, ey; - double_t hx, lx, hy, ly, z; - - /* arrange |x| >= |y| */ - ux.i &= -1ULL>>1; - uy.i &= -1ULL>>1; - if (ux.i < uy.i) { - ut = ux; - ux = uy; - uy = ut; - } - - /* special cases */ - ex = ux.i>>52; - ey = uy.i>>52; - x = ux.f; - y = uy.f; - /* note: hypot(inf,nan) == inf */ - if (ey == 0x7ff) - return y; - if (ex == 0x7ff || uy.i == 0) - return x; - /* note: hypot(x,y) ~= x + y*y/x/2 with inexact for small y/x */ - /* 64 difference is enough for ld80 double_t */ - if (ex - ey > 64) - return x + y; - - /* precise sqrt argument in nearest rounding mode without overflow */ - /* xh*xh must not overflow and xl*xl must not underflow in sq */ - z = 1; - if (ex > 0x3ff+510) { - z = 0x1p700; - x *= 0x1p-700; - y *= 0x1p-700; - } else if (ey < 0x3ff-450) { - z = 0x1p-700; - x *= 0x1p700; - y *= 0x1p700; - } - sq(&hx, &lx, x); - sq(&hy, &ly, y); - return z*sqrt(ly+lx+hy+hx); -} diff --git a/lib/libc/musl/src/math/i386/hypot.s b/lib/libc/musl/src/math/i386/hypot.s deleted file mode 100644 index 299c2e186cab4d9287e08e7eb79522790a35b6fc..0000000000000000000000000000000000000000 --- a/lib/libc/musl/src/math/i386/hypot.s +++ /dev/null @@ -1,45 +0,0 @@ -.global hypot -.type hypot,@function -hypot: - mov 8(%esp),%eax - mov 16(%esp),%ecx - add %eax,%eax - add %ecx,%ecx - and %eax,%ecx - cmp $0xffe00000,%ecx - jae 2f - or 4(%esp),%eax - jnz 1f - fldl 12(%esp) - fabs - ret -1: mov 16(%esp),%eax - add %eax,%eax - or 12(%esp),%eax - jnz 1f - fldl 4(%esp) - fabs - ret -1: fldl 4(%esp) - fld %st(0) - fmulp - fldl 12(%esp) - fld %st(0) - fmulp - faddp - fsqrt - ret -2: sub $0xffe00000,%eax - or 4(%esp),%eax - jnz 1f - fldl 4(%esp) - fabs - ret -1: mov 16(%esp),%eax - add %eax,%eax - sub $0xffe00000,%eax - or 12(%esp),%eax - fldl 12(%esp) - jnz 1f - fabs -1: ret diff --git a/lib/std/math/hypot.zig b/lib/std/math/hypot.zig index f95c3c0bd4b95cc8fdaa748e60b3450e258499ef..40a9518cf49dcbb207af382e0083c8dc2fb2b919 100644 --- a/lib/std/math/hypot.zig +++ b/lib/std/math/hypot.zig @@ -6,10 +6,10 @@ const isNan = math.isNan; const isInf = math.isInf; const inf = math.inf; const nan = math.nan; -const floatEpsAt = math.floatEpsAt; const floatEps = math.floatEps; const floatMin = math.floatMin; const floatMax = math.floatMax; +const floatTrueMin = math.floatTrueMin; /// Returns sqrt(x * x + y * y), avoiding unnecessary overflow and underflow. /// @@ -30,8 +30,7 @@ pub fn hypot(x: anytype, y: anytype) @TypeOf(x, y) { } const lower = @sqrt(floatMin(T)); const upper = @sqrt(floatMax(T) / 2); - const incre = @sqrt(floatEps(T) / 2); - const scale = floatEpsAt(T, incre); + const scale = floatTrueMin(T) * upper; const hypfn = if (emulateFma(T)) hypotUnfused else hypotFused; var major: T = x; var minor: T = y; @@ -46,7 +45,8 @@ pub fn hypot(x: anytype, y: anytype) @TypeOf(x, y) { major = minor; minor = tempo; } - if (major * incre >= minor) return major; + if (minor == 0.0) return major; + if (major - minor == major) return major; if (major > upper) return hypfn(T, major * scale, minor * scale) / scale; if (minor < lower) return hypfn(T, major / scale, minor / scale) * scale; return hypfn(T, major, minor); diff --git a/src/libs/musl.zig b/src/libs/musl.zig index ebbd28566529c41fc3529ff361bc4228f7c9299a..8fd59eb36f7253676408b7a93b91c218b85aef7e 100644 --- a/src/libs/musl.zig +++ b/src/libs/musl.zig @@ -874,7 +874,6 @@ const src_files = [_][]const u8{ "musl/src/math/frexp.c", "musl/src/math/frexpf.c", "musl/src/math/frexpl.c", - "musl/src/math/hypot.c", "musl/src/math/hypotf.c", "musl/src/math/hypotl.c", "musl/src/math/i386/acosf.s", @@ -890,7 +889,6 @@ const src_files = [_][]const u8{ "musl/src/math/i386/expl.s", "musl/src/math/i386/expm1l.s", "musl/src/math/i386/hypotf.s", - "musl/src/math/i386/hypot.s", "musl/src/math/i386/__invtrigl.s", "musl/src/math/i386/ldexpf.s", "musl/src/math/i386/ldexpl.s", diff --git a/src/libs/wasi_libc.zig b/src/libs/wasi_libc.zig index 24a0ead5988a761b1a86b16e38cd438177cceb89..350a4184bfef6351a934e6efdb17b7aebed21f0f 100644 --- a/src/libs/wasi_libc.zig +++ b/src/libs/wasi_libc.zig @@ -730,7 +730,6 @@ const libc_top_half_src_files = [_][]const u8{ "musl/src/math/frexp.c", "musl/src/math/frexpf.c", "musl/src/math/frexpl.c", - "musl/src/math/hypot.c", "musl/src/math/hypotf.c", "musl/src/math/hypotl.c", "musl/src/math/ilogb.c", -- 2.54.0 From 076f7e5bd5389e159865d99e0e86edc905cffc42 Mon Sep 17 00:00:00 2001 From: bgthompson Date: Sat, 31 Jan 2026 22:44:21 +1000 Subject: [PATCH 209/499] removed reduntant @as() from switch in getDaysInMonth --- lib/std/time/epoch.zig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/std/time/epoch.zig b/lib/std/time/epoch.zig index fa7499aec7ec4e86ecd17f200baec90bb8be63d3..4139e82429e5ede0e9afb3f2fb6c488e475cda5c 100644 --- a/lib/std/time/epoch.zig +++ b/lib/std/time/epoch.zig @@ -89,10 +89,10 @@ pub const Month = enum(u4) { pub fn getDaysInMonth(year: Year, month: Month) u5 { return switch (month) { .jan => 31, - .feb => @as(u5, switch (isLeapYear(year)) { + .feb => switch (isLeapYear(year)) { true => 29, false => 28, - }), + }, .mar => 31, .apr => 30, .may => 31, -- 2.54.0 From 387d550b6c3e73989513ea720437fdc5aac195d3 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 4 Feb 2026 13:15:53 -0800 Subject: [PATCH 210/499] compiler: remove btrfs workaround functionality has been fixed in the kernel code for a while now --- lib/std/zig.zig | 1 - src/Package/Fetch.zig | 13 +------------ src/main.zig | 10 +--------- 3 files changed, 2 insertions(+), 22 deletions(-) diff --git a/lib/std/zig.zig b/lib/std/zig.zig index 4f0f47b11f3dfe5b39bcbbef50b562f7a3f87189..16c15e51097200758df482cc5b6487619011234d 100644 --- a/lib/std/zig.zig +++ b/lib/std/zig.zig @@ -737,7 +737,6 @@ pub const EnvVar = enum { ZIG_BUILD_MULTILINE_ERRORS, ZIG_VERBOSE_LINK, ZIG_VERBOSE_CC, - ZIG_BTRFS_WORKAROUND, ZIG_DEBUG_CMD, ZIG_IS_DETECTING_LIBC_PATHS, ZIG_IS_TRYING_TO_NOT_CALL_ITSELF, diff --git a/src/Package/Fetch.zig b/src/Package/Fetch.zig index d42e441d77c72736ebb35c1c5e70fcbdb315d884..5a600421e9e24c5eb905290dcd57512f94668548 100644 --- a/src/Package/Fetch.zig +++ b/src/Package/Fetch.zig @@ -129,7 +129,6 @@ pub const JobQueue = struct { /// two hashes of the same package do not match. /// If this is true, `recursive` must be false. debug_hash: bool, - work_around_btrfs_bug: bool, mode: Mode, /// Set of hashes that will be additionally fetched even if they are marked /// as lazy. @@ -524,16 +523,7 @@ fn runResource( // Fetch and unpack a resource into a temporary directory. var unpack_result = try unpackResource(f, resource, uri_path, tmp_directory); - var pkg_path: Cache.Path = .{ .root_dir = tmp_directory, .sub_path = unpack_result.root_dir }; - - // Apply btrfs workaround if needed. Reopen tmp_directory. - if (native_os == .linux and f.job_queue.work_around_btrfs_bug) { - // https://github.com/ziglang/zig/issues/17095 - pkg_path.root_dir.handle.close(io); - pkg_path.root_dir.handle = cache_root.handle.createDirPathOpen(io, tmp_dir_sub_path, .{ - .open_options = .{ .iterate = true }, - }) catch @panic("btrfs workaround failed"); - } + const pkg_path: Cache.Path = .{ .root_dir = tmp_directory, .sub_path = unpack_result.root_dir }; // Load, parse, and validate the unpacked build.zig.zon file. It is allowed // for the file to be missing, in which case this fetched package is @@ -2276,7 +2266,6 @@ const TestFetchBuilder = struct { .recursive = false, .read_only = false, .debug_hash = false, - .work_around_btrfs_bug = false, .mode = .needed, }; diff --git a/src/main.zig b/src/main.zig index 0efab88f6ada4d8f830a0d7614fa2e3462ba0332..1e9c0de6564be4e81661182f07877fd3a7a17cbc 100644 --- a/src/main.zig +++ b/src/main.zig @@ -5098,8 +5098,6 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8, } } - const work_around_btrfs_bug = native_os == .linux and - EnvVar.ZIG_BTRFS_WORKAROUND.isSet(environ_map); const root_prog_node = std.Progress.start(io, .{ .disable_printing = (color == .off), .root_name = "Compile Build Script", @@ -5244,7 +5242,6 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8, .read_only = false, .recursive = true, .debug_hash = false, - .work_around_btrfs_bug = work_around_btrfs_bug, .unlazy_set = unlazy_set, .mode = fetch_mode, }; @@ -5254,9 +5251,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8, job_queue.global_cache = .{ .path = p, .handle = Io.Dir.cwd().openDir(io, p, .{}) catch |err| { - fatal("unable to open system package directory '{s}': {s}", .{ - p, @errorName(err), - }); + fatal("unable to open system package directory '{s}': {t}", .{ p, err }); }, }; job_queue.read_only = true; @@ -6938,8 +6933,6 @@ fn cmdFetch( dev.check(.fetch_command); const color: Color = .auto; - const work_around_btrfs_bug = native_os == .linux and - EnvVar.ZIG_BTRFS_WORKAROUND.isSet(environ_map); var opt_path_or_url: ?[]const u8 = null; var override_global_cache_dir: ?[]const u8 = EnvVar.ZIG_GLOBAL_CACHE_DIR.get(environ_map); var debug_hash: bool = false; @@ -7010,7 +7003,6 @@ fn cmdFetch( .recursive = false, .read_only = false, .debug_hash = debug_hash, - .work_around_btrfs_bug = work_around_btrfs_bug, .mode = .all, }; defer job_queue.deinit(); -- 2.54.0 From 76d275b20f4ba420f741e019a7456ca392c040e4 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 4 Feb 2026 15:31:42 -0800 Subject: [PATCH 211/499] std.Io.Threaded: flatten some switch cases --- lib/std/Io/Threaded.zig | 80 ++++++++++++++++++----------------------- 1 file changed, 35 insertions(+), 45 deletions(-) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index ab2e9af75765de1f30b6fa6c2f087921ffa54e9b..f998466e6e4b2aac295f4f40bd1d64b676aca179 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -3191,29 +3191,24 @@ fn dirCreateDirPosix(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, perm try syscall.checkCancel(); continue; }, - else => |e| { - syscall.finish(); - switch (e) { - .ACCES => return error.AccessDenied, - .BADF => |err| return errnoBug(err), // File descriptor used after closed. - .PERM => return error.PermissionDenied, - .DQUOT => return error.DiskQuota, - .EXIST => return error.PathAlreadyExists, - .FAULT => |err| return errnoBug(err), - .LOOP => return error.SymLinkLoop, - .MLINK => return error.LinkQuotaExceeded, - .NAMETOOLONG => return error.NameTooLong, - .NOENT => return error.FileNotFound, - .NOMEM => return error.SystemResources, - .NOSPC => return error.NoSpaceLeft, - .NOTDIR => return error.NotDir, - .ROFS => return error.ReadOnlyFileSystem, - // dragonfly: when dir_fd is unlinked from filesystem - .NOTCONN => return error.FileNotFound, - .ILSEQ => return error.BadPathName, - else => |err| return posix.unexpectedErrno(err), - } - }, + .ACCES => return syscall.fail(error.AccessDenied), + .PERM => return syscall.fail(error.PermissionDenied), + .DQUOT => return syscall.fail(error.DiskQuota), + .EXIST => return syscall.fail(error.PathAlreadyExists), + .LOOP => return syscall.fail(error.SymLinkLoop), + .MLINK => return syscall.fail(error.LinkQuotaExceeded), + .NAMETOOLONG => return syscall.fail(error.NameTooLong), + .NOENT => return syscall.fail(error.FileNotFound), + .NOMEM => return syscall.fail(error.SystemResources), + .NOSPC => return syscall.fail(error.NoSpaceLeft), + .NOTDIR => return syscall.fail(error.NotDir), + .ROFS => return syscall.fail(error.ReadOnlyFileSystem), + // dragonfly: when dir_fd is unlinked from filesystem + .NOTCONN => return syscall.fail(error.FileNotFound), + .ILSEQ => return syscall.fail(error.BadPathName), + .BADF => |err| return syscall.errnoBug(err), // File descriptor used after closed. + .FAULT => |err| return syscall.errnoBug(err), + else => |err| return syscall.unexpectedErrno(err), } } } @@ -5261,28 +5256,23 @@ fn dirOpenDirPosix( try syscall.checkCancel(); continue; }, - else => |e| { - syscall.finish(); - switch (e) { - .FAULT => |err| return errnoBug(err), - .INVAL => return error.BadPathName, - .BADF => |err| return errnoBug(err), // File descriptor used after closed. - .ACCES => return error.AccessDenied, - .LOOP => return error.SymLinkLoop, - .MFILE => return error.ProcessFdQuotaExceeded, - .NAMETOOLONG => return error.NameTooLong, - .NFILE => return error.SystemFdQuotaExceeded, - .NODEV => return error.NoDevice, - .NOENT => return error.FileNotFound, - .NOMEM => return error.SystemResources, - .NOTDIR => return error.NotDir, - .PERM => return error.PermissionDenied, - .BUSY => |err| return errnoBug(err), // O_EXCL not passed - .NXIO => return error.NoDevice, - .ILSEQ => return error.BadPathName, - else => |err| return posix.unexpectedErrno(err), - } - }, + .INVAL => return syscall.fail(error.BadPathName), + .ACCES => return syscall.fail(error.AccessDenied), + .LOOP => return syscall.fail(error.SymLinkLoop), + .MFILE => return syscall.fail(error.ProcessFdQuotaExceeded), + .NAMETOOLONG => return syscall.fail(error.NameTooLong), + .NFILE => return syscall.fail(error.SystemFdQuotaExceeded), + .NODEV => return syscall.fail(error.NoDevice), + .NOENT => return syscall.fail(error.FileNotFound), + .NOMEM => return syscall.fail(error.SystemResources), + .NOTDIR => return syscall.fail(error.NotDir), + .PERM => return syscall.fail(error.PermissionDenied), + .NXIO => return syscall.fail(error.NoDevice), + .ILSEQ => return syscall.fail(error.BadPathName), + .FAULT => |err| return syscall.errnoBug(err), + .BADF => |err| return syscall.errnoBug(err), // File descriptor used after closed. + .BUSY => |err| return syscall.errnoBug(err), // O_EXCL not passed + else => |err| return syscall.unexpectedErrno(err), } } } -- 2.54.0 From 64dc1cdad8faf1fd8330d4b0f9149817b5205abf Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 4 Feb 2026 15:55:56 -0800 Subject: [PATCH 212/499] fetch: download to local zig-pkg directory rather than global cache p/ directory. closes #14283 does not recompress packages into global cache yet --- src/Package/Fetch.zig | 148 ++++++++++++++++++++++-------------------- src/main.zig | 52 +++++++++++---- 2 files changed, 115 insertions(+), 85 deletions(-) diff --git a/src/Package/Fetch.zig b/src/Package/Fetch.zig index 5a600421e9e24c5eb905290dcd57512f94668548..96018851ce47e19ede2a6855eb6dad6a07745527 100644 --- a/src/Package/Fetch.zig +++ b/src/Package/Fetch.zig @@ -68,8 +68,7 @@ use_latest_commit: bool, // Above this are fields provided as inputs to `run`. // Below this are fields populated by `run`. -/// This will either be relative to `global_cache`, or to the build root of -/// the root package. +/// Relative to the build root of the root package. package_root: Cache.Path, error_bundle: ErrorBundle.Wip, manifest: ?Manifest, @@ -115,6 +114,9 @@ pub const JobQueue = struct { http_client: *std.http.Client, group: Io.Group = .init, global_cache: Cache.Directory, + local_cache: Cache.Path, + /// Path to "zig-pkg" inside the package in which the user ran `zig build`. + root_pkg_path: Cache.Path, /// If true then, no fetching occurs, and: /// * The `global_cache` directory is assumed to be the direct parent /// directory of on-disk packages rather than having the "p/" directory @@ -325,11 +327,12 @@ pub const RunError = error{ }; pub fn run(f: *Fetch) RunError!void { - const io = f.job_queue.io; + const job_queue = f.job_queue; + const io = job_queue.io; const eb = &f.error_bundle; const arena = f.arena.allocator(); const gpa = f.arena.child_allocator; - const cache_root = f.job_queue.global_cache; + const local_cache_root = job_queue.local_cache; try eb.init(gpa); @@ -350,13 +353,13 @@ pub fn run(f: *Fetch) RunError!void { ); // Packages fetched by URL may not use relative paths to escape outside the // fetched package directory from within the package cache. - if (pkg_root.root_dir.eql(cache_root)) { + if (pkg_root.root_dir.eql(local_cache_root.root_dir)) { // `parent_package_root.sub_path` contains a path like this: // "p/$hash", or // "p/$hash/foo", with possibly more directories after "foo". // We want to fail unless the resolved relative path has a // prefix of "p/$hash/". - const prefix_len: usize = if (f.job_queue.read_only) 0 else "p/".len; + const prefix_len: usize = if (job_queue.read_only) 0 else "p/".len; const parent_sub_path = f.parent_package_root.sub_path; const end = find_end: { if (parent_sub_path.len > prefix_len) { @@ -379,7 +382,7 @@ pub fn run(f: *Fetch) RunError!void { f.package_root = pkg_root; try loadManifest(f, pkg_root); if (!f.has_build_zig) try checkBuildFileExistence(f); - if (!f.job_queue.recursive) return; + if (!job_queue.recursive) return; return queueJobsForDeps(f); }, .remote => |remote| remote, @@ -411,51 +414,39 @@ pub fn run(f: *Fetch) RunError!void { }; if (remote.hash) |expected_hash| { - var prefixed_pkg_sub_path_buffer: [Package.Hash.max_len + 2]u8 = undefined; - prefixed_pkg_sub_path_buffer[0] = 'p'; - prefixed_pkg_sub_path_buffer[1] = fs.path.sep; - const hash_slice = expected_hash.toSlice(); - @memcpy(prefixed_pkg_sub_path_buffer[2..][0..hash_slice.len], hash_slice); - const prefixed_pkg_sub_path = prefixed_pkg_sub_path_buffer[0 .. 2 + hash_slice.len]; - const prefix_len: usize = if (f.job_queue.read_only) "p/".len else 0; - const pkg_sub_path = prefixed_pkg_sub_path[prefix_len..]; - if (cache_root.handle.access(io, pkg_sub_path, .{})) |_| { + const package_root = try job_queue.root_pkg_path.join(arena, expected_hash.toSlice()); + if (package_root.root_dir.handle.access(io, package_root.sub_path, .{})) |_| { assert(f.lazy_status != .unavailable); - f.package_root = .{ - .root_dir = cache_root, - .sub_path = try arena.dupe(u8, pkg_sub_path), - }; + f.package_root = package_root; try loadManifest(f, f.package_root); try checkBuildFileExistence(f); - if (!f.job_queue.recursive) return; + if (!job_queue.recursive) return; return queueJobsForDeps(f); } else |err| switch (err) { error.FileNotFound => { switch (f.lazy_status) { .eager => {}, - .available => if (!f.job_queue.unlazy_set.contains(expected_hash)) { + .available => if (!job_queue.unlazy_set.contains(expected_hash)) { f.lazy_status = .unavailable; return; }, .unavailable => unreachable, } - if (f.job_queue.read_only) return f.fail( + if (job_queue.read_only) return f.fail( f.name_tok, - try eb.printString("package not found at '{f}{s}'", .{ - cache_root, pkg_sub_path, - }), + try eb.printString("package not found at '{f}'", .{package_root}), ); }, else => |e| { try eb.addRootErrorMessage(.{ - .msg = try eb.printString("unable to open global package cache directory '{f}{s}': {s}", .{ - cache_root, pkg_sub_path, @errorName(e), + .msg = try eb.printString("unable to open package cache directory {f}: {t}", .{ + package_root, e, }), }); return error.FetchFailed; }, } - } else if (f.job_queue.read_only) { + } else if (job_queue.read_only) { try eb.addRootErrorMessage(.{ .msg = try eb.addString("dependency is missing hash field"), .src_loc = try f.srcLoc(f.location_tok), @@ -467,7 +458,7 @@ pub fn run(f: *Fetch) RunError!void { const uri = std.Uri.parse(remote.url) catch |err| return f.fail( f.location_tok, - try eb.printString("invalid URI: {s}", .{@errorName(err)}), + try eb.printString("invalid URI: {t}", .{err}), ); var buffer: [init_resource_buffer_size]u8 = undefined; var resource: Resource = undefined; @@ -487,29 +478,30 @@ fn runResource( resource: *Resource, remote_hash: ?Package.Hash, ) RunError!void { - const io = f.job_queue.io; + const job_queue = f.job_queue; + const io = job_queue.io; defer resource.deinit(io); const arena = f.arena.allocator(); const eb = &f.error_bundle; const s = fs.path.sep_str; - const cache_root = f.job_queue.global_cache; + const local_cache_root = job_queue.local_cache; const rand_int = r: { var x: u64 = undefined; io.random(@ptrCast(&x)); break :r x; }; const tmp_dir_sub_path = "tmp" ++ s ++ std.fmt.hex(rand_int); + const tmp_directory_path = try local_cache_root.join(arena, tmp_dir_sub_path); const package_sub_path = blk: { - const tmp_directory_path = try cache_root.join(arena, &.{tmp_dir_sub_path}); var tmp_directory: Cache.Directory = .{ - .path = tmp_directory_path, + .path = tmp_directory_path.sub_path, .handle = handle: { - const dir = cache_root.handle.createDirPathOpen(io, tmp_dir_sub_path, .{ + const dir = tmp_directory_path.root_dir.handle.createDirPathOpen(io, tmp_directory_path.sub_path, .{ .open_options = .{ .iterate = true }, }) catch |err| { try eb.addRootErrorMessage(.{ - .msg = try eb.printString("unable to create temporary directory '{s}': {t}", .{ + .msg = try eb.printString("unable to create temporary directory '{f}': {t}", .{ tmp_directory_path, err, }), }); @@ -545,36 +537,33 @@ fn runResource( // directory. f.computed_hash = try computeHash(f, pkg_path, filter); - break :blk if (unpack_result.root_dir.len > 0) - try fs.path.join(arena, &.{ tmp_dir_sub_path, unpack_result.root_dir }) - else - tmp_dir_sub_path; + if (unpack_result.root_dir.len > 0) + break :blk try tmp_directory_path.join(arena, unpack_result.root_dir); + + break :blk tmp_directory_path; }; const computed_package_hash = computedPackageHash(f); - // Rename the temporary directory into the global zig package cache - // directory. If the hash already exists, delete the temporary directory - // and leave the zig package cache directory untouched as it may be in use - // by the system. This is done even if the hash is invalid, in case the - // package with the different hash is used in the future. - - f.package_root = .{ - .root_dir = cache_root, - .sub_path = try std.fmt.allocPrint(arena, "p" ++ s ++ "{s}", .{computed_package_hash.toSlice()}), - }; - renameTmpIntoCache(io, cache_root.handle, package_sub_path, f.package_root.sub_path) catch |err| { - const src = try cache_root.join(arena, &.{tmp_dir_sub_path}); - const dest = try cache_root.join(arena, &.{f.package_root.sub_path}); + // Rename the temporary directory into the local zig package directory. If + // the hash already exists, delete the temporary directory and leave the + // zig package directory untouched as it may be in use. This is done even + // if the hash is invalid, in case the package with the different hash is + // used in the future. + f.package_root = try job_queue.root_pkg_path.join(arena, computed_package_hash.toSlice()); + renameTmpIntoCache(io, package_sub_path, f.package_root) catch |err| { try eb.addRootErrorMessage(.{ .msg = try eb.printString( - "unable to rename temporary directory '{s}' into package cache directory '{s}': {s}", - .{ src, dest, @errorName(err) }, + "unable to rename temporary directory {f} into package cache directory {f}: {t}", + .{ package_sub_path, f.package_root, err }, ) }); return error.FetchFailed; }; // Remove temporary directory root if not already renamed to global cache. - if (!std.mem.eql(u8, package_sub_path, tmp_dir_sub_path)) { - cache_root.handle.deleteDir(io, tmp_dir_sub_path) catch {}; + if (!package_sub_path.eql(tmp_directory_path)) { + tmp_directory_path.root_dir.handle.deleteDir(io, tmp_directory_path.sub_path) catch |err| switch (err) { + error.Canceled => |e| return e, + else => |e| std.log.warn("failed to delete temporary directory {f}: {t}", .{ tmp_directory_path, e }), + }; } // Validate the computed hash against the expected hash. If invalid, this @@ -614,7 +603,7 @@ fn runResource( // Spawn a new fetch job for each dependency in the manifest file. Use // a mutex and a hash map so that redundant jobs do not get queued up. - if (!f.job_queue.recursive) return; + if (!job_queue.recursive) return; return queueJobsForDeps(f); } @@ -641,8 +630,8 @@ fn checkBuildFileExistence(f: *Fetch) RunError!void { error.FileNotFound => {}, else => |e| { try eb.addRootErrorMessage(.{ - .msg = try eb.printString("unable to access '{f}{s}': {s}", .{ - f.package_root, Package.build_zig_basename, @errorName(e), + .msg = try eb.printString("unable to access '{f}{s}': {t}", .{ + f.package_root, Package.build_zig_basename, e, }), }); return error.FetchFailed; @@ -667,9 +656,7 @@ fn loadManifest(f: *Fetch, pkg_root: Cache.Path) RunError!void { else => |e| { const file_path = try pkg_root.join(arena, Manifest.basename); try eb.addRootErrorMessage(.{ - .msg = try eb.printString("unable to load package manifest '{f}': {s}", .{ - file_path, @errorName(e), - }), + .msg = try eb.printString("unable to load package manifest '{f}': {t}", .{ file_path, e }), }); return error.FetchFailed; }, @@ -1453,14 +1440,20 @@ fn recursiveDirectoryCopy(f: *Fetch, dir: Io.Dir, tmp_dir: Io.Dir) anyerror!void } } -pub fn renameTmpIntoCache(io: Io, cache_dir: Io.Dir, tmp_dir_sub_path: []const u8, dest_dir_sub_path: []const u8) !void { - assert(dest_dir_sub_path[1] == fs.path.sep); +pub fn renameTmpIntoCache(io: Io, tmp_path: Cache.Path, dest_path: Cache.Path) !void { var handled_missing_dir = false; while (true) { - cache_dir.rename(tmp_dir_sub_path, cache_dir, dest_dir_sub_path, io) catch |err| switch (err) { + Io.Dir.rename( + tmp_path.root_dir.handle, + tmp_path.sub_path, + dest_path.root_dir.handle, + dest_path.sub_path, + io, + ) catch |err| switch (err) { error.FileNotFound => { if (handled_missing_dir) return err; - cache_dir.createDir(io, dest_dir_sub_path[0..1], .default_dir) catch |mkd_err| switch (mkd_err) { + const parent_sub_path = Io.Dir.path.dirname(dest_path.sub_path).?; + dest_path.root_dir.handle.createDir(io, parent_sub_path, .default_dir) catch |er| switch (er) { error.PathAlreadyExists => handled_missing_dir = true, else => |e| return e, }; @@ -1468,9 +1461,11 @@ pub fn renameTmpIntoCache(io: Io, cache_dir: Io.Dir, tmp_dir_sub_path: []const u }, error.DirNotEmpty, error.AccessDenied => { // Package has been already downloaded and may already be in use on the system. - cache_dir.deleteTree(io, tmp_dir_sub_path) catch { + tmp_path.root_dir.handle.deleteTree(io, tmp_path.sub_path) catch |er| switch (er) { + error.Canceled => |e| return e, // Garbage files leftover in zig-cache/tmp/ is, as they say // on Star Trek, "operating within normal parameters". + else => |e| std.log.warn("failed to delete temporary directory {f}: {t}", .{ tmp_path, e }), }; }, else => |e| return e, @@ -2244,6 +2239,7 @@ fn saveEmbedFile(io: Io, comptime tarball_name: []const u8, dir: Io.Dir) !void { const TestFetchBuilder = struct { http_client: std.http.Client, global_cache_directory: Cache.Directory, + local_cache_path: Cache.Path, job_queue: Fetch.JobQueue, fetch: Fetch, @@ -2254,15 +2250,25 @@ const TestFetchBuilder = struct { cache_parent_dir: std.Io.Dir, path_or_url: []const u8, ) !*Fetch { - const cache_dir = try cache_parent_dir.createDirPathOpen(io, "zig-global-cache", .{}); + const global_cache_dir = try cache_parent_dir.createDirPathOpen(io, "zig-global-cache", .{}); + const package_root_dir = try cache_parent_dir.createDirPathOpen(io, "local-project-root", .{}); self.http_client = .{ .allocator = allocator, .io = io }; - self.global_cache_directory = .{ .handle = cache_dir, .path = null }; + self.global_cache_directory = .{ .handle = global_cache_dir, .path = "zig-global-cache" }; + self.local_cache_path = .{ + .root_dir = .{ .handle = package_root_dir, .path = "local-project-root" }, + .sub_path = ".zig-cache", + }; self.job_queue = .{ .io = io, .http_client = &self.http_client, .global_cache = self.global_cache_directory, + .local_cache = self.local_cache_path, + .root_pkg_path = .{ + .root_dir = .{ .handle = package_root_dir, .path = "local-project-root" }, + .sub_path = "zig-pkg", + }, .recursive = false, .read_only = false, .debug_hash = false, @@ -2276,7 +2282,7 @@ const TestFetchBuilder = struct { .hash_tok = .none, .name_tok = 0, .lazy_status = .eager, - .parent_package_root = Cache.Path{ .root_dir = Cache.Directory{ .handle = cache_dir, .path = null } }, + .parent_package_root = .{ .root_dir = .{ .handle = package_root_dir, .path = null } }, .parent_manifest_ast = null, .prog_node = std.Progress.Node.none, .job_queue = &self.job_queue, diff --git a/src/main.zig b/src/main.zig index 1e9c0de6564be4e81661182f07877fd3a7a17cbc..02a49ef3e04aab0c27981d373dc3924192387927 100644 --- a/src/main.zig +++ b/src/main.zig @@ -5239,6 +5239,8 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8, .io = io, .http_client = &http_client, .global_cache = dirs.global_cache, + .local_cache = .{ .root_dir = dirs.local_cache, .sub_path = "" }, + .root_pkg_path = .{ .root_dir = build_root.directory, .sub_path = "zig-pkg" }, .read_only = false, .recursive = true, .debug_hash = false, @@ -5248,12 +5250,17 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8, defer job_queue.deinit(); if (system_pkg_dir_path) |p| { - job_queue.global_cache = .{ - .path = p, - .handle = Io.Dir.cwd().openDir(io, p, .{}) catch |err| { - fatal("unable to open system package directory '{s}': {t}", .{ p, err }); + const system_pkg_path: Path = .{ + .root_dir = .{ + .path = p, + .handle = Io.Dir.cwd().openDir(io, p, .{}) catch |err| { + fatal("unable to open system package directory '{s}': {t}", .{ p, err }); + }, }, + .sub_path = "", }; + job_queue.global_cache = system_pkg_path.root_dir; + job_queue.root_pkg_path = system_pkg_path; job_queue.read_only = true; cleanup_build_dir = job_queue.global_cache.handle; } else { @@ -6996,10 +7003,27 @@ fn cmdFetch( }; defer global_cache_directory.handle.close(io); + const cwd_path = try introspect.getResolvedCwd(io, arena); + + var build_root = try findBuildRoot(arena, io, .{ + .cwd_path = cwd_path, + }); + defer build_root.deinit(io); + + const local_cache_path: Path = .{ + .root_dir = build_root.directory, + .sub_path = ".zig-cache", + }; + var job_queue: Package.Fetch.JobQueue = .{ .io = io, .http_client = &http_client, .global_cache = global_cache_directory, + .local_cache = local_cache_path, + .root_pkg_path = .{ + .root_dir = build_root.directory, + .sub_path = "zig-pkg", + }, .recursive = false, .read_only = false, .debug_hash = debug_hash, @@ -7069,13 +7093,6 @@ fn cmdFetch( }, }; - const cwd_path = try introspect.getResolvedCwd(io, arena); - - var build_root = try findBuildRoot(arena, io, .{ - .cwd_path = cwd_path, - }); - defer build_root.deinit(io); - // The name to use in case the manifest file needs to be created now. const init_root_name = fs.path.basename(build_root.directory.path orelse cwd_path); var manifest, var ast = try loadManifest(gpa, arena, io, .{ @@ -7239,18 +7256,25 @@ fn createDependenciesModule( defer tmp_dir.close(io); try tmp_dir.writeFile(io, .{ .sub_path = basename, .data = source }); } + const tmp_dir_path: Path = .{ + .root_dir = dirs.local_cache, + .sub_path = tmp_dir_sub_path, + }; var hh: Cache.HashHelper = .{}; hh.addBytes(build_options.version); hh.addBytes(source); const hex_digest = hh.final(); - const o_dir_sub_path = try arena.dupe(u8, "o" ++ fs.path.sep_str ++ hex_digest); - try Package.Fetch.renameTmpIntoCache(io, dirs.local_cache.handle, tmp_dir_sub_path, o_dir_sub_path); + const o_dir_path: Path = .{ + .root_dir = dirs.local_cache, + .sub_path = try arena.dupe(u8, "o" ++ fs.path.sep_str ++ hex_digest), + }; + try Package.Fetch.renameTmpIntoCache(io, tmp_dir_path, o_dir_path); const deps_mod = try Package.Module.create(arena, .{ .paths = .{ - .root = try .fromRoot(arena, dirs, .local_cache, o_dir_sub_path), + .root = try .fromRoot(arena, dirs, .local_cache, o_dir_path.sub_path), .root_src_path = basename, }, .fully_qualified_name = "root.@dependencies", -- 2.54.0 From df64a3a36815fce6cc8671d047e52795655b3b9b Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 4 Feb 2026 17:40:54 -0800 Subject: [PATCH 213/499] build: packages now require fingerprint also the name must be an enum literal. delete some .tar.gz test data. Test data should be in text form when it can be, and this could definitely be. --- src/Package/Fetch.zig | 132 ------------------ .../Fetch/testdata/duplicate_paths.tar.gz | Bin 3230 -> 0 bytes .../testdata/duplicate_paths_excluded.tar.gz | Bin 3237 -> 0 bytes src/Package/Fetch/testdata/no_root.tar.gz | Bin 3172 -> 0 bytes src/Package/Manifest.zig | 34 +---- src/main.zig | 4 - 6 files changed, 7 insertions(+), 163 deletions(-) delete mode 100644 src/Package/Fetch/testdata/duplicate_paths.tar.gz delete mode 100644 src/Package/Fetch/testdata/duplicate_paths_excluded.tar.gz delete mode 100644 src/Package/Fetch/testdata/no_root.tar.gz diff --git a/src/Package/Fetch.zig b/src/Package/Fetch.zig index 96018851ce47e19ede2a6855eb6dad6a07745527..a9cc86b398ef5ee6d3d83835f41f556ade34782e 100644 --- a/src/Package/Fetch.zig +++ b/src/Package/Fetch.zig @@ -60,8 +60,6 @@ omit_missing_hash_error: bool, /// which specifies inclusion rules. This is intended to be true for the first /// fetch task and false for the recursive dependencies. allow_missing_paths_field: bool, -allow_missing_fingerprint: bool, -allow_name_string: bool, /// If true and URL points to a Git repository, will use the latest commit. use_latest_commit: bool, @@ -675,8 +673,6 @@ fn loadManifest(f: *Fetch, pkg_root: Cache.Path) RunError!void { f.manifest = try Manifest.parse(arena, ast.*, rng.interface(), .{ .allow_missing_paths_field = f.allow_missing_paths_field, - .allow_missing_fingerprint = f.allow_missing_fingerprint, - .allow_name_string = f.allow_name_string, }); const manifest = &f.manifest.?; @@ -794,8 +790,6 @@ fn queueJobsForDeps(f: *Fetch) RunError!void { .job_queue = f.job_queue, .omit_missing_hash_error = false, .allow_missing_paths_field = true, - .allow_missing_fingerprint = true, - .allow_name_string = true, .use_latest_commit = false, .package_root = undefined, @@ -2049,130 +2043,6 @@ const UnpackResult = struct { } }; -test "tarball with duplicate paths" { - // This tarball has duplicate path 'dir1/file1' to simulate case sensitve - // file system on any file sytstem. - // - // duplicate_paths/ - // duplicate_paths/dir1/ - // duplicate_paths/dir1/file1 - // duplicate_paths/dir1/file1 - // duplicate_paths/build.zig.zon - // duplicate_paths/src/ - // duplicate_paths/src/main.zig - // duplicate_paths/src/root.zig - // duplicate_paths/build.zig - // - - const gpa = std.testing.allocator; - const io = std.testing.io; - var tmp = std.testing.tmpDir(.{}); - defer tmp.cleanup(); - - const tarball_name = "duplicate_paths.tar.gz"; - try saveEmbedFile(io, tarball_name, tmp.dir); - const tarball_path = try std.fmt.allocPrint(gpa, ".zig-cache/tmp/{s}/{s}", .{ tmp.sub_path, tarball_name }); - defer gpa.free(tarball_path); - - // Run tarball fetch, expect to fail - var fb: TestFetchBuilder = undefined; - var fetch = try fb.build(gpa, io, tmp.dir, tarball_path); - defer fb.deinit(); - try std.testing.expectError(error.FetchFailed, fetch.run()); - - try fb.expectFetchErrors(1, - \\error: unable to unpack tarball - \\ note: unable to create file 'dir1/file1': PathAlreadyExists - \\ - ); -} - -test "tarball with excluded duplicate paths" { - // Same as previous tarball but has build.zig.zon wich excludes 'dir1'. - // - // .paths = .{ - // "build.zig", - // "build.zig.zon", - // "src", - // } - // - - const gpa = std.testing.allocator; - const io = std.testing.io; - var tmp = std.testing.tmpDir(.{}); - defer tmp.cleanup(); - - const tarball_name = "duplicate_paths_excluded.tar.gz"; - try saveEmbedFile(io, tarball_name, tmp.dir); - const tarball_path = try std.fmt.allocPrint(gpa, ".zig-cache/tmp/{s}/{s}", .{ tmp.sub_path, tarball_name }); - defer gpa.free(tarball_path); - - // Run tarball fetch, should succeed - var fb: TestFetchBuilder = undefined; - var fetch = try fb.build(gpa, io, tmp.dir, tarball_path); - defer fb.deinit(); - try fetch.run(); - - const hex_digest = Package.multiHashHexDigest(fetch.computed_hash.digest); - try std.testing.expectEqualStrings( - "12200bafe035cbb453dd717741b66e9f9d1e6c674069d06121dafa1b2e62eb6b22da", - &hex_digest, - ); - - const expected_files: []const []const u8 = &.{ - "build.zig", - "build.zig.zon", - "src/main.zig", - "src/root.zig", - }; - try fb.expectPackageFiles(expected_files); -} - -test "tarball without root folder" { - // Tarball with root folder. Manifest excludes dir1 and dir2. - // - // build.zig - // build.zig.zon - // dir1/ - // dir1/file2 - // dir1/file1 - // dir2/ - // dir2/file2 - // src/ - // src/main.zig - // - - const gpa = std.testing.allocator; - const io = std.testing.io; - - var tmp = std.testing.tmpDir(.{}); - defer tmp.cleanup(); - - const tarball_name = "no_root.tar.gz"; - try saveEmbedFile(io, tarball_name, tmp.dir); - const tarball_path = try std.fmt.allocPrint(gpa, ".zig-cache/tmp/{s}/{s}", .{ tmp.sub_path, tarball_name }); - defer gpa.free(tarball_path); - - // Run tarball fetch, should succeed - var fb: TestFetchBuilder = undefined; - var fetch = try fb.build(gpa, io, tmp.dir, tarball_path); - defer fb.deinit(); - try fetch.run(); - - const hex_digest = Package.multiHashHexDigest(fetch.computed_hash.digest); - try std.testing.expectEqualStrings( - "12209f939bfdcb8b501a61bb4a43124dfa1b2848adc60eec1e4624c560357562b793", - &hex_digest, - ); - - const expected_files: []const []const u8 = &.{ - "build.zig", - "build.zig.zon", - "src/main.zig", - }; - try fb.expectPackageFiles(expected_files); -} - test "set executable bit based on file content" { if (!Io.File.Permissions.has_executable_bit) return error.SkipZigTest; const gpa = std.testing.allocator; @@ -2288,8 +2158,6 @@ const TestFetchBuilder = struct { .job_queue = &self.job_queue, .omit_missing_hash_error = true, .allow_missing_paths_field = false, - .allow_missing_fingerprint = true, // so we can keep using the old testdata .tar.gz - .allow_name_string = true, // so we can keep using the old testdata .tar.gz .use_latest_commit = true, .package_root = undefined, diff --git a/src/Package/Fetch/testdata/duplicate_paths.tar.gz b/src/Package/Fetch/testdata/duplicate_paths.tar.gz deleted file mode 100644 index 118a934c1b03d764e4854f9aeb60ed684467caad..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3230 zcmV;P3}N#hiwFoH>keiD17vk@Y-wX*bY)*~VRUG7E_7jX0PR|9a~ro6^=JPIgdZBp zrB+W%GjZLClPFWqxOK-$nwh2@5!?ky#QO?>rA#~e?>+YdSniTi;!GWRl3CaziMznX zeVqHi1+%8kbt;{@s-;`ng9pFCIK((Ve@wrR&L1Ckf5-9Q==ALHF9jdjup@j%N`r$00Am)`$QN491EOq-*Fk7)ms!xPy5^zis}u>VK1|6J$l z=)>Co?CkVC?SDx2KR-EsAPxun5B4AIf3NnRH9F6dw|bGht;+kc|KroMv)2BHXUG#r zkIyLn5BC4%Rx)o?B%Tzy)J0QV!ELU3EFK;G8XhlXx`f!Z zDl=84Yf%83LSat@t(a=1GX+gf8@{s^O~H#`XBPYWbLG;bqw0w{oS#U!a-4P8mcJ zBnzN4+s}2W{6=#Lw)th2!Kgj%lMeMny^%$otDRl_&+nG16iuB0mmuOcCWT6+DAl$2 z_SK6CDcTa^@ibGpa`#EEmta`Lti;~T+wN+wWHLO_@rzzPRcvtE8m3mV{ zZ){BsM9>x$SRpb(y4?+ELSQl3dYeFdgxcuKK?@0XKA2Zkf-spZ-iCnSm+jJT-K;9= z#*s&xs^DFzrXV{zcprm|9-`U+`|N~so?*Rc+U}CBW=_JEnUHfB(_5Z&vGE64*ZJD3 zhfJR44SS+#i0%Ry2K?8zD4iqSG}(D`hj^_6(>Ath3}8j_n8q!iM9;AlKPZ$y6Uu8L zxb72LcMBW6iHw6J$cC+cpioeiyb)l%7IkH~$`nD+EKt z8R8`hMW(45;DANlK+C8cUbA9X#Qa^_K1`abatS3X-X)b*b*X=9y;BhbaN%67kOCQX zy+by4UK+_eg zP_m5LLe}$opjtqey#zVwDInxA4lX;Xgt1V_*C6x=G#`1QTh#6}i+D~UQY=)d3<0N0 zd8V9WnJG7*e9~0wkTh)+-nF4FA*>{Ks_1V{Tg!g>Xv3`!=+exvF&O5sZr4~}2o`n1 z)MFJy0pC

kWwy5ur;oK*Un**`@}<%Lf0W9^A#(fJ;nFN!Lq8?ZKtDVAMO$^j}$% z_8+`>{^i9#E-(5IUtK)=>hBjxk&RkcyLtG>J^w)|AG`<5{lCZW*Z(^@=l<{c+40c- z`&jlbP%Mzk+*AMO=p>0lNf|v~gl4>7t(=j2m6(&42p7%6Qh~zkz)AIspi89~ft**J^EV*S6 z&T~bLyhd`))()u@!Z#={==6tWozlZ|H{mk@74R(eg6I}lPbUVS&RTr=q96 zmyH|mCD%qH&5t2IEm{G>v+K>wSu;oBqq6V(tPZuXpt}o}&8|BHd4eJfZKz)w7b_wF7iL5F=b)yzWXM3>r;j0r7)MiW#U6%1UNAZ^BFAQ1rAc(&(+n4^PrFfi9x zq!6lLtYScrz!^^Osq+POp$aY!m3;k4D?MTv}p% zA~1X6m)BK2-ow&+E-r41|6E$%U;IBjK0n`y|M)e;|BoF1{kwqQ;CkT`yc>ANWXcaR z%f|9E!GNCe?>+hveAg(~7&?*vS;yvao-?LxPA1}f zPdt4pjt=kNSN@3O&ikJaT>t-!|DT_p?D#(>4F3Po>p$v=y zDZ{-Pu4`0i-Xc~0P|dhQS`m#*rMdL;^0hBLOp_wbhEif-WWDrj<86?7qf+h<&DO}q z_(&H8GAC-NH%euaSe!D6F~ZIx3Qu3qr#;cHR4*M8x-=QJEvV-37cZ8Y64kX&>yX28 ztGFz=r`)-|K!uVntIDeO7Kj)Pr0~+qfN~}gf1_^EwTi9ZYK5|xE^WBgt>&`Hot^%-M)uvt;!MGA8wjg*9!IZ zS5zEN#4Ef+m47Mcs{Qb_w9acLzAdoVw(i@in*(z<$A{Rk;R}nr_&dDW~5n;`n~*dLXr!JZWH-GgIGthqZdm}cO^vPzs(zn%qLHEF$bDO# zce>OGm5f)Og?MO5-fh~}-KugVZZ_g1E$I9ooi`mV$3H^{t^-2uqx$&5eN1X0w#UER zYnYu>ag!4U=XPr7BuCptYJ6%S6kq$>h5!m(uBz9ep*{*^Mp&$BG48r2KcS_O-Yu99 z*W$Az(0OK@p5vT42D5#@AOuFhTyQibfBltcc^|vPZqNPMMYZDGX`0QcIRu&i&omoy z!9u^vG~jnzv`48?P`G^rr=Wu|6Ata{PEwl$0o!!Od&`pVcY92MrlKzuuyb)L^w-mt z`X}1a?GfGFzM();`mV^e&z6YyP^GqQO0f-H>vz6L(2ZS?`zkH6h%E4ahnQci>3$*x zM-yfAREa5n$;z7ISCj6yIcR>o`rX|2^JH(v&h?z+R0f&jQ@U%Swf>65_gQ4xy)NGQ zS_h@uQl->MG3#!)^Y_f1mQPo&Y=#Z`+BE9#VmSCIz7Ao4ty}4+<)gy&UtrLmQPc*g zp2eWf5)+m@`0zlWRPUND<{ z)kVFA*NP?ktlP{XbM5aF^&|RmER;*9Oacl7yLe{z#833=C$Wh@Q91zW8D5Qfn(uzx z^yLZe(w2Oz54y&RDqU%RogHrtyI)^fs=S^eqm@njZ6p#Ye4WKt5TyCLM4R?l@lF$b z&2L6?JiNPL?8J{yv=P$UD6{QVyxvy?u;>VnMiaqZvtXxDP)(e?!NZ@rFzGh?$;u9UeM$->&U5!Z>aZEe-K%@>@=;Myhq`s9j;UvVEV#)oh4p_g+$N}}H= z`F1Y5QLH~>C#8NM89#i|+{k!4ms^qg78$7~2W6yOKC=*ZcEY!kCf5`geQ7{)M$F^pjh Q<99Uv2QI@^xByT905N}g`2YX_ diff --git a/src/Package/Fetch/testdata/duplicate_paths_excluded.tar.gz b/src/Package/Fetch/testdata/duplicate_paths_excluded.tar.gz deleted file mode 100644 index 760b37cd40fe43e0907f14943a33ae8341f2f649..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3237 zcmV;W3|jLaiwFqP>keiD17vk@Y-wX*bY)*~VRUG7UuAe>Y;|O1WG-}JascgGYjYd7 z71d|`3YZ@n(xq3To-=XXiIb>P&$xBRN}8Fb9TD6GNyPgKfF(^k`tLpW0=wKLrNo)K z@?^5GM-q2|i~BhDfeR*8nd(?NbyG^Wv_}X3hjEN?d3jF1Czt2P?cen{n4Dc4pPpZw zUz|-2jwh#+%hLmKe((UJvQ8Qy4z$d5{4?`@>HTkXBs=y`w3!@zI{TlU9-qSgXU8XJ zz5PF#{pUJWlaFivi;J@lwEr>L|MK+YKpgk>@9p2)|3U3Pt8|(~@AM*iSL6?4|MRoS zNt6H2&o5yA^NW*7Z~uoF(fdPz5#=&d;+Yui;9)T4*GEUYn#FLErJSFSQjto#a5%*DU-LTD$}I!(n)iOQ-( z%nKt*8NZPWB@$Khf(4#fR4)gk#}WR4y$*kKt(`c9dxH3lNF5L*3th6o6 zMihCvdCKf0g|Z?qoY*Lb4?^Y}x77KfCbHIUDI8H+$+S{6@hH=|&Z_JNZgbONaWeTe zJYLCo39(C+Cn}FOA_F#s!k!9RF~v$J3Ywf&CY^${L`;{`E~jGwrQtEzYueD7f)~Ng zES@}>D;F=PB9?ihX;%n-DkRk3?5QC?Oe1R}jD=CqnAIzlZW`LJ@>uP!lQ1unRLQut z{S*onX^}4=gi$|M+9+ES(z#P6hpyuy&sFU7O1rR4)S>-M1e}J2*Gl;OtPmL?-TnqNA+Q+jyp5neLT&BKK?@OfKAIOrgfJN`-i3hR zm+jMU+^opT%8^H#BI8}DrXV{zcprm|9-`U+`|N~so?*Rc+U}DsW=_JEiI8&`(_5Z& zvGoU8mg&Z;hfE%)6?>woi0%Ry2K+a7D4iqSG}?P}k9ds((>AuK3}8j_n941m)}CW2 zeo!cZCY0AgaNVc0?hZCO6B!3lkPTb?K%t<>c_YAjBg(>BJ%dU!ZA@V-1AAGl0fqZi zw{>ff2+CvlhTT3u{%@`FVt=1isnfJ>8~phZ*Z^@G>EfuFax@bD@gC$Dr@&kkMZW+n z_Bt2P6@nq+4DnJ6MW!h#;DANlLd&QWUbA9X#Qc5QK1`aTa1kXd-X)e+wW)t*y;Bhb zaN$&~kOCQX14G6bA9<(YDhWv1MK@<~&zL(()+c;ALPhp>{|si40(Z7sX$qY1Y@pi48u#$cGk zy4_%XCRo%lQ?IKa3izf-S#L;uhzM<>0V0-a&n7hxUbgrjGzXFv?gyPyO8bR1amPOf zFJF9h_4n(m?!(tt&%gfLRg@)z#(i!N4}OeSa2~yM-Ix7p~h68*YjGi7I zmeowmb3u3*j>Mx?p%d}GNzll(2-ip%n68WwL+w7J{(xPANL)EFlXTKV7MNlND#B=? z+_g)tj2VumBc?+y5;dz9QAy;6gXc&TIM^8R-rjOLohdpz(l(Wi{*7RdOge}QECThx zEwLFG0;+SY%9vmB3RJ8&n4|WjZE=w!)aF*5^IV6KZmt%jtQBv_zS2@lKaq zapv4I2tEtfv!$PkN0(F#CqH z>od_&->b?EN7347r1>Glr$sA3c(&fooKE-&}uKYsP`{}ac5|1RJ+xL)`K?*^VR zneu~7lA-)uFra7rdqh8i?;7PQLnrb-irLexmm_@Zn3jrpmB+pls@t-!|6iV+?)g6^^#1?J>p$v=qz)C$B3ygat}4=^ zcw?zNQigjoT-T`3yhW<~p_p-pv>+OZic{(5r5j&*m?}n^4W-1w$a3k|*0(|Gt%|un zG}|B><0D-Z$dss|-pG}S>f)413=wu7Q+WE4K8-}TQoVLa=+Y$AwxF8BU%XgqN)&6K z)**-GR&kzlPq}q{feIyF7KK&KEf6spNa3ZI0p(02{z~1VwW?dc#R_FHUD|M~Tg_#a zIy=UC9WQHQzPCgLVW6l3U)0?U?ZtX6y~|mRk$At~tXTRD+QX8vBMHz4E&ZTV-8atHea6-*G|R)$IqU)~Xb-{czj7 zS}WAoUsG{B7O(LVRsOY{tLDRBq;+01_1g@4?dra%x=CPBavu=&F!d5?jmU6-7<(q` z3xmq~wi(b$HuJsoTDXp)>SrBQy$TP#vf8@xi&8z~`N(3>oYV13Ju}j5hONfnFWkaz z?^C-iYC50Q7ZH47pxz?XL6@A|lpB*Y3TULBB4jt6CODm8{BB7AvyF`|395dd`a~l` z$C3NCI&F2SBPtnhJPY-qC3?SYTeqvqfw7uIFgUkULnpbmT}_Qo4TR#gzikMh(D|x(6B_D+KxTl&suaVvd-4-n zTGP7&^Wj>276m%bjni|SQ-@%-3mAmJ8ZcKJ4ar}BC0ahjE_Ju(;q0OsaqczE_S77L z%>QPZExBN!Uu7EbyDQq`*eEF6K891!!I%k$c6KYNje~$qI^(@%&iA_=ra)8CmkQXq zI2HQqX-oYR?dbN1Zf@UFAjy4KWbLyh;yqNUZIe=LMc4YRFA}t47v#Q)v!q5Ac)v%? zFE?~QQ3pp;%jl^RQ~r{bHN~&S?Qe6?{QBy5d)v>Gy%}5AbCOdTWUimmT@$VKS1i8I zBIEXT@!r=uDBb2NrcR34w8NdhXKuB8+InR(Y|+=GQGXZ1!B6pZ2m@@>NJlLn6|VmR zgZ_-7wm|hP26acIpzxK3SNU*8t(+Hr+iSexgMUY)R%ue_rBYhD4_j233VpX-$=dik z+y?iI+2pG(>NUJkEZOJnW)7L_$rGYJp&utgxp>MXpg^#TXLclhqF+CWZ3L>N1CXBM z)sUz8?#FFcp5QJ`$;bMjYphzOEA6kd>s!P2*H@M*EvLw6dDVOyi9`xtXYmyTY5qRZ zrX5zi(*$4ho6#H(?=~1)@go#%gtR8g?0OZi_Z0ywI>4jBSa8=Y*l7?{Q%~OF;SX(? zw4m@+qctW@@w3Wxb0iN}%G``(VQtfhYsJ{6wyNCr3r=Kk?V@gdazn&#xQ|!Ihi~wq zlXE^vqTed{ZZ6wVtUF^brG6wCKYr5O%6K=IJCXVh8L1`*Wu#m_vk-Rn!ncwp)f5L(T{%gqaXd~M?d<}kAC!{AN}Y@Kl;&+ Xe)OXs{pd$OenaEGmP{QK08jt`nu}hw diff --git a/src/Package/Fetch/testdata/no_root.tar.gz b/src/Package/Fetch/testdata/no_root.tar.gz deleted file mode 100644 index a3a4baf40fd1d6b2bd101ee59f27f88c6a61f97f..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 3172 zcmV-q44d;GiwFq9_zq?O18#3$a&K>RE_7jX0PPz6Z`(FDKj*LD{KL&I93@U-2Uu^1 zWm|?T(7O)Jb^~sAh-rzoxk{o&Qg+=g|NFi7NXnF*^aES69i}&|ttImDeSe9wTIN~u zMlKE>4o(0jlXLugbTK}O|3>olj2A#*_1t$@t)8G@6`G4%o>9($qk0EZ2-3 zNM6eH9qZor{{IwGRT|5T&DiJc3t3c3+kOvz_6AQ6508)8Wp0bY8huUv_7`byjr@w zYnkV4CYX3F(%OoQ%{Gje@Y4!i@*J`0x-5lG4y$^`=A}c?pMA!j0M*Ht_-nvcN@ncs zAp?LN*Or$V*BJv%FN9^PLLLm`d9Kz#fVZTh50a+n9Qc`XLM(ODi*$^IQ z@m_pak{A>A0-o!@Nk9ffFfBm360`(tn7TrRiiUFo|8t;`uei+lEEfrFbw=RVe9mTx z@e=!v9=&j<^pm%@XcfCsMIs9MhLd5mWcvZ4H6j0v(h!sje`%Mv^s`7t^@#wD0&dhB+~ zPSO~i`$zO9l5+T`dm9_!cSxX%F9PCGX+>$t4Nxw*lQ20c2nDnN3J2O@nW6FasqF)?ZG(Xj z7!2l$q9OY0M_S8==mo9karGh^cJ5V8U0TYx&5L{H*xF&zHibZ3zh}6-^1pY8;C2fLU7c9M~4-O!pB}x z^a^6jHh<&#xmHE2OTXBuF_RkNz5?%rc!H@6cn(h?ZqeSgg!tnQN_mRf44aacGB7Pd zx-CU2i~)y_!`+;=^U8S$oxGJRsbOnPYfC~W+6|YcwGT$ zVr!_5L@(|H1(djxUzGh>0xtE;9eWLL`h#8jib|7^mnv@Hxn9%-9F3*b9%r%r1#A%9 z3nJ4(rkxMLdR__Q?6b)9NnJ-rh<${AoHAk4DWL!X0=e+a4A?LD*Dq`vfdY46NSE-c zPs^UVV9@5ryGv8@kv!P5t--9Mkr)s>4-sugM@x}cQ^;s#Jq$>Zr4$SXcDf1zC4V1l z(-tYtX}qtw!*Go=@7Nfl_~DB-w6rG5?0OaLcNGC>v$}7;q)KoYV%8Z9zKvpRQ(M&#Q~WgNxYEA~?AZ^YwopES2T-c99Bq`rektjPg0q+IG*(02C1 zx8ORL7#Lk}dtE_agZ4E=Ox_2eY4hmb<%tjk)kK_soCB z=cCgw{~3?RlY^7d={daa=0A@BKJLAzmxwAY&VIZWP*Ok#%l-weJro}Q?JupZ%xA~P zMifwXB}y-j2ThFz{VHek+Cm-0R&EDVbM|kDD^bM=VS}b9Q0Jtt_ySXJprxs%lCT{J zJxSm{Ag@K=TndO&m=mcB=lNctnATcD{e-P?1~ms3pqM|O5;;wJ+z8B-IF`W*bkR(H zpdfXT6Uzq?kSB#KWlfOqfwEmc+g&PxaJhHeJJ~25S zj*bjp3HDcz%-^PDL_8F}N7OFSL6ACydzn$%icd%)?1^~Ii^`|{_*D1j57@%hRTi>q zqf!tlhbCmrzJK|ANR=sCJWZ5x;Y-a_f%%zAm02*W7Vbu)sPnZnf^Aeyl0|kPL8akT zXb|z0Ydc7r(hmz2kb-C!xBCZJ>LQ86Wan)H(nG5azU-xtz{$sRrLee37H?XksD1p6 zo2jCrhBq{UZZHLUw$!~S*OzU(KzKlHmmeXUebj1Zq38mXd=AQVnx_uF@dxaMHlfCl zWt!LIiMmF-3$S4@|IHmjr${&5mB+p8H4aQ8BsX?Lespb@PlM-}Fa!ok2qy*ax=*Q> z+{fKoNOq8T-r%U8Fcheg4k~b3427F(m{BuryKBQve}i`6IAFuz!6)5^4-VnHpg5;K#+6k^@Q{9#GWD5ny(d84s`6SGoTG;MY3l2`;r<=xu;l!9{!dlmWL2iKZNn)*o+%!>m zUxzr-5Im!4r7T_ATDH?i6K-8V=XwSQ1Hly5%?9=t^r^wPZ!@6^!ho+;PI8097e=wc z5S)n^tUa65fcCPr|6bFeM0Ve6buIA==*?UH(R=>vtE+!rU$q~;yt@4QA6H3{^&0p2 z6SR7F0+_+*_<_%$kNg+o`}#kG=KKHI$r<#2CzJ8n0UJLsT?Bvf{*UyJPJH@sXS}EX zzeE3{@nqcf|BnHU{zngY#>eUZ#OZ(3>HjeR^nd!tiT@Ypr)L)w|0iAie-t47|6$^P z(En(B(Z&Bq0sNTt&}WeUk3Z1=>Hg=*WHjph|1lu`nE6m=yvP4Xlhd=E`@g5B<1YU{ z3cU0FufJl8_R$}(PujO?ztz&;LxJT&Yu1`LqYU^*Z?_#(s0o=Ju1`n}6M$HBH{Z_O=@Q}Ct}4k{I^P4XYSQ<1#wr3y9y_FjDF z3~;I1x-p>p6wOl{tTA|6)iehq%+ZyZAuSE_en|_OD<(M0!RL721}7hjH?pFMW9hD3 zxOskomw^o3RK}|sz`VaLNjHSHqbEll-IJ#4zQpwc@fLV%X(3wQ!rkg{`=BMh7q#sV zlC_prfE?Xt?G>2tY`tAMtLJl}MfQVR6=Cxm;#2T*`#M7CK9uW?e;qV~pWvgfO76k{ z&e;477x(4dYv?w{1K8(zZI*x>C6TG}Q@RZ3FHqCFPY!Pn59#Jg&yds!U(cHx@<%f) z$Kj>ceDfTB_vP8?Ad#iC$YT=j#o+BJP_WXT+lvl3<2KR*pu4V(lUqTHvNtDnIl`(JQ_qz@Ua z9=h$7a@WBAtb?cyI_RK-4m#+dgAO|Apo0!N=%9lRI_RK-4m#+dgAO|Apo0#675ooA KksQ4MPyhh%Hx@Dg diff --git a/src/Package/Manifest.zig b/src/Package/Manifest.zig index a8bc8b501384a01c782719602c321bb7d4525818..e66a78ae14b7c93bd233fe9327e6631b82702170 100644 --- a/src/Package/Manifest.zig +++ b/src/Package/Manifest.zig @@ -49,10 +49,6 @@ arena_state: std.heap.ArenaAllocator.State, pub const ParseOptions = struct { allow_missing_paths_field: bool = false, - /// Deprecated, to be removed after 0.14.0 is tagged. - allow_name_string: bool = true, - /// Deprecated, to be removed after 0.14.0 is tagged. - allow_missing_fingerprint: bool = true, }; pub const Error = Allocator.Error; @@ -77,8 +73,6 @@ pub fn parse(gpa: Allocator, ast: Ast, rng: std.Random, options: ParseOptions) E .dependencies_node = .none, .paths = .{}, .allow_missing_paths_field = options.allow_missing_paths_field, - .allow_name_string = options.allow_name_string, - .allow_missing_fingerprint = options.allow_missing_fingerprint, .minimum_zig_version = null, .buf = .{}, }; @@ -151,8 +145,6 @@ const Parse = struct { dependencies_node: Ast.Node.OptionalIndex, paths: std.StringArrayHashMapUnmanaged(void), allow_missing_paths_field: bool, - allow_name_string: bool, - allow_missing_fingerprint: bool, minimum_zig_version: ?std.SemanticVersion, const InnerError = error{ ParseFailure, OutOfMemory }; @@ -221,12 +213,10 @@ const Parse = struct { }); } p.id = n.id; - } else if (!p.allow_missing_fingerprint) { + } else { try appendError(p, main_token, "missing top-level 'fingerprint' field; suggested value: 0x{x}", .{ Package.Fingerprint.generate(rng, p.name).int(), }); - } else { - p.id = 0; } } @@ -395,19 +385,6 @@ const Parse = struct { const ast = p.ast; const main_token = ast.nodeMainToken(node); - if (p.allow_name_string and ast.nodeTag(node) == .string_literal) { - const name = try parseString(p, node); - if (!std.zig.isValidId(name)) - return fail(p, main_token, "name must be a valid bare zig identifier (hint: switch from string to enum literal)", .{}); - - if (name.len > max_name_len) - return fail(p, main_token, "name '{f}' exceeds max length of {d}", .{ - std.zig.fmtId(name), max_name_len, - }); - - return name; - } - if (ast.nodeTag(node) != .enum_literal) return fail(p, main_token, "expected enum literal", .{}); @@ -606,7 +583,8 @@ test "basic" { const example = \\.{ - \\ .name = "foo", + \\ .name = .foo, + \\ .fingerprint = 0x8c736521490b23df, \\ .version = "3.2.1", \\ .paths = .{""}, \\ .dependencies = .{ @@ -656,7 +634,8 @@ test "minimum_zig_version" { const example = \\.{ - \\ .name = "foo", + \\ .name = .foo, + \\ .fingerprint = 0x8c736521490b23df, \\ .version = "3.2.1", \\ .paths = .{""}, \\ .minimum_zig_version = "0.11.1", @@ -690,7 +669,8 @@ test "minimum_zig_version - invalid version" { const example = \\.{ - \\ .name = "foo", + \\ .name = .foo, + \\ .fingerprint = 0x8c736521490b23df, \\ .version = "3.2.1", \\ .minimum_zig_version = "X.11.1", \\ .paths = .{""}, diff --git a/src/main.zig b/src/main.zig index 02a49ef3e04aab0c27981d373dc3924192387927..4ef99de0aee390d513ca7cea2948aeaf8e163645 100644 --- a/src/main.zig +++ b/src/main.zig @@ -5285,8 +5285,6 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8, .job_queue = &job_queue, .omit_missing_hash_error = true, .allow_missing_paths_field = false, - .allow_missing_fingerprint = false, - .allow_name_string = false, .use_latest_commit = false, .package_root = undefined, @@ -7044,8 +7042,6 @@ fn cmdFetch( .job_queue = &job_queue, .omit_missing_hash_error = true, .allow_missing_paths_field = false, - .allow_missing_fingerprint = true, - .allow_name_string = true, .use_latest_commit = true, .package_root = undefined, -- 2.54.0 From ee21a1f988f05a5d45bcb1724095c27cc2c7259b Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Wed, 4 Feb 2026 21:40:06 -0800 Subject: [PATCH 214/499] fetch: implement recompression After fetching a package and applying the filter by deleting files that are not part of the hash, creates a recompressed $GLOBAL_CACHE/p/$PKG_HASH.tar.gz Checking this cache before fetching network URLs is not yet implemented. --- lib/std/Io.zig | 6 ++ lib/std/compress/flate/Compress.zig | 2 +- src/Package/Fetch.zig | 142 +++++++++++++++++++++++++--- 3 files changed, 136 insertions(+), 14 deletions(-) diff --git a/lib/std/Io.zig b/lib/std/Io.zig index 47ba7c2072c7bc1d954bdc21c85577cb71703bbe..c56ec6886683d707fc5f14db62e9dc7468159430 100644 --- a/lib/std/Io.zig +++ b/lib/std/Io.zig @@ -1031,6 +1031,9 @@ pub const Group = struct { /// Once this function is called, there are resources associated with the /// group. To release those resources, `Group.await` or `Group.cancel` must /// eventually be called. + /// + /// If `error.Canceled` is returned from any operation this task performs, + /// it is asserted that `function` returns `error.Canceled`. pub fn async(g: *Group, io: Io, function: anytype, args: std.meta.ArgsTuple(@TypeOf(function))) void { const Args = @TypeOf(args); const TypeErased = struct { @@ -1050,6 +1053,9 @@ pub const Group = struct { /// Once this function is called, there are resources associated with the /// group. To release those resources, `Group.await` or `Group.cancel` must /// eventually be called. + /// + /// If `error.Canceled` is returned from any operation this task performs, + /// it is asserted that `function` returns `error.Canceled`. pub fn concurrent(g: *Group, io: Io, function: anytype, args: std.meta.ArgsTuple(@TypeOf(function))) ConcurrentError!void { const Args = @TypeOf(args); const TypeErased = struct { diff --git a/lib/std/compress/flate/Compress.zig b/lib/std/compress/flate/Compress.zig index 41b7d8bf04ccf29fa9f35f2906848f62b36f294e..0a85dd9d0a3c2ce53d7735bd8e4dd1aaec52b2b2 100644 --- a/lib/std/compress/flate/Compress.zig +++ b/lib/std/compress/flate/Compress.zig @@ -267,7 +267,7 @@ pub const Options = struct { pub const best = level_9; }; -/// It is asserted `buffer` is least `flate.max_history_len` bytes. +/// It is asserted `buffer` is least `flate.max_window_len` bytes. /// It is asserted `output` has a capacity of at least 8 bytes. pub fn init( output: *Writer, diff --git a/src/Package/Fetch.zig b/src/Package/Fetch.zig index a9cc86b398ef5ee6d3d83835f41f556ade34782e..e9e340c099e57e5a072e7107785253bc34c85e70 100644 --- a/src/Package/Fetch.zig +++ b/src/Package/Fetch.zig @@ -1,28 +1,34 @@ //! Represents one independent job whose responsibility is to: //! -//! 1. Check the global zig package cache to see if the hash already exists. +//! 1. Check the local zig package directory to see if the hash already exists. //! If so, load, parse, and validate the build.zig.zon file therein, and -//! goto step 8. Likewise if the location is a relative path, treat this +//! goto step 9. Likewise if the location is a relative path, treat this //! the same as a cache hit. Otherwise, proceed. -//! 2. Fetch and unpack a URL into a temporary directory. -//! 3. Load, parse, and validate the build.zig.zon file therein. It is allowed +//! 2. Check the global package cache for a compressed tarball matching the +//! hash. If it is found, unpack the contents into a temporary directory inside +//! project local zig cache. Rename this directory into the local zig package +//! directory and goto step 9, skipping step 10. +//! 3. Fetch and unpack a URL into a temporary directory. +//! 4. Load, parse, and validate the build.zig.zon file therein. It is allowed //! for the file to be missing, in which case this fetched package is considered //! to be a "naked" package. -//! 4. Apply inclusion rules of the build.zig.zon to the temporary directory by +//! 5. Apply inclusion rules of the build.zig.zon to the temporary directory by //! deleting excluded files. If any files had errors for files that were //! ultimately excluded, those errors should be ignored, such as failure to //! create symlinks that weren't supposed to be included anyway. -//! 5. Compute the package hash based on the remaining files in the temporary +//! 6. Compute the package hash based on the remaining files in the temporary //! directory. -//! 6. Rename the temporary directory into the global zig package cache -//! directory. If the hash already exists, delete the temporary directory and -//! leave the zig package cache directory untouched as it may be in use by the -//! system. This is done even if the hash is invalid, in case the package with -//! the different hash is used in the future. -//! 7. Validate the computed hash against the expected hash. If invalid, +//! 7. Rename the temporary directory into the local zig package directory. If +//! the hash already exists, delete the temporary directory and leave the zig +//! package directory untouched as it may be in use. This is done even if +//! the hash is invalid, in case the package with the different hash is used +//! in the future. +//! 8. Validate the computed hash against the expected hash. If invalid, //! this job is done. -//! 8. Spawn a new fetch job for each dependency in the manifest file. Use +//! 9. Spawn a new fetch job for each dependency in the manifest file. Use //! a mutex and a hash map so that redundant jobs do not get queued up. +//! 10.Compress the package directory and store it into the global package +//! cache. //! //! All of this must be done with only referring to the state inside this struct //! because this work will be done in a dedicated thread. @@ -110,6 +116,7 @@ pub const JobQueue = struct { all_fetches: std.ArrayList(*Fetch) = .empty, http_client: *std.http.Client, + /// This tracks `Fetch` tasks as well as recompression tasks. group: Io.Group = .init, global_cache: Cache.Directory, local_cache: Cache.Path, @@ -293,8 +300,109 @@ pub const JobQueue = struct { \\ ); } + + fn recompress(jq: *JobQueue, package_hash: Package.Hash) Io.Cancelable!void { + var dest_sub_path_buffer: ["p/".len + Package.Hash.max_len + ".tar.gz".len]u8 = undefined; + const dest_path: Cache.Path = .{ + .root_dir = jq.global_cache, + .sub_path = std.fmt.bufPrint(&dest_sub_path_buffer, "p/{s}.tar.gz", .{ + package_hash.toSlice(), + }) catch unreachable, + }; + + const gpa = jq.http_client.allocator; + + var arena_instance = std.heap.ArenaAllocator.init(gpa); + defer arena_instance.deinit(); + const arena = arena_instance.allocator(); + + recompressFallible(jq, arena, dest_path, package_hash.toSlice()) catch |err| switch (err) { + error.Canceled => |e| return e, + error.ReadFailed => comptime unreachable, + error.WriteFailed => comptime unreachable, + else => |e| std.log.warn("failed caching recompressed tarball to {f}: {t}", .{ dest_path, e }), + }; + } + + fn recompressFallible(jq: *JobQueue, arena: Allocator, dest_path: Cache.Path, package_hash: []const u8) !void { + const gpa = jq.http_client.allocator; + const io = jq.io; + + // We have to walk the file system up front in order to sort the file + // list for determinism purposes. The hash of the recompressed file is + // not critical because the true hash is based on the content alone. + // However, if we want Zig users to be able to share cached package + // data with each other via peer-to-peer protocols, we benefit greatly + // from the data being identical on everyone's computers. + var scanned_files: std.ArrayList([]const u8) = .empty; + defer scanned_files.deinit(gpa); + + var pkg_dir = try jq.root_pkg_path.openDir(io, package_hash, .{ .iterate = true }); + defer pkg_dir.close(io); + + { + var walker = try pkg_dir.walk(gpa); + defer walker.deinit(); + + while (try walker.next(io)) |entry| { + switch (entry.kind) { + .directory => continue, + .file, .sym_link => {}, + else => { + return error.IllegalFileType; + }, + } + const entry_path = try arena.dupe(u8, entry.path); + try scanned_files.append(gpa, entry_path); + } + + std.mem.sortUnstable([]const u8, scanned_files.items, {}, stringCmp); + } + + var atomic_file = try dest_path.root_dir.handle.createFileAtomic(io, dest_path.sub_path, .{ + .make_path = true, + .replace = true, + }); + defer atomic_file.deinit(io); + + var file_write_buffer: [4096]u8 = undefined; + var file_writer = atomic_file.file.writer(io, &file_write_buffer); + + var compress_buffer: [std.compress.flate.max_window_len]u8 = undefined; + var compress = std.compress.flate.Compress.init(&file_writer.interface, &compress_buffer, .gzip, .level_9) catch |err| switch (err) { + error.WriteFailed => return file_writer.err.?, + }; + + var archiver: std.tar.Writer = .{ .underlying_writer = &compress.writer }; + archiver.prefix = package_hash; + + var file_read_buffer: [4096]u8 = undefined; + + for (scanned_files.items) |entry_path| { + var file = try pkg_dir.openFile(io, entry_path, .{}); + defer file.close(io); + var file_reader: Io.File.Reader = .init(file, io, &file_read_buffer); + archiver.writeFile(entry_path, &file_reader, 0) catch |err| switch (err) { + error.ReadFailed => return file_reader.err.?, + error.WriteFailed => return file_writer.err.?, + else => |e| return e, + }; + } + + // intentionally omitting the pointless trailer + //try archiver.finish(); + compress.writer.flush() catch |err| switch (err) { + error.WriteFailed => return file_writer.err.?, + }; + try file_writer.flush(); + try atomic_file.replace(io); + } }; +fn stringCmp(_: void, lhs: []const u8, rhs: []const u8) bool { + return std.mem.lessThan(u8, lhs, rhs); +} + pub const Location = union(enum) { remote: Remote, /// A directory found inside the parent package. @@ -477,8 +585,11 @@ fn runResource( remote_hash: ?Package.Hash, ) RunError!void { const job_queue = f.job_queue; + assert(!job_queue.read_only); + const io = job_queue.io; defer resource.deinit(io); + const arena = f.arena.allocator(); const eb = &f.error_bundle; const s = fs.path.sep_str; @@ -556,6 +667,11 @@ fn runResource( ) }); return error.FetchFailed; }; + + // Spin off a task to recompress the tarball, with filtered files deleted, into + // the global cache. + job_queue.group.async(io, JobQueue.recompress, .{ job_queue, computed_package_hash }); + // Remove temporary directory root if not already renamed to global cache. if (!package_sub_path.eql(tmp_directory_path)) { tmp_directory_path.root_dir.handle.deleteDir(io, tmp_directory_path.sub_path) catch |err| switch (err) { -- 2.54.0 From 7246eee1e706b142abf8183e960fd692fde52bb0 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 5 Feb 2026 16:44:37 -0800 Subject: [PATCH 215/499] std.Progress: add Node.startFmt convenience method for starting a child node with a formatted string as a name. --- lib/std/Progress.zig | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/std/Progress.zig b/lib/std/Progress.zig index 2240f95fddd65b97681d6803f4e9ffef0d0c8369..aee7602bf58a2b30d47afe98387ebeff4024fc22 100644 --- a/lib/std/Progress.zig +++ b/lib/std/Progress.zig @@ -325,6 +325,12 @@ pub const Node = struct { return init(@enumFromInt(free_index), parent, name, estimated_total_items); } + pub fn startFmt(node: Node, estimated_total_items: usize, comptime format: []const u8, args: anytype) Node { + var buffer: [max_name_len]u8 = undefined; + const name = std.fmt.bufPrint(&buffer, format, args) catch &buffer; + return Node.start(node, name, estimated_total_items); + } + /// This is the same as calling `start` and then `end` on the returned `Node`. Thread-safe. pub fn completeOne(n: Node) void { const index = n.index.unwrap() orelse return; -- 2.54.0 From 1f65e7cccc3124ce2fa9a5e8107c32367a39ec29 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 5 Feb 2026 16:46:20 -0800 Subject: [PATCH 216/499] fetch: recompress task integrates with std.Progress --- src/Package/Fetch.zig | 30 ++++++++++++++++++++++-------- src/main.zig | 2 ++ 2 files changed, 24 insertions(+), 8 deletions(-) diff --git a/src/Package/Fetch.zig b/src/Package/Fetch.zig index e9e340c099e57e5a072e7107785253bc34c85e70..7d69265f5c42b8475b2354ca6ea5544ffbdcfea8 100644 --- a/src/Package/Fetch.zig +++ b/src/Package/Fetch.zig @@ -114,6 +114,7 @@ pub const JobQueue = struct { /// field contains references to all of them. /// Protected by `mutex`. all_fetches: std.ArrayList(*Fetch) = .empty, + prog_node: std.Progress.Node, http_client: *std.http.Client, /// This tracks `Fetch` tasks as well as recompression tasks. @@ -302,12 +303,15 @@ pub const JobQueue = struct { } fn recompress(jq: *JobQueue, package_hash: Package.Hash) Io.Cancelable!void { - var dest_sub_path_buffer: ["p/".len + Package.Hash.max_len + ".tar.gz".len]u8 = undefined; + const pkg_hash_slice = package_hash.toSlice(); + + const prog_node = jq.prog_node.startFmt(0, "recompress {s}", .{pkg_hash_slice}); + defer prog_node.end(); + + var dest_sub_path_buf: ["p/".len + Package.Hash.max_len + ".tar.gz".len]u8 = undefined; const dest_path: Cache.Path = .{ .root_dir = jq.global_cache, - .sub_path = std.fmt.bufPrint(&dest_sub_path_buffer, "p/{s}.tar.gz", .{ - package_hash.toSlice(), - }) catch unreachable, + .sub_path = std.fmt.bufPrint(&dest_sub_path_buf, "p/{s}.tar.gz", .{pkg_hash_slice}) catch unreachable, }; const gpa = jq.http_client.allocator; @@ -316,7 +320,7 @@ pub const JobQueue = struct { defer arena_instance.deinit(); const arena = arena_instance.allocator(); - recompressFallible(jq, arena, dest_path, package_hash.toSlice()) catch |err| switch (err) { + recompressFallible(jq, arena, dest_path, pkg_hash_slice, prog_node) catch |err| switch (err) { error.Canceled => |e| return e, error.ReadFailed => comptime unreachable, error.WriteFailed => comptime unreachable, @@ -324,7 +328,13 @@ pub const JobQueue = struct { }; } - fn recompressFallible(jq: *JobQueue, arena: Allocator, dest_path: Cache.Path, package_hash: []const u8) !void { + fn recompressFallible( + jq: *JobQueue, + arena: Allocator, + dest_path: Cache.Path, + pkg_hash_slice: []const u8, + prog_node: std.Progress.Node, + ) !void { const gpa = jq.http_client.allocator; const io = jq.io; @@ -337,7 +347,7 @@ pub const JobQueue = struct { var scanned_files: std.ArrayList([]const u8) = .empty; defer scanned_files.deinit(gpa); - var pkg_dir = try jq.root_pkg_path.openDir(io, package_hash, .{ .iterate = true }); + var pkg_dir = try jq.root_pkg_path.openDir(io, pkg_hash_slice, .{ .iterate = true }); defer pkg_dir.close(io); { @@ -359,6 +369,8 @@ pub const JobQueue = struct { std.mem.sortUnstable([]const u8, scanned_files.items, {}, stringCmp); } + prog_node.setEstimatedTotalItems(scanned_files.items.len); + var atomic_file = try dest_path.root_dir.handle.createFileAtomic(io, dest_path.sub_path, .{ .make_path = true, .replace = true, @@ -374,7 +386,7 @@ pub const JobQueue = struct { }; var archiver: std.tar.Writer = .{ .underlying_writer = &compress.writer }; - archiver.prefix = package_hash; + archiver.prefix = pkg_hash_slice; var file_read_buffer: [4096]u8 = undefined; @@ -387,6 +399,7 @@ pub const JobQueue = struct { error.WriteFailed => return file_writer.err.?, else => |e| return e, }; + prog_node.completeOne(); } // intentionally omitting the pointless trailer @@ -2259,6 +2272,7 @@ const TestFetchBuilder = struct { .read_only = false, .debug_hash = false, .mode = .needed, + .prog_node = std.Progress.Node.none, }; self.fetch = .{ diff --git a/src/main.zig b/src/main.zig index 4ef99de0aee390d513ca7cea2948aeaf8e163645..dba52807f544973724264eb1eda73f8c0452d28a 100644 --- a/src/main.zig +++ b/src/main.zig @@ -5246,6 +5246,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8, .debug_hash = false, .unlazy_set = unlazy_set, .mode = fetch_mode, + .prog_node = fetch_prog_node, }; defer job_queue.deinit(); @@ -7026,6 +7027,7 @@ fn cmdFetch( .read_only = false, .debug_hash = debug_hash, .mode = .all, + .prog_node = root_prog_node, }; defer job_queue.deinit(); -- 2.54.0 From d8171e8a2ee56e76bcd91f187d5ca5664b87bc83 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 5 Feb 2026 17:36:14 -0800 Subject: [PATCH 217/499] fetch: check global cache for compressed tarball before remote URL --- src/Package/Fetch.zig | 72 ++++++++++++++++++++++++++++++------------- 1 file changed, 51 insertions(+), 21 deletions(-) diff --git a/src/Package/Fetch.zig b/src/Package/Fetch.zig index 7d69265f5c42b8475b2354ca6ea5544ffbdcfea8..d873fc9bd9a3eb4d5c316c273a6e4e05e3e77dc4 100644 --- a/src/Package/Fetch.zig +++ b/src/Package/Fetch.zig @@ -40,6 +40,7 @@ const native_os = builtin.os.tag; const std = @import("std"); const Io = std.Io; const fs = std.fs; +const log = std.log.scoped(.fetch); const assert = std.debug.assert; const ascii = std.ascii; const Allocator = std.mem.Allocator; @@ -324,7 +325,7 @@ pub const JobQueue = struct { error.Canceled => |e| return e, error.ReadFailed => comptime unreachable, error.WriteFailed => comptime unreachable, - else => |e| std.log.warn("failed caching recompressed tarball to {f}: {t}", .{ dest_path, e }), + else => |e| log.warn("failed caching recompressed tarball to {f}: {t}", .{ dest_path, e }), }; } @@ -508,14 +509,14 @@ pub fn run(f: *Fetch) RunError!void { .path_or_url => |path_or_url| { if (Io.Dir.cwd().openDir(io, path_or_url, .{ .iterate = true })) |dir| { var resource: Resource = .{ .dir = dir }; - return f.runResource(path_or_url, &resource, null); + return f.runResource(path_or_url, &resource, null, false); } else |dir_err| { var server_header_buffer: [init_resource_buffer_size]u8 = undefined; const file_err = if (dir_err == error.NotDir) e: { if (Io.Dir.cwd().openFile(io, path_or_url, .{})) |file| { var resource: Resource = .{ .file = file.reader(io, &server_header_buffer) }; - return f.runResource(path_or_url, &resource, null); + return f.runResource(path_or_url, &resource, null, false); } else |err| break :e err; } else dir_err; @@ -527,11 +528,13 @@ pub fn run(f: *Fetch) RunError!void { }; var resource: Resource = undefined; try f.initResource(uri, &resource, &server_header_buffer); - return f.runResource(try uri.path.toRawMaybeAlloc(arena), &resource, null); + return f.runResource(try uri.path.toRawMaybeAlloc(arena), &resource, null, false); } }, }; + var resource_buffer: [init_resource_buffer_size]u8 = undefined; + if (remote.hash) |expected_hash| { const package_root = try job_queue.root_pkg_path.join(arena, expected_hash.toSlice()); if (package_root.root_dir.handle.access(io, package_root.sub_path, .{})) |_| { @@ -543,19 +546,13 @@ pub fn run(f: *Fetch) RunError!void { return queueJobsForDeps(f); } else |err| switch (err) { error.FileNotFound => { - switch (f.lazy_status) { - .eager => {}, - .available => if (!job_queue.unlazy_set.contains(expected_hash)) { - f.lazy_status = .unavailable; - return; - }, - .unavailable => unreachable, - } + log.debug("FileNotFound: {f}", .{package_root}); if (job_queue.read_only) return f.fail( f.name_tok, try eb.printString("package not found at '{f}'", .{package_root}), ); }, + error.Canceled => |e| return e, else => |e| { try eb.addRootErrorMessage(.{ .msg = try eb.printString("unable to open package cache directory {f}: {t}", .{ @@ -565,6 +562,38 @@ pub fn run(f: *Fetch) RunError!void { return error.FetchFailed; }, } + + // Check global cache before remote fetch. + const cached_tarball_sub_path = try std.fmt.allocPrint(arena, "p/{s}.tar.gz", .{expected_hash.toSlice()}); + const cached_tarball_path: Cache.Path = .{ + .root_dir = job_queue.global_cache, + .sub_path = cached_tarball_sub_path, + }; + if (cached_tarball_path.root_dir.handle.openFile(io, cached_tarball_path.sub_path, .{})) |file| { + log.debug("found global cached tarball {f}", .{cached_tarball_path}); + var resource: Resource = .{ .file = file.reader(io, &resource_buffer) }; + return f.runResource(cached_tarball_sub_path, &resource, remote.hash, true); + } else |err| switch (err) { + error.FileNotFound => log.debug("FileNotFound: {f}", .{cached_tarball_path}), + error.Canceled => |e| return e, + else => |e| { + try eb.addRootErrorMessage(.{ + .msg = try eb.printString("unable to open globally cached package {f}: {t}", .{ + cached_tarball_path, e, + }), + }); + return error.FetchFailed; + }, + } + + switch (f.lazy_status) { + .eager => {}, + .available => if (!job_queue.unlazy_set.contains(expected_hash)) { + f.lazy_status = .unavailable; + return; + }, + .unavailable => unreachable, + } } else if (job_queue.read_only) { try eb.addRootErrorMessage(.{ .msg = try eb.addString("dependency is missing hash field"), @@ -574,15 +603,13 @@ pub fn run(f: *Fetch) RunError!void { } // Fetch and unpack the remote into a temporary directory. - const uri = std.Uri.parse(remote.url) catch |err| return f.fail( f.location_tok, try eb.printString("invalid URI: {t}", .{err}), ); - var buffer: [init_resource_buffer_size]u8 = undefined; var resource: Resource = undefined; - try f.initResource(uri, &resource, &buffer); - return f.runResource(try uri.path.toRawMaybeAlloc(arena), &resource, remote.hash); + try f.initResource(uri, &resource, &resource_buffer); + return f.runResource(try uri.path.toRawMaybeAlloc(arena), &resource, remote.hash, false); } pub fn deinit(f: *Fetch) void { @@ -596,6 +623,7 @@ fn runResource( uri_path: []const u8, resource: *Resource, remote_hash: ?Package.Hash, + disable_recompress: bool, ) RunError!void { const job_queue = f.job_queue; assert(!job_queue.read_only); @@ -681,15 +709,17 @@ fn runResource( return error.FetchFailed; }; - // Spin off a task to recompress the tarball, with filtered files deleted, into - // the global cache. - job_queue.group.async(io, JobQueue.recompress, .{ job_queue, computed_package_hash }); + if (!disable_recompress) { + // Spin off a task to recompress the tarball, with filtered files deleted, into + // the global cache. + job_queue.group.async(io, JobQueue.recompress, .{ job_queue, computed_package_hash }); + } // Remove temporary directory root if not already renamed to global cache. if (!package_sub_path.eql(tmp_directory_path)) { tmp_directory_path.root_dir.handle.deleteDir(io, tmp_directory_path.sub_path) catch |err| switch (err) { error.Canceled => |e| return e, - else => |e| std.log.warn("failed to delete temporary directory {f}: {t}", .{ tmp_directory_path, e }), + else => |e| log.warn("failed to delete temporary directory {f}: {t}", .{ tmp_directory_path, e }), }; } @@ -1588,7 +1618,7 @@ pub fn renameTmpIntoCache(io: Io, tmp_path: Cache.Path, dest_path: Cache.Path) ! error.Canceled => |e| return e, // Garbage files leftover in zig-cache/tmp/ is, as they say // on Star Trek, "operating within normal parameters". - else => |e| std.log.warn("failed to delete temporary directory {f}: {t}", .{ tmp_path, e }), + else => |e| log.warn("failed to delete temporary directory {f}: {t}", .{ tmp_path, e }), }; }, else => |e| return e, -- 2.54.0 From 36b65ab59e5e514ad06a11cde96b87c565d86ac8 Mon Sep 17 00:00:00 2001 From: Mathieu Suen Date: Tue, 20 Jan 2026 15:15:57 +0100 Subject: [PATCH 218/499] Air: add "unwrap" functions for loading extra data --- src/Air.zig | 281 +++++++++++++++++++++++++++++--- src/Air/Liveness.zig | 91 +++++------ src/Air/Liveness/Verify.zig | 70 +++----- src/Air/print.zig | 138 ++++++---------- src/Air/types_resolved.zig | 52 +++--- src/Sema.zig | 12 +- src/codegen/aarch64/Select.zig | 198 ++++++++++------------ src/codegen/c.zig | 157 +++++++----------- src/codegen/llvm.zig | 147 +++++++---------- src/codegen/riscv64/CodeGen.zig | 120 ++++++-------- src/codegen/sparc64/CodeGen.zig | 102 +++++------- src/codegen/spirv/CodeGen.zig | 126 ++++++-------- src/codegen/wasm/CodeGen.zig | 60 +++---- src/codegen/x86_64/CodeGen.zig | 203 ++++++++++------------- 14 files changed, 864 insertions(+), 893 deletions(-) diff --git a/src/Air.zig b/src/Air.zig index b5cb950d493278244cb5cc9e859d6a69c5aaf338..28b7a27ba992f375fffa3a96e7045f5de5952d1e 100644 --- a/src/Air.zig +++ b/src/Air.zig @@ -281,16 +281,21 @@ pub const Inst = struct { /// also supports enums and pointers. /// Uses the `ty_op` field. bitcast, - /// Uses the `ty_pl` field with payload `Block`. A block runs its body which always ends - /// with a `noreturn` instruction, so the only way to proceed to the code after the `block` - /// is to encounter a `br` that targets this `block`. If the `block` type is `noreturn`, + /// A block runs its body which always ends with a `noreturn` instruction, + /// so the only way to proceed to the code after the `block` is to encounter a `br` + /// that targets this `block`. If the `block` type is `noreturn`, /// then there do not exist any `br` instructions targeting this `block`. + /// Uses the `ty_pl` field with payload `Block`. + /// + /// See `unwrapBlock` for a way to load this tag's data. block, /// A labeled block of code that loops forever. The body must be `noreturn`: loops /// occur through an explicit `repeat` instruction pointing back to this one. /// Result type is always `noreturn`; no instructions in a block follow this one. /// There is always at least one `repeat` instruction referencing the loop. /// Uses the `ty_pl` field. Payload is `Block`. + /// + /// See `unwrapBlock` for a way to load this tag's data. loop, /// Sends control flow back to the beginning of a parent `loop` body. /// Uses the `repeat` field. @@ -319,6 +324,8 @@ pub const Inst = struct { /// Result type is the return type of the function being called. /// Uses the `pl_op` field with the `Call` payload. operand is the callee. /// Triggers `resolveTypeLayout` on the return type of the callee. + /// + /// See `unwrapCall` for a way to load this tag's data. call, /// Same as `call` except with the `always_tail` attribute. call_always_tail, @@ -436,14 +443,20 @@ pub const Inst = struct { /// Conditional branch. /// Result type is always noreturn; no instructions in a block follow this one. /// Uses the `pl_op` field. Operand is the condition. Payload is `CondBr`. + /// + /// See `unwrapCondBr` for a way to load this tags's data. cond_br, /// Switch branch. /// Result type is always noreturn; no instructions in a block follow this one. /// Uses the `pl_op` field. Operand is the condition. Payload is `SwitchBr`. + /// + /// See `unwrapSwitch` for a way to load this tags's data. switch_br, /// Switch branch which can dispatch back to itself with a different operand. /// Result type is always noreturn; no instructions in a block follow this one. /// Uses the `pl_op` field. Operand is the condition. Payload is `SwitchBr`. + /// + /// See `unwrapSwitch` for a way to load this tags's data. loop_switch_br, /// Dispatches back to a branch of a parent `loop_switch_br`. /// Result type is always noreturn; no instructions in a block follow this one. @@ -458,6 +471,8 @@ pub const Inst = struct { /// payload value, as if `unwrap_errunion_payload` was executed on the operand. /// The error branch is considered to have a branch hint of `.unlikely`. /// Uses the `pl_op` field. Payload is `Try`. + /// + /// See `unwrapTry` for a way to load this tag's data. @"try", /// Same as `try` except the error branch hint is `.cold`. try_cold, @@ -465,6 +480,8 @@ pub const Inst = struct { /// result is a pointer to the payload. Result is as if `unwrap_errunion_payload_ptr` /// was executed on the operand. /// Uses the `ty_pl` field. Payload is `TryPtr`. + /// + /// See `unwrapTryPtr` for a way to load this tag's data. try_ptr, /// Same as `try_ptr` except the error branch hint is `.cold`. try_ptr_cold, @@ -476,6 +493,8 @@ pub const Inst = struct { dbg_empty_stmt, /// A block that represents an inlined function call. /// Uses the `ty_pl` field. Payload is `DbgInlineBlock`. + /// + /// See `unwrapBlock` for a way to load this tag's data. dbg_inline_block, /// Marks the beginning of a local variable. The operand is a pointer pointing /// to the storage for the variable. The local may be a const or a var. @@ -715,7 +734,7 @@ pub const Inst = struct { /// Uses the `ty_pl` field, where the payload index points to: /// 1. mask_elem: ShuffleOneMask // for each `mask_len`, which comes from `ty_pl.ty` /// 2. operand: Ref // guaranteed not to be an interned value - /// See `unwrapShuffleOne`. + /// See `unwrapShuffleOne` for a way to load this tag's data. shuffle_one, /// Constructs a vector by selecting elements from two vectors based on a mask. Each mask /// element is either an index into one of the vectors, or "undef". @@ -723,7 +742,7 @@ pub const Inst = struct { /// 1. mask_elem: ShuffleOneMask // for each `mask_len`, which comes from `ty_pl.ty` /// 2. operand_a: Ref // guaranteed not to be an interned value /// 3. operand_b: Ref // guaranteed not to be an interned value - /// See `unwrapShuffleTwo`. + /// See `unwrapShuffleTwo` for a way to load this tag's data.. shuffle_two, /// Constructs a vector element-wise from `a` or `b` based on `pred`. /// Uses the `pl_op` field with `pred` as operand, and payload `Bin`. @@ -944,6 +963,8 @@ pub const Inst = struct { /// The calling convention is given by `func.@"callconv"(target)`. /// The return type (and hence the result type of this instruction) is `func.returnType()`. /// The parameter types are the types of the arguments given in `Air.Call`. + /// + /// See `unwrapCompilerRtCall` for a way to load this tag's data. legalize_compiler_rt_call, pub fn fromCmpOp(op: std.math.CompareOperator, optimized: bool) Tag { @@ -1445,18 +1466,18 @@ pub const ShuffleTwoMask = enum(u32) { /// Trailing: /// 0. `Inst.Ref` for every outputs_len /// 1. `Inst.Ref` for every inputs_len -/// 2. for every outputs_len -/// - constraint: memory at this position is reinterpreted as a null -/// terminated string. -/// - name: memory at this position is reinterpreted as a null -/// terminated string. pad to the next u32 after the null byte. -/// 3. for every inputs_len -/// - constraint: memory at this position is reinterpreted as a null -/// terminated string. -/// - name: memory at this position is reinterpreted as a null -/// terminated string. pad to the next u32 after the null byte. -/// 4. A number of u32 elements follow according to the equation `(source_len + 3) / 4`. +/// 2. A number of u32 elements follow according to the equation `(source_len + 3) / 4`. /// Memory starting at this position is reinterpreted as the source bytes. +/// 3. for every outputs_len +/// - constraint: memory at this position is reinterpreted as a null +/// terminated string. +/// - name: memory at this position is reinterpreted as a null +/// terminated string. pad to the next u32 after the null byte. +/// 4. for every inputs_len +/// - constraint: memory at this position is reinterpreted as a null +/// terminated string. +/// - name: memory at this position is reinterpreted as a null +/// terminated string. pad to the next u32 after the null byte. pub const Asm = struct { /// Length of the assembly source in bytes. source_len: u32, @@ -2157,11 +2178,229 @@ pub fn unwrapSwitch(air: *const Air, switch_inst: Inst.Index) UnwrappedSwitch { }; } -pub fn unwrapShuffleOne(air: *const Air, zcu: *const Zcu, inst_index: Inst.Index) struct { +pub const UnwrappedDbgInlineBlock = struct { + func: InternPool.Index, + body: []const Inst.Index, + ty: Type, +}; + +pub fn unwrapDbgBlock(air: *const Air, inst_index: Inst.Index) UnwrappedDbgInlineBlock { + const data = air.instructions.items(.data)[@intFromEnum(inst_index)]; + const tag = air.instructions.items(.tag)[@intFromEnum(inst_index)]; + assert(tag == .dbg_inline_block); + const payload = data.ty_pl.payload; + const extra = air.extraData(Air.DbgInlineBlock, payload); + return .{ + .func = extra.data.func, + .ty = data.ty_pl.ty.toType(), + .body = @ptrCast(air.extra.items[extra.end..][0..extra.data.body_len]), + }; +} + +pub const UnwrappedBlock = struct { + body: []const Inst.Index, + ty: Type, +}; + +pub fn unwrapBlock(air: *const Air, inst_index: Inst.Index) UnwrappedBlock { + const data = air.instructions.items(.data)[@intFromEnum(inst_index)]; + const tag = air.instructions.items(.tag)[@intFromEnum(inst_index)]; + const payload = switch (tag) { + .block, .loop => data.ty_pl.payload, + else => unreachable, + }; + const extra = air.extraData(Air.Block, payload); + return .{ + .ty = data.ty_pl.ty.toType(), + .body = @ptrCast(air.extra.items[extra.end..][0..extra.data.body_len]), + }; +} + +pub const UnwrappedCall = struct { + callee: Inst.Ref, + args: []const Air.Inst.Ref, +}; + +pub fn unwrapCall(air: *const Air, inst_index: Inst.Index) UnwrappedCall { + const data = air.instructions.items(.data)[@intFromEnum(inst_index)]; + const tag = air.instructions.items(.tag)[@intFromEnum(inst_index)]; + const payload = switch (tag) { + .call, .call_always_tail, .call_never_tail, .call_never_inline => data.pl_op.payload, + else => unreachable, + }; + const extra = air.extraData(Air.Call, payload); + return .{ + .callee = data.pl_op.operand, + .args = @ptrCast(air.extra.items[extra.end..][0..extra.data.args_len]), + }; +} + +pub const UnwrappedCompilerRtCall = struct { + func: CompilerRtFunc, + args: []const Air.Inst.Ref, +}; + +pub fn unwrapCompilerRtCall(air: *const Air, inst_index: Inst.Index) UnwrappedCompilerRtCall { + const data = air.instructions.items(.data)[@intFromEnum(inst_index)]; + const tag = air.instructions.items(.tag)[@intFromEnum(inst_index)]; + assert(tag == .legalize_compiler_rt_call); + const payload = data.legalize_compiler_rt_call.payload; + const extra = air.extraData(Air.Call, payload); + return .{ + .func = data.legalize_compiler_rt_call.func, + .args = @ptrCast(air.extra.items[extra.end..][0..extra.data.args_len]), + }; +} + +pub const UnwrappedCondBr = struct { + condition: Inst.Ref, + then_body: []const Inst.Index, + else_body: []const Inst.Index, + branch_hints: CondBr.BranchHints, +}; + +pub fn unwrapCondBr(air: *const Air, inst_index: Inst.Index) UnwrappedCondBr { + const data = air.instructions.items(.data)[@intFromEnum(inst_index)]; + const tag = air.instructions.items(.tag)[@intFromEnum(inst_index)]; + assert(tag == .cond_br); + const payload = data.pl_op.payload; + const extra = air.extraData(Air.CondBr, payload); + return .{ + .condition = data.pl_op.operand, + .then_body = @ptrCast(air.extra.items[extra.end..][0..extra.data.then_body_len]), + .else_body = @ptrCast(air.extra.items[extra.end + extra.data.then_body_len ..][0..extra.data.else_body_len]), + .branch_hints = extra.data.branch_hints, + }; +} + +pub const UnwrappedTry = struct { + error_union: Inst.Ref, + else_body: []const Inst.Index, +}; + +pub fn unwrapTry(air: *const Air, inst_index: Inst.Index) UnwrappedTry { + const data = air.instructions.items(.data)[@intFromEnum(inst_index)]; + const tag = air.instructions.items(.tag)[@intFromEnum(inst_index)]; + assert(tag == .@"try" or tag == .try_cold); + const payload = data.pl_op.payload; + const extra = air.extraData(Air.Try, payload); + return .{ + .error_union = data.pl_op.operand, + .else_body = @ptrCast(air.extra.items[extra.end..][0..extra.data.body_len]), + }; +} + +pub const UnwrappedTryPtr = struct { + error_union_payload_ptr_ty: Inst.Ref, + error_union_ptr: Inst.Ref, + else_body: []const Inst.Index, +}; + +pub fn unwrapTryPtr(air: *const Air, inst_index: Inst.Index) UnwrappedTryPtr { + const data = air.instructions.items(.data)[@intFromEnum(inst_index)]; + const tag = air.instructions.items(.tag)[@intFromEnum(inst_index)]; + assert(tag == .try_ptr or tag == .try_ptr_cold); + const payload = data.ty_pl.payload; + const extra = air.extraData(Air.TryPtr, payload); + return .{ + .error_union_ptr = extra.data.ptr, + .error_union_payload_ptr_ty = data.ty_pl.ty, + .else_body = @ptrCast(air.extra.items[extra.end..][0..extra.data.body_len]), + }; +} + +pub const UnwrappedAsm = struct { + outputs: []const Air.Inst.Ref, + inputs: []const Air.Inst.Ref, + source: [:0]u8, + input_constraint_names: []const u32, + output_constraint_names: []const u32, + clobbers: InternPool.Index, + is_volatile: bool, + + const AsmIterator = struct { + current: u32, + operands: []const Air.Inst.Ref, + constraint_names: []const u32, + + pub fn next(self: *AsmIterator) ?struct { constraint: []const u8, operand: Inst.Ref, name: []const u8, index: u32 } { + if (self.current >= self.operands.len) { + return null; + } + defer { + self.current += 1; + } + + const constraint_name = std.mem.sliceAsBytes(self.constraint_names); + const constraint = std.mem.sliceTo(constraint_name, 0); + const name = std.mem.sliceTo(constraint_name[constraint.len + 1 ..], 0); + // This equation accounts for the fact that even if we have exactly 4 bytes + // for the string, we still use the next u32 for the null terminator. + const next_offset = std.math.divCeil(usize, constraint.len + 1 + name.len + 1, @sizeOf(u32)) catch unreachable; + self.constraint_names = self.constraint_names[next_offset..]; + + return .{ + .constraint = constraint, + .operand = self.operands[self.current], + .name = name, + .index = self.current, + }; + } + }; + + pub fn iterateInputs(self: *const UnwrappedAsm) AsmIterator { + return .{ + .current = 0, + .operands = self.inputs, + .constraint_names = self.input_constraint_names, + }; + } + + pub fn iterateOutputs(self: *const UnwrappedAsm) AsmIterator { + return .{ + .current = 0, + .operands = self.outputs, + .constraint_names = self.output_constraint_names, + }; + } +}; + +pub fn unwrapAsm(air: *const Air, inst_index: Inst.Index) UnwrappedAsm { + const data = air.instructions.items(.data)[@intFromEnum(inst_index)]; + const tag = air.instructions.items(.tag)[@intFromEnum(inst_index)]; + assert(tag == .assembly); + const payload = data.ty_pl.payload; + const extra = air.extraData(Air.Asm, payload); + const source_start = extra.end + extra.data.flags.outputs_len + extra.data.inputs_len; + const output_constraint_name_start = source_start + (extra.data.source_len / 4) + 1; + const output_constraint_name = air.extra.items[output_constraint_name_start..]; + const outputs: []Inst.Ref = @ptrCast(air.extra.items[extra.end..][0..extra.data.flags.outputs_len]); + // Get the input names and constraints offset place after the output. + var it = UnwrappedAsm.AsmIterator{ + .current = 0, + .constraint_names = output_constraint_name, + .operands = outputs, + }; + while (it.next()) |_| {} + + return .{ + .clobbers = extra.data.clobbers, + .is_volatile = extra.data.flags.is_volatile, + .inputs = @ptrCast(air.extra.items[extra.end + extra.data.flags.outputs_len ..][0..extra.data.inputs_len]), + .outputs = outputs, + .source = std.mem.sliceAsBytes(air.extra.items[source_start..])[0..extra.data.source_len :0], + .output_constraint_names = output_constraint_name, + .input_constraint_names = it.constraint_names, + }; +} + +pub const UnwrappedShuffleOne = struct { result_ty: Type, operand: Inst.Ref, mask: []const ShuffleOneMask, -} { +}; + +pub fn unwrapShuffleOne(air: *const Air, zcu: *const Zcu, inst_index: Inst.Index) UnwrappedShuffleOne { const inst = air.instructions.get(@intFromEnum(inst_index)); switch (inst.tag) { .shuffle_one => {}, @@ -2177,12 +2416,14 @@ pub fn unwrapShuffleOne(air: *const Air, zcu: *const Zcu, inst_index: Inst.Index }; } -pub fn unwrapShuffleTwo(air: *const Air, zcu: *const Zcu, inst_index: Inst.Index) struct { +pub const UnwrappedShuffleTwo = struct { result_ty: Type, operand_a: Inst.Ref, operand_b: Inst.Ref, mask: []const ShuffleTwoMask, -} { +}; + +pub fn unwrapShuffleTwo(air: *const Air, zcu: *const Zcu, inst_index: Inst.Index) UnwrappedShuffleTwo { const inst = air.instructions.get(@intFromEnum(inst_index)); switch (inst.tag) { .shuffle_two => {}, diff --git a/src/Air/Liveness.zig b/src/Air/Liveness.zig index 9b722973a66672d5b4f3d071f6dda99ee76ab084..a85944c4678455126d04403e1c48486db9d6e860 100644 --- a/src/Air/Liveness.zig +++ b/src/Air/Liveness.zig @@ -17,6 +17,7 @@ const trace = @import("../tracy.zig").trace; const Air = @import("../Air.zig"); const InternPool = @import("../InternPool.zig"); const Zcu = @import("../Zcu.zig"); +const Type = @import("../Type.zig"); pub const Verify = @import("Liveness/Verify.zig"); @@ -609,13 +610,11 @@ fn analyzeInst( }, .call, .call_always_tail, .call_never_tail, .call_never_inline => { - const inst_data = inst_datas[@intFromEnum(inst)].pl_op; - const callee = inst_data.operand; - const extra = a.air.extraData(Air.Call, inst_data.payload); - const args = @as([]const Air.Inst.Ref, @ptrCast(a.air.extra.items[extra.end..][0..extra.data.args_len])); + const call = a.air.unwrapCall(inst); + const args = call.args; if (args.len + 1 <= bpi - 1) { var buf = [1]Air.Inst.Ref{.none} ** (bpi - 1); - buf[0] = callee; + buf[0] = call.callee; @memcpy(buf[1..][0..args.len], args); return analyzeOperands(a, pass, data, inst, buf); } @@ -627,7 +626,7 @@ fn analyzeInst( i -= 1; try big.feed(args[i]); } - try big.feed(callee); + try big.feed(call.callee); return big.finish(); }, .select => { @@ -708,18 +707,15 @@ fn analyzeInst( .switch_dispatch => return analyzeInstSwitchDispatch(a, pass, data, inst), .assembly => { - const extra = a.air.extraData(Air.Asm, inst_datas[@intFromEnum(inst)].ty_pl.payload); - const outputs_len = extra.data.flags.outputs_len; - var extra_i: usize = extra.end; - const outputs = @as([]const Air.Inst.Ref, @ptrCast(a.air.extra.items[extra_i..][0..outputs_len])); - extra_i += outputs.len; - const inputs = @as([]const Air.Inst.Ref, @ptrCast(a.air.extra.items[extra_i..][0..extra.data.inputs_len])); - extra_i += inputs.len; + const unwrapped_asm = a.air.unwrapAsm(inst); + + const outputs = unwrapped_asm.outputs; + const inputs = unwrapped_asm.inputs; const num_operands = simple: { var buf = [1]Air.Inst.Ref{.none} ** (bpi - 1); var buf_index: usize = 0; - for (outputs) |output| { + for (unwrapped_asm.outputs) |output| { if (output != .none) { if (buf_index < buf.len) buf[buf_index] = output; buf_index += 1; @@ -748,15 +744,13 @@ fn analyzeInst( } return big.finish(); }, - - inline .block, .dbg_inline_block => |comptime_tag| { - const ty_pl = inst_datas[@intFromEnum(inst)].ty_pl; - const extra = a.air.extraData(switch (comptime_tag) { - .block => Air.Block, - .dbg_inline_block => Air.DbgInlineBlock, - else => unreachable, - }, ty_pl.payload); - return analyzeInstBlock(a, pass, data, inst, ty_pl.ty, @ptrCast(a.air.extra.items[extra.end..][0..extra.data.body_len])); + .dbg_inline_block => { + const block = a.air.unwrapDbgBlock(inst); + return analyzeInstBlock(a, pass, data, inst, block.ty, block.body); + }, + .block => { + const block = a.air.unwrapBlock(inst); + return analyzeInstBlock(a, pass, data, inst, block.ty, block.body); }, .loop => return analyzeInstLoop(a, pass, data, inst), @@ -778,8 +772,8 @@ fn analyzeInst( }, .legalize_compiler_rt_call => { - const extra = a.air.extraData(Air.Call, inst_datas[@intFromEnum(inst)].legalize_compiler_rt_call.payload); - const args: []const Air.Inst.Ref = @ptrCast(a.air.extra.items[extra.end..][0..extra.data.args_len]); + const rt_call = a.air.unwrapCompilerRtCall(inst); + const args = rt_call.args; if (args.len <= bpi - 1) { var buf: [bpi - 1]Air.Inst.Ref = @splat(.none); @memcpy(buf[0..args.len], args); @@ -972,7 +966,7 @@ fn analyzeInstBlock( comptime pass: LivenessPass, data: *LivenessPassData(pass), inst: Air.Inst.Index, - ty: Air.Inst.Ref, + ty: Type, body: []const Air.Inst.Index, ) !void { const gpa = a.gpa; @@ -1005,7 +999,7 @@ fn analyzeInstBlock( // If the block is noreturn, block deaths not only aren't useful, they're impossible to // find: there could be more stuff alive after the block than before it! - if (!a.intern_pool.isNoReturn(ty.toType().toIntern())) { + if (!a.intern_pool.isNoReturn(ty.toIntern())) { // The block kills the difference in the live sets const block_scope = data.block_scopes.get(inst).?; const num_deaths = data.live_set.count() - block_scope.live_set.count(); @@ -1139,9 +1133,8 @@ fn analyzeInstLoop( data: *LivenessPassData(pass), inst: Air.Inst.Index, ) !void { - const inst_datas = a.air.instructions.items(.data); - const extra = a.air.extraData(Air.Block, inst_datas[@intFromEnum(inst)].ty_pl.payload); - const body: []const Air.Inst.Index = @ptrCast(a.air.extra.items[extra.end..][0..extra.data.body_len]); + const block = a.air.unwrapBlock(inst); + const body = block.body; const gpa = a.gpa; try analyzeOperands(a, pass, data, inst, .{ .none, .none, .none }); @@ -1187,44 +1180,38 @@ fn analyzeInstCondBr( inst: Air.Inst.Index, comptime inst_type: enum { cond_br, @"try", try_ptr }, ) !void { - const inst_datas = a.air.instructions.items(.data); const gpa = a.gpa; - const extra = switch (inst_type) { - .cond_br => a.air.extraData(Air.CondBr, inst_datas[@intFromEnum(inst)].pl_op.payload), - .@"try" => a.air.extraData(Air.Try, inst_datas[@intFromEnum(inst)].pl_op.payload), - .try_ptr => a.air.extraData(Air.TryPtr, inst_datas[@intFromEnum(inst)].ty_pl.payload), + const unwrapped_cond = switch (inst_type) { + .cond_br => a.air.unwrapCondBr(inst), + .@"try" => a.air.unwrapTry(inst), + .try_ptr => a.air.unwrapTryPtr(inst), }; const condition = switch (inst_type) { - .cond_br, .@"try" => inst_datas[@intFromEnum(inst)].pl_op.operand, - .try_ptr => extra.data.ptr, + .cond_br => unwrapped_cond.condition, + .@"try" => unwrapped_cond.error_union, + .try_ptr => unwrapped_cond.error_union_ptr, }; - const then_body: []const Air.Inst.Index = switch (inst_type) { - .cond_br => @ptrCast(a.air.extra.items[extra.end..][0..extra.data.then_body_len]), - else => &.{}, // we won't use this + const then_body = switch (inst_type) { + .cond_br => unwrapped_cond.then_body, + // The "then body" is just the remainder of this block + else => &.{}, }; - const else_body: []const Air.Inst.Index = @ptrCast(switch (inst_type) { - .cond_br => a.air.extra.items[extra.end + then_body.len ..][0..extra.data.else_body_len], - .@"try", .try_ptr => a.air.extra.items[extra.end..][0..extra.data.body_len], - }); + const else_body = switch (inst_type) { + .cond_br, .@"try", .try_ptr => unwrapped_cond.else_body, + }; switch (pass) { .loop_analysis => { - switch (inst_type) { - .cond_br => try analyzeBody(a, pass, data, then_body), - .@"try", .try_ptr => {}, - } + try analyzeBody(a, pass, data, then_body); try analyzeBody(a, pass, data, else_body); }, .main_analysis => { - switch (inst_type) { - .cond_br => try analyzeBody(a, pass, data, then_body), - .@"try", .try_ptr => {}, // The "then body" is just the remainder of this block - } + try analyzeBody(a, pass, data, then_body); var then_live = data.live_set.move(); defer then_live.deinit(gpa); diff --git a/src/Air/Liveness/Verify.zig b/src/Air/Liveness/Verify.zig index cdb5786921bb9fe005daa93479db0f9f2ce2c152..fc83574d070c018fc54ed095d4c67f0bf7698fb2 100644 --- a/src/Air/Liveness/Verify.zig +++ b/src/Air/Liveness/Verify.zig @@ -345,37 +345,26 @@ fn verifyBody(self: *Verify, body: []const Air.Inst.Index) Error!void { try self.verifyInst(inst); }, .call, .call_always_tail, .call_never_tail, .call_never_inline => { - const pl_op = data[@intFromEnum(inst)].pl_op; - const extra = self.air.extraData(Air.Call, pl_op.payload); - const args = @as( - []const Air.Inst.Ref, - @ptrCast(self.air.extra.items[extra.end..][0..extra.data.args_len]), - ); + const call = self.air.unwrapCall(inst); + const args = call.args; var bt = self.liveness.iterateBigTomb(inst); - try self.verifyOperand(inst, pl_op.operand, bt.feed()); + try self.verifyOperand(inst, call.callee, bt.feed()); for (args) |arg| { try self.verifyOperand(inst, arg, bt.feed()); } try self.verifyInst(inst); }, .assembly => { - const ty_pl = data[@intFromEnum(inst)].ty_pl; - const extra = self.air.extraData(Air.Asm, ty_pl.payload); - const outputs_len = extra.data.flags.outputs_len; - var extra_i = extra.end; - const outputs: []const Air.Inst.Ref = @ptrCast(self.air.extra.items[extra_i..][0..outputs_len]); - extra_i += outputs.len; - const inputs: []const Air.Inst.Ref = @ptrCast(self.air.extra.items[extra_i..][0..extra.data.inputs_len]); - extra_i += inputs.len; + const unwrapped_asm = self.air.unwrapAsm(inst); var bt = self.liveness.iterateBigTomb(inst); - for (outputs) |output| { + for (unwrapped_asm.outputs) |output| { if (output != .none) { try self.verifyOperand(inst, output, bt.feed()); } } - for (inputs) |input| { + for (unwrapped_asm.inputs) |input| { try self.verifyOperand(inst, input, bt.feed()); } try self.verifyInst(inst); @@ -383,13 +372,12 @@ fn verifyBody(self: *Verify, body: []const Air.Inst.Index) Error!void { // control flow .@"try", .try_cold => { - const pl_op = data[@intFromEnum(inst)].pl_op; - const extra = self.air.extraData(Air.Try, pl_op.payload); - const try_body: []const Air.Inst.Index = @ptrCast(self.air.extra.items[extra.end..][0..extra.data.body_len]); + const unwrapped_try = self.air.unwrapTry(inst); + const try_body = unwrapped_try.else_body; const cond_br_liveness = self.liveness.getCondBr(inst); - try self.verifyOperand(inst, pl_op.operand, self.liveness.operandDies(inst, 0)); + try self.verifyOperand(inst, unwrapped_try.error_union, self.liveness.operandDies(inst, 0)); var live = try self.live.clone(self.gpa); defer live.deinit(self.gpa); @@ -405,13 +393,12 @@ fn verifyBody(self: *Verify, body: []const Air.Inst.Index) Error!void { try self.verifyInst(inst); }, .try_ptr, .try_ptr_cold => { - const ty_pl = data[@intFromEnum(inst)].ty_pl; - const extra = self.air.extraData(Air.TryPtr, ty_pl.payload); - const try_body: []const Air.Inst.Index = @ptrCast(self.air.extra.items[extra.end..][0..extra.data.body_len]); + const unwrapped_try = self.air.unwrapTryPtr(inst); + const try_body = unwrapped_try.else_body; const cond_br_liveness = self.liveness.getCondBr(inst); - try self.verifyOperand(inst, extra.data.ptr, self.liveness.operandDies(inst, 0)); + try self.verifyOperand(inst, unwrapped_try.error_union_ptr, self.liveness.operandDies(inst, 0)); var live = try self.live.clone(self.gpa); defer live.deinit(self.gpa); @@ -458,17 +445,11 @@ fn verifyBody(self: *Verify, body: []const Air.Inst.Index) Error!void { .block, .dbg_inline_block => |tag| { const ty_pl = data[@intFromEnum(inst)].ty_pl; const block_ty = ty_pl.ty.toType(); - const block_body: []const Air.Inst.Index = @ptrCast(switch (tag) { - inline .block, .dbg_inline_block => |comptime_tag| body: { - const extra = self.air.extraData(switch (comptime_tag) { - .block => Air.Block, - .dbg_inline_block => Air.DbgInlineBlock, - else => unreachable, - }, ty_pl.payload); - break :body self.air.extra.items[extra.end..][0..extra.data.body_len]; - }, + const block_body = switch (tag) { + .block => self.air.unwrapBlock(inst).body, + .dbg_inline_block => self.air.unwrapDbgBlock(inst).body, else => unreachable, - }); + }; const block_liveness = self.liveness.getBlock(inst); var orig_live = try self.live.clone(self.gpa); @@ -501,9 +482,7 @@ fn verifyBody(self: *Verify, body: []const Air.Inst.Index) Error!void { try self.verifyInstOperands(inst, .{ .none, .none, .none }); }, .loop => { - const ty_pl = data[@intFromEnum(inst)].ty_pl; - const extra = self.air.extraData(Air.Block, ty_pl.payload); - const loop_body: []const Air.Inst.Index = @ptrCast(self.air.extra.items[extra.end..][0..extra.data.body_len]); + const block = self.air.unwrapBlock(inst); // The same stuff should be alive after the loop as before it. const gop = try self.loops.getOrPut(self.gpa, inst); @@ -514,18 +493,17 @@ fn verifyBody(self: *Verify, body: []const Air.Inst.Index) Error!void { } gop.value_ptr.* = try self.live.clone(self.gpa); - try self.verifyBody(loop_body); + try self.verifyBody(block.body); try self.verifyInstOperands(inst, .{ .none, .none, .none }); }, .cond_br => { - const pl_op = data[@intFromEnum(inst)].pl_op; - const extra = self.air.extraData(Air.CondBr, pl_op.payload); - const then_body: []const Air.Inst.Index = @ptrCast(self.air.extra.items[extra.end..][0..extra.data.then_body_len]); - const else_body: []const Air.Inst.Index = @ptrCast(self.air.extra.items[extra.end + then_body.len ..][0..extra.data.else_body_len]); + const cond_br = self.air.unwrapCondBr(inst); + const then_body = cond_br.then_body; + const else_body = cond_br.else_body; const cond_br_liveness = self.liveness.getCondBr(inst); - try self.verifyOperand(inst, pl_op.operand, self.liveness.operandDies(inst, 0)); + try self.verifyOperand(inst, cond_br.condition, self.liveness.operandDies(inst, 0)); var live = try self.live.clone(self.gpa); defer live.deinit(self.gpa); @@ -589,8 +567,8 @@ fn verifyBody(self: *Verify, body: []const Air.Inst.Index) Error!void { try self.verifyInstOperands(inst, .{ pl_op.operand, bin.lhs, bin.rhs }); }, .legalize_compiler_rt_call => { - const extra = self.air.extraData(Air.Call, data[@intFromEnum(inst)].legalize_compiler_rt_call.payload); - const args: []const Air.Inst.Ref = @ptrCast(self.air.extra.items[extra.end..][0..extra.data.args_len]); + const rt_call = self.air.unwrapCompilerRtCall(inst); + const args = rt_call.args; var bt = self.liveness.iterateBigTomb(inst); for (args) |arg| { try self.verifyOperand(inst, arg, bt.feed()); diff --git a/src/Air/print.zig b/src/Air/print.zig index 98b0a0b242ab7b7c41976c35f76756a5efbd7dec..0c126fab22d364b97c49ed6d896801fe4c66451e 100644 --- a/src/Air/print.zig +++ b/src/Air/print.zig @@ -395,25 +395,17 @@ const Writer = struct { fn writeBlock(w: *Writer, s: *std.Io.Writer, tag: Air.Inst.Tag, inst: Air.Inst.Index) Error!void { const ty_pl = w.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl; try w.writeType(s, ty_pl.ty.toType()); - const body: []const Air.Inst.Index = @ptrCast(switch (tag) { - inline .block, .dbg_inline_block => |comptime_tag| body: { - const extra = w.air.extraData(switch (comptime_tag) { - .block => Air.Block, - .dbg_inline_block => Air.DbgInlineBlock, - else => unreachable, - }, ty_pl.payload); - switch (comptime_tag) { - .block => {}, - .dbg_inline_block => { - try s.writeAll(", "); - try w.writeInstRef(s, Air.internedToRef(extra.data.func), false); - }, - else => unreachable, - } - break :body w.air.extra.items[extra.end..][0..extra.data.body_len]; + + const body = switch (tag) { + .block => w.air.unwrapBlock(inst).body, + .dbg_inline_block => body: { + const dbg_block = w.air.unwrapDbgBlock(inst); + try s.writeAll(", "); + try w.writeInstRef(s, Air.internedToRef(dbg_block.func), false); + break :body dbg_block.body; }, else => unreachable, - }); + }; if (w.skip_body) return s.writeAll(", ..."); const liveness_block: Air.Liveness.BlockSlices = if (w.liveness) |liveness| liveness.getBlock(inst) @@ -434,16 +426,14 @@ const Writer = struct { } fn writeLoop(w: *Writer, s: *std.Io.Writer, inst: Air.Inst.Index) Error!void { - const ty_pl = w.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl; - const extra = w.air.extraData(Air.Block, ty_pl.payload); - const body: []const Air.Inst.Index = @ptrCast(w.air.extra.items[extra.end..][0..extra.data.body_len]); + const block = w.air.unwrapBlock(inst); - try w.writeType(s, ty_pl.ty.toType()); + try w.writeType(s, block.ty); if (w.skip_body) return s.writeAll(", ..."); try s.writeAll(", {\n"); const old_indent = w.indent; w.indent += 2; - try w.writeBody(s, body); + try w.writeBody(s, block.body); w.indent = old_indent; try s.splatByteAll(' ', w.indent); try s.writeAll("}"); @@ -532,11 +522,10 @@ const Writer = struct { } fn writeLegalizeCompilerRtCall(w: *Writer, s: *std.Io.Writer, inst: Air.Inst.Index) Error!void { - const inst_data = w.air.instructions.items(.data)[@intFromEnum(inst)].legalize_compiler_rt_call; - const extra = w.air.extraData(Air.Call, inst_data.payload); - const args: []const Air.Inst.Ref = @ptrCast(w.air.extra.items[extra.end..][0..extra.data.args_len]); + const rt_call = w.air.unwrapCompilerRtCall(inst); + const args = rt_call.args; - try s.print("{t}, [", .{inst_data.func}); + try s.print("{t}, [", .{rt_call.func}); for (args, 0..) |arg, i| { if (i != 0) try s.writeAll(", "); try w.writeOperand(s, inst, i, arg); @@ -666,11 +655,8 @@ const Writer = struct { } fn writeAssembly(w: *Writer, s: *std.Io.Writer, inst: Air.Inst.Index) Error!void { - const ty_pl = w.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl; - const extra = w.air.extraData(Air.Asm, ty_pl.payload); - const is_volatile = extra.data.flags.is_volatile; - const outputs_len = extra.data.flags.outputs_len; - var extra_i: usize = extra.end; + const unwrapped_asm = w.air.unwrapAsm(inst); + const is_volatile = unwrapped_asm.is_volatile; var op_index: usize = 0; const ret_ty = w.typeOfIndex(inst); @@ -680,49 +666,33 @@ const Writer = struct { try s.writeAll(", volatile"); } - const outputs = @as([]const Air.Inst.Ref, @ptrCast(w.air.extra.items[extra_i..][0..outputs_len])); - extra_i += outputs.len; - const inputs = @as([]const Air.Inst.Ref, @ptrCast(w.air.extra.items[extra_i..][0..extra.data.inputs_len])); - extra_i += inputs.len; - - for (outputs) |output| { - const extra_bytes = std.mem.sliceAsBytes(w.air.extra.items[extra_i..]); - const constraint = std.mem.sliceTo(extra_bytes, 0); - const name = std.mem.sliceTo(extra_bytes[constraint.len + 1 ..], 0); - - // This equation accounts for the fact that even if we have exactly 4 bytes - // for the strings and their null terminators, we still use the next u32 - // for the null terminator. - extra_i += (constraint.len + name.len + (2 + 3)) / 4; - - if (output == .none) { + var it = unwrapped_asm.iterateOutputs(); + while (it.next()) |out| { + const name = out.name; + const constraint = out.constraint; + if (out.operand == .none) { try s.print(", [{s}] -> {s}", .{ name, constraint }); } else { try s.print(", [{s}] out {s} = (", .{ name, constraint }); - try w.writeOperand(s, inst, op_index, output); + try w.writeOperand(s, inst, op_index, out.operand); op_index += 1; try s.writeByte(')'); } } - for (inputs) |input| { - const extra_bytes = std.mem.sliceAsBytes(w.air.extra.items[extra_i..]); - const constraint = std.mem.sliceTo(extra_bytes, 0); - const name = std.mem.sliceTo(extra_bytes[constraint.len + 1 ..], 0); - // This equation accounts for the fact that even if we have exactly 4 bytes - // for the strings and their null terminators, we still use the next u32 - // for the null terminator. - extra_i += (constraint.len + name.len + 1) / 4 + 1; - + it = unwrapped_asm.iterateInputs(); + while (it.next()) |in| { + const name = in.name; + const constraint = in.constraint; try s.print(", [{s}] in {s} = (", .{ name, constraint }); - try w.writeOperand(s, inst, op_index, input); + try w.writeOperand(s, inst, op_index, in.operand); op_index += 1; try s.writeByte(')'); } const zcu = w.pt.zcu; const ip = &zcu.intern_pool; - const aggregate = ip.indexToKey(extra.data.clobbers).aggregate; + const aggregate = ip.indexToKey(unwrapped_asm.clobbers).aggregate; const struct_type: Type = .fromInterned(aggregate.ty); switch (aggregate.storage) { .elems => |elems| for (elems, 0..) |elem, i| { @@ -750,7 +720,7 @@ const Writer = struct { try s.print(", {x}", .{bytes}); }, } - const asm_source = std.mem.sliceAsBytes(w.air.extra.items[extra_i..])[0..extra.data.source_len]; + const asm_source = unwrapped_asm.source; try s.print(", \"{f}\"", .{std.zig.fmtString(asm_source)}); } @@ -767,10 +737,9 @@ const Writer = struct { } fn writeCall(w: *Writer, s: *std.Io.Writer, inst: Air.Inst.Index) Error!void { - const pl_op = w.air.instructions.items(.data)[@intFromEnum(inst)].pl_op; - const extra = w.air.extraData(Air.Call, pl_op.payload); - const args = @as([]const Air.Inst.Ref, @ptrCast(w.air.extra.items[extra.end..][0..extra.data.args_len])); - try w.writeOperand(s, inst, 0, pl_op.operand); + const call = w.air.unwrapCall(inst); + const args = call.args; + try w.writeOperand(s, inst, 0, call.callee); try s.writeAll(", ["); for (args, 0..) |arg, i| { if (i != 0) try s.writeAll(", "); @@ -792,15 +761,14 @@ const Writer = struct { } fn writeTry(w: *Writer, s: *std.Io.Writer, inst: Air.Inst.Index) Error!void { - const pl_op = w.air.instructions.items(.data)[@intFromEnum(inst)].pl_op; - const extra = w.air.extraData(Air.Try, pl_op.payload); - const body: []const Air.Inst.Index = @ptrCast(w.air.extra.items[extra.end..][0..extra.data.body_len]); + const unwrapped_try = w.air.unwrapTry(inst); + const body = unwrapped_try.else_body; const liveness_condbr: Air.Liveness.CondBrSlices = if (w.liveness) |liveness| liveness.getCondBr(inst) else .{ .then_deaths = &.{}, .else_deaths = &.{} }; - try w.writeOperand(s, inst, 0, pl_op.operand); + try w.writeOperand(s, inst, 0, unwrapped_try.error_union); if (w.skip_body) return s.writeAll(", ..."); try s.writeAll(", {\n"); const old_indent = w.indent; @@ -826,18 +794,17 @@ const Writer = struct { } fn writeTryPtr(w: *Writer, s: *std.Io.Writer, inst: Air.Inst.Index) Error!void { - const ty_pl = w.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl; - const extra = w.air.extraData(Air.TryPtr, ty_pl.payload); - const body: []const Air.Inst.Index = @ptrCast(w.air.extra.items[extra.end..][0..extra.data.body_len]); + const unwrapped_try = w.air.unwrapTryPtr(inst); + const body = unwrapped_try.else_body; const liveness_condbr: Air.Liveness.CondBrSlices = if (w.liveness) |liveness| liveness.getCondBr(inst) else .{ .then_deaths = &.{}, .else_deaths = &.{} }; - try w.writeOperand(s, inst, 0, extra.data.ptr); + try w.writeOperand(s, inst, 0, unwrapped_try.error_union_ptr); try s.writeAll(", "); - try w.writeType(s, ty_pl.ty.toType()); + try w.writeType(s, unwrapped_try.error_union_payload_ptr_ty.toType()); if (w.skip_body) return s.writeAll(", ..."); try s.writeAll(", {\n"); const old_indent = w.indent; @@ -863,23 +830,22 @@ const Writer = struct { } fn writeCondBr(w: *Writer, s: *std.Io.Writer, inst: Air.Inst.Index) Error!void { - const pl_op = w.air.instructions.items(.data)[@intFromEnum(inst)].pl_op; - const extra = w.air.extraData(Air.CondBr, pl_op.payload); - const then_body: []const Air.Inst.Index = @ptrCast(w.air.extra.items[extra.end..][0..extra.data.then_body_len]); - const else_body: []const Air.Inst.Index = @ptrCast(w.air.extra.items[extra.end + then_body.len ..][0..extra.data.else_body_len]); + const cond_br = w.air.unwrapCondBr(inst); + const then_body = cond_br.then_body; + const else_body = cond_br.else_body; const liveness_condbr: Air.Liveness.CondBrSlices = if (w.liveness) |liveness| liveness.getCondBr(inst) else .{ .then_deaths = &.{}, .else_deaths = &.{} }; - try w.writeOperand(s, inst, 0, pl_op.operand); + try w.writeOperand(s, inst, 0, cond_br.condition); if (w.skip_body) return s.writeAll(", ..."); try s.writeAll(","); - if (extra.data.branch_hints.true != .none) { - try s.print(" {s}", .{@tagName(extra.data.branch_hints.true)}); + if (cond_br.branch_hints.true != .none) { + try s.print(" {s}", .{@tagName(cond_br.branch_hints.true)}); } - if (extra.data.branch_hints.then_cov != .none) { - try s.print(" {s}", .{@tagName(extra.data.branch_hints.then_cov)}); + if (cond_br.branch_hints.then_cov != .none) { + try s.print(" {s}", .{@tagName(cond_br.branch_hints.then_cov)}); } try s.writeAll(" {\n"); const old_indent = w.indent; @@ -897,11 +863,11 @@ const Writer = struct { try w.writeBody(s, then_body); try s.splatByteAll(' ', old_indent); try s.writeAll("},"); - if (extra.data.branch_hints.false != .none) { - try s.print(" {s}", .{@tagName(extra.data.branch_hints.false)}); + if (cond_br.branch_hints.false != .none) { + try s.print(" {s}", .{@tagName(cond_br.branch_hints.false)}); } - if (extra.data.branch_hints.else_cov != .none) { - try s.print(" {s}", .{@tagName(extra.data.branch_hints.else_cov)}); + if (cond_br.branch_hints.else_cov != .none) { + try s.print(" {s}", .{@tagName(cond_br.branch_hints.else_cov)}); } try s.writeAll(" {\n"); diff --git a/src/Air/types_resolved.zig b/src/Air/types_resolved.zig index 752b4eccc325fd4123d088da8a3e3ec7cf24be6e..216f690414abdf2daea6993536b303812a05866e 100644 --- a/src/Air/types_resolved.zig +++ b/src/Air/types_resolved.zig @@ -170,21 +170,21 @@ fn checkBody(air: Air, body: []const Air.Inst.Index, zcu: *Zcu) bool { .block, .loop, => { - const extra = air.extraData(Air.Block, data.ty_pl.payload); - if (!checkType(data.ty_pl.ty.toType(), zcu)) return false; + const block = air.unwrapBlock(inst); + if (!checkType(block.ty, zcu)) return false; if (!checkBody( air, - @ptrCast(air.extra.items[extra.end..][0..extra.data.body_len]), + block.body, zcu, )) return false; }, .dbg_inline_block => { - const extra = air.extraData(Air.DbgInlineBlock, data.ty_pl.payload); - if (!checkType(data.ty_pl.ty.toType(), zcu)) return false; + const block = air.unwrapDbgBlock(inst); + if (!checkType(block.ty, zcu)) return false; if (!checkBody( air, - @ptrCast(air.extra.items[extra.end..][0..extra.data.body_len]), + block.body, zcu, )) return false; }, @@ -342,9 +342,9 @@ fn checkBody(air: Air, body: []const Air.Inst.Index, zcu: *Zcu) bool { .call_never_tail, .call_never_inline, => { - const extra = air.extraData(Air.Call, data.pl_op.payload); - const args: []const Air.Inst.Ref = @ptrCast(air.extra.items[extra.end..][0..extra.data.args_len]); - if (!checkRef(data.pl_op.operand, zcu)) return false; + const call = air.unwrapCall(inst); + const args = call.args; + if (!checkRef(call.callee, zcu)) return false; for (args) |arg| if (!checkRef(arg, zcu)) return false; }, @@ -356,37 +356,37 @@ fn checkBody(air: Air, body: []const Air.Inst.Index, zcu: *Zcu) bool { }, .@"try", .try_cold => { - const extra = air.extraData(Air.Try, data.pl_op.payload); - if (!checkRef(data.pl_op.operand, zcu)) return false; + const unwrapped_try = air.unwrapTry(inst); + if (!checkRef(unwrapped_try.error_union, zcu)) return false; if (!checkBody( air, - @ptrCast(air.extra.items[extra.end..][0..extra.data.body_len]), + unwrapped_try.else_body, zcu, )) return false; }, .try_ptr, .try_ptr_cold => { - const extra = air.extraData(Air.TryPtr, data.ty_pl.payload); - if (!checkType(data.ty_pl.ty.toType(), zcu)) return false; - if (!checkRef(extra.data.ptr, zcu)) return false; + const unwrapped_try = air.unwrapTryPtr(inst); + if (!checkType(unwrapped_try.error_union_payload_ptr_ty.toType(), zcu)) return false; + if (!checkRef(unwrapped_try.error_union_ptr, zcu)) return false; if (!checkBody( air, - @ptrCast(air.extra.items[extra.end..][0..extra.data.body_len]), + unwrapped_try.else_body, zcu, )) return false; }, .cond_br => { - const extra = air.extraData(Air.CondBr, data.pl_op.payload); - if (!checkRef(data.pl_op.operand, zcu)) return false; + const cond_br = air.unwrapCondBr(inst); + if (!checkRef(cond_br.condition, zcu)) return false; if (!checkBody( air, - @ptrCast(air.extra.items[extra.end..][0..extra.data.then_body_len]), + cond_br.then_body, zcu, )) return false; if (!checkBody( air, - @ptrCast(air.extra.items[extra.end + extra.data.then_body_len ..][0..extra.data.else_body_len]), + cond_br.else_body, zcu, )) return false; }, @@ -407,20 +407,20 @@ fn checkBody(air: Air, body: []const Air.Inst.Index, zcu: *Zcu) bool { }, .assembly => { - const extra = air.extraData(Air.Asm, data.ty_pl.payload); + const unwrapped_asm = air.unwrapAsm(inst); if (!checkType(data.ty_pl.ty.toType(), zcu)) return false; // Luckily, we only care about the inputs and outputs, so we don't have to do // the whole null-terminated string dance. - const outputs_len = extra.data.flags.outputs_len; - const outputs: []const Air.Inst.Ref = @ptrCast(air.extra.items[extra.end..][0..outputs_len]); - const inputs: []const Air.Inst.Ref = @ptrCast(air.extra.items[extra.end + outputs_len ..][0..extra.data.inputs_len]); + const outputs = unwrapped_asm.outputs; + const inputs = unwrapped_asm.inputs; + for (outputs) |output| if (output != .none and !checkRef(output, zcu)) return false; for (inputs) |input| if (input != .none and !checkRef(input, zcu)) return false; }, .legalize_compiler_rt_call => { - const extra = air.extraData(Air.Call, data.legalize_compiler_rt_call.payload); - const args: []const Air.Inst.Ref = @ptrCast(air.extra.items[extra.end..][0..extra.data.args_len]); + const rt_call = air.unwrapCompilerRtCall(inst); + const args = rt_call.args; for (args) |arg| if (!checkRef(arg, zcu)) return false; }, diff --git a/src/Sema.zig b/src/Sema.zig index fc71a58f776518edf6d0dd745469bb1c1f77e1ef..87dd6a83bbc9fed4e1533a20499793f10024cdcf 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -16226,6 +16226,12 @@ fn zirAsm( }); sema.appendRefsAssumeCapacity(out_args); sema.appendRefsAssumeCapacity(args); + { + const buffer = mem.sliceAsBytes(sema.air_extra.unusedCapacitySlice()); + @memcpy(buffer[0..asm_source.len], asm_source); + buffer[asm_source.len] = 0; + sema.air_extra.items.len += asm_source.len / 4 + 1; + } for (outputs) |o| { const buffer = mem.sliceAsBytes(sema.air_extra.unusedCapacitySlice()); @memcpy(buffer[0..o.c.len], o.c); @@ -16242,12 +16248,6 @@ fn zirAsm( buffer[input.c.len + 1 + input.n.len] = 0; sema.air_extra.items.len += (input.c.len + input.n.len + (2 + 3)) / 4; } - { - const buffer = mem.sliceAsBytes(sema.air_extra.unusedCapacitySlice()); - @memcpy(buffer[0..asm_source.len], asm_source); - buffer[asm_source.len] = 0; - sema.air_extra.items.len += asm_source.len / 4 + 1; - } return asm_air; } diff --git a/src/codegen/aarch64/Select.zig b/src/codegen/aarch64/Select.zig index 49de055b47b6b55a3119ce52065888af61d8b0f8..55f0d7fcc0e37b35198f01c2ff88c96cfd72aca4 100644 --- a/src/codegen/aarch64/Select.zig +++ b/src/codegen/aarch64/Select.zig @@ -274,10 +274,9 @@ pub fn analyze(isel: *Select, air_body: []const Air.Inst.Index) !void { }, .assembly => { const ty_pl = air_data[@intFromEnum(air_inst_index)].ty_pl; - const extra = isel.air.extraData(Air.Asm, ty_pl.payload); - const operands: []const Air.Inst.Ref = @ptrCast(isel.air.extra.items[extra.end..][0 .. extra.data.flags.outputs_len + extra.data.inputs_len]); + const unwrapped_asm = isel.air.unwrapAsm(air_inst_index); - for (operands) |operand| if (operand != .none) try isel.analyzeUse(operand); + for (unwrapped_asm.outputs) |operand| if (operand != .none) try isel.analyzeUse(operand); if (ty_pl.ty != .void_type) try isel.def_order.putNoClobber(gpa, air_inst_index, {}); air_body_index += 1; @@ -355,23 +354,23 @@ pub fn analyze(isel: *Select, air_body: []const Air.Inst.Index) !void { continue :air_tag air_tags[@intFromEnum(air_inst_index)]; }, inline .block, .dbg_inline_block => |air_tag| { - const ty_pl = air_data[@intFromEnum(air_inst_index)].ty_pl; - const extra = isel.air.extraData(switch (air_tag) { + const air_body_block = switch (air_tag) { else => comptime unreachable, - .block => Air.Block, - .dbg_inline_block => Air.DbgInlineBlock, - }, ty_pl.payload); - const result_ty = ty_pl.ty.toInterned().?; + .block => isel.air.unwrapBlock(air_inst_index), + .dbg_inline_block => isel.air.unwrapDbgBlock(air_inst_index), + }; + + const result_ty = air_body_block.ty.toIntern(); if (result_ty == .noreturn_type) { - try isel.analyze(@ptrCast(isel.air.extra.items[extra.end..][0..extra.data.body_len])); + try isel.analyze(air_body_block.body); air_body_index += 1; break :air_tag; } assert(!(try isel.blocks.getOrPut(gpa, air_inst_index)).found_existing); - try isel.analyze(@ptrCast(isel.air.extra.items[extra.end..][0..extra.data.body_len])); + try isel.analyze(air_body_block.body); const block_entry = isel.blocks.pop().?; assert(block_entry.key == air_inst_index); @@ -382,8 +381,7 @@ pub fn analyze(isel: *Select, air_body: []const Air.Inst.Index) !void { continue :air_tag air_tags[@intFromEnum(air_inst_index)]; }, .loop => { - const ty_pl = air_data[@intFromEnum(air_inst_index)].ty_pl; - const extra = isel.air.extraData(Air.Block, ty_pl.payload); + const air_body_block = isel.air.unwrapBlock(air_inst_index); const initial_dom_start = isel.dom_start; const initial_dom_len = isel.dom_len; @@ -399,7 +397,7 @@ pub fn analyze(isel: *Select, air_body: []const Air.Inst.Index) !void { .repeat_list = undefined, }); try isel.dom.appendNTimes(gpa, 0, std.math.divCeil(usize, isel.dom_len, @bitSizeOf(DomInt)) catch unreachable); - try isel.analyze(@ptrCast(isel.air.extra.items[extra.end..][0..extra.data.body_len])); + try isel.analyze(air_body_block.body); for ( isel.dom.items[initial_dom_start..].ptr, isel.dom.items[isel.dom_start..][0 .. std.math.divCeil(usize, initial_dom_len, @bitSizeOf(DomInt)) catch unreachable], @@ -429,18 +427,17 @@ pub fn analyze(isel: *Select, air_body: []const Air.Inst.Index) !void { .call_never_tail, .call_never_inline, => { - const pl_op = air_data[@intFromEnum(air_inst_index)].pl_op; - const extra = isel.air.extraData(Air.Call, pl_op.payload); - const args: []const Air.Inst.Ref = @ptrCast(isel.air.extra.items[extra.end..][0..extra.data.args_len]); + const air_call = isel.air.unwrapCall(air_inst_index); + const args = air_call.args; isel.saved_registers.insert(.lr); - const callee_ty = isel.air.typeOf(pl_op.operand, ip); + const callee_ty = isel.air.typeOf(air_call.callee, ip); const func_info = switch (ip.indexToKey(callee_ty.toIntern())) { else => unreachable, .func_type => |func_type| func_type, .ptr_type => |ptr_type| ip.indexToKey(ptr_type.child).func_type, }; - try isel.analyzeUse(pl_op.operand); + try isel.analyzeUse(air_call.callee); var param_it: CallAbiIterator = .init; for (args, 0..) |arg, arg_index| { const restore_values_len = isel.values.items.len; @@ -549,13 +546,12 @@ pub fn analyze(isel: *Select, air_body: []const Air.Inst.Index) !void { continue :air_tag air_tags[@intFromEnum(air_inst_index)]; }, .cond_br => { - const pl_op = air_data[@intFromEnum(air_inst_index)].pl_op; - const extra = isel.air.extraData(Air.CondBr, pl_op.payload); + const cond_br = isel.air.unwrapCondBr(air_inst_index); - try isel.analyzeUse(pl_op.operand); + try isel.analyzeUse(cond_br.condition); - try isel.analyze(@ptrCast(isel.air.extra.items[extra.end..][0..extra.data.then_body_len])); - try isel.analyze(@ptrCast(isel.air.extra.items[extra.end + extra.data.then_body_len ..][0..extra.data.else_body_len])); + try isel.analyze(cond_br.then_body); + try isel.analyze(cond_br.else_body); air_body_index += 1; }, @@ -610,11 +606,10 @@ pub fn analyze(isel: *Select, air_body: []const Air.Inst.Index) !void { air_body_index += 1; }, .@"try", .try_cold => { - const pl_op = air_data[@intFromEnum(air_inst_index)].pl_op; - const extra = isel.air.extraData(Air.Try, pl_op.payload); + const unwrapped_try = isel.air.unwrapTry(air_inst_index); - try isel.analyzeUse(pl_op.operand); - try isel.analyze(@ptrCast(isel.air.extra.items[extra.end..][0..extra.data.body_len])); + try isel.analyzeUse(unwrapped_try.error_union); + try isel.analyze(unwrapped_try.else_body); try isel.def_order.putNoClobber(gpa, air_inst_index, {}); air_body_index += 1; @@ -622,11 +617,10 @@ pub fn analyze(isel: *Select, air_body: []const Air.Inst.Index) !void { continue :air_tag air_tags[@intFromEnum(air_inst_index)]; }, .try_ptr, .try_ptr_cold => { - const ty_pl = air_data[@intFromEnum(air_inst_index)].ty_pl; - const extra = isel.air.extraData(Air.TryPtr, ty_pl.payload); + const unwrapped_try = isel.air.unwrapTryPtr(air_inst_index); - try isel.analyzeUse(extra.data.ptr); - try isel.analyze(@ptrCast(isel.air.extra.items[extra.end..][0..extra.data.body_len])); + try isel.analyzeUse(unwrapped_try.error_union_ptr); + try isel.analyze(unwrapped_try.else_body); try isel.def_order.putNoClobber(gpa, air_inst_index, {}); air_body_index += 1; @@ -2698,12 +2692,8 @@ pub fn body(isel: *Select, air_body: []const Air.Inst.Index) error{ OutOfMemory, .inferred_alloc, .inferred_alloc_comptime => unreachable, .assembly => { const ty_pl = air.data(air.inst_index).ty_pl; - const extra = isel.air.extraData(Air.Asm, ty_pl.payload); - var extra_index = extra.end; - const outputs: []const Air.Inst.Ref = @ptrCast(isel.air.extra.items[extra_index..][0..extra.data.flags.outputs_len]); - extra_index += outputs.len; - const inputs: []const Air.Inst.Ref = @ptrCast(isel.air.extra.items[extra_index..][0..extra.data.inputs_len]); - extra_index += inputs.len; + const unwrapped_asm = isel.air.unwrapAsm(air.inst_index); + const inputs = unwrapped_asm.inputs; var as: codegen.aarch64.Assemble = .{ .source = undefined, @@ -2711,15 +2701,12 @@ pub fn body(isel: *Select, air_body: []const Air.Inst.Index) error{ OutOfMemory, }; defer as.operands.deinit(gpa); - for (outputs) |output| { - const extra_bytes = std.mem.sliceAsBytes(isel.air.extra.items[extra_index..]); - const constraint = std.mem.sliceTo(std.mem.sliceAsBytes(isel.air.extra.items[extra_index..]), 0); - const name = std.mem.sliceTo(extra_bytes[constraint.len + 1 ..], 0); - // This equation accounts for the fact that even if we have exactly 4 bytes - // for the string, we still use the next u32 for the null terminator. - extra_index += (constraint.len + name.len + (2 + 3)) / 4; + var it = unwrapped_asm.iterateOutputs(); + while (it.next()) |output| { + const constraint = output.constraint; + const name = output.name; - switch (output) { + switch (output.operand) { else => return isel.fail("invalid constraint: '{s}'", .{constraint}), .none => if (std.mem.startsWith(u8, constraint, "={") and std.mem.endsWith(u8, constraint, "}")) { const output_reg = Register.parse(constraint["={".len .. constraint.len - "}".len]) orelse @@ -2760,54 +2747,51 @@ pub fn body(isel: *Select, air_body: []const Air.Inst.Index) error{ OutOfMemory, } const input_mats = try gpa.alloc(Value.Materialize, inputs.len); + var index: u32 = 0; defer gpa.free(input_mats); - const inputs_extra_index = extra_index; - for (inputs, input_mats) |input, *input_mat| { - const extra_bytes = std.mem.sliceAsBytes(isel.air.extra.items[extra_index..]); - const constraint = std.mem.sliceTo(extra_bytes, 0); - const name = std.mem.sliceTo(extra_bytes[constraint.len + 1 ..], 0); - // This equation accounts for the fact that even if we have exactly 4 bytes - // for the string, we still use the next u32 for the null terminator. - extra_index += (constraint.len + name.len + (2 + 3)) / 4; + it = unwrapped_asm.iterateInputs(); + while (it.next()) |input| : (index += 1) { + const constraint = input.constraint; + const name = input.name; if (std.mem.startsWith(u8, constraint, "{") and std.mem.endsWith(u8, constraint, "}")) { const input_reg = Register.parse(constraint["{".len .. constraint.len - "}".len]) orelse return isel.fail("invalid constraint: '{s}'", .{constraint}); - input_mat.* = .{ .vi = try isel.use(input), .ra = input_reg.alias }; + input_mats[index] = .{ .vi = try isel.use(input.operand), .ra = input_reg.alias }; if (!std.mem.eql(u8, name, "_")) { const operand_gop = try as.operands.getOrPut(gpa, name); if (operand_gop.found_existing) return isel.fail("duplicate input name: '{s}'", .{name}); - const input_ty = isel.air.typeOf(input, ip); + const input_ty = isel.air.typeOf(input.operand, ip); operand_gop.value_ptr.* = .{ .register = switch (input_ty.abiSize(zcu)) { 0 => unreachable, 1...4 => input_reg.alias.w(), 5...8 => input_reg.alias.x(), else => return isel.fail("too big input type: '{f}'", .{ - isel.fmtType(isel.air.typeOf(input, ip)), + isel.fmtType(isel.air.typeOf(input.operand, ip)), }), } }; } } else if (std.mem.eql(u8, constraint, "r")) { - const input_vi = try isel.use(input); - input_mat.* = try input_vi.matReg(isel); + const input_vi = try isel.use(input.operand); + input_mats[index] = try input_vi.matReg(isel); if (!std.mem.eql(u8, name, "_")) { const operand_gop = try as.operands.getOrPut(gpa, name); if (operand_gop.found_existing) return isel.fail("duplicate input name: '{s}'", .{name}); operand_gop.value_ptr.* = .{ .register = switch (input_vi.size(isel)) { 0 => unreachable, - 1...4 => input_mat.ra.w(), - 5...8 => input_mat.ra.x(), + 1...4 => input_mats[index].ra.w(), + 5...8 => input_mats[index].ra.x(), else => return isel.fail("too big input type: '{f}'", .{ - isel.fmtType(isel.air.typeOf(input, ip)), + isel.fmtType(isel.air.typeOf(input.operand, ip)), }), } }; } } else if (std.mem.eql(u8, name, "_")) { - input_mat.vi = try isel.use(input); + input_mats[index].vi = try isel.use(input.operand); } else return isel.fail("invalid constraint: '{s}'", .{constraint}); } - const clobbers = ip.indexToKey(extra.data.clobbers).aggregate; + const clobbers = ip.indexToKey(unwrapped_asm.clobbers).aggregate; const clobbers_ty: ZigType = .fromInterned(clobbers.ty); for (0..clobbers_ty.structFieldCount(zcu)) |field_index| { switch (switch (clobbers.storage) { @@ -2858,7 +2842,7 @@ pub fn body(isel: *Select, air_body: []const Air.Inst.Index) error{ OutOfMemory, } } - as.source = std.mem.sliceAsBytes(isel.air.extra.items[extra_index..])[0..extra.data.source_len :0]; + as.source = unwrapped_asm.source; const asm_start = isel.instructions.items.len; while (as.nextInstruction() catch |err| switch (err) { error.InvalidSyntax => { @@ -2872,21 +2856,18 @@ pub fn body(isel: *Select, air_body: []const Air.Inst.Index) error{ OutOfMemory, }) |instruction| try isel.emit(instruction); std.mem.reverse(codegen.aarch64.encoding.Instruction, isel.instructions.items[asm_start..]); - extra_index = inputs_extra_index; - for (input_mats) |input_mat| { - const extra_bytes = std.mem.sliceAsBytes(isel.air.extra.items[extra_index..]); - const constraint = std.mem.sliceTo(extra_bytes, 0); - const name = std.mem.sliceTo(extra_bytes[constraint.len + 1 ..], 0); - // This equation accounts for the fact that even if we have exactly 4 bytes - // for the string, we still use the next u32 for the null terminator. - extra_index += (constraint.len + name.len + (2 + 3)) / 4; + it = unwrapped_asm.iterateInputs(); + index = 0; + while (it.next()) |input| : (index += 1) { + const constraint = input.constraint; + const name = input.name; if (std.mem.startsWith(u8, constraint, "{") and std.mem.endsWith(u8, constraint, "}")) { - try input_mat.vi.liveOut(isel, input_mat.ra); + try input_mats[index].vi.liveOut(isel, input_mats[index].ra); } else if (std.mem.eql(u8, constraint, "r")) { - try input_mat.finish(isel); + try input_mats[index].finish(isel); } else if (std.mem.eql(u8, name, "_")) { - try input_mat.vi.mat(isel); + try input_mats[index].vi.mat(isel); } else unreachable; } @@ -3515,16 +3496,16 @@ pub fn body(isel: *Select, air_body: []const Air.Inst.Index) error{ OutOfMemory, if (air.next()) |next_air_tag| continue :air_tag next_air_tag; }, .block => { - const ty_pl = air.data(air.inst_index).ty_pl; - const extra = isel.air.extraData(Air.Block, ty_pl.payload); - try isel.block(air.inst_index, ty_pl.ty.toType(), @ptrCast( - isel.air.extra.items[extra.end..][0..extra.data.body_len], - )); + const unwrapped_block = isel.air.unwrapBlock(air.inst_index); + try isel.block( + air.inst_index, + unwrapped_block.ty, + unwrapped_block.body, + ); if (air.next()) |next_air_tag| continue :air_tag next_air_tag; }, .loop => { - const ty_pl = air.data(air.inst_index).ty_pl; - const extra = isel.air.extraData(Air.Block, ty_pl.payload); + const unwrapped_block = isel.air.unwrapBlock(air.inst_index); const loops = isel.loops.values(); const loop_index = isel.loops.getIndex(air.inst_index).?; const loop = &loops[loop_index]; @@ -3558,7 +3539,7 @@ pub fn body(isel: *Select, air_body: []const Air.Inst.Index) error{ OutOfMemory, loop.live_registers = isel.live_registers; loop.repeat_list = Loop.empty_list; - try isel.body(@ptrCast(isel.air.extra.items[extra.end..][0..extra.data.body_len])); + try isel.body(unwrapped_block.body); try isel.merge(&loop.live_registers, .{ .fill_extra = true }); var repeat_label = loop.repeat_list; @@ -3608,10 +3589,9 @@ pub fn body(isel: *Select, air_body: []const Air.Inst.Index) error{ OutOfMemory, if (air.next()) |next_air_tag| continue :air_tag next_air_tag; }, .call => { - const pl_op = air.data(air.inst_index).pl_op; - const extra = isel.air.extraData(Air.Call, pl_op.payload); - const args: []const Air.Inst.Ref = @ptrCast(isel.air.extra.items[extra.end..][0..extra.data.args_len]); - const callee_ty = isel.air.typeOf(pl_op.operand, ip); + const air_call = isel.air.unwrapCall(air.inst_index); + const args = air_call.args; + const callee_ty = isel.air.typeOf(air_call.callee, ip); const func_info = switch (ip.indexToKey(callee_ty.toIntern())) { else => unreachable, .func_type => |func_type| func_type, @@ -3649,7 +3629,7 @@ pub fn body(isel: *Select, air_body: []const Air.Inst.Index) error{ OutOfMemory, try call.finishReturn(isel); try call.prepareCallee(isel); - if (pl_op.operand.toInterned()) |ct_callee| { + if (air_call.callee.toInterned()) |ct_callee| { try isel.nav_relocs.append(gpa, switch (ip.indexToKey(ct_callee)) { else => unreachable, inline .@"extern", .func => |func| .{ @@ -3666,7 +3646,7 @@ pub fn body(isel: *Select, air_body: []const Air.Inst.Index) error{ OutOfMemory, }); try isel.emit(.bl(0)); } else { - const callee_vi = try isel.use(pl_op.operand); + const callee_vi = try isel.use(air_call.callee); const callee_mat = try callee_vi.matReg(isel); try isel.emit(.blr(callee_mat.ra.x())); try callee_mat.finish(isel); @@ -4523,16 +4503,15 @@ pub fn body(isel: *Select, air_body: []const Air.Inst.Index) error{ OutOfMemory, if (air.next()) |next_air_tag| continue :air_tag next_air_tag; }, .cond_br => { - const pl_op = air.data(air.inst_index).pl_op; - const extra = isel.air.extraData(Air.CondBr, pl_op.payload); + const cond_br = isel.air.unwrapCondBr(air.inst_index); - try isel.body(@ptrCast(isel.air.extra.items[extra.end + extra.data.then_body_len ..][0..extra.data.else_body_len])); + try isel.body(cond_br.then_body); const else_label = isel.instructions.items.len; const else_live_registers = isel.live_registers; - try isel.body(@ptrCast(isel.air.extra.items[extra.end..][0..extra.data.then_body_len])); + try isel.body(cond_br.else_body); try isel.merge(&else_live_registers, .{}); - const cond_vi = try isel.use(pl_op.operand); + const cond_vi = try isel.use(cond_br.condition); const cond_mat = try cond_vi.matReg(isel); try isel.emit(.tbz( cond_mat.ra.x(), @@ -4819,13 +4798,12 @@ pub fn body(isel: *Select, air_body: []const Air.Inst.Index) error{ OutOfMemory, if (air.next()) |next_air_tag| continue :air_tag next_air_tag; }, .@"try", .try_cold => { - const pl_op = air.data(air.inst_index).pl_op; - const extra = isel.air.extraData(Air.Try, pl_op.payload); - const error_union_ty = isel.air.typeOf(pl_op.operand, ip); + const unwrapped_try = isel.air.unwrapTry(air.inst_index); + const error_union_ty = isel.air.typeOf(unwrapped_try.error_union, &zcu.intern_pool); const error_union_info = ip.indexToKey(error_union_ty.toIntern()).error_union_type; const payload_ty: ZigType = .fromInterned(error_union_info.payload_type); - const error_union_vi = try isel.use(pl_op.operand); + const error_union_vi = try isel.use(unwrapped_try.error_union); if (isel.live_values.fetchRemove(air.inst_index)) |payload_vi| { defer payload_vi.value.deref(isel); @@ -4840,7 +4818,7 @@ pub fn body(isel: *Select, air_body: []const Air.Inst.Index) error{ OutOfMemory, const cont_label = isel.instructions.items.len; const cont_live_registers = isel.live_registers; - try isel.body(@ptrCast(isel.air.extra.items[extra.end..][0..extra.data.body_len])); + try isel.body(unwrapped_try.else_body); try isel.merge(&cont_live_registers, .{}); var error_set_part_it = error_union_vi.field( @@ -4859,18 +4837,17 @@ pub fn body(isel: *Select, air_body: []const Air.Inst.Index) error{ OutOfMemory, if (air.next()) |next_air_tag| continue :air_tag next_air_tag; }, .try_ptr, .try_ptr_cold => { - const ty_pl = air.data(air.inst_index).ty_pl; - const extra = isel.air.extraData(Air.TryPtr, ty_pl.payload); - const error_union_ty = isel.air.typeOf(extra.data.ptr, ip).childType(zcu); + const unwrapped_try = isel.air.unwrapTryPtr(air.inst_index); + const error_union_ty = isel.air.typeOf(unwrapped_try.error_union_ptr, ip).childType(zcu); const error_union_info = ip.indexToKey(error_union_ty.toIntern()).error_union_type; const payload_ty: ZigType = .fromInterned(error_union_info.payload_type); - const error_union_ptr_vi = try isel.use(extra.data.ptr); + const error_union_ptr_vi = try isel.use(unwrapped_try.error_union_ptr); const error_union_ptr_mat = try error_union_ptr_vi.matReg(isel); if (isel.live_values.fetchRemove(air.inst_index)) |payload_ptr_vi| unused: { defer payload_ptr_vi.value.deref(isel); - switch (codegen.errUnionPayloadOffset(ty_pl.ty.toType().childType(zcu), zcu)) { - 0 => try payload_ptr_vi.value.move(isel, extra.data.ptr), + switch (codegen.errUnionPayloadOffset(unwrapped_try.error_union_payload_ptr_ty.toType().childType(zcu), zcu)) { + 0 => try payload_ptr_vi.value.move(isel, unwrapped_try.error_union_ptr), else => |payload_offset| { const payload_ptr_ra = try payload_ptr_vi.value.defReg(isel) orelse break :unused; const lo12: u12 = @truncate(payload_offset >> 0); @@ -4887,7 +4864,7 @@ pub fn body(isel: *Select, air_body: []const Air.Inst.Index) error{ OutOfMemory, const cont_label = isel.instructions.items.len; const cont_live_registers = isel.live_registers; - try isel.body(@ptrCast(isel.air.extra.items[extra.end..][0..extra.data.body_len])); + try isel.body(unwrapped_try.else_body); try isel.merge(&cont_live_registers, .{}); const error_set_ra = try isel.allocIntReg(); @@ -4913,11 +4890,8 @@ pub fn body(isel: *Select, air_body: []const Air.Inst.Index) error{ OutOfMemory, if (air.next()) |next_air_tag| continue :air_tag next_air_tag; }, .dbg_inline_block => { - const ty_pl = air.data(air.inst_index).ty_pl; - const extra = isel.air.extraData(Air.DbgInlineBlock, ty_pl.payload); - try isel.block(air.inst_index, ty_pl.ty.toType(), @ptrCast( - isel.air.extra.items[extra.end..][0..extra.data.body_len], - )); + const dbg_block = isel.air.unwrapDbgBlock(air.inst_index); + try isel.block(air.inst_index, dbg_block.ty, dbg_block.body); if (air.next()) |next_air_tag| continue :air_tag next_air_tag; }, .dbg_var_ptr, .dbg_var_val, .dbg_arg_inline => { diff --git a/src/codegen/c.zig b/src/codegen/c.zig index f55dd408b52460bad630bf4efd1a2f251a5b8d91..106737a8331c523f9ecddad0e0138f31a10ea06f 100644 --- a/src/codegen/c.zig +++ b/src/codegen/c.zig @@ -4622,9 +4622,8 @@ fn airCall( const gpa = f.object.dg.gpa; const w = &f.object.code.writer; - const pl_op = f.air.instructions.items(.data)[@intFromEnum(inst)].pl_op; - const extra = f.air.extraData(Air.Call, pl_op.payload); - const args: []const Air.Inst.Ref = @ptrCast(f.air.extra.items[extra.end..][0..extra.data.args_len]); + const call = f.air.unwrapCall(inst); + const args = call.args; const resolved_args = try gpa.alloc(CValue, args.len); defer gpa.free(resolved_args); @@ -4653,15 +4652,15 @@ fn airCall( } } - const callee = try f.resolveInst(pl_op.operand); + const callee = try f.resolveInst(call.callee); { var bt = iterateBigTomb(f, inst); - try bt.feed(pl_op.operand); + try bt.feed(call.callee); for (args) |arg| try bt.feed(arg); } - const callee_ty = f.typeOf(pl_op.operand); + const callee_ty = f.typeOf(call.callee); const callee_is_ptr = switch (callee_ty.zigTypeTag(zcu)) { .@"fn" => false, .pointer => true, @@ -4698,7 +4697,7 @@ fn airCall( callee: { known: { - const callee_val = (try f.air.value(pl_op.operand, pt)) orelse break :known; + const callee_val = (try f.air.value(call.callee, pt)) orelse break :known; const fn_nav, const need_cast = switch (ip.indexToKey(callee_val.toIntern())) { .@"extern" => |@"extern"| .{ @"extern".owner_nav, false }, .func => |func| .{ func.owner_nav, Type.fromInterned(func.ty).fnCallingConvention(zcu) != .naked and @@ -4796,13 +4795,12 @@ fn airDbgInlineBlock(f: *Function, inst: Air.Inst.Index) !CValue { const pt = f.object.dg.pt; const zcu = pt.zcu; const ip = &zcu.intern_pool; - const ty_pl = f.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl; - const extra = f.air.extraData(Air.DbgInlineBlock, ty_pl.payload); - const owner_nav = ip.getNav(zcu.funcInfo(extra.data.func).owner_nav); + const block = f.air.unwrapDbgBlock(inst); + const owner_nav = ip.getNav(zcu.funcInfo(block.func).owner_nav); const w = &f.object.code.writer; try w.print("/* inline:{f} */", .{owner_nav.fqn.fmt(&zcu.intern_pool)}); try f.object.newline(); - return lowerBlock(f, inst, @ptrCast(f.air.extra.items[extra.end..][0..extra.data.body_len])); + return lowerBlock(f, inst, block.body); } fn airDbgVar(f: *Function, inst: Air.Inst.Index) !CValue { @@ -4822,9 +4820,8 @@ fn airDbgVar(f: *Function, inst: Air.Inst.Index) !CValue { } fn airBlock(f: *Function, inst: Air.Inst.Index) !CValue { - const ty_pl = f.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl; - const extra = f.air.extraData(Air.Block, ty_pl.payload); - return lowerBlock(f, inst, @ptrCast(f.air.extra.items[extra.end..][0..extra.data.body_len])); + const block = f.air.unwrapBlock(inst); + return lowerBlock(f, inst, block.body); } fn lowerBlock(f: *Function, inst: Air.Inst.Index, body: []const Air.Inst.Index) !CValue { @@ -4873,21 +4870,19 @@ fn lowerBlock(f: *Function, inst: Air.Inst.Index, body: []const Air.Inst.Index) } fn airTry(f: *Function, inst: Air.Inst.Index) !CValue { - const pl_op = f.air.instructions.items(.data)[@intFromEnum(inst)].pl_op; - const extra = f.air.extraData(Air.Try, pl_op.payload); - const body: []const Air.Inst.Index = @ptrCast(f.air.extra.items[extra.end..][0..extra.data.body_len]); - const err_union_ty = f.typeOf(pl_op.operand); - return lowerTry(f, inst, pl_op.operand, body, err_union_ty, false); + const pt = f.object.dg.pt; + const unwrapped_try = f.air.unwrapTry(inst); + const body = unwrapped_try.else_body; + const err_union_ty = f.air.typeOf(unwrapped_try.error_union, &pt.zcu.intern_pool); + return lowerTry(f, inst, unwrapped_try.error_union, body, err_union_ty, false); } fn airTryPtr(f: *Function, inst: Air.Inst.Index) !CValue { const pt = f.object.dg.pt; - const zcu = pt.zcu; - const ty_pl = f.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl; - const extra = f.air.extraData(Air.TryPtr, ty_pl.payload); - const body: []const Air.Inst.Index = @ptrCast(f.air.extra.items[extra.end..][0..extra.data.body_len]); - const err_union_ty = f.typeOf(extra.data.ptr).childType(zcu); - return lowerTry(f, inst, extra.data.ptr, body, err_union_ty, true); + const unwrapped_try = f.air.unwrapTryPtr(inst); + const body = unwrapped_try.else_body; + const err_union_ty = f.air.typeOf(unwrapped_try.error_union_ptr, &pt.zcu.intern_pool).childType(pt.zcu); + return lowerTry(f, inst, unwrapped_try.error_union_ptr, body, err_union_ty, true); } fn lowerTry( @@ -5216,9 +5211,7 @@ fn airUnreach(o: *Object) !void { } fn airLoop(f: *Function, inst: Air.Inst.Index) !void { - const ty_pl = f.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl; - const loop = f.air.extraData(Air.Block, ty_pl.payload); - const body: []const Air.Inst.Index = @ptrCast(f.air.extra.items[loop.end..][0..loop.data.body_len]); + const block = f.air.unwrapBlock(inst); const w = &f.object.code.writer; // `repeat` instructions matching this loop will branch to @@ -5227,16 +5220,15 @@ fn airLoop(f: *Function, inst: Air.Inst.Index) !void { // construct at all! try w.print("zig_loop_{d}:", .{@intFromEnum(inst)}); try f.object.newline(); - try genBodyInner(f, body); // no need to restore state, we're noreturn + try genBodyInner(f, block.body); // no need to restore state, we're noreturn } fn airCondBr(f: *Function, inst: Air.Inst.Index) !void { - const pl_op = f.air.instructions.items(.data)[@intFromEnum(inst)].pl_op; - const cond = try f.resolveInst(pl_op.operand); - try reap(f, inst, &.{pl_op.operand}); - const extra = f.air.extraData(Air.CondBr, pl_op.payload); - const then_body: []const Air.Inst.Index = @ptrCast(f.air.extra.items[extra.end..][0..extra.data.then_body_len]); - const else_body: []const Air.Inst.Index = @ptrCast(f.air.extra.items[extra.end + then_body.len ..][0..extra.data.else_body_len]); + const cond_br = f.air.unwrapCondBr(inst); + const cond = try f.resolveInst(cond_br.condition); + try reap(f, inst, &.{cond_br.condition}); + const then_body = cond_br.then_body; + const else_body = cond_br.else_body; const liveness_condbr = f.liveness.getCondBr(inst); const w = &f.object.code.writer; @@ -5439,16 +5431,11 @@ fn asmInputNeedsLocal(f: *Function, constraint: []const u8, value: CValue) bool fn airAsm(f: *Function, inst: Air.Inst.Index) !CValue { const pt = f.object.dg.pt; const zcu = pt.zcu; - const ty_pl = f.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl; - const extra = f.air.extraData(Air.Asm, ty_pl.payload); - const is_volatile = extra.data.flags.is_volatile; - const outputs_len = extra.data.flags.outputs_len; + const unwrapped_asm = f.air.unwrapAsm(inst); + const is_volatile = unwrapped_asm.is_volatile; const gpa = f.object.dg.gpa; - var extra_i: usize = extra.end; - const outputs: []const Air.Inst.Ref = @ptrCast(f.air.extra.items[extra_i..][0..outputs_len]); - extra_i += outputs.len; - const inputs: []const Air.Inst.Ref = @ptrCast(f.air.extra.items[extra_i..][0..extra.data.inputs_len]); - extra_i += inputs.len; + const outputs = unwrapped_asm.outputs; + const inputs = unwrapped_asm.inputs; const result = result: { const w = &f.object.code.writer; @@ -5469,14 +5456,9 @@ fn airAsm(f: *Function, inst: Air.Inst.Index) !CValue { } else .none; const locals_begin: LocalIndex = @intCast(f.locals.items.len); - const constraints_extra_begin = extra_i; - for (outputs) |output| { - const extra_bytes = mem.sliceAsBytes(f.air.extra.items[extra_i..]); - const constraint = mem.sliceTo(extra_bytes, 0); - const name = mem.sliceTo(extra_bytes[constraint.len + 1 ..], 0); - // This equation accounts for the fact that even if we have exactly 4 bytes - // for the string, we still use the next u32 for the null terminator. - extra_i += (constraint.len + name.len + (2 + 3)) / 4; + var it = unwrapped_asm.iterateOutputs(); + while (it.next()) |output| { + const constraint = output.constraint; if (constraint.len < 2 or constraint[0] != '=' or (constraint[1] == '{' and constraint[constraint.len - 1] != '}')) @@ -5486,7 +5468,7 @@ fn airAsm(f: *Function, inst: Air.Inst.Index) !CValue { const is_reg = constraint[1] == '{'; if (is_reg) { - const output_ty = if (output == .none) inst_ty else f.typeOf(output).childType(zcu); + const output_ty = if (output.operand == .none) inst_ty else f.typeOf(output.operand).childType(zcu); try w.writeAll("register "); const output_local = try f.allocLocalValue(.{ .ctype = try f.ctypeFromType(output_ty, .complete), @@ -5505,13 +5487,10 @@ fn airAsm(f: *Function, inst: Air.Inst.Index) !CValue { try f.object.newline(); } } - for (inputs) |input| { - const extra_bytes = mem.sliceAsBytes(f.air.extra.items[extra_i..]); - const constraint = mem.sliceTo(extra_bytes, 0); - const name = mem.sliceTo(extra_bytes[constraint.len + 1 ..], 0); - // This equation accounts for the fact that even if we have exactly 4 bytes - // for the string, we still use the next u32 for the null terminator. - extra_i += (constraint.len + name.len + (2 + 3)) / 4; + + it = unwrapped_asm.iterateInputs(); + while (it.next()) |input| { + const constraint = input.constraint; if (constraint.len < 1 or mem.indexOfScalar(u8, "=+&%", constraint[0]) != null or (constraint[0] == '{' and constraint[constraint.len - 1] != '}')) @@ -5520,9 +5499,9 @@ fn airAsm(f: *Function, inst: Air.Inst.Index) !CValue { } const is_reg = constraint[0] == '{'; - const input_val = try f.resolveInst(input); + const input_val = try f.resolveInst(input.operand); if (asmInputNeedsLocal(f, constraint, input_val)) { - const input_ty = f.typeOf(input); + const input_ty = f.typeOf(input.operand); if (is_reg) try w.writeAll("register "); const input_local = try f.allocLocalValue(.{ .ctype = try f.ctypeFromType(input_ty, .complete), @@ -5545,7 +5524,7 @@ fn airAsm(f: *Function, inst: Air.Inst.Index) !CValue { } { - const asm_source = mem.sliceAsBytes(f.air.extra.items[extra_i..])[0..extra.data.source_len]; + const asm_source = unwrapped_asm.source; var stack = std.heap.stackFallback(256, f.object.dg.gpa); const allocator = stack.get(); @@ -5599,18 +5578,15 @@ fn airAsm(f: *Function, inst: Air.Inst.Index) !CValue { try w.print("({f}", .{fmtStringLiteral(fixed_asm_source[0..dst_i], null)}); } - extra_i = constraints_extra_begin; var locals_index = locals_begin; try w.writeByte(':'); - for (outputs, 0..) |output, index| { - const extra_bytes = mem.sliceAsBytes(f.air.extra.items[extra_i..]); - const constraint = mem.sliceTo(extra_bytes, 0); - const name = mem.sliceTo(extra_bytes[constraint.len + 1 ..], 0); - // This equation accounts for the fact that even if we have exactly 4 bytes - // for the string, we still use the next u32 for the null terminator. - extra_i += (constraint.len + name.len + (2 + 3)) / 4; - if (index > 0) try w.writeByte(','); + it = unwrapped_asm.iterateOutputs(); + while (it.next()) |output| { + const constraint = output.constraint; + const name = output.name; + + if (output.index > 0) try w.writeByte(','); try w.writeByte(' '); if (!mem.eql(u8, name, "_")) try w.print("[{s}]", .{name}); const is_reg = constraint[1] == '{'; @@ -5618,28 +5594,26 @@ fn airAsm(f: *Function, inst: Air.Inst.Index) !CValue { if (is_reg) { try f.writeCValue(w, .{ .local = locals_index }, .Other); locals_index += 1; - } else if (output == .none) { + } else if (output.operand == .none) { try f.writeCValue(w, inst_local, .FunctionArgument); } else { - try f.writeCValueDeref(w, try f.resolveInst(output)); + try f.writeCValueDeref(w, try f.resolveInst(output.operand)); } try w.writeByte(')'); } try w.writeByte(':'); - for (inputs, 0..) |input, index| { - const extra_bytes = mem.sliceAsBytes(f.air.extra.items[extra_i..]); - const constraint = mem.sliceTo(extra_bytes, 0); - const name = mem.sliceTo(extra_bytes[constraint.len + 1 ..], 0); - // This equation accounts for the fact that even if we have exactly 4 bytes - // for the string, we still use the next u32 for the null terminator. - extra_i += (constraint.len + name.len + (2 + 3)) / 4; - if (index > 0) try w.writeByte(','); + it = unwrapped_asm.iterateInputs(); + while (it.next()) |input| { + const constraint = input.constraint; + const name = input.name; + + if (input.index > 0) try w.writeByte(','); try w.writeByte(' '); if (!mem.eql(u8, name, "_")) try w.print("[{s}]", .{name}); const is_reg = constraint[0] == '{'; - const input_val = try f.resolveInst(input); + const input_val = try f.resolveInst(input.operand); try w.print("{f}(", .{fmtStringLiteral(if (is_reg) "r" else constraint, null)}); try f.writeCValue(w, if (asmInputNeedsLocal(f, constraint, input_val)) local: { const input_local_idx = locals_index; @@ -5650,7 +5624,7 @@ fn airAsm(f: *Function, inst: Air.Inst.Index) !CValue { } try w.writeByte(':'); const ip = &zcu.intern_pool; - const aggregate = ip.indexToKey(extra.data.clobbers).aggregate; + const aggregate = ip.indexToKey(unwrapped_asm.clobbers).aggregate; const struct_type: Type = .fromInterned(aggregate.ty); switch (aggregate.storage) { .elems => |elems| for (elems, 0..) |elem, i| switch (elem) { @@ -5697,22 +5671,17 @@ fn airAsm(f: *Function, inst: Air.Inst.Index) !CValue { try w.writeAll(");"); try f.object.newline(); - extra_i = constraints_extra_begin; locals_index = locals_begin; - for (outputs) |output| { - const extra_bytes = mem.sliceAsBytes(f.air.extra.items[extra_i..]); - const constraint = mem.sliceTo(extra_bytes, 0); - const name = mem.sliceTo(extra_bytes[constraint.len + 1 ..], 0); - // This equation accounts for the fact that even if we have exactly 4 bytes - // for the string, we still use the next u32 for the null terminator. - extra_i += (constraint.len + name.len + (2 + 3)) / 4; + it = unwrapped_asm.iterateOutputs(); + while (it.next()) |output| { + const constraint = output.constraint; const is_reg = constraint[1] == '{'; if (is_reg) { - try f.writeCValueDeref(w, if (output == .none) + try f.writeCValueDeref(w, if (output.operand == .none) .{ .local_ref = inst_local.new_local } else - try f.resolveInst(output)); + try f.resolveInst(output.operand)); try w.writeAll(" = "); try f.writeCValue(w, .{ .local = locals_index }, .Other); locals_index += 1; diff --git a/src/codegen/llvm.zig b/src/codegen/llvm.zig index a178604a6874b68dc30e69f00b7d50bc0e0bfa86..b2a7d230ce7b695f715c2421e9ef2c5f1588f998 100644 --- a/src/codegen/llvm.zig +++ b/src/codegen/llvm.zig @@ -5258,14 +5258,13 @@ pub const FuncGen = struct { }; fn airCall(self: *FuncGen, inst: Air.Inst.Index, modifier: std.builtin.CallModifier) !Builder.Value { - const pl_op = self.air.instructions.items(.data)[@intFromEnum(inst)].pl_op; - const extra = self.air.extraData(Air.Call, pl_op.payload); - const args: []const Air.Inst.Ref = @ptrCast(self.air.extra.items[extra.end..][0..extra.data.args_len]); + const air_call = self.air.unwrapCall(inst); + const args = air_call.args; const o = self.ng.object; const pt = self.ng.pt; const zcu = pt.zcu; const ip = &zcu.intern_pool; - const callee_ty = self.typeOf(pl_op.operand); + const callee_ty = self.typeOf(air_call.callee); const zig_fn_ty = switch (callee_ty.zigTypeTag(zcu)) { .@"fn" => callee_ty, .pointer => callee_ty.childType(zcu), @@ -5273,7 +5272,7 @@ pub const FuncGen = struct { }; const fn_info = zcu.typeToFunc(zig_fn_ty).?; const return_type = Type.fromInterned(fn_info.return_type); - const llvm_fn = try self.resolveInst(pl_op.operand); + const llvm_fn = try self.resolveInst(air_call.callee); const target = zcu.getTarget(); const sret = firstParamSRet(fn_info, zcu, target); @@ -5934,9 +5933,8 @@ pub const FuncGen = struct { } fn airBlock(self: *FuncGen, inst: Air.Inst.Index) !Builder.Value { - const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl; - const extra = self.air.extraData(Air.Block, ty_pl.payload); - return self.lowerBlock(inst, null, @ptrCast(self.air.extra.items[extra.end..][0..extra.data.body_len])); + const block = self.air.unwrapBlock(inst); + return self.lowerBlock(inst, null, block.body); } fn lowerBlock( @@ -6216,11 +6214,10 @@ pub const FuncGen = struct { } fn airCondBr(self: *FuncGen, inst: Air.Inst.Index) !void { - const pl_op = self.air.instructions.items(.data)[@intFromEnum(inst)].pl_op; - const cond = try self.resolveInst(pl_op.operand); - const extra = self.air.extraData(Air.CondBr, pl_op.payload); - const then_body: []const Air.Inst.Index = @ptrCast(self.air.extra.items[extra.end..][0..extra.data.then_body_len]); - const else_body: []const Air.Inst.Index = @ptrCast(self.air.extra.items[extra.end + then_body.len ..][0..extra.data.else_body_len]); + const cond_br = self.air.unwrapCondBr(inst); + const cond = try self.resolveInst(cond_br.condition); + const then_body = cond_br.then_body; + const else_body = cond_br.else_body; const Hint = enum { none, @@ -6230,22 +6227,22 @@ pub const FuncGen = struct { then_cold, else_cold, }; - const hint: Hint = switch (extra.data.branch_hints.true) { - .none => switch (extra.data.branch_hints.false) { + const hint: Hint = switch (cond_br.branch_hints.true) { + .none => switch (cond_br.branch_hints.false) { .none => .none, .likely => .else_likely, .unlikely => .then_likely, .cold => .else_cold, .unpredictable => .unpredictable, }, - .likely => switch (extra.data.branch_hints.false) { + .likely => switch (cond_br.branch_hints.false) { .none => .then_likely, .likely => .unpredictable, .unlikely => .then_likely, .cold => .else_cold, .unpredictable => .unpredictable, }, - .unlikely => switch (extra.data.branch_hints.false) { + .unlikely => switch (cond_br.branch_hints.false) { .none => .else_likely, .likely => .else_likely, .unlikely => .unpredictable, @@ -6267,35 +6264,33 @@ pub const FuncGen = struct { self.wip.cursor = .{ .block = then_block }; if (hint == .then_cold) _ = try self.wip.callIntrinsicAssumeCold(); - try self.genBodyDebugScope(null, then_body, extra.data.branch_hints.then_cov); + try self.genBodyDebugScope(null, then_body, cond_br.branch_hints.then_cov); self.wip.cursor = .{ .block = else_block }; if (hint == .else_cold) _ = try self.wip.callIntrinsicAssumeCold(); - try self.genBodyDebugScope(null, else_body, extra.data.branch_hints.else_cov); + try self.genBodyDebugScope(null, else_body, cond_br.branch_hints.else_cov); // No need to reset the insert cursor since this instruction is noreturn. } fn airTry(self: *FuncGen, inst: Air.Inst.Index, err_cold: bool) !Builder.Value { - const pl_op = self.air.instructions.items(.data)[@intFromEnum(inst)].pl_op; - const err_union = try self.resolveInst(pl_op.operand); - const extra = self.air.extraData(Air.Try, pl_op.payload); - const body: []const Air.Inst.Index = @ptrCast(self.air.extra.items[extra.end..][0..extra.data.body_len]); - const err_union_ty = self.typeOf(pl_op.operand); + const unwrapped_try = self.air.unwrapTry(inst); + const err_union = try self.resolveInst(unwrapped_try.error_union); + const body = unwrapped_try.else_body; + const err_union_ty = self.typeOf(unwrapped_try.error_union); const is_unused = self.liveness.isUnused(inst); return lowerTry(self, err_union, body, err_union_ty, false, false, is_unused, err_cold); } fn airTryPtr(self: *FuncGen, inst: Air.Inst.Index, err_cold: bool) !Builder.Value { const zcu = self.ng.pt.zcu; - const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl; - const extra = self.air.extraData(Air.TryPtr, ty_pl.payload); - const err_union_ptr = try self.resolveInst(extra.data.ptr); - const body: []const Air.Inst.Index = @ptrCast(self.air.extra.items[extra.end..][0..extra.data.body_len]); - const err_union_ty = self.typeOf(extra.data.ptr).childType(zcu); + const unwrapped_try = self.air.unwrapTryPtr(inst); + const err_union_ptr = try self.resolveInst(unwrapped_try.error_union_ptr); + const body = unwrapped_try.else_body; + const err_union_ty = self.typeOf(unwrapped_try.error_union_ptr).childType(zcu); const is_unused = self.liveness.isUnused(inst); - self.maybeMarkAllowZeroAccess(self.typeOf(extra.data.ptr).ptrInfo(zcu)); + self.maybeMarkAllowZeroAccess(self.typeOf(unwrapped_try.error_union_ptr).ptrInfo(zcu)); return lowerTry(self, err_union_ptr, body, err_union_ty, true, true, is_unused, err_cold); } @@ -6627,9 +6622,8 @@ pub const FuncGen = struct { } fn airLoop(self: *FuncGen, inst: Air.Inst.Index) !void { - const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl; - const loop = self.air.extraData(Air.Block, ty_pl.payload); - const body: []const Air.Inst.Index = @ptrCast(self.air.extra.items[loop.end..][0..loop.data.body_len]); + const block = self.air.unwrapBlock(inst); + const body = block.body; const loop_block = try self.wip.block(1, "Loop"); // `airRepeat` will increment incoming each time _ = try self.wip.br(loop_block); @@ -7137,10 +7131,9 @@ pub const FuncGen = struct { } fn airDbgInlineBlock(self: *FuncGen, inst: Air.Inst.Index) !Builder.Value { - const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl; - const extra = self.air.extraData(Air.DbgInlineBlock, ty_pl.payload); + const block = self.air.unwrapDbgBlock(inst); self.arg_inline_index = 0; - return self.lowerBlock(inst, extra.data.func, @ptrCast(self.air.extra.items[extra.end..][0..extra.data.body_len])); + return self.lowerBlock(inst, block.func, block.body); } fn airDbgVarPtr(self: *FuncGen, inst: Air.Inst.Index) !Builder.Value { @@ -7262,17 +7255,12 @@ pub const FuncGen = struct { // this implementation feeds the inline assembly code directly to LLVM. const o = self.ng.object; - const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl; - const extra = self.air.extraData(Air.Asm, ty_pl.payload); - const is_volatile = extra.data.flags.is_volatile; - const outputs_len = extra.data.flags.outputs_len; + const unwrapped_asm = self.air.unwrapAsm(inst); + const is_volatile = unwrapped_asm.is_volatile; const gpa = self.gpa; - var extra_i: usize = extra.end; - const outputs: []const Air.Inst.Ref = @ptrCast(self.air.extra.items[extra_i..][0..outputs_len]); - extra_i += outputs.len; - const inputs: []const Air.Inst.Ref = @ptrCast(self.air.extra.items[extra_i..][0..extra.data.inputs_len]); - extra_i += inputs.len; + const outputs = unwrapped_asm.outputs; + const inputs = unwrapped_asm.inputs; var llvm_constraints: std.ArrayList(u8) = .empty; defer llvm_constraints.deinit(gpa); @@ -7305,14 +7293,10 @@ pub const FuncGen = struct { var name_map: std.StringArrayHashMapUnmanaged(u16) = .empty; try name_map.ensureUnusedCapacity(arena, max_param_count); - var rw_extra_i = extra_i; - for (outputs, llvm_ret_indirect, llvm_rw_vals) |output, *is_indirect, *llvm_rw_val| { - const extra_bytes = std.mem.sliceAsBytes(self.air.extra.items[extra_i..]); - const constraint = std.mem.sliceTo(std.mem.sliceAsBytes(self.air.extra.items[extra_i..]), 0); - const name = std.mem.sliceTo(extra_bytes[constraint.len + 1 ..], 0); - // This equation accounts for the fact that even if we have exactly 4 bytes - // for the string, we still use the next u32 for the null terminator. - extra_i += (constraint.len + name.len + (2 + 3)) / 4; + var it = unwrapped_asm.iterateOutputs(); + while (it.next()) |output| { + const constraint = output.constraint; + const name = output.name; try llvm_constraints.ensureUnusedCapacity(gpa, constraint.len + 3); if (total_i != 0) { @@ -7320,15 +7304,15 @@ pub const FuncGen = struct { } llvm_constraints.appendAssumeCapacity('='); - if (output != .none) { - const output_inst = try self.resolveInst(output); - const output_ty = self.typeOf(output); + if (output.operand != .none) { + const output_inst = try self.resolveInst(output.operand); + const output_ty = self.typeOf(output.operand); assert(output_ty.zigTypeTag(zcu) == .pointer); const elem_llvm_ty = try o.lowerPtrElemTy(pt, output_ty.childType(zcu)); switch (constraint[0]) { '=' => {}, - '+' => llvm_rw_val.* = output_inst, + '+' => llvm_rw_vals[output.index] = output_inst, else => return self.todo("unsupported output constraint on output type '{c}'", .{ constraint[0], }), @@ -7337,8 +7321,8 @@ pub const FuncGen = struct { self.maybeMarkAllowZeroAccess(output_ty.ptrInfo(zcu)); // Pass any non-return outputs indirectly, if the constraint accepts a memory location - is_indirect.* = constraintAllowsMemory(constraint); - if (is_indirect.*) { + llvm_ret_indirect[output.index] = constraintAllowsMemory(constraint); + if (llvm_ret_indirect[output.index]) { // Pass the result by reference as an indirect output (e.g. "=*m") llvm_constraints.appendAssumeCapacity('*'); @@ -7359,7 +7343,7 @@ pub const FuncGen = struct { }), } - is_indirect.* = false; + llvm_ret_indirect[output.index] = false; const ret_ty = self.typeOfIndex(inst); llvm_ret_types[llvm_ret_i] = try o.lowerType(pt, ret_ty); @@ -7387,16 +7371,13 @@ pub const FuncGen = struct { total_i += 1; } - for (inputs) |input| { - const extra_bytes = std.mem.sliceAsBytes(self.air.extra.items[extra_i..]); - const constraint = std.mem.sliceTo(extra_bytes, 0); - const name = std.mem.sliceTo(extra_bytes[constraint.len + 1 ..], 0); - // This equation accounts for the fact that even if we have exactly 4 bytes - // for the string, we still use the next u32 for the null terminator. - extra_i += (constraint.len + name.len + (2 + 3)) / 4; + it = unwrapped_asm.iterateInputs(); + while (it.next()) |input| { + const constraint = input.constraint; + const name = input.name; - const arg_llvm_value = try self.resolveInst(input); - const arg_ty = self.typeOf(input); + const arg_llvm_value = try self.resolveInst(input.operand); + const arg_ty = self.typeOf(input.operand); const is_by_ref = isByRef(arg_ty, zcu); if (is_by_ref) { if (constraintAllowsMemory(constraint)) { @@ -7452,27 +7433,23 @@ pub const FuncGen = struct { total_i += 1; } - for (outputs, llvm_ret_indirect, llvm_rw_vals, 0..) |output, is_indirect, llvm_rw_val, output_index| { - const extra_bytes = std.mem.sliceAsBytes(self.air.extra.items[rw_extra_i..]); - const constraint = std.mem.sliceTo(std.mem.sliceAsBytes(self.air.extra.items[rw_extra_i..]), 0); - const name = std.mem.sliceTo(extra_bytes[constraint.len + 1 ..], 0); - // This equation accounts for the fact that even if we have exactly 4 bytes - // for the string, we still use the next u32 for the null terminator. - rw_extra_i += (constraint.len + name.len + (2 + 3)) / 4; + it = unwrapped_asm.iterateOutputs(); + while (it.next()) |output| { + const constraint = output.constraint; if (constraint[0] != '+') continue; - const rw_ty = self.typeOf(output); + const rw_ty = self.typeOf(output.operand); const llvm_elem_ty = try o.lowerPtrElemTy(pt, rw_ty.childType(zcu)); - if (is_indirect) { - llvm_param_values[llvm_param_i] = llvm_rw_val; - llvm_param_types[llvm_param_i] = llvm_rw_val.typeOfWip(&self.wip); + if (llvm_ret_indirect[output.index]) { + llvm_param_values[llvm_param_i] = llvm_rw_vals[output.index]; + llvm_param_types[llvm_param_i] = llvm_rw_vals[output.index].typeOfWip(&self.wip); } else { const alignment = rw_ty.abiAlignment(zcu).toLlvm(); const loaded = try self.wip.load( if (rw_ty.isVolatilePtr(zcu)) .@"volatile" else .normal, llvm_elem_ty, - llvm_rw_val, + llvm_rw_vals[output.index], alignment, "", ); @@ -7480,18 +7457,18 @@ pub const FuncGen = struct { llvm_param_types[llvm_param_i] = llvm_elem_ty; } - try llvm_constraints.print(gpa, ",{d}", .{output_index}); + try llvm_constraints.print(gpa, ",{d}", .{output.index}); // In the case of indirect inputs, LLVM requires the callsite to have // an elementtype() attribute. - llvm_param_attrs[llvm_param_i] = if (is_indirect) llvm_elem_ty else .none; + llvm_param_attrs[llvm_param_i] = if (llvm_ret_indirect[output.index]) llvm_elem_ty else .none; llvm_param_i += 1; total_i += 1; } const ip = &zcu.intern_pool; - const aggregate = ip.indexToKey(extra.data.clobbers).aggregate; + const aggregate = ip.indexToKey(unwrapped_asm.clobbers).aggregate; const struct_type: Type = .fromInterned(aggregate.ty); if (total_i != 0) try llvm_constraints.append(gpa, ','); switch (aggregate.storage) { @@ -7539,7 +7516,7 @@ pub const FuncGen = struct { if (std.mem.endsWith(u8, llvm_constraints.items, ",")) llvm_constraints.items.len -= 1; - const asm_source = std.mem.sliceAsBytes(self.air.extra.items[extra_i..])[0..extra.data.source_len]; + const asm_source = unwrapped_asm.source; // hackety hacks until stage2 has proper inline asm in the frontend. var rendered_template = std.array_list.Managed(u8).init(gpa); diff --git a/src/codegen/riscv64/CodeGen.zig b/src/codegen/riscv64/CodeGen.zig index 5dc89d9af916d5c6f6cc47dfd32982622b3e9412..1f70f5f4de379ad0e01f75be35f0d13695f5b766 100644 --- a/src/codegen/riscv64/CodeGen.zig +++ b/src/codegen/riscv64/CodeGen.zig @@ -3627,11 +3627,11 @@ fn airRuntimeNavPtr(func: *Func, inst: Air.Inst.Index) !void { } fn airTry(func: *Func, inst: Air.Inst.Index) !void { - const pl_op = func.air.instructions.items(.data)[@intFromEnum(inst)].pl_op; - const extra = func.air.extraData(Air.Try, pl_op.payload); - const body: []const Air.Inst.Index = @ptrCast(func.air.extra.items[extra.end..][0..extra.data.body_len]); - const operand_ty = func.typeOf(pl_op.operand); - const result = try func.genTry(inst, pl_op.operand, body, operand_ty, false); + const zcu = func.pt.zcu; + const unwrapped_try = func.air.unwrapTry(inst); + const body = unwrapped_try.else_body; + const operand_ty = func.air.typeOf(unwrapped_try.error_union, &zcu.intern_pool); + const result = try func.genTry(inst, unwrapped_try.error_union, body, operand_ty, false); return func.finishAir(inst, result, .{ .none, .none, .none }); } @@ -4801,10 +4801,8 @@ fn airFrameAddress(func: *Func, inst: Air.Inst.Index) !void { fn airCall(func: *Func, inst: Air.Inst.Index, modifier: std.builtin.CallModifier) !void { if (modifier == .always_tail) return func.fail("TODO implement tail calls for riscv64", .{}); - const pl_op = func.air.instructions.items(.data)[@intFromEnum(inst)].pl_op; - const callee = pl_op.operand; - const extra = func.air.extraData(Air.Call, pl_op.payload); - const arg_refs: []const Air.Inst.Ref = @ptrCast(func.air.extra.items[extra.end..][0..extra.data.args_len]); + const call = func.air.unwrapCall(inst); + const arg_refs = call.args; const expected_num_args = 8; const ExpectedContents = extern struct { @@ -4822,10 +4820,10 @@ fn airCall(func: *Func, inst: Air.Inst.Index, modifier: std.builtin.CallModifier defer allocator.free(arg_vals); for (arg_vals, arg_refs) |*arg_val, arg_ref| arg_val.* = .{ .air_ref = arg_ref }; - const call_ret = try func.genCall(.{ .air = callee }, arg_tys, arg_vals); + const call_ret = try func.genCall(.{ .air = call.callee }, arg_tys, arg_vals); var bt = func.liveness.iterateBigTomb(inst); - try func.feed(&bt, pl_op.operand); + try func.feed(&bt, call.callee); for (arg_refs) |arg_ref| try func.feed(&bt, arg_ref); const result = if (func.liveness.isUnused(inst)) .unreach else call_ret; @@ -5218,9 +5216,8 @@ fn airDbgStmt(func: *Func, inst: Air.Inst.Index) !void { } fn airDbgInlineBlock(func: *Func, inst: Air.Inst.Index) !void { - const ty_pl = func.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl; - const extra = func.air.extraData(Air.DbgInlineBlock, ty_pl.payload); - try func.lowerBlock(inst, @ptrCast(func.air.extra.items[extra.end..][0..extra.data.body_len])); + const block = func.air.unwrapDbgBlock(inst); + try func.lowerBlock(inst, block.body); } fn airDbgVar(func: *Func, inst: Air.Inst.Index) InnerError!void { @@ -5271,19 +5268,18 @@ fn genVarDbgInfo( } fn airCondBr(func: *Func, inst: Air.Inst.Index) !void { - const pl_op = func.air.instructions.items(.data)[@intFromEnum(inst)].pl_op; - const cond = try func.resolveInst(pl_op.operand); - const cond_ty = func.typeOf(pl_op.operand); - const extra = func.air.extraData(Air.CondBr, pl_op.payload); - const then_body: []const Air.Inst.Index = @ptrCast(func.air.extra.items[extra.end..][0..extra.data.then_body_len]); - const else_body: []const Air.Inst.Index = @ptrCast(func.air.extra.items[extra.end + then_body.len ..][0..extra.data.else_body_len]); + const cond_br = func.air.unwrapCondBr(inst); + const cond = try func.resolveInst(cond_br.condition); + const cond_ty = func.typeOf(cond_br.condition); + const then_body = cond_br.then_body; + const else_body = cond_br.else_body; const liveness_cond_br = func.liveness.getCondBr(inst); // If the condition dies here in this condbr instruction, process // that death now instead of later as this has an effect on // whether it needs to be spilled in the branches if (func.liveness.operandDies(inst, 0)) { - if (pl_op.operand.toIndex()) |op_inst| try func.processDeath(op_inst); + if (cond_br.condition.toIndex()) |op_inst| try func.processDeath(op_inst); } func.scope_generation += 1; @@ -5633,10 +5629,7 @@ fn airIsNonErrPtr(func: *Func, inst: Air.Inst.Index) !void { fn airLoop(func: *Func, inst: Air.Inst.Index) !void { // A loop is a setup to be able to jump back to the beginning. - const ty_pl = func.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl; - const loop = func.air.extraData(Air.Block, ty_pl.payload); - const body: []const Air.Inst.Index = @ptrCast(func.air.extra.items[loop.end..][0..loop.data.body_len]); - + const body = func.air.unwrapBlock(inst); func.scope_generation += 1; const state = try func.saveState(); @@ -5646,7 +5639,7 @@ fn airLoop(func: *Func, inst: Air.Inst.Index) !void { }); defer assert(func.loops.remove(inst)); - try func.genBody(body); + try func.genBody(body.body); func.finishAirBookkeeping(); } @@ -5663,9 +5656,8 @@ fn jump(func: *Func, index: Mir.Inst.Index) !Mir.Inst.Index { } fn airBlock(func: *Func, inst: Air.Inst.Index) !void { - const ty_pl = func.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl; - const extra = func.air.extraData(Air.Block, ty_pl.payload); - try func.lowerBlock(inst, @ptrCast(func.air.extra.items[extra.end..][0..extra.data.body_len])); + const block = func.air.unwrapBlock(inst); + try func.lowerBlock(inst, block.body); } fn lowerBlock(func: *Func, inst: Air.Inst.Index, body: []const Air.Inst.Index) !void { @@ -6053,15 +6045,9 @@ fn airBoolOp(func: *Func, inst: Air.Inst.Index) !void { } fn airAsm(func: *Func, inst: Air.Inst.Index) !void { - const ty_pl = func.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl; - const extra = func.air.extraData(Air.Asm, ty_pl.payload); - const outputs_len = extra.data.flags.outputs_len; - var extra_i: usize = extra.end; - const outputs: []const Air.Inst.Ref = - @ptrCast(func.air.extra.items[extra_i..][0..outputs_len]); - extra_i += outputs.len; - const inputs: []const Air.Inst.Ref = @ptrCast(func.air.extra.items[extra_i..][0..extra.data.inputs_len]); - extra_i += inputs.len; + const unwrapped_asm = func.air.unwrapAsm(inst); + const outputs = unwrapped_asm.outputs; + const inputs = unwrapped_asm.inputs; var result: MCValue = .none; var args = std.array_list.Managed(MCValue).init(func.gpa); @@ -6076,19 +6062,15 @@ fn airAsm(func: *Func, inst: Air.Inst.Index) !void { try arg_map.ensureTotalCapacity(@intCast(outputs.len + inputs.len)); defer arg_map.deinit(); - var outputs_extra_i = extra_i; - for (outputs) |output| { - const extra_bytes = mem.sliceAsBytes(func.air.extra.items[extra_i..]); - const constraint = mem.sliceTo(mem.sliceAsBytes(func.air.extra.items[extra_i..]), 0); - const name = mem.sliceTo(extra_bytes[constraint.len + 1 ..], 0); - // This equation accounts for the fact that even if we have exactly 4 bytes - // for the string, we still use the next u32 for the null terminator. - extra_i += (constraint.len + name.len + (2 + 3)) / 4; + var it = unwrapped_asm.iterateOutputs(); + while (it.next()) |output| { + const constraint = output.constraint; + const name = output.name; const is_read = switch (constraint[0]) { '=' => false, '+' => read: { - if (output == .none) return func.fail( + if (output.operand == .none) return func.fail( "read-write constraint unsupported for asm result: '{s}'", .{constraint}, ); @@ -6100,7 +6082,7 @@ fn airAsm(func: *Func, inst: Air.Inst.Index) !void { const rest = constraint[@as(usize, 1) + @intFromBool(is_early_clobber) ..]; const arg_mcv: MCValue = arg_mcv: { const arg_maybe_reg: ?Register = if (mem.eql(u8, rest, "m")) - if (output != .none) null else return func.fail( + if (output.operand != .none) null else return func.fail( "memory constraint unsupported for asm result: '{s}'", .{constraint}, ) @@ -6115,7 +6097,7 @@ fn airAsm(func: *Func, inst: Air.Inst.Index) !void { break :arg_mcv args.items[index]; } else return func.fail("invalid constraint: '{s}'", .{constraint}); break :arg_mcv if (arg_maybe_reg) |reg| .{ .register = reg } else arg: { - const ptr_mcv = try func.resolveInst(output); + const ptr_mcv = try func.resolveInst(output.operand); switch (ptr_mcv) { .immediate => |addr| if (math.cast(i32, @as(i64, @bitCast(addr)))) |_| break :arg ptr_mcv.deref(), @@ -6131,20 +6113,17 @@ fn airAsm(func: *Func, inst: Air.Inst.Index) !void { if (!mem.eql(u8, name, "_")) arg_map.putAssumeCapacityNoClobber(name, @intCast(args.items.len)); args.appendAssumeCapacity(arg_mcv); - if (output == .none) result = arg_mcv; - if (is_read) try func.load(arg_mcv, .{ .air_ref = output }, func.typeOf(output)); + if (output.operand == .none) result = arg_mcv; + if (is_read) try func.load(arg_mcv, .{ .air_ref = output.operand }, func.typeOf(output.operand)); } - for (inputs) |input| { - const input_bytes = mem.sliceAsBytes(func.air.extra.items[extra_i..]); - const constraint = mem.sliceTo(input_bytes, 0); - const name = mem.sliceTo(input_bytes[constraint.len + 1 ..], 0); - // This equation accounts for the fact that even if we have exactly 4 bytes - // for the string, we still use the next u32 for the null terminator. - extra_i += (constraint.len + name.len + (2 + 3)) / 4; + it = unwrapped_asm.iterateInputs(); + while (it.next()) |input| { + const constraint = input.constraint; + const name = input.name; - const ty = func.typeOf(input); - const input_mcv = try func.resolveInst(input); + const ty = func.typeOf(input.operand); + const input_mcv = try func.resolveInst(input.operand); const arg_mcv: MCValue = if (mem.eql(u8, constraint, "X")) input_mcv else if (mem.startsWith(u8, constraint, "{") and mem.endsWith(u8, constraint, "}")) arg: { @@ -6171,7 +6150,7 @@ fn airAsm(func: *Func, inst: Air.Inst.Index) !void { const zcu = func.pt.zcu; const ip = &zcu.intern_pool; - const aggregate = ip.indexToKey(extra.data.clobbers).aggregate; + const aggregate = ip.indexToKey(unwrapped_asm.clobbers).aggregate; const struct_type: Type = .fromInterned(aggregate.ty); switch (aggregate.storage) { .elems => |elems| for (elems, 0..) |elem, i| { @@ -6231,7 +6210,7 @@ fn airAsm(func: *Func, inst: Air.Inst.Index) !void { labels.deinit(func.gpa); } - const asm_source = std.mem.sliceAsBytes(func.air.extra.items[extra_i..])[0..extra.data.source_len]; + const asm_source = unwrapped_asm.source; var line_it = mem.tokenizeAny(u8, asm_source, "\n\r;"); next_line: while (line_it.next()) |line| { var mnem_it = mem.tokenizeAny(u8, line, " \t"); @@ -6499,19 +6478,14 @@ fn airAsm(func: *Func, inst: Air.Inst.Index) !void { while (label_it.next()) |label| if (label.value_ptr.pending_relocs.items.len > 0) return func.fail("undefined label: '{s}'", .{label.key_ptr.*}); - for (outputs, args.items[0..outputs.len]) |output, arg_mcv| { - const extra_bytes = mem.sliceAsBytes(func.air.extra.items[outputs_extra_i..]); - const constraint = - mem.sliceTo(mem.sliceAsBytes(func.air.extra.items[outputs_extra_i..]), 0); - const name = mem.sliceTo(extra_bytes[constraint.len + 1 ..], 0); - // This equation accounts for the fact that even if we have exactly 4 bytes - // for the string, we still use the next u32 for the null terminator. - outputs_extra_i += (constraint.len + name.len + (2 + 3)) / 4; + it = unwrapped_asm.iterateOutputs(); + while (it.next()) |output| { + const constraint = output.constraint; - if (output == .none) continue; - if (arg_mcv != .register) continue; + if (output.operand == .none) continue; + if (args.items[output.index] != .register) continue; if (constraint.len == 2 and std.ascii.isDigit(constraint[1])) continue; - try func.store(.{ .air_ref = output }, arg_mcv, func.typeOf(output)); + try func.store(.{ .air_ref = output.operand }, args.items[output.index], func.typeOf(output.operand)); } simple: { diff --git a/src/codegen/sparc64/CodeGen.zig b/src/codegen/sparc64/CodeGen.zig index b3416dcedfd7a3f0b9f1c9cb5a80e5e26f387d69..a246df12c59f3277001ba28fd671b6c42fe769e7 100644 --- a/src/codegen/sparc64/CodeGen.zig +++ b/src/codegen/sparc64/CodeGen.zig @@ -877,15 +877,10 @@ fn airArrayToSlice(self: *Self, inst: Air.Inst.Index) !void { } fn airAsm(self: *Self, inst: Air.Inst.Index) !void { - const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl; - const extra = self.air.extraData(Air.Asm, ty_pl.payload); - const is_volatile = extra.data.flags.is_volatile; - const outputs_len = extra.data.flags.outputs_len; - var extra_i: usize = extra.end; - const outputs: []const Air.Inst.Ref = @ptrCast(self.air.extra.items[extra_i .. extra_i + outputs_len]); - extra_i += outputs.len; - const inputs: []const Air.Inst.Ref = @ptrCast(self.air.extra.items[extra_i .. extra_i + extra.data.inputs_len]); - extra_i += inputs.len; + const unwrapped_asm = self.air.unwrapAsm(inst); + const is_volatile = unwrapped_asm.is_volatile; + const outputs = unwrapped_asm.outputs; + const inputs = unwrapped_asm.inputs; const dead = !is_volatile and self.liveness.isUnused(inst); const result: MCValue = if (dead) .dead else result: { @@ -893,27 +888,18 @@ fn airAsm(self: *Self, inst: Air.Inst.Index) !void { return self.fail("TODO implement codegen for asm with more than 1 output", .{}); } - const output_constraint: ?[]const u8 = for (outputs) |output| { - if (output != .none) { + var it = unwrapped_asm.iterateOutputs(); + const output_constraint: ?[]const u8 = while (it.next()) |output| { + if (output.operand != .none) { return self.fail("TODO implement codegen for non-expr asm", .{}); } - const extra_bytes = std.mem.sliceAsBytes(self.air.extra.items[extra_i..]); - const constraint = std.mem.sliceTo(std.mem.sliceAsBytes(self.air.extra.items[extra_i..]), 0); - const name = std.mem.sliceTo(extra_bytes[constraint.len + 1 ..], 0); - // This equation accounts for the fact that even if we have exactly 4 bytes - // for the string, we still use the next u32 for the null terminator. - extra_i += (constraint.len + name.len + (2 + 3)) / 4; - break constraint; + break output.constraint; } else null; - for (inputs) |input| { - const input_bytes = std.mem.sliceAsBytes(self.air.extra.items[extra_i..]); - const constraint = std.mem.sliceTo(input_bytes, 0); - const name = std.mem.sliceTo(input_bytes[constraint.len + 1 ..], 0); - // This equation accounts for the fact that even if we have exactly 4 bytes - // for the string, we still use the next u32 for the null terminator. - extra_i += (constraint.len + name.len + (2 + 3)) / 4; + it = unwrapped_asm.iterateInputs(); + while (it.next()) |input| { + const constraint = input.constraint; if (constraint.len < 3 or constraint[0] != '{' or constraint[constraint.len - 1] != '}') { return self.fail("unrecognized asm input constraint: '{s}'", .{constraint}); @@ -922,15 +908,15 @@ fn airAsm(self: *Self, inst: Air.Inst.Index) !void { const reg = parseRegName(reg_name) orelse return self.fail("unrecognized register: '{s}'", .{reg_name}); - const arg_mcv = try self.resolveInst(input); + const arg_mcv = try self.resolveInst(input.operand); try self.register_manager.getReg(reg, null); - try self.genSetReg(self.typeOf(input), reg, arg_mcv); + try self.genSetReg(self.typeOf(input.operand), reg, arg_mcv); } // TODO honor the clobbers - _ = extra.data.clobbers; + _ = unwrapped_asm.clobbers; - const asm_source = std.mem.sliceAsBytes(self.air.extra.items[extra_i..])[0..extra.data.source_len]; + const asm_source = unwrapped_asm.source; if (mem.eql(u8, asm_source, "ta 0x6d")) { _ = try self.addInst(.{ @@ -1109,9 +1095,8 @@ fn airBitReverse(self: *Self, inst: Air.Inst.Index) !void { } fn airBlock(self: *Self, inst: Air.Inst.Index) !void { - const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl; - const extra = self.air.extraData(Air.Block, ty_pl.payload); - try self.lowerBlock(inst, @ptrCast(self.air.extra.items[extra.end..][0..extra.data.body_len])); + const block = self.air.unwrapBlock(inst); + try self.lowerBlock(inst, block.body); } fn lowerBlock(self: *Self, inst: Air.Inst.Index, body: []const Air.Inst.Index) !void { @@ -1276,11 +1261,9 @@ fn airByteSwap(self: *Self, inst: Air.Inst.Index) !void { fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallModifier) !void { if (modifier == .always_tail) return self.fail("TODO implement tail calls for {}", .{self.target.cpu.arch}); - const pl_op = self.air.instructions.items(.data)[@intFromEnum(inst)].pl_op; - const callee = pl_op.operand; - const extra = self.air.extraData(Air.Call, pl_op.payload); - const args: []const Air.Inst.Ref = @ptrCast(self.air.extra.items[extra.end .. extra.end + extra.data.args_len]); - const ty = self.typeOf(callee); + const call = self.air.unwrapCall(inst); + const args = call.args; + const ty = self.typeOf(call.callee); const pt = self.pt; const zcu = pt.zcu; const ip = &zcu.intern_pool; @@ -1327,7 +1310,7 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallModifier // Due to incremental compilation, how function calls are generated depends // on linking. - if (try self.air.value(callee, pt)) |func_value| switch (ip.indexToKey(func_value.toIntern())) { + if (try self.air.value(call.callee, pt)) |func_value| switch (ip.indexToKey(func_value.toIntern())) { .func => { return self.fail("TODO implement calling functions", .{}); }, @@ -1339,7 +1322,7 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallModifier }, } else { assert(ty.zigTypeTag(zcu) == .pointer); - const mcv = try self.resolveInst(callee); + const mcv = try self.resolveInst(call.callee); try self.genSetReg(ty, .o7, mcv); _ = try self.addInst(.{ @@ -1365,13 +1348,13 @@ fn airCall(self: *Self, inst: Air.Inst.Index, modifier: std.builtin.CallModifier if (args.len + 1 <= Air.Liveness.bpi - 1) { var buf = [1]Air.Inst.Ref{.none} ** (Air.Liveness.bpi - 1); - buf[0] = callee; + buf[0] = call.callee; @memcpy(buf[1..][0..args.len], args); return self.finishAir(inst, result, buf); } var bt = try self.iterateBigTomb(inst, 1 + args.len); - bt.feed(callee); + bt.feed(call.callee); for (args) |arg| { bt.feed(arg); } @@ -1451,9 +1434,7 @@ fn airCmpLtErrorsLen(self: *Self, inst: Air.Inst.Index) !void { } fn airCmpxchg(self: *Self, inst: Air.Inst.Index) !void { - const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl; - const extra = self.air.extraData(Air.Block, ty_pl.payload); - _ = extra; + _ = inst; return self.fail("TODO implement airCmpxchg for {}", .{ self.target.cpu.arch, @@ -1461,11 +1442,10 @@ fn airCmpxchg(self: *Self, inst: Air.Inst.Index) !void { } fn airCondBr(self: *Self, inst: Air.Inst.Index) !void { - const pl_op = self.air.instructions.items(.data)[@intFromEnum(inst)].pl_op; - const condition = try self.resolveInst(pl_op.operand); - const extra = self.air.extraData(Air.CondBr, pl_op.payload); - const then_body: []const Air.Inst.Index = @ptrCast(self.air.extra.items[extra.end..][0..extra.data.then_body_len]); - const else_body: []const Air.Inst.Index = @ptrCast(self.air.extra.items[extra.end + then_body.len ..][0..extra.data.else_body_len]); + const cond_br = self.air.unwrapCondBr(inst); + const condition = try self.resolveInst(cond_br.condition); + const then_body = cond_br.then_body; + const else_body = cond_br.else_body; const liveness_condbr = self.liveness.getCondBr(inst); // Here we emit a branch to the false section. @@ -1475,7 +1455,7 @@ fn airCondBr(self: *Self, inst: Air.Inst.Index) !void { // that death now instead of later as this has an effect on // whether it needs to be spilled in the branches if (self.liveness.operandDies(inst, 0)) { - if (pl_op.operand.toIndex()) |op_index| { + if (cond_br.condition.toIndex()) |op_index| { self.processDeath(op_index); } } @@ -1613,10 +1593,9 @@ fn airCtz(self: *Self, inst: Air.Inst.Index) !void { } fn airDbgInlineBlock(self: *Self, inst: Air.Inst.Index) !void { - const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl; - const extra = self.air.extraData(Air.DbgInlineBlock, ty_pl.payload); + const block = self.air.unwrapDbgBlock(inst); // TODO emit debug info for function change - try self.lowerBlock(inst, @ptrCast(self.air.extra.items[extra.end..][0..extra.data.body_len])); + try self.lowerBlock(inst, block.body); } fn airDbgStmt(self: *Self, inst: Air.Inst.Index) !void { @@ -1780,12 +1759,10 @@ fn airLoad(self: *Self, inst: Air.Inst.Index) !void { fn airLoop(self: *Self, inst: Air.Inst.Index) !void { // A loop is a setup to be able to jump back to the beginning. - const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl; - const loop = self.air.extraData(Air.Block, ty_pl.payload); - const body: []const Air.Inst.Index = @ptrCast(self.air.extra.items[loop.end .. loop.end + loop.data.body_len]); + const block = self.air.unwrapBlock(inst); const start: u32 = @intCast(self.mir_instructions.len); - try self.genBody(body); + try self.genBody(block.body); try self.jump(start); return self.finishAirBookkeeping(); @@ -2606,12 +2583,11 @@ fn airTrunc(self: *Self, inst: Air.Inst.Index) !void { } fn airTry(self: *Self, inst: Air.Inst.Index) !void { - const pl_op = self.air.instructions.items(.data)[@intFromEnum(inst)].pl_op; - const extra = self.air.extraData(Air.Try, pl_op.payload); - const body: []const Air.Inst.Index = @ptrCast(self.air.extra.items[extra.end..][0..extra.data.body_len]); + const unwrapped_try = self.air.unwrapTry(inst); + const body = unwrapped_try.else_body; const result: MCValue = result: { - const error_union_ty = self.typeOf(pl_op.operand); - const error_union = try self.resolveInst(pl_op.operand); + const error_union_ty = self.air.typeOf(unwrapped_try.error_union, &self.pt.zcu.intern_pool); + const error_union = try self.resolveInst(unwrapped_try.error_union); const is_err_result = try self.isErr(error_union_ty, error_union); const reloc = try self.condBr(is_err_result); @@ -2620,7 +2596,7 @@ fn airTry(self: *Self, inst: Air.Inst.Index) !void { try self.performReloc(reloc); break :result try self.errUnionPayload(error_union, error_union_ty); }; - return self.finishAir(inst, result, .{ pl_op.operand, .none, .none }); + return self.finishAir(inst, result, .{ unwrapped_try.error_union, .none, .none }); } fn airUnaryMath(self: *Self, inst: Air.Inst.Index) !void { diff --git a/src/codegen/spirv/CodeGen.zig b/src/codegen/spirv/CodeGen.zig index d4227ddff6acb6acd3fa72bdfd22f7bacba07d64..f7212b6e1b1f449368872d27aa3688f12b891aa9 100644 --- a/src/codegen/spirv/CodeGen.zig +++ b/src/codegen/spirv/CodeGen.zig @@ -5002,9 +5002,8 @@ fn genStructuredBody( } fn airBlock(cg: *CodeGen, inst: Air.Inst.Index) !?Id { - const inst_datas = cg.air.instructions.items(.data); - const extra = cg.air.extraData(Air.Block, inst_datas[@intFromEnum(inst)].ty_pl.payload); - return cg.lowerBlock(inst, @ptrCast(cg.air.extra.items[extra.end..][0..extra.data.body_len])); + const block = cg.air.unwrapBlock(inst); + return cg.lowerBlock(inst, block.body); } fn lowerBlock(cg: *CodeGen, inst: Air.Inst.Index, body: []const Air.Inst.Index) !?Id { @@ -5188,11 +5187,10 @@ fn airBr(cg: *CodeGen, inst: Air.Inst.Index) !void { fn airCondBr(cg: *CodeGen, inst: Air.Inst.Index) !void { const gpa = cg.module.gpa; - const pl_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].pl_op; - const cond_br = cg.air.extraData(Air.CondBr, pl_op.payload); - const then_body: []const Air.Inst.Index = @ptrCast(cg.air.extra.items[cond_br.end..][0..cond_br.data.then_body_len]); - const else_body: []const Air.Inst.Index = @ptrCast(cg.air.extra.items[cond_br.end + then_body.len ..][0..cond_br.data.else_body_len]); - const condition_id = try cg.resolve(pl_op.operand); + const cond_br = cg.air.unwrapCondBr(inst); + const then_body = cond_br.then_body; + const else_body = cond_br.else_body; + const condition_id = try cg.resolve(cond_br.condition); const then_label = cg.module.allocId(); const else_label = cg.module.allocId(); @@ -5251,9 +5249,7 @@ fn airCondBr(cg: *CodeGen, inst: Air.Inst.Index) !void { fn airLoop(cg: *CodeGen, inst: Air.Inst.Index) !void { const gpa = cg.module.gpa; - const ty_pl = cg.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl; - const loop = cg.air.extraData(Air.Block, ty_pl.payload); - const body: []const Air.Inst.Index = @ptrCast(cg.air.extra.items[loop.end..][0..loop.data.body_len]); + const block = cg.air.unwrapBlock(inst); const body_label = cg.module.allocId(); @@ -5284,7 +5280,7 @@ fn airLoop(cg: *CodeGen, inst: Air.Inst.Index) !void { const next_block = try cg.genStructuredBody(.{ .loop = .{ .merge_label = merge_label, .continue_label = continue_label, - } }, body); + } }, block.body); try cg.structuredBreak(next_block); try cg.beginSpvBlock(continue_label); @@ -5294,7 +5290,7 @@ fn airLoop(cg: *CodeGen, inst: Air.Inst.Index) !void { .unstructured => { try cg.body.emit(gpa, .OpBranch, .{ .target_label = body_label }); try cg.beginSpvBlock(body_label); - try cg.genBody(body); + try cg.genBody(block.body); try cg.body.emit(gpa, .OpBranch, .{ .target_label = body_label }); }, @@ -5375,12 +5371,11 @@ fn airRetLoad(cg: *CodeGen, inst: Air.Inst.Index) !void { fn airTry(cg: *CodeGen, inst: Air.Inst.Index) !?Id { const gpa = cg.module.gpa; const zcu = cg.module.zcu; - const pl_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].pl_op; - const err_union_id = try cg.resolve(pl_op.operand); - const extra = cg.air.extraData(Air.Try, pl_op.payload); - const body: []const Air.Inst.Index = @ptrCast(cg.air.extra.items[extra.end..][0..extra.data.body_len]); + const unwrapped_try = cg.air.unwrapTry(inst); + const body = unwrapped_try.else_body; - const err_union_ty = cg.typeOf(pl_op.operand); + const err_union_id = try cg.resolve(unwrapped_try.error_union); + const err_union_ty = cg.air.typeOf(unwrapped_try.error_union, &zcu.intern_pool); const payload_ty = cg.typeOfIndex(inst); const bool_ty_id = try cg.resolveType(.bool, .direct); @@ -5882,12 +5877,11 @@ fn airDbgStmt(cg: *CodeGen, inst: Air.Inst.Index) !void { fn airDbgInlineBlock(cg: *CodeGen, inst: Air.Inst.Index) !?Id { const zcu = cg.module.zcu; - const inst_datas = cg.air.instructions.items(.data); - const extra = cg.air.extraData(Air.DbgInlineBlock, inst_datas[@intFromEnum(inst)].ty_pl.payload); + const block = cg.air.unwrapDbgBlock(inst); const old_base_line = cg.base_line; defer cg.base_line = old_base_line; - cg.base_line = zcu.navSrcLine(zcu.funcInfo(extra.data.func).owner_nav); - return cg.lowerBlock(inst, @ptrCast(cg.air.extra.items[extra.end..][0..extra.data.body_len])); + cg.base_line = zcu.navSrcLine(zcu.funcInfo(block.func).owner_nav); + return cg.lowerBlock(inst, block.body); } fn airDbgVar(cg: *CodeGen, inst: Air.Inst.Index) !void { @@ -5900,52 +5894,34 @@ fn airDbgVar(cg: *CodeGen, inst: Air.Inst.Index) !void { fn airAssembly(cg: *CodeGen, inst: Air.Inst.Index) !?Id { const gpa = cg.module.gpa; const zcu = cg.module.zcu; - const ty_pl = cg.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl; - const extra = cg.air.extraData(Air.Asm, ty_pl.payload); + const unwrapped_asm = cg.air.unwrapAsm(inst); - const is_volatile = extra.data.flags.is_volatile; - const outputs_len = extra.data.flags.outputs_len; + const is_volatile = unwrapped_asm.is_volatile; + const outputs_len = unwrapped_asm.outputs.len; if (!is_volatile and cg.liveness.isUnused(inst)) return null; - var extra_i: usize = extra.end; - const outputs: []const Air.Inst.Ref = @ptrCast(cg.air.extra.items[extra_i..][0..outputs_len]); - extra_i += outputs.len; - const inputs: []const Air.Inst.Ref = @ptrCast(cg.air.extra.items[extra_i..][0..extra.data.inputs_len]); - extra_i += inputs.len; - - if (outputs.len > 1) { + if (outputs_len > 1) { return cg.todo("implement inline asm with more than 1 output", .{}); } var ass: Assembler = .{ .cg = cg }; defer ass.deinit(); - var output_extra_i = extra_i; - for (outputs) |output| { - if (output != .none) { + var it = unwrapped_asm.iterateOutputs(); + while (it.next()) |out| { + if (out.operand != .none) { return cg.todo("implement inline asm with non-returned output", .{}); } - const extra_bytes = std.mem.sliceAsBytes(cg.air.extra.items[extra_i..]); - const constraint = std.mem.sliceTo(std.mem.sliceAsBytes(cg.air.extra.items[extra_i..]), 0); - const name = std.mem.sliceTo(extra_bytes[constraint.len + 1 ..], 0); - extra_i += (constraint.len + name.len + (2 + 3)) / 4; - // TODO: Record output and use it somewhere. } - for (inputs) |input| { - const extra_bytes = std.mem.sliceAsBytes(cg.air.extra.items[extra_i..]); - const constraint = std.mem.sliceTo(extra_bytes, 0); - const name = std.mem.sliceTo(extra_bytes[constraint.len + 1 ..], 0); - // This equation accounts for the fact that even if we have exactly 4 bytes - // for the string, we still use the next u32 for the null terminator. - extra_i += (constraint.len + name.len + (2 + 3)) / 4; + it = unwrapped_asm.iterateInputs(); + while (it.next()) |in| { + const input_ty = cg.typeOf(in.operand); - const input_ty = cg.typeOf(input); - - if (std.mem.eql(u8, constraint, "c")) { + if (std.mem.eql(u8, in.constraint, "c")) { // constant - const val = (try cg.air.value(input, cg.pt)) orelse { + const val = (try cg.air.value(in.operand, cg.pt)) orelse { return cg.fail("assembly inputs with 'c' constraint have to be compile-time known", .{}); }; @@ -5971,37 +5947,36 @@ fn airAssembly(cg: *CodeGen, inst: Air.Inst.Index) !?Id { .undef => return cg.fail("assembly input with 'c' constraint cannot be undefined", .{}), - .int => try ass.value_map.put(gpa, name, .{ .constant = @intCast(val.toUnsignedInt(zcu)) }), - .enum_literal => |str| try ass.value_map.put(gpa, name, .{ .string = str.toSlice(ip) }), + .int => try ass.value_map.put(gpa, in.name, .{ .constant = @intCast(val.toUnsignedInt(zcu)) }), + .enum_literal => |str| try ass.value_map.put(gpa, in.name, .{ .string = str.toSlice(ip) }), else => unreachable, // TODO } - } else if (std.mem.eql(u8, constraint, "t")) { + } else if (std.mem.eql(u8, in.constraint, "t")) { // type if (input_ty.zigTypeTag(zcu) == .type) { // This assembly input is a type instead of a value. // That's fine for now, just make sure to resolve it as such. - const val = (try cg.air.value(input, cg.pt)).?; + const val = (try cg.air.value(in.operand, cg.pt)).?; const ty_id = try cg.resolveType(val.toType(), .direct); - try ass.value_map.put(gpa, name, .{ .ty = ty_id }); + try ass.value_map.put(gpa, in.name, .{ .ty = ty_id }); } else { const ty_id = try cg.resolveType(input_ty, .direct); - try ass.value_map.put(gpa, name, .{ .ty = ty_id }); + try ass.value_map.put(gpa, in.name, .{ .ty = ty_id }); } } else { if (input_ty.zigTypeTag(zcu) == .type) { return cg.fail("use the 't' constraint to supply types to SPIR-V inline assembly", .{}); } - const val_id = try cg.resolve(input); - try ass.value_map.put(gpa, name, .{ .value = val_id }); + const val_id = try cg.resolve(in.operand); + try ass.value_map.put(gpa, in.name, .{ .value = val_id }); } } - // TODO: do something with clobbers - _ = extra.data.clobbers; + _ = unwrapped_asm.clobbers; - const asm_source = std.mem.sliceAsBytes(cg.air.extra.items[extra_i..])[0..extra.data.source_len]; + const asm_source = unwrapped_asm.source; ass.assemble(asm_source) catch |err| switch (err) { error.AssembleFail => { @@ -6033,26 +6008,20 @@ fn airAssembly(cg: *CodeGen, inst: Air.Inst.Index) !?Id { else => |others| return others, }; - for (outputs) |output| { - _ = output; - const extra_bytes = std.mem.sliceAsBytes(cg.air.extra.items[output_extra_i..]); - const constraint = std.mem.sliceTo(std.mem.sliceAsBytes(cg.air.extra.items[output_extra_i..]), 0); - const name = std.mem.sliceTo(extra_bytes[constraint.len + 1 ..], 0); - output_extra_i += (constraint.len + name.len + (2 + 3)) / 4; - - const result = ass.value_map.get(name) orelse return { - return cg.fail("invalid asm output '{s}'", .{name}); + it = unwrapped_asm.iterateOutputs(); + while (it.next()) |out| { + const result = ass.value_map.get(out.name) orelse return { + return cg.fail("invalid asm output '{s}'", .{out.name}); }; - switch (result) { .just_declared, .unresolved_forward_reference => unreachable, .ty => return cg.fail("cannot return spir-v type as value from assembly", .{}), .value => |ref| return ref, .constant, .string => return cg.fail("cannot return constant from assembly", .{}), } - // TODO: Multiple results // TODO: Check that the output type from assembly is the same as the type actually expected by Zig. + } return null; @@ -6063,10 +6032,9 @@ fn airCall(cg: *CodeGen, inst: Air.Inst.Index, modifier: std.builtin.CallModifie const gpa = cg.module.gpa; const zcu = cg.module.zcu; - const pl_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].pl_op; - const extra = cg.air.extraData(Air.Call, pl_op.payload); - const args: []const Air.Inst.Ref = @ptrCast(cg.air.extra.items[extra.end..][0..extra.data.args_len]); - const callee_ty = cg.typeOf(pl_op.operand); + const air_call = cg.air.unwrapCall(inst); + const args = air_call.args; + const callee_ty = cg.typeOf(air_call.callee); const zig_fn_ty = switch (callee_ty.zigTypeTag(zcu)) { .@"fn" => callee_ty, .pointer => return cg.fail("cannot call function pointers", .{}), @@ -6077,7 +6045,7 @@ fn airCall(cg: *CodeGen, inst: Air.Inst.Index, modifier: std.builtin.CallModifie const result_type_id = try cg.resolveFnReturnType(.fromInterned(return_type)); const result_id = cg.module.allocId(); - const callee_id = try cg.resolve(pl_op.operand); + const callee_id = try cg.resolve(air_call.callee); comptime assert(zig_call_abi_ver == 3); diff --git a/src/codegen/wasm/CodeGen.zig b/src/codegen/wasm/CodeGen.zig index b6f8145875b6abe0f63dee3eecf0d791295ba3fc..90de3461cacbfdb90b07a3f2898598ed27be9ac7 100644 --- a/src/codegen/wasm/CodeGen.zig +++ b/src/codegen/wasm/CodeGen.zig @@ -2137,10 +2137,9 @@ fn airRetLoad(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void { fn airCall(cg: *CodeGen, inst: Air.Inst.Index, modifier: std.builtin.CallModifier) InnerError!void { if (modifier == .always_tail) return cg.fail("TODO implement tail calls for wasm", .{}); - const pl_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].pl_op; - const extra = cg.air.extraData(Air.Call, pl_op.payload); - const args: []const Air.Inst.Ref = @ptrCast(cg.air.extra.items[extra.end..][0..extra.data.args_len]); - const ty = cg.typeOf(pl_op.operand); + const call = cg.air.unwrapCall(inst); + const args = call.args; + const ty = cg.typeOf(call.callee); const pt = cg.pt; const zcu = pt.zcu; @@ -2155,7 +2154,7 @@ fn airCall(cg: *CodeGen, inst: Air.Inst.Index, modifier: std.builtin.CallModifie const first_param_sret = firstParamSRet(fn_info.cc, Type.fromInterned(fn_info.return_type), zcu, cg.target); const callee: ?InternPool.Nav.Index = blk: { - const func_val = (try cg.air.value(pl_op.operand, pt)) orelse break :blk null; + const func_val = (try cg.air.value(call.callee, pt)) orelse break :blk null; switch (ip.indexToKey(func_val.toIntern())) { inline .func, .@"extern" => |x| break :blk x.owner_nav, @@ -2189,7 +2188,7 @@ fn airCall(cg: *CodeGen, inst: Air.Inst.Index, modifier: std.builtin.CallModifie // in this case we call a function pointer // so load its value onto the stack assert(ty.zigTypeTag(zcu) == .pointer); - const operand = try cg.resolveInst(pl_op.operand); + const operand = try cg.resolveInst(call.callee); try cg.emitWValue(operand); try cg.mir_func_tys.put(cg.gpa, fn_ty.toIntern(), {}); @@ -2233,7 +2232,7 @@ fn airCall(cg: *CodeGen, inst: Air.Inst.Index, modifier: std.builtin.CallModifie }; var bt = try cg.iterateBigTomb(inst, 1 + args.len); - bt.feed(pl_op.operand); + bt.feed(call.callee); for (args) |arg| bt.feed(arg); return bt.finishAir(result_value); } @@ -3335,9 +3334,8 @@ fn emitUndefined(cg: *CodeGen, ty: Type) InnerError!WValue { } fn airBlock(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void { - const ty_pl = cg.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl; - const extra = cg.air.extraData(Air.Block, ty_pl.payload); - try cg.lowerBlock(inst, ty_pl.ty.toType(), @ptrCast(cg.air.extra.items[extra.end..][0..extra.data.body_len])); + const block = cg.air.unwrapBlock(inst); + try cg.lowerBlock(inst, block.ty, block.body); } fn lowerBlock(cg: *CodeGen, inst: Air.Inst.Index, block_ty: Type, body: []const Air.Inst.Index) InnerError!void { @@ -3381,9 +3379,7 @@ fn endBlock(cg: *CodeGen) !void { } fn airLoop(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void { - const ty_pl = cg.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl; - const loop = cg.air.extraData(Air.Block, ty_pl.payload); - const body: []const Air.Inst.Index = @ptrCast(cg.air.extra.items[loop.end..][0..loop.data.body_len]); + const block = cg.air.unwrapBlock(inst); // result type of loop is always 'noreturn', meaning we can always // emit the wasm type 'block_empty'. @@ -3392,18 +3388,17 @@ fn airLoop(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void { try cg.loops.putNoClobber(cg.gpa, inst, cg.block_depth); defer assert(cg.loops.remove(inst)); - try cg.genBody(body); + try cg.genBody(block.body); try cg.endBlock(); return cg.finishAir(inst, .none, &.{}); } fn airCondBr(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void { - const pl_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].pl_op; - const condition = try cg.resolveInst(pl_op.operand); - const extra = cg.air.extraData(Air.CondBr, pl_op.payload); - const then_body: []const Air.Inst.Index = @ptrCast(cg.air.extra.items[extra.end..][0..extra.data.then_body_len]); - const else_body: []const Air.Inst.Index = @ptrCast(cg.air.extra.items[extra.end + then_body.len ..][0..extra.data.else_body_len]); + const cond_br = cg.air.unwrapCondBr(inst); + const condition = try cg.resolveInst(cond_br.condition); + const then_body = cond_br.then_body; + const else_body = cond_br.else_body; const liveness_condbr = cg.liveness.getCondBr(inst); // result type is always noreturn, so use `block_empty` as type. @@ -6423,10 +6418,9 @@ fn airDbgStmt(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void { } fn airDbgInlineBlock(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void { - const ty_pl = cg.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl; - const extra = cg.air.extraData(Air.DbgInlineBlock, ty_pl.payload); + const block = cg.air.unwrapDbgBlock(inst); // TODO - try cg.lowerBlock(inst, ty_pl.ty.toType(), @ptrCast(cg.air.extra.items[extra.end..][0..extra.data.body_len])); + try cg.lowerBlock(inst, block.ty, block.body); } fn airDbgVar( @@ -6441,24 +6435,22 @@ fn airDbgVar( } fn airTry(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void { - const pl_op = cg.air.instructions.items(.data)[@intFromEnum(inst)].pl_op; - const err_union = try cg.resolveInst(pl_op.operand); - const extra = cg.air.extraData(Air.Try, pl_op.payload); - const body: []const Air.Inst.Index = @ptrCast(cg.air.extra.items[extra.end..][0..extra.data.body_len]); - const err_union_ty = cg.typeOf(pl_op.operand); + const unwrapped_try = cg.air.unwrapTry(inst); + const body = unwrapped_try.else_body; + const err_union = try cg.resolveInst(unwrapped_try.error_union); + const err_union_ty = cg.typeOf(unwrapped_try.error_union); const result = try lowerTry(cg, inst, err_union, body, err_union_ty, false); - return cg.finishAir(inst, result, &.{pl_op.operand}); + return cg.finishAir(inst, result, &.{unwrapped_try.error_union}); } fn airTryPtr(cg: *CodeGen, inst: Air.Inst.Index) InnerError!void { const zcu = cg.pt.zcu; - const ty_pl = cg.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl; - const extra = cg.air.extraData(Air.TryPtr, ty_pl.payload); - const err_union_ptr = try cg.resolveInst(extra.data.ptr); - const body: []const Air.Inst.Index = @ptrCast(cg.air.extra.items[extra.end..][0..extra.data.body_len]); - const err_union_ty = cg.typeOf(extra.data.ptr).childType(zcu); + const unwrapped_try = cg.air.unwrapTryPtr(inst); + const err_union_ptr = try cg.resolveInst(unwrapped_try.error_union_ptr); + const body = unwrapped_try.else_body; + const err_union_ty = cg.typeOf(unwrapped_try.error_union_ptr).childType(zcu); const result = try lowerTry(cg, inst, err_union_ptr, body, err_union_ty, true); - return cg.finishAir(inst, result, &.{extra.data.ptr}); + return cg.finishAir(inst, result, &.{unwrapped_try.error_union_ptr}); } fn lowerTry( diff --git a/src/codegen/x86_64/CodeGen.zig b/src/codegen/x86_64/CodeGen.zig index 382c9e9c3cb1ff24aa64abe5572cd12779dcca00..7da423f4299b1d791ed32d39eca8b28ab2874d7f 100644 --- a/src/codegen/x86_64/CodeGen.zig +++ b/src/codegen/x86_64/CodeGen.zig @@ -67348,21 +67348,19 @@ fn genBody(cg: *CodeGen, body: []const Air.Inst.Index) InnerError!void { }, .bitcast => try cg.airBitCast(inst), .block => { - const ty_pl = air_datas[@intFromEnum(inst)].ty_pl; - const block = cg.air.extraData(Air.Block, ty_pl.payload); + const block = cg.air.unwrapBlock(inst); if (!cg.mod.strip) try cg.asmPseudo(.pseudo_dbg_enter_block_none); - try cg.lowerBlock(inst, @ptrCast(cg.air.extra.items[block.end..][0..block.data.body_len])); + try cg.lowerBlock(inst, block.body); if (!cg.mod.strip) try cg.asmPseudo(.pseudo_dbg_leave_block_none); }, .loop => { - const ty_pl = air_datas[@intFromEnum(inst)].ty_pl; - const block = cg.air.extraData(Air.Block, ty_pl.payload); + const block = cg.air.unwrapBlock(inst); try cg.loops.putNoClobber(cg.gpa, inst, .{ .state = try cg.saveState(), .target = @intCast(cg.mir_instructions.len), }); defer assert(cg.loops.remove(inst)); - try cg.genBodyBlock(@ptrCast(cg.air.extra.items[block.end..][0..block.data.body_len])); + try cg.genBodyBlock(block.body); }, .repeat => { const repeat = air_datas[@intFromEnum(inst)].repeat; @@ -89048,17 +89046,16 @@ fn genBody(cg: *CodeGen, body: []const Air.Inst.Index) InnerError!void { try cg.asmOpOnly(.{ ._, .nop }); }, .dbg_inline_block => { - const ty_pl = air_datas[@intFromEnum(inst)].ty_pl; - const dbg_inline_block = cg.air.extraData(Air.DbgInlineBlock, ty_pl.payload); + const dbg_inline_block = cg.air.unwrapDbgBlock(inst); const old_inline_func = cg.inline_func; defer cg.inline_func = old_inline_func; - cg.inline_func = dbg_inline_block.data.func; + cg.inline_func = dbg_inline_block.func; if (!cg.mod.strip) _ = try cg.addInst(.{ .tag = .pseudo, .ops = .pseudo_dbg_enter_inline_func, - .data = .{ .ip_index = dbg_inline_block.data.func }, + .data = .{ .ip_index = dbg_inline_block.func }, }); - try cg.lowerBlock(inst, @ptrCast(cg.air.extra.items[dbg_inline_block.end..][0..dbg_inline_block.data.body_len])); + try cg.lowerBlock(inst, dbg_inline_block.body); if (!cg.mod.strip) _ = try cg.addInst(.{ .tag = .pseudo, .ops = .pseudo_dbg_leave_inline_func, @@ -175916,10 +175913,8 @@ fn genLocalDebugInfo(cg: *CodeGen, air_tag: Air.Inst.Tag, ty: Type, mcv: MCValue fn airCall(self: *CodeGen, inst: Air.Inst.Index, modifier: std.builtin.CallModifier, opts: CopyOptions) !void { if (modifier == .always_tail) return self.fail("TODO implement tail calls for x86_64", .{}); - const pl_op = self.air.instructions.items(.data)[@intFromEnum(inst)].pl_op; - const extra = self.air.extraData(Air.Call, pl_op.payload); - const arg_refs: []const Air.Inst.Ref = - @ptrCast(self.air.extra.items[extra.end..][0..extra.data.args_len]); + const call = self.air.unwrapCall(inst); + const arg_refs = call.args; const ExpectedContents = extern struct { tys: [32][@sizeOf(Type)]u8 align(@alignOf(Type)), @@ -175937,10 +175932,10 @@ fn airCall(self: *CodeGen, inst: Air.Inst.Index, modifier: std.builtin.CallModif defer allocator.free(arg_vals); for (arg_vals, arg_refs) |*arg_val, arg_ref| arg_val.* = .{ .air_ref = arg_ref }; - const ret = try self.genCall(.{ .air = pl_op.operand }, arg_tys, arg_vals, opts); + const ret = try self.genCall(.{ .air = call.callee }, arg_tys, arg_vals, opts); var bt = self.liveness.iterateBigTomb(inst); - try self.feed(&bt, pl_op.operand); + try self.feed(&bt, call.callee); for (arg_refs) |arg_ref| try self.feed(&bt, arg_ref); const result = if (self.liveness.isUnused(inst)) .unreach else ret; @@ -176300,20 +176295,18 @@ fn airRetLoad(self: *CodeGen, inst: Air.Inst.Index) !void { } fn airTry(self: *CodeGen, inst: Air.Inst.Index) !void { - const pl_op = self.air.instructions.items(.data)[@intFromEnum(inst)].pl_op; - const extra = self.air.extraData(Air.Try, pl_op.payload); - const body: []const Air.Inst.Index = @ptrCast(self.air.extra.items[extra.end..][0..extra.data.body_len]); - const operand_ty = self.typeOf(pl_op.operand); - const result = try self.genTry(inst, pl_op.operand, body, operand_ty, false); + const unwrapped_try = self.air.unwrapTry(inst); + const body = unwrapped_try.else_body; + const operand_ty = self.typeOf(unwrapped_try.error_union); + const result = try self.genTry(inst, unwrapped_try.error_union, body, operand_ty, false); return self.finishAir(inst, result, .{ .none, .none, .none }); } fn airTryPtr(self: *CodeGen, inst: Air.Inst.Index) !void { - const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl; - const extra = self.air.extraData(Air.TryPtr, ty_pl.payload); - const body: []const Air.Inst.Index = @ptrCast(self.air.extra.items[extra.end..][0..extra.data.body_len]); - const operand_ty = self.typeOf(extra.data.ptr); - const result = try self.genTry(inst, extra.data.ptr, body, operand_ty, true); + const unwrapped_try = self.air.unwrapTryPtr(inst); + const body = unwrapped_try.else_body; + const operand_ty = self.typeOf(unwrapped_try.error_union_ptr); + const result = try self.genTry(inst, unwrapped_try.error_union_ptr, body, operand_ty, true); return self.finishAir(inst, result, .{ .none, .none, .none }); } @@ -176391,21 +176384,20 @@ fn genCondBrMir(self: *CodeGen, ty: Type, mcv: MCValue) !Mir.Inst.Index { } fn airCondBr(self: *CodeGen, inst: Air.Inst.Index) !void { - const pl_op = self.air.instructions.items(.data)[@intFromEnum(inst)].pl_op; - const cond = try self.resolveInst(pl_op.operand); - const cond_ty = self.typeOf(pl_op.operand); - const extra = self.air.extraData(Air.CondBr, pl_op.payload); - const then_body: []const Air.Inst.Index = - @ptrCast(self.air.extra.items[extra.end..][0..extra.data.then_body_len]); - const else_body: []const Air.Inst.Index = - @ptrCast(self.air.extra.items[extra.end + then_body.len ..][0..extra.data.else_body_len]); + const cond_br = self.air.unwrapCondBr(inst); + const then_body = cond_br.then_body; + const else_body = cond_br.else_body; + + const cond = try self.resolveInst(cond_br.condition); + const cond_ty = self.typeOf(cond_br.condition); + const liveness_cond_br = self.liveness.getCondBr(inst); // If the condition dies here in this condbr instruction, process // that death now instead of later as this has an effect on // whether it needs to be spilled in the branches if (self.liveness.operandDies(inst, 0)) { - if (pl_op.operand.toIndex()) |op_inst| try self.processDeath(op_inst, .{}); + if (cond_br.condition.toIndex()) |op_inst| try self.processDeath(op_inst, .{}); } const state = try self.saveState(); @@ -177124,14 +177116,10 @@ fn airAsm(self: *CodeGen, inst: Air.Inst.Index) !void { @setEvalBranchQuota(1_100); const pt = self.pt; const zcu = pt.zcu; - const ty_pl = self.air.instructions.items(.data)[@intFromEnum(inst)].ty_pl; - const extra = self.air.extraData(Air.Asm, ty_pl.payload); - const outputs_len = extra.data.flags.outputs_len; - var extra_i: usize = extra.end; - const outputs: []const Air.Inst.Ref = @ptrCast(self.air.extra.items[extra_i..][0..outputs_len]); - extra_i += outputs.len; - const inputs: []const Air.Inst.Ref = @ptrCast(self.air.extra.items[extra_i..][0..extra.data.inputs_len]); - extra_i += inputs.len; + const unwrapped_asm = self.air.unwrapAsm(inst); + + const outputs = unwrapped_asm.outputs; + const inputs = unwrapped_asm.inputs; var result: MCValue = .none; var args: std.array_list.Managed(MCValue) = .init(self.gpa); @@ -177146,36 +177134,29 @@ fn airAsm(self: *CodeGen, inst: Air.Inst.Index) !void { try arg_map.ensureTotalCapacity(@intCast(outputs.len + inputs.len)); defer arg_map.deinit(); - var outputs_extra_i = extra_i; - for (outputs) |output| { - const extra_bytes = std.mem.sliceAsBytes(self.air.extra.items[extra_i..]); - const constraint = std.mem.sliceTo(std.mem.sliceAsBytes(self.air.extra.items[extra_i..]), 0); - const name = std.mem.sliceTo(extra_bytes[constraint.len + 1 ..], 0); - // This equation accounts for the fact that even if we have exactly 4 bytes - // for the string, we still use the next u32 for the null terminator. - extra_i += (constraint.len + name.len + (2 + 3)) / 4; - - const maybe_inst = switch (output) { + var it = unwrapped_asm.iterateOutputs(); + while (it.next()) |out| { + const maybe_inst = switch (out.operand) { .none => inst, else => null, }; - const ty = switch (output) { + const ty = switch (out.operand) { .none => self.typeOfIndex(inst), - else => self.typeOf(output).childType(zcu), + else => self.typeOf(out.operand).childType(zcu), }; - const is_read = switch (constraint[0]) { + const is_read = switch (out.constraint[0]) { '=' => false, '+' => read: { - if (output == .none) return self.fail( + if (out.operand == .none) return self.fail( "read-write constraint unsupported for asm result: '{s}'", - .{constraint}, + .{out.constraint}, ); break :read true; }, - else => return self.fail("invalid constraint: '{s}'", .{constraint}), + else => return self.fail("invalid constraint: '{s}'", .{out.constraint}), }; - const is_early_clobber = constraint[1] == '&'; - const rest = constraint[@as(usize, 1) + @intFromBool(is_early_clobber) ..]; + const is_early_clobber = out.constraint[1] == '&'; + const rest = out.constraint[@as(usize, 1) + @intFromBool(is_early_clobber) ..]; const arg_mcv: MCValue = arg_mcv: { const arg_maybe_reg: ?Register = if (std.mem.eql(u8, rest, "r") or std.mem.eql(u8, rest, "f") or std.mem.eql(u8, rest, "x")) @@ -177189,30 +177170,30 @@ fn airAsm(self: *CodeGen, inst: Air.Inst.Index) !void { @intCast(ty.abiSize(zcu)), ) else if (std.mem.eql(u8, rest, "m")) - if (output != .none) null else return self.fail( + if (out.operand != .none) null else return self.fail( "memory constraint unsupported for asm result: '{s}'", - .{constraint}, + .{out.constraint}, ) else if (std.mem.eql(u8, rest, "g") or std.mem.eql(u8, rest, "rm") or std.mem.eql(u8, rest, "mr") or std.mem.eql(u8, rest, "r,m") or std.mem.eql(u8, rest, "m,r")) self.register_manager.tryAllocReg(maybe_inst, abi.RegisterClass.gp) orelse - if (output != .none) + if (out.operand != .none) null else return self.fail("ran out of registers lowering inline asm", .{}) else if (std.mem.startsWith(u8, rest, "{") and std.mem.endsWith(u8, rest, "}")) parseRegName(rest["{".len .. rest.len - "}".len]) orelse - return self.fail("invalid register constraint: '{s}'", .{constraint}) + return self.fail("invalid register constraint: '{s}'", .{out.constraint}) else if (rest.len == 1 and std.ascii.isDigit(rest[0])) { const index = std.fmt.charToDigit(rest[0], 10) catch unreachable; if (index >= args.items.len) return self.fail("constraint out of bounds: '{s}'", .{ - constraint, + out.constraint, }); break :arg_mcv args.items[index]; - } else return self.fail("invalid constraint: '{s}'", .{constraint}); + } else return self.fail("invalid constraint: '{s}'", .{out.constraint}); break :arg_mcv if (arg_maybe_reg) |reg| .{ .register = reg } else arg: { - const ptr_mcv = try self.resolveInst(output); + const ptr_mcv = try self.resolveInst(out.operand); switch (ptr_mcv) { .immediate => |addr| if (std.math.cast(i32, @as(i64, @bitCast(addr)))) |_| break :arg ptr_mcv.deref(), @@ -177223,30 +177204,24 @@ fn airAsm(self: *CodeGen, inst: Air.Inst.Index) !void { }; }; if (arg_mcv.getReg()) |reg| if (RegisterManager.indexOfRegIntoTracked(reg)) |tracked_index| { - try self.register_manager.getRegIndex(tracked_index, if (output == .none) inst else null); + try self.register_manager.getRegIndex(tracked_index, if (out.operand == .none) inst else null); _ = self.register_manager.lockRegIndexAssumeUnused(tracked_index); }; - if (!std.mem.eql(u8, name, "_")) - arg_map.putAssumeCapacityNoClobber(name, @intCast(args.items.len)); + if (!std.mem.eql(u8, out.name, "_")) + arg_map.putAssumeCapacityNoClobber(out.name, @intCast(args.items.len)); args.appendAssumeCapacity(arg_mcv); - if (output == .none) result = arg_mcv; - if (is_read) try self.load(arg_mcv, self.typeOf(output), .{ .air_ref = output }); + if (out.operand == .none) result = arg_mcv; + if (is_read) try self.load(arg_mcv, self.typeOf(out.operand), .{ .air_ref = out.operand }); } - for (inputs) |input| { - const input_bytes = std.mem.sliceAsBytes(self.air.extra.items[extra_i..]); - const constraint = std.mem.sliceTo(input_bytes, 0); - const name = std.mem.sliceTo(input_bytes[constraint.len + 1 ..], 0); - // This equation accounts for the fact that even if we have exactly 4 bytes - // for the string, we still use the next u32 for the null terminator. - extra_i += (constraint.len + name.len + (2 + 3)) / 4; - - const ty = self.typeOf(input); - const input_mcv = try self.resolveInst(input); - const arg_mcv: MCValue = if (std.mem.eql(u8, constraint, "r") or - std.mem.eql(u8, constraint, "f") or std.mem.eql(u8, constraint, "x")) + it = unwrapped_asm.iterateInputs(); + while (it.next()) |in| { + const ty = self.typeOf(in.operand); + const input_mcv = try self.resolveInst(in.operand); + const arg_mcv: MCValue = if (std.mem.eql(u8, in.constraint, "r") or + std.mem.eql(u8, in.constraint, "f") or std.mem.eql(u8, in.constraint, "x")) arg: { - const rc = switch (constraint[0]) { + const rc = switch (in.constraint[0]) { 'r' => abi.RegisterClass.gp, 'f' => abi.RegisterClass.x87, 'x' => abi.RegisterClass.sse, @@ -177258,14 +177233,14 @@ fn airAsm(self: *CodeGen, inst: Air.Inst.Index) !void { const reg = try self.register_manager.allocReg(null, rc); try self.genSetReg(reg, ty, input_mcv, .{}); break :arg .{ .register = registerAlias(reg, @intCast(ty.abiSize(zcu))) }; - } else if (std.mem.eql(u8, constraint, "i") or std.mem.eql(u8, constraint, "n")) + } else if (std.mem.eql(u8, in.constraint, "i") or std.mem.eql(u8, in.constraint, "n")) switch (input_mcv) { .immediate => |imm| .{ .immediate = imm }, else => return self.fail("immediate operand requires comptime value: '{s}'", .{ - constraint, + in.constraint, }), } - else if (std.mem.eql(u8, constraint, "m")) arg: { + else if (std.mem.eql(u8, in.constraint, "m")) arg: { switch (input_mcv) { .memory => |addr| if (std.math.cast(i32, @as(i64, @bitCast(addr)))) |_| break :arg input_mcv, @@ -177284,9 +177259,9 @@ fn airAsm(self: *CodeGen, inst: Air.Inst.Index) !void { }; try self.genSetReg(addr_reg, .usize, input_mcv.address(), .{}); break :arg .{ .indirect = .{ .reg = addr_reg } }; - } else if (std.mem.eql(u8, constraint, "g") or - std.mem.eql(u8, constraint, "rm") or std.mem.eql(u8, constraint, "mr") or - std.mem.eql(u8, constraint, "r,m") or std.mem.eql(u8, constraint, "m,r")) + } else if (std.mem.eql(u8, in.constraint, "g") or + std.mem.eql(u8, in.constraint, "rm") or std.mem.eql(u8, in.constraint, "mr") or + std.mem.eql(u8, in.constraint, "r,m") or std.mem.eql(u8, in.constraint, "m,r")) arg: { switch (input_mcv) { .register, .indirect, .load_frame => break :arg input_mcv, @@ -177297,30 +177272,30 @@ fn airAsm(self: *CodeGen, inst: Air.Inst.Index) !void { const temp_mcv = try self.allocTempRegOrMem(ty, true); try self.genCopy(ty, temp_mcv, input_mcv, .{}); break :arg temp_mcv; - } else if (std.mem.eql(u8, constraint, "X")) + } else if (std.mem.eql(u8, in.constraint, "X")) input_mcv - else if (std.mem.startsWith(u8, constraint, "{") and std.mem.endsWith(u8, constraint, "}")) arg: { - const reg = parseRegName(constraint["{".len .. constraint.len - "}".len]) orelse - return self.fail("invalid register constraint: '{s}'", .{constraint}); + else if (std.mem.startsWith(u8, in.constraint, "{") and std.mem.endsWith(u8, in.constraint, "}")) arg: { + const reg = parseRegName(in.constraint["{".len .. in.constraint.len - "}".len]) orelse + return self.fail("invalid register constraint: '{s}'", .{in.constraint}); try self.register_manager.getReg(reg, null); try self.genSetReg(reg, ty, input_mcv, .{}); break :arg .{ .register = reg }; - } else if (constraint.len == 1 and std.ascii.isDigit(constraint[0])) arg: { - const index = std.fmt.charToDigit(constraint[0], 10) catch unreachable; - if (index >= args.items.len) return self.fail("constraint out of bounds: '{s}'", .{constraint}); + } else if (in.constraint.len == 1 and std.ascii.isDigit(in.constraint[0])) arg: { + const index = std.fmt.charToDigit(in.constraint[0], 10) catch unreachable; + if (index >= args.items.len) return self.fail("constraint out of bounds: '{s}'", .{in.constraint}); try self.genCopy(ty, args.items[index], input_mcv, .{}); break :arg args.items[index]; - } else return self.fail("invalid constraint: '{s}'", .{constraint}); + } else return self.fail("invalid constraint: '{s}'", .{in.constraint}); if (arg_mcv.getReg()) |reg| if (RegisterManager.indexOfRegIntoTracked(reg)) |_| { _ = self.register_manager.lockReg(reg); }; - if (!std.mem.eql(u8, name, "_")) - arg_map.putAssumeCapacityNoClobber(name, @intCast(args.items.len)); + if (!std.mem.eql(u8, in.name, "_")) + arg_map.putAssumeCapacityNoClobber(in.name, @intCast(args.items.len)); args.appendAssumeCapacity(arg_mcv); } const ip = &zcu.intern_pool; - const aggregate = ip.indexToKey(extra.data.clobbers).aggregate; + const aggregate = ip.indexToKey(unwrapped_asm.clobbers).aggregate; const struct_type: Type = .fromInterned(aggregate.ty); switch (aggregate.storage) { .elems => |elems| for (elems, 0..) |elem, i| switch (elem) { @@ -177390,7 +177365,7 @@ fn airAsm(self: *CodeGen, inst: Air.Inst.Index) !void { labels.deinit(self.gpa); } - const asm_source = std.mem.sliceAsBytes(self.air.extra.items[extra_i..])[0..extra.data.source_len]; + const asm_source = unwrapped_asm.source; var line_it = std.mem.tokenizeAny(u8, asm_source, "\n\r;"); next_line: while (line_it.next()) |line| { var mnem_it = std.mem.tokenizeAny(u8, line, " \t"); @@ -177821,19 +177796,13 @@ fn airAsm(self: *CodeGen, inst: Air.Inst.Index) !void { while (label_it.next()) |label| if (label.value_ptr.pending_relocs.items.len > 0) return self.fail("undefined label: '{s}'", .{label.key_ptr.*}); - for (outputs, args.items[0..outputs.len]) |output, arg_mcv| { - const extra_bytes = std.mem.sliceAsBytes(self.air.extra.items[outputs_extra_i..]); - const constraint = - std.mem.sliceTo(std.mem.sliceAsBytes(self.air.extra.items[outputs_extra_i..]), 0); - const name = std.mem.sliceTo(extra_bytes[constraint.len + 1 ..], 0); - // This equation accounts for the fact that even if we have exactly 4 bytes - // for the string, we still use the next u32 for the null terminator. - outputs_extra_i += (constraint.len + name.len + (2 + 3)) / 4; - - if (output == .none) continue; + it = unwrapped_asm.iterateOutputs(); + while (it.next()) |out| { + const arg_mcv = args.items[it.current - 1]; + if (out.operand == .none) continue; if (arg_mcv != .register) continue; - if (constraint.len == 2 and std.ascii.isDigit(constraint[1])) continue; - try self.store(self.typeOf(output), .{ .air_ref = output }, arg_mcv, .{}); + if (out.constraint.len == 2 and std.ascii.isDigit(out.constraint[1])) continue; + try self.store(self.typeOf(out.operand), .{ .air_ref = out.operand }, arg_mcv, .{}); } simple: { -- 2.54.0 From 3d33735d73de269278fb87d2c9ae8fb7d83977be Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 6 Feb 2026 00:29:31 -0800 Subject: [PATCH 219/499] zig build: add --fork CLI argument closes #31124 --- lib/compiler/build_runner.zig | 63 ++++++++++--------- src/Package.zig | 37 +++++++++++ src/Package/Fetch.zig | 45 +++++--------- src/Package/Manifest.zig | 46 +++++++++++++- src/main.zig | 113 +++++++++++++++++++++++++++++----- 5 files changed, 228 insertions(+), 76 deletions(-) diff --git a/lib/compiler/build_runner.zig b/lib/compiler/build_runner.zig index 2dd18d4f0dcaa426988c8747fa03c49859830d72..4d6b640e88fd5852b03bfa9eb12fbbeb6c237735 100644 --- a/lib/compiler/build_runner.zig +++ b/lib/compiler/build_runner.zig @@ -1573,33 +1573,6 @@ fn printUsage(b: *std.Build, w: *Writer) !void { \\ -fsys=[name] Enable a system integration \\ -fno-sys=[name] Disable a system integration \\ - \\ Available System Integrations: Enabled: - \\ - ); - if (b.graph.system_library_options.entries.len == 0) { - try w.writeAll(" (none) -\n"); - } else { - for (b.graph.system_library_options.keys(), b.graph.system_library_options.values()) |k, v| { - const status = switch (v) { - .declared_enabled => "yes", - .declared_disabled => "no", - .user_enabled, .user_disabled => unreachable, // already emitted error - }; - try w.print(" {s:<43} {s}\n", .{ k, status }); - } - } - - try w.writeAll( - \\ - \\General Options: - \\ -p, --prefix [path] Where to install files (default: zig-out) - \\ --prefix-lib-dir [path] Where to install libraries - \\ --prefix-exe-dir [path] Where to install executables - \\ --prefix-include-dir [path] Where to install C header files - \\ - \\ --release[=mode] Request release mode, optionally specifying a - \\ preferred optimization mode: fast, safe, small - \\ \\ -fdarling, -fno-darling Integration with system-installed Darling to \\ execute macOS programs on Linux hosts \\ (default: no) @@ -1617,8 +1590,35 @@ fn printUsage(b: *std.Build, w: *Writer) !void { \\ -fwine, -fno-wine Integration with system-installed Wine to execute \\ Windows programs on Linux hosts. (default: no) \\ + \\ Available System Integrations: Enabled: + \\ + ); + if (b.graph.system_library_options.entries.len == 0) { + try w.writeAll(" (none) -\n"); + } else { + for (b.graph.system_library_options.keys(), b.graph.system_library_options.values()) |k, v| { + const status = switch (v) { + .declared_enabled => "yes", + .declared_disabled => "no", + .user_enabled, .user_disabled => unreachable, // already emitted error + }; + try w.print(" {s:<43} {s}\n", .{ k, status }); + } + } + + try w.writeAll( + \\ + \\General Options: \\ -h, --help Print this help and exit \\ -l, --list-steps Print available steps + \\ + \\ -p, --prefix [path] Where to install files (default: zig-out) + \\ --prefix-lib-dir [path] Where to install libraries + \\ --prefix-exe-dir [path] Where to install executables + \\ --prefix-include-dir [path] Where to install C header files + \\ --release[=mode] Request release mode, optionally specifying a + \\ preferred optimization mode: fast, safe, small + \\ \\ --verbose Print commands before executing them \\ --color [auto|off|on] Enable or disable colored error messages \\ --error-style [style] Control how build errors are printed @@ -1641,9 +1641,6 @@ fn printUsage(b: *std.Build, w: *Writer) !void { \\ --skip-oom-steps Instead of failing, skip steps that would exceed --maxrss \\ --test-timeout Limit execution time of unit tests, terminating if exceeded. \\ The timeout must include a unit: ns, us, ms, s, m, h - \\ --fetch[=mode] Fetch dependency tree (optionally choose laziness) and exit - \\ needed (Default) Lazy dependencies are fetched as needed - \\ all Lazy dependencies are always fetched \\ --watch Continuously rebuild when source files are modified \\ --debounce Delay before rebuilding after changed file detected \\ --webui[=ip] Enable the web interface on the given IP address @@ -1656,6 +1653,12 @@ fn printUsage(b: *std.Build, w: *Writer) !void { \\ -fincremental Enable incremental compilation \\ -fno-incremental Disable incremental compilation \\ + \\Package Management Options: + \\ --fetch[=mode] Fetch dependency tree (optionally choose laziness) and exit + \\ needed (Default) Lazy dependencies are fetched as needed + \\ all Lazy dependencies are always fetched + \\ --fork=[path] Override one or more packages from dependency tree + \\ \\Advanced Options: \\ -freference-trace[=num] How many lines of reference trace should be shown per compile error \\ -fno-reference-trace Disable reference trace diff --git a/src/Package.zig b/src/Package.zig index fda4c1c1783b64d40cd67be3cdeab98b480b9b12..1307b8e9f998fe97725068d6c94911bc64955a43 100644 --- a/src/Package.zig +++ b/src/Package.zig @@ -137,6 +137,43 @@ pub const Hash = struct { _ = std.fmt.bufPrint(result.bytes[i..], "{x}", .{&bin_digest}) catch unreachable; return result; } + + pub fn projectId(hash: *const Hash) ProjectId { + const name = std.mem.sliceTo(&hash.bytes, '-'); + const hashplus = hash.bytes[std.mem.findScalarLast(u8, &hash.bytes, '-').? + 1 ..]; + var decoded: [6]u8 = undefined; + std.base64.url_safe_no_pad.Decoder.decode(&decoded, hashplus[0..8]) catch unreachable; + const fingerprint_id = std.mem.readInt(u32, decoded[0..4], .little); + return .init(name, fingerprint_id); + } + + test projectId { + const hash: Hash = .fromSlice("pulseaudio-16.1.1-9-mk_62MZkNwBaFwiZ7ZVrYRIf_3dTqqJR5PbMRCJzSuLw"); + const project_id = hash.projectId(); + + var expected_name: [32]u8 = @splat(0); + expected_name[0.."pulseaudio".len].* = "pulseaudio".*; + try std.testing.expectEqualSlices(u8, &expected_name, &project_id.padded_name); + + try std.testing.expectEqual(0xd8fa4f9a, project_id.fingerprint_id); + } +}; + +/// Minimum information required to identify whether a package is an artifact +/// of a given project. +pub const ProjectId = struct { + /// Bytes after name.len are set to zero. + padded_name: [32]u8, + fingerprint_id: u32, + + pub fn init(name: []const u8, fingerprint_id: u32) ProjectId { + var padded_name: [32]u8 = @splat(0); + @memcpy(padded_name[0..name.len], name); + return .{ + .padded_name = padded_name, + .fingerprint_id = fingerprint_id, + }; + } }; pub const MultihashFunction = enum(u16) { diff --git a/src/Package/Fetch.zig b/src/Package/Fetch.zig index d873fc9bd9a3eb4d5c316c273a6e4e05e3e77dc4..7aa3cee00114dcfdd4b700996e845f2e825ba911 100644 --- a/src/Package/Fetch.zig +++ b/src/Package/Fetch.zig @@ -142,6 +142,8 @@ pub const JobQueue = struct { /// Set of hashes that will be additionally fetched even if they are marked /// as lazy. unlazy_set: UnlazySet = .{}, + /// Identifies paths that override all packages in the tree matching + fork_set: ForkSet = .{}, pub const Mode = enum { /// Non-lazy dependencies are always fetched. @@ -152,6 +154,7 @@ pub const JobQueue = struct { }; pub const Table = std.AutoArrayHashMapUnmanaged(Package.Hash, *Fetch); pub const UnlazySet = std.AutoArrayHashMapUnmanaged(Package.Hash, void); + pub const ForkSet = std.AutoArrayHashMapUnmanaged(Package.ProjectId, Cache.Path); pub fn deinit(jq: *JobQueue) void { const io = jq.io; @@ -801,45 +804,29 @@ fn loadManifest(f: *Fetch, pkg_root: Cache.Path) RunError!void { const io = f.job_queue.io; const eb = &f.error_bundle; const arena = f.arena.allocator(); - const manifest_bytes = pkg_root.root_dir.handle.readFileAllocOptions( + const manifest_path = try pkg_root.join(arena, Manifest.basename); + + f.manifest = @as(Manifest, undefined); + + Manifest.load( io, - try fs.path.join(arena, &.{ pkg_root.sub_path, Manifest.basename }), arena, - .limited(Manifest.max_bytes), - .@"1", - 0, + manifest_path, + &f.manifest_ast, + eb, + &f.manifest.?, + f.allow_missing_paths_field, ) catch |err| switch (err) { error.FileNotFound => return, + error.Canceled => |e| return e, + error.ErrorsBundled => return error.FetchFailed, else => |e| { - const file_path = try pkg_root.join(arena, Manifest.basename); try eb.addRootErrorMessage(.{ - .msg = try eb.printString("unable to load package manifest '{f}': {t}", .{ file_path, e }), + .msg = try eb.printString("unable to load package manifest '{f}': {t}", .{ manifest_path, e }), }); return error.FetchFailed; }, }; - - const ast = &f.manifest_ast; - ast.* = try std.zig.Ast.parse(arena, manifest_bytes, .zon); - - if (ast.errors.len > 0) { - const file_path = try std.fmt.allocPrint(arena, "{f}" ++ fs.path.sep_str ++ Manifest.basename, .{pkg_root}); - try std.zig.putAstErrorsIntoBundle(arena, ast.*, file_path, eb); - return error.FetchFailed; - } - - const rng: std.Random.IoSource = .{ .io = io }; - - f.manifest = try Manifest.parse(arena, ast.*, rng.interface(), .{ - .allow_missing_paths_field = f.allow_missing_paths_field, - }); - const manifest = &f.manifest.?; - - if (manifest.errors.len > 0) { - const src_path = try eb.printString("{f}" ++ fs.path.sep_str ++ "{s}", .{ pkg_root, Manifest.basename }); - try manifest.copyErrorsIntoBundle(ast.*, src_path, eb); - return error.FetchFailed; - } } fn queueJobsForDeps(f: *Fetch) RunError!void { diff --git a/src/Package/Manifest.zig b/src/Package/Manifest.zig index e66a78ae14b7c93bd233fe9327e6631b82702170..4a71d15c814f5f90c0373d9383f594987c649d0d 100644 --- a/src/Package/Manifest.zig +++ b/src/Package/Manifest.zig @@ -1,10 +1,13 @@ const Manifest = @This(); + const std = @import("std"); +const Io = std.Io; const mem = std.mem; const Allocator = std.mem.Allocator; const assert = std.debug.assert; const Ast = std.zig.Ast; const testing = std.testing; + const Package = @import("../Package.zig"); pub const max_bytes = 10 * 1024 * 1024; @@ -53,7 +56,7 @@ pub const ParseOptions = struct { pub const Error = Allocator.Error; -pub fn parse(gpa: Allocator, ast: Ast, rng: std.Random, options: ParseOptions) Error!Manifest { +pub fn parse(gpa: Allocator, ast: *const Ast, rng: std.Random, options: ParseOptions) Error!Manifest { const main_node_index = ast.nodeData(.root).node; var arena_instance = std.heap.ArenaAllocator.init(gpa); @@ -61,7 +64,7 @@ pub fn parse(gpa: Allocator, ast: Ast, rng: std.Random, options: ParseOptions) E var p: Parse = .{ .gpa = gpa, - .ast = ast, + .ast = ast.*, .arena = arena_instance.allocator(), .errors = .{}, @@ -578,6 +581,45 @@ const Parse = struct { } }; +pub fn load( + io: Io, + arena: Allocator, + manifest_path: std.Build.Cache.Path, + ast: *std.zig.Ast, + error_bundle: *std.zig.ErrorBundle.Wip, + manifest: *Manifest, + allow_missing_paths_field: bool, +) !void { + const manifest_bytes = try manifest_path.root_dir.handle.readFileAllocOptions( + io, + manifest_path.sub_path, + arena, + .limited(max_bytes), + .@"1", + 0, + ); + + ast.* = try std.zig.Ast.parse(arena, manifest_bytes, .zon); + + if (ast.errors.len > 0) { + const file_path = try manifest_path.joinString(arena, ""); + try std.zig.putAstErrorsIntoBundle(arena, ast.*, file_path, error_bundle); + return error.ErrorsBundled; + } + + const rng: std.Random.IoSource = .{ .io = io }; + + manifest.* = try parse(arena, ast, rng.interface(), .{ + .allow_missing_paths_field = allow_missing_paths_field, + }); + + if (manifest.errors.len > 0) { + const src_path = try error_bundle.printString("{f}", .{manifest_path}); + try manifest.copyErrorsIntoBundle(ast.*, src_path, error_bundle); + return error.ErrorsBundled; + } +} + test "basic" { const gpa = testing.allocator; diff --git a/src/main.zig b/src/main.zig index dba52807f544973724264eb1eda73f8c0452d28a..a59c289ce37d41558ad4265de67acf6a55d0b9b5 100644 --- a/src/main.zig +++ b/src/main.zig @@ -4896,7 +4896,8 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8, var override_global_cache_dir: ?[]const u8 = EnvVar.ZIG_GLOBAL_CACHE_DIR.get(environ_map); var override_local_cache_dir: ?[]const u8 = EnvVar.ZIG_LOCAL_CACHE_DIR.get(environ_map); var override_build_runner: ?[]const u8 = EnvVar.ZIG_BUILD_RUNNER.get(environ_map); - var child_argv = std.array_list.Managed([]const u8).init(arena); + var child_argv: std.ArrayList([]const u8) = .empty; + var forks: std.ArrayList(Fork) = .empty; var reference_trace: ?u32 = null; var debug_compile_errors = false; var verbose_link = (native_os != .wasi or builtin.link_libc) and @@ -4917,24 +4918,24 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8, var debug_libc_paths_file: ?[]const u8 = null; const argv_index_exe = child_argv.items.len; - _ = try child_argv.addOne(); + _ = try child_argv.addOne(arena); const self_exe_path = try process.executablePathAlloc(io, arena); - try child_argv.append(self_exe_path); + try child_argv.append(arena, self_exe_path); const argv_index_zig_lib_dir = child_argv.items.len; - _ = try child_argv.addOne(); + _ = try child_argv.addOne(arena); const argv_index_build_file = child_argv.items.len; - _ = try child_argv.addOne(); + _ = try child_argv.addOne(arena); const argv_index_cache_dir = child_argv.items.len; - _ = try child_argv.addOne(); + _ = try child_argv.addOne(arena); const argv_index_global_cache_dir = child_argv.items.len; - _ = try child_argv.addOne(); + _ = try child_argv.addOne(arena); - try child_argv.appendSlice(&.{ + try child_argv.appendSlice(arena, &.{ "--seed", try std.fmt.allocPrint(arena, "0x{x}", .{randInt(io, u32)}), }); @@ -4955,7 +4956,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8, // read this file in the parent to obtain the results, in the case the child // exits with code 3. const results_tmp_file_nonce = std.fmt.hex(randInt(io, u64)); - try child_argv.append("-Z" ++ results_tmp_file_nonce); + try child_argv.append(arena, "-Z" ++ results_tmp_file_nonce); var color: Color = .auto; var n_jobs: ?u32 = null; @@ -5000,11 +5001,19 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8, fatal("expected [needed|all] after '--fetch=', found '{s}'", .{ sub_arg, }); + } else if (mem.cutPrefix(u8, arg, "--fork=")) |sub_arg| { + try forks.append(arena, .{ + .project_id = undefined, + .path = .{ + .root_dir = .cwd(), + .sub_path = sub_arg, + }, + }); } else if (mem.eql(u8, arg, "--system")) { if (i + 1 >= args.len) fatal("expected argument after '{s}'", .{arg}); i += 1; system_pkg_dir_path = args[i]; - try child_argv.append("--system"); + try child_argv.append(arena, "--system"); continue; } else if (mem.cutPrefix(u8, arg, "-freference-trace=")) |num| { reference_trace = std.fmt.parseUnsigned(u32, num, 10) catch |err| { @@ -5014,7 +5023,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8, reference_trace = null; } else if (mem.eql(u8, arg, "--debug-log")) { if (i + 1 >= args.len) fatal("expected argument after '{s}'", .{arg}); - try child_argv.appendSlice(args[i .. i + 2]); + try child_argv.appendSlice(arena, args[i .. i + 2]); i += 1; if (!build_options.enable_logging) { warn("Zig was compiled without logging enabled (-Dlog). --debug-log has no effect.", .{}); @@ -5070,7 +5079,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8, color = std.meta.stringToEnum(Color, args[i]) orelse { fatal("expected [auto|on|off] after {s}, found '{s}'", .{ arg, args[i] }); }; - try child_argv.appendSlice(&.{ arg, args[i] }); + try child_argv.appendSlice(arena, &.{ arg, args[i] }); continue; } else if (mem.cutPrefix(u8, arg, "-j")) |str| { const num = std.fmt.parseUnsigned(u32, str, 10) catch |err| { @@ -5090,11 +5099,11 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8, } else if (mem.eql(u8, arg, "--")) { // The rest of the args are supposed to get passed onto // build runner's `build.args` - try child_argv.appendSlice(args[i..]); + try child_argv.appendSlice(arena, args[i..]); break; } } - try child_argv.append(arg); + try child_argv.append(arena, arg); } } @@ -5182,6 +5191,25 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8, defer http_client.deinit(); var unlazy_set: Package.Fetch.JobQueue.UnlazySet = .{}; + var fork_set: Package.Fetch.JobQueue.ForkSet = .{}; + + { + // Populate fork_set. + var group: Io.Group = .init; + defer group.cancel(io); + + for (forks.items) |*fork| + group.async(io, loadFork, .{ io, gpa, fork, color }); + + try group.await(io); + + for (forks.items) |*fork| { + const project_id = fork.project_id catch |err| switch (err) { + error.AlreadyReported => process.exit(1), + }; + try fork_set.put(arena, project_id, fork.path); + } + } // This loop is re-evaluated when the build script exits with an indication that it // could not continue due to missing lazy dependencies. @@ -5518,6 +5546,61 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8, } } +const Fork = struct { + path: Path, + project_id: error{AlreadyReported}!Package.ProjectId, +}; + +fn loadFork(io: Io, gpa: Allocator, fork: *Fork, color: Color) Io.Cancelable!void { + fork.project_id = loadForkFallible(io, gpa, fork, color) catch |err| switch (err) { + error.Canceled => |e| return e, + error.AlreadyReported => |e| e, + else => |e| e: { + std.log.err("failed to load fork at {f}: {t}", .{ fork.path, e }); + break :e error.AlreadyReported; + }, + }; +} + +fn loadForkFallible(io: Io, gpa: Allocator, fork: *Fork, color: Color) !Package.ProjectId { + var arena_instance = std.heap.ArenaAllocator.init(gpa); + defer arena_instance.deinit(); + const arena = arena_instance.allocator(); + + var error_bundle: std.zig.ErrorBundle.Wip = undefined; + try error_bundle.init(gpa); + defer error_bundle.deinit(); + + const manifest_path = try fork.path.join(arena, Package.Manifest.basename); + + var manifest_ast: std.zig.Ast = undefined; + var manifest: Package.Manifest = undefined; + + Package.Manifest.load( + io, + arena, + manifest_path, + &manifest_ast, + &error_bundle, + &manifest, + true, + ) catch |err| switch (err) { + error.Canceled => |e| return e, + error.ErrorsBundled => { + assert(error_bundle.root_list.items.len > 0); + var errors = try error_bundle.toOwnedBundle(""); + errors.renderToStderr(io, .{}, color) catch {}; + return error.AlreadyReported; + }, + else => |e| { + std.log.err("failed to load package manifest {f}: {t}", .{ manifest_path, e }); + return error.AlreadyReported; + }, + }; + + return .init(manifest.name, manifest.id); +} + const JitCmdOptions = struct { cmd_name: []const u8, root_src_path: []const u8, @@ -7410,7 +7493,7 @@ fn loadManifest( process.exit(2); } - var manifest = try Package.Manifest.parse(gpa, ast, rng.interface(), .{}); + var manifest = try Package.Manifest.parse(gpa, &ast, rng.interface(), .{}); errdefer manifest.deinit(gpa); if (manifest.errors.len > 0) { -- 2.54.0 From e661e78256e195e23ef7ffded47c2256d0a7620f Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 6 Feb 2026 13:21:13 -0800 Subject: [PATCH 220/499] store the Manifest in the fork set --- lib/compiler/build_runner.zig | 2 +- src/Package.zig | 9 ++++++++ src/Package/Fetch.zig | 21 ++++++++++++++++++- src/main.zig | 39 +++++++++++++++++++---------------- 4 files changed, 51 insertions(+), 20 deletions(-) diff --git a/lib/compiler/build_runner.zig b/lib/compiler/build_runner.zig index 4d6b640e88fd5852b03bfa9eb12fbbeb6c237735..4aa513ec74caa2ecb606078b181fcba8227d2de8 100644 --- a/lib/compiler/build_runner.zig +++ b/lib/compiler/build_runner.zig @@ -1657,7 +1657,7 @@ fn printUsage(b: *std.Build, w: *Writer) !void { \\ --fetch[=mode] Fetch dependency tree (optionally choose laziness) and exit \\ needed (Default) Lazy dependencies are fetched as needed \\ all Lazy dependencies are always fetched - \\ --fork=[path] Override one or more packages from dependency tree + \\ --fork=[path] Override one or more projects from dependency tree \\ \\Advanced Options: \\ -freference-trace[=num] How many lines of reference trace should be shown per compile error diff --git a/src/Package.zig b/src/Package.zig index 1307b8e9f998fe97725068d6c94911bc64955a43..d8bab480f0ecdc4b2f703185466f0dfd0cbfe358 100644 --- a/src/Package.zig +++ b/src/Package.zig @@ -174,6 +174,15 @@ pub const ProjectId = struct { .fingerprint_id = fingerprint_id, }; } + + pub fn eql(a: *const ProjectId, b: *const ProjectId) bool { + return a.fingerprint_id == b.fingerprint_id and std.mem.eql(u8, &a.padded_name, &b.padded_name); + } + + pub fn hash(a: *const ProjectId) u64 { + const x: u64 = @bitCast(a.padded_name[0..8].*); + return std.hash.int(x | a.fingerprint_id); + } }; pub const MultihashFunction = enum(u16) { diff --git a/src/Package/Fetch.zig b/src/Package/Fetch.zig index 7aa3cee00114dcfdd4b700996e845f2e825ba911..1c0f0df7e4cc4b94ba30b3c5e512ddae79009ac8 100644 --- a/src/Package/Fetch.zig +++ b/src/Package/Fetch.zig @@ -154,7 +154,26 @@ pub const JobQueue = struct { }; pub const Table = std.AutoArrayHashMapUnmanaged(Package.Hash, *Fetch); pub const UnlazySet = std.AutoArrayHashMapUnmanaged(Package.Hash, void); - pub const ForkSet = std.AutoArrayHashMapUnmanaged(Package.ProjectId, Cache.Path); + pub const ForkSet = std.ArrayHashMapUnmanaged(Fork, void, Fork.Context, false); + + pub const Fork = struct { + path: Cache.Path, + manifest_ast: std.zig.Ast, + manifest: Package.Manifest, + + pub const Context = struct { + pub fn hash(_: @This(), a: Fork) u32 { + const project_id: Package.ProjectId = .init(a.manifest.name, a.manifest.id); + return @truncate(project_id.hash()); + } + + pub fn eql(_: @This(), a: Fork, b: Fork, _: usize) bool { + const a_project_id: Package.ProjectId = .init(a.manifest.name, a.manifest.id); + const b_project_id: Package.ProjectId = .init(b.manifest.name, b.manifest.id); + return a_project_id.eql(&b_project_id); + } + }; + }; pub fn deinit(jq: *JobQueue) void { const io = jq.io; diff --git a/src/main.zig b/src/main.zig index a59c289ce37d41558ad4265de67acf6a55d0b9b5..a6510f1a4ce02b917a09b62e641a575d1c8421be 100644 --- a/src/main.zig +++ b/src/main.zig @@ -5003,11 +5003,14 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8, }); } else if (mem.cutPrefix(u8, arg, "--fork=")) |sub_arg| { try forks.append(arena, .{ - .project_id = undefined, + .manifest_ast = undefined, + .manifest = undefined, + .error_bundle = undefined, .path = .{ .root_dir = .cwd(), .sub_path = sub_arg, }, + .failed = false, }); } else if (mem.eql(u8, arg, "--system")) { if (i + 1 >= args.len) fatal("expected argument after '{s}'", .{arg}); @@ -5204,10 +5207,12 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8, try group.await(io); for (forks.items) |*fork| { - const project_id = fork.project_id catch |err| switch (err) { - error.AlreadyReported => process.exit(1), - }; - try fork_set.put(arena, project_id, fork.path); + if (fork.failed) process.exit(1); + try fork_set.put(arena, .{ + .path = fork.path, + .manifest_ast = fork.manifest_ast, + .manifest = fork.manifest, + }, {}); } } @@ -5548,21 +5553,24 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8, const Fork = struct { path: Path, - project_id: error{AlreadyReported}!Package.ProjectId, + manifest_ast: std.zig.Ast, + manifest: Package.Manifest, + error_bundle: std.zig.ErrorBundle.Wip, + failed: bool, }; fn loadFork(io: Io, gpa: Allocator, fork: *Fork, color: Color) Io.Cancelable!void { - fork.project_id = loadForkFallible(io, gpa, fork, color) catch |err| switch (err) { + loadForkFallible(io, gpa, fork, color) catch |err| switch (err) { error.Canceled => |e| return e, - error.AlreadyReported => |e| e, - else => |e| e: { + error.AlreadyReported => fork.failed = true, + else => |e| { std.log.err("failed to load fork at {f}: {t}", .{ fork.path, e }); - break :e error.AlreadyReported; + fork.failed = true; }, }; } -fn loadForkFallible(io: Io, gpa: Allocator, fork: *Fork, color: Color) !Package.ProjectId { +fn loadForkFallible(io: Io, gpa: Allocator, fork: *Fork, color: Color) !void { var arena_instance = std.heap.ArenaAllocator.init(gpa); defer arena_instance.deinit(); const arena = arena_instance.allocator(); @@ -5573,16 +5581,13 @@ fn loadForkFallible(io: Io, gpa: Allocator, fork: *Fork, color: Color) !Package. const manifest_path = try fork.path.join(arena, Package.Manifest.basename); - var manifest_ast: std.zig.Ast = undefined; - var manifest: Package.Manifest = undefined; - Package.Manifest.load( io, arena, manifest_path, - &manifest_ast, + &fork.manifest_ast, &error_bundle, - &manifest, + &fork.manifest, true, ) catch |err| switch (err) { error.Canceled => |e| return e, @@ -5597,8 +5602,6 @@ fn loadForkFallible(io: Io, gpa: Allocator, fork: *Fork, color: Color) !Package. return error.AlreadyReported; }, }; - - return .init(manifest.name, manifest.id); } const JitCmdOptions = struct { -- 2.54.0 From 699063c5a0e1bba14710c76ef5b2ed1cb1343b62 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 6 Feb 2026 13:38:03 -0800 Subject: [PATCH 221/499] fetch: implement the fork override --- src/Package/Fetch.zig | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/src/Package/Fetch.zig b/src/Package/Fetch.zig index 1c0f0df7e4cc4b94ba30b3c5e512ddae79009ac8..26e016dea5e4a4245e63d81a6ad69b297098c766 100644 --- a/src/Package/Fetch.zig +++ b/src/Package/Fetch.zig @@ -142,7 +142,8 @@ pub const JobQueue = struct { /// Set of hashes that will be additionally fetched even if they are marked /// as lazy. unlazy_set: UnlazySet = .{}, - /// Identifies paths that override all packages in the tree matching + /// Identifies paths that override all packages in the tree with matching + /// project ids. fork_set: ForkSet = .{}, pub const Mode = enum { @@ -173,6 +174,17 @@ pub const JobQueue = struct { return a_project_id.eql(&b_project_id); } }; + + pub const Adapter = struct { + pub fn hash(_: @This(), a: Package.ProjectId) u32 { + return @truncate(a.hash()); + } + + pub fn eql(_: @This(), a_project_id: Package.ProjectId, b: Fork, _: usize) bool { + const b_project_id: Package.ProjectId = .init(b.manifest.name, b.manifest.id); + return a_project_id.eql(&b_project_id); + } + }; }; pub fn deinit(jq: *JobQueue) void { @@ -558,6 +570,15 @@ pub fn run(f: *Fetch) RunError!void { var resource_buffer: [init_resource_buffer_size]u8 = undefined; if (remote.hash) |expected_hash| { + const expected_project_id: Package.ProjectId = expected_hash.projectId(); + if (job_queue.fork_set.getKeyPtrAdapted(expected_project_id, @as(JobQueue.Fork.Adapter, .{}))) |fork| { + f.package_root = fork.path; + f.manifest_ast = fork.manifest_ast; + f.manifest = fork.manifest; + if (!job_queue.recursive) return; + return queueJobsForDeps(f); + } + const package_root = try job_queue.root_pkg_path.join(arena, expected_hash.toSlice()); if (package_root.root_dir.handle.access(io, package_root.sub_path, .{})) |_| { assert(f.lazy_status != .unavailable); -- 2.54.0 From 5f453b45d395239d1b26d6cec3d3ac7f407e0fb0 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 6 Feb 2026 14:03:09 -0800 Subject: [PATCH 222/499] Package: fix Hash.projectId decoding --- src/Package.zig | 22 +++++++++++++++++----- 1 file changed, 17 insertions(+), 5 deletions(-) diff --git a/src/Package.zig b/src/Package.zig index d8bab480f0ecdc4b2f703185466f0dfd0cbfe358..0215db24a8cf7361c5e19f9a0f6085e86001dad7 100644 --- a/src/Package.zig +++ b/src/Package.zig @@ -139,11 +139,12 @@ pub const Hash = struct { } pub fn projectId(hash: *const Hash) ProjectId { - const name = std.mem.sliceTo(&hash.bytes, '-'); - const hashplus = hash.bytes[std.mem.findScalarLast(u8, &hash.bytes, '-').? + 1 ..]; - var decoded: [6]u8 = undefined; - std.base64.url_safe_no_pad.Decoder.decode(&decoded, hashplus[0..8]) catch unreachable; - const fingerprint_id = std.mem.readInt(u32, decoded[0..4], .little); + const bytes = hash.toSlice(); + const name = std.mem.sliceTo(bytes, '-'); + const encoded_hashplus = bytes[bytes.len - 44 ..]; + var hashplus: [33]u8 = undefined; + std.base64.url_safe_no_pad.Decoder.decode(&hashplus, encoded_hashplus) catch unreachable; + const fingerprint_id = std.mem.readInt(u32, hashplus[0..4], .little); return .init(name, fingerprint_id); } @@ -157,6 +158,17 @@ pub const Hash = struct { try std.testing.expectEqual(0xd8fa4f9a, project_id.fingerprint_id); } + + test "projectId with dashes in the base64" { + const hash: Hash = .fromSlice("dvui-0.4.0-dev-AQFJmayi2gAKE7FeJoF61v5U1IV9-SupoEcFutIZYpkC"); + const project_id = hash.projectId(); + + var expected_name: [32]u8 = @splat(0); + expected_name[0.."dvui".len].* = "dvui".*; + try std.testing.expectEqualSlices(u8, &expected_name, &project_id.padded_name); + + try std.testing.expectEqual(0x99490101, project_id.fingerprint_id); + } }; /// Minimum information required to identify whether a package is an artifact -- 2.54.0 From 632d1fb948f46ae09c8e3a5e12acedafde56084a Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 6 Feb 2026 14:03:43 -0800 Subject: [PATCH 223/499] fetch: fix manifest memory management --- src/Package/Fetch.zig | 30 ++++++++++++++++++------------ src/Package/Manifest.zig | 6 +++--- src/main.zig | 13 ++++++++----- 3 files changed, 29 insertions(+), 20 deletions(-) diff --git a/src/Package/Fetch.zig b/src/Package/Fetch.zig index 26e016dea5e4a4245e63d81a6ad69b297098c766..1eebea4b89ce52507d552221eea60f4b8fea89ee 100644 --- a/src/Package/Fetch.zig +++ b/src/Package/Fetch.zig @@ -76,8 +76,9 @@ use_latest_commit: bool, /// Relative to the build root of the root package. package_root: Cache.Path, error_bundle: ErrorBundle.Wip, -manifest: ?Manifest, +manifest: Manifest, manifest_ast: std.zig.Ast, +have_manifest: bool, computed_hash: ComputedHash, /// Fetch logic notices whether a package has a build.zig file and sets this flag. has_build_zig: bool, @@ -282,7 +283,8 @@ pub const JobQueue = struct { , .{std.zig.fmtString(hash_slice)}); } - if (fetch.manifest) |*manifest| { + if (fetch.have_manifest) { + const manifest = &fetch.manifest; try buf.appendSlice( \\ pub const deps: []const struct { []const u8, []const u8 } = &.{ \\ @@ -317,7 +319,8 @@ pub const JobQueue = struct { ); const root_fetch = jq.all_fetches.items[0]; - const root_manifest = &root_fetch.manifest.?; + assert(root_fetch.have_manifest); + const root_manifest = &root_fetch.manifest; for (root_manifest.dependencies.keys(), root_manifest.dependencies.values()) |name, dep| { const h = depDigest(root_fetch.package_root, jq.global_cache, dep) orelse continue; @@ -716,7 +719,7 @@ fn runResource( try loadManifest(f, pkg_path); const filter: Filter = .{ - .include_paths = if (f.manifest) |m| m.paths else .{}, + .include_paths = if (f.have_manifest) f.manifest.paths else .{}, }; // Ignore errors that were excluded by manifest, such as failure to @@ -809,7 +812,8 @@ fn runResource( pub fn computedPackageHash(f: *const Fetch) Package.Hash { const saturated_size = std.math.cast(u32, f.computed_hash.total_size) orelse std.math.maxInt(u32); - if (f.manifest) |man| { + if (f.have_manifest) { + const man = &f.manifest; var version_buffer: [32]u8 = undefined; const version: []const u8 = std.fmt.bufPrint(&version_buffer, "{f}", .{man.version}) catch &version_buffer; return .init(f.computed_hash.digest, man.name, version, man.id, saturated_size); @@ -846,15 +850,13 @@ fn loadManifest(f: *Fetch, pkg_root: Cache.Path) RunError!void { const arena = f.arena.allocator(); const manifest_path = try pkg_root.join(arena, Manifest.basename); - f.manifest = @as(Manifest, undefined); - Manifest.load( io, arena, manifest_path, &f.manifest_ast, eb, - &f.manifest.?, + &f.manifest, f.allow_missing_paths_field, ) catch |err| switch (err) { error.FileNotFound => return, @@ -867,6 +869,7 @@ fn loadManifest(f: *Fetch, pkg_root: Cache.Path) RunError!void { return error.FetchFailed; }, }; + f.have_manifest = true; } fn queueJobsForDeps(f: *Fetch) RunError!void { @@ -875,7 +878,8 @@ fn queueJobsForDeps(f: *Fetch) RunError!void { assert(f.job_queue.recursive); // If the package does not have a build.zig.zon file then there are no dependencies. - const manifest = f.manifest orelse return; + if (!f.have_manifest) return; + const manifest = &f.manifest; const new_fetches, const prog_names = nf: { const parent_arena = f.arena.allocator(); @@ -980,8 +984,9 @@ fn queueJobsForDeps(f: *Fetch) RunError!void { .package_root = undefined, .error_bundle = undefined, - .manifest = null, + .manifest = undefined, .manifest_ast = undefined, + .have_manifest = false, .computed_hash = undefined, .has_build_zig = false, .oom_flag = false, @@ -1958,7 +1963,7 @@ const Filter = struct { include_paths: std.StringArrayHashMapUnmanaged(void) = .empty, /// sub_path is relative to the package root. - pub fn includePath(self: Filter, sub_path: []const u8) bool { + pub fn includePath(self: *const Filter, sub_path: []const u8) bool { if (self.include_paths.count() == 0) return true; if (self.include_paths.contains("")) return true; if (self.include_paths.contains(".")) return true; @@ -2349,8 +2354,9 @@ const TestFetchBuilder = struct { .package_root = undefined, .error_bundle = undefined, - .manifest = null, + .manifest = undefined, .manifest_ast = undefined, + .have_manifest = false, .computed_hash = undefined, .has_build_zig = false, .oom_flag = false, diff --git a/src/Package/Manifest.zig b/src/Package/Manifest.zig index 4a71d15c814f5f90c0373d9383f594987c649d0d..3370ef1d47dd6c771c5428adabe7160ea4964992 100644 --- a/src/Package/Manifest.zig +++ b/src/Package/Manifest.zig @@ -645,7 +645,7 @@ test "basic" { var rng = std.Random.DefaultPrng.init(0); - var manifest = try Manifest.parse(gpa, ast, rng.random(), .{}); + var manifest = try Manifest.parse(gpa, &ast, rng.random(), .{}); defer manifest.deinit(gpa); try testing.expect(manifest.errors.len == 0); @@ -691,7 +691,7 @@ test "minimum_zig_version" { var rng = std.Random.DefaultPrng.init(0); - var manifest = try Manifest.parse(gpa, ast, rng.random(), .{}); + var manifest = try Manifest.parse(gpa, &ast, rng.random(), .{}); defer manifest.deinit(gpa); try testing.expect(manifest.errors.len == 0); @@ -726,7 +726,7 @@ test "minimum_zig_version - invalid version" { var rng = std.Random.DefaultPrng.init(0); - var manifest = try Manifest.parse(gpa, ast, rng.random(), .{}); + var manifest = try Manifest.parse(gpa, &ast, rng.random(), .{}); defer manifest.deinit(gpa); try testing.expect(manifest.errors.len == 1); diff --git a/src/main.zig b/src/main.zig index a6510f1a4ce02b917a09b62e641a575d1c8421be..9a8b1d31a8307be45072483fba56114e866f3715 100644 --- a/src/main.zig +++ b/src/main.zig @@ -5323,8 +5323,9 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8, .package_root = undefined, .error_bundle = undefined, - .manifest = null, + .manifest = undefined, .manifest_ast = undefined, + .have_manifest = false, .computed_hash = undefined, .has_build_zig = true, .oom_flag = false, @@ -5402,7 +5403,8 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8, // dependencies' build.zig modules by name. for (fetches) |f| { const mod = f.module orelse continue; - const man = f.manifest orelse continue; + if (!f.have_manifest) continue; + const man = &f.manifest; const dep_names = man.dependencies.keys(); try mod.deps.ensureUnusedCapacity(arena, @intCast(dep_names.len)); for (dep_names, man.dependencies.values()) |name, dep| { @@ -7134,8 +7136,9 @@ fn cmdFetch( .package_root = undefined, .error_bundle = undefined, - .manifest = null, + .manifest = undefined, .manifest_ast = undefined, + .have_manifest = false, .computed_hash = undefined, .has_build_zig = false, .oom_flag = false, @@ -7171,9 +7174,9 @@ fn cmdFetch( }, .yes, .exact => |name| name: { if (name) |n| break :name n; - const fetched_manifest = fetch.manifest orelse + if (!fetch.have_manifest) fatal("unable to determine name; fetched package has no build.zig.zon file", .{}); - break :name fetched_manifest.name; + break :name fetch.manifest.name; }, }; -- 2.54.0 From 9d0256271758d24354aff7e5ba1d6927f5fe94f9 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 6 Feb 2026 14:12:52 -0800 Subject: [PATCH 224/499] zig build: don't add --fork to build runner args --- src/main.zig | 1 + 1 file changed, 1 insertion(+) diff --git a/src/main.zig b/src/main.zig index 9a8b1d31a8307be45072483fba56114e866f3715..80921fc02f4d217ac3f568f4da0d5f1508f825ce 100644 --- a/src/main.zig +++ b/src/main.zig @@ -5012,6 +5012,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8, }, .failed = false, }); + continue; } else if (mem.eql(u8, arg, "--system")) { if (i + 1 >= args.len) fatal("expected argument after '{s}'", .{arg}); i += 1; -- 2.54.0 From 9f3b60b23af226c7e62645daa1df27419f0a1b6a Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 6 Feb 2026 14:29:45 -0800 Subject: [PATCH 225/499] fetch: ensure that forks are actually used --- src/Package/Fetch.zig | 3 +++ src/main.zig | 22 ++++++++++++++++++++++ 2 files changed, 25 insertions(+) diff --git a/src/Package/Fetch.zig b/src/Package/Fetch.zig index 1eebea4b89ce52507d552221eea60f4b8fea89ee..00b1bd8f539e5096979ed236c896e7a1847e7d62 100644 --- a/src/Package/Fetch.zig +++ b/src/Package/Fetch.zig @@ -162,6 +162,7 @@ pub const JobQueue = struct { path: Cache.Path, manifest_ast: std.zig.Ast, manifest: Package.Manifest, + uses: usize, pub const Context = struct { pub fn hash(_: @This(), a: Fork) u32 { @@ -575,6 +576,8 @@ pub fn run(f: *Fetch) RunError!void { if (remote.hash) |expected_hash| { const expected_project_id: Package.ProjectId = expected_hash.projectId(); if (job_queue.fork_set.getKeyPtrAdapted(expected_project_id, @as(JobQueue.Fork.Adapter, .{}))) |fork| { + log.debug("using fork {f} for {s}", .{ fork.path, fork.manifest.name }); + fork.uses += 1; f.package_root = fork.path; f.manifest_ast = fork.manifest_ast; f.manifest = fork.manifest; diff --git a/src/main.zig b/src/main.zig index 80921fc02f4d217ac3f568f4da0d5f1508f825ce..ef5c9ec732c147fc207aa68c217177f144eca3f6 100644 --- a/src/main.zig +++ b/src/main.zig @@ -5213,6 +5213,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8, .path = fork.path, .manifest_ast = fork.manifest_ast, .manifest = fork.manifest, + .uses = 0, }, {}); } } @@ -5279,6 +5280,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8, .recursive = true, .debug_hash = false, .unlazy_set = unlazy_set, + .fork_set = fork_set, .mode = fetch_mode, .prog_node = fetch_prog_node, }; @@ -5344,6 +5346,26 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8, job_queue.group.async(io, Package.Fetch.workerRun, .{ &fetch, "root" }); try job_queue.group.await(io); + { + // Ensure that forks were actually used. This is done + // before printing manifest errors because using a fork can + // prevent them. + var any_unused = false; + for (fork_set.keys()) |*fork| { + if (fork.uses == 0) { + std.log.err("fork {f} matched no {s} packages", .{ + fork.path, fork.manifest.name, + }); + any_unused = true; + } else { + std.log.info("fork {f} matched {d} {s} packages", .{ + fork.path, fork.uses, fork.manifest.name, + }); + } + } + if (any_unused) process.exit(1); + } + try job_queue.consolidateErrors(); if (fetch.error_bundle.root_list.items.len > 0) { -- 2.54.0 From b24b0479f6be4e29d9ac4b9c6d2886aa21c412ab Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 6 Feb 2026 15:10:54 -0800 Subject: [PATCH 226/499] fetch: fix missing check for build.zig existence --- src/Package/Fetch.zig | 2 ++ 1 file changed, 2 insertions(+) diff --git a/src/Package/Fetch.zig b/src/Package/Fetch.zig index 00b1bd8f539e5096979ed236c896e7a1847e7d62..d597aa0a87a6114da08e9006d5862ecc87d1f65b 100644 --- a/src/Package/Fetch.zig +++ b/src/Package/Fetch.zig @@ -581,6 +581,8 @@ pub fn run(f: *Fetch) RunError!void { f.package_root = fork.path; f.manifest_ast = fork.manifest_ast; f.manifest = fork.manifest; + f.have_manifest = true; + try checkBuildFileExistence(f); if (!job_queue.recursive) return; return queueJobsForDeps(f); } -- 2.54.0 From 6f18aca09e8ba3a3e4987cc17f6b92b6b433537d Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 6 Feb 2026 15:11:06 -0800 Subject: [PATCH 227/499] main: fix cleanup of forks --- src/main.zig | 101 ++++++++++++++++++++++++++++----------------------- 1 file changed, 55 insertions(+), 46 deletions(-) diff --git a/src/main.zig b/src/main.zig index ef5c9ec732c147fc207aa68c217177f144eca3f6..a02927298750296aecbdb27d16c62fe7e811505f 100644 --- a/src/main.zig +++ b/src/main.zig @@ -5006,6 +5006,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8, .manifest_ast = undefined, .manifest = undefined, .error_bundle = undefined, + .arena_allocator = undefined, .path = .{ .root_dir = .cwd(), .sub_path = sub_arg, @@ -5203,7 +5204,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8, defer group.cancel(io); for (forks.items) |*fork| - group.async(io, loadFork, .{ io, gpa, fork, color }); + group.async(io, Fork.load, .{ io, gpa, fork, color }); try group.await(io); @@ -5217,6 +5218,7 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8, }, {}); } } + defer Fork.deinitList(forks.items); // This loop is re-evaluated when the build script exits with an indication that it // could not continue due to missing lazy dependencies. @@ -5270,6 +5272,9 @@ fn cmdBuild(gpa: Allocator, arena: Allocator, io: Io, args: []const []const u8, const fetch_prog_node = root_prog_node.start("Fetch Packages", 0); defer fetch_prog_node.end(); + // Reset fork match counts. + for (fork_set.keys()) |*fork| fork.uses = 0; + var job_queue: Package.Fetch.JobQueue = .{ .io = io, .http_client = &http_client, @@ -5582,53 +5587,57 @@ const Fork = struct { manifest: Package.Manifest, error_bundle: std.zig.ErrorBundle.Wip, failed: bool, + arena_allocator: std.heap.ArenaAllocator, + + fn load(io: Io, gpa: Allocator, fork: *Fork, color: Color) Io.Cancelable!void { + loadFallible(io, gpa, fork, color) catch |err| switch (err) { + error.Canceled => |e| return e, + error.AlreadyReported => fork.failed = true, + else => |e| { + std.log.err("failed to load fork at {f}: {t}", .{ fork.path, e }); + fork.failed = true; + }, + }; + } + + fn loadFallible(io: Io, gpa: Allocator, fork: *Fork, color: Color) !void { + fork.arena_allocator = .init(gpa); + const arena = fork.arena_allocator.allocator(); + + var error_bundle: std.zig.ErrorBundle.Wip = undefined; + try error_bundle.init(gpa); + defer error_bundle.deinit(); + + const manifest_path = try fork.path.join(arena, Package.Manifest.basename); + + Package.Manifest.load( + io, + arena, + manifest_path, + &fork.manifest_ast, + &error_bundle, + &fork.manifest, + true, + ) catch |err| switch (err) { + error.Canceled => |e| return e, + error.ErrorsBundled => { + assert(error_bundle.root_list.items.len > 0); + var errors = try error_bundle.toOwnedBundle(""); + errors.renderToStderr(io, .{}, color) catch {}; + return error.AlreadyReported; + }, + else => |e| { + std.log.err("failed to load package manifest {f}: {t}", .{ manifest_path, e }); + return error.AlreadyReported; + }, + }; + } + + fn deinitList(forks: []Fork) void { + for (forks) |*fork| fork.arena_allocator.deinit(); + } }; -fn loadFork(io: Io, gpa: Allocator, fork: *Fork, color: Color) Io.Cancelable!void { - loadForkFallible(io, gpa, fork, color) catch |err| switch (err) { - error.Canceled => |e| return e, - error.AlreadyReported => fork.failed = true, - else => |e| { - std.log.err("failed to load fork at {f}: {t}", .{ fork.path, e }); - fork.failed = true; - }, - }; -} - -fn loadForkFallible(io: Io, gpa: Allocator, fork: *Fork, color: Color) !void { - var arena_instance = std.heap.ArenaAllocator.init(gpa); - defer arena_instance.deinit(); - const arena = arena_instance.allocator(); - - var error_bundle: std.zig.ErrorBundle.Wip = undefined; - try error_bundle.init(gpa); - defer error_bundle.deinit(); - - const manifest_path = try fork.path.join(arena, Package.Manifest.basename); - - Package.Manifest.load( - io, - arena, - manifest_path, - &fork.manifest_ast, - &error_bundle, - &fork.manifest, - true, - ) catch |err| switch (err) { - error.Canceled => |e| return e, - error.ErrorsBundled => { - assert(error_bundle.root_list.items.len > 0); - var errors = try error_bundle.toOwnedBundle(""); - errors.renderToStderr(io, .{}, color) catch {}; - return error.AlreadyReported; - }, - else => |e| { - std.log.err("failed to load package manifest {f}: {t}", .{ manifest_path, e }); - return error.AlreadyReported; - }, - }; -} - const JitCmdOptions = struct { cmd_name: []const u8, root_src_path: []const u8, -- 2.54.0 From 355c6260015292642badf0e8b786e676fbb8c961 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 6 Feb 2026 15:22:54 -0800 Subject: [PATCH 228/499] fetch: delete legacy hash functionality This also removes a unit test that violates project policy of having binary artifacts as test data when they can be created during the test instead. --- src/Package.zig | 66 ------ src/Package/Fetch.zig | 221 +----------------- src/Package/Fetch/testdata/executables.tar.gz | Bin 430 -> 0 bytes 3 files changed, 5 insertions(+), 282 deletions(-) delete mode 100644 src/Package/Fetch/testdata/executables.tar.gz diff --git a/src/Package.zig b/src/Package.zig index 0215db24a8cf7361c5e19f9a0f6085e86001dad7..0dca095833309542439b83270d219f6894e40502 100644 --- a/src/Package.zig +++ b/src/Package.zig @@ -6,10 +6,6 @@ pub const Fetch = @import("Package/Fetch.zig"); pub const build_zig_basename = "build.zig"; pub const Manifest = @import("Package/Manifest.zig"); -pub const multihash_len = 1 + 1 + Hash.Algo.digest_length; -pub const multihash_hex_digest_len = 2 * multihash_len; -pub const MultiHashHexDigest = [multihash_hex_digest_len]u8; - pub const Fingerprint = packed struct(u64) { id: u32, checksum: u32, @@ -77,20 +73,6 @@ pub const Hash = struct { return std.mem.eql(u8, &a.bytes, &b.bytes); } - /// Distinguishes whether the legacy multihash format is being stored here. - pub fn isOld(h: *const Hash) bool { - if (h.bytes.len < 2) return false; - const their_multihash_func = std.fmt.parseInt(u8, h.bytes[0..2], 16) catch return false; - if (@as(MultihashFunction, @enumFromInt(their_multihash_func)) != multihash_function) return false; - if (h.toSlice().len != multihash_hex_digest_len) return false; - return std.mem.indexOfScalar(u8, &h.bytes, '-') == null; - } - - test isOld { - const h: Hash = .fromSlice("1220138f4aba0c01e66b68ed9e1e1e74614c06e4743d88bc58af4f1c3dd0aae5fea7"); - try std.testing.expect(h.isOld()); - } - /// Produces "$name-$semver-$hashplus". /// * name is the name field from build.zig.zon, asserted to be at most 32 /// bytes and assumed be a valid zig identifier @@ -197,54 +179,6 @@ pub const ProjectId = struct { } }; -pub const MultihashFunction = enum(u16) { - identity = 0x00, - sha1 = 0x11, - @"sha2-256" = 0x12, - @"sha2-512" = 0x13, - @"sha3-512" = 0x14, - @"sha3-384" = 0x15, - @"sha3-256" = 0x16, - @"sha3-224" = 0x17, - @"sha2-384" = 0x20, - @"sha2-256-trunc254-padded" = 0x1012, - @"sha2-224" = 0x1013, - @"sha2-512-224" = 0x1014, - @"sha2-512-256" = 0x1015, - @"blake2b-256" = 0xb220, - _, -}; - -pub const multihash_function: MultihashFunction = switch (Hash.Algo) { - std.crypto.hash.sha2.Sha256 => .@"sha2-256", - else => unreachable, -}; - -pub fn multiHashHexDigest(digest: Hash.Digest) MultiHashHexDigest { - const hex_charset = std.fmt.hex_charset; - - var result: MultiHashHexDigest = undefined; - - result[0] = hex_charset[@intFromEnum(multihash_function) >> 4]; - result[1] = hex_charset[@intFromEnum(multihash_function) & 15]; - - result[2] = hex_charset[Hash.Algo.digest_length >> 4]; - result[3] = hex_charset[Hash.Algo.digest_length & 15]; - - for (digest, 0..) |byte, i| { - result[4 + i * 2] = hex_charset[byte >> 4]; - result[5 + i * 2] = hex_charset[byte & 15]; - } - return result; -} - -comptime { - // We avoid unnecessary uleb128 code in hexDigest by asserting here the - // values are small enough to be contained in the one-byte encoding. - assert(@intFromEnum(multihash_function) < 127); - assert(Hash.Algo.digest_length < 127); -} - test Hash { const example_digest: Hash.Digest = .{ 0xc7, 0xf5, 0x71, 0xb7, 0xb4, 0xe7, 0x6f, 0x3c, 0xdb, 0x87, 0x7a, 0x7f, 0xdd, 0xf9, 0x77, 0x87, diff --git a/src/Package/Fetch.zig b/src/Package/Fetch.zig index d597aa0a87a6114da08e9006d5862ecc87d1f65b..f51532b10596a21be4ac665d34f0600b4c78b097 100644 --- a/src/Package/Fetch.zig +++ b/src/Package/Fetch.zig @@ -779,21 +779,11 @@ fn runResource( if (remote_hash) |declared_hash| { const hash_tok = f.hash_tok.unwrap().?; - if (declared_hash.isOld()) { - const actual_hex = Package.multiHashHexDigest(f.computed_hash.digest); - if (!std.mem.eql(u8, declared_hash.toSlice(), &actual_hex)) { - return f.fail(hash_tok, try eb.printString( - "hash mismatch: manifest declares '{s}' but the fetched package has '{s}'", - .{ declared_hash.toSlice(), actual_hex }, - )); - } - } else { - if (!computed_package_hash.eql(&declared_hash)) { - return f.fail(hash_tok, try eb.printString( - "hash mismatch: manifest declares '{s}' but the fetched package has '{s}'", - .{ declared_hash.toSlice(), computed_package_hash.toSlice() }, - )); - } + if (!computed_package_hash.eql(&declared_hash)) { + return f.fail(hash_tok, try eb.printString( + "hash mismatch: manifest declares '{s}' but the fetched package has '{s}'", + .{ declared_hash.toSlice(), computed_package_hash.toSlice() }, + )); } } else if (!f.omit_missing_hash_error) { const notes_len = 1; @@ -2239,207 +2229,6 @@ const UnpackResult = struct { } }; -test "set executable bit based on file content" { - if (!Io.File.Permissions.has_executable_bit) return error.SkipZigTest; - const gpa = std.testing.allocator; - const io = std.testing.io; - - var tmp = std.testing.tmpDir(.{}); - defer tmp.cleanup(); - - const tarball_name = "executables.tar.gz"; - try saveEmbedFile(io, tarball_name, tmp.dir); - const tarball_path = try std.fmt.allocPrint(gpa, ".zig-cache/tmp/{s}/{s}", .{ tmp.sub_path, tarball_name }); - defer gpa.free(tarball_path); - - // $ tar -tvf executables.tar.gz - // drwxrwxr-x 0 executables/ - // -rwxrwxr-x 170 executables/hello - // lrwxrwxrwx 0 executables/hello_ln -> hello - // -rw-rw-r-- 0 executables/file1 - // -rw-rw-r-- 17 executables/script_with_shebang_without_exec_bit - // -rwxrwxr-x 7 executables/script_without_shebang - // -rwxrwxr-x 17 executables/script - - var fb: TestFetchBuilder = undefined; - var fetch = try fb.build(gpa, io, tmp.dir, tarball_path); - defer fb.deinit(); - - try fetch.run(); - try std.testing.expectEqualStrings( - "1220fecb4c06a9da8673c87fe8810e15785f1699212f01728eadce094d21effeeef3", - &Package.multiHashHexDigest(fetch.computed_hash.digest), - ); - - var out = try fb.packageDir(); - defer out.close(io); - const S = std.posix.S; - // expect executable bit not set - try std.testing.expect((try out.statFile(io, "file1", .{})).permissions.toMode() & S.IXUSR == 0); - try std.testing.expect((try out.statFile(io, "script_without_shebang", .{})).permissions.toMode() & S.IXUSR == 0); - // expect executable bit set - try std.testing.expect((try out.statFile(io, "hello", .{})).permissions.toMode() & S.IXUSR != 0); - try std.testing.expect((try out.statFile(io, "script", .{})).permissions.toMode() & S.IXUSR != 0); - try std.testing.expect((try out.statFile(io, "script_with_shebang_without_exec_bit", .{})).permissions.toMode() & S.IXUSR != 0); - try std.testing.expect((try out.statFile(io, "hello_ln", .{})).permissions.toMode() & S.IXUSR != 0); - - // - // $ ls -al zig-cache/tmp/OCz9ovUcstDjTC_U/zig-global-cache/p/1220fecb4c06a9da8673c87fe8810e15785f1699212f01728eadce094d21effeeef3 - // -rw-rw-r-- 1 0 Apr file1 - // -rwxrwxr-x 1 170 Apr hello - // lrwxrwxrwx 1 5 Apr hello_ln -> hello - // -rwxrwxr-x 1 17 Apr script - // -rw-rw-r-- 1 7 Apr script_without_shebang - // -rwxrwxr-x 1 17 Apr script_with_shebang_without_exec_bit -} - -fn saveEmbedFile(io: Io, comptime tarball_name: []const u8, dir: Io.Dir) !void { - //const tarball_name = "duplicate_paths_excluded.tar.gz"; - const tarball_content = @embedFile("Fetch/testdata/" ++ tarball_name); - var tmp_file = try dir.createFile(io, tarball_name, .{}); - defer tmp_file.close(io); - try tmp_file.writeStreamingAll(io, tarball_content); -} - -// Builds Fetch with required dependencies, clears dependencies on deinit(). -const TestFetchBuilder = struct { - http_client: std.http.Client, - global_cache_directory: Cache.Directory, - local_cache_path: Cache.Path, - job_queue: Fetch.JobQueue, - fetch: Fetch, - - fn build( - self: *TestFetchBuilder, - allocator: std.mem.Allocator, - io: Io, - cache_parent_dir: std.Io.Dir, - path_or_url: []const u8, - ) !*Fetch { - const global_cache_dir = try cache_parent_dir.createDirPathOpen(io, "zig-global-cache", .{}); - const package_root_dir = try cache_parent_dir.createDirPathOpen(io, "local-project-root", .{}); - - self.http_client = .{ .allocator = allocator, .io = io }; - self.global_cache_directory = .{ .handle = global_cache_dir, .path = "zig-global-cache" }; - self.local_cache_path = .{ - .root_dir = .{ .handle = package_root_dir, .path = "local-project-root" }, - .sub_path = ".zig-cache", - }; - - self.job_queue = .{ - .io = io, - .http_client = &self.http_client, - .global_cache = self.global_cache_directory, - .local_cache = self.local_cache_path, - .root_pkg_path = .{ - .root_dir = .{ .handle = package_root_dir, .path = "local-project-root" }, - .sub_path = "zig-pkg", - }, - .recursive = false, - .read_only = false, - .debug_hash = false, - .mode = .needed, - .prog_node = std.Progress.Node.none, - }; - - self.fetch = .{ - .arena = std.heap.ArenaAllocator.init(allocator), - .location = .{ .path_or_url = path_or_url }, - .location_tok = 0, - .hash_tok = .none, - .name_tok = 0, - .lazy_status = .eager, - .parent_package_root = .{ .root_dir = .{ .handle = package_root_dir, .path = null } }, - .parent_manifest_ast = null, - .prog_node = std.Progress.Node.none, - .job_queue = &self.job_queue, - .omit_missing_hash_error = true, - .allow_missing_paths_field = false, - .use_latest_commit = true, - - .package_root = undefined, - .error_bundle = undefined, - .manifest = undefined, - .manifest_ast = undefined, - .have_manifest = false, - .computed_hash = undefined, - .has_build_zig = false, - .oom_flag = false, - .latest_commit = null, - - .module = null, - }; - return &self.fetch; - } - - fn deinit(self: *TestFetchBuilder) void { - const io = self.job_queue.io; - self.fetch.deinit(); - self.job_queue.deinit(); - self.fetch.prog_node.end(); - self.global_cache_directory.handle.close(io); - self.http_client.deinit(); - } - - fn packageDir(self: *TestFetchBuilder) !Io.Dir { - const io = self.job_queue.io; - const root = self.fetch.package_root; - return try root.root_dir.handle.openDir(io, root.sub_path, .{ .iterate = true }); - } - - // Test helper, asserts thet package dir constains expected_files. - // expected_files must be sorted. - fn expectPackageFiles(self: *TestFetchBuilder, expected_files: []const []const u8) !void { - const io = self.job_queue.io; - const gpa = std.testing.allocator; - - var package_dir = try self.packageDir(); - defer package_dir.close(io); - - var actual_files: std.ArrayList([]u8) = .empty; - defer actual_files.deinit(gpa); - defer for (actual_files.items) |file| gpa.free(file); - var walker = try package_dir.walk(gpa); - defer walker.deinit(); - while (try walker.next(io)) |entry| { - if (entry.kind != .file) continue; - const path = try gpa.dupe(u8, entry.path); - errdefer gpa.free(path); - std.mem.replaceScalar(u8, path, std.fs.path.sep, '/'); - try actual_files.append(gpa, path); - } - std.mem.sortUnstable([]u8, actual_files.items, {}, struct { - fn lessThan(_: void, a: []u8, b: []u8) bool { - return std.mem.lessThan(u8, a, b); - } - }.lessThan); - - try std.testing.expectEqual(expected_files.len, actual_files.items.len); - for (expected_files, 0..) |file_name, i| { - try std.testing.expectEqualStrings(file_name, actual_files.items[i]); - } - try std.testing.expectEqualDeep(expected_files, actual_files.items); - } - - // Test helper, asserts that fetch has failed with `msg` error message. - fn expectFetchErrors(self: *TestFetchBuilder, notes_len: usize, msg: []const u8) !void { - const gpa = std.testing.allocator; - - var errors = try self.fetch.error_bundle.toOwnedBundle(""); - defer errors.deinit(gpa); - - const em = errors.getErrorMessage(errors.getMessages()[0]); - try std.testing.expectEqual(1, em.count); - if (notes_len > 0) { - try std.testing.expectEqual(notes_len, em.notes_len); - } - var aw: Io.Writer.Allocating = .init(gpa); - defer aw.deinit(); - try errors.renderToWriter(.{}, &aw.writer); - try std.testing.expectEqualStrings(msg, aw.written()); - } -}; - test { _ = Filter; _ = FileType; diff --git a/src/Package/Fetch/testdata/executables.tar.gz b/src/Package/Fetch/testdata/executables.tar.gz deleted file mode 100644 index abc650801ea6d416ad3cef2b87a6df205c71663b..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 430 zcmV;f0a5-RiwFP!000001MQd5PJ=KMhPx%ZSz_iR)Of2|TPXFynB8c+@Er)uDkKdv zgJrw(NqhmHVDUm9P1_Af<5Z>&66JgsoE&I#AV2?UD;IJ+%YsnJbckw#XoTP&;KaW(m#?)O=_r7r9a~=*PnC3 zsn*|dTo++d!@F$Ia{cemuP(ZsPMLSn88X=djaK>SgE=E~f{Ga?_TD|U<71u`5$&(T z(oB3Ym&@meeC*Snz6^THQ*)QT4tlE}@(9l-Q+3_{{w#4I{AUxm)Q^7%}~y>E8hQe*@V1r{igSm-+KJi~KZ_ zVUXOelF2Od%{V`dv!B$c)L)BT?*Cl9PheCK0s1$=;lVJBlVKY5Rf>-T1 Date: Fri, 6 Feb 2026 04:36:32 -0500 Subject: [PATCH 229/499] std.Threaded: replace more kernel32 functions with ntdll --- ci/x86_64-windows-debug.ps1 | 35 +- ci/x86_64-windows-release.ps1 | 37 +- lib/compiler/build_runner.zig | 2 +- lib/fuzzer.zig | 2 +- lib/std/Build/Watch.zig | 278 ++-- lib/std/Io/Threaded.zig | 990 +++++++-------- lib/std/Io/Threaded/test.zig | 2 +- lib/std/Thread.zig | 16 +- lib/std/debug.zig | 45 +- lib/std/debug/Dwarf/SelfUnwinder.zig | 3 +- lib/std/debug/SelfInfo/Elf.zig | 16 +- lib/std/debug/SelfInfo/MachO.zig | 20 +- lib/std/debug/SelfInfo/Windows.zig | 237 ++-- lib/std/heap.zig | 6 +- lib/std/os/windows.zig | 1693 +++++++++---------------- lib/std/os/windows/kernel32.zig | 201 +-- lib/std/os/windows/ntdll.zig | 247 ++-- lib/std/os/windows/win32error.zig | 2 +- lib/std/process.zig | 8 +- lib/std/process/Child.zig | 2 +- lib/std/start.zig | 14 +- lib/std/zig/system/windows.zig | 10 +- lib/zig.h | 28 +- stage1/zig.h | 28 +- test/standalone/coff_dwarf/main.zig | 7 +- test/standalone/windows_argv/fuzz.zig | 18 +- test/standalone/windows_argv/lib.zig | 6 +- 27 files changed, 1693 insertions(+), 2260 deletions(-) diff --git a/ci/x86_64-windows-debug.ps1 b/ci/x86_64-windows-debug.ps1 index e6ba8b0861b18003daaca0ab1974dc8f3cfb6ad7..b1486f31bc6f6d57de99ea640bf44865e43861a4 100644 --- a/ci/x86_64-windows-debug.ps1 +++ b/ci/x86_64-windows-debug.ps1 @@ -35,7 +35,7 @@ Set-Location -Path 'build-debug' # CMake gives a syntax error when file paths with backward slashes are used. # Here, we use forward slashes only to work around this. -& cmake .. ` +cmake .. ` -GNinja ` -DCMAKE_INSTALL_PREFIX="stage3-debug" ` -DCMAKE_PREFIX_PATH="$($PREFIX_PATH -Replace "\\", "/")" ` @@ -54,7 +54,7 @@ ninja install CheckLastExitCode Write-Output "Main test suite..." -& "stage3-debug\bin\zig.exe" build test docs ` +stage3-debug\bin\zig build test docs ` --maxrss $ZSF_MAX_RSS ` --zig-lib-dir "$ZIG_LIB_DIR" ` --search-prefix "$PREFIX_PATH" ` @@ -66,26 +66,25 @@ Write-Output "Main test suite..." CheckLastExitCode Write-Output "Build x86_64-windows-msvc behavior tests using the C backend..." -& "stage3-debug\bin\zig.exe" test ` - ..\test\behavior.zig ` - --zig-lib-dir "$ZIG_LIB_DIR" ` - -ofmt=c ` - -femit-bin="test-x86_64-windows-msvc.c" ` - --test-no-exec ` - -target x86_64-windows-msvc ` - -lc -CheckLastExitCode - -& "stage3-debug\bin\zig.exe" build-obj ` +stage3-debug\bin\zig build-obj ` --zig-lib-dir "$ZIG_LIB_DIR" ` -ofmt=c ` -OReleaseSmall ` --name compiler_rt ` -femit-bin="compiler_rt-x86_64-windows-msvc.c" ` - --dep build_options ` -target x86_64-windows-msvc ` - -Mroot="..\lib\compiler_rt.zig" ` - -Mbuild_options="config.zig" + -lc ` + ..\lib\compiler_rt.zig +CheckLastExitCode + +stage3-debug\bin\zig test ` + --zig-lib-dir "$ZIG_LIB_DIR" ` + -ofmt=c ` + -femit-bin="behavior-x86_64-windows-msvc.c" ` + --test-no-exec ` + -target x86_64-windows-msvc ` + -lc ` + ..\test\behavior.zig CheckLastExitCode Import-Module "C:\Program Files (x86)\Microsoft Visual Studio\2022\BuildTools\Common7\Tools\Microsoft.VisualStudio.DevShell.dll" @@ -97,8 +96,8 @@ Enter-VsDevShell -VsInstallPath "C:\Program Files (x86)\Microsoft Visual Studio\ CheckLastExitCode Write-Output "Build and run behavior tests with msvc..." -& cl.exe -I..\lib test-x86_64-windows-msvc.c compiler_rt-x86_64-windows-msvc.c /W3 /Z7 -link -nologo -debug -subsystem:console kernel32.lib ntdll.lib libcmt.lib ws2_32.lib +cl /I..\lib /W3 /Z7 behavior-x86_64-windows-msvc.c compiler_rt-x86_64-windows-msvc.c /link /nologo /debug /subsystem:console kernel32.lib ntdll.lib libcmt.lib ws2_32.lib CheckLastExitCode -& .\test-x86_64-windows-msvc.exe +.\behavior-x86_64-windows-msvc CheckLastExitCode diff --git a/ci/x86_64-windows-release.ps1 b/ci/x86_64-windows-release.ps1 index e22e3af1c79d670d7e488664a715b21f2b9331ef..a8674d1d3dcc6b57e775bd9dec11faf04f1279ab 100644 --- a/ci/x86_64-windows-release.ps1 +++ b/ci/x86_64-windows-release.ps1 @@ -35,7 +35,7 @@ Set-Location -Path 'build-release' # CMake gives a syntax error when file paths with backward slashes are used. # Here, we use forward slashes only to work around this. -& cmake .. ` +cmake .. ` -GNinja ` -DCMAKE_INSTALL_PREFIX="stage3-release" ` -DCMAKE_PREFIX_PATH="$($PREFIX_PATH -Replace "\\", "/")" ` @@ -54,7 +54,7 @@ ninja install CheckLastExitCode Write-Output "Main test suite..." -& "stage3-release\bin\zig.exe" build test docs ` +stage3-release\bin\zig.exe build test docs ` --maxrss $ZSF_MAX_RSS ` --zig-lib-dir "$ZIG_LIB_DIR" ` --search-prefix "$PREFIX_PATH" ` @@ -67,7 +67,7 @@ CheckLastExitCode # Ensure that stage3 and stage4 are byte-for-byte identical. Write-Output "Build and compare stage4..." -& "stage3-release\bin\zig.exe" build ` +stage3-release\bin\zig.exe build ` --prefix stage4-release ` -Denable-llvm ` -Dno-lib ` @@ -85,26 +85,25 @@ Compare-Object (Get-Content stage3-release\bin\zig.exe) (Get-Content stage4-rele CheckLastExitCode Write-Output "Build x86_64-windows-msvc behavior tests using the C backend..." -& "stage3-release\bin\zig.exe" test ` - ..\test\behavior.zig ` - --zig-lib-dir "$ZIG_LIB_DIR" ` - -ofmt=c ` - -femit-bin="test-x86_64-windows-msvc.c" ` - --test-no-exec ` - -target x86_64-windows-msvc ` - -lc -CheckLastExitCode - -& "stage3-release\bin\zig.exe" build-obj ` +stage3-release\bin\zig.exe build-obj ` --zig-lib-dir "$ZIG_LIB_DIR" ` -ofmt=c ` -OReleaseSmall ` --name compiler_rt ` -femit-bin="compiler_rt-x86_64-windows-msvc.c" ` - --dep build_options ` -target x86_64-windows-msvc ` - -Mroot="..\lib\compiler_rt.zig" ` - -Mbuild_options="config.zig" + -lc ` + ..\lib\compiler_rt.zig +CheckLastExitCode + +stage3-release\bin\zig.exe test ` + --zig-lib-dir "$ZIG_LIB_DIR" ` + -ofmt=c ` + -femit-bin="behavior-x86_64-windows-msvc.c" ` + --test-no-exec ` + -target x86_64-windows-msvc ` + -lc ` + ..\test\behavior.zig CheckLastExitCode Import-Module "C:\Program Files (x86)\Microsoft Visual Studio\2022\BuildTools\Common7\Tools\Microsoft.VisualStudio.DevShell.dll" @@ -116,8 +115,8 @@ Enter-VsDevShell -VsInstallPath "C:\Program Files (x86)\Microsoft Visual Studio\ CheckLastExitCode Write-Output "Build and run behavior tests with msvc..." -& cl.exe -I..\lib test-x86_64-windows-msvc.c compiler_rt-x86_64-windows-msvc.c /W3 /Z7 -link -nologo -debug -subsystem:console kernel32.lib ntdll.lib libcmt.lib ws2_32.lib +cl /I..\lib /W3 /Z7 behavior-x86_64-windows-msvc.c compiler_rt-x86_64-windows-msvc.c /link /nologo /debug /subsystem:console kernel32.lib ntdll.lib libcmt.lib ws2_32.lib CheckLastExitCode -& .\test-x86_64-windows-msvc.exe +.\behavior-x86_64-windows-msvc CheckLastExitCode diff --git a/lib/compiler/build_runner.zig b/lib/compiler/build_runner.zig index 4aa513ec74caa2ecb606078b181fcba8227d2de8..faf81194a993db9e0ff90e7a34269a57a216f653 100644 --- a/lib/compiler/build_runner.zig +++ b/lib/compiler/build_runner.zig @@ -621,7 +621,7 @@ pub fn main(init: process.Init.Minimal) !void { }) catch &caption_buf; var debouncing_node = main_progress_node.start(caption, 0); var in_debounce = false; - while (true) switch (try w.wait(gpa, if (in_debounce) .{ .ms = debounce_interval_ms } else .none)) { + while (true) switch (try w.wait(gpa, io, if (in_debounce) .{ .ms = debounce_interval_ms } else .none)) { .timeout => { assert(in_debounce); debouncing_node.end(); diff --git a/lib/fuzzer.zig b/lib/fuzzer.zig index 37ca752d1fa962d0b97d9035fb5823c6e0b61507..9e0c5c6ad649e7cdd454ed6725aec751a490ba77 100644 --- a/lib/fuzzer.zig +++ b/lib/fuzzer.zig @@ -632,7 +632,7 @@ export fn fuzzer_main(limit_kind: abi.LimitKind, amount: u64) void { export fn fuzzer_unslide_address(addr: usize) usize { const si = std.debug.getSelfDebugInfo() catch @compileError("unsupported"); - const slide = si.getModuleSlide(std.debug.getDebugInfoAllocator(), io, addr) catch |err| { + const slide = si.getModuleSlide(io, addr) catch |err| { std.debug.panic("failed to find virtual address slide: {t}", .{err}); }; return addr - slide; diff --git a/lib/std/Build/Watch.zig b/lib/std/Build/Watch.zig index 15ccfcfdf9cde96d937b3fde8424c2e1bfbf200d..c559210b9268d9c0cc44c52f765b81af39b87652 100644 --- a/lib/std/Build/Watch.zig +++ b/lib/std/Build/Watch.zig @@ -180,7 +180,7 @@ const Os = switch (builtin.os.tag) { const gop = try w.dir_table.getOrPut(gpa, path); if (!gop.found_existing) { var mount_id: MountId = undefined; - const dir_handle = Os.getDirHandle(gpa, path, &mount_id) catch |err| switch (err) { + const dir_handle = getDirHandle(gpa, path, &mount_id) catch |err| switch (err) { error.FileNotFound => { std.debug.assert(w.dir_table.swapRemove(path)); continue; @@ -291,12 +291,13 @@ const Os = switch (builtin.os.tag) { w.dir_count = w.dir_table.count(); } - fn wait(w: *Watch, gpa: Allocator, timeout: Timeout) !WaitResult { + fn wait(w: *Watch, gpa: Allocator, io: Io, timeout: Timeout) !WaitResult { + _ = io; const events_len = try std.posix.poll(w.os.poll_fds.values(), timeout.to_i32_ms()); if (events_len == 0) return .timeout; for (w.os.poll_fds.values()) |poll_fd| { - if (poll_fd.revents & std.posix.POLL.IN == std.posix.POLL.IN and try Os.markDirtySteps(w, gpa, poll_fd.fd)) + if (poll_fd.revents & std.posix.POLL.IN == std.posix.POLL.IN and try markDirtySteps(w, gpa, poll_fd.fd)) return .dirty; } return .clean; @@ -306,12 +307,8 @@ const Os = switch (builtin.os.tag) { const windows = std.os.windows; /// Keyed differently but indexes correspond 1:1 with `dir_table`. - handle_table: HandleTable, - dir_list: std.AutoArrayHashMapUnmanaged(usize, *Directory), - io_cp: ?windows.HANDLE, - counter: usize = 0, - - const HandleTable = std.AutoArrayHashMapUnmanaged(FileId, ReactionSet); + handle_table: std.ArrayHashMapUnmanaged(*Directory, void, Directory.TableAdapter, false), + ready_dirs: std.DoublyLinkedList, const FileId = struct { volumeSerialNumber: windows.ULONG, @@ -319,55 +316,61 @@ const Os = switch (builtin.os.tag) { }; const Directory = struct { - handle: windows.HANDLE, + reaction_set: ReactionSet, id: FileId, - overlapped: windows.OVERLAPPED, + file: Io.File, + state: enum { idle, listening, ready }, + iosb: windows.IO_STATUS_BLOCK, // 64 KB is the packet size limit when monitoring over a network. // https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-readdirectorychangesw#remarks - buffer: [64 * 1024]u8 align(@alignOf(windows.FILE_NOTIFY_INFORMATION)) = undefined, + buffer: [64 * 1024]u8 align(@alignOf(windows.FILE.NOTIFY.INFORMATION)), + ready_node: std.DoublyLinkedList.Node, /// Start listening for events, buffer field will be overwritten eventually. - fn startListening(self: *@This()) !void { - const r = windows.kernel32.ReadDirectoryChangesW( - self.handle, - @ptrCast(&self.buffer), - self.buffer.len, - 0, + fn startListening(dir: *Directory, w: *Watch) !void { + assert(dir.file.flags.nonblocking); + assert(dir.state == .idle); + switch (windows.ntdll.NtNotifyChangeDirectoryFileEx( + dir.file.handle, + null, + ¬ifyApc, + w, + &dir.iosb, + &dir.buffer, + dir.buffer.len, .{ - .creation = true, - .dir_name = true, - .file_name = true, - .last_write = true, - .size = true, + .FILE_NAME = true, + .DIR_NAME = true, + .SIZE = true, + .LAST_WRITE = true, + .CREATION = true, }, - null, - &self.overlapped, - null, - ); - if (r == windows.FALSE) { - switch (windows.GetLastError()) { - .INVALID_FUNCTION => return error.ReadDirectoryChangesUnsupported, - else => |err| return windows.unexpectedError(err), - } + windows.FALSE, + .Notify, + )) { + .SUCCESS, .PENDING => dir.state = .listening, + .ILLEGAL_FUNCTION => return error.ReadDirectoryChangesUnsupported, + else => |status| return windows.unexpectedStatus(status), } } - fn init(gpa: Allocator, path: Cache.Path) !*@This() { + fn notifyApc(apc_context: ?*anyopaque, iosb: *windows.IO_STATUS_BLOCK, _: windows.ULONG) callconv(.winapi) void { + const w: *Watch = @ptrCast(@alignCast(apc_context)); + const dir: *Directory = @fieldParentPtr("iosb", iosb); + assert(iosb.u.Status != .PENDING); + assert(dir.state == .listening); + w.os.ready_dirs.append(&dir.ready_node); + dir.state = .ready; + } + + fn init(gpa: Allocator, path: Cache.Path) !*Directory { // The following code is a drawn out NtCreateFile call. (mostly adapted from Io.Dir.makeOpenDirAccessMaskW) // It's necessary in order to get the specific flags that are required when calling ReadDirectoryChangesW. var dir_handle: windows.HANDLE = undefined; const root_fd = path.root_dir.handle.handle; const sub_path = path.subPathOrDot(); - const sub_path_w = try std.Io.Threaded.sliceToPrefixedFileW(root_fd, sub_path); // TODO eliminate this call - const path_len_bytes = std.math.cast(u16, sub_path_w.len * 2) orelse return error.NameTooLong; - - var nt_name = windows.UNICODE_STRING{ - .Length = @intCast(path_len_bytes), - .MaximumLength = @intCast(path_len_bytes), - .Buffer = @constCast(sub_path_w.span().ptr), - }; + const sub_path_w = try Io.Threaded.sliceToPrefixedFileW(root_fd, sub_path); // TODO eliminate this call var iosb: windows.IO_STATUS_BLOCK = undefined; - switch (windows.ntdll.NtCreateFile( &dir_handle, .{ @@ -379,7 +382,7 @@ const Os = switch (builtin.os.tag) { }, &.{ .RootDirectory = if (std.fs.path.isAbsoluteWindowsW(sub_path_w.span())) null else root_fd, - .ObjectName = &nt_name, + .ObjectName = @constCast(&sub_path_w.string()), }, &iosb, null, @@ -410,20 +413,49 @@ const Os = switch (builtin.os.tag) { const dir_id = try getFileId(dir_handle); - const dir_ptr = try gpa.create(@This()); - dir_ptr.* = .{ - .handle = dir_handle, + const dir = try gpa.create(Directory); + dir.* = .{ + .reaction_set = .empty, .id = dir_id, - .overlapped = std.mem.zeroes(windows.OVERLAPPED), + .file = .{ .handle = dir_handle, .flags = .{ .nonblocking = true } }, + .state = .idle, + .iosb = undefined, + .buffer = undefined, + .ready_node = undefined, }; - return dir_ptr; + return dir; } - fn deinit(self: *@This(), gpa: Allocator) void { - _ = windows.kernel32.CancelIo(self.handle); - windows.CloseHandle(self.handle); - gpa.destroy(self); + fn deinit(dir: *Directory, gpa: Allocator, w: *Watch) void { + state: switch (dir.state) { + .idle => {}, + .listening => { + var cancel_iosb: windows.IO_STATUS_BLOCK = undefined; + _ = windows.ntdll.NtCancelIoFileEx(dir.file.handle, &dir.iosb, &cancel_iosb); + while (switch (dir.state) { + .idle => unreachable, + .listening => true, + .ready => false, + }) Io.Threaded.waitForApcOrAlert(); + continue :state .ready; + }, + .ready => w.os.ready_dirs.remove(&dir.ready_node), + } + windows.CloseHandle(dir.file.handle); + gpa.destroy(dir); } + + /// Useful to make `*Directory` a key in `std.ArrayHashMap`. + const TableAdapter = struct { + pub fn hash(_: TableAdapter, lhs_dir: *Directory) u32 { + return @truncate(Hash.hash(lhs_dir.id.volumeSerialNumber, @ptrCast(&lhs_dir.id.indexNumber))); + } + pub fn eql(_: TableAdapter, lhs_dir: *Directory, rhs_dir: *Directory, rhs_index: usize) bool { + _ = rhs_index; + return lhs_dir.id.volumeSerialNumber == rhs_dir.id.volumeSerialNumber and + lhs_dir.id.indexNumber == rhs_dir.id.indexNumber; + } + }; }; fn init(cwd_path: []const u8) !Watch { @@ -433,9 +465,8 @@ const Os = switch (builtin.os.tag) { .dir_count = 0, .os = switch (builtin.os.tag) { .windows => .{ - .handle_table = .{}, - .dir_list = .{}, - .io_cp = null, + .handle_table = .empty, + .ready_dirs = .{}, }, else => {}, }, @@ -479,29 +510,22 @@ const Os = switch (builtin.os.tag) { fn markDirtySteps(w: *Watch, gpa: Allocator, dir: *Directory) !bool { var any_dirty = false; - const bytes_returned = try windows.GetOverlappedResult(dir.handle, &dir.overlapped, false); + const bytes_returned = dir.iosb.Information; if (bytes_returned == 0) { std.log.warn("file system watch queue overflowed; falling back to fstat", .{}); markAllFilesDirty(w, gpa); - try dir.startListening(); + try dir.startListening(w); return true; } var file_name_buf: [std.fs.max_path_bytes]u8 = undefined; - var notify: *align(1) windows.FILE_NOTIFY_INFORMATION = undefined; var offset: usize = 0; while (true) { - notify = @ptrCast(&dir.buffer[offset]); - const file_name_field: [*]u16 = @ptrFromInt(@intFromPtr(notify) + @sizeOf(windows.FILE_NOTIFY_INFORMATION)); - const file_name_len = std.unicode.wtf16LeToWtf8(&file_name_buf, file_name_field[0 .. notify.FileNameLength / 2]); - const file_name = file_name_buf[0..file_name_len]; - if (w.os.handle_table.getIndex(dir.id)) |reaction_set_i| { - const reaction_set = w.os.handle_table.values()[reaction_set_i]; - if (reaction_set.getPtr(".")) |glob_set| - any_dirty = markStepSetDirty(gpa, glob_set, any_dirty); - if (reaction_set.getPtr(file_name)) |step_set| { - any_dirty = markStepSetDirty(gpa, step_set, any_dirty); - } - } + const notify: *windows.FILE.NOTIFY.INFORMATION = @ptrCast(@alignCast(&dir.buffer[offset])); + const file_name = file_name_buf[0..std.unicode.wtf16LeToWtf8(&file_name_buf, notify.fileName())]; + if (dir.reaction_set.getPtr(".")) |glob_set| + any_dirty = markStepSetDirty(gpa, glob_set, any_dirty); + if (dir.reaction_set.getPtr(file_name)) |step_set| + any_dirty = markStepSetDirty(gpa, step_set, any_dirty); if (notify.NextEntryOffset == 0) break; @@ -509,7 +533,7 @@ const Os = switch (builtin.os.tag) { } // We call this now since at this point we have finished reading dir.buffer. - try dir.startListening(); + try dir.startListening(w); return any_dirty; } @@ -517,41 +541,32 @@ const Os = switch (builtin.os.tag) { // Add missing marks and note persisted ones. for (steps) |step| { for (step.inputs.table.keys(), step.inputs.table.values()) |path, *files| { - const reaction_set = rs: { + const dir = dir: { const gop = try w.dir_table.getOrPut(gpa, path); if (!gop.found_existing) { - const dir = try Os.Directory.init(gpa, path); - errdefer dir.deinit(gpa); + const dir: *Directory = try .init(gpa, path); + errdefer dir.deinit(gpa, w); // `dir.id` may already be present in the table in // the case that we have multiple Cache.Path instances // that compare inequal but ultimately point to the same // directory on the file system. // In such case, we must revert adding this directory, but keep // the additions to the step set. - const dh_gop = try w.os.handle_table.getOrPut(gpa, dir.id); + const dh_gop = try w.os.handle_table.getOrPut(gpa, dir); if (dh_gop.found_existing) { - dir.deinit(gpa); + dir.deinit(gpa, w); _ = w.dir_table.pop(); + break :dir w.os.handle_table.keys()[dh_gop.index]; } else { assert(dh_gop.index == gop.index); - dh_gop.value_ptr.* = .{}; - try dir.startListening(); - const key = w.os.counter; - w.os.counter +%= 1; - try w.os.dir_list.put(gpa, key, dir); - w.os.io_cp = try windows.CreateIoCompletionPort( - dir.handle, - w.os.io_cp, - key, - 0, - ); + try dir.startListening(w); + break :dir dir; } - break :rs &w.os.handle_table.values()[dh_gop.index]; } - break :rs &w.os.handle_table.values()[gop.index]; + break :dir w.os.handle_table.keys()[gop.index]; }; for (files.items) |basename| { - const gop = try reaction_set.getOrPut(gpa, basename); + const gop = try dir.reaction_set.getOrPut(gpa, basename); if (!gop.found_existing) gop.value_ptr.* = .{}; try gop.value_ptr.put(gpa, step, w.generation); } @@ -562,11 +577,11 @@ const Os = switch (builtin.os.tag) { // Remove marks for files that are no longer inputs. var i: usize = 0; while (i < w.os.handle_table.entries.len) { + const dir = w.os.handle_table.keys()[i]; { - const reaction_set = &w.os.handle_table.values()[i]; var step_set_i: usize = 0; - while (step_set_i < reaction_set.entries.len) { - const step_set = &reaction_set.values()[step_set_i]; + while (step_set_i < dir.reaction_set.entries.len) { + const step_set = &dir.reaction_set.values()[step_set_i]; var dirent_i: usize = 0; while (dirent_i < step_set.entries.len) { const generations = step_set.values(); @@ -580,53 +595,45 @@ const Os = switch (builtin.os.tag) { step_set_i += 1; continue; } - reaction_set.swapRemoveAt(step_set_i); + dir.reaction_set.swapRemoveAt(step_set_i); } - if (reaction_set.entries.len > 0) { + if (dir.reaction_set.entries.len > 0) { i += 1; continue; } } - w.os.dir_list.values()[i].deinit(gpa); - w.os.dir_list.swapRemoveAt(i); w.dir_table.swapRemoveAt(i); w.os.handle_table.swapRemoveAt(i); + dir.deinit(gpa, w); } w.generation +%= 1; } w.dir_count = w.dir_table.count(); } - fn wait(w: *Watch, gpa: Allocator, timeout: Timeout) !WaitResult { - var bytes_transferred: std.os.windows.DWORD = undefined; - var key: usize = undefined; - var overlapped_ptr: ?*std.os.windows.OVERLAPPED = undefined; - return while (true) switch (std.os.windows.GetQueuedCompletionStatus( - w.os.io_cp.?, - &bytes_transferred, - &key, - &overlapped_ptr, - @bitCast(timeout.to_i32_ms()), - )) { - .Normal => { - if (bytes_transferred == 0) - break error.Unexpected; - - // This 'orelse' detects a race condition that happens when we receive a - // completion notification for a directory that no longer exists in our list. - const dir = w.os.dir_list.get(key) orelse break .clean; - - break if (try Os.markDirtySteps(w, gpa, dir)) - .dirty - else - .clean; - }, - .Timeout => break .timeout, - // This status is issued because CancelIo was called, skip and try again. - .Canceled => continue, - else => break error.Unexpected, - }; + fn wait(w: *Watch, gpa: Allocator, io: Io, timeout: Timeout) !WaitResult { + for (0..2) |attempt| { + while (w.os.ready_dirs.popFirst()) |ready_node| { + const dir: *Directory = @fieldParentPtr("ready_node", ready_node); + assert(dir.state == .ready); + dir.state = .idle; + switch (dir.iosb.u.Status) { + .SUCCESS => return if (try markDirtySteps(w, gpa, dir)) .dirty else .clean, + .PENDING => unreachable, + .CANCELLED => {}, + else => |status| return windows.unexpectedStatus(status), + } + try dir.startListening(w); + } + try io.checkCancel(); + if (attempt == 1) return .timeout; + const delay_interval: windows.LARGE_INTEGER = switch (timeout) { + .none => std.math.minInt(windows.LARGE_INTEGER), + .ms => |ms| -@as(windows.LARGE_INTEGER, ms) * (std.time.ns_per_ms / 100), + }; + _ = windows.ntdll.NtDelayExecution(windows.TRUE, &delay_interval); + } else unreachable; } }, .dragonfly, .freebsd, .netbsd, .openbsd, .ios, .tvos, .visionos, .watchos => struct { @@ -796,7 +803,8 @@ const Os = switch (builtin.os.tag) { w.dir_count = w.dir_table.count(); } - fn wait(w: *Watch, gpa: Allocator, timeout: Timeout) !WaitResult { + fn wait(w: *Watch, gpa: Allocator, io: Io, timeout: Timeout) !WaitResult { + _ = io; var timespec_buffer: posix.timespec = undefined; var event_buffer: [100]posix.Kevent = undefined; var n = try Io.Kqueue.kevent(w.os.kq_fd, &.{}, &event_buffer, timeout.toTimespec(×pec_buffer)); @@ -852,7 +860,8 @@ const Os = switch (builtin.os.tag) { try w.os.fse.setPaths(gpa, steps); w.dir_count = w.os.fse.watch_roots.len; } - fn wait(w: *Watch, gpa: Allocator, timeout: Timeout) !WaitResult { + fn wait(w: *Watch, gpa: Allocator, io: Io, timeout: Timeout) !WaitResult { + _ = io; return w.os.fse.wait(gpa, switch (timeout) { .none => null, .ms => |ms| @as(u64, ms) * std.time.ns_per_ms, @@ -890,10 +899,13 @@ pub const Match = struct { }; fn markAllFilesDirty(w: *Watch, gpa: Allocator) void { - for (w.os.handle_table.values()) |value| { + for (switch (builtin.os.tag) { + .windows => w.os.handle_table.keys(), + else => w.os.handle_table.values(), + }) |item| { const reaction_set = switch (builtin.os.tag) { - .linux => value.reaction_set, - else => value, + .linux, .windows => item.reaction_set, + else => item, }; for (reaction_set.values()) |step_set| { for (step_set.keys()) |step| { @@ -951,6 +963,6 @@ pub const WaitResult = enum { clean, }; -pub fn wait(w: *Watch, gpa: Allocator, timeout: Timeout) !WaitResult { - return Os.wait(w, gpa, timeout); +pub fn wait(w: *Watch, gpa: Allocator, io: Io, timeout: Timeout) !WaitResult { + return Os.wait(w, gpa, io, timeout); } diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index f998466e6e4b2aac295f4f40bd1d64b676aca179..1f450d1dacd6d93992d885e92294d34d66202628 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -83,7 +83,7 @@ csprng: Csprng = .{}, system_basic_information: SystemBasicInformation = .{}, const SystemBasicInformation = if (!is_windows) struct {} else struct { - buffer: windows.SYSTEM_BASIC_INFORMATION = undefined, + buffer: windows.SYSTEM.BASIC_INFORMATION = undefined, initialized: std.atomic.Value(bool) = .{ .raw = false }, }; @@ -1392,7 +1392,7 @@ const AlertableSyscall = struct { } }; -fn waitForApcOrAlert() void { +pub fn waitForApcOrAlert() void { const infinite_timeout: windows.LARGE_INTEGER = std.math.minInt(windows.LARGE_INTEGER); _ = windows.ntdll.NtDelayExecution(windows.TRUE, &infinite_timeout); } @@ -2556,9 +2556,7 @@ fn operate(userdata: ?*anyopaque, operation: Io.Operation) Io.Cancelable!Io.Oper else => |e| e, }, }, - .device_io_control => |*o| return .{ - .device_io_control = try deviceIoControl(t, o), - }, + .device_io_control => |*o| return .{ .device_io_control = try deviceIoControl(o) }, } } @@ -2970,7 +2968,7 @@ fn batchAwaitWindows(b: *Io.Batch, concurrency: bool) error{ Canceled, Concurren break :o; } const buffer = o.data[data_index]; - const short_buffer_len = @min(std.math.maxInt(u32), buffer.len); + const short_buffer_len = std.math.lossyCast(u32, buffer.len); if (o.file.flags.nonblocking) { context.file = o.file.handle; @@ -3259,22 +3257,11 @@ fn dirCreateDirWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, pe _ = t; _ = permissions; // TODO use this value - const sub_path_w_array = try sliceToPrefixedFileW(dir.handle, sub_path); - const sub_path_w = sub_path_w_array.span(); - const path_len_bytes = std.math.cast(u16, sub_path_w.len * 2) orelse return error.NameTooLong; - - var nt_name: windows.UNICODE_STRING = .{ - .Length = path_len_bytes, - .MaximumLength = path_len_bytes, - .Buffer = @constCast(sub_path_w.ptr), - }; - const attr: windows.OBJECT_ATTRIBUTES = .{ - .Length = @sizeOf(windows.OBJECT_ATTRIBUTES), - .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle, - .Attributes = .{ - .INHERIT = false, - }, - .ObjectName = &nt_name, + const sub_path_w = try sliceToPrefixedFileW(dir.handle, sub_path); + const attr: windows.OBJECT.ATTRIBUTES = .{ + .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w.span())) null else dir.handle, + .Attributes = .{ .INHERIT = false }, + .ObjectName = @constCast(&windows.UNICODE_STRING.init(sub_path_w.span())), .SecurityDescriptor = null, .SecurityQualityOfService = null, }; @@ -3438,21 +3425,14 @@ fn dirCreateDirPathOpenWindows( }; components: while (true) { - const sub_path_w_array = try sliceToPrefixedFileW(dir.handle, component.path); - const sub_path_w = sub_path_w_array.span(); + const sub_path_w = try sliceToPrefixedFileW(dir.handle, component.path); + const attr: windows.OBJECT.ATTRIBUTES = .{ + .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w.span())) null else dir.handle, + .ObjectName = @constCast(&sub_path_w.string()), + }; const is_last = it.peekNext() == null; - const create_disposition: w.FILE.CREATE_DISPOSITION = if (is_last) .OPEN_IF else .CREATE; - var result: Dir = .{ .handle = undefined }; - - const path_len_bytes: u16 = @intCast(sub_path_w.len * 2); - var nt_name: w.UNICODE_STRING = .{ - .Length = path_len_bytes, - .MaximumLength = path_len_bytes, - .Buffer = @constCast(sub_path_w.ptr), - }; - var io_status_block: w.IO_STATUS_BLOCK = undefined; - + var iosb: w.IO_STATUS_BLOCK = undefined; const syscall: Syscall = try .start(); while (true) switch (w.ntdll.NtCreateFile( &result.handle, @@ -3468,15 +3448,12 @@ fn dirCreateDirPathOpenWindows( .SYNCHRONIZE = true, }, }, - &.{ - .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle, - .ObjectName = &nt_name, - }, - &io_status_block, + &attr, + &iosb, null, .{ .NORMAL = true }, .VALID_FLAGS, - create_disposition, + if (is_last) .OPEN_IF else .CREATE, .{ .DIRECTORY_FILE = true, .IO = .SYNCHRONOUS_NONALERT, @@ -3944,15 +3921,15 @@ fn fileStatWindows(userdata: ?*anyopaque, file: File) File.StatError!File.Stat { }; } -fn systemBasicInformation(t: *Threaded) ?*const windows.SYSTEM_BASIC_INFORMATION { +fn systemBasicInformation(t: *Threaded) ?*const windows.SYSTEM.BASIC_INFORMATION { if (!t.system_basic_information.initialized.load(.acquire)) { mutexLock(&t.mutex); defer mutexUnlock(&t.mutex); switch (windows.ntdll.NtQuerySystemInformation( - .SystemBasicInformation, + .Basic, &t.system_basic_information.buffer, - @sizeOf(windows.SYSTEM_BASIC_INFORMATION), + @sizeOf(windows.SYSTEM.BASIC_INFORMATION), null, )) { .SUCCESS => {}, @@ -4139,22 +4116,11 @@ fn dirAccessWindows( _ = options; // TODO - const sub_path_w_array = try sliceToPrefixedFileW(dir.handle, sub_path); - const sub_path_w = sub_path_w_array.span(); - - if (sub_path_w[0] == '.' and sub_path_w[1] == 0) return; - if (sub_path_w[0] == '.' and sub_path_w[1] == '.' and sub_path_w[2] == 0) return; - - const path_len_bytes = std.math.cast(u16, std.mem.sliceTo(sub_path_w, 0).len * 2) orelse - return error.NameTooLong; - var nt_name: windows.UNICODE_STRING = .{ - .Length = path_len_bytes, - .MaximumLength = path_len_bytes, - .Buffer = @constCast(sub_path_w.ptr), - }; - const attr: windows.OBJECT_ATTRIBUTES = .{ - .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle, - .ObjectName = &nt_name, + if (std.mem.eql(u8, sub_path, ".") or std.mem.eql(u8, sub_path, "..")) return; + const sub_path_w = try sliceToPrefixedFileW(dir.handle, sub_path); + const attr: windows.OBJECT.ATTRIBUTES = .{ + .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w.span())) null else dir.handle, + .ObjectName = @constCast(&sub_path_w.string()), }; var basic_info: windows.FILE.BASIC_INFORMATION = undefined; const syscall: Syscall = try .start(); @@ -4360,18 +4326,10 @@ fn dirCreateFileWindows( if (std.mem.eql(u8, sub_path, ".")) return error.IsDir; if (std.mem.eql(u8, sub_path, "..")) return error.IsDir; - const sub_path_w_array = try sliceToPrefixedFileW(dir.handle, sub_path); - const sub_path_w = sub_path_w_array.span(); - const path_len_bytes = std.math.cast(u16, sub_path_w.len * 2) orelse return error.NameTooLong; - - var nt_name: windows.UNICODE_STRING = .{ - .Length = path_len_bytes, - .MaximumLength = path_len_bytes, - .Buffer = @constCast(sub_path_w.ptr), - }; - const attr: windows.OBJECT_ATTRIBUTES = .{ - .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle, - .ObjectName = &nt_name, + const sub_path_w = try sliceToPrefixedFileW(dir.handle, sub_path); + const attr: windows.OBJECT.ATTRIBUTES = .{ + .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w.span())) null else dir.handle, + .ObjectName = @constCast(&sub_path_w.string()), }; const create_disposition: windows.FILE.CREATE_DISPOSITION = if (flags.exclusive) .CREATE @@ -4966,20 +4924,14 @@ fn dirOpenFileWindows( pub fn dirOpenFileWtf16( dir_handle: ?windows.HANDLE, - sub_path_w: [:0]const u16, + sub_path_w: []const u16, flags: File.OpenFlags, ) File.OpenError!File { const allow_directory = flags.allow_directory and !flags.isWrite(); if (!allow_directory and std.mem.eql(u16, sub_path_w, &.{'.'})) return error.IsDir; if (!allow_directory and std.mem.eql(u16, sub_path_w, &.{ '.', '.' })) return error.IsDir; - const path_len_bytes = std.math.cast(u16, sub_path_w.len * 2) orelse return error.NameTooLong; const w = windows; - var nt_name: w.UNICODE_STRING = .{ - .Length = path_len_bytes, - .MaximumLength = path_len_bytes, - .Buffer = @constCast(sub_path_w.ptr), - }; var io_status_block: w.IO_STATUS_BLOCK = undefined; var attempt: u5 = 0; var syscall: Syscall = try .start(); @@ -4996,7 +4948,7 @@ pub fn dirOpenFileWtf16( }, &.{ .RootDirectory = dir_handle, - .ObjectName = &nt_name, + .ObjectName = @constCast(&w.UNICODE_STRING.init(sub_path_w)), }, &io_status_block, null, @@ -5329,20 +5281,19 @@ fn dirOpenDirHaiku( pub fn dirOpenDirWindows( dir: Dir, - sub_path_w: [:0]const u16, + sub_path_w: []const u16, options: Dir.OpenOptions, ) Dir.OpenError!Dir { const w = windows; - const path_len_bytes: u16 = @intCast(sub_path_w.len * 2); - var nt_name: w.UNICODE_STRING = .{ - .Length = path_len_bytes, - .MaximumLength = path_len_bytes, - .Buffer = @constCast(sub_path_w.ptr), - }; var io_status_block: w.IO_STATUS_BLOCK = undefined; var result: Dir = .{ .handle = undefined }; + const attr: w.OBJECT.ATTRIBUTES = .{ + .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle, + .ObjectName = @constCast(&w.UNICODE_STRING.init(sub_path_w)), + }; + const syscall: Syscall = try .start(); while (true) switch (w.ntdll.NtCreateFile( &result.handle, @@ -5359,10 +5310,7 @@ pub fn dirOpenDirWindows( .SYNCHRONIZE = true, }, }, - &.{ - .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle, - .ObjectName = &nt_name, - }, + &attr, &io_status_block, null, .{ .NORMAL = true }, @@ -6194,13 +6142,15 @@ pub fn GetFinalPathNameByHandle( input_struct.DeviceNameLength = @intCast(volume_name_u16.len * 2); @memcpy(input_buf[@sizeOf(windows.MOUNTMGR_MOUNT_POINT)..][0 .. volume_name_u16.len * 2], @as([*]const u8, @ptrCast(volume_name_u16.ptr))); - { - const rc = windows.DeviceIoControl(mgmt_handle, windows.IOCTL.MOUNTMGR.QUERY_POINTS, .{ .in = &input_buf, .out = &output_buf }); - switch (rc) { - .SUCCESS => {}, - .OBJECT_NAME_NOT_FOUND => return error.FileNotFound, - else => return windows.unexpectedStatus(rc), - } + switch ((try deviceIoControl(&.{ + .file = .{ .handle = mgmt_handle, .flags = .{ .nonblocking = false } }, + .code = windows.IOCTL.MOUNTMGR.QUERY_POINTS, + .in = &input_buf, + .out = &output_buf, + })).u.Status) { + .SUCCESS => {}, + .OBJECT_NAME_NOT_FOUND => return error.FileNotFound, + else => |status| return windows.unexpectedStatus(status), } const mount_points_struct: *const windows.MOUNTMGR_MOUNT_POINTS = @ptrCast(&output_buf[0]); @@ -6251,11 +6201,15 @@ pub fn GetFinalPathNameByHandle( vol_input_struct.DeviceNameLength = @intCast(symlink.len * 2); @memcpy(@as([*]windows.WCHAR, &vol_input_struct.DeviceName)[0..symlink.len], symlink); - const rc = windows.DeviceIoControl(mgmt_handle, windows.IOCTL.MOUNTMGR.QUERY_DOS_VOLUME_PATH, .{ .in = &vol_input_buf, .out = &vol_output_buf }); - switch (rc) { + switch ((try deviceIoControl(&.{ + .file = .{ .handle = mgmt_handle, .flags = .{ .nonblocking = true } }, + .code = windows.IOCTL.MOUNTMGR.QUERY_DOS_VOLUME_PATH, + .in = &vol_input_buf, + .out = &vol_output_buf, + })).u.Status) { .SUCCESS => {}, .UNRECOGNIZED_VOLUME => return error.UnrecognizedVolume, - else => return windows.unexpectedStatus(rc), + else => |status| return windows.unexpectedStatus(status), } const volume_paths_struct: *const windows.MOUNTMGR_VOLUME_PATHS = @ptrCast(&vol_output_buf[0]); const volume_path = std.mem.sliceTo(@as( @@ -6352,20 +6306,17 @@ pub const QueryObjectNameError = error{ }; pub fn QueryObjectName(handle: windows.HANDLE, out_buffer: []u16) QueryObjectNameError![]u16 { - const out_buffer_aligned = std.mem.alignInSlice(out_buffer, @alignOf(windows.OBJECT_NAME_INFORMATION)) orelse return error.NameTooLong; + const out_buffer_aligned = std.mem.alignInSlice(out_buffer, @alignOf(windows.OBJECT.NAME_INFORMATION)) orelse return error.NameTooLong; - const info: *windows.OBJECT_NAME_INFORMATION = @ptrCast(out_buffer_aligned); + const info: *windows.OBJECT.NAME_INFORMATION = @ptrCast(out_buffer_aligned); // buffer size is specified in bytes const out_buffer_len = std.math.cast(windows.ULONG, out_buffer_aligned.len * 2) orelse std.math.maxInt(windows.ULONG); // last argument would return the length required for full_buffer, not exposed here - return switch (windows.ntdll.NtQueryObject(handle, .ObjectNameInformation, info, out_buffer_len, null)) { - .SUCCESS => blk: { - // info.Name.Buffer from ObQueryNameString is documented to be null (and MaximumLength == 0) - // if the object was "unnamed", not sure if this can happen for file handles - if (info.Name.MaximumLength == 0) break :blk error.Unexpected; - // resulting string length is specified in bytes - const path_length_unterminated = @divExact(info.Name.Length, 2); - break :blk info.Name.Buffer.?[0..path_length_unterminated]; + return switch (windows.ntdll.NtQueryObject(handle, .Name, info, out_buffer_len, null)) { + .SUCCESS => { + // info.Name from ObQueryNameString is documented to be empty if the object + // was "unnamed", not sure if this can happen for file handles + return if (info.Name.isEmpty()) error.Unexpected else info.Name.slice(); }, .ACCESS_DENIED => error.AccessDenied, .INVALID_HANDLE => error.InvalidHandle, @@ -6620,8 +6571,12 @@ pub const WindowsPathSpace = struct { data: [windows.PATH_MAX_WIDE:0]u16, len: usize, - pub fn span(self: *const WindowsPathSpace) [:0]const u16 { - return self.data[0..self.len :0]; + pub fn span(wps: *const WindowsPathSpace) [:0]const u16 { + return wps.data[0..wps.len :0]; + } + + pub fn string(wps: *const WindowsPathSpace) windows.UNICODE_STRING { + return .init(wps.span()); } }; @@ -7076,25 +7031,19 @@ fn dirDeleteWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, remov _ = t; const w = windows; - const sub_path_w_buf = try sliceToPrefixedFileW(dir.handle, sub_path); - const sub_path_w = sub_path_w_buf.span(); - - const path_len_bytes = @as(u16, @intCast(sub_path_w.len * 2)); - var nt_name: w.UNICODE_STRING = .{ - .Length = path_len_bytes, - .MaximumLength = path_len_bytes, - // The Windows API makes this mutable, but it will not mutate here. - .Buffer = @constCast(sub_path_w.ptr), - }; - - if (sub_path_w[0] == '.' and sub_path_w[1] == 0) { - // Windows does not recognize this, but it does work with empty string. - nt_name.Length = 0; - } - if (sub_path_w[0] == '.' and sub_path_w[1] == '.' and sub_path_w[2] == 0) { + if (std.mem.eql(u8, sub_path, "..")) { // Can't remove the parent directory with an open handle. return error.FileBusy; } + var sub_path_w = try sliceToPrefixedFileW(dir.handle, sub_path); + if (std.mem.eql(u8, sub_path, ".")) { + // Windows does not recognize this, but it does work with empty string. + sub_path_w.len = 0; + } + const attr: w.OBJECT.ATTRIBUTES = .{ + .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w.span())) null else dir.handle, + .ObjectName = @constCast(&sub_path_w.string()), + }; var io_status_block: w.IO_STATUS_BLOCK = undefined; var tmp_handle: w.HANDLE = undefined; @@ -7106,10 +7055,7 @@ fn dirDeleteWindows(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, remov .RIGHTS = .{ .DELETE = true }, .SYNCHRONIZE = true, } }, - &.{ - .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle, - .ObjectName = &nt_name, - }, + &attr, &io_status_block, null, .{}, @@ -7741,7 +7687,7 @@ fn dirSymLinkWindows( // https://learn.microsoft.com/en-us/windows/win32/api/winbase/nf-winbase-createsymboliclinkw var is_target_absolute = false; const final_target_path = target_path: { - if (windows.hasCommonNtPrefix(u16, target_path_w.span())) { + if (w.hasCommonNtPrefix(u16, target_path_w.span())) { // Already an NT path, no need to do anything to it break :target_path target_path_w.span(); } else { @@ -7785,13 +7731,16 @@ fn dirSymLinkWindows( @memcpy(buffer[@sizeOf(SYMLINK_DATA)..][0 .. final_target_path.len * 2], @as([*]const u8, @ptrCast(final_target_path))); const paths_start = @sizeOf(SYMLINK_DATA) + final_target_path.len * 2; @memcpy(buffer[paths_start..][0 .. final_target_path.len * 2], @as([*]const u8, @ptrCast(final_target_path))); - const rc = w.DeviceIoControl(symlink_handle, .SET_REPARSE_POINT, .{ .in = buffer[0..buf_len] }); - switch (rc) { + switch ((try deviceIoControl(&.{ + .file = .{ .handle = symlink_handle, .flags = .{ .nonblocking = false } }, + .code = .SET_REPARSE_POINT, + .in = buffer[0..buf_len], + })).u.Status) { .SUCCESS => {}, .PRIVILEGE_NOT_HELD => return error.PermissionDenied, .ACCESS_DENIED => return error.AccessDenied, .INVALID_DEVICE_REQUEST => return error.FileSystem, - else => return windows.unexpectedStatus(rc), + else => |status| return w.unexpectedStatus(status), } } @@ -7905,17 +7854,10 @@ fn dirReadLink(userdata: ?*anyopaque, dir: Dir, sub_path: []const u8, buffer: [] fn dirReadLinkWindows(dir: Dir, sub_path: []const u8, buffer: []u8) Dir.ReadLinkError!usize { // This gets used once for `sub_path` and then reused again temporarily // before converting back to `buffer`. - var sub_path_w_buf = try sliceToPrefixedFileW(dir.handle, sub_path); - const sub_path_w = sub_path_w_buf.span(); - const path_len_bytes = std.math.cast(u16, sub_path_w.len * 2) orelse return error.NameTooLong; - var nt_name: windows.UNICODE_STRING = .{ - .Length = path_len_bytes, - .MaximumLength = path_len_bytes, - .Buffer = @constCast(sub_path_w.ptr), - }; - const attr: windows.OBJECT_ATTRIBUTES = .{ - .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w)) null else dir.handle, - .ObjectName = &nt_name, + var sub_path_w = try sliceToPrefixedFileW(dir.handle, sub_path); + const attr: windows.OBJECT.ATTRIBUTES = .{ + .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w.span())) null else dir.handle, + .ObjectName = @constCast(&sub_path_w.string()), }; var io_status_block: windows.IO_STATUS_BLOCK = undefined; var result_handle: windows.HANDLE = undefined; @@ -8038,14 +7980,14 @@ fn dirReadLinkWindows(dir: Dir, sub_path: []const u8, buffer: []u8) Dir.ReadLink const len = buf.SubstituteNameLength >> 1; const path_buf = @as([*]const u16, &buf.PathBuffer); const is_relative = buf.Flags & windows.SYMLINK_FLAG_RELATIVE != 0; - break :r try parseReadLinkPath(path_buf[offset..][0..len], is_relative, &sub_path_w_buf.data); + break :r try parseReadLinkPath(path_buf[offset..][0..len], is_relative, &sub_path_w.data); }, @as(IoReparseTagInt, @bitCast(windows.IO_REPARSE_TAG.MOUNT_POINT)) => r: { const buf: *const windows.MOUNT_POINT_REPARSE_BUFFER = @ptrCast(@alignCast(&reparse_struct.DataBuffer[0])); const offset = buf.SubstituteNameOffset >> 1; const len = buf.SubstituteNameLength >> 1; const path_buf = @as([*]const u16, &buf.PathBuffer); - break :r try parseReadLinkPath(path_buf[offset..][0..len], false, &sub_path_w_buf.data); + break :r try parseReadLinkPath(path_buf[offset..][0..len], false, &sub_path_w.data); }, else => return error.UnsupportedReparsePointType, }; @@ -8541,13 +8483,14 @@ fn fileSyncWasi(userdata: ?*anyopaque, file: File) File.SyncError!void { fn fileIsTty(userdata: ?*anyopaque, file: File) Io.Cancelable!bool { const t: *Threaded = @ptrCast(@alignCast(userdata)); - return t.isTty(file); + _ = t; + return isTty(file); } -fn isTty(t: *Threaded, file: File) Io.Cancelable!bool { +fn isTty(file: File) Io.Cancelable!bool { if (is_windows) { var get_console_mode = windows.CONSOLE.USER_IO.GET_MODE; - switch ((try t.deviceIoControl(&.{ + switch ((try deviceIoControl(&.{ .file = .{ .handle = windows.peb().ProcessParameters.ConsoleHandle, .flags = .{ .nonblocking = false }, @@ -8626,8 +8569,9 @@ fn isTty(t: *Threaded, file: File) Io.Cancelable!bool { fn fileEnableAnsiEscapeCodes(userdata: ?*anyopaque, file: File) File.EnableAnsiEscapeCodesError!void { const t: *Threaded = @ptrCast(@alignCast(userdata)); + _ = t; - if (!is_windows) return if (!try t.supportsAnsiEscapeCodes(file)) error.NotTerminalDevice; + if (!is_windows) return if (!try supportsAnsiEscapeCodes(file)) error.NotTerminalDevice; // For Windows Terminal, VT Sequences processing is enabled by default. const console: File = .{ @@ -8635,7 +8579,7 @@ fn fileEnableAnsiEscapeCodes(userdata: ?*anyopaque, file: File) File.EnableAnsiE .flags = .{ .nonblocking = false }, }; var get_console_mode = windows.CONSOLE.USER_IO.GET_MODE; - switch ((try t.deviceIoControl(&.{ + switch ((try deviceIoControl(&.{ .file = console, .code = windows.IOCTL.CONDRV.ISSUE_USER_IO, .in = @ptrCast(&get_console_mode.request(file, 0, .{}, 0, .{})), @@ -8661,7 +8605,7 @@ fn fileEnableAnsiEscapeCodes(userdata: ?*anyopaque, file: File) File.EnableAnsiE var set_console_mode = windows.CONSOLE.USER_IO.SET_MODE( get_console_mode.Data | windows.ENABLE_VIRTUAL_TERMINAL_PROCESSING, ); - switch ((try t.deviceIoControl(&.{ + switch ((try deviceIoControl(&.{ .file = console, .code = windows.IOCTL.CONDRV.ISSUE_USER_IO, .in = @ptrCast(&set_console_mode.request(file, 0, .{}, 0, .{})), @@ -8673,13 +8617,14 @@ fn fileEnableAnsiEscapeCodes(userdata: ?*anyopaque, file: File) File.EnableAnsiE fn fileSupportsAnsiEscapeCodes(userdata: ?*anyopaque, file: File) Io.Cancelable!bool { const t: *Threaded = @ptrCast(@alignCast(userdata)); - return t.supportsAnsiEscapeCodes(file); + _ = t; + return supportsAnsiEscapeCodes(file); } -fn supportsAnsiEscapeCodes(t: *Threaded, file: File) Io.Cancelable!bool { +fn supportsAnsiEscapeCodes(file: File) Io.Cancelable!bool { if (is_windows) { var get_console_mode = windows.CONSOLE.USER_IO.GET_MODE; - switch ((try t.deviceIoControl(&.{ + switch ((try deviceIoControl(&.{ .file = .{ .handle = windows.peb().ProcessParameters.ConsoleHandle, .flags = .{ .nonblocking = false }, @@ -8701,7 +8646,7 @@ fn supportsAnsiEscapeCodes(t: *Threaded, file: File) Io.Cancelable!bool { return false; } - if (try t.isTty(file)) return true; + if (try isTty(file)) return true; return false; } @@ -8775,7 +8720,7 @@ fn isCygwinPty(file: File) Io.Cancelable!bool { }, }; - const name_info: *const windows.FILE_NAME_INFO = @ptrCast(&name_info_bytes); + const name_info: *const windows.FILE.NAME_INFORMATION = @ptrCast(&name_info_bytes); const name_bytes = name_info_bytes[name_bytes_offset .. name_bytes_offset + name_info.FileNameLength]; const name_wide = std.mem.bytesAsSlice(u16, name_bytes); // The name we get from NtQueryInformationFile will be prefixed with a '\', e.g. \msys-1888ae32e00d56aa-pty0-to-master @@ -9002,49 +8947,38 @@ fn fileSetTimestamps( _ = t; if (is_windows) { - var access_time_buffer: windows.FILETIME = undefined; - var modify_time_buffer: windows.FILETIME = undefined; - var system_time_buffer: windows.LARGE_INTEGER = undefined; - - if (options.access_timestamp == .now or options.modify_timestamp == .now) { - system_time_buffer = windows.ntdll.RtlGetSystemTimePrecise(); - } - - const access_ptr = switch (options.access_timestamp) { - .unchanged => null, - .now => @panic("TODO do SystemTimeToFileTime logic here"), - .new => |ts| p: { - access_time_buffer = windows.nanoSecondsToFileTime(ts); - break :p &access_time_buffer; + const now_sys = if (options.access_timestamp == .now or options.modify_timestamp == .now) + windows.ntdll.RtlGetSystemTimePrecise() + else + undefined; + var iosb: windows.IO_STATUS_BLOCK = undefined; + var info: windows.FILE.BASIC_INFORMATION = .{ + .CreationTime = 0, + .LastAccessTime = switch (options.access_timestamp) { + .unchanged => 0, + .now => now_sys, + .new => |ts| windows.toSysTime(ts), }, - }; - - const modify_ptr = switch (options.modify_timestamp) { - .unchanged => null, - .now => @panic("TODO do SystemTimeToFileTime logic here"), - .new => |ts| p: { - modify_time_buffer = windows.nanoSecondsToFileTime(ts); - break :p &modify_time_buffer; + .LastWriteTime = switch (options.modify_timestamp) { + .unchanged => 0, + .now => now_sys, + .new => |ts| windows.toSysTime(ts), }, + .ChangeTime = 0, + .FileAttributes = .{}, + }; + var syscall: Syscall = try .start(); + while (true) switch (windows.ntdll.NtSetInformationFile( + file.handle, + &iosb, + &info, + @sizeOf(windows.FILE.BASIC_INFORMATION), + .Basic, + )) { + .SUCCESS => return syscall.finish(), + .CANCELLED => try syscall.checkCancel(), + else => |status| return syscall.unexpectedNtstatus(status), }; - - // https://github.com/ziglang/zig/issues/1840 - const syscall: Syscall = try .start(); - while (true) { - switch (windows.kernel32.SetFileTime(file.handle, null, access_ptr, modify_ptr)) { - 0 => switch (windows.GetLastError()) { - .OPERATION_ABORTED => { - try syscall.checkCancel(); - continue; - }, - else => |err| { - syscall.finish(); - return windows.unexpectedError(err); - }, - }, - else => return syscall.finish(), - } - } } if (native_os == .wasi and !builtin.link_libc) { @@ -9636,15 +9570,43 @@ fn fileReadStreamingPosix(file: File, data: []const []u8) File.ReadStreamingErro } fn fileReadStreamingWindows(file: File, data: []const []u8) File.ReadStreamingError!usize { + var iosb: windows.IO_STATUS_BLOCK = undefined; var index: usize = 0; while (data.len - index != 0 and data[index].len == 0) index += 1; if (data.len - index == 0) return 0; const buffer = data[index]; - const short_buffer_len = @min(std.math.maxInt(u32), buffer.len); - - var iosb: windows.IO_STATUS_BLOCK = undefined; - - if (!file.flags.nonblocking) { + const short_buffer_len = std.math.lossyCast(u32, buffer.len); + if (file.flags.nonblocking) { + var done: bool = false; + switch (windows.ntdll.NtReadFile( + file.handle, + null, // event + flagApc, + &done, // APC context + &iosb, + buffer.ptr, + short_buffer_len, + null, // byte offset + null, // key + )) { + // We must wait for the APC routine. + .PENDING, .SUCCESS => while (!done) { + // Once we get here we must not return from the function until the + // operation completes, thereby releasing reference to the iosb. + const alertable_syscall = AlertableSyscall.start() catch |err| switch (err) { + error.Canceled => |e| { + var cancel_iosb: windows.IO_STATUS_BLOCK = undefined; + _ = windows.ntdll.NtCancelIoFileEx(file.handle, &iosb, &cancel_iosb); + while (!done) waitForApcOrAlert(); + return e; + }, + }; + waitForApcOrAlert(); + alertable_syscall.finish(); + }, + else => |status| iosb.u.Status = status, + } + } else { const syscall: Syscall = try .start(); while (true) switch (windows.ntdll.NtReadFile( file.handle, @@ -9665,41 +9627,10 @@ fn fileReadStreamingWindows(file: File, data: []const []u8) File.ReadStreamingEr else => |status| { syscall.finish(); iosb.u.Status = status; - return ntReadFileResult(&iosb); + break; }, }; } - - var done: bool = false; - - switch (windows.ntdll.NtReadFile( - file.handle, - null, // event - flagApc, - &done, // APC context - &iosb, - buffer.ptr, - short_buffer_len, - null, // byte offset - null, // key - )) { - // We must wait for the APC routine. - .PENDING, .SUCCESS => while (!done) { - // Once we get here we must not return from the function until the - // operation completes, thereby releasing reference to io_status_block. - const alertable_syscall = AlertableSyscall.start() catch |err| switch (err) { - error.Canceled => |e| { - var cancel_iosb: windows.IO_STATUS_BLOCK = undefined; - _ = windows.ntdll.NtCancelIoFileEx(file.handle, &iosb, &cancel_iosb); - while (!done) waitForApcOrAlert(); - return e; - }, - }; - waitForApcOrAlert(); - alertable_syscall.finish(); - }, - else => |status| iosb.u.Status = status, - } return ntReadFileResult(&iosb); } @@ -9714,8 +9645,9 @@ fn ntReadFileResult(io_status_block: *const windows.IO_STATUS_BLOCK) !usize { .CANCELLED => unreachable, .SUCCESS => return io_status_block.Information, .END_OF_FILE, .PIPE_BROKEN => return error.EndOfStream, + .INVALID_HANDLE => return error.NotOpenForReading, .INVALID_DEVICE_REQUEST => return error.IsDir, - .LOCK_NOT_GRANTED => return error.LockViolation, + .FILE_LOCK_CONFLICT => return error.LockViolation, .ACCESS_DENIED => return error.AccessDenied, else => |status| return windows.unexpectedStatus(status), } @@ -9731,7 +9663,7 @@ fn ntWriteFileResult(io_status_block: *const windows.IO_STATUS_BLOCK) !usize { .QUOTA_EXCEEDED => return error.SystemResources, .PIPE_BROKEN => return error.BrokenPipe, .INVALID_HANDLE => return error.NotOpenForWriting, - .LOCK_NOT_GRANTED => return error.LockViolation, + .FILE_LOCK_CONFLICT => return error.LockViolation, .ACCESS_DENIED => return error.AccessDenied, .WORKING_SET_QUOTA => return error.SystemResources, .DISK_FULL => return error.NoSpaceLeft, @@ -9740,8 +9672,6 @@ fn ntWriteFileResult(io_status_block: *const windows.IO_STATUS_BLOCK) !usize { } fn fileReadPositionalPosix(file: File, data: []const []u8, offset: u64) File.ReadPositionalError!usize { - if (!have_preadv) @compileError("TODO implement fileReadPositionalPosix for cursed operating systems that don't support preadv (it's only Haiku)"); - var iovecs_buffer: [max_iovecs_len]posix.iovec = undefined; var i: usize = 0; for (data) |buf| { @@ -9787,9 +9717,44 @@ fn fileReadPositionalPosix(file: File, data: []const []u8, offset: u64) File.Rea } } + if (have_preadv) { + const syscall: Syscall = try .start(); + while (true) { + const rc = preadv_sym(file.handle, dest.ptr, @intCast(dest.len), @bitCast(offset)); + switch (posix.errno(rc)) { + .SUCCESS => { + syscall.finish(); + return @bitCast(rc); + }, + .INTR, .TIMEDOUT => { + try syscall.checkCancel(); + continue; + }, + .NXIO => return syscall.fail(error.Unseekable), + .SPIPE => return syscall.fail(error.Unseekable), + .OVERFLOW => return syscall.fail(error.Unseekable), + .NOBUFS => return syscall.fail(error.SystemResources), + .NOMEM => return syscall.fail(error.SystemResources), + .AGAIN => return syscall.fail(error.WouldBlock), + .IO => return syscall.fail(error.InputOutput), + .ISDIR => return syscall.fail(error.IsDir), + .NOTCONN => |err| return syscall.errnoBug(err), // not a socket + .CONNRESET => |err| return syscall.errnoBug(err), // not a socket + .INVAL => |err| return syscall.errnoBug(err), + .FAULT => |err| return syscall.errnoBug(err), + .BADF => { + syscall.finish(); + if (native_os == .wasi) return error.IsDir; // File operation on directory. + return error.NotOpenForReading; + }, + else => |err| return syscall.unexpectedErrno(err), + } + } + } + const syscall: Syscall = try .start(); while (true) { - const rc = preadv_sym(file.handle, dest.ptr, @intCast(dest.len), @bitCast(offset)); + const rc = posix.pread(file.handle, dest[0].ptr, @intCast(dest[0].len), @bitCast(offset)); switch (posix.errno(rc)) { .SUCCESS => { syscall.finish(); @@ -9838,116 +9803,119 @@ fn fileReadPositionalWindows(file: File, data: []const []u8, offset: u64) File.R } fn readFilePositionalWindows(file: File, buffer: []u8, offset: u64) File.ReadPositionalError!usize { - const DWORD = windows.DWORD; - const want_read_count: DWORD = @min(std.math.maxInt(DWORD), buffer.len); - var overlapped: windows.OVERLAPPED = .{ - .Internal = 0, - .InternalHigh = 0, - .DUMMYUNIONNAME = .{ - .DUMMYSTRUCTNAME = .{ - .Offset = @truncate(offset), - .OffsetHigh = @truncate(offset >> 32), + var iosb: windows.IO_STATUS_BLOCK = undefined; + const short_buffer_len = std.math.lossyCast(u32, buffer.len); + const signed_offset: windows.LARGE_INTEGER = @intCast(offset); + if (file.flags.nonblocking) { + var done: bool = false; + switch (windows.ntdll.NtReadFile( + file.handle, + null, // event + flagApc, + &done, // APC context + &iosb, + buffer.ptr, + short_buffer_len, + &signed_offset, + null, // key + )) { + // We must wait for the APC routine. + .PENDING, .SUCCESS => while (!done) { + // Once we get here we must not return from the function until the + // operation completes, thereby releasing reference to the iosb. + const alertable_syscall = AlertableSyscall.start() catch |err| switch (err) { + error.Canceled => |e| { + var cancel_iosb: windows.IO_STATUS_BLOCK = undefined; + _ = windows.ntdll.NtCancelIoFileEx(file.handle, &iosb, &cancel_iosb); + while (!done) waitForApcOrAlert(); + return e; + }, + }; + waitForApcOrAlert(); + alertable_syscall.finish(); }, - }, - .hEvent = null, - }; - - const syscall: Syscall = try .start(); - while (true) { - var n: DWORD = undefined; - if (windows.kernel32.ReadFile(file.handle, buffer.ptr, want_read_count, &n, &overlapped) != 0) { - syscall.finish(); - return n; + else => |status| iosb.u.Status = status, } - switch (windows.GetLastError()) { - .IO_PENDING => |err| { + } else { + const syscall: Syscall = try .start(); + while (true) switch (windows.ntdll.NtReadFile( + file.handle, + null, // event + null, // APC routine + null, // APC context + &iosb, + buffer.ptr, + short_buffer_len, + &signed_offset, + null, // key + )) { + .PENDING => unreachable, // unrecoverable: wrong File nonblocking flag + .CANCELLED => try syscall.checkCancel(), + else => |status| { syscall.finish(); - return windows.errorBug(err); + iosb.u.Status = status; + break; }, - .OPERATION_ABORTED => { - try syscall.checkCancel(); - continue; - }, - .BROKEN_PIPE, .HANDLE_EOF => { - syscall.finish(); - return 0; - }, - .NETNAME_DELETED => if (is_debug) unreachable else return error.Unexpected, - .LOCK_VIOLATION => return syscall.fail(error.LockViolation), - .ACCESS_DENIED => return syscall.fail(error.AccessDenied), - .INVALID_HANDLE => if (is_debug) unreachable else return error.Unexpected, - // TODO: Determine if INVALID_FUNCTION is possible in more scenarios than just passing - // a handle to a directory. - .INVALID_FUNCTION => return syscall.fail(error.IsDir), - else => |err| { - syscall.finish(); - return windows.unexpectedError(err); - }, - } + }; } + return ntReadFileResult(&iosb) catch |err| switch (err) { + error.EndOfStream => 0, + else => |e| e, + }; } fn fileSeekBy(userdata: ?*anyopaque, file: File, offset: i64) File.SeekError!void { const t: *Threaded = @ptrCast(@alignCast(userdata)); _ = t; - const fd = file.handle; - - if (native_os == .linux and !builtin.link_libc and @sizeOf(usize) == 4) { - var result: u64 = undefined; - const syscall: Syscall = try .start(); - while (true) { - switch (posix.errno(posix.system.llseek(fd, @bitCast(offset), &result, posix.SEEK.CUR))) { - .SUCCESS => { - syscall.finish(); - return; - }, - .INTR => { - try syscall.checkCancel(); - continue; - }, - else => |e| { - syscall.finish(); - switch (e) { - .BADF => |err| return errnoBug(err), // File descriptor used after closed. - .INVAL => return error.Unseekable, - .OVERFLOW => return error.Unseekable, - .SPIPE => return error.Unseekable, - .NXIO => return error.Unseekable, - else => |err| return posix.unexpectedErrno(err), - } - }, - } - } - } if (is_windows) { + var iosb: windows.IO_STATUS_BLOCK = undefined; + var info: windows.FILE.POSITION_INFORMATION = undefined; const syscall: Syscall = try .start(); - while (true) { - if (windows.kernel32.SetFilePointerEx(fd, offset, null, windows.FILE_CURRENT) != 0) { - return syscall.finish(); - } - switch (windows.GetLastError()) { - .OPERATION_ABORTED => { - try syscall.checkCancel(); - continue; - }, - .INVALID_FUNCTION => return syscall.fail(error.Unseekable), - .NEGATIVE_SEEK => return syscall.fail(error.Unseekable), - .INVALID_PARAMETER => unreachable, - .INVALID_HANDLE => unreachable, - else => |err| { - syscall.finish(); - return windows.unexpectedError(err); - }, - } - } + while (true) switch (windows.ntdll.NtQueryInformationFile( + file.handle, + &iosb, + &info, + @sizeOf(windows.FILE.POSITION_INFORMATION), + .Position, + )) { + .SUCCESS => break, + .CANCELLED => try syscall.checkCancel(), + .ACCESS_DENIED => return syscall.fail(error.AccessDenied), + .PIPE_NOT_AVAILABLE => return syscall.fail(error.Unseekable), + else => |status| return syscall.unexpectedNtstatus(status), + }; + info.CurrentByteOffset = @bitCast((if (offset >= 0) std.math.add( + u64, + @bitCast(info.CurrentByteOffset), + @intCast(offset), + ) else std.math.sub( + u64, + @bitCast(info.CurrentByteOffset), + @intCast(-offset), + )) catch |err| switch (err) { + error.Overflow => return syscall.fail(error.Unseekable), + }); + while (true) switch (windows.ntdll.NtSetInformationFile( + file.handle, + &iosb, + &info, + @sizeOf(windows.FILE.POSITION_INFORMATION), + .Position, + )) { + .SUCCESS => return syscall.finish(), + .CANCELLED => try syscall.checkCancel(), + .ACCESS_DENIED => return syscall.fail(error.AccessDenied), + .PIPE_NOT_AVAILABLE => return syscall.fail(error.Unseekable), + else => |status| return syscall.unexpectedNtstatus(status), + }; } if (native_os == .wasi and !builtin.link_libc) { var new_offset: std.os.wasi.filesize_t = undefined; const syscall: Syscall = try .start(); while (true) { - switch (std.os.wasi.fd_seek(fd, offset, .CUR, &new_offset)) { + switch (std.os.wasi.fd_seek(file.handle, offset, .CUR, &new_offset)) { .SUCCESS => { syscall.finish(); return; @@ -9974,9 +9942,37 @@ fn fileSeekBy(userdata: ?*anyopaque, file: File, offset: i64) File.SeekError!voi if (posix.SEEK == void) return error.Unseekable; + if (native_os == .linux and !builtin.link_libc and @sizeOf(usize) == 4) { + var result: u64 = undefined; + const syscall: Syscall = try .start(); + while (true) { + switch (posix.errno(posix.system.llseek(file.handle, @bitCast(offset), &result, posix.SEEK.CUR))) { + .SUCCESS => { + syscall.finish(); + return; + }, + .INTR => { + try syscall.checkCancel(); + continue; + }, + else => |e| { + syscall.finish(); + switch (e) { + .BADF => |err| return errnoBug(err), // File descriptor used after closed. + .INVAL => return error.Unseekable, + .OVERFLOW => return error.Unseekable, + .SPIPE => return error.Unseekable, + .NXIO => return error.Unseekable, + else => |err| return posix.unexpectedErrno(err), + } + }, + } + } + } + const syscall: Syscall = try .start(); while (true) { - switch (posix.errno(lseek_sym(fd, offset, posix.SEEK.CUR))) { + switch (posix.errno(lseek_sym(file.handle, offset, posix.SEEK.CUR))) { .SUCCESS => { syscall.finish(); return; @@ -10003,41 +9999,31 @@ fn fileSeekBy(userdata: ?*anyopaque, file: File, offset: i64) File.SeekError!voi fn fileSeekTo(userdata: ?*anyopaque, file: File, offset: u64) File.SeekError!void { const t: *Threaded = @ptrCast(@alignCast(userdata)); _ = t; - const fd = file.handle; if (is_windows) { - // "The starting point is zero or the beginning of the file. If [FILE_BEGIN] - // is specified, then the liDistanceToMove parameter is interpreted as an unsigned value." - // https://docs.microsoft.com/en-us/windows/desktop/api/fileapi/nf-fileapi-setfilepointerex - const ipos: windows.LARGE_INTEGER = @bitCast(offset); - + var iosb: windows.IO_STATUS_BLOCK = undefined; + var info: windows.FILE.POSITION_INFORMATION = .{ .CurrentByteOffset = @bitCast(offset) }; const syscall: Syscall = try .start(); - while (true) { - if (windows.kernel32.SetFilePointerEx(fd, ipos, null, windows.FILE_BEGIN) != 0) { - return syscall.finish(); - } - switch (windows.GetLastError()) { - .OPERATION_ABORTED => { - try syscall.checkCancel(); - continue; - }, - .INVALID_FUNCTION => return syscall.fail(error.Unseekable), - .NEGATIVE_SEEK => return syscall.fail(error.Unseekable), - .INVALID_PARAMETER => unreachable, - .INVALID_HANDLE => unreachable, - else => |err| { - syscall.finish(); - return windows.unexpectedError(err); - }, - } - } + while (true) switch (windows.ntdll.NtSetInformationFile( + file.handle, + &iosb, + &info, + @sizeOf(windows.FILE.POSITION_INFORMATION), + .Position, + )) { + .SUCCESS => return syscall.finish(), + .CANCELLED => try syscall.checkCancel(), + .ACCESS_DENIED => return syscall.fail(error.AccessDenied), + .PIPE_NOT_AVAILABLE => return syscall.fail(error.Unseekable), + else => |status| return syscall.unexpectedNtstatus(status), + }; } if (native_os == .wasi and !builtin.link_libc) { const syscall: Syscall = try .start(); while (true) { var new_offset: std.os.wasi.filesize_t = undefined; - switch (std.os.wasi.fd_seek(fd, @bitCast(offset), .SET, &new_offset)) { + switch (std.os.wasi.fd_seek(file.handle, @bitCast(offset), .SET, &new_offset)) { .SUCCESS => { syscall.finish(); return; @@ -10064,7 +10050,7 @@ fn fileSeekTo(userdata: ?*anyopaque, file: File, offset: u64) File.SeekError!voi if (posix.SEEK == void) return error.Unseekable; - return posixSeekTo(fd, offset); + return posixSeekTo(file.handle, offset); } fn posixSeekTo(fd: posix.fd_t, offset: u64) File.SeekError!void { @@ -10131,8 +10117,7 @@ fn processExecutableOpen(userdata: ?*anyopaque, flags: File.OpenFlags) process.O // If ImagePathName is a symlink, then it will contain the path of the symlink, // not the path that the symlink points to. However, because we are opening // the file, we can let the openFileW call follow the symlink for us. - const image_path_unicode_string = &windows.peb().ProcessParameters.ImagePathName; - const image_path_name = image_path_unicode_string.Buffer.?[0 .. image_path_unicode_string.Length / 2 :0]; + const image_path_name = windows.peb().ProcessParameters.ImagePathName.sliceZ(); const prefixed_path_w = try wToPrefixedFileW(null, image_path_name); return dirOpenFileWtf16(null, prefixed_path_w.span(), flags); }, @@ -10334,14 +10319,13 @@ fn processExecutablePath(userdata: ?*anyopaque, out_buffer: []u8) process.Execut return error.FileNotFound; }, .windows => { - const w = windows; - const image_path_unicode_string = &w.peb().ProcessParameters.ImagePathName; - const image_path_name = image_path_unicode_string.Buffer.?[0 .. image_path_unicode_string.Length / 2 :0]; - // If ImagePathName is a symlink, then it will contain the path of the // symlink, not the path that the symlink points to. We want the path // that the symlink points to, though, so we need to get the realpath. - var path_name_w_buf = try wToPrefixedFileW(null, image_path_name); + var path_name_w_buf = try wToPrefixedFileW( + null, + windows.peb().ProcessParameters.ImagePathName.sliceZ(), + ); const h_file = handle: { if (OpenFile(path_name_w_buf.span(), .{ @@ -10360,7 +10344,7 @@ fn processExecutablePath(userdata: ?*anyopaque, out_buffer: []u8) process.Execut else => |e| return e, } }; - defer w.CloseHandle(h_file); + defer windows.CloseHandle(h_file); const wide_slice = try GetFinalPathNameByHandle(h_file, .{}, &path_name_w_buf.data); @@ -10388,15 +10372,15 @@ fn fileWritePositional( if (is_windows) { if (header.len != 0) { - return writeFilePositionalWindows(file.handle, header, offset); + return writeFilePositionalWindows(file, header, offset); } for (data[0 .. data.len - 1]) |buf| { if (buf.len == 0) continue; - return writeFilePositionalWindows(file.handle, buf, offset); + return writeFilePositionalWindows(file, buf, offset); } const pattern = data[data.len - 1]; if (pattern.len == 0 or splat == 0) return 0; - return writeFilePositionalWindows(file.handle, pattern, offset); + return writeFilePositionalWindows(file, pattern, offset); } var iovecs: [max_iovecs_len]posix.iovec_const = undefined; @@ -10505,50 +10489,64 @@ fn fileWritePositional( } } -fn writeFilePositionalWindows( - handle: windows.HANDLE, - bytes: []const u8, - offset: u64, -) File.WritePositionalError!usize { - var bytes_written: windows.DWORD = undefined; - var overlapped: windows.OVERLAPPED = .{ - .Internal = 0, - .InternalHigh = 0, - .DUMMYUNIONNAME = .{ - .DUMMYSTRUCTNAME = .{ - .Offset = @truncate(offset), - .OffsetHigh = @truncate(offset >> 32), +fn writeFilePositionalWindows(file: File, buffer: []const u8, offset: u64) File.WritePositionalError!usize { + assert(buffer.len != 0); + var iosb: windows.IO_STATUS_BLOCK = undefined; + const short_buffer_len = std.math.lossyCast(u32, buffer.len); + const signed_offset: windows.LARGE_INTEGER = @intCast(offset); + if (file.flags.nonblocking) { + var done: bool = false; + switch (windows.ntdll.NtWriteFile( + file.handle, + null, // event + flagApc, + &done, // APC context + &iosb, + buffer.ptr, + short_buffer_len, + &signed_offset, + null, // key + )) { + // We must wait for the APC routine. + .PENDING, .SUCCESS => while (!done) { + // Once we get here we must not return from the function until the + // operation completes, thereby releasing reference to the iosb. + const alertable_syscall = AlertableSyscall.start() catch |err| switch (err) { + error.Canceled => |e| { + var cancel_iosb: windows.IO_STATUS_BLOCK = undefined; + _ = windows.ntdll.NtCancelIoFileEx(file.handle, &iosb, &cancel_iosb); + while (!done) waitForApcOrAlert(); + return e; + }, + }; + waitForApcOrAlert(); + alertable_syscall.finish(); }, - }, - .hEvent = null, - }; - const adjusted_len = std.math.lossyCast(u32, bytes.len); - const syscall: Syscall = try .start(); - while (true) { - if (windows.kernel32.WriteFile(handle, bytes.ptr, adjusted_len, &bytes_written, &overlapped) != 0) { - syscall.finish(); - return bytes_written; + else => |status| iosb.u.Status = status, } - switch (windows.GetLastError()) { - .OPERATION_ABORTED => { - try syscall.checkCancel(); - continue; - }, - .INVALID_USER_BUFFER => return syscall.fail(error.SystemResources), - .NOT_ENOUGH_MEMORY => return syscall.fail(error.SystemResources), - .NOT_ENOUGH_QUOTA => return syscall.fail(error.SystemResources), - .NO_DATA => return syscall.fail(error.BrokenPipe), - .INVALID_HANDLE => if (is_debug) unreachable else return error.Unexpected, // use after free - .LOCK_VIOLATION => return syscall.fail(error.LockViolation), - .ACCESS_DENIED => return syscall.fail(error.AccessDenied), - .WORKING_SET_QUOTA => return syscall.fail(error.SystemResources), - .DISK_FULL => return syscall.fail(error.NoSpaceLeft), - else => |err| { + } else { + const syscall: Syscall = try .start(); + while (true) switch (windows.ntdll.NtWriteFile( + file.handle, + null, // event + null, // APC routine + null, // APC context + &iosb, + buffer.ptr, + short_buffer_len, + &signed_offset, + null, // key + )) { + .PENDING => unreachable, // unrecoverable: wrong File nonblocking flag + .CANCELLED => try syscall.checkCancel(), + else => |status| { syscall.finish(); - return windows.unexpectedError(err); + iosb.u.Status = status; + return ntWriteFileResult(&iosb); }, - } + }; } + return ntWriteFileResult(&iosb); } fn fileWriteStreaming( @@ -10673,9 +10671,7 @@ fn fileWriteStreaming( fn fileWriteStreamingWindows(file: File, buffer: []const u8) File.Writer.Error!usize { assert(buffer.len != 0); - var iosb: windows.IO_STATUS_BLOCK = undefined; - if (file.flags.nonblocking) { var done: bool = false; switch (windows.ntdll.NtWriteFile( @@ -10706,7 +10702,6 @@ fn fileWriteStreamingWindows(file: File, buffer: []const u8) File.Writer.Error!u }, else => |status| iosb.u.Status = status, } - return ntWriteFileResult(&iosb); } else { const syscall: Syscall = try .start(); while (true) switch (windows.ntdll.NtWriteFile( @@ -10721,17 +10716,15 @@ fn fileWriteStreamingWindows(file: File, buffer: []const u8) File.Writer.Error!u null, // key )) { .PENDING => unreachable, // unrecoverable: wrong File nonblocking flag - .CANCELLED => { - try syscall.checkCancel(); - continue; - }, + .CANCELLED => try syscall.checkCancel(), else => |status| { syscall.finish(); iosb.u.Status = status; - return ntWriteFileResult(&iosb); + break; }, }; } + return ntWriteFileResult(&iosb); } fn fileWriteFileStreaming( @@ -11433,13 +11426,22 @@ fn nowWindows(clock: Io.Clock) Io.Timestamp { return .{ .nanoseconds = @as(i96, windows.ntdll.RtlGetSystemTimePrecise()) * 100 + epoch_ns }; }, .awake, .boot => { - // QPC on windows doesn't fail on >= XP/2000 and includes time suspended. - const qpc = windows.QueryPerformanceCounter(); // We don't need to cache QPF as it's internally just a memory read to KUSER_SHARED_DATA // (a read-only page of info updated and mapped by the kernel to all processes): // https://docs.microsoft.com/en-us/windows-hardware/drivers/ddi/ntddk/ns-ntddk-kuser_shared_data // https://www.geoffchappell.com/studies/windows/km/ntoskrnl/inc/api/ntexapi_x/kuser_shared_data/index.htm - const qpf = windows.QueryPerformanceFrequency(); + const qpf: u64 = qpf: { + var qpf: windows.LARGE_INTEGER = undefined; + assert(windows.ntdll.RtlQueryPerformanceFrequency(&qpf) != windows.FALSE); + break :qpf @bitCast(qpf); + }; + + // QPC on windows doesn't fail on >= XP/2000 and includes time suspended. + const qpc: u64 = qpc: { + var qpc: windows.LARGE_INTEGER = undefined; + assert(windows.ntdll.RtlQueryPerformanceCounter(&qpc) != windows.FALSE); + break :qpc @bitCast(qpc); + }; // 10Mhz (1 qpc tick every 100ns) is a common enough QPF value that we can optimize on it. // https://github.com/microsoft/STL/blob/785143a0c73f030238ef618890fd4d6ae2b3a3a0/stl/inc/chrono#L694-L701 @@ -11458,7 +11460,7 @@ fn nowWindows(clock: Io.Clock) Io.Timestamp { // https://github.com/reactos/reactos/blob/master/ntoskrnl/ps/query.c#L442-L485 if (windows.ntdll.NtQueryInformationProcess( handle, - windows.PROCESSINFOCLASS.Times, + .Times, ×, @sizeOf(windows.KERNEL_USER_TIMES), null, @@ -11474,7 +11476,7 @@ fn nowWindows(clock: Io.Clock) Io.Timestamp { // https://github.com/reactos/reactos/blob/master/ntoskrnl/ps/query.c#L2971-L3019 if (windows.ntdll.NtQueryInformationThread( handle, - windows.THREADINFOCLASS.Times, + .Times, ×, @sizeOf(windows.KERNEL_USER_TIMES), null, @@ -14132,21 +14134,16 @@ fn processCurrentPath(userdata: ?*anyopaque, buffer: []u8) process.CurrentPathEr } fn processSetCurrentDir(userdata: ?*anyopaque, dir: Dir) process.SetCurrentDirError!void { - if (native_os == .wasi) return error.OperationUnsupported; const t: *Threaded = @ptrCast(@alignCast(userdata)); _ = t; + if (native_os == .wasi) return error.OperationUnsupported; + if (is_windows) { - var dir_path_buffer: [windows.PATH_MAX_WIDE]u16 = undefined; - const dir_path = try GetFinalPathNameByHandle(dir.handle, .{}, &dir_path_buffer); - const path_len_bytes = std.math.cast(u16, dir_path.len * 2) orelse return error.NameTooLong; - var nt_name: windows.UNICODE_STRING = .{ - .Length = path_len_bytes, - .MaximumLength = path_len_bytes, - .Buffer = @constCast(dir_path.ptr), - }; + var dir_path_buf: [windows.PATH_MAX_WIDE]u16 = undefined; + const dir_path = try GetFinalPathNameByHandle(dir.handle, .{}, &dir_path_buf); const syscall: Syscall = try .start(); - while (true) switch (windows.ntdll.RtlSetCurrentDirectory_U(&nt_name)) { + while (true) switch (windows.ntdll.RtlSetCurrentDirectory_U(&.init(dir_path))) { .SUCCESS => return syscall.finish(), .OBJECT_NAME_INVALID => return syscall.fail(error.BadPathName), .OBJECT_NAME_NOT_FOUND => return syscall.fail(error.FileNotFound), @@ -15465,26 +15462,27 @@ fn childKill(userdata: ?*anyopaque, child: *process.Child) void { fn childKillWindows(t: *Threaded, child: *process.Child, exit_code: windows.UINT) !void { _ = t; // TODO cancelation const handle = child.id.?; - if (windows.kernel32.TerminateProcess(handle, exit_code) == 0) { - switch (windows.GetLastError()) { - .ACCESS_DENIED => { - // Usually when TerminateProcess triggers a ACCESS_DENIED error, it - // indicates that the process has already exited, but there may be - // some rare edge cases where our process handle no longer has the - // PROCESS_TERMINATE access right, so let's do another check to make - // sure the process is really no longer running: - const minimal_timeout: windows.LARGE_INTEGER = -1; - switch (windows.ntdll.NtWaitForSingleObject(handle, windows.FALSE, &minimal_timeout)) { - .SUCCESS => return error.AlreadyTerminated, - else => return error.AccessDenied, - } - }, - else => |err| return windows.unexpectedError(err), - } + _ = windows.ntdll.RtlReportSilentProcessExit(handle, @enumFromInt(exit_code)); + switch (windows.ntdll.NtTerminateProcess(handle, @enumFromInt(exit_code))) { + .SUCCESS => { + const infinite_timeout: windows.LARGE_INTEGER = std.math.minInt(windows.LARGE_INTEGER); + _ = windows.ntdll.NtWaitForSingleObject(handle, windows.FALSE, &infinite_timeout); + childCleanupWindows(child); + }, + .ACCESS_DENIED => { + // Usually when TerminateProcess triggers a ACCESS_DENIED error, it + // indicates that the process has already exited, but there may be + // some rare edge cases where our process handle no longer has the + // PROCESS_TERMINATE access right, so let's do another check to make + // sure the process is really no longer running: + const minimal_timeout: windows.LARGE_INTEGER = -1; + return switch (windows.ntdll.NtWaitForSingleObject(handle, windows.FALSE, &minimal_timeout)) { + windows.NTSTATUS.WAIT_0 => error.AlreadyTerminated, + else => error.AccessDenied, + }; + }, + else => |status| return windows.unexpectedStatus(status), } - const infinite_timeout: windows.LARGE_INTEGER = std.math.minInt(windows.LARGE_INTEGER); - _ = windows.ntdll.NtWaitForSingleObject(handle, windows.FALSE, &infinite_timeout); - childCleanupWindows(child); } fn childWaitWindows(child: *process.Child) process.Child.WaitError!process.Child.Term { @@ -15501,12 +15499,12 @@ fn childWaitWindows(child: *process.Child) process.Child.WaitError!process.Child else => |status| return alertable_syscall.unexpectedNtstatus(status), }; - var info: windows.PROCESS_BASIC_INFORMATION = undefined; + var info: windows.PROCESS.BASIC_INFORMATION = undefined; const term: process.Child.Term = switch (windows.ntdll.NtQueryInformationProcess( handle, .BasicInformation, &info, - @sizeOf(windows.PROCESS_BASIC_INFORMATION), + @sizeOf(windows.PROCESS.BASIC_INFORMATION), null, )) { .SUCCESS => .{ .exited = @as(u8, @truncate(@intFromEnum(info.ExitStatus))) }, @@ -15521,12 +15519,12 @@ fn childCleanupWindows(child: *process.Child) void { const handle = child.id orelse return; if (child.request_resource_usage_statistics) { - var vmc: windows.VM_COUNTERS = undefined; + var vmc: windows.PROCESS.VM_COUNTERS = undefined; switch (windows.ntdll.NtQueryInformationProcess( handle, .VmCounters, &vmc, - @sizeOf(windows.VM_COUNTERS), + @sizeOf(windows.PROCESS.VM_COUNTERS), null, )) { .SUCCESS => child.resource_usage_statistics.rusage = vmc, @@ -15853,7 +15851,7 @@ fn processSpawnWindows(userdata: ?*anyopaque, options: process.SpawnOptions) pro .cbReserved2 = 0, .lpReserved2 = null, }; - var piProcInfo: windows.PROCESS_INFORMATION = undefined; + var piProcInfo: windows.PROCESS.INFORMATION = undefined; var arena_allocator = std.heap.ArenaAllocator.init(t.allocator); defer arena_allocator.deinit(); @@ -16062,7 +16060,6 @@ fn getCngDevice(t: *Threaded) Io.RandomSecureError!windows.HANDLE { if (t.random_file.handle) |handle| return handle; } - const device_path = [_]u16{ '\\', 'D', 'e', 'v', 'i', 'c', 'e', '\\', 'C', 'N', 'G' }; var fresh_handle: windows.HANDLE = undefined; var io_status_block: windows.IO_STATUS_BLOCK = undefined; var syscall: Syscall = try .start(); @@ -16072,13 +16069,9 @@ fn getCngDevice(t: *Threaded) Io.RandomSecureError!windows.HANDLE { .STANDARD = .{ .SYNCHRONIZE = true }, .SPECIFIC = .{ .FILE = .{ .READ_DATA = true } }, }, - &.{ - .ObjectName = @constCast(&windows.UNICODE_STRING{ - .Length = @sizeOf(@TypeOf(device_path)), - .MaximumLength = 0, - .Buffer = @constCast(&device_path), - }), - }, + &.{ .ObjectName = @constCast(&windows.UNICODE_STRING.init( + &.{ '\\', 'D', 'e', 'v', 'i', 'c', 'e', '\\', 'C', 'N', 'G' }, + )) }, &io_status_block, .VALID_FLAGS, .{ .IO = .SYNCHRONOUS_NONALERT }, @@ -16111,7 +16104,6 @@ fn getNulDevice(t: *Threaded) !windows.HANDLE { if (t.null_file.handle) |handle| return handle; } - const device_path = [_]u16{ '\\', 'D', 'e', 'v', 'i', 'c', 'e', '\\', 'N', 'u', 'l', 'l' }; var fresh_handle: windows.HANDLE = undefined; var io_status_block: windows.IO_STATUS_BLOCK = undefined; var syscall: Syscall = try .start(); @@ -16123,11 +16115,9 @@ fn getNulDevice(t: *Threaded) !windows.HANDLE { }, &.{ .Attributes = .{ .INHERIT = true }, - .ObjectName = @constCast(&windows.UNICODE_STRING{ - .Length = @sizeOf(@TypeOf(device_path)), - .MaximumLength = 0, - .Buffer = @constCast(&device_path), - }), + .ObjectName = @constCast(&windows.UNICODE_STRING.init( + &.{ '\\', 'D', 'e', 'v', 'i', 'c', 'e', '\\', 'N', 'u', 'l', 'l' }, + )), }, &io_status_block, .VALID_FLAGS, @@ -16173,7 +16163,6 @@ fn getNamedPipeDevice(t: *Threaded) !windows.HANDLE { if (t.pipe_file.handle) |handle| return handle; } - const device_path = [_]u16{ '\\', 'D', 'e', 'v', 'i', 'c', 'e', '\\', 'N', 'a', 'm', 'e', 'd', 'P', 'i', 'p', 'e', '\\' }; var fresh_handle: windows.HANDLE = undefined; var io_status_block: windows.IO_STATUS_BLOCK = undefined; var syscall: Syscall = try .start(); @@ -16181,11 +16170,9 @@ fn getNamedPipeDevice(t: *Threaded) !windows.HANDLE { &fresh_handle, .{ .STANDARD = .{ .SYNCHRONIZE = true } }, &.{ - .ObjectName = @constCast(&windows.UNICODE_STRING{ - .Length = @sizeOf(@TypeOf(device_path)), - .MaximumLength = 0, - .Buffer = @constCast(&device_path), - }), + .ObjectName = @constCast(&windows.UNICODE_STRING.init( + &.{ '\\', 'D', 'e', 'v', 'i', 'c', 'e', '\\', 'N', 'a', 'm', 'e', 'd', 'P', 'i', 'p', 'e', '\\' }, + )), }, &io_status_block, .VALID_FLAGS, @@ -16253,7 +16240,7 @@ fn windowsCreateProcessPathExt( cwd_ptr: ?[*:0]u16, flags: windows.CreateProcessFlags, lpStartupInfo: *windows.STARTUPINFOW, - lpProcessInformation: *windows.PROCESS_INFORMATION, + lpProcessInformation: *windows.PROCESS.INFORMATION, ) !void { const app_name_len = app_buf.items.len; const dir_path_len = dir_buf.items.len; @@ -16341,13 +16328,9 @@ fn windowsCreateProcessPathExt( // On NTFS, `blah.exe*` will always return `blah.exe` first if it exists. // On FAT32, it's possible for something like `blah.exe.obj` to be returned first. while (true) { - const app_name_len_bytes = std.math.cast(u16, app_name_wildcard.len * 2) orelse return error.NameTooLong; - var app_name_unicode_string = windows.UNICODE_STRING{ - .Length = app_name_len_bytes, - .MaximumLength = app_name_len_bytes, - .Buffer = @constCast(app_name_wildcard.ptr), - }; - const rc = windows.ntdll.NtQueryDirectoryFile( + // If we get nothing with the wildcard, then we can just bail out + // as we know appending PATHEXT will not yield anything. + switch (windows.ntdll.NtQueryDirectoryFile( dir.handle, null, null, @@ -16357,18 +16340,14 @@ fn windowsCreateProcessPathExt( file_information_buf.len, .Directory, windows.FALSE, // single result - &app_name_unicode_string, + &.init(app_name_wildcard), windows.FALSE, // restart iteration - ); - - // If we get nothing with the wildcard, then we can just bail out - // as we know appending PATHEXT will not yield anything. - switch (rc) { + )) { .SUCCESS => {}, .NO_SUCH_FILE => return error.FileNotFound, .NO_MORE_FILES => break, .ACCESS_DENIED => return error.AccessDenied, - else => return windows.unexpectedStatus(rc), + else => |status| return windows.unexpectedStatus(status), } // According to the docs, this can only happen if there is not enough room in the @@ -16514,7 +16493,7 @@ fn windowsCreateProcess( cwd_ptr: ?[*:0]u16, flags: windows.CreateProcessFlags, lpStartupInfo: *windows.STARTUPINFOW, - lpProcessInformation: *windows.PROCESS_INFORMATION, + lpProcessInformation: *windows.PROCESS.INFORMATION, ) !void { const syscall: Syscall = try .start(); while (true) { @@ -17138,7 +17117,7 @@ pub const CreatePipeOptions = struct { default_timeout: windows.LARGE_INTEGER = -120 * std.time.ns_per_s / 100, pub const End = struct { - attributes: windows.OBJECT_ATTRIBUTES.ATTRIBUTES = .{}, + attributes: windows.OBJECT.ATTRIBUTES.Flags = .{}, mode: windows.FILE.MODE, }; }; @@ -18373,7 +18352,7 @@ const CreateFileMapError = error{ OutOfMemory, MappingAlreadyExists, Unseekable, - FileLockConflict, + LockViolation, } || Io.Cancelable || Io.UnexpectedError; fn createFileMap( @@ -18408,7 +18387,7 @@ fn createFileMap( file.handle, )) { .SUCCESS => {}, - .FILE_LOCK_CONFLICT => return error.FileLockConflict, + .FILE_LOCK_CONFLICT => return error.LockViolation, .INVALID_FILE_FOR_SECTION => return error.OperationUnsupported, .ACCESS_DENIED => return error.AccessDenied, .SECTION_TOO_BIG => return error.SectionOversize, @@ -18724,7 +18703,7 @@ fn mmSyncWrite(file: File, memory: []u8, offset: u64) File.WritePositionalError! while (true) { const buf = memory[i..]; if (buf.len == 0) break; - i += try writeFilePositionalWindows(file.handle, memory[i..], offset + i); + i += try writeFilePositionalWindows(file, memory[i..], offset + i); } } else if (native_os == .wasi and !builtin.link_libc) { var i: usize = 0; @@ -18809,8 +18788,7 @@ fn mmSyncWrite(file: File, memory: []u8, offset: u64) File.WritePositionalError! } } -fn deviceIoControl(t: *Threaded, o: *const Io.Operation.DeviceIoControl) Io.Cancelable!Io.Operation.DeviceIoControl.Result { - _ = t; +fn deviceIoControl(o: *const Io.Operation.DeviceIoControl) Io.Cancelable!Io.Operation.DeviceIoControl.Result { if (is_windows) { const NtControlFile = switch (o.code.DeviceType) { .FILE_SYSTEM, .NAMED_PIPE => &windows.ntdll.NtFsControlFile, @@ -19104,16 +19082,10 @@ fn OpenFile(sub_path_w: []const u16, options: OpenFileOptions) OpenError!windows var result: windows.HANDLE = undefined; - const path_len_bytes = std.math.cast(u16, sub_path_w.len * 2) orelse return error.NameTooLong; - var nt_name: windows.UNICODE_STRING = .{ - .Length = path_len_bytes, - .MaximumLength = path_len_bytes, - .Buffer = @constCast(sub_path_w.ptr), - }; - const attr: windows.OBJECT_ATTRIBUTES = .{ + const attr: windows.OBJECT.ATTRIBUTES = .{ .RootDirectory = if (Dir.path.isAbsoluteWindowsWtf16(sub_path_w)) null else options.dir, .Attributes = .{ .INHERIT = if (options.sa) |sa| sa.bInheritHandle != windows.FALSE else false }, - .ObjectName = &nt_name, + .ObjectName = @constCast(&windows.UNICODE_STRING.init(sub_path_w)), .SecurityDescriptor = if (options.sa) |ptr| ptr.lpSecurityDescriptor else null, }; diff --git a/lib/std/Io/Threaded/test.zig b/lib/std/Io/Threaded/test.zig index 1c9b188584b588a44c225e51450390787eb81557..fff802b1913734a2baaa9f3b43b626645530ce67 100644 --- a/lib/std/Io/Threaded/test.zig +++ b/lib/std/Io/Threaded/test.zig @@ -290,7 +290,7 @@ fn RtlDosPathNameToNtPathName_U(path: [:0]const u16) !Io.Threaded.WindowsPathSpa defer windows.ntdll.RtlFreeUnicodeString(&out); var path_space: Io.Threaded.WindowsPathSpace = undefined; - const out_path = out.Buffer.?[0 .. out.Length / 2]; + const out_path = out.slice(); @memcpy(path_space.data[0..out_path.len], out_path); path_space.len = out.Length / 2; path_space.data[path_space.len] = 0; diff --git a/lib/std/Thread.zig b/lib/std/Thread.zig index 131f57dcf860d59d89b5cec88aa491bbf06fc719..11466fb9a251a7ad44363995425ea9eccb8a19d1 100644 --- a/lib/std/Thread.zig +++ b/lib/std/Thread.zig @@ -88,20 +88,10 @@ pub fn setName(self: Thread, io: Io, name: []const u8) SetNameError!void { }, .windows => { var buf: [max_name_len]u16 = undefined; - const len = try std.unicode.wtf8ToWtf16Le(&buf, name); - const byte_len = math.cast(c_ushort, len * 2) orelse return error.NameTooLong; - - // Note: NT allocates its own copy, no use-after-free here. - const unicode_string = windows.UNICODE_STRING{ - .Length = byte_len, - .MaximumLength = byte_len, - .Buffer = &buf, - }; - switch (windows.ntdll.NtSetInformationThread( self.getHandle(), .NameInformation, - &unicode_string, + &windows.UNICODE_STRING.init(buf[0..try std.unicode.wtf8ToWtf16Le(&buf, name)]), @sizeOf(windows.UNICODE_STRING), )) { .SUCCESS => return, @@ -217,8 +207,8 @@ pub fn getName(self: Thread, buffer_ptr: *[max_name_len:0]u8) GetNameError!?[]co null, )) { .SUCCESS => { - const string = @as(*const windows.UNICODE_STRING, @ptrCast(&buf)); - const len = std.unicode.wtf16LeToWtf8(buffer, string.Buffer.?[0 .. string.Length / 2]); + const string: *const windows.UNICODE_STRING = @ptrCast(&buf); + const len = std.unicode.wtf16LeToWtf8(buffer, string.slice()); return if (len > 0) buffer[0..len] else null; }, .NOT_IMPLEMENTED => return error.Unsupported, diff --git a/lib/std/debug.zig b/lib/std/debug.zig index 8f83bf19c8704aa41e2dbfae6429ed961cd12bbf..a2b5467c1f4aec44657b58f8c33eab8adac47de0 100644 --- a/lib/std/debug.zig +++ b/lib/std/debug.zig @@ -37,12 +37,13 @@ pub const cpu_context = @import("debug/cpu_context.zig"); /// /// ``` /// pub const init: SelfInfo; -/// pub fn deinit(si: *SelfInfo, gpa: Allocator) void; +/// pub fn deinit(si: *SelfInfo, io: Io) void; /// /// /// Returns the symbol and source location of the instruction at `address`. -/// pub fn getSymbol(si: *SelfInfo, gpa: Allocator, io: Io, address: usize) SelfInfoError!Symbol; +/// pub fn getSymbol(si: *SelfInfo, io: Io, address: usize) SelfInfoError!Symbol; /// /// Returns a name for the "module" (e.g. shared library or executable image) containing `address`. -/// pub fn getModuleName(si: *SelfInfo, gpa: Allocator, address: usize) SelfInfoError![]const u8; +/// pub fn getModuleName(si: *SelfInfo, io: Io, address: usize) SelfInfoError![]const u8; +/// pub fn getModuleSlide(si: *SelfInfo, io: Io, address: usize) SelfInfoError!usize; /// /// /// Whether a reliable stack unwinding strategy, such as DWARF unwinding, is available. /// pub const can_unwind: bool; @@ -51,15 +52,15 @@ pub const cpu_context = @import("debug/cpu_context.zig"); /// /// An address representing the instruction pointer in the last frame. /// pc: usize, /// -/// pub fn init(ctx: *cpu_context.Native, gpa: Allocator) Allocator.Error!UnwindContext; -/// pub fn deinit(ctx: *UnwindContext, gpa: Allocator) void; +/// pub fn init(ctx: *cpu_context.Native) Allocator.Error!UnwindContext; +/// pub fn deinit(ctx: *UnwindContext) void; /// /// Returns the frame pointer associated with the last unwound stack frame. /// /// If the frame pointer is unknown, 0 may be returned instead. /// pub fn getFp(uc: *UnwindContext) usize; /// }; /// /// Only required if `can_unwind == true`. Unwinds a single stack frame, returning the frame's /// /// return address, or 0 if the end of the stack has been reached. -/// pub fn unwindFrame(si: *SelfInfo, gpa: Allocator, io: Io, context: *UnwindContext) SelfInfoError!usize; +/// pub fn unwindFrame(si: *SelfInfo, io: Io, context: *UnwindContext) SelfInfoError!usize; /// ``` pub const SelfInfo = if (@hasDecl(root, "debug") and @hasDecl(root.debug, "SelfInfo")) root.debug.SelfInfo @@ -669,7 +670,6 @@ pub noinline fn writeCurrentStackTrace(options: StackUnwindOptions, t: Io.Termin t.setColor(.reset) catch {}; return; } - const di_gpa = getDebugInfoAllocator(); const di = getSelfDebugInfo() catch |err| switch (err) { error.UnsupportedTarget => { t.setColor(.dim) catch {}; @@ -696,7 +696,7 @@ pub noinline fn writeCurrentStackTrace(options: StackUnwindOptions, t: Io.Termin .useless, .unsafe => {}, .safe, .ideal => continue, // no need to even warn } - const module_name = di.getModuleName(di_gpa, io, unwind_error.address) catch "???"; + const module_name = di.getModuleName(io, unwind_error.address) catch "???"; const caption: []const u8 = switch (unwind_error.err) { error.MissingDebugInfo => "unwind info unavailable", error.InvalidDebugInfo => "unwind info invalid", @@ -741,7 +741,7 @@ pub noinline fn writeCurrentStackTrace(options: StackUnwindOptions, t: Io.Termin } // `ret_addr` is the return address, which is *after* the function call. // Subtract 1 to get an address *in* the function call for a better source location. - try printSourceAtAddress(di_gpa, io, di, t, ret_addr -| StackIterator.ra_call_offset); + try printSourceAtAddress(io, di, t, ret_addr -| StackIterator.ra_call_offset); printed_any_frame = true; }, }; @@ -788,7 +788,6 @@ pub fn writeStackTrace(st: *const StackTrace, t: Io.Terminal) Writer.Error!void // `st` is `@errorReturnTrace()` and errors are encountered while writing the stack trace. const n_frames = st.index; if (n_frames == 0) return writer.writeAll("(empty stack trace)\n"); - const di_gpa = getDebugInfoAllocator(); const di = getSelfDebugInfo() catch |err| switch (err) { error.UnsupportedTarget => { t.setColor(.dim) catch {}; @@ -802,7 +801,7 @@ pub fn writeStackTrace(st: *const StackTrace, t: Io.Terminal) Writer.Error!void for (st.instruction_addresses[0..captured_frames]) |ret_addr| { // `ret_addr` is the return address, which is *after* the function call. // Subtract 1 to get an address *in* the function call for a better source location. - try printSourceAtAddress(di_gpa, io, di, t, ret_addr -| StackIterator.ra_call_offset); + try printSourceAtAddress(io, di, t, ret_addr -| StackIterator.ra_call_offset); } if (n_frames > captured_frames) { t.setColor(.bold) catch {}; @@ -875,7 +874,7 @@ const StackIterator = union(enum) { switch (si.*) { .ctx_first => {}, .fp => {}, - .di => |*unwind_context| unwind_context.deinit(getDebugInfoAllocator()), + .di => |*unwind_context| unwind_context.deinit(), } } @@ -980,8 +979,7 @@ const StackIterator = union(enum) { }, .di => |*unwind_context| { const di = getSelfDebugInfo() catch unreachable; - const di_gpa = getDebugInfoAllocator(); - const ret_addr = di.unwindFrame(di_gpa, io, unwind_context) catch |err| { + const ret_addr = di.unwindFrame(io, unwind_context) catch |err| { const pc = unwind_context.pc; const fp = unwind_context.getFp(); it.* = .{ .fp = fp }; @@ -1109,14 +1107,8 @@ pub inline fn stripInstructionPtrAuthCode(ptr: usize) usize { return ptr; } -fn printSourceAtAddress( - gpa: Allocator, - io: Io, - debug_info: *SelfInfo, - t: Io.Terminal, - address: usize, -) Writer.Error!void { - const symbol: Symbol = debug_info.getSymbol(gpa, io, address) catch |err| switch (err) { +fn printSourceAtAddress(io: Io, debug_info: *SelfInfo, t: Io.Terminal, address: usize) Writer.Error!void { + const symbol: Symbol = debug_info.getSymbol(io, address) catch |err| switch (err) { error.MissingDebugInfo, error.UnsupportedDebugInfo, error.InvalidDebugInfo, @@ -1134,14 +1126,14 @@ fn printSourceAtAddress( break :s .unknown; }, }; - defer if (symbol.source_location) |sl| gpa.free(sl.file_name); + defer if (symbol.source_location) |sl| getDebugInfoAllocator().free(sl.file_name); return printLineInfo( io, t, symbol.source_location, address, symbol.name orelse "???", - symbol.compile_unit_name orelse debug_info.getModuleName(gpa, io, address) catch "???", + symbol.compile_unit_name orelse debug_info.getModuleName(io, address) catch "???", ); } fn printLineInfo( @@ -1608,14 +1600,13 @@ test "manage resources correctly" { return @returnAddress(); } }; - const gpa = testing.allocator; const io = testing.io; var discarding: Writer.Discarding = .init(&.{}); var di: SelfInfo = .init; - defer di.deinit(gpa); + defer di.deinit(io); const t: Io.Terminal = .{ .writer = &discarding.writer, .mode = .no_color }; - try printSourceAtAddress(gpa, io, &di, t, S.showMyTrace()); + try printSourceAtAddress(io, &di, t, S.showMyTrace()); } /// This API helps you track where a value originated and where it was mutated, diff --git a/lib/std/debug/Dwarf/SelfUnwinder.zig b/lib/std/debug/Dwarf/SelfUnwinder.zig index 80908e84bb9eb5f5a1257c3069b1b96157825715..0f5253844de3cdcd97d57e18c8d0ca6c4ed231c8 100644 --- a/lib/std/debug/Dwarf/SelfUnwinder.zig +++ b/lib/std/debug/Dwarf/SelfUnwinder.zig @@ -55,7 +55,8 @@ pub fn init(cpu_context: *const std.debug.cpu_context.Native) SelfUnwinder { }; } -pub fn deinit(unwinder: *SelfUnwinder, gpa: Allocator) void { +pub fn deinit(unwinder: *SelfUnwinder) void { + const gpa = std.debug.getDebugInfoAllocator(); unwinder.cfi_vm.deinit(gpa); unwinder.expr_vm.deinit(gpa); unwinder.* = undefined; diff --git a/lib/std/debug/SelfInfo/Elf.zig b/lib/std/debug/SelfInfo/Elf.zig index 40841bac2c351d824604556c7c67f1548aa964a0..a264b1170db05e5ee5510b75b0bdf354e3ae3026 100644 --- a/lib/std/debug/SelfInfo/Elf.zig +++ b/lib/std/debug/SelfInfo/Elf.zig @@ -11,7 +11,9 @@ pub const init: SelfInfo = .{ .ranges = .empty, .unwind_cache = null, }; -pub fn deinit(si: *SelfInfo, gpa: Allocator) void { +pub fn deinit(si: *SelfInfo, io: Io) void { + _ = io; + const gpa = std.debug.getDebugInfoAllocator(); for (si.modules.items) |*mod| { unwind: { const u = &(mod.unwind orelse break :unwind catch break :unwind); @@ -28,7 +30,8 @@ pub fn deinit(si: *SelfInfo, gpa: Allocator) void { if (si.unwind_cache) |cache| gpa.free(cache); } -pub fn getSymbol(si: *SelfInfo, gpa: Allocator, io: Io, address: usize) Error!std.debug.Symbol { +pub fn getSymbol(si: *SelfInfo, io: Io, address: usize) Error!std.debug.Symbol { + const gpa = std.debug.getDebugInfoAllocator(); const module = try si.findModule(gpa, io, address, .exclusive); defer si.rwlock.unlock(io); @@ -73,13 +76,15 @@ pub fn getSymbol(si: *SelfInfo, gpa: Allocator, io: Io, address: usize) Error!st error.OutOfMemory => |e| return e, }; } -pub fn getModuleName(si: *SelfInfo, gpa: Allocator, io: Io, address: usize) Error![]const u8 { +pub fn getModuleName(si: *SelfInfo, io: Io, address: usize) Error![]const u8 { + const gpa = std.debug.getDebugInfoAllocator(); const module = try si.findModule(gpa, io, address, .shared); defer si.rwlock.unlockShared(io); if (module.name.len == 0) return error.MissingDebugInfo; return module.name; } -pub fn getModuleSlide(si: *SelfInfo, gpa: Allocator, io: Io, address: usize) Error!usize { +pub fn getModuleSlide(si: *SelfInfo, io: Io, address: usize) Error!usize { + const gpa = std.debug.getDebugInfoAllocator(); const module = try si.findModule(gpa, io, address, .shared); defer si.rwlock.unlockShared(io); return module.load_offset; @@ -179,8 +184,9 @@ comptime { } } pub const UnwindContext = Dwarf.SelfUnwinder; -pub fn unwindFrame(si: *SelfInfo, gpa: Allocator, io: Io, context: *UnwindContext) Error!usize { +pub fn unwindFrame(si: *SelfInfo, io: Io, context: *UnwindContext) Error!usize { comptime assert(can_unwind); + const gpa = std.debug.getDebugInfoAllocator(); { si.rwlock.lockSharedUncancelable(io); diff --git a/lib/std/debug/SelfInfo/MachO.zig b/lib/std/debug/SelfInfo/MachO.zig index 1cc2ebed53939959c2e1712d7afdf58dfe1178e5..6b184fec7a16066397c6961b7cc55492ef4fb26e 100644 --- a/lib/std/debug/SelfInfo/MachO.zig +++ b/lib/std/debug/SelfInfo/MachO.zig @@ -6,7 +6,9 @@ pub const init: SelfInfo = .{ .mutex = .init, .modules = .empty, }; -pub fn deinit(si: *SelfInfo, gpa: Allocator) void { +pub fn deinit(si: *SelfInfo, io: Io) void { + _ = io; + const gpa = std.debug.getDebugInfoAllocator(); for (si.modules.keys()) |*module| { unwind: { const u = &(module.unwind orelse break :unwind catch break :unwind); @@ -20,7 +22,8 @@ pub fn deinit(si: *SelfInfo, gpa: Allocator) void { si.modules.deinit(gpa); } -pub fn getSymbol(si: *SelfInfo, gpa: Allocator, io: Io, address: usize) Error!std.debug.Symbol { +pub fn getSymbol(si: *SelfInfo, io: Io, address: usize) Error!std.debug.Symbol { + const gpa = std.debug.getDebugInfoAllocator(); const module = try si.findModule(gpa, io, address); defer si.mutex.unlock(io); @@ -76,9 +79,8 @@ pub fn getSymbol(si: *SelfInfo, gpa: Allocator, io: Io, address: usize) Error!st ) catch null, }; } -pub fn getModuleName(si: *SelfInfo, gpa: Allocator, io: Io, address: usize) Error![]const u8 { +pub fn getModuleName(si: *SelfInfo, io: Io, address: usize) Error![]const u8 { _ = si; - _ = gpa; _ = io; // This function is marked as deprecated; however, it is significantly more // performant than `dladdr` (since the latter also does a very slow symbol @@ -87,7 +89,8 @@ pub fn getModuleName(si: *SelfInfo, gpa: Allocator, io: Io, address: usize) Erro @ptrFromInt(address), ) orelse return error.MissingDebugInfo); } -pub fn getModuleSlide(si: *SelfInfo, gpa: Allocator, io: Io, address: usize) Error!usize { +pub fn getModuleSlide(si: *SelfInfo, io: Io, address: usize) Error!usize { + const gpa = std.debug.getDebugInfoAllocator(); const module = try si.findModule(gpa, io, address); defer si.mutex.unlock(io); const header: *std.macho.mach_header_64 = @ptrFromInt(module.text_base); @@ -107,8 +110,8 @@ pub const UnwindContext = std.debug.Dwarf.SelfUnwinder; /// Unwind a frame using MachO compact unwind info (from `__unwind_info`). /// If the compact encoding can't encode a way to unwind a frame, it will /// defer unwinding to DWARF, in which case `__eh_frame` will be used if available. -pub fn unwindFrame(si: *SelfInfo, gpa: Allocator, io: Io, context: *UnwindContext) Error!usize { - return unwindFrameInner(si, gpa, io, context) catch |err| switch (err) { +pub fn unwindFrame(si: *SelfInfo, io: Io, context: *UnwindContext) Error!usize { + return unwindFrameInner(si, io, context) catch |err| switch (err) { error.InvalidDebugInfo, error.MissingDebugInfo, error.UnsupportedDebugInfo, @@ -134,7 +137,8 @@ pub fn unwindFrame(si: *SelfInfo, gpa: Allocator, io: Io, context: *UnwindContex => return error.InvalidDebugInfo, }; } -fn unwindFrameInner(si: *SelfInfo, gpa: Allocator, io: Io, context: *UnwindContext) !usize { +fn unwindFrameInner(si: *SelfInfo, io: Io, context: *UnwindContext) !usize { + const gpa = std.debug.getDebugInfoAllocator(); const module = try si.findModule(gpa, io, context.pc); defer si.mutex.unlock(io); diff --git a/lib/std/debug/SelfInfo/Windows.zig b/lib/std/debug/SelfInfo/Windows.zig index 75cc329c3b0606846302234a8e93273e8e58b484..7cac79d72759eab9c4e1b66b2923aab96b9a17cc 100644 --- a/lib/std/debug/SelfInfo/Windows.zig +++ b/lib/std/debug/SelfInfo/Windows.zig @@ -1,39 +1,51 @@ mutex: Io.Mutex, +ntdll_handle: ?if (load_dll_notification_procs) *anyopaque else noreturn, +notification_cookie: ?LDR.DLL_NOTIFICATION.COOKIE, modules: std.ArrayList(Module), -module_name_arena: std.heap.ArenaAllocator.State, pub const init: SelfInfo = .{ .mutex = .init, + .ntdll_handle = null, + .notification_cookie = null, .modules = .empty, - .module_name_arena = .{}, }; -pub fn deinit(si: *SelfInfo, gpa: Allocator) void { - for (si.modules.items) |*module| { - di: { - const di = &(module.di orelse break :di catch break :di); - di.deinit(gpa); +pub fn deinit(si: *SelfInfo, io: Io) void { + const gpa = std.debug.getDebugInfoAllocator(); + if (si.notification_cookie) |cookie| unregister: { + switch ((si.getNtdllProc(.LdrUnregisterDllNotification) catch break :unregister)(cookie)) { + .SUCCESS => {}, + else => |status| windows.unexpectedStatus(status) catch break :unregister, } } + if (si.ntdll_handle) |handle| switch (windows.ntdll.LdrUnloadDll(handle)) { + .SUCCESS => {}, + else => |status| windows.unexpectedStatus(status) catch {}, + }; + for (si.modules.items) |*module| module.deinit(gpa, io); si.modules.deinit(gpa); - - var module_name_arena = si.module_name_arena.promote(gpa); - module_name_arena.deinit(); } -pub fn getSymbol(si: *SelfInfo, gpa: Allocator, io: Io, address: usize) Error!std.debug.Symbol { +pub fn getSymbol(si: *SelfInfo, io: Io, address: usize) Error!std.debug.Symbol { + const gpa = std.debug.getDebugInfoAllocator(); try si.mutex.lock(io); defer si.mutex.unlock(io); const module = try si.findModule(gpa, address); const di = try module.getDebugInfo(gpa, io); - return di.getSymbol(gpa, address - module.base_address); + return di.getSymbol(gpa, address - @intFromPtr(module.entry.DllBase)); } -pub fn getModuleName(si: *SelfInfo, gpa: Allocator, io: Io, address: usize) Error![]const u8 { +pub fn getModuleName(si: *SelfInfo, io: Io, address: usize) Error![]const u8 { + const gpa = std.debug.getDebugInfoAllocator(); try si.mutex.lock(io); defer si.mutex.unlock(io); const module = try si.findModule(gpa, address); - return module.name; + return module.name orelse { + const name = try std.unicode.wtf16LeToWtf8Alloc(gpa, module.entry.BaseDllName.slice()); + module.name = name; + return name; + }; } -pub fn getModuleSlide(si: *SelfInfo, gpa: Allocator, io: Io, address: usize) Error!usize { +pub fn getModuleSlide(si: *SelfInfo, io: Io, address: usize) Error!usize { + const gpa = std.debug.getDebugInfoAllocator(); try si.mutex.lock(io); defer si.mutex.unlock(io); const module = try si.findModule(gpa, address); @@ -141,18 +153,16 @@ pub const UnwindContext = struct { .history_table = std.mem.zeroes(windows.UNWIND_HISTORY_TABLE), }; } - pub fn deinit(ctx: *UnwindContext, gpa: Allocator) void { + pub fn deinit(ctx: *UnwindContext) void { _ = ctx; - _ = gpa; } pub fn getFp(ctx: *UnwindContext) usize { return ctx.cur.getRegs().bp; } }; -pub fn unwindFrame(si: *SelfInfo, gpa: Allocator, io: Io, context: *UnwindContext) Error!usize { +pub fn unwindFrame(si: *SelfInfo, io: Io, context: *UnwindContext) Error!usize { _ = si; _ = io; - _ = gpa; const current_regs = context.cur.getRegs(); var image_base: usize = undefined; @@ -188,16 +198,12 @@ pub fn unwindFrame(si: *SelfInfo, gpa: Allocator, io: Io, context: *UnwindContex } const Module = struct { - base_address: usize, - size: u32, - name: []const u8, - handle: windows.HMODULE, - + entry: *const LDR.DATA_TABLE_ENTRY, + name: ?[]const u8, di: ?(Error!DebugInfo), const DebugInfo = struct { arena: std.heap.ArenaAllocator.State, - io: Io, coff_image_base: u64, mapped_file: ?MappedFile, dwarf: ?Dwarf, @@ -210,14 +216,19 @@ const Module = struct { section_view: []const u8, fn deinit(mf: *const MappedFile, io: Io) void { const process_handle = windows.GetCurrentProcess(); - assert(windows.ntdll.NtUnmapViewOfSection(process_handle, @constCast(mf.section_view.ptr)) == .SUCCESS); + switch (windows.ntdll.NtUnmapViewOfSection( + process_handle, + @constCast(mf.section_view.ptr), + )) { + .SUCCESS => {}, + else => |status| windows.unexpectedStatus(status) catch {}, + } windows.CloseHandle(mf.section_handle); mf.file.close(io); } }; - fn deinit(di: *DebugInfo, gpa: Allocator) void { - const io = di.io; + fn deinit(di: *DebugInfo, gpa: Allocator, io: Io) void { if (di.dwarf) |*dwarf| dwarf.deinit(gpa); if (di.pdb) |*pdb| { pdb.file_reader.file.close(io); @@ -262,7 +273,10 @@ const Module = struct { return .{ .name = pdb.getSymbolName(module, vaddr - coff_section.virtual_address), .compile_unit_name = fs.path.basename(module.obj_file_name), - .source_location = pdb.getLineNumberInfo(module, vaddr - coff_section.virtual_address) catch null, + .source_location = pdb.getLineNumberInfo( + module, + vaddr - coff_section.virtual_address, + ) catch null, }; } dwarf: { @@ -286,13 +300,19 @@ const Module = struct { } }; + fn deinit(module: *Module, gpa: Allocator, io: Io) void { + if (module.name) |name| gpa.free(name); + if (module.di) |*di_or_err| if (di_or_err.*) |*di| di.deinit(gpa, io) else |_| {}; + module.* = undefined; + } + fn getDebugInfo(module: *Module, gpa: Allocator, io: Io) Error!*DebugInfo { if (module.di == null) module.di = loadDebugInfo(module, gpa, io); return if (module.di.?) |*di| di else |err| err; } fn loadDebugInfo(module: *const Module, gpa: Allocator, io: Io) Error!DebugInfo { - const mapped_ptr: [*]const u8 = @ptrFromInt(module.base_address); - const mapped = mapped_ptr[0..module.size]; + const mapped_ptr: [*]const u8 = @ptrCast(module.entry.DllBase); + const mapped = mapped_ptr[0..module.entry.SizeOfImage]; var coff_obj = coff.Coff.init(mapped, true) catch return error.InvalidDebugInfo; var arena_instance: std.heap.ArenaAllocator = .init(gpa); @@ -304,18 +324,15 @@ const Module = struct { // a binary is produced with -gdwarf, since the section names are longer than 8 bytes. const mapped_file: ?DebugInfo.MappedFile = mapped: { if (!coff_obj.strtabRequired()) break :mapped null; - var name_buffer: [windows.PATH_MAX_WIDE + 4:0]u16 = undefined; - name_buffer[0..4].* = .{ '\\', '?', '?', '\\' }; // openFileAbsoluteW requires the prefix to be present - const process_handle = windows.GetCurrentProcess(); - const len = windows.kernel32.GetModuleFileNameExW( - process_handle, - module.handle, - name_buffer[4..], - windows.PATH_MAX_WIDE, - ); - if (len == 0) return error.MissingDebugInfo; - const name_w = name_buffer[0 .. len + 4 :0]; - const coff_file = Io.Threaded.dirOpenFileWtf16(null, name_w, .{}) catch |err| switch (err) { + var path_buffer: [4 + windows.PATH_MAX_WIDE]u16 = undefined; + path_buffer[0..4].* = .{ '\\', '?', '?', '\\' }; // openFileAbsoluteW requires the prefix to be present + const path_slice = module.entry.FullDllName.slice(); + @memcpy(path_buffer[4..][0..path_slice.len], path_slice); + const coff_file = Io.Threaded.dirOpenFileWtf16( + null, + path_buffer[0 .. 4 + path_slice.len], + .{}, + ) catch |err| switch (err) { error.Canceled => |e| return e, error.Unexpected => |e| return e, error.FileNotFound => return error.MissingDebugInfo, @@ -359,7 +376,8 @@ const Module = struct { null, null, .{ .READONLY = true }, - // The documentation states that if no AllocationAttribute is specified, then SEC_COMMIT is the default. + // The documentation states that if no AllocationAttribute is specified, + // then SEC_COMMIT is the default. // In practice, this isn't the case and specifying 0 will result in INVALID_PARAMETER_6. .{ .COMMIT = true }, coff_file.handle, @@ -368,6 +386,7 @@ const Module = struct { errdefer windows.CloseHandle(section_handle); var coff_len: usize = 0; var section_view_ptr: ?[*]const u8 = null; + const process_handle = windows.GetCurrentProcess(); const map_section_rc = windows.ntdll.NtMapViewOfSection( section_handle, process_handle, @@ -381,7 +400,13 @@ const Module = struct { .{ .READONLY = true }, ); if (map_section_rc != .SUCCESS) return error.MissingDebugInfo; - errdefer assert(windows.ntdll.NtUnmapViewOfSection(process_handle, @constCast(section_view_ptr.?)) == .SUCCESS); + errdefer switch (windows.ntdll.NtUnmapViewOfSection( + process_handle, + @constCast(section_view_ptr.?), + )) { + .SUCCESS => {}, + else => |status| windows.unexpectedStatus(status) catch {}, + }; const section_view = section_view_ptr.?[0..coff_len]; coff_obj = coff.Coff.init(section_view, false) catch return error.InvalidDebugInfo; break :mapped .{ @@ -496,7 +521,6 @@ const Module = struct { return .{ .arena = arena_instance.state, - .io = io, .coff_image_base = coff_image_base, .mapped_file = mapped_file, .dwarf = opt_dwarf, @@ -509,52 +533,82 @@ const Module = struct { /// Assumes we already hold `si.mutex`. fn findModule(si: *SelfInfo, gpa: Allocator, address: usize) error{ MissingDebugInfo, OutOfMemory, Unexpected }!*Module { for (si.modules.items) |*mod| { - if (address >= mod.base_address and address < mod.base_address + mod.size) { - return mod; - } + const base = @intFromPtr(mod.entry.DllBase); + if (address >= base and address < base + mod.entry.SizeOfImage) return mod; } - - // A new module might have been loaded; rebuild the list. - { - for (si.modules.items) |*mod| { - const di = &(mod.di orelse continue catch continue); - di.deinit(gpa); - } - si.modules.clearRetainingCapacity(); - - var module_name_arena = si.module_name_arena.promote(gpa); - defer si.module_name_arena = module_name_arena.state; - _ = module_name_arena.reset(.retain_capacity); - - const handle = windows.kernel32.CreateToolhelp32Snapshot(windows.TH32CS_SNAPMODULE | windows.TH32CS_SNAPMODULE32, 0); - if (handle == windows.INVALID_HANDLE_VALUE) { - return windows.unexpectedError(windows.GetLastError()); - } - defer windows.CloseHandle(handle); - var entry: windows.MODULEENTRY32 = undefined; - entry.dwSize = @sizeOf(windows.MODULEENTRY32); - var result = windows.kernel32.Module32First(handle, &entry); - while (result != 0) : (result = windows.kernel32.Module32Next(handle, &entry)) { - try si.modules.append(gpa, .{ - .base_address = @intFromPtr(entry.modBaseAddr), - .size = entry.modBaseSize, - .name = try module_name_arena.allocator().dupe( - u8, - std.mem.sliceTo(&entry.szModule, 0), - ), - .handle = entry.hModule, - .di = null, - }); + try si.modules.ensureUnusedCapacity(gpa, 1); + var entry: *LDR.DATA_TABLE_ENTRY = undefined; + switch (windows.ntdll.LdrFindEntryForAddress(@ptrFromInt(address), &entry)) { + .SUCCESS => {}, + .DLL_NOT_FOUND => return error.MissingDebugInfo, + else => |status| return windows.unexpectedStatus(status), + } + if (si.notification_cookie == null) { + var notification_cookie: LDR.DLL_NOTIFICATION.COOKIE = undefined; + switch ((try si.getNtdllProc(.LdrRegisterDllNotification))( + .{}, + &dllNotification, + si, + ¬ification_cookie, + )) { + .SUCCESS => si.notification_cookie = notification_cookie, + else => |status| return windows.unexpectedStatus(status), } } + const mod = si.modules.addOneAssumeCapacity(); + mod.* = .{ .entry = entry, .name = null, .di = null }; + return mod; +} - for (si.modules.items) |*mod| { - if (address >= mod.base_address and address < mod.base_address + mod.size) { - return mod; +inline fn getNtdllProc( + si: *SelfInfo, + comptime proc: std.meta.DeclEnum(windows.ntdll), +) !@TypeOf(&@field(windows.ntdll, @tagName(proc))) { + return if (load_dll_notification_procs) + @ptrCast(try si.loadNtdllProc(@tagName(proc))) + else + &@field(windows.ntdll, @tagName(proc)); +} +fn loadNtdllProc(si: *SelfInfo, name: []const u8) Io.UnexpectedError!*anyopaque { + const ntdll_handle = si.ntdll_handle orelse ntdll_handle: { + var ntdll_handle: *anyopaque = undefined; + switch (windows.ntdll.LdrLoadDll(null, null, &.init( + &.{ 'n', 't', 'd', 'l', 'l', '.', 'd', 'l', 'l' }, + ), &ntdll_handle)) { + .SUCCESS => {}, + else => |status| return windows.unexpectedStatus(status), } + si.ntdll_handle = ntdll_handle; + break :ntdll_handle ntdll_handle; + }; + var proc_addr: *anyopaque = undefined; + switch (windows.ntdll.LdrGetProcedureAddress(ntdll_handle, &.init(name), 0, &proc_addr)) { + .SUCCESS => {}, + else => |status| return windows.unexpectedStatus(status), } + return proc_addr; +} - return error.MissingDebugInfo; +fn dllNotification( + reason: LDR.DLL_NOTIFICATION.REASON, + data: *const LDR.DLL_NOTIFICATION.DATA, + context: ?*anyopaque, +) callconv(.winapi) void { + const si: *SelfInfo = @ptrCast(@alignCast(context)); + switch (reason) { + .LOADED => {}, + .UNLOADED => { + const io = std.Options.debug_io; + si.mutex.lockUncancelable(io); + defer si.mutex.unlock(io); + for (si.modules.items, 0..) |*mod, mod_index| { + if (mod.entry.DllBase != data.Unloaded.DllBase) continue; + mod.deinit(std.debug.getDebugInfoAllocator(), io); + _ = si.modules.swapRemove(mod_index); + break; + } + }, + } } const std = @import("std"); @@ -563,12 +617,23 @@ const Allocator = std.mem.Allocator; const Dwarf = std.debug.Dwarf; const Pdb = std.debug.Pdb; const Error = std.debug.SelfInfoError; -const assert = std.debug.assert; const coff = std.coff; const fs = std.fs; const windows = std.os.windows; +const LDR = windows.LDR; const builtin = @import("builtin"); const native_endian = builtin.target.cpu.arch.endian(); +const load_dll_notification_procs = builtin.abi == .msvc and switch (builtin.zig_backend) { + .stage2_c => true, + else => switch (builtin.output_mode) { + .Exe => false, + .Lib => switch (builtin.link_mode) { + .static => true, + .dynamic => false, + }, + .Obj => true, + }, +}; const SelfInfo = @This(); diff --git a/lib/std/heap.zig b/lib/std/heap.zig index cfe943fc2bfe6c373418e59d540a48f507a5bc8a..ba7ef0aabf4c77ef905aa14836ab0e91102b0ba1 100644 --- a/lib/std/heap.zig +++ b/lib/std/heap.zig @@ -110,11 +110,11 @@ pub fn defaultQueryPageSize() usize { break :size @intCast(vm_info.page_size); }, .windows => { - var sbi: windows.SYSTEM_BASIC_INFORMATION = undefined; + var sbi: windows.SYSTEM.BASIC_INFORMATION = undefined; switch (windows.ntdll.NtQuerySystemInformation( - .SystemBasicInformation, + .Basic, &sbi, - @sizeOf(windows.SYSTEM_BASIC_INFORMATION), + @sizeOf(windows.SYSTEM.BASIC_INFORMATION), null, )) { .SUCCESS => break :size sbi.PageSize, diff --git a/lib/std/os/windows.zig b/lib/std/os/windows.zig index 86d4ce0efd3b8170ef819544d41f2ccc16351be3..0c91f11ff5693ef51e10bfdf9a8f528c3d15e5e5 100644 --- a/lib/std/os/windows.zig +++ b/lib/std/os/windows.zig @@ -24,6 +24,66 @@ pub const nls = @import("windows/nls.zig"); pub const current_process: HANDLE = @ptrFromInt(@as(usize, @bitCast(@as(isize, -1)))); +pub const OBJECT = struct { + // ref: um/winternl.h + + pub const ATTRIBUTES = extern struct { + Length: ULONG = @sizeOf(ATTRIBUTES), + RootDirectory: ?HANDLE = null, + ObjectName: ?*UNICODE_STRING = @constCast(&UNICODE_STRING.empty), + Attributes: Flags = .{}, + SecurityDescriptor: ?*anyopaque = null, + SecurityQualityOfService: ?*anyopaque = null, + + // Valid values for the Attributes field + pub const Flags = packed struct(ULONG) { + Reserved0: u1 = 0, + INHERIT: bool = false, + Reserved2: u2 = 0, + PERMANENT: bool = false, + EXCLUSIVE: bool = false, + /// If name-lookup code should ignore the case of the ObjectName member rather than performing an exact-match search. + CASE_INSENSITIVE: bool = true, + OPENIF: bool = false, + OPENLINK: bool = false, + KERNEL_HANDLE: bool = false, + FORCE_ACCESS_CHECK: bool = false, + IGNORE_IMPERSONATED_DEVICEMAP: bool = false, + DONT_REPARSE: bool = false, + Reserved13: u19 = 0, + + pub const VALID_ATTRIBUTES: ATTRIBUTES = .{ + .INHERIT = true, + .PERMANENT = true, + .EXCLUSIVE = true, + .CASE_INSENSITIVE = true, + .OPENIF = true, + .OPENLINK = true, + .KERNEL_HANDLE = true, + .FORCE_ACCESS_CHECK = true, + .IGNORE_IMPERSONATED_DEVICEMAP = true, + .DONT_REPARSE = true, + }; + }; + }; + + pub const INFORMATION_CLASS = enum(c_int) { + Basic = 0, + Name = 1, + Type = 2, + Types = 3, + HandleFlag = 4, + Session = 5, + _, + + pub const Max: @typeInfo(@This()).@"enum".tag_type = @typeInfo(@This()).@"enum".fields.len; + }; + + pub const NAME_INFORMATION = extern struct { + Name: UNICODE_STRING, + }; +}; + pub const FILE = struct { // ref: km/ntddk.h @@ -73,6 +133,94 @@ pub const FILE = struct { // ref: km/ntifs.h + pub const NAME_FLAGS = packed struct(UCHAR) { + NTFS: bool = false, + DOS: bool = false, + Reserved2: u5 = 0, + UNSPECIFIED: bool = false, + }; + + pub const NOTIFY = struct { + pub const CHANGE = packed struct(ULONG) { + FILE_NAME: bool = false, + DIR_NAME: bool = false, + ATTRIBUTES: bool = false, + SIZE: bool = false, + LAST_WRITE: bool = false, + LAST_ACCESS: bool = false, + CREATION: bool = false, + EA: bool = false, + SECURITY: bool = false, + STREAM_NAME: bool = false, + STREAM_SIZE: bool = false, + STREAM_WRITE: bool = false, + Reserved12: u20 = 0, + }; + + pub const INFORMATION = extern struct { + NextEntryOffset: ULONG, + Action: ULONG, + FileNameLength: ULONG, + FileName: [0]WCHAR, + + pub fn fileName(info: *INFORMATION) []WCHAR { + const ptr: [*]WCHAR = @ptrCast(&info.FileName); + return ptr[0..@divExact(info.FileNameLength, @sizeOf(WCHAR))]; + } + }; + + pub const EXTENDED_INFORMATION = extern struct { + NextEntryOffset: ULONG, + Action: ULONG, + CreationTime: LARGE_INTEGER, + LastModificationTime: LARGE_INTEGER, + LastChangeTime: LARGE_INTEGER, + LastAccessTime: LARGE_INTEGER, + AllocatedLength: LARGE_INTEGER, + FileSize: LARGE_INTEGER, + FileAttributes: ATTRIBUTE, + u: extern union { + ReparsePointTag: ULONG, + EaSize: ULONG, + }, + FileId: LARGE_INTEGER, + ParentFileId: LARGE_INTEGER, + FileNameLength: ULONG, + FileName: [0]WCHAR, + + pub fn fileName(info: *INFORMATION) []WCHAR { + const ptr: [*]WCHAR = @ptrCast(&info.FileName); + return ptr[0..@divExact(info.FileNameLength, @sizeOf(WCHAR))]; + } + }; + + pub const FULL_INFORMATION = extern struct { + NextEntryOffset: ULONG, + Action: ULONG, + CreationTime: LARGE_INTEGER, + LastModificationTime: LARGE_INTEGER, + LastChangeTime: LARGE_INTEGER, + LastAccessTime: LARGE_INTEGER, + AllocatedLength: LARGE_INTEGER, + FileSize: LARGE_INTEGER, + FileAttributes: ATTRIBUTE, + u: extern union { + ReparsePointTag: ULONG, + EaSize: ULONG, + }, + FileId: LARGE_INTEGER, + ParentFileId: LARGE_INTEGER, + FileNameLength: ULONG, + FileNameFlags: NAME_FLAGS, + FileName: [0]WCHAR, + + pub fn fileName(info: *INFORMATION) []WCHAR { + const ptr: [*]WCHAR = @ptrCast(&info.FileName); + return ptr[0..@divExact(info.FileNameLength, @sizeOf(WCHAR))]; + } + }; + }; + pub const PIPE = struct { /// Define the `NamedPipeType` flags for `NtCreateNamedPipeFile` pub const TYPE = packed struct(ULONG) { @@ -357,6 +505,7 @@ pub const FILE = struct { IdAllExtdBothDirectory = 81, StreamReservation = 82, MupProvider = 83, + _, pub const Maximum: @typeInfo(@This()).@"enum".tag_type = 1 + @typeInfo(@This()).@"enum".fields.len; }; @@ -647,6 +796,19 @@ pub const FILE = struct { Mode: MODE, }; }; + + // ref: km/ +}; + +pub const DIRECTORY = struct { + pub const NOTIFY_INFORMATION_CLASS = enum(c_int) { + Notify = 1, + NotifyExtended = 2, + NotifyFull = 3, + _, + + pub const Maximum: @typeInfo(@This()).@"enum".tag_type = 1 + @typeInfo(@This()).@"enum".fields.len; + }; }; pub const CONSOLE = struct { @@ -880,141 +1042,229 @@ pub const CONSOLE = struct { // ref: km/ntddk.h -pub const PROCESSINFOCLASS = enum(c_int) { - BasicInformation = 0, - QuotaLimits = 1, - IoCounters = 2, - VmCounters = 3, - Times = 4, - BasePriority = 5, - RaisePriority = 6, - DebugPort = 7, - ExceptionPort = 8, - AccessToken = 9, - LdtInformation = 10, - LdtSize = 11, - DefaultHardErrorMode = 12, - IoPortHandlers = 13, - PooledUsageAndLimits = 14, - WorkingSetWatch = 15, - UserModeIOPL = 16, - EnableAlignmentFaultFixup = 17, - PriorityClass = 18, - Wx86Information = 19, - HandleCount = 20, - AffinityMask = 21, - PriorityBoost = 22, - DeviceMap = 23, - SessionInformation = 24, - ForegroundInformation = 25, - Wow64Information = 26, - ImageFileName = 27, - LUIDDeviceMapsEnabled = 28, - BreakOnTermination = 29, - DebugObjectHandle = 30, - DebugFlags = 31, - HandleTracing = 32, - IoPriority = 33, - ExecuteFlags = 34, - TlsInformation = 35, - Cookie = 36, - ImageInformation = 37, - CycleTime = 38, - PagePriority = 39, - InstrumentationCallback = 40, - ThreadStackAllocation = 41, - WorkingSetWatchEx = 42, - ImageFileNameWin32 = 43, - ImageFileMapping = 44, - AffinityUpdateMode = 45, - MemoryAllocationMode = 46, - GroupInformation = 47, - TokenVirtualizationEnabled = 48, - OwnerInformation = 49, - WindowInformation = 50, - HandleInformation = 51, - MitigationPolicy = 52, - DynamicFunctionTableInformation = 53, - HandleCheckingMode = 54, - KeepAliveCount = 55, - RevokeFileHandles = 56, - WorkingSetControl = 57, - HandleTable = 58, - CheckStackExtentsMode = 59, - CommandLineInformation = 60, - ProtectionInformation = 61, - MemoryExhaustion = 62, - FaultInformation = 63, - TelemetryIdInformation = 64, - CommitReleaseInformation = 65, - Reserved1Information = 66, - Reserved2Information = 67, - SubsystemProcess = 68, - InPrivate = 70, - RaiseUMExceptionOnInvalidHandleClose = 71, - SubsystemInformation = 75, - Win32kSyscallFilterInformation = 79, - EnergyTrackingState = 82, - NetworkIoCounters = 114, - _, +pub const SYSTEM = struct { + pub const INFORMATION_CLASS = enum(c_int) { + Basic = 0, + Performance = 2, + TimeOfDay = 3, + Process = 5, + ProcessorPerformance = 8, + Interrupt = 23, + Exception = 33, + RegistryQuota = 37, + Lookaside = 45, + CodeIntegrity = 103, + Policy = 134, + _, + }; - pub const Max: @typeInfo(@This()).@"enum".tag_type = 117; + pub const BASIC_INFORMATION = extern struct { + Reserved: ULONG, + TimerResolution: ULONG, + PageSize: ULONG, + NumberOfPhysicalPages: ULONG, + LowestPhysicalPageNumber: ULONG, + HighestPhysicalPageNumber: ULONG, + AllocationGranularity: ULONG, + MinimumUserModeAddress: ULONG_PTR, + MaximumUserModeAddress: ULONG_PTR, + ActiveProcessorsAffinityMask: KAFFINITY, + NumberOfProcessors: UCHAR, + }; }; -pub const THREADINFOCLASS = enum(c_int) { - BasicInformation = 0, - Times = 1, - Priority = 2, - BasePriority = 3, - AffinityMask = 4, - ImpersonationToken = 5, - DescriptorTableEntry = 6, - EnableAlignmentFaultFixup = 7, - EventPair_Reusable = 8, - QuerySetWin32StartAddress = 9, - ZeroTlsCell = 10, - PerformanceCount = 11, - AmILastThread = 12, - IdealProcessor = 13, - PriorityBoost = 14, - SetTlsArrayAddress = 15, - IsIoPending = 16, - // Windows 2000+ from here - HideFromDebugger = 17, - // Windows XP+ from here - BreakOnTermination = 18, - SwitchLegacyState = 19, - IsTerminated = 20, - // Windows Vista+ from here - LastSystemCall = 21, - IoPriority = 22, - CycleTime = 23, - PagePriority = 24, - ActualBasePriority = 25, - TebInformation = 26, - CSwitchMon = 27, - // Windows 7+ from here - CSwitchPmu = 28, - Wow64Context = 29, - GroupInformation = 30, - UmsInformation = 31, - CounterProfiling = 32, - IdealProcessorEx = 33, - // Windows 8+ from here - CpuAccountingInformation = 34, - // Windows 8.1+ from here - SuspendCount = 35, - // Windows 10+ from here - HeterogeneousCpuPolicy = 36, - ContainerId = 37, - NameInformation = 38, - SelectedCpuSets = 39, - SystemThreadInformation = 40, - ActualGroupAffinity = 41, - DynamicCodePolicyInfo = 42, - SubsystemInformation = 45, +pub const PROCESS = struct { + pub const INFORMATION = extern struct { + hProcess: HANDLE, + hThread: HANDLE, + dwProcessId: DWORD, + dwThreadId: DWORD, + }; - pub const Max: @typeInfo(@This()).@"enum".tag_type = 60; + pub const INFOCLASS = enum(c_int) { + BasicInformation = 0, + QuotaLimits = 1, + IoCounters = 2, + VmCounters = 3, + Times = 4, + BasePriority = 5, + RaisePriority = 6, + DebugPort = 7, + ExceptionPort = 8, + AccessToken = 9, + LdtInformation = 10, + LdtSize = 11, + DefaultHardErrorMode = 12, + IoPortHandlers = 13, + PooledUsageAndLimits = 14, + WorkingSetWatch = 15, + UserModeIOPL = 16, + EnableAlignmentFaultFixup = 17, + PriorityClass = 18, + Wx86Information = 19, + HandleCount = 20, + AffinityMask = 21, + PriorityBoost = 22, + DeviceMap = 23, + SessionInformation = 24, + ForegroundInformation = 25, + Wow64Information = 26, + ImageFileName = 27, + LUIDDeviceMapsEnabled = 28, + BreakOnTermination = 29, + DebugObjectHandle = 30, + DebugFlags = 31, + HandleTracing = 32, + IoPriority = 33, + ExecuteFlags = 34, + TlsInformation = 35, + Cookie = 36, + ImageInformation = 37, + CycleTime = 38, + PagePriority = 39, + InstrumentationCallback = 40, + ThreadStackAllocation = 41, + WorkingSetWatchEx = 42, + ImageFileNameWin32 = 43, + ImageFileMapping = 44, + AffinityUpdateMode = 45, + MemoryAllocationMode = 46, + GroupInformation = 47, + TokenVirtualizationEnabled = 48, + OwnerInformation = 49, + WindowInformation = 50, + HandleInformation = 51, + MitigationPolicy = 52, + DynamicFunctionTableInformation = 53, + HandleCheckingMode = 54, + KeepAliveCount = 55, + RevokeFileHandles = 56, + WorkingSetControl = 57, + HandleTable = 58, + CheckStackExtentsMode = 59, + CommandLineInformation = 60, + ProtectionInformation = 61, + MemoryExhaustion = 62, + FaultInformation = 63, + TelemetryIdInformation = 64, + CommitReleaseInformation = 65, + Reserved1Information = 66, + Reserved2Information = 67, + SubsystemProcess = 68, + InPrivate = 70, + RaiseUMExceptionOnInvalidHandleClose = 71, + SubsystemInformation = 75, + Win32kSyscallFilterInformation = 79, + EnergyTrackingState = 82, + NetworkIoCounters = 114, + _, + + pub const Max: @typeInfo(@This()).@"enum".tag_type = 117; + }; + + pub const BASIC_INFORMATION = extern struct { + ExitStatus: NTSTATUS, + PebBaseAddress: *PEB, + AffinityMask: ULONG_PTR, + BasePriority: KPRIORITY, + UniqueProcessId: ULONG_PTR, + InheritedFromUniqueProcessId: ULONG_PTR, + }; + + pub const VM_COUNTERS = extern struct { + PeakVirtualSize: SIZE_T, + VirtualSize: SIZE_T, + PageFaultCount: ULONG, + PeakWorkingSetSize: SIZE_T, + WorkingSetSize: SIZE_T, + QuotaPeakPagedPoolUsage: SIZE_T, + QuotaPagedPoolUsage: SIZE_T, + QuotaPeakNonPagedPoolUsage: SIZE_T, + QuotaNonPagedPoolUsage: SIZE_T, + PagefileUsage: SIZE_T, + PeakPagefileUsage: SIZE_T, + }; +}; + +pub const THREAD = struct { + pub const INFOCLASS = enum(c_int) { + BasicInformation = 0, + Times = 1, + Priority = 2, + BasePriority = 3, + AffinityMask = 4, + ImpersonationToken = 5, + DescriptorTableEntry = 6, + EnableAlignmentFaultFixup = 7, + EventPair_Reusable = 8, + QuerySetWin32StartAddress = 9, + ZeroTlsCell = 10, + PerformanceCount = 11, + AmILastThread = 12, + IdealProcessor = 13, + PriorityBoost = 14, + SetTlsArrayAddress = 15, + IsIoPending = 16, + // Windows 2000+ from here + HideFromDebugger = 17, + // Windows XP+ from here + BreakOnTermination = 18, + SwitchLegacyState = 19, + IsTerminated = 20, + // Windows Vista+ from here + LastSystemCall = 21, + IoPriority = 22, + CycleTime = 23, + PagePriority = 24, + ActualBasePriority = 25, + TebInformation = 26, + CSwitchMon = 27, + // Windows 7+ from here + CSwitchPmu = 28, + Wow64Context = 29, + GroupInformation = 30, + UmsInformation = 31, + CounterProfiling = 32, + IdealProcessorEx = 33, + // Windows 8+ from here + CpuAccountingInformation = 34, + // Windows 8.1+ from here + SuspendCount = 35, + // Windows 10+ from here + HeterogeneousCpuPolicy = 36, + ContainerId = 37, + NameInformation = 38, + SelectedCpuSets = 39, + SystemThreadInformation = 40, + ActualGroupAffinity = 41, + DynamicCodePolicyInfo = 42, + SubsystemInformation = 45, + _, + + pub const Max: @typeInfo(@This()).@"enum".tag_type = 60; + }; + + pub const BASIC_INFORMATION = extern struct { + ExitStatus: NTSTATUS, + TebBaseAddress: PVOID, + ClientId: CLIENT_ID, + AffinityMask: KAFFINITY, + Priority: KPRIORITY, + BasePriority: KPRIORITY, + }; +}; + +pub const MEMORY = struct { + pub const BASIC_INFORMATION = extern struct { + BaseAddress: PVOID, + AllocationBase: PVOID, + AllocationProtect: DWORD, + PartitionId: WORD, + RegionSize: SIZE_T, + State: DWORD, + Protect: DWORD, + Type: DWORD, + }; }; // ref: km/ntifs.h @@ -2560,48 +2810,6 @@ pub fn GetProcessHeap() ?*HEAP { return peb().ProcessHeap; } -// ref: um/winternl.h - -pub const OBJECT_ATTRIBUTES = extern struct { - Length: ULONG = @sizeOf(OBJECT_ATTRIBUTES), - RootDirectory: ?HANDLE = null, - ObjectName: ?*UNICODE_STRING = @constCast(&UNICODE_STRING.empty), - Attributes: ATTRIBUTES = .{}, - SecurityDescriptor: ?*anyopaque = null, - SecurityQualityOfService: ?*anyopaque = null, - - // Valid values for the Attributes field - pub const ATTRIBUTES = packed struct(ULONG) { - Reserved0: u1 = 0, - INHERIT: bool = false, - Reserved2: u2 = 0, - PERMANENT: bool = false, - EXCLUSIVE: bool = false, - /// If name-lookup code should ignore the case of the ObjectName member rather than performing an exact-match search. - CASE_INSENSITIVE: bool = true, - OPENIF: bool = false, - OPENLINK: bool = false, - KERNEL_HANDLE: bool = false, - FORCE_ACCESS_CHECK: bool = false, - IGNORE_IMPERSONATED_DEVICEMAP: bool = false, - DONT_REPARSE: bool = false, - Reserved13: u19 = 0, - - pub const VALID_ATTRIBUTES: ATTRIBUTES = .{ - .INHERIT = true, - .PERMANENT = true, - .EXCLUSIVE = true, - .CASE_INSENSITIVE = true, - .OPENIF = true, - .OPENLINK = true, - .KERNEL_HANDLE = true, - .FORCE_ACCESS_CHECK = true, - .IGNORE_IMPERSONATED_DEVICEMAP = true, - .DONT_REPARSE = true, - }; - }; -}; - // ref none pub fn GetCurrentProcess() HANDLE { @@ -2623,297 +2831,13 @@ pub fn GetCurrentThreadId() DWORD { } pub fn GetLastError() Win32Error { - return @enumFromInt(teb().LastErrorValue); -} -/// A Zig wrapper around `NtDeviceIoControlFile` and `NtFsControlFile` syscalls. -/// It implements similar behavior to `DeviceIoControl` and is meant to serve -/// as a direct substitute for that call. -/// TODO work out if we need to expose other arguments to the underlying syscalls. -pub fn DeviceIoControl( - device: HANDLE, - io_control_code: CTL_CODE, - opts: struct { - event: ?HANDLE = null, - apc_routine: ?*const IO_APC_ROUTINE = null, - apc_context: ?*anyopaque = null, - io_status_block: ?*IO_STATUS_BLOCK = null, - in: []const u8 = &.{}, - out: []u8 = &.{}, - }, -) NTSTATUS { - var io_status_block: IO_STATUS_BLOCK = undefined; - return switch (io_control_code.DeviceType) { - .FILE_SYSTEM, .NAMED_PIPE => ntdll.NtFsControlFile( - device, - opts.event, - opts.apc_routine, - opts.apc_context, - opts.io_status_block orelse &io_status_block, - io_control_code, - if (opts.in.len > 0) opts.in.ptr else null, - @intCast(opts.in.len), - if (opts.out.len > 0) opts.out.ptr else null, - @intCast(opts.out.len), - ), - else => ntdll.NtDeviceIoControlFile( - device, - opts.event, - opts.apc_routine, - opts.apc_context, - opts.io_status_block orelse &io_status_block, - io_control_code, - if (opts.in.len > 0) opts.in.ptr else null, - @intCast(opts.in.len), - if (opts.out.len > 0) opts.out.ptr else null, - @intCast(opts.out.len), - ), - }; -} - -pub fn GetOverlappedResult(h: HANDLE, overlapped: *OVERLAPPED, wait: bool) !DWORD { - var bytes: DWORD = undefined; - if (kernel32.GetOverlappedResult(h, overlapped, &bytes, @intFromBool(wait)) == 0) { - switch (GetLastError()) { - .IO_INCOMPLETE => if (!wait) return error.WouldBlock else unreachable, - else => |err| return unexpectedError(err), - } - } - return bytes; -} - -pub const CreateIoCompletionPortError = error{Unexpected}; - -pub fn CreateIoCompletionPort( - file_handle: HANDLE, - existing_completion_port: ?HANDLE, - completion_key: usize, - concurrent_thread_count: DWORD, -) CreateIoCompletionPortError!HANDLE { - const handle = kernel32.CreateIoCompletionPort(file_handle, existing_completion_port, completion_key, concurrent_thread_count) orelse { - switch (GetLastError()) { - .INVALID_PARAMETER => unreachable, - else => |err| return unexpectedError(err), - } - }; - return handle; -} - -pub const PostQueuedCompletionStatusError = error{Unexpected}; - -pub fn PostQueuedCompletionStatus( - completion_port: HANDLE, - bytes_transferred_count: DWORD, - completion_key: usize, - lpOverlapped: ?*OVERLAPPED, -) PostQueuedCompletionStatusError!void { - if (kernel32.PostQueuedCompletionStatus(completion_port, bytes_transferred_count, completion_key, lpOverlapped) == 0) { - switch (GetLastError()) { - else => |err| return unexpectedError(err), - } - } -} - -pub const GetQueuedCompletionStatusResult = enum { - Normal, - Aborted, - Canceled, - EOF, - Timeout, -}; - -pub fn GetQueuedCompletionStatus( - completion_port: HANDLE, - bytes_transferred_count: *DWORD, - lpCompletionKey: *usize, - lpOverlapped: *?*OVERLAPPED, - dwMilliseconds: DWORD, -) GetQueuedCompletionStatusResult { - if (kernel32.GetQueuedCompletionStatus( - completion_port, - bytes_transferred_count, - lpCompletionKey, - lpOverlapped, - dwMilliseconds, - ) == FALSE) { - switch (GetLastError()) { - .ABANDONED_WAIT_0 => return GetQueuedCompletionStatusResult.Aborted, - .OPERATION_ABORTED => return GetQueuedCompletionStatusResult.Canceled, - .HANDLE_EOF => return GetQueuedCompletionStatusResult.EOF, - .WAIT_TIMEOUT => return GetQueuedCompletionStatusResult.Timeout, - else => |err| { - if (std.debug.runtime_safety) { - @setEvalBranchQuota(2500); - std.debug.panic("unexpected error: {}\n", .{err}); - } - }, - } - } - return GetQueuedCompletionStatusResult.Normal; -} - -pub const GetQueuedCompletionStatusError = error{ - Aborted, - Canceled, - EOF, - Timeout, -} || UnexpectedError; - -pub fn GetQueuedCompletionStatusEx( - completion_port: HANDLE, - completion_port_entries: []OVERLAPPED_ENTRY, - timeout_ms: ?DWORD, - alertable: bool, -) GetQueuedCompletionStatusError!u32 { - var num_entries_removed: u32 = 0; - - const success = kernel32.GetQueuedCompletionStatusEx( - completion_port, - completion_port_entries.ptr, - @as(ULONG, @intCast(completion_port_entries.len)), - &num_entries_removed, - timeout_ms orelse INFINITE, - @intFromBool(alertable), - ); - - if (success == FALSE) { - return switch (GetLastError()) { - .ABANDONED_WAIT_0 => error.Aborted, - .OPERATION_ABORTED => error.Canceled, - .HANDLE_EOF => error.EOF, - .WAIT_TIMEOUT => error.Timeout, - else => |err| unexpectedError(err), - }; - } - - return num_entries_removed; + return teb().LastErrorValue; } pub fn CloseHandle(hObject: HANDLE) void { - assert(ntdll.NtClose(hObject) == .SUCCESS); -} - -pub fn getpeername(s: ws2_32.SOCKET, name: *ws2_32.sockaddr, namelen: *ws2_32.socklen_t) i32 { - return ws2_32.getpeername(s, name, @as(*i32, @ptrCast(namelen))); -} - -pub fn sendmsg( - s: ws2_32.SOCKET, - msg: *ws2_32.WSAMSG_const, - flags: u32, -) i32 { - var bytes_send: DWORD = undefined; - if (ws2_32.WSASendMsg(s, msg, flags, &bytes_send, null, null) == ws2_32.SOCKET_ERROR) { - return ws2_32.SOCKET_ERROR; - } else { - return @as(i32, @as(u31, @intCast(bytes_send))); - } -} - -pub fn sendto(s: ws2_32.SOCKET, buf: [*]const u8, len: usize, flags: u32, to: ?*const ws2_32.sockaddr, to_len: ws2_32.socklen_t) i32 { - var buffer = ws2_32.WSABUF{ .len = @as(u31, @truncate(len)), .buf = @constCast(buf) }; - var bytes_send: DWORD = undefined; - if (ws2_32.WSASendTo(s, @as([*]ws2_32.WSABUF, @ptrCast(&buffer)), 1, &bytes_send, flags, to, @as(i32, @intCast(to_len)), null, null) == ws2_32.SOCKET_ERROR) { - return ws2_32.SOCKET_ERROR; - } else { - return @as(i32, @as(u31, @intCast(bytes_send))); - } -} - -pub fn recvfrom(s: ws2_32.SOCKET, buf: [*]u8, len: usize, flags: u32, from: ?*ws2_32.sockaddr, from_len: ?*ws2_32.socklen_t) i32 { - var buffer = ws2_32.WSABUF{ .len = @as(u31, @truncate(len)), .buf = buf }; - var bytes_received: DWORD = undefined; - var flags_inout = flags; - if (ws2_32.WSARecvFrom(s, @as([*]ws2_32.WSABUF, @ptrCast(&buffer)), 1, &bytes_received, &flags_inout, from, @as(?*i32, @ptrCast(from_len)), null, null) == ws2_32.SOCKET_ERROR) { - return ws2_32.SOCKET_ERROR; - } else { - return @as(i32, @as(u31, @intCast(bytes_received))); - } -} - -pub fn poll(fds: [*]ws2_32.pollfd, n: c_ulong, timeout: i32) i32 { - return ws2_32.WSAPoll(fds, n, timeout); -} - -pub fn WSAIoctl( - s: ws2_32.SOCKET, - dwIoControlCode: DWORD, - inBuffer: ?[]const u8, - outBuffer: []u8, - overlapped: ?*OVERLAPPED, - completionRoutine: ?ws2_32.LPWSAOVERLAPPED_COMPLETION_ROUTINE, -) !DWORD { - var bytes: DWORD = undefined; - switch (ws2_32.WSAIoctl( - s, - dwIoControlCode, - if (inBuffer) |i| i.ptr else null, - if (inBuffer) |i| @as(DWORD, @intCast(i.len)) else 0, - outBuffer.ptr, - @as(DWORD, @intCast(outBuffer.len)), - &bytes, - overlapped, - completionRoutine, - )) { - 0 => {}, - ws2_32.SOCKET_ERROR => switch (ws2_32.WSAGetLastError()) { - else => |err| return unexpectedWSAError(err), - }, - else => unreachable, - } - return bytes; -} - -const GetModuleFileNameError = error{Unexpected}; - -pub fn GetModuleFileNameW(hModule: ?HMODULE, buf_ptr: [*]u16, buf_len: DWORD) GetModuleFileNameError![:0]u16 { - const rc = kernel32.GetModuleFileNameW(hModule, buf_ptr, buf_len); - if (rc == 0) { - switch (GetLastError()) { - else => |err| return unexpectedError(err), - } - } - return buf_ptr[0..rc :0]; -} - -pub const NtAllocateVirtualMemoryError = error{ - AccessDenied, - InvalidParameter, - NoMemory, - Unexpected, -}; - -pub fn NtAllocateVirtualMemory(hProcess: HANDLE, addr: ?*PVOID, zero_bits: ULONG_PTR, size: ?*SIZE_T, alloc_type: ULONG, protect: ULONG) NtAllocateVirtualMemoryError!void { - return switch (ntdll.NtAllocateVirtualMemory(hProcess, addr, zero_bits, size, alloc_type, protect)) { - .SUCCESS => return, - .ACCESS_DENIED => NtAllocateVirtualMemoryError.AccessDenied, - .INVALID_PARAMETER => NtAllocateVirtualMemoryError.InvalidParameter, - .NO_MEMORY => NtAllocateVirtualMemoryError.NoMemory, - else => |st| unexpectedStatus(st), - }; -} - -pub const NtFreeVirtualMemoryError = error{ - AccessDenied, - InvalidParameter, - Unexpected, -}; - -pub fn NtFreeVirtualMemory(hProcess: HANDLE, addr: ?*PVOID, size: *SIZE_T, free_type: ULONG) NtFreeVirtualMemoryError!void { - // TODO: If the return value is .INVALID_PAGE_PROTECTION, call RtlFlushSecureMemoryCache and try again. - return switch (ntdll.NtFreeVirtualMemory(hProcess, addr, size, free_type)) { - .SUCCESS => return, - .ACCESS_DENIED => NtFreeVirtualMemoryError.AccessDenied, - .INVALID_PARAMETER => NtFreeVirtualMemoryError.InvalidParameter, - else => NtFreeVirtualMemoryError.Unexpected, - }; -} - -pub fn SetFileCompletionNotificationModes(handle: HANDLE, flags: UCHAR) !void { - const success = kernel32.SetFileCompletionNotificationModes(handle, flags); - if (success == FALSE) { - return switch (GetLastError()) { - else => |err| unexpectedError(err), - }; + switch (ntdll.NtClose(hObject)) { + .SUCCESS => {}, + else => |status| unexpectedStatus(status) catch {}, } } @@ -2963,114 +2887,75 @@ pub const CreateProcessFlags = packed struct(u32) { create_ignore_system_default: bool = false, }; -pub const LoadLibraryError = error{ - FileNotFound, - Unexpected, -}; - -pub fn LoadLibraryW(lpLibFileName: [*:0]const u16) LoadLibraryError!HMODULE { - return kernel32.LoadLibraryW(lpLibFileName) orelse { - switch (GetLastError()) { - .FILE_NOT_FOUND => return error.FileNotFound, - .PATH_NOT_FOUND => return error.FileNotFound, - .MOD_NOT_FOUND => return error.FileNotFound, - else => |err| return unexpectedError(err), - } - }; -} - -pub const LoadLibraryFlags = enum(DWORD) { - none = 0, - dont_resolve_dll_references = 0x00000001, - load_ignore_code_authz_level = 0x00000010, - load_library_as_datafile = 0x00000002, - load_library_as_datafile_exclusive = 0x00000040, - load_library_as_image_resource = 0x00000020, - load_library_search_application_dir = 0x00000200, - load_library_search_default_dirs = 0x00001000, - load_library_search_dll_load_dir = 0x00000100, - load_library_search_system32 = 0x00000800, - load_library_search_user_dirs = 0x00000400, - load_with_altered_search_path = 0x00000008, - load_library_require_signed_target = 0x00000080, - load_library_safe_current_dirs = 0x00002000, -}; - -pub fn LoadLibraryExW(lpLibFileName: [*:0]const u16, dwFlags: LoadLibraryFlags) LoadLibraryError!HMODULE { - return kernel32.LoadLibraryExW(lpLibFileName, null, @intFromEnum(dwFlags)) orelse { - switch (GetLastError()) { - .FILE_NOT_FOUND => return error.FileNotFound, - .PATH_NOT_FOUND => return error.FileNotFound, - .MOD_NOT_FOUND => return error.FileNotFound, - else => |err| return unexpectedError(err), - } - }; -} - -pub fn FreeLibrary(hModule: HMODULE) void { - assert(kernel32.FreeLibrary(hModule) != 0); -} - -pub fn QueryPerformanceFrequency() u64 { - // "On systems that run Windows XP or later, the function will always succeed" - // https://docs.microsoft.com/en-us/windows/desktop/api/profileapi/nf-profileapi-queryperformancefrequency - var result: LARGE_INTEGER = undefined; - assert(ntdll.RtlQueryPerformanceFrequency(&result) != 0); - // The kernel treats this integer as unsigned. - return @as(u64, @bitCast(result)); -} - -pub fn QueryPerformanceCounter() u64 { - // "On systems that run Windows XP or later, the function will always succeed" - // https://docs.microsoft.com/en-us/windows/desktop/api/profileapi/nf-profileapi-queryperformancecounter - var result: LARGE_INTEGER = undefined; - assert(ntdll.RtlQueryPerformanceCounter(&result) != 0); - // The kernel treats this integer as unsigned. - return @as(u64, @bitCast(result)); -} - -/// This is a workaround for the C backend until zig has the ability to put -/// C code in inline assembly. -extern fn zig_thumb_windows_teb() callconv(.c) *anyopaque; -extern fn zig_aarch64_windows_teb() callconv(.c) *anyopaque; -extern fn zig_x86_windows_teb() callconv(.c) *anyopaque; -extern fn zig_x86_64_windows_teb() callconv(.c) *anyopaque; - pub fn teb() *TEB { - return switch (native_arch) { - .thumb => if (builtin.zig_backend == .stage2_c) - @ptrCast(@alignCast(zig_thumb_windows_teb())) - else - asm ( - \\ mrc p15, 0, %[ptr], c13, c0, 2 - : [ptr] "=r" (-> *TEB), - ), - .aarch64 => if (builtin.zig_backend == .stage2_c) - @ptrCast(@alignCast(zig_aarch64_windows_teb())) - else - asm ( - \\ mov %[ptr], x18 - : [ptr] "=r" (-> *TEB), - ), - .x86 => if (builtin.zig_backend == .stage2_c) - @ptrCast(@alignCast(zig_x86_windows_teb())) - else - asm ( + if (builtin.zig_backend == .stage2_c) return @ptrCast(@alignCast(struct { + /// This is a workaround for the C backend until zig has the ability to put + /// C code in inline assembly. + extern fn zig_windows_teb() callconv(.c) *anyopaque; + }.zig_windows_teb())); + switch (native_arch) { + .thumb => return asm ( + \\ mrc p15, 0, %[ptr], c13, c0, 2 + : [ptr] "=r" (-> *TEB), + ), + .aarch64 => return asm ( + \\ mov %[ptr], x18 + : [ptr] "=r" (-> *TEB), + ), + .x86 => { + comptime assert( + @offsetOf(TEB, "NtTib") + @offsetOf(@FieldType(TEB, "NtTib"), "Self") == 0x18, + ); + return asm ( \\ movl %%fs:0x18, %[ptr] : [ptr] "=r" (-> *TEB), - ), - .x86_64 => if (builtin.zig_backend == .stage2_c) - @ptrCast(@alignCast(zig_x86_64_windows_teb())) - else - asm ( + ); + }, + .x86_64 => { + comptime assert( + @offsetOf(TEB, "NtTib") + @offsetOf(@FieldType(TEB, "NtTib"), "Self") == 0x30, + ); + return asm ( \\ movq %%gs:0x30, %[ptr] : [ptr] "=r" (-> *TEB), - ), + ); + }, else => @compileError("unsupported arch"), - }; + } } pub fn peb() *PEB { + if (builtin.zig_backend == .stage2_c) switch (native_arch) { + .x86, .x86_64 => return @ptrCast(@alignCast(struct { + /// This is a workaround for the C backend until zig has the ability to put + /// C code in inline assembly. + extern fn zig_windows_peb() callconv(.c) *anyopaque; + }.zig_windows_peb())), + else => {}, + } else switch (native_arch) { + .aarch64 => { + comptime assert(@offsetOf(TEB, "ProcessEnvironmentBlock") == 0x60); + return asm ( + \\ ldr %[ptr], [x18, #0x60] + : [ptr] "=r" (-> *PEB), + ); + }, + .x86 => { + comptime assert(@offsetOf(TEB, "ProcessEnvironmentBlock") == 0x30); + return asm ( + \\ movl %%fs:0x30, %[ptr] + : [ptr] "=r" (-> *PEB), + ); + }, + .x86_64 => { + comptime assert(@offsetOf(TEB, "ProcessEnvironmentBlock") == 0x60); + return asm ( + \\ movq %%gs:0x60, %[ptr] + : [ptr] "=r" (-> *PEB), + ); + }, + else => {}, + } return teb().ProcessEnvironmentBlock; } @@ -3089,20 +2974,6 @@ pub fn toSysTime(ns: Io.Timestamp) i64 { return @as(i64, @intCast(hns)) - std.time.epoch.windows * (std.time.ns_per_s / 100); } -pub fn fileTimeToNanoSeconds(ft: FILETIME) Io.Timestamp { - const hns = (@as(i64, ft.dwHighDateTime) << 32) | ft.dwLowDateTime; - return fromSysTime(hns); -} - -/// Converts a number of nanoseconds since the POSIX epoch to a Windows FILETIME. -pub fn nanoSecondsToFileTime(ns: Io.Timestamp) FILETIME { - const adjusted: u64 = @bitCast(toSysTime(ns)); - return .{ - .dwHighDateTime = @as(u32, @truncate(adjusted >> 32)), - .dwLowDateTime = @as(u32, @truncate(adjusted)), - }; -} - /// Use RtlUpcaseUnicodeChar on Windows when not in comptime to avoid including a /// redundant copy of the uppercase data. pub inline fn toUpperWtf16(c: u16) u16 { @@ -3126,7 +2997,7 @@ pub fn eqlIgnoreCaseWtf16(a: []const u16, b: []const u16) bool { // endianness for the uppercasing const a_c_native = std.mem.littleToNative(u16, a_c); const b_c_native = std.mem.littleToNative(u16, b_c); - if (a_c != b_c and nls.upcaseW(a_c_native) != nls.upcaseW(b_c_native)) { + if (a_c != b_c and toUpperWtf16(a_c_native) != toUpperWtf16(b_c_native)) { return false; } } @@ -3134,19 +3005,7 @@ pub fn eqlIgnoreCaseWtf16(a: []const u16, b: []const u16) bool { } // Use RtlEqualUnicodeString on Windows when not in comptime to avoid including a // redundant copy of the uppercase data. - const a_bytes = @as(u16, @intCast(a.len * 2)); - const a_string: UNICODE_STRING = .{ - .Length = a_bytes, - .MaximumLength = a_bytes, - .Buffer = @constCast(a.ptr), - }; - const b_bytes = @as(u16, @intCast(b.len * 2)); - const b_string: UNICODE_STRING = .{ - .Length = b_bytes, - .MaximumLength = b_bytes, - .Buffer = @constCast(b.ptr), - }; - return ntdll.RtlEqualUnicodeString(&a_string, &b_string, TRUE) == TRUE; + return ntdll.RtlEqualUnicodeString(&.init(a), &.init(b), TRUE) == TRUE; } /// Compares two WTF-8 strings using the equivalent functionality of @@ -3464,13 +3323,10 @@ pub const LONG = i32; pub const ULONG64 = u64; pub const ULONGLONG = u64; pub const LONGLONG = i64; -pub const HLOCAL = HANDLE; pub const LANGID = c_ushort; pub const COLORREF = DWORD; -pub const WPARAM = usize; pub const LPARAM = LONG_PTR; -pub const LRESULT = LONG_PTR; pub const va_list = *opaque {}; @@ -3480,6 +3336,40 @@ pub const LPCTSTR = @compileError("Deprecated: choose between `LPCSTR` or `LPCWS pub const PTSTR = @compileError("Deprecated: choose between `PSTR` or `PWSTR` directly instead."); pub const PCTSTR = @compileError("Deprecated: choose between `PCSTR` or `PCWSTR` directly instead."); +fn STRING(comptime C: type) type { + return extern struct { + Length: USHORT, + MaximumLength: USHORT, + Buffer: ?[*]C, + + pub const empty: @This() = .{ .Length = 0, .MaximumLength = 0, .Buffer = null }; + + pub fn init(string: []const C) @This() { + const len: USHORT = @intCast(@sizeOf(C) * string.len); + return .{ + .Length = len, + .MaximumLength = len, + .Buffer = @constCast(string.ptr), + }; + } + + pub fn isEmpty(string: *const @This()) bool { + return string.Length == 0; + } + + pub fn slice(string: *const @This()) []C { + return if (string.isEmpty()) &.{} else string.Buffer.?[0..@divExact(string.Length, @sizeOf(C))]; + } + + pub fn sliceZ(string: *const @This()) [:0]C { + assert(string.Length + @sizeOf(C) <= string.MaximumLength); + return string.Buffer.?[0..@divExact(string.Length, @sizeOf(C)) :0]; + } + }; +} +pub const ANSI_STRING = STRING(CHAR); +pub const UNICODE_STRING = STRING(WCHAR); + pub const TRUE = 1; pub const FALSE = 0; @@ -3509,111 +3399,14 @@ pub const OVERLAPPED = extern struct { hEvent: ?HANDLE, }; -pub const OVERLAPPED_ENTRY = extern struct { - lpCompletionKey: ULONG_PTR, - lpOverlapped: *OVERLAPPED, - Internal: ULONG_PTR, - dwNumberOfBytesTransferred: DWORD, -}; - pub const MAX_PATH = 260; -pub const FILE_INFO_BY_HANDLE_CLASS = enum(u32) { - FileBasicInfo = 0, - FileStandardInfo = 1, - FileNameInfo = 2, - FileRenameInfo = 3, - FileDispositionInfo = 4, - FileAllocationInfo = 5, - FileEndOfFileInfo = 6, - FileStreamInfo = 7, - FileCompressionInfo = 8, - FileAttributeTagInfo = 9, - FileIdBothDirectoryInfo = 10, - FileIdBothDirectoryRestartInfo = 11, - FileIoPriorityHintInfo = 12, - FileRemoteProtocolInfo = 13, - FileFullDirectoryInfo = 14, - FileFullDirectoryRestartInfo = 15, - FileStorageInfo = 16, - FileAlignmentInfo = 17, - FileIdInfo = 18, - FileIdExtdDirectoryInfo = 19, - FileIdExtdDirectoryRestartInfo = 20, -}; - -pub const BY_HANDLE_FILE_INFORMATION = extern struct { - dwFileAttributes: DWORD, - ftCreationTime: FILETIME, - ftLastAccessTime: FILETIME, - ftLastWriteTime: FILETIME, - dwVolumeSerialNumber: DWORD, - nFileSizeHigh: DWORD, - nFileSizeLow: DWORD, - nNumberOfLinks: DWORD, - nFileIndexHigh: DWORD, - nFileIndexLow: DWORD, -}; - -pub const FILE_NAME_INFO = extern struct { - FileNameLength: DWORD, - FileName: [1]WCHAR, -}; - -/// Return the normalized drive name. This is the default. -pub const FILE_NAME_NORMALIZED = 0x0; - -/// Return the opened file name (not normalized). -pub const FILE_NAME_OPENED = 0x8; - -/// Return the path with the drive letter. This is the default. -pub const VOLUME_NAME_DOS = 0x0; - -/// Return the path with a volume GUID path instead of the drive name. -pub const VOLUME_NAME_GUID = 0x1; - -/// Return the path with no drive information. -pub const VOLUME_NAME_NONE = 0x4; - -/// Return the path with the volume device path. -pub const VOLUME_NAME_NT = 0x2; - pub const SECURITY_ATTRIBUTES = extern struct { nLength: DWORD, lpSecurityDescriptor: ?*anyopaque, bInheritHandle: BOOL, }; -pub const PIPE_ACCESS_INBOUND = 0x00000001; -pub const PIPE_ACCESS_OUTBOUND = 0x00000002; -pub const PIPE_ACCESS_DUPLEX = 0x00000003; - -pub const PIPE_TYPE_BYTE = 0x00000000; -pub const PIPE_TYPE_MESSAGE = 0x00000004; - -pub const PIPE_READMODE_BYTE = 0x00000000; -pub const PIPE_READMODE_MESSAGE = 0x00000002; - -pub const PIPE_WAIT = 0x00000000; -pub const PIPE_NOWAIT = 0x00000001; - -pub const CREATE_ALWAYS = 2; -pub const CREATE_NEW = 1; -pub const OPEN_ALWAYS = 4; -pub const OPEN_EXISTING = 3; -pub const TRUNCATE_EXISTING = 5; - -// flags for CreateEvent -pub const CREATE_EVENT_INITIAL_SET = 0x00000002; -pub const CREATE_EVENT_MANUAL_RESET = 0x00000001; - -pub const PROCESS_INFORMATION = extern struct { - hProcess: HANDLE, - hThread: HANDLE, - dwProcessId: DWORD, - dwThreadId: DWORD, -}; - pub const STARTUPINFOW = extern struct { cb: DWORD, lpReserved: ?LPWSTR, @@ -3650,50 +3443,7 @@ pub const STARTF_USESHOWWINDOW = 0x00000001; pub const STARTF_USESIZE = 0x00000002; pub const STARTF_USESTDHANDLES = 0x00000100; -pub const INFINITE = 4294967295; - -pub const MAXIMUM_WAIT_OBJECTS = 64; - -pub const WAIT_ABANDONED = 0x00000080; -pub const WAIT_ABANDONED_0 = WAIT_ABANDONED + 0; -pub const WAIT_OBJECT_0 = 0x00000000; -pub const WAIT_TIMEOUT = 0x00000102; -pub const WAIT_FAILED = 0xFFFFFFFF; - -pub const HANDLE_FLAG_INHERIT = 0x00000001; -pub const HANDLE_FLAG_PROTECT_FROM_CLOSE = 0x00000002; - -pub const MOVEFILE_COPY_ALLOWED = 2; -pub const MOVEFILE_CREATE_HARDLINK = 16; -pub const MOVEFILE_DELAY_UNTIL_REBOOT = 4; -pub const MOVEFILE_FAIL_IF_NOT_TRACKABLE = 32; -pub const MOVEFILE_REPLACE_EXISTING = 1; -pub const MOVEFILE_WRITE_THROUGH = 8; - -pub const FILE_BEGIN = 0; -pub const FILE_CURRENT = 1; -pub const FILE_END = 2; - -pub const PTHREAD_START_ROUTINE = *const fn (LPVOID) callconv(.winapi) DWORD; -pub const LPTHREAD_START_ROUTINE = PTHREAD_START_ROUTINE; - -pub const WIN32_FIND_DATAW = extern struct { - dwFileAttributes: DWORD, - ftCreationTime: FILETIME, - ftLastAccessTime: FILETIME, - ftLastWriteTime: FILETIME, - nFileSizeHigh: DWORD, - nFileSizeLow: DWORD, - dwReserved0: DWORD, - dwReserved1: DWORD, - cFileName: [260]u16, - cAlternateFileName: [14]u16, -}; - -pub const FILETIME = extern struct { - dwLowDateTime: DWORD, - dwHighDateTime: DWORD, -}; +pub const THREAD_START_ROUTINE = fn (LPVOID) callconv(.winapi) DWORD; pub const SYSTEM_INFO = extern struct { anon1: extern union { @@ -3716,7 +3466,6 @@ pub const SYSTEM_INFO = extern struct { pub const HRESULT = c_long; -pub const KNOWNFOLDERID = GUID; pub const GUID = extern struct { Data1: u32, Data2: u16, @@ -3771,75 +3520,11 @@ test GUID { ); } -pub const FOLDERID_LocalAppData = GUID.parse("{F1B32785-6FBA-4FCF-9D55-7B8E7F157091}"); - -pub const KF_FLAG_DEFAULT = 0; -pub const KF_FLAG_NO_APPCONTAINER_REDIRECTION = 65536; -pub const KF_FLAG_CREATE = 32768; -pub const KF_FLAG_DONT_VERIFY = 16384; -pub const KF_FLAG_DONT_UNEXPAND = 8192; -pub const KF_FLAG_NO_ALIAS = 4096; -pub const KF_FLAG_INIT = 2048; -pub const KF_FLAG_DEFAULT_PATH = 1024; -pub const KF_FLAG_NOT_PARENT_RELATIVE = 512; -pub const KF_FLAG_SIMPLE_IDLIST = 256; -pub const KF_FLAG_ALIAS_ONLY = -2147483648; - -pub const S_OK = 0; -pub const S_FALSE = 0x00000001; -pub const E_NOTIMPL = @as(c_long, @bitCast(@as(c_ulong, 0x80004001))); -pub const E_NOINTERFACE = @as(c_long, @bitCast(@as(c_ulong, 0x80004002))); -pub const E_POINTER = @as(c_long, @bitCast(@as(c_ulong, 0x80004003))); -pub const E_ABORT = @as(c_long, @bitCast(@as(c_ulong, 0x80004004))); -pub const E_FAIL = @as(c_long, @bitCast(@as(c_ulong, 0x80004005))); -pub const E_UNEXPECTED = @as(c_long, @bitCast(@as(c_ulong, 0x8000FFFF))); -pub const E_ACCESSDENIED = @as(c_long, @bitCast(@as(c_ulong, 0x80070005))); -pub const E_HANDLE = @as(c_long, @bitCast(@as(c_ulong, 0x80070006))); -pub const E_OUTOFMEMORY = @as(c_long, @bitCast(@as(c_ulong, 0x8007000E))); -pub const E_INVALIDARG = @as(c_long, @bitCast(@as(c_ulong, 0x80070057))); - -pub fn HRESULT_CODE(hr: HRESULT) Win32Error { - return @enumFromInt(hr & 0xFFFF); -} - -pub const FILE_FLAG_BACKUP_SEMANTICS = 0x02000000; -pub const FILE_FLAG_DELETE_ON_CLOSE = 0x04000000; -pub const FILE_FLAG_NO_BUFFERING = 0x20000000; -pub const FILE_FLAG_OPEN_NO_RECALL = 0x00100000; -pub const FILE_FLAG_OPEN_REPARSE_POINT = 0x00200000; -pub const FILE_FLAG_OVERLAPPED = 0x40000000; -pub const FILE_FLAG_POSIX_SEMANTICS = 0x0100000; -pub const FILE_FLAG_RANDOM_ACCESS = 0x10000000; -pub const FILE_FLAG_SESSION_AWARE = 0x00800000; -pub const FILE_FLAG_SEQUENTIAL_SCAN = 0x08000000; -pub const FILE_FLAG_WRITE_THROUGH = 0x80000000; - -pub const RECT = extern struct { - left: LONG, - top: LONG, - right: LONG, - bottom: LONG, -}; - -pub const SMALL_RECT = extern struct { - Left: SHORT, - Top: SHORT, - Right: SHORT, - Bottom: SHORT, -}; - -pub const POINT = extern struct { - x: LONG, - y: LONG, -}; - pub const COORD = extern struct { X: SHORT, Y: SHORT, }; -pub const CREATE_UNICODE_ENVIRONMENT = 1024; - pub const TLS_OUT_OF_INDEXES = 4294967295; pub const IMAGE_TLS_DIRECTORY = extern struct { StartAddressOfRawData: usize, @@ -3854,8 +3539,6 @@ pub const IMAGE_TLS_DIRECTORY32 = IMAGE_TLS_DIRECTORY; pub const PIMAGE_TLS_CALLBACK = ?*const fn (PVOID, DWORD, PVOID) callconv(.winapi) void; -pub const PROV_RSA_FULL = 1; - pub const REGSAM = ACCESS_MASK; pub const LSTATUS = LONG; @@ -3872,9 +3555,6 @@ pub const HKEY_CURRENT_CONFIG: HKEY = @ptrFromInt(0x80000005); pub const HKEY_DYN_DATA: HKEY = @ptrFromInt(0x80000006); pub const HKEY_CURRENT_USER_LOCAL_SETTINGS: HKEY = @ptrFromInt(0x80000007); -/// Open symbolic link. -pub const REG_OPTION_OPEN_LINK: DWORD = 0x8; - pub const RTL_QUERY_REGISTRY_TABLE = extern struct { QueryRoutine: RTL_QUERY_REGISTRY_ROUTINE, Flags: ULONG, @@ -3975,38 +3655,6 @@ pub const REG = struct { pub const QWORD_LITTLE_ENDIAN: ULONG = 11; }; -pub const FILE_NOTIFY_INFORMATION = extern struct { - NextEntryOffset: DWORD, - Action: DWORD, - FileNameLength: DWORD, - // Flexible array member - // FileName: [1]WCHAR, -}; - -pub const FILE_ACTION_ADDED = 0x00000001; -pub const FILE_ACTION_REMOVED = 0x00000002; -pub const FILE_ACTION_MODIFIED = 0x00000003; -pub const FILE_ACTION_RENAMED_OLD_NAME = 0x00000004; -pub const FILE_ACTION_RENAMED_NEW_NAME = 0x00000005; - -pub const LPOVERLAPPED_COMPLETION_ROUTINE = ?*const fn (DWORD, DWORD, *OVERLAPPED) callconv(.winapi) void; - -pub const FileNotifyChangeFilter = packed struct(DWORD) { - file_name: bool = false, - dir_name: bool = false, - attributes: bool = false, - size: bool = false, - last_write: bool = false, - last_access: bool = false, - creation: bool = false, - ea: bool = false, - security: bool = false, - stream_name: bool = false, - stream_size: bool = false, - stream_write: bool = false, - _pad: u20 = 0, -}; - pub const ENABLE_VIRTUAL_TERMINAL_PROCESSING = 0x4; pub const DISABLE_NEWLINE_AUTO_RETURN = 0x8; @@ -4056,26 +3704,6 @@ pub const RTL_RUN_ONCE = extern struct { pub const RTL_RUN_ONCE_INIT = RTL_RUN_ONCE{ .Ptr = null }; -pub const COINIT = struct { - pub const APARTMENTTHREADED = 2; - pub const MULTITHREADED = 0; - pub const DISABLE_OLE1DDE = 4; - pub const SPEED_OVER_MEMORY = 8; -}; - -pub const MEMORY_BASIC_INFORMATION = extern struct { - BaseAddress: PVOID, - AllocationBase: PVOID, - AllocationProtect: DWORD, - PartitionId: WORD, - RegionSize: SIZE_T, - State: DWORD, - Protect: DWORD, - Type: DWORD, -}; - -pub const PMEMORY_BASIC_INFORMATION = *MEMORY_BASIC_INFORMATION; - /// > The maximum path of 32,767 characters is approximate, because the "\\?\" /// > prefix may be expanded to a longer string by the system at run time, and /// > this expansion applies to the total length. @@ -4544,14 +4172,6 @@ pub const UNW_FLAG_EHANDLER = 0x1; pub const UNW_FLAG_UHANDLER = 0x2; pub const UNW_FLAG_CHAININFO = 0x4; -pub const UNICODE_STRING = extern struct { - Length: c_ushort, - MaximumLength: c_ushort, - Buffer: ?[*]WCHAR, - - pub const empty: UNICODE_STRING = .{ .Length = 0, .MaximumLength = 0, .Buffer = null }; -}; - pub const ACTIVATION_CONTEXT_DATA = opaque {}; pub const ASSEMBLY_STORAGE_MAP = opaque {}; pub const FLS_CALLBACK_INFO = opaque {}; @@ -4564,15 +4184,6 @@ pub const CLIENT_ID = extern struct { UniqueThread: HANDLE, }; -pub const THREAD_BASIC_INFORMATION = extern struct { - ExitStatus: NTSTATUS, - TebBaseAddress: PVOID, - ClientId: CLIENT_ID, - AffinityMask: KAFFINITY, - Priority: KPRIORITY, - BasePriority: KPRIORITY, -}; - pub const TEB = extern struct { NtTib: NT_TIB, EnvironmentPointer: PVOID, @@ -4580,7 +4191,7 @@ pub const TEB = extern struct { ActiveRpcHandle: PVOID, ThreadLocalStoragePointer: PVOID, ProcessEnvironmentBlock: *PEB, - LastErrorValue: ULONG, + LastErrorValue: Win32Error, Reserved2: [399 * @sizeOf(PVOID) - @sizeOf(ULONG)]u8, Reserved3: [1952]u8, TlsSlots: [64]PVOID, @@ -4793,7 +4404,7 @@ pub const PEB = extern struct { }; /// The `PEB_LDR_DATA` structure is the main record of what modules are loaded in a process. -/// It is essentially the head of three double-linked lists of `LDR_DATA_TABLE_ENTRY` structures which each represent one loaded module. +/// It is essentially the head of three double-linked lists of `LDR.DATA_TABLE_ENTRY` structures which each represent one loaded module. /// /// Microsoft documentation of this is incomplete, the fields here are taken from various resources including: /// - https://www.geoffchappell.com/studies/windows/win32/ntdll/structs/peb_ldr_data.htm @@ -4825,24 +4436,89 @@ pub const PEB_LDR_DATA = extern struct { ShutdownThreadId: HANDLE, }; -/// Microsoft documentation of this is incomplete, the fields here are taken from various resources including: -/// - https://docs.microsoft.com/en-us/windows/win32/api/winternl/ns-winternl-peb_ldr_data -/// - https://www.geoffchappell.com/studies/windows/km/ntoskrnl/inc/api/ntldr/ldr_data_table_entry.htm -pub const LDR_DATA_TABLE_ENTRY = extern struct { - InLoadOrderLinks: LIST_ENTRY, - InMemoryOrderLinks: LIST_ENTRY, - InInitializationOrderLinks: LIST_ENTRY, - DllBase: PVOID, - EntryPoint: PVOID, - SizeOfImage: ULONG, - FullDllName: UNICODE_STRING, - BaseDllName: UNICODE_STRING, - Reserved5: [3]PVOID, - DUMMYUNIONNAME: extern union { - CheckSum: ULONG, - Reserved6: PVOID, - }, - TimeDateStamp: ULONG, +pub const LDR = struct { + /// Microsoft documentation of this is incomplete, the fields here are taken from various resources including: + /// - https://docs.microsoft.com/en-us/windows/win32/api/winternl/ns-winternl-peb_ldr_data + /// - https://www.geoffchappell.com/studies/windows/km/ntoskrnl/inc/api/ntldr/ldr_data_table_entry.htm + pub const DATA_TABLE_ENTRY = extern struct { + InLoadOrderLinks: LIST_ENTRY, + InMemoryOrderLinks: LIST_ENTRY, + InInitializationOrderLinks: LIST_ENTRY, + DllBase: PVOID, + EntryPoint: PVOID, + SizeOfImage: ULONG, + FullDllName: UNICODE_STRING, + BaseDllName: UNICODE_STRING, + Reserved5: [3]PVOID, + DUMMYUNIONNAME: extern union { + CheckSum: ULONG, + Reserved6: PVOID, + }, + TimeDateStamp: ULONG, + }; + + pub const DLL_NOTIFICATION = struct { + pub const REASON = enum(ULONG) { LOADED = 1, UNLOADED = 2 }; + + pub const DATA = extern union { + Loaded: LOADED, + Unloaded: UNLOADED, + + pub const LOADED = extern struct { + Flags: REGISTER, + FullDllName: *const UNICODE_STRING, + BaseDllName: *const UNICODE_STRING, + DllBase: PVOID, + SizeOfImage: ULONG, + }; + + pub const UNLOADED = extern struct { + Flags: REGISTER, + FullDllName: *const UNICODE_STRING, + BaseDllName: *const UNICODE_STRING, + DllBase: PVOID, + SizeOfImage: ULONG, + }; + }; + + pub const COOKIE = *opaque {}; + + pub const FUNCTION = fn ( + NotificationReason: REASON, + NotificationData: *const DATA, + Context: ?PVOID, + ) callconv(.winapi) void; + + pub const REGISTER = packed struct(ULONG) { + Reserved0: u32 = 0, + }; + }; + + pub const GET_DLL_HANDLE_EX = packed struct(ULONG) { + UNCHANGED_REFCOUNT: bool = false, + PIN: bool = false, + Reserved2: u30 = 0, + }; + + pub const GET_PROCEDURE_ADDRESS = packed struct(ULONG) { + DONT_RECORD_FORWARDER: bool = false, + Reserved1: u31 = 0, + }; + + pub const LOAD = packed struct(ULONG) { + DONT_RESOLVE_DLL_REFERENCES: bool = false, + LIBRARY_AS_DATAFILE: bool = false, + PACKAGED_LIBRARY: bool = false, + WITH_ALTERED_SEARCH_PATH: bool = false, + IGNORE_CODE_AUTHZ_LEVEL: bool = false, + LIBRARY_AS_IMAGE_RESOURCE: bool = false, + LIBRARY_AS_DATAFILE_EXCLUSIVE: bool = false, + LIBRARY_REQUERE_SIGNED_TARGET: bool = false, + LIBRARY_SEARCH_DLL_LOAD_DIR: bool = false, + LIBRARY_SEARCH_USER_DIRS: bool = false, + LIBRARY_SEARCH_SYSTEM32: bool = false, + LIBRARY_SEARCH_DEFAULT_DIRS: bool = false, + }; }; pub const RTL_USER_PROCESS_PARAMETERS = extern struct { @@ -4956,86 +4632,6 @@ pub const MODULEINFO = extern struct { EntryPoint: LPVOID, }; -pub const PSAPI_WS_WATCH_INFORMATION = extern struct { - FaultingPc: LPVOID, - FaultingVa: LPVOID, -}; - -pub const VM_COUNTERS = extern struct { - PeakVirtualSize: SIZE_T, - VirtualSize: SIZE_T, - PageFaultCount: ULONG, - PeakWorkingSetSize: SIZE_T, - WorkingSetSize: SIZE_T, - QuotaPeakPagedPoolUsage: SIZE_T, - QuotaPagedPoolUsage: SIZE_T, - QuotaPeakNonPagedPoolUsage: SIZE_T, - QuotaNonPagedPoolUsage: SIZE_T, - PagefileUsage: SIZE_T, - PeakPagefileUsage: SIZE_T, -}; - -pub const PROCESS_MEMORY_COUNTERS = extern struct { - cb: DWORD, - PageFaultCount: DWORD, - PeakWorkingSetSize: SIZE_T, - WorkingSetSize: SIZE_T, - QuotaPeakPagedPoolUsage: SIZE_T, - QuotaPagedPoolUsage: SIZE_T, - QuotaPeakNonPagedPoolUsage: SIZE_T, - QuotaNonPagedPoolUsage: SIZE_T, - PagefileUsage: SIZE_T, - PeakPagefileUsage: SIZE_T, -}; - -pub const PROCESS_MEMORY_COUNTERS_EX = extern struct { - cb: DWORD, - PageFaultCount: DWORD, - PeakWorkingSetSize: SIZE_T, - WorkingSetSize: SIZE_T, - QuotaPeakPagedPoolUsage: SIZE_T, - QuotaPagedPoolUsage: SIZE_T, - QuotaPeakNonPagedPoolUsage: SIZE_T, - QuotaNonPagedPoolUsage: SIZE_T, - PagefileUsage: SIZE_T, - PeakPagefileUsage: SIZE_T, - PrivateUsage: SIZE_T, -}; - -pub const PERFORMANCE_INFORMATION = extern struct { - cb: DWORD, - CommitTotal: SIZE_T, - CommitLimit: SIZE_T, - CommitPeak: SIZE_T, - PhysicalTotal: SIZE_T, - PhysicalAvailable: SIZE_T, - SystemCache: SIZE_T, - KernelTotal: SIZE_T, - KernelPaged: SIZE_T, - KernelNonpaged: SIZE_T, - PageSize: SIZE_T, - HandleCount: DWORD, - ProcessCount: DWORD, - ThreadCount: DWORD, -}; - -pub const ENUM_PAGE_FILE_INFORMATION = extern struct { - cb: DWORD, - Reserved: DWORD, - TotalSize: SIZE_T, - TotalInUse: SIZE_T, - PeakUsage: SIZE_T, -}; - -pub const PENUM_PAGE_FILE_CALLBACKW = ?*const fn (?LPVOID, *ENUM_PAGE_FILE_INFORMATION, LPCWSTR) callconv(.winapi) BOOL; -pub const PENUM_PAGE_FILE_CALLBACKA = ?*const fn (?LPVOID, *ENUM_PAGE_FILE_INFORMATION, LPCSTR) callconv(.winapi) BOOL; - -pub const PSAPI_WS_WATCH_INFORMATION_EX = extern struct { - BasicInfo: PSAPI_WS_WATCH_INFORMATION, - FaultingThreadId: ULONG_PTR, - Flags: ULONG_PTR, -}; - pub const OSVERSIONINFOW = extern struct { dwOSVersionInfoSize: ULONG, dwMajorVersion: ULONG, @@ -5098,20 +4694,6 @@ pub const MOUNTMGR_VOLUME_PATHS = extern struct { MultiSz: [1]WCHAR, }; -pub const OBJECT_INFORMATION_CLASS = enum(c_int) { - ObjectBasicInformation = 0, - ObjectNameInformation = 1, - ObjectTypeInformation = 2, - ObjectTypesInformation = 3, - ObjectHandleFlagInformation = 4, - ObjectSessionInformation = 5, - MaxObjectInfoClass, -}; - -pub const OBJECT_NAME_INFORMATION = extern struct { - Name: UNICODE_STRING, -}; - pub const SRWLOCK_INIT = SRWLOCK{}; pub const SRWLOCK = extern struct { Ptr: ?PVOID = null, @@ -5122,17 +4704,6 @@ pub const CONDITION_VARIABLE = extern struct { Ptr: ?PVOID = null, }; -pub const FILE_SKIP_COMPLETION_PORT_ON_SUCCESS = 0x1; -pub const FILE_SKIP_SET_EVENT_ON_HANDLE = 0x2; - -pub const CTRL_C_EVENT: DWORD = 0; -pub const CTRL_BREAK_EVENT: DWORD = 1; -pub const CTRL_CLOSE_EVENT: DWORD = 2; -pub const CTRL_LOGOFF_EVENT: DWORD = 5; -pub const CTRL_SHUTDOWN_EVENT: DWORD = 6; - -pub const HANDLER_ROUTINE = *const fn (dwCtrlType: DWORD) callconv(.winapi) BOOL; - /// Processor feature enumeration. pub const PF = enum(DWORD) { /// On a Pentium, a floating-point precision error can occur in rare circumstances. @@ -5431,72 +5002,13 @@ pub const KUSER_SHARED_DATA = extern struct { /// Read-only user-mode address for the shared data. /// https://www.geoffchappell.com/studies/windows/km/ntoskrnl/inc/api/ntexapi_x/kuser_shared_data/index.htm /// https://msrc-blog.microsoft.com/2022/04/05/randomizing-the-kuser_shared_data-structure-on-windows/ -pub const SharedUserData: *const KUSER_SHARED_DATA = @as(*const KUSER_SHARED_DATA, @ptrFromInt(0x7FFE0000)); +pub const SharedUserData: *const KUSER_SHARED_DATA = @ptrFromInt(0x7FFE0000); pub fn IsProcessorFeaturePresent(feature: PF) bool { if (@intFromEnum(feature) >= PROCESSOR_FEATURE_MAX) return false; return SharedUserData.ProcessorFeatures[@intFromEnum(feature)] == 1; } -pub const TH32CS_SNAPHEAPLIST = 0x00000001; -pub const TH32CS_SNAPPROCESS = 0x00000002; -pub const TH32CS_SNAPTHREAD = 0x00000004; -pub const TH32CS_SNAPMODULE = 0x00000008; -pub const TH32CS_SNAPMODULE32 = 0x00000010; -pub const TH32CS_SNAPALL = TH32CS_SNAPHEAPLIST | TH32CS_SNAPPROCESS | TH32CS_SNAPTHREAD | TH32CS_SNAPMODULE; -pub const TH32CS_INHERIT = 0x80000000; - -pub const MAX_MODULE_NAME32 = 255; -pub const MODULEENTRY32 = extern struct { - dwSize: DWORD, - th32ModuleID: DWORD, - th32ProcessID: DWORD, - GlblcntUsage: DWORD, - ProccntUsage: DWORD, - modBaseAddr: *BYTE, - modBaseSize: DWORD, - hModule: HMODULE, - szModule: [MAX_MODULE_NAME32 + 1]CHAR, - szExePath: [MAX_PATH]CHAR, -}; - -pub const SYSTEM_INFORMATION_CLASS = enum(c_int) { - SystemBasicInformation = 0, - SystemPerformanceInformation = 2, - SystemTimeOfDayInformation = 3, - SystemProcessInformation = 5, - SystemProcessorPerformanceInformation = 8, - SystemInterruptInformation = 23, - SystemExceptionInformation = 33, - SystemRegistryQuotaInformation = 37, - SystemLookasideInformation = 45, - SystemCodeIntegrityInformation = 103, - SystemPolicyInformation = 134, -}; - -pub const SYSTEM_BASIC_INFORMATION = extern struct { - Reserved: ULONG, - TimerResolution: ULONG, - PageSize: ULONG, - NumberOfPhysicalPages: ULONG, - LowestPhysicalPageNumber: ULONG, - HighestPhysicalPageNumber: ULONG, - AllocationGranularity: ULONG, - MinimumUserModeAddress: ULONG_PTR, - MaximumUserModeAddress: ULONG_PTR, - ActiveProcessorsAffinityMask: KAFFINITY, - NumberOfProcessors: UCHAR, -}; - -pub const PROCESS_BASIC_INFORMATION = extern struct { - ExitStatus: NTSTATUS, - PebBaseAddress: *PEB, - AffinityMask: ULONG_PTR, - BasePriority: KPRIORITY, - UniqueProcessId: ULONG_PTR, - InheritedFromUniqueProcessId: ULONG_PTR, -}; - // https://github.com/reactos/reactos/blob/master/sdk/include/ndk/pstypes.h#L977-L983 pub const KERNEL_USER_TIMES = extern struct { CreationTime: LARGE_INTEGER, @@ -5505,75 +5017,6 @@ pub const KERNEL_USER_TIMES = extern struct { UserTime: LARGE_INTEGER, }; -pub const ReadMemoryError = error{ - Unexpected, -}; - -pub fn ReadProcessMemory(handle: HANDLE, addr: ?LPVOID, buffer: []u8) ReadMemoryError![]u8 { - var nread: usize = 0; - switch (ntdll.NtReadVirtualMemory( - handle, - addr, - buffer.ptr, - buffer.len, - &nread, - )) { - .SUCCESS => return buffer[0..nread], - // TODO: map errors - else => |rc| return unexpectedStatus(rc), - } -} - -pub const WriteMemoryError = error{ - Unexpected, -}; - -pub fn WriteProcessMemory(handle: HANDLE, addr: ?LPVOID, buffer: []const u8) WriteMemoryError!usize { - var nwritten: usize = 0; - switch (ntdll.NtWriteVirtualMemory( - handle, - addr, - buffer.ptr, - buffer.len, - &nwritten, - )) { - .SUCCESS => return nwritten, - // TODO: map errors - else => |rc| return unexpectedStatus(rc), - } -} - -pub const ProcessBaseAddressError = error{ - AccessDenied, - InvalidHandle, - Unexpected, -} || ReadMemoryError; - -/// Returns the base address of the process loaded into memory. -pub fn ProcessBaseAddress(handle: HANDLE) ProcessBaseAddressError!HMODULE { - var info: PROCESS_BASIC_INFORMATION = undefined; - var nread: DWORD = 0; - const rc = ntdll.NtQueryInformationProcess( - handle, - .BasicInformation, - &info, - @sizeOf(PROCESS_BASIC_INFORMATION), - &nread, - ); - switch (rc) { - .SUCCESS => {}, - .ACCESS_DENIED => return error.AccessDenied, - .INVALID_HANDLE => return error.InvalidHandle, - .INVALID_PARAMETER => unreachable, - else => return unexpectedStatus(rc), - } - - var peb_buf: [@sizeOf(PEB)]u8 align(@alignOf(PEB)) = undefined; - const peb_out = try ReadProcessMemory(handle, info.PebBaseAddress, &peb_buf); - const ppeb: *const PEB = @ptrCast(@alignCast(peb_out.ptr)); - return ppeb.ImageBaseAddress; -} - pub fn wtf8ToWtf16Le(wtf16le: []u16, wtf8: []const u8) error{ BadPathName, NameTooLong }!usize { // Each u8 in UTF-8/WTF-8 correlates to at most one u16 in UTF-16LE/WTF-16LE. if (wtf16le.len < wtf8.len) { diff --git a/lib/std/os/windows/kernel32.zig b/lib/std/os/windows/kernel32.zig index ed9af392a3a09b5ba649f965f6ff1798eada944d..30a9a220222f6856a98b08c81577996c204d19f0 100644 --- a/lib/std/os/windows/kernel32.zig +++ b/lib/std/os/windows/kernel32.zig @@ -1,154 +1,29 @@ const std = @import("../../std.zig"); const windows = std.os.windows; -const ACCESS_MASK = windows.ACCESS_MASK; const BOOL = windows.BOOL; -const CONDITION_VARIABLE = windows.CONDITION_VARIABLE; -const COORD = windows.COORD; const DWORD = windows.DWORD; -const FARPROC = windows.FARPROC; -const FILETIME = windows.FILETIME; const HANDLE = windows.HANDLE; -const HANDLER_ROUTINE = windows.HANDLER_ROUTINE; -const HMODULE = windows.HMODULE; -const LARGE_INTEGER = windows.LARGE_INTEGER; -const LPCSTR = windows.LPCSTR; const LPCVOID = windows.LPCVOID; const LPCWSTR = windows.LPCWSTR; -const LPTHREAD_START_ROUTINE = windows.LPTHREAD_START_ROUTINE; const LPVOID = windows.LPVOID; const LPWSTR = windows.LPWSTR; -const MODULEENTRY32 = windows.MODULEENTRY32; -const OVERLAPPED = windows.OVERLAPPED; -const OVERLAPPED_ENTRY = windows.OVERLAPPED_ENTRY; -const PROCESS_INFORMATION = windows.PROCESS_INFORMATION; +const PROCESS = windows.PROCESS; +const THREAD_START_ROUTINE = windows.THREAD_START_ROUTINE; const SECURITY_ATTRIBUTES = windows.SECURITY_ATTRIBUTES; const SIZE_T = windows.SIZE_T; -const SRWLOCK = windows.SRWLOCK; const STARTUPINFOW = windows.STARTUPINFOW; -const SYSTEM_INFO = windows.SYSTEM_INFO; -const UCHAR = windows.UCHAR; const UINT = windows.UINT; -const ULONG = windows.ULONG; -const ULONG_PTR = windows.ULONG_PTR; const va_list = windows.va_list; -const WCHAR = windows.WCHAR; const Win32Error = windows.Win32Error; -const WORD = windows.WORD; // I/O - Filesystem -pub extern "kernel32" fn ReadDirectoryChangesW( - hDirectory: HANDLE, - lpBuffer: [*]align(@alignOf(windows.FILE_NOTIFY_INFORMATION)) u8, - nBufferLength: DWORD, - bWatchSubtree: BOOL, - dwNotifyFilter: windows.FileNotifyChangeFilter, - lpBytesReturned: ?*DWORD, - lpOverlapped: ?*OVERLAPPED, - lpCompletionRoutine: windows.LPOVERLAPPED_COMPLETION_ROUTINE, -) callconv(.winapi) BOOL; - -// TODO: Wrapper around NtCancelIoFile. -pub extern "kernel32" fn CancelIo( - hFile: HANDLE, -) callconv(.winapi) BOOL; - -// TODO: Wrapper around NtSetInformationFile + `FILE_POSITION_INFORMATION`. -// `FILE_STANDARD_INFORMATION` is also used if dwMoveMethod is `FILE_END` -pub extern "kernel32" fn SetFilePointerEx( - hFile: HANDLE, - liDistanceToMove: LARGE_INTEGER, - lpNewFilePointer: ?*LARGE_INTEGER, - dwMoveMethod: DWORD, -) callconv(.winapi) BOOL; - -// TODO: Wrapper around NtSetInformationFile + `FILE_BASIC_INFORMATION` -pub extern "kernel32" fn SetFileTime( - hFile: HANDLE, - lpCreationTime: ?*const FILETIME, - lpLastAccessTime: ?*const FILETIME, - lpLastWriteTime: ?*const FILETIME, -) callconv(.winapi) BOOL; - -pub extern "kernel32" fn WriteFile( - in_hFile: HANDLE, - in_lpBuffer: [*]const u8, - in_nNumberOfBytesToWrite: DWORD, - out_lpNumberOfBytesWritten: ?*DWORD, - in_out_lpOverlapped: ?*OVERLAPPED, -) callconv(.winapi) BOOL; - -// TODO: Wrapper around NtSetInformationFile + `FILE_IO_COMPLETION_NOTIFICATION_INFORMATION`. -pub extern "kernel32" fn SetFileCompletionNotificationModes( - FileHandle: HANDLE, - Flags: UCHAR, -) callconv(.winapi) BOOL; - -pub extern "kernel32" fn ReadFile( - hFile: HANDLE, - lpBuffer: LPVOID, - nNumberOfBytesToRead: DWORD, - lpNumberOfBytesRead: ?*DWORD, - lpOverlapped: ?*OVERLAPPED, -) callconv(.winapi) BOOL; - pub extern "kernel32" fn GetSystemDirectoryW( lpBuffer: LPWSTR, uSize: UINT, ) callconv(.winapi) UINT; -// I/O - Kernel Objects - -// TODO: Wrapper around NtRemoveIoCompletion. -pub extern "kernel32" fn GetQueuedCompletionStatus( - CompletionPort: HANDLE, - lpNumberOfBytesTransferred: *DWORD, - lpCompletionKey: *ULONG_PTR, - lpOverlapped: *?*OVERLAPPED, - dwMilliseconds: DWORD, -) callconv(.winapi) BOOL; - -// TODO: Wrapper around NtRemoveIoCompletionEx. -pub extern "kernel32" fn GetQueuedCompletionStatusEx( - CompletionPort: HANDLE, - lpCompletionPortEntries: [*]OVERLAPPED_ENTRY, - ulCount: ULONG, - ulNumEntriesRemoved: *ULONG, - dwMilliseconds: DWORD, - fAlertable: BOOL, -) callconv(.winapi) BOOL; - -// TODO: Wrapper around NtSetIoCompletion with `IoStatus = .SUCCESS`. -pub extern "kernel32" fn PostQueuedCompletionStatus( - CompletionPort: HANDLE, - dwNumberOfBytesTransferred: DWORD, - dwCompletionKey: ULONG_PTR, - lpOverlapped: ?*OVERLAPPED, -) callconv(.winapi) BOOL; - -pub extern "kernel32" fn GetOverlappedResult( - hFile: HANDLE, - lpOverlapped: *OVERLAPPED, - lpNumberOfBytesTransferred: *DWORD, - bWait: BOOL, -) callconv(.winapi) BOOL; - -// TODO: Wrapper around NtCreateIoCompletion + NtSetInformationFile with FILE_COMPLETION_INFORMATION. -// This would be better splitting into two functions. -pub extern "kernel32" fn CreateIoCompletionPort( - FileHandle: HANDLE, - ExistingCompletionPort: ?HANDLE, - CompletionKey: ULONG_PTR, - NumberOfConcurrentThreads: DWORD, -) callconv(.winapi) ?HANDLE; - -// TODO: Wrapper around RtlReportSilentProcessExit + NtTerminateProcess. -pub extern "kernel32" fn TerminateProcess( - hProcess: HANDLE, - uExitCode: UINT, -) callconv(.winapi) BOOL; - // Process Management pub extern "kernel32" fn CreateProcessW( @@ -161,86 +36,21 @@ pub extern "kernel32" fn CreateProcessW( lpEnvironment: ?[*:0]const u16, lpCurrentDirectory: ?LPCWSTR, lpStartupInfo: *STARTUPINFOW, - lpProcessInformation: *PROCESS_INFORMATION, + lpProcessInformation: *PROCESS.INFORMATION, ) callconv(.winapi) BOOL; -// TODO: Wrapper around NtQueryInformationProcess with `PROCESS_BASIC_INFORMATION`. -pub extern "kernel32" fn GetExitCodeProcess( - hProcess: HANDLE, - lpExitCode: *DWORD, -) callconv(.winapi) BOOL; - -pub extern "kernel32" fn CreateToolhelp32Snapshot( - dwFlags: DWORD, - th32ProcessID: DWORD, -) callconv(.winapi) HANDLE; - // Threading -// TODO: Already a wrapper for this, see `windows.GetCurrentThreadId`. -pub extern "kernel32" fn GetCurrentThreadId() callconv(.winapi) DWORD; - // TODO: CreateRemoteThread with hProcess=NtCurrentProcess(). pub extern "kernel32" fn CreateThread( lpThreadAttributes: ?*SECURITY_ATTRIBUTES, dwStackSize: SIZE_T, - lpStartAddress: LPTHREAD_START_ROUTINE, + lpStartAddress: *const THREAD_START_ROUTINE, lpParameter: ?LPVOID, dwCreationFlags: DWORD, lpThreadId: ?*DWORD, ) callconv(.winapi) ?HANDLE; -// Code Libraries/Modules - -// TODO: Wrapper around LdrGetDllFullName. -pub extern "kernel32" fn GetModuleFileNameW( - hModule: ?HMODULE, - lpFilename: [*]WCHAR, - nSize: DWORD, -) callconv(.winapi) DWORD; - -extern "kernel32" fn K32GetModuleFileNameExW( - hProcess: HANDLE, - hModule: ?HMODULE, - lpFilename: LPWSTR, - nSize: DWORD, -) callconv(.winapi) DWORD; -pub const GetModuleFileNameExW = K32GetModuleFileNameExW; - -// TODO: Wrapper around ntdll.LdrGetDllHandle, which is a wrapper around LdrGetDllHandleEx -pub extern "kernel32" fn GetModuleHandleW( - lpModuleName: ?LPCWSTR, -) callconv(.winapi) ?HMODULE; - -pub extern "kernel32" fn Module32First( - hSnapshot: HANDLE, - lpme: *MODULEENTRY32, -) callconv(.winapi) BOOL; - -pub extern "kernel32" fn Module32Next( - hSnapshot: HANDLE, - lpme: *MODULEENTRY32, -) callconv(.winapi) BOOL; - -pub extern "kernel32" fn LoadLibraryW( - lpLibFileName: LPCWSTR, -) callconv(.winapi) ?HMODULE; - -pub extern "kernel32" fn LoadLibraryExW( - lpLibFileName: LPCWSTR, - hFile: ?HANDLE, - dwFlags: DWORD, -) callconv(.winapi) ?HMODULE; - -pub extern "kernel32" fn GetProcAddress( - hModule: HMODULE, - lpProcName: LPCSTR, -) callconv(.winapi) ?FARPROC; - -pub extern "kernel32" fn FreeLibrary( - hModule: HMODULE, -) callconv(.winapi) BOOL; - // Error Management pub extern "kernel32" fn FormatMessageW( @@ -252,6 +62,3 @@ pub extern "kernel32" fn FormatMessageW( nSize: DWORD, Arguments: ?*va_list, ) callconv(.winapi) DWORD; - -// TODO: Getter for teb().LastErrorValue. -pub extern "kernel32" fn GetLastError() callconv(.winapi) Win32Error; diff --git a/lib/std/os/windows/ntdll.zig b/lib/std/os/windows/ntdll.zig index bda9fee828b6dc33c44b4a40b090c1d0f6761d84..77799bc1b4d762d7ba67753ccc232d3a63d90ca8 100644 --- a/lib/std/os/windows/ntdll.zig +++ b/lib/std/os/windows/ntdll.zig @@ -2,6 +2,7 @@ const std = @import("../../std.zig"); const windows = std.os.windows; const ACCESS_MASK = windows.ACCESS_MASK; +const ANSI_STRING = windows.ANSI_STRING; const BOOL = windows.BOOL; const BOOLEAN = windows.BOOLEAN; const CONDITION_VARIABLE = windows.CONDITION_VARIABLE; @@ -9,6 +10,7 @@ const CONTEXT = windows.CONTEXT; const CRITICAL_SECTION = windows.CRITICAL_SECTION; const CTL_CODE = windows.CTL_CODE; const CURDIR = windows.CURDIR; +const DIRECTORY = windows.DIRECTORY; const DWORD = windows.DWORD; const DWORD64 = windows.DWORD64; const ERESOURCE = windows.ERESOURCE; @@ -22,18 +24,19 @@ const IO_APC_ROUTINE = windows.IO_APC_ROUTINE; const IO_STATUS_BLOCK = windows.IO_STATUS_BLOCK; const KNONVOLATILE_CONTEXT_POINTERS = windows.KNONVOLATILE_CONTEXT_POINTERS; const LARGE_INTEGER = windows.LARGE_INTEGER; +const LDR = windows.LDR; const LOGICAL = windows.LOGICAL; const LONG = windows.LONG; const LPCVOID = windows.LPCVOID; const LPVOID = windows.LPVOID; const MEM = windows.MEM; const NTSTATUS = windows.NTSTATUS; -const OBJECT_ATTRIBUTES = windows.OBJECT_ATTRIBUTES; -const OBJECT_INFORMATION_CLASS = windows.OBJECT_INFORMATION_CLASS; +const OBJECT = windows.OBJECT; const PAGE = windows.PAGE; const PCWSTR = windows.PCWSTR; -const PROCESSINFOCLASS = windows.PROCESSINFOCLASS; +const PROCESS = windows.PROCESS; const PVOID = windows.PVOID; +const PWSTR = windows.PWSTR; const RTL_OSVERSIONINFOW = windows.RTL_OSVERSIONINFOW; const RTL_QUERY_REGISTRY_TABLE = windows.RTL_QUERY_REGISTRY_TABLE; const RUNTIME_FUNCTION = windows.RUNTIME_FUNCTION; @@ -41,8 +44,8 @@ const SEC = windows.SEC; const SECTION_INHERIT = windows.SECTION_INHERIT; const SIZE_T = windows.SIZE_T; const SRWLOCK = windows.SRWLOCK; -const SYSTEM_INFORMATION_CLASS = windows.SYSTEM_INFORMATION_CLASS; -const THREADINFOCLASS = windows.THREADINFOCLASS; +const SYSTEM = windows.SYSTEM; +const THREAD = windows.THREAD; const ULONG = windows.ULONG; const ULONG_PTR = windows.ULONG_PTR; const UNICODE_STRING = windows.UNICODE_STRING; @@ -91,7 +94,7 @@ pub extern "ntdll" fn RtlCaptureContext( pub extern "ntdll" fn NtSetInformationThread( ThreadHandle: HANDLE, - ThreadInformationClass: THREADINFOCLASS, + ThreadInformationClass: THREAD.INFOCLASS, ThreadInformation: *const anyopaque, ThreadInformationLength: ULONG, ) callconv(.winapi) NTSTATUS; @@ -99,7 +102,7 @@ pub extern "ntdll" fn NtSetInformationThread( pub extern "ntdll" fn NtCreateFile( FileHandle: *HANDLE, DesiredAccess: ACCESS_MASK, - ObjectAttributes: *const OBJECT_ATTRIBUTES, + ObjectAttributes: *const OBJECT.ATTRIBUTES, IoStatusBlock: *IO_STATUS_BLOCK, AllocationSize: ?*const LARGE_INTEGER, FileAttributes: FILE.ATTRIBUTE, @@ -152,7 +155,7 @@ pub extern "ntdll" fn NtLockFile( pub extern "ntdll" fn NtOpenFile( FileHandle: *HANDLE, DesiredAccess: ACCESS_MASK, - ObjectAttributes: *const OBJECT_ATTRIBUTES, + ObjectAttributes: *const OBJECT.ATTRIBUTES, IoStatusBlock: *IO_STATUS_BLOCK, ShareAccess: FILE.SHARE, OpenOptions: FILE.MODE, @@ -233,7 +236,7 @@ pub extern "ntdll" fn NtUnlockFile( pub extern "ntdll" fn NtQueryObject( Handle: HANDLE, - ObjectInformationClass: OBJECT_INFORMATION_CLASS, + ObjectInformationClass: OBJECT.INFORMATION_CLASS, ObjectInformation: ?PVOID, ObjectInformationLength: ULONG, ReturnLength: ?*ULONG, @@ -246,7 +249,7 @@ pub extern "ntdll" fn NtClose( pub extern "ntdll" fn NtCreateSection( SectionHandle: *HANDLE, DesiredAccess: ACCESS_MASK, - ObjectAttributes: ?*const OBJECT_ATTRIBUTES, + ObjectAttributes: ?*const OBJECT.ATTRIBUTES, MaximumSize: ?*const LARGE_INTEGER, SectionPageProtection: PAGE, AllocationAttributes: SEC, @@ -331,7 +334,7 @@ pub extern "ntdll" fn NtWaitForSingleObject( pub extern "ntdll" fn NtQueryInformationProcess( ProcessHandle: HANDLE, - ProcessInformationClass: PROCESSINFOCLASS, + ProcessInformationClass: PROCESS.INFOCLASS, ProcessInformation: *anyopaque, ProcessInformationLength: ULONG, ReturnLength: ?*ULONG, @@ -339,14 +342,14 @@ pub extern "ntdll" fn NtQueryInformationProcess( pub extern "ntdll" fn NtQueryInformationThread( ThreadHandle: HANDLE, - ThreadInformationClass: THREADINFOCLASS, + ThreadInformationClass: THREAD.INFOCLASS, ThreadInformation: *anyopaque, ThreadInformationLength: ULONG, ReturnLength: ?*ULONG, ) callconv(.winapi) NTSTATUS; pub extern "ntdll" fn NtQuerySystemInformation( - SystemInformationClass: SYSTEM_INFORMATION_CLASS, + SystemInformationClass: SYSTEM.INFORMATION_CLASS, SystemInformation: PVOID, SystemInformationLength: ULONG, ReturnLength: ?*ULONG, @@ -354,15 +357,99 @@ pub extern "ntdll" fn NtQuerySystemInformation( // ref none +pub extern "ntdll" fn LdrAddRefDll( + Flags: ULONG, + DllHandle: PVOID, +) callconv(.winapi) NTSTATUS; +pub extern "ntdll" fn LdrLoadDll( + DllPath: ?PCWSTR, + DllCharacteristics: ?*const ULONG, + DllName: *const UNICODE_STRING, + DllHandle: *PVOID, +) callconv(.winapi) NTSTATUS; +pub extern "ntdll" fn LdrUnloadDll( + DllHandle: PVOID, +) callconv(.winapi) NTSTATUS; + +pub extern "ntdll" fn LdrFindEntryForAddress( + DllHandle: PVOID, + Entry: **LDR.DATA_TABLE_ENTRY, +) callconv(.winapi) NTSTATUS; +pub extern "ntdll" fn LdrGetDllFullName( + DllHandle: ?PVOID, + FullDllName: *UNICODE_STRING, +) callconv(.winapi) NTSTATUS; +pub extern "ntdll" fn LdrGetDllPath( + DllName: PCWSTR, + Flags: LDR.LOAD, + DllPath: *PWSTR, + SearchPaths: *PWSTR, +) callconv(.winapi) NTSTATUS; + +pub extern "ntdll" fn LdrGetDllHandle( + DllPath: ?PCWSTR, + DllCharacteristics: ?*const ULONG, + DllName: *const UNICODE_STRING, + DllHandle: *PVOID, +) callconv(.winapi) NTSTATUS; +pub extern "ntdll" fn LdrGetDllHandleByMapping( + BaseAddress: PVOID, + DllHandle: *PVOID, +) callconv(.winapi) NTSTATUS; +pub extern "ntdll" fn LdrGetDllHandleByName( + BaseDllName: *const UNICODE_STRING, + FullDllName: *const UNICODE_STRING, + DllHandle: *PVOID, +) callconv(.winapi) NTSTATUS; +pub extern "ntdll" fn LdrGetDllHandleEx( + Flags: LDR.GET_DLL_HANDLE_EX, + DllPath: ?PCWSTR, + DllCharacteristics: ?*const ULONG, + DllName: *const UNICODE_STRING, + DllHandle: *PVOID, +) callconv(.winapi) NTSTATUS; + +pub extern "ntdll" fn LdrGetProcedureAddress( + DllHandle: PVOID, + ProcedureName: *const ANSI_STRING, + ProcedureNumber: ULONG, + ProcedureAddress: *PVOID, +) callconv(.winapi) NTSTATUS; +pub extern "ntdll" fn LdrGetProcedureAddressEx( + DllHandle: PVOID, + ProcedureName: *const ANSI_STRING, + ProcedureNumber: ULONG, + ProcedureAddress: *PVOID, + Flags: LDR.GET_PROCEDURE_ADDRESS, +) callconv(.winapi) NTSTATUS; +pub extern "ntdll" fn LdrGetProcedureAddressForCaller( + DllHandle: PVOID, + ProcedureName: *const ANSI_STRING, + ProcedureNumber: ULONG, + ProcedureAddress: *PVOID, + Flags: LDR.GET_PROCEDURE_ADDRESS, + CallerAddress: PVOID, +) callconv(.winapi) NTSTATUS; + +pub extern "ntdll" fn LdrRegisterDllNotification( + Flags: LDR.DLL_NOTIFICATION.REGISTER, + NotificationFunction: *const LDR.DLL_NOTIFICATION.FUNCTION, + Context: ?PVOID, + Cookie: *LDR.DLL_NOTIFICATION.COOKIE, +) callconv(.winapi) NTSTATUS; +pub extern "ntdll" fn LdrUnregisterDllNotification( + Cookie: LDR.DLL_NOTIFICATION.COOKIE, +) callconv(.winapi) NTSTATUS; + pub extern "ntdll" fn NtQueryAttributesFile( - ObjectAttributes: *const OBJECT_ATTRIBUTES, + ObjectAttributes: *const OBJECT.ATTRIBUTES, FileAttributes: *FILE.BASIC_INFORMATION, ) callconv(.winapi) NTSTATUS; pub extern "ntdll" fn NtCreateEvent( EventHandle: *HANDLE, DesiredAccess: ACCESS_MASK, - ObjectAttributes: ?*const OBJECT_ATTRIBUTES, + ObjectAttributes: ?*const OBJECT.ATTRIBUTES, EventType: EVENT_TYPE, InitialState: BOOLEAN, ) callconv(.winapi) NTSTATUS; @@ -374,7 +461,7 @@ pub extern "ntdll" fn NtSetEvent( pub extern "ntdll" fn NtCreateKeyedEvent( KeyedEventHandle: *HANDLE, DesiredAccess: ACCESS_MASK, - ObjectAttributes: ?*const OBJECT_ATTRIBUTES, + ObjectAttributes: ?*const OBJECT.ATTRIBUTES, Flags: ULONG, ) callconv(.winapi) NTSTATUS; pub extern "ntdll" fn NtReleaseKeyedEvent( @@ -390,10 +477,57 @@ pub extern "ntdll" fn NtWaitForKeyedEvent( Timeout: ?*const LARGE_INTEGER, ) callconv(.winapi) NTSTATUS; +pub extern "ntdll" fn NtCancelSynchronousIoFile( + ThreadHandle: HANDLE, + IoRequestToCancel: ?*IO_STATUS_BLOCK, + IoStatusBlock: *IO_STATUS_BLOCK, +) callconv(.winapi) NTSTATUS; +pub extern "ntdll" fn NtCancelIoFile( + FileHandle: HANDLE, + IoStatusBlock: *IO_STATUS_BLOCK, +) callconv(.winapi) NTSTATUS; +pub extern "ntdll" fn NtCancelIoFileEx( + FileHandle: HANDLE, + IoRequestToCancel: *const IO_STATUS_BLOCK, + IoStatusBlock: *IO_STATUS_BLOCK, +) callconv(.winapi) NTSTATUS; + +/// This function has been observed to return SUCCESS on timeout on Windows 10 +/// and TIMEOUT on Wine 10.0. +/// +/// This function has been observed on Windows 11 such that positive interval +/// is real time, which can cause waits to be interrupted by changing system +/// time, however negative intervals are not affected by changes to system +/// time. +pub extern "ntdll" fn NtDelayExecution( + Alertable: BOOLEAN, + DelayInterval: *const LARGE_INTEGER, +) callconv(.winapi) NTSTATUS; + +pub extern "ntdll" fn NtNotifyChangeDirectoryFileEx( + FileHandle: HANDLE, + Event: ?HANDLE, + ApcRoutine: ?*const IO_APC_ROUTINE, + ApcContext: ?*anyopaque, + IoStatusBlock: *IO_STATUS_BLOCK, + Buffer: *anyopaque, + Length: ULONG, + CompletionFilter: FILE.NOTIFY.CHANGE, + WatchTree: BOOLEAN, + DirectoryNotifyInformationClass: DIRECTORY.NOTIFY_INFORMATION_CLASS, +) callconv(.winapi) NTSTATUS; + +pub extern "ntdll" fn NtOpenThread( + ThreadHandle: *HANDLE, + DesiredAccess: ACCESS_MASK, + ObjectAttributes: *const OBJECT.ATTRIBUTES, + ClientId: *const windows.CLIENT_ID, +) callconv(.winapi) NTSTATUS; + pub extern "ntdll" fn NtCreateNamedPipeFile( FileHandle: *HANDLE, DesiredAccess: ACCESS_MASK, - ObjectAttributes: *const OBJECT_ATTRIBUTES, + ObjectAttributes: *const OBJECT.ATTRIBUTES, IoStatusBlock: *IO_STATUS_BLOCK, ShareAccess: FILE.SHARE, CreateDisposition: FILE.CREATE_DISPOSITION, @@ -437,7 +571,7 @@ pub extern "ntdll" fn NtUnmapViewOfSectionEx( pub extern "ntdll" fn NtOpenKey( KeyHandle: *HANDLE, DesiredAccess: ACCESS_MASK, - ObjectAttributes: *const OBJECT_ATTRIBUTES, + ObjectAttributes: *const OBJECT.ATTRIBUTES, ) callconv(.winapi) NTSTATUS; pub extern "ntdll" fn NtQueueApcThread( @@ -470,6 +604,19 @@ pub extern "ntdll" fn NtProtectVirtualMemory( OldAccessProtection: *PAGE, ) callconv(.winapi) NTSTATUS; +pub extern "ntdll" fn NtWaitForAlertByThreadId( + Address: ?*const anyopaque, + Timeout: ?*const LARGE_INTEGER, +) callconv(.winapi) NTSTATUS; +pub extern "ntdll" fn NtAlertThreadByThreadId(ThreadId: DWORD) callconv(.winapi) NTSTATUS; +pub extern "ntdll" fn NtAlertThread(ThreadHandle: HANDLE) callconv(.winapi) NTSTATUS; +pub extern "ntdll" fn NtAlertMultipleThreadByThreadId( + ThreadIds: [*]const ULONG_PTR, + ThreadCount: ULONG, + Unknown1: ?*const anyopaque, + Unknown2: ?*const anyopaque, +) callconv(.winapi) NTSTATUS; + pub extern "ntdll" fn NtYieldExecution() callconv(.winapi) NTSTATUS; pub extern "ntdll" fn RtlAddVectoredExceptionHandler( @@ -527,10 +674,6 @@ pub extern "ntdll" fn RtlQueryPerformanceCounter( pub extern "ntdll" fn RtlQueryPerformanceFrequency( PerformanceFrequency: *LARGE_INTEGER, ) callconv(.winapi) BOOL; -pub extern "ntdll" fn NtQueryPerformanceCounter( - PerformanceCounter: *LARGE_INTEGER, - PerformanceFrequency: ?*LARGE_INTEGER, -) callconv(.winapi) NTSTATUS; pub extern "ntdll" fn RtlReAllocateHeap( HeapHandle: *HEAP, @@ -539,8 +682,17 @@ pub extern "ntdll" fn RtlReAllocateHeap( Size: SIZE_T, ) callconv(.winapi) ?PVOID; +pub extern "ntdll" fn RtlReportSilentProcessExit( + ProcessHandle: HANDLE, + ExitStatus: NTSTATUS, +) callconv(.winapi) NTSTATUS; +pub extern "ntdll" fn NtTerminateProcess( + ProcessHandle: ?HANDLE, + ExitStatus: NTSTATUS, +) callconv(.winapi) NTSTATUS; + pub extern "ntdll" fn RtlSetCurrentDirectory_U( - PathName: *UNICODE_STRING, + PathName: *const UNICODE_STRING, ) callconv(.winapi) NTSTATUS; pub extern "ntdll" fn RtlTryAcquireSRWLockExclusive( @@ -572,52 +724,3 @@ pub extern "ntdll" fn RtlWakeConditionVariable( pub extern "ntdll" fn RtlWakeAllConditionVariable( ConditionVariable: *CONDITION_VARIABLE, ) callconv(.winapi) void; - -pub extern "ntdll" fn NtWaitForAlertByThreadId( - Address: ?*const anyopaque, - Timeout: ?*const LARGE_INTEGER, -) callconv(.winapi) NTSTATUS; -pub extern "ntdll" fn NtAlertThreadByThreadId(ThreadId: DWORD) callconv(.winapi) NTSTATUS; -pub extern "ntdll" fn NtAlertThread(ThreadHandle: HANDLE) callconv(.winapi) NTSTATUS; -pub extern "ntdll" fn NtAlertMultipleThreadByThreadId( - ThreadIds: [*]const ULONG_PTR, - ThreadCount: ULONG, - Unknown1: ?*const anyopaque, - Unknown2: ?*const anyopaque, -) callconv(.winapi) NTSTATUS; - -pub extern "ntdll" fn NtOpenThread( - ThreadHandle: *HANDLE, - DesiredAccess: ACCESS_MASK, - ObjectAttributes: *const OBJECT_ATTRIBUTES, - ClientId: *const windows.CLIENT_ID, -) callconv(.winapi) NTSTATUS; - -pub extern "ntdll" fn NtCancelSynchronousIoFile( - ThreadHandle: HANDLE, - IoRequestToCancel: ?*IO_STATUS_BLOCK, - IoStatusBlock: *IO_STATUS_BLOCK, -) callconv(.winapi) NTSTATUS; - -/// This function has been observed to return SUCCESS on timeout on Windows 10 -/// and TIMEOUT on Wine 10.0. -/// -/// This function has been observed on Windows 11 such that positive interval -/// is real time, which can cause waits to be interrupted by changing system -/// time, however negative intervals are not affected by changes to system -/// time. -pub extern "ntdll" fn NtDelayExecution( - Alertable: BOOLEAN, - DelayInterval: *const LARGE_INTEGER, -) callconv(.winapi) NTSTATUS; - -pub extern "ntdll" fn NtCancelIoFile( - FileHandle: HANDLE, - IoStatusBlock: *IO_STATUS_BLOCK, -) callconv(.winapi) NTSTATUS; - -pub extern "ntdll" fn NtCancelIoFileEx( - FileHandle: HANDLE, - IoRequestToCancel: *const IO_STATUS_BLOCK, - IoStatusBlock: *IO_STATUS_BLOCK, -) callconv(.winapi) NTSTATUS; diff --git a/lib/std/os/windows/win32error.zig b/lib/std/os/windows/win32error.zig index a71256228c3faea3ed46def3b64035592581f5a8..4dc826cbb9a8a983c9d998e477273bc5eb883ae9 100644 --- a/lib/std/os/windows/win32error.zig +++ b/lib/std/os/windows/win32error.zig @@ -1,5 +1,5 @@ /// Codes are from https://docs.microsoft.com/en-us/openspecs/windows_protocols/ms-erref/18d8fbe8-a967-4f1c-ae50-99ca8e491d2d -pub const Win32Error = enum(u16) { +pub const Win32Error = enum(u32) { /// The operation completed successfully. SUCCESS = 0, /// Incorrect function. diff --git a/lib/std/process.zig b/lib/std/process.zig index 618d7f8f5bc3cbc1b62e05693d5d550fdcbb5eb1..2e3056b96099412d672fcc8f6fd4b5eca8768096 100644 --- a/lib/std/process.zig +++ b/lib/std/process.zig @@ -243,7 +243,7 @@ pub fn getBaseAddress() usize { .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => { return @intFromPtr(&std.c._mh_execute_header); }, - .windows => return @intFromPtr(windows.kernel32.GetModuleHandleW(null)), + .windows => return @intFromPtr(windows.peb().ImageBaseAddress), else => @compileError("Unsupported OS"), } } @@ -606,11 +606,11 @@ pub fn totalSystemMemory() TotalSystemMemoryError!u64 { return @as(u64, @bitCast(physmem)); }, .windows => { - var sbi: windows.SYSTEM_BASIC_INFORMATION = undefined; + var sbi: windows.SYSTEM.BASIC_INFORMATION = undefined; const rc = windows.ntdll.NtQuerySystemInformation( - .SystemBasicInformation, + .Basic, &sbi, - @sizeOf(windows.SYSTEM_BASIC_INFORMATION), + @sizeOf(windows.SYSTEM.BASIC_INFORMATION), null, ); if (rc != .SUCCESS) { diff --git a/lib/std/process/Child.zig b/lib/std/process/Child.zig index 7ce3143b362de6f5f837f7e513304d54b47fea4b..0cc60a23e999239401c7531cade47eff2d27e52d 100644 --- a/lib/std/process/Child.zig +++ b/lib/std/process/Child.zig @@ -86,7 +86,7 @@ pub const ResourceUsageStatistics = struct { .visionos, .watchos, => @as(?std.posix.rusage, null), - .windows => @as(?std.os.windows.VM_COUNTERS, null), + .windows => @as(?std.os.windows.PROCESS.VM_COUNTERS, null), else => {}, }; }; diff --git a/lib/std/start.zig b/lib/std/start.zig index e39465fe9b395b0cc4a4395eb589d98b42ac3232..9bc001fe598b5182943be40b85794ba95e7c7158 100644 --- a/lib/std/start.zig +++ b/lib/std/start.zig @@ -473,10 +473,9 @@ fn WinStartup() callconv(.withStackAlign(.c, 1)) noreturn { std.Thread.maybeAttachSignalStack(); std.debug.maybeEnableSegfaultHandler(); - const cmd_line = std.os.windows.peb().ProcessParameters.CommandLine; - const cmd_line_w = cmd_line.Buffer.?[0..@divExact(cmd_line.Length, 2)]; - - std.os.windows.ntdll.RtlExitUserProcess(callMain(cmd_line_w, .global)); + std.os.windows.ntdll.RtlExitUserProcess( + callMain(std.os.windows.peb().ProcessParameters.CommandLine.slice(), .global), + ); } fn wWinMainCRTStartup() callconv(.withStackAlign(.c, 1)) noreturn { @@ -647,9 +646,7 @@ fn main(c_argc: c_int, c_argv: [*][*:0]c_char, c_envp: [*:null]?[*:0]c_char) cal // values in their intended encoding from the PEB instead. std.Thread.maybeAttachSignalStack(); std.debug.maybeEnableSegfaultHandler(); - const cmd_line = std.os.windows.peb().ProcessParameters.CommandLine; - const cmd_line_w = cmd_line.Buffer.?[0..@divExact(cmd_line.Length, 2)]; - return callMain(cmd_line_w, .global); + return callMain(std.os.windows.peb().ProcessParameters.CommandLine.slice(), .global); }, else => {}, } @@ -776,8 +773,7 @@ fn call_wWinMain() std.os.windows.INT { // - With STARTF_USESHOWWINDOW unset: // - nShowCmd is always SW_SHOWDEFAULT const SW_SHOWDEFAULT = 10; - const STARTF_USESHOWWINDOW = 1; - if (peb.ProcessParameters.dwFlags & STARTF_USESHOWWINDOW != 0) { + if (peb.ProcessParameters.dwFlags & std.os.windows.STARTF_USESHOWWINDOW != 0) { break :nShowCmd @truncate(peb.ProcessParameters.dwShowWindow); } break :nShowCmd SW_SHOWDEFAULT; diff --git a/lib/std/zig/system/windows.zig b/lib/std/zig/system/windows.zig index 43d407c3a0a0a26b850d0eb8c9fa12cf7707f33a..0a752dd7f9d55d18073830b1bdcfcc6c12af45f7 100644 --- a/lib/std/zig/system/windows.zig +++ b/lib/std/zig/system/windows.zig @@ -100,11 +100,11 @@ fn getCpuInfoFromRegistry(core: usize, args: anytype) !void { REG.MULTI_SZ, => { comptime assert(@sizeOf(std.os.windows.UNICODE_STRING) % 2 == 0); - const unicode = @as(*std.os.windows.UNICODE_STRING, @ptrCast(&tmp_bufs[i])); + const unicode: *std.os.windows.UNICODE_STRING = @ptrCast(&tmp_bufs[i]); unicode.* = .{ .Length = 0, .MaximumLength = max_value_len - @sizeOf(std.os.windows.UNICODE_STRING), - .Buffer = @as([*]u16, @ptrCast(tmp_bufs[i][@sizeOf(std.os.windows.UNICODE_STRING)..])), + .Buffer = @ptrCast(tmp_bufs[i][@sizeOf(std.os.windows.UNICODE_STRING)..]), }; break :blk unicode; }, @@ -159,8 +159,8 @@ fn getCpuInfoFromRegistry(core: usize, args: anytype) !void { REG.MULTI_SZ, => { var buf = @field(args, field.name).value_buf; - const entry = @as(*align(1) const std.os.windows.UNICODE_STRING, @ptrCast(table[i + 1].EntryContext)); - const len = try std.unicode.utf16LeToUtf8(buf, entry.Buffer.?[0 .. entry.Length / 2]); + const entry: *const std.os.windows.UNICODE_STRING = @ptrCast(table[i + 1].EntryContext); + const len = try std.unicode.utf16LeToUtf8(buf, entry.slice()); buf[len] = 0; }, @@ -168,7 +168,7 @@ fn getCpuInfoFromRegistry(core: usize, args: anytype) !void { REG.DWORD_BIG_ENDIAN, REG.QWORD, => { - const entry = @as([*]align(1) const u8, @ptrCast(table[i + 1].EntryContext)); + const entry: [*]const u8 = @ptrCast(table[i + 1].EntryContext); switch (@field(args, field.name).value_type) { REG.DWORD, REG.DWORD_BIG_ENDIAN => { @memcpy(@field(args, field.name).value_buf[0..4], entry[0..4]); diff --git a/lib/zig.h b/lib/zig.h index 7a7a22b17ba14ff003c3b64964f61a88bafb76f7..81a815ab5568b57f5d48c8da91a04b761d4f9a2a 100644 --- a/lib/zig.h +++ b/lib/zig.h @@ -4148,7 +4148,7 @@ static inline void zig_msvc_atomic_store_i128(zig_i128 volatile* obj, zig_i128 a #if defined(zig_thumb) -static inline void* zig_thumb_windows_teb(void) { +static inline void* zig_windows_teb(void) { void* teb = 0; #if defined(zig_msvc) teb = (void*)_MoveFromCoprocessor(15, 0, 13, 0, 2); @@ -4160,7 +4160,7 @@ static inline void* zig_thumb_windows_teb(void) { #elif defined(zig_aarch64) -static inline void* zig_aarch64_windows_teb(void) { +static inline void* zig_windows_teb(void) { void* teb = 0; #if defined(zig_msvc) teb = (void*)__readx18qword(0x0); @@ -4172,7 +4172,7 @@ static inline void* zig_aarch64_windows_teb(void) { #elif defined(zig_x86_32) -static inline void* zig_x86_windows_teb(void) { +static inline void* zig_windows_teb(void) { void* teb = 0; #if defined(zig_msvc) teb = (void*)__readfsdword(0x18); @@ -4182,9 +4182,19 @@ static inline void* zig_x86_windows_teb(void) { return teb; } +static inline void* zig_windows_peb(void) { + void* peb = 0; +#if defined(zig_msvc) + peb = (void*)__readfsdword(0x30); +#elif defined(zig_gnuc_asm) + __asm__ ("movl %%fs:0x30, %[ptr]" : [ptr] "=r" (peb)); +#endif + return peb; +} + #elif defined(zig_x86_64) -static inline void* zig_x86_64_windows_teb(void) { +static inline void* zig_windows_teb(void) { void* teb = 0; #if defined(zig_msvc) teb = (void*)__readgsqword(0x30); @@ -4194,6 +4204,16 @@ static inline void* zig_x86_64_windows_teb(void) { return teb; } +static inline void* zig_windows_peb(void) { + void* peb = 0; +#if defined(zig_msvc) + peb = (void*)__readgsqword(0x60); +#elif defined(zig_gnuc_asm) + __asm__ ("movq %%gs:0x60, %[ptr]" : [ptr] "=r" (peb)); +#endif + return peb; +} + #endif #if defined(zig_loongarch) diff --git a/stage1/zig.h b/stage1/zig.h index 7a7a22b17ba14ff003c3b64964f61a88bafb76f7..81a815ab5568b57f5d48c8da91a04b761d4f9a2a 100644 --- a/stage1/zig.h +++ b/stage1/zig.h @@ -4148,7 +4148,7 @@ static inline void zig_msvc_atomic_store_i128(zig_i128 volatile* obj, zig_i128 a #if defined(zig_thumb) -static inline void* zig_thumb_windows_teb(void) { +static inline void* zig_windows_teb(void) { void* teb = 0; #if defined(zig_msvc) teb = (void*)_MoveFromCoprocessor(15, 0, 13, 0, 2); @@ -4160,7 +4160,7 @@ static inline void* zig_thumb_windows_teb(void) { #elif defined(zig_aarch64) -static inline void* zig_aarch64_windows_teb(void) { +static inline void* zig_windows_teb(void) { void* teb = 0; #if defined(zig_msvc) teb = (void*)__readx18qword(0x0); @@ -4172,7 +4172,7 @@ static inline void* zig_aarch64_windows_teb(void) { #elif defined(zig_x86_32) -static inline void* zig_x86_windows_teb(void) { +static inline void* zig_windows_teb(void) { void* teb = 0; #if defined(zig_msvc) teb = (void*)__readfsdword(0x18); @@ -4182,9 +4182,19 @@ static inline void* zig_x86_windows_teb(void) { return teb; } +static inline void* zig_windows_peb(void) { + void* peb = 0; +#if defined(zig_msvc) + peb = (void*)__readfsdword(0x30); +#elif defined(zig_gnuc_asm) + __asm__ ("movl %%fs:0x30, %[ptr]" : [ptr] "=r" (peb)); +#endif + return peb; +} + #elif defined(zig_x86_64) -static inline void* zig_x86_64_windows_teb(void) { +static inline void* zig_windows_teb(void) { void* teb = 0; #if defined(zig_msvc) teb = (void*)__readgsqword(0x30); @@ -4194,6 +4204,16 @@ static inline void* zig_x86_64_windows_teb(void) { return teb; } +static inline void* zig_windows_peb(void) { + void* peb = 0; +#if defined(zig_msvc) + peb = (void*)__readgsqword(0x60); +#elif defined(zig_gnuc_asm) + __asm__ ("movq %%gs:0x60, %[ptr]" : [ptr] "=r" (peb)); +#endif + return peb; +} + #endif #if defined(zig_loongarch) diff --git a/test/standalone/coff_dwarf/main.zig b/test/standalone/coff_dwarf/main.zig index 79caaeb95a2f569e7a0a4e62e35083216b49cdec..24684d3830e7ff35719fe9d6a90f037d8a984630 100644 --- a/test/standalone/coff_dwarf/main.zig +++ b/test/standalone/coff_dwarf/main.zig @@ -4,17 +4,16 @@ const fatal = std.process.fatal; extern fn add(a: u32, b: u32, addr: *usize) u32; pub fn main(init: std.process.Init) void { - const gpa = init.gpa; const io = init.io; var di: std.debug.SelfInfo = .init; - defer di.deinit(gpa); + defer di.deinit(io); var add_addr: usize = undefined; _ = add(1, 2, &add_addr); - const symbol = di.getSymbol(gpa, io, add_addr) catch |err| fatal("failed to get symbol: {t}", .{err}); - defer if (symbol.source_location) |sl| gpa.free(sl.file_name); + const symbol = di.getSymbol(io, add_addr) catch |err| fatal("failed to get symbol: {t}", .{err}); + defer if (symbol.source_location) |sl| std.debug.getDebugInfoAllocator().free(sl.file_name); if (symbol.name == null) fatal("failed to resolve symbol name", .{}); if (symbol.compile_unit_name == null) fatal("failed to resolve compile unit", .{}); diff --git a/test/standalone/windows_argv/fuzz.zig b/test/standalone/windows_argv/fuzz.zig index 9227bb6f893306686dc0ad08de2cfca89410b815..27b0ba3f06b3bc59387e4369c731bcca628e2685 100644 --- a/test/standalone/windows_argv/fuzz.zig +++ b/test/standalone/windows_argv/fuzz.zig @@ -127,7 +127,7 @@ fn spawnVerify(verify_path: [:0]const u16, cmd_line: [:0]const u16) !windows.DWO .hStdOutput = null, .hStdError = windows.peb().ProcessParameters.hStdError, }; - var proc_info: windows.PROCESS_INFORMATION = undefined; + var proc_info: windows.PROCESS.INFORMATION = undefined; if (windows.kernel32.CreateProcessW( @constCast(verify_path.ptr), @@ -141,7 +141,7 @@ fn spawnVerify(verify_path: [:0]const u16, cmd_line: [:0]const u16) !windows.DWO &startup_info, &proc_info, ) == 0) { - std.process.fatal("kernel32 CreateProcessW failed with {t}", .{windows.kernel32.GetLastError()}); + std.process.fatal("kernel32 CreateProcessW failed with {t}", .{windows.GetLastError()}); } windows.CloseHandle(proc_info.hThread); @@ -156,9 +156,15 @@ fn spawnVerify(verify_path: [:0]const u16, cmd_line: [:0]const u16) !windows.DWO else => |status| return windows.unexpectedStatus(status), } - var exit_code: windows.DWORD = undefined; - if (windows.kernel32.GetExitCodeProcess(child_proc, &exit_code) == 0) { - return error.UnableToGetExitCode; + var info: windows.PROCESS.BASIC_INFORMATION = undefined; + switch (windows.ntdll.NtQueryInformationProcess( + child_proc, + .BasicInformation, + &info, + @sizeOf(windows.PROCESS.BASIC_INFORMATION), + null, + )) { + .SUCCESS => return @intFromEnum(info.ExitStatus), + else => return error.UnableToGetExitCode, } - return exit_code; } diff --git a/test/standalone/windows_argv/lib.zig b/test/standalone/windows_argv/lib.zig index 750501edd21539e5eebe51848aaabd3ace812f59..370e62b7991e172b8a00dadea05d8e4f0b6ed3e3 100644 --- a/test/standalone/windows_argv/lib.zig +++ b/test/standalone/windows_argv/lib.zig @@ -16,9 +16,9 @@ fn testArgv(expected_args: []const [*:0]const u16) !void { defer arena_state.deinit(); const allocator = arena_state.allocator(); - const cmd_line = std.os.windows.peb().ProcessParameters.CommandLine; - const cmd_line_w = cmd_line.Buffer.?[0..@divExact(cmd_line.Length, 2)]; - const raw_args: std.process.Args = .{ .vector = cmd_line_w }; + const raw_args: std.process.Args = .{ + .vector = std.os.windows.peb().ProcessParameters.CommandLine.slice(), + }; const args = try raw_args.toSlice(allocator); var wtf8_buf = std.array_list.Managed(u8).init(allocator); -- 2.54.0 From 703df73f389dae88776caa3a0a026eb8acc7d8f4 Mon Sep 17 00:00:00 2001 From: angus Date: Sat, 7 Feb 2026 14:19:29 +0000 Subject: [PATCH 230/499] initialize mutex correctly in kqueue.zig --- lib/std/Io/Kqueue.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/std/Io/Kqueue.zig b/lib/std/Io/Kqueue.zig index 23cd1b39286ac6f4a0c5041b5b94b9c2896121b1..822c34f789f16bc135a1ca6dab48fe1c7d5a384a 100644 --- a/lib/std/Io/Kqueue.zig +++ b/lib/std/Io/Kqueue.zig @@ -169,7 +169,7 @@ pub fn init(k: *Kqueue, gpa: Allocator, options: InitOptions) !void { errdefer gpa.free(allocated_slice); k.* = .{ .gpa = gpa, - .mutex = .{}, + .mutex = .init, .main_fiber_buffer = undefined, .threads = .{ .allocated = @ptrCast(allocated_slice[0..threads_size]), -- 2.54.0 From 52a6242443e30265ff9e0961ff39bdea8607db45 Mon Sep 17 00:00:00 2001 From: Ben Buhse Date: Sat, 7 Feb 2026 16:21:42 -0600 Subject: [PATCH 231/499] std.os.linux: add F_SEAL constants to F struct Add the missing F_SEAL_SEAL, F_SEAL_SHRINK, F_SEAL_GROW, F_SEAL_WRITE, F_SEAL_FUTURE_WRITE, and F_SEAL_EXEC constants used with F.ADD_SEALS/F.GET_SEALS for memfd file sealing. These are defined in the Linux kernel at include/uapi/linux/fcntl.h. The FreeBSD equivalents already exist in std.c (freebsd.F), but the Linux side was missing them. --- lib/std/os/linux.zig | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/std/os/linux.zig b/lib/std/os/linux.zig index ef4616b111062dd8f5b1bd34794a9fbd15d8d7a6..c89585b9ba82c0ae6896a8284b91974bf2ceaeb2 100644 --- a/lib/std/os/linux.zig +++ b/lib/std/os/linux.zig @@ -1862,6 +1862,14 @@ pub const F = struct { pub const GETPIPE_SZ = LINUX_SPECIFIC_BASE + 8; pub const ADD_SEALS = LINUX_SPECIFIC_BASE + 9; pub const GET_SEALS = LINUX_SPECIFIC_BASE + 10; + + pub const SEAL_SEAL = 0x0001; + pub const SEAL_SHRINK = 0x0002; + pub const SEAL_GROW = 0x0004; + pub const SEAL_WRITE = 0x0008; + pub const SEAL_FUTURE_WRITE = 0x0010; + pub const SEAL_EXEC = 0x0020; + pub const GET_RW_HINT = LINUX_SPECIFIC_BASE + 11; pub const SET_RW_HINT = LINUX_SPECIFIC_BASE + 12; pub const GET_FILE_RW_HINT = LINUX_SPECIFIC_BASE + 13; -- 2.54.0 From f061c0dc2851e5fab34dca1991ebe64cfd156bd5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Sun, 8 Feb 2026 23:32:18 +0100 Subject: [PATCH 232/499] ci: disable loongarch64-linux https://codeberg.org/ziglang/zig/issues/30800 --- .forgejo/workflows/ci.yaml | 41 +++++++++++++++++++------------------- 1 file changed, 21 insertions(+), 20 deletions(-) diff --git a/.forgejo/workflows/ci.yaml b/.forgejo/workflows/ci.yaml index 48813b9001d0463d39b7ce8e2566ae5dad555b87..8ae897599a521ee72f7cbfe3ecde10e7110ff15a 100644 --- a/.forgejo/workflows/ci.yaml +++ b/.forgejo/workflows/ci.yaml @@ -59,26 +59,27 @@ jobs: run: sh ci/aarch64-macos-release.sh timeout-minutes: 120 - loongarch64-linux-debug: - runs-on: [self-hosted, loongarch64-linux] - steps: - - name: Checkout - uses: https://codeberg.org/ziglang/checkout@19af6bac491e2534a4687a50ee84fa7f13258d28 - with: - fetch-depth: 0 - - name: Build and Test - run: sh ci/loongarch64-linux-debug.sh - timeout-minutes: 240 - loongarch64-linux-release: - runs-on: [self-hosted, loongarch64-linux] - steps: - - name: Checkout - uses: https://codeberg.org/ziglang/checkout@19af6bac491e2534a4687a50ee84fa7f13258d28 - with: - fetch-depth: 0 - - name: Build and Test - run: sh ci/loongarch64-linux-release.sh - timeout-minutes: 180 + # https://codeberg.org/ziglang/zig/issues/30800 + #loongarch64-linux-debug: + # runs-on: [self-hosted, loongarch64-linux] + # steps: + # - name: Checkout + # uses: https://codeberg.org/ziglang/checkout@19af6bac491e2534a4687a50ee84fa7f13258d28 + # with: + # fetch-depth: 0 + # - name: Build and Test + # run: sh ci/loongarch64-linux-debug.sh + # timeout-minutes: 240 + #loongarch64-linux-release: + # runs-on: [self-hosted, loongarch64-linux] + # steps: + # - name: Checkout + # uses: https://codeberg.org/ziglang/checkout@19af6bac491e2534a4687a50ee84fa7f13258d28 + # with: + # fetch-depth: 0 + # - name: Build and Test + # run: sh ci/loongarch64-linux-release.sh + # timeout-minutes: 180 powerpc64le-linux-debug: runs-on: [self-hosted, powerpc64le-linux] -- 2.54.0 From 6c6392633329c3654498409d2b02c16c3f5f9dc7 Mon Sep 17 00:00:00 2001 From: Sam K Date: Tue, 7 Jan 2025 16:33:41 +1100 Subject: [PATCH 233/499] Autodoc: display line numbers in source code display --- lib/docs/index.html | 18 +++++++++++++++++- lib/docs/main.js | 9 +++++++++ lib/docs/wasm/html_render.zig | 14 ++++++++++++++ lib/docs/wasm/main.zig | 11 +++++++++++ 4 files changed, 51 insertions(+), 1 deletion(-) diff --git a/lib/docs/index.html b/lib/docs/index.html index e60a3f960a26a12c436a8a3589cc8f48f05350f1..75c0daf79795c28fd288235e675dfb3ccbe85f92 100644 --- a/lib/docs/index.html +++ b/lib/docs/index.html @@ -40,6 +40,15 @@ code a { color: #000000; } + .source-code { + display: grid; + grid-template-columns: auto 1fr; + align-items: start; + } + .source-line-numbers pre { + text-align: right; + color: #666; + } #listFields > div, #listParams > div { margin-bottom: 1em; } @@ -429,7 +438,14 @@