diff options
Diffstat (limited to 'exercises/111_packed.zig')
| -rw-r--r-- | exercises/111_packed.zig | 175 |
1 files changed, 0 insertions, 175 deletions
diff --git a/exercises/111_packed.zig b/exercises/111_packed.zig deleted file mode 100644 index 1058f76..0000000 --- a/exercises/111_packed.zig +++ /dev/null @@ -1,175 +0,0 @@ -// -// We've already learned plenty about bit manipulation using bitwise operations -// in exercices 097 and 098 and in quiz 110. The techniques we already know work -// just fine, but creating masks and shifting individual bits around can become -// quite tedious and unwieldy pretty quickly. -// What if there was a better, a more convenient way to control invidivual bits? -// -// Luckily, Zig has a keyword for exactly this purpose: -// -// packed -// -// It doesn't do anything on its own, to unlock its potential (and to get our -// program to compile) we have to attach it either to a struct or to a union -// declaration: -// -// const Foo = packed struct { ... }; -// const Bar = packed union { ... }; -// -// Now, what does this keyword even do? -// To answer this question we first have to talk about *container layouts*. -// -// Plain structs and unions use the `auto` layout; it gives no guarantees about -// their size or the order of the fields they contain, both are fully up to the -// compiler (though both size and field order *are* guaranteed to be the same -// across any single compilation unit). -// -// Attaching the `packed` keyword to a container makes it use `packed` layout: -// Suddenly, all of its fields are *packed* together tightly without any padding -// in between and their order is guaranteed to be the same as the one specified -// in our source code. For structs, the size of the container is guaranteed to -// be the sum of the (bit-)sizes of all of its fields. For unions, all fields -// have to have the exact same (bit-)size (no padding allowed!); the union itself -// is also guaranteed to be exactly of this size. -// -// If you're familiar with C, you might have already heard of structure packing -// in a different context: arranging fields in a way that minimizes the amount -// of alignment padding between them (or having the compiler do it for you). -// This is *not* what Zig's `packed` keyword is for! -// -// Try to make the comptime assertions below pass: - -const PackedStruct = packed struct { - a: u2, - b: u?, -}; - -comptime { - assert(@bitSizeOf(PackedStruct) == 6); -} - -const PackedUnion = packed union { - a: bool, - b: u?, -}; - -comptime { - assert(@bitSizeOf(PackedUnion) == 1); -} - -// Now, how can we use this new knowledge to manipulate some bits? -// -// As you might have already guessed, `packed` containers are very useful for -// representing bitflags or other tightly packed collections of bit-sized values -// often found in file headers and network protocols. -// -// Let's take a look at a real-life example: -// The LZ4 compression format (†) specifies a frame format to describe compressed -// data. Each LZ4 frame has a descriptor, and each descriptor contains a 'FLG' -// byte that specifies the contents of its frame: - -/// | BitNb | 7-6 | 5 | 4 | 3 | 2 | 1 | 0 | -/// | ------- |-------|-------|----------|------|----------|--------|------| -/// |FieldName|Version|B.Indep|B.Checksum|C.Size|C.Checksum|Reserved|DictID| -/// -const FLG = packed struct(u8) { - dict_id: bool, - reserved: u1 = 0, - content_checksum: bool, - content_size: bool, - block_checksum: bool, - block_indepencence: bool, - version: u2, -}; - -// Wait, what's with the `(u8)` after the `struct` keyword? What do integers have -// to do with all of this? -// Well, this is a good opportunity to come clear about something: -// packed structs and packed unions aren't actually structs or unions at all... -// They are merely integers in disguise! For all intents and purposes, their -// fields are just convenient names for ranges of their underlying bits. To make -// it easier to enforce size requirements for packed containers, Zig allows us -// to specify a *backing integer* for them, just like for enums. -// -// In the case of `FLG`, we want our struct to occupy exactly a single byte, so -// we specify `u8` as the backing integer. It's safe to convert between a packed -// container and its backing integer using the builtin `@bitCast`. -// The LZ4 spec also mandates that reserved bits must always be zero, so it's -// good practice to set `0` as a default value for `reserved`. -// -// The fields of a packed struct start at the least significant bit of its backing -// integer and end at its most significant bit. This is the case no matter what -// endianness our target has. -// -// Try to silence the complaints below: - -const Bits = packed struct(u4) { - a: u1 = 0, - b: u1 = 0, - c: u1 = 0, - d: u1 = 0, -}; - -pub fn main() void { - { - const expected: Bits = @bitCast(@as(u4, 0b1000)); - const my_bits: Bits = .{}; - if (my_bits != expected) complain(my_bits, expected, @src()); - } - - { - const expected: Bits = @bitCast(@as(u4, 0b0001)); - const my_bits: Bits = .{}; - if (my_bits != expected) complain(my_bits, expected, @src()); - } - - { - const expected: Bits = @bitCast(@as(u4, 0b0010)); - const my_bits: Bits = .{}; - if (my_bits != expected) complain(my_bits, expected, @src()); - } - - { - const expected: Bits = @bitCast(@as(u4, 0b0011)); - const my_bits: Bits = .{}; - if (my_bits != expected) complain(my_bits, expected, @src()); - } - - { - const expected: Bits = @bitCast(@as(u4, 0b1101)); - const my_bits: Bits = .{}; - if (my_bits != expected) complain(my_bits, expected, @src()); - } -} - -// As we can see, equality comparisons (`==` and `!=`) work for packed structs. -// They also work for packed unions. However, since packed containers are not -// naturally ordered, we can't use any other comparison operators on them. -// -// It's also possible to use packed containers in `switch` statements, which we -// will cover in the next exercise! -// -// Since packed containers make very strong guarantees about their memory layout, -// only a handful of types are eligible to be part of them. -// The following types are allowed as field types: -// -// - integers -// - floats -// - bool -// - void -// - enums with explicit backing integers -// - packed unions -// - packed structs -// - -const std = @import("std"); -const assert = std.debug.assert; - -fn complain(my_bits: Bits, expected: Bits, src_loc: std.builtin.SourceLocation) void { - std.debug.print( - "That's not quite right! You've got 0b{b:0>4}, but we want 0b{b:0>4} in line {d}.\n", - .{ @as(u4, @bitCast(my_bits)), @as(u4, @bitCast(expected)), src_loc.line }, - ); -} - -// (†) https://github.com/lz4/lz4/blob/5c4c1fb2354133e1f3b087a341576985f8114bd5/doc/lz4_Frame_format.md#frame-descriptor |
