feat(riscv64,loong64): encode AMO atomics, vector slices and bit ops

Assisted-by: GLM 5.3 Flash
This commit is contained in:
2026-09-20 06:44:51 +02:00
parent ca3fdce0e0
commit de5d9f358e
12 changed files with 2059 additions and 37 deletions
+567 -3
View File
@@ -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