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:
1 parent
e43fa39dc9
commit
5de9e985f8
2 files changed
+905
-54
No files matched your search
+245
-54
@@ -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 {
|
||||
|
||||
Reference in new issue
Block a user