Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
c234c3dd5b | ||
|
|
9262990ce5 | ||
|
|
d9f6167a4d |
+34
-3
@@ -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.19.0"
|
||||
var version = "0.22.0"
|
||||
|
||||
func main() {
|
||||
if len(os.Args) < 2 {
|
||||
@@ -472,7 +472,7 @@ requires -p, the package path, and the installed Go toolchain).
|
||||
}
|
||||
|
||||
func cmdVerify(args []string) int {
|
||||
fs := newCommand("verify", "gasm verify <file.s>", `
|
||||
fs := newCommand("verify", "gasm verify [-smoke] [-abi] [-profile] <file.s>", `
|
||||
Assemble FILE (amd64), map it into executable memory and report the available
|
||||
functions. This confirms the assembled image is self-consistent (no
|
||||
unresolved external symbols) and executable — the prerequisite for dynamic
|
||||
@@ -481,11 +481,18 @@ testing.
|
||||
With -smoke, each NOSPLIT function is called with a zeroed argument block to
|
||||
confirm the JIT trampoline works end-to-end. This is safe only for functions
|
||||
that tolerate nil pointers and zero lengths in their arguments.
|
||||
|
||||
With -abi, each function is called with sentinel values in the callee-saved
|
||||
registers (BP, R14) and a red-zone canary below SP; violations are reported.
|
||||
|
||||
With -profile, the static basic-block structure is listed for each function.
|
||||
`)
|
||||
smoke := fs.Bool("smoke", false, "call each NOSPLIT function with zeroed args")
|
||||
abi := fs.Bool("abi", false, "run ABI-checking calls (sentinel registers + red zone)")
|
||||
profile := fs.Bool("profile", false, "list basic-block structure per function")
|
||||
fs.Parse(args)
|
||||
if fs.NArg() != 1 {
|
||||
fmt.Fprintln(os.Stderr, "usage: gasm verify [-smoke] <file.s>")
|
||||
fmt.Fprintln(os.Stderr, "usage: gasm verify [-smoke] [-abi] [-profile] <file.s>")
|
||||
return 2
|
||||
}
|
||||
path := fs.Arg(0)
|
||||
@@ -511,6 +518,16 @@ that tolerate nil pointers and zero lengths in their arguments.
|
||||
flags = " NOSPLIT"
|
||||
}
|
||||
fmt.Printf(" %s: %d bytes, args=%d, frame=%d%s\n", name, fl.Size, fl.Args, fl.Frame, flags)
|
||||
|
||||
if *profile {
|
||||
blocks, err := k.Blocks(name)
|
||||
if err != nil {
|
||||
fmt.Printf(" profile: %v\n", err)
|
||||
} else {
|
||||
fmt.Printf(" blocks: %d\n", len(blocks))
|
||||
}
|
||||
}
|
||||
|
||||
if *smoke && fl.NoSplit {
|
||||
args := make([]byte, fl.Args)
|
||||
_, err := k.CallFunc(name, args)
|
||||
@@ -521,6 +538,20 @@ that tolerate nil pointers and zero lengths in their arguments.
|
||||
fmt.Printf(" smoke: OK\n")
|
||||
}
|
||||
}
|
||||
|
||||
if *abi && fl.NoSplit {
|
||||
args := make([]byte, fl.Args)
|
||||
_, report, err := k.CallFuncChecked(name, args)
|
||||
if err != nil {
|
||||
fmt.Printf(" abi: FAIL — %v\n", err)
|
||||
rc = 1
|
||||
} else if !report.OK() {
|
||||
fmt.Printf(" abi: %s\n", report)
|
||||
rc = 1
|
||||
} else {
|
||||
fmt.Printf(" abi: clean\n")
|
||||
}
|
||||
}
|
||||
}
|
||||
return rc
|
||||
}
|
||||
|
||||
@@ -3,7 +3,7 @@
|
||||
|
||||
# gasm-devkit — developer tooling for Go's Plan 9 assembler (GAsm).
|
||||
|
||||
version := "0.19.0"
|
||||
version := "0.22.0"
|
||||
|
||||
default:
|
||||
@just --list
|
||||
|
||||
@@ -0,0 +1,144 @@
|
||||
// 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"
|
||||
"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)
|
||||
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)
|
||||
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)
|
||||
if err != nil {
|
||||
t.Fatalf("wideCopyAVX512(n=%d): %v", n, err)
|
||||
}
|
||||
if !bytes.Equal(dst, src) {
|
||||
t.Errorf("wideCopyAVX512(n=%d): mismatch", n)
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,103 @@
|
||||
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package verify
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sort"
|
||||
)
|
||||
|
||||
// Block describes one basic block within a function: a maximal sequence of
|
||||
// instructions with a single entry point (a label or the function start) and
|
||||
// a single exit (a jump, conditional jump or RET).
|
||||
type Block struct {
|
||||
Offset int // byte offset within the function
|
||||
Label string // label name ("" for the entry block)
|
||||
}
|
||||
|
||||
// Blocks identifies the basic blocks of a function from its local labels.
|
||||
// Each label is a potential jump target and therefore a block boundary; the
|
||||
// function entry (offset 0) is always a block. The blocks are returned in
|
||||
// ascending offset order.
|
||||
func (k *Kernel) Blocks(name string) ([]Block, error) {
|
||||
idx, ok := k.funcs[name]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("verify: function %q not found", name)
|
||||
}
|
||||
fl := k.img.Funcs[idx]
|
||||
|
||||
blocks := []Block{{Offset: 0, Label: "(entry)"}}
|
||||
// Build a reverse map: offset → label name.
|
||||
offToLabel := make(map[int]string, len(fl.Labels))
|
||||
for label, off := range fl.Labels {
|
||||
if off > 0 && off < fl.Size {
|
||||
offToLabel[off] = label
|
||||
}
|
||||
}
|
||||
// Collect and sort offsets.
|
||||
offsets := make([]int, 0, len(offToLabel))
|
||||
for off := range offToLabel {
|
||||
offsets = append(offsets, off)
|
||||
}
|
||||
sort.Ints(offsets)
|
||||
for _, off := range offsets {
|
||||
blocks = append(blocks, Block{Offset: off, Label: offToLabel[off]})
|
||||
}
|
||||
return blocks, nil
|
||||
}
|
||||
|
||||
// BlockCount returns the number of identified basic blocks for the function.
|
||||
func (k *Kernel) BlockCount(name string) (int, error) {
|
||||
blocks, err := k.Blocks(name)
|
||||
if err != nil {
|
||||
return 0, err
|
||||
}
|
||||
return len(blocks), nil
|
||||
}
|
||||
|
||||
// PathFingerprint is the observable output of one function execution: the
|
||||
// values written back into the result slots of the argument block. Two
|
||||
// executions that produce the same fingerprint took observationally
|
||||
// equivalent paths (though they may differ internally).
|
||||
type PathFingerprint struct {
|
||||
Results []uint64 // the result words from the arg block
|
||||
}
|
||||
|
||||
// ProfilePaths runs the function with each of the given argument blocks and
|
||||
// collects the distinct output fingerprints. This measures path diversity:
|
||||
// how many observationally different execution paths the input corpus
|
||||
// exercises. Combined with Blocks (the static block count), it gives a
|
||||
// lower bound on code coverage.
|
||||
func (k *Kernel) ProfilePaths(name string, argSets [][]byte, resultOffsets []int) ([]PathFingerprint, error) {
|
||||
idx, ok := k.funcs[name]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("verify: function %q not found", name)
|
||||
}
|
||||
fl := k.img.Funcs[idx]
|
||||
|
||||
seen := map[string]bool{}
|
||||
var paths []PathFingerprint
|
||||
|
||||
for _, args := range argSets {
|
||||
if len(args) < fl.Args {
|
||||
return nil, fmt.Errorf("verify: %s: arg block too small", name)
|
||||
}
|
||||
out, err := k.CallFunc(name, args)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
fp := PathFingerprint{}
|
||||
key := ""
|
||||
for _, off := range resultOffsets {
|
||||
v := GetUint64(out, off)
|
||||
fp.Results = append(fp.Results, v)
|
||||
key += fmt.Sprintf("%016x", v)
|
||||
}
|
||||
if !seen[key] {
|
||||
seen[key] = true
|
||||
paths = append(paths, fp)
|
||||
}
|
||||
}
|
||||
return paths, nil
|
||||
}
|
||||
@@ -0,0 +1,85 @@
|
||||
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package verify
|
||||
|
||||
import (
|
||||
"testing"
|
||||
"unsafe"
|
||||
)
|
||||
|
||||
func TestBlocks(t *testing.T) {
|
||||
k := loadBasic(t)
|
||||
|
||||
// The "sum" function has labels: sum_done, sum_loop.
|
||||
blocks, err := k.Blocks("sum")
|
||||
if err != nil {
|
||||
t.Fatalf("Blocks(sum): %v", err)
|
||||
}
|
||||
if len(blocks) < 3 {
|
||||
t.Errorf("sum: expected at least 3 blocks (entry + 2 labels), got %d", len(blocks))
|
||||
}
|
||||
if blocks[0].Offset != 0 {
|
||||
t.Errorf("first block offset = %d, want 0", blocks[0].Offset)
|
||||
}
|
||||
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
|
||||
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)
|
||||
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})
|
||||
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))
|
||||
}
|
||||
@@ -0,0 +1,330 @@
|
||||
// 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"
|
||||
"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
|
||||
}
|
||||
|
||||
// --- Differential tests ---
|
||||
|
||||
func TestFLACDecodeMono16(t *testing.T) {
|
||||
k := loadFLACKernel(t)
|
||||
rng := rand.New(rand.NewSource(7))
|
||||
|
||||
for iter := 0; iter < 500; iter++ {
|
||||
n := rng.Intn(256)
|
||||
src := make([]byte, 2*n)
|
||||
rng.Read(src)
|
||||
|
||||
goDst := make([]int32, n)
|
||||
decodeMono16Go(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("decodeMono16AVX2", args)
|
||||
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))
|
||||
|
||||
for iter := 0; iter < 500; iter++ {
|
||||
n := rng.Intn(256)
|
||||
src := make([]int32, n)
|
||||
for i := range src {
|
||||
src[i] = int32(rng.Intn(65536) - 32768)
|
||||
}
|
||||
|
||||
goDst := make([]byte, 2*n)
|
||||
pack16Go(goDst, src)
|
||||
|
||||
jitDst := make([]byte, 2*n)
|
||||
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)
|
||||
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) {
|
||||
for iter := 0; iter < 200; iter++ {
|
||||
n := rng.Intn(128)
|
||||
left := make([]int32, n)
|
||||
right := make([]int32, 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 := make([]int32, 2*n)
|
||||
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)
|
||||
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))
|
||||
|
||||
for iter := 0; iter < 300; iter++ {
|
||||
n := 1 + rng.Intn(128) // partition size
|
||||
swin := make([]int32, 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 := make([]uint32, n)
|
||||
var jitHist [32]uint16
|
||||
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)
|
||||
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))
|
||||
|
||||
for iter := 0; iter < 300; iter++ {
|
||||
n := 1 + rng.Intn(256)
|
||||
left := make([]int32, n)
|
||||
right := make([]int32, 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)
|
||||
}
|
||||
|
||||
var jitSums [4]uint64
|
||||
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)
|
||||
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))
|
||||
}
|
||||
Reference in New Issue
Block a user