From 19b37569c0e90aeb445bc0c776a2502b465c5dfc Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Balv=C3=ADn?= Date: Wed, 5 Aug 2026 23:09:54 +0200 Subject: [PATCH] fix(verify): fix flaky JIT tests with global buffers and KeepAlive --- verify/abi_test.go | 5 ++ verify/avx512_test.go | 7 ++ verify/coverage_test.go | 5 ++ verify/differential_test.go | 3 + verify/flac_test.go | 161 ++++++++++++++++++++++++++++++------ verify/fuzz.go | 13 +-- verify/jit_test.go | 4 + verify/lz4_test.go | 5 ++ verify/sigparse_test.go | 153 ++++++++++++++++++++++++++++++++++ verify/verify.go | 5 +- 10 files changed, 323 insertions(+), 38 deletions(-) create mode 100644 verify/sigparse_test.go diff --git a/verify/abi_test.go b/verify/abi_test.go index c3e966b..0601c7e 100644 --- a/verify/abi_test.go +++ b/verify/abi_test.go @@ -4,6 +4,7 @@ package verify import ( + "runtime" "testing" "unsafe" ) @@ -100,6 +101,8 @@ func TestABILZ4Kernels(t *testing.T) { 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) } @@ -120,6 +123,8 @@ func TestABILZ4Kernels(t *testing.T) { 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) } diff --git a/verify/avx512_test.go b/verify/avx512_test.go index bb1dd8b..6a80f33 100644 --- a/verify/avx512_test.go +++ b/verify/avx512_test.go @@ -7,6 +7,7 @@ import ( "bytes" "math/rand" "os" + "runtime" "testing" "unsafe" ) @@ -53,6 +54,8 @@ func TestAVX512DecodeKnownAnswers(t *testing.T) { 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) } @@ -91,6 +94,8 @@ func TestAVX512DifferentialFuzz(t *testing.T) { 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) } @@ -134,6 +139,8 @@ func TestAVX512WideCopy(t *testing.T) { 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) } diff --git a/verify/coverage_test.go b/verify/coverage_test.go index c276652..ffd58e5 100644 --- a/verify/coverage_test.go +++ b/verify/coverage_test.go @@ -4,6 +4,7 @@ package verify import ( + "runtime" "testing" "unsafe" ) @@ -44,6 +45,7 @@ func TestProfilePaths(t *testing.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 @@ -63,6 +65,7 @@ func TestProfilePaths(t *testing.T) { 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])) } @@ -73,6 +76,8 @@ func TestProfilePaths(t *testing.T) { // 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) } diff --git a/verify/differential_test.go b/verify/differential_test.go index 78ee77b..775f267 100644 --- a/verify/differential_test.go +++ b/verify/differential_test.go @@ -6,6 +6,7 @@ package verify import ( "bytes" "math/rand" + "runtime" "testing" "unsafe" ) @@ -288,6 +289,8 @@ func callDecodeBlockAVX2Raw(t *testing.T, k *Kernel, src, dst []byte) (int, int) 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) } diff --git a/verify/flac_test.go b/verify/flac_test.go index f2e789a..32cc06b 100644 --- a/verify/flac_test.go +++ b/verify/flac_test.go @@ -7,6 +7,7 @@ import ( "bytes" "math/rand" "os" + "runtime" "testing" "unsafe" ) @@ -157,19 +158,51 @@ func decodeStereo16Go(src []byte, left, right []int32) { // --- 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 := make([]byte, 2*n) + src = src[:2*n] rng.Read(src) goDst := make([]int32, n) decodeMono16Go(src, goDst) - jitDst := make([]int32, n) + 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])) @@ -183,6 +216,8 @@ func TestFLACDecodeMono16(t *testing.T) { 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) } @@ -198,9 +233,11 @@ 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 := make([]int32, n) + src = src[:n] for i := range src { src[i] = int32(rng.Intn(65536) - 32768) } @@ -208,7 +245,10 @@ func TestFLACPack16(t *testing.T) { goDst := make([]byte, 2*n) pack16Go(goDst, src) - jitDst := make([]byte, 2*n) + 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])) @@ -222,6 +262,8 @@ func TestFLACPack16(t *testing.T) { 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) } @@ -247,10 +289,13 @@ func TestFLACDecorrelate(t *testing.T) { 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 := make([]int32, n) - right := make([]int32, n) + 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) @@ -259,7 +304,10 @@ func TestFLACDecorrelate(t *testing.T) { goOut := make([]int32, 2*n) kk.ref(left, right, goOut) - jitOut := make([]int32, 2*n) + 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])) @@ -274,6 +322,9 @@ func TestFLACDecorrelate(t *testing.T) { 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) } @@ -291,9 +342,11 @@ 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 := make([]int32, n+1) + swin = swin[:n+1] for i := range swin { swin[i] = int32(rng.Intn(1<<20) - 1<<19) } @@ -302,8 +355,12 @@ func TestFLACAnalyzeO1Range(t *testing.T) { var goHist [32]uint16 goSum, goOvf := analyzeO1RangeGo(swin, goDstP, &goHist) - jitDstP := make([]uint32, n) - var jitHist [32]uint16 + 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))) @@ -314,6 +371,9 @@ func TestFLACAnalyzeO1Range(t *testing.T) { 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) } @@ -331,8 +391,8 @@ func TestFLACAnalyzeO1Range(t *testing.T) { 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) + if *jitHist != goHist { + t.Fatalf("iter %d: hist mismatch: JIT=%v Go=%v", iter, *jitHist, goHist) } } } @@ -341,10 +401,12 @@ 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 := make([]int32, n) - right := make([]int32, n) + 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) @@ -363,7 +425,8 @@ func TestFLACFastStereoSums(t *testing.T) { goSums[3] += foldAbs(mid) + foldAbs(side) } - var jitSums [4]uint64 + 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)) @@ -374,11 +437,14 @@ func TestFLACFastStereoSums(t *testing.T) { 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) + if *jitSums != goSums { + t.Fatalf("iter %d: sums mismatch:\n JIT=%v\n Go =%v", iter, *jitSums, goSums) } } } @@ -388,12 +454,27 @@ func foldAbs(v int32) uint64 { } // 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 := make([]int32, n+order) + swin = swin[:n+order] for i := range swin { swin[i] = int32(rng.Intn(1<<20) - 1<<19) } @@ -402,8 +483,11 @@ func runAnalyzeTest(t *testing.T, k *Kernel, name string, order int, ref func([] var goHist [32]uint16 goSum, goOvf := ref(swin, goDstP, &goHist) - jitDstP := make([]uint32, n) - var jitHist [32]uint16 + 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))) @@ -413,7 +497,13 @@ func runAnalyzeTest(t *testing.T, k *Kernel, name string, order int, ref func([] 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) } @@ -431,8 +521,8 @@ func runAnalyzeTest(t *testing.T, k *Kernel, name string, order int, ref func([] 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", iter) + if *jitHist != goHist { + t.Fatalf("iter %d: hist mismatch\n JIT=%v\n Go =%v\n n=%d", iter, *jitHist, goHist, n) } } } @@ -508,15 +598,20 @@ 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 := make([]byte, 3*n) + src = src[:3*n] rng.Read(src) goDst := make([]int32, n) decodeMono24Go(src, goDst) - jitDst := make([]int32, n) + 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])) @@ -530,6 +625,8 @@ func TestFLACDecodeMono24(t *testing.T) { 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) } @@ -545,17 +642,24 @@ 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 := make([]byte, 4*n) // [L0,R0,L1,R1,...] + src = src[:4*n] // [L0,R0,L1,R1,...] rng.Read(src) goLeft := make([]int32, n) goRight := make([]int32, n) decodeStereo16Go(src, goLeft, goRight) - jitLeft := make([]int32, n) - jitRight := make([]int32, n) + 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])) @@ -572,6 +676,9 @@ func TestFLACDecodeStereo16(t *testing.T) { 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) } diff --git a/verify/fuzz.go b/verify/fuzz.go index cf8614b..edf6d94 100644 --- a/verify/fuzz.go +++ b/verify/fuzz.go @@ -7,6 +7,7 @@ import ( "fmt" "math/rand" "regexp" + "runtime" "strconv" "strings" "unsafe" @@ -192,7 +193,7 @@ func (k *Kernel) FuzzFunc(name string, sig funcSig, goCode []byte, iterations in if result.FirstFail == "" { result.FirstFail = fmt.Sprintf("iter %d: gasm call: %v", i, err) } - releaseBufs(bufs) + runtime.KeepAlive(bufs) continue } @@ -203,7 +204,7 @@ func (k *Kernel) FuzzFunc(name string, sig funcSig, goCode []byte, iterations in if result.FirstFail == "" { result.FirstFail = fmt.Sprintf("iter %d: go call: %v", i, err) } - releaseBufs(bufs) + runtime.KeepAlive(bufs) continue } @@ -221,7 +222,7 @@ func (k *Kernel) FuzzFunc(name string, sig funcSig, goCode []byte, iterations in } else { result.Matches++ } - releaseBufs(bufs) + runtime.KeepAlive(bufs) } return result } @@ -384,9 +385,3 @@ func equalBytes(a, b []byte) bool { } return true } - -func releaseBufs(bufs [][]byte) { - // Keep buffers alive until after the call; nothing to free in Go, - // but this prevents the compiler from collecting them too early. - _ = bufs -} diff --git a/verify/jit_test.go b/verify/jit_test.go index 254c778..e10f87f 100644 --- a/verify/jit_test.go +++ b/verify/jit_test.go @@ -5,6 +5,7 @@ package verify import ( "bytes" + "runtime" "testing" "unsafe" ) @@ -68,6 +69,7 @@ func TestJITSum(t *testing.T) { PutUint64(args, 16, uint64(cap(tt.data))) out, err := k.CallFunc("sum", args) + runtime.KeepAlive(tt.data) if err != nil { t.Fatalf("CallFunc(sum, %v): %v", tt.data, err) } @@ -111,6 +113,8 @@ func TestJITWideCopy(t *testing.T) { PutUint64(args, 40, uint64(tt.n)) // src_cap _, err := k.CallFunc("wideCopy", args) + runtime.KeepAlive(dst) + runtime.KeepAlive(src) if err != nil { t.Fatalf("CallFunc(wideCopy): %v", err) } diff --git a/verify/lz4_test.go b/verify/lz4_test.go index f63831b..dc0ad40 100644 --- a/verify/lz4_test.go +++ b/verify/lz4_test.go @@ -6,6 +6,7 @@ package verify import ( "bytes" "os" + "runtime" "testing" "unsafe" ) @@ -45,6 +46,8 @@ func callDecodeBlockAVX2(t *testing.T, k *Kernel, src, dst []byte) (int, int) { 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) } @@ -150,6 +153,8 @@ func TestLZ4WideCopyAVX2(t *testing.T) { 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) } diff --git a/verify/sigparse_test.go b/verify/sigparse_test.go new file mode 100644 index 0000000..aabf2e5 --- /dev/null +++ b/verify/sigparse_test.go @@ -0,0 +1,153 @@ +// Copyright (c) 2026 Petr Balvín (https://petrbalvin.org) +// SPDX-License-Identifier: BSD-3-Clause + +package verify + +import ( + "testing" +) + +func TestParseFuncSigExported(t *testing.T) { + tests := []struct { + comment string + wantName string + nParams int + nResults int + }{ + {"// func add(a int64, b int64) int64", "add", 2, 1}, + {"// func wideCopy(dst []byte, src []byte)", "wideCopy", 2, 0}, + {"// func decodeBlockAVX2(src, dst []byte) (n int, code int)", "decodeBlockAVX2", 2, 2}, + {"// func analyzeO1RangeAVX2(swin []int32, dstP []uint32, hist *[32]uint16) (partSum uint64, overflow bool)", "analyzeO1RangeAVX2", 3, 2}, + {"// not a func", "", 0, 0}, + {"// func", "", 0, 0}, + } + for _, tt := range tests { + sig, ok := ParseFuncSig(tt.comment) + if tt.wantName == "" { + if ok { + t.Errorf("ParseFuncSig(%q): expected not ok", tt.comment) + } + continue + } + if !ok { + t.Errorf("ParseFuncSig(%q): expected ok", tt.comment) + continue + } + if sig.Name != tt.wantName { + t.Errorf("ParseFuncSig(%q).Name = %q, want %q", tt.comment, sig.Name, tt.wantName) + } + if len(sig.Params) != tt.nParams { + t.Errorf("ParseFuncSig(%q): %d params, want %d", tt.comment, len(sig.Params), tt.nParams) + } + if len(sig.Results) != tt.nResults { + t.Errorf("ParseFuncSig(%q): %d results, want %d", tt.comment, len(sig.Results), tt.nResults) + } + } +} + +func TestExtractFuncSig(t *testing.T) { + src := `// func add(a int64, b int64) int64 +TEXT ·add(SB), NOSPLIT, $0-24 + +// func wideCopyAVX2(dst, src []byte) +TEXT ·wideCopyAVX2(SB), NOSPLIT, $0-48 +` + sig, ok := ExtractFuncSig(src, "add") + if !ok { + t.Fatal("ExtractFuncSig(add): not found") + } + if sig.Name != "add" { + t.Errorf("Name = %q, want %q", sig.Name, "add") + } + if len(sig.Params) != 2 { + t.Errorf("params = %d, want 2", len(sig.Params)) + } + + sig, ok = ExtractFuncSig(src, "wideCopyAVX2") + if !ok { + t.Fatal("ExtractFuncSig(wideCopyAVX2): not found") + } + if len(sig.Params) != 2 { + t.Errorf("params = %d, want 2", len(sig.Params)) + } + + _, ok = ExtractFuncSig(src, "nonexistent") + if ok { + t.Error("ExtractFuncSig(nonexistent): expected not found") + } +} + +func TestArgLayout(t *testing.T) { + sig := FuncSig{ + Name: "wideCopyAVX2", + Params: []Param{ + {Name: "dst", Typ: "[]byte"}, + {Name: "src", Typ: "[]byte"}, + }, + } + layout := ArgLayout(sig) + if len(layout) != 2 { + t.Fatalf("ArgLayout: %d entries, want 2", len(layout)) + } + // dst: offset 0, size 24 (slice) + if layout[0].Name != "dst" || layout[0].Offset != 0 || layout[0].Size != 24 || !layout[0].IsPtr { + t.Errorf("layout[0] = %+v", layout[0]) + } + // src: offset 24, size 24 (slice) + if layout[1].Name != "src" || layout[1].Offset != 24 || layout[1].Size != 24 || !layout[1].IsPtr { + t.Errorf("layout[1] = %+v", layout[1]) + } +} + +func TestArgLayoutMixed(t *testing.T) { + sig := FuncSig{ + Name: "decodeBlockAVX2", + Params: []Param{ + {Name: "src", Typ: "[]byte"}, + {Name: "dst", Typ: "[]byte"}, + }, + Results: []Param{ + {Name: "n", Typ: "int"}, + {Name: "code", Typ: "int"}, + }, + } + layout := ArgLayout(sig) + if len(layout) != 2 { + t.Fatalf("ArgLayout: %d entries, want 2", len(layout)) + } + // Two slices: 24 + 24 = 48 bytes of params. + if layout[1].Offset != 24 { + t.Errorf("src offset = %d, want 24", layout[1].Offset) + } +} + +func TestParamSize(t *testing.T) { + tests := []struct { + typ string + want int + }{ + {"[]byte", 24}, + {"[]int32", 24}, + {"*uint16", 8}, + {"*[32]uint16", 8}, + {"int", 8}, + {"int64", 8}, + {"uint64", 8}, + {"uintptr", 8}, + {"int32", 4}, + {"uint32", 4}, + {"float32", 4}, + {"int16", 2}, + {"uint16", 2}, + {"int8", 1}, + {"uint8", 1}, + {"byte", 1}, + {"bool", 1}, + {"string", 8}, // unknown type defaults to 8 + } + for _, tt := range tests { + if got := paramSize(tt.typ); got != tt.want { + t.Errorf("paramSize(%q) = %d, want %d", tt.typ, got, tt.want) + } + } +} diff --git a/verify/verify.go b/verify/verify.go index f5b09f7..5906dac 100644 --- a/verify/verify.go +++ b/verify/verify.go @@ -7,6 +7,7 @@ import ( "fmt" "math/rand" "os" + "runtime" "sourcedock.dev/petrbalvin/gasm-devkit/asm" "sourcedock.dev/petrbalvin/gasm-devkit/ast" @@ -153,7 +154,7 @@ func (k *Kernel) FuzzFuncChecked(name string, sig funcSig, iterations int, seed if result.FirstFail == "" { result.FirstFail = fmt.Sprintf("iter %d: call: %v", i, err) } - releaseBufs(bufs) + runtime.KeepAlive(bufs) continue } @@ -166,7 +167,7 @@ func (k *Kernel) FuzzFuncChecked(name string, sig funcSig, iterations int, seed } else { result.Matches++ } - releaseBufs(bufs) + runtime.KeepAlive(bufs) } if violations > 0 && result.FirstFail == "" {