2026-07-30 12:55:54 +02:00
|
|
|
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
|
|
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
|
|
|
|
|
|
package verify
|
|
|
|
|
|
|
|
|
|
import (
|
2026-09-14 23:25:14 +02:00
|
|
|
"bytes"
|
|
|
|
|
"os"
|
2026-07-30 12:55:54 +02:00
|
|
|
"testing"
|
2026-09-14 23:25:14 +02:00
|
|
|
|
|
|
|
|
"sourcedock.dev/petrbalvin/gasm-devkit/asm"
|
|
|
|
|
"sourcedock.dev/petrbalvin/gasm-devkit/parser"
|
2026-07-30 12:55:54 +02:00
|
|
|
)
|
|
|
|
|
|
2026-09-14 23:25:14 +02:00
|
|
|
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)
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-30 12:55:54 +02:00
|
|
|
func TestGroundTruthBasic(t *testing.T) {
|
2026-09-16 23:12:31 +02:00
|
|
|
// Use the simple test kernel; it assembles with go tool asm.
|
2026-07-30 12:55:54 +02:00
|
|
|
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")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-09-19 23:49:19 +02:00
|
|
|
// 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")
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2026-07-30 12:55:54 +02:00
|
|
|
func keys(m map[string][]byte) []string {
|
|
|
|
|
out := make([]string, 0, len(m))
|
|
|
|
|
for k := range m {
|
|
|
|
|
out = append(out, k)
|
|
|
|
|
}
|
|
|
|
|
return out
|
|
|
|
|
}
|
2026-09-14 23:25:14 +02:00
|
|
|
|
|
|
|
|
// 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",
|
2026-09-19 19:17:07 +02:00
|
|
|
"../testdata/verify/indirect_amd64.s",
|
2026-09-20 06:45:03 +02:00
|
|
|
"../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",
|
2026-09-20 14:25:47 +02:00
|
|
|
"../testdata/verify/pfx_amd64.s",
|
2026-09-20 22:02:19 +02:00
|
|
|
"../testdata/verify/vsib_amd64.s",
|
2026-09-21 02:04:44 +02:00
|
|
|
"../testdata/verify/floatimm_amd64.s",
|
|
|
|
|
"../testdata/verify/bookkeep_amd64.s",
|
|
|
|
|
"../testdata/verify/quadreg_amd64.s",
|
2026-09-20 14:25:47 +02:00
|
|
|
"../testdata/verify/rawdata_amd64.s",
|
2026-09-20 21:17:20 +02:00
|
|
|
"../testdata/verify/avx512_amd64.s",
|
2026-09-20 14:25:47 +02:00
|
|
|
"../testdata/verify/pfx_amd64.s",
|
2026-09-20 22:02:19 +02:00
|
|
|
"../testdata/verify/vsib_amd64.s",
|
2026-09-21 02:04:44 +02:00
|
|
|
"../testdata/verify/floatimm_amd64.s",
|
|
|
|
|
"../testdata/verify/bookkeep_amd64.s",
|
|
|
|
|
"../testdata/verify/quadreg_amd64.s",
|
2026-09-20 14:25:47 +02:00
|
|
|
"../testdata/verify/rawdata_amd64.s",
|
2026-09-20 21:17:20 +02:00
|
|
|
"../testdata/verify/avx512_amd64.s",
|
2026-09-20 11:40:39 +02:00
|
|
|
"../testdata/verify/doubleshift_amd64.s",
|
|
|
|
|
"../testdata/verify/ssestatic_amd64.s",
|
2026-09-14 23:25:14 +02:00
|
|
|
} {
|
|
|
|
|
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")
|
|
|
|
|
}
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
}
|