// Copyright (c) 2026 Petr BalvĂ­n (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" ) // TestGroundTruthARM64 assembles the arm64 test kernels with gasm and // compares them byte-for-byte against `go tool asm` (GOARCH=arm64). The // relocation fields of static-symbol references are masked before the // comparison, since the toolchain leaves them zero for the linker. func TestGroundTruthARM64(t *testing.T) { for _, path := range []string{ "../testdata/verify/basic_arm64.s", "../testdata/verify/fp_arm64.s", "../testdata/verify/movimm_arm64.s", "../testdata/verify/branch_arm64.s", "../testdata/verify/call_arm64.s", "../testdata/verify/bigframe_arm64.s", "../testdata/verify/guard_arm64.s", "../testdata/verify/indirect_arm64.s", "../testdata/verify/exclusive_arm64.s", "../testdata/verify/shifts_arm64.s", "../testdata/verify/atomics_arm64.s", "../testdata/verify/crypto_arm64.s", "../testdata/verify/integer_arm64.s", "../testdata/verify/simd_arm64.s", "../testdata/verify/widenimm_arm64.s", "../testdata/verify/carryshift_arm64.s", "../testdata/verify/simdmove_arm64.s", "../testdata/verify/bookkeep_arm64.s", "../testdata/verify/system_arm64.s", } { t.Run(path, func(t *testing.T) { src, err := os.ReadFile(path) if err != nil { t.Fatalf("read: %v", err) } f, errs := parser.Parse(path, string(src)) if len(errs) > 0 { t.Fatalf("parse: %v", errs) } img, err := asm.AssembleFileARM64(f) if err != nil { t.Fatalf("AssembleFileARM64: %v", err) } gt, err := GroundTruthARM64(path) if err != nil { t.Fatalf("GroundTruthARM64: %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) // The Go toolchain may add zero padding at the end of // functions. Compare up to the shorter length, then // verify any trailing bytes are zero. cmpLen := min(len(goCode), len(gasmCode)) if !bytes.Equal(gasmCode[:cmpLen], goCode[:cmpLen]) { t.Errorf("%s: MISMATCH gasm=%d go=%d bytes\n%s", fn.Name, len(gasmCode), len(goCode), diffHex(gasmCode, goCode)) continue } // Check trailing padding is zero. trailingOK := true if len(goCode) > len(gasmCode) { for _, b := range goCode[len(gasmCode):] { if b != 0 { trailingOK = false break } } } if !trailingOK { t.Errorf("%s: non-zero trailing bytes in go tool asm output", fn.Name) continue } matched++ t.Logf("%s: MATCH (%d bytes, go=%d)", fn.Name, fn.Size, len(goCode)) } if matched == 0 { t.Fatal("no functions matched") } }) } }