aboutsummaryrefslogtreecommitdiff
path: root/vendor/libghostty-vt/src/crc32c.zig
diff options
context:
space:
mode:
authorCan Celik <ogulcancelik@gmail.com>2026-09-10 21:51:57 +0300
committerGitHub <noreply@github.com>2026-09-10 21:51:57 +0300
commit425c86179791cd32e2d4ce0ae7f269940b4b0663 (patch)
treee52c4383a6d69216cbe2a32638bf90841d7e2189 /vendor/libghostty-vt/src/crc32c.zig
parent90e947a6e26e02f10ec7f1694c4eb5b78a86406b (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.zig424
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),
+ );
+ }
+}