Compare commits
10 Commits
0af0295acf
...
main
Author | SHA1 | Date | |
---|---|---|---|
f2eafb912b | |||
57e1b7e42f | |||
d72a39006a | |||
040e15f72e | |||
4363360ce4 | |||
64e1daf31f | |||
611549c8e9 | |||
fbbd25a4c8 | |||
dbe6bd50cd | |||
e67d98de86 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,2 +1,2 @@
|
||||
zig-out/
|
||||
zig-cache/
|
||||
.zig-cache/
|
||||
|
@@ -9,9 +9,11 @@ const std = @import("std");
|
||||
|
||||
test "doc test" {
|
||||
const value: u8 = 0b10001011;
|
||||
try std.testing.expectEqual(true, match("10001011", value));
|
||||
|
||||
try std.testing.expectEqual(true, match("1000_1011", value));
|
||||
try std.testing.expectEqual(false, match("11111011", value));
|
||||
try std.testing.expectEqual(true, match("1---1011", value));
|
||||
|
||||
{
|
||||
const ret = extract("1000aaaa", value);
|
||||
try std.testing.expectEqual(@as(u4, 0b1011), ret.a);
|
||||
@@ -34,10 +36,11 @@ test "doc test" {
|
||||
|
||||
| Token | Meaning | Description
|
||||
| ------- | --------- | -----------
|
||||
| `0` | Unset bit | In the equivalent position, the value's bit must be set.
|
||||
| `0` | Clear bit | In the equivalent position, the value's bit must be cleared.
|
||||
| `1` | Set bit | In the equivalent position, the value's bit must be set.
|
||||
| `a..=z` | Variable | Given the 4-bit bit string, `"1aa0"`, the value `0b1010` would produce the variable `a` with the value `0b01`
|
||||
| `-` | Ignored | In the equivalent position, the value's bit does not matter.
|
||||
| `_` | Ignored* | Underscores are completely ignored during parsing, use to make bit strings easier to read e.g. `1111_1111`
|
||||
|
||||
## Notes
|
||||
|
||||
|
52
build.zig
52
build.zig
@@ -15,35 +15,53 @@ pub fn build(b: *std.Build) void {
|
||||
// set a preferred release mode, allowing the user to decide how to optimize.
|
||||
const optimize = b.standardOptimizeOption(.{});
|
||||
|
||||
_ = b.addModule("bit-string", .{ .source_file = .{ .path = "src/lib.zig" } });
|
||||
|
||||
const lib = b.addStaticLibrary(.{
|
||||
.name = "bit-string",
|
||||
// This creates a "module", which represents a collection of source files alongside
|
||||
// some compilation options, such as optimization mode and linked system libraries.
|
||||
// Every executable or library we compile will be based on one or more modules.
|
||||
const lib_mod = b.createModule(.{
|
||||
// `root_source_file` is the Zig "entry point" of the module. If a module
|
||||
// only contains e.g. external object files, you can make this `null`.
|
||||
// In this case the main source file is merely a path, however, in more
|
||||
// complicated build scripts, this could be a generated file.
|
||||
.root_source_file = .{ .path = "src/lib.zig" },
|
||||
.root_source_file = b.path("src/lib.zig"),
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
});
|
||||
|
||||
// Now, we will create a static library based on the module we created above.
|
||||
// This creates a `std.Build.Step.Compile`, which is the build step responsible
|
||||
// for actually invoking the compiler.
|
||||
const lib = b.addLibrary(.{
|
||||
.linkage = .static,
|
||||
.name = "bit_string",
|
||||
.root_module = lib_mod,
|
||||
});
|
||||
|
||||
// This declares intent for the library to be installed into the standard
|
||||
// location when the user invokes the "install" step (the default step when
|
||||
// running `zig build`).
|
||||
b.installArtifact(lib);
|
||||
|
||||
// Creates a step for unit testing. This only builds the test executable
|
||||
// but does not run it.
|
||||
const lib_tests = b.addTest(.{
|
||||
.root_source_file = .{ .path = "src/test.zig" },
|
||||
.target = target,
|
||||
.optimize = optimize,
|
||||
const docs = b.addInstallDirectory(.{
|
||||
.install_dir = .prefix,
|
||||
.install_subdir = "docs",
|
||||
.source_dir = lib.getEmittedDocs(),
|
||||
});
|
||||
|
||||
const run_main_tests = b.addRunArtifact(lib_tests);
|
||||
// Creates a step for unit testing. This only builds the test executable
|
||||
// but does not run it.
|
||||
const lib_unit_tests = b.addTest(.{
|
||||
.root_module = lib_mod,
|
||||
});
|
||||
|
||||
// This creates a build step. It will be visible in the `zig build --help` menu,
|
||||
// and can be selected like this: `zig build test`
|
||||
// This will evaluate the `test` step rather than the default, which is "install".
|
||||
const test_step = b.step("test", "Run library tests");
|
||||
test_step.dependOn(&run_main_tests.step);
|
||||
const run_lib_unit_tests = b.addRunArtifact(lib_unit_tests);
|
||||
|
||||
// Similar to creating the run step earlier, this exposes a `test` step to
|
||||
// the `zig build --help` menu, providing a way for the user to request
|
||||
// running the unit tests.
|
||||
const test_step = b.step("test", "Run unit tests");
|
||||
test_step.dependOn(&run_lib_unit_tests.step);
|
||||
|
||||
const docs_step = b.step("docs", "Emit docs");
|
||||
docs_step.dependOn(&docs.step);
|
||||
}
|
||||
|
@@ -1,5 +1,86 @@
|
||||
.{
|
||||
.name = "bit-string",
|
||||
// This is the default name used by packages depending on this one. For
|
||||
// example, when a user runs `zig fetch --save <url>`, this field is used
|
||||
// as the key in the `dependencies` table. Although the user can choose a
|
||||
// different name, most users will stick with this provided value.
|
||||
//
|
||||
// It is redundant to include "zig" in this name because it is already
|
||||
// within the Zig package namespace.
|
||||
.name = .bit_string,
|
||||
|
||||
// This is a [Semantic Version](https://semver.org/).
|
||||
// In a future version of Zig it will be used for package deduplication.
|
||||
.version = "0.1.0",
|
||||
.dependencies = .{},
|
||||
|
||||
// Together with name, this represents a globally unique package
|
||||
// identifier. This field is generated by the Zig toolchain when the
|
||||
// package is first created, and then *never changes*. This allows
|
||||
// unambiguous detection of one package being an updated version of
|
||||
// another.
|
||||
//
|
||||
// When forking a Zig project, this id should be regenerated (delete the
|
||||
// field and run `zig build`) if the upstream project is still maintained.
|
||||
// Otherwise, the fork is *hostile*, attempting to take control over the
|
||||
// original project's identity. Thus it is recommended to leave the comment
|
||||
// on the following line intact, so that it shows up in code reviews that
|
||||
// modify the field.
|
||||
.fingerprint = 0x14a39efa6e959493, // Changing this has security and trust implications.
|
||||
|
||||
// Tracks the earliest Zig version that the package considers to be a
|
||||
// supported use case.
|
||||
.minimum_zig_version = "0.14.0",
|
||||
|
||||
// This field is optional.
|
||||
// Each dependency must either provide a `url` and `hash`, or a `path`.
|
||||
// `zig build --fetch` can be used to fetch all dependencies of a package, recursively.
|
||||
// Once all dependencies are fetched, `zig build` no longer requires
|
||||
// internet connectivity.
|
||||
.dependencies = .{
|
||||
// See `zig fetch --save <url>` for a command-line interface for adding dependencies.
|
||||
//.example = .{
|
||||
// // When updating this field to a new URL, be sure to delete the corresponding
|
||||
// // `hash`, otherwise you are communicating that you expect to find the old hash at
|
||||
// // the new URL. If the contents of a URL change this will result in a hash mismatch
|
||||
// // which will prevent zig from using it.
|
||||
// .url = "https://example.com/foo.tar.gz",
|
||||
//
|
||||
// // This is computed from the file contents of the directory of files that is
|
||||
// // obtained after fetching `url` and applying the inclusion rules given by
|
||||
// // `paths`.
|
||||
// //
|
||||
// // This field is the source of truth; packages do not come from a `url`; they
|
||||
// // come from a `hash`. `url` is just one of many possible mirrors for how to
|
||||
// // obtain a package matching this `hash`.
|
||||
// //
|
||||
// // Uses the [multihash](https://multiformats.io/multihash/) format.
|
||||
// .hash = "...",
|
||||
//
|
||||
// // When this is provided, the package is found in a directory relative to the
|
||||
// // build root. In this case the package's hash is irrelevant and therefore not
|
||||
// // computed. This field and `url` are mutually exclusive.
|
||||
// .path = "foo",
|
||||
//
|
||||
// // When this is set to `true`, a package is declared to be lazily
|
||||
// // fetched. This makes the dependency only get fetched if it is
|
||||
// // actually used.
|
||||
// .lazy = false,
|
||||
//},
|
||||
},
|
||||
|
||||
// Specifies the set of files and directories that are included in this package.
|
||||
// Only files and directories listed here are included in the `hash` that
|
||||
// is computed for this package. Only files listed here will remain on disk
|
||||
// when using the zig package manager. As a rule of thumb, one should list
|
||||
// files required for compilation plus any license(s).
|
||||
// Paths are relative to the build root. Use the empty string (`""`) to refer to
|
||||
// the build root itself.
|
||||
// A directory listed here means that all files within, recursively, are included.
|
||||
.paths = .{
|
||||
"build.zig",
|
||||
"build.zig.zon",
|
||||
"src",
|
||||
// For example...
|
||||
//"LICENSE",
|
||||
//"README.md",
|
||||
},
|
||||
}
|
||||
|
222
src/lib.zig
222
src/lib.zig
@@ -7,7 +7,7 @@
|
||||
//! test "doc test" {
|
||||
//! const value: u8 = 0b10001011;
|
||||
//!
|
||||
//! try std.testing.expectEqual(true, match("10001011", value));
|
||||
//! try std.testing.expectEqual(true, match("1000_1011", value));
|
||||
//! try std.testing.expectEqual(false, match("11111011", value));
|
||||
//! try std.testing.expectEqual(true, match("1---1011", value));
|
||||
//!
|
||||
@@ -29,12 +29,13 @@
|
||||
//! }
|
||||
//! ```
|
||||
//! ## Syntax
|
||||
//! | Token | Meaning | Description
|
||||
//! | ------- | --------- | -----------
|
||||
//! | `0` | Unset bit | In the equivalent position, the value's bit must be set.
|
||||
//! | `1` | Set bit | In the equivalent position, the value's bit must be set.
|
||||
//! | `a..=z` | Variable | Given the 4-bit bit string, `"1aa0"`, the value `0b1010` would produce the variable `a` with the value `0b01`
|
||||
//! | `-` | Ignored | In the equivalent position, the value's bit does not matter.
|
||||
//! | Token | Meaning | Description |
|
||||
//! | :-----: | --------- | ------------------------------------------------------------------------------------------------------------- |
|
||||
//! | `0` | Clear bit | In the equivalent position, the value's bit must be cleared. |
|
||||
//! | `1` | Set bit | In the equivalent position, the value's bit must be set. |
|
||||
//! | `a..=z` | Variable | Given the 4-bit bit string, `"1aa0"`, the value `0b1010` would produce the variable `a` with the value `0b01` |
|
||||
//! | `-` | Ignored | In the equivalent position, the value's bit does not matter. |
|
||||
//! | `_` | Ignored* | Underscores are completely ignored during parsing, use to make bit strings easier to read e.g. `1111_1111` |
|
||||
//!
|
||||
//! ## Notes
|
||||
//! - This library does the majority of it's work at `comptime`. Due to this, you cannot create strings to match against at runtime.
|
||||
@@ -44,14 +45,6 @@ const std = @import("std");
|
||||
const Log2Int = std.math.Log2Int;
|
||||
|
||||
/// Test to see if a value matches the provided bit-string
|
||||
///
|
||||
/// ### Example
|
||||
/// ```zig
|
||||
/// match("1100", @as(u4, 0b1100)) // true
|
||||
/// match("1100", @as(u4, 0b1110)) // false
|
||||
/// match("1--0", @as(u4, 0b1010)) // true
|
||||
/// match("1ab0", @as(u4, 0b1010)) // true
|
||||
/// ```
|
||||
pub fn match(comptime bit_string: []const u8, value: anytype) bool {
|
||||
@setEvalBranchQuota(std.math.maxInt(u32)); // FIXME: bad practice
|
||||
|
||||
@@ -59,16 +52,18 @@ pub fn match(comptime bit_string: []const u8, value: anytype) bool {
|
||||
comptime verify(ValT, bit_string);
|
||||
|
||||
const masks: struct { ValT, ValT } = comptime blk: {
|
||||
const bit_count = @typeInfo(ValT).Int.bits;
|
||||
const bit_count = @typeInfo(ValT).int.bits;
|
||||
|
||||
var set: ValT = 0;
|
||||
var clr: ValT = 0;
|
||||
var offset = 0;
|
||||
|
||||
// FIXME: I linear search bit_string 4 separate times. Consider doing a single search and compromizing on memory + stateless API? (imagine a "regex compile"-like API)
|
||||
// FIXME: I linear search like this 5 times across the entire lib. Consider structuring this like a regex lib (compiling a match)
|
||||
for (bit_string, 0..) |char, i| {
|
||||
switch (char) {
|
||||
'0' => clr |= @as(ValT, 1) << @intCast(bit_count - 1 - i),
|
||||
'1' => set |= @as(ValT, 1) << @intCast(bit_count - 1 - i),
|
||||
'0' => clr |= @as(ValT, 1) << @intCast((bit_count - 1 - (i - offset))),
|
||||
'1' => set |= @as(ValT, 1) << @intCast((bit_count - 1 - (i - offset))),
|
||||
'_' => offset += 1,
|
||||
'a'...'z', '-' => continue,
|
||||
else => @compileError("'" ++ [_]u8{char} ++ "' was unexpected when parsing bitstring"),
|
||||
}
|
||||
@@ -83,12 +78,14 @@ pub fn match(comptime bit_string: []const u8, value: anytype) bool {
|
||||
return (value & set_mask) == set_mask and (~value & clr_mask) == clr_mask;
|
||||
}
|
||||
|
||||
test "match" {
|
||||
test match {
|
||||
// doc tests
|
||||
try std.testing.expectEqual(true, match("1100", @as(u4, 0b1100))); // true
|
||||
try std.testing.expectEqual(false, match("1100", @as(u4, 0b1110))); // false
|
||||
try std.testing.expectEqual(true, match("1--0", @as(u4, 0b1010))); // true
|
||||
try std.testing.expectEqual(true, match("1ab0", @as(u4, 0b1010))); // true
|
||||
try std.testing.expectEqual(true, match("1100", @as(u4, 0b1100)));
|
||||
try std.testing.expectEqual(false, match("1100", @as(u4, 0b1110)));
|
||||
|
||||
try std.testing.expectEqual(true, match("1--0", @as(u4, 0b1010)));
|
||||
try std.testing.expectEqual(true, match("1ab0", @as(u4, 0b1010)));
|
||||
try std.testing.expectEqual(true, match("11_00", @as(u4, 0b1100)));
|
||||
|
||||
// other tests
|
||||
try std.testing.expectEqual(true, match("11111111", @as(u8, 0b11111111)));
|
||||
@@ -98,18 +95,19 @@ test "match" {
|
||||
try std.testing.expectEqual(true, match("aaa---11", @as(u8, 0b01011111)));
|
||||
try std.testing.expectEqual(true, match("1a0b1c0d", @as(u8, 0b10011101)));
|
||||
try std.testing.expectEqual(false, match("aaa---11", @as(u8, 0b01011110)));
|
||||
|
||||
try std.testing.expectEqual(true, match("1111_1111", @as(u8, 0b11111111)));
|
||||
try std.testing.expectEqual(true, match("________11111111", @as(u8, 0b11111111)));
|
||||
try std.testing.expectEqual(true, match("11111111________", @as(u8, 0b11111111)));
|
||||
|
||||
try std.testing.expectEqual(true, match(
|
||||
"11111111_11111111_11111111_11111111_11111111_11111111_11111111_11111111",
|
||||
@as(u64, 0xFFFF_FFFF_FFFF_FFFF),
|
||||
));
|
||||
}
|
||||
|
||||
/// Extracts the variables (defined in the bit string) from a value.
|
||||
///
|
||||
/// ### Examples
|
||||
/// ```
|
||||
/// const ret = extract("aaaa", @as(u4, 0b1001)); // ret.a == 0b1001
|
||||
/// const ret = extract("abcd", @as(u4, 0b1001)); // ret.a == 0b1, ret.b == 0b0, ret.c == 0b0, ret.d == 0b1
|
||||
/// const ret = extract("a0ab", @as(u4, 0b1001)); // ret.a == 0b10, ret.b == 0b1
|
||||
/// const ret = extract("-a-a", @as(u4, 0b1001)); // ret.a == 0b01
|
||||
/// ```
|
||||
///
|
||||
/// Note: In Debug and ReleaseSafe builds, there's a runtime assert that
|
||||
/// ensures that the value matches against the bit string.
|
||||
pub fn extract(comptime bit_string: []const u8, value: anytype) Bitfield(bit_string) {
|
||||
@@ -125,35 +123,33 @@ pub fn extract(comptime bit_string: []const u8, value: anytype) Bitfield(bit_str
|
||||
|
||||
var ret: ReturnT = undefined;
|
||||
|
||||
inline for (@typeInfo(ReturnT).Struct.fields) |field| {
|
||||
inline for (@typeInfo(ReturnT).@"struct".fields) |field| {
|
||||
@field(ret, field.name) = blk: {
|
||||
var masked_val: ValT = 0;
|
||||
var offset: usize = 0; // FIXME(URGENT): this whole block should be happening at comptime...
|
||||
|
||||
for (bit_string, 0..) |char, i| {
|
||||
const rev = @typeInfo(ValT).Int.bits - 1 - i;
|
||||
if (char == field.name[0]) masked_val |= @as(ValT, 1) << @intCast(rev); // no penalty
|
||||
const rev = @typeInfo(ValT).int.bits - 1 - (i - offset);
|
||||
|
||||
switch (char) {
|
||||
'_' => offset += 1,
|
||||
else => if (char == field.name[0]) {
|
||||
masked_val |= @as(ValT, 1) << @intCast(rev); // no penalty
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// TODO: decide at compile time if we're calling the 32-bit or 64-bit version of `PEXT`
|
||||
const PextT = if (@typeInfo(ValT).int.bits > 32) u64 else u32;
|
||||
const use_hw = bmi2 and !@inComptime();
|
||||
|
||||
// invariant: the bit count in the field we're writing to and the
|
||||
// # of bits we happened to find in this linear search are identical
|
||||
//
|
||||
// we're confident in this because it's guaranteed to be the same bit_string,
|
||||
// and it's the same linear search. If you're reading this double check that this is still the case lol
|
||||
break :blk @truncate(if (bmi2 and !@inComptime()) pext.hardware(u32, value, masked_val) else pext.software(u32, value, masked_val));
|
||||
break :blk @truncate(if (use_hw) pext.hw(PextT, value, masked_val) else pext.sw(PextT, value, masked_val));
|
||||
};
|
||||
}
|
||||
|
||||
return ret;
|
||||
}
|
||||
|
||||
pub fn matchExtract(comptime bit_string: []const u8, value: anytype) ?Bitfield(bit_string) {
|
||||
if (!match(bit_string, value)) return null;
|
||||
return extract(bit_string, value);
|
||||
}
|
||||
|
||||
test "extract" {
|
||||
test extract {
|
||||
// doc tests
|
||||
{
|
||||
const ret = extract("aaaa", @as(u4, 0b1001));
|
||||
@@ -175,6 +171,10 @@ test "extract" {
|
||||
const ret = extract("-a-a", @as(u4, 0b1001));
|
||||
try std.testing.expectEqual(@as(u2, 0b01), ret.a);
|
||||
}
|
||||
{
|
||||
const ret = extract("aa_aa", @as(u4, 0b1001));
|
||||
try std.testing.expectEqual(@as(u4, 0b1001), ret.a);
|
||||
}
|
||||
|
||||
// other tests
|
||||
{
|
||||
@@ -197,27 +197,41 @@ test "extract" {
|
||||
const ret = extract("--------", @as(u8, 0b00000000));
|
||||
const T = @TypeOf(ret);
|
||||
|
||||
try std.testing.expectEqual(@as(usize, 0), @typeInfo(T).Struct.fields.len);
|
||||
try std.testing.expectEqual(@as(usize, 0), @typeInfo(T).@"struct".fields.len);
|
||||
}
|
||||
{
|
||||
const ret = extract("00000000", @as(u8, 0b00000000));
|
||||
const T = @TypeOf(ret);
|
||||
|
||||
try std.testing.expectEqual(@as(usize, 0), @typeInfo(T).Struct.fields.len);
|
||||
try std.testing.expectEqual(@as(usize, 0), @typeInfo(T).@"struct".fields.len);
|
||||
}
|
||||
{
|
||||
const ret = extract("0-0-0-0-", @as(u8, 0b01010101));
|
||||
const T = @TypeOf(ret);
|
||||
|
||||
try std.testing.expectEqual(@as(usize, 0), @typeInfo(T).Struct.fields.len);
|
||||
try std.testing.expectEqual(@as(usize, 0), @typeInfo(T).@"struct".fields.len);
|
||||
}
|
||||
|
||||
{
|
||||
const ret = extract(
|
||||
"11111111_ssssssss_11111111_dddddddd_11111111_vvvvvvvv_11111111_xxxxxxxx",
|
||||
@as(u64, 0xFF55_FF77_FF33_FF00),
|
||||
);
|
||||
|
||||
try std.testing.expectEqual(@as(u8, 0x55), ret.s);
|
||||
try std.testing.expectEqual(@as(u8, 0x77), ret.d);
|
||||
try std.testing.expectEqual(@as(u8, 0x33), ret.v);
|
||||
try std.testing.expectEqual(@as(u8, 0x00), ret.x);
|
||||
}
|
||||
}
|
||||
|
||||
pub fn matchExtract(comptime bit_string: []const u8, value: anytype) ?Bitfield(bit_string) {
|
||||
if (!match(bit_string, value)) return null;
|
||||
return extract(bit_string, value);
|
||||
}
|
||||
|
||||
/// Parses a bit string and reifies a struct that will contain fields that correspond to the variables present in the bit string.
|
||||
///
|
||||
///
|
||||
/// Note: If it weren't for the return type of `extract()`, this type would be a private implementation detail
|
||||
///
|
||||
/// TODO: I will probably rename this type
|
||||
pub fn Bitfield(comptime bit_string: []const u8) type {
|
||||
const StructField = std.builtin.Type.StructField;
|
||||
@@ -252,18 +266,18 @@ pub fn Bitfield(comptime bit_string: []const u8) type {
|
||||
things[pos].bits += 1;
|
||||
things[pos].char = c;
|
||||
},
|
||||
'1', '0', '-' => continue,
|
||||
else => @compileError("error when parsing bitset string"),
|
||||
'1', '0', '-', '_' => continue,
|
||||
else => @compileError("unexpected char '" ++ [_]u8{char} ++ "' when parsing bit string"),
|
||||
}
|
||||
}
|
||||
|
||||
for (things, &tmp) |th, *field| {
|
||||
const FieldInt = @Type(.{ .Int = .{ .signedness = .unsigned, .bits = th.bits } });
|
||||
const FieldInt = @Type(.{ .int = .{ .signedness = .unsigned, .bits = th.bits } });
|
||||
|
||||
field.* = .{
|
||||
.name = &.{th.char.?},
|
||||
.type = FieldInt,
|
||||
.default_value = null,
|
||||
.default_value_ptr = null,
|
||||
.is_comptime = false,
|
||||
.alignment = @alignOf(FieldInt),
|
||||
};
|
||||
@@ -272,8 +286,8 @@ pub fn Bitfield(comptime bit_string: []const u8) type {
|
||||
break :blk tmp;
|
||||
};
|
||||
|
||||
return @Type(.{ .Struct = .{
|
||||
.layout = .Auto,
|
||||
return @Type(.{ .@"struct" = .{
|
||||
.layout = .auto,
|
||||
.fields = &fields,
|
||||
.decls = &.{},
|
||||
.is_tuple = false,
|
||||
@@ -283,17 +297,20 @@ pub fn Bitfield(comptime bit_string: []const u8) type {
|
||||
fn verify(comptime T: type, comptime bit_string: []const u8) void {
|
||||
const info = @typeInfo(T);
|
||||
|
||||
// FIXME: remove the need for this
|
||||
if (info.Int.bits > 32) @compileError("TODO: 64-bit `PEXT` software implementation");
|
||||
std.debug.assert(info != .comptime_int);
|
||||
std.debug.assert(info.int.signedness == .unsigned);
|
||||
std.debug.assert(info.int.bits <= 64); // x86 PEXT u32 and u64 operands only
|
||||
|
||||
std.debug.assert(info != .ComptimeInt);
|
||||
std.debug.assert(info.Int.signedness == .unsigned);
|
||||
std.debug.assert(info.Int.bits <= 64); // x86 PEXT u32 and u64 operands only
|
||||
std.debug.assert(bit_string.len == info.Int.bits); // TODO: Support Underscores?
|
||||
var underscore_count = 0;
|
||||
for (bit_string) |c| {
|
||||
if (c == '_') underscore_count += 1;
|
||||
}
|
||||
|
||||
std.debug.assert((bit_string.len - underscore_count) == info.int.bits);
|
||||
}
|
||||
|
||||
const pext = struct {
|
||||
fn hardware(comptime T: type, value: T, mask: T) T {
|
||||
fn hw(comptime T: type, value: T, mask: T) T {
|
||||
return switch (T) {
|
||||
u32 => asm ("pextl %[mask], %[value], %[ret]"
|
||||
: [ret] "=r" (-> T),
|
||||
@@ -309,72 +326,77 @@ const pext = struct {
|
||||
};
|
||||
}
|
||||
|
||||
// why we need this: https://github.com/ziglang/zig/issues/14995 (ideally compiler-rt implements this for us)
|
||||
fn software(comptime T: type, value: T, mask: T) T {
|
||||
inline fn sw(comptime T: type, value: T, mask: T) T {
|
||||
// FIXME: will be replaced in the future by https://github.com/ziglang/zig/issues/14995 (hopefully?)
|
||||
|
||||
return switch (T) {
|
||||
u32 => {
|
||||
// TODO: Looks (and is) like C code :pensive:
|
||||
u32, u64 => {
|
||||
// code source: https://stackoverflow.com/questions/41720249/detecting-matching-bits-in-c
|
||||
// TODO: rewrite more in generic/idiomatic zig
|
||||
const log2_bits = @typeInfo(Log2Int(T)).int.bits;
|
||||
|
||||
var _value: T = value;
|
||||
var _mask: T = mask;
|
||||
var val: T = value & mask; // immediately clear irrelevant bits
|
||||
var msk: T = mask;
|
||||
|
||||
_value &= _mask;
|
||||
var mk: T = ~_mask << 1;
|
||||
var mp: T = undefined;
|
||||
var mv: T = undefined;
|
||||
var t: T = undefined;
|
||||
var mk: T = ~msk << 1; // count 0s to the right
|
||||
|
||||
inline for (0..log2_bits) |i| {
|
||||
var mp: T = mk ^ (mk << 1);
|
||||
inline for (1..log2_bits) |j| {
|
||||
mp = mp ^ (mp << (1 << j)); // parallel suffix
|
||||
}
|
||||
|
||||
const mv = (mp & msk); // bits to move
|
||||
msk = ((msk ^ mv) | (mv >> (1 << i))); // compress mask
|
||||
|
||||
const t = (val & mv);
|
||||
val = ((val ^ t) | (t >> (1 << i))); // compress val
|
||||
|
||||
inline for (0..@typeInfo(u5).Int.bits) |i| {
|
||||
mp = mk ^ (mk << 1); // parallel suffix
|
||||
mp = mp ^ (mp << 2);
|
||||
mp = mp ^ (mp << 4);
|
||||
mp = mp ^ (mp << 8);
|
||||
mp = mp ^ (mp << 16);
|
||||
mv = (mp & _mask); // bits to move
|
||||
_mask = ((_mask ^ mv) | (mv >> (1 << i))); // compress _mask
|
||||
t = (_value & mv);
|
||||
_value = ((_value ^ t) | (t >> (1 << i))); // compress _value
|
||||
mk &= ~mp;
|
||||
}
|
||||
|
||||
return _value;
|
||||
return val;
|
||||
},
|
||||
u64 => @compileError("TODO: find/write branchless software impl of `PEXT` for 64-bit values"),
|
||||
else => @compileError("pext is sunsupported for " ++ @typeName(T) ++ "."),
|
||||
};
|
||||
}
|
||||
|
||||
test "pext" {
|
||||
test pext {
|
||||
const builtin = @import("builtin");
|
||||
|
||||
try std.testing.expectEqual(@as(u32, 0x0001_2567), pext.sw(u32, 0x12345678, 0xFF00FFF0));
|
||||
try std.testing.expectEqual(@as(u64, 0x0001_2567), pext.sw(u64, 0x12345678, 0xFF00FFF0));
|
||||
|
||||
switch (builtin.cpu.arch) {
|
||||
.x86_64 => if (std.Target.x86.featureSetHas(builtin.cpu.features, .bmi2)) {
|
||||
try std.testing.expectEqual(@as(u32, 0x0001_2567), pext.hardware(u32, 0x12345678, 0xFF00FFF0));
|
||||
try std.testing.expectEqual(@as(u64, 0x0001_2567), pext.hardware(u64, 0x12345678, 0xFF00FFF0));
|
||||
var rand_impl = std.Random.DefaultPrng.init(0xBAADF00D_DEADCAFE);
|
||||
|
||||
// random tests
|
||||
// TODO: when implemented, test 64-bit fallback `PEXT` as well
|
||||
var rand_impl = std.rand.DefaultPrng.init(0xBAADF00D_DEADCAFE);
|
||||
for (0..100) |_| {
|
||||
const value = rand_impl.random().int(u32);
|
||||
const mask = rand_impl.random().int(u32);
|
||||
|
||||
try std.testing.expectEqual(pext.hardware(u32, value, mask), pext.software(u32, value, mask));
|
||||
try std.testing.expectEqual(pext.hw(u32, value, mask), pext.sw(u32, value, mask));
|
||||
}
|
||||
|
||||
for (0..100) |_| {
|
||||
const value = rand_impl.random().int(u64);
|
||||
const mask = rand_impl.random().int(u64);
|
||||
|
||||
try std.testing.expectEqual(pext.hw(u64, value, mask), pext.sw(u64, value, mask));
|
||||
}
|
||||
},
|
||||
else => {},
|
||||
else => return error.SkipZigTest,
|
||||
}
|
||||
|
||||
// example values from: https://en.wikipedia.org/w/index.php?title=X86_Bit_manipulation_instruction_set&oldid=1170426748
|
||||
try std.testing.expectEqual(@as(u32, 0x0001_2567), pext.software(u32, 0x12345678, 0xFF00FFF0));
|
||||
try std.testing.expectEqual(@as(u32, 0x0001_2567), pext.sw(u32, 0x12345678, 0xFF00FFF0));
|
||||
}
|
||||
};
|
||||
|
||||
test "doc test" {
|
||||
const value: u8 = 0b10001011;
|
||||
|
||||
try std.testing.expectEqual(true, match("10001011", value));
|
||||
try std.testing.expectEqual(true, match("1000_1011", value));
|
||||
try std.testing.expectEqual(false, match("11111011", value));
|
||||
try std.testing.expectEqual(true, match("1---1011", value));
|
||||
|
||||
|
Reference in New Issue
Block a user