From c458f18ef0b7501e9859cb34f7c57ef2f88adc11 Mon Sep 17 00:00:00 2001 From: IOKG04 Date: Sat, 2 Aug 2025 00:47:19 +0200 Subject: [PATCH 1/5] functions for comparing sentinel terminated pointers probably still gonna change the naming. TODO: more functions, maybe link to `std.mem.len()` from there I am happy to report though, that I tested the speed of these, even if in a very bad way, but still conclusive enough, and the versions I wrote/stole from ifreund are faster :3 note that I didn't test the `allEquals()` function, but I'll just assume it's faster too.. https://github.com/ziglang/zig/pull/7848#issuecomment-765467508 --- lib/std/mem.zig | 6 ++++ lib/std/mem/sentinelPtrs.zig | 58 ++++++++++++++++++++++++++++++++++++ 2 files changed, 64 insertions(+) create mode 100644 lib/std/mem/sentinelPtrs.zig diff --git a/lib/std/mem.zig b/lib/std/mem.zig index 3b72a2b57989..43914f255527 100644 --- a/lib/std/mem.zig +++ b/lib/std/mem.zig @@ -18,6 +18,8 @@ pub const byte_size_in_bits = 8; pub const Allocator = @import("mem/Allocator.zig"); +pub const sentinelPtrs = @import("mem/sentinelPtrs.zig"); + /// Stored as a power-of-two. pub const Alignment = enum(math.Log2Int(usize)) { @"1" = 0, @@ -4842,3 +4844,7 @@ test "read/write(Var)PackedInt" { } } } + +test { + _ = &sentinelPtrs; +} diff --git a/lib/std/mem/sentinelPtrs.zig b/lib/std/mem/sentinelPtrs.zig new file mode 100644 index 000000000000..6248fde52cb1 --- /dev/null +++ b/lib/std/mem/sentinelPtrs.zig @@ -0,0 +1,58 @@ +const std = @import("../std.zig"); +const testing = std.testing; + +/// Returns true if and only if the pointers have the same length and all elements +/// compare true using equality operator. +pub fn eql(comptime T: type, comptime sentinel: T, a: [*:sentinel]const T, b: [*:sentinel]const T) bool { + if (a == b) return true; + + var i: usize = 0; + while (a[i] == b[i]) : (i += 1) { + if (a[i] == sentinel) return true; + } + return false; +} + +test eql { + try testing.expect(eql(u8, 0, "abcd", "abcd")); + try testing.expect(!eql(u8, 0, "abcdef", "abZdef")); + try testing.expect(!eql(u8, 0, "abcdefg", "abcdef")); + + try testing.expect(eql(u16, 1, ([5]u16{ 5, 6, 7, 8, 1 })[0..4 :1].ptr, ([5]u16{ 5, 6, 7, 8, 1 })[0..4 :1].ptr)); + try testing.expect(!eql(u16, 1, ([7]u16{ 5, 6, 7, 8, 9, 10, 1 })[0..6 :1].ptr, ([7]u16{ 5, 6, 17, 8, 9, 10, 1 })[0..6 :1].ptr)); + try testing.expect(!eql(u16, 1, ([8]u16{ 5, 6, 7, 8, 9, 10, 11, 1 })[0..7 :1].ptr, ([7]u16{ 5, 6, 7, 8, 9, 10, 1 })[0..6 :1].ptr)); +} + +/// Returns true if and only if the pointer and slice have the same length and all elements +/// compare true using equality operator. +pub fn eqlSlice(comptime T: type, comptime sentinel: T, a: [*:sentinel]const T, b: []const T) bool { + for (b, 0..) |b_elem, i| { + if (a[i] == sentinel or a[i] != b_elem) return false; + } + return a[b.len] == sentinel; +} + +test eqlSlice { + try testing.expect(eqlSlice(u8, 0, "abcd", "abcd")); + try testing.expect(!eqlSlice(u8, 0, "abcdef", "abZdef")); + try testing.expect(!eqlSlice(u8, 0, "abcdefg", "abcdef")); + + try testing.expect(eqlSlice(u16, 1, ([5]u16{ 5, 6, 7, 8, 1 })[0..4 :1].ptr, &[4]u16{ 5, 6, 7, 8 })); + try testing.expect(!eqlSlice(u16, 1, ([7]u16{ 5, 6, 7, 8, 9, 10, 1 })[0..6 :1].ptr, &[6]u16{ 5, 6, 17, 8, 9, 10 })); + try testing.expect(!eqlSlice(u16, 1, ([8]u16{ 5, 6, 7, 8, 9, 10, 11, 1 })[0..7 :1].ptr, &[6]u16{ 5, 6, 7, 8, 9, 10 })); +} + +/// Returns true if all elements in the pointer are equal to the scalar value provided +pub fn allEqual(comptime T: type, comptime sentinel: T, ptr: [*:sentinel]const T, scalar: T) bool { + var i: usize = 0; + while (ptr[i] != sentinel) : (i += 1) { + if (ptr[i] != scalar) return false; + } + return true; +} + +test allEqual { + try testing.expect(allEqual(u8, 0, "aaaa", 'a')); + try testing.expect(!allEqual(u8, 0, "abaa", 'a')); + try testing.expect(allEqual(u8, 0, "", 'a')); +} From a7b9fad1d67d712dfa3ca4a26b38e3adc6eadde4 Mon Sep 17 00:00:00 2001 From: IOKG04 Date: Sun, 3 Aug 2025 01:54:45 +0200 Subject: [PATCH 2/5] more functions! also i tested the `eql` and `eqlSlice` functions for speed, and.. its all definitely faster than using `span()`, but the unexpected result is that if you have a sentinel-terminated slice, it is faster to compare the pointers, than it is to do the slice (at least for small (`< 32` or so) slice size). --- lib/std/mem.zig | 4 +- lib/std/mem/sentinelPtrs.zig | 79 ++++++++++++++++++++++++++++++++++++ 2 files changed, 80 insertions(+), 3 deletions(-) diff --git a/lib/std/mem.zig b/lib/std/mem.zig index 43914f255527..c587f923785e 100644 --- a/lib/std/mem.zig +++ b/lib/std/mem.zig @@ -642,9 +642,7 @@ pub fn order(comptime T: type, lhs: []const T, rhs: []const T) math.Order { /// Compares two many-item pointers with NUL-termination lexicographically. pub fn orderZ(comptime T: type, lhs: [*:0]const T, rhs: [*:0]const T) math.Order { - var i: usize = 0; - while (lhs[i] == rhs[i] and lhs[i] != 0) : (i += 1) {} - return math.order(lhs[i], rhs[i]); + return sentinelPtrs.order(T, 0, lhs, rhs); } test order { diff --git a/lib/std/mem/sentinelPtrs.zig b/lib/std/mem/sentinelPtrs.zig index 6248fde52cb1..66b4d68f2bc8 100644 --- a/lib/std/mem/sentinelPtrs.zig +++ b/lib/std/mem/sentinelPtrs.zig @@ -1,4 +1,8 @@ const std = @import("../std.zig"); +const debug = std.debug; +const assert = debug.assert; +const math = std.math; +const mem = std.mem; const testing = std.testing; /// Returns true if and only if the pointers have the same length and all elements @@ -56,3 +60,78 @@ test allEqual { try testing.expect(!allEqual(u8, 0, "abaa", 'a')); try testing.expect(allEqual(u8, 0, "", 'a')); } + +/// Returns the smallest number in a pointer. O(n). +/// `ptr` must have at least one element before a sentinel. +pub fn min(comptime T: type, comptime sentinel: T, ptr: [*:sentinel]const T) T { + assert(ptr[0] != sentinel); + var best = ptr[0]; + var i: usize = 1; + while (ptr[i] != sentinel) : (i += 1) { + best = @min(best, ptr[i]); + } + return best; +} + +test min { + try testing.expectEqual(min(u8, 0, "abcdefg"), 'a'); + try testing.expectEqual(min(u8, 0, "bcdefga"), 'a'); + try testing.expectEqual(min(u8, 0, "a"), 'a'); +} + +/// Returns the largest number in a pointer. O(n). +/// `ptr` must have at least one element before a sentinel. +pub fn max(comptime T: type, comptime sentinel: T, ptr: [*:sentinel]const T) T { + assert(ptr[0] != sentinel); + var best = ptr[0]; + var i: usize = 1; + while (ptr[i] != sentinel) : (i += 1) { + best = @max(best, ptr[i]); + } + return best; +} + +test max { + try testing.expectEqual(max(u8, 0, "abcdefg"), 'g'); + try testing.expectEqual(max(u8, 0, "gabcdef"), 'g'); + try testing.expectEqual(max(u8, 0, "g"), 'g'); +} + +/// Compares two pointers of numbers lexicographically. O(n). +pub fn order(comptime T: type, comptime sentinel: T, lhs: [*:sentinel]const T, rhs: [*:sentinel]const T) math.Order { + var i: usize = 0; + while (lhs[i] == rhs[i] and lhs[i] != sentinel) : (i += 1) {} + return math.order(lhs[i], rhs[i]); +} + +test order { + try testing.expect(order(u8, 0, "abcd", "bee") == .lt); + try testing.expect(order(u8, 0, "abc", "abc") == .eq); + try testing.expect(order(u8, 0, "abc", "abc0") == .lt); + try testing.expect(order(u8, 0, "", "") == .eq); + try testing.expect(order(u8, 0, "", "a") == .lt); +} + +/// Returns true if lhs < rhs, false otherwise +pub fn lessThan(comptime T: type, comptime sentinel: T, lhs: [*:sentinel]const T, rhs: [*:sentinel]const T) bool { + return order(T, sentinel, lhs, rhs) == .lt; +} + +test lessThan { + try testing.expect(lessThan(u8, 0, "abcd", "bee")); + try testing.expect(!lessThan(u8, 0, "abc", "abc")); + try testing.expect(lessThan(u8, 0, "abc", "abc0")); + try testing.expect(!lessThan(u8, 0, "", "")); + try testing.expect(lessThan(u8, 0, "", "a")); +} + +/// Takes a sentinel-terminated pointer and iterates over the memory to find the +/// sentinel and determine the length. +/// `[*c]` pointers are assumed to be non-null and 0-terminated. +pub const len = mem.len; + +/// Takes a sentinel-terminated pointer and returns a slice, iterating over the +/// memory to find the sentinel and determine the length. +/// Pointer attributes such as const are preserved. +/// `[*c]` pointers are assumed to be non-null and 0-terminated. +pub const span = mem.span; From 8d3ded482d718e23cfdfd7838268ee11b6b3944a Mon Sep 17 00:00:00 2001 From: IOKG04 Date: Sun, 3 Aug 2025 23:34:37 +0200 Subject: [PATCH 3/5] naming and some more tests just to ensure i didn't turn the wrong `0` into a `sentinel` in `order()` --- lib/std/mem.zig | 6 +++--- lib/std/mem/{sentinelPtrs.zig => sentinel_terminated.zig} | 6 ++++++ 2 files changed, 9 insertions(+), 3 deletions(-) rename lib/std/mem/{sentinelPtrs.zig => sentinel_terminated.zig} (90%) diff --git a/lib/std/mem.zig b/lib/std/mem.zig index c587f923785e..c4d7fb1ac09c 100644 --- a/lib/std/mem.zig +++ b/lib/std/mem.zig @@ -18,7 +18,7 @@ pub const byte_size_in_bits = 8; pub const Allocator = @import("mem/Allocator.zig"); -pub const sentinelPtrs = @import("mem/sentinelPtrs.zig"); +pub const sentinel_terminated = @import("mem/sentinel_terminated.zig"); /// Stored as a power-of-two. pub const Alignment = enum(math.Log2Int(usize)) { @@ -642,7 +642,7 @@ pub fn order(comptime T: type, lhs: []const T, rhs: []const T) math.Order { /// Compares two many-item pointers with NUL-termination lexicographically. pub fn orderZ(comptime T: type, lhs: [*:0]const T, rhs: [*:0]const T) math.Order { - return sentinelPtrs.order(T, 0, lhs, rhs); + return sentinel_terminated.order(T, 0, lhs, rhs); } test order { @@ -4844,5 +4844,5 @@ test "read/write(Var)PackedInt" { } test { - _ = &sentinelPtrs; + _ = sentinel_terminated; } diff --git a/lib/std/mem/sentinelPtrs.zig b/lib/std/mem/sentinel_terminated.zig similarity index 90% rename from lib/std/mem/sentinelPtrs.zig rename to lib/std/mem/sentinel_terminated.zig index 66b4d68f2bc8..ec1cc6e38af4 100644 --- a/lib/std/mem/sentinelPtrs.zig +++ b/lib/std/mem/sentinel_terminated.zig @@ -110,6 +110,12 @@ test order { try testing.expect(order(u8, 0, "abc", "abc0") == .lt); try testing.expect(order(u8, 0, "", "") == .eq); try testing.expect(order(u8, 0, "", "a") == .lt); + + try testing.expect(order(u16, 1, ([_]u16{ 2, 3, 4, 5, 1 })[0..4 :1].ptr, ([_]u16{ 2, 3, 4, 5, 1 })[0..4 :1].ptr) == .eq); + try testing.expect(order(u16, 1, ([_]u16{ 2, 3, 4, 1 })[0..3 :1].ptr, ([_]u16{ 2, 3, 4, 5, 1 })[0..4 :1].ptr) == .lt); + try testing.expect(order(u16, 1, ([_]u16{ 3, 4, 5, 6, 1 })[0..4 :1].ptr, ([_]u16{ 2, 3, 4, 5, 1 })[0..4 :1].ptr) == .gt); + try testing.expect(order(u16, 1, ([_]u16{1})[0..0 :1].ptr, ([_]u16{1})[0..0 :1].ptr) == .eq); + try testing.expect(order(u16, 1, ([_]u16{1})[0..0 :1].ptr, ([_]u16{ 2, 1 })[0..1 :1].ptr) == .lt); } /// Returns true if lhs < rhs, false otherwise From aefbda3d8167f74857b8186985eb56695bf253fb Mon Sep 17 00:00:00 2001 From: IOKG04 Date: Mon, 4 Aug 2025 00:27:11 +0200 Subject: [PATCH 4/5] make `order()` work with types where the sentinel isn't the lowest value closes https://github.com/ziglang/zig/pull/22618 (from where i stole the code) i don't think this syntax will work with pull requests, but i can try at least :3 --- lib/std/mem.zig | 2 ++ lib/std/mem/sentinel_terminated.zig | 4 +++- 2 files changed, 5 insertions(+), 1 deletion(-) diff --git a/lib/std/mem.zig b/lib/std/mem.zig index c4d7fb1ac09c..5a244fd6a6c2 100644 --- a/lib/std/mem.zig +++ b/lib/std/mem.zig @@ -651,6 +651,7 @@ test order { try testing.expect(order(u8, "abc", "abc0") == .lt); try testing.expect(order(u8, "", "") == .eq); try testing.expect(order(u8, "", "a") == .lt); + try testing.expect(order(i8, &.{ -1, -2 }, &.{ -1, -2, -3 }) == .lt); } test orderZ { @@ -659,6 +660,7 @@ test orderZ { try testing.expect(orderZ(u8, "abc", "abc0") == .lt); try testing.expect(orderZ(u8, "", "") == .eq); try testing.expect(orderZ(u8, "", "a") == .lt); + try testing.expect(order(i8, &.{ -1, -2 }, &.{ -1, -2, -3 }) == .lt); } /// Returns true if lhs < rhs, false otherwise diff --git a/lib/std/mem/sentinel_terminated.zig b/lib/std/mem/sentinel_terminated.zig index ec1cc6e38af4..a3d7ce0cb1d5 100644 --- a/lib/std/mem/sentinel_terminated.zig +++ b/lib/std/mem/sentinel_terminated.zig @@ -101,7 +101,7 @@ test max { pub fn order(comptime T: type, comptime sentinel: T, lhs: [*:sentinel]const T, rhs: [*:sentinel]const T) math.Order { var i: usize = 0; while (lhs[i] == rhs[i] and lhs[i] != sentinel) : (i += 1) {} - return math.order(lhs[i], rhs[i]); + return if (lhs[i] == sentinel) if (rhs[i] == sentinel) .eq else .lt else if (rhs[i] == sentinel) .gt else math.order(lhs[i], rhs[i]); } test order { @@ -116,6 +116,8 @@ test order { try testing.expect(order(u16, 1, ([_]u16{ 3, 4, 5, 6, 1 })[0..4 :1].ptr, ([_]u16{ 2, 3, 4, 5, 1 })[0..4 :1].ptr) == .gt); try testing.expect(order(u16, 1, ([_]u16{1})[0..0 :1].ptr, ([_]u16{1})[0..0 :1].ptr) == .eq); try testing.expect(order(u16, 1, ([_]u16{1})[0..0 :1].ptr, ([_]u16{ 2, 1 })[0..1 :1].ptr) == .lt); + + try testing.expect(order(i8, 0, ([_]i8{ -1, -2, 0 })[0..2 :0].ptr, ([_]i8{ -1, -2, -3, 0 })[0..3 :0].ptr) == .lt); } /// Returns true if lhs < rhs, false otherwise From 67e1266f03cf242fe6387e66e850609b64b42eec Mon Sep 17 00:00:00 2001 From: IOKG04 Date: Mon, 4 Aug 2025 17:26:47 +0200 Subject: [PATCH 5/5] some usecases found some cases where these functions are useful the test finished with some errors on my system, but it always does, even if a commit is confirmed to work, probably just something wrong on my end. the compiler still builds tho and test-std also finishes cleanly, so ill just hope there isn't too much wrong --- lib/std/debug/Coverage.zig | 20 ++++++++++---------- lib/std/process.zig | 2 +- src/main.zig | 4 +--- 3 files changed, 12 insertions(+), 14 deletions(-) diff --git a/lib/std/debug/Coverage.zig b/lib/std/debug/Coverage.zig index 58e600dc6370..06d82689a191 100644 --- a/lib/std/debug/Coverage.zig +++ b/lib/std/debug/Coverage.zig @@ -40,9 +40,9 @@ pub const String = enum(u32) { pub fn eql(self: @This(), a: String, b: String, b_index: usize) bool { _ = b_index; - const a_slice = span(self.string_bytes[@intFromEnum(a)..]); - const b_slice = span(self.string_bytes[@intFromEnum(b)..]); - return std.mem.eql(u8, a_slice, b_slice); + const a_ptr: [*:0]const u8 = @ptrCast(&self.string_bytes[@intFromEnum(a)]); + const b_ptr: [*:0]const u8 = @ptrCast(&self.string_bytes[@intFromEnum(b)]); + return std.mem.sentinel_terminated.eql(u8, 0, a_ptr, b_ptr); } pub fn hash(self: @This(), a: String) u32 { @@ -55,8 +55,8 @@ pub const String = enum(u32) { pub fn eql(self: @This(), a_slice: []const u8, b: String, b_index: usize) bool { _ = b_index; - const b_slice = span(self.string_bytes[@intFromEnum(b)..]); - return std.mem.eql(u8, a_slice, b_slice); + const b_ptr: [*:0]const u8 = @ptrCast(&self.string_bytes[@intFromEnum(b)]); + return std.mem.sentinel_terminated.eqlSlice(u8, 0, b_ptr, a_slice); } pub fn hash(self: @This(), a: []const u8) u32 { _ = self; @@ -97,9 +97,9 @@ pub const File = extern struct { pub fn eql(self: MapContext, a: File, b: File, b_index: usize) bool { _ = b_index; if (a.directory_index != b.directory_index) return false; - const a_basename = span(self.string_bytes[@intFromEnum(a.basename)..]); - const b_basename = span(self.string_bytes[@intFromEnum(b.basename)..]); - return std.mem.eql(u8, a_basename, b_basename); + const a_basename_ptr: [*:0]const u8 = @ptrCast(&self.string_bytes[@intFromEnum(a.basename)]); + const b_basename_ptr: [*:0]const u8 = @ptrCast(&self.string_bytes[@intFromEnum(b.basename)]); + return std.mem.sentinel_terminated.eql(u8, 0, a_basename_ptr, b_basename_ptr); } }; @@ -119,8 +119,8 @@ pub const File = extern struct { pub fn eql(self: @This(), a: Entry, b: File, b_index: usize) bool { _ = b_index; if (a.directory_index != b.directory_index) return false; - const b_basename = span(self.string_bytes[@intFromEnum(b.basename)..]); - return std.mem.eql(u8, a.basename, b_basename); + const b_basename_ptr: [*:0]const u8 = @ptrCast(&self.string_bytes[@intFromEnum(b.basename)]); + return std.mem.sentinel_terminated.eqlSlice(u8, 0, b_basename_ptr, a.basename); } }; }; diff --git a/lib/std/process.zig b/lib/std/process.zig index 58d16eef1db5..b2d2d01ca1ee 100644 --- a/lib/std/process.zig +++ b/lib/std/process.zig @@ -2017,7 +2017,7 @@ test createNullDelimitedEnvMap { "XCURSOR_SIZE=24", }) |target| { for (environ) |variable| { - if (mem.eql(u8, mem.span(variable orelse continue), target)) break; + if (mem.sentinel_terminated.eqlSlice(u8, 0, variable orelse continue, target)) break; } else { try testing.expect(false); // Environment variable not found } diff --git a/src/main.zig b/src/main.zig index 9349899a561a..ee32da667f6b 100644 --- a/src/main.zig +++ b/src/main.zig @@ -6290,11 +6290,9 @@ fn detectNativeCpuWithLLVM( var result = std.Target.Cpu.baseline(arch, builtin.os); if (llvm_cpu_name_z) |cpu_name_z| { - const llvm_cpu_name = mem.span(cpu_name_z); - for (arch.allCpuModels()) |model| { const this_llvm_name = model.llvm_name orelse continue; - if (mem.eql(u8, this_llvm_name, llvm_cpu_name)) { + if (mem.sentinel_terminated.eqlSlice(u8, 0, cpu_name_z, this_llvm_name)) { // Here we use the non-dependencies-populated set, // so that subtracting features later in this function // affect the prepopulated set.