feat(riscv64,loong64): encode AMO atomics, vector slices and bit ops
Assisted-by: GLM 5.3 Flash
This commit is contained in:
+567
-3
@@ -224,6 +224,87 @@ func riscvInstrSize(instr *ast.Instr, fi riscvFrameInfo) int {
|
||||
}
|
||||
return riscvItypeImmediateSize(mnem, imm)
|
||||
}
|
||||
// The toolchain's synthesised instructions: some emit one word, others
|
||||
// expand to a fixed sequence.
|
||||
return riscvExtendedSize(mnem, ops)
|
||||
}
|
||||
|
||||
// riscvExtendedSize returns the encoded size of the instructions the
|
||||
// toolchain synthesises from other instructions (the ternary expansions and
|
||||
// the vector slice); every caller keeps the layout in step with
|
||||
// encodeRISCVExtended, which emits exactly these bytes.
|
||||
func riscvExtendedSize(mnem string, ops []*ast.Operand) int {
|
||||
switch mnem {
|
||||
case "NOP":
|
||||
// The toolchain drops a bare NOP entirely.
|
||||
return 0
|
||||
case "ANDN", "ORN":
|
||||
return 8
|
||||
case "MAX", "MAXU", "MIN", "MINU":
|
||||
if riscvIdenticalMinMax(mnem, ops) {
|
||||
rd := regFromOperand(ops[1])
|
||||
if len(ops) == 3 {
|
||||
rd = regFromOperand(ops[2])
|
||||
}
|
||||
if rd != 0 {
|
||||
return 2 // C.MV, or C.LI when the sources are X0
|
||||
}
|
||||
return 4
|
||||
}
|
||||
return 20
|
||||
case "ROR", "RORW":
|
||||
if len(ops) >= 1 && isImmOperand(ops[0]) {
|
||||
// SRL + [compressed] SLL of the reverse shift + OR.
|
||||
return 4 + riscvRevShiftSize(mnem, ops) + 4
|
||||
}
|
||||
return 16 // SUB + shift + shift + OR
|
||||
case "RORIW":
|
||||
return 12
|
||||
}
|
||||
return 4
|
||||
}
|
||||
|
||||
// riscvIdenticalMinMax reports whether a MIN/MAX sees two identical source
|
||||
// registers (the toolchain folds that to ADDI $0).
|
||||
func riscvIdenticalMinMax(mnem string, ops []*ast.Operand) bool {
|
||||
if mnem != "MAX" && mnem != "MAXU" && mnem != "MIN" && mnem != "MINU" {
|
||||
return false
|
||||
}
|
||||
if len(ops) != 2 && len(ops) != 3 {
|
||||
return false
|
||||
}
|
||||
rs1 := regFromOperand(ops[1])
|
||||
rs2 := regFromOperand(ops[0])
|
||||
rd := rs1
|
||||
if len(ops) == 3 {
|
||||
rd = regFromOperand(ops[2])
|
||||
}
|
||||
if rs1 == rd {
|
||||
// The toolchain swaps the sources so the destination-identical one
|
||||
// is processed first; identical sources stay identical.
|
||||
rs1, rs2 = rs2, rs1
|
||||
}
|
||||
return rs1 >= 0 && rs1 == rs2
|
||||
}
|
||||
|
||||
// riscvRevShiftSize returns the size of the reverse-shift instruction inside
|
||||
// a ROR/RORW immediate expansion: the SLLI of the complementary amount, which
|
||||
// compresses to C.SLLI only in the 64-bit form when rd == rs1, both non-zero,
|
||||
// and the amount lands in 1-63. The W forms have no compressed shift.
|
||||
func riscvRevShiftSize(mnem string, ops []*ast.Operand) int {
|
||||
if mnem != "ROR" {
|
||||
return 4 // SLLIW has no compressed form
|
||||
}
|
||||
imm := int(immFromOperand(ops[0]))
|
||||
rs1 := regFromOperand(ops[1])
|
||||
rd := rs1
|
||||
if len(ops) == 3 {
|
||||
rd = regFromOperand(ops[2])
|
||||
}
|
||||
sll := (-imm) & 63
|
||||
if rd == rs1 && rd != 0 && sll >= 1 && sll <= 63 {
|
||||
return 2 // C.SLLI
|
||||
}
|
||||
return 4
|
||||
}
|
||||
|
||||
@@ -482,6 +563,16 @@ func encodeRISCVInstr(instr *ast.Instr, pc int, offsets map[string]int, fi riscv
|
||||
return []byte{byte(word), byte(word >> 8), byte(word >> 16), byte(word >> 24)}, nil
|
||||
}
|
||||
|
||||
// The toolchain's synthesised instructions and the RVV slice: expanded
|
||||
// encodings the main table does not carry. FSGNJD is a plain table
|
||||
// entry and stays with the FP arithmetic path.
|
||||
if code, handled, err := encodeRISCVExtended(mnem, instr, pc, offsets); handled {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return code, nil
|
||||
}
|
||||
|
||||
enc, ok := riscvInstrTable[mnem]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("unsupported RISC-V instruction %q", mnem)
|
||||
@@ -573,9 +664,11 @@ func encodeRISCVInstr(instr *ast.Instr, pc int, offsets map[string]int, fi riscv
|
||||
}
|
||||
word = riscvSType(enc, rs1, rs2, imm)
|
||||
|
||||
// LR (load-reserved): INSTR (addr), dst, 2 operands.
|
||||
// LR (load-reserved): INSTR (addr), dst. The toolchain reads the
|
||||
// operands positionally, so the base register comes from the first
|
||||
// operand and the destination from the second whatever their parens.
|
||||
case len(ops) == 2 && isLRInstr(mnem):
|
||||
rs1, _ := memFromOperandWithFrame(ops[0], fi)
|
||||
rs1 := regFromOperand(ops[0])
|
||||
rd := regFromOperand(ops[1])
|
||||
if rd < 0 || rs1 < 0 {
|
||||
return nil, fmt.Errorf("invalid operand in %s", mnem)
|
||||
@@ -1475,6 +1568,477 @@ func extractITypeParams(instr *ast.Instr) (rd, rs1 int, imm int32) {
|
||||
return
|
||||
}
|
||||
|
||||
// ---- toolchain-synthesised instructions and the RVV slice ----
|
||||
|
||||
// encodeRISCVExtended encodes the instructions the Go toolchain synthesises
|
||||
// from other instructions (ANDN/ORN, MIN/MAX, ROR and friends, the branch
|
||||
// pseudos and FABSD), the CSR read RDTIME, and the RVV vector slice the
|
||||
// compiler's kernels use. handled reports whether the mnemonic belongs to
|
||||
// this group; err carries the diagnostic when it does but cannot be encoded.
|
||||
// Each expansion reproduces the toolchain's instruction-for-instruction
|
||||
// sequence, including its use of X31 (TMP) and its RVC compression.
|
||||
func encodeRISCVExtended(mnem string, instr *ast.Instr, pc int, offsets map[string]int) ([]byte, bool, error) {
|
||||
ops := instr.Operands
|
||||
switch mnem {
|
||||
case "NOP":
|
||||
if len(ops) != 0 {
|
||||
return nil, true, fmt.Errorf("NOP takes no operands")
|
||||
}
|
||||
// The toolchain drops a bare NOP: no bytes at all.
|
||||
return nil, true, nil
|
||||
|
||||
case "RDTIME":
|
||||
// RDTIME rd reads the time CSR through CSRRS with a zero source.
|
||||
if len(ops) != 1 {
|
||||
return nil, true, fmt.Errorf("RDTIME expects 1 operand, got %d", len(ops))
|
||||
}
|
||||
rd := regFromOperand(ops[0])
|
||||
if rd < 0 {
|
||||
return nil, true, fmt.Errorf("RDTIME: invalid register")
|
||||
}
|
||||
return wordLE(riscvIType(riscvEnc{0x73, 0x2, 0x00}, rd, 0, 0xC01)), true, nil
|
||||
|
||||
case "NEG", "NOT", "SEQZ":
|
||||
if len(ops) != 1 && len(ops) != 2 {
|
||||
return nil, true, fmt.Errorf("%s expects 1 or 2 operands, got %d", mnem, len(ops))
|
||||
}
|
||||
rs := regFromOperand(ops[0])
|
||||
rd := rs
|
||||
if len(ops) == 2 {
|
||||
rd = regFromOperand(ops[1])
|
||||
}
|
||||
if rs < 0 || rd < 0 {
|
||||
return nil, true, fmt.Errorf("%s: invalid register", mnem)
|
||||
}
|
||||
var word uint32
|
||||
switch mnem {
|
||||
case "NEG":
|
||||
word = riscvRType(riscvInstrTable["SUB"], rd, 0, rs)
|
||||
case "NOT":
|
||||
word = riscvIType(riscvInstrTable["XORI"], rd, rs, -1)
|
||||
case "SEQZ":
|
||||
word = riscvIType(riscvInstrTable["SLTIU"], rd, rs, 1)
|
||||
}
|
||||
return wordLE(word), true, nil
|
||||
|
||||
case "ANDN", "ORN":
|
||||
if len(ops) != 2 && len(ops) != 3 {
|
||||
return nil, true, fmt.Errorf("%s expects 2 or 3 operands, got %d", mnem, len(ops))
|
||||
}
|
||||
rs2 := regFromOperand(ops[0]) // the operand to invert
|
||||
rs1 := regFromOperand(ops[1])
|
||||
rd := rs1
|
||||
if len(ops) == 3 {
|
||||
rd = regFromOperand(ops[2])
|
||||
}
|
||||
if rs1 < 0 || rs2 < 0 || rd < 0 {
|
||||
return nil, true, fmt.Errorf("%s: invalid register", mnem)
|
||||
}
|
||||
notReg := rd
|
||||
if rs1 == notReg {
|
||||
notReg = 31 // TMP, when the destination would be clobbered
|
||||
}
|
||||
out := wordLE(riscvIType(riscvInstrTable["XORI"], notReg, rs2, -1))
|
||||
op := riscvInstrTable["AND"]
|
||||
if mnem == "ORN" {
|
||||
op = riscvInstrTable["OR"]
|
||||
}
|
||||
return append(out, wordLE(riscvRType(op, rd, rs1, notReg))...), true, nil
|
||||
|
||||
case "MAX", "MAXU", "MIN", "MINU":
|
||||
if len(ops) != 2 && len(ops) != 3 {
|
||||
return nil, true, fmt.Errorf("%s expects 2 or 3 operands, got %d", mnem, len(ops))
|
||||
}
|
||||
rs2 := regFromOperand(ops[0])
|
||||
rs1 := regFromOperand(ops[1])
|
||||
rd := rs1
|
||||
if len(ops) == 3 {
|
||||
rd = regFromOperand(ops[2])
|
||||
}
|
||||
if rs1 < 0 || rs2 < 0 || rd < 0 {
|
||||
return nil, true, fmt.Errorf("%s: invalid register", mnem)
|
||||
}
|
||||
if rs1 == rd {
|
||||
// Process the destination-identical source first, as the
|
||||
// toolchain does, so the sequence stays in place.
|
||||
rs1, rs2 = rs2, rs1
|
||||
}
|
||||
if rs1 == rs2 {
|
||||
// Identical inputs fold to ADDI $0 (compressed to C.MV and
|
||||
// friends by the toolchain's compressor).
|
||||
return riscvFoldedMove(rd, rs1), true, nil
|
||||
}
|
||||
slt1, slt2 := rs2, rs1
|
||||
cmp := riscvInstrTable["SLT"]
|
||||
if mnem == "MAX" || mnem == "MAXU" {
|
||||
slt1, slt2 = slt2, slt1
|
||||
}
|
||||
if mnem == "MAXU" || mnem == "MINU" {
|
||||
cmp = riscvInstrTable["SLTU"]
|
||||
}
|
||||
var out []byte
|
||||
out = append(out, wordLE(riscvRType(cmp, 31, slt1, slt2))...) // the compare into TMP
|
||||
out = append(out, wordLE(riscvRType(riscvInstrTable["SUB"], 31, 0, 31))...) // NEG TMP
|
||||
out = append(out, wordLE(riscvRType(riscvInstrTable["XOR"], rd, rs1, rs2))...)
|
||||
out = append(out, wordLE(riscvRType(riscvInstrTable["AND"], rd, 31, rd))...)
|
||||
out = append(out, wordLE(riscvRType(riscvInstrTable["XOR"], rd, rs1, rd))...)
|
||||
return out, true, nil
|
||||
|
||||
case "ROR", "RORW", "RORIW":
|
||||
if len(ops) != 2 && len(ops) != 3 {
|
||||
return nil, true, fmt.Errorf("%s expects 2 or 3 operands, got %d", mnem, len(ops))
|
||||
}
|
||||
if isImmOperand(ops[0]) {
|
||||
// Immediate rotate: SRLI the amount, SLLI the complement, OR.
|
||||
imm := int(immFromOperand(ops[0]))
|
||||
shiftW := 63
|
||||
srlEnc := riscvInstrTable["SRLI"]
|
||||
sllEnc := riscvInstrTable["SLLI"]
|
||||
if mnem != "ROR" {
|
||||
shiftW = 31
|
||||
srlEnc = riscvInstrTable["SRLIW"]
|
||||
sllEnc = riscvInstrTable["SLLIW"]
|
||||
}
|
||||
if imm < 0 || imm > shiftW {
|
||||
return nil, true, fmt.Errorf("%s: shift amount out of range [0, %d]", mnem, shiftW)
|
||||
}
|
||||
rs1 := regFromOperand(ops[1])
|
||||
rd := rs1
|
||||
if len(ops) == 3 {
|
||||
rd = regFromOperand(ops[2])
|
||||
}
|
||||
if rs1 < 0 || rd < 0 {
|
||||
return nil, true, fmt.Errorf("%s: invalid register", mnem)
|
||||
}
|
||||
var out []byte
|
||||
out = append(out, wordLE(riscvRType(srlEnc, 31, rs1, imm))...)
|
||||
sll := (-imm) & shiftW
|
||||
if mnem == "ROR" && rd == rs1 && rd != 0 && sll >= 1 && sll <= 63 {
|
||||
out = append(out, word16(rvcSLLI(uint32(rd), uint32(sll)))...) // C.SLLI
|
||||
} else {
|
||||
out = append(out, wordLE(riscvRType(sllEnc, rd, rs1, sll))...)
|
||||
}
|
||||
return append(out, wordLE(riscvRType(riscvInstrTable["OR"], rd, 31, rd))...), true, nil
|
||||
}
|
||||
// Register rotate: OR of the two opposite shifts through TMP.
|
||||
if mnem == "RORIW" {
|
||||
return nil, true, fmt.Errorf("RORIW takes an immediate shift amount")
|
||||
}
|
||||
rs2 := regFromOperand(ops[0])
|
||||
rs1 := regFromOperand(ops[1])
|
||||
rd := rs1
|
||||
if len(ops) == 3 {
|
||||
rd = regFromOperand(ops[2])
|
||||
}
|
||||
if rs1 < 0 || rs2 < 0 || rd < 0 {
|
||||
return nil, true, fmt.Errorf("%s: invalid register", mnem)
|
||||
}
|
||||
sllEnc := riscvInstrTable["SLL"]
|
||||
srlEnc := riscvInstrTable["SRL"]
|
||||
if mnem == "RORW" {
|
||||
sllEnc = riscvInstrTable["SLLW"]
|
||||
srlEnc = riscvInstrTable["SRLW"]
|
||||
}
|
||||
var out []byte
|
||||
out = append(out, wordLE(riscvRType(riscvInstrTable["SUB"], 31, 0, rs2))...) // NEG
|
||||
out = append(out, wordLE(riscvRType(sllEnc, 31, rs1, 31))...)
|
||||
out = append(out, wordLE(riscvRType(srlEnc, rd, rs1, rs2))...)
|
||||
out = append(out, wordLE(riscvRType(riscvInstrTable["OR"], rd, 31, rd))...)
|
||||
return out, true, nil
|
||||
|
||||
case "BGT", "BGTU", "BLE", "BLEU":
|
||||
// The reversed conditional branches: BGT a, b, label is BLT b, a.
|
||||
if len(ops) != 3 {
|
||||
return nil, true, fmt.Errorf("%s expects 3 operands, got %d", mnem, len(ops))
|
||||
}
|
||||
a := regFromOperand(ops[0])
|
||||
b := regFromOperand(ops[1])
|
||||
if a < 0 || b < 0 {
|
||||
return nil, true, fmt.Errorf("%s: invalid register", mnem)
|
||||
}
|
||||
target := labelFromOperand(ops[2])
|
||||
targetOff, ok := offsets[target]
|
||||
if !ok {
|
||||
return nil, true, fmt.Errorf("undefined label %q%s", target, suggestLabel(target, offsets))
|
||||
}
|
||||
offset := int32(targetOff - pc)
|
||||
if err := riscvCheckBranchOffset(target, offset); err != nil {
|
||||
return nil, true, err
|
||||
}
|
||||
var enc riscvEnc
|
||||
switch mnem {
|
||||
case "BGT":
|
||||
enc = riscvEnc{0x63, 0x4, 0x00} // blt b, a
|
||||
case "BGTU":
|
||||
enc = riscvEnc{0x63, 0x6, 0x00} // bltu b, a
|
||||
case "BLE":
|
||||
enc = riscvEnc{0x63, 0x5, 0x00} // bge b, a
|
||||
case "BLEU":
|
||||
enc = riscvEnc{0x63, 0x7, 0x00} // bgeu b, a
|
||||
}
|
||||
return wordLE(riscvBType(enc, b, a, offset)), true, nil
|
||||
|
||||
case "FABSD":
|
||||
// FABSD rs, rd is FSGNJX.D (sign XOR, funct3 2) with the source in
|
||||
if len(ops) != 2 {
|
||||
return nil, true, fmt.Errorf("FABSD expects 2 operands, got %d", len(ops))
|
||||
}
|
||||
rs := regFromOperand(ops[0])
|
||||
rd := regFromOperand(ops[1])
|
||||
if rs < 0 || rd < 0 {
|
||||
return nil, true, fmt.Errorf("FABSD: invalid register")
|
||||
}
|
||||
return wordLE(riscvRType(riscvEnc{0x53, 0x2, 0x11}, rd, rs, rs)), true, nil
|
||||
|
||||
default:
|
||||
return encodeRISCVVector(mnem, ops)
|
||||
}
|
||||
}
|
||||
|
||||
// riscvFoldedMove emits the ADDI $0, rs, rd the toolchain folds identical
|
||||
// MIN/MAX inputs into, with the same compression its compressor applies to
|
||||
// the folded form.
|
||||
func riscvFoldedMove(rd, rs int) []byte {
|
||||
switch {
|
||||
case rd != 0 && rs != 0:
|
||||
return word16(rvcCR(0x8, uint32(rd), uint32(rs))) // C.MV
|
||||
case rd == 0 && rs == 0:
|
||||
return word16(0x0001) // C.NOP
|
||||
case rs == 0:
|
||||
return word16(rvcCI(0x2, uint32(rd), 0)) // C.LI rd, $0
|
||||
default:
|
||||
return wordLE(riscvIType(riscvEnc{0x13, 0x0, 0x00}, rd, rs, 0))
|
||||
}
|
||||
}
|
||||
|
||||
// encodeRISCVVector encodes the RVV slice GOROOT's kernels use. Registers
|
||||
// are accepted in either spelling: the vector V registers and the integer
|
||||
// registers share their 5-bit numbers, and the superset keeps hand-written
|
||||
// probes simple. handled is always true: every name reaching here is one of
|
||||
// the vector mnemonics.
|
||||
func encodeRISCVVector(mnem string, ops []*ast.Operand) ([]byte, bool, error) {
|
||||
reg := regFromOperand
|
||||
switch mnem {
|
||||
case "VSETVLI", "VSETIVLI":
|
||||
// INSTR avl, vsew, vlmul, vta, vma, rd.
|
||||
if len(ops) != 6 {
|
||||
return nil, true, fmt.Errorf("%s expects 6 operands, got %d", mnem, len(ops))
|
||||
}
|
||||
avl := 0
|
||||
if isImmOperand(ops[0]) {
|
||||
avl = int(immFromOperand(ops[0]))
|
||||
if avl < 0 || avl > 31 {
|
||||
return nil, true, fmt.Errorf("%s: avl immediate out of range [0, 31]", mnem)
|
||||
}
|
||||
} else {
|
||||
avl = reg(ops[0])
|
||||
if avl < 0 {
|
||||
return nil, true, fmt.Errorf("%s: invalid avl register", mnem)
|
||||
}
|
||||
}
|
||||
if mnem == "VSETIVLI" && !isImmOperand(ops[0]) {
|
||||
return nil, true, fmt.Errorf("VSETIVLI expects an immediate avl")
|
||||
}
|
||||
vsew, err := riscvVTypeToken(operandRegName(ops[1]), "E", map[string]int{"8": 0, "16": 1, "32": 2, "64": 3})
|
||||
if err != nil {
|
||||
return nil, true, fmt.Errorf("%s: %w", mnem, err)
|
||||
}
|
||||
vlmul, err := riscvVTypeToken(operandRegName(ops[2]), "M", map[string]int{"1": 0, "2": 1, "4": 2, "8": 3, "F8": 5, "F4": 6, "F2": 7})
|
||||
if err != nil {
|
||||
return nil, true, fmt.Errorf("%s: %w", mnem, err)
|
||||
}
|
||||
vta := 0
|
||||
switch operandRegName(ops[3]) {
|
||||
case "TA":
|
||||
vta = 1
|
||||
case "TU":
|
||||
default:
|
||||
return nil, true, fmt.Errorf("%s: invalid tail policy %q (want TA or TU)", mnem, operandRegName(ops[3]))
|
||||
}
|
||||
vma := 0
|
||||
switch operandRegName(ops[4]) {
|
||||
case "MA":
|
||||
vma = 1
|
||||
case "MU":
|
||||
default:
|
||||
return nil, true, fmt.Errorf("%s: invalid mask policy %q (want MA or MU)", mnem, operandRegName(ops[4]))
|
||||
}
|
||||
rd := reg(ops[5])
|
||||
if rd < 0 {
|
||||
return nil, true, fmt.Errorf("%s: invalid destination register", mnem)
|
||||
}
|
||||
// An immediate avl always encodes as vsetivli, even under the
|
||||
// VSETVLI spelling: the toolchain canonicalises the pair, and
|
||||
// `VSETVLI $15` and `VSETIVLI $15` come out byte-identical
|
||||
// (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 "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 {
|
||||
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
|
||||
}
|
||||
|
||||
// riscvVTypeToken parses a vsetvli configuration token (E8, M8, MF2 and
|
||||
// friends): the letter prefix selects the field and the suffix its value
|
||||
// through the given table.
|
||||
func riscvVTypeToken(name, prefix string, codes map[string]int) (int, error) {
|
||||
if len(name) <= len(prefix) || name[:len(prefix)] != prefix {
|
||||
return 0, fmt.Errorf("invalid vtype token %q (want %s<width>)", name, prefix)
|
||||
}
|
||||
code, ok := codes[name[len(prefix):]]
|
||||
if !ok {
|
||||
return 0, fmt.Errorf("invalid vtype token %q", name)
|
||||
}
|
||||
return code, nil
|
||||
}
|
||||
|
||||
// riscvVecMem reads a vector memory operand: a bare base register, the only
|
||||
// addressing form the vector loads and stores carry. Frame-pseudo bases are
|
||||
// rejected: the toolchain resolves no frame reference on the vector forms.
|
||||
func riscvVecMem(op *ast.Operand) (rs1 int, ok bool) {
|
||||
if op.Addr.Sym != nil && op.Addr.Sym.Pseudo != "" {
|
||||
return -1, false
|
||||
}
|
||||
if op.Addr.Base == "" || op.Addr.Offset != 0 {
|
||||
return -1, false
|
||||
}
|
||||
rs1 = riscvRegNum(op.Addr.Base)
|
||||
return rs1, rs1 >= 0
|
||||
}
|
||||
|
||||
// Instruction type classifiers.
|
||||
func isRTypeInstr(m string) bool {
|
||||
switch m {
|
||||
@@ -1547,7 +2111,7 @@ func isFPArithInstr(m string) bool {
|
||||
switch m {
|
||||
case "FADDS", "FSUBS", "FMULS", "FDIVS",
|
||||
"FADDD", "FSUBD", "FMULD", "FDIVD",
|
||||
"FSQRTS", "FSQRTD", "FMINS", "FMAXS", "FMIND", "FMAXD":
|
||||
"FSQRTS", "FSQRTD", "FMINS", "FMAXS", "FMIND", "FMAXD", "FSGNJD":
|
||||
return true
|
||||
}
|
||||
return false
|
||||
|
||||
Reference in New Issue
Block a user