fix(asm): route the riscv64 register moves through the toolchain forms
Assisted-by: GLM 5.3
This commit is contained in:
1 parent
da35883633
commit
36d1ac804a
1 file changed
+209
-9
+209
-9
@@ -6,8 +6,10 @@ package asm
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math"
|
||||
"math/bits"
|
||||
"slices"
|
||||
"strconv"
|
||||
"strings"
|
||||
|
||||
"sourcedock.dev/petrbalvin/gasm-sdk/ast"
|
||||
@@ -368,6 +370,17 @@ func riscvInstrSize(instr *ast.Instr, fi riscvFrameInfo) int {
|
||||
return len(riscvReturn(fi))
|
||||
}
|
||||
if strings.HasPrefix(mnem, "MOV") && len(ops) == 2 {
|
||||
// FP constant: FMV from X0 for a zero bit pattern, otherwise the
|
||||
// 8-byte AUIPC + FLW/FLD pool load.
|
||||
if (mnem == "MOVF" || mnem == "MOVD") && isImmOperand(ops[0]) &&
|
||||
!ops[0].Imm.HasVal && ops[0].Imm.Sym == nil && ops[0].Imm.Str == "" {
|
||||
if pattern, _, err := riscvFPConstBits(mnem, ops[0]); err == nil {
|
||||
if pattern == 0 {
|
||||
return 4
|
||||
}
|
||||
return 8
|
||||
}
|
||||
}
|
||||
// MOV $sym(SB), rd → 8 bytes (AUIPC + ADDI).
|
||||
if isImmOperand(ops[0]) && ops[0].Imm.Sym != nil && ops[0].Imm.Sym.Pseudo == "SB" {
|
||||
return 8
|
||||
@@ -410,6 +423,29 @@ func riscvInstrSize(instr *ast.Instr, fi riscvFrameInfo) int {
|
||||
if isMemOperand(ops[1]) && !isMemOperand(ops[0]) {
|
||||
return riscvFrameMemSize(ops[1], fi)
|
||||
}
|
||||
// Register-to-register width moves: the SLLI + SRAI/SRLI extension
|
||||
// pairs carry their per-half compression; the single-word forms are
|
||||
// four bytes before the RVC pass (bare MOV compresses to C.MV or
|
||||
// C.LI, which the post-encoding pass resolves from real bytes).
|
||||
if !isMemOperand(ops[0]) && !isMemOperand(ops[1]) {
|
||||
rd := regFromOperand(ops[1])
|
||||
rs1 := regFromOperand(ops[0])
|
||||
switch mnem {
|
||||
case "MOVB", "MOVH":
|
||||
shamt := 56
|
||||
if mnem == "MOVH" {
|
||||
shamt = 48
|
||||
}
|
||||
return len(riscvExtendBytes(rd, rs1, shamt, true))
|
||||
case "MOVHU", "MOVWU":
|
||||
shamt := 48
|
||||
if mnem == "MOVWU" {
|
||||
shamt = 32
|
||||
}
|
||||
return len(riscvExtendBytes(rd, rs1, shamt, false))
|
||||
}
|
||||
return 4
|
||||
}
|
||||
}
|
||||
// I-type arithmetic with a large immediate expands to several instructions.
|
||||
if (mnem == "ADDI" || mnem == "ANDI" || mnem == "ORI" || mnem == "XORI") && len(ops) >= 1 && isImmOperand(ops[0]) {
|
||||
@@ -1831,6 +1867,43 @@ func encodeRISCVMov(instr *ast.Instr, fi riscvFrameInfo, relocs *[]Reloc, lits *
|
||||
|
||||
// Immediate → register.
|
||||
if isImmOperand(src) {
|
||||
mnem := strings.ToUpper(instr.Mnemonic.Text)
|
||||
// FP constant → FP register: a zero bit pattern moves through FMV
|
||||
// from X0, anything else loads from the pooled $f32/$f64 constant
|
||||
// symbol the toolchain synthesises (AUIPC + FLW/FLD through TMP).
|
||||
// An integer constant is not an FP load source, exactly as the
|
||||
// toolchain rejects the non-FCONST forms.
|
||||
if (mnem == "MOVF" || mnem == "MOVD") && !src.Imm.HasVal && src.Imm.Sym == nil && src.Imm.Str == "" {
|
||||
rd := regFromOperand(dst)
|
||||
if rd < 0 || !riscvIsFloatRegOperand(dst) {
|
||||
return nil, fmt.Errorf("%s $float: invalid destination register", mnem)
|
||||
}
|
||||
pattern, _, err := riscvFPConstBits(mnem, src)
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: invalid floating-point constant %q", mnem, src.Imm.Float)
|
||||
}
|
||||
if pattern == 0 {
|
||||
op := uint32(0x78) << 25 // FMV.W.X
|
||||
if mnem == "MOVD" {
|
||||
op = uint32(0x79) << 25 // FMV.D.X
|
||||
}
|
||||
return wordLE(op | uint32(rd)<<7 | 0x53), nil
|
||||
}
|
||||
var name string
|
||||
var data []byte
|
||||
double := mnem == "MOVD"
|
||||
if double {
|
||||
name = fmt.Sprintf("$f64.%016x", pattern)
|
||||
data = riscvLiteralBytes(int64(pattern))
|
||||
} else {
|
||||
name = fmt.Sprintf("$f32.%08x", uint32(pattern))
|
||||
data = []byte{byte(pattern), byte(pattern >> 8), byte(pattern >> 16), byte(pattern >> 24)}
|
||||
}
|
||||
if lits != nil {
|
||||
lits.add(name, data)
|
||||
}
|
||||
return encodeRISCVSBFPLoad(name, rd, double, relocs), nil
|
||||
}
|
||||
// MOV $sym(SB), rd, load address of a static symbol or external.
|
||||
if src.Imm.Sym != nil && src.Imm.Sym.Pseudo == "SB" {
|
||||
rd := regFromOperand(dst)
|
||||
@@ -1874,7 +1947,6 @@ func encodeRISCVMov(instr *ast.Instr, fi riscvFrameInfo, relocs *[]Reloc, lits *
|
||||
// Memory → register (load).
|
||||
if isMemOperand(src) && !isMemOperand(dst) {
|
||||
rd := regFromOperand(dst)
|
||||
// MOV sym(SB), rd, load from static data.
|
||||
if src.Addr.Sym != nil && src.Addr.Sym.Pseudo == "SB" {
|
||||
if rd < 0 {
|
||||
return nil, fmt.Errorf("MOV sym(SB): invalid destination register")
|
||||
@@ -1891,7 +1963,6 @@ func encodeRISCVMov(instr *ast.Instr, fi riscvFrameInfo, relocs *[]Reloc, lits *
|
||||
// Register → memory (store).
|
||||
if !isMemOperand(src) && isMemOperand(dst) {
|
||||
rs2 := regFromOperand(src)
|
||||
// MOV rd, sym(SB), store to static data.
|
||||
if dst.Addr.Sym != nil && dst.Addr.Sym.Pseudo == "SB" {
|
||||
if rs2 < 0 {
|
||||
return nil, fmt.Errorf("MOV rd, sym(SB): invalid source register")
|
||||
@@ -1905,8 +1976,12 @@ func encodeRISCVMov(instr *ast.Instr, fi riscvFrameInfo, relocs *[]Reloc, lits *
|
||||
return riscvFrameMemOp(riscvMovEnc(strings.ToUpper(instr.Mnemonic.Text), true), true, rs2, rs1, off), nil
|
||||
}
|
||||
|
||||
// Register → register: MOVD/MOVF are FP moves (fsgnj with rs2 = rs1),
|
||||
// everything else is ADDI $0, src, dst.
|
||||
// Register → register. The width suffix selects the toolchain's
|
||||
// synthesis: MOVF/MOVD between the integer and FP banks are FMV and
|
||||
// inside the FP bank FSGNJ with rs2 = rs1; MOVW is ADDIW $0; MOVBU is
|
||||
// ANDI $255; MOVB and MOVH sign-extend through SLLI+SRAI and MOVHU/
|
||||
// MOVWU zero-extend through SLLI+SRLI; bare MOV is ADDI $0, which the
|
||||
// RVC pass compresses.
|
||||
{
|
||||
rs1 := regFromOperand(src)
|
||||
rd := regFromOperand(dst)
|
||||
@@ -1914,12 +1989,50 @@ func encodeRISCVMov(instr *ast.Instr, fi riscvFrameInfo, relocs *[]Reloc, lits *
|
||||
return nil, fmt.Errorf("MOV: invalid register operand")
|
||||
}
|
||||
mnem := strings.ToUpper(instr.Mnemonic.Text)
|
||||
if mnem == "MOVD" || mnem == "MOVF" {
|
||||
op := uint32(0x20000053) // FSGNJ.S
|
||||
if mnem == "MOVD" {
|
||||
op = 0x22000053 // FSGNJ.D
|
||||
srcF, dstF := riscvIsFloatRegOperand(src), riscvIsFloatRegOperand(dst)
|
||||
switch mnem {
|
||||
case "MOVF", "MOVD":
|
||||
switch {
|
||||
case srcF && dstF:
|
||||
op := uint32(0x20000053) // FSGNJ.S
|
||||
if mnem == "MOVD" {
|
||||
op = 0x22000053 // FSGNJ.D
|
||||
}
|
||||
return wordLE(op | uint32(rs1)<<15 | uint32(rs1)<<20 | uint32(rd)<<7), nil
|
||||
case dstF && !srcF:
|
||||
op := uint32(0x78) << 25 // FMV.W.X
|
||||
if mnem == "MOVD" {
|
||||
op = uint32(0x79) << 25 // FMV.D.X
|
||||
}
|
||||
return wordLE(op | uint32(rs1)<<15 | uint32(rd)<<7 | 0x53), nil
|
||||
case srcF && !dstF:
|
||||
op := uint32(0x70) << 25 // FMV.X.W
|
||||
if mnem == "MOVD" {
|
||||
op = uint32(0x71) << 25 // FMV.X.D
|
||||
}
|
||||
return wordLE(op | uint32(rs1)<<15 | uint32(rd)<<7 | 0x53), nil
|
||||
}
|
||||
return wordLE(op | uint32(rs1)<<15 | uint32(rs1)<<20 | uint32(rd)<<7), nil
|
||||
return nil, fmt.Errorf("%s: both registers must be in the same bank", mnem)
|
||||
case "MOVW":
|
||||
// ADDIW $0, rs, rd; the two-operand-only form never has
|
||||
// rd == rs1 in real sources, and the toolchain's C.ADDIW
|
||||
// forbids a zero immediate, so the word stays uncompressed.
|
||||
return wordLE(riscvIType(riscvEnc{0x1B, 0x0, 0x00}, rd, rs1, 0)), nil
|
||||
case "MOVBU":
|
||||
// ANDI $255, rs, rd; 255 never fits C.ANDI's six signed bits.
|
||||
return wordLE(riscvIType(riscvEnc{0x13, 0x7, 0x00}, rd, rs1, 0xFF)), nil
|
||||
case "MOVB", "MOVH":
|
||||
shamt := 56
|
||||
if mnem == "MOVH" {
|
||||
shamt = 48
|
||||
}
|
||||
return riscvExtendBytes(rd, rs1, shamt, true), nil
|
||||
case "MOVHU", "MOVWU":
|
||||
shamt := 48
|
||||
if mnem == "MOVWU" {
|
||||
shamt = 32
|
||||
}
|
||||
return riscvExtendBytes(rd, rs1, shamt, false), nil
|
||||
}
|
||||
word := riscvIType(riscvEnc{0x13, 0x0, 0x00}, rd, rs1, 0)
|
||||
return []byte{byte(word), byte(word >> 8), byte(word >> 16), byte(word >> 24)}, nil
|
||||
@@ -2238,6 +2351,93 @@ func riscvLiteralBytes(v int64) []byte {
|
||||
byte(v >> 32), byte(v >> 40), byte(v >> 48), byte(v >> 56)}
|
||||
}
|
||||
|
||||
// riscvIsFloatRegOperand reports whether the operand spells a floating-point
|
||||
// register: every F-bank spelling starts with F, and the one integer name
|
||||
// that does (FP, the frame pointer alias of X8) is excluded.
|
||||
func riscvIsFloatRegOperand(op *ast.Operand) bool {
|
||||
name := ""
|
||||
if op.Addr.Base != "" {
|
||||
name = op.Addr.Base
|
||||
} else if op.Addr.Sym != nil {
|
||||
name = op.Addr.Sym.Name
|
||||
}
|
||||
return name != "FP" && strings.HasPrefix(name, "F")
|
||||
}
|
||||
|
||||
// riscvFPConstBits resolves an FP constant operand to the bit pattern the
|
||||
// toolchain moves or pools: MOVF narrows through float32 first, MOVD keeps
|
||||
// the float64 bits. The bare spelling fills Imm.Float and the parenthesised
|
||||
// one ($ (709.78…)) leaves only the raw text, exactly as on arm64.
|
||||
func riscvFPConstBits(mnem string, src *ast.Operand) (uint64, bool, error) {
|
||||
text := src.Imm.Float
|
||||
if text == "" {
|
||||
s := strings.Join(strings.Fields(src.Raw), "")
|
||||
s = strings.TrimPrefix(s, "$")
|
||||
if strings.HasPrefix(s, "(") && strings.HasSuffix(s, ")") &&
|
||||
strings.ContainsAny(s[1:len(s)-1], ".eE") {
|
||||
text = s[1 : len(s)-1]
|
||||
}
|
||||
}
|
||||
if text == "" {
|
||||
return 0, false, fmt.Errorf("not a floating-point constant")
|
||||
}
|
||||
f, err := strconv.ParseFloat(text, 64)
|
||||
if err != nil {
|
||||
return 0, false, err
|
||||
}
|
||||
if src.Imm.Neg {
|
||||
f = -f
|
||||
}
|
||||
if mnem == "MOVF" {
|
||||
return uint64(math.Float32bits(float32(f))), false, nil
|
||||
}
|
||||
return math.Float64bits(f), true, nil
|
||||
}
|
||||
|
||||
// riscvExtendBytes emits the SLLI + SRAI/SRLI pair the toolchain synthesises
|
||||
// for the MOVB/MOVH/MOVHU/MOVWU register moves, with the per-half compression
|
||||
// its compress pass applies: the SLLI compresses only in place (rd == rs1),
|
||||
// the SRAI/SRLI only for the prime registers X8 to X15.
|
||||
func riscvExtendBytes(rd, rs1, shamt int, arithmetic bool) []byte {
|
||||
var out []byte
|
||||
if rd == rs1 && rd != 0 {
|
||||
out = word16(rvcSLLI(uint32(rd), uint32(shamt)))
|
||||
} else {
|
||||
out = wordLE(riscvIType(riscvEnc{0x13, 0x1, 0x00}, rd, rs1, int32(shamt)))
|
||||
}
|
||||
if isRVCIntReg(rd) {
|
||||
funct2 := uint32(0x0)
|
||||
if arithmetic {
|
||||
funct2 = 0x1
|
||||
}
|
||||
out = append(out, word16(rvcCBShift(funct2, rvcReg3(rd), uint32(shamt)))...)
|
||||
} else {
|
||||
imm := int32(shamt)
|
||||
if arithmetic {
|
||||
imm = 0x400 | int32(shamt) // funct6 010000, the SRAI half
|
||||
}
|
||||
out = append(out, wordLE(riscvIType(riscvEnc{0x13, 0x5, 0x00}, rd, rd, imm))...)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// encodeRISCVSBFPLoad emits AUIPC X31 + FLW/FLD against a pooled constant
|
||||
// symbol, the toolchain's form for an FP destination: the address lands in
|
||||
// TMP because the destination register is not an integer one. The single
|
||||
// R_RISCV_PCREL_ITYPE relocation covers the pair.
|
||||
func encodeRISCVSBFPLoad(name string, rd int, double bool, relocs *[]Reloc) []byte {
|
||||
if relocs != nil {
|
||||
*relocs = append(*relocs, Reloc{Off: 0, After: 8, Name: name, Kind: RelRISCVPCRELIType})
|
||||
}
|
||||
auipc := riscvUType(riscvEnc{0x17, 0x0, 0x00}, 31, 0)
|
||||
width := uint32(0x2) // FLW
|
||||
if double {
|
||||
width = 0x3 // FLD
|
||||
}
|
||||
fl := riscvIType(riscvEnc{0x07, width, 0x00}, rd, 31, 0)
|
||||
return append(wordLE(auipc), wordLE(fl)...)
|
||||
}
|
||||
|
||||
// RiscvLiteral is one pooled 64-bit constant: a MOV whose immediate sits
|
||||
// beyond both the 32-bit span and the shift sequences loads its bits from a
|
||||
// read-only data symbol named like the toolchain's $i64 pool.
|
||||
|
||||
Reference in new issue
Block a user