feat(asm): encode the riscv64 vector load and store families

The vector memory section stopped at the three hand-written shapes the
GOROOT kernels use: every other spelling the toolchain accepts, the
width variants, the constant-stride and indexed accesses, the segment
families, the fault-only-first loads, the whole-register moves and the
bit-mask pair were names without an encoder.  The mnemonic now parses
into its own fields (direction, segment count, addressing mode, width,
fault-only-first and whole-register markers) and one encoder lays the
word down, with the optional V0 mask operand and the toolchain's
operand shapes.  VSETVL joins the configuration settings.  The
toolchain's whole vector memory section, six hundred and twenty-eight
statements of masked and unmasked forms, is a differential test against
the oracle, word for word.

Assisted-by: GLM 5.3 Flash
This commit is contained in:
petrbalvin committed 2026-10-07 00:47:27 +02:00
1 parent e43fa39dc9
commit 5de9e985f8
2 files changed
+905 -54

No files matched your search

+245 -54
View File
@@ -3560,7 +3560,28 @@ func rvcCB(funct3, rs1 uint32, off int32) uint16 {
// the vector mnemonics.
func encodeRISCVVector(mnem string, ops []*ast.Operand) ([]byte, bool, error) {
reg := regFromOperand
// The general vector load and store families: unit, constant-stride and
// indexed, with and without segments, the fault-only-first loads and the
// whole-register moves. riscvIsVecLS parses the mnemonic.
if riscvIsVecLS(mnem) {
return encodeRISCVVecLS(mnem, ops)
}
switch mnem {
case "VSETVL":
// INSTR rs2, rs1, rd: the register form of the configuration
// setting. The toolchain writes funct7 0x40 above the standard
// fields, its own disambiguator against the immediate forms.
if len(ops) != 3 {
return nil, true, fmt.Errorf("%s expects 3 operands, got %d", mnem, len(ops))
}
rs2 := reg(ops[0])
rs1 := reg(ops[1])
rd := reg(ops[2])
if rs1 < 0 || rs2 < 0 || rd < 0 {
return nil, true, fmt.Errorf("%s: invalid register", mnem)
}
return wordLE(riscvRType(riscvEnc{0x57, 0x7, 0x40}, rd, rs1, rs2)), true, nil
case "VSETVLI", "VSETIVLI":
// INSTR avl, vsew, vlmul, vta, vma, rd.
if len(ops) != 6 {
@@ -3616,60 +3637,6 @@ func encodeRISCVVector(mnem string, ops []*ast.Operand) ([]byte, bool, error) {
ivli := mnem == "VSETIVLI" || isImmOperand(ops[0])
return wordLE(riscvVSetEnc(ivli, avl, riscvVType(vsew, vlmul, vta, vma), rd)), true, nil
case "VLE8V":
// Unit-stride load: INSTR (base), vd.
if len(ops) != 2 {
return nil, true, fmt.Errorf("%s expects 2 operands, got %d", mnem, len(ops))
}
rs1, ok := riscvVecMem(ops[0])
if !ok {
return nil, true, fmt.Errorf("%s: invalid memory operand", mnem)
}
vd := reg(ops[1])
if vd < 0 {
return nil, true, fmt.Errorf("%s: invalid vector register", mnem)
}
return wordLE(riscvVLSType(0x07, 0, 0, 0, 0, rs1, vd)), true, nil
case "VSE8V", "VSE32V":
// Unit-stride store: INSTR vs3, (base).
if len(ops) != 2 {
return nil, true, fmt.Errorf("%s expects 2 operands, got %d", mnem, len(ops))
}
vs3 := reg(ops[0])
rs1, ok := riscvVecMem(ops[1])
if !ok {
return nil, true, fmt.Errorf("%s: invalid memory operand", mnem)
}
if vs3 < 0 {
return nil, true, fmt.Errorf("%s: invalid vector register", mnem)
}
width := 0
if mnem == "VSE32V" {
width = 6
}
return wordLE(riscvVLSType(0x27, 0, 0, width, 0, rs1, vs3)), true, nil
case "VLSSEG4E32V", "VLSSEG8E32V":
// Constant-stride segmented load: INSTR (base), stride, vd.
if len(ops) != 3 {
return nil, true, fmt.Errorf("%s expects 3 operands, got %d", mnem, len(ops))
}
rs1, ok := riscvVecMem(ops[0])
if !ok {
return nil, true, fmt.Errorf("%s: invalid memory operand", mnem)
}
rs2 := reg(ops[1])
vd := reg(ops[2])
if rs2 < 0 || vd < 0 {
return nil, true, fmt.Errorf("%s: invalid register operand", mnem)
}
nf := 3 // 4 fields
if mnem == "VLSSEG8E32V" {
nf = 7 // 8 fields
}
return wordLE(riscvVLSType(0x07, nf, 2, 6, int32(rs2), rs1, vd)), true, nil
case "VADDVV", "VXORVV", "VMSNEVV":
// Vector-vector: INSTR vs1, vs2, vd.
if len(ops) != 3 {
@@ -3781,6 +3748,230 @@ func riscvVecMem(op *ast.Operand) (rs1 int, ok bool) {
return rs1, rs1 >= 0
}
// riscvVecLS is one parsed vector load/store mnemonic: the direction, the
// field counts and the fixed rs2 content (0 for plain forms, the
// fault-only-first marker, the mask pair's 11 or the whole-register marker).
type riscvVecLS struct {
load bool // true for the VL families, false for the VS families
nf int // segment count minus one
mop int // 0 unit, 1 indexed-ux, 2 constant-stride, 3 indexed-ox
width int // 0 = 8-bit, 5 = 16-bit, 6 = 32-bit, 7 = 64-bit
ff bool // fault-only-first: the fixed rs2 field carries 16
rs2f int // fixed rs2 field: the whole-register and mask markers
}
// riscvVecWidths maps the width segment of a vector load/store name onto the
// instruction's width field.
var riscvVecWidths = map[string]int{"8": 0, "16": 5, "32": 6, "64": 7}
// riscvParseVecLS parses a vector load/store mnemonic into its fields. The
// families the toolchain spells: the unit, constant-stride and indexed
// accesses (VLE8V, VLSE8V, VLUXEI8V, VLOXEI8V and the stores), each with its
// segment variants (VLSEG2E8V, VLSSEG2E8V, VLUXSEG2EI8V, ...), the
// fault-only-first loads (VLE8FFV, VLSEG2E8FFV), the whole-register moves
// (VL1RV, VL2RE64V, VS8RV) and the bit-mask pair (VLMV, VSMV).
func riscvParseVecLS(m string) (riscvVecLS, bool) {
// The whole-register spellings and the mask pair: exact names.
whole := func(load bool, nf, rs2f int) (riscvVecLS, bool) {
return riscvVecLS{load: load, nf: nf, rs2f: rs2f}, true
}
switch m {
case "VLMV":
return whole(true, 0, 11)
case "VSMV":
return whole(false, 0, 11)
case "VL1RV":
return whole(true, 0, 8)
case "VS1RV":
return whole(false, 0, 8)
case "VL2RV":
return whole(true, 1, 8)
case "VS2RV":
return whole(false, 1, 8)
case "VL4RV":
return whole(true, 3, 8)
case "VS4RV":
return whole(false, 3, 8)
case "VL8RV":
return whole(true, 7, 8)
case "VS8RV":
return whole(false, 7, 8)
}
// VL{n}RE{w}V: the whole-register loads with an explicit width; the
// encoding is the width-less spelling's with the width field filled.
if len(m) >= 7 && m[1] == 'L' && m[2] >= '1' && m[2] <= '8' && m[3:5] == "RE" && strings.HasSuffix(m, "V") {
n := int(m[2] - '0')
w, ok := riscvParseVecLSWidth(m[5 : len(m)-1])
if !ok {
return riscvVecLS{}, false
}
rs2f := 8
return riscvVecLS{load: true, nf: n - 1, width: w, rs2f: rs2f}, true
}
if len(m) < 4 || m[0] != 'V' || (m[1] != 'L' && m[1] != 'S') {
return riscvVecLS{}, false
}
v := riscvVecLS{load: m[1] == 'L'}
rest := m[2:]
// The segment families carry the count: SEG<n>E, SSEG<n>E, UXSEG<n>EI,
// OXSEG<n>EI.
for _, fam := range []struct {
prefix string
mop int
ei bool
}{
{"SSEG", 2, false},
{"UXSEG", 1, true},
{"OXSEG", 3, true},
{"SEG", 0, false},
} {
if !strings.HasPrefix(rest, fam.prefix) {
continue
}
tail := rest[len(fam.prefix):]
if len(tail) < 3 || tail[0] < '2' || tail[0] > '8' || tail[1] != 'E' {
return riscvVecLS{}, false
}
v.nf = int(tail[0] - '0')
v.nf-- // the field is the count minus one
tail = tail[2:]
if fam.ei {
if !strings.HasPrefix(tail, "I") {
return riscvVecLS{}, false
}
tail = tail[1:]
}
v.mop = fam.mop
rest = tail
break
}
if v.nf == 0 {
// The flat families: SE<w>V, UXEI<w>V, OXEI<w>V, E<w>V.
switch {
case strings.HasPrefix(rest, "SE"):
v.mop = 2
rest = rest[2:]
case strings.HasPrefix(rest, "UXEI"):
v.mop = 1
rest = rest[4:]
case strings.HasPrefix(rest, "OXEI"):
v.mop = 3
rest = rest[4:]
case strings.HasPrefix(rest, "E"):
rest = rest[1:]
default:
return riscvVecLS{}, false
}
}
// The tail: <width>V, or <width>FFV on the fault-only-first loads.
ff := false
if strings.HasSuffix(rest, "FFV") {
ff = v.load
rest = rest[:len(rest)-3]
} else if strings.HasSuffix(rest, "V") {
rest = rest[:len(rest)-1]
} else {
return riscvVecLS{}, false
}
w, ok := riscvParseVecLSWidth(rest)
if !ok {
return riscvVecLS{}, false
}
v.width = w
v.ff = ff
if ff {
v.rs2f = 16
}
return v, true
}
// riscvParseVecLSWidth parses a vector width segment ("8", "16", "32", "64")
// onto its width field. The second result reports whether the text is a
// width the families carry.
func riscvParseVecLSWidth(s string) (int, bool) {
w, ok := riscvVecWidths[s]
return w, ok
}
// riscvIsVecLS reports whether m is one of the vector load/store mnemonics
// encodeRISCVVecLS handles.
func riscvIsVecLS(m string) bool {
_, ok := riscvParseVecLS(m)
return ok
}
// encodeRISCVVecLS encodes one vector load or store. The operand shapes are
// the toolchain's: (base), vd for the unit loads; (base), rs2|vs2 [, V0], vd
// for the stride, indexed and segment forms with their optional V0 mask;
// stores mirror them with vs3 first and (base) last.
func encodeRISCVVecLS(mnem string, ops []*ast.Operand) ([]byte, bool, error) {
v, ok := riscvParseVecLS(mnem)
if !ok {
return nil, true, fmt.Errorf("unsupported vector load/store %q", mnem)
}
op := uint32(0x27)
if v.load {
op = 0x07
}
strided := v.mop == 2
indexed := v.mop == 1 || v.mop == 3
whole := v.rs2f == 2 || v.rs2f == 8
// Split the operands: the memory end fixes one operand, the register end
// the other, and a V0 beside the register end is the mask.
memIdx, regIdx := 0, len(ops)-1
if !v.load {
memIdx, regIdx = len(ops)-1, 0
}
rs1, ok := riscvVecMem(ops[memIdx])
if !ok {
return nil, true, fmt.Errorf("%s: invalid memory operand", mnem)
}
vd := regFromOperand(ops[regIdx])
if vd < 0 {
kind := "vd"
if !v.load {
kind = "vs1"
}
return nil, true, fmt.Errorf("%s: expected vector register in %s position", mnem, kind)
}
rs2 := v.rs2f
masked := false
for _, mid := range ops[min(memIdx, regIdx)+1 : max(memIdx, regIdx)] {
// The mask operand is the vector register V0: name-checked, so an
// integer X0 in the stride position is not mistaken for it.
if regFromOperand(mid) == 0 && strings.HasPrefix(strings.ToUpper(mid.Raw), "V") {
masked = true
continue
}
if !strided && !indexed {
return nil, true, fmt.Errorf("%s: too many operands for instruction", mnem)
}
if rs2 != v.rs2f {
return nil, true, fmt.Errorf("%s: too many operands for instruction", mnem)
}
rs2 = regFromOperand(mid)
if rs2 < 0 {
return nil, true, fmt.Errorf("%s: invalid register operand", mnem)
}
if indexed && strings.HasPrefix(strings.ToUpper(mid.Raw), "X") {
return nil, true, fmt.Errorf("%s: expected vector register in vs2 position", mnem)
}
if strided && !strings.HasPrefix(strings.ToUpper(mid.Raw), "X") {
return nil, true, fmt.Errorf("%s: expected integer register in rs2 position", mnem)
}
}
if masked && whole {
return nil, true, fmt.Errorf("%s: too many operands for instruction", mnem)
}
word := uint32(v.nf&7)<<29 | uint32(v.mop&3)<<26 | uint32(rs2&0x1F)<<20 |
uint32(rs1&0x1F)<<15 | uint32(v.width&7)<<12 | uint32(vd&0x1F)<<7 | op
if !masked {
word |= 1 << 25
}
return wordLE(word), true, nil
}
// Instruction type classifiers.
func isRTypeInstr(m string) bool {
switch m {