110 lines
3.3 KiB
Go
110 lines
3.3 KiB
Go
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
|||
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
||
|
|
|
||
|
|
package asm
|
||
|
|
|
||
|
|
import (
|
||
|
|
"fmt"
|
||
|
|
"os"
|
||
|
|
"path/filepath"
|
||
|
|
"slices"
|
||
|
|
"strings"
|
||
|
|
"testing"
|
||
|
|
|
||
|
|
"sourcedock.dev/petrbalvin/gasm-sdk/parser"
|
||
|
|
)
|
||
|
|
|
||
|
|
// TestRISCVCSRDifferential proves the whole CSR name table against the
|
||
|
|
// toolchain at once: one TEXT whose body reads every register the table
|
||
|
|
// carries (CSRRW X0, NAME, X5), assembled by gasm and by go tool asm, must
|
||
|
|
// agree byte for byte. A single wrong address names its register through
|
||
|
|
// the first differing word: the CSR address occupies the instruction's top
|
||
|
|
// twelve bits, so 329 names spread over distinct words identify themselves.
|
||
|
|
func TestRISCVCSRDifferential(t *testing.T) {
|
||
|
|
names := make([]string, 0, len(riscvCSRNames))
|
||
|
|
for name := range riscvCSRNames {
|
||
|
|
names = append(names, name)
|
||
|
|
}
|
||
|
|
slices.Sort(names)
|
||
|
|
|
||
|
|
var body strings.Builder
|
||
|
|
for _, name := range names {
|
||
|
|
body.WriteString(fmt.Sprintf("\tCSRRW X0, %s, X5\n", name))
|
||
|
|
}
|
||
|
|
src := "#include \"textflag.h\"\n\nTEXT ·csrs(SB), NOSPLIT, $0\n" + body.String() + "\tRET\n"
|
||
|
|
|
||
|
|
dir := t.TempDir()
|
||
|
|
path := filepath.Join(dir, "csrs_riscv64.s")
|
||
|
|
if err := os.WriteFile(path, []byte(src), 0o644); err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
assertRISCVDifferential(t, path, src, "csrs")
|
||
|
|
}
|
||
|
|
|
||
|
|
// TestRISCVCSRPseudosDifferential pins the CSR pseudo spellings against the
|
||
|
|
// oracle: the write-only forms (source first, CSR second), their immediate
|
||
|
|
// variants and the CSRR read pseudo, one word each.
|
||
|
|
func TestRISCVCSRPseudosDifferential(t *testing.T) {
|
||
|
|
src := `#include "textflag.h"
|
||
|
|
|
||
|
|
TEXT ·csrps(SB), NOSPLIT, $0
|
||
|
|
CSRS X5, TIME
|
||
|
|
CSRC X5, CYCLE
|
||
|
|
CSRW X5, INSTRET
|
||
|
|
CSRSI $1, TIME
|
||
|
|
CSRCI $2, CYCLE
|
||
|
|
CSRWI $3, INSTRET
|
||
|
|
CSRR VL, X10
|
||
|
|
CSRRW X0, TIME, X11
|
||
|
|
CSRRS X0, CYCLE, X12
|
||
|
|
CSRRC X0, INSTRET, X13
|
||
|
|
CSRRWI $4, TIME, X14
|
||
|
|
CSRRSI $5, CYCLE, X15
|
||
|
|
CSRRCI $6, INSTRET, X16
|
||
|
|
RET
|
||
|
|
`
|
||
|
|
dir := t.TempDir()
|
||
|
|
path := filepath.Join(dir, "csrps_riscv64.s")
|
||
|
|
if err := os.WriteFile(path, []byte(src), 0o644); err != nil {
|
||
|
|
t.Fatal(err)
|
||
|
|
}
|
||
|
|
assertRISCVDifferential(t, path, src, "csrps")
|
||
|
|
}
|
||
|
|
|
||
|
|
// assertRISCVDifferential assembles the same source with gasm and with the
|
||
|
|
// toolchain for riscv64 and requires the named function's code bytes to
|
||
|
|
// agree. The live oracle is a deliberate-run comparison, so -short skips it
|
||
|
|
// (the push pipeline's mode); the golden bytes of the individual encoders
|
||
|
|
// are pinned separately in every mode.
|
||
|
|
func assertRISCVDifferential(t *testing.T, path, src, fn string) {
|
||
|
|
t.Helper()
|
||
|
|
oracle := oracleFuncCode(t, toolAsmObject(t, path, "riscv64"))
|
||
|
|
|
||
|
|
// The oracle keys its functions by the qualified object name
|
||
|
|
// (pkg.name); match on the local part.
|
||
|
|
want := map[string][]byte{}
|
||
|
|
for name, code := range oracle {
|
||
|
|
if _, after, ok := strings.Cut(name, "."); ok {
|
||
|
|
want[after] = code
|
||
|
|
} else {
|
||
|
|
want[name] = code
|
||
|
|
}
|
||
|
|
}
|
||
|
|
if want[fn] == nil {
|
||
|
|
t.Fatalf("the oracle object carries no function %q (has %v)", fn, keysOf(want))
|
||
|
|
}
|
||
|
|
|
||
|
|
f, perrs := parser.Parse(path, src)
|
||
|
|
if len(perrs) > 0 {
|
||
|
|
t.Fatalf("parse: %v", perrs[0])
|
||
|
|
}
|
||
|
|
img, err := AssembleFileRISCV(f)
|
||
|
|
if err != nil {
|
||
|
|
t.Fatalf("AssembleFileRISCV: %v", err)
|
||
|
|
}
|
||
|
|
got := trimTrailingZeroWords(img.Code)
|
||
|
|
wantB := trimTrailingZeroWords(want[fn])
|
||
|
|
if !slices.Equal(got, wantB) {
|
||
|
|
t.Errorf("%s: gasm and go tool asm disagree:\n gasm % x\n go % x", fn, got, wantB)
|
||
|
|
}
|
||
|
|
}
|