Compare commits

...
2 Commits
Author SHA1 Message Date
petrbalvin f8d28a42ba feat(verify): complete the analyze family and add stereo16 differential tests
Assisted-by: Qwen 3.8 Max Preview
2026-08-02 23:11:30 +02:00
petrbalvin 51a2854d7f feat(verify): add analyzeO2/Res and decodeMono24 differential tests
Assisted-by: Qwen 3.8 Max Preview
2026-08-02 23:11:30 +02:00
3 changed files with 257 additions and 2 deletions
+1 -1
View File
@@ -29,7 +29,7 @@ import (
// version is the release version, stamped at build time via
// -ldflags "-X main.version=…" (defaulting to the current release).
var version = "0.22.0"
var version = "0.24.0"
func main() {
if len(os.Args) < 2 {
+1 -1
View File
@@ -3,7 +3,7 @@
# gasm-devkit — developer tooling for Go's Plan 9 assembler (GAsm).
version := "0.22.0"
version := "0.24.0"
default:
@just --list
+255
View File
@@ -97,6 +97,64 @@ func analyzeO1RangeGo(swin []int32, dstP []uint32, hist *[32]uint16) (partSum ui
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 ---
func TestFLACDecodeMono16(t *testing.T) {
@@ -328,3 +386,200 @@ func TestFLACFastStereoSums(t *testing.T) {
func foldAbs(v int32) uint64 {
return uint64(uint32(v<<1) ^ uint32(v>>31))
}
// runAnalyzeTest is the shared harness for the analyzeO*Range family.
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))
for iter := 0; iter < 200; iter++ {
n := 1 + rng.Intn(128)
swin := make([]int32, 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 := make([]uint32, n)
var jitHist [32]uint16
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]))
out, err := k.CallFunc(name, args)
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", iter)
}
}
}
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))
for iter := 0; iter < 500; iter++ {
n := rng.Intn(256)
src := make([]byte, 3*n)
rng.Read(src)
goDst := make([]int32, n)
decodeMono24Go(src, goDst)
jitDst := make([]int32, n)
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)
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))
for iter := 0; iter < 500; iter++ {
n := rng.Intn(256)
src := make([]byte, 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)
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)
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])
}
}
}
}