feat(asm): encode the riscv64 vector arithmetic families

Assisted-by: GLM 5.3 Flash
This commit is contained in:
petrbalvin committed 2026-10-07 00:47:27 +02:00
1 parent 5de9e985f8
commit 778c297214
3 files changed
+1812 -102

No files matched your search

+564 -81
View File
@@ -3636,88 +3636,571 @@ func encodeRISCVVector(mnem string, ops []*ast.Operand) ([]byte, bool, error) {
// (0xcd07f657) from GOARCH=riscv64 go tool asm.
ivli := mnem == "VSETIVLI" || isImmOperand(ops[0])
return wordLE(riscvVSetEnc(ivli, avl, riscvVType(vsew, vlmul, vta, vma), rd)), true, nil
case "VADDVV", "VXORVV", "VMSNEVV":
// Vector-vector: INSTR vs1, vs2, vd.
if len(ops) != 3 {
return nil, true, fmt.Errorf("%s expects 3 operands, got %d", mnem, len(ops))
}
vs1, vs2, vd := reg(ops[0]), reg(ops[1]), reg(ops[2])
if vs1 < 0 || vs2 < 0 || vd < 0 {
return nil, true, fmt.Errorf("%s: invalid vector register", mnem)
}
funct6 := map[string]int{"VADDVV": 0x00, "VXORVV": 0x0B, "VMSNEVV": 0x19}[mnem]
return wordLE(riscvVVInstr(funct6, riscvVf3VV, int32(vs1), vs2, vd)), true, nil
case "VADDVX", "VMSEQVX":
// Vector-scalar: INSTR rs1, vs2, vd (the scalar in the rs1 field).
if len(ops) != 3 {
return nil, true, fmt.Errorf("%s expects 3 operands, got %d", mnem, len(ops))
}
rs1, vs2, vd := reg(ops[0]), reg(ops[1]), reg(ops[2])
if rs1 < 0 || vs2 < 0 || vd < 0 {
return nil, true, fmt.Errorf("%s: invalid register operand", mnem)
}
funct6 := 0x00
if mnem == "VMSEQVX" {
funct6 = 0x18
}
return wordLE(riscvVVInstr(funct6, riscvVf3VX, int32(rs1), vs2, vd)), true, nil
case "VSLLVI", "VSRLVI":
// Vector-immediate shift: INSTR $uimm, vs2, vd.
if len(ops) != 3 {
return nil, true, fmt.Errorf("%s expects 3 operands, got %d", mnem, len(ops))
}
imm := int(immFromOperand(ops[0]))
if imm < 0 || imm > 31 {
return nil, true, fmt.Errorf("%s: immediate out of range [0, 31]", mnem)
}
vs2, vd := reg(ops[1]), reg(ops[2])
if vs2 < 0 || vd < 0 {
return nil, true, fmt.Errorf("%s: invalid vector register", mnem)
}
funct6 := 0x25 // vsll.vi
if mnem == "VSRLVI" {
funct6 = 0x28 // vsrl.vi
}
return wordLE(riscvVVInstr(funct6, riscvVf3VI, int32(imm), vs2, vd)), true, nil
case "VFIRSTM":
// vmfirst.m rd, vs2: the unmasked form carries 0x11 in the rs1 field
// and sets the mask bit (funct7 = 0x20 | 1).
if len(ops) != 2 {
return nil, true, fmt.Errorf("VFIRSTM expects 2 operands, got %d", len(ops))
}
vs2, rd := reg(ops[0]), reg(ops[1])
if vs2 < 0 || rd < 0 {
return nil, true, fmt.Errorf("VFIRSTM: invalid register operand")
}
return wordLE(riscvVUnaryInstr(0x10, riscvVf3MV, 0x11, vs2, rd)), true, nil
case "VIDV":
// vid.v vd (vs2 must be v0; the unmasked form sets the mask bit).
if len(ops) != 1 {
return nil, true, fmt.Errorf("VIDV expects 1 operand, got %d", len(ops))
}
vd := reg(ops[0])
if vd < 0 {
return nil, true, fmt.Errorf("VIDV: invalid vector register")
}
return wordLE(riscvVUnaryInstr(0x14, riscvVf3MV, 0x11, 0, vd)), true, nil
case "VMV4RV":
// vmv4r.v vd, vs2: whole-register group move.
if len(ops) != 2 {
return nil, true, fmt.Errorf("VMV4RV expects 2 operands, got %d", len(ops))
}
vs2, vd := reg(ops[0]), reg(ops[1])
if vs2 < 0 || vd < 0 {
return nil, true, fmt.Errorf("VMV4RV: invalid vector register")
}
return wordLE(riscvVUnaryInstr(0x27, 0x3, 0x3, vs2, vd)), true, nil
}
return nil, false, nil
// Every remaining OP-V mnemonic the toolchain knows: the arithmetic
// table, dispatched by operand class.
return encodeRISCVVecOp(mnem, ops)
}
// encodeRISCVVecOp encodes one vector arithmetic instruction through the
// extracted table. The entry's class places the operands in the rs1 and vs2
// fields, an optional V0 between the sources and the destination clears the
// vm bit, and the transform classes rewrite the pseudo forms the toolchain
// expands before encoding (the swapped comparisons, VNEGV and friends).
func encodeRISCVVecOp(mnem string, ops []*ast.Operand) ([]byte, bool, error) {
op, ok := riscvVecOps[mnem]
if !ok {
return nil, false, nil
}
// vecReg reads a register operand; what names the field in the error.
vecReg := func(o *ast.Operand, what string) (int, error) {
r := regFromOperand(o)
if r < 0 {
return 0, fmt.Errorf("%s: invalid %s", mnem, what)
}
return r, nil
}
// vecImm reads the immediate the entry's form bounds: signed five bits
// [-16, 15], or unsigned [0, 31] for the shifts and slides.
vecImm := func(o *ast.Operand) (int32, error) {
if !isImmOperand(o) {
return 0, fmt.Errorf("%s expects an immediate first operand", mnem)
}
v := immFromOperand(o)
if op.immU {
if v < 0 || v > 31 {
return 0, fmt.Errorf("%s: immediate %d out of range [0, 31]", mnem, v)
}
} else if v < -16 || v > 15 {
return 0, fmt.Errorf("%s: immediate %d out of range [-16, 15]", mnem, v)
}
return int32(v), nil
}
// vecMask reads the optional mask operand: only V0 is lawful.
vecMask := func(o *ast.Operand) error {
if regFromOperand(o) != 0 {
return fmt.Errorf("%s: the vector mask register must be V0", mnem)
}
return nil
}
// vm carries the funct7 with the vm bit set for the unmasked form: the
// toolchain ORs 1 when no V0 follows the sources.
vm := func(masked bool) uint32 {
if !masked {
return op.funct7 | 1
}
return op.funct7
}
// word builds the instruction from the entry's fields.
word := func(funct7 uint32, rs1Field int32, vs2 int, funct3 uint32, vd int) ([]byte, bool, error) {
return wordLE(riscvVecWord(funct7, rs1Field, vs2, funct3, vd)), true, nil
}
// rename resolves a transform to its target table entry.
rename := func(to string) (riscvVecOp, error) {
t, ok := riscvVecOps[to]
if !ok {
return riscvVecOp{}, fmt.Errorf("%s: transform target %q not in the table", mnem, to)
}
return t, nil
}
switch op.class {
case vecVV:
// INSTR vs1|$imm, vs2 [, V0], vd.
if len(ops) != 3 && len(ops) != 4 {
return nil, true, fmt.Errorf("%s expects 3 or 4 operands, got %d", mnem, len(ops))
}
masked := len(ops) == 4
if masked {
if err := vecMask(ops[2]); err != nil {
return nil, true, err
}
}
vs2, err := vecReg(ops[1], "vs2")
if err != nil {
return nil, true, err
}
vd, err := vecReg(ops[len(ops)-1], "vd")
if err != nil {
return nil, true, err
}
var rs1Field int32
if op.imm {
if rs1Field, err = vecImm(ops[0]); err != nil {
return nil, true, err
}
} else {
var vs1 int
if vs1, err = vecReg(ops[0], "vs1"); err != nil {
return nil, true, err
}
rs1Field = int32(vs1)
}
return word(vm(masked), rs1Field, vs2, op.funct3, vd)
case vecMACC:
// INSTR vs2, vs1 [, V0], vd: the multiply-accumulate order, the
// addend in the rs1 field and the multiplicand in vs2.
if len(ops) != 3 && len(ops) != 4 {
return nil, true, fmt.Errorf("%s expects 3 or 4 operands, got %d", mnem, len(ops))
}
masked := len(ops) == 4
if masked {
if err := vecMask(ops[2]); err != nil {
return nil, true, err
}
}
vs2, err := vecReg(ops[0], "vs2")
if err != nil {
return nil, true, err
}
vd, err := vecReg(ops[len(ops)-1], "vd")
if err != nil {
return nil, true, err
}
var rs1Field int32
if op.imm {
if rs1Field, err = vecImm(ops[1]); err != nil {
return nil, true, err
}
} else {
var vs1 int
if vs1, err = vecReg(ops[1], "vs1"); err != nil {
return nil, true, err
}
rs1Field = int32(vs1)
}
return word(vm(masked), rs1Field, vs2, op.funct3, vd)
case vecSWAPVV:
// VMSGT*/VMSGE*/VMFGT*/VMFGE* swap the two sources and lower to the
// VMSLT*/VMSLE*/VMFLT*/VMFLE* entries the table carries.
if len(ops) != 3 && len(ops) != 4 {
return nil, true, fmt.Errorf("%s expects 3 or 4 operands, got %d", mnem, len(ops))
}
masked := len(ops) == 4
if masked {
if err := vecMask(ops[2]); err != nil {
return nil, true, err
}
}
t, err := rename(map[string]string{
"VMSGTVV": "VMSLTVV", "VMSGTUVV": "VMSLTUVV",
"VMSGEVV": "VMSLEVV", "VMSGEUVV": "VMSLEUVV",
"VMFGTVV": "VMFLTVV", "VMFGEVV": "VMFLEVV",
}[mnem])
if err != nil {
return nil, true, err
}
vs2, err := vecReg(ops[0], "vs2")
if err != nil {
return nil, true, err
}
vd, err := vecReg(ops[len(ops)-1], "vd")
if err != nil {
return nil, true, err
}
vs1, err := vecReg(ops[1], "vs1")
if err != nil {
return nil, true, err
}
f7 := t.funct7
if !masked {
f7 |= 1
}
return word(f7, int32(vs1), vs2, t.funct3, vd)
case vecSWAPVI:
// VMSLTVI and the VMSGE*VI forms subtract one from the immediate and
// lower to the VMSLE*/VMSGT* entries.
if len(ops) != 3 && len(ops) != 4 {
return nil, true, fmt.Errorf("%s expects 3 or 4 operands, got %d", mnem, len(ops))
}
masked := len(ops) == 4
if masked {
if err := vecMask(ops[2]); err != nil {
return nil, true, err
}
}
t, err := rename(map[string]string{
"VMSLTVI": "VMSLEVI", "VMSLTUVI": "VMSLEUVI",
"VMSGEVI": "VMSGTVI", "VMSGEUVI": "VMSGTUVI",
}[mnem])
if err != nil {
return nil, true, err
}
imm, err := vecImm(ops[0])
if err != nil {
return nil, true, err
}
imm--
if imm < -16 || imm > 15 {
return nil, true, fmt.Errorf("%s: immediate %d leaves [-16, 15] after the swap", mnem, imm+1)
}
vs2, err := vecReg(ops[1], "vs2")
if err != nil {
return nil, true, err
}
vd, err := vecReg(ops[len(ops)-1], "vd")
if err != nil {
return nil, true, err
}
f7 := t.funct7
if !masked {
f7 |= 1
}
return word(f7, imm, vs2, t.funct3, vd)
case vecUNARY, vecM2I:
// INSTR vs2 [, V0], vd: one vector source, the fixed rs1 field; the
// m2i members take the destination in the integer file.
if len(ops) != 2 && len(ops) != 3 {
return nil, true, fmt.Errorf("%s expects 2 or 3 operands, got %d", mnem, len(ops))
}
masked := len(ops) == 3
if masked {
if err := vecMask(ops[1]); err != nil {
return nil, true, err
}
}
vs2, err := vecReg(ops[0], "vs2")
if err != nil {
return nil, true, err
}
vd, err := vecReg(ops[len(ops)-1], "vd")
if err != nil {
return nil, true, err
}
return word(vm(masked), int32(op.rs1), vs2, op.funct3, vd)
case vecNEG:
// VNEGV, VWCVTXXV, VWCVTUXXV and VNCVTXXW read as one-operand forms
// of VRSUBVX, VWADDVX, VWADDUVX and VNSRLWX with X0 in the rs1 field.
if len(ops) != 2 && len(ops) != 3 {
return nil, true, fmt.Errorf("%s expects 2 or 3 operands, got %d", mnem, len(ops))
}
masked := len(ops) == 3
if masked {
if err := vecMask(ops[1]); err != nil {
return nil, true, err
}
}
t, err := rename(map[string]string{
"VNEGV": "VRSUBVX", "VWCVTXXV": "VWADDVX",
"VWCVTUXXV": "VWADDUVX", "VNCVTXXW": "VNSRLWX",
}[mnem])
if err != nil {
return nil, true, err
}
vs2, err := vecReg(ops[0], "vs2")
if err != nil {
return nil, true, err
}
vd, err := vecReg(ops[len(ops)-1], "vd")
if err != nil {
return nil, true, err
}
f7 := t.funct7
if !masked {
f7 |= 1
}
return word(f7, 0, vs2, t.funct3, vd)
case vecVNOT:
// VNOTV reads as VXORVI with the all-ones immediate.
if len(ops) != 2 && len(ops) != 3 {
return nil, true, fmt.Errorf("%s expects 2 or 3 operands, got %d", mnem, len(ops))
}
masked := len(ops) == 3
if masked {
if err := vecMask(ops[1]); err != nil {
return nil, true, err
}
}
t, err := rename("VXORVI")
if err != nil {
return nil, true, err
}
vs2, err := vecReg(ops[0], "vs2")
if err != nil {
return nil, true, err
}
vd, err := vecReg(ops[len(ops)-1], "vd")
if err != nil {
return nil, true, err
}
f7 := t.funct7
if !masked {
f7 |= 1
}
return word(f7, -1, vs2, t.funct3, vd)
case vecVFABS:
// VFABSV and VFNEGV read as VFSGNJXVV/VFSGNJNVVV with the source in
// both the rs1 and vs2 fields.
if len(ops) != 2 && len(ops) != 3 {
return nil, true, fmt.Errorf("%s expects 2 or 3 operands, got %d", mnem, len(ops))
}
masked := len(ops) == 3
if masked {
if err := vecMask(ops[1]); err != nil {
return nil, true, err
}
}
t, err := rename(map[string]string{
"VFABSV": "VFSGNJXVV", "VFNEGV": "VFSGNJNVV",
}[mnem])
if err != nil {
return nil, true, err
}
vs2, err := vecReg(ops[0], "vs2")
if err != nil {
return nil, true, err
}
vd, err := vecReg(ops[len(ops)-1], "vd")
if err != nil {
return nil, true, err
}
f7 := t.funct7
if !masked {
f7 |= 1
}
return word(f7, int32(vs2), vs2, t.funct3, vd)
case vecVMVV:
// INSTR vs2, vd (vmv.v.v/vmv.v.x): the source in the rs1 field, V0
// fixed in vs2, the vm bit from the table.
if len(ops) != 2 {
return nil, true, fmt.Errorf("%s expects 2 operands, got %d", mnem, len(ops))
}
vs2, err := vecReg(ops[0], "vs2")
if err != nil {
return nil, true, err
}
vd, err := vecReg(ops[1], "vd")
if err != nil {
return nil, true, err
}
return word(op.funct7, int32(vs2), 0, op.funct3, vd)
case vecVMVI:
// INSTR $imm, vd (vmv.v.i): the immediate in the rs1 field, V0 in
// vs2, the vm bit from the table.
if len(ops) != 2 {
return nil, true, fmt.Errorf("%s expects 2 operands, got %d", mnem, len(ops))
}
imm, err := vecImm(ops[0])
if err != nil {
return nil, true, err
}
vd, err := vecReg(ops[1], "vd")
if err != nil {
return nil, true, err
}
return word(op.funct7, imm, 0, op.funct3, vd)
case vecVFMVVF:
// INSTR fs1, vd (vfmv.v.f): the scalar in the rs1 field, V0 in vs2.
if len(ops) != 2 {
return nil, true, fmt.Errorf("%s expects 2 operands, got %d", mnem, len(ops))
}
fs1, err := vecReg(ops[0], "fs1")
if err != nil {
return nil, true, err
}
vd, err := vecReg(ops[1], "vd")
if err != nil {
return nil, true, err
}
return word(op.funct7, int32(fs1), 0, op.funct3, vd)
case vecTWO:
// INSTR vs2, vd: two-operand forms with the fixed rs1 field (the
// extensions and conversions, the whole-register moves, the scalar
// reads).
if len(ops) != 2 {
return nil, true, fmt.Errorf("%s expects 2 operands, got %d", mnem, len(ops))
}
vs2, err := vecReg(ops[0], "vs2")
if err != nil {
return nil, true, err
}
vd, err := vecReg(ops[1], "vd")
if err != nil {
return nil, true, err
}
return word(op.funct7, int32(op.rs1), vs2, op.funct3, vd)
case vecTWOX:
// INSTR xs1|fs1, vd: two-operand forms with the fixed vs2 field
// (vmv.s.x and vfmv.s.f).
if len(ops) != 2 {
return nil, true, fmt.Errorf("%s expects 2 operands, got %d", mnem, len(ops))
}
rs1, err := vecReg(ops[0], "rs1")
if err != nil {
return nil, true, err
}
vd, err := vecReg(ops[1], "vd")
if err != nil {
return nil, true, err
}
return word(op.funct7, int32(rs1), int(op.rs1), op.funct3, vd)
case vecADC:
// INSTR vs1|$imm, vs2, V0, vd: the carry forms, the mask mandatory,
// V0 rejected as the destination.
if len(ops) != 4 {
return nil, true, fmt.Errorf("%s expects 4 operands, got %d", mnem, len(ops))
}
if err := vecMask(ops[2]); err != nil {
return nil, true, err
}
vs2, err := vecReg(ops[1], "vs2")
if err != nil {
return nil, true, err
}
vd, err := vecReg(ops[3], "vd")
if err != nil {
return nil, true, err
}
if vd == 0 {
return nil, true, fmt.Errorf("%s: invalid destination register V0", mnem)
}
var rs1Field int32
if op.imm {
if rs1Field, err = vecImm(ops[0]); err != nil {
return nil, true, err
}
} else {
var vs1 int
if vs1, err = vecReg(ops[0], "vs1"); err != nil {
return nil, true, err
}
rs1Field = int32(vs1)
}
return word(op.funct7, rs1Field, vs2, op.funct3, vd)
case vecMERGE:
// INSTR vs1|fs1|$imm, vs2, V0, vd: the merge forms, the mask
// mandatory, V0 allowed as the destination.
if len(ops) != 4 {
return nil, true, fmt.Errorf("%s expects 4 operands, got %d", mnem, len(ops))
}
if err := vecMask(ops[2]); err != nil {
return nil, true, err
}
vs2, err := vecReg(ops[1], "vs2")
if err != nil {
return nil, true, err
}
vd, err := vecReg(ops[3], "vd")
if err != nil {
return nil, true, err
}
var rs1Field int32
if op.imm {
if rs1Field, err = vecImm(ops[0]); err != nil {
return nil, true, err
}
} else {
var vs1 int
if vs1, err = vecReg(ops[0], "vs1"); err != nil {
return nil, true, err
}
rs1Field = int32(vs1)
}
return word(op.funct7, rs1Field, vs2, op.funct3, vd)
case vecVMADC:
// INSTR vs1|$imm, vs2, vd: the carry-producing forms; the third
// operand names the destination and may be V0.
if len(ops) != 3 {
return nil, true, fmt.Errorf("%s expects 3 operands, got %d", mnem, len(ops))
}
vs2, err := vecReg(ops[1], "vs2")
if err != nil {
return nil, true, err
}
vd, err := vecReg(ops[2], "vd")
if err != nil {
return nil, true, err
}
var rs1Field int32
if op.imm {
if rs1Field, err = vecImm(ops[0]); err != nil {
return nil, true, err
}
} else {
var vs1 int
if vs1, err = vecReg(ops[0], "vs1"); err != nil {
return nil, true, err
}
rs1Field = int32(vs1)
}
return word(op.funct7, rs1Field, vs2, op.funct3, vd)
case vecMM:
// INSTR vs1, vs2, vd: the mask-mask forms. VMMVM and VMNOTM take
// two operands and fold the second source into the first; the vm
// bit stays as the table carries it.
folded := mnem == "VMMVM" || mnem == "VMNOTM"
if (folded && len(ops) != 2) || (!folded && len(ops) != 3) {
return nil, true, fmt.Errorf("%s expects %d operands, got %d", mnem, map[bool]int{true: 2, false: 3}[folded], len(ops))
}
vs1, err := vecReg(ops[0], "vs1")
if err != nil {
return nil, true, err
}
vs2 := vs1
if !folded {
if vs2, err = vecReg(ops[1], "vs2"); err != nil {
return nil, true, err
}
}
vd, err := vecReg(ops[len(ops)-1], "vd")
if err != nil {
return nil, true, err
}
return word(op.funct7, int32(vs1), vs2, op.funct3, vd)
case vecVMCLR:
// INSTR vd: the whole-mask clears and sets, one register in all
// three fields.
if len(ops) != 1 {
return nil, true, fmt.Errorf("%s expects 1 operand, got %d", mnem, len(ops))
}
t, err := rename(map[string]string{
"VMCLRM": "VMXORMM", "VMSETM": "VMXNORMM",
}[mnem])
if err != nil {
return nil, true, err
}
r, err := vecReg(ops[0], "vd")
if err != nil {
return nil, true, err
}
return word(t.funct7, int32(r), r, t.funct3, r)
case vecVID:
// INSTR [V0,] vd: the element index, the mask before the destination.
if len(ops) != 1 && len(ops) != 2 {
return nil, true, fmt.Errorf("%s expects 1 or 2 operands, got %d", mnem, len(ops))
}
masked := len(ops) == 2
if masked {
if err := vecMask(ops[0]); err != nil {
return nil, true, err
}
}
vd, err := vecReg(ops[len(ops)-1], "vd")
if err != nil {
return nil, true, err
}
return word(vm(masked), int32(op.rs1), 0, op.funct3, vd)
}
return nil, true, fmt.Errorf("%s: unhandled vector operand class", mnem)
}
// riscvVTypeToken parses a vsetvli configuration token (E8, M8, MF2 and