// Copyright (c) 2026 Petr BalvĂ­n (https://petrbalvin.org) // SPDX-License-Identifier: BSD-3-Clause package verify import ( "bytes" "fmt" "os" "strings" "testing" "sourcedock.dev/petrbalvin/gasm-devkit/asm" "sourcedock.dev/petrbalvin/gasm-devkit/parser" ) // TestGroundTruthLOONG64 assembles the loong64 test kernels with gasm and // compares them byte-for-byte against `go tool asm` (GOARCH=loong64). The // relocation fields of static-symbol references are masked before the // comparison, since the toolchain leaves them zero for the linker. func TestGroundTruthLOONG64(t *testing.T) { for _, path := range []string{ "../testdata/verify/basic_loong64.s", "../testdata/verify/fp_loong64.s", "../testdata/verify/bigframe_loong64.s", "../testdata/verify/guard_loong64.s", "../testdata/verify/indirect_loong64.s", "../testdata/verify/movwfp_loong64.s", "../testdata/verify/branchu_loong64.s", "../testdata/verify/atomics_loong64.s", "../testdata/verify/vector_loong64.s", "../testdata/verify/pcalign_loong64.s", "../testdata/verify/l64forms_loong64.s", "trampoline_loong64.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.AssembleFileLOONG64(f) if err != nil { t.Fatalf("AssembleFileLOONG64: %v", err) } gt, err := GroundTruthLOONG64(path) if err != nil { t.Fatalf("GroundTruthLOONG64: %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++ t.Logf("%s: MATCH (%d bytes)", fn.Name, fn.Size) } if matched == 0 { t.Fatal("no functions matched") } }) } } // maskRelocs zeroes the 4-byte immediate fields of the relocation sites. func maskRelocs(code []byte, relocs []asm.Reloc) []byte { for _, r := range relocs { for j := r.Off; j < r.Off+4 && j < len(code); j++ { code[j] = 0 } } return code } func diffHex(a, b []byte) string { var out bytes.Buffer n := max(len(b), len(a)) for i := 0; i < n; i += 4 { ab, bb := "??", "??" if i+4 <= len(a) { ab = fmt.Sprintf("%02x%02x%02x%02x", a[i], a[i+1], a[i+2], a[i+3]) } else if i < len(a) { var sb strings.Builder for j := i; j < len(a); j++ { fmt.Fprintf(&sb, "%02x", a[j]) } ab = sb.String() } if i+4 <= len(b) { bb = fmt.Sprintf("%02x%02x%02x%02x", b[i], b[i+1], b[i+2], b[i+3]) } else if i < len(b) { var sb strings.Builder for j := i; j < len(b); j++ { fmt.Fprintf(&sb, "%02x", b[j]) } bb = sb.String() } mark := " " if ab != bb { mark = "!" } fmt.Fprintf(&out, "%04x: %s %s %s\n", i, ab, bb, mark) } return out.String() }