From 4c09f4aa037dfd8047d82edbc65624e4bf591f83 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Thu, 26 Feb 2026 18:44:23 -0800 Subject: [PATCH] Configuration: implement UnionList storage --- lib/compiler/Maker/ScannedConfig.zig | 3 + lib/compiler/configurer.zig | 120 +++++++++++--- lib/std/Build/Module.zig | 14 +- lib/std/builtin.zig | 2 +- lib/std/zig/Configuration.zig | 228 +++++++++++++++++++++++---- 5 files changed, 301 insertions(+), 66 deletions(-) diff --git a/lib/compiler/Maker/ScannedConfig.zig b/lib/compiler/Maker/ScannedConfig.zig index 18dcf7d00c..de846c52bb 100644 --- a/lib/compiler/Maker/ScannedConfig.zig +++ b/lib/compiler/Maker/ScannedConfig.zig @@ -15,6 +15,7 @@ pub fn print(sc: *const ScannedConfig, w: *Writer) Writer.Error!void { std.log.err("TODO also print unlazy deps", .{}); std.log.err("TODO also print system integrations", .{}); std.log.err("TODO also print available options", .{}); + std.log.err("TODO also print modules", .{}); const c = &sc.configuration; var serializer: Serializer = .{ .writer = w }; var s = try serializer.beginStruct(.{}); @@ -83,6 +84,7 @@ fn printValue(sc: *const ScannedConfig, s: *Serializer, comptime Field: type, fi .flag_optional => comptime unreachable, .flag_length_prefixed_list => comptime unreachable, .enum_optional => comptime unreachable, + .union_list => comptime unreachable, } else if (std.enums.tagName(Field, field_value)) |name| { try s.ident(name); } else { @@ -105,6 +107,7 @@ fn printValue(sc: *const ScannedConfig, s: *Serializer, comptime Field: type, fi try printValue(sc, s, @TypeOf(field_value.slice), field_value.slice); }, .extended => @compileError("TODO"), + .union_list => @compileError("TODO"), }, else => @compileError("not implemented: " ++ @typeName(Field)), }, diff --git a/lib/compiler/configurer.zig b/lib/compiler/configurer.zig index 8841aaf553..beb8c7df26 100644 --- a/lib/compiler/configurer.zig +++ b/lib/compiler/configurer.zig @@ -224,6 +224,8 @@ const Serialize = struct { wc: *Configuration.Wip, module_map: std.AutoArrayHashMapUnmanaged(*std.Build.Module, Configuration.Module.Index) = .empty, package_map: std.AutoArrayHashMapUnmanaged(*std.Build, Configuration.Package.Index) = .empty, + /// Index corresponds to `Configuration.steps` index. + step_map: std.AutoArrayHashMapUnmanaged(*Step, void) = .empty, fn builderToPackage(s: *Serialize, b: *std.Build) !Configuration.Package.Index { if (b.pkg_hash.len == 0) return .root; @@ -291,6 +293,56 @@ const Serialize = struct { return if (opt_slice) |slice| try s.wc.addString(slice) else null; } + fn addSystemLib(s: *Serialize, sl: *const std.Build.Module.SystemLib) !Configuration.SystemLib.Index { + log.err("TODO deduplicate addSystemLib", .{}); + const wc = s.wc; + return @enumFromInt(try wc.addExtra(@as(Configuration.SystemLib, .{ + .flags = .{ + .needed = sl.needed, + .weak = sl.weak, + .use_pkg_config = sl.use_pkg_config, + .preferred_link_mode = sl.preferred_link_mode, + .search_strategy = sl.search_strategy, + }, + .name = try wc.addString(sl.name), + }))); + } + + fn addCSourceFile(s: *Serialize, csf: *const std.Build.Module.CSourceFile) !Configuration.CSourceFile.Index { + log.err("TODO addCSourceFile trailing data", .{}); + const wc = s.wc; + return @enumFromInt(try wc.addExtra(@as(Configuration.CSourceFile, .{ + .flags = .{ + .args_len = @intCast(csf.flags.len), + .lang = .init(csf.language), + }, + .file = try addLazyPath(s, csf.file), + }))); + } + + fn addCSourceFiles(s: *Serialize, csf: *const std.Build.Module.CSourceFiles) !Configuration.CSourceFiles.Index { + log.err("TODO addCSourceFiles trailing data", .{}); + const wc = s.wc; + return @enumFromInt(try wc.addExtra(@as(Configuration.CSourceFiles, .{ + .flags = .{ + .args_len = @intCast(csf.flags.len), + .lang = .init(csf.language), + }, + .root = try addLazyPath(s, csf.root), + .files_len = @intCast(csf.files.len), + }))); + } + + fn addRcSourceFile(s: *Serialize, rsf: *const std.Build.Module.RcSourceFile) !Configuration.RcSourceFile.Index { + log.err("TODO addRcSourceFile trailing data", .{}); + const wc = s.wc; + return @enumFromInt(try wc.addExtra(@as(Configuration.RcSourceFile, .{ + .file = try addLazyPath(s, rsf.file), + .args_len = @intCast(rsf.flags.len), + .include_paths_len = @intCast(rsf.include_paths.len), + }))); + } + fn initStringList(s: *Serialize, list: []const []const u8) ![]const Configuration.String { const wc = s.wc; const result = try s.arena.alloc(Configuration.String, list.len); @@ -312,6 +364,35 @@ const Serialize = struct { const arena = s.arena; const gpa = wc.gpa; + const include_dirs = try arena.alloc(Configuration.Module.IncludeDir, m.include_dirs.items.len); + for (include_dirs, m.include_dirs.items) |*dest, src| dest.* = switch (src) { + .path => |lp| .{ .path = try addLazyPath(s, lp) }, + .path_system => |lp| .{ .path_system = try addLazyPath(s, lp) }, + .path_after => |lp| .{ .path_after = try addLazyPath(s, lp) }, + .framework_path => |lp| .{ .framework_path = try addLazyPath(s, lp) }, + .framework_path_system => |lp| .{ .framework_path_system = try addLazyPath(s, lp) }, + .embed_path => |lp| .{ .embed_path = try addLazyPath(s, lp) }, + .other_step => |cs| .{ .other_step = stepIndex(s, &cs.step) }, + .config_header_step => |chs| .{ .config_header_step = stepIndex(s, &chs.step) }, + }; + + const rpaths = try arena.alloc(Configuration.Module.RPath, m.rpaths.items.len); + for (rpaths, m.rpaths.items) |*dest, src| dest.* = switch (src) { + .lazy_path => |lp| .{ .lazy_path = try addLazyPath(s, lp) }, + .special => |slice| .{ .special = try wc.addString(slice) }, + }; + + const link_objects = try arena.alloc(Configuration.Module.LinkObject, m.link_objects.items.len); + for (link_objects, m.link_objects.items) |*dest, *src| dest.* = switch (src.*) { + .static_path => |lp| .{ .static_path = try addLazyPath(s, lp) }, + .other_step => |cs| .{ .other_step = stepIndex(s, &cs.step) }, + .system_lib => |*sl| .{ .system_lib = try addSystemLib(s, sl) }, + .assembly_file => |lp| .{ .assembly_file = try addLazyPath(s, lp) }, + .c_source_file => |csf| .{ .c_source_file = try addCSourceFile(s, csf) }, + .c_source_files => |csf| .{ .c_source_files = try addCSourceFiles(s, csf) }, + .win32_resource_file => |wrf| .{ .win32_resource_file = try addRcSourceFile(s, wrf) }, + }; + const lib_paths = try arena.alloc(Configuration.LazyPath, m.lib_paths.items.len); for (lib_paths, m.lib_paths.items) |*dest, src| dest.* = try addLazyPath(s, src); @@ -352,11 +433,11 @@ const Serialize = struct { .fuzz = .init(m.strip), .code_model = m.code_model, .c_macros = c_macros.len != 0, - .include_dirs = m.include_dirs.items.len != 0, + .include_dirs = include_dirs.len != 0, .lib_paths = lib_paths.len != 0, - .rpaths = m.rpaths.items.len != 0, + .rpaths = rpaths.len != 0, .frameworks = m.frameworks.entries.len != 0, - .link_objects = m.link_objects.items.len != 0, + .link_objects = link_objects.len != 0, .export_symbol_names = export_symbol_names.len != 0, }, .flags2 = .{ @@ -376,6 +457,9 @@ const Serialize = struct { .c_macros = .{ .slice = c_macros }, .lib_paths = .{ .slice = lib_paths }, .export_symbol_names = .{ .slice = export_symbol_names }, + .include_dirs = .init(include_dirs), + .rpaths = .init(rpaths), + .link_objects = .init(link_objects), }))); log.err("TODO serialize the trailing Module data", .{}); @@ -384,6 +468,10 @@ const Serialize = struct { return module_index; } + + fn stepIndex(s: *const Serialize, step: *Step) Configuration.Step.Index { + return @enumFromInt(s.step_map.getIndex(step).?); + } }; fn serialize(b: *std.Build, wc: *Configuration.Wip, writer: *Io.Writer) !void { @@ -396,34 +484,32 @@ fn serialize(b: *std.Build, wc: *Configuration.Wip, writer: *Io.Writer) !void { // Starting from all top-level steps in `b`, traverse the entire step graph // and add all step dependencies implied by module graphs. const top_level_steps = b.top_level_steps.values(); - // Index corresponds to `Configuration.steps` index. - var step_map: std.AutoArrayHashMapUnmanaged(*Step, void) = .empty; - try step_map.ensureUnusedCapacity(arena, top_level_steps.len); + try s.step_map.ensureUnusedCapacity(arena, top_level_steps.len); for (top_level_steps) |tls| { - step_map.putAssumeCapacityNoClobber(&tls.step, {}); + s.step_map.putAssumeCapacityNoClobber(&tls.step, {}); } { - while (wc.steps.items.len < step_map.count()) { - const step = step_map.keys()[wc.steps.items.len]; + while (wc.steps.items.len < s.step_map.count()) { + const step = s.step_map.keys()[wc.steps.items.len]; // Set up any implied dependencies for this step. It's important that we do this first, so // that the loop below discovers steps implied by the module graph. try createModuleDependenciesForStep(step); - try step_map.ensureUnusedCapacity(arena, step.dependencies.items.len); + try s.step_map.ensureUnusedCapacity(arena, step.dependencies.items.len); for (step.dependencies.items) |other_step| { - step_map.putAssumeCapacity(other_step, {}); + s.step_map.putAssumeCapacity(other_step, {}); } // Add and then de-duplicate dependencies. const deps = d: { const deps: Configuration.Deps = @enumFromInt(wc.extra.items.len); for (try wc.reserveLengthPrefixed(step.dependencies.items.len), step.dependencies.items) |*dep, dep_step| - dep.* = @intCast(step_map.getIndex(dep_step).?); + dep.* = @intCast(s.step_map.getIndex(dep_step).?); break :d try wc.dedupeDeps(deps); }; - try wc.steps.ensureTotalCapacity(gpa, step_map.entries.capacity); + try wc.steps.ensureTotalCapacity(gpa, s.step_map.entries.capacity); wc.steps.appendAssumeCapacity(.{ .name = try wc.addString(step.name), .owner = try s.builderToPackage(step.owner), @@ -613,7 +699,7 @@ fn serialize(b: *std.Build, wc: *Configuration.Wip, writer: *Io.Writer) !void { .emitted_pdb = try s.addOptionalLazyPathEnum(ia.emitted_pdb), .h_dir = try addInstallDir(wc, ia.h_dir), .emitted_h = try s.addOptionalLazyPathEnum(ia.emitted_h), - .artifact = stepIndex(&step_map, &ia.artifact.step), + .artifact = s.stepIndex(&ia.artifact.step), }))); }, .install_file => @panic("TODO"), @@ -688,7 +774,7 @@ fn serialize(b: *std.Build, wc: *Configuration.Wip, writer: *Io.Writer) !void { } try wc.write(writer, .{ - .default_step = stepIndex(&step_map, b.default_step), + .default_step = s.stepIndex(b.default_step), }); } @@ -714,10 +800,6 @@ fn addInstallDir(wc: *Configuration.Wip, install_dir: ?std.Build.InstallDir) !Co } } -fn stepIndex(step_map: *const std.AutoArrayHashMapUnmanaged(*Step, void), step: *Step) Configuration.Step.Index { - return @enumFromInt(step_map.getIndex(step).?); -} - /// If the given `Step` is a `Step.Compile`, adds any dependencies for that step which /// are implied by the module graph rooted at `step.cast(Step.Compile).?.root_module`. fn createModuleDependenciesForStep(step: *Step) Allocator.Error!void { diff --git a/lib/std/Build/Module.zig b/lib/std/Build/Module.zig index 7b42c9a110..dd3b6b0251 100644 --- a/lib/std/Build/Module.zig +++ b/lib/std/Build/Module.zig @@ -73,18 +73,8 @@ pub const SystemLib = struct { preferred_link_mode: std.builtin.LinkMode, search_strategy: SystemLib.SearchStrategy, - pub const UsePkgConfig = enum { - /// Don't use pkg-config, just pass -lfoo where foo is name. - no, - /// Try to get information on how to link the library from pkg-config. - /// If that fails, fall back to passing -lfoo where foo is name. - yes, - /// Try to get information on how to link the library from pkg-config. - /// If that fails, error out. - force, - }; - - pub const SearchStrategy = enum { paths_first, mode_first, no_fallback }; + pub const UsePkgConfig = std.Build.Configuration.SystemLib.UsePkgConfig; + pub const SearchStrategy = std.Build.Configuration.SystemLib.SearchStrategy; }; pub const CSourceLanguage = enum { diff --git a/lib/std/builtin.zig b/lib/std/builtin.zig index 80d8178a2e..17a7126523 100644 --- a/lib/std/builtin.zig +++ b/lib/std/builtin.zig @@ -871,7 +871,7 @@ pub const OutputMode = enum { /// This data structure is used by the Zig language code generation and /// therefore must be kept in sync with the compiler implementation. -pub const LinkMode = enum { +pub const LinkMode = enum(u1) { static, dynamic, }; diff --git a/lib/std/zig/Configuration.zig b/lib/std/zig/Configuration.zig index 41adeb088e..8f6d03d1c5 100644 --- a/lib/std/zig/Configuration.zig +++ b/lib/std/zig/Configuration.zig @@ -1071,9 +1071,6 @@ pub const Package = struct { /// Trailing: /// * frameworks: FlagsPrefixedList(FrameworkFlags), // if flag is set -/// * include_dirs: UnionList(IncludeDir), // if flag is set -/// * rpaths: UnionList(RPath), // if flag is set -/// * link_objects: UnionList(LinkObject), // if flag is set pub const Module = struct { flags: Flags, flags2: Flags2, @@ -1084,6 +1081,9 @@ pub const Module = struct { c_macros: Storage.FlagLengthPrefixedList(.flags, .c_macros, String), lib_paths: Storage.FlagLengthPrefixedList(.flags, .lib_paths, LazyPath), export_symbol_names: Storage.FlagLengthPrefixedList(.flags, .export_symbol_names, String), + include_dirs: Storage.UnionList(.flags, .include_dirs, IncludeDir), + rpaths: Storage.UnionList(.flags, .rpaths, RPath), + link_objects: Storage.UnionList(.flags, .link_objects, LinkObject), pub const Optimize = enum(u3) { debug, @@ -1204,7 +1204,7 @@ pub const Module = struct { static_path: LazyPath, /// Always `Step.Tag.compile`. other_step: Step.Index, - system_lib: SystemLib, + system_lib: SystemLib.Index, assembly_file: LazyPath, c_source_file: CSourceFile.Index, c_source_files: CSourceFiles.Index, @@ -1328,8 +1328,18 @@ pub const SystemLib = struct { _, }; - pub const UsePkgConfig = enum(u2) { no, yes, force }; - pub const LinkMode = enum { static, dynamic }; + pub const UsePkgConfig = enum(u2) { + /// Don't use pkg-config, just pass -lfoo where foo is name. + no, + /// Try to get information on how to link the library from pkg-config. + /// If that fails, fall back to passing -lfoo where foo is name. + yes, + /// Try to get information on how to link the library from pkg-config. + /// If that fails, error out. + force, + }; + + pub const LinkMode = std.builtin.LinkMode; pub const Flags = packed struct(u32) { needed: bool, @@ -1337,18 +1347,19 @@ pub const SystemLib = struct { use_pkg_config: UsePkgConfig, preferred_link_mode: LinkMode, search_strategy: SearchStrategy, + _: u25 = 0, }; pub const SearchStrategy = enum(u2) { paths_first, mode_first, no_fallback }; }; /// Trailing: -/// * flag: String, // for each flags_len +/// * arg: String, // for each args_len /// * sub_path: String, // for each files_len pub const CSourceFiles = struct { + flags: Flags, root: LazyPath, files_len: u32, - flags: Flags, pub const Index = enum(u32) { _, @@ -1356,16 +1367,16 @@ pub const CSourceFiles = struct { pub const Flags = packed struct(u32) { /// C compiler CLI flags. - flags_len: u29, + args_len: u29, lang: OptionalCSourceLanguage, }; }; /// Trailing: -/// * flag: String, // for each flags_len +/// * arg: String, // for each args_len pub const CSourceFile = struct { - file: LazyPath, flags: Flags, + file: LazyPath, pub const Index = enum(u32) { _, @@ -1373,11 +1384,24 @@ pub const CSourceFile = struct { pub const Flags = packed struct(u32) { /// C compiler CLI flags. - flags_len: u29, + args_len: u29, lang: OptionalCSourceLanguage, }; }; +/// Trailing: +/// * arg: String, // for each args_len +/// * include_path: String, // for each include_paths_len +pub const RcSourceFile = struct { + file: LazyPath, + args_len: u32, + include_paths_len: u32, + + pub const Index = enum(u32) { + _, + }; +}; + pub const OptionalCSourceLanguage = enum(u3) { c, cpp, @@ -1386,29 +1410,17 @@ pub const OptionalCSourceLanguage = enum(u3) { assembly, assembly_with_preprocessor, default, -}; -pub const RcSourceFile = struct { - file: LazyPath, - /// Any option that rc.exe accepts will work here, with the exception of: - /// - `/fo`: The output filename is set by the build system - /// - `/p`: Only running the preprocessor is not supported in this context - /// - `/:no-preprocess` (non-standard option): Not supported in this context - /// - Any MUI-related option - /// https://learn.microsoft.com/en-us/windows/win32/menurc/using-rc-the-rc-command-line- - /// - /// Implicitly defined options: - /// /x (ignore the INCLUDE environment variable) - /// /D_DEBUG or /DNDEBUG depending on the optimization mode - flags: []const []const u8 = &.{}, - /// Include paths that may or may not exist yet and therefore need to be - /// specified as a LazyPath. Each path will be appended to the flags - /// as `/I `. - include_paths: []const LazyPath = &.{}, - - pub const Index = enum(u32) { - _, - }; + pub fn init(x: ?std.Build.Module.CSourceLanguage) @This() { + return switch (x orelse return .default) { + .c => .c, + .cpp => .cpp, + .objective_c => .objective_c, + .objective_cpp => .objective_cpp, + .assembly => .assembly, + .assembly_with_preprocessor => .assembly_with_preprocessor, + }; + } }; pub const ResolvedTarget = struct { @@ -1691,6 +1703,7 @@ pub const Storage = enum { enum_optional, extended, flag_length_prefixed_list, + union_list, /// The presence of the field is determined by a boolean within a packed /// struct. @@ -1769,6 +1782,63 @@ pub const Storage = enum { }; } + /// `UnionArg` is a tagged union with a small integer for the enum tag. + /// + /// A field in flags determines whether the metadata is present. + /// + /// The metadata is bit-packed consecutive packed struct which is the + /// `UnionArg` enum tag combined with a "last" marker boolean field. + /// When "last" is true, the element is the last one, providing + /// the length of the list. + /// + /// Following is each element of the list; each bitcastable to u32. + pub fn UnionList( + comptime flags_arg: @EnumLiteral(), + comptime flag_arg: @EnumLiteral(), + comptime UnionArg: type, + ) type { + return struct { + /// When serializing it is UnionArg slice pointer. + /// When deserializing it is extra index of first UnionArg element. + data: ?*const anyopaque, + len: usize, + + pub const storage: Storage = .union_list; + pub const flags = flags_arg; + pub const flag = flag_arg; + pub const Union = UnionArg; + + pub const Tag = @typeInfo(Union).@"union".tag_type.?; + pub const MetaInt = @Int(.unsigned, @bitSizeOf(Tag) + 1); + pub const Meta = packed struct(MetaInt) { + tag: Tag, + last: bool, + }; + + /// Valid to call only when serializing. + pub fn init(slice: []const Union) @This() { + return .{ .data = slice.ptr, .len = slice.len }; + } + + /// Valid to call only when deserializing. + pub fn get(this: *const @This(), extra: []const u32) []const u32 { + return extra[@intFromPtr(this.data)..][0..this.len]; + } + + /// Valid to call only when deserializing. + pub fn tag(this: *const @This(), extra: []const u32, i: usize) Tag { + _ = this; + _ = extra; + _ = i; + @panic("TODO implement UnionList.tag"); + } + + fn extraLen(len: usize) usize { + return len + (len * @bitSizeOf(Meta) + 31) / 32; + } + }; + } + pub fn dataLength(buffer: []const u32, i: usize, comptime S: type) usize { var end = i; _ = data(buffer, &end, S); @@ -1848,6 +1918,23 @@ pub const Storage = enum { defer i.* = data_start + len; return .{ .slice = @ptrCast(buffer[data_start..][0..len]) }; }, + .union_list => { + const flags = @field(container, @tagName(Field.flags)); + const flag = @field(flags, @tagName(Field.flag)); + if (!flag) return .{ .data = null, .len = 0 }; + const meta_start = i.*; + const meta_buffer = buffer[meta_start..]; + var len: u32 = 0; + var bit_offset: usize = 0; + while (true) : (bit_offset += @bitSizeOf(Field.Meta)) { + const meta = loadBits(u32, meta_buffer, bit_offset, Field.Meta); + len += 1; + if (meta.last) break; + } + const end = meta_start + Field.extraLen(len); + i.* = end; + return .{ .data = end - len, .len = len }; + }, }, }, .@"extern" => comptime unreachable, @@ -1884,6 +1971,7 @@ pub const Storage = enum { .auto => switch (Field.storage) { .flag_optional, .enum_optional, .extended => 1, .flag_length_prefixed_list => field.slice.len + 1, + .union_list => Field.extraLen(field.len), }, .@"extern" => comptime unreachable, }, @@ -1947,6 +2035,33 @@ pub const Storage = enum { @memcpy(buffer[i + 1 ..][0..len], @as([]const u32, @ptrCast(value.slice))); return len + 1; }, + .union_list => { + if (value.len == 0) return 0; + const Tag = @typeInfo(Field.Union).@"union".tag_type.?; + const slice_ptr: [*]const Field.Union = @ptrCast(@alignCast(value.data)); + const slice = slice_ptr[0..value.len]; + const meta_buffer = buffer[i..][0 .. (slice.len * @bitSizeOf(Field.Meta) + 31) / 32]; + for (slice[0 .. slice.len - 1], 0..) |elem, elem_index| { + const union_tag: Tag = elem; + storeBits(u32, meta_buffer, elem_index * @bitSizeOf(Field.Meta), @as(Field.Meta, .{ + .tag = union_tag, + .last = false, + })); + } else { + const elem_index = slice.len - 1; + const elem = slice[elem_index]; + const union_tag: Tag = elem; + storeBits(u32, meta_buffer, elem_index * @bitSizeOf(Field.Meta), @as(Field.Meta, .{ + .tag = union_tag, + .last = true, + })); + } + var total: usize = meta_buffer.len; + for (i + meta_buffer.len.., slice) |elem_index, src| switch (src) { + inline else => |x| total += setExtraField(buffer, elem_index, @TypeOf(x), x), + }; + return total; + }, }, }, .@"extern" => comptime unreachable, @@ -2000,3 +2115,48 @@ pub fn load(arena: Allocator, reader: *Io.Reader) LoadError!Configuration { try reader.readVecAll(&vecs); return result; } + +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))); +} + +pub fn storeBits(comptime Int: type, buffer: []Int, bit_offset: usize, value: anytype) void { + const Value = @TypeOf(value); + const ValueInt = @Int(.unsigned, @bitSizeOf(Value)); + const value_int: ValueInt = @bitCast(value); + const index = bit_offset / @bitSizeOf(Int); + 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); + } else { + const DoubleInt = @Int(.unsigned, @bitSizeOf(Int) * 2); + 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); + } +} + +test "loadBits and storeBits" { + var buffer: [2]u32 = .{ + 0b01111111000000001111111100000000, + 0b11111111000000001111111100000100, + }; + try std.testing.expectEqual(0b100, loadBits(u32, &buffer, 6, u3)); + try std.testing.expectEqual(0b100011, loadBits(u32, &buffer, 29, u6)); + + storeBits(u32, &buffer, 6, @as(u3, 0b010)); + storeBits(u32, &buffer, 29, @as(u6, 0b010010)); + + try std.testing.expectEqual(0b010, loadBits(u32, &buffer, 6, u3)); + try std.testing.expectEqual(0b010010, loadBits(u32, &buffer, 29, u6)); +}