fix(asm): reject the operand shapes the toolchain rejects on riscv64

Assisted-by: GLM 5.3 Flash
This commit is contained in:
petrbalvin committed 2026-10-07 19:40:32 +02:00
1 parent 6c66f5bbd9
commit f0942ff7f3
3 files changed
+1014 -456

No files matched your search

+817 -348
View File
File diff suppressed because it is too large. Load diff
+195 -106
View File
@@ -3,153 +3,192 @@
package asm
import "strings"
// RISC-V register encoding: maps register names to their 5-bit numbers.
// The Go assembler uses the standard RISC-V ABI naming.
// riscvRegNum returns the 5-bit register number for a RISC-V register name.
// Returns -1 if the register is not recognized.
func riscvRegNum(name string) int {
n, _ := riscvBankedRegNum(name)
return n
}
// riscvRegBank classifies a RISC-V register name by its bank: the integer
// file (X0-X31 with the ABI aliases), the floating-point file (F0-F31 with
// the FP aliases) and the vector file (V0-V31). The toolchain's validate
// stage checks every operand against its instruction's bank and rejects the
// wrong one; the bank is what makes "F1" and "X1" different operands even
// though both encode as the number 1.
type riscvRegBank uint8
const (
riscvBankNone riscvRegBank = iota // not a register name
riscvBankInt // X0-X31, ZERO, RA, SP, ...
riscvBankFloat // F0-F31, FT0-FT11, FS0-FS11, FA0-FA7
riscvBankVec // V0-V31
)
// String names the bank the way the bank-mismatch diagnostics spell it.
func (b riscvRegBank) String() string {
switch b {
case riscvBankInt:
return "integer"
case riscvBankFloat:
return "float"
case riscvBankVec:
return "vector"
}
return "none"
}
// riscvBankedRegNum returns the 5-bit register number for a name together
// with the bank the name belongs to. riscvBankNone with a negative number
// names no register at all.
func riscvBankedRegNum(name string) (int, riscvRegBank) {
switch name {
// Floating-point registers (F0-F31).
case "F0", "FT0":
return 0, riscvBankFloat
case "F1", "FT1":
return 1, riscvBankFloat
case "F2", "FT2":
return 2, riscvBankFloat
case "F3", "FT3":
return 3, riscvBankFloat
case "F4", "FT4":
return 4, riscvBankFloat
case "F5", "FT5":
return 5, riscvBankFloat
case "F6", "FT6":
return 6, riscvBankFloat
case "F7", "FT7":
return 7, riscvBankFloat
case "F8", "FS0":
return 8, riscvBankFloat
case "F9", "FS1":
return 9, riscvBankFloat
case "F10", "FA0":
return 10, riscvBankFloat
case "F11", "FA1":
return 11, riscvBankFloat
case "F12", "FA2":
return 12, riscvBankFloat
case "F13", "FA3":
return 13, riscvBankFloat
case "F14", "FA4":
return 14, riscvBankFloat
case "F15", "FA5":
return 15, riscvBankFloat
case "F16", "FA6":
return 16, riscvBankFloat
case "F17", "FA7":
return 17, riscvBankFloat
case "F18", "FS2":
return 18, riscvBankFloat
case "F19", "FS3":
return 19, riscvBankFloat
case "F20", "FS4":
return 20, riscvBankFloat
case "F21", "FS5":
return 21, riscvBankFloat
case "F22", "FS6":
return 22, riscvBankFloat
case "F23", "FS7":
return 23, riscvBankFloat
case "F24", "FS8":
return 24, riscvBankFloat
case "F25", "FS9":
return 25, riscvBankFloat
case "F26", "FS10":
return 26, riscvBankFloat
case "F27", "FS11":
return 27, riscvBankFloat
case "F28", "FT8":
return 28, riscvBankFloat
case "F29", "FT9":
return 29, riscvBankFloat
case "F30", "FT10":
return 30, riscvBankFloat
case "F31", "FT11":
return 31, riscvBankFloat
}
// Vector registers V0-V31 (the "V" extension). They share the
// register numbering with the integer file: a bare number 0-31.
if len(name) >= 2 && name[0] == 'V' {
if n, ok := parseRegDigits(name[1:], 31); ok {
return n, riscvBankVec
}
}
switch name {
// Numbered integer registers.
case "X0", "ZERO":
return 0
return 0, riscvBankInt
case "X1", "RA", "LR":
return 1
return 1, riscvBankInt
case "X2", "SP":
return 2
return 2, riscvBankInt
case "X3", "GP":
return 3
return 3, riscvBankInt
case "X4", "TP":
return 4
return 4, riscvBankInt
case "X5", "T0":
return 5
return 5, riscvBankInt
case "X6", "T1":
return 6
return 6, riscvBankInt
case "X7", "T2":
return 7
return 7, riscvBankInt
case "X8", "S0", "FP":
return 8
return 8, riscvBankInt
case "X9", "S1":
return 9
return 9, riscvBankInt
case "X10", "A0":
return 10
return 10, riscvBankInt
case "X11", "A1":
return 11
return 11, riscvBankInt
case "X12", "A2":
return 12
return 12, riscvBankInt
case "X13", "A3":
return 13
return 13, riscvBankInt
case "X14", "A4":
return 14
return 14, riscvBankInt
case "X15", "A5":
return 15
return 15, riscvBankInt
case "X16", "A6":
return 16
return 16, riscvBankInt
case "X17", "A7":
return 17
return 17, riscvBankInt
case "X18", "S2":
return 18
return 18, riscvBankInt
case "X19", "S3":
return 19
return 19, riscvBankInt
case "X20", "S4":
return 20
return 20, riscvBankInt
case "X21", "S5":
return 21
return 21, riscvBankInt
case "X22", "S6":
return 22
return 22, riscvBankInt
case "X23", "S7":
return 23
return 23, riscvBankInt
case "X24", "S8":
return 24
return 24, riscvBankInt
case "X25", "S9":
return 25
return 25, riscvBankInt
case "X26", "S10", "CTXT":
return 26
return 26, riscvBankInt
case "X27", "S11", "g":
return 27
return 27, riscvBankInt
case "X28", "T3":
return 28
return 28, riscvBankInt
case "X29", "T4":
return 29
return 29, riscvBankInt
case "X30", "T5":
return 30
return 30, riscvBankInt
case "X31", "T6", "TMP":
return 31
// Floating-point registers (F0-F31).
case "F0", "FT0":
return 0
case "F1", "FT1":
return 1
case "F2", "FT2":
return 2
case "F3", "FT3":
return 3
case "F4", "FT4":
return 4
case "F5", "FT5":
return 5
case "F6", "FT6":
return 6
case "F7", "FT7":
return 7
case "F8", "FS0":
return 8
case "F9", "FS1":
return 9
case "F10", "FA0":
return 10
case "F11", "FA1":
return 11
case "F12", "FA2":
return 12
case "F13", "FA3":
return 13
case "F14", "FA4":
return 14
case "F15", "FA5":
return 15
case "F16", "FA6":
return 16
case "F17", "FA7":
return 17
case "F18", "FS2":
return 18
case "F19", "FS3":
return 19
case "F20", "FS4":
return 20
case "F21", "FS5":
return 21
case "F22", "FS6":
return 22
case "F23", "FS7":
return 23
case "F24", "FS8":
return 24
case "F25", "FS9":
return 25
case "F26", "FS10":
return 26
case "F27", "FS11":
return 27
case "F28", "FT8":
return 28
case "F29", "FT9":
return 29
case "F30", "FT10":
return 30
case "F31", "FT11":
return 31
default:
// Vector registers V0-V31 (the "V" extension). They share the
// register numbering with the integer file: a bare number 0-31.
if len(name) >= 2 && name[0] == 'V' {
if n, ok := parseRegDigits(name[1:], 31); ok {
return n
}
}
return -1
return 31, riscvBankInt
}
return -1, riscvBankNone
}
// parseRegDigits parses a decimal register suffix and reports whether it is
@@ -472,6 +511,56 @@ var riscvCvtTable = map[string]riscvCvtEnc{
"FMVSX": {0x78, 0x0, 0x0, 0x53},
}
// riscvCvtBanks answers which register bank each operand of an FP
// conversion or move carries, read from the mnemonic's direction: FCVTWS
// moves float32 into an integer register (rd integer, rs1 float), FCVTSW
// the reverse, FCLASS reads a float into an integer mask and the FMV
// spellings follow their X position. The rd bank comes first.
func riscvCvtBanks(mnem string) (riscvRegBank, riscvRegBank) {
// The classify and bit-move forms.
switch {
case strings.HasPrefix(mnem, "FCLASS"):
return riscvBankInt, riscvBankFloat
case strings.HasPrefix(mnem, "FMV"):
if len(mnem) > 3 && mnem[3] == 'X' {
return riscvBankInt, riscvBankFloat // FMVXD, FMVXW, FMVXS
}
return riscvBankFloat, riscvBankInt // FMVDX, FMVWX, FMVFS, FMVSX
}
if !strings.HasPrefix(mnem, "FCVT") || len(mnem) < 6 {
return riscvBankNone, riscvBankNone
}
rest := mnem[4:]
// intWU/LU + floatSDQ: float → integer.
switch rest[:2] {
case "WU", "LU":
if isRiscvWidthLetter(rest[2]) {
return riscvBankInt, riscvBankFloat
}
}
switch rest[0] {
case 'W', 'L':
if isRiscvWidthLetter(rest[1]) {
return riscvBankInt, riscvBankFloat
}
}
// floatSDQ + intWU/LU: integer → float.
if isRiscvWidthLetter(rest[0]) {
switch rest[1:] {
case "W", "WU", "L", "LU":
return riscvBankFloat, riscvBankInt
}
// float → float width conversion.
return riscvBankFloat, riscvBankFloat
}
return riscvBankNone, riscvBankNone
}
// isRiscvWidthLetter reports whether c names a float width (S, D or Q).
func isRiscvWidthLetter(c byte) bool {
return c == 'S' || c == 'D' || c == 'Q'
}
// riscvCvtType encodes an FP conversion instruction.
// Layout: funct7 | rs2(convtype) | rs1 | funct3(rm) | rd | opcode.
func riscvCvtType(enc riscvCvtEnc, rd, rs1 int) uint32 {
+2 -2
View File
@@ -209,12 +209,12 @@ func TestRISCVToolchainRejections(t *testing.T) {
{
name: "fence predecessor",
src: "\tFENCE X, W\n",
want: "FENCE: invalid predecessor operand",
want: "invalid FENCE predecessor operand",
},
{
name: "fence successor",
src: "\tFENCE W, Q\n",
want: "FENCE: invalid successor operand",
want: "invalid FENCE successor operand",
},
{
name: "immediate CSR pseudo with register",