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:
petrbalvin committed 2026-10-07 00:47:27 +02:00
1 parent 103864e8b2
commit bffe408afa
2 files changed
+442 -17

No files matched your search

+332 -17
View File
@@ -528,24 +528,339 @@ func riscvIsCondBranch(mnem string) bool {
return false
}
// riscvCSRNames maps the standard CSR mnemonics the assembler accepts onto
// their addresses.
// riscvCSRNames maps every CSR mnemonic the assembler accepts onto its
// address: the RISC-V privileged specification's register set as the Go
// toolchain spells it, so a name `go tool asm` reads resolves here too.
var riscvCSRNames = map[string]int32{
"FFLAGS": 0x001,
"FRM": 0x002,
"FCSR": 0x003,
"VSTART": 0x008,
"VXSAT": 0x009,
"VXRM": 0x00A,
"VCSR": 0x00F,
"CYCLE": 0xC00,
"TIME": 0xC01,
"INSTRET": 0xC02,
"CYCLEH": 0xC80,
"TIMEH": 0xC81,
"INSTRETH": 0xC82,
"VL": 0xC20,
"VLENB": 0xC22,
"FFLAGS": 0x001,
"FRM": 0x002,
"FCSR": 0x003,
"UTVT": 0x007,
"VSTART": 0x008,
"VXSAT": 0x009,
"VXRM": 0x00A,
"VCSR": 0x00F,
"SSP": 0x011,
"SEED": 0x015,
"JVT": 0x017,
"UNXTI": 0x045,
"UINTSTATUS": 0x046,
"USCRATCHCSW": 0x048,
"USCRATCHCSWL": 0x049,
"SSTATUS": 0x100,
"SIE": 0x104,
"STVEC": 0x105,
"SCOUNTEREN": 0x106,
"STVT": 0x107,
"SENVCFG": 0x10A,
"SSTATEEN0": 0x10C,
"SSTATEEN1": 0x10D,
"SSTATEEN2": 0x10E,
"SSTATEEN3": 0x10F,
"SCOUNTINHIBIT": 0x120,
"SSCRATCH": 0x140,
"SEPC": 0x141,
"SCAUSE": 0x142,
"STVAL": 0x143,
"SIP": 0x144,
"SNXTI": 0x145,
"SINTSTATUS": 0x146,
"SSCRATCHCSW": 0x148,
"SSCRATCHCSWL": 0x149,
"STIMECMP": 0x14D,
"SCTRCTL": 0x14E,
"SCTRSTATUS": 0x14F,
"SISELECT": 0x150,
"SIREG": 0x151,
"SIREG2": 0x152,
"SIREG3": 0x153,
"SIREG4": 0x155,
"SIREG5": 0x156,
"SIREG6": 0x157,
"STOPEI": 0x15C,
"SCTRDEPTH": 0x15F,
"SATP": 0x180,
"SRMCFG": 0x181,
"SPMPEN": 0x183,
"VSSTATUS": 0x200,
"VSIE": 0x204,
"VSTVEC": 0x205,
"VSSCRATCH": 0x240,
"VSEPC": 0x241,
"VSCAUSE": 0x242,
"VSTVAL": 0x243,
"VSIP": 0x244,
"VSTIMECMP": 0x24D,
"VSCTRCTL": 0x24E,
"VSISELECT": 0x250,
"VSIREG": 0x251,
"VSIREG2": 0x252,
"VSIREG3": 0x253,
"VSIREG4": 0x255,
"VSIREG5": 0x256,
"VSIREG6": 0x257,
"VSTOPEI": 0x25C,
"VSATP": 0x280,
"MSTATUS": 0x300,
"MISA": 0x301,
"MEDELEG": 0x302,
"MIDELEG": 0x303,
"MIE": 0x304,
"MTVEC": 0x305,
"MCOUNTEREN": 0x306,
"MTVT": 0x307,
"MVIEN": 0x308,
"MVIP": 0x309,
"MENVCFG": 0x30A,
"MSTATEEN0": 0x30C,
"MSTATEEN1": 0x30D,
"MSTATEEN2": 0x30E,
"MSTATEEN3": 0x30F,
"MPMPDELEG": 0x316,
"MCOUNTINHIBIT": 0x320,
"MCYCLECFG": 0x321,
"MINSTRETCFG": 0x322,
"MHPMEVENT3": 0x323,
"MHPMEVENT4": 0x324,
"MHPMEVENT5": 0x325,
"MHPMEVENT6": 0x326,
"MHPMEVENT7": 0x327,
"MHPMEVENT8": 0x328,
"MHPMEVENT9": 0x329,
"MHPMEVENT10": 0x32A,
"MHPMEVENT11": 0x32B,
"MHPMEVENT12": 0x32C,
"MHPMEVENT13": 0x32D,
"MHPMEVENT14": 0x32E,
"MHPMEVENT15": 0x32F,
"MHPMEVENT16": 0x330,
"MHPMEVENT17": 0x331,
"MHPMEVENT18": 0x332,
"MHPMEVENT19": 0x333,
"MHPMEVENT20": 0x334,
"MHPMEVENT21": 0x335,
"MHPMEVENT22": 0x336,
"MHPMEVENT23": 0x337,
"MHPMEVENT24": 0x338,
"MHPMEVENT25": 0x339,
"MHPMEVENT26": 0x33A,
"MHPMEVENT27": 0x33B,
"MHPMEVENT28": 0x33C,
"MHPMEVENT29": 0x33D,
"MHPMEVENT30": 0x33E,
"MHPMEVENT31": 0x33F,
"MSCRATCH": 0x340,
"MEPC": 0x341,
"MCAUSE": 0x342,
"MTVAL": 0x343,
"MIP": 0x344,
"MNXTI": 0x345,
"MINTSTATUS": 0x346,
"MSCRATCHCSW": 0x348,
"MSCRATCHCSWL": 0x349,
"MTINST": 0x34A,
"MTVAL2": 0x34B,
"MCTRCTL": 0x34E,
"MISELECT": 0x350,
"MIREG": 0x351,
"MIREG2": 0x352,
"MIREG3": 0x353,
"MIREG4": 0x355,
"MIREG5": 0x356,
"MIREG6": 0x357,
"MTOPEI": 0x35C,
"PMPCFG0": 0x3A0,
"PMPCFG1": 0x3A1,
"PMPCFG2": 0x3A2,
"PMPCFG3": 0x3A3,
"PMPCFG4": 0x3A4,
"PMPCFG5": 0x3A5,
"PMPCFG6": 0x3A6,
"PMPCFG7": 0x3A7,
"PMPCFG8": 0x3A8,
"PMPCFG9": 0x3A9,
"PMPCFG10": 0x3AA,
"PMPCFG11": 0x3AB,
"PMPCFG12": 0x3AC,
"PMPCFG13": 0x3AD,
"PMPCFG14": 0x3AE,
"PMPCFG15": 0x3AF,
"PMPADDR0": 0x3B0,
"PMPADDR1": 0x3B1,
"PMPADDR2": 0x3B2,
"PMPADDR3": 0x3B3,
"PMPADDR4": 0x3B4,
"PMPADDR5": 0x3B5,
"PMPADDR6": 0x3B6,
"PMPADDR7": 0x3B7,
"PMPADDR8": 0x3B8,
"PMPADDR9": 0x3B9,
"PMPADDR10": 0x3BA,
"PMPADDR11": 0x3BB,
"PMPADDR12": 0x3BC,
"PMPADDR13": 0x3BD,
"PMPADDR14": 0x3BE,
"PMPADDR15": 0x3BF,
"PMPADDR16": 0x3C0,
"PMPADDR17": 0x3C1,
"PMPADDR18": 0x3C2,
"PMPADDR19": 0x3C3,
"PMPADDR20": 0x3C4,
"PMPADDR21": 0x3C5,
"PMPADDR22": 0x3C6,
"PMPADDR23": 0x3C7,
"PMPADDR24": 0x3C8,
"PMPADDR25": 0x3C9,
"PMPADDR26": 0x3CA,
"PMPADDR27": 0x3CB,
"PMPADDR28": 0x3CC,
"PMPADDR29": 0x3CD,
"PMPADDR30": 0x3CE,
"PMPADDR31": 0x3CF,
"PMPADDR32": 0x3D0,
"PMPADDR33": 0x3D1,
"PMPADDR34": 0x3D2,
"PMPADDR35": 0x3D3,
"PMPADDR36": 0x3D4,
"PMPADDR37": 0x3D5,
"PMPADDR38": 0x3D6,
"PMPADDR39": 0x3D7,
"PMPADDR40": 0x3D8,
"PMPADDR41": 0x3D9,
"PMPADDR42": 0x3DA,
"PMPADDR43": 0x3DB,
"PMPADDR44": 0x3DC,
"PMPADDR45": 0x3DD,
"PMPADDR46": 0x3DE,
"PMPADDR47": 0x3DF,
"PMPADDR48": 0x3E0,
"PMPADDR49": 0x3E1,
"PMPADDR50": 0x3E2,
"PMPADDR51": 0x3E3,
"PMPADDR52": 0x3E4,
"PMPADDR53": 0x3E5,
"PMPADDR54": 0x3E6,
"PMPADDR55": 0x3E7,
"PMPADDR56": 0x3E8,
"PMPADDR57": 0x3E9,
"PMPADDR58": 0x3EA,
"PMPADDR59": 0x3EB,
"PMPADDR60": 0x3EC,
"PMPADDR61": 0x3ED,
"PMPADDR62": 0x3EE,
"PMPADDR63": 0x3EF,
"SCONTEXT": 0x5A8,
"HSTATUS": 0x600,
"HEDELEG": 0x602,
"HIDELEG": 0x603,
"HIE": 0x604,
"HTIMEDELTA": 0x605,
"HCOUNTEREN": 0x606,
"HGEIE": 0x607,
"HVIEN": 0x608,
"HVICTL": 0x609,
"HENVCFG": 0x60A,
"HSTATEEN0": 0x60C,
"HSTATEEN1": 0x60D,
"HSTATEEN2": 0x60E,
"HSTATEEN3": 0x60F,
"HTVAL": 0x643,
"HIP": 0x644,
"HVIP": 0x645,
"HVIPRIO1": 0x646,
"HVIPRIO2": 0x647,
"HTINST": 0x64A,
"HGATP": 0x680,
"HCONTEXT": 0x6A8,
"MSECCFG": 0x747,
"TSELECT": 0x7A0,
"TDATA1": 0x7A1,
"TDATA2": 0x7A2,
"TDATA3": 0x7A3,
"TINFO": 0x7A4,
"TCONTROL": 0x7A5,
"MCONTEXT": 0x7A8,
"MSCONTEXT": 0x7AA,
"DCSR": 0x7B0,
"DPC": 0x7B1,
"DSCRATCH0": 0x7B2,
"DSCRATCH1": 0x7B3,
"MCYCLE": 0xB00,
"MINSTRET": 0xB02,
"MHPMCOUNTER3": 0xB03,
"MHPMCOUNTER4": 0xB04,
"MHPMCOUNTER5": 0xB05,
"MHPMCOUNTER6": 0xB06,
"MHPMCOUNTER7": 0xB07,
"MHPMCOUNTER8": 0xB08,
"MHPMCOUNTER9": 0xB09,
"MHPMCOUNTER10": 0xB0A,
"MHPMCOUNTER11": 0xB0B,
"MHPMCOUNTER12": 0xB0C,
"MHPMCOUNTER13": 0xB0D,
"MHPMCOUNTER14": 0xB0E,
"MHPMCOUNTER15": 0xB0F,
"MHPMCOUNTER16": 0xB10,
"MHPMCOUNTER17": 0xB11,
"MHPMCOUNTER18": 0xB12,
"MHPMCOUNTER19": 0xB13,
"MHPMCOUNTER20": 0xB14,
"MHPMCOUNTER21": 0xB15,
"MHPMCOUNTER22": 0xB16,
"MHPMCOUNTER23": 0xB17,
"MHPMCOUNTER24": 0xB18,
"MHPMCOUNTER25": 0xB19,
"MHPMCOUNTER26": 0xB1A,
"MHPMCOUNTER27": 0xB1B,
"MHPMCOUNTER28": 0xB1C,
"MHPMCOUNTER29": 0xB1D,
"MHPMCOUNTER30": 0xB1E,
"MHPMCOUNTER31": 0xB1F,
"CYCLE": 0xC00,
"TIME": 0xC01,
"INSTRET": 0xC02,
"HPMCOUNTER3": 0xC03,
"HPMCOUNTER4": 0xC04,
"HPMCOUNTER5": 0xC05,
"HPMCOUNTER6": 0xC06,
"HPMCOUNTER7": 0xC07,
"HPMCOUNTER8": 0xC08,
"HPMCOUNTER9": 0xC09,
"HPMCOUNTER10": 0xC0A,
"HPMCOUNTER11": 0xC0B,
"HPMCOUNTER12": 0xC0C,
"HPMCOUNTER13": 0xC0D,
"HPMCOUNTER14": 0xC0E,
"HPMCOUNTER15": 0xC0F,
"HPMCOUNTER16": 0xC10,
"HPMCOUNTER17": 0xC11,
"HPMCOUNTER18": 0xC12,
"HPMCOUNTER19": 0xC13,
"HPMCOUNTER20": 0xC14,
"HPMCOUNTER21": 0xC15,
"HPMCOUNTER22": 0xC16,
"HPMCOUNTER23": 0xC17,
"HPMCOUNTER24": 0xC18,
"HPMCOUNTER25": 0xC19,
"HPMCOUNTER26": 0xC1A,
"HPMCOUNTER27": 0xC1B,
"HPMCOUNTER28": 0xC1C,
"HPMCOUNTER29": 0xC1D,
"HPMCOUNTER30": 0xC1E,
"HPMCOUNTER31": 0xC1F,
"VL": 0xC20,
"VTYPE": 0xC21,
"VLENB": 0xC22,
"SCOUNTOVF": 0xDA0,
"STOPI": 0xDB0,
"HGEIP": 0xE12,
"VSTOPI": 0xEB0,
"MVENDORID": 0xF11,
"MARCHID": 0xF12,
"MIMPID": 0xF13,
"MHARTID": 0xF14,
"MCONFIGPTR": 0xF15,
"MTOPI": 0xFB0,
}
// riscvCSRAddress resolves a CSR operand: an integer immediate or one of the
+110
View File
@@ -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)
}
}