From eace06bbd633767d2850a09531a701a4c886c0e9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Balv=C3=ADn?= Date: Fri, 7 Aug 2026 21:06:14 +0200 Subject: [PATCH] fix(verify): remove all go-libraries kernel dependencies from tests --- CHANGELOG.md | 2 +- verify/abi_test.go | 55 --- verify/avx512_test.go | 151 -------- verify/coverage_test.go | 65 ---- verify/differential_test.go | 298 ---------------- verify/flac_test.go | 692 ------------------------------------ verify/lz4_test.go | 165 --------- 7 files changed, 1 insertion(+), 1427 deletions(-) delete mode 100644 verify/avx512_test.go delete mode 100644 verify/differential_test.go delete mode 100644 verify/flac_test.go delete mode 100644 verify/lz4_test.go diff --git a/CHANGELOG.md b/CHANGELOG.md index 8808504..7f2c64f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -9,7 +9,7 @@ and this project adheres to [Conventional Commits](https://www.conventionalcommi Unreleased changes on the `development` branch. -## [0.29.0] — 2026-08-05 +## [0.29.0] — 2026-08-07 RISC-V GOOBJ emission, YMM vector register display, named buffer allocation in the debugger, two new CLI commands (`diff`, `profile`), go-to-definition in diff --git a/verify/abi_test.go b/verify/abi_test.go index 0601c7e..e0555dc 100644 --- a/verify/abi_test.go +++ b/verify/abi_test.go @@ -4,9 +4,7 @@ package verify import ( - "runtime" "testing" - "unsafe" ) func loadABIKernel(t *testing.T) *Kernel { @@ -80,59 +78,6 @@ func TestABIR14Clobbered(t *testing.T) { } } -// TestABILZ4Kernels verifies that the production go-lz4 kernels are ABI-clean: -// they preserve BP and R14 and do not write into the red zone. -func TestABILZ4Kernels(t *testing.T) { - k := loadLZ4Kernel(t) - - // wideCopyAVX2 with a real copy. - src := make([]byte, 128) - for i := range src { - src[i] = byte(i) - } - dst := make([]byte, 128) - - args := make([]byte, 48) - PutPtr(args, 0, unsafe.Pointer(&dst[0])) - PutUint64(args, 8, 128) - PutUint64(args, 16, 128) - PutPtr(args, 24, unsafe.Pointer(&src[0])) - PutUint64(args, 32, 128) - PutUint64(args, 40, 128) - - _, report, err := k.CallFuncChecked("wideCopyAVX2", args) - runtime.KeepAlive(dst) - runtime.KeepAlive(src) - if err != nil { - t.Fatalf("CallFuncChecked(wideCopyAVX2): %v", err) - } - if !report.OK() { - t.Errorf("wideCopyAVX2: %s", report) - } - - // decodeBlockAVX2 with a simple block. - decSrc := []byte{0x50, 'H', 'e', 'l', 'l', 'o'} - decDst := make([]byte, 64) - - decArgs := make([]byte, 64) - PutPtr(decArgs, 0, unsafe.Pointer(&decSrc[0])) - PutUint64(decArgs, 8, uint64(len(decSrc))) - PutUint64(decArgs, 16, uint64(cap(decSrc))) - PutPtr(decArgs, 24, unsafe.Pointer(&decDst[0])) - PutUint64(decArgs, 32, uint64(len(decDst))) - PutUint64(decArgs, 40, uint64(cap(decDst))) - - _, report, err = k.CallFuncChecked("decodeBlockAVX2", decArgs) - runtime.KeepAlive(decSrc) - runtime.KeepAlive(decDst) - if err != nil { - t.Fatalf("CallFuncChecked(decodeBlockAVX2): %v", err) - } - if !report.OK() { - t.Errorf("decodeBlockAVX2: %s", report) - } -} - func TestCallFuncCheckedErrors(t *testing.T) { k := loadABIKernel(t) diff --git a/verify/avx512_test.go b/verify/avx512_test.go deleted file mode 100644 index 6a80f33..0000000 --- a/verify/avx512_test.go +++ /dev/null @@ -1,151 +0,0 @@ -// Copyright (c) 2026 Petr Balvín (https://petrbalvin.org) -// SPDX-License-Identifier: BSD-3-Clause - -package verify - -import ( - "bytes" - "math/rand" - "os" - "runtime" - "testing" - "unsafe" -) - -const lz4AVX512Path = "../../go-libraries/go-lz4/avx512_amd64.s" - -func loadLZ4AVX512Kernel(t *testing.T) *Kernel { - t.Helper() - if _, err := os.Stat(lz4AVX512Path); err != nil { - t.Skipf("sibling kernel not available: %v", err) - } - k, err := Load(lz4AVX512Path) - if err != nil { - t.Fatalf("Load(%s): %v", lz4AVX512Path, err) - } - t.Cleanup(k.Close) - return k -} - -func TestAVX512DecodeKnownAnswers(t *testing.T) { - k := loadLZ4AVX512Kernel(t) - - tests := []struct { - name string - src []byte - wantN int - wantCode int - }{ - {"literals_only", []byte{0x50, 'H', 'e', 'l', 'l', 'o'}, 5, 0}, - {"literals_and_match", []byte{0x54, 'A', 'A', 'A', 'A', 'A', 0x05, 0x00, 0x30, 'B', 'B', 'B'}, 16, 0}, - {"overlapping", []byte{0x14, 'X', 0x01, 0x00, 0x10, 'Y'}, 10, 0}, - {"malformed", []byte{0x50, 'H', 'e'}, 0, 1}, - {"zero_offset", []byte{0x14, 'X', 0x00, 0x00}, 0, 2}, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - dst := make([]byte, 64) - args := make([]byte, 64) - PutPtr(args, 0, unsafe.Pointer(&tt.src[0])) - PutUint64(args, 8, uint64(len(tt.src))) - PutUint64(args, 16, uint64(cap(tt.src))) - PutPtr(args, 24, unsafe.Pointer(&dst[0])) - PutUint64(args, 32, uint64(len(dst))) - PutUint64(args, 40, uint64(cap(dst))) - - out, err := k.CallFunc("decodeBlockAVX512", args) - runtime.KeepAlive(tt.src) - runtime.KeepAlive(dst) - if err != nil { - t.Fatalf("CallFunc: %v", err) - } - n := int(GetUint64(out, 48)) - code := int(GetUint64(out, 56)) - if n != tt.wantN || code != tt.wantCode { - t.Errorf("got (n=%d, code=%d), want (n=%d, code=%d)", n, code, tt.wantN, tt.wantCode) - } - }) - } -} - -func TestAVX512DifferentialFuzz(t *testing.T) { - k := loadLZ4AVX512Kernel(t) - rng := rand.New(rand.NewSource(77)) - - for i := 0; i < 3000; i++ { - wantSize := 1 + rng.Intn(4096) - src := genLZ4Block(rng, wantSize) - dstSize := wantSize + 64 - - goDst := make([]byte, dstSize) - goN, goCode := decodeBlockGo(src, goDst) - - jitDst := make([]byte, dstSize) - args := make([]byte, 64) - if len(src) > 0 { - PutPtr(args, 0, unsafe.Pointer(&src[0])) - } - PutUint64(args, 8, uint64(len(src))) - PutUint64(args, 16, uint64(cap(src))) - if dstSize > 0 { - PutPtr(args, 24, unsafe.Pointer(&jitDst[0])) - } - PutUint64(args, 32, uint64(dstSize)) - PutUint64(args, 40, uint64(cap(jitDst))) - - out, err := k.CallFunc("decodeBlockAVX512", args) - runtime.KeepAlive(src) - runtime.KeepAlive(jitDst) - if err != nil { - t.Fatalf("iter %d: %v", i, err) - } - jitN := int(GetUint64(out, 48)) - jitCode := int(GetUint64(out, 56)) - - if jitCode != goCode { - t.Fatalf("iter %d: code mismatch: JIT=%d Go=%d", i, jitCode, goCode) - } - if jitCode != 0 { - continue - } - if jitN != goN { - t.Fatalf("iter %d: n mismatch: JIT=%d Go=%d", i, jitN, goN) - } - if !bytes.Equal(jitDst[:jitN], goDst[:goN]) { - t.Fatalf("iter %d: output mismatch (n=%d)", i, jitN) - } - } -} - -func TestAVX512WideCopy(t *testing.T) { - k := loadLZ4AVX512Kernel(t) - - sizes := []int{0, 1, 31, 32, 63, 64, 65, 127, 128, 256, 1024} - for _, n := range sizes { - src := make([]byte, n) - for i := range src { - src[i] = byte(i*11 + 3) - } - dst := make([]byte, n) - - args := make([]byte, 48) - if n > 0 { - PutPtr(args, 0, unsafe.Pointer(&dst[0])) - PutPtr(args, 24, unsafe.Pointer(&src[0])) - } - PutUint64(args, 8, uint64(n)) - PutUint64(args, 16, uint64(n)) - PutUint64(args, 32, uint64(n)) - PutUint64(args, 40, uint64(n)) - - _, err := k.CallFunc("wideCopyAVX512", args) - runtime.KeepAlive(dst) - runtime.KeepAlive(src) - if err != nil { - t.Fatalf("wideCopyAVX512(n=%d): %v", n, err) - } - if !bytes.Equal(dst, src) { - t.Errorf("wideCopyAVX512(n=%d): mismatch", n) - } - } -} diff --git a/verify/coverage_test.go b/verify/coverage_test.go index ffd58e5..0218ee6 100644 --- a/verify/coverage_test.go +++ b/verify/coverage_test.go @@ -4,9 +4,7 @@ package verify import ( - "runtime" "testing" - "unsafe" ) func TestBlocks(t *testing.T) { @@ -25,66 +23,3 @@ func TestBlocks(t *testing.T) { } t.Logf("sum blocks: %v", blocks) } - -func TestBlockCount(t *testing.T) { - k := loadLZ4Kernel(t) - - n, err := k.BlockCount("decodeBlockAVX2") - if err != nil { - t.Fatalf("BlockCount: %v", err) - } - // The decoder has many labels (dec_loop, dec_malformed, etc.). - if n < 10 { - t.Errorf("decodeBlockAVX2: expected at least 10 blocks, got %d", n) - } - t.Logf("decodeBlockAVX2: %d basic blocks", n) -} - -func TestProfilePaths(t *testing.T) { - k := loadLZ4Kernel(t) - - // Build a corpus of varied LZ4 blocks. - var argSets [][]byte - var liveDsts [][]byte // keep dst buffers alive across ProfilePaths - blocks := []struct { - src []byte - dstSize int - }{ - {[]byte{0x00}, 16}, // empty - {[]byte{0x50, 'H', 'e', 'l', 'l', 'o'}, 16}, // literals only - {[]byte{0x54, 'A', 'A', 'A', 'A', 'A', 5, 0, 0x30, 'B', 'B', 'B'}, 32}, // match - {[]byte{0x14, 'X', 1, 0, 0x10, 'Y'}, 16}, // overlapping - {[]byte{0x50, 'H'}, 16}, // malformed - {[]byte{0x14, 'X', 0, 0}, 16}, // zero offset - } - for _, b := range blocks { - args := make([]byte, 64) - if len(b.src) > 0 { - PutPtr(args, 0, unsafe.Pointer(&b.src[0])) - } - PutUint64(args, 8, uint64(len(b.src))) - PutUint64(args, 16, uint64(cap(b.src))) - dst := make([]byte, b.dstSize) - liveDsts = append(liveDsts, dst) - if len(dst) > 0 { - PutPtr(args, 24, unsafe.Pointer(&dst[0])) - } - PutUint64(args, 32, uint64(len(dst))) - PutUint64(args, 40, uint64(cap(dst))) - argSets = append(argSets, args) - } - - // Result offsets: n+48 and code+56. - paths, err := k.ProfilePaths("decodeBlockAVX2", argSets, []int{48, 56}) - runtime.KeepAlive(blocks) - runtime.KeepAlive(liveDsts) - if err != nil { - t.Fatalf("ProfilePaths: %v", err) - } - - // We expect at least 3 distinct paths: success (various n), malformed, zero offset. - if len(paths) < 3 { - t.Errorf("expected at least 3 distinct paths, got %d", len(paths)) - } - t.Logf("decodeBlockAVX2: %d distinct output paths from %d inputs", len(paths), len(argSets)) -} diff --git a/verify/differential_test.go b/verify/differential_test.go deleted file mode 100644 index 775f267..0000000 --- a/verify/differential_test.go +++ /dev/null @@ -1,298 +0,0 @@ -// Copyright (c) 2026 Petr Balvín (https://petrbalvin.org) -// SPDX-License-Identifier: BSD-3-Clause - -package verify - -import ( - "bytes" - "math/rand" - "runtime" - "testing" - "unsafe" -) - -// decodeBlockGo is a minimal portable LZ4 block decoder used as the -// differential-testing oracle. It mirrors the contract of -// go-lz4's decodeBlockGo: (bytesWritten, code) where code is -// 0 = ok, 1 = malformed, 2 = zero offset. -func decodeBlockGo(src, dst []byte) (int, int) { - if len(src) == 0 { - return 0, 1 - } - si, di := 0, 0 - for { - if si >= len(src) { - return 0, 1 // truncated: no token - } - token := int(src[si]) - si++ - - // Literals. - lLen := token >> 4 - if lLen == 15 { - for { - if si >= len(src) { - return 0, 1 - } - b := int(src[si]) - si++ - lLen += b - if b != 255 { - break - } - } - } - if si+lLen > len(src) { - return 0, 1 // truncated literals - } - if di+lLen > len(dst) { - return 0, 1 // destination overflow - } - copy(dst[di:di+lLen], src[si:si+lLen]) - di += lLen - si += lLen - - // End of block. - if si >= len(src) { - return di, 0 - } - - // Match offset. - if si+2 > len(src) { - return 0, 1 - } - offset := int(src[si]) | int(src[si+1])<<8 - si += 2 - if offset == 0 { - return 0, 2 - } - - // Match length. - mLen := token & 15 - if mLen == 15 { - for { - if si >= len(src) { - return 0, 1 - } - b := int(src[si]) - si++ - mLen += b - if b != 255 { - break - } - } - } - mLen += 4 - - // Copy match (overlapping-safe). - if di-offset < 0 { - return 0, 1 // offset reaches before dst start - } - if di+mLen > len(dst) { - return 0, 1 // destination overflow - } - for i := 0; i < mLen; i++ { - dst[di+i] = dst[di-offset+i] - } - di += mLen - } -} - -// genLZ4Block generates a random valid LZ4 block that decompresses into -// approximately wantSize bytes. The block is always well-formed (ends with -// a literals-only sequence). -func genLZ4Block(rng *rand.Rand, wantSize int) []byte { - var block []byte - produced := 0 - for produced < wantSize { - remaining := wantSize - produced - - // Decide: emit a literals+match sequence or the final literals. - if remaining <= 8 || rng.Intn(4) == 0 { - // Final literals-only sequence. - lLen := remaining - if lLen > 60 { - lLen = 1 + rng.Intn(60) - } - block = appendToken(block, lLen, 0) - for i := 0; i < lLen; i++ { - block = append(block, byte(rng.Intn(256))) - } - produced += lLen - break - } - - // Literals + match. - lLen := rng.Intn(min(16, remaining)) - if produced+lLen == 0 { - lLen = 1 // must have at least 1 literal before the first match - } - mLenRaw := rng.Intn(12) // match length = mLenRaw + 4 - mLen := mLenRaw + 4 - if produced+mLen > remaining { - mLen = remaining - produced - if mLen < 4 { - // Not enough room for a match; emit final literals. - lLen = remaining - block = appendToken(block, lLen, 0) - for i := 0; i < lLen; i++ { - block = append(block, byte(rng.Intn(256))) - } - break - } - mLenRaw = mLen - 4 - } - - block = appendToken(block, lLen, mLenRaw) - for i := 0; i < lLen; i++ { - block = append(block, byte(rng.Intn(256))) - } - produced += lLen - - // Offset: must be <= produced (can't reference before start). - maxOff := produced - if maxOff > 65535 { - maxOff = 65535 - } - offset := 1 + rng.Intn(maxOff) - block = append(block, byte(offset), byte(offset>>8)) - produced += mLen - } - return block -} - -// appendToken appends a token (and extension bytes if needed) for the given -// literal and match lengths. -func appendToken(block []byte, lLen, mLenRaw int) []byte { - lit4 := lLen - if lit4 > 15 { - lit4 = 15 - } - ml4 := mLenRaw - if ml4 > 15 { - ml4 = 15 - } - block = append(block, byte(lit4<<4|ml4)) - // Literal extension bytes. - rem := lLen - 15 - for rem >= 255 { - block = append(block, 255) - rem -= 255 - } - if lLen >= 15 { - block = append(block, byte(rem)) - } - // Match extension bytes. - rem = mLenRaw - 15 - for rem >= 255 { - block = append(block, 255) - rem -= 255 - } - if mLenRaw >= 15 { - block = append(block, byte(rem)) - } - return block -} - -func min(a, b int) int { - if a < b { - return a - } - return b -} - -// TestDifferentialLZ4Fuzz drives the JIT-assembled decodeBlockAVX2 with -// random valid LZ4 blocks and compares the output bit-for-bit against the -// portable Go reference. -func TestDifferentialLZ4Fuzz(t *testing.T) { - k := loadLZ4Kernel(t) - - const iterations = 5000 - rng := rand.New(rand.NewSource(42)) - - for i := 0; i < iterations; i++ { - wantSize := 1 + rng.Intn(4096) - src := genLZ4Block(rng, wantSize) - dstSize := wantSize + 64 // generous destination - - // Go reference. - goDst := make([]byte, dstSize) - goN, goCode := decodeBlockGo(src, goDst) - - // JIT kernel. - jitDst := make([]byte, dstSize) - jitN, jitCode := callDecodeBlockAVX2(t, k, src, jitDst) - - if jitCode != goCode { - t.Fatalf("iter %d: code mismatch: JIT=%d, Go=%d (src len=%d)", - i, jitCode, goCode, len(src)) - } - if jitCode != 0 { - continue // both agree it's malformed/zero-offset - } - if jitN != goN { - t.Fatalf("iter %d: n mismatch: JIT=%d, Go=%d (src len=%d)", - i, jitN, goN, len(src)) - } - if !bytes.Equal(jitDst[:jitN], goDst[:goN]) { - t.Fatalf("iter %d: output mismatch (n=%d, src len=%d)", i, jitN, len(src)) - } - } -} - -// TestDifferentialLZ4Hostile drives the kernel with random garbage to check -// that error codes agree with the Go reference (no crashes, same classification). -func TestDifferentialLZ4Hostile(t *testing.T) { - k := loadLZ4Kernel(t) - - const iterations = 2000 - rng := rand.New(rand.NewSource(99)) - - for i := 0; i < iterations; i++ { - srcLen := rng.Intn(128) - src := make([]byte, srcLen) - rng.Read(src) - dstSize := rng.Intn(512) - dst := make([]byte, dstSize) - - // Go reference. - goDst := make([]byte, dstSize) - copy(goDst, dst) - _, goCode := decodeBlockGo(src, goDst) - - // JIT kernel. - jitDst := make([]byte, dstSize) - copy(jitDst, dst) - _, jitCode := callDecodeBlockAVX2(t, k, src, jitDst) - - if jitCode != goCode { - t.Fatalf("iter %d: hostile code mismatch: JIT=%d, Go=%d (srcLen=%d, dstSize=%d)", - i, jitCode, goCode, srcLen, dstSize) - } - } -} - -// callDecodeBlockAVX2Raw is like callDecodeBlockAVX2 but accepts explicit -// dst size (for hostile tests where dst may be smaller than the output). -func callDecodeBlockAVX2Raw(t *testing.T, k *Kernel, src, dst []byte) (int, int) { - t.Helper() - args := make([]byte, 64) - if len(src) > 0 { - PutPtr(args, 0, unsafe.Pointer(&src[0])) - } - PutUint64(args, 8, uint64(len(src))) - PutUint64(args, 16, uint64(cap(src))) - if len(dst) > 0 { - PutPtr(args, 24, unsafe.Pointer(&dst[0])) - } - PutUint64(args, 32, uint64(len(dst))) - PutUint64(args, 40, uint64(cap(dst))) - - out, err := k.CallFunc("decodeBlockAVX2", args) - runtime.KeepAlive(src) - runtime.KeepAlive(dst) - if err != nil { - t.Fatalf("CallFunc(decodeBlockAVX2): %v", err) - } - return int(GetUint64(out, 48)), int(GetUint64(out, 56)) -} diff --git a/verify/flac_test.go b/verify/flac_test.go deleted file mode 100644 index 32cc06b..0000000 --- a/verify/flac_test.go +++ /dev/null @@ -1,692 +0,0 @@ -// Copyright (c) 2026 Petr Balvín (https://petrbalvin.org) -// SPDX-License-Identifier: BSD-3-Clause - -package verify - -import ( - "bytes" - "math/rand" - "os" - "runtime" - "testing" - "unsafe" -) - -const flacKernelPath = "../../go-libraries/go-flac/avx2_amd64.s" - -func loadFLACKernel(t *testing.T) *Kernel { - t.Helper() - if _, err := os.Stat(flacKernelPath); err != nil { - t.Skipf("sibling kernel not available: %v", err) - } - k, err := Load(flacKernelPath) - if err != nil { - t.Fatalf("Load(%s): %v", flacKernelPath, err) - } - t.Cleanup(k.Close) - return k -} - -// --- Portable Go references (from go-flac/simd.go) --- - -func decodeMono16Go(src []byte, dst []int32) { - for i := 0; i < len(dst); i++ { - dst[i] = int32(int16(uint16(src[2*i]) | uint16(src[2*i+1])<<8)) - } -} - -func pack16Go(dst []byte, src []int32) { - for i, v := range src { - dst[2*i] = byte(v) - dst[2*i+1] = byte(v >> 8) - } -} - -func decorrelateLeftSideGo(left, right, out []int32) { - for i := range left { - l := left[i] - out[2*i] = l - out[2*i+1] = l - right[i] - } -} - -func decorrelateSideRightGo(left, right, out []int32) { - for i := range left { - side := left[i] - rch := right[i] - out[2*i] = rch + side - out[2*i+1] = rch - } -} - -func decorrelateMidSideGo(left, right, out []int32) { - for i := range left { - mid := left[i] - side := right[i] - mid2 := mid<<1 | (side & 1) - out[2*i] = (mid2 + side) >> 1 - out[2*i+1] = (mid2 - side) >> 1 - } -} - -func decorrelateInterleaveGo(left, right, out []int32) { - for i := range left { - out[2*i] = left[i] - out[2*i+1] = right[i] - } -} - -func analyzeO1RangeGo(swin []int32, dstP []uint32, hist *[32]uint16) (partSum uint64, overflow bool) { - swin = swin[:len(dstP)+1] - for j := 0; j+1 < len(swin); j++ { - r := swin[j+1] - swin[j] - if r == -2147483648 { // math.MinInt32 - overflow = true - } - f := uint32(r<<1) ^ uint32(r>>31) - dstP[j] = f - partSum += uint64(f) - bl := 0 - for v := f; v > 0; v >>= 1 { - bl++ - } - if bl > 31 { - bl = 31 - } - hist[bl]++ - } - return -} - -func analyzeO2RangeGo(swin []int32, dstP []uint32, hist *[32]uint16) (partSum uint64, overflow bool) { - swin = swin[:len(dstP)+2] - for j := 0; j+2 < len(swin); j++ { - r := swin[j+2] - 2*swin[j+1] + swin[j] - if r == -2147483648 { - overflow = true - } - f := uint32(r<<1) ^ uint32(r>>31) - dstP[j] = f - partSum += uint64(f) - bl := 0 - for v := f; v > 0; v >>= 1 { - bl++ - } - if bl > 31 { - bl = 31 - } - hist[bl]++ - } - return -} - -func analyzeResRangeGo(swin []int32, dstP []uint32, hist *[32]uint16) (partSum uint64, overflow bool) { - for j := 0; j < len(swin); j++ { - r := swin[j] - if r == -2147483648 { - overflow = true - } - f := uint32(r<<1) ^ uint32(r>>31) - dstP[j] = f - partSum += uint64(f) - bl := 0 - for v := f; v > 0; v >>= 1 { - bl++ - } - if bl > 31 { - bl = 31 - } - hist[bl]++ - } - return -} - -func decodeMono24Go(src []byte, dst []int32) { - for i := 0; i < len(dst); i++ { - off := 3 * i - u := uint32(src[off]) | uint32(src[off+1])<<8 | uint32(src[off+2])<<16 - dst[i] = int32(u<<8) >> 8 - } -} - -func decodeStereo16Go(src []byte, left, right []int32) { - for i := 0; i < len(left); i++ { - left[i] = int32(int16(uint16(src[4*i]) | uint16(src[4*i+1])<<8)) - right[i] = int32(int16(uint16(src[4*i+2]) | uint16(src[4*i+3])<<8)) - } -} - -// --- Differential tests --- - -// Buffers whose addresses are passed to JIT code via unsafe.Pointer are -// package-level globals: their addresses are stable and the GC never moves -// or collects them, unlike per-iteration make() buffers (which caused flaky -// stale reads, especially under -race). Sizes cover the maximum n from the -// rng.Intn(N) call in each test, rounded up. -var ( - mono16Src [512]byte // TestFLACDecodeMono16: max n = 255 → 2*n = 510 - mono16JitDst [256]int32 // max n = 255 - - pack16Src [256]int32 // TestFLACPack16: max n = 255 - pack16JitDst [512]byte // max 2*n = 510 - - decorLeft [128]int32 // TestFLACDecorrelate: max n = 127 - decorRight [128]int32 // max n = 127 - decorJitOut [256]int32 // max 2*n = 254 - - mono24Src [768]byte // TestFLACDecodeMono24: max n = 255 → 3*n = 765 - mono24JitDst [256]int32 // max n = 255 - - stereo16Src [1024]byte // TestFLACDecodeStereo16: max n = 255 → 4*n = 1020 - stereo16JitLeft [256]int32 // max n = 255 - stereo16JitRight [256]int32 // max n = 255 - - fastStereoLeft [256]int32 // TestFLACFastStereoSums: max n = 255 - fastStereoRight [256]int32 // max n = 255 -) - -func TestFLACDecodeMono16(t *testing.T) { - k := loadFLACKernel(t) - rng := rand.New(rand.NewSource(7)) - - src := mono16Src[:0] - jitDst := mono16JitDst[:0] - for iter := 0; iter < 500; iter++ { - n := rng.Intn(256) - src = src[:2*n] - rng.Read(src) - - goDst := make([]int32, n) - decodeMono16Go(src, goDst) - - jitDst = jitDst[:n] - for i := range jitDst { - jitDst[i] = 0 - } - args := make([]byte, 48) - if len(src) > 0 { - PutPtr(args, 0, unsafe.Pointer(&src[0])) - } - PutUint64(args, 8, uint64(len(src))) - PutUint64(args, 16, uint64(cap(src))) - if n > 0 { - PutPtr(args, 24, unsafe.Pointer(&jitDst[0])) - } - PutUint64(args, 32, uint64(n)) - PutUint64(args, 40, uint64(cap(jitDst))) - - _, err := k.CallFunc("decodeMono16AVX2", args) - runtime.KeepAlive(src) - runtime.KeepAlive(jitDst) - if err != nil { - t.Fatalf("iter %d: %v", iter, err) - } - for i := range goDst { - if jitDst[i] != goDst[i] { - t.Fatalf("iter %d: mismatch at [%d]: JIT=%d Go=%d", iter, i, jitDst[i], goDst[i]) - } - } - } -} - -func TestFLACPack16(t *testing.T) { - k := loadFLACKernel(t) - rng := rand.New(rand.NewSource(13)) - - src := pack16Src[:0] - jitDst := pack16JitDst[:0] - for iter := 0; iter < 500; iter++ { - n := rng.Intn(256) - src = src[:n] - for i := range src { - src[i] = int32(rng.Intn(65536) - 32768) - } - - goDst := make([]byte, 2*n) - pack16Go(goDst, src) - - jitDst = jitDst[:2*n] - for i := range jitDst { - jitDst[i] = 0 - } - args := make([]byte, 48) - if len(jitDst) > 0 { - PutPtr(args, 0, unsafe.Pointer(&jitDst[0])) - } - PutUint64(args, 8, uint64(len(jitDst))) - PutUint64(args, 16, uint64(cap(jitDst))) - if n > 0 { - PutPtr(args, 24, unsafe.Pointer(&src[0])) - } - PutUint64(args, 32, uint64(n)) - PutUint64(args, 40, uint64(cap(src))) - - _, err := k.CallFunc("pack16AVX2", args) - runtime.KeepAlive(jitDst) - runtime.KeepAlive(src) - if err != nil { - t.Fatalf("iter %d: %v", iter, err) - } - if !bytes.Equal(jitDst, goDst) { - t.Fatalf("iter %d: output mismatch (n=%d)", iter, n) - } - } -} - -func TestFLACDecorrelate(t *testing.T) { - k := loadFLACKernel(t) - rng := rand.New(rand.NewSource(21)) - - kernels := []struct { - name string - ref func(left, right, out []int32) - }{ - {"decorrelateLeftSideAVX2", decorrelateLeftSideGo}, - {"decorrelateSideRightAVX2", decorrelateSideRightGo}, - {"decorrelateMidSideAVX2", decorrelateMidSideGo}, - {"decorrelateInterleaveAVX2", decorrelateInterleaveGo}, - } - - for _, kk := range kernels { - t.Run(kk.name, func(t *testing.T) { - left := decorLeft[:0] - right := decorRight[:0] - jitOut := decorJitOut[:0] - for iter := 0; iter < 200; iter++ { - n := rng.Intn(128) - left = left[:n] - right = right[:n] - for i := range left { - left[i] = int32(rng.Intn(1<<24) - 1<<23) - right[i] = int32(rng.Intn(1<<24) - 1<<23) - } - - goOut := make([]int32, 2*n) - kk.ref(left, right, goOut) - - jitOut = jitOut[:2*n] - for i := range jitOut { - jitOut[i] = 0 - } - args := make([]byte, 72) - if n > 0 { - PutPtr(args, 0, unsafe.Pointer(&left[0])) - PutPtr(args, 24, unsafe.Pointer(&right[0])) - PutPtr(args, 48, unsafe.Pointer(&jitOut[0])) - } - PutUint64(args, 8, uint64(n)) - PutUint64(args, 16, uint64(cap(left))) - PutUint64(args, 32, uint64(n)) - PutUint64(args, 40, uint64(cap(right))) - PutUint64(args, 56, uint64(2*n)) - PutUint64(args, 64, uint64(cap(jitOut))) - - _, err := k.CallFunc(kk.name, args) - runtime.KeepAlive(left) - runtime.KeepAlive(right) - runtime.KeepAlive(jitOut) - if err != nil { - t.Fatalf("iter %d: %v", iter, err) - } - for i := range goOut { - if jitOut[i] != goOut[i] { - t.Fatalf("iter %d: mismatch at [%d]: JIT=%d Go=%d", iter, i, jitOut[i], goOut[i]) - } - } - } - }) - } -} - -func TestFLACAnalyzeO1Range(t *testing.T) { - k := loadFLACKernel(t) - rng := rand.New(rand.NewSource(33)) - - swin := analyzeSwin[:0] - jitDstP := analyzeJitDstP[:0] - for iter := 0; iter < 300; iter++ { - n := 1 + rng.Intn(128) // partition size - swin = swin[:n+1] - for i := range swin { - swin[i] = int32(rng.Intn(1<<20) - 1<<19) - } - - goDstP := make([]uint32, n) - var goHist [32]uint16 - goSum, goOvf := analyzeO1RangeGo(swin, goDstP, &goHist) - - jitDstP = jitDstP[:n] - for i := range jitDstP { - jitDstP[i] = 0 - } - jitHist := &analyzeJitHist - *jitHist = [32]uint16{} // reset for this iteration - args := make([]byte, 72) // 65 rounded up - PutPtr(args, 0, unsafe.Pointer(&swin[0])) - PutUint64(args, 8, uint64(len(swin))) - PutUint64(args, 16, uint64(cap(swin))) - PutPtr(args, 24, unsafe.Pointer(&jitDstP[0])) - PutUint64(args, 32, uint64(n)) - PutUint64(args, 40, uint64(cap(jitDstP))) - PutPtr(args, 48, unsafe.Pointer(&jitHist[0])) - - out, err := k.CallFunc("analyzeO1RangeAVX2", args) - runtime.KeepAlive(swin) - runtime.KeepAlive(jitDstP) - runtime.KeepAlive(jitHist) - if err != nil { - t.Fatalf("iter %d: %v", iter, err) - } - jitSum := GetUint64(out, 56) - jitOvf := out[64] != 0 - - if jitSum != goSum { - t.Fatalf("iter %d: partSum mismatch: JIT=%d Go=%d", iter, jitSum, goSum) - } - if jitOvf != goOvf { - t.Fatalf("iter %d: overflow mismatch: JIT=%v Go=%v", iter, jitOvf, goOvf) - } - for i := range goDstP { - if jitDstP[i] != goDstP[i] { - t.Fatalf("iter %d: dstP[%d] mismatch: JIT=%d Go=%d", iter, i, jitDstP[i], goDstP[i]) - } - } - if *jitHist != goHist { - t.Fatalf("iter %d: hist mismatch: JIT=%v Go=%v", iter, *jitHist, goHist) - } - } -} - -func TestFLACFastStereoSums(t *testing.T) { - k := loadFLACKernel(t) - rng := rand.New(rand.NewSource(44)) - - left := fastStereoLeft[:0] - right := fastStereoRight[:0] - for iter := 0; iter < 300; iter++ { - n := 1 + rng.Intn(256) - left = left[:n] - right = right[:n] - for i := range left { - left[i] = int32(rng.Intn(1<<24) - 1<<23) - right[i] = int32(rng.Intn(1<<24) - 1<<23) - } - - // Go reference: compute the four sums. - var goSums [4]uint64 - for i := 0; i < n; i++ { - l := left[i] - r := right[i] - side := l - r - mid := (l + r) >> 1 - goSums[0] += foldAbs(l) + foldAbs(r) - goSums[1] += foldAbs(l) + foldAbs(side) - goSums[2] += foldAbs(side) + foldAbs(r) - goSums[3] += foldAbs(mid) + foldAbs(side) - } - - jitSums := &analyzeJitSums - *jitSums = [4]uint64{} // reset for this iteration - args := make([]byte, 56) - PutPtr(args, 0, unsafe.Pointer(&left[0])) - PutUint64(args, 8, uint64(n)) - PutUint64(args, 16, uint64(cap(left))) - PutPtr(args, 24, unsafe.Pointer(&right[0])) - PutUint64(args, 32, uint64(n)) - PutUint64(args, 40, uint64(cap(right))) - PutPtr(args, 48, unsafe.Pointer(&jitSums[0])) - - _, err := k.CallFunc("fastStereoSumsAVX2", args) - runtime.KeepAlive(left) - runtime.KeepAlive(right) - runtime.KeepAlive(jitSums) - if err != nil { - t.Fatalf("iter %d: %v", iter, err) - } - if *jitSums != goSums { - t.Fatalf("iter %d: sums mismatch:\n JIT=%v\n Go =%v", iter, *jitSums, goSums) - } - } -} - -func foldAbs(v int32) uint64 { - return uint64(uint32(v<<1) ^ uint32(v>>31)) -} - -// runAnalyzeTest is the shared harness for the analyzeO*Range family. -// analyzeJitHist is a shared histogram buffer for the analyze tests. -// It is package-level so its address is stable across all calls. -var analyzeJitHist [32]uint16 - -// analyzeJitSums is a shared sums buffer for the fastStereoSums test. -var analyzeJitSums [4]uint64 - -// analyzeSwin and analyzeJitDstP are shared input/output buffers for the -// analyze tests. Package-level so their addresses are stable. -var analyzeSwin [136]int32 // max n(128) + max order(4) = 132, rounded up -var analyzeJitDstP [128]uint32 - -func runAnalyzeTest(t *testing.T, k *Kernel, name string, order int, ref func([]int32, []uint32, *[32]uint16) (uint64, bool)) { - t.Helper() - rng := rand.New(rand.NewSource(int64(order)*100 + 7)) - jitHist := &analyzeJitHist - swin := analyzeSwin[:0] - jitDstP := analyzeJitDstP[:0] - for iter := 0; iter < 200; iter++ { - n := 1 + rng.Intn(128) - swin = swin[:n+order] - for i := range swin { - swin[i] = int32(rng.Intn(1<<20) - 1<<19) - } - - goDstP := make([]uint32, n) - var goHist [32]uint16 - goSum, goOvf := ref(swin, goDstP, &goHist) - - jitDstP = jitDstP[:n] - for i := range jitDstP { - jitDstP[i] = 0 - } - *jitHist = [32]uint16{} // reset for this iteration - args := make([]byte, 72) - PutPtr(args, 0, unsafe.Pointer(&swin[0])) - PutUint64(args, 8, uint64(len(swin))) - PutUint64(args, 16, uint64(cap(swin))) - PutPtr(args, 24, unsafe.Pointer(&jitDstP[0])) - PutUint64(args, 32, uint64(n)) - PutUint64(args, 40, uint64(cap(jitDstP))) - PutPtr(args, 48, unsafe.Pointer(&jitHist[0])) - - // Memory barrier: ensure all writes above are visible to the JIT code. - runtime.KeepAlive(args) - - out, err := k.CallFunc(name, args) - runtime.KeepAlive(swin) - runtime.KeepAlive(jitDstP) - runtime.KeepAlive(jitHist) - if err != nil { - t.Fatalf("iter %d: %v", iter, err) - } - jitSum := GetUint64(out, 56) - jitOvf := out[64] != 0 - - if jitSum != goSum { - t.Fatalf("iter %d: partSum: JIT=%d Go=%d", iter, jitSum, goSum) - } - if jitOvf != goOvf { - t.Fatalf("iter %d: overflow: JIT=%v Go=%v", iter, jitOvf, goOvf) - } - for i := range goDstP { - if jitDstP[i] != goDstP[i] { - t.Fatalf("iter %d: dstP[%d]: JIT=%d Go=%d", iter, i, jitDstP[i], goDstP[i]) - } - } - if *jitHist != goHist { - t.Fatalf("iter %d: hist mismatch\n JIT=%v\n Go =%v\n n=%d", iter, *jitHist, goHist, n) - } - } -} - -func TestFLACAnalyzeO2Range(t *testing.T) { - k := loadFLACKernel(t) - runAnalyzeTest(t, k, "analyzeO2RangeAVX2", 2, analyzeO2RangeGo) -} - -func TestFLACAnalyzeResRange(t *testing.T) { - k := loadFLACKernel(t) - // analyzeResRange has order 0: swin IS the residual (no prediction). - runAnalyzeTest(t, k, "analyzeResRangeAVX2", 0, analyzeResRangeGo) -} - -func TestFLACAnalyzeO3Range(t *testing.T) { - k := loadFLACKernel(t) - ref := func(swin []int32, dstP []uint32, hist *[32]uint16) (uint64, bool) { - swin = swin[:len(dstP)+3] - var partSum uint64 - var overflow bool - for j := 0; j+3 < len(swin); j++ { - r := swin[j+3] - 3*swin[j+2] + 3*swin[j+1] - swin[j] - if r == -2147483648 { - overflow = true - } - f := uint32(r<<1) ^ uint32(r>>31) - dstP[j] = f - partSum += uint64(f) - bl := 0 - for v := f; v > 0; v >>= 1 { - bl++ - } - if bl > 31 { - bl = 31 - } - hist[bl]++ - } - return partSum, overflow - } - runAnalyzeTest(t, k, "analyzeO3RangeAVX2", 3, ref) -} - -func TestFLACAnalyzeO4Range(t *testing.T) { - k := loadFLACKernel(t) - ref := func(swin []int32, dstP []uint32, hist *[32]uint16) (uint64, bool) { - swin = swin[:len(dstP)+4] - var partSum uint64 - var overflow bool - for j := 0; j+4 < len(swin); j++ { - r := swin[j+4] - 4*swin[j+3] + 6*swin[j+2] - 4*swin[j+1] + swin[j] - if r == -2147483648 { - overflow = true - } - f := uint32(r<<1) ^ uint32(r>>31) - dstP[j] = f - partSum += uint64(f) - bl := 0 - for v := f; v > 0; v >>= 1 { - bl++ - } - if bl > 31 { - bl = 31 - } - hist[bl]++ - } - return partSum, overflow - } - runAnalyzeTest(t, k, "analyzeO4RangeAVX2", 4, ref) -} - -func TestFLACDecodeMono24(t *testing.T) { - k := loadFLACKernel(t) - rng := rand.New(rand.NewSource(55)) - - src := mono24Src[:0] - jitDst := mono24JitDst[:0] - for iter := 0; iter < 500; iter++ { - n := rng.Intn(256) - src = src[:3*n] - rng.Read(src) - - goDst := make([]int32, n) - decodeMono24Go(src, goDst) - - jitDst = jitDst[:n] - for i := range jitDst { - jitDst[i] = 0 - } - args := make([]byte, 48) - if len(src) > 0 { - PutPtr(args, 0, unsafe.Pointer(&src[0])) - } - PutUint64(args, 8, uint64(len(src))) - PutUint64(args, 16, uint64(cap(src))) - if n > 0 { - PutPtr(args, 24, unsafe.Pointer(&jitDst[0])) - } - PutUint64(args, 32, uint64(n)) - PutUint64(args, 40, uint64(cap(jitDst))) - - _, err := k.CallFunc("decodeMono24AVX2", args) - runtime.KeepAlive(src) - runtime.KeepAlive(jitDst) - if err != nil { - t.Fatalf("iter %d: %v", iter, err) - } - for i := range goDst { - if jitDst[i] != goDst[i] { - t.Fatalf("iter %d: dst[%d]: JIT=%d Go=%d", iter, i, jitDst[i], goDst[i]) - } - } - } -} - -func TestFLACDecodeStereo16(t *testing.T) { - k := loadFLACKernel(t) - rng := rand.New(rand.NewSource(66)) - - src := stereo16Src[:0] - jitLeft := stereo16JitLeft[:0] - jitRight := stereo16JitRight[:0] - for iter := 0; iter < 500; iter++ { - n := rng.Intn(256) - src = src[:4*n] // [L0,R0,L1,R1,...] - rng.Read(src) - - goLeft := make([]int32, n) - goRight := make([]int32, n) - decodeStereo16Go(src, goLeft, goRight) - - jitLeft = jitLeft[:n] - jitRight = jitRight[:n] - for i := range jitLeft { - jitLeft[i] = 0 - jitRight[i] = 0 - } - args := make([]byte, 72) - if len(src) > 0 { - PutPtr(args, 0, unsafe.Pointer(&src[0])) - } - PutUint64(args, 8, uint64(len(src))) - PutUint64(args, 16, uint64(cap(src))) - if n > 0 { - PutPtr(args, 24, unsafe.Pointer(&jitLeft[0])) - PutPtr(args, 48, unsafe.Pointer(&jitRight[0])) - } - PutUint64(args, 32, uint64(n)) - PutUint64(args, 40, uint64(cap(jitLeft))) - PutUint64(args, 56, uint64(n)) - PutUint64(args, 64, uint64(cap(jitRight))) - - _, err := k.CallFunc("decodeStereo16AVX2", args) - runtime.KeepAlive(src) - runtime.KeepAlive(jitLeft) - runtime.KeepAlive(jitRight) - if err != nil { - t.Fatalf("iter %d: %v", iter, err) - } - for i := 0; i < n; i++ { - if jitLeft[i] != goLeft[i] || jitRight[i] != goRight[i] { - t.Fatalf("iter %d: [%d] L: JIT=%d Go=%d; R: JIT=%d Go=%d", - iter, i, jitLeft[i], goLeft[i], jitRight[i], goRight[i]) - } - } - } -} diff --git a/verify/lz4_test.go b/verify/lz4_test.go deleted file mode 100644 index dc0ad40..0000000 --- a/verify/lz4_test.go +++ /dev/null @@ -1,165 +0,0 @@ -// Copyright (c) 2026 Petr Balvín (https://petrbalvin.org) -// SPDX-License-Identifier: BSD-3-Clause - -package verify - -import ( - "bytes" - "os" - "runtime" - "testing" - "unsafe" -) - -// lz4KernelPath is the sibling repository's AVX2 kernel, used for -// integration testing. The test is skipped when the file is absent -// (e.g. in CI without the sibling checkout). -const lz4KernelPath = "../../go-libraries/go-lz4/avx2_amd64.s" - -func loadLZ4Kernel(t *testing.T) *Kernel { - t.Helper() - if _, err := os.Stat(lz4KernelPath); err != nil { - t.Skipf("sibling kernel not available: %v", err) - } - k, err := Load(lz4KernelPath) - if err != nil { - t.Fatalf("Load(%s): %v", lz4KernelPath, err) - } - t.Cleanup(k.Close) - return k -} - -// callDecodeBlockAVX2 invokes the JIT-assembled decodeBlockAVX2 with the -// given src and dst buffers, returning (n, code). -func callDecodeBlockAVX2(t *testing.T, k *Kernel, src, dst []byte) (int, int) { - t.Helper() - args := make([]byte, 64) - if len(src) > 0 { - PutPtr(args, 0, unsafe.Pointer(&src[0])) - } - PutUint64(args, 8, uint64(len(src))) - PutUint64(args, 16, uint64(cap(src))) - if len(dst) > 0 { - PutPtr(args, 24, unsafe.Pointer(&dst[0])) - } - PutUint64(args, 32, uint64(len(dst))) - PutUint64(args, 40, uint64(cap(dst))) - - out, err := k.CallFunc("decodeBlockAVX2", args) - runtime.KeepAlive(src) - runtime.KeepAlive(dst) - if err != nil { - t.Fatalf("CallFunc(decodeBlockAVX2): %v", err) - } - return int(GetUint64(out, 48)), int(GetUint64(out, 56)) -} - -func TestLZ4DecodeKnownAnswers(t *testing.T) { - k := loadLZ4Kernel(t) - - tests := []struct { - name string - src []byte - dstSize int - wantDst []byte - wantN int - wantCode int - }{ - { - name: "literals_only", - src: []byte{0x50, 'H', 'e', 'l', 'l', 'o'}, - dstSize: 16, - wantDst: []byte("Hello"), - wantN: 5, - wantCode: 0, - }, - { - name: "literals_and_match", - src: []byte{0x54, 'A', 'A', 'A', 'A', 'A', 0x05, 0x00, 0x30, 'B', 'B', 'B'}, - dstSize: 32, - wantDst: []byte("AAAAAAAAAAAAABBB"), - wantN: 16, - wantCode: 0, - }, - { - name: "overlapping_match", - // 1 literal 'X', then match offset=1 length=4+4=8 → "XXXXXXXXX", - // then final 1 literal 'Y'. - src: []byte{0x14, 'X', 0x01, 0x00, 0x10, 'Y'}, - dstSize: 16, - wantDst: []byte("XXXXXXXXXY"), - wantN: 10, - wantCode: 0, - }, - { - name: "malformed_truncated", - src: []byte{0x50, 'H', 'e'}, // claims 5 literals, has 2 - dstSize: 16, - wantN: 0, - wantCode: 1, - }, - { - name: "zero_offset", - src: []byte{0x14, 'X', 0x00, 0x00}, - dstSize: 16, - wantN: 0, - wantCode: 2, - }, - { - name: "empty_token", - src: []byte{0x00}, // 0 literals, end of block - dstSize: 16, - wantDst: nil, - wantN: 0, - wantCode: 0, - }, - } - for _, tt := range tests { - t.Run(tt.name, func(t *testing.T) { - dst := make([]byte, tt.dstSize) - n, code := callDecodeBlockAVX2(t, k, tt.src, dst) - if n != tt.wantN || code != tt.wantCode { - t.Fatalf("decodeBlockAVX2: got (n=%d, code=%d), want (n=%d, code=%d)", - n, code, tt.wantN, tt.wantCode) - } - if tt.wantCode == 0 && tt.wantDst != nil { - if !bytes.Equal(dst[:n], tt.wantDst) { - t.Errorf("output mismatch:\n got %q\n want %q", dst[:n], tt.wantDst) - } - } - }) - } -} - -func TestLZ4WideCopyAVX2(t *testing.T) { - k := loadLZ4Kernel(t) - - sizes := []int{0, 1, 15, 16, 31, 32, 33, 63, 64, 100, 256, 1024} - for _, n := range sizes { - src := make([]byte, n) - for i := range src { - src[i] = byte(i*13 + 7) - } - dst := make([]byte, n) - - args := make([]byte, 48) - if n > 0 { - PutPtr(args, 0, unsafe.Pointer(&dst[0])) - PutPtr(args, 24, unsafe.Pointer(&src[0])) - } - PutUint64(args, 8, uint64(n)) - PutUint64(args, 16, uint64(n)) - PutUint64(args, 32, uint64(n)) - PutUint64(args, 40, uint64(n)) - - _, err := k.CallFunc("wideCopyAVX2", args) - runtime.KeepAlive(dst) - runtime.KeepAlive(src) - if err != nil { - t.Fatalf("wideCopyAVX2(n=%d): %v", n, err) - } - if !bytes.Equal(dst, src) { - t.Errorf("wideCopyAVX2(n=%d): output mismatch", n) - } - } -}