diff --git a/asm/riscv_assemble.go b/asm/riscv_assemble.go index 7f44eaa..b31b9a5 100644 --- a/asm/riscv_assemble.go +++ b/asm/riscv_assemble.go @@ -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 diff --git a/asm/riscv_csr_test.go b/asm/riscv_csr_test.go new file mode 100644 index 0000000..e425e4a --- /dev/null +++ b/asm/riscv_csr_test.go @@ -0,0 +1,110 @@ +// Copyright (c) 2026 Petr Balvín (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) + } +}