feat(asm): resolve every CSR name the toolchain knows
The riscv64 assembler carried fourteen hand-picked CSR names where the toolchain resolves three hundred and twenty-nine: a CSRR/CSRW family instruction naming any privileged register beyond the few base ones came out as unknown CSR. The table now carries the RISC-V privileged specification's register set exactly as go tool asm spells it, and a differential test assembles every name through both assemblers and requires the words to agree byte for byte. Assisted-by: GLM 5.3 Flash
This commit is contained in:
1 parent
103864e8b2
commit
bffe408afa
2 files changed
+442
-17
No files matched your search
@@ -0,0 +1,110 @@
|
||||
// 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)
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user