Files
gasm-sdk/verify/groundtruth_test.go
T
2026-09-20 11:40:39 +02:00

162 lines
4.4 KiB
Go

// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
// SPDX-License-Identifier: BSD-3-Clause
package verify
import (
"bytes"
"os"
"testing"
"sourcedock.dev/petrbalvin/gasm-devkit/asm"
"sourcedock.dev/petrbalvin/gasm-devkit/parser"
)
func mustRead(t *testing.T, path string) string {
t.Helper()
b, err := os.ReadFile(path)
if err != nil {
t.Fatalf("read: %v", err)
}
return string(b)
}
func TestGroundTruthBasic(t *testing.T) {
// Use the simple test kernel; it assembles with go tool asm.
gt, err := GroundTruth("../testdata/verify/basic_amd64.s")
if err != nil {
t.Fatalf("GroundTruth: %v", err)
}
if len(gt) == 0 {
t.Fatal("no functions extracted from ground truth")
}
// The "add" function should be present and non-empty.
code, ok := gt["add"]
if !ok {
t.Fatalf("function 'add' not found in ground truth; got: %v", keys(gt))
}
if len(code) == 0 {
t.Fatal("add: zero-length code")
}
t.Logf("ground truth functions: %v", keys(gt))
}
func TestGroundTruthComparison(t *testing.T) {
// Assemble with gasm and compare against go tool asm.
k, err := Load("../testdata/verify/basic_amd64.s")
if err != nil {
t.Fatalf("Load: %v", err)
}
defer k.Close()
gt, err := GroundTruth("../testdata/verify/basic_amd64.s")
if err != nil {
t.Fatalf("GroundTruth: %v", err)
}
for _, name := range k.FuncNames() {
fl, _ := k.Func(name)
gasmCode := k.Image().Code[fl.Offset : fl.Offset+fl.Size]
goCode, ok := gt[name]
if !ok {
t.Errorf("%s: not in ground truth", name)
continue
}
if len(gasmCode) != len(goCode) {
t.Errorf("%s: size mismatch: gasm=%d go=%d", name, len(gasmCode), len(goCode))
continue
}
for i := range gasmCode {
if gasmCode[i] != goCode[i] {
t.Errorf("%s: byte %d differs: gasm=%02x go=%02x", name, i, gasmCode[i], goCode[i])
break
}
}
}
}
func TestGroundTruthBadFile(t *testing.T) {
_, err := GroundTruth("/nonexistent/file_amd64.s")
if err == nil {
t.Fatal("expected error for nonexistent file")
}
}
// TestGroundTruthCrossEnvGOARCH checks that a GOARCH exported in the
// ambient environment does not override cross-assembly: the child
// assembler must honour the requested GOARCH even when the environment
// already carries a GOARCH entry (os.Getenv reads the first entry, so a
// duplicate would win).
func TestGroundTruthCrossEnvGOARCH(t *testing.T) {
t.Setenv("GOARCH", "amd64")
gt, err := GroundTruthARM64("../testdata/verify/basic_arm64.s")
if err != nil {
t.Fatalf("GroundTruthARM64 with ambient GOARCH=amd64: %v", err)
}
if len(gt) == 0 {
t.Fatal("no functions extracted from ground truth")
}
}
func keys(m map[string][]byte) []string {
out := make([]string, 0, len(m))
for k := range m {
out = append(out, k)
}
return out
}
// TestGroundTruthAMD64 runs the amd64 kernels through the same live
// comparison the other arches use: gasm output versus go tool asm output,
// with relocation fields masked on both sides.
func TestGroundTruthAMD64(t *testing.T) {
for _, path := range []string{
"../testdata/verify/basic_amd64.s",
"../testdata/verify/bigframe_amd64.s",
"../testdata/verify/guard_amd64.s",
"../testdata/verify/indirect_amd64.s",
"../testdata/verify/widen_amd64.s",
"../testdata/verify/scalar_amd64.s",
"../testdata/verify/atomics_amd64.s",
"../testdata/verify/system_amd64.s",
"../testdata/verify/crypto_amd64.s",
"../testdata/verify/sse_amd64.s",
"../testdata/verify/avx_amd64.s",
"../testdata/verify/doubleshift_amd64.s",
"../testdata/verify/ssestatic_amd64.s",
} {
t.Run(path, func(t *testing.T) {
f, errs := parser.Parse(path, mustRead(t, path))
if len(errs) > 0 {
t.Fatalf("parse: %v", errs)
}
img, err := asm.AssembleFile(f)
if err != nil {
t.Fatalf("AssembleFile: %v", err)
}
gt, err := GroundTruth(path)
if err != nil {
t.Fatalf("GroundTruth: %v", err)
}
matched := 0
for _, fn := range img.Funcs {
gasmCode := maskRelocs(append([]byte(nil), img.Code[fn.Offset:fn.Offset+fn.Size]...), fn.Relocs)
goCode, ok := gt[fn.Name]
if !ok {
t.Errorf("%s: not in ground truth (%d functions)", fn.Name, len(gt))
continue
}
goCode = maskRelocs(goCode, fn.Relocs)
if !bytes.Equal(gasmCode, goCode) {
t.Errorf("%s: MISMATCH gasm=%d go=%d bytes\n%s", fn.Name, len(gasmCode), len(goCode), diffHex(gasmCode, goCode))
continue
}
matched++
}
if matched == 0 {
t.Fatal("no functions matched")
}
})
}
}