diff --git a/cmd/gasm/main.go b/cmd/gasm/main.go index ce86d6b..5e2a53e 100644 --- a/cmd/gasm/main.go +++ b/cmd/gasm/main.go @@ -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.23.0" func main() { if len(os.Args) < 2 { diff --git a/justfile b/justfile index c75416c..118df8e 100644 --- a/justfile +++ b/justfile @@ -3,7 +3,7 @@ # gasm-devkit — developer tooling for Go's Plan 9 assembler (GAsm). -version := "0.22.0" +version := "0.23.0" default: @just --list diff --git a/verify/flac_test.go b/verify/flac_test.go index b4d3be1..63dc3b6 100644 --- a/verify/flac_test.go +++ b/verify/flac_test.go @@ -97,6 +97,57 @@ 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 + } +} + // --- Differential tests --- func TestFLACDecodeMono16(t *testing.T) { @@ -328,3 +379,101 @@ 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 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]) + } + } + } +}