├── .gitignore ├── LICENSE ├── README.md ├── build.zig ├── build.zig.zon └── src ├── lib.zig └── test.zig /.gitignore: -------------------------------------------------------------------------------- 1 | zig-out/ 2 | .zig-cache/ 3 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2023 Rekai Nyangadzayi Musuka 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # Bit String 2 | 3 | A library to check and extract values from integers based on a "bit string". Primarily intended for (my) emulator instruction decoding, but maybe someone else can find a use for it? 4 | 5 | ## Example 6 | 7 | ```zig 8 | const std = @import("std"); 9 | 10 | test "doc test" { 11 | const value: u8 = 0b10001011; 12 | 13 | try std.testing.expectEqual(true, match("1000_1011", value)); 14 | try std.testing.expectEqual(false, match("11111011", value)); 15 | try std.testing.expectEqual(true, match("1---1011", value)); 16 | 17 | { 18 | const ret = extract("1000aaaa", value); 19 | try std.testing.expectEqual(@as(u4, 0b1011), ret.a); 20 | } 21 | { 22 | const ret = extract("1aaa1aaa", value); 23 | try std.testing.expectEqual(@as(u6, 0b000011), ret.a); 24 | } 25 | { 26 | const ret = extract("1---abcd", value); 27 | try std.testing.expectEqual(@as(u3, 0b1), ret.a); 28 | try std.testing.expectEqual(@as(u3, 0b0), ret.b); 29 | try std.testing.expectEqual(@as(u3, 0b1), ret.c); 30 | try std.testing.expectEqual(@as(u3, 0b1), ret.d); 31 | } 32 | } 33 | ``` 34 | 35 | ## Syntax 36 | 37 | | Token | Meaning | Description 38 | | ------- | --------- | ----------- 39 | | `0` | Clear bit | In the equivalent position, the value's bit must be cleared. 40 | | `1` | Set bit | In the equivalent position, the value's bit must be set. 41 | | `a..=z` | Variable | Given the 4-bit bit string, `"1aa0"`, the value `0b1010` would produce the variable `a` with the value `0b01` 42 | | `-` | Ignored | In the equivalent position, the value's bit does not matter. 43 | | `_` | Ignored* | Underscores are completely ignored during parsing, use to make bit strings easier to read e.g. `1111_1111` 44 | 45 | ## Notes 46 | 47 | - This library does the majority of it's work at `comptime`. Due to this, you cannot create strings to match against at runtime. 48 | - Variables do not have to be "sequential". This means the 5-bit bit string `"1aa0a"` with the value `0b10101` will produce the variable `a` with the value `0b011`. 49 | -------------------------------------------------------------------------------- /build.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | // Although this function looks imperative, note that its job is to 4 | // declaratively construct a build graph that will be executed by an external 5 | // runner. 6 | pub fn build(b: *std.Build) void { 7 | // Standard target options allows the person running `zig build` to choose 8 | // what target to build for. Here we do not override the defaults, which 9 | // means any target is allowed, and the default is native. Other options 10 | // for restricting supported target set are available. 11 | const target = b.standardTargetOptions(.{}); 12 | 13 | // Standard optimization options allow the person running `zig build` to select 14 | // between Debug, ReleaseSafe, ReleaseFast, and ReleaseSmall. Here we do not 15 | // set a preferred release mode, allowing the user to decide how to optimize. 16 | const optimize = b.standardOptimizeOption(.{}); 17 | 18 | // This creates a "module", which represents a collection of source files alongside 19 | // some compilation options, such as optimization mode and linked system libraries. 20 | // Every executable or library we compile will be based on one or more modules. 21 | const lib_mod = b.createModule(.{ 22 | // `root_source_file` is the Zig "entry point" of the module. If a module 23 | // only contains e.g. external object files, you can make this `null`. 24 | // In this case the main source file is merely a path, however, in more 25 | // complicated build scripts, this could be a generated file. 26 | .root_source_file = b.path("src/lib.zig"), 27 | .target = target, 28 | .optimize = optimize, 29 | }); 30 | 31 | // Now, we will create a static library based on the module we created above. 32 | // This creates a `std.Build.Step.Compile`, which is the build step responsible 33 | // for actually invoking the compiler. 34 | const lib = b.addLibrary(.{ 35 | .linkage = .static, 36 | .name = "bit_string", 37 | .root_module = lib_mod, 38 | }); 39 | 40 | // This declares intent for the library to be installed into the standard 41 | // location when the user invokes the "install" step (the default step when 42 | // running `zig build`). 43 | b.installArtifact(lib); 44 | 45 | const docs = b.addInstallDirectory(.{ 46 | .install_dir = .prefix, 47 | .install_subdir = "docs", 48 | .source_dir = lib.getEmittedDocs(), 49 | }); 50 | 51 | // Creates a step for unit testing. This only builds the test executable 52 | // but does not run it. 53 | const lib_unit_tests = b.addTest(.{ 54 | .root_module = lib_mod, 55 | }); 56 | 57 | const run_lib_unit_tests = b.addRunArtifact(lib_unit_tests); 58 | 59 | // Similar to creating the run step earlier, this exposes a `test` step to 60 | // the `zig build --help` menu, providing a way for the user to request 61 | // running the unit tests. 62 | const test_step = b.step("test", "Run unit tests"); 63 | test_step.dependOn(&run_lib_unit_tests.step); 64 | 65 | const docs_step = b.step("docs", "Emit docs"); 66 | docs_step.dependOn(&docs.step); 67 | } 68 | -------------------------------------------------------------------------------- /build.zig.zon: -------------------------------------------------------------------------------- 1 | .{ 2 | // This is the default name used by packages depending on this one. For 3 | // example, when a user runs `zig fetch --save `, this field is used 4 | // as the key in the `dependencies` table. Although the user can choose a 5 | // different name, most users will stick with this provided value. 6 | // 7 | // It is redundant to include "zig" in this name because it is already 8 | // within the Zig package namespace. 9 | .name = .bit_string, 10 | 11 | // This is a [Semantic Version](https://semver.org/). 12 | // In a future version of Zig it will be used for package deduplication. 13 | .version = "0.1.0", 14 | 15 | // Together with name, this represents a globally unique package 16 | // identifier. This field is generated by the Zig toolchain when the 17 | // package is first created, and then *never changes*. This allows 18 | // unambiguous detection of one package being an updated version of 19 | // another. 20 | // 21 | // When forking a Zig project, this id should be regenerated (delete the 22 | // field and run `zig build`) if the upstream project is still maintained. 23 | // Otherwise, the fork is *hostile*, attempting to take control over the 24 | // original project's identity. Thus it is recommended to leave the comment 25 | // on the following line intact, so that it shows up in code reviews that 26 | // modify the field. 27 | .fingerprint = 0x14a39efa6e959493, // Changing this has security and trust implications. 28 | 29 | // Tracks the earliest Zig version that the package considers to be a 30 | // supported use case. 31 | .minimum_zig_version = "0.14.0", 32 | 33 | // This field is optional. 34 | // Each dependency must either provide a `url` and `hash`, or a `path`. 35 | // `zig build --fetch` can be used to fetch all dependencies of a package, recursively. 36 | // Once all dependencies are fetched, `zig build` no longer requires 37 | // internet connectivity. 38 | .dependencies = .{ 39 | // See `zig fetch --save ` for a command-line interface for adding dependencies. 40 | //.example = .{ 41 | // // When updating this field to a new URL, be sure to delete the corresponding 42 | // // `hash`, otherwise you are communicating that you expect to find the old hash at 43 | // // the new URL. If the contents of a URL change this will result in a hash mismatch 44 | // // which will prevent zig from using it. 45 | // .url = "https://example.com/foo.tar.gz", 46 | // 47 | // // This is computed from the file contents of the directory of files that is 48 | // // obtained after fetching `url` and applying the inclusion rules given by 49 | // // `paths`. 50 | // // 51 | // // This field is the source of truth; packages do not come from a `url`; they 52 | // // come from a `hash`. `url` is just one of many possible mirrors for how to 53 | // // obtain a package matching this `hash`. 54 | // // 55 | // // Uses the [multihash](https://multiformats.io/multihash/) format. 56 | // .hash = "...", 57 | // 58 | // // When this is provided, the package is found in a directory relative to the 59 | // // build root. In this case the package's hash is irrelevant and therefore not 60 | // // computed. This field and `url` are mutually exclusive. 61 | // .path = "foo", 62 | // 63 | // // When this is set to `true`, a package is declared to be lazily 64 | // // fetched. This makes the dependency only get fetched if it is 65 | // // actually used. 66 | // .lazy = false, 67 | //}, 68 | }, 69 | 70 | // Specifies the set of files and directories that are included in this package. 71 | // Only files and directories listed here are included in the `hash` that 72 | // is computed for this package. Only files listed here will remain on disk 73 | // when using the zig package manager. As a rule of thumb, one should list 74 | // files required for compilation plus any license(s). 75 | // Paths are relative to the build root. Use the empty string (`""`) to refer to 76 | // the build root itself. 77 | // A directory listed here means that all files within, recursively, are included. 78 | .paths = .{ 79 | "build.zig", 80 | "build.zig.zon", 81 | "src", 82 | // For example... 83 | //"LICENSE", 84 | //"README.md", 85 | }, 86 | } 87 | -------------------------------------------------------------------------------- /src/lib.zig: -------------------------------------------------------------------------------- 1 | //! A library to check and extract values from integers based on a "bit string". Primarily intended for (my) emulator instruction decoding, but maybe someone else can find a use for it? 2 | //! 3 | //! ## Example 4 | //! ```zig 5 | //! const std = @import("std"); 6 | //! 7 | //! test "doc test" { 8 | //! const value: u8 = 0b10001011; 9 | //! 10 | //! try std.testing.expectEqual(true, match("1000_1011", value)); 11 | //! try std.testing.expectEqual(false, match("11111011", value)); 12 | //! try std.testing.expectEqual(true, match("1---1011", value)); 13 | //! 14 | //! { 15 | //! const ret = extract("1000aaaa", value); 16 | //! try std.testing.expectEqual(@as(u4, 0b1011), ret.a); 17 | //! } 18 | //! { 19 | //! const ret = extract("1aaa1aaa", value); 20 | //! try std.testing.expectEqual(@as(u6, 0b000011), ret.a); 21 | //! } 22 | //! { 23 | //! const ret = extract("1---abcd", value); 24 | //! try std.testing.expectEqual(@as(u3, 0b1), ret.a); 25 | //! try std.testing.expectEqual(@as(u3, 0b0), ret.b); 26 | //! try std.testing.expectEqual(@as(u3, 0b1), ret.c); 27 | //! try std.testing.expectEqual(@as(u3, 0b1), ret.d); 28 | //! } 29 | //! } 30 | //! ``` 31 | //! ## Syntax 32 | //! | Token | Meaning | Description | 33 | //! | :-----: | --------- | ------------------------------------------------------------------------------------------------------------- | 34 | //! | `0` | Clear bit | In the equivalent position, the value's bit must be cleared. | 35 | //! | `1` | Set bit | In the equivalent position, the value's bit must be set. | 36 | //! | `a..=z` | Variable | Given the 4-bit bit string, `"1aa0"`, the value `0b1010` would produce the variable `a` with the value `0b01` | 37 | //! | `-` | Ignored | In the equivalent position, the value's bit does not matter. | 38 | //! | `_` | Ignored* | Underscores are completely ignored during parsing, use to make bit strings easier to read e.g. `1111_1111` | 39 | //! 40 | //! ## Notes 41 | //! - This library does the majority of it's work at `comptime`. Due to this, you cannot create strings to match against at runtime. 42 | //! - Variables do not have to be "sequential". This means the 5-bit bit string `"1aa0a"` with the value `0b10101` will produce the variable `a` with the value `0b011`. 43 | 44 | const std = @import("std"); 45 | const Log2Int = std.math.Log2Int; 46 | 47 | /// Test to see if a value matches the provided bit-string 48 | pub fn match(comptime bit_string: []const u8, value: anytype) bool { 49 | @setEvalBranchQuota(std.math.maxInt(u32)); // FIXME: bad practice 50 | 51 | const ValT = @TypeOf(value); 52 | comptime verify(ValT, bit_string); 53 | 54 | const masks: struct { ValT, ValT } = comptime blk: { 55 | const bit_count = @typeInfo(ValT).int.bits; 56 | 57 | var set: ValT = 0; 58 | var clr: ValT = 0; 59 | var offset = 0; 60 | 61 | // FIXME: I linear search like this 5 times across the entire lib. Consider structuring this like a regex lib (compiling a match) 62 | for (bit_string, 0..) |char, i| { 63 | switch (char) { 64 | '0' => clr |= @as(ValT, 1) << @intCast((bit_count - 1 - (i - offset))), 65 | '1' => set |= @as(ValT, 1) << @intCast((bit_count - 1 - (i - offset))), 66 | '_' => offset += 1, 67 | 'a'...'z', '-' => continue, 68 | else => @compileError("'" ++ [_]u8{char} ++ "' was unexpected when parsing bitstring"), 69 | } 70 | } 71 | 72 | break :blk .{ set, clr }; 73 | }; 74 | 75 | const set_mask = masks[0]; 76 | const clr_mask = masks[1]; 77 | 78 | return (value & set_mask) == set_mask and (~value & clr_mask) == clr_mask; 79 | } 80 | 81 | test match { 82 | // doc tests 83 | try std.testing.expectEqual(true, match("1100", @as(u4, 0b1100))); 84 | try std.testing.expectEqual(false, match("1100", @as(u4, 0b1110))); 85 | 86 | try std.testing.expectEqual(true, match("1--0", @as(u4, 0b1010))); 87 | try std.testing.expectEqual(true, match("1ab0", @as(u4, 0b1010))); 88 | try std.testing.expectEqual(true, match("11_00", @as(u4, 0b1100))); 89 | 90 | // other tests 91 | try std.testing.expectEqual(true, match("11111111", @as(u8, 0b11111111))); 92 | try std.testing.expectEqual(true, match("10110011", @as(u8, 0b10110011))); 93 | try std.testing.expectEqual(true, match("101aaabb", @as(u8, 0b10110001))); 94 | try std.testing.expectEqual(true, match("abcdefgh", @as(u8, 0b10110101))); 95 | try std.testing.expectEqual(true, match("aaa---11", @as(u8, 0b01011111))); 96 | try std.testing.expectEqual(true, match("1a0b1c0d", @as(u8, 0b10011101))); 97 | try std.testing.expectEqual(false, match("aaa---11", @as(u8, 0b01011110))); 98 | 99 | try std.testing.expectEqual(true, match("1111_1111", @as(u8, 0b11111111))); 100 | try std.testing.expectEqual(true, match("________11111111", @as(u8, 0b11111111))); 101 | try std.testing.expectEqual(true, match("11111111________", @as(u8, 0b11111111))); 102 | 103 | try std.testing.expectEqual(true, match( 104 | "11111111_11111111_11111111_11111111_11111111_11111111_11111111_11111111", 105 | @as(u64, 0xFFFF_FFFF_FFFF_FFFF), 106 | )); 107 | } 108 | 109 | /// Extracts the variables (defined in the bit string) from a value. 110 | /// 111 | /// Note: In Debug and ReleaseSafe builds, there's a runtime assert that 112 | /// ensures that the value matches against the bit string. 113 | pub fn extract(comptime bit_string: []const u8, value: anytype) Bitfield(bit_string) { 114 | const builtin = @import("builtin"); 115 | 116 | const ValT = @TypeOf(value); 117 | const ReturnT = Bitfield(bit_string); 118 | const bmi2 = switch (builtin.target.cpu.arch) { 119 | .x86_64 => std.Target.x86.featureSetHas(builtin.cpu.features, .bmi2), 120 | else => false, 121 | }; 122 | comptime verify(ValT, bit_string); 123 | 124 | var ret: ReturnT = undefined; 125 | 126 | inline for (@typeInfo(ReturnT).@"struct".fields) |field| { 127 | @field(ret, field.name) = blk: { 128 | var masked_val: ValT = 0; 129 | var offset: usize = 0; // FIXME(URGENT): this whole block should be happening at comptime... 130 | 131 | for (bit_string, 0..) |char, i| { 132 | const rev = @typeInfo(ValT).int.bits - 1 - (i - offset); 133 | 134 | switch (char) { 135 | '_' => offset += 1, 136 | else => if (char == field.name[0]) { 137 | masked_val |= @as(ValT, 1) << @intCast(rev); // no penalty 138 | }, 139 | } 140 | } 141 | 142 | const PextT = if (@typeInfo(ValT).int.bits > 32) u64 else u32; 143 | const use_hw = bmi2 and !@inComptime(); 144 | 145 | break :blk @truncate(if (use_hw) pext.hw(PextT, value, masked_val) else pext.sw(PextT, value, masked_val)); 146 | }; 147 | } 148 | 149 | return ret; 150 | } 151 | 152 | test extract { 153 | // doc tests 154 | { 155 | const ret = extract("aaaa", @as(u4, 0b1001)); 156 | try std.testing.expectEqual(@as(u4, 0b1001), ret.a); 157 | } 158 | { 159 | const ret = extract("abcd", @as(u4, 0b1001)); 160 | try std.testing.expectEqual(@as(u1, 0b1), ret.a); 161 | try std.testing.expectEqual(@as(u1, 0b0), ret.b); 162 | try std.testing.expectEqual(@as(u1, 0b0), ret.c); 163 | try std.testing.expectEqual(@as(u1, 0b1), ret.d); 164 | } 165 | { 166 | const ret = extract("a0ab", @as(u4, 0b1001)); 167 | try std.testing.expectEqual(@as(u2, 0b10), ret.a); 168 | try std.testing.expectEqual(@as(u1, 0b01), ret.b); 169 | } 170 | { 171 | const ret = extract("-a-a", @as(u4, 0b1001)); 172 | try std.testing.expectEqual(@as(u2, 0b01), ret.a); 173 | } 174 | { 175 | const ret = extract("aa_aa", @as(u4, 0b1001)); 176 | try std.testing.expectEqual(@as(u4, 0b1001), ret.a); 177 | } 178 | 179 | // other tests 180 | { 181 | const ret = extract("10aaabbc", @as(u8, 0b10110011)); 182 | try std.testing.expectEqual(@as(u3, 0b110), ret.a); 183 | try std.testing.expectEqual(@as(u2, 0b01), ret.b); 184 | try std.testing.expectEqual(@as(u1, 0b1), ret.c); 185 | } 186 | { 187 | const ret = extract("1111abababab1010", @as(u16, 0b1111_1110_1101_1010)); 188 | try std.testing.expectEqual(@as(u4, 0b1110), ret.a); 189 | try std.testing.expectEqual(@as(u4, 0b1011), ret.b); 190 | } 191 | { 192 | const ret = extract("--------ddddrrrr", @as(u16, 0b0000_0010_1011_0110)); 193 | try std.testing.expectEqual(@as(u4, 0b1011), ret.d); 194 | try std.testing.expectEqual(@as(u4, 0b0110), ret.r); 195 | } 196 | { 197 | const ret = extract("--------", @as(u8, 0b00000000)); 198 | const T = @TypeOf(ret); 199 | 200 | try std.testing.expectEqual(@as(usize, 0), @typeInfo(T).@"struct".fields.len); 201 | } 202 | { 203 | const ret = extract("00000000", @as(u8, 0b00000000)); 204 | const T = @TypeOf(ret); 205 | 206 | try std.testing.expectEqual(@as(usize, 0), @typeInfo(T).@"struct".fields.len); 207 | } 208 | { 209 | const ret = extract("0-0-0-0-", @as(u8, 0b01010101)); 210 | const T = @TypeOf(ret); 211 | 212 | try std.testing.expectEqual(@as(usize, 0), @typeInfo(T).@"struct".fields.len); 213 | } 214 | 215 | { 216 | const ret = extract( 217 | "11111111_ssssssss_11111111_dddddddd_11111111_vvvvvvvv_11111111_xxxxxxxx", 218 | @as(u64, 0xFF55_FF77_FF33_FF00), 219 | ); 220 | 221 | try std.testing.expectEqual(@as(u8, 0x55), ret.s); 222 | try std.testing.expectEqual(@as(u8, 0x77), ret.d); 223 | try std.testing.expectEqual(@as(u8, 0x33), ret.v); 224 | try std.testing.expectEqual(@as(u8, 0x00), ret.x); 225 | } 226 | } 227 | 228 | pub fn matchExtract(comptime bit_string: []const u8, value: anytype) ?Bitfield(bit_string) { 229 | if (!match(bit_string, value)) return null; 230 | return extract(bit_string, value); 231 | } 232 | 233 | /// Parses a bit string and reifies a struct that will contain fields that correspond to the variables present in the bit string. 234 | /// 235 | /// TODO: I will probably rename this type 236 | pub fn Bitfield(comptime bit_string: []const u8) type { 237 | const StructField = std.builtin.Type.StructField; 238 | 239 | const alphabet_set: u26 = tmp: { 240 | var bit_set: u26 = 0; 241 | 242 | for (bit_string) |char| { 243 | switch (char) { 244 | 'a'...'z' => |c| bit_set |= @as(u26, 1) << @intCast(c - 'a'), 245 | else => continue, 246 | } 247 | } 248 | 249 | break :tmp bit_set; 250 | }; 251 | 252 | const field_len = @popCount(alphabet_set); 253 | 254 | const fields = blk: { 255 | var tmp: [field_len]StructField = undefined; 256 | 257 | const Tmp = struct { bits: u8 = 0, char: ?u8 = null }; 258 | var things: [field_len]Tmp = [_]Tmp{.{}} ** field_len; // TODO: rename this lol 259 | 260 | for (bit_string) |char| { 261 | switch (char) { 262 | 'a'...'z' => |c| { 263 | const bit_in_set = @as(u26, 1) << @intCast(c - 'a'); 264 | const pos = @popCount(alphabet_set & ~(bit_in_set - 1)) - 1; // TODO: investigate this off-by-one 265 | 266 | things[pos].bits += 1; 267 | things[pos].char = c; 268 | }, 269 | '1', '0', '-', '_' => continue, 270 | else => @compileError("unexpected char '" ++ [_]u8{char} ++ "' when parsing bit string"), 271 | } 272 | } 273 | 274 | for (things, &tmp) |th, *field| { 275 | const FieldInt = @Type(.{ .int = .{ .signedness = .unsigned, .bits = th.bits } }); 276 | 277 | field.* = .{ 278 | .name = &.{th.char.?}, 279 | .type = FieldInt, 280 | .default_value_ptr = null, 281 | .is_comptime = false, 282 | .alignment = @alignOf(FieldInt), 283 | }; 284 | } 285 | 286 | break :blk tmp; 287 | }; 288 | 289 | return @Type(.{ .@"struct" = .{ 290 | .layout = .auto, 291 | .fields = &fields, 292 | .decls = &.{}, 293 | .is_tuple = false, 294 | } }); 295 | } 296 | 297 | fn verify(comptime T: type, comptime bit_string: []const u8) void { 298 | const info = @typeInfo(T); 299 | 300 | std.debug.assert(info != .comptime_int); 301 | std.debug.assert(info.int.signedness == .unsigned); 302 | std.debug.assert(info.int.bits <= 64); // x86 PEXT u32 and u64 operands only 303 | 304 | var underscore_count = 0; 305 | for (bit_string) |c| { 306 | if (c == '_') underscore_count += 1; 307 | } 308 | 309 | std.debug.assert((bit_string.len - underscore_count) == info.int.bits); 310 | } 311 | 312 | const pext = struct { 313 | fn hw(comptime T: type, value: T, mask: T) T { 314 | return switch (T) { 315 | u32 => asm ("pextl %[mask], %[value], %[ret]" 316 | : [ret] "=r" (-> T), 317 | : [value] "r" (value), 318 | [mask] "r" (mask), 319 | ), 320 | u64 => asm ("pextq %[mask], %[value], %[ret]" 321 | : [ret] "=r" (-> T), 322 | : [value] "r" (value), 323 | [mask] "r" (mask), 324 | ), 325 | else => @compileError("pext is sunsupported for " ++ @typeName(T) ++ "."), 326 | }; 327 | } 328 | 329 | inline fn sw(comptime T: type, value: T, mask: T) T { 330 | // FIXME: will be replaced in the future by https://github.com/ziglang/zig/issues/14995 (hopefully?) 331 | 332 | return switch (T) { 333 | u32, u64 => { 334 | // code source: https://stackoverflow.com/questions/41720249/detecting-matching-bits-in-c 335 | // TODO: rewrite more in generic/idiomatic zig 336 | const log2_bits = @typeInfo(Log2Int(T)).int.bits; 337 | 338 | var val: T = value & mask; // immediately clear irrelevant bits 339 | var msk: T = mask; 340 | 341 | var mk: T = ~msk << 1; // count 0s to the right 342 | 343 | inline for (0..log2_bits) |i| { 344 | var mp: T = mk ^ (mk << 1); 345 | inline for (1..log2_bits) |j| { 346 | mp = mp ^ (mp << (1 << j)); // parallel suffix 347 | } 348 | 349 | const mv = (mp & msk); // bits to move 350 | msk = ((msk ^ mv) | (mv >> (1 << i))); // compress mask 351 | 352 | const t = (val & mv); 353 | val = ((val ^ t) | (t >> (1 << i))); // compress val 354 | 355 | mk &= ~mp; 356 | } 357 | 358 | return val; 359 | }, 360 | else => @compileError("pext is sunsupported for " ++ @typeName(T) ++ "."), 361 | }; 362 | } 363 | 364 | test pext { 365 | const builtin = @import("builtin"); 366 | 367 | try std.testing.expectEqual(@as(u32, 0x0001_2567), pext.sw(u32, 0x12345678, 0xFF00FFF0)); 368 | try std.testing.expectEqual(@as(u64, 0x0001_2567), pext.sw(u64, 0x12345678, 0xFF00FFF0)); 369 | 370 | switch (builtin.cpu.arch) { 371 | .x86_64 => if (std.Target.x86.featureSetHas(builtin.cpu.features, .bmi2)) { 372 | var rand_impl = std.Random.DefaultPrng.init(0xBAADF00D_DEADCAFE); 373 | 374 | for (0..100) |_| { 375 | const value = rand_impl.random().int(u32); 376 | const mask = rand_impl.random().int(u32); 377 | 378 | try std.testing.expectEqual(pext.hw(u32, value, mask), pext.sw(u32, value, mask)); 379 | } 380 | 381 | for (0..100) |_| { 382 | const value = rand_impl.random().int(u64); 383 | const mask = rand_impl.random().int(u64); 384 | 385 | try std.testing.expectEqual(pext.hw(u64, value, mask), pext.sw(u64, value, mask)); 386 | } 387 | }, 388 | else => return error.SkipZigTest, 389 | } 390 | 391 | // example values from: https://en.wikipedia.org/w/index.php?title=X86_Bit_manipulation_instruction_set&oldid=1170426748 392 | try std.testing.expectEqual(@as(u32, 0x0001_2567), pext.sw(u32, 0x12345678, 0xFF00FFF0)); 393 | } 394 | }; 395 | 396 | test "doc test" { 397 | const value: u8 = 0b10001011; 398 | 399 | try std.testing.expectEqual(true, match("1000_1011", value)); 400 | try std.testing.expectEqual(false, match("11111011", value)); 401 | try std.testing.expectEqual(true, match("1---1011", value)); 402 | 403 | { 404 | const ret = extract("1000aaaa", value); 405 | try std.testing.expectEqual(@as(u4, 0b1011), ret.a); 406 | } 407 | { 408 | const ret = extract("1aaa1aaa", value); 409 | try std.testing.expectEqual(@as(u6, 0b000011), ret.a); 410 | } 411 | { 412 | const ret = extract("1---abcd", value); 413 | try std.testing.expectEqual(@as(u3, 0b1), ret.a); 414 | try std.testing.expectEqual(@as(u3, 0b0), ret.b); 415 | try std.testing.expectEqual(@as(u3, 0b1), ret.c); 416 | try std.testing.expectEqual(@as(u3, 0b1), ret.d); 417 | } 418 | } 419 | -------------------------------------------------------------------------------- /src/test.zig: -------------------------------------------------------------------------------- 1 | const std = @import("std"); 2 | 3 | comptime { 4 | _ = @import("lib.zig"); 5 | } 6 | 7 | test { 8 | std.testing.refAllDecls(@This()); 9 | } 10 | --------------------------------------------------------------------------------