feat(asm): add AArch64 arm64 encoder with ground-truth verification
Assisted-by: MiMo V2.5 Pro
This commit is contained in:
@@ -0,0 +1,83 @@
|
||||
// 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"
|
||||
)
|
||||
|
||||
// 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",
|
||||
} {
|
||||
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 := len(gasmCode)
|
||||
if len(goCode) < cmpLen {
|
||||
cmpLen = len(goCode)
|
||||
}
|
||||
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")
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -38,6 +38,12 @@ func GroundTruthLOONG64(path string) (map[string][]byte, error) {
|
||||
return groundTruthArch(path, "loong64")
|
||||
}
|
||||
|
||||
// GroundTruthARM64 assembles the given .s file with the Go toolchain in
|
||||
// AArch64 cross-assembly mode (GOARCH=arm64).
|
||||
func GroundTruthARM64(path string) (map[string][]byte, error) {
|
||||
return groundTruthArch(path, "arm64")
|
||||
}
|
||||
|
||||
func groundTruthArch(path, goarch string) (map[string][]byte, error) {
|
||||
goroot := runtime.GOROOT()
|
||||
asmBin := filepath.Join(goroot, "pkg", "tool", runtime.GOOS+"_"+runtime.GOARCH, "asm")
|
||||
@@ -58,6 +64,7 @@ func groundTruthArch(path, goarch string) (map[string][]byte, error) {
|
||||
pkg = strings.TrimSuffix(pkg, "_amd64")
|
||||
pkg = strings.TrimSuffix(pkg, "_riscv64")
|
||||
pkg = strings.TrimSuffix(pkg, "_loong64")
|
||||
pkg = strings.TrimSuffix(pkg, "_arm64")
|
||||
|
||||
cmd := exec.Command(asmBin, "-I", includeDir, "-p", pkg, "-o", objPath, path)
|
||||
if goarch != "" {
|
||||
|
||||
Reference in New Issue
Block a user