From be07f95cd7e543abdcc06292892eb57ee1b83153 Mon Sep 17 00:00:00 2001 From: Ryan Liptak Date: Sun, 7 Jun 2026 20:55:13 -0700 Subject: [PATCH 1/2] Configuration: fix loadBits/storeBits on big endian architectures Fixes #35669 --- lib/std/Build.zig | 1 + lib/std/Build/Configuration.zig | 103 +++++++++++++++++++++++++++----- 2 files changed, 89 insertions(+), 15 deletions(-) diff --git a/lib/std/Build.zig b/lib/std/Build.zig index 2261aa4c9241452fb2f5f54e4d41cb01de7d5ef0..ec552b98ed2bed478aef81c77c0549a12d6075aa 100644 --- a/lib/std/Build.zig +++ b/lib/std/Build.zig @@ -2707,4 +2707,5 @@ pub fn systemIntegrationOption( test { _ = Cache; _ = Step; + _ = Configuration; } diff --git a/lib/std/Build/Configuration.zig b/lib/std/Build/Configuration.zig index d01ad0c89785c79c3d42d185cf371dd0bd7ec38f..928200bba339c047ced3c06dd50bddfe2d1414cb 100644 --- a/lib/std/Build/Configuration.zig +++ b/lib/std/Build/Configuration.zig @@ -1,10 +1,12 @@ const Configuration = @This(); const std = @import("../std.zig"); +const builtin = @import("builtin"); const Io = std.Io; const Allocator = std.mem.Allocator; const assert = std.debug.assert; const max_u32 = std.math.maxInt(u32); +const native_endian = builtin.target.cpu.arch.endian(); string_bytes: []u8, steps: []Step, @@ -3433,18 +3435,46 @@ pub fn load(arena: Allocator, reader: *Io.Reader) LoadError!Configuration { return result; } +/// Loads bits using native endianness when `value` spans multiple bytes. +/// On big endian architectures, `bit_offset` uses MSb 0 bit numbering. +/// On little endian architectures, `bit_offset` uses LSb 0 bit numbering. +/// See `storeBits`. pub fn loadBits(comptime Int: type, buffer: []const Int, bit_offset: usize, comptime Result: type) Result { const index = bit_offset / @bitSizeOf(Int); const small_bit_offset = bit_offset % @bitSizeOf(Int); const ResultInt = @Int(.unsigned, @bitSizeOf(Result)); - const result: ResultInt = @truncate(buffer[index] >> @intCast(small_bit_offset)); - const available_bits = @bitSizeOf(Int) - small_bit_offset; - if (available_bits >= @bitSizeOf(ResultInt)) return @bitCast(result); - const missing_bits = @bitSizeOf(ResultInt) - available_bits; - const upper: ResultInt = @truncate(buffer[index + 1] & ((@as(usize, 1) << @intCast(missing_bits)) - 1)); - return @bitCast(result | (upper << @intCast(available_bits))); + switch (native_endian) { + .little => { + const result: ResultInt = @truncate(buffer[index] >> @intCast(small_bit_offset)); + const available_bits = @bitSizeOf(Int) - small_bit_offset; + if (available_bits >= @bitSizeOf(ResultInt)) return @bitCast(result); + const missing_bits = @bitSizeOf(ResultInt) - available_bits; + const upper: ResultInt = @truncate(buffer[index + 1] & ((@as(usize, 1) << @intCast(missing_bits)) - 1)); + return @bitCast(result | (upper << @intCast(available_bits))); + }, + .big => { + const available_bits = @bitSizeOf(Int) - small_bit_offset; + if (available_bits >= @bitSizeOf(ResultInt)) { + const shift = available_bits - @bitSizeOf(ResultInt); + const result: ResultInt = @truncate(buffer[index] >> @intCast(shift)); + return @bitCast(result); + } + const mask = (@as(Int, 1) << @intCast(available_bits)) - 1; + const result: ResultInt = @intCast(buffer[index] & mask); + const missing_bits = @bitSizeOf(ResultInt) - available_bits; + const lower: ResultInt = @truncate(buffer[index + 1] >> @intCast(@bitSizeOf(Int) - missing_bits)); + return @bitCast((result << @intCast(missing_bits)) | lower); + }, + } } +/// Store bits using native endianness when `value` spans multiple bytes. +/// On big endian architectures: +/// - For a given value, the bits of an earlier byte are more significant than the bits of subsequent bytes. +/// - `bit_offset` uses MSb 0 bit numbering. +/// On little endian architectures: +/// - For a given value, the bits of an earlier byte are less significant than the bits of subsequent bytes. +/// - `bit_offset` uses LSb 0 bit numbering. pub fn storeBits(comptime Int: type, buffer: []Int, bit_offset: usize, value: anytype) void { const Value = @TypeOf(value); const ValueInt = @Int(.unsigned, @bitSizeOf(Value)); @@ -3453,27 +3483,70 @@ pub fn storeBits(comptime Int: type, buffer: []Int, bit_offset: usize, value: an const small_bit_offset = bit_offset % @bitSizeOf(Int); const available_bits = @bitSizeOf(Int) - small_bit_offset; if (available_bits >= @bitSizeOf(ValueInt)) { - buffer[index] &= ~(((@as(Int, 1) << @intCast(@bitSizeOf(Value))) - 1) << @intCast(small_bit_offset)); - buffer[index] |= @as(Int, value_int) << @intCast(small_bit_offset); + const shift = switch (native_endian) { + .little => small_bit_offset, + .big => available_bits - @bitSizeOf(ValueInt), + }; + buffer[index] &= ~(((@as(Int, 1) << @intCast(@bitSizeOf(Value))) - 1) << @intCast(shift)); + buffer[index] |= @as(Int, value_int) << @intCast(shift); } else { const DoubleInt = @Int(.unsigned, @bitSizeOf(Int) * 2); + const shift = switch (native_endian) { + .little => small_bit_offset, + .big => @bitSizeOf(DoubleInt) - small_bit_offset - @bitSizeOf(ValueInt), + }; const ptr: *align(@alignOf(Int)) DoubleInt = @ptrCast(buffer[index..][0..2]); - ptr.* &= ~(((@as(DoubleInt, 1) << @intCast(@bitSizeOf(Value))) - 1) << @intCast(small_bit_offset)); - ptr.* |= @as(DoubleInt, value_int) << @intCast(small_bit_offset); + ptr.* &= ~(((@as(DoubleInt, 1) << @intCast(@bitSizeOf(Value))) - 1) << @intCast(shift)); + ptr.* |= @as(DoubleInt, value_int) << @intCast(shift); } } test "loadBits and storeBits" { - var buffer: [2]u32 = .{ - 0b01111111000000001111111100000000, - 0b11111111000000001111111100000100, + var buffer: [2]u32 = switch (native_endian) { + .little => .{ + //──┐ 0b100011 (end) ┌─┐ 0b100 + 0b01111111000000001111111100000000, + // n <── bit offset 0 ┘ + // ┌── 0b100011 (start) + 0b11111111000000001111111100000100, + }, + .big => .{ + // ┌─┐ 0b100 ┌── 0b100011 (start) + 0b11111110000000001111111100000100, + //└ bit offset 0 ──> n + //──┐ 0b100011 (end) + 0b01111111000000001111111100000000, + }, }; + try std.testing.expectEqual(0b100, loadBits(u32, &buffer, 6, u3)); try std.testing.expectEqual(0b100011, loadBits(u32, &buffer, 29, u6)); + storeBits(u32, &buffer, 0, @as(u1, 0b0)); storeBits(u32, &buffer, 6, @as(u3, 0b010)); - storeBits(u32, &buffer, 29, @as(u6, 0b010010)); + storeBits(u32, &buffer, 29, @as(u6, 0b010110)); + storeBits(u32, &buffer, 40, @as(u17, 0b01110110011111110)); + try std.testing.expectEqual(0b0, loadBits(u32, &buffer, 0, u1)); try std.testing.expectEqual(0b010, loadBits(u32, &buffer, 6, u3)); - try std.testing.expectEqual(0b010010, loadBits(u32, &buffer, 29, u6)); + try std.testing.expectEqual(0b010110, loadBits(u32, &buffer, 29, u6)); + try std.testing.expectEqual(0b01110110011111110, loadBits(u32, &buffer, 40, u17)); + + // Test roundtripping of size/offset combinations + inline for (1..32) |value_size| { + for (0..64) |bit_offset| { + if (value_size + bit_offset > @bitSizeOf(@TypeOf(buffer))) continue; + + buffer = .{ 0, 0 }; + + const Value = @Int(.unsigned, value_size); + const value: Value = @intCast((@as(u32, 1) << @intCast(@bitSizeOf(Value))) - 1); + storeBits(u32, &buffer, bit_offset, value); + std.testing.expectEqual(value, loadBits(u32, &buffer, bit_offset, Value)) catch |err| { + std.debug.print("value size: {} bit offset: {}\n", .{ value_size, bit_offset }); + std.debug.print("buffer: {b:0>32} {b:0>32}\n", .{ buffer[0], buffer[1] }); + return err; + }; + } + } } -- 2.54.0 From 1042cbe0100ba0fbd8265f975c49356d31e4bd91 Mon Sep 17 00:00:00 2001 From: Ryan Liptak Date: Sun, 7 Jun 2026 20:58:45 -0700 Subject: [PATCH 2/2] Build: fix tryFindProgram on Windows and reintroduce ref in test block Also reduce the amount of unnecessary allocations, since one buffer can be used for appending all supported PATHEXT extensions Fixes #35668 --- lib/std/Build.zig | 7 ++++++- lib/std/process.zig | 10 +++++++++- 2 files changed, 15 insertions(+), 2 deletions(-) diff --git a/lib/std/Build.zig b/lib/std/Build.zig index ec552b98ed2bed478aef81c77c0549a12d6075aa..798729533c70421c8c5126a22540341f3129091d 100644 --- a/lib/std/Build.zig +++ b/lib/std/Build.zig @@ -1836,10 +1836,14 @@ fn tryFindProgram(b: *Build, full_path: []const u8) ?[]const u8 { if (b.graph.environ_map.get("PATHEXT")) |PATHEXT| { var it = mem.tokenizeScalar(u8, PATHEXT, fs.path.delimiter); + const extended_path_buf = arena.alloc(u8, full_path.len + 1 + std.process.WindowsExtension.max_len) catch @panic("OOM"); + @memcpy(extended_path_buf[0..full_path.len], full_path); + while (it.next()) |ext| { if (!supportedWindowsProgramExtension(ext)) continue; - const extended_path = try mem.concat(arena, u8, &.{ full_path, ext }); + @memcpy(extended_path_buf[full_path.len..][0..ext.len], ext); + const extended_path = extended_path_buf[0 .. full_path.len + ext.len]; if (Io.Dir.cwd().access(io, extended_path, .{ .execute = true })) |_| { return extended_path; @@ -2708,4 +2712,5 @@ test { _ = Cache; _ = Step; _ = Configuration; + _ = &findProgram; } diff --git a/lib/std/process.zig b/lib/std/process.zig index bcee558a65eef07afab17b484cc012326bb5004c..f3ef291763d642426770883e886c8a5a50aeae41 100644 --- a/lib/std/process.zig +++ b/lib/std/process.zig @@ -315,7 +315,15 @@ pub fn replacePath(io: Io, dir: Io.Dir, options: ReplaceOptions) ReplaceError { pub const ArgExpansion = enum { expand, no_expand }; /// File name extensions supported natively by `CreateProcess()` on Windows. -pub const WindowsExtension = enum { bat, cmd, com, exe }; +pub const WindowsExtension = enum { + bat, + cmd, + com, + exe, + + /// Length of the longest supported extension (in ASCII characters) + pub const max_len = 3; +}; pub const SpawnError = error{ /// The operating system does not support creating child processes. -- 2.54.0