From a1827d597723aff6236001bd0bb74a6b0bab9ce7 Mon Sep 17 00:00:00 2001 From: Jacob Young Date: Wed, 26 Nov 2025 02:34:43 -0500 Subject: [PATCH 01/53] x86_64: fix abi of a struct that partially fits in registers Closes #26035 --- src/codegen/x86_64/CodeGen.zig | 6 ++++ test/c_abi/cfuncs.c | 63 +++++++++++++++++++--------------- test/c_abi/main.zig | 63 +++++++++++++++++++--------------- 3 files changed, 78 insertions(+), 54 deletions(-) diff --git a/src/codegen/x86_64/CodeGen.zig b/src/codegen/x86_64/CodeGen.zig index 99d768805f60..08ca72a5d3e7 100644 --- a/src/codegen/x86_64/CodeGen.zig +++ b/src/codegen/x86_64/CodeGen.zig @@ -181159,6 +181159,9 @@ fn resolveCallingConventionValues( else => unreachable, } + const save_param_gpr_index = param_gpr_index; + const save_param_sse_index = param_gpr_index; + var arg_mcv: [4]MCValue = undefined; var arg_mcv_len: u32 = 0; @@ -181258,6 +181261,9 @@ fn resolveCallingConventionValues( continue; } + param_gpr_index = save_param_gpr_index; + param_sse_index = save_param_sse_index; + const param_align = ty.abiAlignment(zcu).max(.@"8"); result.stack_byte_count = @intCast(param_align.forward(result.stack_byte_count)); result.stack_align = result.stack_align.max(param_align); diff --git a/test/c_abi/cfuncs.c b/test/c_abi/cfuncs.c index 940ddac9d21e..a79201a9dd36 100644 --- a/test/c_abi/cfuncs.c +++ b/test/c_abi/cfuncs.c @@ -212,55 +212,64 @@ struct Struct_u64_u64 { struct Struct_u64_u64 zig_ret_struct_u64_u64(void); -void zig_struct_u64_u64_0(struct Struct_u64_u64); -void zig_struct_u64_u64_1(size_t, struct Struct_u64_u64); -void zig_struct_u64_u64_2(size_t, size_t, struct Struct_u64_u64); -void zig_struct_u64_u64_3(size_t, size_t, size_t, struct Struct_u64_u64); -void zig_struct_u64_u64_4(size_t, size_t, size_t, size_t, struct Struct_u64_u64); -void zig_struct_u64_u64_5(size_t, size_t, size_t, size_t, size_t, struct Struct_u64_u64); -void zig_struct_u64_u64_6(size_t, size_t, size_t, size_t, size_t, size_t, struct Struct_u64_u64); -void zig_struct_u64_u64_7(size_t, size_t, size_t, size_t, size_t, size_t, size_t, struct Struct_u64_u64); -void zig_struct_u64_u64_8(size_t, size_t, size_t, size_t, size_t, size_t, size_t, size_t, struct Struct_u64_u64); +void zig_struct_u64_u64_0(struct Struct_u64_u64, size_t); +void zig_struct_u64_u64_1(size_t, struct Struct_u64_u64, size_t); +void zig_struct_u64_u64_2(size_t, size_t, struct Struct_u64_u64, size_t); +void zig_struct_u64_u64_3(size_t, size_t, size_t, struct Struct_u64_u64, size_t); +void zig_struct_u64_u64_4(size_t, size_t, size_t, size_t, struct Struct_u64_u64, size_t); +void zig_struct_u64_u64_5(size_t, size_t, size_t, size_t, size_t, struct Struct_u64_u64, size_t); +void zig_struct_u64_u64_6(size_t, size_t, size_t, size_t, size_t, size_t, struct Struct_u64_u64, size_t); +void zig_struct_u64_u64_7(size_t, size_t, size_t, size_t, size_t, size_t, size_t, struct Struct_u64_u64, size_t); +void zig_struct_u64_u64_8(size_t, size_t, size_t, size_t, size_t, size_t, size_t, size_t, struct Struct_u64_u64, size_t); struct Struct_u64_u64 c_ret_struct_u64_u64(void) { return (struct Struct_u64_u64){ 21, 22 }; } -void c_struct_u64_u64_0(struct Struct_u64_u64 s) { +void c_struct_u64_u64_0(struct Struct_u64_u64 s, size_t i) { assert_or_panic(s.a == 23); assert_or_panic(s.b == 24); + assert_or_panic(i == 1); } -void c_struct_u64_u64_1(size_t, struct Struct_u64_u64 s) { +void c_struct_u64_u64_1(size_t a0, struct Struct_u64_u64 s, size_t i) { assert_or_panic(s.a == 25); assert_or_panic(s.b == 26); + assert_or_panic(i == 2); } -void c_struct_u64_u64_2(size_t, size_t, struct Struct_u64_u64 s) { +void c_struct_u64_u64_2(size_t, size_t, struct Struct_u64_u64 s, size_t i) { assert_or_panic(s.a == 27); assert_or_panic(s.b == 28); + assert_or_panic(i == 3); } -void c_struct_u64_u64_3(size_t, size_t, size_t, struct Struct_u64_u64 s) { +void c_struct_u64_u64_3(size_t, size_t, size_t, struct Struct_u64_u64 s, size_t i) { assert_or_panic(s.a == 29); assert_or_panic(s.b == 30); + assert_or_panic(i == 4); } -void c_struct_u64_u64_4(size_t, size_t, size_t, size_t, struct Struct_u64_u64 s) { +void c_struct_u64_u64_4(size_t, size_t, size_t, size_t, struct Struct_u64_u64 s, size_t i) { assert_or_panic(s.a == 31); assert_or_panic(s.b == 32); + assert_or_panic(i == 5); } -void c_struct_u64_u64_5(size_t, size_t, size_t, size_t, size_t, struct Struct_u64_u64 s) { +void c_struct_u64_u64_5(size_t, size_t, size_t, size_t, size_t, struct Struct_u64_u64 s, size_t i) { assert_or_panic(s.a == 33); assert_or_panic(s.b == 34); + assert_or_panic(i == 6); } -void c_struct_u64_u64_6(size_t, size_t, size_t, size_t, size_t, size_t, struct Struct_u64_u64 s) { +void c_struct_u64_u64_6(size_t, size_t, size_t, size_t, size_t, size_t, struct Struct_u64_u64 s, size_t i) { assert_or_panic(s.a == 35); assert_or_panic(s.b == 36); + assert_or_panic(i == 7); } -void c_struct_u64_u64_7(size_t, size_t, size_t, size_t, size_t, size_t, size_t, struct Struct_u64_u64 s) { +void c_struct_u64_u64_7(size_t, size_t, size_t, size_t, size_t, size_t, size_t, struct Struct_u64_u64 s, size_t i) { assert_or_panic(s.a == 37); assert_or_panic(s.b == 38); + assert_or_panic(i == 8); } -void c_struct_u64_u64_8(size_t, size_t, size_t, size_t, size_t, size_t, size_t, size_t, struct Struct_u64_u64 s) { +void c_struct_u64_u64_8(size_t, size_t, size_t, size_t, size_t, size_t, size_t, size_t, struct Struct_u64_u64 s, size_t i) { assert_or_panic(s.a == 39); assert_or_panic(s.b == 40); + assert_or_panic(i == 9); } struct Struct_f32 { @@ -2737,15 +2746,15 @@ void run_c_tests(void) { struct Struct_u64_u64 s = zig_ret_struct_u64_u64(); assert_or_panic(s.a == 1); assert_or_panic(s.b == 2); - zig_struct_u64_u64_0((struct Struct_u64_u64){ .a = 3, .b = 4 }); - zig_struct_u64_u64_1(0, (struct Struct_u64_u64){ .a = 5, .b = 6 }); - zig_struct_u64_u64_2(0, 1, (struct Struct_u64_u64){ .a = 7, .b = 8 }); - zig_struct_u64_u64_3(0, 1, 2, (struct Struct_u64_u64){ .a = 9, .b = 10 }); - zig_struct_u64_u64_4(0, 1, 2, 3, (struct Struct_u64_u64){ .a = 11, .b = 12 }); - zig_struct_u64_u64_5(0, 1, 2, 3, 4, (struct Struct_u64_u64){ .a = 13, .b = 14 }); - zig_struct_u64_u64_6(0, 1, 2, 3, 4, 5, (struct Struct_u64_u64){ .a = 15, .b = 16 }); - zig_struct_u64_u64_7(0, 1, 2, 3, 4, 5, 6, (struct Struct_u64_u64){ .a = 17, .b = 18 }); - zig_struct_u64_u64_8(0, 1, 2, 3, 4, 5, 6, 7, (struct Struct_u64_u64){ .a = 19, .b = 20 }); + zig_struct_u64_u64_0((struct Struct_u64_u64){ .a = 3, .b = 4 }, 1); + zig_struct_u64_u64_1(0, (struct Struct_u64_u64){ .a = 5, .b = 6 }, 2); + zig_struct_u64_u64_2(0, 1, (struct Struct_u64_u64){ .a = 7, .b = 8 }, 3); + zig_struct_u64_u64_3(0, 1, 2, (struct Struct_u64_u64){ .a = 9, .b = 10 }, 4); + zig_struct_u64_u64_4(0, 1, 2, 3, (struct Struct_u64_u64){ .a = 11, .b = 12 }, 5); + zig_struct_u64_u64_5(0, 1, 2, 3, 4, (struct Struct_u64_u64){ .a = 13, .b = 14 }, 6); + zig_struct_u64_u64_6(0, 1, 2, 3, 4, 5, (struct Struct_u64_u64){ .a = 15, .b = 16 }, 7); + zig_struct_u64_u64_7(0, 1, 2, 3, 4, 5, 6, (struct Struct_u64_u64){ .a = 17, .b = 18 }, 8); + zig_struct_u64_u64_8(0, 1, 2, 3, 4, 5, 6, 7, (struct Struct_u64_u64){ .a = 19, .b = 20 }, 9); } #if !defined(ZIG_RISCV64) diff --git a/test/c_abi/main.zig b/test/c_abi/main.zig index f2b1b5014b4b..1714a39a4107 100644 --- a/test/c_abi/main.zig +++ b/test/c_abi/main.zig @@ -278,54 +278,63 @@ export fn zig_ret_struct_u64_u64() Struct_u64_u64 { return .{ .a = 1, .b = 2 }; } -export fn zig_struct_u64_u64_0(s: Struct_u64_u64) void { +export fn zig_struct_u64_u64_0(s: Struct_u64_u64, i: usize) void { expect(s.a == 3) catch @panic("test failure"); expect(s.b == 4) catch @panic("test failure"); + expect(i == 1) catch @panic("test failure"); } -export fn zig_struct_u64_u64_1(_: usize, s: Struct_u64_u64) void { +export fn zig_struct_u64_u64_1(_: usize, s: Struct_u64_u64, i: usize) void { expect(s.a == 5) catch @panic("test failure"); expect(s.b == 6) catch @panic("test failure"); + expect(i == 2) catch @panic("test failure"); } -export fn zig_struct_u64_u64_2(_: usize, _: usize, s: Struct_u64_u64) void { +export fn zig_struct_u64_u64_2(_: usize, _: usize, s: Struct_u64_u64, i: usize) void { expect(s.a == 7) catch @panic("test failure"); expect(s.b == 8) catch @panic("test failure"); + expect(i == 3) catch @panic("test failure"); } -export fn zig_struct_u64_u64_3(_: usize, _: usize, _: usize, s: Struct_u64_u64) void { +export fn zig_struct_u64_u64_3(_: usize, _: usize, _: usize, s: Struct_u64_u64, i: usize) void { expect(s.a == 9) catch @panic("test failure"); expect(s.b == 10) catch @panic("test failure"); + expect(i == 4) catch @panic("test failure"); } -export fn zig_struct_u64_u64_4(_: usize, _: usize, _: usize, _: usize, s: Struct_u64_u64) void { +export fn zig_struct_u64_u64_4(_: usize, _: usize, _: usize, _: usize, s: Struct_u64_u64, i: usize) void { expect(s.a == 11) catch @panic("test failure"); expect(s.b == 12) catch @panic("test failure"); + expect(i == 5) catch @panic("test failure"); } -export fn zig_struct_u64_u64_5(_: usize, _: usize, _: usize, _: usize, _: usize, s: Struct_u64_u64) void { +export fn zig_struct_u64_u64_5(_: usize, _: usize, _: usize, _: usize, _: usize, s: Struct_u64_u64, i: usize) void { expect(s.a == 13) catch @panic("test failure"); expect(s.b == 14) catch @panic("test failure"); + expect(i == 6) catch @panic("test failure"); } -export fn zig_struct_u64_u64_6(_: usize, _: usize, _: usize, _: usize, _: usize, _: usize, s: Struct_u64_u64) void { +export fn zig_struct_u64_u64_6(_: usize, _: usize, _: usize, _: usize, _: usize, _: usize, s: Struct_u64_u64, i: usize) void { expect(s.a == 15) catch @panic("test failure"); expect(s.b == 16) catch @panic("test failure"); + expect(i == 7) catch @panic("test failure"); } -export fn zig_struct_u64_u64_7(_: usize, _: usize, _: usize, _: usize, _: usize, _: usize, _: usize, s: Struct_u64_u64) void { +export fn zig_struct_u64_u64_7(_: usize, _: usize, _: usize, _: usize, _: usize, _: usize, _: usize, s: Struct_u64_u64, i: usize) void { expect(s.a == 17) catch @panic("test failure"); expect(s.b == 18) catch @panic("test failure"); + expect(i == 8) catch @panic("test failure"); } -export fn zig_struct_u64_u64_8(_: usize, _: usize, _: usize, _: usize, _: usize, _: usize, _: usize, _: usize, s: Struct_u64_u64) void { +export fn zig_struct_u64_u64_8(_: usize, _: usize, _: usize, _: usize, _: usize, _: usize, _: usize, _: usize, s: Struct_u64_u64, i: usize) void { expect(s.a == 19) catch @panic("test failure"); expect(s.b == 20) catch @panic("test failure"); + expect(i == 9) catch @panic("test failure"); } extern fn c_ret_struct_u64_u64() Struct_u64_u64; -extern fn c_struct_u64_u64_0(Struct_u64_u64) void; -extern fn c_struct_u64_u64_1(usize, Struct_u64_u64) void; -extern fn c_struct_u64_u64_2(usize, usize, Struct_u64_u64) void; -extern fn c_struct_u64_u64_3(usize, usize, usize, Struct_u64_u64) void; -extern fn c_struct_u64_u64_4(usize, usize, usize, usize, Struct_u64_u64) void; -extern fn c_struct_u64_u64_5(usize, usize, usize, usize, usize, Struct_u64_u64) void; -extern fn c_struct_u64_u64_6(usize, usize, usize, usize, usize, usize, Struct_u64_u64) void; -extern fn c_struct_u64_u64_7(usize, usize, usize, usize, usize, usize, usize, Struct_u64_u64) void; -extern fn c_struct_u64_u64_8(usize, usize, usize, usize, usize, usize, usize, usize, Struct_u64_u64) void; +extern fn c_struct_u64_u64_0(Struct_u64_u64, usize) void; +extern fn c_struct_u64_u64_1(usize, Struct_u64_u64, usize) void; +extern fn c_struct_u64_u64_2(usize, usize, Struct_u64_u64, usize) void; +extern fn c_struct_u64_u64_3(usize, usize, usize, Struct_u64_u64, usize) void; +extern fn c_struct_u64_u64_4(usize, usize, usize, usize, Struct_u64_u64, usize) void; +extern fn c_struct_u64_u64_5(usize, usize, usize, usize, usize, Struct_u64_u64, usize) void; +extern fn c_struct_u64_u64_6(usize, usize, usize, usize, usize, usize, Struct_u64_u64, usize) void; +extern fn c_struct_u64_u64_7(usize, usize, usize, usize, usize, usize, usize, Struct_u64_u64, usize) void; +extern fn c_struct_u64_u64_8(usize, usize, usize, usize, usize, usize, usize, usize, Struct_u64_u64, usize) void; test "C ABI struct u64 u64" { if (builtin.cpu.arch.isMIPS64()) return error.SkipZigTest; @@ -336,15 +345,15 @@ test "C ABI struct u64 u64" { const s = c_ret_struct_u64_u64(); try expect(s.a == 21); try expect(s.b == 22); - c_struct_u64_u64_0(.{ .a = 23, .b = 24 }); - c_struct_u64_u64_1(0, .{ .a = 25, .b = 26 }); - c_struct_u64_u64_2(0, 1, .{ .a = 27, .b = 28 }); - c_struct_u64_u64_3(0, 1, 2, .{ .a = 29, .b = 30 }); - c_struct_u64_u64_4(0, 1, 2, 3, .{ .a = 31, .b = 32 }); - c_struct_u64_u64_5(0, 1, 2, 3, 4, .{ .a = 33, .b = 34 }); - c_struct_u64_u64_6(0, 1, 2, 3, 4, 5, .{ .a = 35, .b = 36 }); - c_struct_u64_u64_7(0, 1, 2, 3, 4, 5, 6, .{ .a = 37, .b = 38 }); - c_struct_u64_u64_8(0, 1, 2, 3, 4, 5, 6, 7, .{ .a = 39, .b = 40 }); + c_struct_u64_u64_0(.{ .a = 23, .b = 24 }, 1); + c_struct_u64_u64_1(0, .{ .a = 25, .b = 26 }, 2); + c_struct_u64_u64_2(0, 1, .{ .a = 27, .b = 28 }, 3); + c_struct_u64_u64_3(0, 1, 2, .{ .a = 29, .b = 30 }, 4); + c_struct_u64_u64_4(0, 1, 2, 3, .{ .a = 31, .b = 32 }, 5); + c_struct_u64_u64_5(0, 1, 2, 3, 4, .{ .a = 33, .b = 34 }, 6); + c_struct_u64_u64_6(0, 1, 2, 3, 4, 5, .{ .a = 35, .b = 36 }, 7); + c_struct_u64_u64_7(0, 1, 2, 3, 4, 5, 6, .{ .a = 37, .b = 38 }, 8); + c_struct_u64_u64_8(0, 1, 2, 3, 4, 5, 6, 7, .{ .a = 39, .b = 40 }, 9); } const Struct_f32 = extern struct { From 854774d468b0a439def7eef73c544ac8d64f1b62 Mon Sep 17 00:00:00 2001 From: Frank Denis Date: Wed, 26 Nov 2025 23:04:41 +0100 Subject: [PATCH 02/53] http.requestHasBody: a PUT response can have a body MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit HEAD/TRACE are bodyless, but PUT responses are body-capable per RFC 7231. --- lib/std/http.zig | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/std/http.zig b/lib/std/http.zig index eedd729576e0..a768372eccae 100644 --- a/lib/std/http.zig +++ b/lib/std/http.zig @@ -44,8 +44,8 @@ pub const Method = enum { /// Actual behavior from clients may vary and should still be checked pub fn responseHasBody(m: Method) bool { return switch (m) { - .GET, .POST, .DELETE, .CONNECT, .OPTIONS, .PATCH => true, - .HEAD, .PUT, .TRACE => false, + .GET, .POST, .PUT, .DELETE, .CONNECT, .OPTIONS, .PATCH => true, + .HEAD, .TRACE => false, }; } From ca96d853ffaceae85f8038bb3abb6ca188c0a3e1 Mon Sep 17 00:00:00 2001 From: Frank Denis Date: Thu, 27 Nov 2025 12:10:17 +0100 Subject: [PATCH 03/53] std.crypto: add hybrid post-quantum/traditional key encapsulation Hybrid KEMs combine a post-quantum secure KEM with a traditional elliptic curve Diffie-Hellman key exchange. The hybrid construction provides security against both classical and quantum adversaries: even if one component is broken, the combined scheme remains secure as long as the other component holds. The implementation follows the IETF CFRG draft specification for concrete hybrid KEMs: https://datatracker.ietf.org/doc/draft-irtf-cfrg-concrete-hybrid-kems/ --- lib/std/crypto.zig | 1 + lib/std/crypto/hybrid_kem.zig | 1089 +++++++++++++++++++++++++++++++++ 2 files changed, 1090 insertions(+) create mode 100644 lib/std/crypto/hybrid_kem.zig diff --git a/lib/std/crypto.zig b/lib/std/crypto.zig index 1da25abe1788..a942d6538f8d 100644 --- a/lib/std/crypto.zig +++ b/lib/std/crypto.zig @@ -116,6 +116,7 @@ pub const dh = struct { /// Key Encapsulation Mechanisms. pub const kem = struct { + pub const hybrid = @import("crypto/hybrid_kem.zig"); pub const kyber_d00 = @import("crypto/ml_kem.zig").d00; pub const ml_kem = @import("crypto/ml_kem.zig").nist; }; diff --git a/lib/std/crypto/hybrid_kem.zig b/lib/std/crypto/hybrid_kem.zig new file mode 100644 index 000000000000..ee38a8022dda --- /dev/null +++ b/lib/std/crypto/hybrid_kem.zig @@ -0,0 +1,1089 @@ +//! Hybrid Post-Quantum/Traditional Key Encapsulation Mechanisms. +//! +//! Hybrid KEMs combine a post-quantum secure KEM with a traditional +//! elliptic curve Diffie-Hellman key exchange. +//! +//! The hybrid construction provides security against both classical and quantum +//! adversaries: even if one component is broken, the combined scheme remains secure +//! as long as the other component holds. +//! +//! The implementation follows the IETF CFRG draft specification for concrete hybrid KEMs: +//! https://datatracker.ietf.org/doc/draft-irtf-cfrg-concrete-hybrid-kems/ +//! +//! The combiner uses the C2PRI construction to derive the final shared secret +//! from the component shared secrets, ciphertext, and public key. + +const std = @import("std"); +const crypto = std.crypto; +const fmt = std.fmt; +const mem = std.mem; + +const sha3 = crypto.hash.sha3; +const ml_kem = crypto.kem.ml_kem; +const X25519 = crypto.dh.X25519; +const P256 = crypto.ecc.P256; +const P384 = crypto.ecc.P384; + +/// ML-KEM-768 combined with X25519 (Curve25519) aka X-Wing. +/// Targets approximately 128-bit post-quantum security level. +pub const MlKem768X25519 = HybridKem(.{ + .name = "MLKEM768-X25519", + .label = "\\.//^\\", + .PqKem = ml_kem.MLKem768, + .Group = X25519Group, + .pq_nseed = 64, + .Nseed = 32, + .Nek = 1216, + .Ndk = 32, + .Nct = 1120, + .Nss = 32, +}); + +/// ML-KEM-768 combined with NIST P-256. +/// Targets approximately 128-bit post-quantum security level. +pub const MlKem768P256 = HybridKem(.{ + .name = "MLKEM768-P256", + .label = "MLKEM768-P256", + .PqKem = ml_kem.MLKem768, + .Group = P256Group, + .pq_nseed = 64, + .Nseed = 32, + .Nek = 1249, + .Ndk = 32, + .Nct = 1153, + .Nss = 32, +}); + +/// ML-KEM-1024 combined with NIST P-384. +/// Targets approximately 192-bit post-quantum security level. +pub const MlKem1024P384 = HybridKem(.{ + .name = "MLKEM1024-P384", + .label = "MLKEM1024-P384", + .PqKem = ml_kem.MLKem1024, + .Group = P384Group, + .pq_nseed = 64, + .Nseed = 32, + .Nek = 1665, + .Ndk = 32, + .Nct = 1665, + .Nss = 32, +}); + +/// Configuration parameters for a hybrid KEM. +pub const Params = struct { + /// Human-readable name of the hybrid KEM (e.g., "MLKEM768-X25519"). + name: []const u8, + /// Domain separation label used in the combiner function. + label: []const u8, + /// The post-quantum KEM type (e.g., ml_kem.MLKem768). + PqKem: type, + /// The traditional elliptic curve group type. + Group: type, + /// Seed length in bytes for the post-quantum KEM key generation. + pq_nseed: usize, + /// Seed length in bytes for the hybrid KEM (decapsulation key size). + Nseed: usize, + /// Encapsulation key (public key) length in bytes. + Nek: usize, + /// Decapsulation key (secret key) length in bytes. + Ndk: usize, + /// Ciphertext length in bytes. + Nct: usize, + /// Shared secret length in bytes. + Nss: usize, + /// Extendable output function for seed expansion (default: SHAKE256). + Xof: type = sha3.Shake256, +}; + +/// Constructs a hybrid KEM type from the given parameters. +/// +/// A hybrid KEM combines a post-quantum KEM with a traditional elliptic curve +/// Diffie-Hellman key exchange. The shared secrets from both components are +/// combined using the C2PRI combiner construction with SHA3-256. +/// +/// The resulting type provides: +/// - `PublicKey`: The hybrid encapsulation (public) key +/// - `SecretKey`: The hybrid decapsulation (secret) key +/// - `KeyPair`: A public/secret key pair +/// - `EncapsulatedSecret`: A shared secret with its ciphertext +pub fn HybridKem(comptime params: Params) type { + return struct { + const is_nist_curve = params.Group == P256Group or params.Group == P384Group; + + fn expandRandomnessSeed(seed: [32]u8) ![params.Group.seed_length]u8 { + if (!is_nist_curve) return seed; + var xof = params.Xof.init(.{}); + xof.update(&seed); + var expanded: [params.Group.seed_length]u8 = undefined; + xof.squeeze(&expanded); + return expanded; + } + + fn expandDecapsKeyG(seed: [params.Nseed]u8) !struct { + ek_pq: params.PqKem.PublicKey, + ek_t: [params.Group.element_length]u8, + dk_pq: params.PqKem.SecretKey, + dk_t: [params.Group.scalar_length]u8, + } { + var xof = params.Xof.init(.{}); + xof.update(&seed); + var seeds: [params.pq_nseed + params.Group.seed_length]u8 = undefined; + xof.squeeze(&seeds); + + const kp_pq = try params.PqKem.KeyPair.generateDeterministic(seeds[0..params.pq_nseed].*); + const dk_t = try params.Group.randomScalar(seeds[params.pq_nseed..]); + const ek_t_point = try params.Group.mulBase(dk_t); + + return .{ + .ek_pq = kp_pq.public_key, + .ek_t = if (is_nist_curve) params.Group.encodePoint(ek_t_point) else ek_t_point, + .dk_pq = kp_pq.secret_key, + .dk_t = dk_t, + }; + } + + fn c2priCombiner(ss_pq: [32]u8, ss_t: [params.Group.scalar_length]u8, ct_t: []const u8, ek_t: []const u8) [params.Nss]u8 { + var hasher = sha3.Sha3_256.init(.{}); + hasher.update(&ss_pq); + hasher.update(&ss_t); + hasher.update(ct_t); + hasher.update(ek_t); + hasher.update(params.label); + var output: [params.Nss]u8 = undefined; + hasher.final(&output); + return output; + } + + /// A hybrid KEM public key (encapsulation key). + /// + /// The public key is the concatenation of the post-quantum KEM public key + /// and the traditional elliptic curve public key. + pub const PublicKey = struct { + bytes: [params.Nek]u8, + + /// Size of a serialized representation of the key, in bytes. + pub const encoded_length = params.Nek; + + /// Serializes the key into a byte array. + pub fn toBytes(self: PublicKey) [encoded_length]u8 { + return self.bytes; + } + + /// Deserializes the key from a byte array. + pub fn fromBytes(buf: *const [encoded_length]u8) PublicKey { + return .{ .bytes = buf.* }; + } + + /// Generates a shared secret and encapsulates it for the public key. + /// If `seed` is `null`, uses random bytes from `std.crypto.random`. + /// If `seed` is set, encapsulation is deterministic (for testing only). + pub fn encaps(self: PublicKey, seed: ?[]const u8) !EncapsulatedSecret { + const pq_nek = params.PqKem.PublicKey.encoded_length; + const ek_pq = try params.PqKem.PublicKey.fromBytes(self.bytes[0..pq_nek]); + const ek_t = self.bytes[pq_nek..][0..params.Group.element_length]; + + var seed_pq: [32]u8 = undefined; + var seed_t_expanded: [params.Group.seed_length]u8 = undefined; + + if (seed) |r| { + if (r.len < 32) return error.InsufficientRandomness; + seed_pq = r[0..32].*; + + const t_randomness = r[32..]; + if (t_randomness.len < params.Group.seed_length) { + // Provided randomness is shorter than seed_length, use it directly + // (test vectors provide just enough for randomScalar) + @memcpy(seed_t_expanded[0..t_randomness.len], t_randomness); + // Pad the rest with zeros if needed (shouldn't be used by randomScalar) + if (t_randomness.len < params.Group.seed_length) { + @memset(seed_t_expanded[t_randomness.len..], 0); + } + } else { + // Full randomness provided + @memcpy(&seed_t_expanded, t_randomness[0..params.Group.seed_length]); + } + } else { + crypto.random.bytes(&seed_pq); + var seed_t: [32]u8 = undefined; + crypto.random.bytes(&seed_t); + seed_t_expanded = try expandRandomnessSeed(seed_t); + } + + const pq_encap = ek_pq.encaps(seed_pq); + const sk_e = try params.Group.randomScalar(&seed_t_expanded); + const ct_t_point = try params.Group.mulBase(sk_e); + const ct_t = if (is_nist_curve) params.Group.encodePoint(ct_t_point) else ct_t_point; + + const ek_t_point = if (is_nist_curve) try params.Group.decodePoint(ek_t) else ek_t.*; + const ss_t = params.Group.elementToSharedSecret(try params.Group.mul(ek_t_point, sk_e)); + + var ct_h: [params.Nct]u8 = undefined; + @memcpy(ct_h[0..pq_encap.ciphertext.len], &pq_encap.ciphertext); + @memcpy(ct_h[pq_encap.ciphertext.len..], &ct_t); + + return .{ + .shared_secret = c2priCombiner(pq_encap.shared_secret, ss_t, &ct_t, ek_t), + .ciphertext = ct_h, + }; + } + }; + + /// A hybrid KEM secret key (decapsulation key). + /// + /// The secret key is stored as a seed from which the actual key material + /// is derived on demand. This is more compact than storing expanded keys. + pub const SecretKey = struct { + seed: [params.Nseed]u8, + + /// Size of a serialized representation of the key, in bytes. + pub const encoded_length = params.Ndk; + + /// Serializes the key into a byte array. + pub fn toBytes(self: SecretKey) [encoded_length]u8 { + return self.seed; + } + + /// Deserializes the key from a byte array. + pub fn fromBytes(buf: *const [encoded_length]u8) SecretKey { + return .{ .seed = buf.* }; + } + + /// Decapsulates the shared secret from the ciphertext using the secret key. + pub fn decaps(self: SecretKey, ct: *const [params.Nct]u8) ![params.Nss]u8 { + const expanded = try expandDecapsKeyG(self.seed); + const pq_ct_len = params.PqKem.ciphertext_length; + const ct_t = ct[pq_ct_len..][0..params.Group.element_length]; + + const ss_pq = try expanded.dk_pq.decaps(ct[0..pq_ct_len]); + const ct_t_point = if (is_nist_curve) try params.Group.decodePoint(ct_t) else ct_t.*; + const ss_t = params.Group.elementToSharedSecret(try params.Group.mul(ct_t_point, expanded.dk_t)); + + return c2priCombiner(ss_pq, ss_t, ct_t, &expanded.ek_t); + } + }; + + /// A hybrid KEM key pair. + pub const KeyPair = struct { + public_key: PublicKey, + secret_key: SecretKey, + + /// Deterministically derives a key pair from a cryptographically secure seed. + /// + /// Except in tests, applications should generally call `generate()` instead. + pub fn generateDeterministic(seed: [params.Nseed]u8) !KeyPair { + const expanded = try expandDecapsKeyG(seed); + var ek_bytes: [params.Nek]u8 = undefined; + const pq_ek = expanded.ek_pq.toBytes(); + @memcpy(ek_bytes[0..pq_ek.len], &pq_ek); + @memcpy(ek_bytes[pq_ek.len..], &expanded.ek_t); + return .{ .public_key = .{ .bytes = ek_bytes }, .secret_key = .{ .seed = seed } }; + } + + /// Generates a new random key pair. + pub fn generate() !KeyPair { + var seed: [params.Nseed]u8 = undefined; + crypto.random.bytes(&seed); + return generateDeterministic(seed); + } + }; + + /// An encapsulated shared secret with its ciphertext. + pub const EncapsulatedSecret = struct { + /// Length in bytes of the shared secret. + pub const shared_length = params.Nss; + /// Length in bytes of the ciphertext. + pub const ciphertext_length = params.Nct; + + /// The shared secret (output of the combiner function). + shared_secret: [shared_length]u8, + /// The ciphertext to be transmitted to the decapsulating party. + ciphertext: [ciphertext_length]u8, + }; + }; +} + +fn NistCurveGroup(comptime Curve: type) type { + return struct { + pub const scalar_length = Curve.scalar.encoded_length; + pub const seed_length = scalar_length * 4; + pub const element_length = scalar_length + scalar_length + 1; + + pub fn randomScalar(seed: []const u8) ![scalar_length]u8 { + var offset: usize = 0; + while (offset + scalar_length <= seed.len) : (offset += scalar_length) { + const bytes = seed[offset..][0..scalar_length].*; + Curve.scalar.rejectNonCanonical(bytes, .big) catch continue; + return bytes; + } + return error.RejectionSamplingFailed; + } + + pub fn mul(p: Curve, scalar: [scalar_length]u8) !Curve { + return p.mul(scalar, .big); + } + + pub fn mulBase(scalar: [scalar_length]u8) !Curve { + return Curve.basePoint.mul(scalar, .big); + } + + pub fn elementToSharedSecret(p: Curve) [scalar_length]u8 { + const affine = p.affineCoordinates(); + return affine.x.toBytes(.big); + } + + pub fn encodePoint(p: Curve) [element_length]u8 { + return p.toUncompressedSec1(); + } + + pub fn decodePoint(bytes: *const [element_length]u8) !Curve { + return Curve.fromSec1(bytes); + } + }; +} + +const P256Group = NistCurveGroup(P256); +const P384Group = NistCurveGroup(P384); + +const X25519Group = struct { + pub const seed_length = 32; + pub const element_length = 32; + pub const scalar_length = 32; + + pub fn randomScalar(seed: []const u8) ![scalar_length]u8 { + if (seed.len < scalar_length) return error.InsufficientSeed; + return seed[0..scalar_length].*; + } + + pub fn mulBase(scalar: [scalar_length]u8) ![element_length]u8 { + return X25519.recoverPublicKey(scalar); + } + + pub fn mul(point: [element_length]u8, scalar: [scalar_length]u8) ![scalar_length]u8 { + return X25519.scalarmult(scalar, point); + } + + pub fn elementToSharedSecret(ss: [scalar_length]u8) [scalar_length]u8 { + return ss; + } + + pub fn encodePoint(p: [element_length]u8) [element_length]u8 { + return p; + } + + pub fn decodePoint(bytes: *const [element_length]u8) [element_length]u8 { + return bytes.*; + } +}; + +const testing = std.testing; + +test "MLKEM768-X25519 basic round trip" { + var seed: [32]u8 = undefined; + @memset(&seed, 0x42); + + const kp = try MlKem768X25519.KeyPair.generateDeterministic(seed); + + var enc_seed: [64]u8 = undefined; + @memset(&enc_seed, 0x43); + + const encap_result = try kp.public_key.encaps(&enc_seed); + const ss_decap = try kp.secret_key.decaps(&encap_result.ciphertext); + + try testing.expectEqualSlices(u8, &encap_result.shared_secret, &ss_decap); +} + +test "MLKEM768-X25519 test vector 0" { + var seed: [32]u8 = undefined; + var expected_ek: [1216]u8 = undefined; + var randomness: [64]u8 = undefined; + var expected_ct: [1120]u8 = undefined; + var expected_ss: [32]u8 = undefined; + + _ = try fmt.hexToBytes(&seed, "0000000000000000000000000000000000000000000000000000000000000000"); + _ = try fmt.hexToBytes(&expected_ek, "3d209f716752f6408e7f89bceef97ac388530045377927644ef046c0a7cae978c8841a0133aac4f1e1a7027277f671219cf58b85d29c8fec08edd432e787a3cf9936fe0026a113cb9efb1d7214049527bfe2141ea170b0294a59403ab0ce16760a8baa95b823cbb8aacdcc17ef32775223c791e3740163941f9bb3f63346bef1c050c31f932c62719429aff14c2bd438ab135bed692d56c77c04cbbffd6335b578318b513771e84b14ea821262141ca006ccb8bf2500aa1008970f216fe7f1ae34125aa290492c069a189222adc322f97649c762c7d3128ad3bb2667971d0744014bc3b67445cbcd0b3e7ea69fb1cb9f9c331f97487920187292926d04a25a2650abbd44982bb0c3c6301fe6a61330d24d8a3c7021dc3e3392c79a139b37613bba67a2984298507b84a4d61eef18acfb979af2d39caa4c0db4513815359d76fc378c63a7f4f3053b17168d0221cf0c2eec5514ba235f81d04d67c3b5c518094917671c26a7c046457533cc32844581277a03eb065c4529a779a9a5878f2aac3f81db9ed3d8c9345697058cbb99d379bca16d8fdb61d129960390524791b9d3e501b900bd1e5002e095be06c23f1fb212f5801f24b6b28c0c5493d246d02aa29fa3acfbe15ac4e212eb0b6f69ebbea259a2703aa4c308224bdb741c65c7a5d4bff788279507bbfe513d7aa5694e7b3cdf62ab36432742d4a0ca9b3570ba742fa803b46989c8526ea586cc4fc32866143b79601725fa545fd280b404530318bbc3371194710b6d74beaa629eb18a36a953b75915ae96999ba5c88cdc56a46861c50032c9b630bcc1445a30878979bc55a2c0955bf399b231203b90c651b6afe0e242b5a543250b142f7291ed753d816098f7913302a8ce91641716623d4fc2ac6772aa5f3674042b7c4a18a2186289a4ac4e200774596ca03e6798c7506b984999db6ac142586bae0799f1e776f9f5247dc574d8556ddf9bbbc4ca3643263457f74248010d62d4311268360aecb4902b450bf2050ecb8ba7a92820d233f5a14ed31225a1d17ca6f19e825894cfb1807d922cbd60761134be419144bcf72006366a4460137ad9136c113f05eb54c409520edc72e4150cc3a24b0f819eec11bbd19ca9645b0810a60b4a8a9e9c3955396a1653955b047bcf4f98433c27236c570d75f809e44aaf2dc33665826351872c293350ab324518c8c0c80b521c80c81a56bdc968a5650315a830c8bb17532c62ccc23b1d46412c256b224fd4674491803501d0143125c7577239689965b6989ca561793c0f85c62a9e13487da17662a7188c70b1040a67ed4c3f85e74e3691822fb96314d6134fe6a626b3cbe1461d62a7b573b2cc75579ffa22967e36ceb2a1aa0b71875a22751d706b72ca9ecd0c8100ad0aa58009a5c83fffe91759e6baa0a9345af99fe3b69509dbc84032868844ab3f65bb1df8beadf36442e48e339c967023a525411544c789a2f04dacd06ffef78302210450b931f6b4c32aab34a3f5260b810f4c9a946fc22d3baabaa80ba8d9955d6dc35e8609b4256b482cdc9d8977c1a47a354e7c527fdb1672e166917b95cd6351820261daab361f8a2dcbb240c55abd6a8105e5291b427b566d731e6b7047189cff20d8b120e0b3e72472d1b0086812200fd3698e23f06e4f4e08bbb54cc2f63601b7f85accfeea2d17964c66b5194b0f08e18519faaee194e3c102823062"); + _ = try fmt.hexToBytes(&randomness, "64646464646464646464646464646464646464646464646464646464646464646464646464646464646464646464646464646464646464646464646464646464"); + _ = try fmt.hexToBytes(&expected_ct, "d81018a94f8078e02105beaa814e003390befa4589bb614f77397af42d8e8150796f2c88a4efca81b8cf93c0ae3716c54ec1b045e3875f38c2dd12d7f717bd7fb701a9fecda5ed8b764c9a35d4a5c1d8930f6071f653eebb2d1afa77debb8302d16f17e0f5f3920a71a4d49beafa0e1c7e443f8abca64a65a9e81a97e7357bf902573363c0e1a12e5228036828e3f759121fada92441fe334e85d79347e470d2fed945541d832c54baaa3cb7526c3853954db4f73547cc7c27fd38398bfa7704952cb841e38b270e4db7435f0ee22f57d7ad3270bd0c88e71b4b864cf2277c65daa10a6dad4c7abecd95cc4ebec39c08404b522e4ecc1545713f76bebd3b5a0f2feb3461936065dbd13f6a1f61e1b142a2af2e5a482ba2c50cf0317049c0b3bfd6d5e9240eba9111d2030fdea17e33b6524020d30b0c4f8069285f3a6ca267d287d01e827d8422bf5426e11688bfc73756af1841b1c87e126cb50c914b5b2b8673488ad3b074cad77a3840eb12dd688f313ee1e9ff8c479a678f276356fc9d65e1d5b4c1e9855b4175db144f7767c12061769190fe6b5e51563b91f94d131a2b796bd2980ed0dab4ae7a7110e920007a757158a5eb8662cbf89ddffe9d8196821313cdc00108853fc4746b111d5b56da638d8ed2973918960f5dfe93ead3ae521e957cec3c8d843e8fce234c70ad055177f235439d6098bdd771b1cfcfadaab4f50a7378185c62409f383c8ff658c2a2af66498cfd81e962766ac6b774e88424fb4f331837d0a28502708477caf8780a156d723f68fca791e1cd2397bfc2b24c77c765d9b2af36f732d52107517efd8157b283b440a613f756c364ca108971a8878199a93f260baec3e850033cc032c2e53f823576affb4d3b116e2d16049152c35aaa263ab376f0ad5ede6a749607a283e3016e62191c0e8fde33e718cd989591c9a205d608d99fcb8a7471603d716cb01b56328d7d880aec2851f4e6d8b5016c25647e9026ebb441543e8012dbfcf078d4012b8c39184dd64f3821b4774ae4e36365f8baf2bd1f6667c017a1e65ff8a1554458fb3f367c02721752bfa56fc7fd566ae95ffb208f919ef12f4cf8a2fdd141a8df559bddb7b8d1f04ee6d4cf7805d142989caf216dfae985faaab9974f6d9f8aa1129084db8db912b1655f595ffbaa66491ab4655fd734cfd4bb0c0289d4bcc8fc5e9943b351cb147c8db059a24004d1c3e3bb4c14a881e5101acb736c65c5d579acb67ee85a560277b43338fe79d34b772c5da001da3b5a3383dd81319a0b4542e6d7e46eed5314cc70eb231de27b6e760db598ba19995cf69be0e4458e35f3f274aca2455d43fe3344e183c6dc47c857dbe9907b41e41006d91b25adcafc098fe66f7554be8dad493c4f4b1dbf7a51464139db474afab5572f92a2232b59be56a72c0505149dae5cde1e602877037de7802b5f6fa47a4c9a3e52d6ca15339920254e9ffb53c7b834cc0288ed9905a1841e9390ea94a8898bd4c6b6d6027e4d43c7867242515bbeefe12340fc6b3d57762f8badb69433f9c6d060f85f5e5c6b6803a816d141c075f63541ad10"); + _ = try fmt.hexToBytes(&expected_ss, "e5ba94031ea6efd69c09c254f6d9783136ba6037e2d4c43bcccf19d6f3f4343a"); + + const kp = try MlKem768X25519.KeyPair.generateDeterministic(seed); + try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes()); + + const enc_result = try kp.public_key.encaps(&randomness); + try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext); + try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret); + + const dec_ss = try kp.secret_key.decaps(&enc_result.ciphertext); + try testing.expectEqualSlices(u8, &expected_ss, &dec_ss); +} + +test "MLKEM768-X25519 test vector 1" { + var seed: [32]u8 = undefined; + var expected_ek: [1216]u8 = undefined; + var randomness: [64]u8 = undefined; + var expected_ct: [1120]u8 = undefined; + var expected_ss: [32]u8 = undefined; + + _ = try fmt.hexToBytes(&seed, "0101010101010101010101010101010101010101010101010101010101010101"); + _ = try fmt.hexToBytes(&expected_ek, "ec7b50cddc8360f98b189bac73d395ef947b37d8453886a253269f7b18b9eb78c1b63212471a0f979793f9936b3f496f4b5394ea69c2a35729f91c688f6bbbb864cd5e87108676c4014c2ba98204f911becae33a71e832ac012bb827578810955f8c6e2d26c0b17b7ba574990884546ba58bf6785721f3854f434cfea602e8595c71642e8d4c70934b7e54c638f5a13e1a136bc86565e6b40abc163ca65650baf953de7bb99b138ac1b695023103c9b417853c9d42e54fdb816174659d85a783e3d4613db1cbbaa63fb667a4a636804b6c4ae821ac5d6556688bab1dc10d6779b485c63c0ddacb91837c4ff3402e6214188072b4186a39c65bde524c683c95d3c8b65e37104f551b6a3602eda50b787182d703ac6a221428b4553e3b99c2b251ef642e31256c329b21d1246a71456fce700d7f50cfe5390a1c37bc133809f102c22914a1402c205c0512b733afeea04411ca5ebb0bca9392b1ee23935eb196024732daa2a1f79358e6e74b73c965a9e74778dc6921442b19328f6216a5e814ccc0639a863a437a614def5a61f38852151011b04a37bbc78c1eba4d8d1b3a1622a0dff74d25c731abb2a5fe5919f835bd3dd97330cbb7dba0b74260c963402160c4017d92256a3713c9e77ea0f4901accbd38715511784c9ec287dd85a769e081854b32aba9322a3840f6065133228c41851afcb40ea509cfbb86145fb8853ce14c649691136b8660b0077f3b2f9da82d483c1414c39a9777665899131a8336fb828480986df102628d10b54239cc20231457d4bbb7016f76029661f14ffd3532e2f8494e1613430730ab915683c3c8c4db2b4373a3057a097e23333605398b15cc4d6ac3fbd0732f21026bb0cd51fb738a740467114e7c66256b830022f28c028392cff8013d617c77a47bbda11c4a522f8f2b49f2822cc06338605671fca4518df9b3c506532c9cca3175330f8733ce11cb3fd8b95239ceebc9483cb68bff43b622911fcf4a9c57c226caa38bf0b081535999f573016b14563ec4826dc281dbabc633868a1d903d59207fc662a293735085c01f40b5b56cbb795ecabfad709d611cac73eaca579768213c18c969c59be58fcef6bdd8a85192907cd0773f81eaa24be07e0d620e9685acb0c6b0f54b47dffb510384241c4b733fe08dacb2852b2b74cc014e974a5e9db35d80d7b83ad31da1487a0170ba7fbc1c551a6f1eecb572084180b256962748d5e3200b731ac7c3928585a153b167c92a48cd91668c773707c054af16aa7bfacaa161a620600e8d08cc97601a53391da0247e5fca60cd1bb65ec0417177a9eb78cde5aa1dfae34e948417b3cc0b223803f5f40e8ae3a382848ff80c4185824076423ae4c137bd30bd81f04095c20a01e0a49f664f8f2b7f6bf6a990993cbc0596a514ccebc578c6418e825903ae11ac52831c6a48c67727409ed7274eea03eef32094271b02d4535563aa4924a2666871a4b690540c78b06043bca31ca4e42a03650ecab74792017217d10615d0acdee124e3222c90d79362207e4f7779e097501cf140b1a3431ee0cf27b23a50373d59976d82b5b1ce165f4aa1361157afad564081c85777584dd6058a1a4663b53234d7264fbac6877351d1928c6780f77d47209337271e305370df9aeffb74d7c75de55c006e2b2a979aaa76aaed9e76fa61e2a0a9aff50c054b3f819ee2da1cc9134008b9f5ec05"); + _ = try fmt.hexToBytes(&randomness, "65656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565"); + _ = try fmt.hexToBytes(&expected_ct, "600ecf4026683898d0e339eeea9ebd437a4a802952bf32bfa326b48eb74946d0cdd5437e70df4b6b7acbf79efe60ddcde985acfd8c2d23775e1ecc54eb6ee03dcc9b4aac150172737831adfaf0e63a4782bad2a785b9c39bf5e34640ea3da447efc2a03e23a337ad1f542c32c2eb46f7b88d0bfba87d8efe8cd4456e6f21beebcf3dd502b53d537395750ce963d289eff74621545d4a5d9262bd14b3dddd4ef880e65cbe8f2f8aad826f57f727a60aaaeee8c69e89af6c539fd4f267c44bee8b5385a460a7c8e4a809959df86c1136ee23e7544cfa7524c6c04ed9ec29aed307b5dfd0108f29294aceeb517a098b8cdbad5911bc75e96258bbf38a20288c6911b2346f842c0943bf8e9c34a0a8e518e92c8761c6efe6b1d3ea8aed9b2c4feacfbf3559f3a5e46e4dfddf81936183d4d1f9c8c1616b14db2057435ad71655d743fad4987a19e821d0ac666ff3e46b7cc0e90b85e1966962279a48afe2e9bbce89c819a8ba52476af074a071495398bd497f4d4f34026025452975cfdaa3e7e183a962bda009108221ecb20d218c42e38774019d2dc32621278b5e88f99b62a9e746d16c1691ccf3e7e9185c3c493e7617f451f632c161fbe6d8ac3217f10ed4bfeee47e4960ec4a53e4852ca0241543848422044a67567a83e09d8e74b9d11af17d53c49565ca53deda7c4df076a3e1b6368b1931d81db93e87f75bea6924a321376fe73b5a5b07b80a98dc3ab8d14732540f1b4b7176e274a905d453eac1caafe2bfe4e6c904556ca91b01b2302215ab3dfe6b49f46963df632a9e7cb8439cd5ee56a1f8e2cd3faae5d8f3462d0ff931f5038cfa70259d963163d6163ef22b0c32081bff2763e98da87817048d4ce755e5d2b1cfe7d6eeab0fdbe766c95f125537a04bcf99026f9bd5be3b26b9b7614f132f6747dd6d96009a85ae6cbb1a14b9231099b67b04d7849875b6492f3b6482f8bdac305f7ec29f28ef4739934c6a7a2800fbdbff6eb2237d6a085ddfab8519db1d2b1e63aa6cb9b3b044278947dc3bcd329aa427d13267f93a6cf2aee8a2ab74d4288fe0b676ea85586834ab57e863d4805703eb8bf6e71fbb11a386e7b64a0d661dbd05f5e2924f1419bc799a089d44dda9066c6c503f8c80be8daa99bd48338daeb4911acf19328103c96f40a77ffd827d52294ba21ad1d52fc27e8b12ad65887024f41e63fcfe654152676ac363f2377c5b0b437e075897e33dd8d57227fc5a536629efad998a279103150e7d47e4b7d11a0d649d146c6560c48c9c0c56c811cfa6f3f62cf717ae571597bc297deed887672d8a8cec2929c2b55b95f26bd5d10ef30c0c4a6295d5ca601538f5a20ac1064d2f4c2a078af1b1629a0c203ad047125eb9dce0d1260eef19cd4ad8ec5d73a01ba23e266cc6dd266c5a81af58ccf1b5ce0440efdd1fe7bb42177679b5e5095ffa0d453bc17b8921008531c2d096a3a4a48563370462a59fe1faff2a81603f2d09ca2e0beac44204a4e03aa852745b1747bb6c424206ae093ecb917897fb41b1fcf75c1227fa08876056a5ed07ac44fe0c116ec2de80b8e523959d7824"); + _ = try fmt.hexToBytes(&expected_ss, "750300db25bff9620e893c2c6fcab9bf04d7f2e543b5b39420485626fa274908"); + + const kp = try MlKem768X25519.KeyPair.generateDeterministic(seed); + try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes()); + + const enc_result = try kp.public_key.encaps(&randomness); + try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext); + try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret); + + const dec_ss = try kp.secret_key.decaps(&enc_result.ciphertext); + try testing.expectEqualSlices(u8, &expected_ss, &dec_ss); +} + +test "MLKEM768-X25519 test vector 2" { + var seed: [32]u8 = undefined; + var expected_ek: [1216]u8 = undefined; + var randomness: [64]u8 = undefined; + var expected_ct: [1120]u8 = undefined; + var expected_ss: [32]u8 = undefined; + + _ = try fmt.hexToBytes(&seed, "0202020202020202020202020202020202020202020202020202020202020202"); + _ = try fmt.hexToBytes(&expected_ek, "08118d8819772292c976ec971ee3039195800c823544484595cc63450b9db9414330419208c509cb62626067a8fd8259160105b6d8a4023b056f5ac2d6159fa5f245f00c719539a4601466f6b45b2a68bdb0db7424d4cad475a8d68b0d6e086c3f012414e22900f01179e8c90a8ba1d285cdcc7c7ab1c7064e2c15233acee183a1075c04f092a5e3676b1ec06d15d348e7781346ec95806a5e00f64d1e101bfb28bbc6829372f32bedcc0de9a70a02e508b760422505481709bef10697fbfa219b99a815f47e4bacab0e789ca1e414db529deb043bd8521c2d456a062a65aeba2a40dc6b9ce02474a71fdf852f343b22d2110519955b964382e74a3cf78586eaba353b98228b0268f8480b02c4b4ee208198fbc472117c4be567858c097fb0869a40588418973c58753da845e36c0adf39c53ea9c8ae24c343bc87bcc3be69ea40d9490592ec99e9a853f4f22b024a1b15962b049a62bc198706e310c8524c51872718d76c6db25cd824a1b75a94a7d341ca40be1283b2111b687be69d38b7297a90384b0c0269ac911cd80384b326357262ac13680dca37ee18477ab23e16448805676adbe1a5b7732e73c0abc3c5188e6c7ada3c1f1f124663e83ad5899674253e9bb15f39908ba4917dc11025f7504425a305848661c38cb09a82026d20c9bd23949f285519db298418718023c8d7e37a5bb1062951b4325249aad59acc06b10161416ccc78db6c6c3f685ad3d9b4916c622163017f9662da4234bab8b8b1e77290867d48c28a2cb33c7d2c7874c2be936ca0d6ba907d6a823fa24a18c56cc124209ea488ce620c18d00ffc8b8f0c11cc5850c30b3a0fb7faaf6e526f9b08972207a38f760bad1824726017a30634fd239ebda59651b4c8162346bb3652dec39f56626547829ffbb052a5a6930f2700fad33fb1eca8bbc40fcfe778189398b5527a09a24a53a958e5c25353951b78d85916457c1c5046e497ae0fa24810e82d360050e4fa1bf55b719ab8a080c23dcd80c0d4915d8458652d476f50f3a5b80ba6ffa9a76bd9524dbb39df7826cc507a9d31aa29b6207eaa52b3e224259c4931b1ced9b97a42e6745752ac0603917a694d7ec95145094ed089008af675fe51b2b79970abc282dcb632c1fc3dab85ad14893d0ab63b9e21a368845e872bb1468aa252554f59f90c9675cd044b930e37a96a213a28277614443cca4317e4a2af9f4c7124fa76e1d48f38981d7df03d2bf840610861b210300156c3f999aaac1b2983c45cf0002c922037bb4055dd1c27df511ced577bb8046e003507aa7b2c52194680b93e2eb40719539b8a93b8e83b9e205714927264cbf0653d4429f504816766bf97f14141622e91b20177d98b8db9351b39632be41a48f39ee050cc1919143a2448d09c2fb15a680ebc07963b788480631eca37e19213dbb6c5e8dac3eee81b0292cbc681563d05779b79b5d8ec37b331b3c021719cb95e69ec99a0f97b723303278b9403fbaa7f71ba70d61d082c91a6aa2c8a3543b5281e1605832c740bf67036f2373571f09482b27272c8ac2c69b01f715dda83377aa093a044541f6000848ab1ea65cab1345135a4552be42b27c4b980065694134a90d436f0e091b02a02aa99eac7339907afbbc158a5127540423f23f6927eff66915d745f4d42825a57744a69ae7c493df9ed49f2f7eb1d2a9b72432b61352a9a953730c6295c"); + _ = try fmt.hexToBytes(&randomness, "66666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666"); + _ = try fmt.hexToBytes(&expected_ct, "413c55d5710bae6376761dada807daffd4dc45f9f70d825e0d46176d4a342f58f20d61879215bbe4a774588838175342628a905da0dbaa1346e8e913f4738defa0768445f1c625d296ab06cd547b93e764a388e63815b588059796e9bf3fbead072727703b036aa73223007ad1caaaea0c6cc38d385beb06fe8d372e9145c08e1bc3cb5ccb12f450ab0f6f9da5529629a3f1ff6312346b6d2fcb20461e7b3b245a97a03ef27f2e5442daf2a5ea317f454528e9749f06342aa7594ea9bda0cdcc7c0953c36372359ffd69f2aedc1adabf8a3540e32ab36ebc1350aded1072afe3b78a6ec2f943d560f4849d6bb1ee24679e8f70cc0f4cabe7d4cbc6e090353ce8414a93de9c84a32e197a2ac95e9fbc5d616f85fe199e80793f6dccac203d2f236e7bad1a4e7ff51b3f3326a9742826ac6a23ef5a945aaffb54faf50a8f0b8c09c55cf2bc812e30fb3e687eca91b494785f121241a1ea8d0cea089216c5a96a467c06d4f0a10c2a6bf551637f0fd5635dc1734e96eca5e7c545d66435b8b5dd88eff4c2cb3c73c49dfc9e56c293febef797a7d36d21ba30361f7fec7b0e51793f6fdc2214f420b713a1598f4dda1a29f9124469407e5c5c5c908e39a78ea0fcfe4df3419692435a92e0f9a5846690706cdd23b1825be8d0a843756fd97b4f277cf0714a0d9da3ccf1a31a07178399b803c7b4837980bc0172f58716b3baee5e86441d32bf31c7ed6e9c6d55eb1ed528a4a306dec7f37b3a575086385a9f4641ef28da16d35578c743c8eccb0581b2fd308a3c9fa15c8319954c8f4259ab09f178508720ebb8a0d893a8c45ec23b2c1c2e43db439ff71fea6a9fdcd8a9d3c6e0f8b9e9e71ddc2aa52fc5cbf22ed67217d847e4c84b72e7f201aca56c7d1d5e51e0c03cb596a01d20203b38e0e7d3086c83a4a1930754134904487c43fb96deb449aa832e63a82d132660cb7976d9d50742641c28c8e2e1bb00a2c65e9f8b9591501ad60568af112a5cbab134bb472fecbdf24badbc6562201e022c23fbc6354292ab743a863a139dd4d67b1bdb553b3c57a5c7f5b98cf145ac142e1ad6ad5ea3954fa3c2b8ebfb6cd05b915dd1d87262d7ab1f1b47cc0a3babc15a7a1415976644c54e29338d79afa9d12a669d3c67bf70e604157815f041556a5cc1c8429880a5449d033bb3f1f2b879f0e689fc2a3e2972f75f6f25b95bead0460f35ef71d0bdba380efbabed6365c6e7fcf2e22361b572029f0c90f2f74c8e40c7941ed8b6eef5a722bf2e5141cf43ed2a69b87901d546a85765fc494531e61f3d723107659b4ce1f294c352fc45c28a82cb3c242e5d6b9cf43d071bd55b8bc0d47b225463a5075639569cb073ffc4e07417dbc5a30a8e30545264d64d98d13336fdb6bdf8c71041e995cd433a77a9d4ee25e20f757cf76dd702f7c8f22a2677f03dcba47ea1996b9d783e44737ec501a8c75acb6d7606a2b6eb1e069576f3a87b32e587923fb79171c77083bb629efda6b9ddc1d566d72a53161c165d0ccea7674e5b1af42b219e4d800da968d2a5fcb009c784f4746c7138edb9ee4844b739e830b05cf424"); + _ = try fmt.hexToBytes(&expected_ss, "87292f18b2e7af74bb8839ddee15e832d2f4bfac14dc84f824906d951436aafa"); + + const kp = try MlKem768X25519.KeyPair.generateDeterministic(seed); + try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes()); + + const enc_result = try kp.public_key.encaps(&randomness); + try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext); + try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret); + + const dec_ss = try kp.secret_key.decaps(&enc_result.ciphertext); + try testing.expectEqualSlices(u8, &expected_ss, &dec_ss); +} + +test "MLKEM768-X25519 test vector 3" { + var seed: [32]u8 = undefined; + var expected_ek: [1216]u8 = undefined; + var randomness: [64]u8 = undefined; + var expected_ct: [1120]u8 = undefined; + var expected_ss: [32]u8 = undefined; + + _ = try fmt.hexToBytes(&seed, "0303030303030303030303030303030303030303030303030303030303030303"); + _ = try fmt.hexToBytes(&expected_ek, "04ccc68ebb7b60c6148fa94574ebaedb9c5ce5eb1ad69cc5a4d7bbf9a410295540bba271ae8a178bd025ea28887ca8808aba8fac7a44260c6e39b883110155d0dcb82ae7609bc803a8f927dcc2c2033cb07ba90fc8b068188a7214e713579537e69801ce304251eabc8b34b03ad71246cc22f7671adf572cd6a2730976953535c59d7c93baa5788854ad52f48e9cdc8accf4a3441392f006378a450289053c2ea689a80699de7acc6bec4c2b182e3194acc9fb1ce3e1b389c5879ce66bc692a3b15343d6e557ff93127ae69007e6913d51ce703898c5449ed360259384819529c7b54b9f5b6c9755d0a627554772846127c8a580a5b6a9ea6695449be144306d64cc8102127278827dc25da96b6f4823ce5ed25a2b199f3902cd8bb82058d4694b40ce0ce1cac1ea8a047a1ced942d78ac2923c9ced1d563fcc24895b1683615b134856ae3f36e4b382bb160171ed400902499c6a0b1b8701d89093d6d72a992c9ad296308a2d44d26816b1c942a3bc479979516a0dcbf16bbca1140554be69885a477d078ac0a267e8294111cbb42bac3cc733269c7f84bf315258cf8cd04774f9ca932c7416785796ecd781e17b6ac32277d6d141906811430da866478499a8c94c521aa4d4663af067fcd2c823252c1dfba800c7b1681955de39112c2fc5f4a14020469bc22278a84494806d43528f8ad8efc88643038d9d71c79a50710dbacff686396c34f9800b0671b0da3c2ad4dbb3751b3b3749b63e132a336f19ae8f37a79f6c713c48b724c80465b28e578658bc8329f0c1d75d0ab1d632027a3cc8d749216f50440109ad7637f3ab1661ee7637eb06e70d4c15db81a2ea25ecae4524476106210362a7b35c013120b51ad9b2c88b452457b75a98fa3ccdcb183aebb1684b72c3fb5434c277907680e8ceca5d2fb88dcb43b050a5557b19d159a6050a6115c1c49232cb5150463e165acd4629ca4927e3ce46ddd1a733dd23f77b44dfb7b46ebe4291594015a4ab5ff00b643321856b3b062ac28f2c4cf7b30716b6c9a53271139685b3d2bc20cf5c766265a6666652393af58ac25efcb502a30b9f6bc2524cc615a911b1de63bf1710f9f829c91a573a960a44d43138a65c42d41000c601c7ad1537633433edb5023387aad5007a09827a55b6a7e7a2911307e0f226e9e52ae3ba4c0f14a1efe9571352278c5a4a08cbba7403c35ec69cc2a2b364ed145b11a4e8594087236a4ba7a2d003aa89fb2c97580c3ce115727ea0e99ea81cc8354164b0602e53cb33638dad5a39632b70f4706eb8bb6d598c6717627fe3c02420583cbdac9460a9d55588ffafba8cf748c16283b0e96b8f95100caa7a8d12029a433ab4efbb9b2067a0c615d5358aceea04b4ca4bf584a452fe2768cb5bab0fb52aec375c3337fae44b305f1389f123103669c7901a1821c4715b5cac1c89389cc9f4df192cca789ed236f7b378fb2915106ea3cc1b90ce9d6c07b36c4f09b5654e3a116c60dda685b9ef6893b1b42348a3ac87284eed683fb346d25a562b90a778e6b546e40b4fd7c1d3c01983f851474c3832297019a808ef64957d7902578981d9231b382169d533a2202e95d18f152e8fc25b615bb8a65482a2167df6ab2811c73bf8fefbec077f1e668494d422443a083b9619254ec3e6bc6f30245fb738564e071c7c638448d7882a566c87e7399522756588c2a86dad8f2021e"); + _ = try fmt.hexToBytes(&randomness, "67676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767"); + _ = try fmt.hexToBytes(&expected_ct, "f40d556d507802ebe7be0e65ab82b0aa9ff26c825d24f98148f75d7e910d6c7f5852e7023a4e246be81b807fea96928da6a8c4d23b8326ea90aa7f1f0673a57277bdd08960f7f886777f1725b882e1996632584fce4356f7345abd8f480514275fbbd96ec6d3202de46e18e7a827d4dc62c1681c36018861c33cb2f4e13ba5da65558441caa6ecf82a5e74842268f4ef5036deb2f0f6f5cb422dc2b723a7f2f69830a52326621ee034c8a9e80f39070456bfdde653314040b4f41590723b66a2d7e41153932521e8e51108d2d98841b2983baa43b0dd6a46783be063850b22a2dd05c1f9f578360dc3dfef4d79e8c20d2d0c45687ae19395355e7cdcfa039e3c36b6df4ea11b4122918fd6ec63ec672ace58a8ed9dfc61e7d3b5829d574833e0e61fa08419cbda05a85b3c4b9957a0968d5825d0d052013e75f138c8d74929a631a0ba2ec9555e2767f17e6e22890a5cb00f63f09e00b8decccf7d7d0e369c4396cb429e53d8cd4636ea630d6fc55143e6146a969ed0839ab05dd079da3f946b3deaad2774360529f2aa7e6c400b66a5c449dd8362fae1a1bbf110229810e0d4725cfa2dfdc046d4c18637e1de3ec2b52055c237238de0167eb0844c24a8fd91ac9f66f86efc945c0a50672926efab53bd0725ae9ff36e9fcbecd58212cbe7e0f248b9ab90b2f56497f196198043fa10de909b05bf3dd1d20630f9707095f4f80d044418e67ccd79e8f28db7acb1083a2bc63233a4f4798f13f21e81da6ee03c614cb367aff05410960fd366df06691b374247de70fcf916e653b2bf01b49cf116324e8104da61a621b566a62c97c6c058208e4825727cbb6c2ebaa0e888659094aa03709659e272b4209c18366196110d71120b203fc71dd5d3c17be4580e5ade64a3fdeea5b85ad33fcebf30b857dc7cfe3ba52ea8269cadd7dda308460201e0119f8918de8980f04b318f39487e65ef0e0b83c2396d2fd87f4d54dd00b405063f072659d6b11513f448b20deda3d874987c252b7d16d94c4f811c97134e5e00bff8300e718e17de3735bb4bc052100a3823f8db4be2d7554003481ce6d899d74c1ad9944c01d933305851458933b3f780ad6c1db489da507621e39be174c71f73ec9c1ef644578bb1566136f17e91b475fdbf354cf4f5a6ee300d3938f5b4a7b9bcb90188a3d9c8fab1326df69f5c3753a8ff9c5a7bbc4e2255954dfb6a2ce81381eaf9d224005e050eb5f53d05f0a41bcf4f3c0e8771e84eaa46ce27b0438d4ce3ebf9eb25b5351643a26f607c41b6494ac77e4c4a2fafac8e3cd872f31816501efd66c41795fa0d01ac0290253cf6c9d0dd8d5865684c1a02748a824417827ee374404a59ba87c3ec3caadf08b0f920667fae9ad18560edc3f8571986ca0bf1ef114d394c08ec5ff221e9f9ce7b6508eb6c38d6041fbe7319bd8874f35bace85c0bcc08cdcc642ae7fc264b9be08a26e5ec6a3618a078a128d0b8daf46e404eee4123b379a81680c8336036d9a44c12bcc23ed7b1b96442108843e5fcee6ca3bf39d976ab36a808531bd7ec2e4b4f8aae3a90134534d50f411d377a2c05"); + _ = try fmt.hexToBytes(&expected_ss, "38c469d91f19aeb79dbf1aaae4d1195216c86186b00a798ea3a6e544cf9c074a"); + + const kp = try MlKem768X25519.KeyPair.generateDeterministic(seed); + try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes()); + + const enc_result = try kp.public_key.encaps(&randomness); + try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext); + try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret); + + const dec_ss = try kp.secret_key.decaps(&enc_result.ciphertext); + try testing.expectEqualSlices(u8, &expected_ss, &dec_ss); +} + +test "MLKEM768-X25519 test vector 4" { + var seed: [32]u8 = undefined; + var expected_ek: [1216]u8 = undefined; + var randomness: [64]u8 = undefined; + var expected_ct: [1120]u8 = undefined; + var expected_ss: [32]u8 = undefined; + + _ = try fmt.hexToBytes(&seed, "0404040404040404040404040404040404040404040404040404040404040404"); + _ = try fmt.hexToBytes(&expected_ek, "6afa82ec0b8e8449591cb9a11be36dc43146c18a6ad3f175897933d25944c3c643f50332f1cc4f6a9707a387b5c40661ec41b36d2bbc4507758387672fb4a1c1196eaa81b01be2864cc958a0642496f264b4042ec5f7b25a5220a8a9c9633081aef104ef9abd4da0af068a84e7b03f21e6b60212a02dc48ba7865877084a1b025443e38d60bb6fae8ba88ea4538d088bea758f6f9a5c26a9c44699ad43080d3fdcc2d910b16b297bb86436e3d5b79dc01230124867c1be64a9cab7aab9fdb47eadd15d00ed84ac9a37406a3665d7cbdd106d1e6a29a81c94e3c841fe9cb583004aa471ad2eb477623401e6959a04439bae06a07058cf2ed94bce1839e636a8c0b22f782483ce798e609bcd784605eadc0a06c7699b1b9ece550484124095a13371028101c95721c2c71b098b15f3998785b361924f89dba9f292b9b18cbe232cb76e41bb5b35cad976962fca2957eb8e06356c250525e8825dce66478fa587ab6b429bb287aaf21a58c900de0946ebc284873450c7e7a0e08c856dca2191195f73b3aecce1b1c1c29cb24a6c2fc3c14ca1495182645cf8486cb877d067440f118b80713784474bd0214f23fc09e2c8b5df357b3733c13545c9490378291646fbdb8403818f7d571a40ba7f5cea480fa00ba15373c60255e54384e96c455da13fd7dbcbc1e7a5e9d655b0145976e190780b55c9c86ca6c988d1230aabc8558ceca8d5cbb844ca8d95b30b64c02440522661c9b07c866e497b5a7daa2398a5a888a4244d90210105460fab31c6db3fd57941b869c24218b2ebd7ae67885e1fd691a3dc4388b872f4cab5fcd94067ea953d73acf1f4c752776357974b0ad7872ad603c30a174f963983739e5fa5304d1705c6791e9c90342683a3b176a2897ab9142190b3900d089c77ece41316b40611fc6bf7c81eb7b589d65524f9c6888fd7c53ba810b0c70766430638595dc3134ef5da09c668a804ea1e2f1469edba0d457acd8281828d02a915bcbc05949d2edc5c1c5856fa798e5cd3587472903c6cc2bb43b03229352587cde988ab60049b6f872a9aa82f25b5346fc56fe8686423b27e0f368a43d36a21602b4056022d25217df44a1fa330122152cd2a581ca05d3801bf9f0a31bf79b8a7394fb8c9bc1652cfea9c386ca7c703b5754c8243f7b0a36bc290e0733a17a94431d7b05e0b4298a8b287fb1ad3b32a57693a1e7867b0f3c483c0abdd2c05c81c26a60320d91207f204c2e8393a78e7572c8c296260351eb62940c394943c6a622a8710aa09a076b513dbbfddfc3105379da08b97a6fc68f2105088cb35f9683144ba24673513f0b6aaca32c0e77173d7387294a9b96a08a014ac88afaca949c2c3760b461dfc1ba5c01a956cac46a01dc7d9a992e1aa704aa15af525911857d46682b137a3719aaa71f7a8ba88afd6d43b127805f6c2cb55f88d6c590775577373ac87356a56b89341d0e8657737249bca66b0b4194a5c13dce045039164fe1530e38981f08c66ad2bb0531c3631a86f6cfa71571b777b2461601bc7a146c358955a5b44130d26b5c35322138133c083aba300a5def2c4f0d94d4267a2ad23420b768c77d8a1f0428536667d2df42127012291511a8c72738b82f0cff83b9315330886d611180d2383cf551afec4aa7515eb138a398eb44c0ca4c2487488da7b0a98110cb697ec5031f995d7022be6c799f9d3759079144e"); + _ = try fmt.hexToBytes(&randomness, "68686868686868686868686868686868686868686868686868686868686868686868686868686868686868686868686868686868686868686868686868686868"); + _ = try fmt.hexToBytes(&expected_ct, "cfe31862853164950123baa75549418c7a406cfb60003b825eb292f1ba1f25c3b5b044dce0b8b16cb254d69658d516cd3445e3f18bcde32c4f46b4ac25dca4c4573647ab7cefd7fcbb7c192d997194654aeac0f593eddc4464e7e125672fc7265f1664b32199cad45095359a4dd80e9637f0140504a7b1303fa69d121d2d214d5ea44e0046a120fef7d573016d8edf0c20749f05edccca4a30565df6e79015f04b03623d3aa25cdbaff330633470c0289689988c27fe49acc957d3be72c4bce05f1c80f3c2ad5b4e8a2714c1d1ea518f431f97f6d68eafb06ad7226a29a2b3a9e5403cdd923400a4303054876986f834848a4902659b288d5e3ef26a9ecb3f1be3630037d147301498bfe198b8116f244a12547ffe6a5006f748c0485fd72232e0c55df090011946c8b493f8aaf92de07396e901fb4dd8a4f291645267e7ec0335eaedeca28ab4c36328c73203dbca87e40dcf007bf8687dd4a776151e9d234f52442a33c7566b007f6537837cf752602624030615d0cb88238b91f578e0728b32734363944bbfce5884dc3c777e0b3f1ee4029298895e6b82f6f2307dfc267e27ca8d7d9b49b90786b7f39d906ca7b6527d5e31316fce0214418f95ab9504c98ba9e868754cb813014cbb196eac152861af719c7632710754cd2c72544c25b66d1d016f97a409ecd11577a358647e1726da16d0a0e2591eb3b7cac7fe47fbeef10c6eeb9289aa4154d42ea75b864e0a1215ae35dd0db3fbbef39ad399c3d04d0d4ad9f2ce442bc07dabd366307eefcb2ab483dbe80b3eb4fe966131a587ffb2e3664d31e2c520722dc1a1f5d27ed0e937c4c89963576cdca001361f11bd39e2e2b367943305fcddcbce6460a8bafeff8394beba9bb893f1a7abfd2e80bf10f0546e72a4051883e1f7edfe12ee1505d9503a83ddb2b998cb2475b88d280f1df688f472968f8c718f1f9fbd39fb3073312bc54c755210d0ef49f9f2bcf06a1099132a1e08d84b68543848e1538edd881620560e54d8d6f9a71ca2fd44f8fab9d094f1dce52f40c5aafe1f73e98a2a395f48d5da98fa5c95e4afaf84e7a138807f71eb64fcb3b5169e8bedae1dffd725ad6fba9fd50f39db9432cd5f379b680c09c5313ea73517f017ddcca33a405c0ca293d8c34714aec241b9a634ec65c8c0b56e59df8e668e74d2494bca14d8102dc0592dbf93c0ad5f9dc89ac24a7e981feca0461d3fce1eec98a4afbb0b7e6c46aec385c9c0fbc203264aa6aa71c67fff159ccbac01cdd85c28835a98e9b7d0cd4c4330a0a5334b5838c072df4bd7297251cbf85dd8306e1a8ec893f4b9558133e20e0c9598f054e2b0b77d4494c393c5dd0e83a6520243edc56200f1cd34b8f69d53e4a823a46852e61672ba69447ee49522978c64616ea42a0b0c6cc953c748a550dbabd74010cdb8fd19c8862473f826267c8cf41cfc36100665ebd766390d83c1b2f795cb6d3c38dcb8e98c6ec0cd5111108a079d57f9b16960d94a4f238a6d7a25b6253b0ae0088ce414406fa7d020da785d9aa3cdc88c2401760535ad5f436dc83a542e294faaf07fb2253b009416"); + _ = try fmt.hexToBytes(&expected_ss, "a638747e2b93607e6a651d528435afe07e8733ccd150507b96b639f6ff4a10ee"); + + const kp = try MlKem768X25519.KeyPair.generateDeterministic(seed); + try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes()); + + const enc_result = try kp.public_key.encaps(&randomness); + try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext); + try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret); + + const dec_ss = try kp.secret_key.decaps(&enc_result.ciphertext); + try testing.expectEqualSlices(u8, &expected_ss, &dec_ss); +} + +test "MLKEM768-X25519 test vector 5" { + var seed: [32]u8 = undefined; + var expected_ek: [1216]u8 = undefined; + var randomness: [64]u8 = undefined; + var expected_ct: [1120]u8 = undefined; + var expected_ss: [32]u8 = undefined; + + _ = try fmt.hexToBytes(&seed, "0505050505050505050505050505050505050505050505050505050505050505"); + _ = try fmt.hexToBytes(&expected_ek, "33393446a76c2c01b44da5696d8452b29ab7b91787765366bc1a15b149792427844ff68753f957187c102d0940c498b67332a7f9cbb82fd3bec9914e05a955035362dc7462071c19e225ac96c12fa34a2e86e0a84a0c633e044b3d93969705833a53a4257b7f4c1515ceaa8af9eb7f53ca7cb402a4aba11c9d3206f8209c436bc3ea0487c456a79e240fc4f5829e4977817aac9de9a0cf6cc28131be92c2a6ec672bca1a8b3758092f8b87a11565c0312e82939f8fd18024a71bb98a885c6ab99299cd43a6208d2198a45bcdbec590157375c16a456327bbd44011b635cd1f678277347aa5a765b7208acaa4316ea523f59a0671594e56121654f3581e882072396932da2e2ea02bf5489e546c452b7c6f60e8a4c6112f6ccc7e3a7a44748aa1308807a08c15d78a2e19a2750a916b8c46844b257c2663a8895b3df8c40960e6b912623c8a92c9fb435d20e6cda9fb456e55c1dd90a5f64a5ac2c6125046175751aa742971f1728328c353f4a7a08ff16b8f978ae41724494c436471505e2c17ba958ade7b2afbf9cc2665518949755fd5bb8b15ac9b80b78fa615a918c4a99549c37c6ad0017c51c3963e3408b11848ddd4afe96b67cd66c76e817d4c150de52a720505b9a4b09008243c0fb05ef004b9c2c223f9230dc57b1dd3d1873457842b47c59b9a6b39d7b61787b42a500c9f57114ec737bf7c45303b8a3fa78b3c649cfbd166c60c37dea89e2df71ce4c605fe45abcaf3a604f548ee5030fdf49ad4129b94516a22a8b728112bfd209e5a5b565e20a8dce97748a79063790bec93bc89103c32419bbadc1ffc679e48139a0c88563f170d165729e3d710365c404dfc87d7c31efd24785a9265e55c4a624315db4005019818a1e7c0367a70fbd16098e27a640b0f3dd9515d82be0637506ff171c6a31edf453b40a6162feb7c3543506bc1105ce196e46097db749b485808dcc3b4221470ac21880bc23c7ef4b315f40df40802f7324957b450032c3104f871250590a4c9a29687c70e02b46350781abc7c2b256c54bbc96eb20b891877c3a673f67c18197a0e3d657196d5673efb19ebf9b3e49691612834cb662d2828938b1ac1a45881b9e51c786c1a321814a8c396421b7f313cc2e4447e6e87971e1660d94aad5f13b26bf49fa499822b1959f48aa88dfa5101f463d5bbc8ca21b9d0857fd2e42d5c1c7a9192765a3013eef54ca9e43fa99abfd8053e9059650b964d6aea8c26c13adc0a6d91208f2dea0da4d57a3a8a6be9156a4065058cf965da10586b0945c30965bf8a436aaa115ab170071c3080f5c723c10d05963a162622438682fee1b619c2b10c207ad7d38ab1ea7eda0924d9ab6b443386af7b07fe26b8fc6911789666bc14619a381eba7966e0b4ab0e528083806f46456f97e59276d5a6119b766b1847d3a1ad3c4022cd0616bbc348d4e072f7d68272617b03bc9b25f569d0b11dab8c5a7ee11ee6c761ee684625322c6cdc815ee1c0acd29a8e1c826cf89f25944593336643a46483dcaca9e1bae9a196f692ac0532cd91113d5d974963a524bf708c44e198dd212dfcf69213a42e32e6b7c860148ed38c51002b83b0c4b979b52a753bf2c3381c027623b8a8251e60f55524b56b959a14d84637086a15567717dbda7aff9fd61d09589ac8c08808f7869e6331a5689dec617d7ce2b8d5e76c307d6b15e1e3cb07c9c51a4f42"); + _ = try fmt.hexToBytes(&randomness, "69696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969"); + _ = try fmt.hexToBytes(&expected_ct, "6dc476cbc91fa3895cfd69bda5799b95b7c0dc87e5c78dec9b367df58abf3e7852afb7b31caff856e5e4136a23a24f303c6100d9a0a6673164f6f44ea472f4614c2b42496d0049a27e02f1aa45ba04442a74b5e0035df3d8877266a4241e650a86ccdfb0c6cef22c62f376543bd5749353f808308eb74ed031e73c9f42ebdc8a484b30769a17906e39fe8fa65c0f88fcc3436c0bfe26a4995d82a41c6a99140a36d166cba6ad2dd065ac19cebadfd455c9fd5d15ab2facaf9f5c79bcd12d31ef88842403ebfef226f9a1de78d18d2a0a57a7808e5c4d9c7c510bd1813db60a2e0121e72c52edf77084222dbcd94a10cd4dc3b09080e8e5a8971761cd46ded65c78ac4d896d818a400ed832830570d492665dec3f8c11168eee66f33d0c13ce3062252f107a3777d1d91234f58598184ff827f6b2e73638fce0e4e51ef704e1d3f8dd5312191e36db31d82b2b76869d7eb03474ff616008158f3bf966a236cb3e8c52de4d010cc712abc23dff6f5ce00631fd3dbaceba030c2ec18fd0c39c9c60134a4dfca521dcd71cda1179d8ef08f06a42686c360c9272f15b4258e78514c8814ad70a1092d26557a45764041b8324bbcd69b5d2868a6fd96c871cf83413113c898e4bb983187fa4c1cbb73cd41d3d4f4db185047afdd241f1b7e7007c00b03aa8ab836cedad6127938f87861108047298d3d945b343c62e2fe852a0354ff31dadc1ed08a4ddab41d91c0262283b11fcbb1ddb4e9fddd7cc338e925cedfadf4e306f84863fd45a70f57df0384e1234220103b0f693144a5ecc9d99ab1d6f725740d1bb09c3a4b4ea614ac01bfb1288bbf14dcd572ecbb6c822fe541b04a0d6498b5a14727c2d543c9647277bd67822a5fabda4a98f23ef50ad12b321377fd4ee24b234b0f296049906aced9d08671de994956a2179394d37b585a31e405325ef880a108ec492c1c87da90adba72d8be4643ccf29431cae053f9cf5271f7e1c7a4de093ceb053351873d657737ebbef086640d417c6a05ae7fa9e30b1129213595290427032f26ebcd1546b9c9c1bcf01ff3d18fb1ebdbe0fee540f4b5318ba65877f457736d30c750e42e0e773aaa6e526bc6143ef631f6b3f8811a026374dc28e06c92acd09e1f3f97c12e512b778038563b67de0d6b34a48157f1b936abb8b9f3da3adb88b5f36d0dae48627cdc1a403810c816e32e0d1112594a4f372e9abe4e6721ff83f488421022189b82e1f7f27a33e4d11969522e91d19fbe1a3b9eb6eb3dbdc2b199db9e86e56bf695d0e6e79457226b2b3cefdd2ee3775dae268020392f0336c6fcb3928bdfdeac71246343c08f2be397daa1fb9c252653307d0f7bc24111ba8df6ab29923b4e7f3471be2923f46dccabb2063427f3d4e53baf6a9ecb88c12d9c233ff14f63ccfd76a8f046cc2d96733e72d56c835dbc9a1aa2a5bdee915ae7eb3e5dbbc8ba24df6e634bffb4364a81039b06a2c5cb31c9cd8985e7fe2985c3e0a8f1d52feb2bfc3549c3817989cc1746fcfe8938888e12274481b1b8caa8136a07b19cdc42dbff58c341de0087d8feaecf29eec0ed4fd0c3355"); + _ = try fmt.hexToBytes(&expected_ss, "0594af7fe7a398ffe68f37a9f73506d6a3f7fd82b24e26f4b93269684ed47172"); + + const kp = try MlKem768X25519.KeyPair.generateDeterministic(seed); + try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes()); + + const enc_result = try kp.public_key.encaps(&randomness); + try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext); + try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret); + + const dec_ss = try kp.secret_key.decaps(&enc_result.ciphertext); + try testing.expectEqualSlices(u8, &expected_ss, &dec_ss); +} + +test "MLKEM768-X25519 test vector 6" { + var seed: [32]u8 = undefined; + var expected_ek: [1216]u8 = undefined; + var randomness: [64]u8 = undefined; + var expected_ct: [1120]u8 = undefined; + var expected_ss: [32]u8 = undefined; + + _ = try fmt.hexToBytes(&seed, "0606060606060606060606060606060606060606060606060606060606060606"); + _ = try fmt.hexToBytes(&expected_ek, "c3f9a6305222862c2c89ec0dbd805c547b2120405ba8e994947b1d74eacc58277260ba837de29a0a2b84a7fc80589334fb2626c9f61c01c88d60b20b0728110f0840fc01a6187a506ab23d2b459fe0853c44f86a699533284577f7913b69898361bb67d3711680c6c25f2524c56022227477f28a194e90b4702901b0046ba798cd1b843192f3bc841c8f9748a1ab34b5336b12c58585fcd010bfd6c48e775e761525b260c7f6e646e8f17690aab51bab30c9763fc53b8547d0b56b0c82c458c61d84587d989785600b8e987a36423d388ca11d61765a01a4a79ca20a925908494eae0a3b6db1b08cbbb7a4ab5edcd96bd76725e928055b0c2883ebce0a59bc9ec282cf300ba6c5380d1b70f13b2dc6f616c76087a73a70daab7a6f97171ca5997bf40e87b95f33a53347ba6c7ba89bcec9041701226fda1243f4b1fa047740c7b753d630fe386f09e2a2f96559ae979ed9fa5a78d1310ef311f97cc3b6a738bee45b75e3a0cc714c81f72187b105a2120b9beaaac116bf9835c504713f2218b526f13d8a8c02c95aa0f2927c27e996e4564578e056227b07bbda8af0cc5d99f55eab9868b8eb4d1b14065843b229ea3e2a0a748d89c4c304198cd44cf77180bb8664f9aa1a9511527233cdb6826976d34276f62257ec382172bf97b310bdd34056ea61f2d74ce1cc9544b02263cc3d3133a308911556345a17c32acb470444f5536fd21f81cc3c968ab7e4db9cea564c55d3a601066375f6417691c0ee3b39228860f1bc00693293c4bca327176da52b7282495bafc2932d1154c03672f4db49bd40967ca41fb8f8696382a5208c5547640e5193459b3507f4817ae3707930508969a18b1d168e67b5939532160b50a538ec9074e733dfaa4af09755f831720cec8519f3bd74809539347b8786868fb2ab9ab742fe020c87930d37c45aa9b836c986ba01804a5de26974ac647d8b921484b748d9999447875fe2746ad1112fc57ea3999f5899374ce2536c58310033ccdabc75d63a641a5651bd08079a41c1e59894cb3a1c8bac7614e960581a1d191a3a386209b6e82efc92b655c51d91e11e8d39190863909a97cc6017af2c35500b860a21c9c48d48834eb561f58c683372968fd89be52a26995317855b3123acc2071683ca669d78c3cce5bb9c9f9429a643bbb1401d1d665dbf45ace6524cba3b0d33582105dc7c38165ff0959d325914f437a4008ca751878bb8287eeaf092021b1b3647bb4a9b562a7aae80625739f66b636c309a09376ac4cec6d76ce4629aef46ae488b4c6961069527594e0c22ec55226f47085bec5e0343cbfb421ffc838b56e9553e411ce39c811a1b4a53b051b404af3b58bf651376360c040a607f946c6357020ba1fa99daf19ad3f020431a98b1647040038383719ff9fc7d4cc75d6bcb6d4f00c834874b01b9a85e0c0f5c632734a3264d156c973c14cae920bf0917cccc924ba90fef187f9783119a7a268621c7f29bbb28da1cceab369609c64efc5e91a79451bc8100eabc85541d1f2435b96b532fa14c1cb76b99908c418118e2472c66511ab558aa06f03fd8fc15f10a2099dc4df1360d15910d2a7747d8abab40fa3d77b7470d8477cd63024aa40268f55139d37548223ed5823f136805752c0b5f7f23edfd90a89dd1f90ee344df0b7e3dbaa619370b955942588f0ce49e72f156ba743af7ccf9d4a89f310c3012"); + _ = try fmt.hexToBytes(&randomness, "6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a"); + _ = try fmt.hexToBytes(&expected_ct, "56a06e6443b53c24f647cb6798257899ea1c49cbfd4c4e3e54dd73a1139dc4df519067a5934733a34538f941cd9a2d9de371623247f1bbcb728d8cfd59b59d6995a2430ab14490b336ac502cd21dea47dc7c89fa026310ec05fbbe50179b45e4b68c507fd90fab06e36fb1c9b21c06115fe42beba15083385764cde8c527c3669507bcaded45f70a8cca789ad71e9087948e4f1d682c4a77ad306c25b1e6364f43fb51c0c5d72f455da72f46041f8fcb143f67c7517b425e24cd803032645cfeba77e0d8d2d0cfb57768b8fa3238c10ebb99471710973bf9d22fea51471354e53f153dfbac695a6f8ccb10524a0453e804bb013b450f38007fe21ce898c052a132ea74c9e69512ca4c18c32ae37884df520d12721b95acb515067f3516ced73c930786d6924cc7dcc08b50a3abef4e0df82aa0b9c7bc199cb2dcbc7cbaa38ffbeea60e41ae20e8400730132f3627e1f62fc784f222213c2c479c4ed1f2159f724eddff87e6a4b5b3445e189590de4bb62bf66e930550144b44e23d6c045d779ace9f94c0a688f729a0b94821b669b5ec49652e55a8c19cfcb4d389066bb3aa4d9d7195d30c496ec4250138ea1c335d6181b25793ff5d0feaf91b7d27c826ecda49a9b9c8ff880091963a2ccbdb5cb45eaafbfc8b93e753bf323e2f76b0a61af96997d97f429db90898fbcc2dddcebf04d0e2a3d688fa56655b41b0a628e4164a31308799ea58984d44739d9720dcfdf4bcf2808217408c433d263787405d11dce0854fbafe59f58d39a2995b991187ff86d3906e1e27812d5acd2a8d11b95ea20c7bf1e8ca9146cd9bdd10f899e3289bb1838191feab937a6749c965c01b41e6febb62b515efc416b9470214d78d4a47bde46b0c8a5f8c4672e8d95dff71b9d7bcd2f94c1d115461c5a847baa8ee4a8a67dedef4ea40403326710c23394b7b38e193b01670669f2c6e06c7963da8debe103a33b49541fd5e0bc77b916dae2244d36bfc3d53f0f1ba51eec5a18c2f258edefa2946f799b88e17ea475e8bda8cffa3531ed246820882e32f7ae0425b129a8b0d20e6205d1a7f85bd8efd9fbc7dab57eb45d187a25d29b71d69254a36a5125b6af9db41caf369183987e0f7077253969d9d1c0887a35fd2ea34eea81fc930b1a1aa11db1e996a11c49c39f570d0e3cfb3ef5811a678dc4451c5cf64898ae3b53e417c69f09438c997fbb453b9d88a1cb603e2016c1ee79a36f2e9df75f65eafa3db33f0f377c71e72f77af85a0114306e1b21921d532b6d140e3a2c5a479f33c1582386c70c75e84765735b610559e8aac6149ad174213f2a1595476b29075b71a025f05fec67fed05e5f193f2b71e0352755ff4aa61bc63f02405760333516766906158a2cf812fdd5d11eed2ca3e440458ae1bf76cb642db5772d672d932a336cd838339d9df2dc2459a15c173b9e803a53112a081e7c2c945786b24e6d226aefbe7cb184a23dd504813975e25067f33bc90e13ecd4857473e0bb0fa33b3f5f0255eb51da214717511378927c0da6244664d91d63fce62edf776cbfda72c88b68025f05cb0ca76c460d37fb8d30"); + _ = try fmt.hexToBytes(&expected_ss, "d11018de4d36cb4544612190b4613a7b76c703fc4cb782fb26de49adde017e97"); + + const kp = try MlKem768X25519.KeyPair.generateDeterministic(seed); + try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes()); + + const enc_result = try kp.public_key.encaps(&randomness); + try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext); + try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret); + + const dec_ss = try kp.secret_key.decaps(&enc_result.ciphertext); + try testing.expectEqualSlices(u8, &expected_ss, &dec_ss); +} + +test "MLKEM768-X25519 test vector 7" { + var seed: [32]u8 = undefined; + var expected_ek: [1216]u8 = undefined; + var randomness: [64]u8 = undefined; + var expected_ct: [1120]u8 = undefined; + var expected_ss: [32]u8 = undefined; + + _ = try fmt.hexToBytes(&seed, "0707070707070707070707070707070707070707070707070707070707070707"); + _ = try fmt.hexToBytes(&expected_ek, "0e12760b95266a50a52bc6ba3df697c1261d3ba510987a299a83cc0b9134c2ea4ddc47889b72c6dea65b49e066b1a2822f269e8ea904a667cfe6f7035aa83cd9c041efc38d23053ea6d754c550cd5d1936d4e74ea047c4dcf757de9550972c82abcbad83c32838c18451240c0907a573451dc746bc491c143d14837af83c4f4b1f5aaa59bd5a2b03c144ba6ccb445bce2d384ab40823271342128a2b7eb68624eba59b3b2b57d9a4ac7b630d063400a275ad71b6b08a8cfe93a64d368cef666a4d208b8f39c442400c1a7c4677658576417b5b026d99cb80e01428be00c720e220a2705936a62d6e737900bd8c3d0b464ed7650dd7c84c250dd73c7536abbb4013809927ccffa5391bb254294c7be0596dd29aa37445957b2aa88dfc54447894773a34e8e87b05520c9c2ab4dfd69cdd9a52c7e9352a3ab94c07a26d468f10b2632a6a524e06234fbb2008f17cee6a59efc51b4c483fa5264c9fb325e9d7b9b66c6aae91ba9f9a29743968c5533a3750bb9554223b60c8b964cfa7346948cac1000d840488c3d211b4e7d463ddb15551862f01d77e87b60899e25164cccc814caa5c0555f5413ffbdcb5101458a26061560463ab33cc2174c61f5861986b55d6206612f386c996561b51b63a11be965a1fae45b251476e96c3864925646b6246935bbfa9f4bb39437195c6b98cf81a071a70f26b0ea61628fdbc91ed299b6326ce7f220aab6965be645ff7c51248e456adb343f10990243401d8e05ebc48b58d2a77b21341ef513e66157b83596f1f447695e8c82279922e15987ab14259157ee6e07b5ee28dc41c68dbc14c39131fc7a4ccf6420f65479701413d0eab484bd55dc1669e4e3a3eebcb118c899b2cca6cb2dbacf6f5a717ab0bbaf02141299d32c88426aa4866173cc12313f2c52624005e6fe176ac02bf45a9964659020bd03b9ddb8a99322b0452a2eddc6ea752473f1c1c9ab051dc1973e81b1e8d746406148a8621575f7c71cd2879350a2970740156c69c9ff4cfe97a3ff0a90f16e90336f6825bdb8ba0a3ccb2f181d26cc88605493b442a9d5a9f788895b720516c865d575c75276c937e2252a486c90e64b1c115060dacaccc3372d1475c3381098275bda23a52083c6e671825e57b40606226247432213988a7672d80ca32a2356e161b5a3d5a218cf0c6788c62b786003154c90312001e73bcfdd3312573504a0c402666620b31606883580ea09f690c0cdd948520b19bab2b30fda60dd903c8b4f01a83238ebda1636c911895480a8e5430da58462455488b71a735ca62c1928722d57c3d86ce66760433c15357eba556db67adb16f8a02cd995c2525d21b49a06020daaaeaea50d332952f399bdc859574c9c9c6056765d1c257e5a655c9a6c619167963afc850027a00730d60b33e2b897e71b56466be0fc98a1c314b67b3bae780a622c58eb05570024c989b286763ba420ef234477c2dd8bc86b9c9adfed5a8c7b948b6735bad758c04f08025eb01d217ab9407881878466904b2e4965b2275155fd1b8000c7b2983a9e8f87243551dc31b884867b3e9418c1d5196f83b2e7edca55cb21dcee430969366fd02a85debbd759763010cb918b13460c33bda4b43c2aaec2b4d19800eb45b65c915f8b969eff9e9e356faea1c9cf69659599b22f57b8184a0ce2e4091f1b4b03b77635cf149b6492cf75d285e028a57744c424673"); + _ = try fmt.hexToBytes(&randomness, "6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b"); + _ = try fmt.hexToBytes(&expected_ct, "892f7a7985caf0c231b2b35e3bc7de3e5a4739b72d3f9be342e326c0173d55a5089ebe9826c834792a3236800eecc2612b4d92ecfdbed013a6bfcd4d1ffe5c4150e7257a9e7c0b2e0cbee757fc86b5debe03aaf796c76a5825cc78667189e0e37f4a1c3dda770eb8e6b978c89181f4871be9a295832334dc3a7f2b953f7df43f321137a9ad5b27e344678a79e386e402236124753e6dfcba0dd0dd97017461c2fe039c72e9d0073676f0749d8cc9bcd6ae0fa6f14db0b52575845c5b59b82aeae93c8fa1bcec6edd081517d6f2d3ed91575b2dd7b5893f3dd755d842bcf3e8d14afc5c236ec66236b33ba875953633eaa23afe2974d00179b72d117d9b8dee5110036f418baa0be052ec09d70fa36c943bb65050468bdfd63435f2edd7962cc98a0fc6b1d2a2c31a3581c31741790e4f7acb162a7146f399605a08cc99b48a96caccc3118ad9fbfbba58f29a121ccc604e9c0987f2250be2072189d8b01d594e946f0e5bb1b4e045ac5e5608736472386584f455be6e74b9cb31d6ffce008ce283b3f834c56f64ef829dd492eb70f6815c45128c66e77324bb03f71baf57bf0e200b5a59bfb1628e2bf209e8d3d39a2fdcc9c3cc7655224efa1efa86f550e72c11b5ebaa0b375e11d77d0a4cfe4f1f5eb048f0cff1c2882fbe01d14fcd97cb2ed5a40aa97c125f81c8c91164cbe8477058ff9e380604da9c2ac1b061853c2e4e979811bebb99948911dab9cb03d7c4975461d8e723bc415a3912fe1108de37b32b49174b722022c80dfad2881ad1db9f1d6d06090cb1421d3798fab2df0f4408c11d07be121ba66b406a6bcd892cd499e35e1c2c20c15c87ee0e79612cbdd04576955bf61153eee2798c26e4d6d3050f3de5f6771add0495459e5300bb4e139839bf6a4206d7865c159d1ba9bd566e73d9a085007681d3307040c58616f369c6f2baa54fd59b4e27b806513ff678c2c6bdcd423e67047460a3a39cb04ead7b095317f0993f3ed75b5fc8b74c458a3bd6347c6a82640f4041f0168690f8f68f2cfaa4205969fb4dc9ad42d26b3fc2ba5bb08b322d0203118666b665e2041fbef0ee957f73f60fec892a65316d3f733112ef2a19c79c2595ad99a4d0c98cd148326ee8f2f7b79a161333302268c4270a96d5d67e3503b688a332f26543ce54c3dc3817ddd616624ef715a41f1a80f4510fc769892196ac3f4c62dd0391d4c5f215ab447299c7bbd0ea1af7d621ad9d662abd35ef65f900a40b36f32f0352c97a90e76217c317f1890de7703d6009a218e75037b68c6d34f0f8ca6bef01af7894883493da5c47d0fa116af94f948747c969b9779baeb8b279916b6ad0eea9ffc4afc1bd907673203413b609b47ee0fdce780e82a6987d63887c285da97609736ced5fad8faeef141d8d0344d70ab0795a6668fae59aa65d411107631415112288c7e3284e26b5dcdabf6960821bb74c79fbc6fa73da77a4220943e86cdaf4a73e1c5bd918eacd53f6e085a694b9337385b409bb1f8604ab2b9bbd390f69cdf50ec28462fb3f0798f9fe2c39f3823bb41cd3effe70bb5c81735be46a143135c58454"); + _ = try fmt.hexToBytes(&expected_ss, "eae6cff2b4d6971efa91c6333986693db4f5c46207af27ce6f5964c0eb4aef50"); + + const kp = try MlKem768X25519.KeyPair.generateDeterministic(seed); + try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes()); + + const enc_result = try kp.public_key.encaps(&randomness); + try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext); + try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret); + + const dec_ss = try kp.secret_key.decaps(&enc_result.ciphertext); + try testing.expectEqualSlices(u8, &expected_ss, &dec_ss); +} + +test "MLKEM768-X25519 test vector 8" { + var seed: [32]u8 = undefined; + var expected_ek: [1216]u8 = undefined; + var randomness: [64]u8 = undefined; + var expected_ct: [1120]u8 = undefined; + var expected_ss: [32]u8 = undefined; + + _ = try fmt.hexToBytes(&seed, "0808080808080808080808080808080808080808080808080808080808080808"); + _ = try fmt.hexToBytes(&expected_ek, "003180282264d14206367c682c2a282a58c11467550795801e36393cc16e49f565bac995ab504448d4840b85526e836ca6d641f151bd50897e1f9a5f6c718a61f97f866b364a217c87e52cd28765ab18bf3c931a35150d1c0c32c6b863872a23373057bec82e671986fad2cb15a06a9687a0ce923754138b4c54ba93c082c147cdf186c0d5746cb6d54532a2afaaa701220342f6303ea7b66d9942460722298e3bc2f0a612a22a986946737d057fbf3ace20cba6f97503fe03caa5c3974e8aaaa3c7ba62bc70d1547ea9336ad15315cd8a2919b7683b568aebe54b385612f8829399d83aa3c2308c49a73379481a690afba636e14c7e6f31678c022b552b19a6b90b252a7122575ba041c2daba8db6ab6cb9d13b5bb391ef0b48fd578e67cab1d4d01cf4b492f136c9c4807b4a2479f35b255f234436d71a123b47384c1b6909b0d8c55575615a314178675cbc94e746a415bbdc912474042a93d776a89913a2102efa9377b85b0149075eaae72c6aac3d91c74f71d38c063963d3fa0c4da13f5202931f06a4515b08e22260309b882252c972ea2e06160e1537bb38eb97e19909e58a3f3aa007daba597d08ae46767beb517fab0124bb39ba6eabaf131b9611a9bbb26c1c52556030599be5382b99c79f8d4356f3dc35b08678a2a1b24b3cbb6da29f03a1bcf1bacc87a642d676abe5826e3836aecc878d2d67580e39b9c55b7ac1e6b8a4ca6ec2e8934412258c0ab6708135fd030cdb34429ee0a19c782623f081c0564b7dc0bd8c35b831672a8413754127a45ac7572185185f444f017640b8a4c0819927866332c45374c6b25b88416d4094081e832f26d44f8cdb04bde72f17e38986a6a693604c637cb75805092cfa7b2921665e0803d182600e001b6f023cc39396c20018cfdbc85ac8bbe5cb6fc094bc55c854e28aa3ceabc7f4e1ba13277628a1304d6c4527533cf9d2b6d43a1f6c71c71586121c3254315a6cdc02b9afa12184e51d3db071cc671864f2ac51fc653a1b628173061068924f890b18acc74c4524240696bee4c099552ec02354bf272c94b541b5f4495e73c181827120872cb4a36388966a081010b80a9cf37c4715556f9d0c3a5f41c587a7cead9ab40e366b53612c745632b0146368e0a52f0b078eb20a6006cbf5591e393c19ecfb8e5435a73cc0c5ce3a008f5053df1647293271e1366867232cdbb8c58066c105701735f911db1ca2c1b07737467805e304d243c733a77ce448b639c295392b1458b4a1a8474ca2968e0c78917a2b255fc247fe493bd0d9bec244351890a895e470d298052750166b75a98a855549c386b140bc24326b044a469dfa660fe51a928271d011cd54682433f5583c5205898c216cf80cc5456bce0accf8862800cd36661332261297ee9854d230b432d39c5ea6672634a294c929e091329a37bb5f348f30f482699a88deba93d58745f2b013a993816e4c1b90d28118d9cafe2a48bd024be328490b703177b50ed619a9b293944f30bd62119eb15486a6c3659eb0aa97e1832d82723627670c30b546337428a7364ec49539709dad61943a545088c754e27c857910713376b859e392d0554a09c04d29aa8adb8754a491059903221d689fbaee7d0a714487053a812bf22bd20bcc6b3aba85396dac337ad43e54945218a81ef4d5e62b8608e130d9278612d248353ce8338d4ae997cc9f20d8726e"); + _ = try fmt.hexToBytes(&randomness, "6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c"); + _ = try fmt.hexToBytes(&expected_ct, "fefebc6f6f4dab06bd9b8d0a3a688b06e4b3ce999cff7eb763d80f6d288d9d8c58d240d8d217064c9848a1508726829e177a02b99ac33ac76e50a82d31e9f952098c4731a0fc35c9da8e9c87af7306ee7171e3aee3a3d2d046bccc1594a9686a7749b906346a7c863d8dd34acced601a3953a05e8ceabf34797b4606eff63e66fbdeccaf981c13660a8b8de661c5b753f73c7e2cbc733379c4d9c36b93b756f5becb1ed569d6db0b2d2b1102e6064cc4311da3030c3f1a1126df69fcd2c5c3084ee1cc75761e5358ac39c4ebd472caf4e555f8da38dba8ef862c92eacddec9c270b5c71274c15fc38674bebabdc7cae2444dec1e7977679578f051fe3ee9d7a188565d5fce8fbcb842b28a4d38d77eb37dac58099664fd1b8069ceef1e06ba95a39bf48f7098a9bff84f6f2df86643db53455be4bfa4848d93caa1fc1398ac240d186bb3334bb5381c9d089ae3c8637deedeaa576e6b92cc7b66bd3a31f3c956b4aeb4eb688b249cf9b57a56d3656b5374f806b02875562ea15fcb619395ec913026dbe5a6d488cf907745cc65dbc9274ccb5e2461b9e923862a67744c8b8ff19dda1068fe408859f89b5b8550ed08956ce2b5c9a617e7ef6022772a58b63bbdbf5eb057c4bdf30083bc5de8a22628fd845bc6ebcfe27277d1d7f57ceeb7beb07a20ca446fc65a372929920792d2c46afe34be5a052ab43db9e0b3c2d024cb720e42bd20db4a53e5bb32a1356989b1d5850611918549e9f57001c9488b8eb12a36e85595ca45934fea1d601b42c67078a4fbe701496e443b49fc8822fad0c8fc3a25b3910a5158b380aa678cda329e2cedaec1c4a32e51b4baf7091efa5edd6ae2aec450e15cfec1ced9a1d158c9f8f19e21155e4163eaecf7f01d16ba495fde48f39645a44d3733ea265cff5d4f2129e3c83a6ca33a6223f855a91ad49776d64ecfae6878668efeeac930ab6fe9183370c61727b4049a1dff8579947bf16ad1c0e210524c4f5ae7548f3d3e532e694abed9303ff260178505542141ca980de12090eec01da3399e8e454934bb15759d348404d647bb2a409b9d8a956021bdb85ddba1978593aa56d961ede3a707e7b0bb6f325229ce754c702d9ebc35a2663c0ceec6b3d04ebd91a63854736ef4b601495c26aa4927b9ccbae3adc808b32571ff32288d46c2967a96b53e756027d5eafed7dc8d7d5337f98309ffd1a300eba2c02221a3f100b68cb066861fbcb39252ac446c3ed3a9639708fd866e9fc0bfe4a7aec198696aa13979287dcf1fdcd143a44a78cc6006beba2e1ae70e8e857764d233e54830473fbbd2b173a34c16c2032d004fe19d35d8a5277824260f32ae501e24592916ea46ec14a9bc301fd0c900e33a1b24cd153a25afaa34ee26cbc9215fc6fa18972714aab79b72939b9f4d3190b9bd64282c0e630b6d8c6755d291e33dc3cf6b6a740b565b1cc292f2cef3b8cc6a6f13a3c7b06525e4e7897f63c82502f7fd77bae5a80a63c7436fdc8387787ee34a000ac1512c5bb3820dc6a9b49ecb1b04fb2b34113e75b7e22b6b8058859d0d2a505af21b5fbbf10eff906dde67"); + _ = try fmt.hexToBytes(&expected_ss, "83ec9c8227c5306cb0e60bdf12678a5de9e24fe51855290a64f49e9795415661"); + + const kp = try MlKem768X25519.KeyPair.generateDeterministic(seed); + try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes()); + + const enc_result = try kp.public_key.encaps(&randomness); + try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext); + try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret); + + const dec_ss = try kp.secret_key.decaps(&enc_result.ciphertext); + try testing.expectEqualSlices(u8, &expected_ss, &dec_ss); +} + +test "MLKEM768-X25519 test vector 9" { + var seed: [32]u8 = undefined; + var expected_ek: [1216]u8 = undefined; + var randomness: [64]u8 = undefined; + var expected_ct: [1120]u8 = undefined; + var expected_ss: [32]u8 = undefined; + + _ = try fmt.hexToBytes(&seed, "0909090909090909090909090909090909090909090909090909090909090909"); + _ = try fmt.hexToBytes(&expected_ek, "460393d6222435d92a68eb6b2384ae414ba04199ac14e561f6711facf87180a4af6156a5cf589c8ec7983054764231646f07c57ae4247de37bb73758a60b4d3334985238563005ab337c6075b81416c241fce764505acd0fe7b0fbb859e4172390e670089414107549ed298bca658a229079866b8b960a7c7fe46cc47a1773010f106b6f5cc9c2f906465851a7716649b6a67ff073807cfc7be4cac44cd1af6a372cb04161d4d39861780c6f62051dfcc39c143dcbe49bbf5b58a8769b927173e4c7096c91310a91484cc80c55217b76124d0db0965f84bef016040cf19947d61e05a14f322bbd918392f5083be540a122a3b1d7bc7c49a868f6384dd5259850d427166752bf286a3ae729e3a5a5e76209de5b9c0783a4c70504a338af3d178ebd114b7ef700cc17919b258682d21a1244a002fbca209a80cb2a94e4476aaeb7cd002929ef999095c893624667ae2bc8712b20e35815599196de478f674b50e28313d7695ce2ba3dc02594171a1ba7a095a4351a0f3306f51b4c09ccafea587693a9519f4b4976260445421a865a400a63b0a03a8d9a35ce3335bec1912460e1a5b4685f8fb039a5cb444ca21f87c522c7e6554f29007c0562e5f02eee849f29d39bc15b58ff58b252581adbb17a42f2357ce62746b1347a4123d02aa5c3974eed863eb4f309c5b265d038aa6f994e8de94da384321fc65bda704a15216451465f9d44b7e8a9b7e6d356e8a399e7eb39d0b46d85ec0cc93c56a6d570c9a8865aa6c74f358ff1c03b80042567796c41f418747126bb4ac29d738372fca1a9646ad6e7901581c6845c91c3989058a82bd80117e7c73bd2da15cff522fbf599fa6840317608e410b52b4c477827877ff612cf89cf6c87c8a72b175b6c8627e0a10b937cc672c1af54b64328c733c22585b88c54d30c1a22bfb6a2559b178db7a0aab6001ddac9250416ae5c11c0a5a26a09d38518b28f8e3a7f2eb51e0d679f1d928b77e103f4fa6218f68a1de0af5ea0bd1a26bb624879ab47a28e9c40042b29cbd678f7ab65353807d41993bfe11607313430e5aa8c27b9af3a5f10531aecb1a1c3710f2b992c99b604f7b2a3269a49091a3a56dc8b78487ad9ac24e36c97d351855ee1715083a3e9c32f52f735a76cb008714fa83bc70e4caab72c485ac8431cc97cfba727fcc0af9c542d7d9199bc4b50a9ba83db77c7dc206fda0547e2795a8dc36fd1b33d2ddab089d95eb16b15da94a74ed8b7e8daadf8180721e0a1e31002497c83ce1747fbe78cac291ffd967f23f47a8a30485aa352cfd4c6907cb8b201bc3b9281eb51985e11cbb4b5250ef7ca13c70674f395c52b177541b57b3aac77e382d5bc6e0c32aade73c74a163c0f43a59c616d76393f39166de8318fef1b2c47075c19885ecea7b4acc674d1c72a2e4ca70d28325f12cc33ac19aa9c5e5ab31c7ac6933c1a0a79065fa1e0237cd90a13c7b759980262378d4a17b85c184f84761e071a973784b1c30a9358b76a7af10727f36960f21c6e078d33fb528373a436fb237f87317b81c04926245982aea0b39c24a67effe65a91b211ad7334f8e4858a3b7d0ef21b3e443c0cb64e089408006b103f3841cab70b046b98e2c8aed94ad683208e265424b077389c2ac6571f216fac52e35cd280e51aaf66328162012edaa88f37a3f6e9c0c89861d6df5d8a3a453cca19d8746cdbee424f00c338"); + _ = try fmt.hexToBytes(&randomness, "6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d"); + _ = try fmt.hexToBytes(&expected_ct, "a857a7425c6077dafaef133c62869e523a4b41110cededea6ee66b9a6bba634d92cb41d5fbef2ccd6f5f057951fe1b44e224cd2314c9ffcdf4c358979d6d065f1f12a51033640c07965e6d4342afd7b57d528786b398e13b6eca581b96fe77778d73bfa2422e43093f7c96434103b18cbdf0aa6549da8e169b66f42921b89ac8267de35446008948d6026a0bcbe61357e97c7fe7a95337dfa3177ca6167c3a2af8dc38e3d708665824acf97d6d7b152bff781451c587a282adce031b3efa26b6d8bf4b49733c03dcc4300076049e278d3cd872174bc374d77038c75203ba45d30bd4110b62346c3f0bb1d8563d0e2d58cac839fa693ca96a56eeab1aa4d26cc95e013674eec4f8f08095059f081a0d4dfc0735653f3097f1a37da334007082ba6619844547e0d3296669eeceb02ecbf0c5c5a88d5d1c432398361529e0d91a628e568cd9a18cb3e8ca41315ccbd8ff9cbbc0677150f2e06274ec9e2733f3111cd4f1b900c7a3a80b517a7d6917fd0753f59e8bf03267e3a43d60e1857d4922cd0f14b6d28602792dbfe2b4488e807023ff4a8b65f75fe1a0826d5ff490bac7be071b9545f6e7ee758caf460e7e92e52eb83dcafb5bedeecf44a5f5ef23690bd83aa5ed2ce0420c697dfe4eaeff5d64be78fd3d9c96f66fcfbe015d8a1e75e93eed734c267fa515220e9937fdf8df271f69c8e3d6dec76152404d862434211bd1e4b53cb9951c48b796fbf4287d3386c65217b41f2c414daada2023c43c08f8b6edbecc7f750968c4f16c3c983a95d73c25d3cd4aac5bd5941fa376e1934436acecf9bb9bd4409d0944d393d852aa15d6363bbd83cc24b1ea841e00b99dbe9f7b9fc2a415063865b43ecc9db0a95336820d40ec4d7325d5066eeb2a144ed3c27f5072c9a2e596d395bd069fdb8871521a08d0e9c1810467a271b4067a54d9d81eca8f26d18acb41a7de599aab1f79ddd0128c07e44cf2ccef72368cb4474db47ef43917940a80d05aea5e8c933a0ef8a8516a6dc2a50df273288d97a9788629f001e487434e6a4192466e63f0e185810665267a021896967d833c1f108645a5d17334d5dde8fd617c786665d7df9762b8ec0676af697dca63e62b597aa2cf89c558accc872ce9a1277f88c5ac160b2d13a27de6b4027353adc3bf596d3d6527532e66225fa53e2546f6ceb240435bb93d10e4677208e990f622276d04ab8f2e4ce2e5180a436d33adbf29c2cef8704a41074ed16c164ffcd1c8f09a2f752c098caab09da3db75d84ca510542c3adea1757a553bf53657f03feec803950747c8ddceeb31bd658072f01b7972df731af5ff53bf8a8361cc918c6a00693134fe6e385cc70484d2e3e6d365c14fbe6e2332eaf86b7afb8da619dd4c13e86f62ed63da3b99895d6ee47b440e16a3615f341d4be41239336b020174819681732503f275bf8bc494c251e5448ff32bffb3fe0787482b796c37fd8d25a28ea78620046b192ce46fa4c798c0feed1819614413326eb373e622834afeaff81aff308caaf835635de5eb039fc126a017b9e789e1db721cdd51f7ed3f5515d18e2f4e2dd7851a"); + _ = try fmt.hexToBytes(&expected_ss, "d1b5f13316ff420d4ca22c9a8e3f93d27f735d1da53ebc979cce23f9747e3261"); + + const kp = try MlKem768X25519.KeyPair.generateDeterministic(seed); + try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes()); + + const enc_result = try kp.public_key.encaps(&randomness); + try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext); + try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret); + + const dec_ss = try kp.secret_key.decaps(&enc_result.ciphertext); + try testing.expectEqualSlices(u8, &expected_ss, &dec_ss); +} + +test "MLKEM768-P256 test vector 0" { + var seed: [32]u8 = undefined; + var expected_ek: [1249]u8 = undefined; + var randomness: [128]u8 = undefined; + var expected_ct: [1153]u8 = undefined; + var expected_ss: [32]u8 = undefined; + + _ = try fmt.hexToBytes(&seed, "0000000000000000000000000000000000000000000000000000000000000000"); + _ = try fmt.hexToBytes(&expected_ek, "3d209f716752f6408e7f89bceef97ac388530045377927644ef046c0a7cae978c8841a0133aac4f1e1a7027277f671219cf58b85d29c8fec08edd432e787a3cf9936fe0026a113cb9efb1d7214049527bfe2141ea170b0294a59403ab0ce16760a8baa95b823cbb8aacdcc17ef32775223c791e3740163941f9bb3f63346bef1c050c31f932c62719429aff14c2bd438ab135bed692d56c77c04cbbffd6335b578318b513771e84b14ea821262141ca006ccb8bf2500aa1008970f216fe7f1ae34125aa290492c069a189222adc322f97649c762c7d3128ad3bb2667971d0744014bc3b67445cbcd0b3e7ea69fb1cb9f9c331f97487920187292926d04a25a2650abbd44982bb0c3c6301fe6a61330d24d8a3c7021dc3e3392c79a139b37613bba67a2984298507b84a4d61eef18acfb979af2d39caa4c0db4513815359d76fc378c63a7f4f3053b17168d0221cf0c2eec5514ba235f81d04d67c3b5c518094917671c26a7c046457533cc32844581277a03eb065c4529a779a9a5878f2aac3f81db9ed3d8c9345697058cbb99d379bca16d8fdb61d129960390524791b9d3e501b900bd1e5002e095be06c23f1fb212f5801f24b6b28c0c5493d246d02aa29fa3acfbe15ac4e212eb0b6f69ebbea259a2703aa4c308224bdb741c65c7a5d4bff788279507bbfe513d7aa5694e7b3cdf62ab36432742d4a0ca9b3570ba742fa803b46989c8526ea586cc4fc32866143b79601725fa545fd280b404530318bbc3371194710b6d74beaa629eb18a36a953b75915ae96999ba5c88cdc56a46861c50032c9b630bcc1445a30878979bc55a2c0955bf399b231203b90c651b6afe0e242b5a543250b142f7291ed753d816098f7913302a8ce91641716623d4fc2ac6772aa5f3674042b7c4a18a2186289a4ac4e200774596ca03e6798c7506b984999db6ac142586bae0799f1e776f9f5247dc574d8556ddf9bbbc4ca3643263457f74248010d62d4311268360aecb4902b450bf2050ecb8ba7a92820d233f5a14ed31225a1d17ca6f19e825894cfb1807d922cbd60761134be419144bcf72006366a4460137ad9136c113f05eb54c409520edc72e4150cc3a24b0f819eec11bbd19ca9645b0810a60b4a8a9e9c3955396a1653955b047bcf4f98433c27236c570d75f809e44aaf2dc33665826351872c293350ab324518c8c0c80b521c80c81a56bdc968a5650315a830c8bb17532c62ccc23b1d46412c256b224fd4674491803501d0143125c7577239689965b6989ca561793c0f85c62a9e13487da17662a7188c70b1040a67ed4c3f85e74e3691822fb96314d6134fe6a626b3cbe1461d62a7b573b2cc75579ffa22967e36ceb2a1aa0b71875a22751d706b72ca9ecd0c8100ad0aa58009a5c83fffe91759e6baa0a9345af99fe3b69509dbc84032868844ab3f65bb1df8beadf36442e48e339c967023a525411544c789a2f04dacd06ffef78302210450b931f6b4c32aab34a3f5260b810f4c9a946fc22d3baabaa80ba8d9955d6dc35e8609b4256b482cdc9d8977c1a47a354e7c527fdb1672e166917b95cd6351820261daab361f8a2dcbb240c55abd6a8105e5291b427b566d731e6b7047189cff20d8b120e0b3e72472d1b0086812200fd3698e23f06e4f4e08bbb54cc2049c039c845be659999c8fa48d7f62327c146cf1bc0b0bb1b91b30174b7bc220d422023bff6b0dee263532c503f3982e4d3e27071b855578a9a9aa63b8a8c339bf"); + _ = try fmt.hexToBytes(&randomness, "6464646464646464646464646464646464646464646464646464646464646464646464646464646464646464646464646464646464646464646464646464646464646464646464646464646464646464646464646464646464646464646464646464646464646464646464646464646464646464646464646464646464646464"); + _ = try fmt.hexToBytes(&expected_ct, "d81018a94f8078e02105beaa814e003390befa4589bb614f77397af42d8e8150796f2c88a4efca81b8cf93c0ae3716c54ec1b045e3875f38c2dd12d7f717bd7fb701a9fecda5ed8b764c9a35d4a5c1d8930f6071f653eebb2d1afa77debb8302d16f17e0f5f3920a71a4d49beafa0e1c7e443f8abca64a65a9e81a97e7357bf902573363c0e1a12e5228036828e3f759121fada92441fe334e85d79347e470d2fed945541d832c54baaa3cb7526c3853954db4f73547cc7c27fd38398bfa7704952cb841e38b270e4db7435f0ee22f57d7ad3270bd0c88e71b4b864cf2277c65daa10a6dad4c7abecd95cc4ebec39c08404b522e4ecc1545713f76bebd3b5a0f2feb3461936065dbd13f6a1f61e1b142a2af2e5a482ba2c50cf0317049c0b3bfd6d5e9240eba9111d2030fdea17e33b6524020d30b0c4f8069285f3a6ca267d287d01e827d8422bf5426e11688bfc73756af1841b1c87e126cb50c914b5b2b8673488ad3b074cad77a3840eb12dd688f313ee1e9ff8c479a678f276356fc9d65e1d5b4c1e9855b4175db144f7767c12061769190fe6b5e51563b91f94d131a2b796bd2980ed0dab4ae7a7110e920007a757158a5eb8662cbf89ddffe9d8196821313cdc00108853fc4746b111d5b56da638d8ed2973918960f5dfe93ead3ae521e957cec3c8d843e8fce234c70ad055177f235439d6098bdd771b1cfcfadaab4f50a7378185c62409f383c8ff658c2a2af66498cfd81e962766ac6b774e88424fb4f331837d0a28502708477caf8780a156d723f68fca791e1cd2397bfc2b24c77c765d9b2af36f732d52107517efd8157b283b440a613f756c364ca108971a8878199a93f260baec3e850033cc032c2e53f823576affb4d3b116e2d16049152c35aaa263ab376f0ad5ede6a749607a283e3016e62191c0e8fde33e718cd989591c9a205d608d99fcb8a7471603d716cb01b56328d7d880aec2851f4e6d8b5016c25647e9026ebb441543e8012dbfcf078d4012b8c39184dd64f3821b4774ae4e36365f8baf2bd1f6667c017a1e65ff8a1554458fb3f367c02721752bfa56fc7fd566ae95ffb208f919ef12f4cf8a2fdd141a8df559bddb7b8d1f04ee6d4cf7805d142989caf216dfae985faaab9974f6d9f8aa1129084db8db912b1655f595ffbaa66491ab4655fd734cfd4bb0c0289d4bcc8fc5e9943b351cb147c8db059a24004d1c3e3bb4c14a881e5101acb736c65c5d579acb67ee85a560277b43338fe79d34b772c5da001da3b5a3383dd81319a0b4542e6d7e46eed5314cc70eb231de27b6e760db598ba19995cf69be0e4458e35f3f274aca2455d43fe3344e183c6dc47c857dbe9907b41e41006d91b25adcafc098fe66f7554be8dad493c4f4b1dbf7a51464139db474afab5572f92a2232b59be56a72c0505149dae5cde1e602877037de7802b5f6fa47a4c9a3e52d6ca15339920254e9ffb53c7b834cc0288ed9905a1841e9390ea94a8898bd4c6b6d6027e4d43c7867242515bbeefe12340fc04428a824ea7cf56ad2a64ed368b71315d80cee846007cff1d2eea2c3f0f921537304ae598f98dd10d1f102811a4e2d161c3fd8bbb193d4b25bee950ac839c0f9d"); + _ = try fmt.hexToBytes(&expected_ss, "9bd018e869bb01b63fb8f5da374a73d347ea14cb2bc570b13d0908e2288ec456"); + + const kp = try MlKem768P256.KeyPair.generateDeterministic(seed); + try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes()); + + const enc_result = try kp.public_key.encaps(&randomness); + try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext); + try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret); + + const dec_ss = try kp.secret_key.decaps(&enc_result.ciphertext); + try testing.expectEqualSlices(u8, &expected_ss, &dec_ss); +} + +test "MLKEM768-P256 test vector 1" { + var seed: [32]u8 = undefined; + var expected_ek: [1249]u8 = undefined; + var randomness: [128]u8 = undefined; + var expected_ct: [1153]u8 = undefined; + var expected_ss: [32]u8 = undefined; + + _ = try fmt.hexToBytes(&seed, "0101010101010101010101010101010101010101010101010101010101010101"); + _ = try fmt.hexToBytes(&expected_ek, "ec7b50cddc8360f98b189bac73d395ef947b37d8453886a253269f7b18b9eb78c1b63212471a0f979793f9936b3f496f4b5394ea69c2a35729f91c688f6bbbb864cd5e87108676c4014c2ba98204f911becae33a71e832ac012bb827578810955f8c6e2d26c0b17b7ba574990884546ba58bf6785721f3854f434cfea602e8595c71642e8d4c70934b7e54c638f5a13e1a136bc86565e6b40abc163ca65650baf953de7bb99b138ac1b695023103c9b417853c9d42e54fdb816174659d85a783e3d4613db1cbbaa63fb667a4a636804b6c4ae821ac5d6556688bab1dc10d6779b485c63c0ddacb91837c4ff3402e6214188072b4186a39c65bde524c683c95d3c8b65e37104f551b6a3602eda50b787182d703ac6a221428b4553e3b99c2b251ef642e31256c329b21d1246a71456fce700d7f50cfe5390a1c37bc133809f102c22914a1402c205c0512b733afeea04411ca5ebb0bca9392b1ee23935eb196024732daa2a1f79358e6e74b73c965a9e74778dc6921442b19328f6216a5e814ccc0639a863a437a614def5a61f38852151011b04a37bbc78c1eba4d8d1b3a1622a0dff74d25c731abb2a5fe5919f835bd3dd97330cbb7dba0b74260c963402160c4017d92256a3713c9e77ea0f4901accbd38715511784c9ec287dd85a769e081854b32aba9322a3840f6065133228c41851afcb40ea509cfbb86145fb8853ce14c649691136b8660b0077f3b2f9da82d483c1414c39a9777665899131a8336fb828480986df102628d10b54239cc20231457d4bbb7016f76029661f14ffd3532e2f8494e1613430730ab915683c3c8c4db2b4373a3057a097e23333605398b15cc4d6ac3fbd0732f21026bb0cd51fb738a740467114e7c66256b830022f28c028392cff8013d617c77a47bbda11c4a522f8f2b49f2822cc06338605671fca4518df9b3c506532c9cca3175330f8733ce11cb3fd8b95239ceebc9483cb68bff43b622911fcf4a9c57c226caa38bf0b081535999f573016b14563ec4826dc281dbabc633868a1d903d59207fc662a293735085c01f40b5b56cbb795ecabfad709d611cac73eaca579768213c18c969c59be58fcef6bdd8a85192907cd0773f81eaa24be07e0d620e9685acb0c6b0f54b47dffb510384241c4b733fe08dacb2852b2b74cc014e974a5e9db35d80d7b83ad31da1487a0170ba7fbc1c551a6f1eecb572084180b256962748d5e3200b731ac7c3928585a153b167c92a48cd91668c773707c054af16aa7bfacaa161a620600e8d08cc97601a53391da0247e5fca60cd1bb65ec0417177a9eb78cde5aa1dfae34e948417b3cc0b223803f5f40e8ae3a382848ff80c4185824076423ae4c137bd30bd81f04095c20a01e0a49f664f8f2b7f6bf6a990993cbc0596a514ccebc578c6418e825903ae11ac52831c6a48c67727409ed7274eea03eef32094271b02d4535563aa4924a2666871a4b690540c78b06043bca31ca4e42a03650ecab74792017217d10615d0acdee124e3222c90d79362207e4f7779e097501cf140b1a3431ee0cf27b23a50373d59976d82b5b1ce165f4aa1361157afad564081c85777584dd6058a1a4663b53234d7264fbac6877351d1928c6780f77d47209337271e305370df9aeffb74d7c75de55c006e2b2a047a93f1833da6f7c4226f8c05392ba90626e4ed81bc9dff573aa243055f08a567e29577ce8dafa08297e6b04a779bed852b95ec0ac14e04b1cb58eb656455bad9"); + _ = try fmt.hexToBytes(&randomness, "6565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565"); + _ = try fmt.hexToBytes(&expected_ct, "600ecf4026683898d0e339eeea9ebd437a4a802952bf32bfa326b48eb74946d0cdd5437e70df4b6b7acbf79efe60ddcde985acfd8c2d23775e1ecc54eb6ee03dcc9b4aac150172737831adfaf0e63a4782bad2a785b9c39bf5e34640ea3da447efc2a03e23a337ad1f542c32c2eb46f7b88d0bfba87d8efe8cd4456e6f21beebcf3dd502b53d537395750ce963d289eff74621545d4a5d9262bd14b3dddd4ef880e65cbe8f2f8aad826f57f727a60aaaeee8c69e89af6c539fd4f267c44bee8b5385a460a7c8e4a809959df86c1136ee23e7544cfa7524c6c04ed9ec29aed307b5dfd0108f29294aceeb517a098b8cdbad5911bc75e96258bbf38a20288c6911b2346f842c0943bf8e9c34a0a8e518e92c8761c6efe6b1d3ea8aed9b2c4feacfbf3559f3a5e46e4dfddf81936183d4d1f9c8c1616b14db2057435ad71655d743fad4987a19e821d0ac666ff3e46b7cc0e90b85e1966962279a48afe2e9bbce89c819a8ba52476af074a071495398bd497f4d4f34026025452975cfdaa3e7e183a962bda009108221ecb20d218c42e38774019d2dc32621278b5e88f99b62a9e746d16c1691ccf3e7e9185c3c493e7617f451f632c161fbe6d8ac3217f10ed4bfeee47e4960ec4a53e4852ca0241543848422044a67567a83e09d8e74b9d11af17d53c49565ca53deda7c4df076a3e1b6368b1931d81db93e87f75bea6924a321376fe73b5a5b07b80a98dc3ab8d14732540f1b4b7176e274a905d453eac1caafe2bfe4e6c904556ca91b01b2302215ab3dfe6b49f46963df632a9e7cb8439cd5ee56a1f8e2cd3faae5d8f3462d0ff931f5038cfa70259d963163d6163ef22b0c32081bff2763e98da87817048d4ce755e5d2b1cfe7d6eeab0fdbe766c95f125537a04bcf99026f9bd5be3b26b9b7614f132f6747dd6d96009a85ae6cbb1a14b9231099b67b04d7849875b6492f3b6482f8bdac305f7ec29f28ef4739934c6a7a2800fbdbff6eb2237d6a085ddfab8519db1d2b1e63aa6cb9b3b044278947dc3bcd329aa427d13267f93a6cf2aee8a2ab74d4288fe0b676ea85586834ab57e863d4805703eb8bf6e71fbb11a386e7b64a0d661dbd05f5e2924f1419bc799a089d44dda9066c6c503f8c80be8daa99bd48338daeb4911acf19328103c96f40a77ffd827d52294ba21ad1d52fc27e8b12ad65887024f41e63fcfe654152676ac363f2377c5b0b437e075897e33dd8d57227fc5a536629efad998a279103150e7d47e4b7d11a0d649d146c6560c48c9c0c56c811cfa6f3f62cf717ae571597bc297deed887672d8a8cec2929c2b55b95f26bd5d10ef30c0c4a6295d5ca601538f5a20ac1064d2f4c2a078af1b1629a0c203ad047125eb9dce0d1260eef19cd4ad8ec5d73a01ba23e266cc6dd266c5a81af58ccf1b5ce0440efdd1fe7bb42177679b5e5095ffa0d453bc17b8921008531c2d096a3a4a48563370462a59fe1faff2a81603f2d09ca2e0beac44204a4e03aa852745b1747bb6c424206ae093ecb91789704203948060efd681edaf7f1ab49d5f631f1122d2e0cb452a2ce5490497b32df225372b80b86e5a710a64e8b4d92318414a179479b96827f3379de247163949084"); + _ = try fmt.hexToBytes(&expected_ss, "d33a83edb4fb5a7508686ba4ddff001c17ba8890e9e3be067d50c959b512f9be"); + + const kp = try MlKem768P256.KeyPair.generateDeterministic(seed); + try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes()); + + const enc_result = try kp.public_key.encaps(&randomness); + try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext); + try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret); + + const dec_ss = try kp.secret_key.decaps(&enc_result.ciphertext); + try testing.expectEqualSlices(u8, &expected_ss, &dec_ss); +} + +test "MLKEM768-P256 test vector 2" { + var seed: [32]u8 = undefined; + var expected_ek: [1249]u8 = undefined; + var randomness: [128]u8 = undefined; + var expected_ct: [1153]u8 = undefined; + var expected_ss: [32]u8 = undefined; + + _ = try fmt.hexToBytes(&seed, "0202020202020202020202020202020202020202020202020202020202020202"); + _ = try fmt.hexToBytes(&expected_ek, "08118d8819772292c976ec971ee3039195800c823544484595cc63450b9db9414330419208c509cb62626067a8fd8259160105b6d8a4023b056f5ac2d6159fa5f245f00c719539a4601466f6b45b2a68bdb0db7424d4cad475a8d68b0d6e086c3f012414e22900f01179e8c90a8ba1d285cdcc7c7ab1c7064e2c15233acee183a1075c04f092a5e3676b1ec06d15d348e7781346ec95806a5e00f64d1e101bfb28bbc6829372f32bedcc0de9a70a02e508b760422505481709bef10697fbfa219b99a815f47e4bacab0e789ca1e414db529deb043bd8521c2d456a062a65aeba2a40dc6b9ce02474a71fdf852f343b22d2110519955b964382e74a3cf78586eaba353b98228b0268f8480b02c4b4ee208198fbc472117c4be567858c097fb0869a40588418973c58753da845e36c0adf39c53ea9c8ae24c343bc87bcc3be69ea40d9490592ec99e9a853f4f22b024a1b15962b049a62bc198706e310c8524c51872718d76c6db25cd824a1b75a94a7d341ca40be1283b2111b687be69d38b7297a90384b0c0269ac911cd80384b326357262ac13680dca37ee18477ab23e16448805676adbe1a5b7732e73c0abc3c5188e6c7ada3c1f1f124663e83ad5899674253e9bb15f39908ba4917dc11025f7504425a305848661c38cb09a82026d20c9bd23949f285519db298418718023c8d7e37a5bb1062951b4325249aad59acc06b10161416ccc78db6c6c3f685ad3d9b4916c622163017f9662da4234bab8b8b1e77290867d48c28a2cb33c7d2c7874c2be936ca0d6ba907d6a823fa24a18c56cc124209ea488ce620c18d00ffc8b8f0c11cc5850c30b3a0fb7faaf6e526f9b08972207a38f760bad1824726017a30634fd239ebda59651b4c8162346bb3652dec39f56626547829ffbb052a5a6930f2700fad33fb1eca8bbc40fcfe778189398b5527a09a24a53a958e5c25353951b78d85916457c1c5046e497ae0fa24810e82d360050e4fa1bf55b719ab8a080c23dcd80c0d4915d8458652d476f50f3a5b80ba6ffa9a76bd9524dbb39df7826cc507a9d31aa29b6207eaa52b3e224259c4931b1ced9b97a42e6745752ac0603917a694d7ec95145094ed089008af675fe51b2b79970abc282dcb632c1fc3dab85ad14893d0ab63b9e21a368845e872bb1468aa252554f59f90c9675cd044b930e37a96a213a28277614443cca4317e4a2af9f4c7124fa76e1d48f38981d7df03d2bf840610861b210300156c3f999aaac1b2983c45cf0002c922037bb4055dd1c27df511ced577bb8046e003507aa7b2c52194680b93e2eb40719539b8a93b8e83b9e205714927264cbf0653d4429f504816766bf97f14141622e91b20177d98b8db9351b39632be41a48f39ee050cc1919143a2448d09c2fb15a680ebc07963b788480631eca37e19213dbb6c5e8dac3eee81b0292cbc681563d05779b79b5d8ec37b331b3c021719cb95e69ec99a0f97b723303278b9403fbaa7f71ba70d61d082c91a6aa2c8a3543b5281e1605832c740bf67036f2373571f09482b27272c8ac2c69b01f715dda83377aa093a044541f6000848ab1ea65cab1345135a4552be42b27c4b980065694134a90d436f0e091b02a02aa99eac7339907afbbc158a5127540423f23f6927eff66915d745f4d420459d5057631d94214461c9be803e9d6080e108978f60af4795fc34870e1c018198d85a4a116ee21df20fe81d9218ee22a9e81652b806a71aee14f84507485356e"); + _ = try fmt.hexToBytes(&randomness, "6666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666"); + _ = try fmt.hexToBytes(&expected_ct, "413c55d5710bae6376761dada807daffd4dc45f9f70d825e0d46176d4a342f58f20d61879215bbe4a774588838175342628a905da0dbaa1346e8e913f4738defa0768445f1c625d296ab06cd547b93e764a388e63815b588059796e9bf3fbead072727703b036aa73223007ad1caaaea0c6cc38d385beb06fe8d372e9145c08e1bc3cb5ccb12f450ab0f6f9da5529629a3f1ff6312346b6d2fcb20461e7b3b245a97a03ef27f2e5442daf2a5ea317f454528e9749f06342aa7594ea9bda0cdcc7c0953c36372359ffd69f2aedc1adabf8a3540e32ab36ebc1350aded1072afe3b78a6ec2f943d560f4849d6bb1ee24679e8f70cc0f4cabe7d4cbc6e090353ce8414a93de9c84a32e197a2ac95e9fbc5d616f85fe199e80793f6dccac203d2f236e7bad1a4e7ff51b3f3326a9742826ac6a23ef5a945aaffb54faf50a8f0b8c09c55cf2bc812e30fb3e687eca91b494785f121241a1ea8d0cea089216c5a96a467c06d4f0a10c2a6bf551637f0fd5635dc1734e96eca5e7c545d66435b8b5dd88eff4c2cb3c73c49dfc9e56c293febef797a7d36d21ba30361f7fec7b0e51793f6fdc2214f420b713a1598f4dda1a29f9124469407e5c5c5c908e39a78ea0fcfe4df3419692435a92e0f9a5846690706cdd23b1825be8d0a843756fd97b4f277cf0714a0d9da3ccf1a31a07178399b803c7b4837980bc0172f58716b3baee5e86441d32bf31c7ed6e9c6d55eb1ed528a4a306dec7f37b3a575086385a9f4641ef28da16d35578c743c8eccb0581b2fd308a3c9fa15c8319954c8f4259ab09f178508720ebb8a0d893a8c45ec23b2c1c2e43db439ff71fea6a9fdcd8a9d3c6e0f8b9e9e71ddc2aa52fc5cbf22ed67217d847e4c84b72e7f201aca56c7d1d5e51e0c03cb596a01d20203b38e0e7d3086c83a4a1930754134904487c43fb96deb449aa832e63a82d132660cb7976d9d50742641c28c8e2e1bb00a2c65e9f8b9591501ad60568af112a5cbab134bb472fecbdf24badbc6562201e022c23fbc6354292ab743a863a139dd4d67b1bdb553b3c57a5c7f5b98cf145ac142e1ad6ad5ea3954fa3c2b8ebfb6cd05b915dd1d87262d7ab1f1b47cc0a3babc15a7a1415976644c54e29338d79afa9d12a669d3c67bf70e604157815f041556a5cc1c8429880a5449d033bb3f1f2b879f0e689fc2a3e2972f75f6f25b95bead0460f35ef71d0bdba380efbabed6365c6e7fcf2e22361b572029f0c90f2f74c8e40c7941ed8b6eef5a722bf2e5141cf43ed2a69b87901d546a85765fc494531e61f3d723107659b4ce1f294c352fc45c28a82cb3c242e5d6b9cf43d071bd55b8bc0d47b225463a5075639569cb073ffc4e07417dbc5a30a8e30545264d64d98d13336fdb6bdf8c71041e995cd433a77a9d4ee25e20f757cf76dd702f7c8f22a2677f03dcba47ea1996b9d783e44737ec501a8c75acb6d7606a2b6eb1e069576f3a87b32e587923fb79171c77083bb629efda6b9ddc1d566d72a53161c165d0ccea7674e5b1af42b040bbbc5e8bc84bd33d1d3ce03ffac9a747f4c1993fddb2ec93a4116a86f022a77c3c17191559a4c2a1aa57e79b8d1977da2c959172f478e341e27028d69fffb7b"); + _ = try fmt.hexToBytes(&expected_ss, "d17a7ca859a18cfbb5fc7bcba2ec54aa0e9ac44959023199fff5b60db9f24401"); + + const kp = try MlKem768P256.KeyPair.generateDeterministic(seed); + try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes()); + + const enc_result = try kp.public_key.encaps(&randomness); + try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext); + try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret); + + const dec_ss = try kp.secret_key.decaps(&enc_result.ciphertext); + try testing.expectEqualSlices(u8, &expected_ss, &dec_ss); +} + +test "MLKEM768-P256 test vector 3" { + var seed: [32]u8 = undefined; + var expected_ek: [1249]u8 = undefined; + var randomness: [128]u8 = undefined; + var expected_ct: [1153]u8 = undefined; + var expected_ss: [32]u8 = undefined; + + _ = try fmt.hexToBytes(&seed, "0303030303030303030303030303030303030303030303030303030303030303"); + _ = try fmt.hexToBytes(&expected_ek, "04ccc68ebb7b60c6148fa94574ebaedb9c5ce5eb1ad69cc5a4d7bbf9a410295540bba271ae8a178bd025ea28887ca8808aba8fac7a44260c6e39b883110155d0dcb82ae7609bc803a8f927dcc2c2033cb07ba90fc8b068188a7214e713579537e69801ce304251eabc8b34b03ad71246cc22f7671adf572cd6a2730976953535c59d7c93baa5788854ad52f48e9cdc8accf4a3441392f006378a450289053c2ea689a80699de7acc6bec4c2b182e3194acc9fb1ce3e1b389c5879ce66bc692a3b15343d6e557ff93127ae69007e6913d51ce703898c5449ed360259384819529c7b54b9f5b6c9755d0a627554772846127c8a580a5b6a9ea6695449be144306d64cc8102127278827dc25da96b6f4823ce5ed25a2b199f3902cd8bb82058d4694b40ce0ce1cac1ea8a047a1ced942d78ac2923c9ced1d563fcc24895b1683615b134856ae3f36e4b382bb160171ed400902499c6a0b1b8701d89093d6d72a992c9ad296308a2d44d26816b1c942a3bc479979516a0dcbf16bbca1140554be69885a477d078ac0a267e8294111cbb42bac3cc733269c7f84bf315258cf8cd04774f9ca932c7416785796ecd781e17b6ac32277d6d141906811430da866478499a8c94c521aa4d4663af067fcd2c823252c1dfba800c7b1681955de39112c2fc5f4a14020469bc22278a84494806d43528f8ad8efc88643038d9d71c79a50710dbacff686396c34f9800b0671b0da3c2ad4dbb3751b3b3749b63e132a336f19ae8f37a79f6c713c48b724c80465b28e578658bc8329f0c1d75d0ab1d632027a3cc8d749216f50440109ad7637f3ab1661ee7637eb06e70d4c15db81a2ea25ecae4524476106210362a7b35c013120b51ad9b2c88b452457b75a98fa3ccdcb183aebb1684b72c3fb5434c277907680e8ceca5d2fb88dcb43b050a5557b19d159a6050a6115c1c49232cb5150463e165acd4629ca4927e3ce46ddd1a733dd23f77b44dfb7b46ebe4291594015a4ab5ff00b643321856b3b062ac28f2c4cf7b30716b6c9a53271139685b3d2bc20cf5c766265a6666652393af58ac25efcb502a30b9f6bc2524cc615a911b1de63bf1710f9f829c91a573a960a44d43138a65c42d41000c601c7ad1537633433edb5023387aad5007a09827a55b6a7e7a2911307e0f226e9e52ae3ba4c0f14a1efe9571352278c5a4a08cbba7403c35ec69cc2a2b364ed145b11a4e8594087236a4ba7a2d003aa89fb2c97580c3ce115727ea0e99ea81cc8354164b0602e53cb33638dad5a39632b70f4706eb8bb6d598c6717627fe3c02420583cbdac9460a9d55588ffafba8cf748c16283b0e96b8f95100caa7a8d12029a433ab4efbb9b2067a0c615d5358aceea04b4ca4bf584a452fe2768cb5bab0fb52aec375c3337fae44b305f1389f123103669c7901a1821c4715b5cac1c89389cc9f4df192cca789ed236f7b378fb2915106ea3cc1b90ce9d6c07b36c4f09b5654e3a116c60dda685b9ef6893b1b42348a3ac87284eed683fb346d25a562b90a778e6b546e40b4fd7c1d3c01983f851474c3832297019a808ef64957d7902578981d9231b382169d533a2202e95d18f152e8fc25b615bb8a65482a2167df6ab2811c73bf8fefbec077f1e668494d422443a083b9619254ec3e6bc6f3020478cd35c1616ff29a1960108be12b65f0a299f3e1a5efe7664ddbe33852e4805aaf60435efc123144540391b81683b5712fefe40126d070b4f9fc5e409da896a4"); + _ = try fmt.hexToBytes(&randomness, "6767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767"); + _ = try fmt.hexToBytes(&expected_ct, "f40d556d507802ebe7be0e65ab82b0aa9ff26c825d24f98148f75d7e910d6c7f5852e7023a4e246be81b807fea96928da6a8c4d23b8326ea90aa7f1f0673a57277bdd08960f7f886777f1725b882e1996632584fce4356f7345abd8f480514275fbbd96ec6d3202de46e18e7a827d4dc62c1681c36018861c33cb2f4e13ba5da65558441caa6ecf82a5e74842268f4ef5036deb2f0f6f5cb422dc2b723a7f2f69830a52326621ee034c8a9e80f39070456bfdde653314040b4f41590723b66a2d7e41153932521e8e51108d2d98841b2983baa43b0dd6a46783be063850b22a2dd05c1f9f578360dc3dfef4d79e8c20d2d0c45687ae19395355e7cdcfa039e3c36b6df4ea11b4122918fd6ec63ec672ace58a8ed9dfc61e7d3b5829d574833e0e61fa08419cbda05a85b3c4b9957a0968d5825d0d052013e75f138c8d74929a631a0ba2ec9555e2767f17e6e22890a5cb00f63f09e00b8decccf7d7d0e369c4396cb429e53d8cd4636ea630d6fc55143e6146a969ed0839ab05dd079da3f946b3deaad2774360529f2aa7e6c400b66a5c449dd8362fae1a1bbf110229810e0d4725cfa2dfdc046d4c18637e1de3ec2b52055c237238de0167eb0844c24a8fd91ac9f66f86efc945c0a50672926efab53bd0725ae9ff36e9fcbecd58212cbe7e0f248b9ab90b2f56497f196198043fa10de909b05bf3dd1d20630f9707095f4f80d044418e67ccd79e8f28db7acb1083a2bc63233a4f4798f13f21e81da6ee03c614cb367aff05410960fd366df06691b374247de70fcf916e653b2bf01b49cf116324e8104da61a621b566a62c97c6c058208e4825727cbb6c2ebaa0e888659094aa03709659e272b4209c18366196110d71120b203fc71dd5d3c17be4580e5ade64a3fdeea5b85ad33fcebf30b857dc7cfe3ba52ea8269cadd7dda308460201e0119f8918de8980f04b318f39487e65ef0e0b83c2396d2fd87f4d54dd00b405063f072659d6b11513f448b20deda3d874987c252b7d16d94c4f811c97134e5e00bff8300e718e17de3735bb4bc052100a3823f8db4be2d7554003481ce6d899d74c1ad9944c01d933305851458933b3f780ad6c1db489da507621e39be174c71f73ec9c1ef644578bb1566136f17e91b475fdbf354cf4f5a6ee300d3938f5b4a7b9bcb90188a3d9c8fab1326df69f5c3753a8ff9c5a7bbc4e2255954dfb6a2ce81381eaf9d224005e050eb5f53d05f0a41bcf4f3c0e8771e84eaa46ce27b0438d4ce3ebf9eb25b5351643a26f607c41b6494ac77e4c4a2fafac8e3cd872f31816501efd66c41795fa0d01ac0290253cf6c9d0dd8d5865684c1a02748a824417827ee374404a59ba87c3ec3caadf08b0f920667fae9ad18560edc3f8571986ca0bf1ef114d394c08ec5ff221e9f9ce7b6508eb6c38d6041fbe7319bd8874f35bace85c0bcc08cdcc642ae7fc264b9be08a26e5ec6a3618a078a128d0b8daf46e404eee4123b379a81680c8336036d9a44c12bcc23ed7b1b96442108843e5fcee042394b456984b83ea7859f7a0fb6475d53b4fd55236ecf4f33183c1d719ee10683a6b689b463a823665a40b63735647c2930f3c4b1d8787d4199bfc2db8499e61"); + _ = try fmt.hexToBytes(&expected_ss, "b3db69cc0385b1ba716128b70bd4b93288f945f418cef51d122ca7a4de564a81"); + + const kp = try MlKem768P256.KeyPair.generateDeterministic(seed); + try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes()); + + const enc_result = try kp.public_key.encaps(&randomness); + try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext); + try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret); + + const dec_ss = try kp.secret_key.decaps(&enc_result.ciphertext); + try testing.expectEqualSlices(u8, &expected_ss, &dec_ss); +} + +test "MLKEM768-P256 test vector 4" { + var seed: [32]u8 = undefined; + var expected_ek: [1249]u8 = undefined; + var randomness: [128]u8 = undefined; + var expected_ct: [1153]u8 = undefined; + var expected_ss: [32]u8 = undefined; + + _ = try fmt.hexToBytes(&seed, "0404040404040404040404040404040404040404040404040404040404040404"); + _ = try fmt.hexToBytes(&expected_ek, "6afa82ec0b8e8449591cb9a11be36dc43146c18a6ad3f175897933d25944c3c643f50332f1cc4f6a9707a387b5c40661ec41b36d2bbc4507758387672fb4a1c1196eaa81b01be2864cc958a0642496f264b4042ec5f7b25a5220a8a9c9633081aef104ef9abd4da0af068a84e7b03f21e6b60212a02dc48ba7865877084a1b025443e38d60bb6fae8ba88ea4538d088bea758f6f9a5c26a9c44699ad43080d3fdcc2d910b16b297bb86436e3d5b79dc01230124867c1be64a9cab7aab9fdb47eadd15d00ed84ac9a37406a3665d7cbdd106d1e6a29a81c94e3c841fe9cb583004aa471ad2eb477623401e6959a04439bae06a07058cf2ed94bce1839e636a8c0b22f782483ce798e609bcd784605eadc0a06c7699b1b9ece550484124095a13371028101c95721c2c71b098b15f3998785b361924f89dba9f292b9b18cbe232cb76e41bb5b35cad976962fca2957eb8e06356c250525e8825dce66478fa587ab6b429bb287aaf21a58c900de0946ebc284873450c7e7a0e08c856dca2191195f73b3aecce1b1c1c29cb24a6c2fc3c14ca1495182645cf8486cb877d067440f118b80713784474bd0214f23fc09e2c8b5df357b3733c13545c9490378291646fbdb8403818f7d571a40ba7f5cea480fa00ba15373c60255e54384e96c455da13fd7dbcbc1e7a5e9d655b0145976e190780b55c9c86ca6c988d1230aabc8558ceca8d5cbb844ca8d95b30b64c02440522661c9b07c866e497b5a7daa2398a5a888a4244d90210105460fab31c6db3fd57941b869c24218b2ebd7ae67885e1fd691a3dc4388b872f4cab5fcd94067ea953d73acf1f4c752776357974b0ad7872ad603c30a174f963983739e5fa5304d1705c6791e9c90342683a3b176a2897ab9142190b3900d089c77ece41316b40611fc6bf7c81eb7b589d65524f9c6888fd7c53ba810b0c70766430638595dc3134ef5da09c668a804ea1e2f1469edba0d457acd8281828d02a915bcbc05949d2edc5c1c5856fa798e5cd3587472903c6cc2bb43b03229352587cde988ab60049b6f872a9aa82f25b5346fc56fe8686423b27e0f368a43d36a21602b4056022d25217df44a1fa330122152cd2a581ca05d3801bf9f0a31bf79b8a7394fb8c9bc1652cfea9c386ca7c703b5754c8243f7b0a36bc290e0733a17a94431d7b05e0b4298a8b287fb1ad3b32a57693a1e7867b0f3c483c0abdd2c05c81c26a60320d91207f204c2e8393a78e7572c8c296260351eb62940c394943c6a622a8710aa09a076b513dbbfddfc3105379da08b97a6fc68f2105088cb35f9683144ba24673513f0b6aaca32c0e77173d7387294a9b96a08a014ac88afaca949c2c3760b461dfc1ba5c01a956cac46a01dc7d9a992e1aa704aa15af525911857d46682b137a3719aaa71f7a8ba88afd6d43b127805f6c2cb55f88d6c590775577373ac87356a56b89341d0e8657737249bca66b0b4194a5c13dce045039164fe1530e38981f08c66ad2bb0531c3631a86f6cfa71571b777b2461601bc7a146c358955a5b44130d26b5c35322138133c083aba300a5def2c4f0d94d4267a2ad23420b768c77d8a1f0428536667d2df42127012291511a8c72738b82f0cff83b9315330886d611180d2383cf551afec4aa7515eb138a398eb44c0499863a8fd0da2530b7deefd5f1183646010555333a6cd0e6ad0d6bf919678252c49358a35322a458530b7c2b60f3b170d50a5892e06a9f82ee0e34d56c99312b"); + _ = try fmt.hexToBytes(&randomness, "6868686868686868686868686868686868686868686868686868686868686868686868686868686868686868686868686868686868686868686868686868686868686868686868686868686868686868686868686868686868686868686868686868686868686868686868686868686868686868686868686868686868686868"); + _ = try fmt.hexToBytes(&expected_ct, "cfe31862853164950123baa75549418c7a406cfb60003b825eb292f1ba1f25c3b5b044dce0b8b16cb254d69658d516cd3445e3f18bcde32c4f46b4ac25dca4c4573647ab7cefd7fcbb7c192d997194654aeac0f593eddc4464e7e125672fc7265f1664b32199cad45095359a4dd80e9637f0140504a7b1303fa69d121d2d214d5ea44e0046a120fef7d573016d8edf0c20749f05edccca4a30565df6e79015f04b03623d3aa25cdbaff330633470c0289689988c27fe49acc957d3be72c4bce05f1c80f3c2ad5b4e8a2714c1d1ea518f431f97f6d68eafb06ad7226a29a2b3a9e5403cdd923400a4303054876986f834848a4902659b288d5e3ef26a9ecb3f1be3630037d147301498bfe198b8116f244a12547ffe6a5006f748c0485fd72232e0c55df090011946c8b493f8aaf92de07396e901fb4dd8a4f291645267e7ec0335eaedeca28ab4c36328c73203dbca87e40dcf007bf8687dd4a776151e9d234f52442a33c7566b007f6537837cf752602624030615d0cb88238b91f578e0728b32734363944bbfce5884dc3c777e0b3f1ee4029298895e6b82f6f2307dfc267e27ca8d7d9b49b90786b7f39d906ca7b6527d5e31316fce0214418f95ab9504c98ba9e868754cb813014cbb196eac152861af719c7632710754cd2c72544c25b66d1d016f97a409ecd11577a358647e1726da16d0a0e2591eb3b7cac7fe47fbeef10c6eeb9289aa4154d42ea75b864e0a1215ae35dd0db3fbbef39ad399c3d04d0d4ad9f2ce442bc07dabd366307eefcb2ab483dbe80b3eb4fe966131a587ffb2e3664d31e2c520722dc1a1f5d27ed0e937c4c89963576cdca001361f11bd39e2e2b367943305fcddcbce6460a8bafeff8394beba9bb893f1a7abfd2e80bf10f0546e72a4051883e1f7edfe12ee1505d9503a83ddb2b998cb2475b88d280f1df688f472968f8c718f1f9fbd39fb3073312bc54c755210d0ef49f9f2bcf06a1099132a1e08d84b68543848e1538edd881620560e54d8d6f9a71ca2fd44f8fab9d094f1dce52f40c5aafe1f73e98a2a395f48d5da98fa5c95e4afaf84e7a138807f71eb64fcb3b5169e8bedae1dffd725ad6fba9fd50f39db9432cd5f379b680c09c5313ea73517f017ddcca33a405c0ca293d8c34714aec241b9a634ec65c8c0b56e59df8e668e74d2494bca14d8102dc0592dbf93c0ad5f9dc89ac24a7e981feca0461d3fce1eec98a4afbb0b7e6c46aec385c9c0fbc203264aa6aa71c67fff159ccbac01cdd85c28835a98e9b7d0cd4c4330a0a5334b5838c072df4bd7297251cbf85dd8306e1a8ec893f4b9558133e20e0c9598f054e2b0b77d4494c393c5dd0e83a6520243edc56200f1cd34b8f69d53e4a823a46852e61672ba69447ee49522978c64616ea42a0b0c6cc953c748a550dbabd74010cdb8fd19c8862473f826267c8cf41cfc36100665ebd766390d83c1b2f795cb6d3c38dcb8e98c6ec0cd5111108a079d57f9b16960d94a4f238a6d7a25b6253b0ae0088ce414406fa7d02004692681e4d957786be9fb7f064113526389dab6ff0a2dd6dde42c5e579bf996d6ea33dc52c8b15c92b6172be0781e33960066650088274ae577e6465e8fcfcae7"); + _ = try fmt.hexToBytes(&expected_ss, "794e2f99d9a890c338c24a018035f8f51db576faf0cde96cf777a81bdccce864"); + + const kp = try MlKem768P256.KeyPair.generateDeterministic(seed); + try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes()); + + const enc_result = try kp.public_key.encaps(&randomness); + try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext); + try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret); + + const dec_ss = try kp.secret_key.decaps(&enc_result.ciphertext); + try testing.expectEqualSlices(u8, &expected_ss, &dec_ss); +} + +test "MLKEM768-P256 test vector 5" { + var seed: [32]u8 = undefined; + var expected_ek: [1249]u8 = undefined; + var randomness: [128]u8 = undefined; + var expected_ct: [1153]u8 = undefined; + var expected_ss: [32]u8 = undefined; + + _ = try fmt.hexToBytes(&seed, "0505050505050505050505050505050505050505050505050505050505050505"); + _ = try fmt.hexToBytes(&expected_ek, "33393446a76c2c01b44da5696d8452b29ab7b91787765366bc1a15b149792427844ff68753f957187c102d0940c498b67332a7f9cbb82fd3bec9914e05a955035362dc7462071c19e225ac96c12fa34a2e86e0a84a0c633e044b3d93969705833a53a4257b7f4c1515ceaa8af9eb7f53ca7cb402a4aba11c9d3206f8209c436bc3ea0487c456a79e240fc4f5829e4977817aac9de9a0cf6cc28131be92c2a6ec672bca1a8b3758092f8b87a11565c0312e82939f8fd18024a71bb98a885c6ab99299cd43a6208d2198a45bcdbec590157375c16a456327bbd44011b635cd1f678277347aa5a765b7208acaa4316ea523f59a0671594e56121654f3581e882072396932da2e2ea02bf5489e546c452b7c6f60e8a4c6112f6ccc7e3a7a44748aa1308807a08c15d78a2e19a2750a916b8c46844b257c2663a8895b3df8c40960e6b912623c8a92c9fb435d20e6cda9fb456e55c1dd90a5f64a5ac2c6125046175751aa742971f1728328c353f4a7a08ff16b8f978ae41724494c436471505e2c17ba958ade7b2afbf9cc2665518949755fd5bb8b15ac9b80b78fa615a918c4a99549c37c6ad0017c51c3963e3408b11848ddd4afe96b67cd66c76e817d4c150de52a720505b9a4b09008243c0fb05ef004b9c2c223f9230dc57b1dd3d1873457842b47c59b9a6b39d7b61787b42a500c9f57114ec737bf7c45303b8a3fa78b3c649cfbd166c60c37dea89e2df71ce4c605fe45abcaf3a604f548ee5030fdf49ad4129b94516a22a8b728112bfd209e5a5b565e20a8dce97748a79063790bec93bc89103c32419bbadc1ffc679e48139a0c88563f170d165729e3d710365c404dfc87d7c31efd24785a9265e55c4a624315db4005019818a1e7c0367a70fbd16098e27a640b0f3dd9515d82be0637506ff171c6a31edf453b40a6162feb7c3543506bc1105ce196e46097db749b485808dcc3b4221470ac21880bc23c7ef4b315f40df40802f7324957b450032c3104f871250590a4c9a29687c70e02b46350781abc7c2b256c54bbc96eb20b891877c3a673f67c18197a0e3d657196d5673efb19ebf9b3e49691612834cb662d2828938b1ac1a45881b9e51c786c1a321814a8c396421b7f313cc2e4447e6e87971e1660d94aad5f13b26bf49fa499822b1959f48aa88dfa5101f463d5bbc8ca21b9d0857fd2e42d5c1c7a9192765a3013eef54ca9e43fa99abfd8053e9059650b964d6aea8c26c13adc0a6d91208f2dea0da4d57a3a8a6be9156a4065058cf965da10586b0945c30965bf8a436aaa115ab170071c3080f5c723c10d05963a162622438682fee1b619c2b10c207ad7d38ab1ea7eda0924d9ab6b443386af7b07fe26b8fc6911789666bc14619a381eba7966e0b4ab0e528083806f46456f97e59276d5a6119b766b1847d3a1ad3c4022cd0616bbc348d4e072f7d68272617b03bc9b25f569d0b11dab8c5a7ee11ee6c761ee684625322c6cdc815ee1c0acd29a8e1c826cf89f25944593336643a46483dcaca9e1bae9a196f692ac0532cd91113d5d974963a524bf708c44e198dd212dfcf69213a42e32e6b7c860148ed38c51002b83b0c4b979b52a753bf2c3381c027623b8a8251e60f55524b56b959a14d84637086a15567717dbda7aff9fd61d09589ac8c0044538bafea1200548046b8fe3681cfcb43a8a1b5df785f3b0cc713c0bbbffd9fd691df9acd2258c9671a14b4cee573e711cf6d8eb9ee01f9563dea7438d484b5d"); + _ = try fmt.hexToBytes(&randomness, "6969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969"); + _ = try fmt.hexToBytes(&expected_ct, "6dc476cbc91fa3895cfd69bda5799b95b7c0dc87e5c78dec9b367df58abf3e7852afb7b31caff856e5e4136a23a24f303c6100d9a0a6673164f6f44ea472f4614c2b42496d0049a27e02f1aa45ba04442a74b5e0035df3d8877266a4241e650a86ccdfb0c6cef22c62f376543bd5749353f808308eb74ed031e73c9f42ebdc8a484b30769a17906e39fe8fa65c0f88fcc3436c0bfe26a4995d82a41c6a99140a36d166cba6ad2dd065ac19cebadfd455c9fd5d15ab2facaf9f5c79bcd12d31ef88842403ebfef226f9a1de78d18d2a0a57a7808e5c4d9c7c510bd1813db60a2e0121e72c52edf77084222dbcd94a10cd4dc3b09080e8e5a8971761cd46ded65c78ac4d896d818a400ed832830570d492665dec3f8c11168eee66f33d0c13ce3062252f107a3777d1d91234f58598184ff827f6b2e73638fce0e4e51ef704e1d3f8dd5312191e36db31d82b2b76869d7eb03474ff616008158f3bf966a236cb3e8c52de4d010cc712abc23dff6f5ce00631fd3dbaceba030c2ec18fd0c39c9c60134a4dfca521dcd71cda1179d8ef08f06a42686c360c9272f15b4258e78514c8814ad70a1092d26557a45764041b8324bbcd69b5d2868a6fd96c871cf83413113c898e4bb983187fa4c1cbb73cd41d3d4f4db185047afdd241f1b7e7007c00b03aa8ab836cedad6127938f87861108047298d3d945b343c62e2fe852a0354ff31dadc1ed08a4ddab41d91c0262283b11fcbb1ddb4e9fddd7cc338e925cedfadf4e306f84863fd45a70f57df0384e1234220103b0f693144a5ecc9d99ab1d6f725740d1bb09c3a4b4ea614ac01bfb1288bbf14dcd572ecbb6c822fe541b04a0d6498b5a14727c2d543c9647277bd67822a5fabda4a98f23ef50ad12b321377fd4ee24b234b0f296049906aced9d08671de994956a2179394d37b585a31e405325ef880a108ec492c1c87da90adba72d8be4643ccf29431cae053f9cf5271f7e1c7a4de093ceb053351873d657737ebbef086640d417c6a05ae7fa9e30b1129213595290427032f26ebcd1546b9c9c1bcf01ff3d18fb1ebdbe0fee540f4b5318ba65877f457736d30c750e42e0e773aaa6e526bc6143ef631f6b3f8811a026374dc28e06c92acd09e1f3f97c12e512b778038563b67de0d6b34a48157f1b936abb8b9f3da3adb88b5f36d0dae48627cdc1a403810c816e32e0d1112594a4f372e9abe4e6721ff83f488421022189b82e1f7f27a33e4d11969522e91d19fbe1a3b9eb6eb3dbdc2b199db9e86e56bf695d0e6e79457226b2b3cefdd2ee3775dae268020392f0336c6fcb3928bdfdeac71246343c08f2be397daa1fb9c252653307d0f7bc24111ba8df6ab29923b4e7f3471be2923f46dccabb2063427f3d4e53baf6a9ecb88c12d9c233ff14f63ccfd76a8f046cc2d96733e72d56c835dbc9a1aa2a5bdee915ae7eb3e5dbbc8ba24df6e634bffb4364a81039b06a2c5cb31c9cd8985e7fe2985c3e0a8f1d52feb2bfc3549c3817989cc1746fcfe8938888e12274480491e5606800961dc7aea82d793c00281add579d5b6724edf7a5baae285fbc7aebfd9c9304f962b145573b14191fae24fe3a912920593fbb7cc14186b3374849b7"); + _ = try fmt.hexToBytes(&expected_ss, "6f1c317d16608f309e6b59a48be62722081f4e2de9e2742a6d0e94679ba856f7"); + + const kp = try MlKem768P256.KeyPair.generateDeterministic(seed); + try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes()); + + const enc_result = try kp.public_key.encaps(&randomness); + try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext); + try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret); + + const dec_ss = try kp.secret_key.decaps(&enc_result.ciphertext); + try testing.expectEqualSlices(u8, &expected_ss, &dec_ss); +} + +test "MLKEM768-P256 test vector 6" { + var seed: [32]u8 = undefined; + var expected_ek: [1249]u8 = undefined; + var randomness: [128]u8 = undefined; + var expected_ct: [1153]u8 = undefined; + var expected_ss: [32]u8 = undefined; + + _ = try fmt.hexToBytes(&seed, "0606060606060606060606060606060606060606060606060606060606060606"); + _ = try fmt.hexToBytes(&expected_ek, "c3f9a6305222862c2c89ec0dbd805c547b2120405ba8e994947b1d74eacc58277260ba837de29a0a2b84a7fc80589334fb2626c9f61c01c88d60b20b0728110f0840fc01a6187a506ab23d2b459fe0853c44f86a699533284577f7913b69898361bb67d3711680c6c25f2524c56022227477f28a194e90b4702901b0046ba798cd1b843192f3bc841c8f9748a1ab34b5336b12c58585fcd010bfd6c48e775e761525b260c7f6e646e8f17690aab51bab30c9763fc53b8547d0b56b0c82c458c61d84587d989785600b8e987a36423d388ca11d61765a01a4a79ca20a925908494eae0a3b6db1b08cbbb7a4ab5edcd96bd76725e928055b0c2883ebce0a59bc9ec282cf300ba6c5380d1b70f13b2dc6f616c76087a73a70daab7a6f97171ca5997bf40e87b95f33a53347ba6c7ba89bcec9041701226fda1243f4b1fa047740c7b753d630fe386f09e2a2f96559ae979ed9fa5a78d1310ef311f97cc3b6a738bee45b75e3a0cc714c81f72187b105a2120b9beaaac116bf9835c504713f2218b526f13d8a8c02c95aa0f2927c27e996e4564578e056227b07bbda8af0cc5d99f55eab9868b8eb4d1b14065843b229ea3e2a0a748d89c4c304198cd44cf77180bb8664f9aa1a9511527233cdb6826976d34276f62257ec382172bf97b310bdd34056ea61f2d74ce1cc9544b02263cc3d3133a308911556345a17c32acb470444f5536fd21f81cc3c968ab7e4db9cea564c55d3a601066375f6417691c0ee3b39228860f1bc00693293c4bca327176da52b7282495bafc2932d1154c03672f4db49bd40967ca41fb8f8696382a5208c5547640e5193459b3507f4817ae3707930508969a18b1d168e67b5939532160b50a538ec9074e733dfaa4af09755f831720cec8519f3bd74809539347b8786868fb2ab9ab742fe020c87930d37c45aa9b836c986ba01804a5de26974ac647d8b921484b748d9999447875fe2746ad1112fc57ea3999f5899374ce2536c58310033ccdabc75d63a641a5651bd08079a41c1e59894cb3a1c8bac7614e960581a1d191a3a386209b6e82efc92b655c51d91e11e8d39190863909a97cc6017af2c35500b860a21c9c48d48834eb561f58c683372968fd89be52a26995317855b3123acc2071683ca669d78c3cce5bb9c9f9429a643bbb1401d1d665dbf45ace6524cba3b0d33582105dc7c38165ff0959d325914f437a4008ca751878bb8287eeaf092021b1b3647bb4a9b562a7aae80625739f66b636c309a09376ac4cec6d76ce4629aef46ae488b4c6961069527594e0c22ec55226f47085bec5e0343cbfb421ffc838b56e9553e411ce39c811a1b4a53b051b404af3b58bf651376360c040a607f946c6357020ba1fa99daf19ad3f020431a98b1647040038383719ff9fc7d4cc75d6bcb6d4f00c834874b01b9a85e0c0f5c632734a3264d156c973c14cae920bf0917cccc924ba90fef187f9783119a7a268621c7f29bbb28da1cceab369609c64efc5e91a79451bc8100eabc85541d1f2435b96b532fa14c1cb76b99908c418118e2472c66511ab558aa06f03fd8fc15f10a2099dc4df1360d15910d2a7747d8abab40fa3d77b7470d8477cd63024aa40268f55139d37548223ed5823f136805752c0b5f7f23edfd90a89dd1f90ee344df04636cb97be2406e5515641b4c5ad186a0789e8ba48ab830d4f570cfd158fdd63d38de1e9252baeca9a76c4afc7768266428caae913d203f4709ef6dd7e67544a9"); + _ = try fmt.hexToBytes(&randomness, "6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a"); + _ = try fmt.hexToBytes(&expected_ct, "56a06e6443b53c24f647cb6798257899ea1c49cbfd4c4e3e54dd73a1139dc4df519067a5934733a34538f941cd9a2d9de371623247f1bbcb728d8cfd59b59d6995a2430ab14490b336ac502cd21dea47dc7c89fa026310ec05fbbe50179b45e4b68c507fd90fab06e36fb1c9b21c06115fe42beba15083385764cde8c527c3669507bcaded45f70a8cca789ad71e9087948e4f1d682c4a77ad306c25b1e6364f43fb51c0c5d72f455da72f46041f8fcb143f67c7517b425e24cd803032645cfeba77e0d8d2d0cfb57768b8fa3238c10ebb99471710973bf9d22fea51471354e53f153dfbac695a6f8ccb10524a0453e804bb013b450f38007fe21ce898c052a132ea74c9e69512ca4c18c32ae37884df520d12721b95acb515067f3516ced73c930786d6924cc7dcc08b50a3abef4e0df82aa0b9c7bc199cb2dcbc7cbaa38ffbeea60e41ae20e8400730132f3627e1f62fc784f222213c2c479c4ed1f2159f724eddff87e6a4b5b3445e189590de4bb62bf66e930550144b44e23d6c045d779ace9f94c0a688f729a0b94821b669b5ec49652e55a8c19cfcb4d389066bb3aa4d9d7195d30c496ec4250138ea1c335d6181b25793ff5d0feaf91b7d27c826ecda49a9b9c8ff880091963a2ccbdb5cb45eaafbfc8b93e753bf323e2f76b0a61af96997d97f429db90898fbcc2dddcebf04d0e2a3d688fa56655b41b0a628e4164a31308799ea58984d44739d9720dcfdf4bcf2808217408c433d263787405d11dce0854fbafe59f58d39a2995b991187ff86d3906e1e27812d5acd2a8d11b95ea20c7bf1e8ca9146cd9bdd10f899e3289bb1838191feab937a6749c965c01b41e6febb62b515efc416b9470214d78d4a47bde46b0c8a5f8c4672e8d95dff71b9d7bcd2f94c1d115461c5a847baa8ee4a8a67dedef4ea40403326710c23394b7b38e193b01670669f2c6e06c7963da8debe103a33b49541fd5e0bc77b916dae2244d36bfc3d53f0f1ba51eec5a18c2f258edefa2946f799b88e17ea475e8bda8cffa3531ed246820882e32f7ae0425b129a8b0d20e6205d1a7f85bd8efd9fbc7dab57eb45d187a25d29b71d69254a36a5125b6af9db41caf369183987e0f7077253969d9d1c0887a35fd2ea34eea81fc930b1a1aa11db1e996a11c49c39f570d0e3cfb3ef5811a678dc4451c5cf64898ae3b53e417c69f09438c997fbb453b9d88a1cb603e2016c1ee79a36f2e9df75f65eafa3db33f0f377c71e72f77af85a0114306e1b21921d532b6d140e3a2c5a479f33c1582386c70c75e84765735b610559e8aac6149ad174213f2a1595476b29075b71a025f05fec67fed05e5f193f2b71e0352755ff4aa61bc63f02405760333516766906158a2cf812fdd5d11eed2ca3e440458ae1bf76cb642db5772d672d932a336cd838339d9df2dc2459a15c173b9e803a53112a081e7c2c945786b24e6d226aefbe7cb184a23dd504813975e25067f33bc90e13ecd4857473e0bb0fa33b3f5f0255eb51da214717511378927c0d04d1bd4143e815d832d69af2ab208a19243a0371c1c70ad3821863aa851b5447c53c391417dcf8e0506aab617792f24c80e4122f99ebb25341898e30d8dbda4def"); + _ = try fmt.hexToBytes(&expected_ss, "e324b3ae9780cc6d0a54aa4262db9c9c5948ebd45df2f3d10eb22ee23bdaddea"); + + const kp = try MlKem768P256.KeyPair.generateDeterministic(seed); + try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes()); + + const enc_result = try kp.public_key.encaps(&randomness); + try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext); + try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret); + + const dec_ss = try kp.secret_key.decaps(&enc_result.ciphertext); + try testing.expectEqualSlices(u8, &expected_ss, &dec_ss); +} + +test "MLKEM768-P256 test vector 7" { + var seed: [32]u8 = undefined; + var expected_ek: [1249]u8 = undefined; + var randomness: [128]u8 = undefined; + var expected_ct: [1153]u8 = undefined; + var expected_ss: [32]u8 = undefined; + + _ = try fmt.hexToBytes(&seed, "0707070707070707070707070707070707070707070707070707070707070707"); + _ = try fmt.hexToBytes(&expected_ek, "0e12760b95266a50a52bc6ba3df697c1261d3ba510987a299a83cc0b9134c2ea4ddc47889b72c6dea65b49e066b1a2822f269e8ea904a667cfe6f7035aa83cd9c041efc38d23053ea6d754c550cd5d1936d4e74ea047c4dcf757de9550972c82abcbad83c32838c18451240c0907a573451dc746bc491c143d14837af83c4f4b1f5aaa59bd5a2b03c144ba6ccb445bce2d384ab40823271342128a2b7eb68624eba59b3b2b57d9a4ac7b630d063400a275ad71b6b08a8cfe93a64d368cef666a4d208b8f39c442400c1a7c4677658576417b5b026d99cb80e01428be00c720e220a2705936a62d6e737900bd8c3d0b464ed7650dd7c84c250dd73c7536abbb4013809927ccffa5391bb254294c7be0596dd29aa37445957b2aa88dfc54447894773a34e8e87b05520c9c2ab4dfd69cdd9a52c7e9352a3ab94c07a26d468f10b2632a6a524e06234fbb2008f17cee6a59efc51b4c483fa5264c9fb325e9d7b9b66c6aae91ba9f9a29743968c5533a3750bb9554223b60c8b964cfa7346948cac1000d840488c3d211b4e7d463ddb15551862f01d77e87b60899e25164cccc814caa5c0555f5413ffbdcb5101458a26061560463ab33cc2174c61f5861986b55d6206612f386c996561b51b63a11be965a1fae45b251476e96c3864925646b6246935bbfa9f4bb39437195c6b98cf81a071a70f26b0ea61628fdbc91ed299b6326ce7f220aab6965be645ff7c51248e456adb343f10990243401d8e05ebc48b58d2a77b21341ef513e66157b83596f1f447695e8c82279922e15987ab14259157ee6e07b5ee28dc41c68dbc14c39131fc7a4ccf6420f65479701413d0eab484bd55dc1669e4e3a3eebcb118c899b2cca6cb2dbacf6f5a717ab0bbaf02141299d32c88426aa4866173cc12313f2c52624005e6fe176ac02bf45a9964659020bd03b9ddb8a99322b0452a2eddc6ea752473f1c1c9ab051dc1973e81b1e8d746406148a8621575f7c71cd2879350a2970740156c69c9ff4cfe97a3ff0a90f16e90336f6825bdb8ba0a3ccb2f181d26cc88605493b442a9d5a9f788895b720516c865d575c75276c937e2252a486c90e64b1c115060dacaccc3372d1475c3381098275bda23a52083c6e671825e57b40606226247432213988a7672d80ca32a2356e161b5a3d5a218cf0c6788c62b786003154c90312001e73bcfdd3312573504a0c402666620b31606883580ea09f690c0cdd948520b19bab2b30fda60dd903c8b4f01a83238ebda1636c911895480a8e5430da58462455488b71a735ca62c1928722d57c3d86ce66760433c15357eba556db67adb16f8a02cd995c2525d21b49a06020daaaeaea50d332952f399bdc859574c9c9c6056765d1c257e5a655c9a6c619167963afc850027a00730d60b33e2b897e71b56466be0fc98a1c314b67b3bae780a622c58eb05570024c989b286763ba420ef234477c2dd8bc86b9c9adfed5a8c7b948b6735bad758c04f08025eb01d217ab9407881878466904b2e4965b2275155fd1b8000c7b2983a9e8f87243551dc31b884867b3e9418c1d5196f83b2e7edca55cb21dcee430969366fd02a85debbd759763010cb918b13460c33bda4b43c2aaec2b4d19800eb45b65c915f8b969eff9e9e356faea1c9cf69659599b22f50436ea13cf2f1f0ec871c3a10ac0b36d877f200bfb728d9f3f8f5f014f211938c615f1f10e5000eb9d2243df6e9c8da3aa6f2f41be8f46294d2e49c4b78409101c"); + _ = try fmt.hexToBytes(&randomness, "6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b"); + _ = try fmt.hexToBytes(&expected_ct, "892f7a7985caf0c231b2b35e3bc7de3e5a4739b72d3f9be342e326c0173d55a5089ebe9826c834792a3236800eecc2612b4d92ecfdbed013a6bfcd4d1ffe5c4150e7257a9e7c0b2e0cbee757fc86b5debe03aaf796c76a5825cc78667189e0e37f4a1c3dda770eb8e6b978c89181f4871be9a295832334dc3a7f2b953f7df43f321137a9ad5b27e344678a79e386e402236124753e6dfcba0dd0dd97017461c2fe039c72e9d0073676f0749d8cc9bcd6ae0fa6f14db0b52575845c5b59b82aeae93c8fa1bcec6edd081517d6f2d3ed91575b2dd7b5893f3dd755d842bcf3e8d14afc5c236ec66236b33ba875953633eaa23afe2974d00179b72d117d9b8dee5110036f418baa0be052ec09d70fa36c943bb65050468bdfd63435f2edd7962cc98a0fc6b1d2a2c31a3581c31741790e4f7acb162a7146f399605a08cc99b48a96caccc3118ad9fbfbba58f29a121ccc604e9c0987f2250be2072189d8b01d594e946f0e5bb1b4e045ac5e5608736472386584f455be6e74b9cb31d6ffce008ce283b3f834c56f64ef829dd492eb70f6815c45128c66e77324bb03f71baf57bf0e200b5a59bfb1628e2bf209e8d3d39a2fdcc9c3cc7655224efa1efa86f550e72c11b5ebaa0b375e11d77d0a4cfe4f1f5eb048f0cff1c2882fbe01d14fcd97cb2ed5a40aa97c125f81c8c91164cbe8477058ff9e380604da9c2ac1b061853c2e4e979811bebb99948911dab9cb03d7c4975461d8e723bc415a3912fe1108de37b32b49174b722022c80dfad2881ad1db9f1d6d06090cb1421d3798fab2df0f4408c11d07be121ba66b406a6bcd892cd499e35e1c2c20c15c87ee0e79612cbdd04576955bf61153eee2798c26e4d6d3050f3de5f6771add0495459e5300bb4e139839bf6a4206d7865c159d1ba9bd566e73d9a085007681d3307040c58616f369c6f2baa54fd59b4e27b806513ff678c2c6bdcd423e67047460a3a39cb04ead7b095317f0993f3ed75b5fc8b74c458a3bd6347c6a82640f4041f0168690f8f68f2cfaa4205969fb4dc9ad42d26b3fc2ba5bb08b322d0203118666b665e2041fbef0ee957f73f60fec892a65316d3f733112ef2a19c79c2595ad99a4d0c98cd148326ee8f2f7b79a161333302268c4270a96d5d67e3503b688a332f26543ce54c3dc3817ddd616624ef715a41f1a80f4510fc769892196ac3f4c62dd0391d4c5f215ab447299c7bbd0ea1af7d621ad9d662abd35ef65f900a40b36f32f0352c97a90e76217c317f1890de7703d6009a218e75037b68c6d34f0f8ca6bef01af7894883493da5c47d0fa116af94f948747c969b9779baeb8b279916b6ad0eea9ffc4afc1bd907673203413b609b47ee0fdce780e82a6987d63887c285da97609736ced5fad8faeef141d8d0344d70ab0795a6668fae59aa65d411107631415112288c7e3284e26b5dcdabf6960821bb74c79fbc6fa73da77a4220943e86cdaf4a73e1c5bd918eacd53f6e085a694b9337385b409bb1f8604ab2b9bbd390f69cdf50ec2049f2e279a3aec28262115bb6dfa3236bd67d6324c60df93974d6e02c4633cbfb821200ce8b43977992a98e1c712fd8187eaf9542db5f05267f6fcc09dc523c960"); + _ = try fmt.hexToBytes(&expected_ss, "5f8deab6c11f05ee208ba968d28917f8219ba2c0c30d41651c3382f4d56483c6"); + + const kp = try MlKem768P256.KeyPair.generateDeterministic(seed); + try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes()); + + const enc_result = try kp.public_key.encaps(&randomness); + try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext); + try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret); + + const dec_ss = try kp.secret_key.decaps(&enc_result.ciphertext); + try testing.expectEqualSlices(u8, &expected_ss, &dec_ss); +} + +test "MLKEM768-P256 test vector 8" { + var seed: [32]u8 = undefined; + var expected_ek: [1249]u8 = undefined; + var randomness: [128]u8 = undefined; + var expected_ct: [1153]u8 = undefined; + var expected_ss: [32]u8 = undefined; + + _ = try fmt.hexToBytes(&seed, "0808080808080808080808080808080808080808080808080808080808080808"); + _ = try fmt.hexToBytes(&expected_ek, "003180282264d14206367c682c2a282a58c11467550795801e36393cc16e49f565bac995ab504448d4840b85526e836ca6d641f151bd50897e1f9a5f6c718a61f97f866b364a217c87e52cd28765ab18bf3c931a35150d1c0c32c6b863872a23373057bec82e671986fad2cb15a06a9687a0ce923754138b4c54ba93c082c147cdf186c0d5746cb6d54532a2afaaa701220342f6303ea7b66d9942460722298e3bc2f0a612a22a986946737d057fbf3ace20cba6f97503fe03caa5c3974e8aaaa3c7ba62bc70d1547ea9336ad15315cd8a2919b7683b568aebe54b385612f8829399d83aa3c2308c49a73379481a690afba636e14c7e6f31678c022b552b19a6b90b252a7122575ba041c2daba8db6ab6cb9d13b5bb391ef0b48fd578e67cab1d4d01cf4b492f136c9c4807b4a2479f35b255f234436d71a123b47384c1b6909b0d8c55575615a314178675cbc94e746a415bbdc912474042a93d776a89913a2102efa9377b85b0149075eaae72c6aac3d91c74f71d38c063963d3fa0c4da13f5202931f06a4515b08e22260309b882252c972ea2e06160e1537bb38eb97e19909e58a3f3aa007daba597d08ae46767beb517fab0124bb39ba6eabaf131b9611a9bbb26c1c52556030599be5382b99c79f8d4356f3dc35b08678a2a1b24b3cbb6da29f03a1bcf1bacc87a642d676abe5826e3836aecc878d2d67580e39b9c55b7ac1e6b8a4ca6ec2e8934412258c0ab6708135fd030cdb34429ee0a19c782623f081c0564b7dc0bd8c35b831672a8413754127a45ac7572185185f444f017640b8a4c0819927866332c45374c6b25b88416d4094081e832f26d44f8cdb04bde72f17e38986a6a693604c637cb75805092cfa7b2921665e0803d182600e001b6f023cc39396c20018cfdbc85ac8bbe5cb6fc094bc55c854e28aa3ceabc7f4e1ba13277628a1304d6c4527533cf9d2b6d43a1f6c71c71586121c3254315a6cdc02b9afa12184e51d3db071cc671864f2ac51fc653a1b628173061068924f890b18acc74c4524240696bee4c099552ec02354bf272c94b541b5f4495e73c181827120872cb4a36388966a081010b80a9cf37c4715556f9d0c3a5f41c587a7cead9ab40e366b53612c745632b0146368e0a52f0b078eb20a6006cbf5591e393c19ecfb8e5435a73cc0c5ce3a008f5053df1647293271e1366867232cdbb8c58066c105701735f911db1ca2c1b07737467805e304d243c733a77ce448b639c295392b1458b4a1a8474ca2968e0c78917a2b255fc247fe493bd0d9bec244351890a895e470d298052750166b75a98a855549c386b140bc24326b044a469dfa660fe51a928271d011cd54682433f5583c5205898c216cf80cc5456bce0accf8862800cd36661332261297ee9854d230b432d39c5ea6672634a294c929e091329a37bb5f348f30f482699a88deba93d58745f2b013a993816e4c1b90d28118d9cafe2a48bd024be328490b703177b50ed619a9b293944f30bd62119eb15486a6c3659eb0aa97e1832d82723627670c30b546337428a7364ec49539709dad61943a545088c754e27c857910713376b859e392d0554a09c04d29aa8adb8754a491059903221d689fbaee7d0a714487053a812bf22bd20bcc6b3aba85396dac337ad43e5494047412cf9d1e0099d80dedf5f435390786c8a9c327e76394d42037808c7f2c12253e45cbff4a9f544b175d24b7655a9d946ab533b0fa10f5108b17c30772cf4172"); + _ = try fmt.hexToBytes(&randomness, "6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c"); + _ = try fmt.hexToBytes(&expected_ct, "fefebc6f6f4dab06bd9b8d0a3a688b06e4b3ce999cff7eb763d80f6d288d9d8c58d240d8d217064c9848a1508726829e177a02b99ac33ac76e50a82d31e9f952098c4731a0fc35c9da8e9c87af7306ee7171e3aee3a3d2d046bccc1594a9686a7749b906346a7c863d8dd34acced601a3953a05e8ceabf34797b4606eff63e66fbdeccaf981c13660a8b8de661c5b753f73c7e2cbc733379c4d9c36b93b756f5becb1ed569d6db0b2d2b1102e6064cc4311da3030c3f1a1126df69fcd2c5c3084ee1cc75761e5358ac39c4ebd472caf4e555f8da38dba8ef862c92eacddec9c270b5c71274c15fc38674bebabdc7cae2444dec1e7977679578f051fe3ee9d7a188565d5fce8fbcb842b28a4d38d77eb37dac58099664fd1b8069ceef1e06ba95a39bf48f7098a9bff84f6f2df86643db53455be4bfa4848d93caa1fc1398ac240d186bb3334bb5381c9d089ae3c8637deedeaa576e6b92cc7b66bd3a31f3c956b4aeb4eb688b249cf9b57a56d3656b5374f806b02875562ea15fcb619395ec913026dbe5a6d488cf907745cc65dbc9274ccb5e2461b9e923862a67744c8b8ff19dda1068fe408859f89b5b8550ed08956ce2b5c9a617e7ef6022772a58b63bbdbf5eb057c4bdf30083bc5de8a22628fd845bc6ebcfe27277d1d7f57ceeb7beb07a20ca446fc65a372929920792d2c46afe34be5a052ab43db9e0b3c2d024cb720e42bd20db4a53e5bb32a1356989b1d5850611918549e9f57001c9488b8eb12a36e85595ca45934fea1d601b42c67078a4fbe701496e443b49fc8822fad0c8fc3a25b3910a5158b380aa678cda329e2cedaec1c4a32e51b4baf7091efa5edd6ae2aec450e15cfec1ced9a1d158c9f8f19e21155e4163eaecf7f01d16ba495fde48f39645a44d3733ea265cff5d4f2129e3c83a6ca33a6223f855a91ad49776d64ecfae6878668efeeac930ab6fe9183370c61727b4049a1dff8579947bf16ad1c0e210524c4f5ae7548f3d3e532e694abed9303ff260178505542141ca980de12090eec01da3399e8e454934bb15759d348404d647bb2a409b9d8a956021bdb85ddba1978593aa56d961ede3a707e7b0bb6f325229ce754c702d9ebc35a2663c0ceec6b3d04ebd91a63854736ef4b601495c26aa4927b9ccbae3adc808b32571ff32288d46c2967a96b53e756027d5eafed7dc8d7d5337f98309ffd1a300eba2c02221a3f100b68cb066861fbcb39252ac446c3ed3a9639708fd866e9fc0bfe4a7aec198696aa13979287dcf1fdcd143a44a78cc6006beba2e1ae70e8e857764d233e54830473fbbd2b173a34c16c2032d004fe19d35d8a5277824260f32ae501e24592916ea46ec14a9bc301fd0c900e33a1b24cd153a25afaa34ee26cbc9215fc6fa18972714aab79b72939b9f4d3190b9bd64282c0e630b6d8c6755d291e33dc3cf6b6a740b565b1cc292f2cef3b8cc6a6f13a3c7b06525e4e7897f63c82502f7fd77bae5a80a63c7436fdc8387787ee34a000ac1512c5bb3820dc6a9b49e048ae622962818294b8210f6b88c29739ef9e92b3953c7e073ae38dde178f60320fc974a311b78d6820593ce11cff1ef147dca0d291186a45d823ee52d4faeb861"); + _ = try fmt.hexToBytes(&expected_ss, "f9d4fe89be29e5438f8ed2ee8d368c365e550b6336c55a4d91f3d96dafb3191a"); + + const kp = try MlKem768P256.KeyPair.generateDeterministic(seed); + try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes()); + + const enc_result = try kp.public_key.encaps(&randomness); + try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext); + try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret); + + const dec_ss = try kp.secret_key.decaps(&enc_result.ciphertext); + try testing.expectEqualSlices(u8, &expected_ss, &dec_ss); +} + +test "MLKEM768-P256 test vector 9" { + var seed: [32]u8 = undefined; + var expected_ek: [1249]u8 = undefined; + var randomness: [128]u8 = undefined; + var expected_ct: [1153]u8 = undefined; + var expected_ss: [32]u8 = undefined; + + _ = try fmt.hexToBytes(&seed, "0909090909090909090909090909090909090909090909090909090909090909"); + _ = try fmt.hexToBytes(&expected_ek, "460393d6222435d92a68eb6b2384ae414ba04199ac14e561f6711facf87180a4af6156a5cf589c8ec7983054764231646f07c57ae4247de37bb73758a60b4d3334985238563005ab337c6075b81416c241fce764505acd0fe7b0fbb859e4172390e670089414107549ed298bca658a229079866b8b960a7c7fe46cc47a1773010f106b6f5cc9c2f906465851a7716649b6a67ff073807cfc7be4cac44cd1af6a372cb04161d4d39861780c6f62051dfcc39c143dcbe49bbf5b58a8769b927173e4c7096c91310a91484cc80c55217b76124d0db0965f84bef016040cf19947d61e05a14f322bbd918392f5083be540a122a3b1d7bc7c49a868f6384dd5259850d427166752bf286a3ae729e3a5a5e76209de5b9c0783a4c70504a338af3d178ebd114b7ef700cc17919b258682d21a1244a002fbca209a80cb2a94e4476aaeb7cd002929ef999095c893624667ae2bc8712b20e35815599196de478f674b50e28313d7695ce2ba3dc02594171a1ba7a095a4351a0f3306f51b4c09ccafea587693a9519f4b4976260445421a865a400a63b0a03a8d9a35ce3335bec1912460e1a5b4685f8fb039a5cb444ca21f87c522c7e6554f29007c0562e5f02eee849f29d39bc15b58ff58b252581adbb17a42f2357ce62746b1347a4123d02aa5c3974eed863eb4f309c5b265d038aa6f994e8de94da384321fc65bda704a15216451465f9d44b7e8a9b7e6d356e8a399e7eb39d0b46d85ec0cc93c56a6d570c9a8865aa6c74f358ff1c03b80042567796c41f418747126bb4ac29d738372fca1a9646ad6e7901581c6845c91c3989058a82bd80117e7c73bd2da15cff522fbf599fa6840317608e410b52b4c477827877ff612cf89cf6c87c8a72b175b6c8627e0a10b937cc672c1af54b64328c733c22585b88c54d30c1a22bfb6a2559b178db7a0aab6001ddac9250416ae5c11c0a5a26a09d38518b28f8e3a7f2eb51e0d679f1d928b77e103f4fa6218f68a1de0af5ea0bd1a26bb624879ab47a28e9c40042b29cbd678f7ab65353807d41993bfe11607313430e5aa8c27b9af3a5f10531aecb1a1c3710f2b992c99b604f7b2a3269a49091a3a56dc8b78487ad9ac24e36c97d351855ee1715083a3e9c32f52f735a76cb008714fa83bc70e4caab72c485ac8431cc97cfba727fcc0af9c542d7d9199bc4b50a9ba83db77c7dc206fda0547e2795a8dc36fd1b33d2ddab089d95eb16b15da94a74ed8b7e8daadf8180721e0a1e31002497c83ce1747fbe78cac291ffd967f23f47a8a30485aa352cfd4c6907cb8b201bc3b9281eb51985e11cbb4b5250ef7ca13c70674f395c52b177541b57b3aac77e382d5bc6e0c32aade73c74a163c0f43a59c616d76393f39166de8318fef1b2c47075c19885ecea7b4acc674d1c72a2e4ca70d28325f12cc33ac19aa9c5e5ab31c7ac6933c1a0a79065fa1e0237cd90a13c7b759980262378d4a17b85c184f84761e071a973784b1c30a9358b76a7af10727f36960f21c6e078d33fb528373a436fb237f87317b81c04926245982aea0b39c24a67effe65a91b211ad7334f8e4858a3b7d0ef21b3e443c0cb64e089408006b103f3841cab70b046b98e2c8aed94ad683208e265424b077389c2ac6571f216fac52e35cd280e51aaf6632816204c82a04f09e4fda885db34fcc3be3ff41f95700e4593bf825f93ab30469733d373e450b125db222ab72f2ade96db31fee093fcf254511547ac1ca7e926778f133"); + _ = try fmt.hexToBytes(&randomness, "6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d6d"); + _ = try fmt.hexToBytes(&expected_ct, "a857a7425c6077dafaef133c62869e523a4b41110cededea6ee66b9a6bba634d92cb41d5fbef2ccd6f5f057951fe1b44e224cd2314c9ffcdf4c358979d6d065f1f12a51033640c07965e6d4342afd7b57d528786b398e13b6eca581b96fe77778d73bfa2422e43093f7c96434103b18cbdf0aa6549da8e169b66f42921b89ac8267de35446008948d6026a0bcbe61357e97c7fe7a95337dfa3177ca6167c3a2af8dc38e3d708665824acf97d6d7b152bff781451c587a282adce031b3efa26b6d8bf4b49733c03dcc4300076049e278d3cd872174bc374d77038c75203ba45d30bd4110b62346c3f0bb1d8563d0e2d58cac839fa693ca96a56eeab1aa4d26cc95e013674eec4f8f08095059f081a0d4dfc0735653f3097f1a37da334007082ba6619844547e0d3296669eeceb02ecbf0c5c5a88d5d1c432398361529e0d91a628e568cd9a18cb3e8ca41315ccbd8ff9cbbc0677150f2e06274ec9e2733f3111cd4f1b900c7a3a80b517a7d6917fd0753f59e8bf03267e3a43d60e1857d4922cd0f14b6d28602792dbfe2b4488e807023ff4a8b65f75fe1a0826d5ff490bac7be071b9545f6e7ee758caf460e7e92e52eb83dcafb5bedeecf44a5f5ef23690bd83aa5ed2ce0420c697dfe4eaeff5d64be78fd3d9c96f66fcfbe015d8a1e75e93eed734c267fa515220e9937fdf8df271f69c8e3d6dec76152404d862434211bd1e4b53cb9951c48b796fbf4287d3386c65217b41f2c414daada2023c43c08f8b6edbecc7f750968c4f16c3c983a95d73c25d3cd4aac5bd5941fa376e1934436acecf9bb9bd4409d0944d393d852aa15d6363bbd83cc24b1ea841e00b99dbe9f7b9fc2a415063865b43ecc9db0a95336820d40ec4d7325d5066eeb2a144ed3c27f5072c9a2e596d395bd069fdb8871521a08d0e9c1810467a271b4067a54d9d81eca8f26d18acb41a7de599aab1f79ddd0128c07e44cf2ccef72368cb4474db47ef43917940a80d05aea5e8c933a0ef8a8516a6dc2a50df273288d97a9788629f001e487434e6a4192466e63f0e185810665267a021896967d833c1f108645a5d17334d5dde8fd617c786665d7df9762b8ec0676af697dca63e62b597aa2cf89c558accc872ce9a1277f88c5ac160b2d13a27de6b4027353adc3bf596d3d6527532e66225fa53e2546f6ceb240435bb93d10e4677208e990f622276d04ab8f2e4ce2e5180a436d33adbf29c2cef8704a41074ed16c164ffcd1c8f09a2f752c098caab09da3db75d84ca510542c3adea1757a553bf53657f03feec803950747c8ddceeb31bd658072f01b7972df731af5ff53bf8a8361cc918c6a00693134fe6e385cc70484d2e3e6d365c14fbe6e2332eaf86b7afb8da619dd4c13e86f62ed63da3b99895d6ee47b440e16a3615f341d4be41239336b020174819681732503f275bf8bc494c251e5448ff32bffb3fe0787482b796c37fd8d25a28ea78620046b192ce46fa4c798c0feed1819614413326eb373e622834afeaff81aff308caaf83504d31b9898e848b396bc86cc55c519f1a01125995311ba37d96aa59ff464baf8885f3bdd10b1c92f0b286990048d58105a7ab5ec1755763ecb4d20fd1817d6b2fd"); + _ = try fmt.hexToBytes(&expected_ss, "a0071e94b4d0275ed91dfe4f3fe87dd4a534269c7a5991fdc27ac31933fad24c"); + + const kp = try MlKem768P256.KeyPair.generateDeterministic(seed); + try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes()); + + const enc_result = try kp.public_key.encaps(&randomness); + try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext); + try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret); + + const dec_ss = try kp.secret_key.decaps(&enc_result.ciphertext); + try testing.expectEqualSlices(u8, &expected_ss, &dec_ss); +} + +test "MLKEM1024-P384 test vector 0" { + var seed: [32]u8 = undefined; + var expected_ek: [1665]u8 = undefined; + var randomness: [80]u8 = undefined; + var expected_ct: [1665]u8 = undefined; + var expected_ss: [32]u8 = undefined; + + _ = try fmt.hexToBytes(&seed, "0000000000000000000000000000000000000000000000000000000000000000"); + _ = try fmt.hexToBytes(&expected_ek, "a10bc8b554cd51980cdbbccc3041420fd320fe8b74c7a84278c63c17070dc231b61ab269b9d677d920261186654b4571f51797d5c342b8070bc6c92bca16adecc631e4e94c7508b111730c749c73e2d6a6f97155cb269ccc06a71a21bef3d269463c935048a7f4636c7b320073709023f7b04d0530571a9a6f718280870bb63875d3f599bc229b95869cd5bb5d26640856d40b828198fdf2c099998ffdf772e462336c521cd326b5e4997bd95c135c57bd02c7afa80a2923d510951778ee5125b2aa18f90445453b85789224725b259279698ac9426c882baabc38d4fb3a3f6831180918b9825e0e418154d78aebab5e7e7066e69b2567476bf1177fe079a38298be6f01b098c33851ab25312b52e32a5750c2b73d293c0b810473b310aaf062f19914c7377b2e90388f575bf5e6853453b95a74aa18d62d4ae37e6996a48ab5217488a92d7b01e315c50b68204143792afc4f8367c0ce065ab32014bdb5515fe0594608aad1218994724afaaaa2df0355f46666b6e02a387b6d3da4713edb610bb048c3a2078b800e9ea483f2009c96d24c71b2cbc8e1200c0277383c5c27895e298c3607701ce58702a91903274a041408234cb0021ef2b1c5131419b444dc84b89d147d1fe43c43f676d906735d9ca2a59c2232d97fd4aa1ae2bb3d1b170ca553cb2574954fdc6689fac623cbaa31982d82424d5a564fef7a8ba51b44df15053b2b45bec4aa1ed49929123daf754175c5938258c608b24d062042ab4bbee5e553a5ea627521738ae5ab2e06bd98b020787b2f5fa51eb4c46c2bf90e55a49560340667f88ac41432b7f551dfd98c037c79f79b41b985a8b1f51345550cd816714362040778c43e378a288394bd028c8c31b5a904bc4a5648a596035cb38f0e276e12c9a96f8425056b05a136642dd2cb75463036485ba1a50539e420e1e31dfac529cad6c68ec06746749473e050a4ac92b7199beceb239b6c12c8e716b66607aeca64a5850b01f99d0b176a7759781ed77cb1ba40d17ac5c6cb06c942c002c2cf6efcb121f10ad2a45ff781426e7104cbdca73b81865ab22b00ba834355ae485a262f354248932c2be178369a3dd7e2428fdc379346ab2b754c43db657460cb09c5c48b5810cb7a5c6156cf87440c9e36a4869a8ac458b382fc178915a9ce1bcdda7c48807c207e656ffb80bf33e32bc8c7b20ef60572612ceac99ad1c56ce5a764b29b74c17a5b510b1afcb18a1afc35c12ac213725325f9b7a2eb338fe4c0080c31a58a995db7027d900e78544887f90ada467d0e383c119c5399310bc6735874e8804ff6c2bae57f2c3357cb627033c12a5924b20ce5abf113172bd2b77086cac543811793bba71734c9f005ac2656460bc30a442b388725758a623e37ba6e293abfb84f344229f373c214ca776a7c05adc465fed93b9cf77f0022ab71f1adde369dd8f420a58c057c14cc18dc47da7c12b086473eab419652967001c4e42a381c8ba539a875d21a9945133bab9bc1e53a600de77cbfb2aeab6b19ced4c6eaa8998ee6a1577255f7132d80a32d6c0c6ec44c9c4b28699a645bb0bc958e00275077925309519b0824c7000dfa61912ec049063a067d00b059053e508a5bfee63473869c8a8510af898cd7572854f5c38af96f5f97a7372632ea7bb4b6fb831c612af71191ff9806b379bcd43c6059b7b1f953741444af713c155d962722b947aa23a32a89b356a6a7508aad63968c1dea78ff18aac27a89aa7b42b0d7481dd3cc649421e51397782218ac5441760ba51a0328d66b436fec32d7aa4d68e0cad1bc14f7241c903480f809983fc2c30d93138cf63b59bc737ac08192893d039187a811bef3d3209eb7b8d1e05b5b251cef760a210b2732867ab32049ba3c354e3858aee7b71df792924730d8e842e484122b50677b0a306e61cf21b62091da18b937192936a09e5a418cf78b666157dd477af1c36a12320129522840e370941157808782a5335b0ac10d70e1beafd401074b84b9826cc58aad217bae0f419b2da896133272d8f22c6f420fcc738fccc1082fc93c7df0994c6bcf2cc8a29037b6bb2b4bcef4b0ee8caf8506bc5ecba082a56806c1cede0b944338a69a668254c1150ae05030e256b2b67661ba027d97576da613ac8c7c29051f1240b96b0c127e264d5e1dbbfe9561a567d5c9103673b446b3ccea6c5f7f34f09348a5d4a58b0498871dc940ee97b50c0336f9a60c3299f99560ac70657a27befa702265ce590583e04a28326092d3dea2118dd1df5e81d7d3014ec4b5ce67dcb45ef001769dd5d5ada76934d38d740924712bfae672169d8f8744c151346d285fbb653f83aa0f"); + _ = try fmt.hexToBytes(&randomness, "6464646464646464646464646464646464646464646464646464646464646464646464646464646464646464646464646464646464646464646464646464646464646464646464646464646464646464"); + _ = try fmt.hexToBytes(&expected_ct, "dc63d18bb9715fb6e3ba71cb439fcd3377a75305cc9b144e6758bf5794a272e6b4a0da33234c0ac1bb5b4e60e4c82eb1fb780d59e4e4616641a0595ba031e3ae69d971dcd5fff14e21731a8e1a221f46c7820d214630b707fa1b0de3a484698f3d49e0a75f1212b8c42d330dd909f15eac0402f19ee77fba9447e1c44304b0d8c371c17c5549fdbdec1e0a2e7be9f577d7a4b5b2618d9ba67ab95a0297cd5c5a13c89cc5a57cbd9a8ae38d66455c9a3d2bc55b498775fee2f6dc224d376d5f526a8354c8ed724f60337e900b85627972383e1fd987d407a8834005814a4fdc94c947e5f3471459288cfb127952b3208f10c914200bbaac5fcebd2bc9e2848492bab17b9288ca8b81d1c2ac9522dcc0b6d5f51e10f3afbb5d65fbf919edef6323c4e92c6b0690c10db25a9182de9e919ea1b3e65ae6150635d5180ebd7d23a2264828bc3ee1fd34dba1924ad0db30c747e05baa9148f1a032769c685e04665fd802a79c4624f69a9198a426eac1b217d903cdacf8844e73365f3a219a700dda27edf6bea33602617c5fd105b301b884bfaaa1163b791ec09f82523fef65c87b75ed063ceb127729b82c8712e1f41b547d095f55ee71f3f8b47a306cb5d9bdd817854c74a42eebf934a1136dea3fbc546ad8ce51b3171913722f08b0261d197590342bfe4108dcb08c62a98610cbfb8d3b2831f56dcac2220e29a5811f38f0824f21a6cbebc64fd89a09b110dffbe03799ffc74fe565c80dbf6a66acd7bfd14cb90acba03405a7982d4c1c68caa75f8b72e4dd6401d7dce4db4f6b820a7886a604b66b4e5b9eea5e5eddc2bca458a25977bd1f02874c5d9daf2baf56b3040f24ce7fe14cc14d61c7960db4decb37d9779c8e36d69a7763066d8c1149312d26887a693dc222daa892dd00cd8f3a558cf605e4c65c011c2e9f0d671ba10af2bb90ee0351ae5078eb7878399ec9eb4ace87a68269618bda12a7aed6fda0385496c5d10ac36b35255f4a31edfa8a2c516b65c63431013ed4909ec7a787a5efb9d3c3887b80ac18a44934b6559bd8a84b18e86fa1b0b9e1d9f92ba495ba5595d82e5095612b79e805154bf428a7071662c7cefb6450165c6f8f6954c37219bff4a49894a8aa37f940a40f4ec942c281e6c47ea408199927a724ff1c7460fc8fd47a98d0c9d4d1f07994d8084f6e084935ad7c2985282fabd5ca13b942e10d35278f4ff4cb1cb96f3c862410e79144a46b4db1a3c3d4d63018ec5c01ca48cb67081482e7d434b4abe5fa3071f2fbb533f745602b0da6183b28e6c5dfa42dab7ae0bbbf7638e106be1bd7312cba399e08c96dbd69a128a2face2d4a02951533a25e82fe63d0aaaa2e8c75150215c93ab06c22f9cab8d1cae7424f8baa09b3260ecfa3c7c8d55a276b4b317f72ec86b1b145a63aca83ef8c1204d8ab0c96ea3f742de39db47020616e139285814f188029ace4587f14cf12b5ed81086d8213cf8cb578341e04e16f519b77ff4c2644a5732639d658d0c4eaf992bd7dbd5011b700a5fa63dc1b24a84a3c80656bab5705dc3a74312c80e8bdb24a7ac6e27bcb8c07ece62c6e5777dd3dc0657181f440c7524d907dd27950bcb252aef7f8cbf453cee3fe3143a665072c787cea76de323aa41537df2f3a40a518a694b918953bde8d57084e32d3b1fdcf9d153e73f02624beaf6ebe23e6828a6a489583494f3cd790fc96bb6f5d8b198402965e2e668e6581e7cf1c8a47a92198388f2b4cd38df660f0ddd48ad126819c4435af3a12c89113d778ac544fd8079cb8aaa97d2ff1b608da574c4dcd87f4979390de3be405f0e47788dd0b01662805079fd73c64e9278c036544add3694c838bfcfb08c8a5efb09549442123eaa59fa30fbb9198105f6be00163bac076193f6721c539714108bbfae167f5db8085c5838618f32a968bbb25c40645a17c17b9bec64aea45832eec5adc25b53e677f67566fbf5ce2d9193a06bd9b477e601d589b25f422defc49105252cd9ca6adcbb36be8a01a8472b4d463f655be14ccff9b0571a2048e31c14b9b23e2d43fafa3f85ece6fd41896cc5c68993dbaa926f285ec94c72887de9564881d735c05f83aa474b3d4cd133a630ac63850771cb5270f6cb7a391170d66af3e4901b6eb0253f3f34ef57d6babd97aa99ce718c3bcb53ff13d4028a0c943bb9681106ce176242cccb75df1d3f8d3706e5b068b042c3154d5e6292581b36499e6b069b9a490aa67f0675390539da8555e6a4e8a35a86fdfea83e1387bf4acc650ec1edae7c99aa3a48306ee1d1a5e513c0c6901f64d0a3ee285de3c11d49f90cd4323dafda14832f0d8b760c0e5a48633c967cfaf"); + _ = try fmt.hexToBytes(&expected_ss, "8c028c6ea72a1c59408e2b15dd8fed8008517e861cd2329b159bda1919ea656c"); + + const kp = try MlKem1024P384.KeyPair.generateDeterministic(seed); + try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes()); + + const enc_result = try kp.public_key.encaps(&randomness); + try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext); + try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret); + + const dec_ss = try kp.secret_key.decaps(&enc_result.ciphertext); + try testing.expectEqualSlices(u8, &expected_ss, &dec_ss); +} + +test "MLKEM1024-P384 test vector 1" { + var seed: [32]u8 = undefined; + var expected_ek: [1665]u8 = undefined; + var randomness: [80]u8 = undefined; + var expected_ct: [1665]u8 = undefined; + var expected_ss: [32]u8 = undefined; + + _ = try fmt.hexToBytes(&seed, "0101010101010101010101010101010101010101010101010101010101010101"); + _ = try fmt.hexToBytes(&expected_ek, "6a0684b22b798e76a4407b87f6b7540d7ab75460692a8a91070456ed6457281542abb01adcac98daf4a62f3a81dc7962b6a23cc6624d96cacf0e5c8894651ec6806792f1c9b457211567515a65a184fcb27c86bef2b215237c119c9b29d205c0a4171b70c34da35427547497f827c8642031964b10b12aafa6c0278d9917cbb39e3c7b20151c167331764b447ea7097131fb4f36814d16290c3cac0759381747b4a5a8fbc170f8afb865c2962339988acad6a17cfe12aa7102aae24124537b1042c979cbd1a03e8a15261bb9476100360a2588e19ad3dc6f124b6567c25dc00ac4a4f515a23086b5035625a88458e121ed805143b8bccd95b08295bfedc2bcbe947f75527285231109c7a197ab406f6c8b5e927572a766f5d2b6a83816afb0513e81a02049a166778774714b7d121c6fb646189102923683b68abdb2fa11bed51a5dc30f0df593fe4a459ab2aa8eec26d69525b790001b820545656b0517989c5243f6e313c08720b9052c624b9cf382b92341ab3cd31007d68353077df88126e3762e8478359778bb3d14163098c5dd35b9b9d87c5e6874004d126ddc8839f48cfb6a717737010ab66188c24314fb0104c6c14895a42b33781afc01f84214897c7f7ea48662f18e1050513d91c83337c10db5cc7cfb78d86b615d5b1c74062cfb5162cfd2499e31a6e3e8cadc6cc2066215fb78a1b986a1be2086c73a5142a35923b61f76a1b669f2690d3857c3d903d9a6647d9b2d3f545703075f7b3021225c7daab74b4e327496a9ac8088418de6ad08ba4b3a57967c6c4963b87ec1012f1356051537ceaef13956dc134dcab19729c3fada994aa706c4832ce47b0479b51c13995eb6d895a7045df0d8b696f3a8a529a1abf8bd66e91e6af46d747307cadb76e7191d2ca90c8519394d3c41524089247258014bbf1a1b8add2709f50480d5d08b4d9a64688321c6cacc03115d30e09d016a1704db4689545b3802a07f4b3fa82142bbe8940c7ac15f19c3ee20bdfac11bfe8b26495107a88b9eb911228a2c83cde5b0fb82725d4808ea5a903ba2b9698a2dad9b55ebf6199f396ee19ac2998c7f5f1b9f67e4a82fb396d7b0a4bba7343dbcacffc3b113c44d6d033781f289a3cb8255d8bed93b0194b95024549614f45622e134dcfb6df598061048796649330bf8ce4ed35097f68503e1b1d8c2a2ad43af99d830af606ac2b013cec9367a9a6b5861a6ed618b3e6291743866cf8661db45cfb9b168dc0acabf1945cd698ef2d11cc9f1548bf48eed6bc301a233098b0cfec71cb479630f2aa6567c8b2a10717ce985bd21c9b6823519c01a40f82436ac734e4b8e15e28590ea212e992fa46438fd373cfeb83071c1549171c76fc80dcb17310de56eaa624043072632b14ab9636c37fca0f283742d0c8a7634b4c7f65cceb1204b6ac55cdb5fd508b37db8182cdb9cd5e705f4baa5ae85a78c013e2e7c168f2193bee3c81c95c446741bb730611963a01b1c385f7054dee45ffa75bb7cd12f41b25cd09ca575a09928c4bec4b61f67447ff5f38dfbd2044febbf8b83760bc91f5ef2247cd63ce25a7459247e8165b848010c237967f94b11e021237ca414ec0238171a4259a581323b3d6f091620b164725144d6b326afd748f3665482748d92315628339300353c370259fe86ae62931d94e2136a8a8fe5664324d3a3cfc5acd6d00ac8b72d67377bbfe1899dbac69004a0c2b9ab047a0150f903400b6d3f009a0a6a7ff388ce1ee43d9ab6314d720b770175d3402bb7ca39690aad41904cfb54a951c64ece057d9a6b610088630b826b682990962ab9d09a5fd1533044fc131015511932192ecbac76711e28743c921aad59dbb3d4978bc3147e859c40bc98bdc8680962536f1590207ec9a7dbf795a2d48468d3abe638c7c494241dd4a3994c2b62c65593756151e6a98ba50b7165c9e5146db36285a736383f391f80270920447918011064c78d4e738d20eb54f0e45551946e79281052ab6141ecb67834a55a27082b303facc4bdc9ec7733d7c5c8625a8eb4cc444501dc2180f5eb4ffd84b9f97960a1396af06c91429a9b92c4a54d12994364cf770a0a5ff4480738890b0198d813813664af9708a21b435ed6bb883dc2682fcc5928458a22101ddb594a616c34ab8322573226cc9b498a40808330885f206b72049a068b70202d8d71fdb3cb177d4670d5a5fe04584e2dc615b45facdbf7dbd82ae961faeec8274218f2e9f4a20d6aa9c78a7894b7d83ff770d2a87504d5be54954b03ed4236c6d1485236fcbae4af881600bb618ab00c7f20c27a3a2767d729980e3607313e83cdc11aa972d4e74b4c00c2ffb9"); + _ = try fmt.hexToBytes(&randomness, "6565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565656565"); + _ = try fmt.hexToBytes(&expected_ct, "b7ef5cf25fce247ee4a1ac8a0b6bfcb31ff4060ee7082c257897829268f59afa7c973e233c666ede6754fe326d01dc8709797232a833a1353d651250437d21d5a97cd761a335683830b1e7167cd6ac96c812614f32cdbb6495c807f921486066c270b560a79470ff198b5c10fd5ec63b9fcb3199f3aa410688883513e47c3ad9020ee303dca0c2adfd980fbda3f7abdcb7b1c38d9df943bf12bbc2c3aa2dbb856fcb9c30aebd64f2925aada5a3b25efeb10dc7a2423d60b277730d5f3800bc8ddeb252c6824b9b805f4de3729f0306a38854d8f9a63535c3cfa0479937a5dfa59a3273dd357276d8dd4d48d23c32e316952e4c877a4a72dac1e9815f0e589ca62721165633433ef333b842c09b178c417b748d8cb2c5ad56ee3b3f1bddd7da8b263a17fe759e25c50c257689039af122233bc0828f0a6b380bc959ad3077998703e530b13b249b91f7d1547682cbb1425b6084bafeddf3009653ad1fe547c4828859fe7b060a4e8c29932919e7f06de1c5101fb26bbc899a37a9239183d05859ef00bc9a6a3832129551c16bc75fb750447f20a38f12010d1d1c9ed462f593408fab42a6ce07bc8ab6e7df262649431fe85ff80d3027a862d140d75c9ea16f73eb8f38052a535fa72b370b19802c8a4d75a0e59766c81c60e582125522f15aa3f2e187d55cd2a0ebd8982ff5c671b95ccab54ac3cc544f6b07c6dbf58293502c27ec25ebcd9a44e2adba24a220dfe5ff3fa92ba509b2f4363facaa29ad7f9ee279f9a112f5307ff98cb1be7a237f56a97ac74343e5b5f6ac04676b560fbd5ac7e633e1b2b64d63586b13c735e7073855442002c0ea27d8bfd9b5e5147a62ac903efa1876e1a026abf31c3aec9b01d58c38c8c4dc5742bac200ba347f3da2e5bac62b213fe93f500a7a4340ff9f468519aca36b1bbd44c9ae4eed93d4daa1c847fdc072145939ff3473623250aab4707031c24efedd9e14680fd9a017729d8db87a132bfbce8fb4a524c2e32d469bcbad71ced78fb80c1cd9232b60c2836f019330a3f6deab21832f60faa346fd7b251768905b538d883cd23b0c2a7c283d33e083148ea24064e3b689f922e7f5ca7ca4da9bb8412bd7909f31e0f2963f958eb1431262a522f79d86c748460df92272dddc45bea7cc9cb78c35079baa70c3ca12720109bc514efb3dbf3b60fed6c49824535f50ce417475e0efbd7599d9071cbddc94790ee5251c685fe5abaf4f05f11413cda68af6e435f944eb69ad78ffc67ed25fdcca7e4378e9c282cbfb4779c9230276d487496ab7e064ec3d2acf6daa062616fa21cab9ae9b1c69de3d986f49125d9316bc38695a27e406842e9304b5863135e8b93b2b656e2292e6995ac80b404f6fb5b4afecbb3c2d07c43c1c3f031ca1be352f2c5d6ab020f7a3d97f8fc4e74a0877fb5a01898eff75583ac512f23067b9d9235a6fe9251f8581f68ceeaad4fd2e649e887b8d6ffd6a76f79f12408928c78a8eaa870a0e213a1598a5eb009d36eb0500950d6a32457c7d8c2e1605d755eb48959324bc0c9abb103f09a0907188bfe949afdb079ee5b30caed68bfa901b31531b78584727783e9593c20cb937ae6a19503ac2877f7225ee07289170d7700dccd64f4e5cc1846405b327dc7cae6731064a1178fbb178fc4e36ce169ac7df5d981081256e03b5987dea146cdf8dda5618fa7f956f5c3ee7eeb530b84407ce251ef012f1a9427d3679233995845ec09dc6c998fb73d123d87c07ca68453458ae7131992ae1151adeaac646d85d1c58ee99dc4853ce1305733bd4b903b1f18d2071f101957338b4de94f9ff27618873ecc05a352a1ead8d63e484911465f79512042e9d9182e667ac055077de3ea66abe4c71f635d6b1f37f43ae1801ce8764cfb9342b1f0f1358b68f2ea0a9ac93996bb93c1b6e9776fc9dc95351c40b05d5c1e69568d93eb85b0ea0a4cfa2d90065ecaf7f472e44f14e15e04d1cb17da07bfa76b1eb2ecbc054298e5dd6c5be7771e2e56a8d8212d5f8381e7357255e1a669c32009ce129622fc19b18f93f06f407384045713ea5ab64e7c55e204095c57bb1e8075004e9ebdd2e63e1a4bd94134b7d1d1fa08e1de070682ad8894edf639578b70804e4ca3ed6f4dd22d570fa42dc3534fb3498609f5f5b8e77115bcd295657f9c48f3aa96cffe6412e6c969fbc49332c2a8d8ec5d6e2c3596d878a361104025f5f77f84dc9ca38e6037abc429c6f69d3a237ea0e396e4f659e41068f4a08e4fafa287f17bbccc8b357b6e49ed038b5bf39d7b22836f0309969ef69f1898cca11479f4db4ce2eaa3e7987dd35516c0b8f3157886e5732cd865f794a2a29ed"); + _ = try fmt.hexToBytes(&expected_ss, "b258b8edf4851b7c1a86cc820743811c52d4bff678e8b94cf6bb5f3714b7dbd6"); + + const kp = try MlKem1024P384.KeyPair.generateDeterministic(seed); + try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes()); + + const enc_result = try kp.public_key.encaps(&randomness); + try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext); + try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret); + + const dec_ss = try kp.secret_key.decaps(&enc_result.ciphertext); + try testing.expectEqualSlices(u8, &expected_ss, &dec_ss); +} + +test "MLKEM1024-P384 test vector 2" { + var seed: [32]u8 = undefined; + var expected_ek: [1665]u8 = undefined; + var randomness: [80]u8 = undefined; + var expected_ct: [1665]u8 = undefined; + var expected_ss: [32]u8 = undefined; + + _ = try fmt.hexToBytes(&seed, "0202020202020202020202020202020202020202020202020202020202020202"); + _ = try fmt.hexToBytes(&expected_ek, "da2020c3757b1fd29fcc785aa999bfb9fb1ac97a1efa300bd08356087ca3b8e685c00013ba18266cf1cacf42879ef4cbc583a3e277328fa0385f34c179a230534061fa65a280c53c5016ca2499881c1876d9635741ca65d760070b5cbf7186c3c3747453021e2e880b8ff8578cda8d84bc3e600b07f7960107b8cccccc8486db306958486dd3cd24f89f2bd93f63757620ccb2d40cb5b40b8fec178cab0c8339c5be3af98567e1678ef79adb21437da71d710250f166577115b290b81ea5a153130cb9b0bc467a75127af4702318c99a8944218a6df1763550eccb1ee500b7ba9d8e180af6ea84f4b278385ac57a37b0cfb06872ab40c2f017aab9c1876004fc9b3484da407a8538fe86754c407868d5222c069088b95f49e83f663c9551712db139c7bc4a024b3b08fb32a5fd926109183284a2afa6e894da8838cc24549c4029c5d099ec8a4af2a23a6ac74ca2b07fec86a812ea7d2f7679a5f72445c347c6dace5e7a6c95319a58055b86d4cd92863df4fa1f96c818dde32efcd140d0332357d442fc3a6258a242005a8d7f279cc9074dbc0862ff553541e4671a879dc9415a897aa13176892a2a0e39119b8bc5159b487ea39196f09093da51a3274087f59400a8ebbfa32481596a2b22417489d0992059900c55c9321823ca5c1b3f17930ccb819560593720a417f58ac9263a74094499b74d448c9d9a96acff9b50b765cd49ab1791d82def1c80307102ac406047106f3cd787c280b42f002a35920e1839b626cc29bf922939a7bf7c4b0294368b4ef397a9015d7937ac8f7926ec022755767a2a296506242aa5c95c712a9cba5c4f20080083c5bdcb0bac0ffa0b726b1f4eb5991f8b36ce582491536f0d32ab5c651c8389b2bb85a1750cad8ff8801356266b77baab0369b6516146dc7ac0e2140fd41b0c701b4c583d7fe01b7073422ba20662d5c22f407ae4c61322151c5eea77d3712e6a442fb93939856062d1eb495f402abb764db0d43999b53abf1ba582085dadf87e1c3a6f9d11aa61016f2148ba07d4269eb543a534519823c73a570931533aa3bc7ab591c7f8cb20fc339f7bc86fd1c02fa296c610f97e93c8326273a3d1361eef770774c059b777a3e6fcbf3a3544bf22000bc574211ace2dcbb3a51721fb6295d4324400ec4ff5822947d0335926193f155b4b3aa5c07b896f9c57c957b217ebc9bce9163893b0d759875157234c4708ece63cb905a3ea2313597b20a93907f8144f28ba2056501288666f37f25608901270b5bfbff78c6f994f80854f26344c0cf49d89b45c53b9b56951ca819a48ac5144c43855a585809ad53cb35087b1e783ad0b6a4d1bbc1604c86fe46d0d286ddbbc53fb90ca779b79813220b8c224fe2317cb9740e21b14a6ca8ea473ac1c195ccb8371cc649575f3879a3a8de3f150f3968a0318b511a47a2df378b525731815c2ec3684a40aa6f96484fb646befd10895a2c40056a87ebb51257bb7e2fca1a0b8b4e6d99dbfd8625853cc455c07903b84fef6326b7a1b793aa482d59113e79439eb3d7c6c4e43e5068f369761d4454244ca235200e30bcb1a92bdd70532076bc86dec10afbb97924c8175a8b69c8b72b9b0805a621dedf28fe73679175b44d5dc97668795d7913ca8b8635c470be0947e60fa621fd0783d301b9577a6da919a1a5c2417cb6d3ef71258f72107d45f79e7204c791b9aa86af78a9ba721cb8cd29997a68ad850862b7bacf3236ae0d290f9e861396c5bcc0b7e2cea2e2160bd86fb3257f75a8ca1acd4e436b1dcb67c48c1a58496a0d4b26605cfa1766a3ed264a5a3cee8c9b9990c4fe985a3edbcc39df9908348120fb81285c8b19f2cc31286433bf949814221cbc15a92b12e3e059675075d31a16f0b44bbd1100e87c0105354109f89312b491bb4ac9e6dacc6f107282d77bee5b726e0573910cc9feaa138e7ca1869b27e6c1a4e11a2a6ef9557aac75434259792556cc9e077a1e4c28dc52e22b40dc4486e13a76aaff3c57bd89f5508908697bc9033623f416027b72740583400c1069af8227a1233aff04a9552370facbde5f728812614b6974f4612334971167388867e88151df0ad07739bd472cf80ab52cc66563dd02062695bb3223ec4163aca8c3f3bd3a04d43284be0c3c09065dc305741099a50578228d5e480b0b4eaff53ed5c441b045d40cf107477beeb65464dbb33ba8204eed3e2657e4e77785bafa23eb5ff537e3fbc747b6fff2bb8adb2fe0f4b790097f85d4da3e252de3ee068ac3c61d3eaae8585377f539b894462178f399cc673b645df5193a70442206d10f8be2229770ece52cf7ac559de87e38fa44e11596658"); + _ = try fmt.hexToBytes(&randomness, "6666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666666"); + _ = try fmt.hexToBytes(&expected_ct, "dc29c7e08271fe62f2ddb83c28f8d2b9c9d921679c2db284a2dab0dad7dffb16c6e28261d013b872f006cbaaaf683c60ae425e946d0b7bfd01ed9f181af60ed1fab63327869456446ef94dcd3485cafbd6c4414593428a7d1c21021b2cd4c92a49422c95c669239d22864c60035b77811f59b0995a21ac2fdc12fdc20b660dcbf2b39b3c6ea082bcc463d8300b9ac620922fbd18e7f2450b902067d6645d3e09a90e525ee18f3f7924365a877fe78204e2b77e81530f65d3c8a999da301d5ccd4acab70412cd6c79cfdc78025b3bbb7637c36093bae04a1b78924280ddb6a79bd5c2e2ee30f73ff7ff878f028021967adda04dabd8b1b8db747e87e2144232bc1e1aeabb6ce697aaad4bba725b54bb4dc2b52687a60b68384124dcf5719e643ad63b3af8f1fb0b1d9ded7066860a7cf6a5a661d7a207dea2d330d02b1cc4744234944d0ec1ddbaf83727fcf521ff2cf04fd0360d7460dc312cd1a78231dcdbc62a378d1e6ad6fd677327f3db12c904c582189213e642683e6fb40f912d035e145d80822bf7631bef6b2f417bc9c011f5b41260b298d8efa391e777166cd9a61ca79f482244af217cf7bf600c17a2e02153d6185b259afa4cd98dccc80510857a27e4d76539feb08c06c98cac68d81162df5b6d311da9b1193a6c65070e579adfbe15ed7ed1d996bf1e70ad7a3f08500c989dd7ff9d9950c43ebcd865a6f76e5ded66ae606edeb48d492d44a3a9923c6c15a53db2130b2aa45898e80b0b2e45b986a5e47ab95b01e36d39fe0e1c3af35c8874a72a5154d088afde172d5dfd800443552ea614a8aca28d808dbc7ced4b519bce7ca0109e6d53ba8515f45d3a283c51eb6567748e9ebbf3fdb90d860ab9a062d941641d17a4051752e5b94cd20a04cd737e02a5e240e668f9a20ec8be368197255e265b3c47da59607a4f0ff49dec1db4c805fd78315d07cce287de286f7574319afdb00e806e4f1b30b56bcbfe2c86c495431edf3176b65498ce8b7a5b2bd2568cd9003686048ec8940cea9b73eee194d394408510baf0fa576be1058a6ac74ed2de2015d0a6757052dbc55ce385acc31e266e165f56f68a9019896e4b78a1d8e1c36d3a7a3c3a9f239a60d987113af9a76ce960e9e2e9855142cbb881681e0f2944f7550b27efcaf2f165eb4138f06143f59a2f9dec289b68a164e68b4a911e8b2ac96f532287a01a37a21768dcc450ce25b4c0460a162989b150d87538652b645d4c17e625eb949138f26ff01f19f8b83bad74b4e66c82291619022129bae11e53812a759a41d7e4fc922dd776a67919cd40ca26bf908996b36750006e528033b85a663bc7a717b027e8b17f761d6fdf6a4c7dea2cdeef9e72de0108a5608343f12076ff0ae0c5dde2ede197c0f72b4732f5599ba11ccff6e16768e3bbb661ea4ad0cc2c69a725f80cc820fb94bdc9dc9c61ce1a955559d5427d6c0b354cd3a83f3cfdc4e7d1f01a4d5ef1ff54239e5d9d8e6384c5516290797d5641fb290b2065be0426c7a05898df9dfd3fccc0841a96cef6d312ef36ca01bd9be5dee8b9dc95637789b1e7ca05b51bec1e8e17aaf198e2b8eec015e921b820da20126926b460a3cefad98d6111e5bb9143328eb270d38bbcbd430a7a6f3235333684a77e040bd2a3a27aa350cfdcb34edd48fea62bfc6152527300b9447c67340ad97dc43c1fccdc6812e45ac28b379ecbe8c06da3b6d3546e4acfccc0faf26cb3240eecef89690f0f884739b880c3a3940a0cdc5fedcfb5bc7044bdb7d8502a5dd6f6ab3e029c8141209b5f9e196261921d5be6f79544fa7361651039f2a97fad392392932b57259ebd740a7100c959901d587da7df9f6961052cfabfe55a12746a5dea3be2c120b25a1a50a2177d7cb4c81be846c7c67f2214f85ae223271e83747aecd899e7efe8ed67aca9936df81bcb5602e0eea600dafcde932b0d6da9e96d4021a4be612ce6e25bce8a71ec218b254e50998d436ada860cbb920b79ca917669be9d54eb63701706b5d0da2d8dcdba97ffcbd7bd76b1b5a29cef2c7cd9f5ad00fe40969ddf9b8920b1af9f86eb690f93705afae26625508ccc2c475a8ec94646999444d9326ceacf92559427b33c68dcf7c7b5fa76866ac88803d1a9ebda8700afd31af16746d1efec44259a281875066f7e30fc9631da7c0ff98727f84418da3bb39d3e63f03e8f3fd6dbe001fa9104e36e0b36bc7be4eb11f50c9b88f5afbfb0022de2db3a8148dcc1f91575b55f47bedea0e916caca594e6f6bf709c634f8fc292d6c0b83b120d5f26f21682007e17d25807dd9b42365550ee4954c5537e2d846ca833330bc465fea5d7d6a5c5bf3"); + _ = try fmt.hexToBytes(&expected_ss, "64a60015ced50f3972d4bb5cfb566f2a800056693945b309abd0ddeee3c1062a"); + + const kp = try MlKem1024P384.KeyPair.generateDeterministic(seed); + try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes()); + + const enc_result = try kp.public_key.encaps(&randomness); + try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext); + try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret); + + const dec_ss = try kp.secret_key.decaps(&enc_result.ciphertext); + try testing.expectEqualSlices(u8, &expected_ss, &dec_ss); +} + +test "MLKEM1024-P384 test vector 3" { + var seed: [32]u8 = undefined; + var expected_ek: [1665]u8 = undefined; + var randomness: [80]u8 = undefined; + var expected_ct: [1665]u8 = undefined; + var expected_ss: [32]u8 = undefined; + + _ = try fmt.hexToBytes(&seed, "0303030303030303030303030303030303030303030303030303030303030303"); + _ = try fmt.hexToBytes(&expected_ek, "a7a3aa9b70890e37309eaa5b38333f8ca91ef7b72c01781d6cd542c2db1e1383c49b78757b678856016995b51709a2150aac314a815630c408fc5944004ac3709a5fa6f13367b3397338c8adea1d72e8b092e9b6659c95f1a3c25937766e1c8f02b66a7e95935022161472420086678d507dcd660df1212ffcd84c80488e33a269d192c3f09a1ba6a2bc5a5061cb32c7c18958902bb767d6bda6563023d7ace28bb0c7642c839547e46103c0c5311e5bab4f41aeb16abad34a0a92465c5aeb7085d88659a175c6e717b4778ff3ea8d19e6c793d5ac41b0532a043b6c3826352090b47a33a2b0567d555bc7484cce1120b45a55a9aa148bf135045c941805bd4243a96c2c7a9fe51aa8705c0da35b6f6689e0578ade37c2ff134d7e366ce7492b7905497502159a964cf7b1b87c1186d79892256a7f5e7228de625fb03c91560c8f52f4c6cc7714f5d70729d7a42c6057d26506b910599b10925562c02e74354551ba0b7b959610ca08b540346c3620d4b83d9b1707755432567fbf66b3df324a0a8798f16a4086ac5628743570642ff5facd9be5085e99a31a7670c33c5ba991cb1d14cdb5231704336df02c51384a2bbbaac9c210063eb5763dd291671aa511cb5738f265352661ab6cc7bac2755d0a87a0a7bdfe435ecd0714e07439eb04076f654d4e51cf86289504640d3709b6c7717634b11cf55b87a303bd28965a0d31089a6c8313fcb10bb31cf7b4a6b45c7d18189d5ec43294483ecb3073e2558c5054102e5b3f3727579dfb4277890e900c20bdf5a1619ac89e4b9c2ef3cba18748cf8b95e3ac38820476a3f254ec323eee9053626707c12a9518865372053102687b7508c7c2ca80b4fb7685509f80a3206bd28af2551c662580232ca8b6441971116723e04077bc37e0e2640b8000b9f80c372c1f69662e24d53862006fd7d445acd5457794ceb3490cc24a5281b6324e760985847b7b9612ba3ba0d0e492bfd0b47f4069a2e54373a05628957bbaa04cfbd2280abb1a9ad89d3b688656e76ec539ce8838a269b11c124b5875695ac0cb3d2d940a6cd85a2b93266d56a71618be0a577357a78817379e83abb820d1a206d779aa887e5ec402d92c9da9b9254d03a27c17aef650ac8c4381f830c588022125f4181ecb5c4d5636b7065d93454c47d784ac35a08501c520112a604b3d1ff81f94a20fc090a3cfc9754e578cd0ca9c34360ca4f08176e35cc0945f69294eb644a6a31177220c02c0149d1ec4a613bbc2168136aee307e62ac4288b0b64f39e5432983103c57771a030d3b32f053408351e48f11435b693a9560415450138bb3c703a29f2009bd8a890d9b650419bc5f1948d1358a9f041047d030d2e1026f75532ada2763f98003f8a4a2540c19aac7eb5c817d2aabff9eb556e449c8d447015799d179b8fb07684415a5b770c10a2eab05ec3908d547f6e16058fc399af653d6c440f7784bea7619056b79474eb8838cb4b83a029a0d9148ce8bb188cade92024d7f90f37c12fcf73bcec61cf81566b11171725226646230134158762a4c5d1b609c660c9b2c93f0a72b7066b8c219a07c893b89b22705ed94da06c97e31a95246b2d4773593d1b5e2c07bb6d3a5149b0ab21539f200c8445160c916a35d17930a74659321c65876342747710c4c573f69c7e25fa3c697b32d3746e2e7b34f54147b5f348d27534c73a0d6382a8b622a665da907fe40b19e0856593be36c7954976233c8219c8a86cbafcc8c5d20c0926757dd17b81666b78c0346a352a18c2ac7294b5d0a8b8d8643689c809fdc47022492dee3b15b713b07990b81230c4c0906476a09fd65445aa3061d71b277d806735218389cc903676c6eb776148396c2d32a49b6a5172a8cf56267f780a2b10175eaef72efce516812bbf3469582b1c6798a62709131cd419ab2faa5855b6451866809ae198859914d8855ba9d109419b2dc18c322da220fde6ab21ab7eadf10deab29a6bdcc46c6bb65efc06485a9e6673b665036cda98bf2c64884d08cf76b086a9ca7866b5c8ab6134fe202b82794922e083c0e354b1546ee9c6c59898c972a12a423c32f23670762c8ef95a63d4d5ba20fac2eada0abd647e2d77157e8cc7ea2c901b3a873e5b017596a30f66606ed85e452437fad731186aa9acc4c508ad2a4f246fcc0326ea60a0341c81a88c588e0fa10e146db47bf379320b4dd504621bdba7406b8a3238a2e65070ae95026f6bfc7d946e1530c334f92a50151a6e73b55a4567697045286ceb5bfc150519382c565868a75ada2db86b091661e0209b62470616819572f4580dcfc1b799f5c7e5cf37bcb9fad65a9bcdaf332e3663"); + _ = try fmt.hexToBytes(&randomness, "6767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767676767"); + _ = try fmt.hexToBytes(&expected_ct, "e6d40ddf8ba0b0a4a08042ab0a9c0915b51e330251fb8dfe8bb1bde64b387785de977142587424bb00533be0429643d77c8736ed8a1dcc6a8d8f6a5e5c1aaf619344eb42cb56d135b18c1eaaa5e87be1ce67eebdf514399f2161f6c2a38d597550e1e7490951938c80909063d9b6dd19434c8aef5f2ce5b4a4d393ac36c0c8e287647f6dcc584b8ab14c6f357a19fd535ca7381a76e58e6095adb0179be4a19114bcc0423f604826f5a8d813c52ae802b7beff1edda7ba0f301b552e916e650808323dde239be8dd561da9c8d07986d010219eb364d15788084725ca425218af2722a51c5741e81cb787fc60ae97e3edcd3e683b3dfebf2a8a0269c737b5ac44a03342257de219627d4b302914d09d9aafb95388098631c3ce81c0edfb7c293622868d0e080c019c9c34efd4bb8f3fb148e7267b1b9bba2f7d23d387d5accc137333e45b76182cfd29f22c477aabbf4b6e5d24f7eb2efd3c1c4e6b6e70159e44df6682b1860d329b5f5986b8a2f4f73beb6200a78edcabc6168bd63fb41a663a263d2c4f74b84fe04d393e7e5bfb684b4549887c30e26d02ba8ecd2bd0e6a9be1e2e8706305f7fc18b6baf0ecca93f82a978385227fb38cb2f0ff4b7606a5672fe2abb9bba4f27c3b33b567a4e4a18bef73672c013544ac2978e1eb589bf42242892b8ab3ffb240a1d778267137695b031fd148583ad6985a92c8c6dddd6b7f82b63c738ec8df6b969098ee7687784bf8234ff52e7fe4ba1b892ab8a38a4a5750828c50b68ddd9a8ea0ef34d2a3a779d6916c1f56e0732e44eef7535e6e1f1717e3553db771059996e78c3469d0c60b461e89f6afdfce270cc8bb45729e26f4db325d925c81585f5d29268f3e6fa4e2ecf8456d6c2edc61c025796b708d08dc497483463fe63ae5cd69c57edf7766ad1f2b231dcf376010eebe13806a1b3c51610d7b35b2b00fce2ff3b815c9197776c6b96ac1612d9af7521ccfa92fb3af0cde612f9d7c55912f98f14a14fbc4819e49115cb3007abe2c3f5069dd950ed40a79451186952e02c381b8d33c6a6b6a5538bba5c23e78091742fd816e932cad065642c13a99d64d828275683cfd16a3a6b853bd2414f3b609f9f1f5eaa3ddc25863fddcb09fc60f82ccf49679c35e2d9a6399d97d71c7ad808953deb2696f39c62d753fa291b95015b192c2914ce4a31ee540b9b396828053e45458220b52947409df006ff165f9f5cf43729274783db7562439e34fdaa4ef6de9d3ccb55bc79e3e18a5018d2ae90b1605dc397c72fab29be1b155dc21e62db17890c376564484655f0807830548892bc9a2fb70ca653d70a15d73936fd71839fb55fb83060756807e71d5f87d3999d2e86dba0116bd90b8c0d45e590425fe7cdd9dea7585180bd1817186cc52ea79bb221f8459b136e39be7feb7a489b988c3bfc9890ca8d2b91d3c6197454c138f5e12e231bc3b690829031356eeb38741553774016515994243e071f12996ace3f812efd6b79f84fd7c10f72fb751689606d94e01dac7bd28491b5ee592ab3ecdfa7bca01fb07e7cbaf94ba5bf9ec0b00ce7baf47b8cc9b9251ba505ddaea9f7f6f14ea36ecadc09cee557fe254c4353cc2b558bc9016dd0d079568d2a4de616099083ed9e13a493c9413b9a4a151d22b0fbeaf773e7e69cb37736593b1885b11a4f441d0718128ff0b5bb66a061484569d6616b57666e47a8e3696871d24636d9d1e23ec64f8bf11e7b7315d060539ef9b94051feecd6a1efd6a0c1b662a04eefd6db64ca7f7dbd36a8b637b98dc39dbec46f6d804e35d9b10d80479e45f664ae1d1f6abce7ba153bda60002244dd33ec57fd4b27d9ef5d7b2c04757baac4a222afd09dede3b5e19f6744330238410c4edde037a95faf805285e7758435128d16384186daf0a72652f19e4902112dd0c78c10446eb00dc312a9eaa190057f0dec993322d9e2c338d694ff204e7602c9898610ac462aec8bcc66ed439da1d85b6f6cdc24e0b01a018130204efa313e1ba53469b526734dff82b98489d63c44240fb93ac53858ffe056dfa935d565ce33bce416ebe9937206fcc78237ce755b1fb22c3a4ad0601e84495765f7d8b48a7e4c421a881c21b5a77964ce0e94e428ba8c4d4de25b644140cab513687dc242732f07c2f4773d8952cf1c66fb345331679bbbae98ff5162ff31e6253d71d26b054d5c7ab0fad20a2f0404b6f05e2da70b8a532584a4561e870d472388e85868a5a9040342dc362b319bc554f1b99e3ef29dd6d7b39df6ee9818eb4539eced50a45fe98cd5f5f786f1d2832bbfe1b058b8e0dca415a90f99fa7aa33e5219aecff4f39fa8b5c3edf699a9ff"); + _ = try fmt.hexToBytes(&expected_ss, "fc5e3871973b49b494090579634603099faf8f0bb022c4daba472cb771f1bac7"); + + const kp = try MlKem1024P384.KeyPair.generateDeterministic(seed); + try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes()); + + const enc_result = try kp.public_key.encaps(&randomness); + try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext); + try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret); + + const dec_ss = try kp.secret_key.decaps(&enc_result.ciphertext); + try testing.expectEqualSlices(u8, &expected_ss, &dec_ss); +} + +test "MLKEM1024-P384 test vector 4" { + var seed: [32]u8 = undefined; + var expected_ek: [1665]u8 = undefined; + var randomness: [80]u8 = undefined; + var expected_ct: [1665]u8 = undefined; + var expected_ss: [32]u8 = undefined; + + _ = try fmt.hexToBytes(&seed, "0404040404040404040404040404040404040404040404040404040404040404"); + _ = try fmt.hexToBytes(&expected_ek, "27160bd008ab6864b35c32ceec05a51637cd44560833656624e34ff9cc88279b60f3a97a1ea43c2397cb1243bc2531c3db46124113c14d59b50e988bdcb86419878a4f8721f68ba3c9e8382539983b3c59808c3a50d5661f6553955170278667cc2372d82343af3a1fed09be09919ecd257fc25889fffc407745983c092e7af35cd4b23d57d935d9c399f4a4cf53967a0bc4309c106b1e3914316b1e22ac9e4a55441b35cfb54959af1332cb6435d9ba77f18ba709aa83fe4b671c89888d52304979ceaa88339f24b992648af55c4e7f4aab5e815f81936a74001b6a5ab5204935983a8073e61dbe49938c685e4c3a14cc578f11dca9a48491f05acce985bb84f14eeb8bbd62ec203bc573acd1212ef84bca45b4f523b9d8d5c15887be74447c2d877d64c175a0d45ccbb8bb82b26232256709c8a568662f08a1675baa3a698bbea2d5532287a9de62909512641295a9627bad0296ae59e3b2051372624384aa61b4e9b19f0dca1bac6c10a31277189a1331f5b05a112c26f70859163147215b4b2c00d2a4057fe39d9d28968798a66b3732a7763934f81de2a91d69e01b1d7807acdc2ef893be10224794099c3d6904deab0d9c48ab51f31b85295accb4c96e9c2296fc36ea1b826c499912275b368499423c2abb70cd08a005f17c2215972936ac5f25b2cb3ae1ba9f86be02668633e582bd2a9e5226ca2b1946c9d7503708a7877505da727cadc693420acc892701619c9ec8792c00669bb5c5a26c8045aed7390374bad5d52390730cde23237eeb2546b0aa469b3387f147bb413b170aa4812604898801a45905214b0f2fab10058a532f379bfcf96a76725704e07c3e24c5c86b504a74ad31ac8b29f9c99889a28c42256397c78ad0870aca087b36b2d1031e1e8435103072262156b93215a7f0ae8f0c5b7f7c81d5713f26d77d0ffa243241c95fd30f95164c50197b7b1a2ea1dc4efee0ce52976d89ac9d29e11ac5392ce313bb92679542dcb364ac9f764c0c192b3ed3b535de1a51f4e5895d48a5c106b0e959165f536db2ec4cab684e8c956777bcba1bf23b66730d5a2abdbca85a3c265dba69c5af5b7364018092d97a97a85a2fa38969688cf8fca69410b3b02c380352b800227b99a57144f352c3f6c7a092659553b7ac4a392b2512b90825cb28283708b2c067cedc208cdce82734d819f0447eebb17bd9d29272d15c0708760c517d8d5046b87aa51929314361a70ea4491137b288b55cbbd005abd9bf4c878a29e7b5434a15ee9731ecb42f80709993e570f7285045f8338649a335da2e03489a9a0a0257d042ce220f91b4475ab64228d64e0dbb3ee264883f605c0b7240fecc8f33ab94e0005a6df218bd393f73d3a18aaba6a8d15df009238fa3c85ae7c9170a013382a57b684964761ed6e1172f0c7e9aac6b9e040c389b3c67e04b1202aff784b954acafde9c7ac838b0407078fd1a5bed71cdb74018a3b94aa24a18960a1e22c31ea251c125013b5d7a46db184d82a35bfea1a9a609909d2686247555695a7686f69846d576212c72d1958bf27c037e2a13d8b84a7f5c10e546757bd87b3ca85a42238a4d837490742f28694b8db5c5221baadd9c7e6f09acceec5f7e39b717337970b8921d7c6861179fbac3154051ab236a56a1aa077ce90cd0157fcdb9c7a92950ea0b29f02c3478d333cb3a130cf30d9916c810b12bee8473bf2b521b278a8e458b5898a512b77f87f9840550450be694c5e9ba4b38b703519cac558296ab08881a7986c7818017393593315838b3fbcb8d95f854d391afa6589090a8ac9964b9a09c721716407f29c97e3546b4d90d60da11ef608567f0730ac09d5bbb0f93a930d8c533fc245833c4902ad0b53a8736b5770d008d2c972605c4319d78a69a64a4358929ca1e15a5329393493cac5ee83a834681268ab4dfca1e7c13c4570c56555195bf6c7af3c5a1204555ca8380d814c0e9ab830a376f46570b27171c910c41521810ea21685fc4c5fadcb5684205800703abb9b39818a2a6d4af4ce52fd73b4b620ca9a5133257d40ea91b2b6d6bccaf54c991d68222fc81dcc5b32e936d3f28157339cae6fb931c05247913812dc41682f91f75f3173bd072171aa880b1a12470276826a5bda028b23bba0708093ce795c4fa8f37c43cd70a2d06a2a858ae4dbd792ad37fbb40c43d94064c5dc13597c5981a8bddf98e045825acf2447c53b568af3835c532fb0214aca9d4626da03f97563cfdcc97c4cc773e2e79c60029468539d898913b7cedfe6179cdc1ab2b27170be8a352d83ce3cb78809392091fac41712d0ba2401e10939d93d16c77b5a1be9e9b056a8944aa"); + _ = try fmt.hexToBytes(&randomness, "6868686868686868686868686868686868686868686868686868686868686868686868686868686868686868686868686868686868686868686868686868686868686868686868686868686868686868"); + _ = try fmt.hexToBytes(&expected_ct, "94bb545f2e6e3baf2b0a9bfe14dfe38c34d73d7a2512ae51b8e1c92b3e335aeee4b9189567877bf56c6073b81f3f6d35c1c11a4925c4abce2da026b67ba02b2edcfe6454165ee103225987d4654f45517a4d05cfd42a18cdd4462cc6e455c54e845c5fa6b50ccf8229a35245a1adf27779aae1c97c6143223539d97d2ec7a91f602774e43be57aede2297f31cfe7bd24450a408f611ba8ec5977d7f8106df0361b7d959ea971a1c57513b99c8f61e1c48a10648d2a987bb5a86841852b5258d8df8c7c48ee5d1f24c68f4300e6f0e727cd487f747b5826edb7ecf1e291f6de1e80fdf6caf6297a2f76d735fb70c21877a9a22177891da422b1b06a0c98aa2f2847c862169829e8ce2477a494aea4bb94aa87bd0517044271dba667d4e3d0f06f52b424a1bf8b5d1d1fe2fe0eaae058f05193be403fbb8cfba5b8ea821c057cc3f5a44048d1f2fdee5567858bfc8aad944e3e2335733b16ec3bff838de5697ecfbff71d19fa6e466120728bf8d95873eca59566ecec7cfce924572ef4c4b6614c6cfd4cc6c9febf08045d813865cb810a8b67bdd2e3057b2cd5ad1385569178407beb92c7190ec91ce403f0e9636d414c299502446be23baeae5cf25976ad6f2f21ed0a582b8969e390803d2683bc91c5af92ce637c5bba627bc1dcf0d4936e84419a615a9a8ddda4100ad3f1fe6aeeee1c865b4b59efa73856a1f2bf06d661b069005ab96944322a4aedf0420893be68cfe9cc59188836a7a1fcbe7fb26806de62931f72fa5403587c425a974defaf228e9f8f71428eecf487eff5a7c71fe62c31c5911d9c96bde7f766a4bf26ae88f9d53de35e6dac62ce3ef51576a45e8b907f1ff0a5292641850cae8b2116bc5502ccdc26e27027560cb776a54114ba1ccba9f9e1f91a99f80293f50d5397121f8816c1cdbe60352cf9f9c779333cc72e7ae51cd2819394822a2c3f2edfbb0099b272c2f883fa11c3bbeaec067ca54caf11ddc45b1ea79c573b84cb96e89fdbb899571c00661e8544a64ae0a96af5ccb65268ea80841f6ef8a832ff83b9d65d313309ee8d4a15eb03b233272a83cc0fbc587d06a6deba39e2c6ab6f7b8fc7f23841f5b4b7751fafd6fe28c439c3e39f03db741b81757d443f4aa8141789e81401edafebbd6bff34bc7f1f3f5006aed3033fba74f369fe37864a592da2379ec49d97433d0f8aff262fd5465c9db351215c21fc1adf5cdab6ccfc49f28627296f6ae7075bfbffe1ab2c72fc404256aa253300b53042da954e6fd16dbfb6759f186b0d215ebfd09d21b7f5385a27c77f65d5dca4ae62550f05b8689c7c0ee041652179d5aacb1d338ef667abc1aa74a11aec98d91f92d878c6452878baa22b0595d0dd888fd498670ba01471ebc246d5faec58946f57d09b8fa49419995b75a6260f56c8657abd288d00cb6b29699394f48f02180a45dc4c595cb7a62700ad7130f19cefe07814134059f428d1f19a0c786edbabe2e1d085859fad0d3c455a3ebc1056fd1017fefd7bdaa370191fa912d5c1e8005c8f6a6a2c9b933ba2d90181202c7107ebd5d7c1dd70f52ca0ed678ede21346ac308f846c4c26cece324b2dc83d5d4b68f0fbb0a9d6a9fe5ea71c6f53fecf61913573606e5f29f23c1f683f31688399bb2856f15e11a8c3f5d9425b708d2bb74064956c43983979d6ae434610d394e5a81acb26951f81ea965d2c4dd12edfcd5954214d58abfc620a76e4f6ff37859d110a0718c397133cef8600f44596559b163626cb1557be60772374be9e5ae26fa629c1e11f931978067969231405614f602261f7fc7c77acb57d2b82e8054d0aa811cca46e0209a733f4b70fe37776768512cb2ec63d90cfcc0493aee22fc46c3ae9b47024238993228406583e200d641165fe3c4c1dcf3614842ac0840108db7a5fd136ddf7ab814d5026c4e54f9c3f40c8ab9d33b5118f22146938b0234c6ff71ac1b08a04968ec277b84a3f4b21cd20ef50c0dc2f2c71ff2171927c4cf68fb460b4837d66cf9e92051237497f9c1efdfe16a04d317263e6a825daaa91f4c073e09dbded783d13cf65cd5223ada3416d7704267c8a1a64966aa43a93b7b0524b1058b01d7281d43ed038d1a95139e280546e3123011b9d766d6685f9b5b93d8e927663a09634f38adb3f16c7c2c63992983ca5a4fcaf8c240d194f87567cfb51daca300b6c7bf21ad968b15dc1465040ad9fd3e2cfa1c805fed13a55a17e9c5922adb096d5b253299024718b53d3bf0c98bfdc0a7b4a88c321600ddf539ac791808fdf8b189c8ad00dc5397814d330521c1e357f39b9c60867b78895038ef2468175040231cdee02250b2dabb6f6ff5"); + _ = try fmt.hexToBytes(&expected_ss, "c3f7adc77f966a24f642197c478583b6eb8c2fe3fab1980925b628b9ed5ffeef"); + + const kp = try MlKem1024P384.KeyPair.generateDeterministic(seed); + try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes()); + + const enc_result = try kp.public_key.encaps(&randomness); + try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext); + try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret); + + const dec_ss = try kp.secret_key.decaps(&enc_result.ciphertext); + try testing.expectEqualSlices(u8, &expected_ss, &dec_ss); +} + +test "MLKEM1024-P384 test vector 5" { + var seed: [32]u8 = undefined; + var expected_ek: [1665]u8 = undefined; + var randomness: [80]u8 = undefined; + var expected_ct: [1665]u8 = undefined; + var expected_ss: [32]u8 = undefined; + + _ = try fmt.hexToBytes(&seed, "0505050505050505050505050505050505050505050505050505050505050505"); + _ = try fmt.hexToBytes(&expected_ek, "5b91a689d96b5c8330d76361fbbaa995e78b4c5229ab4a321513988b1a8c58664d9aa70e056813a064020e51c7bd033105c72fc1aa5da5f16973919d17445a75b3cbd5c20f42c8c14717b78608b241d37c80ba20e50c20048aa67f17ccec332c4b58266b0bb4a42a099723063765578cd35614dbcbcad14755159b185865c2e6bac9c4ac5242a0b4eb63c96c90ef63869bc8a7b00a3bb62b5d35ca3bd1e0b90cb75f24d5c78201874eda2fc0985823090ba8c56778cca9c3780e2c7c3c5911515efa88ac37068d12bf95e262d9bc0c540154f168a6d2eb4ed1ac21cc295008c3ca1954919a0a76a4963090e915d2293bc0942a01eb8c5e4c3db93b32147ba890d9a5574b5e119b14d732431bf27a2bc74e8cfa205488b273c16070287b505a4c6d4b564e4188c5146532f0c87070816cb42dd80aad7a028d9105343b6724fb81952751765a0917305714dd60360cb7add1473f4651b4716577138396a3798447d549fa01cbd8d18b395259b1dc4b66d7b08e2284d9b577d3828714d6887dc243a0707d5f097c8ec33ad6247daaf92fa834042ed774b3c35da3195a290c6513d0c008d6b2de4aa0efc9500a42949a5407338890c2720f8f3a5519640436b85fb2c53ae9e8cc202c04ddcc6de99bb1a1181608a3887906cb1db845ac73c1269b1aec2582bf5865fe837355b4b3828059d812b6e2186bfd5679eaba1e2fe4a367157feb1b0f5d6161924aadf9c637e17bbeef2a8504815b40d8c43280c3c3cb9a9bf5c7d15926c1a464f90577b3fc72b9b679cd4a8a67f9576c98b5f1e4218d498af76782cf2b5c4eec64767645b338455dc4af8585ab98c8a8002c27891429850b9f0a84af0043c831778dfe79acecf868dcb6174e2bb15c96374ce968ffb6c9ec60972353b1b0a77c066c2ef9d6691ea5a7bb28b119ac91ee371865bc89d4780c3fe8955212cbfd5c12732c2e43aa0c52d133739cc7b5007a414374b5029bc983ba203000bd7a887dea2c00cb110233b5f6876d02d539c5d6027f0319fa2b9af0eba53619a4f447ca71ea97dec65ffb5541af64c406bb25c2970b2bb457b0b755ae94aaa398c38a945274a75c2beb2c4610031b7a41fb694a1a880f97bb88d3579eec2390b246ab36f208e79c65d4a6194b770ef2a0caba95b41b315545d0080e6927ac4a793a95acaa870451b6c5cad62669465c9f130548cb73ac57b616343dece0a9c1e249edd4487023a216b95862474cf8330524f4306fd640b7eabd0ce87ef55b80e721ccac2c5b0e0b16b5bb74f6139fd2d49813765988689e32358ea49b3425101a50fa1766d5880f3a3c35683000f6b2e296b069994d83b51232914d5c1325225a06ef52a49497142d06c16f485e1c10553e11a253799eb155659de16bf9eb127ceb86230cc369487c7a276991541460b6246c998bd72152770c094c5364c41357ef8105fd7a25306b33c696562a05c33f1b11d46906f9e38a4e8368ab4a5b62971d9c1b094f286077f6906f463be2ac1ba05204a129bb1334082bd71810e5be4ec75350709e57b6b266c9c5b9113675b0a7b6b5b9bb18718b90bca8f79184060e3fb13b1f75c87616cc52348e99c79458e149f3f6a4a9782523213f07b2bc9e4accab111feda22e6619b64bc04e769b7cb4c445fe5338c2a5362908a818a6b851aa1832933e30a69d9dc339ad0177a34073322211478878768b350224c3026b12ae40254d302e6392422c506cdee44f819b4bc11525a26b891ad5053623870b1957a274904e4ccdc562ae169605517c7ee3148409a78568daa374982c9c920d5821a085a529756171dc59aa253878a2d3bc6c53c8b7cc51be85233603211c87573ed90cc1d59819ba11e00a4eced6cfa6d26af46a971699038b66475e82bc23b5410c02466acc37a7d79f1a6848aa973a9d628afb4c90f1a7074698bf34f13674f62f81db5411395e9914095be77f14020711254a4078a4999c854c2ccbd196b60311305fe322ab32983a535fcb81162e6a75eba885f6d2228a28ce72f697728a1abefa9493e1388910c8977cce95ab4a5625a600fcbae4f03f2681020b18092119584d27799f7c3d0154c8a050296a4c70abb55423c57c597701770031fa2c8d5d4413f8f15cb615b15f936a93d61f8ad971c68cb4d7b239ca53a5eff8b3d99488032e03682ff7c9c54af9d21b77d559c7fa678a9487d60781bd1004f92f4a2fb94044ebc13e4afff556e83312d782870ac897002110583502b4c062955262c9edbf352a49bf3247a5144186d78d3b28fbd764132787444fa933c565091883535c7eb9b8daab2638fafccd40c49de6050e28446c1cded052f676a243"); + _ = try fmt.hexToBytes(&randomness, "6969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969696969"); + _ = try fmt.hexToBytes(&expected_ct, "913ebdd7649538471b99c731ad2351ab626b20bd6d9a4bf713e6669c80f750d8b1277b3f22ec2e7a73c5fd422327ee91bc5ad7046460bd27f6c10dc4d5e9688c871b4aeaee4051dcfe085a1b87354c8fd8fe4f72051b0e87f30c21f02a8dc65ad004b0ebc8facf684c8c67b0fd7d280bb5349209856ca86edb80ec2757f80d9a00c85b1b92a013a969ffa25cbee722c788d631c67914e360481cdb6414277cf71f647765924bb568df6c37c0578e145b95f487f4814fa9eaedfc235b87e5eb417c4f56a8e19c92f018e6330fbfae0c082a9c6f179bff9b79fcb0a880e14472c2b4a1ac4b2466755bfbfcb1167b8147606f8331c2d5ed62216fe475310c4e2ea6e426dc2de4e67d93bbda215aef0b3d536a6c99078e513f4b77d22728fe23efa6e881d92a5db5249b4f89aba7a703fe2333e17072199d266050cb2ff4a2319c219122988448645968fe098be68d7a30f6b13ffa8ee05f01b9d5728db03b4c37761d9cc9eae459159edcaa87442e067224d0b84b54293da273a80697158cd93ad7a08cb9427bc8d0395af0aead90b10d8c2a47f9cec11953adf2e74b5467ee2f16e0f3cb2b693a2d2541d643fac3d7bec1b03b30cc482b7b8105167f0f4478bf71f7dbd2c4199878887fa7136cbfdb329edfa2508dc5b305b844d05aaf0914b6682dec0f57def521e606b9d15ab9de438106fef0d0834c9839e765c7869b9b76aadb4cfc2d9f02393d4979bf54f59f8d55a38e4f2d653f5a4bb5c250abb6fc65d6a8496b778a19fa1a1c7fb8f4fa6f6fac1f47b3fa97f2bc9fc1a6fcdea508bebfa8b1542878dfc06413dfb414e2cc119c3709e5049e5c891d9b5188da8fa10527bc44440ceb347b7acee506634ae57636dbee18a460485cc2dbb90f34489df3b6e583ceabee9f075a72e8e2d43ef7d71ceddd61af0c7fca81a049b7e454477df86c0bdddf676afa186edd716273ccf9805c690fd8ccce852c70bcd9eceabaf807867989d52cd9280d0a4816b093335716c6b4321954c9aaa6f8fda48549987fad3d95213f6d66f62c406e101a4958588250d15d879f2f83f785a5446b396f73074ba69d98ba1be0b6ea340fcc2d0eede24972a6c460c59642546cd8ba9df1bbc050b1b7c0c4d2b123fb8dd66f32126a744b8f1af573ad36e6f8c8aeea44e04695e0ce7df7cd35a4fcdf29eca300eaf35c04ad9ec68e286fd991fefe9d58b96e6c2b86dfa6abc314d9f83432357747d7d129d69dcad7994df4b13fae3028a5b402d4fb1731ea32e6969770c0599bdc7c52e27ef1477d0e00fb97c3d8e5eac967956c05d6d35c9d956c36606e17c9e566195341ecdd6a87a98923fc7833bad39249823c3fa905cb2b69c70e8f984c3b5b165b3621a50ae69686185da98a0035d112a05a9a3a5615afac3cc95ab4cdf385c1cf3471fbcad069e6ed6944ccc27a99e85257ebff3d331fc695fdda1246ea63646a02b91764b9ba025f27c7c66fc1311d8fb6ea45b0caa563042c18a3caabe3cf8e2c3ff062e20960c73d8c5b43a9b0d47dfaa852190ff58db48eb73325211c462ee43aeeacf83d257b23a0f1f89f64e1338b2232021e0b8e2cb910b28b476f0cae0d757ef65b6a0f799ebb9a9a22b67c636fb1f1def2064b362ef6c59ec8e780054c7164d949305ea7e14fe9293e4276a3aed2e40bf46ff87565379fba60b71e57092afb970c096442d74f1bcd902e5d987f15e03d844e00a9c34b08d1bc94091e7e0bb40e92504bf12a13b8bc8a84b09ef32947f6394b0f7e07754079e77f7dac507798f3e5ff05b8a8d630f39fe954ef891f17069022e79bb677a73c4f7e4adc7e8ff5ebb48676b4e4d21a6aa9b667dad22e62338d0be0ea51694d2ef289e64a169e6c5b755c729e12aecd0f98e9c8091b86f92fa0315312289beecf266ceb67ebfbe7b1cb657be89fb8ba9f232b413abdae08a20bf7c81b4398e092edb84302a44bac26325fef80bdd0cff5dac1e0b9b59559a3c55c32aee31ef884b648f55bee6a3993eeca7cd840a60fe2fd247430eda76868c776bdee99493496f36c5c0d5af6f89fd0292f8e8fcd11547668b67da74dddad202935f91951960dc256b418d78b3dded73591bd07bae1c620d3b94125020c9bd6b0eeb0cc493858f7a83765a40e94ae875807720be76a35bb5e1784c09090efb4dac017dcd80f3445aacc24053bdb6d550fa85e23710b8a471dfb0490f44dcc658982c31b1e161fdb5abe572bfee732c8c1ea15d0eb993387e604ce6ddb7f5456c0811bb60555ff065939a2587a42b5184449fb2795853647a62b002f4b5c791869a9df2cb91fe757eb2c84299819332193e9465ffa0af6b6948fa5"); + _ = try fmt.hexToBytes(&expected_ss, "a75ad7dfa8fdd4756fe5c0bdcf287e258562c5b3b394bfd949231966dcac02df"); + + const kp = try MlKem1024P384.KeyPair.generateDeterministic(seed); + try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes()); + + const enc_result = try kp.public_key.encaps(&randomness); + try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext); + try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret); + + const dec_ss = try kp.secret_key.decaps(&enc_result.ciphertext); + try testing.expectEqualSlices(u8, &expected_ss, &dec_ss); +} + +test "MLKEM1024-P384 test vector 6" { + var seed: [32]u8 = undefined; + var expected_ek: [1665]u8 = undefined; + var randomness: [80]u8 = undefined; + var expected_ct: [1665]u8 = undefined; + var expected_ss: [32]u8 = undefined; + + _ = try fmt.hexToBytes(&seed, "0606060606060606060606060606060606060606060606060606060606060606"); + _ = try fmt.hexToBytes(&expected_ek, "e6db2e7d848e7c8c6ed67c3e8304ac26d3214d361a3086c1c064bde1157745b79bf863b62a043bf31740adb250fa338a7df37dee88830ec31e511c3985c111f299135f9ccd0ab056ceb3476ee730d0735f1ecc36b2779525e184ac19bade2c8b8af42bc4b00ce5239feb4879c15b47f88bac364328c1410c92fb0e15c2c0da79bc6b8080d20b9340f346344a69da81931f79aa9232a7d54834fe71ca3e802525f16a345cce38eb3f3ec2c4adb2afe8685fc0c544127559a959074e784598f05dc6c79be84ca742f775c377cc0ecc36b8d00976b64c19821cd580aacc4a8f27841f6374bcd59b094e94ad0e1a576f6508a7308ea6cab2584a7ef50b887d8807aa76460de35d8dbb1768c335e10b05ed2a4d9ce83920fca30a00714b6aabc4e6823ad2af6fda9fda50b908a2a1f956589cdcbed50c460e3aa21511620569593c80774e7966b793639a616cca6c8f0bf5356e05ceeb27292e76b15ec82f70aa4a8afc3aa24b887d92b4c2ea81d9d1c10bc1210ab2b644c1a35a990c035ba07095a7e0569c3b4c39bd100762c6ba6fd9c0b3978b22211f6129a8dccc5032283bcac01b25141c982808c1cb11e112819ff558faa9396bb77cfdda8085a602981889cd272e56850e0e2355c0989a9ae38d2c546662a5cce84520c88973e01205dec9c6f523b34c10055d6bab3fe63864337fc6058540aa49216996c48b1d3473bc94aca4586a21e92093dc8204a1d651fcd03608b368a17c6182c52b437b7db0fc2426c03c21684962f633f2e05e293a095d40a5f5558fcb26303c8b72820115b324519e61cec83953ef3a1e8361a719a4cdcda41ef5eb89961554bc326da195bf8c2ab944c368533ac86de9b23e13951c8867e35b6fa933a432682ac365ba2aa7a529b6871d728ced34418ad58f8469972722a1a159ba04972162671815f36739723fc54768874029bc3584ee292c5c58beffa0bb51282850fc6a73fb6483eb1c224ac0ed2c1321eb42f976644340c994b2c55c035aa5c0c8c60964f263b02be9b3d6b87038722f2cf741b265b7fcba6bfe203291a09564938bca82ce69492c831a0cbe986739b54a8004bb4c5b7d0ed471f5627c67350a2d5069c80c04d335c75f4083818c6cf8a04d855b11c2c6743eac9d1d947efee59a34461f4cc0c9f1a78cfe8c883417480c3c725b958207b35b3885129fcc08719292e27491903567b3a009ab6b5f38015abbd03fe7b5ccf6b70d53b77e131640ddd37bae220b60c15a83f638e5f92d897476573c2bd5d7092a60cd42124abf32197385a1f90435be101216e81261906d2e599c2f698b029c193c17760417b436ba86fb0a41bd37ab93a37a462346cae3a65437b26fc54df4580439e22ee71c6ac82765aa7acd5b136d6889a2dc136791cb6492f7c0e655ad4ee0b3627a2cb1b8992069c6accaa9fbc5c1a098250fc148b23c3e1487bedba4a4d59722c56a9f95fc9d7234274071a16f5b5875892da1691e3be71bb017b6f97623ca01204f970eb588270c68a08f67607498122ceca637b90e0b3743e6783a0c08579433a83f0272cf863917f757ef1a18b5bba14046696dd0a473188c5dd70ce7f39d6c1123e2241843e002446145b5cc61804950038b6e4f7727d2c5043db32d445c4d0a4756fc91cc27d9330b09b8ff8c8561475b1ae3051b84343e21b3ae25029837bd48a12c95e76eeaf09011a72d378c8a0eb4c9f396ac00ec1811bc9399c987c34a3ef0777e5f575c4c227f8883a66cb43b3db75494ec0060169dc69973fec3cfd9f27d048979d48cc188668fa7729f16d489b7a73e6ef0bcfb31a41ad8179752253d38348226687f489f4d7569e74325c1e8246cfa6847b35072b553981c0b3f566d6f449f3a80a1a8534eec3136489103bfd55c3cc929e49730de9a34c89a3028336dffdc2e6df7b2aea1c43cc835530a6b99b2372556a20fa078b5c80232d36068120074707948b062b289a5d7618b74d65a7ad2b6b743048466438de17a9e571db54630c4ba881aeccdbfd45c4818b01e0240218ccb1aba0fad273899817869409f7409b77c3a2693597740921648c5669d788c479cb7fc8a743bd23228c02753ea6191523d09180d0bdc944ee37ab0e83d39d5802cd20e21c8ccab7033320cadc2104e29bc4d1612a38638cd39f299a0b95caafe5fc95956d82859b1f37ce95d3fb88d0244137561cd922cc4046f4356c2e3e6e9aaf6243a71de6c76f1c63106efae0a85abc53039d3b5d9a8e32ee03fb7abb085f9c304b8405192ce3c9508e104def48d030e57e6cc3ec1eafa408dec210645a6243de6a6c4f593c29fce358ba48539929ef3c584e5ca42af8f"); + _ = try fmt.hexToBytes(&randomness, "6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a6a"); + _ = try fmt.hexToBytes(&expected_ct, "aa5a25b8315b9e6c940207124c6f2ceb59a295b7683fd6762d91b7ac37f593fffd172954d708d937d99801708a7c9ab15d5fae5111ff6584c845a405376bafd33cfbb120247e010b9e82a2eb5fa7097882eb54ea99b37f5612aa95eb830fca30301e9f7e5ce1cc0e3fbd39c3ecc3cbb9b1662c8018c6039d07aae9e32d1a768f72c659b8f98af17a8180077c5918acf645d355d29a8ef0937ee354909ba30bbae355d84f954f6a909dbff2f69512960885e60e6a9da86011af4e1402be1e3fb8e83f26b7454de4fb64b9724d90ef8f5b7213acdd079dfd75e8b742ee3d161404f0b600e5906baade188d3bc192842dfcc3693709f8d3b0dc692874c7c454256004d66843ab5ee32e9c61f62b8984a28c333804af2a028dc20b9ba5785d2342112e2534480972c65a3a547bb856b8e9d348701e67f7fac37322714032415b4ad524b7fddf623501467470bacb0b79f815d3ef0f9e2237f23e601acbf3b56725bba4f158fef519fe53d94e27149a658119f6305887189c1c0f655558ddb98a1778e6b1a5deac60651bb97ca4313e1d45742f0dd0dec3b28b369ca52eb87257b37d9a50cfd825880ebe2774808be2ba9325c928ff7c2da2c9d46dec148e682d174608615da0261ba4d138b0fce3d0bf2911df0133fcfa5e1c78c419b5c4f9124d20a89a7d61e0cb2ae397e83fac851c34a058392f8df965085b7b935e98f94b1e14230b060b3df8b533d0d028b75db559b65a9d37e4158cbf9a51881c7644d56d25cff7ebce0d5b4aa43fcb01cb15b138d469d8db30eb7b72133bf2a815f65b96e1077e03715547f0093240f8c2618304b3a3ecc33396a6104b167636b9adc65e8d5ba70a9fe7323d369083f51a284d7d21d47fcb393e4fee816c36b4d46bc1239caa431c01dcfc87b865c9ce69cc2dbfd263671eb5e093c144717d07f645741b85ac5d0a214389ad1b958bdfe7f45204f49648a21494941f482c626e7af0fabdce53e76dd7ae501c50decad7203113482eaa55a4fe29ab979604a1a67ad0f446c5ba998e48abce37d387102594843f86104229c995aacfa59cd0491c621ef2abb03b75ed090545bef7f875a32aa630dc056d45e5389aab9e441ab89a3b33a651e420cc9e992ec474c7ec37842e8a0633f9fda5e39cc975575077850710ffe0db9fd220408d98efdbe501025c05345440437d7213d5523aef7d5c283778d218811c7cf11def4b665b9a62b699d1a3ce33a7c247a5e99e2ddf9c9a4b06d1ecf27c0a57b18d17d5990b2cd310629d4a7faa2343a66d6a40c45442899d5fb20e6d236692e605d2a30b535ca70518258438f7eb38b9589e3681c552cd174f88c3e729d50381299919485c3ec3a5a86da59f375304d4867b26ffb7ee194b3b1f15330cc9bfc4dabdd59446e85e4f1b168d1c05d14ae55087e1fe353205f1e873d4750cb0c425f94b822aa1d54b1f1a6bb7ed1009e19084d97aad29066a5b453211423969aab9ff456444e8ee80d37b59698bf807156e4f404d8f3d99aba1230f25cb5155105d2249e581b122abe7c8bf62b3938665acb49caa6be426ce44f9c018b59b97290a46f0978e110f8d2d07fec0d972a5d0ff0e9f56d0f55a7d7f8d11600332877a114451575101d18907083d4760b861eab94bfa67e5f58ba8883dfec7df4632178073322ca7ac66dd2b44ce73c3b410861071f166c888078c61c9dee4f810e5c0278e4ee53abb395966d82002d09d2a127ac44351a6a8ca45dedc05e4b3c45b64b855fcba017b96de8bc128c74f4569a3da57fc7d1e91d51a9d495045e203c863a2ee3711eec0d28989b03a43402b125f752f8f3dffb8b2f0eb043b2d1342633563c07b32857e487b5bac705045e43c8ecaa261e21887022203990b58ed87eea1ba73f46f69e483cec4567f285bf1e0a2d9f0e6c68771144410739bffe823b7e8ca2c31bfc381a2d6499667098d065f8e3ab61094baff3a336648ea79454f73aca9f9d45d2ffaf52258e92fa381f2478a6afb403563d9e00dd789de64503dcb08d41dd24dfaccd1babce39b344c5cdb8a3442ee66695eb4521e5f88aee992f2ac5cce42bc1baa45d9a9621b126d7c0576cd48e081b28727484dc4f135257013f3106cd8d3e1ae8cec97f8360922c2cb6c929ab81e20ae1505dba6d5178325640ebcaf0c6ab8ef74740f72a070c7bb548487a89fbecbeabf67e02e2679e3c5e50455914a6caba74acdc6697c2f4c089984587a729fdd6aee5bf96bf2c523beea2eabef56675c505e2cb191ed3428379eee81be9797e045622bfc163fdf6f4d4cc72784e2a47f1804db75d94a278a63952c7e310820983d3fcb118aa0f69567e41a"); + _ = try fmt.hexToBytes(&expected_ss, "8f81b7d72b0c023dbe66df377cfaed177c7516a06650aafec6046410dd0da9b0"); + + const kp = try MlKem1024P384.KeyPair.generateDeterministic(seed); + try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes()); + + const enc_result = try kp.public_key.encaps(&randomness); + try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext); + try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret); + + const dec_ss = try kp.secret_key.decaps(&enc_result.ciphertext); + try testing.expectEqualSlices(u8, &expected_ss, &dec_ss); +} + +test "MLKEM1024-P384 test vector 7" { + var seed: [32]u8 = undefined; + var expected_ek: [1665]u8 = undefined; + var randomness: [80]u8 = undefined; + var expected_ct: [1665]u8 = undefined; + var expected_ss: [32]u8 = undefined; + + _ = try fmt.hexToBytes(&seed, "0707070707070707070707070707070707070707070707070707070707070707"); + _ = try fmt.hexToBytes(&expected_ek, "36f2b5d9481ba9714e75c63de35c4d847ab513d440e0ab2a2e42231ff448de283cce31a9b391ac3ecbb29d005dbb819d44b88490d82ebd13a4213052741b3c6f797e89040490430b4246b782c049e556824ed9744295c878773643c57db8ec4c8b6a43f5488e29e509274722bc896371f58ad761c2a747ba00e50b152c4368f8b7500003d50965aa0caa4627337290549d9aa2f23b63e84c7075c98f4a342d8fd4ba96357576ca49d991b98ca497d54a3a4a984d0aea4bd61a187f639957e27b1e69b549c923ad253318f02c2b4931a8c8299d5b8a32038f2bd05b6b10824c08a4f02c9821aa3019a45654dc40574b9cec01a758f2c9e2a94b73d982a671c54cd8ad6ae95c6f3713807aab89162dfa68734b6657aa47212c8a17fd45bbb15a9a5eb40adc0b158caa14659ca60a073d51b1c5ea8992d24b4651581095e4bd1b5151e4f33c7113a32db916af09c43d3062ed113c9d38027ef545cdb1252f34229b45b3317597dd6353e5d32674f02b0f7975ce97cefa1b8b97d1249f5108b1068eb3da77af88ba4dea14346463293260474294cdf77ff10c75e5d9a33c751d5a5a61146917f94a789a770f01500ce3a5b545e6af86608825507ae7cc604ab5718c4481e68a064a420aa853c4d559095c53a01da46004e6c89dd9995847473dc85fba5c11b222377995a90415aba01472357b3f7c524c1d413c07b1c23bf9a331a36c24e4c9edd568bf2b2bb8fb5e4ad613bfa36a9d0c5142e60aa0e5b9e5a48c2952270faa185ca59a24a098ae615fd1678e07fc7d2a727753cc2c74d189771995ed266658cb24b8dcaf067213cf6067e95c4a51b9b55e9c05e4d0c184039d88869b6b1846ac9b31cf59221ff6c5eca2274150cd5873072d8c583a35488d187f4fa1314c6a116db548b5795da2532655da402eb9069b77295205516fa810a6f9aa9af60b51819e28da1ab1e950359a9fd0ca5971c8aab5250d3f7230a07b9244b019b8ea5534e805452b0ac3dac8954b36e97b80e5314759c3886cfb5192b234fd0172d5f565d1ac8465d205bfb55f741c366d425d95e4a4483b84becaa6d5ba48bf9c9c41723c1fe54909c02fa67b62cf6a7c776748934a817c97062907850a86269999b4342c8f9f803da54a8e3079a6d7548114968a48459784b4c6eed326fed305682a39202170367571a95cb8ddeb7bce9c93ab311d656663cc88bcc6c1b2e1fc9c633830d955aef7f019726167d5f5b81c369546083e611b6314f73b3dd90b4079c5d62a6eeaf14ea5426b7551c16b371ecf537bbfe6a0fc577fbe56242dfc9db6a1035305182b6a265bda1c42982d76a59009e5aa546824233c7e60ebb865146b21408d925c2cdd01187ce7732c60643845a3a6b82ae9d8484688931f612fb7dc8d3225b392993463748f90d8689f12464235a607d68ebdb932053b75e25408a7907e03e936da36300b9b3d98f51d7b61afac32b015d3057a55022af2387701c5b04020a4217edff1c62fa25a49c407e0fa18cfd513793b86532524b64b67929727b7482b14e14116c744c4893d6f6931bce982a9c46dc6048ed8e99f3048914d4476f355cb59472cc092805910a8f53aa8dcf939698abb30730bff24be1d49c7626bb439d87621785eef26aa2da7801714c704609fd3955b9d1058dc2052aff8634076bb01426bb0f3c1696bb773f3a920d90e4aa7c839b14b69d9138b1413000aa8f3895dc0a893a6faafc2e7940d62616ed37d46578d6c03505f1588495b3537f95ade1aaee4c1c3dd0a35ead94cd30a5ced265a0842745476a164d9ad12a5aa7e3b55709c1ef6f7357442bbf63291321981d7682cfe480d03c5a12015ba4219b76cfa997736c246d888a65c470e98bc3d574b4d7ac0072b062267bf81a91181f14ba3d202bf99ae47f1cfb564966f3c3582c85ba7c66ed9058a11856730f71807e4191673b4cc324f360cc527a060e73b511ebc10f1ca2bc79318c6c748effccd928277deccb7ba36ad4dacb2b98749c89892aaec7d07c162bb6b7a3c52873993a8b7392b36a86c853494d0150b0d97171e08360b9c3f598b5673c67b145b20781a7a7c16270d2166bf633e861b0fcb6535418a6106a0aff085cdf6e73d837941e823ac064424e388b02473096c8a8d6c816a9311a5ef68897fc92cbe922749dd602e7ccd9a5faff76e5f193bfc19d784de0c826d9eb5b1585ce28204d0fe95ac293d3aacd0dc58f6fc9d801516045da004311ca5a20587611c5c4a513c4c702981b88f3de97bf00bfdf6138bd6ed1a11d3c0d2df7b7b9ebed10ffbb85db1be4abf5e9a62de86dd46530d6a7478fb6805fbd32d4495530ba393d42801"); + _ = try fmt.hexToBytes(&randomness, "6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b6b"); + _ = try fmt.hexToBytes(&expected_ct, "4dc3b02448f339df1362f71a0929acbdfb2a0425f54adc2f98a62f468dfe9f756e898689ffc2684d66e00b15e67d85cbfd059ef9f5f72743dfdaaa99f0668ae422b41bf76584a5bd2fbabf699ea7f56be4b1f75d5859ddecf9555472ad5c56e26eb060316e25efdd57cfd367e459bbbca12ab0e3ef02111606dc4935d9d80ce9eae8348f57df27ae3c575113588e5e311f96ab3497774e0ef1e27e2c5b397028f9888fa82dee054ff4883b98b8e9f01ee8a26f35a967cbaa5c93082e67e6fca4e6bbf9c2c53a9c699642ec2d06f2db513235040a74ef68bc7f601477019bcbd47c19fc7c2edac12a570c9797a81b0d59597b1865a4d904c6ff99d0e7e6b452dfc4ccaa79e4c633079d9c280b52aaff716cd1936c4b56a517069cd1b69b1c9f08dd03403e6765d1c18fc5e3ad219d80a83c16ef440a53cd07221f12b2c4351c0cc22cbfc9a8bd648f7258194faa9f7263c1235d517c43f62fcd896b7c63bfb25d80857d6c3f5fd352221b80e780ce76c7d7a9c573de96f06173f4d9c2d8b8f3ac87e4154934b7884a3103e9fea436224e632c83bd3b07496f2b628ef8198f945de14cfc3a90998475e4a332f5646bb6f5f9b5a944ccbe0631204a110d66a7ca111b9da15245a982c2b63446bc2a2084a26f1d69cbcc68c6d4cf64efce166d4b8a6d9552a3a81999fa270ba3a9e8e7cfb280da4251875824efbb56161f2996e9d2cd6a66980256b41f10704a93d2f4cd87868a6b2967ca92a20a71b8104b5536e014ef837fc78d8dc8ee3a297395aac377b85ecf5393b33aaeddceb1e10c24f24f9480ca4e4ec275f0abdb36124ff7d7c79ecd11b678ea4bc125307e023cd4c3649e49906bface2d2b73250910c1d4cf081038008ec7f0756ca945d3362f5bcb9138e8289f77e0f8e1e54ebd2198ceff11f4ef1b53d0cb028b3cac0bfd0cad0015a1d83413f1929d6a505fc51329ea46c9680786582378724ddd4300e7147ad2b5a1f9221723bfd57c2a2d3a33e7b1cbf50ff99ed27bd1e5f541e9a5bc522685a74c7cf2f749a1ef3de87e069ffc0784e6c19b39168bed86bbcbc4b771b46839c275d0dc767ad1420f56143d031622fb792c020dd29444886eedf3b671b2a3bf875f4b6bb604de6a0dda58d8a794697829ebabc37d1249ec869b46657fbdeaf5dc5da782051107b192382b3c0820b64e0f2ec15083de32369da090b914121e41dac1dd7edfaea4e1ca0c3d98763252bfe365535c42af6460abdf64940a01f6d6ee98d28ded7098b2160a486166ab36fbaf392e57086c2ba71538c6008a9c459ed3bbb0c5340e3a11ac84d6eb2e752ac581b53a62c418f2264a79d7a1ede8861db4a4e7e404038f86229a80e184fe3d7ac24a2bb97146db09e2014d7f665a202edbb88793569f47edd5d13903913c89bdb2f64dac267307dd2db2aace86fe881de213094de8bab534ba01359ad449aa6500de2064ecf29af5b3b86c96883ed2579ebd703a97c3e982747e2c4213c6af9fb78cadd8168b007f79c9b3abc58c83e487ace180e1fb41334bdb1ce9ed54be7eaaa52613c8dd2280a87c94e94f237377a811a440ce59dd98ea59878f1496e8bd0f1b842a5ff1e9368172fbe2c800fa52bd6014d2065c6ccf2a1382fa5ca3097696a1a69a759bdaa4f529a41b70681c9aeddb1488aab7c10e290a0f353ea14004154f4b7c23baf6ed314d2b84a7c760b3df187b5b1470700aae1eb3ea84d3faf246411e282ed5d40790a1a1fc67facf4ffffd8cc6a8cceda432ad04a454bba7d0247b6042e5be5ab2445d5b730ece63c2b336fcae8232db3794d67e0ab3e98e45aebbe634e42c3be695676ec04fd8b013445bb0599a04d43fa26cb4405db6bec6188c8cc94dacfc435ee64e3356b97d45125520964d30772b5d87fc702ce02fa137f2b4c4fcc12e852fc746715d95023903cb3a7e3c2eae8196443ef3b843b979bc29ed63b31d219349a1f21b7328c3dfce4c6362c96f0dafd01f081515232700d00d00f7fea78f47342e52641ad6413d227895c49b7eded1adb3809aeb7cad24bf122269e39d7ae9e35da8f193092d2dd284e88b5bd5cfaef220b6ac9245a063676d3f9d0d3b844d6164fa23dd7cd8c60f80fc5b5e3454ed7748c65b4cf3db68384529251cd68e2dbdb3020e8d73e212f057f2fbb146a845af750fc2fe2a7e26f2e47dbf0c2968712074ae8534c356728f04ecb409f3bf877d3fda4ab6cc03ef31cd88d3804c75fcbd3ade2e41e99d0e0400c563187cb0ad529a1341d48e54d2f5aa141e925fe47cc85329490de157d7cbd77c9216fdfe4e8dc03c3b29966d08237a9db8222fe4bfd74b92d221d80a4aaf59"); + _ = try fmt.hexToBytes(&expected_ss, "0e5a2eb3ae0f3af82978e6a71b5e2a0830850af49d877a0deec5144061ce44f0"); + + const kp = try MlKem1024P384.KeyPair.generateDeterministic(seed); + try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes()); + + const enc_result = try kp.public_key.encaps(&randomness); + try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext); + try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret); + + const dec_ss = try kp.secret_key.decaps(&enc_result.ciphertext); + try testing.expectEqualSlices(u8, &expected_ss, &dec_ss); +} + +test "MLKEM1024-P384 test vector 8" { + var seed: [32]u8 = undefined; + var expected_ek: [1665]u8 = undefined; + var randomness: [80]u8 = undefined; + var expected_ct: [1665]u8 = undefined; + var expected_ss: [32]u8 = undefined; + + _ = try fmt.hexToBytes(&seed, "0808080808080808080808080808080808080808080808080808080808080808"); + _ = try fmt.hexToBytes(&expected_ek, "438844d7cbc33abcc0c58337f8e8c084c0795812ba6603a9c64bb47c84acbfd95c148414fe5410427b4f04b1c6b96cbd24958fc7e33387c22dcec51ea94c6c551688ee2981a5e8cacec65748359fcab62adf51bad4338f34b86568b679530cbbf42b475c2909cff88ab126583d80495f05a410f27b6fc3c895d492e3175c8973a18ea53f88a99ad2464a4e19c4caf5a035d77e7f173aa9e4c09f7b595cf5b69af91931d5a58edc9c7dc92ce2347f2537166879cf353c2ec22609a3c3789f80857b207ce860cacff65b504b8795993a2305cbd9e26b14d36b9d24bfa822b0a83643cd8a09f1a22a2a923081083afab30f09b085f55571de75aefae2939a387343fa602255b4ed9392b633c2945807eec74f0b212d0e685ae5075f775b09411a96ffea4d9a091d20c5200b481265150ebda2016bcb3064b08c2c929c0992c338bb58d45724fd2799df75428f7ab6ffb2785de7b66ad0c01033807e8741ba8731fef560e9566359720081a39d8615aaf99bb4dbf335f648b4dcf3991223235ecc1da6460886657e3be2c065c3cc51f30393f42c9baa84d4cc4d0d872512ab42e363acba1594d4769190c444094891c975c74d0baa484b622dc48538264837c3855d00c6f6026fa29106c3669e822b2d719b4c41e550229a145189cf2836594356b0bb9b7a95d69e22c867f314753e8c589be21268a94cfff9506f1112eff38494e6742272a05649592021643546920759b2c96570dbe05345c14cd4d6b112506f92e99948952442fca689c87fa003cd24d2857377668668c1eadc8584bccce5517e605b0730a23e46055e177503c71b44fda56489f10a2051730757caded74932f673f30483ca10c6338ca0efc509b6486bbe33b857b746d8229a14e62f8370bd58c1b0c163a8ffb5411946cfaada15faba918e24cd16d40fdff6b66f8627dddb2c6c3c2b43f48a42986671624a2c8290d9091c9f05cfb63c4d26114cbb6bc8a96c4dd5b77c83c2ba88810bfb346eec477ccf84aecbd4a34891074b331d1cf7b9bb8779a3372d7db423ef79cf734b8bc493bb52ea94536533bab89480ec86eb83214b5c1e77702124b4bb8ff2c71f976d567cce51245d6ff22b1df00bb920b42a05532951371f3c39fed7cd95343f47923a41e6496d938190665eb49b80aed185281ace2191c7c1c7ca8a808f5518b8884984660b8c8477bcbb178ab7f87a0f572436c1c19bc7a731e6a03495422823acb62861777944999180460c1d6573205779cbb3962bb779aeea3c9f5d821befd1b6f8f65504d868d1916794a05abd53b51468b9856130c6c4b81cab8ec393652aec06e7603445b83c06458e51e04bac3cc0a3450d061735be154b7e19299fc70a5fd72e11658e2cf62ff696163e7cc38406ca8fbac345b06c8a27c08f7924b53562e8472ccbc1081bf36bc9fc3e00dc005969691c3acc5a01ada084b5b7888cd03bcaf9da868bc400483755481631d5766a1b873a55d33811b584fb0280973560c5c4a75db01d5da4bc21b08bb0005013ca8972831c17209cbd4222a1f75527949ef30c3f1f7873c4e792f7454fa6a9173fc67cb66c08789985633282372038b881bf055862470a470369b00049196682cd8827a691c60e046909048a85047251b7fb2a0f0ac9ed271ffde282db57480c10bcd74890cdf91f7790b0fda7608b6c612ce5c18d30452852c3cb370642621fef811530061cc0f612fcab47ab97a4337674972c4e9542332978499f234f64d8788504031a579ab6a02add3c40366b52388368a60ca0df9a0ade438c23accb062bc586d428582a752cd4767fda155c812c0399574606cf90c009e0a7944dccbe887aa596864a71355626018105f705d0e05075362884b2c2ff929d0fa05fb4771bad5b73b5b8a9695174801087d9f64e17a026c96cb608675fb8174e74226e120c2199dc2eff362a45206b1b4a26ab511aa875ac8df8bea946c305023812ecc888227759b03aa2c260fc7b965657c2bed35ce3f82f10a2b863f65467291d4b996c2f30af4c5469292782353970e4549e180a94fdfb24a0636384e02b5a861252ec5a1acb3b0396bfdd241e3522a49f526fdf9a5647d7cd7cf27bfd3796f138616cc3a5d163071a63a83da2a8fbd7c0f020565e46b3c6182fc21462abb7605f387ccaa15c5ad115d428a8f75faf9577f754b4872cc55aa3f67683ac6830636f25cf04c77e9f4dd5fa8af50d958361b98902ee569f7c67efdd4b15aef42b7b018872346ae3d0d4f2af556401d3142b85233cf6f6a118b60c9317edf492881d1f2b651e057f202f662a54f7044ffa61b781f8c35fff2e86267ff0b8525ac13e4bf4a12e"); + _ = try fmt.hexToBytes(&randomness, "6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c6c"); + _ = try fmt.hexToBytes(&expected_ct, "4aaedad0befe99ae3e9d21c745135279dbafa79e9935f328ecbb3258c6dc66557da90d2ada312b9b5b113b47f1dab9de69f14e6421d336d525bd4098288854d6ea2e449af19d702cb5f22ce8d6da820b4cb76a2e5b93ab390706dbc7570a9dc6f103928db508227b37c32a00ca4b25623396ffbdbf95703203ebfef5b07a450591792fd0adbb1277655fbc908ba0952758b74610f8152d8e59bb74e2f76dcd29a530c01aa3ee35689d16ba790d3b84dc4eab52d60388eaa807a06be5d86a473fb55eff6c70e73edc73f8b6c0ec7e6b472d5a3b82f1eb6ec508f1c12d58f42f870af9173da7555b080be6a53233c0a6954d7f5501b0c522c69bb5853c6147ec2cc74e5a333d641fa130a63373497f844c16113aa7a14d8edb0097137710730d03ab6950a4a61d6659991bc70b9703f2ee670e5bb1986a9471f9c211a0ec8b38405a487b6ea425b598478caca583e3b6f968b73a72d34a81af674009c8a9c294269869c6d695982d4ba517646cd045db6e33d89bc91724608d5c7ee67e24e0613ed0c1c00e4eef02122994d6197b7da3fcd3590d42365d6b138f09e1c61eed7f73d630a96f5a392c3033cd02d823b8dde7d52e7444bdb91f76dbb4dd0c8c9c081dea1c81cfe138d15d06af32136668658b9118a065d4203911c1ed7a3ccf5f02355de1191ffdb02de1d0b3c1585f0ab81aacd405ec25b62f116b9a951f57d780a4156143cb813edad47d41408a89daa1edd5a0f4d4a80598a739f6ef0a16e8b502b9408603ce5615b632b1aa8d0940b7f04029b06ff019271f90452667c054813afdce2c5a10583913b37c01800dbb8cb4c8f7fa182079a2ac609c5209e8f9727f4edd0825e8ddbbb696f3ab76621dbef8f6df51a34cb9179ad3cb6de31a4552206ed8089c69439fdf5137012158d33bc97cd8185e94c24cc7303fcc9e801df3fb3b8f4fe268bdc50137cc1eaa1723bed666d0aae9e452e166a356d04c84c65d19f8ebddc51bba49c85866dce77a4f9193bf6b238f1ea886ffc44db55e0458391f9a252d1c20ed1921ed378b1e246a490b6221ea73db863c9bd04090a557481a24fdd75b12799d042a0e4c929e5570d41d7f562e90a314d3044daceee81cffa37ab5cb1083559bb321948437d9a5caff4562302f22f77754f834379b72e461958cf62bec4f1bcee846501b08aba0a11ca10d41dfcb04267ecfa0372896ba35acf9983b96c20fa5a0b9b2a824af911a0678c0cb6110a2ce520ee10bf32efd72b1487b48ed9689d02def24567bcfdb76cdb17d31f540e715424b2a37c5c6ee44eb951737e3718ee01b95a9eef71b7d73d7d101a3e53c9073d360fa5bb340a3e14a06cb875c66f22c6791fd3ad7f720069874c77d7ccac01f13d870acd3055f0fce01a8529e8c058e1fba29dc2387927be0c8e95de7c70fd945ddb289288b9d7d7aa5be60504ec16c8392984784715dad292c1887d7cbb2809dfd45431c820fb28085036d4c903da826c26a141645fdd1834f6ea5fa9d87023129cc6467b25902d9fdd441c7a60ffeb5280f97a05e28bbe4e8702b101ab501cf3a186519979dd7681363a877dea4aa16fcedfa55893fd69e0f4cf7be703921805e0eb6047109808d174473ebeca08f795537a8621a5cb555f2b63e763d2e7cdebe9da4cac208f737d1c86732aee5bcc40b6b469e4c5a00b5f82494ac09626beeb21925719d07cb6bc65c2d00bf18f270403db32f151818dd8a2c3f9712a02691fcd3f82e8a1c8f7c90c5dde688a3135448025c7cebc2046fedd6e920075eb2debb5804ccaed5b66955b4c4606d7d52bcbf9937e21a7bc2f35d4bd6f98986127cbb6ce68f27b88a4d0b6c3e7f97216bf500af9ab3654c278e95dee865750937662ead1d807388acc004cfb4f737c32c4ab8719e15473d128f5ae4b4ca4c0b3348d811c348e9d4fd64e74350f4eac0de6dc3ad94793e3ffeea1abf89ef586b13f93bbf176d5d6e99df043a118a183eee4d66069c18d24f5a27c1777b2e30501d64658c5bb490259b15ad7c1d07a5bc9a73f6fe1f9176a3622f207eb941be7c736bfaf356ab4280c29f9735cdc6d37f489c9dfe1fbbcb3a67206aa22d221214603d4a9bb9f32e93b01b7595cee7156d939a0b63df1d89d4a603594314127f7c751bc28215ed5963045ba3e186e429cb08165fec5fab493a7c890e29279a3265b9055cf605508f1681a3f904e68f85580d097533d64a56cfc76d712e42729b87f4c28353d317eb8c157e2a920e2f350b47387f86e8dc86a14e6e4cfbbc73f8c02d6fef675f6e74605203b670fe50f33a7b8bc670cbb6d0ced5ada15cf8e436e0e1a55c9ebc67ce0a8fb93754"); + _ = try fmt.hexToBytes(&expected_ss, "3fa279b00ab2798d2d70a2578ea2c76539cbea4bd86d1aa09e92bf0cc40e6626"); + + const kp = try MlKem1024P384.KeyPair.generateDeterministic(seed); + try testing.expectEqualSlices(u8, &expected_ek, &kp.public_key.toBytes()); + + const enc_result = try kp.public_key.encaps(&randomness); + try testing.expectEqualSlices(u8, &expected_ct, &enc_result.ciphertext); + try testing.expectEqualSlices(u8, &expected_ss, &enc_result.shared_secret); + + const dec_ss = try kp.secret_key.decaps(&enc_result.ciphertext); + try testing.expectEqualSlices(u8, &expected_ss, &dec_ss); +} From e427ba9cd59dc876d27e4a71104b5156460ca618 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 27 Nov 2025 20:48:54 +0100 Subject: [PATCH 04/53] std.sort.partitionPoint: faster implementation (#30005) Migrated from https://github.com/ziglang/zig/pull/21419 Co-authored-by: Jonathan Hallstrom Reviewed-on: https://codeberg.org/ziglang/zig/pulls/30005 --- lib/std/sort.zig | 23 ++++++++++++++--------- 1 file changed, 14 insertions(+), 9 deletions(-) diff --git a/lib/std/sort.zig b/lib/std/sort.zig index 8705d2401730..9bee64ebe55c 100644 --- a/lib/std/sort.zig +++ b/lib/std/sort.zig @@ -678,18 +678,23 @@ pub fn partitionPoint( context: anytype, comptime predicate: fn (@TypeOf(context), T) bool, ) usize { - var low: usize = 0; - var high: usize = items.len; + var it: usize = 0; + var len: usize = items.len; - while (low < high) { - const mid = low + (high - low) / 2; - if (predicate(context, items[mid])) { - low = mid + 1; - } else { - high = mid; + while (len > 1) { + const half: usize = len / 2; + len -= half; + if (predicate(context, items[it + half - 1])) { + @branchHint(.unpredictable); + it += half; } } - return low; + + if (it < items.len) { + it += @intFromBool(predicate(context, items[it])); + } + + return it; } test partitionPoint { From 7af412b6b442214572559bec86ca1756cda3a2cf Mon Sep 17 00:00:00 2001 From: Purple <38256064+IamSanjid@users.noreply.github.com> Date: Sun, 1 Sep 2024 09:52:31 +0600 Subject: [PATCH 05/53] Update std.zig.system.NativePaths.detect to support some more flags on NixOS like environment. Basically detect `-idirafter` flag in `NIX_CFLAGS_COMPILE` and treat it like `-isystem`, also detect `NIX_CFLAGS_LINK` environment variable and treat it like the `NIX_LDFLAGS` . Reference: https://github.com/NixOS/nixpkgs/blob/74eefb4210ef545cebd6a7a25356734b0b49854f/pkgs/build-support/build-fhsenv-chroot/env.nix#L83 --- lib/std/zig/system/NativePaths.zig | 38 +++++++++++++++++++++++++++--- 1 file changed, 35 insertions(+), 3 deletions(-) diff --git a/lib/std/zig/system/NativePaths.zig b/lib/std/zig/system/NativePaths.zig index 70b72b0581c3..eea12ee2721b 100644 --- a/lib/std/zig/system/NativePaths.zig +++ b/lib/std/zig/system/NativePaths.zig @@ -21,9 +21,9 @@ pub fn detect(arena: Allocator, native_target: *const std.Target) !NativePaths { var it = mem.tokenizeScalar(u8, nix_cflags_compile, ' '); while (true) { const word = it.next() orelse break; - if (mem.eql(u8, word, "-isystem")) { + if (mem.eql(u8, word, "-isystem") or mem.eql(u8, word, "-idirafter")) { const include_path = it.next() orelse { - try self.addWarning("Expected argument after -isystem in NIX_CFLAGS_COMPILE"); + try self.addWarningFmt("Expected argument after {s} in NIX_CFLAGS_COMPILE", .{word}); break; }; try self.addIncludeDir(include_path); @@ -65,7 +65,7 @@ pub fn detect(arena: Allocator, native_target: *const std.Target) !NativePaths { const lib_path = word[2..]; try self.addLibDir(lib_path); try self.addRPath(lib_path); - } else if (mem.startsWith(u8, word, "-l")) { + } else if (mem.startsWith(u8, word, "-l") or mem.startsWith(u8, word, "-static")) { // Ignore this argument. } else { try self.addWarningFmt("Unrecognized C flag from NIX_LDFLAGS: {s}", .{word}); @@ -77,6 +77,38 @@ pub fn detect(arena: Allocator, native_target: *const std.Target) !NativePaths { error.EnvironmentVariableNotFound => {}, error.OutOfMemory => |e| return e, } + if (process.getEnvVarOwned(arena, "NIX_CFLAGS_LINK")) |nix_cflags_link| { + is_nix = true; + var it = mem.tokenizeScalar(u8, nix_cflags_link, ' '); + while (true) { + const word = it.next() orelse break; + if (mem.eql(u8, word, "-rpath")) { + const rpath = it.next() orelse { + try self.addWarning("Expected argument after -rpath in NIX_CFLAGS_LINK"); + break; + }; + try self.addRPath(rpath); + } else if (mem.eql(u8, word, "-L") or mem.eql(u8, word, "-l")) { + _ = it.next() orelse { + try self.addWarning("Expected argument after -L or -l in NIX_CFLAGS_LINK"); + break; + }; + } else if (mem.startsWith(u8, word, "-L")) { + const lib_path = word[2..]; + try self.addLibDir(lib_path); + try self.addRPath(lib_path); + } else if (mem.startsWith(u8, word, "-l") or mem.startsWith(u8, word, "-static")) { + // Ignore this argument. + } else { + try self.addWarningFmt("Unrecognized C flag from NIX_CFLAGS_LINK: {s}", .{word}); + break; + } + } + } else |err| switch (err) { + error.InvalidWtf8 => unreachable, + error.EnvironmentVariableNotFound => {}, + error.OutOfMemory => |e| return e, + } if (is_nix) { return self; } From fbafbd426268e0bbd3c938936459d680dc4eaaa6 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Thu, 27 Nov 2025 21:07:58 +0100 Subject: [PATCH 06/53] ci: pin actions/checkout to current ziglang/checkout@HEAD --- .forgejo/workflows/ci.yaml | 34 +++++++++++++++++----------------- 1 file changed, 17 insertions(+), 17 deletions(-) diff --git a/.forgejo/workflows/ci.yaml b/.forgejo/workflows/ci.yaml index 3f646c48aaba..e0b39b6e275c 100644 --- a/.forgejo/workflows/ci.yaml +++ b/.forgejo/workflows/ci.yaml @@ -19,7 +19,7 @@ jobs: runs-on: [self-hosted, aarch64-linux] steps: - name: Checkout - uses: actions/checkout@v4 + uses: https://codeberg.org/ziglang/checkout@19af6bac491e2534a4687a50ee84fa7f13258d28 with: fetch-depth: 0 - name: Build and Test @@ -29,7 +29,7 @@ jobs: runs-on: [self-hosted, aarch64-linux] steps: - name: Checkout - uses: actions/checkout@v4 + uses: https://codeberg.org/ziglang/checkout@19af6bac491e2534a4687a50ee84fa7f13258d28 with: fetch-depth: 0 - name: Build and Test @@ -40,7 +40,7 @@ jobs: runs-on: [self-hosted, aarch64-macos] steps: - name: Checkout - uses: actions/checkout@v4 + uses: https://codeberg.org/ziglang/checkout@19af6bac491e2534a4687a50ee84fa7f13258d28 with: fetch-depth: 0 - name: Build and Test @@ -50,7 +50,7 @@ jobs: runs-on: [self-hosted, aarch64-macos] steps: - name: Checkout - uses: actions/checkout@v4 + uses: https://codeberg.org/ziglang/checkout@19af6bac491e2534a4687a50ee84fa7f13258d28 with: fetch-depth: 0 - name: Build and Test @@ -61,7 +61,7 @@ jobs: runs-on: [self-hosted, loongarch64-linux] steps: - name: Checkout - uses: actions/checkout@v4 + uses: https://codeberg.org/ziglang/checkout@19af6bac491e2534a4687a50ee84fa7f13258d28 with: fetch-depth: 0 - name: Build and Test @@ -71,7 +71,7 @@ jobs: runs-on: [self-hosted, loongarch64-linux] steps: - name: Checkout - uses: actions/checkout@v4 + uses: https://codeberg.org/ziglang/checkout@19af6bac491e2534a4687a50ee84fa7f13258d28 with: fetch-depth: 0 - name: Build and Test @@ -83,7 +83,7 @@ jobs: runs-on: [self-hosted, riscv64-linux] steps: - name: Checkout - uses: actions/checkout@v4 + uses: https://codeberg.org/ziglang/checkout@19af6bac491e2534a4687a50ee84fa7f13258d28 with: fetch-depth: 0 - name: Build and Test @@ -94,7 +94,7 @@ jobs: runs-on: [self-hosted, riscv64-linux] steps: - name: Checkout - uses: actions/checkout@v4 + uses: https://codeberg.org/ziglang/checkout@19af6bac491e2534a4687a50ee84fa7f13258d28 with: fetch-depth: 0 - name: Build and Test @@ -105,7 +105,7 @@ jobs: runs-on: [self-hosted, s390x-linux] steps: - name: Checkout - uses: actions/checkout@v4 + uses: https://codeberg.org/ziglang/checkout@19af6bac491e2534a4687a50ee84fa7f13258d28 with: fetch-depth: 0 - name: Build and Test @@ -115,7 +115,7 @@ jobs: runs-on: [self-hosted, s390x-linux] steps: - name: Checkout - uses: actions/checkout@v4 + uses: https://codeberg.org/ziglang/checkout@19af6bac491e2534a4687a50ee84fa7f13258d28 with: fetch-depth: 0 - name: Build and Test @@ -126,7 +126,7 @@ jobs: runs-on: [self-hosted, x86_64-freebsd] steps: - name: Checkout - uses: actions/checkout@v4 + uses: https://codeberg.org/ziglang/checkout@19af6bac491e2534a4687a50ee84fa7f13258d28 with: fetch-depth: 0 - name: Build and Test @@ -136,7 +136,7 @@ jobs: runs-on: [self-hosted, x86_64-freebsd] steps: - name: Checkout - uses: actions/checkout@v4 + uses: https://codeberg.org/ziglang/checkout@19af6bac491e2534a4687a50ee84fa7f13258d28 with: fetch-depth: 0 - name: Build and Test @@ -147,7 +147,7 @@ jobs: runs-on: [self-hosted, x86_64-linux] steps: - name: Checkout - uses: actions/checkout@v4 + uses: https://codeberg.org/ziglang/checkout@19af6bac491e2534a4687a50ee84fa7f13258d28 with: fetch-depth: 0 - name: Build and Test @@ -157,7 +157,7 @@ jobs: runs-on: [self-hosted, x86_64-linux] steps: - name: Checkout - uses: actions/checkout@v4 + uses: https://codeberg.org/ziglang/checkout@19af6bac491e2534a4687a50ee84fa7f13258d28 with: fetch-depth: 0 - name: Build and Test @@ -167,7 +167,7 @@ jobs: runs-on: [self-hosted, x86_64-linux] steps: - name: Checkout - uses: actions/checkout@v4 + uses: https://codeberg.org/ziglang/checkout@19af6bac491e2534a4687a50ee84fa7f13258d28 with: fetch-depth: 0 - name: Build and Test @@ -178,7 +178,7 @@ jobs: runs-on: [self-hosted, x86_64-windows] steps: - name: Checkout - uses: actions/checkout@v4 + uses: https://codeberg.org/ziglang/checkout@19af6bac491e2534a4687a50ee84fa7f13258d28 with: fetch-depth: 0 - name: Build and Test @@ -188,7 +188,7 @@ jobs: runs-on: [self-hosted, x86_64-windows] steps: - name: Checkout - uses: actions/checkout@v4 + uses: https://codeberg.org/ziglang/checkout@19af6bac491e2534a4687a50ee84fa7f13258d28 with: fetch-depth: 0 - name: Build and Test From e19c61a16e0b9dfd36f575057dd2af42d54efde6 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 28 Nov 2025 07:38:24 -0800 Subject: [PATCH 07/53] issue templates: use anchors in URLs --- .forgejo/ISSUE_TEMPLATE/config.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.forgejo/ISSUE_TEMPLATE/config.yml b/.forgejo/ISSUE_TEMPLATE/config.yml index 186e4522a0fb..9b11403037cd 100644 --- a/.forgejo/ISSUE_TEMPLATE/config.yml +++ b/.forgejo/ISSUE_TEMPLATE/config.yml @@ -1,7 +1,7 @@ contact_links: - name: Language Proposal about: "Please do not submit a proposal to change the language" - url: https://ziglang.org/code-of-conduct + url: https://ziglang.org/code-of-conduct/#no-language-proposals - name: Question about: "Please use one of the community spaces instead for questions or general discussions." url: https://ziglang.org/community @@ -10,4 +10,4 @@ contact_links: url: https://codeberg.org/ziglang/translate-c - name: Copilot and Other LLMs about: "Please do not use GitHub Copilot or any other LLM to write an issue." - url: https://ziglang.org/code-of-conduct + url: https://ziglang.org/code-of-conduct/#strict-no-llm-no-ai-policy From 713716770ebe7ae1b55cfbe40478b149aac152fb Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Fri, 28 Nov 2025 18:29:05 +0100 Subject: [PATCH 08/53] README: github -> codeberg for some links --- README.md | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index b22475b1be10..5f6ce4dea3da 100644 --- a/README.md +++ b/README.md @@ -128,7 +128,7 @@ while your Zig build has the option to be a Debug build. It also works completely independently from MSVC so you don't need it to be installed. Determine the URL by -[looking at the CI script](https://github.com/ziglang/zig/blob/master/ci/x86_64-windows-debug.ps1#L1-L4). +[looking at the CI script](https://codeberg.org/ziglang/zig/src/branch/master/ci/x86_64-windows-debug.ps1#L1-L4). It will look something like this (replace `$VERSION` with the one you see by following the above link): @@ -665,11 +665,11 @@ based on Clang, but is now based on Aro: Test coverage as well as bug reports have been moved to this repository: -[ziglang/translate-c](https://github.com/ziglang/translate-c/) +[ziglang/translate-c](https://codeberg.org/ziglang/translate-c/) In the future, [@cImport will move to the build system](https://github.com/ziglang/zig/issues/20630), but for now, the translate-c logic is copy-pasted from that project into -[ziglang/zig](https://github.com/ziglang/zig/), powering both `zig translate-c` +[ziglang/zig](https://codeberg.org/ziglang/zig/), powering both `zig translate-c` and `@cImport`. Please see the readme of the translate-c project for how to contribute. Once an From a8f9b5dc0695bd0fb54d35f75c66175cc6ee87a8 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Fri, 28 Nov 2025 22:06:46 +0100 Subject: [PATCH 09/53] ci: apply workaround for #22213 to x86_64-linux scripts --- ci/x86_64-linux-debug-llvm.sh | 5 ++++- ci/x86_64-linux-debug.sh | 5 ++++- ci/x86_64-linux-release.sh | 10 ++++++++-- 3 files changed, 16 insertions(+), 4 deletions(-) diff --git a/ci/x86_64-linux-debug-llvm.sh b/ci/x86_64-linux-debug-llvm.sh index 3c11820352b5..aa8ef2ab9f1f 100755 --- a/ci/x86_64-linux-debug-llvm.sh +++ b/ci/x86_64-linux-debug-llvm.sh @@ -34,7 +34,10 @@ cmake .. \ -DZIG_STATIC=ON \ -DZIG_NO_LIB=ON \ -DZIG_EXTRA_BUILD_ARGS="-Duse-llvm=true" \ - -GNinja + -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. diff --git a/ci/x86_64-linux-debug.sh b/ci/x86_64-linux-debug.sh index fb2804487776..3306b8676b8e 100755 --- a/ci/x86_64-linux-debug.sh +++ b/ci/x86_64-linux-debug.sh @@ -33,7 +33,10 @@ cmake .. \ -DZIG_TARGET_MCPU="$MCPU" \ -DZIG_STATIC=ON \ -DZIG_NO_LIB=ON \ - -GNinja + -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. diff --git a/ci/x86_64-linux-release.sh b/ci/x86_64-linux-release.sh index c7b3dc7e364c..95d751be492e 100755 --- a/ci/x86_64-linux-release.sh +++ b/ci/x86_64-linux-release.sh @@ -39,7 +39,10 @@ cmake .. \ -DZIG_TARGET_MCPU="$MCPU" \ -DZIG_STATIC=ON \ -DZIG_NO_LIB=ON \ - -GNinja + -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. @@ -97,7 +100,10 @@ cmake .. \ -DZIG_TARGET_MCPU="$MCPU" \ -DZIG_STATIC=ON \ -DZIG_NO_LIB=ON \ - -GNinja + -GNinja \ + -DCMAKE_C_LINKER_DEPFILE_SUPPORTED=FALSE \ + -DCMAKE_CXX_LINKER_DEPFILE_SUPPORTED=FALSE +# https://github.com/ziglang/zig/issues/22213 unset CC unset CXX From a38220376e4734e07cbcc897ef74182649844658 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Fri, 28 Nov 2025 22:57:33 +0100 Subject: [PATCH 10/53] Revert "ci: apply workaround for #22213 to x86_64-linux scripts" This reverts commit a8f9b5dc0695bd0fb54d35f75c66175cc6ee87a8. --- ci/x86_64-linux-debug-llvm.sh | 5 +---- ci/x86_64-linux-debug.sh | 5 +---- ci/x86_64-linux-release.sh | 10 ++-------- 3 files changed, 4 insertions(+), 16 deletions(-) diff --git a/ci/x86_64-linux-debug-llvm.sh b/ci/x86_64-linux-debug-llvm.sh index aa8ef2ab9f1f..3c11820352b5 100755 --- a/ci/x86_64-linux-debug-llvm.sh +++ b/ci/x86_64-linux-debug-llvm.sh @@ -34,10 +34,7 @@ cmake .. \ -DZIG_STATIC=ON \ -DZIG_NO_LIB=ON \ -DZIG_EXTRA_BUILD_ARGS="-Duse-llvm=true" \ - -GNinja \ - -DCMAKE_C_LINKER_DEPFILE_SUPPORTED=FALSE \ - -DCMAKE_CXX_LINKER_DEPFILE_SUPPORTED=FALSE -# https://github.com/ziglang/zig/issues/22213 + -GNinja # 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. diff --git a/ci/x86_64-linux-debug.sh b/ci/x86_64-linux-debug.sh index 3306b8676b8e..fb2804487776 100755 --- a/ci/x86_64-linux-debug.sh +++ b/ci/x86_64-linux-debug.sh @@ -33,10 +33,7 @@ cmake .. \ -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 + -GNinja # 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. diff --git a/ci/x86_64-linux-release.sh b/ci/x86_64-linux-release.sh index 95d751be492e..c7b3dc7e364c 100755 --- a/ci/x86_64-linux-release.sh +++ b/ci/x86_64-linux-release.sh @@ -39,10 +39,7 @@ cmake .. \ -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 + -GNinja # 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. @@ -100,10 +97,7 @@ cmake .. \ -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 + -GNinja unset CC unset CXX From e52232cd570935d7880cbfe52215dbdf0f2d2243 Mon Sep 17 00:00:00 2001 From: Matthew Lugg Date: Sat, 29 Nov 2025 11:55:26 +0000 Subject: [PATCH 11/53] print_zir: fix typo --- src/print_zir.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/src/print_zir.zig b/src/print_zir.zig index 316632f2d330..78b451a3f127 100644 --- a/src/print_zir.zig +++ b/src/print_zir.zig @@ -595,7 +595,7 @@ const Writer = struct { }, .reify_slice_arg_ty => { - const reify_slice_arg_info: Zir.Inst.ReifySliceArgInfo = @enumFromInt(extended.operand); + const reify_slice_arg_info: Zir.Inst.ReifySliceArgInfo = @enumFromInt(extended.small); const extra = self.code.extraData(Zir.Inst.UnNode, extended.operand).data; try stream.print("{t}, ", .{reify_slice_arg_info}); try self.writeInstRef(stream, extra.operand); From 8f5db19791dde73e8e969c6ebf821767f1c3e50b Mon Sep 17 00:00:00 2001 From: Matthew Lugg Date: Sat, 29 Nov 2025 11:55:36 +0000 Subject: [PATCH 12/53] Sema: initialize OPV comptime allocs correctly This was caused a `[0]std.builtin.Type.StructField.Attributes` to be considered `undefined`, even though that type is OPV so should prefer its OPV `.{}` over `undefined`. Resolves: #30039 --- src/Sema.zig | 4 +++- test/behavior/type.zig | 9 +++++++++ 2 files changed, 12 insertions(+), 1 deletion(-) diff --git a/src/Sema.zig b/src/Sema.zig index 324f1b686757..d31447544c45 100644 --- a/src/Sema.zig +++ b/src/Sema.zig @@ -175,9 +175,11 @@ const ComptimeAlloc = struct { /// `src` may be `null` if `is_const` will be set. fn newComptimeAlloc(sema: *Sema, block: *Block, src: LazySrcLoc, ty: Type, alignment: Alignment) !ComptimeAllocIndex { + const pt = sema.pt; + const init_val = try sema.typeHasOnePossibleValue(ty) orelse try pt.undefValue(ty); const idx = sema.comptime_allocs.items.len; try sema.comptime_allocs.append(sema.gpa, .{ - .val = .{ .interned = try sema.pt.intern(.{ .undef = ty.toIntern() }) }, + .val = .{ .interned = init_val.toIntern() }, .is_const = false, .src = src, .alignment = alignment, diff --git a/test/behavior/type.zig b/test/behavior/type.zig index 411a77f9e5fb..ef27522a6303 100644 --- a/test/behavior/type.zig +++ b/test/behavior/type.zig @@ -427,3 +427,12 @@ test "undefined type value" { }; comptime assert(@TypeOf(S.undef_type) == type); } + +test "reify struct with zero fields through const arrays" { + const names: [0][]const u8 = .{}; + const types: [0]type = .{}; + const attrs: [0]std.builtin.Type.StructField.Attributes = .{}; + const S = @Struct(.auto, null, &names, &types, &attrs); + comptime assert(@typeInfo(S) == .@"struct"); + comptime assert(@typeInfo(S).@"struct".fields.len == 0); +} From 7d9ad992ab636d51f24125e3ebb046d4333b1f80 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Sat, 29 Nov 2025 19:02:04 +0100 Subject: [PATCH 13/53] issue templates: update issue labels to match the actual org labels --- .forgejo/ISSUE_TEMPLATE/bug.yml | 2 +- .forgejo/ISSUE_TEMPLATE/error_message.yml | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/.forgejo/ISSUE_TEMPLATE/bug.yml b/.forgejo/ISSUE_TEMPLATE/bug.yml index 516498132d07..5560b2412d70 100644 --- a/.forgejo/ISSUE_TEMPLATE/bug.yml +++ b/.forgejo/ISSUE_TEMPLATE/bug.yml @@ -1,6 +1,6 @@ name: Bug Report description: File a bug report -labels: ["kind/bug"] +labels: ["bug"] body: - type: markdown attributes: diff --git a/.forgejo/ISSUE_TEMPLATE/error_message.yml b/.forgejo/ISSUE_TEMPLATE/error_message.yml index 1bbbbb2e0752..3f0f3bc307a7 100644 --- a/.forgejo/ISSUE_TEMPLATE/error_message.yml +++ b/.forgejo/ISSUE_TEMPLATE/error_message.yml @@ -1,6 +1,6 @@ name: Error message improvement description: Compiler produces an unhelpful or misleading error message. -labels: ["kind/error message"] +labels: ["error message"] body: - type: input id: version From 5e00a0c9b5627195ae9b6e09a9be3e186d4f76d5 Mon Sep 17 00:00:00 2001 From: Frank Denis Date: Sat, 29 Nov 2025 19:25:07 +0100 Subject: [PATCH 14/53] std.crypto.aes: expose the inverse MixColumns operation MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit The inverse MixColumns operation is already used internally for AES decryption, but it wasn’t exposed in the public API because it didn’t seem necessary at the time. Since then, several new AES-based block ciphers and permutations (such as Vistrutah and Areion) have been developed, and they require this operation to be implementable in Zig. Since then, new interesting AES-based block ciphers and permutations (Vistrutah, Areion, etc). have been invented, and require that operation to be implementable in Zig. --- lib/std/crypto/aes.zig | 30 ++++++++++++++++++++++++++++++ lib/std/crypto/aes/aesni.zig | 22 ++++++++++++++++++++++ lib/std/crypto/aes/armcrypto.zig | 20 ++++++++++++++++++++ lib/std/crypto/aes/soft.zig | 29 +++++++++++++++++++++++++++++ 4 files changed, 101 insertions(+) diff --git a/lib/std/crypto/aes.zig b/lib/std/crypto/aes.zig index 130b461a5ebe..b660fabe25ae 100644 --- a/lib/std/crypto/aes.zig +++ b/lib/std/crypto/aes.zig @@ -108,6 +108,36 @@ test "expand 128-bit key" { } } +test "invMixColumns" { + const key = [_]u8{ 0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6, 0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c }; + const enc_ctx = Aes128.initEnc(key); + const dec_ctx = Aes128.initDec(key); + + for (1..10) |i| { + const enc_rk = enc_ctx.key_schedule.round_keys[10 - i]; + const dec_rk = dec_ctx.key_schedule.round_keys[i]; + const computed = enc_rk.invMixColumns(); + try testing.expectEqualSlices(u8, &dec_rk.toBytes(), &computed.toBytes()); + } +} + +test "BlockVec invMixColumns" { + const input = [_]u8{ + 0x5f, 0x57, 0xf7, 0x1d, 0x72, 0xf5, 0xbe, 0xb9, 0x64, 0xbc, 0x3b, 0xf9, 0x15, 0x92, 0x29, 0x1a, + 0x2b, 0x7e, 0x15, 0x16, 0x28, 0xae, 0xd2, 0xa6, 0xab, 0xf7, 0x15, 0x88, 0x09, 0xcf, 0x4f, 0x3c, + }; + + const vec2 = BlockVec(2).fromBytes(&input); + const result_vec = vec2.invMixColumns(); + const result_bytes = result_vec.toBytes(); + + for (0..2) |i| { + const block = Block.fromBytes(input[i * 16 ..][0..16]); + const expected = block.invMixColumns().toBytes(); + try testing.expectEqualSlices(u8, &expected, result_bytes[i * 16 ..][0..16]); + } +} + test "expand 256-bit key" { const key = [_]u8{ 0x60, 0x3d, 0xeb, 0x10, diff --git a/lib/std/crypto/aes/aesni.zig b/lib/std/crypto/aes/aesni.zig index 64bf37b46ee3..c7b82e0fb964 100644 --- a/lib/std/crypto/aes/aesni.zig +++ b/lib/std/crypto/aes/aesni.zig @@ -96,6 +96,17 @@ pub const Block = struct { return Block{ .repr = block1.repr | block2.repr }; } + /// Apply the inverse MixColumns operation to a block. + pub fn invMixColumns(block: Block) Block { + return Block{ + .repr = asm ( + \\ vaesimc %[in], %[out] + : [out] "=x" (-> Repr), + : [in] "x" (block.repr), + ), + }; + } + /// Perform operations on multiple blocks in parallel. pub const parallel = struct { const cpu = std.Target.x86.cpu; @@ -308,6 +319,17 @@ pub fn BlockVec(comptime blocks_count: comptime_int) type { } return out; } + + /// Apply the inverse MixColumns operation to each block in the vector. + pub fn invMixColumns(block_vec: Self) Self { + var out_bytes: [blocks_count * 16]u8 = undefined; + const in_bytes = block_vec.toBytes(); + inline for (0..blocks_count) |i| { + const block = Block.fromBytes(in_bytes[i * 16 ..][0..16]); + out_bytes[i * 16 ..][0..16].* = block.invMixColumns().toBytes(); + } + return fromBytes(&out_bytes); + } }; } diff --git a/lib/std/crypto/aes/armcrypto.zig b/lib/std/crypto/aes/armcrypto.zig index 714f3c0c3219..02cf207777a8 100644 --- a/lib/std/crypto/aes/armcrypto.zig +++ b/lib/std/crypto/aes/armcrypto.zig @@ -99,6 +99,17 @@ pub const Block = struct { return Block{ .repr = block1.repr | block2.repr }; } + /// Apply the inverse MixColumns operation to a block. + pub fn invMixColumns(block: Block) Block { + return Block{ + .repr = asm ( + \\ aesimc %[out].16b, %[in].16b + : [out] "=x" (-> Repr), + : [in] "x" (block.repr), + ), + }; + } + /// Perform operations on multiple blocks in parallel. pub const parallel = struct { /// The recommended number of AES encryption/decryption to perform in parallel for the chosen implementation. @@ -275,6 +286,15 @@ pub fn BlockVec(comptime blocks_count: comptime_int) type { } return out; } + + /// Apply the inverse MixColumns operation to each block in the vector. + pub fn invMixColumns(block_vec: Self) Self { + var out: Self = undefined; + inline for (0..native_words) |i| { + out.repr[i] = block_vec.repr[i].invMixColumns(); + } + return out; + } }; } diff --git a/lib/std/crypto/aes/soft.zig b/lib/std/crypto/aes/soft.zig index cec5abff4888..989635208bfe 100644 --- a/lib/std/crypto/aes/soft.zig +++ b/lib/std/crypto/aes/soft.zig @@ -265,6 +265,26 @@ pub const Block = struct { return Block{ .repr = x }; } + /// Apply the inverse MixColumns operation to a block. + pub fn invMixColumns(block: Block) Block { + var out: Repr = undefined; + inline for (0..4) |i| { + const col = block.repr[i]; + const b0: u8 = @truncate(col); + const b1: u8 = @truncate(col >> 8); + const b2: u8 = @truncate(col >> 16); + const b3: u8 = @truncate(col >> 24); + + const r0 = mul(0x0e, b0) ^ mul(0x0b, b1) ^ mul(0x0d, b2) ^ mul(0x09, b3); + const r1 = mul(0x09, b0) ^ mul(0x0e, b1) ^ mul(0x0b, b2) ^ mul(0x0d, b3); + const r2 = mul(0x0d, b0) ^ mul(0x09, b1) ^ mul(0x0e, b2) ^ mul(0x0b, b3); + const r3 = mul(0x0b, b0) ^ mul(0x0d, b1) ^ mul(0x09, b2) ^ mul(0x0e, b3); + + out[i] = @as(u32, r0) | (@as(u32, r1) << 8) | (@as(u32, r2) << 16) | (@as(u32, r3) << 24); + } + return Block{ .repr = out }; + } + /// Perform operations on multiple blocks in parallel. pub const parallel = struct { /// The recommended number of AES encryption/decryption to perform in parallel for the chosen implementation. @@ -441,6 +461,15 @@ pub fn BlockVec(comptime blocks_count: comptime_int) type { } return out; } + + /// Apply the inverse MixColumns operation to each block in the vector. + pub fn invMixColumns(block_vec: Self) Self { + var out: Self = undefined; + for (0..native_words) |i| { + out.repr[i] = block_vec.repr[i].invMixColumns(); + } + return out; + } }; } From a0289d0cce370729099a1ec0228e7f49bcdabc80 Mon Sep 17 00:00:00 2001 From: Nikolay Govorov Date: Tue, 18 Nov 2025 18:15:22 +0000 Subject: [PATCH 15/53] std.posix.accept: handle non-listening socket EINVAL --- lib/std/Io/Threaded.zig | 2 +- lib/std/Io/net.zig | 4 ++++ 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 0e5c12504b15..b5d40b5b01be 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -3900,7 +3900,7 @@ fn netAcceptPosix(userdata: ?*anyopaque, listen_fd: net.Socket.Handle) net.Serve .BADF => |err| return errnoBug(err), // File descriptor used after closed. .CONNABORTED => return error.ConnectionAborted, .FAULT => |err| return errnoBug(err), - .INVAL => |err| return errnoBug(err), + .INVAL => return error.SocketNotListening, .NOTSOCK => |err| return errnoBug(err), .MFILE => return error.ProcessFdQuotaExceeded, .NFILE => return error.SystemFdQuotaExceeded, diff --git a/lib/std/Io/net.zig b/lib/std/Io/net.zig index ee1e93ced21d..c9bc65dd3b85 100644 --- a/lib/std/Io/net.zig +++ b/lib/std/Io/net.zig @@ -1333,6 +1333,10 @@ pub const Server = struct { /// Not enough free memory. This often means that the memory allocation is limited /// by the socket buffer limits, not by the system memory. SystemResources, + /// Either `listen` was never called, or `shutdown` was called (possibly while + /// this call was blocking). This allows `shutdown` to be used as a concurrent + /// cancellation mechanism. + SocketNotListening, /// The network subsystem has failed. NetworkDown, /// No connection is already queued and ready to be accepted, and From bfe3317059131ab552f7583b88d6bc82609d198c Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?fn=20=E2=8C=83=20=E2=8C=A5?= <70830482+FnControlOption@users.noreply.github.com> Date: Wed, 16 Apr 2025 11:59:47 -0700 Subject: [PATCH 16/53] Return a `usize` from `@abs` if given an `isize` Also: - `c_ushort` for `c_short` - `c_uint` for `c_int` - `c_ulong` for `c_long` - `c_ulonglong` for `c_longlong` --- src/Type.zig | 23 ++++++++++++------ test/behavior/abs.zig | 54 +++++++++++++++++++++++++++++++++++++++++++ 2 files changed, 70 insertions(+), 7 deletions(-) diff --git a/src/Type.zig b/src/Type.zig index 3c13f3db949d..5577d82335a5 100644 --- a/src/Type.zig +++ b/src/Type.zig @@ -3445,13 +3445,22 @@ pub fn optEuBaseType(ty: Type, zcu: *const Zcu) Type { pub fn toUnsigned(ty: Type, pt: Zcu.PerThread) !Type { const zcu = pt.zcu; - return switch (ty.zigTypeTag(zcu)) { - .int => pt.intType(.unsigned, ty.intInfo(zcu).bits), - .vector => try pt.vectorType(.{ - .len = ty.vectorLen(zcu), - .child = (try ty.childType(zcu).toUnsigned(pt)).toIntern(), - }), - else => unreachable, + return switch (ty.toIntern()) { + // zig fmt: off + .usize_type, .isize_type => .usize, + .c_ushort_type, .c_short_type => .c_ushort, + .c_uint_type, .c_int_type => .c_uint, + .c_ulong_type, .c_long_type => .c_ulong, + .c_ulonglong_type, .c_longlong_type => .c_ulonglong, + // zig fmt: on + else => switch (ty.zigTypeTag(zcu)) { + .int => pt.intType(.unsigned, ty.intInfo(zcu).bits), + .vector => try pt.vectorType(.{ + .len = ty.vectorLen(zcu), + .child = (try ty.childType(zcu).toUnsigned(pt)).toIntern(), + }), + else => unreachable, + }, }; } diff --git a/test/behavior/abs.zig b/test/behavior/abs.zig index 078362cf3e6c..1b12e08eccbb 100644 --- a/test/behavior/abs.zig +++ b/test/behavior/abs.zig @@ -1,5 +1,6 @@ const builtin = @import("builtin"); const std = @import("std"); +const assert = std.debug.assert; const expect = std.testing.expect; test "@abs integers" { @@ -48,6 +49,33 @@ fn testAbsIntegers() !void { } } +test "@abs signed C ABI integers" { + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_riscv64) return error.SkipZigTest; // TODO + + const S = struct { + fn doTheTest() !void { + try testOne(isize, usize); + try testOne(c_short, c_ushort); + try testOne(c_int, c_uint); + try testOne(c_long, c_ulong); + if (!builtin.cpu.arch.isSpirV()) try testOne(c_longlong, c_ulonglong); + } + fn testOne(comptime Signed: type, comptime Unsigned: type) !void { + var negative_one: Signed = undefined; + negative_one = -1; + const one = @abs(negative_one); + comptime assert(@TypeOf(one) == Unsigned); + try expect(one == 1); + } + }; + + try S.doTheTest(); + try comptime S.doTheTest(); +} + test "@abs unsigned integers" { if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO @@ -87,6 +115,32 @@ fn testAbsUnsignedIntegers() !void { } } +test "@abs unsigned C ABI integers" { + if (builtin.zig_backend == .stage2_aarch64) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO + if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO + + const S = struct { + fn doTheTest() !void { + try testOne(usize); + try testOne(c_ushort); + try testOne(c_uint); + try testOne(c_ulong); + if (!builtin.cpu.arch.isSpirV()) try testOne(c_ulonglong); + } + fn testOne(comptime Unsigned: type) !void { + var one: Unsigned = undefined; + one = 1; + const still_one = @abs(one); + comptime assert(@TypeOf(still_one) == Unsigned); + try expect(still_one == 1); + } + }; + + try S.doTheTest(); + try comptime S.doTheTest(); +} + test "@abs big int <= 128 bits" { if (builtin.zig_backend == .stage2_arm) return error.SkipZigTest; // TODO if (builtin.zig_backend == .stage2_sparc64) return error.SkipZigTest; // TODO From 95f93a0b281e32583edef36808231a5f61fb7de1 Mon Sep 17 00:00:00 2001 From: rpkak Date: Mon, 1 Dec 2025 12:56:11 +0100 Subject: [PATCH 17/53] std.zig.Ast: count addrspace tokens correctly Before this PR this ```zig const namespace = struct { extern const num: u8 addrspace(.generic); }; // comment ``` got formatted to this ```zig const namespace = struct { extern const num: u8 addrspace(.generic); ( ``` Co-authored-by: rpkak Co-committed-by: rpkak --- lib/std/zig/Ast.zig | 3 +++ lib/std/zig/parser_test.zig | 10 ++++++++++ 2 files changed, 13 insertions(+) diff --git a/lib/std/zig/Ast.zig b/lib/std/zig/Ast.zig index 72ca20a6c0a6..c9294aa0af45 100644 --- a/lib/std/zig/Ast.zig +++ b/lib/std/zig/Ast.zig @@ -1195,6 +1195,9 @@ pub fn lastToken(tree: Ast, node: Node.Index) TokenIndex { if (extra.section_node.unwrap()) |section_node| { end_offset += 1; // for the rparen n = section_node; + } else if (extra.addrspace_node.unwrap()) |addrspace_node| { + end_offset += 1; // for the rparen + n = addrspace_node; } else if (extra.align_node.unwrap()) |align_node| { end_offset += 1; // for the rparen n = align_node; diff --git a/lib/std/zig/parser_test.zig b/lib/std/zig/parser_test.zig index b563fa90e33c..84320e5dbc2b 100644 --- a/lib/std/zig/parser_test.zig +++ b/lib/std/zig/parser_test.zig @@ -6084,6 +6084,16 @@ test "zig fmt: do not canonicalize invalid cast builtins" { ); } +test "zig fmt: extern addrspace in struct" { + try testCanonical( + \\const namespace = struct { + \\ extern const num: u8 addrspace(.generic); + \\}; + \\// comment + \\ + ); +} + test "recovery: top level" { try testError( \\test "" {inline} From 29e418cbfb3d5e5800ac2ad267b21cf2b8942c2c Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 27 Nov 2025 08:42:06 -0800 Subject: [PATCH 18/53] std.Io.Threaded: fix the cancellation race Now, before a syscall is entered, beginSyscall is called, which may return error.Canceled. After syscall returns, whether error or success, endSyscall is called. If the syscall returns EINTR then checkCancel is called. `cancelRequested` is removed from the std.Io VTable for now, with plans to replace it with a more powerful API that allows protection against cancellation requests. closes #25751 --- lib/std/Io.zig | 5 - lib/std/Io/Threaded.zig | 3019 +++++++++++++++++++++++++-------------- 2 files changed, 1910 insertions(+), 1114 deletions(-) diff --git a/lib/std/Io.zig b/lib/std/Io.zig index aa860abb364e..eb854e3ecab0 100644 --- a/lib/std/Io.zig +++ b/lib/std/Io.zig @@ -620,11 +620,6 @@ pub const VTable = struct { result: []u8, result_alignment: std.mem.Alignment, ) void, - /// Returns whether the current thread of execution is known to have - /// been requested to cancel. - /// - /// Thread-safe. - cancelRequested: *const fn (?*anyopaque) bool, /// When this function returns, implementation guarantees that `start` has /// either already been called, or a unit of concurrency has been assigned diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index b5d40b5b01be..ee26c6153d56 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -50,6 +50,8 @@ cpu_count_error: ?std.Thread.CpuCountError, /// available count, subtract this from either `async_limit` or /// `concurrent_limit`. busy_count: usize = 0, +main_thread: Thread, +pid: Pid = .unknown, wsa: if (is_windows) Wsa else struct {} = .{}, @@ -57,7 +59,79 @@ have_signal_handler: bool, old_sig_io: if (have_sig_io) posix.Sigaction else void, old_sig_pipe: if (have_sig_pipe) posix.Sigaction else void, -threadlocal var current_closure: ?*Closure = null; +pub const Pid = if (native_os == .linux) enum(posix.pid_t) { + unknown = 0, + _, +} else enum(u0) { unknown = 0 }; + +const Thread = struct { + /// The value that needs to be passed to pthread_kill or tgkill in order to + /// send a signal. + signal_id: SignalId, + current_closure: ?*Closure = null, + + const SignalId = if (std.Thread.use_pthreads) std.c.pthread_t else std.Thread.Id; + + threadlocal var current: ?*Thread = null; + + fn getCurrent(t: *Threaded) *Thread { + return current orelse return &t.main_thread; + } + + fn checkCancel(thread: *Thread) error{Canceled}!void { + const closure = thread.current_closure orelse return; + switch (@cmpxchgStrong( + CancelStatus, + &closure.cancel_status, + .requested, + .acknowledged, + .acq_rel, + .acquire, + ) orelse return error.Canceled) { + .none => return, + .requested => unreachable, + .acknowledged => unreachable, + _ => return, + } + } + + fn beginSyscall(thread: *Thread) error{Canceled}!void { + const closure = thread.current_closure orelse return; + + switch (@cmpxchgStrong( + CancelStatus, + &closure.cancel_status, + .none, + .fromSignalId(thread.signal_id), + .acq_rel, + .acquire, + ) orelse return) { + .none => unreachable, + .requested => { + @atomicStore(CancelStatus, &closure.cancel_status, .acknowledged, .release); + return error.Canceled; + }, + .acknowledged => unreachable, + _ => unreachable, + } + } + + fn endSyscall(thread: *Thread) void { + const closure = thread.current_closure orelse return; + _ = @cmpxchgStrong( + CancelStatus, + &closure.cancel_status, + .fromSignalId(thread.signal_id), + .none, + .acq_rel, + .acquire, + ); + } + + fn currentSignalId() SignalId { + return if (std.Thread.use_pthreads) std.c.pthread_self() else std.Thread.getCurrentId(); + } +}; const max_iovecs_len = 8; const splat_buffer_size = 64; @@ -66,48 +140,93 @@ comptime { if (@TypeOf(posix.IOV_MAX) != void) assert(max_iovecs_len <= posix.IOV_MAX); } -const CancelId = enum(usize) { +const CancelStatus = enum(usize) { + /// Cancellation has neither been requested, nor checked. The async + /// operation will check status before entering a blocking syscall. + /// This is also the status used for uninteruptible tasks. none = 0, - canceling = std.math.maxInt(usize), + /// Cancellation has been requested and the status will be checked before + /// entering a blocking syscall. + requested = std.math.maxInt(usize) - 1, + /// Cancellation has been acknowledged and is in progress. Signals should + /// not be sent. + acknowledged = std.math.maxInt(usize), + /// Stores a `Thread.SignalId` and indicates that sending a signal to this thread + /// is needed in order to cancel. This state is set before going into + /// a blocking operation that needs to get unblocked via signal. _, - const ThreadId = if (std.Thread.use_pthreads) std.c.pthread_t else std.Thread.Id; + const Unpacked = union(enum) { + none, + requested, + acknowledged, + signal_id: Thread.SignalId, + }; - fn currentThread() CancelId { - if (std.Thread.use_pthreads) { - return @enumFromInt(@intFromPtr(std.c.pthread_self())); - } else { - return @enumFromInt(std.Thread.getCurrentId()); - } + fn unpack(cs: CancelStatus) Unpacked { + return switch (cs) { + .none => .none, + .requested => .requested, + .acknowledged => .acknowledged, + _ => |signal_id| .{ + .signal_id = if (std.Thread.use_pthreads) + @ptrFromInt(@intFromEnum(signal_id)) + else + @truncate(@intFromEnum(signal_id)), + }, + }; } - fn toThreadId(cancel_id: CancelId) ThreadId { - if (std.Thread.use_pthreads) { - return @ptrFromInt(@intFromEnum(cancel_id)); - } else { - return @intCast(@intFromEnum(cancel_id)); - } + fn fromSignalId(signal_id: Thread.SignalId) CancelStatus { + return if (std.Thread.use_pthreads) + @enumFromInt(@intFromPtr(signal_id)) + else + @enumFromInt(signal_id); } }; const Closure = struct { start: Start, node: std.SinglyLinkedList.Node = .{}, - cancel_tid: CancelId, - - const Start = *const fn (*Closure) void; - - fn requestCancel(closure: *Closure) void { - switch (@atomicRmw(CancelId, &closure.cancel_tid, .Xchg, .canceling, .acq_rel)) { - .none, .canceling => {}, - else => |tid| { - if (std.Thread.use_pthreads) { - const rc = std.c.pthread_kill(tid.toThreadId(), .IO); - if (is_debug) assert(rc == 0); - } else if (native_os == .linux) { - _ = std.os.linux.tgkill(std.os.linux.getpid(), @bitCast(tid.toThreadId()), .IO); - } - }, + cancel_status: CancelStatus, + + const Start = *const fn (*Closure, *Threaded) void; + + fn requestCancel(closure: *Closure, t: *Threaded) void { + var signal_id = switch (@atomicRmw(CancelStatus, &closure.cancel_status, .Xchg, .requested, .monotonic).unpack()) { + .none, .acknowledged, .requested => return, + .signal_id => |signal_id| signal_id, + }; + // The task will enter a blocking syscall before checking for cancellation again. + // We can send a signal to interrupt the syscall, but if it arrives before + // the syscall instruction, it will be missed. Therefore, this code tries + // again until the cancellation request is acknowledged. + const max_attempts = 3; + for (0..max_attempts) |_| { + if (std.Thread.use_pthreads) { + const rc = std.c.pthread_kill(signal_id, .IO); + if (is_debug) assert(rc == 0); + } else if (native_os == .linux) { + const pid: posix.pid_t = p: { + const cached_pid = @atomicLoad(Pid, &t.pid, .monotonic); + if (cached_pid != .unknown) break :p @intFromEnum(cached_pid); + const pid = std.os.linux.getpid(); + @atomicStore(Pid, &t.pid, @enumFromInt(pid), .monotonic); + break :p pid; + }; + _ = std.os.linux.tgkill(pid, @bitCast(signal_id), .IO); + } else { + return; + } + + // TODO make this a nanosleep with 1 << attempt duration + std.Thread.yield() catch {}; + + switch (@atomicRmw(CancelStatus, &closure.cancel_status, .Xchg, .requested, .monotonic).unpack()) { + .requested => continue, + .none, .acknowledged => return, + .signal_id => |new_signal_id| signal_id = new_signal_id, + } } } }; @@ -136,6 +255,9 @@ pub fn init( .old_sig_io = undefined, .old_sig_pipe = undefined, .have_signal_handler = false, + .main_thread = .{ + .signal_id = Thread.currentSignalId(), + }, }; if (posix.Sigaction != void) { @@ -169,6 +291,7 @@ pub const init_single_threaded: Threaded = .{ .old_sig_io = undefined, .old_sig_pipe = undefined, .have_signal_handler = false, + .main_thread = .{ .signal_id = undefined }, }; pub fn setAsyncLimit(t: *Threaded, new_limit: Io.Limit) void { @@ -201,6 +324,11 @@ fn join(t: *Threaded) void { } fn worker(t: *Threaded) void { + var thread: Thread = .{ + .signal_id = Thread.currentSignalId(), + }; + Thread.current = &thread; + defer t.wait_group.finish(); t.mutex.lock(); @@ -210,7 +338,7 @@ fn worker(t: *Threaded) void { while (t.run_queue.popFirst()) |closure_node| { t.mutex.unlock(); const closure: *Closure = @fieldParentPtr("node", closure_node); - closure.start(closure); + closure.start(closure, t); t.mutex.lock(); t.busy_count -= 1; } @@ -227,7 +355,6 @@ pub fn io(t: *Threaded) Io { .concurrent = concurrent, .await = await, .cancel = cancel, - .cancelRequested = cancelRequested, .select = select, .groupAsync = groupAsync, @@ -324,7 +451,6 @@ pub fn ioBasic(t: *Threaded) Io { .concurrent = concurrent, .await = await, .cancel = cancel, - .cancelRequested = cancelRequested, .select = select, .groupAsync = groupAsync, @@ -418,24 +544,12 @@ const AsyncClosure = struct { const done_reset_event: *ResetEvent = @ptrFromInt(@alignOf(ResetEvent)); - fn start(closure: *Closure) void { + fn start(closure: *Closure, t: *Threaded) void { const ac: *AsyncClosure = @alignCast(@fieldParentPtr("closure", closure)); - const tid: CancelId = .currentThread(); - if (@cmpxchgStrong(CancelId, &closure.cancel_tid, .none, tid, .acq_rel, .acquire)) |cancel_tid| { - assert(cancel_tid == .canceling); - // Even though we already know the task is canceled, we must still - // run the closure in order to make the return value valid and in - // case there are side effects. - } - current_closure = closure; + const current_thread = Thread.getCurrent(t); + current_thread.current_closure = closure; ac.func(ac.contextPointer(), ac.resultPointer()); - current_closure = null; - - // In case a cancel happens after successful task completion, prevents - // signal from being delivered to the thread in `requestCancel`. - if (@cmpxchgStrong(CancelId, &closure.cancel_tid, tid, .none, .acq_rel, .acquire)) |cancel_tid| { - assert(cancel_tid == .canceling); - } + current_thread.current_closure = null; if (@atomicRmw(?*ResetEvent, &ac.select_condition, .Xchg, done_reset_event, .release)) |select_reset| { assert(select_reset != done_reset_event); @@ -476,7 +590,7 @@ const AsyncClosure = struct { const actual_result_offset = actual_result_addr - @intFromPtr(ac); ac.* = .{ .closure = .{ - .cancel_tid = .none, + .cancel_status = .none, .start = start, }, .func = func, @@ -493,7 +607,7 @@ const AsyncClosure = struct { fn waitAndDeinit(ac: *AsyncClosure, t: *Threaded, result: []u8) void { ac.reset_event.wait(t) catch |err| switch (err) { error.Canceled => { - ac.closure.requestCancel(); + ac.closure.requestCancel(t); ac.reset_event.waitUncancelable(); }, }; @@ -604,7 +718,6 @@ fn concurrent( const GroupClosure = struct { closure: Closure, - t: *Threaded, group: *Io.Group, /// Points to sibling `GroupClosure`. Used for walking the group to cancel all. node: std.SinglyLinkedList.Node, @@ -612,26 +725,15 @@ const GroupClosure = struct { context_alignment: Alignment, alloc_len: usize, - fn start(closure: *Closure) void { + fn start(closure: *Closure, t: *Threaded) void { const gc: *GroupClosure = @alignCast(@fieldParentPtr("closure", closure)); - const tid: CancelId = .currentThread(); + const current_thread = Thread.getCurrent(t); const group = gc.group; const group_state: *std.atomic.Value(usize) = @ptrCast(&group.state); const reset_event: *ResetEvent = @ptrCast(&group.context); - if (@cmpxchgStrong(CancelId, &closure.cancel_tid, .none, tid, .acq_rel, .acquire)) |cancel_tid| { - assert(cancel_tid == .canceling); - // Even though we already know the task is canceled, we must still - // run the closure in case there are side effects. - } - current_closure = closure; + current_thread.current_closure = closure; gc.func(group, gc.contextPointer()); - current_closure = null; - - // In case a cancel happens after successful task completion, prevents - // signal from being delivered to the thread in `requestCancel`. - if (@cmpxchgStrong(CancelId, &closure.cancel_tid, tid, .none, .acq_rel, .acquire)) |cancel_tid| { - assert(cancel_tid == .canceling); - } + current_thread.current_closure = null; const prev_state = group_state.fetchSub(sync_one_pending, .acq_rel); assert((prev_state / sync_one_pending) > 0); @@ -647,7 +749,6 @@ const GroupClosure = struct { /// Does not initialize the `node` field. fn init( gpa: Allocator, - t: *Threaded, group: *Io.Group, context: []const u8, context_alignment: Alignment, @@ -662,10 +763,9 @@ const GroupClosure = struct { gc.* = .{ .closure = .{ - .cancel_tid = .none, + .cancel_status = .none, .start = start, }, - .t = t, .group = group, .node = undefined, .func = func, @@ -696,7 +796,7 @@ fn groupAsync( if (builtin.single_threaded) return start(group, context.ptr); const gpa = t.allocator; - const gc = GroupClosure.init(gpa, t, group, context, context_alignment, start) catch + const gc = GroupClosure.init(gpa, group, context, context_alignment, start) catch return start(group, context.ptr); t.mutex.lock(); @@ -752,7 +852,7 @@ fn groupConcurrent( const t: *Threaded = @ptrCast(@alignCast(userdata)); const gpa = t.allocator; - const gc = GroupClosure.init(gpa, t, group, context, context_alignment, start) catch + const gc = GroupClosure.init(gpa, group, context, context_alignment, start) catch return error.ConcurrencyUnavailable; t.mutex.lock(); @@ -806,7 +906,7 @@ fn groupWait(userdata: ?*anyopaque, group: *Io.Group, token: *anyopaque) void { var node: *std.SinglyLinkedList.Node = @ptrCast(@alignCast(token)); while (true) { const gc: *GroupClosure = @fieldParentPtr("node", node); - gc.closure.requestCancel(); + gc.closure.requestCancel(t); node = node.next orelse break; } reset_event.waitUncancelable(); @@ -832,7 +932,7 @@ fn groupCancel(userdata: ?*anyopaque, group: *Io.Group, token: *anyopaque) void var node: *std.SinglyLinkedList.Node = @ptrCast(@alignCast(token)); while (true) { const gc: *GroupClosure = @fieldParentPtr("node", node); - gc.closure.requestCancel(); + gc.closure.requestCancel(t); node = node.next orelse break; } } @@ -875,30 +975,20 @@ fn cancel( _ = result_alignment; const t: *Threaded = @ptrCast(@alignCast(userdata)); const ac: *AsyncClosure = @ptrCast(@alignCast(any_future)); - ac.closure.requestCancel(); + ac.closure.requestCancel(t); ac.waitAndDeinit(t, result); } -fn cancelRequested(userdata: ?*anyopaque) bool { - const t: *Threaded = @ptrCast(@alignCast(userdata)); - _ = t; - const closure = current_closure orelse return false; - return @atomicLoad(CancelId, &closure.cancel_tid, .acquire) == .canceling; -} - -fn checkCancel(t: *Threaded) error{Canceled}!void { - if (cancelRequested(t)) return error.Canceled; -} - fn mutexLock(userdata: ?*anyopaque, prev_state: Io.Mutex.State, mutex: *Io.Mutex) Io.Cancelable!void { if (builtin.single_threaded) unreachable; // Interface should have prevented this. if (native_os == .netbsd) @panic("TODO"); const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); if (prev_state == .contended) { - try futexWait(t, @ptrCast(&mutex.state), @intFromEnum(Io.Mutex.State.contended)); + try futexWait(current_thread, @ptrCast(&mutex.state), @intFromEnum(Io.Mutex.State.contended)); } while (@atomicRmw(Io.Mutex.State, &mutex.state, .Xchg, .contended, .acquire) != .unlocked) { - try futexWait(t, @ptrCast(&mutex.state), @intFromEnum(Io.Mutex.State.contended)); + try futexWait(current_thread, @ptrCast(&mutex.state), @intFromEnum(Io.Mutex.State.contended)); } } @@ -960,6 +1050,7 @@ fn conditionWait(userdata: ?*anyopaque, cond: *Io.Condition, mutex: *Io.Mutex) I if (builtin.single_threaded) unreachable; // Deadlock. if (native_os == .netbsd) @panic("TODO"); const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); const t_io = ioBasic(t); comptime assert(@TypeOf(cond.state) == u64); const ints: *[2]std.atomic.Value(u32) = @ptrCast(&cond.state); @@ -988,7 +1079,7 @@ fn conditionWait(userdata: ?*anyopaque, cond: *Io.Condition, mutex: *Io.Mutex) I defer mutex.lockUncancelable(t_io); while (true) { - try futexWait(t, cond_epoch, epoch); + try futexWait(current_thread, cond_epoch, epoch); epoch = cond_epoch.load(.acquire); state = cond_state.load(.monotonic); @@ -1074,35 +1165,46 @@ const dirMake = switch (native_os) { fn dirMakePosix(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, mode: Io.Dir.Mode) Io.Dir.MakeError!void { const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); var path_buffer: [posix.PATH_MAX]u8 = undefined; const sub_path_posix = try pathToPosix(sub_path, &path_buffer); + try current_thread.beginSyscall(); while (true) { - try t.checkCancel(); switch (posix.errno(posix.system.mkdirat(dir.handle, sub_path_posix, mode))) { - .SUCCESS => return, - .INTR => continue, - .CANCELED => return error.Canceled, - - .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), + .SUCCESS => { + current_thread.endSyscall(); + return; + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .CANCELED => return error.Canceled, + .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), + } + }, } } } @@ -1110,11 +1212,18 @@ fn dirMakePosix(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, mode: fn dirMakeWasi(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, mode: Io.Dir.Mode) Io.Dir.MakeError!void { if (builtin.link_libc) return dirMakePosix(userdata, dir, sub_path, mode); const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); + try current_thread.beginSyscall(); while (true) { - try t.checkCancel(); switch (std.os.wasi.path_create_directory(dir.handle, sub_path.ptr, sub_path.len)) { - .SUCCESS => return, - .INTR => continue, + .SUCCESS => { + current_thread.endSyscall(); + return; + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, .CANCELED => return error.Canceled, .ACCES => return error.AccessDenied, @@ -1140,7 +1249,8 @@ fn dirMakeWasi(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, mode: I fn dirMakeWindows(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, mode: Io.Dir.Mode) Io.Dir.MakeError!void { const t: *Threaded = @ptrCast(@alignCast(userdata)); - try t.checkCancel(); + const current_thread = Thread.getCurrent(t); + try current_thread.checkCancel(); const sub_path_w = try windows.sliceToPrefixedFileW(dir.handle, sub_path); _ = mode; @@ -1213,6 +1323,7 @@ fn dirMakeOpenPathWindows( options: Io.Dir.OpenOptions, ) Io.Dir.MakeOpenPathError!Io.Dir { const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); const w = windows; const access_mask = w.STANDARD_RIGHTS_READ | w.FILE_READ_ATTRIBUTES | w.FILE_READ_EA | w.SYNCHRONIZE | w.FILE_TRAVERSE | @@ -1226,7 +1337,7 @@ fn dirMakeOpenPathWindows( }; while (true) { - try t.checkCancel(); + try current_thread.checkCancel(); const sub_path_w_array = try w.sliceToPrefixedFileW(dir.handle, component.path); const sub_path_w = sub_path_w_array.span(); @@ -1328,8 +1439,7 @@ fn dirMakeOpenPathWasi( fn dirStat(userdata: ?*anyopaque, dir: Io.Dir) Io.Dir.StatError!Io.Dir.Stat { const t: *Threaded = @ptrCast(@alignCast(userdata)); - try t.checkCancel(); - + _ = t; _ = dir; @panic("TODO implement dirStat"); } @@ -1348,6 +1458,7 @@ fn dirStatPathLinux( options: Io.Dir.StatPathOptions, ) Io.Dir.StatPathError!Io.File.Stat { const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); const linux = std.os.linux; var path_buffer: [posix.PATH_MAX]u8 = undefined; @@ -1356,8 +1467,8 @@ fn dirStatPathLinux( const flags: u32 = linux.AT.NO_AUTOMOUNT | @as(u32, if (!options.follow_symlinks) linux.AT.SYMLINK_NOFOLLOW else 0); + try current_thread.beginSyscall(); while (true) { - try t.checkCancel(); var statx = std.mem.zeroes(linux.Statx); const rc = linux.statx( dir.handle, @@ -1367,20 +1478,30 @@ fn dirStatPathLinux( &statx, ); switch (linux.errno(rc)) { - .SUCCESS => return statFromLinux(&statx), - .INTR => continue, - .CANCELED => return error.Canceled, - - .ACCES => return error.AccessDenied, - .BADF => |err| return errnoBug(err), // File descriptor used after closed. - .FAULT => |err| return errnoBug(err), - .INVAL => |err| return errnoBug(err), - .LOOP => return error.SymLinkLoop, - .NAMETOOLONG => |err| return errnoBug(err), // Handled by pathToPosix() above. - .NOENT => return error.FileNotFound, - .NOTDIR => return error.NotDir, - .NOMEM => return error.SystemResources, - else => |err| return posix.unexpectedErrno(err), + .SUCCESS => { + current_thread.endSyscall(); + return statFromLinux(&statx); + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .CANCELED => return error.Canceled, + .ACCES => return error.AccessDenied, + .BADF => |err| return errnoBug(err), // File descriptor used after closed. + .FAULT => |err| return errnoBug(err), + .INVAL => |err| return errnoBug(err), + .LOOP => return error.SymLinkLoop, + .NAMETOOLONG => |err| return errnoBug(err), // Handled by pathToPosix() above. + .NOENT => return error.FileNotFound, + .NOTDIR => return error.NotDir, + .NOMEM => return error.SystemResources, + else => |err| return posix.unexpectedErrno(err), + } + }, } } } @@ -1392,32 +1513,43 @@ fn dirStatPathPosix( options: Io.Dir.StatPathOptions, ) Io.Dir.StatPathError!Io.File.Stat { const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); var path_buffer: [posix.PATH_MAX]u8 = undefined; const sub_path_posix = try pathToPosix(sub_path, &path_buffer); const flags: u32 = if (!options.follow_symlinks) posix.AT.SYMLINK_NOFOLLOW else 0; + try current_thread.beginSyscall(); while (true) { - try t.checkCancel(); var stat = std.mem.zeroes(posix.Stat); switch (posix.errno(fstatat_sym(dir.handle, sub_path_posix, &stat, flags))) { - .SUCCESS => return statFromPosix(&stat), - .INTR => continue, - .CANCELED => return error.Canceled, - - .INVAL => |err| return errnoBug(err), - .BADF => |err| return errnoBug(err), // File descriptor used after closed. - .NOMEM => return error.SystemResources, - .ACCES => return error.AccessDenied, - .PERM => return error.PermissionDenied, - .FAULT => |err| return errnoBug(err), - .NAMETOOLONG => return error.NameTooLong, - .LOOP => return error.SymLinkLoop, - .NOENT => return error.FileNotFound, - .NOTDIR => return error.FileNotFound, - .ILSEQ => return error.BadPathName, - else => |err| return posix.unexpectedErrno(err), + .SUCCESS => { + current_thread.endSyscall(); + return statFromPosix(&stat); + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .CANCELED => return error.Canceled, + .INVAL => |err| return errnoBug(err), + .BADF => |err| return errnoBug(err), // File descriptor used after closed. + .NOMEM => return error.SystemResources, + .ACCES => return error.AccessDenied, + .PERM => return error.PermissionDenied, + .FAULT => |err| return errnoBug(err), + .NAMETOOLONG => return error.NameTooLong, + .LOOP => return error.SymLinkLoop, + .NOENT => return error.FileNotFound, + .NOTDIR => return error.FileNotFound, + .ILSEQ => return error.BadPathName, + else => |err| return posix.unexpectedErrno(err), + } + }, } } } @@ -1444,29 +1576,40 @@ fn dirStatPathWasi( ) Io.Dir.StatPathError!Io.File.Stat { if (builtin.link_libc) return dirStatPathPosix(userdata, dir, sub_path, options); const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); const wasi = std.os.wasi; const flags: wasi.lookupflags_t = .{ .SYMLINK_FOLLOW = options.follow_symlinks, }; var stat: wasi.filestat_t = undefined; + try current_thread.beginSyscall(); while (true) { - try t.checkCancel(); switch (wasi.path_filestat_get(dir.handle, flags, sub_path.ptr, sub_path.len, &stat)) { - .SUCCESS => return statFromWasi(&stat), - .INTR => continue, - .CANCELED => return error.Canceled, - - .INVAL => |err| return errnoBug(err), - .BADF => |err| return errnoBug(err), // File descriptor used after closed. - .NOMEM => return error.SystemResources, - .ACCES => return error.AccessDenied, - .FAULT => |err| return errnoBug(err), - .NAMETOOLONG => return error.NameTooLong, - .NOENT => return error.FileNotFound, - .NOTDIR => return error.FileNotFound, - .NOTCAPABLE => return error.AccessDenied, - .ILSEQ => return error.BadPathName, - else => |err| return posix.unexpectedErrno(err), + .SUCCESS => { + current_thread.endSyscall(); + return statFromWasi(&stat); + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .CANCELED => return error.Canceled, + .INVAL => |err| return errnoBug(err), + .BADF => |err| return errnoBug(err), // File descriptor used after closed. + .NOMEM => return error.SystemResources, + .ACCES => return error.AccessDenied, + .FAULT => |err| return errnoBug(err), + .NAMETOOLONG => return error.NameTooLong, + .NOENT => return error.FileNotFound, + .NOTDIR => return error.FileNotFound, + .NOTCAPABLE => return error.AccessDenied, + .ILSEQ => return error.BadPathName, + else => |err| return posix.unexpectedErrno(err), + } + }, } } } @@ -1480,31 +1623,44 @@ const fileStat = switch (native_os) { fn fileStatPosix(userdata: ?*anyopaque, file: Io.File) Io.File.StatError!Io.File.Stat { const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); if (posix.Stat == void) return error.Streaming; + try current_thread.beginSyscall(); while (true) { - try t.checkCancel(); var stat = std.mem.zeroes(posix.Stat); switch (posix.errno(fstat_sym(file.handle, &stat))) { - .SUCCESS => return statFromPosix(&stat), - .INTR => continue, - .CANCELED => return error.Canceled, - - .INVAL => |err| return errnoBug(err), - .BADF => |err| return errnoBug(err), // File descriptor used after closed. - .NOMEM => return error.SystemResources, - .ACCES => return error.AccessDenied, - else => |err| return posix.unexpectedErrno(err), + .SUCCESS => { + current_thread.endSyscall(); + return statFromPosix(&stat); + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .CANCELED => return error.Canceled, + .INVAL => |err| return errnoBug(err), + .BADF => |err| return errnoBug(err), // File descriptor used after closed. + .NOMEM => return error.SystemResources, + .ACCES => return error.AccessDenied, + else => |err| return posix.unexpectedErrno(err), + } + }, } } } fn fileStatLinux(userdata: ?*anyopaque, file: Io.File) Io.File.StatError!Io.File.Stat { const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); const linux = std.os.linux; + + try current_thread.beginSyscall(); while (true) { - try t.checkCancel(); var statx = std.mem.zeroes(linux.Statx); const rc = linux.statx( file.handle, @@ -1514,27 +1670,38 @@ fn fileStatLinux(userdata: ?*anyopaque, file: Io.File) Io.File.StatError!Io.File &statx, ); switch (linux.errno(rc)) { - .SUCCESS => return statFromLinux(&statx), - .INTR => continue, - .CANCELED => return error.Canceled, - - .ACCES => |err| return errnoBug(err), - .BADF => |err| return errnoBug(err), // File descriptor used after closed. - .FAULT => |err| return errnoBug(err), - .INVAL => |err| return errnoBug(err), - .LOOP => |err| return errnoBug(err), - .NAMETOOLONG => |err| return errnoBug(err), - .NOENT => |err| return errnoBug(err), - .NOMEM => return error.SystemResources, - .NOTDIR => |err| return errnoBug(err), - else => |err| return posix.unexpectedErrno(err), + .SUCCESS => { + current_thread.endSyscall(); + return statFromLinux(&statx); + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .CANCELED => return error.Canceled, + .ACCES => |err| return errnoBug(err), + .BADF => |err| return errnoBug(err), // File descriptor used after closed. + .FAULT => |err| return errnoBug(err), + .INVAL => |err| return errnoBug(err), + .LOOP => |err| return errnoBug(err), + .NAMETOOLONG => |err| return errnoBug(err), + .NOENT => |err| return errnoBug(err), + .NOMEM => return error.SystemResources, + .NOTDIR => |err| return errnoBug(err), + else => |err| return posix.unexpectedErrno(err), + } + }, } } } fn fileStatWindows(userdata: ?*anyopaque, file: Io.File) Io.File.StatError!Io.File.Stat { const t: *Threaded = @ptrCast(@alignCast(userdata)); - try t.checkCancel(); + const current_thread = Thread.getCurrent(t); + try current_thread.checkCancel(); var io_status_block: windows.IO_STATUS_BLOCK = undefined; var info: windows.FILE_ALL_INFORMATION = undefined; @@ -1581,21 +1748,34 @@ fn fileStatWindows(userdata: ?*anyopaque, file: Io.File) Io.File.StatError!Io.Fi fn fileStatWasi(userdata: ?*anyopaque, file: Io.File) Io.File.StatError!Io.File.Stat { if (builtin.link_libc) return fileStatPosix(userdata, file); + const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); + + try current_thread.beginSyscall(); while (true) { - try t.checkCancel(); var stat: std.os.wasi.filestat_t = undefined; switch (std.os.wasi.fd_filestat_get(file.handle, &stat)) { - .SUCCESS => return statFromWasi(&stat), - .INTR => continue, - .CANCELED => return error.Canceled, - - .INVAL => |err| return errnoBug(err), - .BADF => |err| return errnoBug(err), // File descriptor used after closed. - .NOMEM => return error.SystemResources, - .ACCES => return error.AccessDenied, - .NOTCAPABLE => return error.AccessDenied, - else => |err| return posix.unexpectedErrno(err), + .SUCCESS => { + current_thread.endSyscall(); + return statFromWasi(&stat); + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .CANCELED => return error.Canceled, + .INVAL => |err| return errnoBug(err), + .BADF => |err| return errnoBug(err), // File descriptor used after closed. + .NOMEM => return error.SystemResources, + .ACCES => return error.AccessDenied, + .NOTCAPABLE => return error.AccessDenied, + else => |err| return posix.unexpectedErrno(err), + } + }, } } } @@ -1613,6 +1793,7 @@ fn dirAccessPosix( options: Io.Dir.AccessOptions, ) Io.Dir.AccessError!void { const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); var path_buffer: [posix.PATH_MAX]u8 = undefined; const sub_path_posix = try pathToPosix(sub_path, &path_buffer); @@ -1624,27 +1805,37 @@ fn dirAccessPosix( @as(u32, if (options.write) posix.W_OK else 0) | @as(u32, if (options.execute) posix.X_OK else 0); + try current_thread.beginSyscall(); while (true) { - try t.checkCancel(); switch (posix.errno(posix.system.faccessat(dir.handle, sub_path_posix, mode, flags))) { - .SUCCESS => return, - .INTR => continue, - .CANCELED => return error.Canceled, - - .ACCES => return error.AccessDenied, - .PERM => return error.PermissionDenied, - .ROFS => return error.ReadOnlyFileSystem, - .LOOP => return error.SymLinkLoop, - .TXTBSY => return error.FileBusy, - .NOTDIR => return error.FileNotFound, - .NOENT => return error.FileNotFound, - .NAMETOOLONG => return error.NameTooLong, - .INVAL => |err| return errnoBug(err), - .FAULT => |err| return errnoBug(err), - .IO => return error.InputOutput, - .NOMEM => return error.SystemResources, - .ILSEQ => return error.BadPathName, - else => |err| return posix.unexpectedErrno(err), + .SUCCESS => { + current_thread.endSyscall(); + return; + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .CANCELED => return error.Canceled, + .ACCES => return error.AccessDenied, + .PERM => return error.PermissionDenied, + .ROFS => return error.ReadOnlyFileSystem, + .LOOP => return error.SymLinkLoop, + .TXTBSY => return error.FileBusy, + .NOTDIR => return error.FileNotFound, + .NOENT => return error.FileNotFound, + .NAMETOOLONG => return error.NameTooLong, + .INVAL => |err| return errnoBug(err), + .FAULT => |err| return errnoBug(err), + .IO => return error.InputOutput, + .NOMEM => return error.SystemResources, + .ILSEQ => return error.BadPathName, + else => |err| return posix.unexpectedErrno(err), + } + }, } } } @@ -1657,29 +1848,41 @@ fn dirAccessWasi( ) Io.Dir.AccessError!void { if (builtin.link_libc) return dirAccessPosix(userdata, dir, sub_path, options); const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); const wasi = std.os.wasi; const flags: wasi.lookupflags_t = .{ .SYMLINK_FOLLOW = options.follow_symlinks, }; var stat: wasi.filestat_t = undefined; + + try current_thread.beginSyscall(); while (true) { - try t.checkCancel(); switch (wasi.path_filestat_get(dir.handle, flags, sub_path.ptr, sub_path.len, &stat)) { - .SUCCESS => break, - .INTR => continue, - .CANCELED => return error.Canceled, - - .INVAL => |err| return errnoBug(err), - .BADF => |err| return errnoBug(err), // File descriptor used after closed. - .NOMEM => return error.SystemResources, - .ACCES => return error.AccessDenied, - .FAULT => |err| return errnoBug(err), - .NAMETOOLONG => return error.NameTooLong, - .NOENT => return error.FileNotFound, - .NOTDIR => return error.FileNotFound, - .NOTCAPABLE => return error.AccessDenied, - .ILSEQ => return error.BadPathName, - else => |err| return posix.unexpectedErrno(err), + .SUCCESS => { + current_thread.endSyscall(); + break; + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .CANCELED => return error.Canceled, + .INVAL => |err| return errnoBug(err), + .BADF => |err| return errnoBug(err), // File descriptor used after closed. + .NOMEM => return error.SystemResources, + .ACCES => return error.AccessDenied, + .FAULT => |err| return errnoBug(err), + .NAMETOOLONG => return error.NameTooLong, + .NOENT => return error.FileNotFound, + .NOTDIR => return error.FileNotFound, + .NOTCAPABLE => return error.AccessDenied, + .ILSEQ => return error.BadPathName, + else => |err| return posix.unexpectedErrno(err), + } + }, } } @@ -1717,7 +1920,8 @@ fn dirAccessWindows( options: Io.Dir.AccessOptions, ) Io.Dir.AccessError!void { const t: *Threaded = @ptrCast(@alignCast(userdata)); - try t.checkCancel(); + const current_thread = Thread.getCurrent(t); + try current_thread.checkCancel(); _ = options; // TODO @@ -1768,6 +1972,7 @@ fn dirCreateFilePosix( flags: Io.File.CreateFlags, ) Io.File.OpenError!Io.File { const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); var path_buffer: [posix.PATH_MAX]u8 = undefined; const sub_path_posix = try pathToPosix(sub_path, &path_buffer); @@ -1796,40 +2001,50 @@ fn dirCreateFilePosix( }, }; + try current_thread.beginSyscall(); const fd: posix.fd_t = while (true) { - try t.checkCancel(); const rc = openat_sym(dir.handle, sub_path_posix, os_flags, flags.mode); switch (posix.errno(rc)) { - .SUCCESS => break @intCast(rc), - .INTR => continue, - .CANCELED => return error.Canceled, - - .FAULT => |err| return errnoBug(err), - .INVAL => return error.BadPathName, - .BADF => |err| return errnoBug(err), // File descriptor used after closed. - .ACCES => return error.AccessDenied, - .FBIG => return error.FileTooBig, - .OVERFLOW => return error.FileTooBig, - .ISDIR => return error.IsDir, - .LOOP => return error.SymLinkLoop, - .MFILE => return error.ProcessFdQuotaExceeded, - .NAMETOOLONG => return error.NameTooLong, - .NFILE => return error.SystemFdQuotaExceeded, - .NODEV => return error.NoDevice, - .NOENT => return error.FileNotFound, - .SRCH => return error.ProcessNotFound, - .NOMEM => return error.SystemResources, - .NOSPC => return error.NoSpaceLeft, - .NOTDIR => return error.NotDir, - .PERM => return error.PermissionDenied, - .EXIST => return error.PathAlreadyExists, - .BUSY => return error.DeviceBusy, - .OPNOTSUPP => return error.FileLocksNotSupported, - .AGAIN => return error.WouldBlock, - .TXTBSY => return error.FileBusy, - .NXIO => return error.NoDevice, - .ILSEQ => return error.BadPathName, - else => |err| return posix.unexpectedErrno(err), + .SUCCESS => { + current_thread.endSyscall(); + break @intCast(rc); + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .CANCELED => return error.Canceled, + .FAULT => |err| return errnoBug(err), + .INVAL => return error.BadPathName, + .BADF => |err| return errnoBug(err), // File descriptor used after closed. + .ACCES => return error.AccessDenied, + .FBIG => return error.FileTooBig, + .OVERFLOW => return error.FileTooBig, + .ISDIR => return error.IsDir, + .LOOP => return error.SymLinkLoop, + .MFILE => return error.ProcessFdQuotaExceeded, + .NAMETOOLONG => return error.NameTooLong, + .NFILE => return error.SystemFdQuotaExceeded, + .NODEV => return error.NoDevice, + .NOENT => return error.FileNotFound, + .SRCH => return error.ProcessNotFound, + .NOMEM => return error.SystemResources, + .NOSPC => return error.NoSpaceLeft, + .NOTDIR => return error.NotDir, + .PERM => return error.PermissionDenied, + .EXIST => return error.PathAlreadyExists, + .BUSY => return error.DeviceBusy, + .OPNOTSUPP => return error.FileLocksNotSupported, + .AGAIN => return error.WouldBlock, + .TXTBSY => return error.FileBusy, + .NXIO => return error.NoDevice, + .ILSEQ => return error.BadPathName, + else => |err| return posix.unexpectedErrno(err), + } + }, } }; errdefer posix.close(fd); @@ -1841,42 +2056,71 @@ fn dirCreateFilePosix( .shared => posix.LOCK.SH | lock_nonblocking, .exclusive => posix.LOCK.EX | lock_nonblocking, }; + + try current_thread.beginSyscall(); while (true) { - try t.checkCancel(); switch (posix.errno(posix.system.flock(fd, lock_flags))) { - .SUCCESS => break, - .INTR => continue, - .CANCELED => return error.Canceled, - - .BADF => |err| return errnoBug(err), // File descriptor used after closed. - .INVAL => |err| return errnoBug(err), // invalid parameters - .NOLCK => return error.SystemResources, - .AGAIN => return error.WouldBlock, - .OPNOTSUPP => return error.FileLocksNotSupported, - else => |err| return posix.unexpectedErrno(err), - } + .SUCCESS => { + current_thread.endSyscall(); + break; + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .CANCELED => return error.Canceled, + .BADF => |err| return errnoBug(err), // File descriptor used after closed. + .INVAL => |err| return errnoBug(err), // invalid parameters + .NOLCK => return error.SystemResources, + .AGAIN => return error.WouldBlock, + .OPNOTSUPP => return error.FileLocksNotSupported, + else => |err| return posix.unexpectedErrno(err), + } + }, + } } } if (have_flock_open_flags and flags.lock_nonblocking) { + try current_thread.beginSyscall(); var fl_flags: usize = while (true) { - try t.checkCancel(); const rc = posix.system.fcntl(fd, posix.F.GETFL, @as(usize, 0)); switch (posix.errno(rc)) { - .SUCCESS => break @intCast(rc), - .INTR => continue, - .CANCELED => return error.Canceled, - else => |err| return posix.unexpectedErrno(err), + .SUCCESS => { + current_thread.endSyscall(); + break @intCast(rc); + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + else => |err| { + current_thread.endSyscall(); + return posix.unexpectedErrno(err); + }, } }; + fl_flags |= @as(usize, 1 << @bitOffsetOf(posix.O, "NONBLOCK")); + + try current_thread.beginSyscall(); while (true) { - try t.checkCancel(); switch (posix.errno(posix.system.fcntl(fd, posix.F.SETFL, fl_flags))) { - .SUCCESS => break, - .INTR => continue, - .CANCELED => return error.Canceled, - else => |err| return posix.unexpectedErrno(err), + .SUCCESS => { + current_thread.endSyscall(); + break; + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + else => |err| { + current_thread.endSyscall(); + return posix.unexpectedErrno(err); + }, } } } @@ -1892,7 +2136,8 @@ fn dirCreateFileWindows( ) Io.File.OpenError!Io.File { const w = windows; const t: *Threaded = @ptrCast(@alignCast(userdata)); - try t.checkCancel(); + const current_thread = Thread.getCurrent(t); + try current_thread.checkCancel(); const sub_path_w_array = try w.sliceToPrefixedFileW(dir.handle, sub_path); const sub_path_w = sub_path_w_array.span(); @@ -1939,6 +2184,7 @@ fn dirCreateFileWasi( flags: Io.File.CreateFlags, ) Io.File.OpenError!Io.File { const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); const wasi = std.os.wasi; const lookup_flags: wasi.lookupflags_t = .{}; const oflags: wasi.oflags_t = .{ @@ -1966,35 +2212,45 @@ fn dirCreateFileWasi( }; const inheriting: wasi.rights_t = .{}; var fd: posix.fd_t = undefined; + try current_thread.beginSyscall(); while (true) { - try t.checkCancel(); switch (wasi.path_open(dir.handle, lookup_flags, sub_path.ptr, sub_path.len, oflags, base, inheriting, fdflags, &fd)) { - .SUCCESS => return .{ .handle = fd }, - .INTR => continue, - .CANCELED => return error.Canceled, - - .FAULT => |err| return errnoBug(err), - .INVAL => return error.BadPathName, - .BADF => |err| return errnoBug(err), // File descriptor used after closed. - .ACCES => return error.AccessDenied, - .FBIG => return error.FileTooBig, - .OVERFLOW => return error.FileTooBig, - .ISDIR => return error.IsDir, - .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, - .NOSPC => return error.NoSpaceLeft, - .NOTDIR => return error.NotDir, - .PERM => return error.PermissionDenied, - .EXIST => return error.PathAlreadyExists, - .BUSY => return error.DeviceBusy, - .NOTCAPABLE => return error.AccessDenied, - .ILSEQ => return error.BadPathName, - else => |err| return posix.unexpectedErrno(err), + .SUCCESS => { + current_thread.endSyscall(); + return .{ .handle = fd }; + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .CANCELED => return error.Canceled, + .FAULT => |err| return errnoBug(err), + .INVAL => return error.BadPathName, + .BADF => |err| return errnoBug(err), // File descriptor used after closed. + .ACCES => return error.AccessDenied, + .FBIG => return error.FileTooBig, + .OVERFLOW => return error.FileTooBig, + .ISDIR => return error.IsDir, + .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, + .NOSPC => return error.NoSpaceLeft, + .NOTDIR => return error.NotDir, + .PERM => return error.PermissionDenied, + .EXIST => return error.PathAlreadyExists, + .BUSY => return error.DeviceBusy, + .NOTCAPABLE => return error.AccessDenied, + .ILSEQ => return error.BadPathName, + else => |err| return posix.unexpectedErrno(err), + } + }, } } } @@ -2012,6 +2268,7 @@ fn dirOpenFilePosix( flags: Io.File.OpenFlags, ) Io.File.OpenError!Io.File { const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); var path_buffer: [posix.PATH_MAX]u8 = undefined; const sub_path_posix = try pathToPosix(sub_path, &path_buffer); @@ -2048,40 +2305,50 @@ fn dirOpenFilePosix( }, }; + try current_thread.beginSyscall(); const fd: posix.fd_t = while (true) { - try t.checkCancel(); const rc = openat_sym(dir.handle, sub_path_posix, os_flags, @as(posix.mode_t, 0)); switch (posix.errno(rc)) { - .SUCCESS => break @intCast(rc), - .INTR => continue, - .CANCELED => return error.Canceled, - - .FAULT => |err| return errnoBug(err), - .INVAL => return error.BadPathName, - .BADF => |err| return errnoBug(err), // File descriptor used after closed. - .ACCES => return error.AccessDenied, - .FBIG => return error.FileTooBig, - .OVERFLOW => return error.FileTooBig, - .ISDIR => return error.IsDir, - .LOOP => return error.SymLinkLoop, - .MFILE => return error.ProcessFdQuotaExceeded, - .NAMETOOLONG => return error.NameTooLong, - .NFILE => return error.SystemFdQuotaExceeded, - .NODEV => return error.NoDevice, - .NOENT => return error.FileNotFound, - .SRCH => return error.ProcessNotFound, - .NOMEM => return error.SystemResources, - .NOSPC => return error.NoSpaceLeft, - .NOTDIR => return error.NotDir, - .PERM => return error.PermissionDenied, - .EXIST => return error.PathAlreadyExists, - .BUSY => return error.DeviceBusy, - .OPNOTSUPP => return error.FileLocksNotSupported, - .AGAIN => return error.WouldBlock, - .TXTBSY => return error.FileBusy, - .NXIO => return error.NoDevice, - .ILSEQ => return error.BadPathName, - else => |err| return posix.unexpectedErrno(err), + .SUCCESS => { + current_thread.endSyscall(); + break @intCast(rc); + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .CANCELED => return error.Canceled, + .FAULT => |err| return errnoBug(err), + .INVAL => return error.BadPathName, + .BADF => |err| return errnoBug(err), // File descriptor used after closed. + .ACCES => return error.AccessDenied, + .FBIG => return error.FileTooBig, + .OVERFLOW => return error.FileTooBig, + .ISDIR => return error.IsDir, + .LOOP => return error.SymLinkLoop, + .MFILE => return error.ProcessFdQuotaExceeded, + .NAMETOOLONG => return error.NameTooLong, + .NFILE => return error.SystemFdQuotaExceeded, + .NODEV => return error.NoDevice, + .NOENT => return error.FileNotFound, + .SRCH => return error.ProcessNotFound, + .NOMEM => return error.SystemResources, + .NOSPC => return error.NoSpaceLeft, + .NOTDIR => return error.NotDir, + .PERM => return error.PermissionDenied, + .EXIST => return error.PathAlreadyExists, + .BUSY => return error.DeviceBusy, + .OPNOTSUPP => return error.FileLocksNotSupported, + .AGAIN => return error.WouldBlock, + .TXTBSY => return error.FileBusy, + .NXIO => return error.NoDevice, + .ILSEQ => return error.BadPathName, + else => |err| return posix.unexpectedErrno(err), + } + }, } }; errdefer posix.close(fd); @@ -2093,42 +2360,70 @@ fn dirOpenFilePosix( .shared => posix.LOCK.SH | lock_nonblocking, .exclusive => posix.LOCK.EX | lock_nonblocking, }; + try current_thread.beginSyscall(); while (true) { - try t.checkCancel(); switch (posix.errno(posix.system.flock(fd, lock_flags))) { - .SUCCESS => break, - .INTR => continue, - .CANCELED => return error.Canceled, - - .BADF => |err| return errnoBug(err), // File descriptor used after closed. - .INVAL => |err| return errnoBug(err), // invalid parameters - .NOLCK => return error.SystemResources, - .AGAIN => return error.WouldBlock, - .OPNOTSUPP => return error.FileLocksNotSupported, - else => |err| return posix.unexpectedErrno(err), + .SUCCESS => { + current_thread.endSyscall(); + break; + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .CANCELED => return error.Canceled, + .BADF => |err| return errnoBug(err), // File descriptor used after closed. + .INVAL => |err| return errnoBug(err), // invalid parameters + .NOLCK => return error.SystemResources, + .AGAIN => return error.WouldBlock, + .OPNOTSUPP => return error.FileLocksNotSupported, + else => |err| return posix.unexpectedErrno(err), + } + }, } } } if (have_flock_open_flags and flags.lock_nonblocking) { + try current_thread.beginSyscall(); var fl_flags: usize = while (true) { - try t.checkCancel(); const rc = posix.system.fcntl(fd, posix.F.GETFL, @as(usize, 0)); switch (posix.errno(rc)) { - .SUCCESS => break @intCast(rc), - .INTR => continue, - .CANCELED => return error.Canceled, - else => |err| return posix.unexpectedErrno(err), + .SUCCESS => { + current_thread.endSyscall(); + break @intCast(rc); + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + else => |err| { + current_thread.endSyscall(); + return posix.unexpectedErrno(err); + }, } }; + fl_flags |= @as(usize, 1 << @bitOffsetOf(posix.O, "NONBLOCK")); + + try current_thread.beginSyscall(); while (true) { - try t.checkCancel(); switch (posix.errno(posix.system.fcntl(fd, posix.F.SETFL, fl_flags))) { - .SUCCESS => break, - .INTR => continue, - .CANCELED => return error.Canceled, - else => |err| return posix.unexpectedErrno(err), + .SUCCESS => { + current_thread.endSyscall(); + break; + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + else => |err| { + current_thread.endSyscall(); + return posix.unexpectedErrno(err); + }, } } } @@ -2158,7 +2453,7 @@ pub fn dirOpenFileWtf16( if (std.mem.eql(u16, sub_path_w, &.{'.'})) return error.IsDir; if (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 current_thread = Thread.getCurrent(t); const w = windows; var nt_name: w.UNICODE_STRING = .{ @@ -2187,7 +2482,7 @@ pub fn dirOpenFileWtf16( var attempt: u5 = 0; const handle = while (true) { - try t.checkCancel(); + try current_thread.checkCancel(); var result: w.HANDLE = undefined; const rc = w.ntdll.NtCreateFile( @@ -2281,6 +2576,7 @@ fn dirOpenFileWasi( ) Io.File.OpenError!Io.File { if (builtin.link_libc) return dirOpenFilePosix(userdata, dir, sub_path, flags); const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); const wasi = std.os.wasi; var base: std.os.wasi.rights_t = .{}; // POLL_FD_READWRITE only grants extra rights if the corresponding FD_READ and/or FD_WRITE @@ -2310,33 +2606,44 @@ fn dirOpenFileWasi( const inheriting: wasi.rights_t = .{}; const fdflags: wasi.fdflags_t = .{}; var fd: posix.fd_t = undefined; + try current_thread.beginSyscall(); while (true) { - try t.checkCancel(); switch (wasi.path_open(dir.handle, lookup_flags, sub_path.ptr, sub_path.len, oflags, base, inheriting, fdflags, &fd)) { - .SUCCESS => return .{ .handle = fd }, - .INTR => continue, - .CANCELED => return error.Canceled, - - .FAULT => |err| return errnoBug(err), - .BADF => |err| return errnoBug(err), // File descriptor used after closed. - .ACCES => return error.AccessDenied, - .FBIG => return error.FileTooBig, - .OVERFLOW => return error.FileTooBig, - .ISDIR => return error.IsDir, - .LOOP => return error.SymLinkLoop, - .MFILE => return error.ProcessFdQuotaExceeded, - .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 => return error.DeviceBusy, - .NOTCAPABLE => return error.AccessDenied, - .NAMETOOLONG => return error.NameTooLong, - .INVAL => return error.BadPathName, - .ILSEQ => return error.BadPathName, - else => |err| return posix.unexpectedErrno(err), + .SUCCESS => { + errdefer posix.close(fd); + current_thread.endSyscall(); + return .{ .handle = fd }; + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .CANCELED => return error.Canceled, + .FAULT => |err| return errnoBug(err), + .BADF => |err| return errnoBug(err), // File descriptor used after closed. + .ACCES => return error.AccessDenied, + .FBIG => return error.FileTooBig, + .OVERFLOW => return error.FileTooBig, + .ISDIR => return error.IsDir, + .LOOP => return error.SymLinkLoop, + .MFILE => return error.ProcessFdQuotaExceeded, + .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 => return error.DeviceBusy, + .NOTCAPABLE => return error.AccessDenied, + .NAMETOOLONG => return error.NameTooLong, + .INVAL => return error.BadPathName, + .ILSEQ => return error.BadPathName, + else => |err| return posix.unexpectedErrno(err), + } + }, } } } @@ -2361,6 +2668,8 @@ fn dirOpenDirPosix( return dirOpenDirWindows(t, dir, sub_path_w.span(), options); } + const current_thread = Thread.getCurrent(t); + var path_buffer: [posix.PATH_MAX]u8 = undefined; const sub_path_posix = try pathToPosix(sub_path, &path_buffer); @@ -2381,31 +2690,41 @@ fn dirOpenDirPosix( if (@hasField(posix.O, "PATH") and !options.iterate) flags.PATH = true; + try current_thread.beginSyscall(); while (true) { - try t.checkCancel(); const rc = openat_sym(dir.handle, sub_path_posix, flags, @as(usize, 0)); switch (posix.errno(rc)) { - .SUCCESS => return .{ .handle = @intCast(rc) }, - .INTR => continue, - .CANCELED => return error.Canceled, - - .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 => return error.DeviceBusy, - .NXIO => return error.NoDevice, - .ILSEQ => return error.BadPathName, - else => |err| return posix.unexpectedErrno(err), + .SUCCESS => { + current_thread.endSyscall(); + return .{ .handle = @intCast(rc) }; + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .CANCELED => return error.Canceled, + .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 => return error.DeviceBusy, + .NXIO => return error.NoDevice, + .ILSEQ => return error.BadPathName, + else => |err| return posix.unexpectedErrno(err), + } + }, } } } @@ -2417,34 +2736,46 @@ fn dirOpenDirHaiku( options: Io.Dir.OpenOptions, ) Io.Dir.OpenError!Io.Dir { const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); var path_buffer: [posix.PATH_MAX]u8 = undefined; const sub_path_posix = try pathToPosix(sub_path, &path_buffer); _ = options; + try current_thread.beginSyscall(); while (true) { - try t.checkCancel(); const rc = posix.system._kern_open_dir(dir.handle, sub_path_posix); - if (rc >= 0) return .{ .handle = rc }; + if (rc >= 0) { + current_thread.endSyscall(); + return .{ .handle = rc }; + } switch (@as(posix.E, @enumFromInt(rc))) { - .INTR => continue, - .CANCELED => return error.Canceled, - .FAULT => |err| return errnoBug(err), - .INVAL => |err| return errnoBug(err), - .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 => return error.DeviceBusy, - else => |err| return posix.unexpectedErrno(err), + .INTR => { + try current_thread.checkCancel(); + continue; + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .CANCELED => return error.Canceled, + .FAULT => |err| return errnoBug(err), + .INVAL => |err| return errnoBug(err), + .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 => return error.DeviceBusy, + else => |err| return posix.unexpectedErrno(err), + } + }, } } } @@ -2455,6 +2786,7 @@ pub fn dirOpenDirWindows( sub_path_w: [:0]const u16, options: Io.Dir.OpenOptions, ) Io.Dir.OpenError!Io.Dir { + const current_thread = Thread.getCurrent(t); const w = windows; // TODO remove some of these flags if options.access_sub_paths is false const base_flags = w.STANDARD_RIGHTS_READ | w.FILE_READ_ATTRIBUTES | w.FILE_READ_EA | @@ -2478,7 +2810,7 @@ pub fn dirOpenDirWindows( const open_reparse_point: w.DWORD = if (!options.follow_symlinks) w.FILE_OPEN_REPARSE_POINT else 0x0; var io_status_block: w.IO_STATUS_BLOCK = undefined; var result: Io.Dir = .{ .handle = undefined }; - try t.checkCancel(); + try current_thread.checkCancel(); const rc = w.ntdll.NtCreateFile( &result.handle, access_mask, @@ -2527,6 +2859,7 @@ fn dirOpenDirWasi( ) Io.Dir.OpenError!Io.Dir { if (builtin.link_libc) return dirOpenDirPosix(userdata, dir, sub_path, options); const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); const wasi = std.os.wasi; var base: std.os.wasi.rights_t = .{ @@ -2556,31 +2889,40 @@ fn dirOpenDirWasi( const oflags: wasi.oflags_t = .{ .DIRECTORY = true }; const fdflags: wasi.fdflags_t = .{}; var fd: posix.fd_t = undefined; - + try current_thread.beginSyscall(); while (true) { - try t.checkCancel(); switch (wasi.path_open(dir.handle, lookup_flags, sub_path.ptr, sub_path.len, oflags, base, base, fdflags, &fd)) { - .SUCCESS => return .{ .handle = fd }, - .INTR => continue, - .CANCELED => return error.Canceled, - - .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 => return error.DeviceBusy, - .NOTCAPABLE => return error.AccessDenied, - .ILSEQ => return error.BadPathName, - else => |err| return posix.unexpectedErrno(err), + .SUCCESS => { + current_thread.endSyscall(); + return .{ .handle = fd }; + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .CANCELED => return error.Canceled, + .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 => return error.DeviceBusy, + .NOTCAPABLE => return error.AccessDenied, + .ILSEQ => return error.BadPathName, + else => |err| return posix.unexpectedErrno(err), + } + }, } } } @@ -2598,6 +2940,7 @@ const fileReadStreaming = switch (native_os) { fn fileReadStreamingPosix(userdata: ?*anyopaque, file: Io.File, data: [][]u8) Io.File.Reader.Error!usize { const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); var iovecs_buffer: [max_iovecs_len]posix.iovec = undefined; var i: usize = 0; @@ -2611,59 +2954,82 @@ fn fileReadStreamingPosix(userdata: ?*anyopaque, file: Io.File, data: [][]u8) Io const dest = iovecs_buffer[0..i]; assert(dest[0].len > 0); - if (native_os == .wasi and !builtin.link_libc) while (true) { - try t.checkCancel(); - var nread: usize = undefined; - switch (std.os.wasi.fd_read(file.handle, dest.ptr, dest.len, &nread)) { - .SUCCESS => return nread, - .INTR => continue, - .CANCELED => return error.Canceled, - - .INVAL => |err| return errnoBug(err), - .FAULT => |err| return errnoBug(err), - .BADF => return error.NotOpenForReading, // 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, - .TIMEDOUT => return error.Timeout, - .NOTCAPABLE => return error.AccessDenied, - else => |err| return posix.unexpectedErrno(err), + if (native_os == .wasi and !builtin.link_libc) { + try current_thread.beginSyscall(); + while (true) { + var nread: usize = undefined; + switch (std.os.wasi.fd_read(file.handle, dest.ptr, dest.len, &nread)) { + .SUCCESS => { + current_thread.endSyscall(); + return nread; + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .CANCELED => return error.Canceled, + .INVAL => |err| return errnoBug(err), + .FAULT => |err| return errnoBug(err), + .BADF => return error.NotOpenForReading, // 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, + .TIMEDOUT => return error.Timeout, + .NOTCAPABLE => return error.AccessDenied, + else => |err| return posix.unexpectedErrno(err), + } + }, + } } - }; + } + try current_thread.beginSyscall(); while (true) { - try t.checkCancel(); const rc = posix.system.readv(file.handle, dest.ptr, @intCast(dest.len)); switch (posix.errno(rc)) { - .SUCCESS => return @intCast(rc), - .INTR => continue, - .CANCELED => return error.Canceled, - - .INVAL => |err| return errnoBug(err), - .FAULT => |err| return errnoBug(err), - .SRCH => return error.ProcessNotFound, - .AGAIN => return error.WouldBlock, - .BADF => |err| { - if (native_os == .wasi) return error.NotOpenForReading; // File operation on directory. - return errnoBug(err); // File descriptor used after closed. - }, - .IO => return error.InputOutput, - .ISDIR => return error.IsDir, - .NOBUFS => return error.SystemResources, - .NOMEM => return error.SystemResources, - .NOTCONN => return error.SocketUnconnected, - .CONNRESET => return error.ConnectionResetByPeer, - .TIMEDOUT => return error.Timeout, - else => |err| return posix.unexpectedErrno(err), + .SUCCESS => { + current_thread.endSyscall(); + return @intCast(rc); + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .CANCELED => return error.Canceled, + .INVAL => |err| return errnoBug(err), + .FAULT => |err| return errnoBug(err), + .SRCH => return error.ProcessNotFound, + .AGAIN => return error.WouldBlock, + .BADF => |err| { + if (native_os == .wasi) return error.NotOpenForReading; // File operation on directory. + return errnoBug(err); // File descriptor used after closed. + }, + .IO => return error.InputOutput, + .ISDIR => return error.IsDir, + .NOBUFS => return error.SystemResources, + .NOMEM => return error.SystemResources, + .NOTCONN => return error.SocketUnconnected, + .CONNRESET => return error.ConnectionResetByPeer, + .TIMEDOUT => return error.Timeout, + else => |err| return posix.unexpectedErrno(err), + } + }, } } } fn fileReadStreamingWindows(userdata: ?*anyopaque, file: Io.File, data: [][]u8) Io.File.Reader.Error!usize { const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); const DWORD = windows.DWORD; var index: usize = 0; @@ -2672,7 +3038,7 @@ fn fileReadStreamingWindows(userdata: ?*anyopaque, file: Io.File, data: [][]u8) const want_read_count: DWORD = @min(std.math.maxInt(DWORD), buffer.len); while (true) { - try t.checkCancel(); + try current_thread.checkCancel(); var n: DWORD = undefined; if (windows.kernel32.ReadFile(file.handle, buffer.ptr, want_read_count, &n, null) != 0) return n; @@ -2692,6 +3058,7 @@ fn fileReadStreamingWindows(userdata: ?*anyopaque, file: Io.File, data: [][]u8) fn fileReadPositionalPosix(userdata: ?*anyopaque, file: Io.File, data: [][]u8, offset: u64) Io.File.ReadPositionalError!usize { const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); if (!have_preadv) @compileError("TODO"); @@ -2707,60 +3074,82 @@ fn fileReadPositionalPosix(userdata: ?*anyopaque, file: Io.File, data: [][]u8, o const dest = iovecs_buffer[0..i]; assert(dest[0].len > 0); - if (native_os == .wasi and !builtin.link_libc) while (true) { - try t.checkCancel(); - var nread: usize = undefined; - switch (std.os.wasi.fd_pread(file.handle, dest.ptr, dest.len, offset, &nread)) { - .SUCCESS => return nread, - .INTR => continue, - .CANCELED => return error.Canceled, - - .INVAL => |err| return errnoBug(err), - .FAULT => |err| return errnoBug(err), - .AGAIN => |err| return errnoBug(err), - .BADF => return error.NotOpenForReading, // 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, - .TIMEDOUT => return error.Timeout, - .NXIO => return error.Unseekable, - .SPIPE => return error.Unseekable, - .OVERFLOW => return error.Unseekable, - .NOTCAPABLE => return error.AccessDenied, - else => |err| return posix.unexpectedErrno(err), + if (native_os == .wasi and !builtin.link_libc) { + try current_thread.beginSyscall(); + while (true) { + var nread: usize = undefined; + switch (std.os.wasi.fd_pread(file.handle, dest.ptr, dest.len, offset, &nread)) { + .SUCCESS => { + current_thread.endSyscall(); + return nread; + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .CANCELED => return error.Canceled, + .INVAL => |err| return errnoBug(err), + .FAULT => |err| return errnoBug(err), + .AGAIN => |err| return errnoBug(err), + .BADF => return error.NotOpenForReading, // 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, + .TIMEDOUT => return error.Timeout, + .NXIO => return error.Unseekable, + .SPIPE => return error.Unseekable, + .OVERFLOW => return error.Unseekable, + .NOTCAPABLE => return error.AccessDenied, + else => |err| return posix.unexpectedErrno(err), + } + }, + } } - }; + } + try current_thread.beginSyscall(); while (true) { - try t.checkCancel(); const rc = preadv_sym(file.handle, dest.ptr, @intCast(dest.len), @bitCast(offset)); switch (posix.errno(rc)) { - .SUCCESS => return @bitCast(rc), - .INTR => continue, - .CANCELED => return error.Canceled, - - .INVAL => |err| return errnoBug(err), - .FAULT => |err| return errnoBug(err), - .SRCH => return error.ProcessNotFound, - .AGAIN => return error.WouldBlock, - .BADF => |err| { - if (native_os == .wasi) return error.NotOpenForReading; // File operation on directory. - return errnoBug(err); // File descriptor used after closed. - }, - .IO => return error.InputOutput, - .ISDIR => return error.IsDir, - .NOBUFS => return error.SystemResources, - .NOMEM => return error.SystemResources, - .NOTCONN => return error.SocketUnconnected, - .CONNRESET => return error.ConnectionResetByPeer, - .TIMEDOUT => return error.Timeout, - .NXIO => return error.Unseekable, - .SPIPE => return error.Unseekable, - .OVERFLOW => return error.Unseekable, - else => |err| return posix.unexpectedErrno(err), + .SUCCESS => { + current_thread.endSyscall(); + return @bitCast(rc); + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .CANCELED => return error.Canceled, + .INVAL => |err| return errnoBug(err), + .FAULT => |err| return errnoBug(err), + .SRCH => return error.ProcessNotFound, + .AGAIN => return error.WouldBlock, + .BADF => |err| { + if (native_os == .wasi) return error.NotOpenForReading; // File operation on directory. + return errnoBug(err); // File descriptor used after closed. + }, + .IO => return error.InputOutput, + .ISDIR => return error.IsDir, + .NOBUFS => return error.SystemResources, + .NOMEM => return error.SystemResources, + .NOTCONN => return error.SocketUnconnected, + .CONNRESET => return error.ConnectionResetByPeer, + .TIMEDOUT => return error.Timeout, + .NXIO => return error.Unseekable, + .SPIPE => return error.Unseekable, + .OVERFLOW => return error.Unseekable, + else => |err| return posix.unexpectedErrno(err), + } + }, } } } @@ -2772,6 +3161,7 @@ const fileReadPositional = switch (native_os) { fn fileReadPositionalWindows(userdata: ?*anyopaque, file: Io.File, data: [][]u8, offset: u64) Io.File.ReadPositionalError!usize { const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); const DWORD = windows.DWORD; @@ -2793,7 +3183,7 @@ fn fileReadPositionalWindows(userdata: ?*anyopaque, file: Io.File, data: [][]u8, }; while (true) { - try t.checkCancel(); + try current_thread.checkCancel(); var n: DWORD = undefined; if (windows.kernel32.ReadFile(file.handle, buffer.ptr, want_read_count, &n, &overlapped) != 0) return n; @@ -2813,8 +3203,7 @@ fn fileReadPositionalWindows(userdata: ?*anyopaque, file: Io.File, data: [][]u8, fn fileSeekBy(userdata: ?*anyopaque, file: Io.File, offset: i64) Io.File.SeekError!void { const t: *Threaded = @ptrCast(@alignCast(userdata)); - try t.checkCancel(); - + _ = t; _ = file; _ = offset; @panic("TODO implement fileSeekBy"); @@ -2822,63 +3211,96 @@ fn fileSeekBy(userdata: ?*anyopaque, file: Io.File, offset: i64) Io.File.SeekErr fn fileSeekTo(userdata: ?*anyopaque, file: Io.File, offset: u64) Io.File.SeekError!void { const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); const fd = file.handle; - if (native_os == .linux and !builtin.link_libc and @sizeOf(usize) == 4) while (true) { - try t.checkCancel(); - var result: u64 = undefined; - switch (posix.errno(posix.system.llseek(fd, offset, &result, posix.SEEK.SET))) { - .SUCCESS => return, - .INTR => continue, - .CANCELED => return error.Canceled, - - .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 (native_os == .linux and !builtin.link_libc and @sizeOf(usize) == 4) { + try current_thread.beginSyscall(); + while (true) { + var result: u64 = undefined; + switch (posix.errno(posix.system.llseek(fd, offset, &result, posix.SEEK.SET))) { + .SUCCESS => { + current_thread.endSyscall(); + return; + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .CANCELED => return error.Canceled, + .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 (native_os == .windows) { - try t.checkCancel(); + try current_thread.checkCancel(); return windows.SetFilePointerEx_BEGIN(fd, offset); } if (native_os == .wasi and !builtin.link_libc) while (true) { - try t.checkCancel(); var new_offset: std.os.wasi.filesize_t = undefined; + try current_thread.beginSyscall(); switch (std.os.wasi.fd_seek(fd, @bitCast(offset), .SET, &new_offset)) { - .SUCCESS => return, - .INTR => continue, - .CANCELED => return error.Canceled, - - .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, - .NOTCAPABLE => return error.AccessDenied, - else => |err| return posix.unexpectedErrno(err), + .SUCCESS => { + current_thread.endSyscall(); + return; + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .CANCELED => return error.Canceled, + .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, + .NOTCAPABLE => return error.AccessDenied, + else => |err| return posix.unexpectedErrno(err), + } + }, } }; if (posix.SEEK == void) return error.Unseekable; + try current_thread.beginSyscall(); while (true) { - try t.checkCancel(); switch (posix.errno(lseek_sym(fd, @bitCast(offset), posix.SEEK.SET))) { - .SUCCESS => return, - .INTR => continue, - .CANCELED => return error.Canceled, - - .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), + .SUCCESS => { + current_thread.endSyscall(); + return; + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .CANCELED => return error.Canceled, + .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), + } + }, } } } @@ -2907,8 +3329,8 @@ fn fileWritePositional( offset: u64, ) Io.File.WritePositionalError!usize { const t: *Threaded = @ptrCast(@alignCast(userdata)); + _ = t; while (true) { - try t.checkCancel(); _ = file; _ = buffer; _ = offset; @@ -2918,8 +3340,8 @@ fn fileWritePositional( fn fileWriteStreaming(userdata: ?*anyopaque, file: Io.File, buffer: [][]const u8) Io.File.WriteStreamingError!usize { const t: *Threaded = @ptrCast(@alignCast(userdata)); + _ = t; while (true) { - try t.checkCancel(); _ = file; _ = buffer; @panic("TODO implement fileWriteStreaming"); @@ -2997,6 +3419,7 @@ const sleep = switch (native_os) { fn sleepLinux(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void { const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); const clock_id: posix.clockid_t = clockToPosix(switch (timeout) { .none => .awake, .duration => |d| d.clock, @@ -3008,25 +3431,37 @@ fn sleepLinux(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void { .deadline => |deadline| deadline.raw.nanoseconds, }; var timespec: posix.timespec = timestampToPosix(deadline_nanoseconds); + try current_thread.beginSyscall(); while (true) { - try t.checkCancel(); switch (std.os.linux.errno(std.os.linux.clock_nanosleep(clock_id, .{ .ABSTIME = switch (timeout) { .none, .duration => false, .deadline => true, } }, ×pec, ×pec))) { - .SUCCESS => return, - .INTR => continue, - .CANCELED => return error.Canceled, - .INVAL => return error.UnsupportedClock, - else => |err| return posix.unexpectedErrno(err), + .SUCCESS => { + current_thread.endSyscall(); + return; + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .CANCELED => return error.Canceled, + .INVAL => return error.UnsupportedClock, + else => |err| return posix.unexpectedErrno(err), + } + }, } } } fn sleepWindows(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void { const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); const t_io = ioBasic(t); - try t.checkCancel(); + try current_thread.checkCancel(); const ms = ms: { const d = (try timeout.toDurationFromNow(t_io)) orelse break :ms std.math.maxInt(windows.DWORD); @@ -3038,9 +3473,8 @@ fn sleepWindows(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void { fn sleepWasi(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void { const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); const t_io = ioBasic(t); - try t.checkCancel(); - const w = std.os.wasi; const clock: w.subscription_clock_t = if (try timeout.toDurationFromNow(t_io)) |d| .{ @@ -3063,11 +3497,14 @@ fn sleepWasi(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void { }; var event: w.event_t = undefined; var nevents: usize = undefined; + try current_thread.beginSyscall(); _ = w.poll_oneoff(&in, &event, 1, &nevents); + current_thread.endSyscall(); } fn sleepPosix(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void { const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); 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; @@ -3079,12 +3516,18 @@ fn sleepPosix(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void { }; break :t timestampToPosix(d.raw.toNanoseconds()); }; + try current_thread.beginSyscall(); while (true) { - try t.checkCancel(); switch (posix.errno(posix.system.nanosleep(×pec, ×pec))) { - .INTR => continue, - .CANCELED => return error.Canceled, - else => return, // This prong handles success as well as unexpected errors. + .INTR => { + try current_thread.checkCancel(); + continue; + }, + else => { + // This prong handles success as well as unexpected errors. + current_thread.endSyscall(); + return; + }, } } } @@ -3127,34 +3570,48 @@ fn netListenIpPosix( ) IpAddress.ListenError!net.Server { if (!have_networking) return error.NetworkDown; const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); const family = posixAddressFamily(&address); - const socket_fd = try openSocketPosix(t, family, .{ + const socket_fd = try openSocketPosix(current_thread, family, .{ .mode = options.mode, .protocol = options.protocol, }); errdefer posix.close(socket_fd); if (options.reuse_address) { - try setSocketOption(t, socket_fd, posix.SOL.SOCKET, posix.SO.REUSEADDR, 1); + try setSocketOption(current_thread, socket_fd, posix.SOL.SOCKET, posix.SO.REUSEADDR, 1); if (@hasDecl(posix.SO, "REUSEPORT")) - try setSocketOption(t, socket_fd, posix.SOL.SOCKET, posix.SO.REUSEPORT, 1); + try setSocketOption(current_thread, socket_fd, posix.SOL.SOCKET, posix.SO.REUSEPORT, 1); } var storage: PosixAddress = undefined; var addr_len = addressToPosix(&address, &storage); - try posixBind(t, socket_fd, &storage.any, addr_len); + try posixBind(current_thread, socket_fd, &storage.any, addr_len); + try current_thread.beginSyscall(); while (true) { - try t.checkCancel(); switch (posix.errno(posix.system.listen(socket_fd, options.kernel_backlog))) { - .SUCCESS => break, - .ADDRINUSE => return error.AddressInUse, - .BADF => |err| return errnoBug(err), // File descriptor used after closed. - else => |err| return posix.unexpectedErrno(err), + .SUCCESS => { + current_thread.endSyscall(); + break; + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .CANCELED => return error.Canceled, + .ADDRINUSE => return error.AddressInUse, + .BADF => |err| return errnoBug(err), // File descriptor used after closed. + else => |err| return posix.unexpectedErrno(err), + } + }, } } - try posixGetSockName(t, socket_fd, &storage.any, &addr_len); + try posixGetSockName(current_thread, socket_fd, &storage.any, &addr_len); return .{ .socket = .{ .handle = socket_fd, @@ -3170,8 +3627,9 @@ fn netListenIpWindows( ) IpAddress.ListenError!net.Server { if (!have_networking) return error.NetworkDown; const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); const family = posixAddressFamily(&address); - const socket_handle = try openSocketWsa(t, family, .{ + const socket_handle = try openSocketWsa(t, current_thread, family, .{ .mode = options.mode, .protocol = options.protocol, }); @@ -3183,52 +3641,73 @@ fn netListenIpWindows( var storage: WsaAddress = undefined; var addr_len = addressToWsa(&address, &storage); + try current_thread.beginSyscall(); while (true) { - try t.checkCancel(); const rc = ws2_32.bind(socket_handle, &storage.any, addr_len); if (rc != ws2_32.SOCKET_ERROR) break; switch (ws2_32.WSAGetLastError()) { - .EINTR => continue, - .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled, + .EINTR => { + try current_thread.checkCancel(); + continue; + }, .NOTINITIALISED => { try initializeWsa(t); + try current_thread.checkCancel(); continue; }, - .EADDRINUSE => return error.AddressInUse, - .EADDRNOTAVAIL => return error.AddressUnavailable, - .ENOTSOCK => |err| return wsaErrorBug(err), - .EFAULT => |err| return wsaErrorBug(err), - .EINVAL => |err| return wsaErrorBug(err), - .ENOBUFS => return error.SystemResources, - .ENETDOWN => return error.NetworkDown, - else => |err| return windows.unexpectedWSAError(err), + else => |e| { + current_thread.endSyscall(); + switch (e) { + .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled, + .EADDRINUSE => return error.AddressInUse, + .EADDRNOTAVAIL => return error.AddressUnavailable, + .ENOTSOCK => |err| return wsaErrorBug(err), + .EFAULT => |err| return wsaErrorBug(err), + .EINVAL => |err| return wsaErrorBug(err), + .ENOBUFS => return error.SystemResources, + .ENETDOWN => return error.NetworkDown, + else => |err| return windows.unexpectedWSAError(err), + } + }, } } + try current_thread.checkCancel(); while (true) { - try t.checkCancel(); const rc = ws2_32.listen(socket_handle, options.kernel_backlog); - if (rc != ws2_32.SOCKET_ERROR) break; + if (rc != ws2_32.SOCKET_ERROR) { + current_thread.endSyscall(); + break; + } switch (ws2_32.WSAGetLastError()) { - .EINTR => continue, - .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled, + .EINTR => { + try current_thread.checkCancel(); + continue; + }, .NOTINITIALISED => { try initializeWsa(t); + try current_thread.checkCancel(); continue; }, - .ENETDOWN => return error.NetworkDown, - .EADDRINUSE => return error.AddressInUse, - .EISCONN => |err| return wsaErrorBug(err), - .EINVAL => |err| return wsaErrorBug(err), - .EMFILE, .ENOBUFS => return error.SystemResources, - .ENOTSOCK => |err| return wsaErrorBug(err), - .EOPNOTSUPP => |err| return wsaErrorBug(err), - .EINPROGRESS => |err| return wsaErrorBug(err), - else => |err| return windows.unexpectedWSAError(err), + else => |e| { + current_thread.endSyscall(); + switch (e) { + .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled, + .ENETDOWN => return error.NetworkDown, + .EADDRINUSE => return error.AddressInUse, + .EISCONN => |err| return wsaErrorBug(err), + .EINVAL => |err| return wsaErrorBug(err), + .EMFILE, .ENOBUFS => return error.SystemResources, + .ENOTSOCK => |err| return wsaErrorBug(err), + .EOPNOTSUPP => |err| return wsaErrorBug(err), + .EINPROGRESS => |err| return wsaErrorBug(err), + else => |err| return windows.unexpectedWSAError(err), + } + }, } } - try wsaGetSockName(t, socket_handle, &storage.any, &addr_len); + try wsaGetSockName(t, current_thread, socket_handle, &storage.any, &addr_len); return .{ .socket = .{ @@ -3256,7 +3735,8 @@ fn netListenUnixPosix( ) net.UnixAddress.ListenError!net.Socket.Handle { if (!net.has_unix_sockets) return error.AddressFamilyUnsupported; const t: *Threaded = @ptrCast(@alignCast(userdata)); - const socket_fd = openSocketPosix(t, posix.AF.UNIX, .{ .mode = .stream }) catch |err| switch (err) { + const current_thread = Thread.getCurrent(t); + const socket_fd = openSocketPosix(current_thread, posix.AF.UNIX, .{ .mode = .stream }) catch |err| switch (err) { error.ProtocolUnsupportedBySystem => return error.AddressFamilyUnsupported, error.ProtocolUnsupportedByAddressFamily => return error.AddressFamilyUnsupported, error.SocketModeUnsupported => return error.AddressFamilyUnsupported, @@ -3267,15 +3747,28 @@ fn netListenUnixPosix( var storage: UnixAddress = undefined; const addr_len = addressUnixToPosix(address, &storage); - try posixBindUnix(t, socket_fd, &storage.any, addr_len); + try posixBindUnix(current_thread, socket_fd, &storage.any, addr_len); + try current_thread.beginSyscall(); while (true) { - try t.checkCancel(); switch (posix.errno(posix.system.listen(socket_fd, options.kernel_backlog))) { - .SUCCESS => break, - .ADDRINUSE => return error.AddressInUse, - .BADF => |err| return errnoBug(err), // File descriptor used after closed. - else => |err| return posix.unexpectedErrno(err), + .SUCCESS => { + current_thread.endSyscall(); + break; + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .CANCELED => return error.Canceled, + .ADDRINUSE => return error.AddressInUse, + .BADF => |err| return errnoBug(err), // File descriptor used after closed. + else => |err| return posix.unexpectedErrno(err), + } + }, } } @@ -3289,8 +3782,9 @@ fn netListenUnixWindows( ) net.UnixAddress.ListenError!net.Socket.Handle { if (!net.has_unix_sockets) return error.AddressFamilyUnsupported; const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); - const socket_handle = openSocketWsa(t, posix.AF.UNIX, .{ .mode = .stream }) catch |err| switch (err) { + const socket_handle = openSocketWsa(t, current_thread, posix.AF.UNIX, .{ .mode = .stream }) catch |err| switch (err) { error.ProtocolUnsupportedByAddressFamily => return error.AddressFamilyUnsupported, else => |e| return e, }; @@ -3299,52 +3793,67 @@ fn netListenUnixWindows( var storage: WsaAddress = undefined; const addr_len = addressUnixToWsa(address, &storage); + try current_thread.beginSyscall(); while (true) { - try t.checkCancel(); const rc = ws2_32.bind(socket_handle, &storage.any, addr_len); if (rc != ws2_32.SOCKET_ERROR) break; switch (ws2_32.WSAGetLastError()) { - .EINTR => continue, - .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled, + .EINTR => { + try current_thread.checkCancel(); + continue; + }, .NOTINITIALISED => { try initializeWsa(t); + try current_thread.checkCancel(); continue; }, - .EADDRINUSE => return error.AddressInUse, - .EADDRNOTAVAIL => return error.AddressUnavailable, - .ENOTSOCK => |err| return wsaErrorBug(err), - .EFAULT => |err| return wsaErrorBug(err), - .EINVAL => |err| return wsaErrorBug(err), - .ENOBUFS => return error.SystemResources, - .ENETDOWN => return error.NetworkDown, - else => |err| return windows.unexpectedWSAError(err), + else => |e| { + current_thread.endSyscall(); + switch (e) { + .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled, + .EADDRINUSE => return error.AddressInUse, + .EADDRNOTAVAIL => return error.AddressUnavailable, + .ENOTSOCK => |err| return wsaErrorBug(err), + .EFAULT => |err| return wsaErrorBug(err), + .EINVAL => |err| return wsaErrorBug(err), + .ENOBUFS => return error.SystemResources, + .ENETDOWN => return error.NetworkDown, + else => |err| return windows.unexpectedWSAError(err), + } + }, } } while (true) { - try t.checkCancel(); + try current_thread.checkCancel(); const rc = ws2_32.listen(socket_handle, options.kernel_backlog); - if (rc != ws2_32.SOCKET_ERROR) break; + if (rc != ws2_32.SOCKET_ERROR) { + current_thread.endSyscall(); + return socket_handle; + } switch (ws2_32.WSAGetLastError()) { .EINTR => continue, - .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled, .NOTINITIALISED => { try initializeWsa(t); continue; }, - .ENETDOWN => return error.NetworkDown, - .EADDRINUSE => return error.AddressInUse, - .EISCONN => |err| return wsaErrorBug(err), - .EINVAL => |err| return wsaErrorBug(err), - .EMFILE, .ENOBUFS => return error.SystemResources, - .ENOTSOCK => |err| return wsaErrorBug(err), - .EOPNOTSUPP => |err| return wsaErrorBug(err), - .EINPROGRESS => |err| return wsaErrorBug(err), - else => |err| return windows.unexpectedWSAError(err), + else => |e| { + current_thread.endSyscall(); + switch (e) { + .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled, + .ENETDOWN => return error.NetworkDown, + .EADDRINUSE => return error.AddressInUse, + .EISCONN => |err| return wsaErrorBug(err), + .EINVAL => |err| return wsaErrorBug(err), + .EMFILE, .ENOBUFS => return error.SystemResources, + .ENOTSOCK => |err| return wsaErrorBug(err), + .EOPNOTSUPP => |err| return wsaErrorBug(err), + .EINPROGRESS => |err| return wsaErrorBug(err), + else => |err| return windows.unexpectedWSAError(err), + } + }, } } - - return socket_handle; } fn netListenUnixUnavailable( @@ -3358,172 +3867,275 @@ fn netListenUnixUnavailable( return error.AddressFamilyUnsupported; } -fn posixBindUnix(t: *Threaded, fd: posix.socket_t, addr: *const posix.sockaddr, addr_len: posix.socklen_t) !void { +fn posixBindUnix( + current_thread: *Thread, + fd: posix.socket_t, + addr: *const posix.sockaddr, + addr_len: posix.socklen_t, +) !void { + try current_thread.beginSyscall(); while (true) { - try t.checkCancel(); switch (posix.errno(posix.system.bind(fd, addr, addr_len))) { - .SUCCESS => break, - .INTR => continue, - .CANCELED => return error.Canceled, - - .ACCES => return error.AccessDenied, - .ADDRINUSE => return error.AddressInUse, - .AFNOSUPPORT => return error.AddressFamilyUnsupported, - .ADDRNOTAVAIL => return error.AddressUnavailable, - .NOMEM => return error.SystemResources, - - .LOOP => return error.SymLinkLoop, - .NOENT => return error.FileNotFound, - .NOTDIR => return error.NotDir, - .ROFS => return error.ReadOnlyFileSystem, - .PERM => return error.PermissionDenied, - - .BADF => |err| return errnoBug(err), // File descriptor used after closed. - .INVAL => |err| return errnoBug(err), // invalid parameters - .NOTSOCK => |err| return errnoBug(err), // invalid `sockfd` - .FAULT => |err| return errnoBug(err), // invalid `addr` pointer - .NAMETOOLONG => |err| return errnoBug(err), - else => |err| return posix.unexpectedErrno(err), + .SUCCESS => { + current_thread.endSyscall(); + break; + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .CANCELED => return error.Canceled, + .ACCES => return error.AccessDenied, + .ADDRINUSE => return error.AddressInUse, + .AFNOSUPPORT => return error.AddressFamilyUnsupported, + .ADDRNOTAVAIL => return error.AddressUnavailable, + .NOMEM => return error.SystemResources, + + .LOOP => return error.SymLinkLoop, + .NOENT => return error.FileNotFound, + .NOTDIR => return error.NotDir, + .ROFS => return error.ReadOnlyFileSystem, + .PERM => return error.PermissionDenied, + + .BADF => |err| return errnoBug(err), // File descriptor used after closed. + .INVAL => |err| return errnoBug(err), // invalid parameters + .NOTSOCK => |err| return errnoBug(err), // invalid `sockfd` + .FAULT => |err| return errnoBug(err), // invalid `addr` pointer + .NAMETOOLONG => |err| return errnoBug(err), + else => |err| return posix.unexpectedErrno(err), + } + }, } } } -fn posixBind(t: *Threaded, socket_fd: posix.socket_t, addr: *const posix.sockaddr, addr_len: posix.socklen_t) !void { +fn posixBind( + current_thread: *Thread, + socket_fd: posix.socket_t, + addr: *const posix.sockaddr, + addr_len: posix.socklen_t, +) !void { + try current_thread.beginSyscall(); while (true) { - try t.checkCancel(); switch (posix.errno(posix.system.bind(socket_fd, addr, addr_len))) { - .SUCCESS => break, - .INTR => continue, - .CANCELED => return error.Canceled, - - .ADDRINUSE => return error.AddressInUse, - .BADF => |err| return errnoBug(err), // File descriptor used after closed. - .INVAL => |err| return errnoBug(err), // invalid parameters - .NOTSOCK => |err| return errnoBug(err), // invalid `sockfd` - .AFNOSUPPORT => return error.AddressFamilyUnsupported, - .ADDRNOTAVAIL => return error.AddressUnavailable, - .FAULT => |err| return errnoBug(err), // invalid `addr` pointer - .NOMEM => return error.SystemResources, - else => |err| return posix.unexpectedErrno(err), + .SUCCESS => { + current_thread.endSyscall(); + break; + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .CANCELED => return error.Canceled, + .ADDRINUSE => return error.AddressInUse, + .BADF => |err| return errnoBug(err), // File descriptor used after closed. + .INVAL => |err| return errnoBug(err), // invalid parameters + .NOTSOCK => |err| return errnoBug(err), // invalid `sockfd` + .AFNOSUPPORT => return error.AddressFamilyUnsupported, + .ADDRNOTAVAIL => return error.AddressUnavailable, + .FAULT => |err| return errnoBug(err), // invalid `addr` pointer + .NOMEM => return error.SystemResources, + else => |err| return posix.unexpectedErrno(err), + } + }, } } } -fn posixConnect(t: *Threaded, socket_fd: posix.socket_t, addr: *const posix.sockaddr, addr_len: posix.socklen_t) !void { +fn posixConnect( + current_thread: *Thread, + socket_fd: posix.socket_t, + addr: *const posix.sockaddr, + addr_len: posix.socklen_t, +) !void { + try current_thread.beginSyscall(); while (true) { - try t.checkCancel(); switch (posix.errno(posix.system.connect(socket_fd, addr, addr_len))) { - .SUCCESS => return, - .INTR => continue, - .CANCELED => return error.Canceled, - - .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), + .SUCCESS => { + current_thread.endSyscall(); + return; + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .CANCELED => return error.Canceled, + .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), + } + }, } } } -fn posixConnectUnix(t: *Threaded, fd: posix.socket_t, addr: *const posix.sockaddr, addr_len: posix.socklen_t) !void { +fn posixConnectUnix( + current_thread: *Thread, + fd: posix.socket_t, + addr: *const posix.sockaddr, + addr_len: posix.socklen_t, +) !void { + try current_thread.beginSyscall(); while (true) { - try t.checkCancel(); switch (posix.errno(posix.system.connect(fd, addr, addr_len))) { - .SUCCESS => return, - .INTR => continue, - .CANCELED => return error.Canceled, - - .AFNOSUPPORT => return error.AddressFamilyUnsupported, - .AGAIN => return error.WouldBlock, - .INPROGRESS => return error.WouldBlock, - .ACCES => return error.AccessDenied, - - .LOOP => return error.SymLinkLoop, - .NOENT => return error.FileNotFound, - .NOTDIR => return error.NotDir, - .ROFS => return error.ReadOnlyFileSystem, - .PERM => return error.PermissionDenied, - - .BADF => |err| return errnoBug(err), // File descriptor used after closed. - .CONNABORTED => |err| return errnoBug(err), - .FAULT => |err| return errnoBug(err), - .ISCONN => |err| return errnoBug(err), - .NOTSOCK => |err| return errnoBug(err), - .PROTOTYPE => |err| return errnoBug(err), - else => |err| return posix.unexpectedErrno(err), + .SUCCESS => { + current_thread.endSyscall(); + return; + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .CANCELED => return error.Canceled, + .AFNOSUPPORT => return error.AddressFamilyUnsupported, + .AGAIN => return error.WouldBlock, + .INPROGRESS => return error.WouldBlock, + .ACCES => return error.AccessDenied, + + .LOOP => return error.SymLinkLoop, + .NOENT => return error.FileNotFound, + .NOTDIR => return error.NotDir, + .ROFS => return error.ReadOnlyFileSystem, + .PERM => return error.PermissionDenied, + + .BADF => |err| return errnoBug(err), // File descriptor used after closed. + .CONNABORTED => |err| return errnoBug(err), + .FAULT => |err| return errnoBug(err), + .ISCONN => |err| return errnoBug(err), + .NOTSOCK => |err| return errnoBug(err), + .PROTOTYPE => |err| return errnoBug(err), + else => |err| return posix.unexpectedErrno(err), + } + }, } } } -fn posixGetSockName(t: *Threaded, socket_fd: posix.fd_t, addr: *posix.sockaddr, addr_len: *posix.socklen_t) !void { +fn posixGetSockName( + current_thread: *Thread, + socket_fd: posix.fd_t, + addr: *posix.sockaddr, + addr_len: *posix.socklen_t, +) !void { + try current_thread.beginSyscall(); while (true) { - try t.checkCancel(); switch (posix.errno(posix.system.getsockname(socket_fd, addr, addr_len))) { - .SUCCESS => break, - .INTR => continue, - .CANCELED => return error.Canceled, - - .BADF => |err| return errnoBug(err), // File descriptor used after closed. - .FAULT => |err| return errnoBug(err), - .INVAL => |err| return errnoBug(err), // invalid parameters - .NOTSOCK => |err| return errnoBug(err), // always a race condition - .NOBUFS => return error.SystemResources, - else => |err| return posix.unexpectedErrno(err), + .SUCCESS => { + current_thread.endSyscall(); + break; + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .CANCELED => return error.Canceled, + .BADF => |err| return errnoBug(err), // File descriptor used after closed. + .FAULT => |err| return errnoBug(err), + .INVAL => |err| return errnoBug(err), // invalid parameters + .NOTSOCK => |err| return errnoBug(err), // always a race condition + .NOBUFS => return error.SystemResources, + else => |err| return posix.unexpectedErrno(err), + } + }, } } } -fn wsaGetSockName(t: *Threaded, handle: ws2_32.SOCKET, addr: *ws2_32.sockaddr, addr_len: *i32) !void { +fn wsaGetSockName( + t: *Threaded, + current_thread: *Thread, + handle: ws2_32.SOCKET, + addr: *ws2_32.sockaddr, + addr_len: *i32, +) !void { + try current_thread.beginSyscall(); while (true) { - try t.checkCancel(); const rc = ws2_32.getsockname(handle, addr, addr_len); - if (rc != ws2_32.SOCKET_ERROR) break; + if (rc != ws2_32.SOCKET_ERROR) { + current_thread.endSyscall(); + return; + } switch (ws2_32.WSAGetLastError()) { - .EINTR => continue, - .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled, + .EINTR => { + try current_thread.checkCancel(); + continue; + }, .NOTINITIALISED => { try initializeWsa(t); + try current_thread.checkCancel(); continue; }, - .ENETDOWN => return error.NetworkDown, - .EFAULT => |err| return wsaErrorBug(err), - .ENOTSOCK => |err| return wsaErrorBug(err), - .EINVAL => |err| return wsaErrorBug(err), - else => |err| return windows.unexpectedWSAError(err), + else => |e| { + current_thread.endSyscall(); + switch (e) { + .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled, + .ENETDOWN => return error.NetworkDown, + .EFAULT => |err| return wsaErrorBug(err), + .ENOTSOCK => |err| return wsaErrorBug(err), + .EINVAL => |err| return wsaErrorBug(err), + else => |err| return windows.unexpectedWSAError(err), + } + }, } } } -fn setSocketOption(t: *Threaded, fd: posix.fd_t, level: i32, opt_name: u32, option: u32) !void { +fn setSocketOption(current_thread: *Thread, fd: posix.fd_t, level: i32, opt_name: u32, option: u32) !void { const o: []const u8 = @ptrCast(&option); + try current_thread.beginSyscall(); while (true) { - try t.checkCancel(); switch (posix.errno(posix.system.setsockopt(fd, level, opt_name, o.ptr, @intCast(o.len)))) { - .SUCCESS => return, - .INTR => continue, - .CANCELED => return error.Canceled, - - .BADF => |err| return errnoBug(err), // File descriptor used after closed. - .NOTSOCK => |err| return errnoBug(err), - .INVAL => |err| return errnoBug(err), - .FAULT => |err| return errnoBug(err), - else => |err| return posix.unexpectedErrno(err), + .SUCCESS => { + current_thread.endSyscall(); + return; + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .CANCELED => return error.Canceled, + .BADF => |err| return errnoBug(err), // File descriptor used after closed. + .NOTSOCK => |err| return errnoBug(err), + .INVAL => |err| return errnoBug(err), + .FAULT => |err| return errnoBug(err), + else => |err| return posix.unexpectedErrno(err), + } + }, } } } @@ -3557,16 +4169,17 @@ fn netConnectIpPosix( if (!have_networking) return error.NetworkDown; if (options.timeout != .none) @panic("TODO implement netConnectIpPosix with timeout"); const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); const family = posixAddressFamily(address); - const socket_fd = try openSocketPosix(t, family, .{ + const socket_fd = try openSocketPosix(current_thread, family, .{ .mode = options.mode, .protocol = options.protocol, }); errdefer posix.close(socket_fd); var storage: PosixAddress = undefined; var addr_len = addressToPosix(address, &storage); - try posixConnect(t, socket_fd, &storage.any, addr_len); - try posixGetSockName(t, socket_fd, &storage.any, &addr_len); + try posixConnect(current_thread, socket_fd, &storage.any, addr_len); + try posixGetSockName(current_thread, socket_fd, &storage.any, &addr_len); return .{ .socket = .{ .handle = socket_fd, .address = addressFromPosix(&storage), @@ -3581,8 +4194,9 @@ fn netConnectIpWindows( if (!have_networking) return error.NetworkDown; if (options.timeout != .none) @panic("TODO implement netConnectIpWindows with timeout"); const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); const family = posixAddressFamily(address); - const socket_handle = try openSocketWsa(t, family, .{ + const socket_handle = try openSocketWsa(t, current_thread, family, .{ .mode = options.mode, .protocol = options.protocol, }); @@ -3591,36 +4205,48 @@ fn netConnectIpWindows( var storage: WsaAddress = undefined; var addr_len = addressToWsa(address, &storage); + try current_thread.beginSyscall(); while (true) { const rc = ws2_32.connect(socket_handle, &storage.any, addr_len); - if (rc != ws2_32.SOCKET_ERROR) break; + if (rc != ws2_32.SOCKET_ERROR) { + current_thread.endSyscall(); + break; + } switch (ws2_32.WSAGetLastError()) { - .EINTR => continue, - .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled, + .EINTR => { + try current_thread.checkCancel(); + continue; + }, .NOTINITIALISED => { try initializeWsa(t); + try current_thread.checkCancel(); continue; }, - - .EADDRNOTAVAIL => return error.AddressUnavailable, - .ECONNREFUSED => return error.ConnectionRefused, - .ECONNRESET => return error.ConnectionResetByPeer, - .ETIMEDOUT => return error.Timeout, - .EHOSTUNREACH => return error.HostUnreachable, - .ENETUNREACH => return error.NetworkUnreachable, - .EFAULT => |err| return wsaErrorBug(err), - .EINVAL => |err| return wsaErrorBug(err), - .EISCONN => |err| return wsaErrorBug(err), - .ENOTSOCK => |err| return wsaErrorBug(err), - .EWOULDBLOCK => return error.WouldBlock, - .EACCES => return error.AccessDenied, - .ENOBUFS => return error.SystemResources, - .EAFNOSUPPORT => return error.AddressFamilyUnsupported, - else => |err| return windows.unexpectedWSAError(err), + else => |e| { + current_thread.endSyscall(); + switch (e) { + .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled, + .EADDRNOTAVAIL => return error.AddressUnavailable, + .ECONNREFUSED => return error.ConnectionRefused, + .ECONNRESET => return error.ConnectionResetByPeer, + .ETIMEDOUT => return error.Timeout, + .EHOSTUNREACH => return error.HostUnreachable, + .ENETUNREACH => return error.NetworkUnreachable, + .EFAULT => |err| return wsaErrorBug(err), + .EINVAL => |err| return wsaErrorBug(err), + .EISCONN => |err| return wsaErrorBug(err), + .ENOTSOCK => |err| return wsaErrorBug(err), + .EWOULDBLOCK => return error.WouldBlock, + .EACCES => return error.AccessDenied, + .ENOBUFS => return error.SystemResources, + .EAFNOSUPPORT => return error.AddressFamilyUnsupported, + else => |err| return windows.unexpectedWSAError(err), + } + }, } } - try wsaGetSockName(t, socket_handle, &storage.any, &addr_len); + try wsaGetSockName(t, current_thread, socket_handle, &storage.any, &addr_len); return .{ .socket = .{ .handle = socket_handle, @@ -3645,14 +4271,15 @@ fn netConnectUnixPosix( ) net.UnixAddress.ConnectError!net.Socket.Handle { if (!net.has_unix_sockets) return error.AddressFamilyUnsupported; const t: *Threaded = @ptrCast(@alignCast(userdata)); - const socket_fd = openSocketPosix(t, posix.AF.UNIX, .{ .mode = .stream }) catch |err| switch (err) { + const current_thread = Thread.getCurrent(t); + const socket_fd = openSocketPosix(current_thread, posix.AF.UNIX, .{ .mode = .stream }) catch |err| switch (err) { error.OptionUnsupported => return error.Unexpected, else => |e| return e, }; errdefer posix.close(socket_fd); var storage: UnixAddress = undefined; const addr_len = addressUnixToPosix(address, &storage); - try posixConnectUnix(t, socket_fd, &storage.any, addr_len); + try posixConnectUnix(current_thread, socket_fd, &storage.any, addr_len); return socket_fd; } @@ -3662,8 +4289,9 @@ fn netConnectUnixWindows( ) net.UnixAddress.ConnectError!net.Socket.Handle { if (!net.has_unix_sockets) return error.AddressFamilyUnsupported; const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); - const socket_handle = try openSocketWsa(t, posix.AF.UNIX, .{ .mode = .stream }); + const socket_handle = try openSocketWsa(t, current_thread, posix.AF.UNIX, .{ .mode = .stream }); errdefer closeSocketWindows(socket_handle); var storage: WsaAddress = undefined; const addr_len = addressUnixToWsa(address, &storage); @@ -3711,13 +4339,14 @@ fn netBindIpPosix( ) IpAddress.BindError!net.Socket { if (!have_networking) return error.NetworkDown; const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); const family = posixAddressFamily(address); - const socket_fd = try openSocketPosix(t, family, options); + const socket_fd = try openSocketPosix(current_thread, family, options); errdefer posix.close(socket_fd); var storage: PosixAddress = undefined; var addr_len = addressToPosix(address, &storage); - try posixBind(t, socket_fd, &storage.any, addr_len); - try posixGetSockName(t, socket_fd, &storage.any, &addr_len); + try posixBind(current_thread, socket_fd, &storage.any, addr_len); + try posixGetSockName(current_thread, socket_fd, &storage.any, &addr_len); return .{ .handle = socket_fd, .address = addressFromPosix(&storage), @@ -3731,8 +4360,9 @@ fn netBindIpWindows( ) IpAddress.BindError!net.Socket { if (!have_networking) return error.NetworkDown; const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); const family = posixAddressFamily(address); - const socket_handle = try openSocketWsa(t, family, .{ + const socket_handle = try openSocketWsa(t, current_thread, family, .{ .mode = options.mode, .protocol = options.protocol, }); @@ -3741,29 +4371,41 @@ fn netBindIpWindows( var storage: WsaAddress = undefined; var addr_len = addressToWsa(address, &storage); + try current_thread.beginSyscall(); while (true) { - try t.checkCancel(); const rc = ws2_32.bind(socket_handle, &storage.any, addr_len); - if (rc != ws2_32.SOCKET_ERROR) break; + if (rc != ws2_32.SOCKET_ERROR) { + current_thread.endSyscall(); + break; + } switch (ws2_32.WSAGetLastError()) { - .EINTR => continue, - .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled, + .EINTR => { + try current_thread.checkCancel(); + continue; + }, .NOTINITIALISED => { try initializeWsa(t); + try current_thread.checkCancel(); continue; }, - .EADDRINUSE => return error.AddressInUse, - .EADDRNOTAVAIL => return error.AddressUnavailable, - .ENOTSOCK => |err| return wsaErrorBug(err), - .EFAULT => |err| return wsaErrorBug(err), - .EINVAL => |err| return wsaErrorBug(err), - .ENOBUFS => return error.SystemResources, - .ENETDOWN => return error.NetworkDown, - else => |err| return windows.unexpectedWSAError(err), + else => |e| { + current_thread.endSyscall(); + switch (e) { + .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled, + .EADDRINUSE => return error.AddressInUse, + .EADDRNOTAVAIL => return error.AddressUnavailable, + .ENOTSOCK => |err| return wsaErrorBug(err), + .EFAULT => |err| return wsaErrorBug(err), + .EINVAL => |err| return wsaErrorBug(err), + .ENOBUFS => return error.SystemResources, + .ENETDOWN => return error.NetworkDown, + else => |err| return windows.unexpectedWSAError(err), + } + }, } } - try wsaGetSockName(t, socket_handle, &storage.any, &addr_len); + try wsaGetSockName(t, current_thread, socket_handle, &storage.any, &addr_len); return .{ .handle = socket_handle, @@ -3783,7 +4425,7 @@ fn netBindIpUnavailable( } fn openSocketPosix( - t: *Threaded, + current_thread: *Thread, family: posix.sa_family_t, options: IpAddress.BindOptions, ) error{ @@ -3800,8 +4442,8 @@ fn openSocketPosix( }!posix.socket_t { const mode = posixSocketMode(options.mode); const protocol = posixProtocol(options.protocol); + try current_thread.beginSyscall(); const socket_fd = while (true) { - try t.checkCancel(); 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)) { @@ -3809,60 +4451,90 @@ fn openSocketPosix( const fd: posix.fd_t = @intCast(socket_rc); errdefer posix.close(fd); if (socket_flags_unsupported) while (true) { - try t.checkCancel(); + try current_thread.checkCancel(); switch (posix.errno(posix.system.fcntl(fd, posix.F.SETFD, @as(usize, posix.FD_CLOEXEC)))) { .SUCCESS => break, .INTR => continue, - .CANCELED => return error.Canceled, - else => |err| return posix.unexpectedErrno(err), + else => |e| { + current_thread.endSyscall(); + switch (e) { + .CANCELED => return error.Canceled, + else => |err| return posix.unexpectedErrno(err), + } + }, } }; + current_thread.endSyscall(); break fd; }, - .INTR => continue, - .CANCELED => return error.Canceled, - - .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), + .INTR => { + try current_thread.checkCancel(); + continue; + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .CANCELED => return error.Canceled, + .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), + } + }, } }; errdefer posix.close(socket_fd); if (options.ip6_only) { if (posix.IPV6 == void) return error.OptionUnsupported; - try setSocketOption(t, socket_fd, posix.IPPROTO.IPV6, posix.IPV6.V6ONLY, 0); + try setSocketOption(current_thread, socket_fd, posix.IPPROTO.IPV6, posix.IPV6.V6ONLY, 0); } return socket_fd; } -fn openSocketWsa(t: *Threaded, family: posix.sa_family_t, options: IpAddress.BindOptions) !ws2_32.SOCKET { +fn openSocketWsa( + t: *Threaded, + current_thread: *Thread, + family: posix.sa_family_t, + options: IpAddress.BindOptions, +) !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; + try current_thread.beginSyscall(); while (true) { - try t.checkCancel(); const rc = ws2_32.WSASocketW(family, @bitCast(mode), @bitCast(protocol), null, 0, flags); - if (rc != ws2_32.INVALID_SOCKET) return rc; + if (rc != ws2_32.INVALID_SOCKET) { + current_thread.endSyscall(); + return rc; + } switch (ws2_32.WSAGetLastError()) { - .EINTR => continue, - .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled, + .EINTR => { + try current_thread.checkCancel(); + continue; + }, .NOTINITIALISED => { try initializeWsa(t); + try current_thread.checkCancel(); continue; }, - .EAFNOSUPPORT => return error.AddressFamilyUnsupported, - .EMFILE => return error.ProcessFdQuotaExceeded, - .ENOBUFS => return error.SystemResources, - .EPROTONOSUPPORT => return error.ProtocolUnsupportedByAddressFamily, - else => |err| return windows.unexpectedWSAError(err), + else => |e| { + current_thread.endSyscall(); + switch (e) { + .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled, + .EAFNOSUPPORT => return error.AddressFamilyUnsupported, + .EMFILE => return error.ProcessFdQuotaExceeded, + .ENOBUFS => return error.SystemResources, + .EPROTONOSUPPORT => return error.ProtocolUnsupportedByAddressFamily, + else => |err| return windows.unexpectedWSAError(err), + } + }, } } } @@ -3870,10 +4542,11 @@ fn openSocketWsa(t: *Threaded, family: posix.sa_family_t, options: IpAddress.Bin fn netAcceptPosix(userdata: ?*anyopaque, listen_fd: net.Socket.Handle) net.Server.AcceptError!net.Stream { if (!have_networking) return error.NetworkDown; const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); var storage: PosixAddress = undefined; var addr_len: posix.socklen_t = @sizeOf(PosixAddress); + try current_thread.beginSyscall(); const fd = while (true) { - try t.checkCancel(); const rc = if (have_accept4) posix.system.accept4(listen_fd, &storage.any, &addr_len, posix.SOCK.CLOEXEC) else @@ -3883,33 +4556,43 @@ fn netAcceptPosix(userdata: ?*anyopaque, listen_fd: net.Socket.Handle) net.Serve const fd: posix.fd_t = @intCast(rc); errdefer posix.close(fd); if (!have_accept4) while (true) { - try t.checkCancel(); + try current_thread.checkCancel(); switch (posix.errno(posix.system.fcntl(fd, posix.F.SETFD, @as(usize, posix.FD_CLOEXEC)))) { .SUCCESS => break, .INTR => continue, - .CANCELED => return error.Canceled, - else => |err| return posix.unexpectedErrno(err), + else => |err| { + current_thread.endSyscall(); + return posix.unexpectedErrno(err); + }, } }; + current_thread.endSyscall(); break fd; }, - .INTR => continue, - .CANCELED => return error.Canceled, - - .AGAIN => |err| return errnoBug(err), - .BADF => |err| return errnoBug(err), // File descriptor used after closed. - .CONNABORTED => return error.ConnectionAborted, - .FAULT => |err| return errnoBug(err), - .INVAL => return error.SocketNotListening, - .NOTSOCK => |err| return errnoBug(err), - .MFILE => return error.ProcessFdQuotaExceeded, - .NFILE => return error.SystemFdQuotaExceeded, - .NOBUFS => return error.SystemResources, - .NOMEM => return error.SystemResources, - .OPNOTSUPP => |err| return errnoBug(err), - .PROTO => return error.ProtocolFailure, - .PERM => return error.BlockedByFirewall, - else => |err| return posix.unexpectedErrno(err), + .INTR => { + try current_thread.checkCancel(); + continue; + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .CANCELED => return error.Canceled, + .AGAIN => |err| return errnoBug(err), + .BADF => |err| return errnoBug(err), // File descriptor used after closed. + .CONNABORTED => return error.ConnectionAborted, + .FAULT => |err| return errnoBug(err), + .INVAL => return error.SocketNotListening, + .NOTSOCK => |err| return errnoBug(err), + .MFILE => return error.ProcessFdQuotaExceeded, + .NFILE => return error.SystemFdQuotaExceeded, + .NOBUFS => return error.SystemResources, + .NOMEM => return error.SystemResources, + .OPNOTSUPP => |err| return errnoBug(err), + .PROTO => return error.ProtocolFailure, + .PERM => return error.BlockedByFirewall, + else => |err| return posix.unexpectedErrno(err), + } + }, } }; return .{ .socket = .{ @@ -3921,31 +4604,44 @@ fn netAcceptPosix(userdata: ?*anyopaque, listen_fd: net.Socket.Handle) net.Serve fn netAcceptWindows(userdata: ?*anyopaque, listen_handle: net.Socket.Handle) net.Server.AcceptError!net.Stream { if (!have_networking) return error.NetworkDown; const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); var storage: WsaAddress = undefined; var addr_len: i32 = @sizeOf(WsaAddress); + try current_thread.beginSyscall(); while (true) { - try t.checkCancel(); const rc = ws2_32.accept(listen_handle, &storage.any, &addr_len); - if (rc != ws2_32.INVALID_SOCKET) return .{ .socket = .{ - .handle = rc, - .address = addressFromWsa(&storage), - } }; + if (rc != ws2_32.INVALID_SOCKET) { + current_thread.endSyscall(); + return .{ .socket = .{ + .handle = rc, + .address = addressFromWsa(&storage), + } }; + } switch (ws2_32.WSAGetLastError()) { - .EINTR => continue, - .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled, + .EINTR => { + try current_thread.checkCancel(); + continue; + }, .NOTINITIALISED => { try initializeWsa(t); + try current_thread.checkCancel(); continue; }, - .ECONNRESET => return error.ConnectionAborted, - .EFAULT => |err| return wsaErrorBug(err), - .ENOTSOCK => |err| return wsaErrorBug(err), - .EINVAL => |err| return wsaErrorBug(err), - .EMFILE => return error.ProcessFdQuotaExceeded, - .ENETDOWN => return error.NetworkDown, - .ENOBUFS => return error.SystemResources, - .EOPNOTSUPP => |err| return wsaErrorBug(err), - else => |err| return windows.unexpectedWSAError(err), + else => |e| { + current_thread.endSyscall(); + switch (e) { + .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled, + .ECONNRESET => return error.ConnectionAborted, + .EFAULT => |err| return wsaErrorBug(err), + .ENOTSOCK => |err| return wsaErrorBug(err), + .EINVAL => |err| return wsaErrorBug(err), + .EMFILE => return error.ProcessFdQuotaExceeded, + .ENETDOWN => return error.NetworkDown, + .ENOBUFS => return error.SystemResources, + .EOPNOTSUPP => |err| return wsaErrorBug(err), + else => |err| return windows.unexpectedWSAError(err), + } + }, } } } @@ -3959,6 +4655,7 @@ fn netAcceptUnavailable(userdata: ?*anyopaque, listen_handle: net.Socket.Handle) fn netReadPosix(userdata: ?*anyopaque, fd: net.Socket.Handle, data: [][]u8) net.Stream.Reader.Error!usize { if (!have_networking) return error.NetworkDown; const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); var iovecs_buffer: [max_iovecs_len]posix.iovec = undefined; var i: usize = 0; @@ -3972,48 +4669,70 @@ fn netReadPosix(userdata: ?*anyopaque, fd: net.Socket.Handle, data: [][]u8) net. const dest = iovecs_buffer[0..i]; assert(dest[0].len > 0); - if (native_os == .wasi and !builtin.link_libc) while (true) { - try t.checkCancel(); - var n: usize = undefined; - switch (std.os.wasi.fd_read(fd, dest.ptr, dest.len, &n)) { - .SUCCESS => return n, - .INTR => continue, - .CANCELED => return error.Canceled, - - .INVAL => |err| return errnoBug(err), - .FAULT => |err| return errnoBug(err), - .AGAIN => |err| return errnoBug(err), - .BADF => |err| return errnoBug(err), // File descriptor used after closed. - .NOBUFS => return error.SystemResources, - .NOMEM => return error.SystemResources, - .NOTCONN => return error.SocketUnconnected, - .CONNRESET => return error.ConnectionResetByPeer, - .TIMEDOUT => return error.Timeout, - .NOTCAPABLE => return error.AccessDenied, - else => |err| return posix.unexpectedErrno(err), + if (native_os == .wasi and !builtin.link_libc) { + try current_thread.beginSyscall(); + while (true) { + var n: usize = undefined; + switch (std.os.wasi.fd_read(fd, dest.ptr, dest.len, &n)) { + .SUCCESS => { + current_thread.endSyscall(); + return n; + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .CANCELED => return error.Canceled, + .INVAL => |err| return errnoBug(err), + .FAULT => |err| return errnoBug(err), + .AGAIN => |err| return errnoBug(err), + .BADF => |err| return errnoBug(err), // File descriptor used after closed. + .NOBUFS => return error.SystemResources, + .NOMEM => return error.SystemResources, + .NOTCONN => return error.SocketUnconnected, + .CONNRESET => return error.ConnectionResetByPeer, + .TIMEDOUT => return error.Timeout, + .NOTCAPABLE => return error.AccessDenied, + else => |err| return posix.unexpectedErrno(err), + } + }, + } } - }; + } + try current_thread.beginSyscall(); while (true) { - try t.checkCancel(); const rc = posix.system.readv(fd, dest.ptr, @intCast(dest.len)); switch (posix.errno(rc)) { - .SUCCESS => return @intCast(rc), - .INTR => continue, - .CANCELED => return error.Canceled, - - .INVAL => |err| return errnoBug(err), - .FAULT => |err| return errnoBug(err), - .AGAIN => |err| return errnoBug(err), - .BADF => |err| return errnoBug(err), // File descriptor used after closed. - .NOBUFS => return error.SystemResources, - .NOMEM => return error.SystemResources, - .NOTCONN => return error.SocketUnconnected, - .CONNRESET => return error.ConnectionResetByPeer, - .TIMEDOUT => return error.Timeout, - .PIPE => return error.SocketUnconnected, - .NETDOWN => return error.NetworkDown, - else => |err| return posix.unexpectedErrno(err), + .SUCCESS => { + current_thread.endSyscall(); + return @intCast(rc); + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .CANCELED => return error.Canceled, + .INVAL => |err| return errnoBug(err), + .FAULT => |err| return errnoBug(err), + .AGAIN => |err| return errnoBug(err), + .BADF => |err| return errnoBug(err), // File descriptor used after closed. + .NOBUFS => return error.SystemResources, + .NOMEM => return error.SystemResources, + .NOTCONN => return error.SocketUnconnected, + .CONNRESET => return error.ConnectionResetByPeer, + .TIMEDOUT => return error.Timeout, + .PIPE => return error.SocketUnconnected, + .NETDOWN => return error.NetworkDown, + else => |err| return posix.unexpectedErrno(err), + } + }, } } } @@ -4021,6 +4740,7 @@ fn netReadPosix(userdata: ?*anyopaque, fd: net.Socket.Handle, data: [][]u8) net. fn netReadWindows(userdata: ?*anyopaque, handle: net.Socket.Handle, data: [][]u8) net.Stream.Reader.Error!usize { if (!have_networking) return error.NetworkDown; const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); const bufs = b: { var iovec_buffer: [max_iovecs_len]ws2_32.WSABUF = undefined; @@ -4048,7 +4768,7 @@ fn netReadWindows(userdata: ?*anyopaque, handle: net.Socket.Handle, data: [][]u8 }; while (true) { - try t.checkCancel(); + try current_thread.checkCancel(); var flags: u32 = 0; var overlapped: windows.OVERLAPPED = std.mem.zeroes(windows.OVERLAPPED); @@ -4108,6 +4828,7 @@ fn netSendPosix( ) struct { ?net.Socket.SendError, usize } { if (!have_networking) return .{ error.NetworkDown, 0 }; const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); const posix_flags: u32 = @as(u32, if (@hasDecl(posix.MSG, "CONFIRM") and flags.confirm) posix.MSG.CONFIRM else 0) | @@ -4120,10 +4841,10 @@ fn netSendPosix( var i: usize = 0; while (messages.len - i != 0) { if (have_sendmmsg) { - i += netSendMany(t, handle, messages[i..], posix_flags) catch |err| return .{ err, i }; + i += netSendMany(current_thread, handle, messages[i..], posix_flags) catch |err| return .{ err, i }; continue; } - netSendOne(t, handle, &messages[i], posix_flags) catch |err| return .{ err, i }; + netSendOne(t, current_thread, handle, &messages[i], posix_flags) catch |err| return .{ err, i }; i += 1; } return .{ null, i }; @@ -4159,6 +4880,7 @@ fn netSendUnavailable( fn netSendOne( t: *Threaded, + current_thread: *Thread, handle: net.Socket.Handle, message: *net.OutgoingMessage, flags: u32, @@ -4175,75 +4897,92 @@ fn netSendOne( .controllen = @intCast(message.control.len), .flags = 0, }; + try current_thread.beginSyscall(); while (true) { - try t.checkCancel(); const rc = posix.system.sendmsg(handle, &msg, flags); if (is_windows) { - if (rc == ws2_32.SOCKET_ERROR) { - switch (ws2_32.WSAGetLastError()) { - .EINTR => continue, - .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled, - .NOTINITIALISED => { - try initializeWsa(t); - continue; - }, - .EACCES => return error.AccessDenied, - .EADDRNOTAVAIL => return error.AddressUnavailable, - .ECONNRESET => return error.ConnectionResetByPeer, - .EMSGSIZE => return error.MessageOversize, - .ENOBUFS => return error.SystemResources, - .ENOTSOCK => return error.FileDescriptorNotASocket, - .EAFNOSUPPORT => return error.AddressFamilyUnsupported, - .EDESTADDRREQ => unreachable, // A destination address is required. - .EFAULT => unreachable, // The lpBuffers, lpTo, lpOverlapped, lpNumberOfBytesSent, or lpCompletionRoutine parameters are not part of the user address space, or the lpTo parameter is too small. - .EHOSTUNREACH => return error.NetworkUnreachable, - .EINVAL => unreachable, - .ENETDOWN => return error.NetworkDown, - .ENETRESET => return error.ConnectionResetByPeer, - .ENETUNREACH => return error.NetworkUnreachable, - .ENOTCONN => return error.SocketUnconnected, - .ESHUTDOWN => |err| return wsaErrorBug(err), - else => |err| return windows.unexpectedWSAError(err), - } - } else { + if (rc != ws2_32.SOCKET_ERROR) { + current_thread.endSyscall(); message.data_len = @intCast(rc); return; } + switch (ws2_32.WSAGetLastError()) { + .EINTR => { + try current_thread.checkCancel(); + continue; + }, + .NOTINITIALISED => { + try initializeWsa(t); + try current_thread.checkCancel(); + continue; + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled, + .EACCES => return error.AccessDenied, + .EADDRNOTAVAIL => return error.AddressUnavailable, + .ECONNRESET => return error.ConnectionResetByPeer, + .EMSGSIZE => return error.MessageOversize, + .ENOBUFS => return error.SystemResources, + .ENOTSOCK => return error.FileDescriptorNotASocket, + .EAFNOSUPPORT => return error.AddressFamilyUnsupported, + .EDESTADDRREQ => unreachable, // A destination address is required. + .EFAULT => unreachable, // The lpBuffers, lpTo, lpOverlapped, lpNumberOfBytesSent, or lpCompletionRoutine parameters are not part of the user address space, or the lpTo parameter is too small. + .EHOSTUNREACH => return error.NetworkUnreachable, + .EINVAL => unreachable, + .ENETDOWN => return error.NetworkDown, + .ENETRESET => return error.ConnectionResetByPeer, + .ENETUNREACH => return error.NetworkUnreachable, + .ENOTCONN => return error.SocketUnconnected, + .ESHUTDOWN => |err| return wsaErrorBug(err), + else => |err| return windows.unexpectedWSAError(err), + } + }, + } } switch (posix.errno(rc)) { .SUCCESS => { + current_thread.endSyscall(); message.data_len = @intCast(rc); return; }, - .INTR => continue, - .CANCELED => return error.Canceled, - - .ACCES => return error.AccessDenied, - .ALREADY => return error.FastOpenAlreadyInProgress, - .BADF => |err| return errnoBug(err), // File descriptor used after closed. - .CONNRESET => return error.ConnectionResetByPeer, - .DESTADDRREQ => |err| return errnoBug(err), - .FAULT => |err| return errnoBug(err), - .INVAL => |err| return errnoBug(err), - .ISCONN => |err| return errnoBug(err), - .MSGSIZE => return error.MessageOversize, - .NOBUFS => return error.SystemResources, - .NOMEM => return error.SystemResources, - .NOTSOCK => |err| return errnoBug(err), - .OPNOTSUPP => |err| return errnoBug(err), - .PIPE => return error.SocketUnconnected, - .AFNOSUPPORT => return error.AddressFamilyUnsupported, - .HOSTUNREACH => return error.HostUnreachable, - .NETUNREACH => return error.NetworkUnreachable, - .NOTCONN => return error.SocketUnconnected, - .NETDOWN => return error.NetworkDown, - else => |err| return posix.unexpectedErrno(err), + .INTR => { + try current_thread.checkCancel(); + continue; + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .CANCELED => return error.Canceled, + .ACCES => return error.AccessDenied, + .ALREADY => return error.FastOpenAlreadyInProgress, + .BADF => |err| return errnoBug(err), // File descriptor used after closed. + .CONNRESET => return error.ConnectionResetByPeer, + .DESTADDRREQ => |err| return errnoBug(err), + .FAULT => |err| return errnoBug(err), + .INVAL => |err| return errnoBug(err), + .ISCONN => |err| return errnoBug(err), + .MSGSIZE => return error.MessageOversize, + .NOBUFS => return error.SystemResources, + .NOMEM => return error.SystemResources, + .NOTSOCK => |err| return errnoBug(err), + .OPNOTSUPP => |err| return errnoBug(err), + .PIPE => return error.SocketUnconnected, + .AFNOSUPPORT => return error.AddressFamilyUnsupported, + .HOSTUNREACH => return error.HostUnreachable, + .NETUNREACH => return error.NetworkUnreachable, + .NOTCONN => return error.SocketUnconnected, + .NETDOWN => return error.NetworkDown, + else => |err| return posix.unexpectedErrno(err), + } + }, } } } fn netSendMany( - t: *Threaded, + current_thread: *Thread, handle: net.Socket.Handle, messages: []net.OutgoingMessage, flags: u32, @@ -4273,40 +5012,48 @@ fn netSendMany( }; } + try current_thread.beginSyscall(); while (true) { - try t.checkCancel(); const rc = posix.system.sendmmsg(handle, clamped_msgs.ptr, @intCast(clamped_msgs.len), flags); switch (posix.errno(rc)) { .SUCCESS => { + current_thread.endSyscall(); const n: usize = @intCast(rc); for (clamped_messages[0..n], clamped_msgs[0..n]) |*message, *msg| { message.data_len = msg.len; } return n; }, - .INTR => continue, - .CANCELED => return error.Canceled, - - .AGAIN => |err| return errnoBug(err), - .ALREADY => return error.FastOpenAlreadyInProgress, - .BADF => |err| return errnoBug(err), // File descriptor used after closed. - .CONNRESET => return error.ConnectionResetByPeer, - .DESTADDRREQ => |err| return errnoBug(err), // The socket is not connection-mode, and no peer address is set. - .FAULT => |err| return errnoBug(err), // An invalid user space address was specified for an argument. - .INVAL => |err| return errnoBug(err), // Invalid argument passed. - .ISCONN => |err| return errnoBug(err), // connection-mode socket was connected already but a recipient was specified - .MSGSIZE => return error.MessageOversize, - .NOBUFS => return error.SystemResources, - .NOMEM => return error.SystemResources, - .NOTSOCK => |err| return errnoBug(err), // The file descriptor sockfd does not refer to a socket. - .OPNOTSUPP => |err| return errnoBug(err), // Some bit in the flags argument is inappropriate for the socket type. - .PIPE => return error.SocketUnconnected, - .AFNOSUPPORT => return error.AddressFamilyUnsupported, - .HOSTUNREACH => return error.HostUnreachable, - .NETUNREACH => return error.NetworkUnreachable, - .NOTCONN => return error.SocketUnconnected, - .NETDOWN => return error.NetworkDown, - else => |err| return posix.unexpectedErrno(err), + .INTR => { + try current_thread.checkCancel(); + continue; + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .CANCELED => return error.Canceled, + .AGAIN => |err| return errnoBug(err), + .ALREADY => return error.FastOpenAlreadyInProgress, + .BADF => |err| return errnoBug(err), // File descriptor used after closed. + .CONNRESET => return error.ConnectionResetByPeer, + .DESTADDRREQ => |err| return errnoBug(err), // The socket is not connection-mode, and no peer address is set. + .FAULT => |err| return errnoBug(err), // An invalid user space address was specified for an argument. + .INVAL => |err| return errnoBug(err), // Invalid argument passed. + .ISCONN => |err| return errnoBug(err), // connection-mode socket was connected already but a recipient was specified + .MSGSIZE => return error.MessageOversize, + .NOBUFS => return error.SystemResources, + .NOMEM => return error.SystemResources, + .NOTSOCK => |err| return errnoBug(err), // The file descriptor sockfd does not refer to a socket. + .OPNOTSUPP => |err| return errnoBug(err), // Some bit in the flags argument is inappropriate for the socket type. + .PIPE => return error.SocketUnconnected, + .AFNOSUPPORT => return error.AddressFamilyUnsupported, + .HOSTUNREACH => return error.HostUnreachable, + .NETUNREACH => return error.NetworkUnreachable, + .NOTCONN => return error.SocketUnconnected, + .NETDOWN => return error.NetworkDown, + else => |err| return posix.unexpectedErrno(err), + } + }, } } } @@ -4321,6 +5068,7 @@ fn netReceivePosix( ) struct { ?net.Socket.ReceiveTimeoutError, usize } { if (!have_networking) return .{ error.NetworkDown, 0 }; const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); const t_io = io(t); // recvmmsg is useless, here's why: @@ -4351,8 +5099,6 @@ fn netReceivePosix( const deadline = timeout.toDeadline(t_io) catch |err| return .{ err, message_i }; recv: while (true) { - t.checkCancel() catch |err| return .{ err, message_i }; - if (message_buffer.len - message_i == 0) return .{ null, message_i }; const message = &message_buffer[message_i]; const remaining_data_buffer = data_buffer[data_i..]; @@ -4368,7 +5114,9 @@ fn netReceivePosix( .flags = undefined, }; + current_thread.beginSyscall() catch |err| return .{ err, message_i }; const recv_rc = posix.system.recvmsg(handle, &msg, posix_flags); + current_thread.endSyscall(); switch (posix.errno(recv_rc)) { .SUCCESS => { const data = remaining_data_buffer[0..@intCast(recv_rc)]; @@ -4389,7 +5137,6 @@ fn netReceivePosix( continue; }, .AGAIN => while (true) { - t.checkCancel() catch |err| return .{ err, message_i }; if (message_i != 0) return .{ null, message_i }; const max_poll_ms = std.math.maxInt(u31); @@ -4399,7 +5146,10 @@ fn netReceivePosix( break :t @intCast(@min(max_poll_ms, duration.raw.toMilliseconds())); } else max_poll_ms; + current_thread.beginSyscall() catch |err| return .{ err, message_i }; const poll_rc = posix.system.poll(&poll_fds, poll_fds.len, timeout_ms); + current_thread.endSyscall(); + switch (posix.errno(poll_rc)) { .SUCCESS => { if (poll_rc == 0) { @@ -4486,6 +5236,7 @@ fn netWritePosix( ) net.Stream.Writer.Error!usize { if (!have_networking) return error.NetworkDown; const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); var iovecs: [max_iovecs_len]posix.iovec_const = undefined; var msg: posix.msghdr_const = .{ @@ -4526,35 +5277,45 @@ fn netWritePosix( }, }; const flags = posix.MSG.NOSIGNAL; + try current_thread.beginSyscall(); while (true) { - try t.checkCancel(); const rc = posix.system.sendmsg(fd, &msg, flags); switch (posix.errno(rc)) { - .SUCCESS => return @intCast(rc), - .INTR => continue, - .CANCELED => return error.Canceled, - - .ACCES => |err| return errnoBug(err), - .AGAIN => |err| return errnoBug(err), - .ALREADY => return error.FastOpenAlreadyInProgress, - .BADF => |err| return errnoBug(err), // File descriptor used after closed. - .CONNRESET => return error.ConnectionResetByPeer, - .DESTADDRREQ => |err| return errnoBug(err), // The socket is not connection-mode, and no peer address is set. - .FAULT => |err| return errnoBug(err), // An invalid user space address was specified for an argument. - .INVAL => |err| return errnoBug(err), // Invalid argument passed. - .ISCONN => |err| return errnoBug(err), // connection-mode socket was connected already but a recipient was specified - .MSGSIZE => |err| return errnoBug(err), - .NOBUFS => return error.SystemResources, - .NOMEM => return error.SystemResources, - .NOTSOCK => |err| return errnoBug(err), // The file descriptor sockfd does not refer to a socket. - .OPNOTSUPP => |err| return errnoBug(err), // Some bit in the flags argument is inappropriate for the socket type. - .PIPE => return error.SocketUnconnected, - .AFNOSUPPORT => return error.AddressFamilyUnsupported, - .HOSTUNREACH => return error.HostUnreachable, - .NETUNREACH => return error.NetworkUnreachable, - .NOTCONN => return error.SocketUnconnected, - .NETDOWN => return error.NetworkDown, - else => |err| return posix.unexpectedErrno(err), + .SUCCESS => { + current_thread.endSyscall(); + return @intCast(rc); + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .CANCELED => return error.Canceled, + .ACCES => |err| return errnoBug(err), + .AGAIN => |err| return errnoBug(err), + .ALREADY => return error.FastOpenAlreadyInProgress, + .BADF => |err| return errnoBug(err), // File descriptor used after closed. + .CONNRESET => return error.ConnectionResetByPeer, + .DESTADDRREQ => |err| return errnoBug(err), // The socket is not connection-mode, and no peer address is set. + .FAULT => |err| return errnoBug(err), // An invalid user space address was specified for an argument. + .INVAL => |err| return errnoBug(err), // Invalid argument passed. + .ISCONN => |err| return errnoBug(err), // connection-mode socket was connected already but a recipient was specified + .MSGSIZE => |err| return errnoBug(err), + .NOBUFS => return error.SystemResources, + .NOMEM => return error.SystemResources, + .NOTSOCK => |err| return errnoBug(err), // The file descriptor sockfd does not refer to a socket. + .OPNOTSUPP => |err| return errnoBug(err), // Some bit in the flags argument is inappropriate for the socket type. + .PIPE => return error.SocketUnconnected, + .AFNOSUPPORT => return error.AddressFamilyUnsupported, + .HOSTUNREACH => return error.HostUnreachable, + .NETUNREACH => return error.NetworkUnreachable, + .NOTCONN => return error.SocketUnconnected, + .NETDOWN => return error.NetworkDown, + else => |err| return posix.unexpectedErrno(err), + } + }, } } } @@ -4567,6 +5328,7 @@ fn netWriteWindows( splat: usize, ) net.Stream.Writer.Error!usize { const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); comptime assert(native_os == .windows); var iovecs: [max_iovecs_len]ws2_32.WSABUF = undefined; @@ -4600,7 +5362,7 @@ fn netWriteWindows( }; while (true) { - try t.checkCancel(); + try current_thread.checkCancel(); var n: u32 = undefined; var overlapped: windows.OVERLAPPED = std.mem.zeroes(windows.OVERLAPPED); @@ -4707,9 +5469,10 @@ fn netInterfaceNameResolve( ) net.Interface.Name.ResolveError!net.Interface { if (!have_networking) return error.InterfaceNotFound; const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); if (native_os == .linux) { - const sock_fd = openSocketPosix(t, posix.AF.UNIX, .{ .mode = .dgram }) catch |err| switch (err) { + const sock_fd = openSocketPosix(current_thread, posix.AF.UNIX, .{ .mode = .dgram }) catch |err| switch (err) { error.ProcessFdQuotaExceeded => return error.SystemResources, error.SystemFdQuotaExceeded => return error.SystemResources, error.AddressFamilyUnsupported => return error.Unexpected, @@ -4726,32 +5489,42 @@ fn netInterfaceNameResolve( .ifru = undefined, }; + try current_thread.beginSyscall(); while (true) { - try t.checkCancel(); switch (posix.errno(posix.system.ioctl(sock_fd, posix.SIOCGIFINDEX, @intFromPtr(&ifr)))) { - .SUCCESS => return .{ .index = @bitCast(ifr.ifru.ivalue) }, - .INTR => continue, - .CANCELED => return error.Canceled, - - .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), + .SUCCESS => { + current_thread.endSyscall(); + return .{ .index = @bitCast(ifr.ifru.ivalue) }; + }, + .INTR => { + try current_thread.checkCancel(); + continue; + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .CANCELED => return error.Canceled, + .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), + } + }, } } } if (native_os == .windows) { - try t.checkCancel(); + try current_thread.checkCancel(); @panic("TODO implement netInterfaceNameResolve for Windows"); } if (builtin.link_libc) { - try t.checkCancel(); + try current_thread.checkCancel(); const index = std.c.if_nametoindex(&name.bytes); if (index == 0) return error.InterfaceNotFound; return .{ .index = @bitCast(index) }; @@ -4771,7 +5544,8 @@ fn netInterfaceNameResolveUnavailable( fn netInterfaceName(userdata: ?*anyopaque, interface: net.Interface) net.Interface.NameError!net.Interface.Name { const t: *Threaded = @ptrCast(@alignCast(userdata)); - try t.checkCancel(); + const current_thread = Thread.getCurrent(t); + try current_thread.checkCancel(); if (native_os == .linux) { _ = interface; @@ -4802,8 +5576,9 @@ fn netLookup( options: HostName.LookupOptions, ) void { const t: *Threaded = @ptrCast(@alignCast(userdata)); + const current_thread = Thread.getCurrent(t); const t_io = io(t); - resolved.putOneUncancelable(t_io, .{ .end = netLookupFallible(t, host_name, resolved, options) }); + resolved.putOneUncancelable(t_io, .{ .end = netLookupFallible(t, current_thread, host_name, resolved, options) }); } fn netLookupUnavailable( @@ -4821,6 +5596,7 @@ fn netLookupUnavailable( fn netLookupFallible( t: *Threaded, + current_thread: *Thread, host_name: HostName, resolved: *Io.Queue(HostName.LookupResult), options: HostName.LookupOptions, @@ -4866,7 +5642,7 @@ fn netLookupFallible( var res: *ws2_32.ADDRINFOEXW = undefined; const timeout: ?*ws2_32.timeval = null; while (true) { - try t.checkCancel(); // TODO make requestCancel call GetAddrInfoExCancel + try current_thread.checkCancel(); // TODO make requestCancel call GetAddrInfoExCancel // 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, cancel_handle)); @@ -5013,23 +5789,39 @@ fn netLookupFallible( .next = null, }; var res: ?*posix.addrinfo = null; + try current_thread.beginSyscall(); while (true) { - try t.checkCancel(); switch (posix.system.getaddrinfo(name_c.ptr, port_c.ptr, &hints, &res)) { - @as(posix.system.EAI, @enumFromInt(0)) => break, - .ADDRFAMILY => return error.AddressFamilyUnsupported, - .AGAIN => return error.NameServerFailure, - .FAIL => return error.NameServerFailure, - .FAMILY => return error.AddressFamilyUnsupported, - .MEMORY => return error.SystemResources, - .NODATA => return error.UnknownHostName, - .NONAME => return error.UnknownHostName, + @as(posix.system.EAI, @enumFromInt(0)) => { + current_thread.endSyscall(); + break; + }, .SYSTEM => switch (posix.errno(-1)) { - .INTR => continue, - .CANCELED => return error.Canceled, - else => |e| return posix.unexpectedErrno(e), + .INTR => { + try current_thread.checkCancel(); + continue; + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .CANCELED => return error.Canceled, + else => |inner| return posix.unexpectedErrno(inner), + } + }, + }, + else => |e| { + current_thread.endSyscall(); + switch (e) { + .ADDRFAMILY => return error.AddressFamilyUnsupported, + .AGAIN => return error.NameServerFailure, + .FAIL => return error.NameServerFailure, + .FAMILY => return error.AddressFamilyUnsupported, + .MEMORY => return error.SystemResources, + .NODATA => return error.UnknownHostName, + .NONAME => return error.UnknownHostName, + else => return error.Unexpected, + } }, - else => return error.Unexpected, } } defer if (res) |some| posix.system.freeaddrinfo(some); @@ -5726,12 +6518,12 @@ fn copyCanon(canonical_name_buffer: *[HostName.max_len]u8, name: []const u8) Hos /// ulock_wait2() uses 64-bit nano-second timeouts (with the same convention) const darwin_supports_ulock_wait2 = builtin.os.version_range.semver.min.major >= 11; -fn futexWait(t: *Threaded, ptr: *const std.atomic.Value(u32), expect: u32) Io.Cancelable!void { +fn futexWait(current_thread: *Thread, ptr: *const std.atomic.Value(u32), expect: u32) Io.Cancelable!void { @branchHint(.cold); if (builtin.cpu.arch.isWasm()) { comptime assert(builtin.cpu.has(.wasm, .atomics)); - try t.checkCancel(); + try current_thread.checkCancel(); const timeout: i64 = -1; const signed_expect: i32 = @bitCast(expect); const result = asm volatile ( @@ -5754,8 +6546,9 @@ fn futexWait(t: *Threaded, ptr: *const std.atomic.Value(u32), expect: u32) Io.Ca } else switch (native_os) { .linux => { const linux = std.os.linux; - try t.checkCancel(); + try current_thread.beginSyscall(); const rc = linux.futex_4arg(ptr, .{ .cmd = .WAIT, .private = true }, expect, null); + current_thread.endSyscall(); if (is_debug) switch (linux.errno(rc)) { .SUCCESS => {}, // notified by `wake()` .INTR => {}, // gives caller a chance to check cancellation @@ -5772,11 +6565,12 @@ fn futexWait(t: *Threaded, ptr: *const std.atomic.Value(u32), expect: u32) Io.Ca .op = .COMPARE_AND_WAIT, .NO_ERRNO = true, }; - try t.checkCancel(); + try current_thread.beginSyscall(); const status = if (darwin_supports_ulock_wait2) c.__ulock_wait2(flags, ptr, expect, 0, 0) else c.__ulock_wait(flags, ptr, expect, 0); + current_thread.endSyscall(); if (status >= 0) return; @@ -5791,7 +6585,7 @@ fn futexWait(t: *Threaded, ptr: *const std.atomic.Value(u32), expect: u32) Io.Ca }; }, .windows => { - try t.checkCancel(); + try current_thread.checkCancel(); switch (windows.ntdll.RtlWaitOnAddress(ptr, &expect, @sizeOf(@TypeOf(expect)), null)) { .SUCCESS => {}, .CANCELLED => return error.Canceled, @@ -5800,8 +6594,9 @@ fn futexWait(t: *Threaded, ptr: *const std.atomic.Value(u32), expect: u32) Io.Ca }, .freebsd => { const flags = @intFromEnum(std.c.UMTX_OP.WAIT_UINT_PRIVATE); - try t.checkCancel(); + try current_thread.beginSyscall(); const rc = std.c._umtx_op(@intFromPtr(&ptr.raw), flags, @as(c_ulong, expect), 0, 0); + current_thread.endSyscall(); if (is_debug) switch (posix.errno(rc)) { .SUCCESS => {}, .FAULT => unreachable, // one of the args points to invalid memory @@ -6050,8 +6845,9 @@ const ResetEventFutex = enum(u32) { if (state == .unset) { state = @cmpxchgStrong(ResetEventFutex, ref, state, .waiting, .acquire, .acquire) orelse .waiting; } + const current_thread = Thread.getCurrent(t); while (state == .waiting) { - try futexWait(t, @ptrCast(ref), @intFromEnum(ResetEventFutex.waiting)); + try futexWait(current_thread, @ptrCast(ref), @intFromEnum(ResetEventFutex.waiting)); state = @atomicLoad(ResetEventFutex, ref, .acquire); } assert(state == .is_set); @@ -6140,6 +6936,7 @@ const ResetEventPosix = struct { .waiting => unreachable, // Invalid state. .is_set => return, }; + const current_thread = Thread.getCurrent(t); assert(std.c.pthread_mutex_lock(&rep.mutex) == .SUCCESS); defer assert(std.c.pthread_mutex_unlock(&rep.mutex) == .SUCCESS); sw: switch (rep.state) { @@ -6148,8 +6945,9 @@ const ResetEventPosix = struct { continue :sw .waiting; }, .waiting => { - try t.checkCancel(); + try current_thread.beginSyscall(); assert(std.c.pthread_cond_wait(&rep.cond, &rep.mutex) == .SUCCESS); + current_thread.endSyscall(); continue :sw rep.state; }, .is_set => return, @@ -6222,10 +7020,10 @@ const Wsa = struct { } || Io.UnexpectedError; }; -fn initializeWsa(t: *Threaded) error{NetworkDown}!void { +fn initializeWsa(t: *Threaded) error{ NetworkDown, Canceled }!void { const t_io = io(t); const wsa = &t.wsa; - wsa.mutex.lockUncancelable(t_io); + try wsa.mutex.lock(t_io); defer wsa.mutex.unlock(t_io); switch (wsa.status) { .uninitialized => { @@ -6237,12 +7035,15 @@ fn initializeWsa(t: *Threaded) error{NetworkDown}!void { wsa.status = .initialized; return; }, - else => |err_int| switch (@as(ws2_32.WinsockError, @enumFromInt(@as(u16, @intCast(err_int))))) { - .SYSNOTREADY => wsa.init_error = error.NetworkDown, - .VERNOTSUPPORTED => wsa.init_error = error.VersionUnsupported, - .EINPROGRESS => wsa.init_error = error.BlockingOperationInProgress, - .EPROCLIM => wsa.init_error = error.ProcessFdQuotaExceeded, - else => |err| wsa.init_error = windows.unexpectedWSAError(err), + else => |err_int| { + wsa.status = .failure; + wsa.init_error = switch (@as(ws2_32.WinsockError, @enumFromInt(@as(u16, @intCast(err_int))))) { + .SYSNOTREADY => error.NetworkDown, + .VERNOTSUPPORTED => error.VersionUnsupported, + .EINPROGRESS => error.BlockingOperationInProgress, + .EPROCLIM => error.ProcessFdQuotaExceeded, + else => |err| windows.unexpectedWSAError(err), + }; }, } }, From d60760d61e19018aa354fbb2f241444db88ca51b Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Fri, 28 Nov 2025 18:07:42 -0800 Subject: [PATCH 19/53] std.Io.Threaded: tune requestCancel On a heavily loaded Linux 6.17.5, I observed a maximum of 20 attempts not acknowledged before the timeout (including exponential backoff) was sufficient, despite the heavy load. The time wasted here sleeping is mitigated by the fact that, later on, the system will likely wait for the canceled task, causing it to indefinitely yield until the canceled task finishes, and the task must acknowledge the cancel before it proceeds to that point. --- lib/std/Io/Threaded.zig | 27 ++++++++++++++++++++++----- 1 file changed, 22 insertions(+), 5 deletions(-) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index ee26c6153d56..e258d627576f 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -201,8 +201,22 @@ const Closure = struct { // We can send a signal to interrupt the syscall, but if it arrives before // the syscall instruction, it will be missed. Therefore, this code tries // again until the cancellation request is acknowledged. - const max_attempts = 3; - for (0..max_attempts) |_| { + + // 1 << 10 ns is about 1 microsecond, approximately syscall overhead. + // 1 << 20 ns is about 1 millisecond. + // 1 << 30 ns is about 1 second. + // + // On a heavily loaded Linux 6.17.5, I observed a maximum of 20 + // attempts not acknowledged before the timeout (including exponential + // backoff) was sufficient, despite the heavy load. + // + // The time wasted here sleeping is mitigated by the fact that, later + // on, the system will likely wait for the canceled task, causing it + // to indefinitely yield until the canceled task finishes, and the + // task must acknowledge the cancel before it proceeds to that point. + const max_attempts = 22; + + for (0..max_attempts) |attempt_index| { if (std.Thread.use_pthreads) { const rc = std.c.pthread_kill(signal_id, .IO); if (is_debug) assert(rc == 0); @@ -219,11 +233,14 @@ const Closure = struct { return; } - // TODO make this a nanosleep with 1 << attempt duration - std.Thread.yield() catch {}; + var timespec: posix.timespec = .{ + .sec = 0, + .nsec = @as(isize, 1) << @intCast(attempt_index), + }; + _ = posix.system.nanosleep(×pec, ×pec); switch (@atomicRmw(CancelStatus, &closure.cancel_status, .Xchg, .requested, .monotonic).unpack()) { - .requested => continue, + .requested => continue, // Retry needed in case other thread hasn't yet entered the syscall. .none, .acknowledged => return, .signal_id => |new_signal_id| signal_id = new_signal_id, } From 39ac40209b57d105dff28988a954a594e2e9dd8f Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sat, 29 Nov 2025 07:36:44 -0800 Subject: [PATCH 20/53] std.Io.Threaded: use musl's beautiful pthread_cancel semantics --- lib/std/Io/Threaded.zig | 186 ++++++++++++++++++++++------------------ lib/std/c.zig | 17 ++++ lib/std/posix.zig | 1 + 3 files changed, 121 insertions(+), 83 deletions(-) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index e258d627576f..b093bfd127fa 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -3,6 +3,7 @@ const Threaded = @This(); const builtin = @import("builtin"); const native_os = builtin.os.tag; const is_windows = native_os == .windows; +const is_musl = builtin.link_libc and builtin.abi.isMusl(); const windows = std.os.windows; const ws2_32 = std.os.windows.ws2_32; const is_debug = builtin.mode == .Debug; @@ -111,7 +112,7 @@ const Thread = struct { @atomicStore(CancelStatus, &closure.cancel_status, .acknowledged, .release); return error.Canceled; }, - .acknowledged => unreachable, + .acknowledged => return, _ => unreachable, } } @@ -128,6 +129,13 @@ const Thread = struct { ); } + fn endSyscallCanceled(thread: *Thread) Io.Cancelable { + if (thread.current_closure) |closure| { + @atomicStore(CancelStatus, &closure.cancel_status, .acknowledged, .release); + } + return error.Canceled; + } + fn currentSignalId() SignalId { return if (std.Thread.use_pthreads) std.c.pthread_self() else std.Thread.getCurrentId(); } @@ -197,6 +205,13 @@ const Closure = struct { .none, .acknowledged, .requested => return, .signal_id => |signal_id| signal_id, }; + // Musl has an undocumented extension that makes pthread_cancel have the useful, desired + // behavior of causing the next syscall to return ECANCELED. + if (is_musl) { + _ = std.c.pthread_cancel(signal_id); + return; + } + // The task will enter a blocking syscall before checking for cancellation again. // We can send a signal to interrupt the syscall, but if it arrives before // the syscall instruction, it will be missed. Therefore, this code tries @@ -218,8 +233,7 @@ const Closure = struct { for (0..max_attempts) |attempt_index| { if (std.Thread.use_pthreads) { - const rc = std.c.pthread_kill(signal_id, .IO); - if (is_debug) assert(rc == 0); + if (std.c.pthread_kill(signal_id, .IO) != 0) return; } else if (native_os == .linux) { const pid: posix.pid_t = p: { const cached_pid = @atomicLoad(Pid, &t.pid, .monotonic); @@ -228,7 +242,7 @@ const Closure = struct { @atomicStore(Pid, &t.pid, @enumFromInt(pid), .monotonic); break :p pid; }; - _ = std.os.linux.tgkill(pid, @bitCast(signal_id), .IO); + if (std.os.linux.tgkill(pid, @bitCast(signal_id), .IO) != 0) return; } else { return; } @@ -354,6 +368,11 @@ fn worker(t: *Threaded) void { while (true) { while (t.run_queue.popFirst()) |closure_node| { t.mutex.unlock(); + + // Musl has an undocumented extension that makes pthread_cancel have the useful, desired + // behavior of causing the next syscall to return ECANCELED. + if (is_musl) assert(std.c.pthread_setcancelstate(.MASKED, null) == .SUCCESS); + const closure: *Closure = @fieldParentPtr("node", closure_node); closure.start(closure, t); t.mutex.lock(); @@ -1198,10 +1217,10 @@ fn dirMakePosix(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, mode: try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { - .CANCELED => return error.Canceled, .ACCES => return error.AccessDenied, .BADF => |err| return errnoBug(err), // File descriptor used after closed. .PERM => return error.PermissionDenied, @@ -1241,25 +1260,29 @@ fn dirMakeWasi(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, mode: I try current_thread.checkCancel(); continue; }, - .CANCELED => return error.Canceled, - - .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, - .NOTCAPABLE => return error.AccessDenied, - .ILSEQ => return error.BadPathName, - else => |err| return posix.unexpectedErrno(err), + .CANCELED => return current_thread.endSyscallCanceled(), + else => |e| { + current_thread.endSyscall(); + 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, + .NOTCAPABLE => return error.AccessDenied, + .ILSEQ => return error.BadPathName, + else => |err| return posix.unexpectedErrno(err), + } + }, } } } @@ -1503,10 +1526,10 @@ fn dirStatPathLinux( try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { - .CANCELED => return error.Canceled, .ACCES => return error.AccessDenied, .BADF => |err| return errnoBug(err), // File descriptor used after closed. .FAULT => |err| return errnoBug(err), @@ -1549,10 +1572,10 @@ fn dirStatPathPosix( try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { - .CANCELED => return error.Canceled, .INVAL => |err| return errnoBug(err), .BADF => |err| return errnoBug(err), // File descriptor used after closed. .NOMEM => return error.SystemResources, @@ -1610,10 +1633,10 @@ fn dirStatPathWasi( try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { - .CANCELED => return error.Canceled, .INVAL => |err| return errnoBug(err), .BADF => |err| return errnoBug(err), // File descriptor used after closed. .NOMEM => return error.SystemResources, @@ -1656,10 +1679,10 @@ fn fileStatPosix(userdata: ?*anyopaque, file: Io.File) Io.File.StatError!Io.File try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { - .CANCELED => return error.Canceled, .INVAL => |err| return errnoBug(err), .BADF => |err| return errnoBug(err), // File descriptor used after closed. .NOMEM => return error.SystemResources, @@ -1695,10 +1718,10 @@ fn fileStatLinux(userdata: ?*anyopaque, file: Io.File) Io.File.StatError!Io.File try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { - .CANCELED => return error.Canceled, .ACCES => |err| return errnoBug(err), .BADF => |err| return errnoBug(err), // File descriptor used after closed. .FAULT => |err| return errnoBug(err), @@ -1781,10 +1804,10 @@ fn fileStatWasi(userdata: ?*anyopaque, file: Io.File) Io.File.StatError!Io.File. try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { - .CANCELED => return error.Canceled, .INVAL => |err| return errnoBug(err), .BADF => |err| return errnoBug(err), // File descriptor used after closed. .NOMEM => return error.SystemResources, @@ -1833,10 +1856,10 @@ fn dirAccessPosix( try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { - .CANCELED => return error.Canceled, .ACCES => return error.AccessDenied, .PERM => return error.PermissionDenied, .ROFS => return error.ReadOnlyFileSystem, @@ -1883,10 +1906,10 @@ fn dirAccessWasi( try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { - .CANCELED => return error.Canceled, .INVAL => |err| return errnoBug(err), .BADF => |err| return errnoBug(err), // File descriptor used after closed. .NOMEM => return error.SystemResources, @@ -2030,10 +2053,10 @@ fn dirCreateFilePosix( try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { - .CANCELED => return error.Canceled, .FAULT => |err| return errnoBug(err), .INVAL => return error.BadPathName, .BADF => |err| return errnoBug(err), // File descriptor used after closed. @@ -2085,10 +2108,10 @@ fn dirCreateFilePosix( try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { - .CANCELED => return error.Canceled, .BADF => |err| return errnoBug(err), // File descriptor used after closed. .INVAL => |err| return errnoBug(err), // invalid parameters .NOLCK => return error.SystemResources, @@ -2240,10 +2263,10 @@ fn dirCreateFileWasi( try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { - .CANCELED => return error.Canceled, .FAULT => |err| return errnoBug(err), .INVAL => return error.BadPathName, .BADF => |err| return errnoBug(err), // File descriptor used after closed. @@ -2334,10 +2357,10 @@ fn dirOpenFilePosix( try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { - .CANCELED => return error.Canceled, .FAULT => |err| return errnoBug(err), .INVAL => return error.BadPathName, .BADF => |err| return errnoBug(err), // File descriptor used after closed. @@ -2388,10 +2411,10 @@ fn dirOpenFilePosix( try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { - .CANCELED => return error.Canceled, .BADF => |err| return errnoBug(err), // File descriptor used after closed. .INVAL => |err| return errnoBug(err), // invalid parameters .NOLCK => return error.SystemResources, @@ -2417,6 +2440,7 @@ fn dirOpenFilePosix( try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |err| { current_thread.endSyscall(); return posix.unexpectedErrno(err); @@ -2437,6 +2461,7 @@ fn dirOpenFilePosix( try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |err| { current_thread.endSyscall(); return posix.unexpectedErrno(err); @@ -2635,10 +2660,10 @@ fn dirOpenFileWasi( try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { - .CANCELED => return error.Canceled, .FAULT => |err| return errnoBug(err), .BADF => |err| return errnoBug(err), // File descriptor used after closed. .ACCES => return error.AccessDenied, @@ -2719,10 +2744,10 @@ fn dirOpenDirPosix( try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { - .CANCELED => return error.Canceled, .FAULT => |err| return errnoBug(err), .INVAL => return error.BadPathName, .BADF => |err| return errnoBug(err), // File descriptor used after closed. @@ -2772,10 +2797,10 @@ fn dirOpenDirHaiku( try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { - .CANCELED => return error.Canceled, .FAULT => |err| return errnoBug(err), .INVAL => |err| return errnoBug(err), .BADF => |err| return errnoBug(err), // File descriptor used after closed. @@ -2917,10 +2942,10 @@ fn dirOpenDirWasi( try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { - .CANCELED => return error.Canceled, .FAULT => |err| return errnoBug(err), .INVAL => return error.BadPathName, .BADF => |err| return errnoBug(err), // File descriptor used after closed. @@ -2984,10 +3009,10 @@ fn fileReadStreamingPosix(userdata: ?*anyopaque, file: Io.File, data: [][]u8) Io try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { - .CANCELED => return error.Canceled, .INVAL => |err| return errnoBug(err), .FAULT => |err| return errnoBug(err), .BADF => return error.NotOpenForReading, // File operation on directory. @@ -3018,10 +3043,10 @@ fn fileReadStreamingPosix(userdata: ?*anyopaque, file: Io.File, data: [][]u8) Io try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { - .CANCELED => return error.Canceled, .INVAL => |err| return errnoBug(err), .FAULT => |err| return errnoBug(err), .SRCH => return error.ProcessNotFound, @@ -3104,10 +3129,10 @@ fn fileReadPositionalPosix(userdata: ?*anyopaque, file: Io.File, data: [][]u8, o try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { - .CANCELED => return error.Canceled, .INVAL => |err| return errnoBug(err), .FAULT => |err| return errnoBug(err), .AGAIN => |err| return errnoBug(err), @@ -3142,10 +3167,10 @@ fn fileReadPositionalPosix(userdata: ?*anyopaque, file: Io.File, data: [][]u8, o try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { - .CANCELED => return error.Canceled, .INVAL => |err| return errnoBug(err), .FAULT => |err| return errnoBug(err), .SRCH => return error.ProcessNotFound, @@ -3244,10 +3269,10 @@ fn fileSeekTo(userdata: ?*anyopaque, file: Io.File, offset: u64) Io.File.SeekErr try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { - .CANCELED => return error.Canceled, .BADF => |err| return errnoBug(err), // File descriptor used after closed. .INVAL => return error.Unseekable, .OVERFLOW => return error.Unseekable, @@ -3277,10 +3302,10 @@ fn fileSeekTo(userdata: ?*anyopaque, file: Io.File, offset: u64) Io.File.SeekErr try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { - .CANCELED => return error.Canceled, .BADF => |err| return errnoBug(err), // File descriptor used after closed. .INVAL => return error.Unseekable, .OVERFLOW => return error.Unseekable, @@ -3306,10 +3331,10 @@ fn fileSeekTo(userdata: ?*anyopaque, file: Io.File, offset: u64) Io.File.SeekErr try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { - .CANCELED => return error.Canceled, .BADF => |err| return errnoBug(err), // File descriptor used after closed. .INVAL => return error.Unseekable, .OVERFLOW => return error.Unseekable, @@ -3430,7 +3455,8 @@ fn nowWasi(userdata: ?*anyopaque, clock: Io.Clock) Io.Clock.Error!Io.Timestamp { const sleep = switch (native_os) { .windows => sleepWindows, .wasi => sleepWasi, - .linux => sleepLinux, + // Since we use musl's pthread_cancel, it's important that all the syscalls go through libc. + .linux => if (is_musl) sleepPosix else sleepLinux, else => sleepPosix, }; @@ -3462,10 +3488,10 @@ fn sleepLinux(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void { try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { - .CANCELED => return error.Canceled, .INVAL => return error.UnsupportedClock, else => |err| return posix.unexpectedErrno(err), } @@ -3540,11 +3566,9 @@ fn sleepPosix(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void { try current_thread.checkCancel(); continue; }, - else => { - // This prong handles success as well as unexpected errors. - current_thread.endSyscall(); - return; - }, + .CANCELED => return current_thread.endSyscallCanceled(), + // This prong handles success as well as unexpected errors. + else => return current_thread.endSyscall(), } } } @@ -3616,10 +3640,10 @@ fn netListenIpPosix( try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { - .CANCELED => return error.Canceled, .ADDRINUSE => return error.AddressInUse, .BADF => |err| return errnoBug(err), // File descriptor used after closed. else => |err| return posix.unexpectedErrno(err), @@ -3777,10 +3801,10 @@ fn netListenUnixPosix( try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { - .CANCELED => return error.Canceled, .ADDRINUSE => return error.AddressInUse, .BADF => |err| return errnoBug(err), // File descriptor used after closed. else => |err| return posix.unexpectedErrno(err), @@ -3901,10 +3925,10 @@ fn posixBindUnix( try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { - .CANCELED => return error.Canceled, .ACCES => return error.AccessDenied, .ADDRINUSE => return error.AddressInUse, .AFNOSUPPORT => return error.AddressFamilyUnsupported, @@ -3946,10 +3970,10 @@ fn posixBind( try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { - .CANCELED => return error.Canceled, .ADDRINUSE => return error.AddressInUse, .BADF => |err| return errnoBug(err), // File descriptor used after closed. .INVAL => |err| return errnoBug(err), // invalid parameters @@ -3982,10 +4006,10 @@ fn posixConnect( try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { - .CANCELED => return error.Canceled, .ADDRNOTAVAIL => return error.AddressUnavailable, .AFNOSUPPORT => return error.AddressFamilyUnsupported, .AGAIN, .INPROGRESS => return error.WouldBlock, @@ -4029,10 +4053,10 @@ fn posixConnectUnix( try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { - .CANCELED => return error.Canceled, .AFNOSUPPORT => return error.AddressFamilyUnsupported, .AGAIN => return error.WouldBlock, .INPROGRESS => return error.WouldBlock, @@ -4074,10 +4098,10 @@ fn posixGetSockName( try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { - .CANCELED => return error.Canceled, .BADF => |err| return errnoBug(err), // File descriptor used after closed. .FAULT => |err| return errnoBug(err), .INVAL => |err| return errnoBug(err), // invalid parameters @@ -4142,10 +4166,10 @@ fn setSocketOption(current_thread: *Thread, fd: posix.fd_t, level: i32, opt_name try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { - .CANCELED => return error.Canceled, .BADF => |err| return errnoBug(err), // File descriptor used after closed. .NOTSOCK => |err| return errnoBug(err), .INVAL => |err| return errnoBug(err), @@ -4472,12 +4496,10 @@ fn openSocketPosix( switch (posix.errno(posix.system.fcntl(fd, posix.F.SETFD, @as(usize, posix.FD_CLOEXEC)))) { .SUCCESS => break, .INTR => continue, - else => |e| { + .CANCELED => return current_thread.endSyscallCanceled(), + else => |err| { current_thread.endSyscall(); - switch (e) { - .CANCELED => return error.Canceled, - else => |err| return posix.unexpectedErrno(err), - } + return posix.unexpectedErrno(err); }, } }; @@ -4488,10 +4510,10 @@ fn openSocketPosix( try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { - .CANCELED => return error.Canceled, .AFNOSUPPORT => return error.AddressFamilyUnsupported, .INVAL => return error.ProtocolUnsupportedBySystem, .MFILE => return error.ProcessFdQuotaExceeded, @@ -4590,10 +4612,10 @@ fn netAcceptPosix(userdata: ?*anyopaque, listen_fd: net.Socket.Handle) net.Serve try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { - .CANCELED => return error.Canceled, .AGAIN => |err| return errnoBug(err), .BADF => |err| return errnoBug(err), // File descriptor used after closed. .CONNABORTED => return error.ConnectionAborted, @@ -4699,10 +4721,10 @@ fn netReadPosix(userdata: ?*anyopaque, fd: net.Socket.Handle, data: [][]u8) net. try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { - .CANCELED => return error.Canceled, .INVAL => |err| return errnoBug(err), .FAULT => |err| return errnoBug(err), .AGAIN => |err| return errnoBug(err), @@ -4732,10 +4754,10 @@ fn netReadPosix(userdata: ?*anyopaque, fd: net.Socket.Handle, data: [][]u8) net. try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { - .CANCELED => return error.Canceled, .INVAL => |err| return errnoBug(err), .FAULT => |err| return errnoBug(err), .AGAIN => |err| return errnoBug(err), @@ -4968,10 +4990,10 @@ fn netSendOne( try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { - .CANCELED => return error.Canceled, .ACCES => return error.AccessDenied, .ALREADY => return error.FastOpenAlreadyInProgress, .BADF => |err| return errnoBug(err), // File descriptor used after closed. @@ -5045,10 +5067,10 @@ fn netSendMany( try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { - .CANCELED => return error.Canceled, .AGAIN => |err| return errnoBug(err), .ALREADY => return error.FastOpenAlreadyInProgress, .BADF => |err| return errnoBug(err), // File descriptor used after closed. @@ -5178,7 +5200,7 @@ fn netReceivePosix( continue :recv; }, .INTR => continue, - .CANCELED => return .{ error.Canceled, message_i }, + .CANCELED => return .{ current_thread.endSyscallCanceled(), message_i }, .FAULT => |err| return .{ errnoBug(err), message_i }, .INVAL => |err| return .{ errnoBug(err), message_i }, @@ -5187,7 +5209,7 @@ fn netReceivePosix( } }, .INTR => continue, - .CANCELED => return .{ error.Canceled, message_i }, + .CANCELED => return .{ current_thread.endSyscallCanceled(), message_i }, .BADF => |err| return .{ errnoBug(err), message_i }, .NFILE => return .{ error.SystemFdQuotaExceeded, message_i }, @@ -5306,10 +5328,10 @@ fn netWritePosix( try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { - .CANCELED => return error.Canceled, .ACCES => |err| return errnoBug(err), .AGAIN => |err| return errnoBug(err), .ALREADY => return error.FastOpenAlreadyInProgress, @@ -5405,7 +5427,7 @@ fn netWriteWindows( }; switch (wsa_error) { .EINTR => continue, - .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return error.Canceled, + .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return current_thread.endSyscallCanceled(), .NOTINITIALISED => { try initializeWsa(t); continue; @@ -5517,10 +5539,10 @@ fn netInterfaceNameResolve( try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { - .CANCELED => return error.Canceled, .INVAL => |err| return errnoBug(err), // Bad parameters. .NOTTY => |err| return errnoBug(err), .NXIO => |err| return errnoBug(err), @@ -5818,12 +5840,10 @@ fn netLookupFallible( try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); - switch (e) { - .CANCELED => return error.Canceled, - else => |inner| return posix.unexpectedErrno(inner), - } + return posix.unexpectedErrno(e); }, }, else => |e| { diff --git a/lib/std/c.zig b/lib/std/c.zig index f22112a086f7..886c5e902cdf 100644 --- a/lib/std/c.zig +++ b/lib/std/c.zig @@ -10881,6 +10881,23 @@ pub extern "c" fn pthread_create( start_routine: *const fn (?*anyopaque) callconv(.c) ?*anyopaque, noalias arg: ?*anyopaque, ) E; +pub const pthread_cancelstate = switch (native_os) { + .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => enum(c_int) { + ENABLE = 1, + DISABLE = 0, + }, + .linux => if (native_abi.isMusl()) enum(c_int) { + ENABLE = 0, + DISABLE = 1, + MASKED = 2, + } else if (native_abi.isGnu()) enum(c_int) { + ENABLE = 0, + DISABLE = 1, + }, + else => void, +}; +pub extern "c" fn pthread_setcancelstate(pthread_cancelstate, ?*pthread_cancelstate) E; +pub extern "c" fn pthread_cancel(pthread_t) E; pub extern "c" fn pthread_attr_init(attr: *pthread_attr_t) E; pub extern "c" fn pthread_attr_setstack(attr: *pthread_attr_t, stackaddr: *anyopaque, stacksize: usize) E; pub extern "c" fn pthread_attr_setstacksize(attr: *pthread_attr_t, stacksize: usize) E; diff --git a/lib/std/posix.zig b/lib/std/posix.zig index 3e80466cd3b7..417691053774 100644 --- a/lib/std/posix.zig +++ b/lib/std/posix.zig @@ -1360,6 +1360,7 @@ pub fn writev(fd: fd_t, iov: []const iovec_const) WriteError!usize { .PIPE => return error.BrokenPipe, .CONNRESET => return error.ConnectionResetByPeer, .BUSY => return error.DeviceBusy, + .CANCELED => return error.Canceled, else => |err| return unexpectedErrno(err), } } From 9e981c3ae517738eacc0456632651aae5626d109 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sat, 29 Nov 2025 07:38:33 -0800 Subject: [PATCH 21/53] std.os.linux: delete unnecessary `@compileError` Without this, it already fails to compile with a sufficiently helpful error message. --- lib/std/os/linux.zig | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/lib/std/os/linux.zig b/lib/std/os/linux.zig index 2029356a66b5..a41ead548526 100644 --- a/lib/std/os/linux.zig +++ b/lib/std/os/linux.zig @@ -1748,9 +1748,7 @@ pub fn settimeofday(tv: *const timeval, tz: *const timezone) usize { } pub fn nanosleep(req: *const timespec, rem: ?*timespec) usize { - if (native_arch == .riscv32) { - @compileError("No nanosleep syscall on this architecture."); - } else return syscall2(.nanosleep, @intFromPtr(req), @intFromPtr(rem)); + return syscall2(.nanosleep, @intFromPtr(req), @intFromPtr(rem)); } pub fn pause() usize { From 144206856e26300cc814b3c894527b062db0e69f Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sat, 29 Nov 2025 07:52:32 -0800 Subject: [PATCH 22/53] std.Io.Threaded: fix compilation for riscv32-linux --- 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 b093bfd127fa..03670a9eb712 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -251,7 +251,11 @@ const Closure = struct { .sec = 0, .nsec = @as(isize, 1) << @intCast(attempt_index), }; - _ = posix.system.nanosleep(×pec, ×pec); + if (native_os == .linux) { + _ = std.os.linux.clock_nanosleep(posix.CLOCK.MONOTONIC, .{ .ABSTIME = false }, ×pec, ×pec); + } else { + _ = posix.system.nanosleep(×pec, ×pec); + } switch (@atomicRmw(CancelStatus, &closure.cancel_status, .Xchg, .requested, .monotonic).unpack()) { .requested => continue, // Retry needed in case other thread hasn't yet entered the syscall. From de87bad4c3240ab34ee81023e6df1ba94d6d5e6a Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sat, 29 Nov 2025 08:43:57 -0800 Subject: [PATCH 23/53] std.Io.Threaded: don't solve the cancel race after all Unfortunately, trying again until the cancellation request is acknowledged has been observed to incur a large amount of overhead, and usually strong cancellation guarantees are not needed, so the race condition is not handled here. Users who want to avoid this have this menu of options instead: * Use no libc, in which case Zig std lib can avoid the race (tracking issue: https://codeberg.org/ziglang/zig/issues/30049) * Use musl libc * Use `std.Io.Evented`. But this is not implemented yet. Tracked by - https://codeberg.org/ziglang/zig/issues/30050 - https://codeberg.org/ziglang/zig/issues/30051 glibc + threaded is the only problematic combination. --- lib/std/Io/Threaded.zig | 76 +++++++++++++++-------------------------- 1 file changed, 27 insertions(+), 49 deletions(-) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 03670a9eb712..f285a51d6ce1 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -201,7 +201,7 @@ const Closure = struct { const Start = *const fn (*Closure, *Threaded) void; fn requestCancel(closure: *Closure, t: *Threaded) void { - var signal_id = switch (@atomicRmw(CancelStatus, &closure.cancel_status, .Xchg, .requested, .monotonic).unpack()) { + const signal_id = switch (@atomicRmw(CancelStatus, &closure.cancel_status, .Xchg, .requested, .monotonic).unpack()) { .none, .acknowledged, .requested => return, .signal_id => |signal_id| signal_id, }; @@ -214,54 +214,32 @@ const Closure = struct { // The task will enter a blocking syscall before checking for cancellation again. // We can send a signal to interrupt the syscall, but if it arrives before - // the syscall instruction, it will be missed. Therefore, this code tries - // again until the cancellation request is acknowledged. - - // 1 << 10 ns is about 1 microsecond, approximately syscall overhead. - // 1 << 20 ns is about 1 millisecond. - // 1 << 30 ns is about 1 second. - // - // On a heavily loaded Linux 6.17.5, I observed a maximum of 20 - // attempts not acknowledged before the timeout (including exponential - // backoff) was sufficient, despite the heavy load. + // the syscall instruction, it will be missed. // - // The time wasted here sleeping is mitigated by the fact that, later - // on, the system will likely wait for the canceled task, causing it - // to indefinitely yield until the canceled task finishes, and the - // task must acknowledge the cancel before it proceeds to that point. - const max_attempts = 22; - - for (0..max_attempts) |attempt_index| { - if (std.Thread.use_pthreads) { - if (std.c.pthread_kill(signal_id, .IO) != 0) return; - } else if (native_os == .linux) { - const pid: posix.pid_t = p: { - const cached_pid = @atomicLoad(Pid, &t.pid, .monotonic); - if (cached_pid != .unknown) break :p @intFromEnum(cached_pid); - const pid = std.os.linux.getpid(); - @atomicStore(Pid, &t.pid, @enumFromInt(pid), .monotonic); - break :p pid; - }; - if (std.os.linux.tgkill(pid, @bitCast(signal_id), .IO) != 0) return; - } else { - return; - } - - var timespec: posix.timespec = .{ - .sec = 0, - .nsec = @as(isize, 1) << @intCast(attempt_index), + // Unfortunately, trying again until the cancellation request is + // acknowledged has been observed to incur a large amount of overhead, + // and usually strong cancellation guarantees are not needed, so the + // race condition is not handled here. Users who want to avoid this + // have this menu of options instead: + // * Use no libc, in which case Zig std lib can avoid the race (tracking + // issue: https://codeberg.org/ziglang/zig/issues/30049) + // * Use musl libc instead of glibc + // * Use `std.Io.Evented`. But this is not implemented yet. Tracked by + // - https://codeberg.org/ziglang/zig/issues/30050 + // - https://codeberg.org/ziglang/zig/issues/30051 + if (std.Thread.use_pthreads) { + if (std.c.pthread_kill(signal_id, .IO) != 0) return; + } else if (native_os == .linux) { + const pid: posix.pid_t = p: { + const cached_pid = @atomicLoad(Pid, &t.pid, .monotonic); + if (cached_pid != .unknown) break :p @intFromEnum(cached_pid); + const pid = std.os.linux.getpid(); + @atomicStore(Pid, &t.pid, @enumFromInt(pid), .monotonic); + break :p pid; }; - if (native_os == .linux) { - _ = std.os.linux.clock_nanosleep(posix.CLOCK.MONOTONIC, .{ .ABSTIME = false }, ×pec, ×pec); - } else { - _ = posix.system.nanosleep(×pec, ×pec); - } - - switch (@atomicRmw(CancelStatus, &closure.cancel_status, .Xchg, .requested, .monotonic).unpack()) { - .requested => continue, // Retry needed in case other thread hasn't yet entered the syscall. - .none, .acknowledged => return, - .signal_id => |new_signal_id| signal_id = new_signal_id, - } + if (std.os.linux.tgkill(pid, @bitCast(signal_id), .IO) != 0) return; + } else { + return; } } }; @@ -303,7 +281,7 @@ pub fn init( .mask = posix.sigemptyset(), .flags = 0, }; - if (have_sig_io) posix.sigaction(.IO, &act, &t.old_sig_io); + if (!is_musl and have_sig_io) posix.sigaction(.IO, &act, &t.old_sig_io); if (have_sig_pipe) posix.sigaction(.PIPE, &act, &t.old_sig_pipe); t.have_signal_handler = true; } @@ -341,7 +319,7 @@ pub fn deinit(t: *Threaded) void { if (ws2_32.WSACleanup() != 0) recoverableOsBugDetected(); } if (posix.Sigaction != void and t.have_signal_handler) { - if (have_sig_io) posix.sigaction(.IO, &t.old_sig_io, null); + if (!is_musl and have_sig_io) posix.sigaction(.IO, &t.old_sig_io, null); if (have_sig_pipe) posix.sigaction(.PIPE, &t.old_sig_pipe, null); } t.* = undefined; From c4f5dda135616207c24a6009e221724f5a6fa6aa Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sat, 29 Nov 2025 08:58:50 -0800 Subject: [PATCH 24/53] std.Io.Threaded: re-introduce retry logic behind config --- lib/std/Io/Threaded.zig | 94 ++++++++++++++++++++++++++++++----------- 1 file changed, 69 insertions(+), 25 deletions(-) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index f285a51d6ce1..9c818e179446 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -53,6 +53,22 @@ cpu_count_error: ?std.Thread.CpuCountError, busy_count: usize = 0, main_thread: Thread, pid: Pid = .unknown, +/// When a cancel request is made, blocking syscalls can be unblocked by +/// issuing a signal. However, if the signal arrives after the check and before +/// the syscall instruction, it is missed. +/// +/// This option solves the race condition by retrying the signal delivery +/// until it is acknowledged, with an exponential backoff. +/// +/// Unfortunately, trying again until the cancellation request is acknowledged +/// has been observed to be relatively slow, and usually strong cancellation +/// guarantees are not needed, so this defaults to off. +/// +/// Musl libc does not have this problem because of a clever, undocumented +/// extension related to pthread_cancel, which this code integrates with. +/// When compiling with no libc, `Threaded` does not yet implement the +/// equivalent trick (tracked by https://codeberg.org/ziglang/zig/issues/30049). +robust_cancel: RobustCancel = if (is_musl) .enabled else .disabled, wsa: if (is_windows) Wsa else struct {} = .{}, @@ -60,6 +76,15 @@ have_signal_handler: bool, old_sig_io: if (have_sig_io) posix.Sigaction else void, old_sig_pipe: if (have_sig_pipe) posix.Sigaction else void, +pub const RobustCancel = if (is_musl) enum { + enabled, +} else if (std.Thread.use_pthreads or native_os == .linux) enum { + enabled, + disabled, +} else enum { + disabled, +}; + pub const Pid = if (native_os == .linux) enum(posix.pid_t) { unknown = 0, _, @@ -201,7 +226,7 @@ const Closure = struct { const Start = *const fn (*Closure, *Threaded) void; fn requestCancel(closure: *Closure, t: *Threaded) void { - const signal_id = switch (@atomicRmw(CancelStatus, &closure.cancel_status, .Xchg, .requested, .monotonic).unpack()) { + var signal_id = switch (@atomicRmw(CancelStatus, &closure.cancel_status, .Xchg, .requested, .monotonic).unpack()) { .none, .acknowledged, .requested => return, .signal_id => |signal_id| signal_id, }; @@ -214,32 +239,51 @@ const Closure = struct { // The task will enter a blocking syscall before checking for cancellation again. // We can send a signal to interrupt the syscall, but if it arrives before - // the syscall instruction, it will be missed. + // the syscall instruction, it will be missed. Therefore, this code tries + // again until the cancellation request is acknowledged. + + // 1 << 10 ns is about 1 microsecond, approximately syscall overhead. + // 1 << 20 ns is about 1 millisecond. + // 1 << 30 ns is about 1 second. // - // Unfortunately, trying again until the cancellation request is - // acknowledged has been observed to incur a large amount of overhead, - // and usually strong cancellation guarantees are not needed, so the - // race condition is not handled here. Users who want to avoid this - // have this menu of options instead: - // * Use no libc, in which case Zig std lib can avoid the race (tracking - // issue: https://codeberg.org/ziglang/zig/issues/30049) - // * Use musl libc instead of glibc - // * Use `std.Io.Evented`. But this is not implemented yet. Tracked by - // - https://codeberg.org/ziglang/zig/issues/30050 - // - https://codeberg.org/ziglang/zig/issues/30051 - if (std.Thread.use_pthreads) { - if (std.c.pthread_kill(signal_id, .IO) != 0) return; - } else if (native_os == .linux) { - const pid: posix.pid_t = p: { - const cached_pid = @atomicLoad(Pid, &t.pid, .monotonic); - if (cached_pid != .unknown) break :p @intFromEnum(cached_pid); - const pid = std.os.linux.getpid(); - @atomicStore(Pid, &t.pid, @enumFromInt(pid), .monotonic); - break :p pid; + // On a heavily loaded Linux 6.17.5, I observed a maximum of 20 + // attempts not acknowledged before the timeout (including exponential + // backoff) was sufficient, despite the heavy load. + const max_attempts = 22; + + for (0..max_attempts) |attempt_index| { + if (std.Thread.use_pthreads) { + if (std.c.pthread_kill(signal_id, .IO) != 0) return; + } else if (native_os == .linux) { + const pid: posix.pid_t = p: { + const cached_pid = @atomicLoad(Pid, &t.pid, .monotonic); + if (cached_pid != .unknown) break :p @intFromEnum(cached_pid); + const pid = std.os.linux.getpid(); + @atomicStore(Pid, &t.pid, @enumFromInt(pid), .monotonic); + break :p pid; + }; + if (std.os.linux.tgkill(pid, @bitCast(signal_id), .IO) != 0) return; + } else { + return; + } + + if (t.robust_cancel != .enabled) return; + + var timespec: posix.timespec = .{ + .sec = 0, + .nsec = @as(isize, 1) << @intCast(attempt_index), }; - if (std.os.linux.tgkill(pid, @bitCast(signal_id), .IO) != 0) return; - } else { - return; + if (native_os == .linux) { + _ = std.os.linux.clock_nanosleep(posix.CLOCK.MONOTONIC, .{ .ABSTIME = false }, ×pec, ×pec); + } else { + _ = posix.system.nanosleep(×pec, ×pec); + } + + switch (@atomicRmw(CancelStatus, &closure.cancel_status, .Xchg, .requested, .monotonic).unpack()) { + .requested => continue, // Retry needed in case other thread hasn't yet entered the syscall. + .none, .acknowledged => return, + .signal_id => |new_signal_id| signal_id = new_signal_id, + } } } }; From 85053a6a36ac1d76ab226943ddecb849e21ef4c6 Mon Sep 17 00:00:00 2001 From: David Rubin Date: Sat, 29 Nov 2025 19:16:11 -0800 Subject: [PATCH 25/53] link.Elf: implement aarch64 relocation --- src/link/Elf/Atom.zig | 39 +++++++-------------------------------- src/link/aarch64.zig | 6 ++++++ 2 files changed, 13 insertions(+), 32 deletions(-) diff --git a/src/link/Elf/Atom.zig b/src/link/Elf/Atom.zig index 8d8bc71a3065..80ca049deb6c 100644 --- a/src/link/Elf/Atom.zig +++ b/src/link/Elf/Atom.zig @@ -1499,22 +1499,18 @@ const aarch64 = struct { .ABS64 => { try atom.scanReloc(symbol, rel, dynAbsRelocAction(symbol, elf_file), elf_file); }, - .ADR_PREL_PG_HI21 => { try atom.scanReloc(symbol, rel, pcRelocAction(symbol, elf_file), elf_file); }, - .ADR_GOT_PAGE => { // TODO: relax if possible symbol.flags.needs_got = true; }, - .LD64_GOT_LO12_NC, .LD64_GOTPAGE_LO15, => { symbol.flags.needs_got = true; }, - .CALL26, .JUMP26, => { @@ -1522,25 +1518,21 @@ const aarch64 = struct { symbol.flags.needs_plt = true; } }, - .TLSLE_ADD_TPREL_HI12, .TLSLE_ADD_TPREL_LO12_NC, => { if (is_dyn_lib) try atom.reportPicError(symbol, rel, elf_file); }, - .TLSIE_ADR_GOTTPREL_PAGE21, .TLSIE_LD64_GOTTPREL_LO12_NC, => { symbol.flags.needs_gottp = true; }, - .TLSGD_ADR_PAGE21, .TLSGD_ADD_LO12_NC, => { symbol.flags.needs_tlsgd = true; }, - .TLSDESC_ADR_PAGE21, .TLSDESC_LD64_LO12, .TLSDESC_ADD_LO12, @@ -1551,18 +1543,17 @@ const aarch64 = struct { symbol.flags.needs_tlsdesc = true; } }, - .ADD_ABS_LO12_NC, .ADR_PREL_LO21, - .LDST8_ABS_LO12_NC, + .CONDBR19, + .LDST128_ABS_LO12_NC, .LDST16_ABS_LO12_NC, .LDST32_ABS_LO12_NC, .LDST64_ABS_LO12_NC, - .LDST128_ABS_LO12_NC, + .LDST8_ABS_LO12_NC, .PREL32, .PREL64, => {}, - else => try atom.reportUnhandledRelocError(rel, elf_file), } } @@ -1599,7 +1590,6 @@ const aarch64 = struct { r_offset, ); }, - .CALL26, .JUMP26, => { @@ -1611,27 +1601,26 @@ const aarch64 = struct { }; util.writeBranchImm(disp, code); }, - + .CONDBR19 => { + const value = math.cast(i19, S + A - P) orelse return error.Overflow; + util.writeCondBrImm(value, code); + }, .PREL32 => { const value = math.cast(i32, S + A - P) orelse return error.Overflow; mem.writeInt(u32, code, @bitCast(value), .little); }, - .PREL64 => { const value = S + A - P; mem.writeInt(u64, code_buffer[r_offset..][0..8], @bitCast(value), .little); }, - .ADR_PREL_LO21 => { const value = math.cast(i21, S + A - P) orelse return error.Overflow; util.writeAdrInst(value, code); }, - .ADR_PREL_PG_HI21 => { // TODO: check for relaxation of ADRP+ADD util.writeAdrInst(try util.calcNumberOfPages(P, S + A), code); }, - .ADR_GOT_PAGE => if (target.flags.has_got) { util.writeAdrInst(try util.calcNumberOfPages(P, G + GOT + A), code); } else { @@ -1644,18 +1633,15 @@ const aarch64 = struct { r_offset, }); }, - .LD64_GOT_LO12_NC => { assert(target.flags.has_got); const taddr = @as(u64, @intCast(G + GOT + A)); util.writeLoadStoreRegInst(@divExact(@as(u12, @truncate(taddr)), 8), code); }, - .ADD_ABS_LO12_NC => { const taddr = @as(u64, @intCast(S + A)); util.writeAddImmInst(@truncate(taddr), code); }, - .LDST8_ABS_LO12_NC, .LDST16_ABS_LO12_NC, .LDST32_ABS_LO12_NC, @@ -1674,44 +1660,37 @@ const aarch64 = struct { }; util.writeLoadStoreRegInst(off, code); }, - .TLSLE_ADD_TPREL_HI12 => { const value = math.cast(i12, (S + A - TP) >> 12) orelse return error.Overflow; util.writeAddImmInst(@bitCast(value), code); }, - .TLSLE_ADD_TPREL_LO12_NC => { const value: i12 = @truncate(S + A - TP); util.writeAddImmInst(@bitCast(value), code); }, - .TLSIE_ADR_GOTTPREL_PAGE21 => { const S_ = target.gotTpAddress(elf_file); relocs_log.debug(" [{x} => {x}]", .{ P, S_ + A }); util.writeAdrInst(try util.calcNumberOfPages(P, S_ + A), code); }, - .TLSIE_LD64_GOTTPREL_LO12_NC => { const S_ = target.gotTpAddress(elf_file); relocs_log.debug(" [{x} => {x}]", .{ P, S_ + A }); const off: u12 = try math.divExact(u12, @truncate(@as(u64, @bitCast(S_ + A))), 8); util.writeLoadStoreRegInst(off, code); }, - .TLSGD_ADR_PAGE21 => { const S_ = target.tlsGdAddress(elf_file); relocs_log.debug(" [{x} => {x}]", .{ P, S_ + A }); util.writeAdrInst(try util.calcNumberOfPages(P, S_ + A), code); }, - .TLSGD_ADD_LO12_NC => { const S_ = target.tlsGdAddress(elf_file); relocs_log.debug(" [{x} => {x}]", .{ P, S_ + A }); const off: u12 = @truncate(@as(u64, @bitCast(S_ + A))); util.writeAddImmInst(off, code); }, - .TLSDESC_ADR_PAGE21 => { if (target.flags.has_tlsdesc) { const S_ = target.tlsDescAddress(elf_file); @@ -1722,7 +1701,6 @@ const aarch64 = struct { util.encoding.Instruction.nop().write(code); } }, - .TLSDESC_LD64_LO12 => { if (target.flags.has_tlsdesc) { const S_ = target.tlsDescAddress(elf_file); @@ -1734,7 +1712,6 @@ const aarch64 = struct { util.encoding.Instruction.nop().write(code); } }, - .TLSDESC_ADD_LO12 => { if (target.flags.has_tlsdesc) { const S_ = target.tlsDescAddress(elf_file); @@ -1747,13 +1724,11 @@ const aarch64 = struct { util.encoding.Instruction.movz(.x0, value, .{ .lsl = .@"16" }).write(code); } }, - .TLSDESC_CALL => if (!target.flags.has_tlsdesc) { relocs_log.debug(" relaxing br => movk(x0, {x})", .{S + A - TP}); const value: u16 = @bitCast(@as(i16, @truncate(S + A - TP))); util.encoding.Instruction.movk(.x0, value, .{}).write(code); }, - else => try atom.reportUnhandledRelocError(rel, elf_file), } } diff --git a/src/link/aarch64.zig b/src/link/aarch64.zig index c9defc27b319..0536316f4c59 100644 --- a/src/link/aarch64.zig +++ b/src/link/aarch64.zig @@ -29,6 +29,12 @@ pub fn writeBranchImm(disp: i28, code: *[4]u8) void { inst.write(code); } +pub fn writeCondBrImm(disp: i19, code: *[4]u8) void { + var inst: encoding.Instruction = .read(code); + inst.branch_exception_generating_system.conditional_branch_immediate.group.imm19 = @intCast(@shrExact(disp, 2)); + inst.write(code); +} + const assert = std.debug.assert; const builtin = @import("builtin"); const math = std.math; From 103467fa6c38c6bbfc6d2d9f3da28a551f6a9d17 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sun, 30 Nov 2025 19:43:53 -0800 Subject: [PATCH 26/53] std.Io.Threaded: make is_musl linux-only --- 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 9c818e179446..9d472f686e0a 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -3,7 +3,7 @@ const Threaded = @This(); const builtin = @import("builtin"); const native_os = builtin.os.tag; const is_windows = native_os == .windows; -const is_musl = builtin.link_libc and builtin.abi.isMusl(); +const is_musl = native_os == .linux and builtin.link_libc and builtin.abi.isMusl(); const windows = std.os.windows; const ws2_32 = std.os.windows.ws2_32; const is_debug = builtin.mode == .Debug; From 57f5de5b7703f9d27e8c12ca71e2db32cab43cf3 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sun, 30 Nov 2025 20:55:21 -0800 Subject: [PATCH 27/53] std.Io.Threaded: use the correct mmsghdr struct --- 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 9d472f686e0a..3b3ef98ba464 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -5052,7 +5052,7 @@ fn netSendMany( messages: []net.OutgoingMessage, flags: u32, ) net.Socket.SendError!usize { - var msg_buffer: [64]std.os.linux.mmsghdr = undefined; + var msg_buffer: [64]posix.system.mmsghdr = undefined; var addr_buffer: [msg_buffer.len]PosixAddress = undefined; var iovecs_buffer: [msg_buffer.len]posix.iovec = undefined; const min_len: usize = @min(messages.len, msg_buffer.len); From 54a84964f8e3cd015688c6929612f7438b7e9553 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 1 Dec 2025 17:47:47 -0800 Subject: [PATCH 28/53] std.os.linux: SIG enum is non-exhaustive --- lib/std/os/linux.zig | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/std/os/linux.zig b/lib/std/os/linux.zig index a41ead548526..b80d7c606f05 100644 --- a/lib/std/os/linux.zig +++ b/lib/std/os/linux.zig @@ -3771,6 +3771,7 @@ pub const SIG = if (is_mips) enum(u32) { PROF = 29, XCPU = 30, XFZ = 31, + _, } else if (is_sparc) enum(u32) { pub const BLOCK = 1; pub const UNBLOCK = 2; @@ -3816,6 +3817,7 @@ pub const SIG = if (is_mips) enum(u32) { LOST = 29, USR1 = 30, USR2 = 31, + _, } else enum(u32) { pub const BLOCK = 0; pub const UNBLOCK = 1; @@ -3859,6 +3861,7 @@ pub const SIG = if (is_mips) enum(u32) { IO = 29, PWR = 30, SYS = 31, + _, }; pub const kernel_rwf = u32; From bf0ffc45b9ca76250749097522714b7e3831a0c1 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 1 Dec 2025 19:02:09 -0800 Subject: [PATCH 29/53] std.Io.Threaded: musl: handle ECANCELED same as EINTR Otherwise the pthread_cancel can affect unrelated tasks. --- lib/std/Io/Threaded.zig | 247 +++++++++++++++------------------------- 1 file changed, 92 insertions(+), 155 deletions(-) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 3b3ef98ba464..d74ae0fb0774 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -93,10 +93,10 @@ pub const Pid = if (native_os == .linux) enum(posix.pid_t) { const Thread = struct { /// The value that needs to be passed to pthread_kill or tgkill in order to /// send a signal. - signal_id: SignalId, + signal_id: SignaleeId, current_closure: ?*Closure = null, - const SignalId = if (std.Thread.use_pthreads) std.c.pthread_t else std.Thread.Id; + const SignaleeId = if (std.Thread.use_pthreads) std.c.pthread_t else std.Thread.Id; threadlocal var current: ?*Thread = null; @@ -113,11 +113,15 @@ const Thread = struct { .acknowledged, .acq_rel, .acquire, - ) orelse return error.Canceled) { - .none => return, + ) orelse { + if (is_musl) assert(std.c.pthread_setcancelstate(.DISABLE, null) == .SUCCESS); + return error.Canceled; + }) { .requested => unreachable, .acknowledged => unreachable, - _ => return, + .none, _ => { + if (is_musl) assert(std.c.pthread_setcancelstate(.MASKED, null) == .SUCCESS); + }, } } @@ -128,13 +132,14 @@ const Thread = struct { CancelStatus, &closure.cancel_status, .none, - .fromSignalId(thread.signal_id), + .fromSignaleeId(thread.signal_id), .acq_rel, .acquire, ) orelse return) { .none => unreachable, .requested => { @atomicStore(CancelStatus, &closure.cancel_status, .acknowledged, .release); + if (is_musl) assert(std.c.pthread_setcancelstate(.DISABLE, null) == .SUCCESS); return error.Canceled; }, .acknowledged => return, @@ -144,24 +149,22 @@ const Thread = struct { fn endSyscall(thread: *Thread) void { const closure = thread.current_closure orelse return; - _ = @cmpxchgStrong( + const prev = @cmpxchgStrong( CancelStatus, &closure.cancel_status, - .fromSignalId(thread.signal_id), + .fromSignaleeId(thread.signal_id), .none, .acq_rel, .acquire, - ); - } - - fn endSyscallCanceled(thread: *Thread) Io.Cancelable { - if (thread.current_closure) |closure| { - @atomicStore(CancelStatus, &closure.cancel_status, .acknowledged, .release); + ) orelse return; + if (is_musl and prev == .requested) { + // They called pthread_cancel, but we want to disarm it since + // the next call to beginSyscall will notice requested status. + assert(std.c.pthread_setcancelstate(.DISABLE, null) == .SUCCESS); } - return error.Canceled; } - fn currentSignalId() SignalId { + fn currentSignalId() SignaleeId { return if (std.Thread.use_pthreads) std.c.pthread_self() else std.Thread.getCurrentId(); } }; @@ -184,7 +187,7 @@ const CancelStatus = enum(usize) { /// Cancellation has been acknowledged and is in progress. Signals should /// not be sent. acknowledged = std.math.maxInt(usize), - /// Stores a `Thread.SignalId` and indicates that sending a signal to this thread + /// Stores a `Thread.SignaleeId` and indicates that sending a signal to this thread /// is needed in order to cancel. This state is set before going into /// a blocking operation that needs to get unblocked via signal. _, @@ -193,7 +196,7 @@ const CancelStatus = enum(usize) { none, requested, acknowledged, - signal_id: Thread.SignalId, + signal_id: Thread.SignaleeId, }; fn unpack(cs: CancelStatus) Unpacked { @@ -210,7 +213,7 @@ const CancelStatus = enum(usize) { }; } - fn fromSignalId(signal_id: Thread.SignalId) CancelStatus { + fn fromSignaleeId(signal_id: Thread.SignaleeId) CancelStatus { return if (std.Thread.use_pthreads) @enumFromInt(@intFromPtr(signal_id)) else @@ -397,6 +400,10 @@ fn worker(t: *Threaded) void { // Musl has an undocumented extension that makes pthread_cancel have the useful, desired // behavior of causing the next syscall to return ECANCELED. + // + // The call to `requestCancel` and this can race, leading to + // ECANCELED being returned for a syscall in an unrelated task, + // which is why EINTR and ECANCELED are both handled with a check. if (is_musl) assert(std.c.pthread_setcancelstate(.MASKED, null) == .SUCCESS); const closure: *Closure = @fieldParentPtr("node", closure_node); @@ -1239,11 +1246,10 @@ fn dirMakePosix(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, mode: current_thread.endSyscall(); return; }, - .INTR => { + .INTR, .CANCELED => { try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -1282,11 +1288,10 @@ fn dirMakeWasi(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, mode: I current_thread.endSyscall(); return; }, - .INTR => { + .INTR, .CANCELED => { try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -1548,11 +1553,10 @@ fn dirStatPathLinux( current_thread.endSyscall(); return statFromLinux(&statx); }, - .INTR => { + .INTR, .CANCELED => { try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -1594,11 +1598,10 @@ fn dirStatPathPosix( current_thread.endSyscall(); return statFromPosix(&stat); }, - .INTR => { + .INTR, .CANCELED => { try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -1655,11 +1658,10 @@ fn dirStatPathWasi( current_thread.endSyscall(); return statFromWasi(&stat); }, - .INTR => { + .INTR, .CANCELED => { try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -1701,11 +1703,10 @@ fn fileStatPosix(userdata: ?*anyopaque, file: Io.File) Io.File.StatError!Io.File current_thread.endSyscall(); return statFromPosix(&stat); }, - .INTR => { + .INTR, .CANCELED => { try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -1740,11 +1741,10 @@ fn fileStatLinux(userdata: ?*anyopaque, file: Io.File) Io.File.StatError!Io.File current_thread.endSyscall(); return statFromLinux(&statx); }, - .INTR => { + .INTR, .CANCELED => { try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -1826,11 +1826,10 @@ fn fileStatWasi(userdata: ?*anyopaque, file: Io.File) Io.File.StatError!Io.File. current_thread.endSyscall(); return statFromWasi(&stat); }, - .INTR => { + .INTR, .CANCELED => { try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -1878,11 +1877,10 @@ fn dirAccessPosix( current_thread.endSyscall(); return; }, - .INTR => { + .INTR, .CANCELED => { try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -1928,11 +1926,10 @@ fn dirAccessWasi( current_thread.endSyscall(); break; }, - .INTR => { + .INTR, .CANCELED => { try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -2075,11 +2072,10 @@ fn dirCreateFilePosix( current_thread.endSyscall(); break @intCast(rc); }, - .INTR => { + .INTR, .CANCELED => { try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -2130,11 +2126,10 @@ fn dirCreateFilePosix( current_thread.endSyscall(); break; }, - .INTR => { + .INTR, .CANCELED => { try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -2159,7 +2154,7 @@ fn dirCreateFilePosix( current_thread.endSyscall(); break @intCast(rc); }, - .INTR => { + .INTR, .CANCELED => { try current_thread.checkCancel(); continue; }, @@ -2179,7 +2174,7 @@ fn dirCreateFilePosix( current_thread.endSyscall(); break; }, - .INTR => { + .INTR, .CANCELED => { try current_thread.checkCancel(); continue; }, @@ -2285,11 +2280,10 @@ fn dirCreateFileWasi( current_thread.endSyscall(); return .{ .handle = fd }; }, - .INTR => { + .INTR, .CANCELED => { try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -2379,11 +2373,10 @@ fn dirOpenFilePosix( current_thread.endSyscall(); break @intCast(rc); }, - .INTR => { + .INTR, .CANCELED => { try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -2433,11 +2426,10 @@ fn dirOpenFilePosix( current_thread.endSyscall(); break; }, - .INTR => { + .INTR, .CANCELED => { try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -2462,11 +2454,10 @@ fn dirOpenFilePosix( current_thread.endSyscall(); break @intCast(rc); }, - .INTR => { + .INTR, .CANCELED => { try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |err| { current_thread.endSyscall(); return posix.unexpectedErrno(err); @@ -2483,11 +2474,10 @@ fn dirOpenFilePosix( current_thread.endSyscall(); break; }, - .INTR => { + .INTR, .CANCELED => { try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |err| { current_thread.endSyscall(); return posix.unexpectedErrno(err); @@ -2682,11 +2672,10 @@ fn dirOpenFileWasi( current_thread.endSyscall(); return .{ .handle = fd }; }, - .INTR => { + .INTR, .CANCELED => { try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -2766,11 +2755,10 @@ fn dirOpenDirPosix( current_thread.endSyscall(); return .{ .handle = @intCast(rc) }; }, - .INTR => { + .INTR, .CANCELED => { try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -2819,11 +2807,10 @@ fn dirOpenDirHaiku( return .{ .handle = rc }; } switch (@as(posix.E, @enumFromInt(rc))) { - .INTR => { + .INTR, .CANCELED => { try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -2964,11 +2951,10 @@ fn dirOpenDirWasi( current_thread.endSyscall(); return .{ .handle = fd }; }, - .INTR => { + .INTR, .CANCELED => { try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -3031,11 +3017,10 @@ fn fileReadStreamingPosix(userdata: ?*anyopaque, file: Io.File, data: [][]u8) Io current_thread.endSyscall(); return nread; }, - .INTR => { + .INTR, .CANCELED => { try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -3065,11 +3050,10 @@ fn fileReadStreamingPosix(userdata: ?*anyopaque, file: Io.File, data: [][]u8) Io current_thread.endSyscall(); return @intCast(rc); }, - .INTR => { + .INTR, .CANCELED => { try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -3151,11 +3135,10 @@ fn fileReadPositionalPosix(userdata: ?*anyopaque, file: Io.File, data: [][]u8, o current_thread.endSyscall(); return nread; }, - .INTR => { + .INTR, .CANCELED => { try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -3189,11 +3172,10 @@ fn fileReadPositionalPosix(userdata: ?*anyopaque, file: Io.File, data: [][]u8, o current_thread.endSyscall(); return @bitCast(rc); }, - .INTR => { + .INTR, .CANCELED => { try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -3291,11 +3273,10 @@ fn fileSeekTo(userdata: ?*anyopaque, file: Io.File, offset: u64) Io.File.SeekErr current_thread.endSyscall(); return; }, - .INTR => { + .INTR, .CANCELED => { try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -3324,11 +3305,10 @@ fn fileSeekTo(userdata: ?*anyopaque, file: Io.File, offset: u64) Io.File.SeekErr current_thread.endSyscall(); return; }, - .INTR => { + .INTR, .CANCELED => { try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -3353,11 +3333,10 @@ fn fileSeekTo(userdata: ?*anyopaque, file: Io.File, offset: u64) Io.File.SeekErr current_thread.endSyscall(); return; }, - .INTR => { + .INTR, .CANCELED => { try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -3510,11 +3489,10 @@ fn sleepLinux(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void { current_thread.endSyscall(); return; }, - .INTR => { + .INTR, .CANCELED => { try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -3588,11 +3566,10 @@ fn sleepPosix(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void { try current_thread.beginSyscall(); while (true) { switch (posix.errno(posix.system.nanosleep(×pec, ×pec))) { - .INTR => { + .INTR, .CANCELED => { try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), // This prong handles success as well as unexpected errors. else => return current_thread.endSyscall(), } @@ -3662,11 +3639,10 @@ fn netListenIpPosix( current_thread.endSyscall(); break; }, - .INTR => { + .INTR, .CANCELED => { try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -3711,7 +3687,10 @@ fn netListenIpWindows( try current_thread.beginSyscall(); while (true) { const rc = ws2_32.bind(socket_handle, &storage.any, addr_len); - if (rc != ws2_32.SOCKET_ERROR) break; + if (rc != ws2_32.SOCKET_ERROR) { + current_thread.endSyscall(); + break; + } switch (ws2_32.WSAGetLastError()) { .EINTR => { try current_thread.checkCancel(); @@ -3739,7 +3718,7 @@ fn netListenIpWindows( } } - try current_thread.checkCancel(); + try current_thread.beginSyscall(); while (true) { const rc = ws2_32.listen(socket_handle, options.kernel_backlog); if (rc != ws2_32.SOCKET_ERROR) { @@ -3823,11 +3802,10 @@ fn netListenUnixPosix( current_thread.endSyscall(); break; }, - .INTR => { + .INTR, .CANCELED => { try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -3947,11 +3925,10 @@ fn posixBindUnix( current_thread.endSyscall(); break; }, - .INTR => { + .INTR, .CANCELED => { try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -3992,11 +3969,10 @@ fn posixBind( current_thread.endSyscall(); break; }, - .INTR => { + .INTR, .CANCELED => { try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -4028,11 +4004,10 @@ fn posixConnect( current_thread.endSyscall(); return; }, - .INTR => { + .INTR, .CANCELED => { try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -4075,11 +4050,10 @@ fn posixConnectUnix( current_thread.endSyscall(); return; }, - .INTR => { + .INTR, .CANCELED => { try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -4120,11 +4094,10 @@ fn posixGetSockName( current_thread.endSyscall(); break; }, - .INTR => { + .INTR, .CANCELED => { try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -4188,11 +4161,10 @@ fn setSocketOption(current_thread: *Thread, fd: posix.fd_t, level: i32, opt_name current_thread.endSyscall(); return; }, - .INTR => { + .INTR, .CANCELED => { try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -4521,8 +4493,7 @@ fn openSocketPosix( try current_thread.checkCancel(); switch (posix.errno(posix.system.fcntl(fd, posix.F.SETFD, @as(usize, posix.FD_CLOEXEC)))) { .SUCCESS => break, - .INTR => continue, - .CANCELED => return current_thread.endSyscallCanceled(), + .INTR, .CANCELED => continue, else => |err| { current_thread.endSyscall(); return posix.unexpectedErrno(err); @@ -4532,11 +4503,10 @@ fn openSocketPosix( current_thread.endSyscall(); break fd; }, - .INTR => { + .INTR, .CANCELED => { try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -4624,7 +4594,7 @@ fn netAcceptPosix(userdata: ?*anyopaque, listen_fd: net.Socket.Handle) net.Serve try current_thread.checkCancel(); switch (posix.errno(posix.system.fcntl(fd, posix.F.SETFD, @as(usize, posix.FD_CLOEXEC)))) { .SUCCESS => break, - .INTR => continue, + .INTR, .CANCELED => continue, else => |err| { current_thread.endSyscall(); return posix.unexpectedErrno(err); @@ -4634,11 +4604,10 @@ fn netAcceptPosix(userdata: ?*anyopaque, listen_fd: net.Socket.Handle) net.Serve current_thread.endSyscall(); break fd; }, - .INTR => { + .INTR, .CANCELED => { try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -4743,11 +4712,10 @@ fn netReadPosix(userdata: ?*anyopaque, fd: net.Socket.Handle, data: [][]u8) net. current_thread.endSyscall(); return n; }, - .INTR => { + .INTR, .CANCELED => { try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -4776,11 +4744,10 @@ fn netReadPosix(userdata: ?*anyopaque, fd: net.Socket.Handle, data: [][]u8) net. current_thread.endSyscall(); return @intCast(rc); }, - .INTR => { + .INTR, .CANCELED => { try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -5012,11 +4979,10 @@ fn netSendOne( message.data_len = @intCast(rc); return; }, - .INTR => { + .INTR, .CANCELED => { try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -5089,11 +5055,10 @@ fn netSendMany( } return n; }, - .INTR => { + .INTR, .CANCELED => { try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -5225,8 +5190,7 @@ fn netReceivePosix( } continue :recv; }, - .INTR => continue, - .CANCELED => return .{ current_thread.endSyscallCanceled(), message_i }, + .INTR, .CANCELED => continue, .FAULT => |err| return .{ errnoBug(err), message_i }, .INVAL => |err| return .{ errnoBug(err), message_i }, @@ -5234,8 +5198,7 @@ fn netReceivePosix( else => |err| return .{ posix.unexpectedErrno(err), message_i }, } }, - .INTR => continue, - .CANCELED => return .{ current_thread.endSyscallCanceled(), message_i }, + .INTR, .CANCELED => continue, .BADF => |err| return .{ errnoBug(err), message_i }, .NFILE => return .{ error.SystemFdQuotaExceeded, message_i }, @@ -5350,11 +5313,10 @@ fn netWritePosix( current_thread.endSyscall(); return @intCast(rc); }, - .INTR => { + .INTR, .CANCELED => { try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -5452,8 +5414,7 @@ fn netWriteWindows( else => |err| err, }; switch (wsa_error) { - .EINTR => continue, - .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return current_thread.endSyscallCanceled(), + .EINTR, .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => continue, .NOTINITIALISED => { try initializeWsa(t); continue; @@ -5561,11 +5522,10 @@ fn netInterfaceNameResolve( current_thread.endSyscall(); return .{ .index = @bitCast(ifr.ifru.ivalue) }; }, - .INTR => { + .INTR, .CANCELED => { try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -5862,11 +5822,10 @@ fn netLookupFallible( break; }, .SYSTEM => switch (posix.errno(-1)) { - .INTR => { + .INTR, .CANCELED => { try current_thread.checkCancel(); continue; }, - .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); return posix.unexpectedErrno(e); @@ -6612,15 +6571,15 @@ fn futexWait(current_thread: *Thread, ptr: *const std.atomic.Value(u32), expect: try current_thread.beginSyscall(); const rc = linux.futex_4arg(ptr, .{ .cmd = .WAIT, .private = true }, expect, null); current_thread.endSyscall(); - if (is_debug) switch (linux.errno(rc)) { + switch (linux.errno(rc)) { .SUCCESS => {}, // notified by `wake()` - .INTR => {}, // gives caller a chance to check cancellation + .INTR => {}, // caller's responsibility to retry .AGAIN => {}, // ptr.* != expect .INVAL => {}, // possibly timeout overflow - .TIMEDOUT => unreachable, - .FAULT => unreachable, // ptr was invalid - else => unreachable, - }; + .TIMEDOUT => recoverableOsBugDetected(), + .FAULT => recoverableOsBugDetected(), // ptr was invalid + else => recoverableOsBugDetected(), + } }, .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => { const c = std.c; @@ -6703,7 +6662,7 @@ pub fn futexWaitUncancelable(ptr: *const std.atomic.Value(u32), expect: u32) voi const rc = linux.futex_4arg(ptr, .{ .cmd = .WAIT, .private = true }, expect, null); switch (linux.errno(rc)) { .SUCCESS => {}, // notified by `wake()` - .INTR => {}, // gives caller a chance to check cancellation + .INTR => {}, // caller's responsibility to repeat .AGAIN => {}, // ptr.* != expect .INVAL => {}, // possibly timeout overflow .TIMEDOUT => recoverableOsBugDetected(), @@ -6757,28 +6716,6 @@ pub fn futexWaitUncancelable(ptr: *const std.atomic.Value(u32), expect: u32) voi } } -pub fn futexWaitDurationUncancelable(ptr: *const std.atomic.Value(u32), expect: u32, timeout: Io.Duration) void { - @branchHint(.cold); - - if (native_os == .linux) { - const linux = std.os.linux; - var ts = timestampToPosix(timeout.toNanoseconds()); - const rc = linux.futex_4arg(ptr, .{ .cmd = .WAIT, .private = true }, expect, &ts); - if (is_debug) switch (linux.errno(rc)) { - .SUCCESS => {}, // notified by `wake()` - .INTR => {}, // gives caller a chance to check cancellation - .AGAIN => {}, // ptr.* != expect - .TIMEDOUT => {}, - .INVAL => {}, // possibly timeout overflow - .FAULT => unreachable, // ptr was invalid - else => unreachable, - }; - return; - } else { - @compileError("TODO"); - } -} - pub fn futexWake(ptr: *const std.atomic.Value(u32), max_waiters: u32) void { @branchHint(.cold); From cf82064ebc3063c6a8e8f51388783ed1c01d6281 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 1 Dec 2025 19:07:39 -0800 Subject: [PATCH 30/53] std.Io.Threaded: don't use pthread_cancel with musl It doesn't support setting the "canceled" status to false, so once a thread has been canceled, all operations on the thread start permanently failing. --- lib/std/Io/Threaded.zig | 52 +++++++---------------------------------- 1 file changed, 8 insertions(+), 44 deletions(-) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index d74ae0fb0774..d53e8094cf7d 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -3,7 +3,6 @@ const Threaded = @This(); const builtin = @import("builtin"); const native_os = builtin.os.tag; const is_windows = native_os == .windows; -const is_musl = native_os == .linux and builtin.link_libc and builtin.abi.isMusl(); const windows = std.os.windows; const ws2_32 = std.os.windows.ws2_32; const is_debug = builtin.mode == .Debug; @@ -63,12 +62,7 @@ pid: Pid = .unknown, /// Unfortunately, trying again until the cancellation request is acknowledged /// has been observed to be relatively slow, and usually strong cancellation /// guarantees are not needed, so this defaults to off. -/// -/// Musl libc does not have this problem because of a clever, undocumented -/// extension related to pthread_cancel, which this code integrates with. -/// When compiling with no libc, `Threaded` does not yet implement the -/// equivalent trick (tracked by https://codeberg.org/ziglang/zig/issues/30049). -robust_cancel: RobustCancel = if (is_musl) .enabled else .disabled, +robust_cancel: RobustCancel = .disabled, wsa: if (is_windows) Wsa else struct {} = .{}, @@ -76,9 +70,7 @@ have_signal_handler: bool, old_sig_io: if (have_sig_io) posix.Sigaction else void, old_sig_pipe: if (have_sig_pipe) posix.Sigaction else void, -pub const RobustCancel = if (is_musl) enum { - enabled, -} else if (std.Thread.use_pthreads or native_os == .linux) enum { +pub const RobustCancel = if (std.Thread.use_pthreads or native_os == .linux) enum { enabled, disabled, } else enum { @@ -113,15 +105,10 @@ const Thread = struct { .acknowledged, .acq_rel, .acquire, - ) orelse { - if (is_musl) assert(std.c.pthread_setcancelstate(.DISABLE, null) == .SUCCESS); - return error.Canceled; - }) { + ) orelse return error.Canceled) { .requested => unreachable, .acknowledged => unreachable, - .none, _ => { - if (is_musl) assert(std.c.pthread_setcancelstate(.MASKED, null) == .SUCCESS); - }, + .none, _ => {}, } } @@ -139,7 +126,6 @@ const Thread = struct { .none => unreachable, .requested => { @atomicStore(CancelStatus, &closure.cancel_status, .acknowledged, .release); - if (is_musl) assert(std.c.pthread_setcancelstate(.DISABLE, null) == .SUCCESS); return error.Canceled; }, .acknowledged => return, @@ -149,7 +135,7 @@ const Thread = struct { fn endSyscall(thread: *Thread) void { const closure = thread.current_closure orelse return; - const prev = @cmpxchgStrong( + _ = @cmpxchgStrong( CancelStatus, &closure.cancel_status, .fromSignaleeId(thread.signal_id), @@ -157,11 +143,6 @@ const Thread = struct { .acq_rel, .acquire, ) orelse return; - if (is_musl and prev == .requested) { - // They called pthread_cancel, but we want to disarm it since - // the next call to beginSyscall will notice requested status. - assert(std.c.pthread_setcancelstate(.DISABLE, null) == .SUCCESS); - } } fn currentSignalId() SignaleeId { @@ -233,13 +214,6 @@ const Closure = struct { .none, .acknowledged, .requested => return, .signal_id => |signal_id| signal_id, }; - // Musl has an undocumented extension that makes pthread_cancel have the useful, desired - // behavior of causing the next syscall to return ECANCELED. - if (is_musl) { - _ = std.c.pthread_cancel(signal_id); - return; - } - // The task will enter a blocking syscall before checking for cancellation again. // We can send a signal to interrupt the syscall, but if it arrives before // the syscall instruction, it will be missed. Therefore, this code tries @@ -328,7 +302,7 @@ pub fn init( .mask = posix.sigemptyset(), .flags = 0, }; - if (!is_musl and have_sig_io) posix.sigaction(.IO, &act, &t.old_sig_io); + if (have_sig_io) posix.sigaction(.IO, &act, &t.old_sig_io); if (have_sig_pipe) posix.sigaction(.PIPE, &act, &t.old_sig_pipe); t.have_signal_handler = true; } @@ -366,7 +340,7 @@ pub fn deinit(t: *Threaded) void { if (ws2_32.WSACleanup() != 0) recoverableOsBugDetected(); } if (posix.Sigaction != void and t.have_signal_handler) { - if (!is_musl and have_sig_io) posix.sigaction(.IO, &t.old_sig_io, null); + if (have_sig_io) posix.sigaction(.IO, &t.old_sig_io, null); if (have_sig_pipe) posix.sigaction(.PIPE, &t.old_sig_pipe, null); } t.* = undefined; @@ -397,15 +371,6 @@ fn worker(t: *Threaded) void { while (true) { while (t.run_queue.popFirst()) |closure_node| { t.mutex.unlock(); - - // Musl has an undocumented extension that makes pthread_cancel have the useful, desired - // behavior of causing the next syscall to return ECANCELED. - // - // The call to `requestCancel` and this can race, leading to - // ECANCELED being returned for a syscall in an unrelated task, - // which is why EINTR and ECANCELED are both handled with a check. - if (is_musl) assert(std.c.pthread_setcancelstate(.MASKED, null) == .SUCCESS); - const closure: *Closure = @fieldParentPtr("node", closure_node); closure.start(closure, t); t.mutex.lock(); @@ -3460,8 +3425,7 @@ fn nowWasi(userdata: ?*anyopaque, clock: Io.Clock) Io.Clock.Error!Io.Timestamp { const sleep = switch (native_os) { .windows => sleepWindows, .wasi => sleepWasi, - // Since we use musl's pthread_cancel, it's important that all the syscalls go through libc. - .linux => if (is_musl) sleepPosix else sleepLinux, + .linux => sleepLinux, else => sleepPosix, }; From bb3f56d5d5424c7be42132c047a7146e82b49c74 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Mon, 1 Dec 2025 19:11:47 -0800 Subject: [PATCH 31/53] std.Io.Threaded: separate out ECANCELED handling again If ECANCELED occurs, it's from pthread_cancel which will *permanently* set that thread to be in a "canceling" state, which means the cancel cannot be ignored. That means it cannot be retried, like EINTR. It must be acknowledged. --- lib/std/Io/Threaded.zig | 166 +++++++++++++++++++++++++++------------- 1 file changed, 112 insertions(+), 54 deletions(-) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index d53e8094cf7d..0e17cc01d028 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -145,6 +145,13 @@ const Thread = struct { ) orelse return; } + fn endSyscallCanceled(thread: *Thread) Io.Cancelable { + if (thread.current_closure) |closure| { + @atomicStore(CancelStatus, &closure.cancel_status, .acknowledged, .release); + } + return error.Canceled; + } + fn currentSignalId() SignaleeId { return if (std.Thread.use_pthreads) std.c.pthread_self() else std.Thread.getCurrentId(); } @@ -1211,10 +1218,11 @@ fn dirMakePosix(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, mode: current_thread.endSyscall(); return; }, - .INTR, .CANCELED => { + .INTR => { try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -1253,10 +1261,11 @@ fn dirMakeWasi(userdata: ?*anyopaque, dir: Io.Dir, sub_path: []const u8, mode: I current_thread.endSyscall(); return; }, - .INTR, .CANCELED => { + .INTR => { try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -1518,10 +1527,11 @@ fn dirStatPathLinux( current_thread.endSyscall(); return statFromLinux(&statx); }, - .INTR, .CANCELED => { + .INTR => { try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -1563,10 +1573,11 @@ fn dirStatPathPosix( current_thread.endSyscall(); return statFromPosix(&stat); }, - .INTR, .CANCELED => { + .INTR => { try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -1623,10 +1634,11 @@ fn dirStatPathWasi( current_thread.endSyscall(); return statFromWasi(&stat); }, - .INTR, .CANCELED => { + .INTR => { try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -1668,10 +1680,11 @@ fn fileStatPosix(userdata: ?*anyopaque, file: Io.File) Io.File.StatError!Io.File current_thread.endSyscall(); return statFromPosix(&stat); }, - .INTR, .CANCELED => { + .INTR => { try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -1706,10 +1719,11 @@ fn fileStatLinux(userdata: ?*anyopaque, file: Io.File) Io.File.StatError!Io.File current_thread.endSyscall(); return statFromLinux(&statx); }, - .INTR, .CANCELED => { + .INTR => { try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -1791,10 +1805,11 @@ fn fileStatWasi(userdata: ?*anyopaque, file: Io.File) Io.File.StatError!Io.File. current_thread.endSyscall(); return statFromWasi(&stat); }, - .INTR, .CANCELED => { + .INTR => { try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -1842,10 +1857,11 @@ fn dirAccessPosix( current_thread.endSyscall(); return; }, - .INTR, .CANCELED => { + .INTR => { try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -1891,10 +1907,11 @@ fn dirAccessWasi( current_thread.endSyscall(); break; }, - .INTR, .CANCELED => { + .INTR => { try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -2037,10 +2054,11 @@ fn dirCreateFilePosix( current_thread.endSyscall(); break @intCast(rc); }, - .INTR, .CANCELED => { + .INTR => { try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -2091,10 +2109,11 @@ fn dirCreateFilePosix( current_thread.endSyscall(); break; }, - .INTR, .CANCELED => { + .INTR => { try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -2119,7 +2138,7 @@ fn dirCreateFilePosix( current_thread.endSyscall(); break @intCast(rc); }, - .INTR, .CANCELED => { + .INTR => { try current_thread.checkCancel(); continue; }, @@ -2139,7 +2158,7 @@ fn dirCreateFilePosix( current_thread.endSyscall(); break; }, - .INTR, .CANCELED => { + .INTR => { try current_thread.checkCancel(); continue; }, @@ -2245,10 +2264,11 @@ fn dirCreateFileWasi( current_thread.endSyscall(); return .{ .handle = fd }; }, - .INTR, .CANCELED => { + .INTR => { try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -2338,10 +2358,11 @@ fn dirOpenFilePosix( current_thread.endSyscall(); break @intCast(rc); }, - .INTR, .CANCELED => { + .INTR => { try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -2391,10 +2412,11 @@ fn dirOpenFilePosix( current_thread.endSyscall(); break; }, - .INTR, .CANCELED => { + .INTR => { try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -2419,10 +2441,11 @@ fn dirOpenFilePosix( current_thread.endSyscall(); break @intCast(rc); }, - .INTR, .CANCELED => { + .INTR => { try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |err| { current_thread.endSyscall(); return posix.unexpectedErrno(err); @@ -2439,10 +2462,11 @@ fn dirOpenFilePosix( current_thread.endSyscall(); break; }, - .INTR, .CANCELED => { + .INTR => { try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |err| { current_thread.endSyscall(); return posix.unexpectedErrno(err); @@ -2637,10 +2661,11 @@ fn dirOpenFileWasi( current_thread.endSyscall(); return .{ .handle = fd }; }, - .INTR, .CANCELED => { + .INTR => { try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -2720,10 +2745,11 @@ fn dirOpenDirPosix( current_thread.endSyscall(); return .{ .handle = @intCast(rc) }; }, - .INTR, .CANCELED => { + .INTR => { try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -2772,10 +2798,11 @@ fn dirOpenDirHaiku( return .{ .handle = rc }; } switch (@as(posix.E, @enumFromInt(rc))) { - .INTR, .CANCELED => { + .INTR => { try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -2916,10 +2943,11 @@ fn dirOpenDirWasi( current_thread.endSyscall(); return .{ .handle = fd }; }, - .INTR, .CANCELED => { + .INTR => { try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -2982,10 +3010,11 @@ fn fileReadStreamingPosix(userdata: ?*anyopaque, file: Io.File, data: [][]u8) Io current_thread.endSyscall(); return nread; }, - .INTR, .CANCELED => { + .INTR => { try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -3015,10 +3044,11 @@ fn fileReadStreamingPosix(userdata: ?*anyopaque, file: Io.File, data: [][]u8) Io current_thread.endSyscall(); return @intCast(rc); }, - .INTR, .CANCELED => { + .INTR => { try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -3100,10 +3130,11 @@ fn fileReadPositionalPosix(userdata: ?*anyopaque, file: Io.File, data: [][]u8, o current_thread.endSyscall(); return nread; }, - .INTR, .CANCELED => { + .INTR => { try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -3137,10 +3168,11 @@ fn fileReadPositionalPosix(userdata: ?*anyopaque, file: Io.File, data: [][]u8, o current_thread.endSyscall(); return @bitCast(rc); }, - .INTR, .CANCELED => { + .INTR => { try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -3238,10 +3270,11 @@ fn fileSeekTo(userdata: ?*anyopaque, file: Io.File, offset: u64) Io.File.SeekErr current_thread.endSyscall(); return; }, - .INTR, .CANCELED => { + .INTR => { try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -3270,10 +3303,11 @@ fn fileSeekTo(userdata: ?*anyopaque, file: Io.File, offset: u64) Io.File.SeekErr current_thread.endSyscall(); return; }, - .INTR, .CANCELED => { + .INTR => { try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -3298,10 +3332,11 @@ fn fileSeekTo(userdata: ?*anyopaque, file: Io.File, offset: u64) Io.File.SeekErr current_thread.endSyscall(); return; }, - .INTR, .CANCELED => { + .INTR => { try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -3453,10 +3488,11 @@ fn sleepLinux(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void { current_thread.endSyscall(); return; }, - .INTR, .CANCELED => { + .INTR => { try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -3530,10 +3566,11 @@ fn sleepPosix(userdata: ?*anyopaque, timeout: Io.Timeout) Io.SleepError!void { try current_thread.beginSyscall(); while (true) { switch (posix.errno(posix.system.nanosleep(×pec, ×pec))) { - .INTR, .CANCELED => { + .INTR => { try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), // This prong handles success as well as unexpected errors. else => return current_thread.endSyscall(), } @@ -3603,10 +3640,11 @@ fn netListenIpPosix( current_thread.endSyscall(); break; }, - .INTR, .CANCELED => { + .INTR => { try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -3766,10 +3804,11 @@ fn netListenUnixPosix( current_thread.endSyscall(); break; }, - .INTR, .CANCELED => { + .INTR => { try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -3889,10 +3928,11 @@ fn posixBindUnix( current_thread.endSyscall(); break; }, - .INTR, .CANCELED => { + .INTR => { try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -3933,10 +3973,11 @@ fn posixBind( current_thread.endSyscall(); break; }, - .INTR, .CANCELED => { + .INTR => { try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -3968,10 +4009,11 @@ fn posixConnect( current_thread.endSyscall(); return; }, - .INTR, .CANCELED => { + .INTR => { try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -4014,10 +4056,11 @@ fn posixConnectUnix( current_thread.endSyscall(); return; }, - .INTR, .CANCELED => { + .INTR => { try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -4058,10 +4101,11 @@ fn posixGetSockName( current_thread.endSyscall(); break; }, - .INTR, .CANCELED => { + .INTR => { try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -4125,10 +4169,11 @@ fn setSocketOption(current_thread: *Thread, fd: posix.fd_t, level: i32, opt_name current_thread.endSyscall(); return; }, - .INTR, .CANCELED => { + .INTR => { try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -4457,7 +4502,8 @@ fn openSocketPosix( try current_thread.checkCancel(); switch (posix.errno(posix.system.fcntl(fd, posix.F.SETFD, @as(usize, posix.FD_CLOEXEC)))) { .SUCCESS => break, - .INTR, .CANCELED => continue, + .INTR => continue, + .CANCELED => return current_thread.endSyscallCanceled(), else => |err| { current_thread.endSyscall(); return posix.unexpectedErrno(err); @@ -4467,10 +4513,11 @@ fn openSocketPosix( current_thread.endSyscall(); break fd; }, - .INTR, .CANCELED => { + .INTR => { try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -4558,7 +4605,7 @@ fn netAcceptPosix(userdata: ?*anyopaque, listen_fd: net.Socket.Handle) net.Serve try current_thread.checkCancel(); switch (posix.errno(posix.system.fcntl(fd, posix.F.SETFD, @as(usize, posix.FD_CLOEXEC)))) { .SUCCESS => break, - .INTR, .CANCELED => continue, + .INTR => continue, else => |err| { current_thread.endSyscall(); return posix.unexpectedErrno(err); @@ -4568,10 +4615,11 @@ fn netAcceptPosix(userdata: ?*anyopaque, listen_fd: net.Socket.Handle) net.Serve current_thread.endSyscall(); break fd; }, - .INTR, .CANCELED => { + .INTR => { try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -4676,10 +4724,11 @@ fn netReadPosix(userdata: ?*anyopaque, fd: net.Socket.Handle, data: [][]u8) net. current_thread.endSyscall(); return n; }, - .INTR, .CANCELED => { + .INTR => { try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -4708,10 +4757,11 @@ fn netReadPosix(userdata: ?*anyopaque, fd: net.Socket.Handle, data: [][]u8) net. current_thread.endSyscall(); return @intCast(rc); }, - .INTR, .CANCELED => { + .INTR => { try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -4943,10 +4993,11 @@ fn netSendOne( message.data_len = @intCast(rc); return; }, - .INTR, .CANCELED => { + .INTR => { try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -5019,10 +5070,11 @@ fn netSendMany( } return n; }, - .INTR, .CANCELED => { + .INTR => { try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -5154,7 +5206,8 @@ fn netReceivePosix( } continue :recv; }, - .INTR, .CANCELED => continue, + .INTR => continue, + .CANCELED => return .{ current_thread.endSyscallCanceled(), message_i }, .FAULT => |err| return .{ errnoBug(err), message_i }, .INVAL => |err| return .{ errnoBug(err), message_i }, @@ -5162,7 +5215,8 @@ fn netReceivePosix( else => |err| return .{ posix.unexpectedErrno(err), message_i }, } }, - .INTR, .CANCELED => continue, + .INTR => continue, + .CANCELED => return .{ current_thread.endSyscallCanceled(), message_i }, .BADF => |err| return .{ errnoBug(err), message_i }, .NFILE => return .{ error.SystemFdQuotaExceeded, message_i }, @@ -5277,10 +5331,11 @@ fn netWritePosix( current_thread.endSyscall(); return @intCast(rc); }, - .INTR, .CANCELED => { + .INTR => { try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -5378,7 +5433,8 @@ fn netWriteWindows( else => |err| err, }; switch (wsa_error) { - .EINTR, .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => continue, + .EINTR => continue, + .ECANCELLED, .E_CANCELLED, .OPERATION_ABORTED => return current_thread.endSyscallCanceled(), .NOTINITIALISED => { try initializeWsa(t); continue; @@ -5486,10 +5542,11 @@ fn netInterfaceNameResolve( current_thread.endSyscall(); return .{ .index = @bitCast(ifr.ifru.ivalue) }; }, - .INTR, .CANCELED => { + .INTR => { try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); switch (e) { @@ -5786,10 +5843,11 @@ fn netLookupFallible( break; }, .SYSTEM => switch (posix.errno(-1)) { - .INTR, .CANCELED => { + .INTR => { try current_thread.checkCancel(); continue; }, + .CANCELED => return current_thread.endSyscallCanceled(), else => |e| { current_thread.endSyscall(); return posix.unexpectedErrno(e); From 6fe95c28cf0909b4ea172c700a0df6416385cf94 Mon Sep 17 00:00:00 2001 From: Frank Denis Date: Tue, 2 Dec 2025 23:03:52 +0100 Subject: [PATCH 32/53] Argon2: use the std.Io interface Also reduce the memory required by tests. 4GB for every test is way too much and doesn't provide much benefits in testing the algorithms. --- lib/std/crypto/argon2.zig | 95 +++++++++++++++++++----------------- lib/std/crypto/benchmark.zig | 13 ++++- 2 files changed, 62 insertions(+), 46 deletions(-) diff --git a/lib/std/crypto/argon2.zig b/lib/std/crypto/argon2.zig index 3737d252e915..42165ca52427 100644 --- a/lib/std/crypto/argon2.zig +++ b/lib/std/crypto/argon2.zig @@ -7,12 +7,11 @@ const builtin = @import("builtin"); const blake2 = crypto.hash.blake2; const crypto = std.crypto; +const Io = std.Io; const math = std.math; const mem = std.mem; const phc_format = pwhash.phc_format; const pwhash = crypto.pwhash; - -const Thread = std.Thread; const Blake2b512 = blake2.Blake2b512; const Blocks = std.array_list.AlignedManaged([block_length]u64, .@"16"); const H0 = [Blake2b512.digest_length + 8]u8; @@ -204,20 +203,20 @@ fn initBlocks( } fn processBlocks( - allocator: mem.Allocator, blocks: *Blocks, time: u32, memory: u32, threads: u24, mode: Mode, -) KdfError!void { + io: Io, +) void { const lanes = memory / threads; const segments = lanes / sync_points; if (builtin.single_threaded or threads == 1) { processBlocksSt(blocks, time, memory, threads, mode, lanes, segments); } else { - try processBlocksMt(allocator, blocks, time, memory, threads, mode, lanes, segments); + processBlocksMt(blocks, time, memory, threads, mode, lanes, segments, io); } } @@ -243,7 +242,6 @@ fn processBlocksSt( } fn processBlocksMt( - allocator: mem.Allocator, blocks: *Blocks, time: u32, memory: u32, @@ -251,26 +249,20 @@ fn processBlocksMt( mode: Mode, lanes: u32, segments: u32, -) KdfError!void { - var threads_list = try std.array_list.Managed(Thread).initCapacity(allocator, threads); - defer threads_list.deinit(); - + io: Io, +) void { var n: u32 = 0; while (n < time) : (n += 1) { var slice: u32 = 0; while (slice < sync_points) : (slice += 1) { + var group: Io.Group = .init; var lane: u24 = 0; while (lane < threads) : (lane += 1) { - const thread = try Thread.spawn(.{}, processSegment, .{ + group.async(io, processSegment, .{ blocks, time, memory, threads, mode, lanes, segments, n, slice, lane, }); - threads_list.appendAssumeCapacity(thread); - } - lane = 0; - while (lane < threads) : (lane += 1) { - threads_list.items[lane].join(); } - threads_list.clearRetainingCapacity(); + group.wait(io); } } } @@ -489,6 +481,7 @@ pub fn kdf( salt: []const u8, params: Params, mode: Mode, + io: Io, ) KdfError!void { if (derived_key.len < 4) return KdfError.WeakParameters; if (derived_key.len > max_int) return KdfError.OutputTooLong; @@ -510,7 +503,7 @@ pub fn kdf( blocks.appendNTimesAssumeCapacity(@splat(0), memory); initBlocks(&blocks, &h0, memory, params.p); - try processBlocks(allocator, &blocks, params.t, memory, params.p, mode); + processBlocks(&blocks, params.t, memory, params.p, mode, io); finalize(&blocks, memory, params.p, derived_key); } @@ -533,6 +526,7 @@ const PhcFormatHasher = struct { params: Params, mode: Mode, buf: []u8, + io: Io, ) HasherError![]const u8 { if (params.secret != null or params.ad != null) return HasherError.InvalidEncoding; @@ -540,7 +534,7 @@ const PhcFormatHasher = struct { crypto.random.bytes(&salt); var hash: [default_hash_len]u8 = undefined; - try kdf(allocator, &hash, password, &salt, params, mode); + try kdf(allocator, &hash, password, &salt, params, mode, io); return phc_format.serialize(HashResult{ .alg_id = @tagName(mode), @@ -557,6 +551,7 @@ const PhcFormatHasher = struct { allocator: mem.Allocator, str: []const u8, password: []const u8, + io: Io, ) HasherError!void { const hash_result = try phc_format.deserialize(HashResult, str); @@ -572,7 +567,7 @@ const PhcFormatHasher = struct { if (expected_hash.len > hash_buf.len) return HasherError.InvalidEncoding; const hash = hash_buf[0..expected_hash.len]; - try kdf(allocator, hash, password, hash_result.salt.constSlice(), params, mode); + try kdf(allocator, hash, password, hash_result.salt.constSlice(), params, mode, io); if (!mem.eql(u8, hash, expected_hash)) return HasherError.PasswordVerificationFailed; } }; @@ -595,6 +590,7 @@ pub fn strHash( password: []const u8, options: HashOptions, out: []u8, + io: Io, ) Error![]const u8 { const allocator = options.allocator orelse return Error.AllocatorRequired; switch (options.encoding) { @@ -604,6 +600,7 @@ pub fn strHash( options.params, options.mode, out, + io, ), .crypt => return Error.InvalidEncoding, } @@ -621,9 +618,10 @@ pub fn strVerify( str: []const u8, password: []const u8, options: VerifyOptions, + io: Io, ) Error!void { const allocator = options.allocator orelse return Error.AllocatorRequired; - return PhcFormatHasher.verify(allocator, str, password); + return PhcFormatHasher.verify(allocator, str, password, io); } test "argon2d" { @@ -640,6 +638,7 @@ test "argon2d" { &salt, .{ .t = 3, .m = 32, .p = 4, .secret = &secret, .ad = &ad }, .argon2d, + std.testing.io, ); const want = [_]u8{ @@ -665,6 +664,7 @@ test "argon2i" { &salt, .{ .t = 3, .m = 32, .p = 4, .secret = &secret, .ad = &ad }, .argon2i, + std.testing.io, ); const want = [_]u8{ @@ -690,6 +690,7 @@ test "argon2id" { &salt, .{ .t = 3, .m = 32, .p = 4, .secret = &secret, .ad = &ad }, .argon2id, + std.testing.io, ); const want = [_]u8{ @@ -800,44 +801,44 @@ test "kdf" { .{ .mode = .argon2i, .time = 4, - .memory = 4096, + .memory = 256, .threads = 4, - .hash = "a11f7b7f3f93f02ad4bddb59ab62d121e278369288a0d0e7", + .hash = "f7dbbacbf16999e3700817a7e06f65a8db2e9fa9504ede4c", }, .{ .mode = .argon2d, .time = 4, - .memory = 4096, + .memory = 256, .threads = 4, - .hash = "935598181aa8dc2b720914aa6435ac8d3e3a4210c5b0fb2d", + .hash = "ea2970501cf49faa5ba1d2e6370204e9b57ca90a8fea937b", }, .{ .mode = .argon2id, .time = 4, - .memory = 4096, + .memory = 256, .threads = 4, - .hash = "145db9733a9f4ee43edf33c509be96b934d505a4efb33c5a", + .hash = "fbd40d5a8cb92f88c20bda4b3cdb1f9d5af1efa937032410", }, .{ .mode = .argon2i, .time = 4, - .memory = 1024, + .memory = 256, .threads = 8, - .hash = "0cdd3956aa35e6b475a7b0c63488822f774f15b43f6e6e17", + .hash = "15d3c398364e53f68fd12d19baf3f21432d964254fe27467", }, .{ .mode = .argon2d, .time = 4, - .memory = 1024, + .memory = 256, .threads = 8, - .hash = "83604fc2ad0589b9d055578f4d3cc55bc616df3578a896e9", + .hash = "23c9adc06f06e21e4612c1466a1be02627690932b02c0df0", }, .{ .mode = .argon2id, .time = 4, - .memory = 1024, + .memory = 256, .threads = 8, - .hash = "8dafa8e004f8ea96bf7c0f93eecf67a6047476143d15577f", + .hash = "f22802f8ca47be93f9954e4ce20c1e944e938fbd4a125d9d", }, .{ .mode = .argon2i, @@ -863,23 +864,23 @@ test "kdf" { .{ .mode = .argon2i, .time = 3, - .memory = 1024, + .memory = 256, .threads = 6, - .hash = "d236b29c2b2a09babee842b0dec6aa1e83ccbdea8023dced", + .hash = "ebc8f91964abd8ceab49a12963b0a9e57d635bfa2aad2884", }, .{ .mode = .argon2d, .time = 3, - .memory = 1024, + .memory = 256, .threads = 6, - .hash = "a3351b0319a53229152023d9206902f4ef59661cdca89481", + .hash = "1dd7202fd68da6675f769f4034b7a1db30d8785331954117", }, .{ .mode = .argon2id, .time = 3, - .memory = 1024, + .memory = 256, .threads = 6, - .hash = "1640b932f4b60e272f5d2207b9a9c626ffa1bd88d2349016", + .hash = "424436b6ee22a66b04b9d0cf78f190305c5c166bae8baa09", }, }; for (test_vectors) |v| { @@ -894,6 +895,7 @@ test "kdf" { salt, .{ .t = v.time, .m = v.memory, .p = v.threads }, v.mode, + std.testing.io, ); try std.testing.expectEqualSlices(u8, &dk, &want); @@ -903,6 +905,7 @@ test "kdf" { test "phc format hasher" { const allocator = std.testing.allocator; const password = "testpass"; + const io = std.testing.io; var buf: [128]u8 = undefined; const hash = try PhcFormatHasher.create( @@ -911,25 +914,29 @@ test "phc format hasher" { .{ .t = 3, .m = 32, .p = 4 }, .argon2id, &buf, + io, ); - try PhcFormatHasher.verify(allocator, hash, password); + try PhcFormatHasher.verify(allocator, hash, password, io); } test "password hash and password verify" { const allocator = std.testing.allocator; const password = "testpass"; + const io = std.testing.io; var buf: [128]u8 = undefined; const hash = try strHash( password, .{ .allocator = allocator, .params = .{ .t = 3, .m = 32, .p = 4 } }, &buf, + io, ); - try strVerify(hash, password, .{ .allocator = allocator }); + try strVerify(hash, password, .{ .allocator = allocator }, io); } test "kdf derived key length" { const allocator = std.testing.allocator; + const io = std.testing.io; const password = "testpass"; const salt = "saltsalt"; @@ -937,11 +944,11 @@ test "kdf derived key length" { const mode = Mode.argon2id; var dk1: [11]u8 = undefined; - try kdf(allocator, &dk1, password, salt, params, mode); + try kdf(allocator, &dk1, password, salt, params, mode, io); var dk2: [77]u8 = undefined; - try kdf(allocator, &dk2, password, salt, params, mode); + try kdf(allocator, &dk2, password, salt, params, mode, io); var dk3: [111]u8 = undefined; - try kdf(allocator, &dk3, password, salt, params, mode); + try kdf(allocator, &dk3, password, salt, params, mode, io); } diff --git a/lib/std/crypto/benchmark.zig b/lib/std/crypto/benchmark.zig index f2952762105c..73a1ce3ad5fd 100644 --- a/lib/std/crypto/benchmark.zig +++ b/lib/std/crypto/benchmark.zig @@ -450,6 +450,7 @@ fn benchmarkPwhash( comptime ty: anytype, comptime params: *const anyopaque, comptime count: comptime_int, + io: std.Io, ) !f64 { const password = "testpass" ** 2; const opts = ty.HashOptions{ @@ -459,12 +460,20 @@ fn benchmarkPwhash( }; var buf: [256]u8 = undefined; + const strHash = ty.strHash; + const strHashFnInfo = @typeInfo(@TypeOf(strHash)).@"fn"; + const needs_io = strHashFnInfo.params.len == 4; + var timer = try Timer.start(); const start = timer.lap(); { var i: usize = 0; while (i < count) : (i += 1) { - _ = try ty.strHash(password, opts, &buf); + if (needs_io) { + _ = try strHash(password, opts, &buf, io); + } else { + _ = try strHash(password, opts, &buf); + } mem.doNotOptimizeAway(&buf); } } @@ -623,7 +632,7 @@ pub fn main() !void { inline for (pwhashes) |H| { if (filter == null or std.mem.indexOf(u8, H.name, filter.?) != null) { - const throughput = try benchmarkPwhash(arena_allocator, H.ty, H.params, mode(64)); + const throughput = try benchmarkPwhash(arena_allocator, H.ty, H.params, mode(64), io); try stdout.print("{s:>17}: {d:10.3} s/ops\n", .{ H.name, throughput }); try stdout.flush(); } From be9649f4ea5a32fdb5b5ce6488a6ae7d30f3acb0 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Wed, 3 Dec 2025 00:24:58 +0100 Subject: [PATCH 33/53] ci: set a sensible maxrss in x86_64-windows scripts --- ci/x86_64-windows-debug.ps1 | 1 + ci/x86_64-windows-release.ps1 | 1 + 2 files changed, 2 insertions(+) diff --git a/ci/x86_64-windows-debug.ps1 b/ci/x86_64-windows-debug.ps1 index 5cd25e680df2..9675e34723bd 100644 --- a/ci/x86_64-windows-debug.ps1 +++ b/ci/x86_64-windows-debug.ps1 @@ -54,6 +54,7 @@ CheckLastExitCode Write-Output "Main test suite..." & "stage3-debug\bin\zig.exe" build test docs ` + --maxrss 32212254720 ` --zig-lib-dir "$ZIG_LIB_DIR" ` --search-prefix "$PREFIX_PATH" ` -Dstatic-llvm ` diff --git a/ci/x86_64-windows-release.ps1 b/ci/x86_64-windows-release.ps1 index 900224cf9e7d..c0ecb85c470a 100644 --- a/ci/x86_64-windows-release.ps1 +++ b/ci/x86_64-windows-release.ps1 @@ -54,6 +54,7 @@ CheckLastExitCode Write-Output "Main test suite..." & "stage3-release\bin\zig.exe" build test docs ` + --maxrss 32212254720 ` --zig-lib-dir "$ZIG_LIB_DIR" ` --search-prefix "$PREFIX_PATH" ` -Dstatic-llvm ` From cb115cf73a986ab3f246628a332d06c0deb8feda Mon Sep 17 00:00:00 2001 From: Zihad Date: Wed, 3 Dec 2025 00:09:00 +0600 Subject: [PATCH 34/53] std.process.ArgIteratorWasi: fix no-args deinit --- lib/std/process.zig | 3 +++ 1 file changed, 3 insertions(+) diff --git a/lib/std/process.zig b/lib/std/process.zig index a638eb649660..d0424d09d189 100644 --- a/lib/std/process.zig +++ b/lib/std/process.zig @@ -690,6 +690,9 @@ pub const ArgIteratorWasi = struct { /// Call to free the internal buffer of the iterator. pub fn deinit(self: *ArgIteratorWasi) void { + // Nothing is allocated when there are no args + if (self.args.len == 0) return; + const last_item = self.args[self.args.len - 1]; const last_byte_addr = @intFromPtr(last_item.ptr) + last_item.len + 1; // null terminated const first_item_ptr = self.args[0].ptr; From ad9a5187ac5d42beeece355788d5620e4a0fc6ef Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Wed, 3 Dec 2025 07:14:24 +0100 Subject: [PATCH 35/53] build: add some missing darwin tags --- build.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/build.zig b/build.zig index 51c4af91dbe2..1b986d619225 100644 --- a/build.zig +++ b/build.zig @@ -798,7 +798,7 @@ fn addCmakeCfgOptionsToExe( }; mod.linkSystemLibrary("unwind", .{}); }, - .ios, .macos, .watchos, .tvos, .visionos => { + .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => { mod.link_libcpp = true; }, .windows => { From e2a9e568b4cdc2cdb71265c0edb8d9288ded621b Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Fri, 28 Nov 2025 01:35:10 +0100 Subject: [PATCH 36/53] build: add -Dskip-spirv and -Dskip-wasm options --- build.zig | 14 ++++++++++++++ test/src/Cases.zig | 5 +++++ test/src/Libc.zig | 3 +++ test/tests.zig | 9 +++++++++ 4 files changed, 31 insertions(+) diff --git a/build.zig b/build.zig index 1b986d619225..3d826485abd0 100644 --- a/build.zig +++ b/build.zig @@ -91,6 +91,8 @@ pub fn build(b: *std.Build) !void { const skip_libc = b.option(bool, "skip-libc", "Main test suite skips tests that link libc") orelse false; const skip_single_threaded = b.option(bool, "skip-single-threaded", "Main test suite skips tests that are single-threaded") orelse false; const skip_compile_errors = b.option(bool, "skip-compile-errors", "Main test suite skips compile error tests") orelse false; + const skip_spirv = b.option(bool, "skip-spirv", "Main test suite skips targets with spirv32/spirv64 architecture") orelse false; + const skip_wasm = b.option(bool, "skip-wasm", "Main test suite skips targets with wasm32/wasm64 architecture") orelse false; const skip_freebsd = b.option(bool, "skip-freebsd", "Main test suite skips targets with freebsd OS") orelse false; const skip_netbsd = b.option(bool, "skip-netbsd", "Main test suite skips targets with netbsd OS") orelse false; const skip_windows = b.option(bool, "skip-windows", "Main test suite skips targets with windows OS") orelse false; @@ -421,6 +423,8 @@ pub fn build(b: *std.Build) !void { .test_target_filters = test_target_filters, .skip_compile_errors = skip_compile_errors, .skip_non_native = skip_non_native, + .skip_spirv = skip_spirv, + .skip_wasm = skip_wasm, .skip_freebsd = skip_freebsd, .skip_netbsd = skip_netbsd, .skip_windows = skip_windows, @@ -452,6 +456,8 @@ pub fn build(b: *std.Build) !void { .skip_single_threaded = skip_single_threaded, .skip_non_native = skip_non_native, .test_default_only = no_matrix, + .skip_spirv = skip_spirv, + .skip_wasm = skip_wasm, .skip_freebsd = skip_freebsd, .skip_netbsd = skip_netbsd, .skip_windows = skip_windows, @@ -475,6 +481,8 @@ pub fn build(b: *std.Build) !void { .skip_single_threaded = true, .skip_non_native = skip_non_native, .test_default_only = no_matrix, + .skip_spirv = skip_spirv, + .skip_wasm = skip_wasm, .skip_freebsd = skip_freebsd, .skip_netbsd = skip_netbsd, .skip_windows = skip_windows, @@ -497,6 +505,8 @@ pub fn build(b: *std.Build) !void { .skip_single_threaded = true, .skip_non_native = skip_non_native, .test_default_only = no_matrix, + .skip_spirv = skip_spirv, + .skip_wasm = skip_wasm, .skip_freebsd = skip_freebsd, .skip_netbsd = skip_netbsd, .skip_windows = skip_windows, @@ -519,6 +529,8 @@ pub fn build(b: *std.Build) !void { .skip_single_threaded = skip_single_threaded, .skip_non_native = skip_non_native, .test_default_only = no_matrix, + .skip_spirv = skip_spirv, + .skip_wasm = skip_wasm, .skip_freebsd = skip_freebsd, .skip_netbsd = skip_netbsd, .skip_windows = skip_windows, @@ -560,6 +572,7 @@ pub fn build(b: *std.Build) !void { test_step.dependOn(tests.addCAbiTests(b, .{ .test_target_filters = test_target_filters, .skip_non_native = skip_non_native, + .skip_wasm = skip_wasm, .skip_freebsd = skip_freebsd, .skip_netbsd = skip_netbsd, .skip_windows = skip_windows, @@ -616,6 +629,7 @@ pub fn build(b: *std.Build) !void { .optimize_modes = optimization_modes, .test_filters = test_filters, .test_target_filters = test_target_filters, + .skip_wasm = skip_wasm, // Highest RSS observed in any test case was exactly 1802878976 on x86_64-linux. .max_rss = 2253598720, })) |test_libc_step| test_step.dependOn(test_libc_step); diff --git a/test/src/Cases.zig b/test/src/Cases.zig index 95ee7e21e751..f39820c7b667 100644 --- a/test/src/Cases.zig +++ b/test/src/Cases.zig @@ -444,6 +444,8 @@ pub const CaseTestOptions = struct { test_target_filters: []const []const u8, skip_compile_errors: bool, skip_non_native: bool, + skip_spirv: bool, + skip_wasm: bool, skip_freebsd: bool, skip_netbsd: bool, skip_windows: bool, @@ -472,6 +474,9 @@ pub fn lowerToBuildSteps( if (options.skip_non_native and !case.target.query.isNative()) continue; + if (options.skip_spirv and case.target.query.cpu_arch != null and case.target.query.cpu_arch.?.isSpirV()) continue; + if (options.skip_wasm and case.target.query.cpu_arch != null and case.target.query.cpu_arch.?.isWasm()) continue; + if (options.skip_freebsd and case.target.query.os_tag == .freebsd) continue; if (options.skip_netbsd and case.target.query.os_tag == .netbsd) continue; if (options.skip_windows and case.target.query.os_tag == .windows) continue; diff --git a/test/src/Libc.zig b/test/src/Libc.zig index 708e4c7bb800..7c61b58e0289 100644 --- a/test/src/Libc.zig +++ b/test/src/Libc.zig @@ -10,6 +10,7 @@ pub const Options = struct { optimize_modes: []const std.builtin.OptimizeMode, test_filters: []const []const u8, test_target_filters: []const []const u8, + skip_wasm: bool, max_rss: usize, }; @@ -41,6 +42,8 @@ pub fn addLibcTestCase( } pub fn addTarget(libc: *const Libc, target: std.Build.ResolvedTarget) void { + if (libc.options.skip_wasm and target.query.cpu_arch != null and target.query.cpu_arch.?.isWasm()) return; + if (libc.options.test_target_filters.len > 0) { const triple_txt = target.query.zigTriple(libc.b.allocator) catch @panic("OOM"); for (libc.options.test_target_filters) |filter| { diff --git a/test/tests.zig b/test/tests.zig index 4ea42c2eb563..9dae4786bd1d 100644 --- a/test/tests.zig +++ b/test/tests.zig @@ -2246,6 +2246,8 @@ const ModuleTestOptions = struct { test_default_only: bool, skip_single_threaded: bool, skip_non_native: bool, + skip_spirv: bool, + skip_wasm: bool, skip_freebsd: bool, skip_netbsd: bool, skip_windows: bool, @@ -2281,6 +2283,9 @@ pub fn addModuleTests(b: *std.Build, options: ModuleTestOptions) *Step { if (options.skip_non_native and !test_target.target.isNative()) continue; + if (options.skip_spirv and test_target.target.cpu_arch != null and test_target.target.cpu_arch.?.isSpirV()) continue; + if (options.skip_wasm and test_target.target.cpu_arch != null and test_target.target.cpu_arch.?.isWasm()) continue; + if (options.skip_freebsd and test_target.target.os_tag == .freebsd) continue; if (options.skip_netbsd and test_target.target.os_tag == .netbsd) continue; if (options.skip_windows and test_target.target.os_tag == .windows) continue; @@ -2519,6 +2524,7 @@ pub fn wouldUseLlvm(use_llvm: ?bool, query: std.Target.Query, optimize_mode: Opt const CAbiTestOptions = struct { test_target_filters: []const []const u8, skip_non_native: bool, + skip_wasm: bool, skip_freebsd: bool, skip_netbsd: bool, skip_windows: bool, @@ -2538,6 +2544,9 @@ pub fn addCAbiTests(b: *std.Build, options: CAbiTestOptions) *Step { for (c_abi_targets) |c_abi_target| { if (options.skip_non_native and !c_abi_target.target.isNative()) continue; + + if (options.skip_wasm and c_abi_target.target.cpu_arch != null and c_abi_target.target.cpu_arch.?.isWasm()) continue; + if (options.skip_freebsd and c_abi_target.target.os_tag == .freebsd) continue; if (options.skip_netbsd and c_abi_target.target.os_tag == .netbsd) continue; if (options.skip_windows and c_abi_target.target.os_tag == .windows) continue; From c21ce534943464c99c8867aa77881e347a5ba700 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Fri, 28 Nov 2025 01:35:48 +0100 Subject: [PATCH 37/53] ci: skip spirv and wasm tests on x86_64-freebsd These are already built and run on x86_64-linux. --- ci/x86_64-freebsd-debug.sh | 2 ++ ci/x86_64-freebsd-release.sh | 2 ++ 2 files changed, 4 insertions(+) diff --git a/ci/x86_64-freebsd-debug.sh b/ci/x86_64-freebsd-debug.sh index ae2fdbce61d6..570f46fb0919 100755 --- a/ci/x86_64-freebsd-debug.sh +++ b/ci/x86_64-freebsd-debug.sh @@ -46,6 +46,8 @@ ninja install stage3-debug/bin/zig build test docs \ --maxrss 42949672960 \ -Dstatic-llvm \ + -Dskip-spirv \ + -Dskip-wasm \ -Dskip-linux \ -Dskip-netbsd \ -Dskip-windows \ diff --git a/ci/x86_64-freebsd-release.sh b/ci/x86_64-freebsd-release.sh index 39d653cc97b4..d7641fb86ccd 100755 --- a/ci/x86_64-freebsd-release.sh +++ b/ci/x86_64-freebsd-release.sh @@ -46,6 +46,8 @@ ninja install stage3-release/bin/zig build test docs \ --maxrss 42949672960 \ -Dstatic-llvm \ + -Dskip-spirv \ + -Dskip-wasm \ -Dskip-linux \ -Dskip-netbsd \ -Dskip-windows \ From e270c97ed119fb179bf0156caad6c6ea31453bd5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Wed, 3 Dec 2025 11:48:29 +0100 Subject: [PATCH 38/53] ci: don't skip release mode tests on x86_64-windows-debug pulsar is much faster than george so we don't need to do this anymore. --- ci/x86_64-windows-debug.ps1 | 1 - 1 file changed, 1 deletion(-) diff --git a/ci/x86_64-windows-debug.ps1 b/ci/x86_64-windows-debug.ps1 index 9675e34723bd..31389a1b994a 100644 --- a/ci/x86_64-windows-debug.ps1 +++ b/ci/x86_64-windows-debug.ps1 @@ -59,7 +59,6 @@ Write-Output "Main test suite..." --search-prefix "$PREFIX_PATH" ` -Dstatic-llvm ` -Dskip-non-native ` - -Dskip-release ` -Dskip-test-incremental ` -Denable-symlinks-windows ` --test-timeout 30m From 9eed87f93ea9afeb16bede0b3c162a7055d89a76 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Wed, 3 Dec 2025 03:30:06 +0100 Subject: [PATCH 39/53] std.process.Child: enable rusage collection for freebsd, illumos, serenity --- lib/std/process/Child.zig | 28 +++++++++++++++++++++++++--- 1 file changed, 25 insertions(+), 3 deletions(-) diff --git a/lib/std/process/Child.zig b/lib/std/process/Child.zig index da8d803d6056..7d56e92be654 100644 --- a/lib/std/process/Child.zig +++ b/lib/std/process/Child.zig @@ -122,7 +122,7 @@ pub const ResourceUsageStatistics = struct { /// if available. pub inline fn getMaxRss(rus: ResourceUsageStatistics) ?usize { switch (native_os) { - .linux => { + .freebsd, .illumos, .linux, .serenity => { if (rus.rusage) |ru| { return @as(usize, @intCast(ru.maxrss)) * 1024; } else { @@ -149,7 +149,18 @@ pub const ResourceUsageStatistics = struct { } const rusage_init = switch (native_os) { - .linux, .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => @as(?posix.rusage, null), + .freebsd, + .illumos, + .linux, + .serenity, + .driverkit, + .ios, + .maccatalyst, + .macos, + .tvos, + .visionos, + .watchos, + => @as(?posix.rusage, null), .windows => @as(?windows.VM_COUNTERS, null), else => {}, }; @@ -486,7 +497,18 @@ fn waitUnwrappedPosix(self: *ChildProcess) void { const res: posix.WaitPidResult = res: { if (self.request_resource_usage_statistics) { switch (native_os) { - .linux, .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos => { + .freebsd, + .illumos, + .linux, + .serenity, + .driverkit, + .ios, + .maccatalyst, + .macos, + .tvos, + .visionos, + .watchos, + => { var ru: posix.rusage = undefined; const res = posix.wait4(self.id, 0, &ru); self.resource_usage_statistics.rusage = ru; From 2659fadb959b8ad0a5db374b3d7b3892ea0ee4f7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Wed, 3 Dec 2025 03:36:02 +0100 Subject: [PATCH 40/53] std.c: add rusage for dragonfly, netbsd, openbsd --- lib/std/c.zig | 23 ++++++++++++++++++++++- 1 file changed, 22 insertions(+), 1 deletion(-) diff --git a/lib/std/c.zig b/lib/std/c.zig index 886c5e902cdf..f3fdeb6d95d5 100644 --- a/lib/std/c.zig +++ b/lib/std/c.zig @@ -4471,7 +4471,7 @@ pub const rusage = switch (native_os) { pub const SELF = 1; pub const CHILDREN = 2; }, - .freebsd => extern struct { + .freebsd, .openbsd => extern struct { utime: timeval, stime: timeval, maxrss: c_long, @@ -4493,6 +4493,27 @@ pub const rusage = switch (native_os) { pub const CHILDREN = -1; pub const THREAD = 1; }, + .dragonfly, .netbsd => extern struct { + utime: timeval, + stime: timeval, + maxrss: c_long, + ixrss: c_long, + idrss: c_long, + isrss: c_long, + minflt: c_long, + majflt: c_long, + nswap: c_long, + inblock: c_long, + oublock: c_long, + msgsnd: c_long, + msgrcv: c_long, + nsignals: c_long, + nvcsw: c_long, + nivcsw: c_long, + + pub const SELF = 0; + pub const CHILDREN = -1; + }, else => void, }; From 44543800a59968a4c7a2cdcf10837bc17b52553d Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Wed, 3 Dec 2025 03:39:23 +0100 Subject: [PATCH 41/53] std.process.Child: enable rusage collection for dragonfly, netbsd, openbsd --- lib/std/process/Child.zig | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/lib/std/process/Child.zig b/lib/std/process/Child.zig index 7d56e92be654..7df2d897988c 100644 --- a/lib/std/process/Child.zig +++ b/lib/std/process/Child.zig @@ -122,7 +122,7 @@ pub const ResourceUsageStatistics = struct { /// if available. pub inline fn getMaxRss(rus: ResourceUsageStatistics) ?usize { switch (native_os) { - .freebsd, .illumos, .linux, .serenity => { + .dragonfly, .freebsd, .netbsd, .openbsd, .illumos, .linux, .serenity => { if (rus.rusage) |ru| { return @as(usize, @intCast(ru.maxrss)) * 1024; } else { @@ -149,7 +149,10 @@ pub const ResourceUsageStatistics = struct { } const rusage_init = switch (native_os) { + .dragonfly, .freebsd, + .netbsd, + .openbsd, .illumos, .linux, .serenity, @@ -497,7 +500,10 @@ fn waitUnwrappedPosix(self: *ChildProcess) void { const res: posix.WaitPidResult = res: { if (self.request_resource_usage_statistics) { switch (native_os) { + .dragonfly, .freebsd, + .netbsd, + .openbsd, .illumos, .linux, .serenity, From 2728eb5d5ed0a7cdf33140b31ffa364eaffd7afd Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Wed, 3 Dec 2025 01:06:31 +0100 Subject: [PATCH 42/53] build: adjust max_rss on a per-CI-host basis This avoids pessimizing concurrency on all machines due to e.g. the macOS machine having high memory usage across the board due to 16K page size. This also adds max_rss to test-unit and test-c-abi since those tend to eat a decent chunk of memory too. --- build.zig | 166 +++++++++++++++++++++++++++++++++++++++++++++++-- test/tests.zig | 9 ++- 2 files changed, 168 insertions(+), 7 deletions(-) diff --git a/build.zig b/build.zig index 3d826485abd0..114428bcba76 100644 --- a/build.zig +++ b/build.zig @@ -465,8 +465,29 @@ pub fn build(b: *std.Build) !void { .skip_linux = skip_linux, .skip_llvm = skip_llvm, .skip_libc = skip_libc, - // 3888779264 was observed on an x86_64-linux-gnu host. - .max_rss = 4000000000, + .max_rss = switch (b.graph.host.result.os.tag) { + .freebsd => switch (b.graph.host.result.cpu.arch) { + .x86_64 => 1_060_217_241, + else => 1_100_000_000, + }, + .linux => switch (b.graph.host.result.cpu.arch) { + .aarch64 => 659_809_075, + .loongarch64 => 598_902_374, + .riscv64 => 731_258_880, + .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, + }, })); test_modules_step.dependOn(tests.addModuleTests(b, .{ @@ -491,6 +512,29 @@ 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 => switch (b.graph.host.result.cpu.arch) { + .x86_64 => 743_802_470, + else => 800_000_000, + }, + .linux => switch (b.graph.host.result.cpu.arch) { + .aarch64 => 639_565_414, + .loongarch64 => 598_884_352, + .riscv64 => 636_429_516, + .s390x => 574_166_630, + .x86_64 => 764_861_644, + else => 800_000_000, + }, + .macos => switch (b.graph.host.result.cpu.arch) { + .aarch64 => 701_413_785, + else => 800_000_000, + }, + .windows => switch (b.graph.host.result.cpu.arch) { + .x86_64 => 536_414_208, + else => 600_000_000, + }, + else => 800_000_000, + }, })); test_modules_step.dependOn(tests.addModuleTests(b, .{ @@ -515,6 +559,29 @@ 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 => switch (b.graph.host.result.cpu.arch) { + .x86_64 => 557_892_403, + else => 600_000_000, + }, + .linux => switch (b.graph.host.result.cpu.arch) { + .aarch64 => 615_302_758, + .loongarch64 => 598_974_464, + .riscv64 => 382_786_764, + .s390x => 395_555_635, + .x86_64 => 692_348_518, + else => 700_000_000, + }, + .macos => switch (b.graph.host.result.cpu.arch) { + .aarch64 => 451_389_030, + else => 500_000_000, + }, + .windows => switch (b.graph.host.result.cpu.arch) { + .x86_64 => 367_747_072, + else => 400_000_000, + }, + else => 700_000_000, + }, })); test_modules_step.dependOn(tests.addModuleTests(b, .{ @@ -538,8 +605,29 @@ pub fn build(b: *std.Build) !void { .skip_linux = skip_linux, .skip_llvm = skip_llvm, .skip_libc = skip_libc, - // I observed a value of 5605064704 on the M2 CI. - .max_rss = 6165571174, + .max_rss = switch (b.graph.host.result.os.tag) { + .freebsd => switch (b.graph.host.result.cpu.arch) { + .x86_64 => 3_756_422_348, + else => 3_800_000_000, + }, + .linux => switch (b.graph.host.result.cpu.arch) { + .aarch64 => 6_732_817_203, + .loongarch64 => 3_216_349_593, + .riscv64 => 3_570_899_763, + .s390x => 3_652_514_201, + .x86_64 => 3_249_546_854, + else => 6_800_000_000, + }, + .macos => switch (b.graph.host.result.cpu.arch) { + .aarch64 => 8_273_795_481, + else => 8_300_000_000, + }, + .windows => switch (b.graph.host.result.cpu.arch) { + .x86_64 => 3_750_236_160, + else => 3_800_000_000, + }, + else => 8_300_000_000, + }, })); const unit_tests_step = b.step("test-unit", "Run the compiler source unit tests"); @@ -555,6 +643,29 @@ pub fn build(b: *std.Build) !void { .use_llvm = use_llvm, .use_lld = use_llvm, .zig_lib_dir = b.path("lib"), + .max_rss = switch (b.graph.host.result.os.tag) { + .freebsd => switch (b.graph.host.result.cpu.arch) { + .x86_64 => 2_188_099_584, + else => 2_200_000_000, + }, + .linux => switch (b.graph.host.result.cpu.arch) { + .aarch64 => 1_991_934_771, + .loongarch64 => 1_844_538_572, + .riscv64 => 2_459_003_289, + .s390x => 1_781_248_409, + .x86_64 => 977_192_550, + else => 2_500_000_000, + }, + .macos => switch (b.graph.host.result.cpu.arch) { + .aarch64 => 2_062_393_344, + else => 2_100_000_000, + }, + .windows => switch (b.graph.host.result.cpu.arch) { + .x86_64 => 1_953_087_488, + else => 2_000_000_000, + }, + else => 2_500_000_000, + }, }); if (link_libc) { unit_tests.root_module.link_libc = true; @@ -580,6 +691,29 @@ pub fn build(b: *std.Build) !void { .skip_linux = skip_linux, .skip_llvm = skip_llvm, .skip_release = skip_release, + .max_rss = switch (b.graph.host.result.os.tag) { + .freebsd => switch (b.graph.host.result.cpu.arch) { + .x86_64 => 727_221_862, + else => 800_000_000, + }, + .linux => switch (b.graph.host.result.cpu.arch) { + .aarch64 => 1_318_185_369, + .loongarch64 => 1_422_904_524, + .riscv64 => 449_924_710, + .s390x => 1_946_743_603, + .x86_64 => 2_139_993_292, + else => 2_200_000_000, + }, + .macos => switch (b.graph.host.result.cpu.arch) { + .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, + }, + else => 2_200_000_000, + }, })); test_step.dependOn(tests.addLinkTests(b, enable_macos_sdk, enable_ios_sdk, enable_symlinks_windows)); test_step.dependOn(tests.addStackTraceTests(b, test_filters, skip_non_native)); @@ -735,7 +869,29 @@ fn addCompilerMod(b: *std.Build, options: AddCompilerModOptions) *std.Build.Modu fn addCompilerStep(b: *std.Build, options: AddCompilerModOptions) *std.Build.Step.Compile { const exe = b.addExecutable(.{ .name = "zig", - .max_rss = 7_800_000_000, + .max_rss = switch (b.graph.host.result.os.tag) { + .freebsd => switch (b.graph.host.result.cpu.arch) { + .x86_64 => 6_044_158_771, + else => 6_100_000_000, + }, + .linux => switch (b.graph.host.result.cpu.arch) { + .aarch64 => 6_240_805_683, + .loongarch64 => 5_024_158_515, + .riscv64 => 6_996_309_196, + .s390x => 4_997_174_476, + .x86_64 => 5_486_090_649, + else => 7_000_000_000, + }, + .macos => switch (b.graph.host.result.cpu.arch) { + .aarch64 => 6_639_145_779, + else => 6_700_000_000, + }, + .windows => switch (b.graph.host.result.cpu.arch) { + .x86_64 => 5_770_394_009, + else => 5_800_000_000, + }, + else => 7_000_000_000, + }, .root_module = addCompilerMod(b, options), }); exe.stack_size = stack_size; diff --git a/test/tests.zig b/test/tests.zig index 9dae4786bd1d..306602d587bb 100644 --- a/test/tests.zig +++ b/test/tests.zig @@ -2344,8 +2344,11 @@ fn addOneModuleTest( const libc_suffix = if (test_target.link_libc == true) "-libc" else ""; const model_txt = target.cpu.model.name; - // wasm32-wasi builds need more RAM, idk why - const max_rss = if (target.os.tag == .wasi) + // These emulated targets need a lot more RAM for unknown reasons. + const max_rss = if (mem.eql(u8, options.name, "std") and + (target.cpu.arch == .hexagon or + (target.cpu.arch.isRISCV() and !resolved_target.query.isNative()) or + target.cpu.arch.isWasm())) options.max_rss * 2 else options.max_rss; @@ -2532,6 +2535,7 @@ const CAbiTestOptions = struct { skip_linux: bool, skip_llvm: bool, skip_release: bool, + max_rss: usize = 0, }; pub fn addCAbiTests(b: *std.Build, options: CAbiTestOptions) *Step { @@ -2604,6 +2608,7 @@ pub fn addCAbiTests(b: *std.Build, options: CAbiTestOptions) *Step { .root_module = test_mod, .use_llvm = c_abi_target.use_llvm, .use_lld = c_abi_target.use_lld, + .max_rss = options.max_rss, }); // This test is intentionally trying to check if the external ABI is From 78cba869289c66be875a72850437f95fe7541e92 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Wed, 3 Dec 2025 21:29:51 +0100 Subject: [PATCH 43/53] ci: set maxrss from $ZSF_MAX_RSS if provided by the runner All of our runners now define this. When running a CI script locally, this will not be set, so we default to 0, aka "all available system memory". --- ci/aarch64-linux-debug.sh | 2 +- ci/aarch64-linux-release.sh | 2 +- ci/aarch64-macos-debug.sh | 1 + ci/aarch64-macos-release.sh | 1 + ci/aarch64-windows.ps1 | 2 ++ ci/loongarch64-linux-debug.sh | 2 +- ci/loongarch64-linux-release.sh | 2 +- ci/riscv64-linux-debug.sh | 2 +- ci/riscv64-linux-release.sh | 2 +- ci/s390x-linux-debug.sh | 2 +- ci/s390x-linux-release.sh | 2 +- ci/x86_64-freebsd-debug.sh | 2 +- ci/x86_64-freebsd-release.sh | 2 +- ci/x86_64-linux-debug-llvm.sh | 2 +- ci/x86_64-linux-debug.sh | 2 +- ci/x86_64-linux-release.sh | 2 +- ci/x86_64-windows-debug.ps1 | 3 ++- ci/x86_64-windows-release.ps1 | 3 ++- 18 files changed, 21 insertions(+), 15 deletions(-) diff --git a/ci/aarch64-linux-debug.sh b/ci/aarch64-linux-debug.sh index 0892840f942e..7a4a6daa2aef 100755 --- a/ci/aarch64-linux-debug.sh +++ b/ci/aarch64-linux-debug.sh @@ -44,7 +44,7 @@ ninja install # No -fqemu and -fwasmtime here as they're covered by the x86_64-linux scripts. stage3-debug/bin/zig build test docs \ - --maxrss 44918199637 \ + --maxrss ${ZSF_MAX_RSS:-0} \ -Dstatic-llvm \ -Dskip-non-native \ -Dtarget=native-native-musl \ diff --git a/ci/aarch64-linux-release.sh b/ci/aarch64-linux-release.sh index 7b0947d385ca..39ad9767ab62 100755 --- a/ci/aarch64-linux-release.sh +++ b/ci/aarch64-linux-release.sh @@ -44,7 +44,7 @@ ninja install # No -fqemu and -fwasmtime here as they're covered by the x86_64-linux scripts. stage3-release/bin/zig build test docs \ - --maxrss 44918199637 \ + --maxrss ${ZSF_MAX_RSS:-0} \ -Dstatic-llvm \ -Dskip-non-native \ -Dtarget=native-native-musl \ diff --git a/ci/aarch64-macos-debug.sh b/ci/aarch64-macos-debug.sh index 2ae63e755593..a0681430dce8 100755 --- a/ci/aarch64-macos-debug.sh +++ b/ci/aarch64-macos-debug.sh @@ -42,6 +42,7 @@ cmake .. \ ninja install stage3-debug/bin/zig build test docs \ + --maxrss ${ZSF_MAX_RSS:-0} \ --zig-lib-dir "$PWD/../lib" \ -Denable-macos-sdk \ -Dstatic-llvm \ diff --git a/ci/aarch64-macos-release.sh b/ci/aarch64-macos-release.sh index 2e3a06a53bfa..46c611f100f0 100755 --- a/ci/aarch64-macos-release.sh +++ b/ci/aarch64-macos-release.sh @@ -51,6 +51,7 @@ stage3-release/bin/zig build test docs \ # Ensure that stage3 and stage4 are byte-for-byte identical. stage3-release/bin/zig build \ + --maxrss ${ZSF_MAX_RSS:-0} \ --prefix stage4-release \ -Denable-llvm \ -Dno-lib \ diff --git a/ci/aarch64-windows.ps1 b/ci/aarch64-windows.ps1 index 610f94a64f8a..96e076425650 100644 --- a/ci/aarch64-windows.ps1 +++ b/ci/aarch64-windows.ps1 @@ -5,6 +5,7 @@ $ZIG_LLVM_CLANG_LLD_URL = "https://ziglang.org/deps/$ZIG_LLVM_CLANG_LLD_NAME.zip $PREFIX_PATH = "$(Get-Location)\..\$ZIG_LLVM_CLANG_LLD_NAME" $ZIG = "$PREFIX_PATH\bin\zig.exe" $ZIG_LIB_DIR = "$(Get-Location)\lib" +$ZSF_MAX_RSS = if ($Env:ZSF_MAX_RSS) { $Env:ZSF_MAX_RSS } else { 0 } if (!(Test-Path "..\$ZIG_LLVM_CLANG_LLD_NAME.zip")) { Write-Output "Downloading $ZIG_LLVM_CLANG_LLD_URL" @@ -54,6 +55,7 @@ CheckLastExitCode Write-Output "Main test suite..." & "stage3-release\bin\zig.exe" build test docs ` + --maxrss $ZSF_MAX_RSS ` --zig-lib-dir "$ZIG_LIB_DIR" ` --search-prefix "$PREFIX_PATH" ` -Dstatic-llvm ` diff --git a/ci/loongarch64-linux-debug.sh b/ci/loongarch64-linux-debug.sh index 9757542cbb4b..4cba17b03190 100755 --- a/ci/loongarch64-linux-debug.sh +++ b/ci/loongarch64-linux-debug.sh @@ -45,7 +45,7 @@ ninja install # No -fqemu and -fwasmtime here as they're covered by the x86_64-linux scripts. stage3-debug/bin/zig build test docs \ - --maxrss 60129542144 \ + --maxrss ${ZSF_MAX_RSS:-0} \ -Dstatic-llvm \ -Dskip-non-native \ -Dtarget=native-native-musl \ diff --git a/ci/loongarch64-linux-release.sh b/ci/loongarch64-linux-release.sh index edb46d8565be..5b05284d2666 100755 --- a/ci/loongarch64-linux-release.sh +++ b/ci/loongarch64-linux-release.sh @@ -45,7 +45,7 @@ ninja install # No -fqemu and -fwasmtime here as they're covered by the x86_64-linux scripts. stage3-release/bin/zig build test docs \ - --maxrss 60129542144 \ + --maxrss ${ZSF_MAX_RSS:-0} \ -Dstatic-llvm \ -Dskip-non-native \ -Dtarget=native-native-musl \ diff --git a/ci/riscv64-linux-debug.sh b/ci/riscv64-linux-debug.sh index 51e038b369d3..5e7ae7785bb5 100755 --- a/ci/riscv64-linux-debug.sh +++ b/ci/riscv64-linux-debug.sh @@ -44,7 +44,7 @@ ninja install # No -fqemu and -fwasmtime here as they're covered by the x86_64-linux scripts. stage3-debug/bin/zig build test-cases test-modules test-unit test-c-abi test-stack-traces test-error-traces test-llvm-ir \ - --maxrss 68719476736 \ + --maxrss ${ZSF_MAX_RSS:-0} \ -Dstatic-llvm \ -Dskip-non-native \ -Dskip-single-threaded \ diff --git a/ci/riscv64-linux-release.sh b/ci/riscv64-linux-release.sh index 1de0335f42ec..92b3d43465d8 100755 --- a/ci/riscv64-linux-release.sh +++ b/ci/riscv64-linux-release.sh @@ -44,7 +44,7 @@ ninja install # No -fqemu and -fwasmtime here as they're covered by the x86_64-linux scripts. stage3-release/bin/zig build test-cases test-modules test-unit test-c-abi test-stack-traces test-error-traces test-llvm-ir \ - --maxrss 68719476736 \ + --maxrss ${ZSF_MAX_RSS:-0} \ -Dstatic-llvm \ -Dskip-non-native \ -Dskip-single-threaded \ diff --git a/ci/s390x-linux-debug.sh b/ci/s390x-linux-debug.sh index ce5057634f6b..ffe4d0f02b3c 100755 --- a/ci/s390x-linux-debug.sh +++ b/ci/s390x-linux-debug.sh @@ -45,7 +45,7 @@ ninja install # No -fqemu and -fwasmtime here as they're covered by the x86_64-linux scripts. stage3-debug/bin/zig build test docs \ - --maxrss 30064771072 \ + --maxrss ${ZSF_MAX_RSS:-0} \ -Dstatic-llvm \ -Dskip-non-native \ -Dtarget=native-native-musl \ diff --git a/ci/s390x-linux-release.sh b/ci/s390x-linux-release.sh index e474b89ffe12..7fb6cd3641fa 100755 --- a/ci/s390x-linux-release.sh +++ b/ci/s390x-linux-release.sh @@ -45,7 +45,7 @@ ninja install # No -fqemu and -fwasmtime here as they're covered by the x86_64-linux scripts. stage3-release/bin/zig build test docs \ - --maxrss 30064771072 \ + --maxrss ${ZSF_MAX_RSS:-0} \ -Dstatic-llvm \ -Dskip-non-native \ -Dtarget=native-native-musl \ diff --git a/ci/x86_64-freebsd-debug.sh b/ci/x86_64-freebsd-debug.sh index 570f46fb0919..c94dcfb2bb44 100755 --- a/ci/x86_64-freebsd-debug.sh +++ b/ci/x86_64-freebsd-debug.sh @@ -44,7 +44,7 @@ unset CXX ninja install stage3-debug/bin/zig build test docs \ - --maxrss 42949672960 \ + --maxrss ${ZSF_MAX_RSS:-0} \ -Dstatic-llvm \ -Dskip-spirv \ -Dskip-wasm \ diff --git a/ci/x86_64-freebsd-release.sh b/ci/x86_64-freebsd-release.sh index d7641fb86ccd..87092526a5f4 100755 --- a/ci/x86_64-freebsd-release.sh +++ b/ci/x86_64-freebsd-release.sh @@ -44,7 +44,7 @@ unset CXX ninja install stage3-release/bin/zig build test docs \ - --maxrss 42949672960 \ + --maxrss ${ZSF_MAX_RSS:-0} \ -Dstatic-llvm \ -Dskip-spirv \ -Dskip-wasm \ diff --git a/ci/x86_64-linux-debug-llvm.sh b/ci/x86_64-linux-debug-llvm.sh index 3c11820352b5..f6ac310ef72b 100755 --- a/ci/x86_64-linux-debug-llvm.sh +++ b/ci/x86_64-linux-debug-llvm.sh @@ -49,7 +49,7 @@ stage3-debug/bin/zig build \ -Dno-lib stage3-debug/bin/zig build test docs \ - --maxrss 21000000000 \ + --maxrss ${ZSF_MAX_RSS:-0} \ -Dlldb=$HOME/deps/lldb-zig/Debug-e0a42bb34/bin/lldb \ -fqemu \ -fwasmtime \ diff --git a/ci/x86_64-linux-debug.sh b/ci/x86_64-linux-debug.sh index fb2804487776..98d2ff2ee897 100755 --- a/ci/x86_64-linux-debug.sh +++ b/ci/x86_64-linux-debug.sh @@ -48,7 +48,7 @@ stage3-debug/bin/zig build \ -Dno-lib stage3-debug/bin/zig build test docs \ - --maxrss 21000000000 \ + --maxrss ${ZSF_MAX_RSS:-0} \ -Dlldb=$HOME/deps/lldb-zig/Debug-e0a42bb34/bin/lldb \ -fqemu \ -fwasmtime \ diff --git a/ci/x86_64-linux-release.sh b/ci/x86_64-linux-release.sh index c7b3dc7e364c..9f89d4370228 100755 --- a/ci/x86_64-linux-release.sh +++ b/ci/x86_64-linux-release.sh @@ -54,7 +54,7 @@ stage3-release/bin/zig build \ -Dno-lib stage3-release/bin/zig build test docs \ - --maxrss 21000000000 \ + --maxrss ${ZSF_MAX_RSS:-0} \ -Dlldb=$HOME/deps/lldb-zig/Release-e0a42bb34/bin/lldb \ -fqemu \ -fwasmtime \ diff --git a/ci/x86_64-windows-debug.ps1 b/ci/x86_64-windows-debug.ps1 index 31389a1b994a..e6ba8b0861b1 100644 --- a/ci/x86_64-windows-debug.ps1 +++ b/ci/x86_64-windows-debug.ps1 @@ -5,6 +5,7 @@ $ZIG_LLVM_CLANG_LLD_URL = "https://ziglang.org/deps/$ZIG_LLVM_CLANG_LLD_NAME.zip $PREFIX_PATH = "$($Env:USERPROFILE)\$ZIG_LLVM_CLANG_LLD_NAME" $ZIG = "$PREFIX_PATH\bin\zig.exe" $ZIG_LIB_DIR = "$(Get-Location)\lib" +$ZSF_MAX_RSS = if ($Env:ZSF_MAX_RSS) { $Env:ZSF_MAX_RSS } else { 0 } if (!(Test-Path "$PREFIX_PATH.zip")) { Write-Output "Downloading $ZIG_LLVM_CLANG_LLD_URL" @@ -54,7 +55,7 @@ CheckLastExitCode Write-Output "Main test suite..." & "stage3-debug\bin\zig.exe" build test docs ` - --maxrss 32212254720 ` + --maxrss $ZSF_MAX_RSS ` --zig-lib-dir "$ZIG_LIB_DIR" ` --search-prefix "$PREFIX_PATH" ` -Dstatic-llvm ` diff --git a/ci/x86_64-windows-release.ps1 b/ci/x86_64-windows-release.ps1 index c0ecb85c470a..e22e3af1c79d 100644 --- a/ci/x86_64-windows-release.ps1 +++ b/ci/x86_64-windows-release.ps1 @@ -5,6 +5,7 @@ $ZIG_LLVM_CLANG_LLD_URL = "https://ziglang.org/deps/$ZIG_LLVM_CLANG_LLD_NAME.zip $PREFIX_PATH = "$($Env:USERPROFILE)\$ZIG_LLVM_CLANG_LLD_NAME" $ZIG = "$PREFIX_PATH\bin\zig.exe" $ZIG_LIB_DIR = "$(Get-Location)\lib" +$ZSF_MAX_RSS = if ($Env:ZSF_MAX_RSS) { $Env:ZSF_MAX_RSS } else { 0 } if (!(Test-Path "$PREFIX_PATH.zip")) { Write-Output "Downloading $ZIG_LLVM_CLANG_LLD_URL" @@ -54,7 +55,7 @@ CheckLastExitCode Write-Output "Main test suite..." & "stage3-release\bin\zig.exe" build test docs ` - --maxrss 32212254720 ` + --maxrss $ZSF_MAX_RSS ` --zig-lib-dir "$ZIG_LIB_DIR" ` --search-prefix "$PREFIX_PATH" ` -Dstatic-llvm ` From c166bb36f6cbb882eac42f5522b07837ef7bf550 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Thu, 4 Dec 2025 20:52:34 +0100 Subject: [PATCH 44/53] ci: reduce x86_64-linux timeouts These excessive timeouts should no longer be necessary with the recent tuning of job capacity and maxrss on these machines. --- .forgejo/workflows/ci.yaml | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/.forgejo/workflows/ci.yaml b/.forgejo/workflows/ci.yaml index e0b39b6e275c..6ef295600962 100644 --- a/.forgejo/workflows/ci.yaml +++ b/.forgejo/workflows/ci.yaml @@ -152,7 +152,7 @@ jobs: fetch-depth: 0 - name: Build and Test run: sh ci/x86_64-linux-debug.sh - timeout-minutes: 240 + timeout-minutes: 180 x86_64-linux-debug-llvm: runs-on: [self-hosted, x86_64-linux] steps: @@ -162,7 +162,7 @@ jobs: fetch-depth: 0 - name: Build and Test run: sh ci/x86_64-linux-debug-llvm.sh - timeout-minutes: 480 + timeout-minutes: 360 x86_64-linux-release: runs-on: [self-hosted, x86_64-linux] steps: @@ -172,7 +172,7 @@ jobs: fetch-depth: 0 - name: Build and Test run: sh ci/x86_64-linux-release.sh - timeout-minutes: 480 + timeout-minutes: 360 x86_64-windows-debug: runs-on: [self-hosted, x86_64-windows] From 58e3c2cefd784aed617a53f333c133da07381f2a Mon Sep 17 00:00:00 2001 From: Loris Cro Date: Fri, 5 Dec 2025 11:37:13 +0100 Subject: [PATCH 45/53] make Io.net.sendMany compile --- lib/std/Io/net.zig | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/lib/std/Io/net.zig b/lib/std/Io/net.zig index c9bc65dd3b85..65d2dfd5e468 100644 --- a/lib/std/Io/net.zig +++ b/lib/std/Io/net.zig @@ -1090,7 +1090,8 @@ pub const Socket = struct { } pub fn sendMany(s: *const Socket, io: Io, messages: []OutgoingMessage, flags: SendFlags) SendError!void { - return io.vtable.netSend(io.userdata, s.handle, messages, flags); + const err, const n = io.vtable.netSend(io.userdata, s.handle, messages, flags); + if (n != messages.len) return err.?; } pub const ReceiveError = error{ From adc5a39de2a8d6da0d0785d43d01fde9fe8e23fe Mon Sep 17 00:00:00 2001 From: Luna Schwalbe Date: Tue, 2 Dec 2025 17:14:53 +0100 Subject: [PATCH 46/53] Change github links to codeberg --- README.md | 12 +++++------- lib/libc/glibc/README.md | 2 +- lib/std/Build.zig | 2 +- tools/update_glibc.zig | 2 +- 4 files changed, 8 insertions(+), 10 deletions(-) diff --git a/README.md b/README.md index 5f6ce4dea3da..b9acf00abd44 100644 --- a/README.md +++ b/README.md @@ -485,16 +485,14 @@ interpret your words. ### Find a Contributor Friendly Issue The issue label -[Contributor Friendly](https://github.com/ziglang/zig/issues?q=is%3Aissue+is%3Aopen+label%3A%22contributor+friendly%22) +[Contributor Friendly](https://codeberg.org/ziglang/zig/issues?labels=741726&state=open) exists to help you find issues that are **limited in scope and/or knowledge of Zig internals.** Please note that issues labeled -[Proposal](https://github.com/ziglang/zig/issues?q=is%3Aissue+is%3Aopen+label%3Aproposal) -but do not also have the -[Accepted](https://github.com/ziglang/zig/issues?q=is%3Aissue+is%3Aopen+label%3Aaccepted) -label are still under consideration, and efforts to implement such a proposal -have a high risk of being wasted. If you are interested in a proposal which is +[Proposal: Proposed](https://codeberg.org/ziglang/zig/issues?labels=746937&state=open) +are still under consideration, and efforts to implement such a proposal have +a high risk of being wasted. If you are interested in a proposal which is still under consideration, please express your interest in the issue tracker, providing extra insights and considerations that others have not yet expressed. The most highly regarded argument in such a discussion is a real world use case. @@ -777,7 +775,7 @@ If you will be debugging the Zig compiler itself, or if you will be debugging any project compiled with Zig's LLVM backend (not recommended with the LLDB fork, prefer vanilla LLDB with a version that matches the version of LLVM that Zig is using), you can get a better debugging experience by using -[`lldb_pretty_printers.py`](https://github.com/ziglang/zig/blob/master/tools/lldb_pretty_printers.py). +[`lldb_pretty_printers.py`](https://codeberg.org/ziglang/zig/src/branch/master/tools/lldb_pretty_printers.py). Put this line in `~/.lldbinit`: diff --git a/lib/libc/glibc/README.md b/lib/libc/glibc/README.md index 7fba7cc994ac..499e49b4d0fd 100644 --- a/lib/libc/glibc/README.md +++ b/lib/libc/glibc/README.md @@ -39,7 +39,7 @@ v2.2.5. The file `lib/libc/glibc/abilist` is a Zig-specific binary blob that defines the supported glibc versions and the set of symbols each version -must define. See https://github.com/ziglang/glibc-abi-tool for the +must define. See https://codeberg.org/ziglang/libc-abi-tools for the tooling to generate this blob. The code in `glibc.zig` parses the abilist to build version-specific stub libraries on demand. diff --git a/lib/std/Build.zig b/lib/std/Build.zig index 25d1ff6d95b8..50a280493891 100644 --- a/lib/std/Build.zig +++ b/lib/std/Build.zig @@ -79,7 +79,7 @@ enable_rosetta: bool = false, enable_wasmtime: bool = false, /// Use system Wine installation to run cross compiled Windows build artifacts. enable_wine: bool = false, -/// After following the steps in https://github.com/ziglang/zig/wiki/Updating-libc#glibc, +/// After following the steps in https://codeberg.org/ziglang/infra/src/branch/master/libc-update/glibc.md, /// this will be the directory $glibc-build-dir/install/glibcs /// Given the example of the aarch64 target, this is the directory /// that contains the path `aarch64-linux-gnu/lib/ld-linux-aarch64.so.1`. diff --git a/tools/update_glibc.zig b/tools/update_glibc.zig index 297c5c65b562..cebd593fe1d8 100644 --- a/tools/update_glibc.zig +++ b/tools/update_glibc.zig @@ -1,6 +1,6 @@ //! This script updates the .c, .h, .s, and .S files that make up the start //! files such as crt1.o. Not to be confused with -//! https://github.com/ziglang/glibc-abi-tool/ which updates the `abilists` +//! https://codeberg.org/ziglang/libc-abi-tools which updates the `abilists` //! file. //! //! Example usage: From 032e3c92547e33be0ad0f2c6bef1c1e2a51d505c Mon Sep 17 00:00:00 2001 From: Aidan Welch Date: Tue, 2 Dec 2025 10:59:53 +0100 Subject: [PATCH 47/53] std.Io.Timestamp: when creating a Clock.Timestamp actually set .raw instead of the non-existant .nanoseconds --- lib/std/Io.zig | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/std/Io.zig b/lib/std/Io.zig index eb854e3ecab0..ffd7837baf2d 100644 --- a/lib/std/Io.zig +++ b/lib/std/Io.zig @@ -890,7 +890,7 @@ pub const Timestamp = struct { } pub fn withClock(t: Timestamp, clock: Clock) Clock.Timestamp { - return .{ .nanoseconds = t.nanoseconds, .clock = clock }; + return .{ .raw = t, .clock = clock }; } pub fn fromNanoseconds(x: i96) Timestamp { From ea94ac52c531dd7e2ac2128f097fa036f5075403 Mon Sep 17 00:00:00 2001 From: Matthew Lugg Date: Fri, 5 Dec 2025 15:10:03 +0100 Subject: [PATCH 48/53] std.debug: skip `manage resources correctly` with cbe --- lib/std/debug.zig | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/lib/std/debug.zig b/lib/std/debug.zig index 29c0731f4e6e..f93612df15aa 100644 --- a/lib/std/debug.zig +++ b/lib/std/debug.zig @@ -1604,6 +1604,13 @@ pub fn dumpStackPointerAddr(prefix: []const u8) void { test "manage resources correctly" { if (SelfInfo == void) return error.SkipZigTest; + if (builtin.zig_backend == .stage2_c) { + // The C backend emits an extremely large C source file, meaning it has a huge + // amount of debug information. Parsing this debug information makes this test + // take too long to be worth running. + return error.SkipZigTest; + } + const S = struct { noinline fn showMyTrace() usize { return @returnAddress(); From 4ce7b57e86ca1d5e71100c6cab5c75aad929a430 Mon Sep 17 00:00:00 2001 From: Matthew Lugg Date: Thu, 4 Dec 2025 12:11:09 +0000 Subject: [PATCH 49/53] std.heap: rework `c_allocator` The main goal here was to avoid allocating padding and header space if `malloc` already guarantees the alignment we need via `max_align_t`. Previously, the compiler was using `std.heap.raw_c_allocator` as its GPA in some cases depending on `std.c.max_align_t`, but that's pretty fragile (it meant we had to encode our alignment requirements into `src/main.zig`!). Perhaps more importantly, that solution is unnecessarily restrictive: since Zig's `Allocator` API passes the `Alignment` not only to `alloc`, but also to `free` etc, we are able to use a different strategy depending on its value. So `c_allocator` can simply compare the requested align to `Alignment.of(std.c.max_align_t)`, and use a raw `malloc` call (no header needed!) if it will guarantee a suitable alignment (which, in practice, will be true the vast majority of the time). So in short, this makes `std.heap.c_allocator` more memory efficient, and probably removes any incentive to use `std.heap.raw_c_allocator`. I also refactored the `c_allocator` implementation while doing this, just to neaten things up a little. --- lib/std/heap.zig | 253 +++++++++++++++++++++++++++++------------------ src/main.zig | 9 +- 2 files changed, 158 insertions(+), 104 deletions(-) diff --git a/lib/std/heap.zig b/lib/std/heap.zig index 445b5da4550e..970d75e7f3c1 100644 --- a/lib/std/heap.zig +++ b/lib/std/heap.zig @@ -141,7 +141,19 @@ test defaultQueryPageSize { assert(std.math.isPowerOfTwo(defaultQueryPageSize())); } -const CAllocator = struct { +/// A wrapper around the C memory allocation API which supports the full `Allocator` +/// interface, including arbitrary alignment. Simple `malloc` calls are used when +/// possible, but large requested alignments may require larger buffers in order to +/// satisfy the request. As well as `malloc`, `realloc`, and `free`, the extension +/// functions `malloc_usable_size` and `posix_memalign` are used when available. +/// +/// For an allocator that directly calls `malloc`/`realloc`/`free`, with no padding +/// or special handling, see `raw_c_allocator`. +pub const c_allocator: Allocator = .{ + .ptr = undefined, + .vtable = &c_allocator_impl.vtable, +}; +const c_allocator_impl = struct { comptime { if (!builtin.link_libc) { @compileError("C allocator is only available when linking against libc"); @@ -155,67 +167,55 @@ const CAllocator = struct { .free = free, }; - pub const supports_malloc_size = @TypeOf(malloc_size) != void; - pub const malloc_size = if (@TypeOf(c.malloc_size) != void) - c.malloc_size - else if (@TypeOf(c.malloc_usable_size) != void) - c.malloc_usable_size - else if (@TypeOf(c._msize) != void) - c._msize - else {}; - - pub const supports_posix_memalign = switch (builtin.os.tag) { - .dragonfly, .netbsd, .freebsd, .illumos, .openbsd, .linux, .driverkit, .ios, .maccatalyst, .macos, .tvos, .visionos, .watchos, .serenity => true, + const have_posix_memalign = switch (builtin.os.tag) { + .dragonfly, + .netbsd, + .freebsd, + .illumos, + .openbsd, + .linux, + .driverkit, + .ios, + .maccatalyst, + .macos, + .tvos, + .visionos, + .watchos, + .serenity, + => true, else => false, }; - fn getHeader(ptr: [*]u8) *[*]u8 { - return @ptrCast(@alignCast(ptr - @sizeOf(usize))); - } - - fn alignedAlloc(len: usize, alignment: Alignment) ?[*]u8 { - const alignment_bytes = alignment.toByteUnits(); - if (supports_posix_memalign) { - // The posix_memalign only accepts alignment values that are a - // multiple of the pointer size - const effective_alignment = @max(alignment_bytes, @sizeOf(usize)); - - var aligned_ptr: ?*anyopaque = undefined; - if (c.posix_memalign(&aligned_ptr, effective_alignment, len) != 0) - return null; - - return @ptrCast(aligned_ptr); - } - - // Thin wrapper around regular malloc, overallocate to account for - // alignment padding and store the original malloc()'ed pointer before - // the aligned address. - const unaligned_ptr = @as([*]u8, @ptrCast(c.malloc(len + alignment_bytes - 1 + @sizeOf(usize)) orelse return null)); - const unaligned_addr = @intFromPtr(unaligned_ptr); - const aligned_addr = mem.alignForward(usize, unaligned_addr + @sizeOf(usize), alignment_bytes); - const aligned_ptr = unaligned_ptr + (aligned_addr - unaligned_addr); - getHeader(aligned_ptr).* = unaligned_ptr; - - return aligned_ptr; - } - - fn alignedFree(ptr: [*]u8) void { - if (supports_posix_memalign) { - return c.free(ptr); + fn allocStrat(need_align: Alignment) union(enum) { + raw, + posix_memalign: if (have_posix_memalign) void else noreturn, + manual_align: if (have_posix_memalign) noreturn else void, + } { + // If `malloc` guarantees `need_align`, always prefer a raw allocation. + if (Alignment.compare(need_align, .lte, .of(c.max_align_t))) { + return .raw; } - - const unaligned_ptr = getHeader(ptr).*; - c.free(unaligned_ptr); + // Use `posix_memalign` if available. Otherwise, we must manually align the allocation. + return if (have_posix_memalign) .posix_memalign else .manual_align; } - fn alignedAllocSize(ptr: [*]u8) usize { - if (supports_posix_memalign) { - return CAllocator.malloc_size(ptr); - } - - const unaligned_ptr = getHeader(ptr).*; - const delta = @intFromPtr(ptr) - @intFromPtr(unaligned_ptr); - return CAllocator.malloc_size(unaligned_ptr) - delta; + /// If `allocStrat(a) == .manual_align`, an allocation looks like this: + /// + /// unaligned_ptr hdr_ptr aligned_ptr + /// v v v + /// +---------------+--------+--------------+ + /// | padding | header | usable bytes | + /// +---------------+--------+--------------+ + /// + /// * `unaligned_ptr` is the raw return value of `malloc`. + /// * `aligned_ptr` is computed by aligning `unaligned_ptr` forward; it is what `alloc` returns. + /// * `hdr_ptr` points to a pointer-sized header directly before the usable space. This header + /// contains the value `unaligned_ptr`, so that we can pass it to `free` later. This is + /// necessary because the width of the padding is unknown. + /// + /// This function accepts `aligned_ptr` and offsets it backwards to return `hdr_ptr`. + fn manualAlignHeader(aligned_ptr: [*]u8) *[*]u8 { + return @ptrCast(@alignCast(aligned_ptr - @sizeOf(usize))); } fn alloc( @@ -226,67 +226,120 @@ const CAllocator = struct { ) ?[*]u8 { _ = return_address; assert(len > 0); - return alignedAlloc(len, alignment); + switch (allocStrat(alignment)) { + .raw => { + // C only needs to respect `max_align_t` up to the allocation size due to object + // alignment rules. If necessary, extend the allocation size. + const actual_len = @max(len, @alignOf(std.c.max_align_t)); + const ptr = c.malloc(actual_len) orelse return null; + assert(alignment.check(@intFromPtr(ptr))); + return @ptrCast(ptr); + }, + .posix_memalign => { + // The posix_memalign only accepts alignment values that are a + // multiple of the pointer size + const effective_alignment = @max(alignment.toByteUnits(), @sizeOf(usize)); + var aligned_ptr: ?*anyopaque = undefined; + if (c.posix_memalign(&aligned_ptr, effective_alignment, len) != 0) { + return null; + } + assert(alignment.check(@intFromPtr(aligned_ptr))); + return @ptrCast(aligned_ptr); + }, + .manual_align => { + // Overallocate to account for alignment padding and store the original pointer + // returned by `malloc` before the aligned address. + const padded_len = len + @sizeOf(usize) + alignment.toByteUnits() - 1; + const unaligned_ptr: [*]u8 = @ptrCast(c.malloc(padded_len) orelse return null); + const unaligned_addr = @intFromPtr(unaligned_ptr); + const aligned_addr = alignment.forward(unaligned_addr + @sizeOf(usize)); + const aligned_ptr = unaligned_ptr + (aligned_addr - unaligned_addr); + manualAlignHeader(aligned_ptr).* = unaligned_ptr; + return aligned_ptr; + }, + } } fn resize( _: *anyopaque, - buf: []u8, + memory: []u8, alignment: Alignment, new_len: usize, return_address: usize, ) bool { - _ = alignment; _ = return_address; - if (new_len <= buf.len) { - return true; - } - if (CAllocator.supports_malloc_size) { - const full_len = alignedAllocSize(buf.ptr); - if (new_len <= full_len) { - return true; - } + assert(new_len > 0); + if (new_len <= memory.len) { + return true; // in-place shrink always works } - return false; + const mallocSize = func: { + if (@TypeOf(c.malloc_size) != void) break :func c.malloc_size; + if (@TypeOf(c.malloc_usable_size) != void) break :func c.malloc_usable_size; + if (@TypeOf(c._msize) != void) break :func c._msize; + return false; // we don't know how much space is actually available + }; + const usable_len: usize = switch (allocStrat(alignment)) { + .raw, .posix_memalign => mallocSize(memory.ptr), + .manual_align => usable_len: { + const unaligned_ptr = manualAlignHeader(memory.ptr).*; + const full_len = mallocSize(unaligned_ptr); + const padding = @intFromPtr(memory.ptr) - @intFromPtr(unaligned_ptr); + break :usable_len full_len - padding; + }, + }; + return new_len <= usable_len; } fn remap( - context: *anyopaque, + ctx: *anyopaque, memory: []u8, alignment: Alignment, new_len: usize, return_address: usize, ) ?[*]u8 { - // realloc would potentially return a new allocation that does not - // respect the original alignment. - return if (resize(context, memory, alignment, new_len, return_address)) memory.ptr else null; + assert(new_len > 0); + // Prefer resizing in-place if possible, since `realloc` could be expensive even if legal. + if (resize(ctx, memory, alignment, new_len, return_address)) { + return memory.ptr; + } + switch (allocStrat(alignment)) { + .raw => { + // `malloc` and friends guarantee the required alignment, so we can try `realloc`. + // C only needs to respect `max_align_t` up to the allocation size due to object + // alignment rules. If necessary, extend the allocation size. + const actual_len = @max(new_len, @alignOf(std.c.max_align_t)); + const new_ptr = c.realloc(memory.ptr, actual_len) orelse return null; + assert(alignment.check(@intFromPtr(new_ptr))); + return @ptrCast(new_ptr); + }, + .posix_memalign, .manual_align => { + // `realloc` would potentially return a new allocation which does not respect + // the original alignment, so we can't do anything more. + return null; + }, + } } fn free( _: *anyopaque, - buf: []u8, + memory: []u8, alignment: Alignment, return_address: usize, ) void { - _ = alignment; _ = return_address; - alignedFree(buf.ptr); + switch (allocStrat(alignment)) { + .raw, .posix_memalign => c.free(memory.ptr), + .manual_align => c.free(manualAlignHeader(memory.ptr).*), + } } }; -/// Supports the full Allocator interface, including alignment, and exploiting -/// `malloc_usable_size` if available. For an allocator that directly calls -/// `malloc`/`free`, see `raw_c_allocator`. -pub const c_allocator: Allocator = .{ - .ptr = undefined, - .vtable = &CAllocator.vtable, -}; - -/// Asserts allocations are within `@alignOf(std.c.max_align_t)` and directly -/// calls `malloc`/`free`. Does not attempt to utilize `malloc_usable_size`. -/// This allocator is safe to use as the backing allocator with -/// `ArenaAllocator` for example and is more optimal in such a case than -/// `c_allocator`. +/// Asserts that allocations have alignments which `malloc` can satisfy. This means that +/// the requested alignment is no greater than `@min(@alignOf(std.c.max_align_t), size)`. +/// +/// This allocator is rarely appropriate to use. In general, prefer `c_allocator`, which +/// does not have any special requirements of its input, but is still highly efficient for +/// allocation requests which obey `malloc` alignment rules. pub const raw_c_allocator: Allocator = .{ .ptr = undefined, .vtable = &raw_c_allocator_vtable, @@ -306,13 +359,20 @@ fn rawCAlloc( ) ?[*]u8 { _ = context; _ = return_address; - assert(alignment.compare(.lte, .of(std.c.max_align_t))); - // Note that this pointer cannot be aligncasted to max_align_t because if - // len is < max_align_t then the alignment can be smaller. For example, if - // max_align_t is 16, but the user requests 8 bytes, there is no built-in - // type in C that is size 8 and has 16 byte alignment, so the alignment may - // be 8 bytes rather than 16. Similarly if only 1 byte is requested, malloc - // is allowed to return a 1-byte aligned pointer. + // `std.c.max_align_t` isn't the whole story, because if `len` is smaller than + // every C type with alignment `max_align_t`, the allocation can be less-aligned. + // The implementation need only guarantee that any type of length `len` would be + // suitably aligned. + // + // For instance, if `len == 8` and `alignment == .@"16"`, then `malloc` may not + // fulfil this request, because there is necessarily no C type with 8-byte size + // but 16-byte alignment. + // + // In theory, the resulting rule here would be target-specific, but in practice, + // the smallest type with an alignment of `max_align_t` has the same size (it's + // usually `c_longdouble`), so we can just check that `alignment <= len`. + assert(alignment.toByteUnits() <= len); + assert(Alignment.compare(alignment, .lte, .of(std.c.max_align_t))); return @ptrCast(c.malloc(len)); } @@ -339,8 +399,9 @@ fn rawCRemap( return_address: usize, ) ?[*]u8 { _ = context; - _ = alignment; _ = return_address; + // See `rawCMalloc` for an explanation of this `assert` call. + assert(alignment.toByteUnits() <= new_len); return @ptrCast(c.realloc(memory.ptr, new_len)); } diff --git a/src/main.zig b/src/main.zig index c08e9da44951..2d561233b9a4 100644 --- a/src/main.zig +++ b/src/main.zig @@ -167,14 +167,7 @@ pub fn main() anyerror!void { const gpa, const is_debug = gpa: { if (build_options.debug_gpa) break :gpa .{ debug_allocator.allocator(), true }; if (native_os == .wasi) break :gpa .{ std.heap.wasm_allocator, false }; - if (builtin.link_libc) { - // We would prefer to use raw libc allocator here, but cannot use - // it if it won't support the alignment we need. - if (@alignOf(std.c.max_align_t) < @max(@alignOf(i128), std.atomic.cache_line)) { - break :gpa .{ std.heap.c_allocator, false }; - } - break :gpa .{ std.heap.raw_c_allocator, false }; - } + if (builtin.link_libc) break :gpa .{ std.heap.c_allocator, false }; break :gpa switch (builtin.mode) { .Debug, .ReleaseSafe => .{ debug_allocator.allocator(), true }, .ReleaseFast, .ReleaseSmall => .{ std.heap.smp_allocator, false }, From 1e616096d42fc793f463acd44b81489987a69934 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Alex=20R=C3=B8nne=20Petersen?= Date: Sat, 6 Dec 2025 01:50:27 +0100 Subject: [PATCH 50/53] build: bump test-zigc max_rss to 767_483_904 on x86_64-linux --- build.zig | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/build.zig b/build.zig index 114428bcba76..4f1d8b98bbc8 100644 --- a/build.zig +++ b/build.zig @@ -569,8 +569,8 @@ pub fn build(b: *std.Build) !void { .loongarch64 => 598_974_464, .riscv64 => 382_786_764, .s390x => 395_555_635, - .x86_64 => 692_348_518, - else => 700_000_000, + .x86_64 => 767_483_904, + else => 800_000_000, }, .macos => switch (b.graph.host.result.cpu.arch) { .aarch64 => 451_389_030, @@ -580,7 +580,7 @@ pub fn build(b: *std.Build) !void { .x86_64 => 367_747_072, else => 400_000_000, }, - else => 700_000_000, + else => 800_000_000, }, })); From 9abf0559ef012a01b3313c09f64467eb92f4e16f Mon Sep 17 00:00:00 2001 From: Stephen Gregoratto Date: Fri, 17 Oct 2025 14:41:54 +1100 Subject: [PATCH 51/53] Linux: Update `Statx` structure Also removes the blank lines between members, and a comptime sizeOf check. --- lib/std/os/linux.zig | 79 ++++++++++++++++++++++++-------------------- 1 file changed, 43 insertions(+), 36 deletions(-) diff --git a/lib/std/os/linux.zig b/lib/std/os/linux.zig index b80d7c606f05..28d02814f637 100644 --- a/lib/std/os/linux.zig +++ b/lib/std/os/linux.zig @@ -6963,73 +6963,80 @@ pub const STATX_ATTR_ENCRYPTED = 0x0800; pub const STATX_ATTR_AUTOMOUNT = 0x1000; pub const statx_timestamp = extern struct { + /// Number of seconds before or after `1970-01-01T00:00:00Z`. sec: i64, + /// Number of nanoseconds (0..999,999,999) after `sec`. nsec: u32, + // Reserved for future increases in resolution. __pad1: u32, }; /// Renamed to `Statx` to not conflict with the `statx` function. pub const Statx = extern struct { - /// Mask of bits indicating filled fields + /// Mask of bits indicating filled fields. mask: u32, - - /// Block size for filesystem I/O + /// Block size for filesystem I/O. blksize: u32, - - /// Extra file attribute indicators + /// Extra file attribute indicators. attributes: u64, - - /// Number of hard links + /// Number of hard links. nlink: u32, - - /// User ID of owner + /// User ID of owner. uid: uid_t, - - /// Group ID of owner + /// Group ID of owner. gid: gid_t, - - /// File type and mode + /// File type and mode. mode: u16, - __pad1: u16, - - /// Inode number + __spare0: u16, + /// Inode number. ino: u64, - - /// Total size in bytes + /// Total size in bytes. size: u64, - - /// Number of 512B blocks allocated + /// Number of 512B blocks allocated. blocks: u64, - /// Mask to show what's supported in `attributes`. attributes_mask: u64, - - /// Last access file timestamp + /// Last access file timestamp. atime: statx_timestamp, - - /// Creation file timestamp + /// Creation file timestamp. btime: statx_timestamp, - - /// Last status change file timestamp + /// Last status change file timestamp. ctime: statx_timestamp, - - /// Last modification file timestamp + /// Last modification file timestamp. mtime: statx_timestamp, - /// Major ID, if this file represents a device. rdev_major: u32, - /// Minor ID, if this file represents a device. rdev_minor: u32, - /// Major ID of the device containing the filesystem where this file resides. dev_major: u32, - /// Minor ID of the device containing the filesystem where this file resides. dev_minor: u32, - - __pad2: [14]u64, -}; + /// Mount ID + mnt_id: u64, + /// Memory buffer alignment for direct I/O. + dio_mem_align: u32, + /// File offset alignment for direct I/O. + dio_offset_align: u32, + /// Subvolume identifier. + subvol: u64, + /// Min atomic write unit in bytes. + atomic_write_unit_min: u32, + /// Max atomic write unit in bytes. + atomic_write_unit_max: u32, + /// Max atomic write segment count. + atomic_write_segments_max: u32, + /// File offset alignment for direct I/O reads. + dio_read_offset_align: u32, + /// Optimised max atomic write unit in bytes. + atomic_write_unit_max_opt: u32, + __spare2: [1]u32, + __spare3: [8]u64, +}; + +comptime { + assert(@sizeOf(Statx) == 0x100); +} pub const addrinfo = extern struct { flags: AI, From 2023f02459eee145b266d970b4fe7a787ff6316b Mon Sep 17 00:00:00 2001 From: Stephen Gregoratto Date: Sun, 19 Oct 2025 22:48:54 +1100 Subject: [PATCH 52/53] Linux: Nuke `Stat` bits in favour of statx Maintaining the POSIX `stat` bits for Zig is a pain. The order and bit-length of members differ between all architectures, and int types can be signed or unsigned. The libcs deal with this by introducing the own version of `struct stat` and copying the kernel structure members to it. In the case of glibc, they did it twice thanks to the largefile transition! In practice, the project needs to maintain three versions of `struct stat`: - What the kernel defines. - What musl wants for `struct stat`. - What glibc wants for `struct stat64`. Make sure to use `fstatat64`! This isn't as simple as running `zig translate-c`. In #21440 I had to: - Compile toolchains for each arch+glibc/musl combo. - Create a test `fstat` program with/without `FILE_OFFSET_BITS=64`. - Dump the value for `struct stat`. - Stare at `std.os.linux`/`std.c` and cry. - Add some missing padding. The fact that so many target checks in the `linux` and `posix` tests exist is most likely due to writing to padding bits and failing later. The solution to this madness is `statx(2)`: - It takes a single structure that is the same for all arches AND libcs. - It uses a custom timestamp format, but it is 64-bit ready. - It gives the same info as `fstatat(2)` and more! - Unlike `fstatat(2)`, you can request a subset of the info required based on passing a mask. It's so good that modern Linux arches (e.g. riscv) don't even implement `stat`, with the libcs using a generic `struct stat` and copying from `struct statx`. Therefore, this commit rips out all the `stat` bits from `std.os.linux` and `std.c`. `std.posix.Stat` is now `void`, and calling `std.posix.*stat` is an compile-time error. A wrapper around `statx` has been added to `std.os.linux`, and callers have been upgraded to use it. Tests have also been updated to use `statx` where possible. While I was here, I converted the mask and file attributes to be packed struct bitfields. A nice side effect is checking that you actually recieved the members you asked for via `Statx.mask`, which I have used by adding `assert`s at specific callsites. --- lib/std/Io/Threaded.zig | 14 +- lib/std/c.zig | 168 +------------- lib/std/os/linux.zig | 213 +++++++++++------- lib/std/os/linux/IoUring.zig | 6 +- lib/std/os/linux/aarch64.zig | 32 --- lib/std/os/linux/arm.zig | 32 --- lib/std/os/linux/hexagon.zig | 32 --- lib/std/os/linux/io_uring_sqe.zig | 4 +- lib/std/os/linux/loongarch64.zig | 34 --- lib/std/os/linux/m68k.zig | 31 --- lib/std/os/linux/mips.zig | 32 --- lib/std/os/linux/mips64.zig | 44 ---- lib/std/os/linux/mipsn32.zig | 44 ---- lib/std/os/linux/or1k.zig | 32 --- lib/std/os/linux/powerpc.zig | 32 --- lib/std/os/linux/powerpc64.zig | 30 --- lib/std/os/linux/riscv32.zig | 33 --- lib/std/os/linux/riscv64.zig | 33 --- lib/std/os/linux/s390x.zig | 31 --- lib/std/os/linux/sparc64.zig | 35 --- lib/std/os/linux/test.zig | 30 +-- lib/std/os/linux/x32.zig | 33 --- lib/std/os/linux/x86.zig | 32 --- lib/std/os/linux/x86_64.zig | 33 --- lib/std/posix.zig | 38 ++-- lib/std/posix/test.zig | 98 ++++++-- src/link/MappedFile.zig | 14 ++ .../glibc_compat/glibc_runtime_check.zig | 9 +- test/standalone/posix/relpaths.zig | 38 ++-- 29 files changed, 310 insertions(+), 927 deletions(-) diff --git a/lib/std/Io/Threaded.zig b/lib/std/Io/Threaded.zig index 0e17cc01d028..2224db7bd222 100644 --- a/lib/std/Io/Threaded.zig +++ b/lib/std/Io/Threaded.zig @@ -1519,12 +1519,17 @@ fn dirStatPathLinux( dir.handle, sub_path_posix, flags, - linux.STATX_INO | linux.STATX_SIZE | linux.STATX_TYPE | linux.STATX_MODE | linux.STATX_ATIME | linux.STATX_MTIME | linux.STATX_CTIME, + .{ .TYPE = true, .MODE = true, .ATIME = true, .MTIME = true, .CTIME = true }, &statx, ); switch (linux.errno(rc)) { .SUCCESS => { current_thread.endSyscall(); + assert(statx.mask.TYPE); + assert(statx.mask.MODE); + assert(statx.mask.ATIME); + assert(statx.mask.MTIME); + assert(statx.mask.CTIME); return statFromLinux(&statx); }, .INTR => { @@ -1711,12 +1716,17 @@ fn fileStatLinux(userdata: ?*anyopaque, file: Io.File) Io.File.StatError!Io.File file.handle, "", linux.AT.EMPTY_PATH, - linux.STATX_INO | linux.STATX_SIZE | linux.STATX_TYPE | linux.STATX_MODE | linux.STATX_ATIME | linux.STATX_MTIME | linux.STATX_CTIME, + .{ .TYPE = true, .MODE = true, .ATIME = true, .MTIME = true, .CTIME = true }, &statx, ); switch (linux.errno(rc)) { .SUCCESS => { current_thread.endSyscall(); + assert(statx.mask.TYPE); + assert(statx.mask.MODE); + assert(statx.mask.ATIME); + assert(statx.mask.MTIME); + assert(statx.mask.CTIME); return statFromLinux(&statx); }, .INTR => { diff --git a/lib/std/c.zig b/lib/std/c.zig index f3fdeb6d95d5..bfc978c32916 100644 --- a/lib/std/c.zig +++ b/lib/std/c.zig @@ -7586,166 +7586,6 @@ pub const EAI = if (builtin.abi.isAndroid()) enum(c_int) { pub const dl_iterate_phdr_callback = *const fn (info: *dl_phdr_info, size: usize, data: ?*anyopaque) callconv(.c) c_int; pub const Stat = switch (native_os) { - .linux => switch (native_arch) { - .sparc64 => extern struct { - dev: u64, - __pad1: u16, - ino: ino_t, - mode: u32, - nlink: u32, - - uid: u32, - gid: u32, - rdev: u64, - __pad2: u16, - - size: off_t, - blksize: isize, - blocks: i64, - - atim: timespec, - mtim: timespec, - ctim: timespec, - __reserved: [2]usize, - - pub fn atime(self: @This()) timespec { - return self.atim; - } - - pub fn mtime(self: @This()) timespec { - return self.mtim; - } - - pub fn ctime(self: @This()) timespec { - return self.ctim; - } - }, - .mips, .mipsel => if (builtin.target.abi.isMusl()) extern struct { - dev: dev_t, - __pad0: [2]i32, - ino: ino_t, - mode: mode_t, - nlink: nlink_t, - uid: uid_t, - gid: gid_t, - rdev: dev_t, - __pad1: [2]i32, - size: off_t, - atim: timespec, - mtim: timespec, - ctim: timespec, - blksize: blksize_t, - __pad3: i32, - blocks: blkcnt_t, - __pad4: [14]i32, - - pub fn atime(self: @This()) timespec { - return self.atim; - } - - pub fn mtime(self: @This()) timespec { - return self.mtim; - } - - pub fn ctime(self: @This()) timespec { - return self.ctim; - } - } else extern struct { - dev: u32, - __pad0: [3]u32, - ino: ino_t, - mode: mode_t, - nlink: nlink_t, - uid: uid_t, - gid: gid_t, - rdev: u32, - __pad1: [3]u32, - size: off_t, - atim: timespec, - mtim: timespec, - ctim: timespec, - blksize: blksize_t, - __pad3: u32, - blocks: blkcnt_t, - __pad4: [14]u32, - - pub fn atime(self: @This()) timespec { - return self.atim; - } - - pub fn mtime(self: @This()) timespec { - return self.mtim; - } - - pub fn ctime(self: @This()) timespec { - return self.ctim; - } - }, - .mips64, .mips64el => if (builtin.target.abi.isMusl()) extern struct { - dev: dev_t, - __pad0: [3]i32, - ino: ino_t, - mode: mode_t, - nlink: nlink_t, - uid: uid_t, - gid: gid_t, - rdev: dev_t, - __pad1: [2]u32, - size: off_t, - __pad2: i32, - atim: timespec, - mtim: timespec, - ctim: timespec, - blksize: blksize_t, - __pad3: u32, - blocks: blkcnt_t, - __pad4: [14]i32, - - pub fn atime(self: @This()) timespec { - return self.atim; - } - - pub fn mtime(self: @This()) timespec { - return self.mtim; - } - - pub fn ctime(self: @This()) timespec { - return self.ctim; - } - } else extern struct { - dev: dev_t, - __pad0: [3]u32, - ino: ino_t, - mode: mode_t, - nlink: nlink_t, - uid: uid_t, - gid: gid_t, - rdev: dev_t, - __pad1: [3]u32, - size: off_t, - atim: timespec, - mtim: timespec, - ctim: timespec, - blksize: blksize_t, - __pad3: u32, - blocks: blkcnt_t, - __pad4: [14]i32, - - pub fn atime(self: @This()) timespec { - return self.atim; - } - - pub fn mtime(self: @This()) timespec { - return self.mtim; - } - - pub fn ctime(self: @This()) timespec { - return self.ctim; - } - }, - - else => std.os.linux.Stat, // libc stat is the same as kernel stat. - }, .emscripten => emscripten.Stat, .wasi => extern struct { // Match wasi-libc's `struct stat` in lib/libc/include/wasm-wasi-musl/__struct_stat.h @@ -10422,6 +10262,7 @@ pub const fstat = switch (native_os) { else => private.fstat, }, .netbsd => private.__fstat50, + .linux => {}, else => private.fstat, }; @@ -10430,8 +10271,12 @@ pub const fstatat = switch (native_os) { .x86_64 => private.@"fstatat$INODE64", else => private.fstatat, }, + .linux => {}, else => private.fstatat, }; + +pub extern "c" fn statx(dirfd: fd_t, path: [*:0]const u8, flags: u32, mask: linux.STATX, buf: *linux.Statx) c_int; + pub extern "c" fn getpwent() ?*passwd; pub extern "c" fn endpwent() void; pub extern "c" fn setpwent() void; @@ -10505,8 +10350,6 @@ pub extern "c" fn inotify_init1(flags: c_uint) c_int; pub extern "c" fn inotify_add_watch(fd: fd_t, pathname: [*:0]const u8, mask: u32) c_int; pub extern "c" fn inotify_rm_watch(fd: fd_t, wd: c_int) c_int; -pub extern "c" fn fstat64(fd: fd_t, buf: *Stat) c_int; -pub extern "c" fn fstatat64(dirfd: fd_t, noalias path: [*:0]const u8, noalias stat_buf: *Stat, flags: u32) c_int; pub extern "c" fn fallocate64(fd: fd_t, mode: c_int, offset: off_t, len: off_t) c_int; pub extern "c" fn fopen64(noalias filename: [*:0]const u8, noalias modes: [*:0]const u8) ?*FILE; pub extern "c" fn ftruncate64(fd: c_int, length: off_t) c_int; @@ -11550,7 +11393,6 @@ const private = struct { extern "c" fn sigprocmask(how: c_int, noalias set: ?*const sigset_t, noalias oset: ?*sigset_t) c_int; extern "c" fn socket(domain: c_uint, sock_type: c_uint, protocol: c_uint) c_int; extern "c" fn socketpair(domain: c_uint, sock_type: c_uint, protocol: c_uint, sv: *[2]fd_t) c_int; - extern "c" fn stat(noalias path: [*:0]const u8, noalias buf: *Stat) c_int; extern "c" fn sigaltstack(ss: ?*stack_t, old_ss: ?*stack_t) c_int; extern "c" fn sysconf(sc: c_int) c_long; extern "c" fn shm_open(name: [*:0]const u8, flag: c_int, mode: mode_t) c_int; diff --git a/lib/std/os/linux.zig b/lib/std/os/linux.zig index 28d02814f637..c529eb2ab3c4 100644 --- a/lib/std/os/linux.zig +++ b/lib/std/os/linux.zig @@ -95,15 +95,14 @@ pub fn clone( pub const ARCH = arch_bits.ARCH; pub const HWCAP = arch_bits.HWCAP; pub const SC = arch_bits.SC; -pub const Stat = arch_bits.Stat; pub const VDSO = arch_bits.VDSO; -pub const blkcnt_t = arch_bits.blkcnt_t; -pub const blksize_t = arch_bits.blksize_t; -pub const dev_t = arch_bits.dev_t; -pub const ino_t = arch_bits.ino_t; -pub const mode_t = arch_bits.mode_t; -pub const nlink_t = arch_bits.nlink_t; -pub const off_t = arch_bits.off_t; +pub const blkcnt_t = u64; +pub const blksize_t = u32; +pub const dev_t = u64; +pub const ino_t = u64; +pub const mode_t = u32; +pub const nlink_t = u32; +pub const off_t = i64; pub const time_t = arch_bits.time_t; pub const user_desc = arch_bits.user_desc; @@ -2199,61 +2198,13 @@ pub fn accept4(fd: i32, noalias addr: ?*sockaddr, noalias len: ?*socklen_t, flag return syscall4(.accept4, @as(usize, @bitCast(@as(isize, fd))), @intFromPtr(addr), @intFromPtr(len), flags); } -pub fn fstat(fd: i32, stat_buf: *Stat) usize { - if (native_arch == .riscv32 or native_arch.isLoongArch()) { - // riscv32 and loongarch have made the interesting decision to not implement some of - // the older stat syscalls, including this one. - @compileError("No fstat syscall on this architecture."); - } else if (@hasField(SYS, "fstat64")) { - return syscall2(.fstat64, @as(usize, @bitCast(@as(isize, fd))), @intFromPtr(stat_buf)); - } else { - return syscall2(.fstat, @as(usize, @bitCast(@as(isize, fd))), @intFromPtr(stat_buf)); - } -} - -pub fn stat(pathname: [*:0]const u8, statbuf: *Stat) usize { - if (native_arch == .riscv32 or native_arch.isLoongArch()) { - // riscv32 and loongarch have made the interesting decision to not implement some of - // the older stat syscalls, including this one. - @compileError("No stat syscall on this architecture."); - } else if (@hasField(SYS, "stat64")) { - return syscall2(.stat64, @intFromPtr(pathname), @intFromPtr(statbuf)); - } else { - return syscall2(.stat, @intFromPtr(pathname), @intFromPtr(statbuf)); - } -} - -pub fn lstat(pathname: [*:0]const u8, statbuf: *Stat) usize { - if (native_arch == .riscv32 or native_arch.isLoongArch()) { - // riscv32 and loongarch have made the interesting decision to not implement some of - // the older stat syscalls, including this one. - @compileError("No lstat syscall on this architecture."); - } else if (@hasField(SYS, "lstat64")) { - return syscall2(.lstat64, @intFromPtr(pathname), @intFromPtr(statbuf)); - } else { - return syscall2(.lstat, @intFromPtr(pathname), @intFromPtr(statbuf)); - } -} - -pub fn fstatat(dirfd: i32, path: [*:0]const u8, stat_buf: *Stat, flags: u32) usize { - if (native_arch == .riscv32 or native_arch.isLoongArch()) { - // riscv32 and loongarch have made the interesting decision to not implement some of - // the older stat syscalls, including this one. - @compileError("No fstatat syscall on this architecture."); - } else if (@hasField(SYS, "fstatat64")) { - return syscall4(.fstatat64, @as(usize, @bitCast(@as(isize, dirfd))), @intFromPtr(path), @intFromPtr(stat_buf), flags); - } else { - return syscall4(.fstatat, @as(usize, @bitCast(@as(isize, dirfd))), @intFromPtr(path), @intFromPtr(stat_buf), flags); - } -} - -pub fn statx(dirfd: i32, path: [*:0]const u8, flags: u32, mask: u32, statx_buf: *Statx) usize { +pub fn statx(dirfd: i32, path: [*:0]const u8, flags: u32, mask: STATX, statx_buf: *Statx) usize { return syscall5( .statx, @as(usize, @bitCast(@as(isize, dirfd))), @intFromPtr(path), flags, - mask, + @as(u32, @bitCast(mask)), @intFromPtr(statx_buf), ); } @@ -6940,27 +6891,85 @@ pub const utsname = extern struct { }; pub const HOST_NAME_MAX = 64; -pub const STATX_TYPE = 0x0001; -pub const STATX_MODE = 0x0002; -pub const STATX_NLINK = 0x0004; -pub const STATX_UID = 0x0008; -pub const STATX_GID = 0x0010; -pub const STATX_ATIME = 0x0020; -pub const STATX_MTIME = 0x0040; -pub const STATX_CTIME = 0x0080; -pub const STATX_INO = 0x0100; -pub const STATX_SIZE = 0x0200; -pub const STATX_BLOCKS = 0x0400; -pub const STATX_BASIC_STATS = 0x07ff; - -pub const STATX_BTIME = 0x0800; - -pub const STATX_ATTR_COMPRESSED = 0x0004; -pub const STATX_ATTR_IMMUTABLE = 0x0010; -pub const STATX_ATTR_APPEND = 0x0020; -pub const STATX_ATTR_NODUMP = 0x0040; -pub const STATX_ATTR_ENCRYPTED = 0x0800; -pub const STATX_ATTR_AUTOMOUNT = 0x1000; +/// Flags used to request specific members in `Statx` be filled out. +/// The `Statx.mask` member will be updated with what information the kernel +/// returned. Callers must check this field since support varies by kernel +/// version and filesystem. +pub const STATX = packed struct(u32) { + /// Want `mode & S.IFMT`. + TYPE: bool = false, + /// Want `mode & ~S.IFMT`. + MODE: bool = false, + /// Want the `nlink` member. + NLINK: bool = false, + /// Want the `uid` member. + UID: bool = false, + /// Want the `gid` member. + GID: bool = false, + /// Want the `atime` member. + ATIME: bool = false, + /// Want the `mtime` member. + MTIME: bool = false, + /// Want the `ctime` member. + CTIME: bool = false, + /// Want the `ino` member. + INO: bool = false, + /// Want the `size` member. + SIZE: bool = false, + /// Want the `blocks` member. + BLOCKS: bool = false, + /// Want the `btime` member. + BTIME: bool = false, + /// Want the `mnt_id` member. + MNT_ID: bool = false, + /// Want the `dio_mem_align` and `dio_offset_align` members. + DIOALIGN: bool = false, + /// Want the `stx_mnt_id` member. + MNT_ID_UNIQUE: bool = false, + /// Want the `sub` member. + SUBVOL: bool = false, + /// Want the `atomic_write_unit_min`, `atomic_write_unit_max` and + /// `atomic_write_segments_max` members. + WRITE_ATOMIC: bool = false, + /// Want the `dio_read_offset_align` member. + DIO_READ_ALIGN: bool = false, + __pad: u13 = 0, + /// Reserved for future expansion; must not be set. + __RESERVED: bool = false, + + pub const BASIC_STATS: STATX = @bitCast(@as(u32, 0x7ff)); +}; + +/// Attributes about the state or features of a file as a bitmask. +/// Flags marked [I] correspond to the `FS_IOC_SETFLAGS` values semantically. +/// See [FS_IOC_SETFLAGS(2const)](https://man7.org/linux/man-pages/man2/FS_IOC_GETFLAGS.2const.html) +/// for more. +pub const STATX_ATTR = packed struct(u64) { + __pad1: u3 = 0, + /// [I] File is compressed by the fs. + COMPRESSED: bool = false, + __pad2: u1 = 0, + /// [I] File is marked immutable. + IMMUTABLE: bool = false, + /// [I] File is append-only. + APPEND: bool = false, + /// [I] File is not to be dumped. + NODUMP: bool = false, + /// [I] File requires a key to decrypt in the filesystem. + ENCRYPTED: bool = false, + /// File names a directory that triggers an automount. + AUTOMOUNT: bool = false, + /// File names the root of a mount. + MOUNT_ROOT: bool = false, + /// [I] File is protected by the `dm-verity` device. + VERITY: bool = false, + /// File is currently in the CPU direct access state. + /// Does not correspond to the per-inode DAX flag that some filesystems support. + DAX: bool = false, + /// File supports atomic write operations. + WRITE_ATOMIC: bool = false, + __pad3: u50 = 0, +}; pub const statx_timestamp = extern struct { /// Number of seconds before or after `1970-01-01T00:00:00Z`. @@ -6974,11 +6983,11 @@ pub const statx_timestamp = extern struct { /// Renamed to `Statx` to not conflict with the `statx` function. pub const Statx = extern struct { /// Mask of bits indicating filled fields. - mask: u32, + mask: STATX, /// Block size for filesystem I/O. blksize: u32, /// Extra file attribute indicators. - attributes: u64, + attributes: STATX_ATTR, /// Number of hard links. nlink: u32, /// User ID of owner. @@ -6995,7 +7004,7 @@ pub const Statx = extern struct { /// Number of 512B blocks allocated. blocks: u64, /// Mask to show what's supported in `attributes`. - attributes_mask: u64, + attributes_mask: STATX_ATTR, /// Last access file timestamp. atime: statx_timestamp, /// Creation file timestamp. @@ -9977,6 +9986,46 @@ pub const wrapped = struct { } } + pub const StatxError = std.posix.UnexpectedError || error{ + /// Search permission is dened for one of the directories in `path`. + AccessDenied, + /// Too many symbolic links were encountered traversing `path`. + SymLinkLoop, + /// `path` is too long. + NameTooLong, + /// One of: + /// - A component of `path` does not exist. + /// - A component of `path` is not a directory. + /// - `path` is a relative and `dirfd` is not a directory file descriptor. + FileNotFound, + /// Insufficient memory is available. + SystemResources, + }; + + pub fn statx(dirfd: fd_t, path: [*:0]const u8, flags: u32, mask: STATX) StatxError!Statx { + 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; + + var stx = std.mem.zeroes(Statx); + const rc = sys.statx(dirfd, path, flags, mask, &stx); + return switch (errno(rc)) { + .SUCCESS => stx, + .ACCES => error.AccessDenied, + .BADF => unreachable, + .FAULT => unreachable, + .INVAL => unreachable, + .LOOP => error.SymLinkLoop, + .NAMETOOLONG => error.NameTooLong, + .NOENT => error.FileNotFound, + .NOTDIR => error.FileNotFound, + .NOMEM => error.FileNotFound, + else => |err| unexpectedErrno(err), + }; + } + const unexpectedErrno = std.posix.unexpectedErrno; fn invalidApiUsage() error{Unexpected} { diff --git a/lib/std/os/linux/IoUring.zig b/lib/std/os/linux/IoUring.zig index 4992a352d21b..8568acbcc404 100644 --- a/lib/std/os/linux/IoUring.zig +++ b/lib/std/os/linux/IoUring.zig @@ -958,7 +958,7 @@ pub fn statx( fd: linux.fd_t, path: [:0]const u8, flags: u32, - mask: u32, + mask: linux.STATX, buf: *linux.Statx, ) !*linux.io_uring_sqe { const sqe = try self.get_sqe(); @@ -2691,7 +2691,7 @@ test "statx" { tmp.dir.fd, path, 0, - linux.STATX_SIZE, + .{ .SIZE = true }, &buf, ); try testing.expectEqual(linux.IORING_OP.STATX, sqe.opcode); @@ -2718,7 +2718,7 @@ test "statx" { .flags = 0, }, cqe); - try testing.expect(buf.mask & linux.STATX_SIZE == linux.STATX_SIZE); + try testing.expect(buf.mask.SIZE); try testing.expectEqual(@as(u64, 6), buf.size); } diff --git a/lib/std/os/linux/aarch64.zig b/lib/std/os/linux/aarch64.zig index 4977593ef5a9..1f3d79a5c20c 100644 --- a/lib/std/os/linux/aarch64.zig +++ b/lib/std/os/linux/aarch64.zig @@ -151,35 +151,3 @@ pub const off_t = i64; pub const ino_t = u64; pub const dev_t = u64; pub const blkcnt_t = i64; - -// The `stat` definition used by the Linux kernel. -pub const Stat = extern struct { - dev: dev_t, - ino: ino_t, - mode: mode_t, - nlink: nlink_t, - uid: std.os.linux.uid_t, - gid: std.os.linux.gid_t, - rdev: dev_t, - __pad: u64, - size: off_t, - blksize: blksize_t, - __pad2: i32, - blocks: blkcnt_t, - atim: std.os.linux.timespec, - mtim: std.os.linux.timespec, - ctim: std.os.linux.timespec, - __unused: [2]u32, - - pub fn atime(self: @This()) std.os.linux.timespec { - return self.atim; - } - - pub fn mtime(self: @This()) std.os.linux.timespec { - return self.mtim; - } - - pub fn ctime(self: @This()) std.os.linux.timespec { - return self.ctim; - } -}; diff --git a/lib/std/os/linux/arm.zig b/lib/std/os/linux/arm.zig index 0a5b25f9f078..167036ea7e3f 100644 --- a/lib/std/os/linux/arm.zig +++ b/lib/std/os/linux/arm.zig @@ -187,35 +187,3 @@ pub const off_t = i64; pub const ino_t = u64; pub const dev_t = u64; pub const blkcnt_t = i64; - -// The `stat` definition used by the Linux kernel. -pub const Stat = extern struct { - dev: dev_t, - __dev_padding: u32, - __ino_truncated: u32, - mode: mode_t, - nlink: nlink_t, - uid: std.os.linux.uid_t, - gid: std.os.linux.gid_t, - rdev: dev_t, - __rdev_padding: u32, - size: off_t, - blksize: blksize_t, - blocks: blkcnt_t, - atim: std.os.linux.timespec, - mtim: std.os.linux.timespec, - ctim: std.os.linux.timespec, - ino: ino_t, - - pub fn atime(self: @This()) std.os.linux.timespec { - return self.atim; - } - - pub fn mtime(self: @This()) std.os.linux.timespec { - return self.mtim; - } - - pub fn ctime(self: @This()) std.os.linux.timespec { - return self.ctim; - } -}; diff --git a/lib/std/os/linux/hexagon.zig b/lib/std/os/linux/hexagon.zig index d3b4149d65dd..51252bb3b989 100644 --- a/lib/std/os/linux/hexagon.zig +++ b/lib/std/os/linux/hexagon.zig @@ -128,36 +128,4 @@ pub const ino_t = u64; pub const dev_t = u64; pub const blkcnt_t = i64; -// The `stat` definition used by the Linux kernel. -pub const Stat = extern struct { - dev: dev_t, - ino: ino_t, - mode: mode_t, - nlink: nlink_t, - uid: std.os.linux.uid_t, - gid: std.os.linux.gid_t, - rdev: dev_t, - __pad: u32, - size: off_t, - blksize: blksize_t, - __pad2: i32, - blocks: blkcnt_t, - atim: std.os.linux.timespec, - mtim: std.os.linux.timespec, - ctim: std.os.linux.timespec, - __unused: [2]u32, - - pub fn atime(self: @This()) std.os.linux.timespec { - return self.atim; - } - - pub fn mtime(self: @This()) std.os.linux.timespec { - return self.mtim; - } - - pub fn ctime(self: @This()) std.os.linux.timespec { - return self.ctim; - } -}; - pub const VDSO = void; diff --git a/lib/std/os/linux/io_uring_sqe.zig b/lib/std/os/linux/io_uring_sqe.zig index 5658206a66a8..808276170a22 100644 --- a/lib/std/os/linux/io_uring_sqe.zig +++ b/lib/std/os/linux/io_uring_sqe.zig @@ -420,10 +420,10 @@ pub const io_uring_sqe = extern struct { fd: linux.fd_t, path: [*:0]const u8, flags: u32, - mask: u32, + mask: linux.STATX, buf: *linux.Statx, ) void { - sqe.prep_rw(.STATX, fd, @intFromPtr(path), mask, @intFromPtr(buf)); + sqe.prep_rw(.STATX, fd, @intFromPtr(path), @as(u32, @bitCast(mask)), @intFromPtr(buf)); sqe.rw_flags = flags; } diff --git a/lib/std/os/linux/loongarch64.zig b/lib/std/os/linux/loongarch64.zig index 41450c997670..59cf8f6c1d35 100644 --- a/lib/std/os/linux/loongarch64.zig +++ b/lib/std/os/linux/loongarch64.zig @@ -125,47 +125,13 @@ pub fn clone() callconv(.naked) u64 { ); } -pub const blksize_t = i32; pub const nlink_t = u32; pub const time_t = i64; -pub const mode_t = u32; pub const off_t = i64; pub const ino_t = u64; pub const dev_t = u32; pub const blkcnt_t = i64; -// The `stat` definition used by the Linux kernel. -pub const Stat = extern struct { - dev: dev_t, - ino: ino_t, - mode: mode_t, - nlink: nlink_t, - uid: std.os.linux.uid_t, - gid: std.os.linux.gid_t, - rdev: dev_t, - _pad1: u64, - size: off_t, - blksize: blksize_t, - _pad2: i32, - blocks: blkcnt_t, - atim: std.os.linux.timespec, - mtim: std.os.linux.timespec, - ctim: std.os.linux.timespec, - _pad3: [2]u32, - - pub fn atime(self: @This()) std.os.linux.timespec { - return self.atim; - } - - pub fn mtime(self: @This()) std.os.linux.timespec { - return self.mtim; - } - - pub fn ctime(self: @This()) std.os.linux.timespec { - return self.ctim; - } -}; - pub const VDSO = struct { pub const CGT_SYM = "__vdso_clock_gettime"; pub const CGT_VER = "LINUX_5.10"; diff --git a/lib/std/os/linux/m68k.zig b/lib/std/os/linux/m68k.zig index 29d9adf1f7b0..b782c456f1e6 100644 --- a/lib/std/os/linux/m68k.zig +++ b/lib/std/os/linux/m68k.zig @@ -151,36 +151,5 @@ pub const ino_t = u64; pub const dev_t = u64; pub const blkcnt_t = i64; -pub const Stat = extern struct { - dev: dev_t, - __pad: i16, - __ino_truncated: i32, - mode: mode_t, - nlink: nlink_t, - uid: std.os.linux.uid_t, - gid: std.os.linux.gid_t, - rdev: dev_t, - __pad2: i16, - size: off_t, - blksize: blksize_t, - blocks: blkcnt_t, - atim: std.os.linux.timespec, - mtim: std.os.linux.timespec, - ctim: std.os.linux.timespec, - ino: ino_t, - - pub fn atime(self: @This()) std.os.linux.timespec { - return self.atim; - } - - pub fn mtime(self: @This()) std.os.linux.timespec { - return self.mtim; - } - - pub fn ctime(self: @This()) std.os.linux.timespec { - return self.ctim; - } -}; - // No VDSO used as of glibc 112a0ae18b831bf31f44d81b82666980312511d6. pub const VDSO = void; diff --git a/lib/std/os/linux/mips.zig b/lib/std/os/linux/mips.zig index 3bd7790bd527..c28a884e1a3e 100644 --- a/lib/std/os/linux/mips.zig +++ b/lib/std/os/linux/mips.zig @@ -238,35 +238,3 @@ pub const off_t = i64; pub const ino_t = u64; pub const dev_t = u64; pub const blkcnt_t = i64; - -// The `stat64` definition used by the Linux kernel. -pub const Stat = extern struct { - dev: dev_t, - __pad0: [2]u32, // -1 because our dev_t is u64 (kernel dev_t is really u32). - ino: ino_t, - mode: mode_t, - nlink: nlink_t, - uid: std.os.linux.uid_t, - gid: std.os.linux.gid_t, - rdev: dev_t, - __pad1: [2]u32, - size: off_t, - atim: std.os.linux.timespec, - mtim: std.os.linux.timespec, - ctim: std.os.linux.timespec, - blksize: blksize_t, - __pad3: u32, - blocks: blkcnt_t, - - pub fn atime(self: @This()) std.os.linux.timespec { - return self.atim; - } - - pub fn mtime(self: @This()) std.os.linux.timespec { - return self.mtim; - } - - pub fn ctime(self: @This()) std.os.linux.timespec { - return self.ctim; - } -}; diff --git a/lib/std/os/linux/mips64.zig b/lib/std/os/linux/mips64.zig index 82ad6184f1af..d96daaefa6b7 100644 --- a/lib/std/os/linux/mips64.zig +++ b/lib/std/os/linux/mips64.zig @@ -192,47 +192,3 @@ pub const off_t = i64; pub const ino_t = u64; pub const dev_t = u64; pub const blkcnt_t = i64; - -// The `stat` definition used by the Linux kernel. -pub const Stat = extern struct { - dev: dev_t, - __pad0: [2]u32, // -1 because our dev_t is u64 (kernel dev_t is really u32). - ino: ino_t, - mode: mode_t, - nlink: nlink_t, - uid: std.os.linux.uid_t, - gid: std.os.linux.gid_t, - rdev: dev_t, - __pad1: [2]u32, // -1 because our dev_t is u64 (kernel dev_t is really u32). - size: off_t, - atim: u32, - atim_nsec: u32, - mtim: u32, - mtim_nsec: u32, - ctim: u32, - ctim_nsec: u32, - blksize: blksize_t, - __pad3: u32, - blocks: blkcnt_t, - - pub fn atime(self: @This()) std.os.linux.timespec { - return .{ - .sec = self.atim, - .nsec = self.atim_nsec, - }; - } - - pub fn mtime(self: @This()) std.os.linux.timespec { - return .{ - .sec = self.mtim, - .nsec = self.mtim_nsec, - }; - } - - pub fn ctime(self: @This()) std.os.linux.timespec { - return .{ - .sec = self.ctim, - .nsec = self.ctim_nsec, - }; - } -}; diff --git a/lib/std/os/linux/mipsn32.zig b/lib/std/os/linux/mipsn32.zig index 584edf7c8019..858db84a20f1 100644 --- a/lib/std/os/linux/mipsn32.zig +++ b/lib/std/os/linux/mipsn32.zig @@ -192,47 +192,3 @@ pub const off_t = i64; pub const ino_t = u64; pub const dev_t = u64; pub const blkcnt_t = i64; - -// The `stat` definition used by the Linux kernel. -pub const Stat = extern struct { - dev: dev_t, - __pad0: [2]u32, // -1 because our dev_t is u64 (kernel dev_t is really u32). - ino: ino_t, - mode: mode_t, - nlink: nlink_t, - uid: std.os.linux.uid_t, - gid: std.os.linux.gid_t, - rdev: dev_t, - __pad1: [2]u32, // -1 because our dev_t is u64 (kernel dev_t is really u32). - size: off_t, - atim: u32, - atim_nsec: u32, - mtim: u32, - mtim_nsec: u32, - ctim: u32, - ctim_nsec: u32, - blksize: blksize_t, - __pad3: u32, - blocks: blkcnt_t, - - pub fn atime(self: @This()) std.os.linux.timespec { - return .{ - .sec = self.atim, - .nsec = self.atim_nsec, - }; - } - - pub fn mtime(self: @This()) std.os.linux.timespec { - return .{ - .sec = self.mtim, - .nsec = self.mtim_nsec, - }; - } - - pub fn ctime(self: @This()) std.os.linux.timespec { - return .{ - .sec = self.ctim, - .nsec = self.ctim_nsec, - }; - } -}; diff --git a/lib/std/os/linux/or1k.zig b/lib/std/os/linux/or1k.zig index 1054e52d1991..d0a5d47d6001 100644 --- a/lib/std/os/linux/or1k.zig +++ b/lib/std/os/linux/or1k.zig @@ -139,35 +139,3 @@ pub const off_t = i64; pub const ino_t = u64; pub const dev_t = u64; pub const blkcnt_t = i64; - -// The `stat64` definition used by the Linux kernel. -pub const Stat = extern struct { - dev: dev_t, - ino: ino_t, - mode: mode_t, - nlink: nlink_t, - uid: std.os.linux.uid_t, - gid: std.os.linux.gid_t, - rdev: dev_t, - _pad0: [2]u32, - size: off_t, - blksize: blksize_t, - _pad1: u32, - blocks: blkcnt_t, - atim: std.os.linux.timespec, - mtim: std.os.linux.timespec, - ctim: std.os.linux.timespec, - _pad2: [2]u32, - - pub fn atime(self: @This()) std.os.linux.timespec { - return self.atim; - } - - pub fn mtime(self: @This()) std.os.linux.timespec { - return self.mtim; - } - - pub fn ctime(self: @This()) std.os.linux.timespec { - return self.ctim; - } -}; diff --git a/lib/std/os/linux/powerpc.zig b/lib/std/os/linux/powerpc.zig index d96a9d67f01d..4b998af0fd1f 100644 --- a/lib/std/os/linux/powerpc.zig +++ b/lib/std/os/linux/powerpc.zig @@ -276,35 +276,3 @@ pub const mode_t = u32; pub const off_t = i64; pub const ino_t = u64; pub const dev_t = u64; -pub const blkcnt_t = i64; - -// The `stat` definition used by the Linux kernel. -pub const Stat = extern struct { - dev: dev_t, - ino: ino_t, - mode: mode_t, - nlink: nlink_t, - uid: std.os.linux.uid_t, - gid: std.os.linux.gid_t, - rdev: dev_t, - __rdev_padding: i16, - size: off_t, - blksize: blksize_t, - blocks: blkcnt_t, - atim: std.os.linux.timespec, - mtim: std.os.linux.timespec, - ctim: std.os.linux.timespec, - __unused: [2]u32, - - pub fn atime(self: @This()) std.os.linux.timespec { - return self.atim; - } - - pub fn mtime(self: @This()) std.os.linux.timespec { - return self.mtim; - } - - pub fn ctime(self: @This()) std.os.linux.timespec { - return self.ctim; - } -}; diff --git a/lib/std/os/linux/powerpc64.zig b/lib/std/os/linux/powerpc64.zig index d1663feaa88e..a94611c98bde 100644 --- a/lib/std/os/linux/powerpc64.zig +++ b/lib/std/os/linux/powerpc64.zig @@ -262,33 +262,3 @@ pub const off_t = i64; pub const ino_t = u64; pub const dev_t = u64; pub const blkcnt_t = i64; - -// The `stat` definition used by the Linux kernel. -pub const Stat = extern struct { - dev: dev_t, - ino: ino_t, - nlink: nlink_t, - mode: mode_t, - uid: std.os.linux.uid_t, - gid: std.os.linux.gid_t, - rdev: dev_t, - size: off_t, - blksize: blksize_t, - blocks: blkcnt_t, - atim: std.os.linux.timespec, - mtim: std.os.linux.timespec, - ctim: std.os.linux.timespec, - __unused: [3]u64, - - pub fn atime(self: @This()) std.os.linux.timespec { - return self.atim; - } - - pub fn mtime(self: @This()) std.os.linux.timespec { - return self.mtim; - } - - pub fn ctime(self: @This()) std.os.linux.timespec { - return self.ctim; - } -}; diff --git a/lib/std/os/linux/riscv32.zig b/lib/std/os/linux/riscv32.zig index 34f73506a1ab..d7223bde182a 100644 --- a/lib/std/os/linux/riscv32.zig +++ b/lib/std/os/linux/riscv32.zig @@ -131,39 +131,6 @@ pub const mode_t = u32; pub const off_t = i64; pub const ino_t = u64; pub const dev_t = u64; -pub const blkcnt_t = i64; - -// The `stat` definition used by the Linux kernel. -pub const Stat = extern struct { - dev: dev_t, - ino: ino_t, - mode: mode_t, - nlink: nlink_t, - uid: std.os.linux.uid_t, - gid: std.os.linux.gid_t, - rdev: dev_t, - __pad: u32, - size: off_t, - blksize: blksize_t, - __pad2: i32, - blocks: blkcnt_t, - atim: std.os.linux.timespec, - mtim: std.os.linux.timespec, - ctim: std.os.linux.timespec, - __unused: [2]u32, - - pub fn atime(self: @This()) std.os.linux.timespec { - return self.atim; - } - - pub fn mtime(self: @This()) std.os.linux.timespec { - return self.mtim; - } - - pub fn ctime(self: @This()) std.os.linux.timespec { - return self.ctim; - } -}; pub const VDSO = struct { pub const CGT_SYM = "__vdso_clock_gettime"; diff --git a/lib/std/os/linux/riscv64.zig b/lib/std/os/linux/riscv64.zig index e404693df08e..59b12250ac0f 100644 --- a/lib/std/os/linux/riscv64.zig +++ b/lib/std/os/linux/riscv64.zig @@ -131,39 +131,6 @@ pub const mode_t = u32; pub const off_t = i64; pub const ino_t = u64; pub const dev_t = u64; -pub const blkcnt_t = i64; - -// The `stat` definition used by the Linux kernel. -pub const Stat = extern struct { - dev: dev_t, - ino: ino_t, - mode: mode_t, - nlink: nlink_t, - uid: std.os.linux.uid_t, - gid: std.os.linux.gid_t, - rdev: dev_t, - __pad: u64, - size: off_t, - blksize: blksize_t, - __pad2: i32, - blocks: blkcnt_t, - atim: std.os.linux.timespec, - mtim: std.os.linux.timespec, - ctim: std.os.linux.timespec, - __unused: [2]u32, - - pub fn atime(self: @This()) std.os.linux.timespec { - return self.atim; - } - - pub fn mtime(self: @This()) std.os.linux.timespec { - return self.mtim; - } - - pub fn ctime(self: @This()) std.os.linux.timespec { - return self.ctim; - } -}; pub const VDSO = struct { pub const CGT_SYM = "__vdso_clock_gettime"; diff --git a/lib/std/os/linux/s390x.zig b/lib/std/os/linux/s390x.zig index 0a09982f2a1c..13b1b5307ac8 100644 --- a/lib/std/os/linux/s390x.zig +++ b/lib/std/os/linux/s390x.zig @@ -159,37 +159,6 @@ pub const mode_t = u32; pub const off_t = i64; pub const ino_t = u64; pub const dev_t = u64; -pub const blkcnt_t = i64; - -// The `stat` definition used by the Linux kernel. -pub const Stat = extern struct { - dev: dev_t, - ino: ino_t, - nlink: nlink_t, - mode: mode_t, - uid: std.os.linux.uid_t, - gid: std.os.linux.gid_t, - rdev: dev_t, - size: off_t, - atim: std.os.linux.timespec, - mtim: std.os.linux.timespec, - ctim: std.os.linux.timespec, - blksize: blksize_t, - blocks: blkcnt_t, - __unused: [3]c_ulong, - - pub fn atime(self: @This()) std.os.linux.timespec { - return self.atim; - } - - pub fn mtime(self: @This()) std.os.linux.timespec { - return self.mtim; - } - - pub fn ctime(self: @This()) std.os.linux.timespec { - return self.ctim; - } -}; pub const VDSO = struct { pub const CGT_SYM = "__kernel_clock_gettime"; diff --git a/lib/std/os/linux/sparc64.zig b/lib/std/os/linux/sparc64.zig index 76506dbfa3df..ad03d398b763 100644 --- a/lib/std/os/linux/sparc64.zig +++ b/lib/std/os/linux/sparc64.zig @@ -236,38 +236,3 @@ pub const dev_t = u64; pub const nlink_t = u32; pub const blksize_t = i64; pub const blkcnt_t = i64; - -// The `stat64` definition used by the kernel. -pub const Stat = extern struct { - dev: dev_t, - ino: ino_t, - nlink: nlink_t, - _pad: i32, - - mode: mode_t, - uid: std.os.linux.uid_t, - gid: std.os.linux.gid_t, - __pad0: u32, - - rdev: dev_t, - size: i64, - blksize: blksize_t, - blocks: blkcnt_t, - - atim: std.os.linux.timespec, - mtim: std.os.linux.timespec, - ctim: std.os.linux.timespec, - __unused: [3]u64, - - pub fn atime(self: @This()) std.os.linux.timespec { - return self.atim; - } - - pub fn mtime(self: @This()) std.os.linux.timespec { - return self.mtim; - } - - pub fn ctime(self: @This()) std.os.linux.timespec { - return self.ctim; - } -}; diff --git a/lib/std/os/linux/test.zig b/lib/std/os/linux/test.zig index f5533a54bdf4..a1b7a31354b9 100644 --- a/lib/std/os/linux/test.zig +++ b/lib/std/os/linux/test.zig @@ -10,8 +10,6 @@ const expectEqual = std.testing.expectEqual; const fs = std.fs; test "fallocate" { - if (builtin.cpu.arch.isMIPS64() and (builtin.abi == .gnuabin32 or builtin.abi == .muslabin32)) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/23809 - var tmp = std.testing.tmpDir(.{}); defer tmp.cleanup(); @@ -84,26 +82,22 @@ test "statx" { var file = try tmp.dir.createFile(tmp_file_name, .{}); defer file.close(); - var statx_buf: linux.Statx = undefined; - switch (linux.errno(linux.statx(file.handle, "", linux.AT.EMPTY_PATH, linux.STATX_BASIC_STATS, &statx_buf))) { - .SUCCESS => {}, - else => unreachable, - } - - if (builtin.cpu.arch == .riscv32 or builtin.cpu.arch.isLoongArch()) return error.SkipZigTest; // No fstatat, so the rest of the test is meaningless. - - var stat_buf: linux.Stat = undefined; - switch (linux.errno(linux.fstatat(file.handle, "", &stat_buf, linux.AT.EMPTY_PATH))) { + var buf: linux.Statx = undefined; + switch (linux.errno(linux.statx(file.handle, "", linux.AT.EMPTY_PATH, .BASIC_STATS, &buf))) { .SUCCESS => {}, else => unreachable, } - try expect(stat_buf.mode == statx_buf.mode); - try expect(@as(u32, @bitCast(stat_buf.uid)) == statx_buf.uid); - try expect(@as(u32, @bitCast(stat_buf.gid)) == statx_buf.gid); - try expect(@as(u64, @bitCast(@as(i64, stat_buf.size))) == statx_buf.size); - try expect(@as(u64, @bitCast(@as(i64, stat_buf.blksize))) == statx_buf.blksize); - try expect(@as(u64, @bitCast(@as(i64, stat_buf.blocks))) == statx_buf.blocks); + const uid = linux.getuid(); + const gid = linux.getgid(); + if (buf.mask.MODE) + try expectEqual(@as(linux.mode_t, linux.S.IFREG), buf.mode & linux.S.IFMT); + if (buf.mask.UID) + try expectEqual(uid, buf.uid); + if (buf.mask.GID) + try expectEqual(gid, buf.gid); + if (buf.mask.SIZE) + try expectEqual(@as(u64, 0), buf.size); } test "user and group ids" { diff --git a/lib/std/os/linux/x32.zig b/lib/std/os/linux/x32.zig index ac596844d670..3ebebc1d8b77 100644 --- a/lib/std/os/linux/x32.zig +++ b/lib/std/os/linux/x32.zig @@ -155,36 +155,3 @@ pub const ARCH = struct { pub const GET_FS = 0x1003; pub const GET_GS = 0x1004; }; - -// The `stat` definition used by the Linux kernel. -pub const Stat = extern struct { - dev: dev_t, - ino: ino_t, - nlink: nlink_t, - - mode: mode_t, - uid: std.os.linux.uid_t, - gid: std.os.linux.gid_t, - __pad0: u32, - rdev: dev_t, - size: off_t, - blksize: blksize_t, - blocks: i64, - - atim: std.os.linux.timespec, - mtim: std.os.linux.timespec, - ctim: std.os.linux.timespec, - __unused: [3]i32, - - pub fn atime(self: @This()) std.os.linux.timespec { - return self.atim; - } - - pub fn mtime(self: @This()) std.os.linux.timespec { - return self.mtim; - } - - pub fn ctime(self: @This()) std.os.linux.timespec { - return self.ctim; - } -}; diff --git a/lib/std/os/linux/x86.zig b/lib/std/os/linux/x86.zig index a68a4af317b6..f40f4492d126 100644 --- a/lib/std/os/linux/x86.zig +++ b/lib/std/os/linux/x86.zig @@ -204,38 +204,6 @@ pub const ino_t = u64; pub const dev_t = u64; pub const blkcnt_t = i64; -// The `stat` definition used by the Linux kernel. -pub const Stat = extern struct { - dev: dev_t, - __dev_padding: u32, - __ino_truncated: u32, - mode: mode_t, - nlink: nlink_t, - uid: std.os.linux.uid_t, - gid: std.os.linux.gid_t, - rdev: dev_t, - __rdev_padding: u32, - size: off_t, - blksize: blksize_t, - blocks: blkcnt_t, - atim: std.os.linux.timespec, - mtim: std.os.linux.timespec, - ctim: std.os.linux.timespec, - ino: ino_t, - - pub fn atime(self: @This()) std.os.linux.timespec { - return self.atim; - } - - pub fn mtime(self: @This()) std.os.linux.timespec { - return self.mtim; - } - - pub fn ctime(self: @This()) std.os.linux.timespec { - return self.ctim; - } -}; - pub const user_desc = extern struct { entry_number: u32, base_addr: u32, diff --git a/lib/std/os/linux/x86_64.zig b/lib/std/os/linux/x86_64.zig index d9c2d17f09e3..f3e31675141a 100644 --- a/lib/std/os/linux/x86_64.zig +++ b/lib/std/os/linux/x86_64.zig @@ -155,36 +155,3 @@ pub const ARCH = struct { pub const GET_FS = 0x1003; pub const GET_GS = 0x1004; }; - -// The `stat` definition used by the Linux kernel. -pub const Stat = extern struct { - dev: dev_t, - ino: ino_t, - nlink: u64, - - mode: u32, - uid: std.os.linux.uid_t, - gid: std.os.linux.gid_t, - __pad0: u32, - rdev: dev_t, - size: off_t, - blksize: i64, - blocks: i64, - - atim: std.os.linux.timespec, - mtim: std.os.linux.timespec, - ctim: std.os.linux.timespec, - __unused: [3]i64, - - pub fn atime(self: @This()) std.os.linux.timespec { - return self.atim; - } - - pub fn mtime(self: @This()) std.os.linux.timespec { - return self.mtim; - } - - pub fn ctime(self: @This()) std.os.linux.timespec { - return self.ctim; - } -}; diff --git a/lib/std/posix.zig b/lib/std/posix.zig index 417691053774..6210111b7275 100644 --- a/lib/std/posix.zig +++ b/lib/std/posix.zig @@ -119,7 +119,18 @@ pub const STDIN_FILENO = system.STDIN_FILENO; pub const STDOUT_FILENO = system.STDOUT_FILENO; pub const SYS = system.SYS; pub const Sigaction = system.Sigaction; -pub const Stat = system.Stat; +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, +}; pub const T = system.T; pub const TCP = system.TCP; pub const VDSO = system.VDSO; @@ -480,15 +491,21 @@ fn fchmodat2(dirfd: fd_t, path: []const u8, mode: mode_t, flags: u32) FChmodAtEr } defer close(pathfd); - const stat = fstatatZ(pathfd, "", AT.EMPTY_PATH) catch |err| switch (err) { + const path_mode = if (linux.wrapped.statx( + pathfd, + "", + AT.EMPTY_PATH, + .{ .TYPE = true }, + )) |stx| blk: { + assert(stx.mask.TYPE); + break :blk stx.mode; + } else |err| switch (err) { error.NameTooLong => unreachable, error.FileNotFound => unreachable, - error.Streaming => unreachable, - error.BadPathName => return error.Unexpected, - error.Canceled => return error.Canceled, else => |e| return e, }; - if ((stat.mode & S.IFMT) == S.IFLNK) + // Even though we only wanted TYPE, the kernel can still fill in the additional bits. + if ((path_mode & S.IFMT) == S.IFLNK) return error.OperationNotSupported; var procfs_buf: ["/proc/self/fd/-2147483648\x00".len]u8 = undefined; @@ -3842,13 +3859,9 @@ pub fn fstat(fd: fd_t) FStatError!Stat { if (native_os == .wasi and !builtin.link_libc) { return Stat.fromFilestat(try std.os.fstat_wasi(fd)); } - if (native_os == .windows) { - @compileError("fstat is not yet implemented on Windows"); - } - const fstat_sym = if (lfs64_abi) system.fstat64 else system.fstat; var stat = mem.zeroes(Stat); - switch (errno(fstat_sym(fd, &stat))) { + switch (errno(system.fstat(fd, &stat))) { .SUCCESS => return stat, .INVAL => unreachable, .BADF => unreachable, // Always a race condition. @@ -3888,9 +3901,8 @@ pub fn fstatatZ(dirfd: fd_t, pathname: [*:0]const u8, flags: u32) FStatAtError!S @compileError("use std.Io instead"); } - const fstatat_sym = if (lfs64_abi) system.fstatat64 else system.fstatat; var stat = mem.zeroes(Stat); - switch (errno(fstatat_sym(dirfd, pathname, &stat, flags))) { + switch (errno(system.fstatat(dirfd, pathname, &stat, flags))) { .SUCCESS => return stat, .INVAL => unreachable, .BADF => unreachable, // Always a race condition. diff --git a/lib/std/posix/test.zig b/lib/std/posix/test.zig index aaaaa1d948a9..c69b2d1bece8 100644 --- a/lib/std/posix/test.zig +++ b/lib/std/posix/test.zig @@ -16,6 +16,7 @@ const AtomicRmwOp = std.builtin.AtomicRmwOp; const AtomicOrder = std.builtin.AtomicOrder; const native_os = builtin.target.os.tag; const tmpDir = std.testing.tmpDir; +const AT = posix.AT; // NOTE: several additional tests are in test/standalone/posix/. Any tests that mutate // process-wide POSIX state (cwd, signals, etc) cannot be Zig unit tests and should be over there. @@ -123,10 +124,24 @@ fn testReadlink(target_path: []const u8, symlink_path: []const u8) !void { try expect(mem.eql(u8, target_path, given)); } -test "linkat with different directories" { - if ((builtin.cpu.arch == .riscv32 or builtin.cpu.arch.isLoongArch()) and builtin.os.tag == .linux and !builtin.link_libc) return error.SkipZigTest; // No `fstatat()`. - if (builtin.cpu.arch.isMIPS64()) return error.SkipZigTest; // `nstat.nlink` assertion is failing with LLVM 20+ for unclear reasons. +fn getLinkInfo(fd: posix.fd_t) !struct { posix.ino_t, posix.nlink_t } { + if (native_os == .linux) { + const stx = try linux.wrapped.statx( + fd, + "", + posix.AT.EMPTY_PATH, + .{ .INO = true, .NLINK = true }, + ); + std.debug.assert(stx.mask.INO); + std.debug.assert(stx.mask.NLINK); + return .{ stx.ino, stx.nlink }; + } + const st = try posix.fstat(fd); + return .{ st.ino, st.nlink }; +} + +test "linkat with different directories" { switch (native_os) { .wasi, .linux, .illumos => {}, else => return error.SkipZigTest, @@ -153,19 +168,48 @@ test "linkat with different directories" { defer nfd.close(); { - const estat = try posix.fstat(efd.handle); - const nstat = try posix.fstat(nfd.handle); - try testing.expectEqual(estat.ino, nstat.ino); - try testing.expectEqual(@as(@TypeOf(nstat.nlink), 2), nstat.nlink); + const eino, _ = try getLinkInfo(efd.handle); + const nino, const nlink = try getLinkInfo(nfd.handle); + try testing.expectEqual(eino, nino); + try testing.expectEqual(@as(posix.nlink_t, 2), nlink); } // Test 2: remove link try posix.unlinkat(subdir.fd, link_name, 0); + _, const elink = try getLinkInfo(efd.handle); + try testing.expectEqual(@as(posix.nlink_t, 1), elink); +} - { - const estat = try posix.fstat(efd.handle); - try testing.expectEqual(@as(@TypeOf(estat.nlink), 1), estat.nlink); - } +test "fstatat" { + if (posix.Stat == void) return error.SkipZigTest; + + var tmp = tmpDir(.{}); + defer tmp.cleanup(); + + // create dummy file + const contents = "nonsense"; + try tmp.dir.writeFile(.{ .sub_path = "file.txt", .data = contents }); + + // fetch file's info on the opened fd directly + const file = try tmp.dir.openFile("file.txt", .{}); + const stat = try posix.fstat(file.handle); + defer file.close(); + + // now repeat but using `fstatat` instead + const statat = try posix.fstatat(tmp.dir.fd, "file.txt", posix.AT.SYMLINK_NOFOLLOW); + + try expectEqual(stat.dev, statat.dev); + try expectEqual(stat.ino, statat.ino); + try expectEqual(stat.nlink, statat.nlink); + try expectEqual(stat.mode, statat.mode); + try expectEqual(stat.uid, statat.uid); + try expectEqual(stat.gid, statat.gid); + try expectEqual(stat.rdev, statat.rdev); + try expectEqual(stat.size, statat.size); + try expectEqual(stat.blksize, statat.blksize); + // The stat.blocks/statat.blocks count is managed by the filesystem and may + // change if the file is stored in a journal or "inline". + // try expectEqual(stat.blocks, statat.blocks); } test "readlinkat" { @@ -906,14 +950,31 @@ test "pwrite with empty buffer" { try expectEqual(rc, 0); } +fn getFileMode(dir: posix.fd_t, path: []const u8) !posix.mode_t { + const path_z = try posix.toPosixPath(path); + const mode: posix.mode_t = if (native_os == .linux) blk: { + const stx = try linux.wrapped.statx( + dir, + &path_z, + posix.AT.SYMLINK_NOFOLLOW, + .{ .MODE = true }, + ); + std.debug.assert(stx.mask.MODE); + break :blk stx.mode; + } else blk: { + const st = try posix.fstatatZ(dir, &path_z, posix.AT.SYMLINK_NOFOLLOW); + break :blk st.mode; + }; + + return mode & 0b111_111_111; +} + fn expectMode(dir: posix.fd_t, file: []const u8, mode: posix.mode_t) !void { - const st = try posix.fstatat(dir, file, posix.AT.SYMLINK_NOFOLLOW); - try expectEqual(mode, st.mode & 0b111_111_111); + const actual = try getFileMode(dir, file); + try expectEqual(mode, actual & 0b111_111_111); } test "fchmodat smoke test" { - if (builtin.cpu.arch.isMIPS64() and (builtin.abi == .gnuabin32 or builtin.abi == .muslabin32)) return error.SkipZigTest; // https://github.com/ziglang/zig/issues/23808 - if (!std.fs.has_executable_bit) return error.SkipZigTest; var tmp = tmpDir(.{}); @@ -928,13 +989,8 @@ test "fchmodat smoke test" { ); posix.close(fd); - if ((builtin.cpu.arch == .riscv32 or builtin.cpu.arch.isLoongArch()) and builtin.os.tag == .linux and !builtin.link_libc) return error.SkipZigTest; // No `fstatat()`. - try posix.symlinkat("regfile", tmp.dir.fd, "symlink"); - const sym_mode = blk: { - const st = try posix.fstatat(tmp.dir.fd, "symlink", posix.AT.SYMLINK_NOFOLLOW); - break :blk st.mode & 0b111_111_111; - }; + const sym_mode = try getFileMode(tmp.dir.fd, "symlink"); try posix.fchmodat(tmp.dir.fd, "regfile", 0o640, 0); try expectMode(tmp.dir.fd, "regfile", 0o640); diff --git a/src/link/MappedFile.zig b/src/link/MappedFile.zig index e189a1df9444..c916c68d2876 100644 --- a/src/link/MappedFile.zig +++ b/src/link/MappedFile.zig @@ -54,6 +54,20 @@ pub fn init(file: std.Io.File, gpa: std.mem.Allocator) !MappedFile { }, }; } + if (is_linux) { + const statx = try linux.wrapped.statx( + mf.file.handle, + "", + std.posix.AT.EMPTY_PATH, + .{ .TYPE = true, .SIZE = true, .BLOCKS = true }, + ); + 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) }; + } 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) }; diff --git a/test/standalone/glibc_compat/glibc_runtime_check.zig b/test/standalone/glibc_compat/glibc_runtime_check.zig index 30265a3adce9..82f4a54ee1e8 100644 --- a/test/standalone/glibc_compat/glibc_runtime_check.zig +++ b/test/standalone/glibc_compat/glibc_runtime_check.zig @@ -23,16 +23,19 @@ const c_string = @cImport( // Version of glibc this test is being built to run against const glibc_ver = builtin.os.versionRange().gnuLibCVersion().?; +extern "c" fn fstatat(dirfd: i32, path: [*:0]const u8, buf: [*]const u8, flag: u32) c_int; +extern "c" fn stat(noalias path: [*:0]const u8, noalias buf: [*]const u8) c_int; + // PR #17034 - fstat moved between libc_nonshared and libc fn checkStat() !void { const cwdFd = std.fs.cwd().fd; - var stat = std.mem.zeroes(std.c.Stat); - var result = std.c.fstatat(cwdFd, "a_file_that_definitely_does_not_exist", &stat, 0); + var buf: [256]u8 = @splat(0); + var result = fstatat(cwdFd, "a_file_that_definitely_does_not_exist", &buf, 0); assert(result == -1); assert(std.posix.errno(result) == .NOENT); - result = std.c.stat("a_file_that_definitely_does_not_exist", &stat); + result = stat("a_file_that_definitely_does_not_exist", &buf); assert(result == -1); assert(std.posix.errno(result) == .NOENT); } diff --git a/test/standalone/posix/relpaths.zig b/test/standalone/posix/relpaths.zig index f7ced28cdbf1..40e2e09464bf 100644 --- a/test/standalone/posix/relpaths.zig +++ b/test/standalone/posix/relpaths.zig @@ -48,20 +48,29 @@ fn test_symlink(a: std.mem.Allocator, tmp: std.testing.TmpDir) !void { try std.testing.expectEqualStrings(target_name, given); } +fn getLinkInfo(fd: std.posix.fd_t) !struct { std.posix.ino_t, std.posix.nlink_t } { + if (builtin.target.os.tag == .linux) { + const stx = try std.os.linux.wrapped.statx( + fd, + "", + std.posix.AT.EMPTY_PATH, + .{ .INO = true, .NLINK = true }, + ); + std.debug.assert(stx.mask.INO); + std.debug.assert(stx.mask.NLINK); + return .{ stx.ino, stx.nlink }; + } + + const st = try std.posix.fstat(fd); + return .{ st.ino, st.nlink }; +} + fn test_link(tmp: std.testing.TmpDir) !void { switch (builtin.target.os.tag) { .linux, .illumos => {}, else => return, } - if ((builtin.cpu.arch == .riscv32 or builtin.cpu.arch.isLoongArch()) and builtin.target.os.tag == .linux and !builtin.link_libc) { - return; // No `fstat()`. - } - - if (builtin.cpu.arch.isMIPS64()) { - return; // `nstat.nlink` assertion is failing with LLVM 20+ for unclear reasons. - } - const target_name = "link-target"; const link_name = "newlink"; @@ -78,17 +87,16 @@ fn test_link(tmp: std.testing.TmpDir) !void { defer nfd.close(); { - const estat = try std.posix.fstat(efd.handle); - const nstat = try std.posix.fstat(nfd.handle); - try std.testing.expectEqual(estat.ino, nstat.ino); - try std.testing.expectEqual(@as(@TypeOf(nstat.nlink), 2), nstat.nlink); + const eino, _ = try getLinkInfo(efd.handle); + const nino, const nlink = try getLinkInfo(nfd.handle); + try std.testing.expectEqual(eino, nino); + try std.testing.expectEqual(@as(std.posix.nlink_t, 2), nlink); } // Test 2: Remove the link and see the stats update try std.posix.unlink(link_name); - { - const estat = try std.posix.fstat(efd.handle); - try std.testing.expectEqual(@as(@TypeOf(estat.nlink), 1), estat.nlink); + _, const elink = try getLinkInfo(efd.handle); + try std.testing.expectEqual(@as(std.posix.nlink_t, 1), elink); } } From f4f268b669c1fe02d93d8a38acccddb8d0d43b34 Mon Sep 17 00:00:00 2001 From: Stephen Gregoratto Date: Mon, 20 Oct 2025 09:29:12 +1100 Subject: [PATCH 53/53] Linux: Dedupe generic decls Commit #fc7a5f2 moved many of the `_t` types up a level, but didn't remove them from arch_bits. Since `Stat` is gone, all but `time_t` can be removed. --- lib/std/os/linux/aarch64.zig | 7 ------- lib/std/os/linux/arm.zig | 7 ------- lib/std/os/linux/hexagon.zig | 7 ------- lib/std/os/linux/loongarch64.zig | 5 ----- lib/std/os/linux/m68k.zig | 7 ------- lib/std/os/linux/mips.zig | 7 ------- lib/std/os/linux/mips64.zig | 7 ------- lib/std/os/linux/mipsn32.zig | 7 ------- lib/std/os/linux/or1k.zig | 7 ------- lib/std/os/linux/powerpc.zig | 6 ------ lib/std/os/linux/powerpc64.zig | 7 ------- lib/std/os/linux/riscv32.zig | 6 ------ lib/std/os/linux/riscv64.zig | 6 ------ lib/std/os/linux/s390x.zig | 6 ------ lib/std/os/linux/sparc64.zig | 7 ------- lib/std/os/linux/x32.zig | 7 ------- lib/std/os/linux/x86.zig | 7 ------- lib/std/os/linux/x86_64.zig | 7 ------- 18 files changed, 120 deletions(-) diff --git a/lib/std/os/linux/aarch64.zig b/lib/std/os/linux/aarch64.zig index 1f3d79a5c20c..ddf1a61a253e 100644 --- a/lib/std/os/linux/aarch64.zig +++ b/lib/std/os/linux/aarch64.zig @@ -143,11 +143,4 @@ pub const VDSO = struct { pub const CGT_VER = "LINUX_2.6.39"; }; -pub const blksize_t = i32; -pub const nlink_t = u32; pub const time_t = i64; -pub const mode_t = u32; -pub const off_t = i64; -pub const ino_t = u64; -pub const dev_t = u64; -pub const blkcnt_t = i64; diff --git a/lib/std/os/linux/arm.zig b/lib/std/os/linux/arm.zig index 167036ea7e3f..0bcdaaf31931 100644 --- a/lib/std/os/linux/arm.zig +++ b/lib/std/os/linux/arm.zig @@ -179,11 +179,4 @@ pub const HWCAP = struct { pub const EVTSTRM = 1 << 21; }; -pub const blksize_t = i32; -pub const nlink_t = u32; pub const time_t = i32; -pub const mode_t = u32; -pub const off_t = i64; -pub const ino_t = u64; -pub const dev_t = u64; -pub const blkcnt_t = i64; diff --git a/lib/std/os/linux/hexagon.zig b/lib/std/os/linux/hexagon.zig index 51252bb3b989..ff5331467d15 100644 --- a/lib/std/os/linux/hexagon.zig +++ b/lib/std/os/linux/hexagon.zig @@ -119,13 +119,6 @@ pub fn clone() callconv(.naked) u32 { ); } -pub const blksize_t = i32; -pub const nlink_t = u32; pub const time_t = i64; -pub const mode_t = u32; -pub const off_t = i64; -pub const ino_t = u64; -pub const dev_t = u64; -pub const blkcnt_t = i64; pub const VDSO = void; diff --git a/lib/std/os/linux/loongarch64.zig b/lib/std/os/linux/loongarch64.zig index 59cf8f6c1d35..8e7677f276ff 100644 --- a/lib/std/os/linux/loongarch64.zig +++ b/lib/std/os/linux/loongarch64.zig @@ -125,12 +125,7 @@ pub fn clone() callconv(.naked) u64 { ); } -pub const nlink_t = u32; pub const time_t = i64; -pub const off_t = i64; -pub const ino_t = u64; -pub const dev_t = u32; -pub const blkcnt_t = i64; pub const VDSO = struct { pub const CGT_SYM = "__vdso_clock_gettime"; diff --git a/lib/std/os/linux/m68k.zig b/lib/std/os/linux/m68k.zig index b782c456f1e6..2c5f13aa6321 100644 --- a/lib/std/os/linux/m68k.zig +++ b/lib/std/os/linux/m68k.zig @@ -142,14 +142,7 @@ pub fn restore_rt() callconv(.naked) noreturn { ); } -pub const blksize_t = i32; -pub const nlink_t = u32; pub const time_t = i32; -pub const mode_t = u32; -pub const off_t = i64; -pub const ino_t = u64; -pub const dev_t = u64; -pub const blkcnt_t = i64; // No VDSO used as of glibc 112a0ae18b831bf31f44d81b82666980312511d6. pub const VDSO = void; diff --git a/lib/std/os/linux/mips.zig b/lib/std/os/linux/mips.zig index c28a884e1a3e..bcca7d1ef205 100644 --- a/lib/std/os/linux/mips.zig +++ b/lib/std/os/linux/mips.zig @@ -230,11 +230,4 @@ pub const VDSO = struct { pub const CGT_VER = "LINUX_2.6"; }; -pub const blksize_t = u32; -pub const nlink_t = u32; pub const time_t = i32; -pub const mode_t = u32; -pub const off_t = i64; -pub const ino_t = u64; -pub const dev_t = u64; -pub const blkcnt_t = i64; diff --git a/lib/std/os/linux/mips64.zig b/lib/std/os/linux/mips64.zig index d96daaefa6b7..ec5681b5138c 100644 --- a/lib/std/os/linux/mips64.zig +++ b/lib/std/os/linux/mips64.zig @@ -184,11 +184,4 @@ pub const VDSO = struct { pub const CGT_VER = "LINUX_2.6"; }; -pub const blksize_t = u32; -pub const nlink_t = u32; pub const time_t = i32; -pub const mode_t = u32; -pub const off_t = i64; -pub const ino_t = u64; -pub const dev_t = u64; -pub const blkcnt_t = i64; diff --git a/lib/std/os/linux/mipsn32.zig b/lib/std/os/linux/mipsn32.zig index 858db84a20f1..4f7c7d60fdfe 100644 --- a/lib/std/os/linux/mipsn32.zig +++ b/lib/std/os/linux/mipsn32.zig @@ -184,11 +184,4 @@ pub const VDSO = struct { pub const CGT_VER = "LINUX_2.6"; }; -pub const blksize_t = u32; -pub const nlink_t = u32; pub const time_t = i32; -pub const mode_t = u32; -pub const off_t = i64; -pub const ino_t = u64; -pub const dev_t = u64; -pub const blkcnt_t = i64; diff --git a/lib/std/os/linux/or1k.zig b/lib/std/os/linux/or1k.zig index d0a5d47d6001..45352e4791ee 100644 --- a/lib/std/os/linux/or1k.zig +++ b/lib/std/os/linux/or1k.zig @@ -131,11 +131,4 @@ pub fn clone() callconv(.naked) u32 { pub const VDSO = void; -pub const blksize_t = u32; -pub const nlink_t = u32; pub const time_t = i32; -pub const mode_t = u32; -pub const off_t = i64; -pub const ino_t = u64; -pub const dev_t = u64; -pub const blkcnt_t = i64; diff --git a/lib/std/os/linux/powerpc.zig b/lib/std/os/linux/powerpc.zig index 4b998af0fd1f..78c6aadda741 100644 --- a/lib/std/os/linux/powerpc.zig +++ b/lib/std/os/linux/powerpc.zig @@ -269,10 +269,4 @@ pub const VDSO = struct { pub const CGT_VER = "LINUX_2.6.15"; }; -pub const blksize_t = i32; -pub const nlink_t = u32; pub const time_t = i32; -pub const mode_t = u32; -pub const off_t = i64; -pub const ino_t = u64; -pub const dev_t = u64; diff --git a/lib/std/os/linux/powerpc64.zig b/lib/std/os/linux/powerpc64.zig index a94611c98bde..f4375a4545b7 100644 --- a/lib/std/os/linux/powerpc64.zig +++ b/lib/std/os/linux/powerpc64.zig @@ -254,11 +254,4 @@ pub const VDSO = struct { pub const CGT_VER = "LINUX_2.6.15"; }; -pub const blksize_t = i64; -pub const nlink_t = u64; pub const time_t = i64; -pub const mode_t = u32; -pub const off_t = i64; -pub const ino_t = u64; -pub const dev_t = u64; -pub const blkcnt_t = i64; diff --git a/lib/std/os/linux/riscv32.zig b/lib/std/os/linux/riscv32.zig index d7223bde182a..c1b216b38ede 100644 --- a/lib/std/os/linux/riscv32.zig +++ b/lib/std/os/linux/riscv32.zig @@ -124,13 +124,7 @@ pub fn clone() callconv(.naked) u32 { ); } -pub const blksize_t = i32; -pub const nlink_t = u32; pub const time_t = i64; -pub const mode_t = u32; -pub const off_t = i64; -pub const ino_t = u64; -pub const dev_t = u64; pub const VDSO = struct { pub const CGT_SYM = "__vdso_clock_gettime"; diff --git a/lib/std/os/linux/riscv64.zig b/lib/std/os/linux/riscv64.zig index 59b12250ac0f..50a456836a16 100644 --- a/lib/std/os/linux/riscv64.zig +++ b/lib/std/os/linux/riscv64.zig @@ -124,13 +124,7 @@ pub fn clone() callconv(.naked) u64 { ); } -pub const blksize_t = i32; -pub const nlink_t = u32; pub const time_t = i64; -pub const mode_t = u32; -pub const off_t = i64; -pub const ino_t = u64; -pub const dev_t = u64; pub const VDSO = struct { pub const CGT_SYM = "__vdso_clock_gettime"; diff --git a/lib/std/os/linux/s390x.zig b/lib/std/os/linux/s390x.zig index 13b1b5307ac8..17a558e83d6e 100644 --- a/lib/std/os/linux/s390x.zig +++ b/lib/std/os/linux/s390x.zig @@ -152,13 +152,7 @@ pub fn restore_rt() callconv(.naked) noreturn { ); } -pub const blksize_t = i64; -pub const nlink_t = u64; pub const time_t = i64; -pub const mode_t = u32; -pub const off_t = i64; -pub const ino_t = u64; -pub const dev_t = u64; pub const VDSO = struct { pub const CGT_SYM = "__kernel_clock_gettime"; diff --git a/lib/std/os/linux/sparc64.zig b/lib/std/os/linux/sparc64.zig index ad03d398b763..59880bba873d 100644 --- a/lib/std/os/linux/sparc64.zig +++ b/lib/std/os/linux/sparc64.zig @@ -228,11 +228,4 @@ pub const VDSO = struct { pub const CGT_VER = "LINUX_2.6"; }; -pub const off_t = i64; -pub const ino_t = u64; pub const time_t = i64; -pub const mode_t = u32; -pub const dev_t = u64; -pub const nlink_t = u32; -pub const blksize_t = i64; -pub const blkcnt_t = i64; diff --git a/lib/std/os/linux/x32.zig b/lib/std/os/linux/x32.zig index 3ebebc1d8b77..97deb7640f13 100644 --- a/lib/std/os/linux/x32.zig +++ b/lib/std/os/linux/x32.zig @@ -132,14 +132,7 @@ pub fn restore_rt() callconv(.naked) noreturn { } } -pub const mode_t = u32; pub const time_t = i32; -pub const nlink_t = u32; -pub const blksize_t = i32; -pub const blkcnt_t = i32; -pub const off_t = i64; -pub const ino_t = u64; -pub const dev_t = u64; pub const VDSO = struct { pub const CGT_SYM = "__vdso_clock_gettime"; diff --git a/lib/std/os/linux/x86.zig b/lib/std/os/linux/x86.zig index f40f4492d126..e95afe23a61b 100644 --- a/lib/std/os/linux/x86.zig +++ b/lib/std/os/linux/x86.zig @@ -195,14 +195,7 @@ pub const VDSO = struct { pub const CGT_VER = "LINUX_2.6"; }; -pub const blksize_t = i32; -pub const nlink_t = u32; pub const time_t = i32; -pub const mode_t = u32; -pub const off_t = i64; -pub const ino_t = u64; -pub const dev_t = u64; -pub const blkcnt_t = i64; pub const user_desc = extern struct { entry_number: u32, diff --git a/lib/std/os/linux/x86_64.zig b/lib/std/os/linux/x86_64.zig index f3e31675141a..cab71d03e059 100644 --- a/lib/std/os/linux/x86_64.zig +++ b/lib/std/os/linux/x86_64.zig @@ -132,14 +132,7 @@ pub fn restore_rt() callconv(.naked) noreturn { } } -pub const mode_t = u64; pub const time_t = i64; -pub const nlink_t = u64; -pub const blksize_t = i64; -pub const blkcnt_t = i64; -pub const off_t = i64; -pub const ino_t = u64; -pub const dev_t = u64; pub const VDSO = struct { pub const CGT_SYM = "__vdso_clock_gettime";