fix(asm): carry the riscv64 scalar pseudos and the swapped branches

Assisted-by: GLM 5.3 Flash
This commit is contained in:
petrbalvin committed 2026-10-07 00:47:27 +02:00
1 parent 82e8919208
commit 955bc6643e
2 files changed
+124 -46

No files matched your search

+91 -46
View File
@@ -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)
+33
View File
@@ -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")
}