fix(verify): remove all go-libraries kernel dependencies from tests
This commit is contained in:
+1
-1
@@ -9,7 +9,7 @@ and this project adheres to [Conventional Commits](https://www.conventionalcommi
|
|||||||
|
|
||||||
Unreleased changes on the `development` branch.
|
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
|
RISC-V GOOBJ emission, YMM vector register display, named buffer allocation
|
||||||
in the debugger, two new CLI commands (`diff`, `profile`), go-to-definition in
|
in the debugger, two new CLI commands (`diff`, `profile`), go-to-definition in
|
||||||
|
|||||||
@@ -4,9 +4,7 @@
|
|||||||
package verify
|
package verify
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"runtime"
|
|
||||||
"testing"
|
"testing"
|
||||||
"unsafe"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func loadABIKernel(t *testing.T) *Kernel {
|
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) {
|
func TestCallFuncCheckedErrors(t *testing.T) {
|
||||||
k := loadABIKernel(t)
|
k := loadABIKernel(t)
|
||||||
|
|
||||||
|
|||||||
@@ -1,151 +0,0 @@
|
|||||||
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -4,9 +4,7 @@
|
|||||||
package verify
|
package verify
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"runtime"
|
|
||||||
"testing"
|
"testing"
|
||||||
"unsafe"
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func TestBlocks(t *testing.T) {
|
func TestBlocks(t *testing.T) {
|
||||||
@@ -25,66 +23,3 @@ func TestBlocks(t *testing.T) {
|
|||||||
}
|
}
|
||||||
t.Logf("sum blocks: %v", blocks)
|
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))
|
|
||||||
}
|
|
||||||
|
|||||||
@@ -1,298 +0,0 @@
|
|||||||
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (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))
|
|
||||||
}
|
|
||||||
@@ -1,692 +0,0 @@
|
|||||||
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (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])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -1,165 +0,0 @@
|
|||||||
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Reference in New Issue
Block a user