feat(asm): add LoongArch encoder with ELF and GOOBJ emission

Assisted-by: DeepSeek V4 Pro
This commit is contained in:
2026-08-13 11:24:44 +02:00
parent 2d1176e045
commit 31ee8e7941
24 changed files with 4777 additions and 149 deletions
+7
View File
@@ -32,6 +32,12 @@ func GroundTruthRISCV(path string) (map[string][]byte, error) {
return groundTruthArch(path, "riscv64")
}
// GroundTruthLOONG64 assembles the given .s file with the Go toolchain in
// LoongArch cross-assembly mode (GOARCH=loong64).
func GroundTruthLOONG64(path string) (map[string][]byte, error) {
return groundTruthArch(path, "loong64")
}
func groundTruthArch(path, goarch string) (map[string][]byte, error) {
goroot := runtime.GOROOT()
asmBin := filepath.Join(goroot, "pkg", "tool", runtime.GOOS+"_"+runtime.GOARCH, "asm")
@@ -51,6 +57,7 @@ func groundTruthArch(path, goarch string) (map[string][]byte, error) {
pkg := strings.TrimSuffix(base, ".s")
pkg = strings.TrimSuffix(pkg, "_amd64")
pkg = strings.TrimSuffix(pkg, "_riscv64")
pkg = strings.TrimSuffix(pkg, "_loong64")
cmd := exec.Command(asmBin, "-I", includeDir, "-p", pkg, "-o", objPath, path)
if goarch != "" {
+97
View File
@@ -0,0 +1,97 @@
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
// SPDX-License-Identifier: BSD-3-Clause
package verify
import (
"bytes"
"fmt"
"os"
"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",
} {
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 := len(a)
if len(b) > n {
n = len(b)
}
for i := 0; i < n; i += 4 {
ab, bb := "??", "??"
if i < len(a) {
ab = fmt.Sprintf("%02x%02x%02x%02x", a[i], a[i+1], a[i+2], a[i+3])
}
if i < len(b) {
bb = fmt.Sprintf("%02x%02x%02x%02x", b[i], b[i+1], b[i+2], b[i+3])
}
mark := " "
if ab != bb {
mark = "!"
}
fmt.Fprintf(&out, "%04x: %s %s %s\n", i, ab, bb, mark)
}
return out.String()
}