diff options
| author | Can Celik <ogulcancelik@gmail.com> | 2026-09-10 21:51:57 +0300 |
|---|---|---|
| committer | GitHub <noreply@github.com> | 2026-09-10 21:51:57 +0300 |
| commit | 425c86179791cd32e2d4ce0ae7f269940b4b0663 (patch) | |
| tree | e52c4383a6d69216cbe2a32638bf90841d7e2189 /vendor/libghostty-vt/src/crc32c.zig | |
| parent | 90e947a6e26e02f10ec7f1694c4eb5b78a86406b (diff) | |
chore: upgrade libghostty and preserve terminal behavior (#3906)
Diffstat (limited to 'vendor/libghostty-vt/src/crc32c.zig')
| -rw-r--r-- | vendor/libghostty-vt/src/crc32c.zig | 424 |
1 files changed, 424 insertions, 0 deletions
diff --git a/vendor/libghostty-vt/src/crc32c.zig b/vendor/libghostty-vt/src/crc32c.zig new file mode 100644 index 00000000..3d8aaca1 --- /dev/null +++ b/vendor/libghostty-vt/src/crc32c.zig @@ -0,0 +1,424 @@ +//! CRC32C with hardware acceleration. +//! +//! The Zig standard library implementation processes one byte per table +//! lookup (as of Zig 0.16), which is more than an order of magnitude slower +//! than the dedicated CRC32C instructions available on aarch64 (CRC +//! extension) and x86_64 (SSE4.2). This module selects the best backend at +//! compile time. +//! +//! Targets without a dedicated instruction, such as WebAssembly, use a +//! custom implementation that is faster than Zig's stdlib. +//! +//! The resulting value is identical across all backends: this is the +//! iSCSI CRC32C parameter set (reflected, initial and final XOR +//! `0xFFFFFFFF`), matching `std.hash.crc.Crc32Iscsi`. + +const std = @import("std"); +const builtin = @import("builtin"); + +const Backend = enum { + aarch64_crc, + x86_64_sse42, + software, +}; + +const backend: Backend = backend: { + switch (builtin.cpu.arch) { + .aarch64, + .aarch64_be, + => if (std.Target.aarch64.featureSetHas( + builtin.cpu.features, + .crc, + )) break :backend .aarch64_crc, + + // The self-hosted x86_64 backend cannot encode the CRC32 + // instruction forms used below, so that combination falls back to + // the portable implementation. + .x86_64 => if (builtin.zig_backend == .stage2_llvm and + std.Target.x86.featureSetHas( + builtin.cpu.features, + .sse4_2, + )) break :backend .x86_64_sse42, + + else => {}, + } + break :backend .software; +}; + +/// Streaming CRC32C with the same interface shape as `std.hash.crc` types. +pub const Crc32c = struct { + crc: u32, + + pub fn init() Crc32c { + return .{ .crc = 0xFFFF_FFFF }; + } + + pub fn update(self: *Crc32c, bytes: []const u8) void { + self.crc = switch (comptime backend) { + .aarch64_crc, .x86_64_sse42 => updateHardware(self.crc, bytes), + .software => Software.update(self.crc, bytes), + }; + } + + pub fn final(self: Crc32c) u32 { + return self.crc ^ 0xFFFF_FFFF; + } + + pub fn hash(bytes: []const u8) u32 { + var c: Crc32c = .init(); + c.update(bytes); + return c.final(); + } +}; + +/// One update pass using the dedicated CRC32C instructions. Both supported +/// architectures handle unaligned loads efficiently, so the loop reads +/// little-endian words directly from the input. +fn updateHardware(initial: u32, bytes: []const u8) u32 { + var crc = initial; + var remaining = bytes; + + while (remaining.len >= 8) : (remaining = remaining[8..]) { + crc = step(u64, crc, std.mem.readInt( + u64, + remaining[0..8], + .little, + )); + } + if (remaining.len >= 4) { + crc = step(u32, crc, std.mem.readInt( + u32, + remaining[0..4], + .little, + )); + remaining = remaining[4..]; + } + for (remaining) |byte| crc = step(u8, crc, byte); + return crc; +} + +/// One CRC32C instruction folding `value` into the running CRC. +inline fn step(comptime T: type, crc: u32, value: T) u32 { + return switch (comptime backend) { + .aarch64_crc => switch (T) { + u8 => asm ("crc32cb %[out:w], %[crc:w], %[value:w]" + : [out] "=r" (-> u32), + : [crc] "r" (crc), + [value] "r" (value), + ), + u32 => asm ("crc32cw %[out:w], %[crc:w], %[value:w]" + : [out] "=r" (-> u32), + : [crc] "r" (crc), + [value] "r" (value), + ), + u64 => asm ("crc32cx %[out:w], %[crc:w], %[value:x]" + : [out] "=r" (-> u32), + : [crc] "r" (crc), + [value] "r" (value), + ), + else => comptime unreachable, + }, + + .x86_64_sse42 => switch (T) { + u8 => asm ("crc32b %[value], %[out]" + : [out] "=r" (-> u32), + : [value] "r" (value), + [crc_in] "0" (crc), + ), + u32 => asm ("crc32l %[value], %[out]" + : [out] "=r" (-> u32), + : [value] "r" (value), + [crc_in] "0" (crc), + ), + u64 => @truncate(asm ("crc32q %[value], %[out]" + : [out] "=r" (-> u64), + : [value] "r" (value), + [crc_in] "0" (@as(u64, crc)), + )), + else => comptime unreachable, + }, + + .software => comptime unreachable, + }; +} + +/// The portable software backend, used by targets without a dedicated +/// CRC32C instruction. +const Software = struct { + /// The reflected CRC32C (Castagnoli) polynomial. + const reflected_poly: u32 = 0x82F63B78; + + /// Number of slicing tables, which is also the bytes folded per + /// iteration. + const slices = 16; + + /// Inputs below this length use the single-stream pass: the + /// stream-combine matrix work would not pay for itself. + const multi_stream_threshold = 4096; + + /// Slicing tables: `tables[i][b]` is the CRC of byte `b` followed by + /// `i` zero bytes. Table zero is the classic one-byte-per-step table; + /// the higher tables let one iteration fold a whole block with + /// independent lookups instead of a byte-by-byte dependency chain. + const tables: [slices][256]u32 = tables: { + @setEvalBranchQuota(200_000); + var result: [slices][256]u32 = undefined; + for (0..256) |n| { + var crc: u32 = n; + for (0..8) |_| { + crc = (crc >> 1) ^ (reflected_poly * (crc & 1)); + } + result[0][n] = crc; + } + for (1..slices) |i| { + for (0..256) |n| { + const prev = result[i - 1][n]; + result[i][n] = (prev >> 8) ^ result[0][prev & 0xFF]; + } + } + break :tables result; + }; + + fn update(initial: u32, bytes: []const u8) u32 { + if (bytes.len >= multi_stream_threshold) return updateMulti( + initial, + bytes, + ); + + return updateSingle(initial, bytes); + } + + /// One single-stream update pass using slicing. + fn updateSingle(initial: u32, bytes: []const u8) u32 { + const t = &tables; + var crc = initial; + var i: usize = 0; + while (i + slices <= bytes.len) : (i += slices) { + crc = foldChunk(bytes, i, crc); + } + for (bytes[i..]) |byte| { + crc = (crc >> 8) ^ t[0][(crc ^ byte) & 0xFF]; + } + return crc; + } + + /// One update pass as three independent interleaved streams. Faster + /// for large enough inputs. + fn updateMulti(initial: u32, bytes: []const u8) u32 { + // Both leading parts are block multiples so the interleaved loop + // needs no tail handling; the third part absorbs the remainder. + const part = (bytes.len / 3) & ~@as(usize, slices - 1); + const p0 = bytes[0..part]; + const p1 = bytes[part..][0..part]; + const p2 = bytes[2 * part ..]; + + var s0 = initial; + var s1: u32 = 0; + var s2: u32 = 0; + var i: usize = 0; + while (i + slices <= part) : (i += slices) { + s0 = foldChunk(p0, i, s0); + s1 = foldChunk(p1, i, s1); + s2 = foldChunk(p2, i, s2); + } + s2 = updateSingle(s2, p2[part..]); + + const s01 = s1 ^ zeroShift(s0, p1.len); + return s2 ^ zeroShift(s01, p2.len); + } + + /// Fold one aligned block through the per-position slicing tables. + /// The running CRC must already be XORed into the block's first word. + inline fn foldBlock(comptime len: usize, words: *const [len / 4]u32) u32 { + const t = &tables; + var crc: u32 = 0; + inline for (0..len / 4) |w| { + const word = words[w]; + const base = len - 1 - w * 4; + crc ^= t[base][word & 0xFF] ^ + t[base - 1][(word >> 8) & 0xFF] ^ + t[base - 2][(word >> 16) & 0xFF] ^ + t[base - 3][word >> 24]; + } + return crc; + } + + /// Fold the block starting at `offset`, chaining the running CRC state. + inline fn foldChunk(bytes: []const u8, offset: usize, crc: u32) u32 { + var words: [slices / 4]u32 = undefined; + inline for (&words, 0..) |*word, w| { + word.* = std.mem.readInt( + u32, + bytes[offset + w * 4 ..][0..4], + .little, + ); + } + words[0] ^= crc; + return foldBlock(slices, &words); + } + + /// Advance a CRC state as if `len` zero bytes had been processed. + fn zeroShift(state: u32, len: usize) u32 { + var s = state; + var remaining = len; + var k: usize = 0; + while (remaining != 0) : ({ + remaining >>= 1; + k += 1; + }) { + if (remaining & 1 != 0) { + const mat = &zero_shift_matrices[k / 2]; + s = matTimesVec(mat, s); + if (k % 2 != 0) s = matTimesVec(mat, s); + } + } + return s; + } + + /// Multiply the GF(2) matrix by a CRC state column vector. + inline fn matTimesVec(mat: *const [32]u32, vec: u32) u32 { + var sum: u32 = 0; + var v = vec; + var i: usize = 0; + while (v != 0) : ({ + v >>= 1; + i += 1; + }) { + if (v & 1 != 0) sum ^= mat[i]; + } + return sum; + } + + const zero_shift_matrices: [32][32]u32 = matrices: { + @setEvalBranchQuota(500_000); + var matrices: [32][32]u32 = undefined; + var previous: [32]u32 = undefined; + for (0..32) |i| { + const unit: u32 = 1 << i; + previous[i] = (unit >> 8) ^ tables[0][unit & 0xFF]; + } + matrices[0] = previous; + for (1..64) |k| { + var squared: [32]u32 = undefined; + for (0..32) |i| { + squared[i] = matTimesVec(&previous, previous[i]); + } + previous = squared; + if (k % 2 == 0) matrices[k / 2] = squared; + } + break :matrices matrices; + }; +}; + +/// The standard-library implementation of the same parameter set. This is +/// the reference the tests compare against. +const Reference = std.hash.crc.Crc32Iscsi; + +test "software slicing matches the standard library" { + // The selected backend may be hardware, so cover the sliced software + // path directly: every length around the sixteen-byte boundary, several + // alignments, and continuation across arbitrary split points. + var bytes: [512 + 19]u8 = undefined; + var prng = std.Random.DefaultPrng.init(0x511C); + prng.random().bytes(&bytes); + + for (0..64 + 1) |len| { + for (0..4) |offset| { + const input = bytes[offset..][0..len]; + var reference: Reference = .{ .crc = 0xFFFF_FFFF }; + reference.update(input); + try std.testing.expectEqual( + reference.crc, + Software.update(0xFFFF_FFFF, input), + ); + } + } + + const long = bytes[0..512]; + var reference: Reference = .{ .crc = 0xFFFF_FFFF }; + reference.update(long); + for ([_]usize{ 0, 1, 15, 16, 17, 100, 511, 512 }) |split| { + const first = Software.update(0xFFFF_FFFF, long[0..split]); + try std.testing.expectEqual( + reference.crc, + Software.update(first, long[split..]), + ); + } +} + +test "software multi-stream matches the standard library" { + // Lengths around and far above the interleaving threshold, plus odd + // remainders, so all three streams and both combine steps are covered. + var bytes: [96 * 1024]u8 = undefined; + var prng = std.Random.DefaultPrng.init(0x3517_1A3B); + prng.random().bytes(&bytes); + + for ([_]usize{ + Software.multi_stream_threshold - 1, + Software.multi_stream_threshold, + Software.multi_stream_threshold + 1, + Software.multi_stream_threshold + 97, + 12 * 1024, + 64 * 1024 + 31, + bytes.len, + }) |len| { + const input = bytes[0..len]; + var reference: Reference = .{ .crc = 0xFFFF_FFFF }; + reference.update(input); + try std.testing.expectEqual( + reference.crc, + Software.update(0xFFFF_FFFF, input), + ); + + // Continuation across a split inside the multi-stream range. + const first = Software.update(0xFFFF_FFFF, input[0 .. len / 2]); + try std.testing.expectEqual( + reference.crc, + Software.update(first, input[len / 2 ..]), + ); + } +} + +test "matches the check value" { + // The catalog check value for CRC-32/ISCSI. + try std.testing.expectEqual( + @as(u32, 0xE3069283), + Crc32c.hash("123456789"), + ); +} + +test "matches the standard library at every length and split" { + var bytes: [259]u8 = undefined; + var prng = std.Random.DefaultPrng.init(0xC5C32C); + prng.random().bytes(&bytes); + + for (0..bytes.len + 1) |len| { + const input = bytes[0..len]; + try std.testing.expectEqual( + Reference.hash(input), + Crc32c.hash(input), + ); + + // Streaming across arbitrary split points must not change the + // result: word batching may not leak state between updates. + var split: Crc32c = .init(); + split.update(input[0 .. len / 3]); + split.update(input[len / 3 .. len - len / 3]); + split.update(input[len - len / 3 ..]); + try std.testing.expectEqual(Reference.hash(input), split.final()); + } +} + +test "matches the standard library at every alignment" { + var bytes: [64 + 16]u8 = undefined; + var prng = std.Random.DefaultPrng.init(0xA11C); + prng.random().bytes(&bytes); + + for (0..16) |offset| { + const input = bytes[offset..][0..64]; + try std.testing.expectEqual( + Reference.hash(input), + Crc32c.hash(input), + ); + } +} |
