diff --git a/asm/riscv_assemble.go b/asm/riscv_assemble.go index 53687c6..e5954cb 100644 --- a/asm/riscv_assemble.go +++ b/asm/riscv_assemble.go @@ -437,7 +437,7 @@ func riscvExtendedSize(mnem string, ops []*ast.Operand) int { case "NOP": // The toolchain drops a bare NOP entirely. return 0 - case "ANDN", "ORN", "XNOR": + case "ANDN", "ORN", "XNOR", "FNES", "FNED": return 8 case "MAX", "MAXU", "MIN", "MINU": if riscvIdenticalMinMax(mnem, ops) { @@ -894,7 +894,7 @@ func riscvPCRelOffset(instr *ast.Instr) (int, bool) { if len(instr.Operands) != 1 && len(instr.Operands) != 2 { return 0, false } - case "BEQ", "BNE", "BLT", "BGE", "BLTU", "BGEU", + case "BEQ", "BNE", "BLT", "BGE", "BLTU", "BGEU", "BGT", "BLE", "BGTU", "BLEU", "BEQZ", "BNEZ", "BLTZ", "BGEZ", "BLEZ", "BGTZ": if len(instr.Operands) < 2 { return 0, false @@ -1138,16 +1138,22 @@ func encodeRISCVInstr(instr *ast.Instr, pc int, offsets map[string]int, fi riscv word = riscvJType(0, offset) return []byte{byte(word), byte(word >> 8), byte(word >> 16), byte(word >> 24)}, nil } - // JMP (X5): an indirect branch, the toolchain's JALR X0, 0(X5). + // JMP (X5) and JMP 4(X5): an indirect branch, the toolchain's + // JALR X0, imm(X5) with the offset carried in the I-type + // immediate (JMP 4(X5) is 0x67804200). if ops[0].Addr.Sym == nil && ops[0].Addr.Base != "" { - if ops[0].Addr.Offset != 0 || ops[0].Addr.Index != "" { + if ops[0].Addr.Index != "" { return nil, fmt.Errorf("JMP: invalid indirect operand %q", ops[0].Raw) } rs1 := riscvRegNum(ops[0].Addr.Base) if rs1 < 0 { return nil, fmt.Errorf("JMP: unknown branch register %q", ops[0].Addr.Base) } - word = riscvIType(riscvEnc{0x67, 0x0, 0x00}, 0, rs1, 0) + imm := int32(ops[0].Addr.Offset) + if imm < -2048 || imm > 2047 { + return nil, fmt.Errorf("JMP: displacement %d does not fit in 12 bits", imm) + } + word = riscvIType(riscvEnc{0x67, 0x0, 0x00}, 0, rs1, imm) return []byte{byte(word), byte(word >> 8), byte(word >> 16), byte(word >> 24)}, nil } } @@ -1301,6 +1307,47 @@ func encodeRISCVInstr(instr *ast.Instr, pc int, offsets map[string]int, fi riscv } word = riscvRType(riscvEnc{0x73, 0x0, 0x09}, 0, rs1, rs2) return []byte{byte(word), byte(word >> 8), byte(word >> 16), byte(word >> 24)}, nil + + case "NEG", "NEGW": + // INSTR rs [, rd]: SUB/SUBW with X0 in the rs1 field, the + // one-operand form negating in place (ANEG: NEG rs, rd -> SUB rs, + // X0, rd). The toolchain pins the bytes: NEG X5 is 0x405002b3. + if len(ops) != 1 && len(ops) != 2 { + return nil, 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, fmt.Errorf("%s: invalid register operand", mnem) + } + enc := riscvEnc{0x33, 0x0, 0x20} // sub + if mnem == "NEGW" { + enc = riscvEnc{0x3B, 0x0, 0x20} // subw + } + word = riscvRType(enc, rd, 0, rs) + return []byte{byte(word), byte(word >> 8), byte(word >> 16), byte(word >> 24)}, nil + + case "SEQZ", "SNEZ": + // INSTR rs, rd: the set-equal and set-not-equal pseudos read as + // SLTIU $1 and SLTU against X0 (ASEQZ/ASNEZ). The toolchain pins + // the bytes: SEQZ X14, X15 is 0x00173793. + if len(ops) != 2 { + return nil, fmt.Errorf("%s expects 2 operands, got %d", mnem, len(ops)) + } + rs := regFromOperand(ops[0]) + rd := regFromOperand(ops[1]) + if rs < 0 || rd < 0 { + return nil, fmt.Errorf("%s: invalid register operand", mnem) + } + if mnem == "SEQZ" { + word = riscvIType(riscvEnc{0x13, 0x3, 0x00}, rd, rs, 1) // sltiu $1 + } else { + word = riscvRType(riscvEnc{0x33, 0x3, 0x00}, rd, 0, rs) // sltu rd, x0, rs + } + return []byte{byte(word), byte(word >> 8), byte(word >> 16), byte(word >> 24)}, nil } // FP conversion / move instructions use a separate table (rs2 encodes @@ -2767,7 +2814,9 @@ func encodeRISCVExtended(mnem string, instr *ast.Instr, pc int, offsets map[stri } return wordLE(riscvIType(riscvEnc{0x73, 0x2, 0x00}, rd, 0, 0xC01)), true, nil - case "NEG", "NOT", "SEQZ": + case "NOT": + // NEG and SEQZ have their own cases in encodeRISCVInstr's pseudo + // switch; NOT reads as XORI $-1. if len(ops) != 1 && len(ops) != 2 { return nil, true, fmt.Errorf("%s expects 1 or 2 operands, got %d", mnem, len(ops)) } @@ -2781,12 +2830,8 @@ func encodeRISCVExtended(mnem string, instr *ast.Instr, pc int, offsets map[stri } 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 @@ -2975,49 +3020,49 @@ func encodeRISCVExtended(mnem string, instr *ast.Instr, pc int, offsets map[stri 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 + // BGT/BGTU/BLE/BLEU have no extended handler: the main table's + // branch path owns them, including the N(PC) forms. - case "FABSD": - // FABSD rs, rd is FSGNJX.D (sign XOR, funct3 2) with the source in + case "FABSS", "FABSD", "FNEGS", "FNEGD": + // INSTR fs, fd: the sign-injection pseudos, the source in both the + // rs1 and rs2 fields (AFABSS: FSGNJXS rs, rs, rd; AFNEGS: FSGNJNS). + // The toolchain pins the bytes: FABSS F0, F1 is 0x200020d3. if len(ops) != 2 { - return nil, true, fmt.Errorf("FABSD expects 2 operands, got %d", len(ops)) + return nil, true, fmt.Errorf("%s expects 2 operands, got %d", mnem, len(ops)) } rs := regFromOperand(ops[0]) rd := regFromOperand(ops[1]) if rs < 0 || rd < 0 { - return nil, true, fmt.Errorf("FABSD: invalid register") + return nil, true, fmt.Errorf("%s: invalid register", mnem) } - return wordLE(riscvRType(riscvEnc{0x53, 0x2, 0x11}, rd, rs, rs)), true, nil + enc := map[string]riscvEnc{ + "FABSS": {0x53, 0x2, 0x10}, // fsgnjx.s + "FABSD": {0x53, 0x2, 0x11}, // fsgnjx.d + "FNEGS": {0x53, 0x1, 0x10}, // fsgnjn.s + "FNEGD": {0x53, 0x1, 0x11}, // fsgnjn.d + }[mnem] + return wordLE(riscvRType(enc, rd, rs, rs)), true, nil + + case "FNES", "FNED": + // INSTR fs1, fs2, xrd: the not-equal pseudos read as FEQ.S/FEQ.D + // followed by XORI $1 on the result register (AFNES), two words. + if len(ops) != 3 { + return nil, true, fmt.Errorf("%s expects 3 operands, got %d", mnem, len(ops)) + } + rs2 := regFromOperand(ops[0]) + rs1 := regFromOperand(ops[1]) + rd := regFromOperand(ops[2]) + if rs1 < 0 || rs2 < 0 || rd < 0 { + return nil, true, fmt.Errorf("%s: invalid register", mnem) + } + feq := riscvEnc{0x53, 0x2, 0x50} // feq.s + if mnem == "FNED" { + feq = riscvEnc{0x53, 0x2, 0x51} // feq.d + } + eq := riscvRType(feq, rd, rs1, rs2) + not := riscvIType(riscvEnc{0x13, 0x4, 0x00}, rd, rd, 1) // xori $1 + return []byte{byte(eq), byte(eq >> 8), byte(eq >> 16), byte(eq >> 24), + byte(not), byte(not >> 8), byte(not >> 16), byte(not >> 24)}, true, nil default: return encodeRISCVVector(mnem, ops) diff --git a/asm/riscv_vecarith_test.go b/asm/riscv_vecarith_test.go index a3c70a8..4cea24f 100644 --- a/asm/riscv_vecarith_test.go +++ b/asm/riscv_vecarith_test.go @@ -810,3 +810,36 @@ TEXT ·vecarith(SB), NOSPLIT, $0 } assertRISCVDifferential(t, path, src, "vecarith") } + +// TestRISCVScalarPseudos_Differential proves the scalar pseudo tail against +// the toolchain: the NEG/NEGW and SEQZ/SNEZ one- and two-operand forms, the +// sign-injection pseudos and the two-word not-equal expansions, exactly as +// the toolchain's own testdata spells them. +func TestRISCVScalarPseudos_Differential(t *testing.T) { + src := `#include "textflag.h" + +TEXT ·pseudos(SB), NOSPLIT, $0-8 + NEG X5 + NEG X5, X6 + NEGW X5 + NEGW X5, X6 + SEQZ X14, X15 + SNEZ X14, X15 + FABSS F0, F1 + FNEGS F0, F1 + FNES F0, F1, X7 + FABSD F0, F1 + FNEGD F0, F1 + FNED F0, F1, X5 + FLTD F0, F1, X5 + FLED F0, F1, X5 + FEQD F0, F1, X5 + RET +` + dir := t.TempDir() + path := filepath.Join(dir, "pseudos_riscv64.s") + if err := os.WriteFile(path, []byte(src), 0o644); err != nil { + t.Fatal(err) + } + assertRISCVDifferential(t, path, src, "pseudos") +}