feat(riscv64,loong64): operand tail, float DATA and honest port classification
Assisted-by: GLM 5.3 Flash
This commit is contained in:
+286
-35
@@ -6,6 +6,7 @@ package asm
|
||||
import (
|
||||
"errors"
|
||||
"fmt"
|
||||
"math/bits"
|
||||
"slices"
|
||||
"strings"
|
||||
|
||||
@@ -14,13 +15,14 @@ import (
|
||||
|
||||
// assembleRISCV assembles a RISC-V TEXT function body into machine code.
|
||||
// It handles the full RV64IMAFDC instruction set including RVC compression.
|
||||
func assembleRISCV(t *ast.Text) ([]byte, map[string]int, []Reloc, []LineEntry, []SpadjStep, error) {
|
||||
func assembleRISCV(t *ast.Text) ([]byte, map[string]int, []Reloc, []LineEntry, []SpadjStep, []RiscvLiteral, error) {
|
||||
fi := riscvComputeFrame(t)
|
||||
prologue := riscvPrologue(fi)
|
||||
guardLen, err := riscvGuardLen(fi)
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, nil, err
|
||||
return nil, nil, nil, nil, nil, nil, err
|
||||
}
|
||||
lits := &riscvLiterals{}
|
||||
|
||||
var relocs []Reloc
|
||||
var spadj []SpadjStep
|
||||
@@ -74,9 +76,9 @@ func assembleRISCV(t *ast.Text) ([]byte, map[string]int, []Reloc, []LineEntry, [
|
||||
pc := len(prologue)
|
||||
for i := range recs {
|
||||
branchLike := isBranchLike(recs[i].instr.Mnemonic.Text) || riscvIsCondBranch(recs[i].instr.Mnemonic.Text)
|
||||
code, err := encodeRISCVInstr(recs[i].instr, pc, offsets, fi, nil, nil) // no relocs in Pass 2
|
||||
code, err := encodeRISCVInstr(recs[i].instr, pc, offsets, fi, nil, nil, lits) // no relocs in Pass 2
|
||||
if err != nil && !(branchLike && riscvIsRangeError(err)) {
|
||||
return nil, nil, nil, nil, nil, fmt.Errorf("%s: %w", recs[i].instr.Mnemonic.Text, err)
|
||||
return nil, nil, nil, nil, nil, nil, fmt.Errorf("%s: %w", recs[i].instr.Mnemonic.Text, err)
|
||||
}
|
||||
if err != nil {
|
||||
code = make([]byte, 4)
|
||||
@@ -178,13 +180,20 @@ func assembleRISCV(t *ast.Text) ([]byte, map[string]int, []Reloc, []LineEntry, [
|
||||
}
|
||||
}
|
||||
if !changed {
|
||||
// Capture the final pcs for the N(PC) branch forms: their target
|
||||
// is the instruction N source slots away, resolved by index.
|
||||
// Capture the final pcs for the N(PC) branch and jump forms: the
|
||||
// target is the instruction N source slots away (N=0 the branch
|
||||
// itself, N negative backwards), resolved by index against the
|
||||
// final layout.
|
||||
pcRelPcs = map[*ast.Instr]int{}
|
||||
for i := range recs {
|
||||
if _, ok := riscvPCRelOffset(recs[i].instr); ok {
|
||||
pcRelPcs[recs[i].instr] = pcs[i]
|
||||
n, ok := riscvPCRelOffset(recs[i].instr)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
if i+n < 0 || i+n >= len(recs) {
|
||||
continue
|
||||
}
|
||||
pcRelPcs[recs[i].instr] = pcs[i+n]
|
||||
}
|
||||
break
|
||||
}
|
||||
@@ -198,7 +207,7 @@ func assembleRISCV(t *ast.Text) ([]byte, map[string]int, []Reloc, []LineEntry, [
|
||||
var out []byte
|
||||
guardBytes, guardReloc, err := riscvGuard(fi)
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, nil, err
|
||||
return nil, nil, nil, nil, nil, nil, err
|
||||
}
|
||||
if fi.needSplit {
|
||||
out = append(out, guardBytes...)
|
||||
@@ -220,11 +229,11 @@ func assembleRISCV(t *ast.Text) ([]byte, map[string]int, []Reloc, []LineEntry, [
|
||||
// The JMP a relaxation inserted: JAL X0 to the original target.
|
||||
targetOff, ok := offsets[r.jmpTo]
|
||||
if !ok {
|
||||
return nil, nil, nil, nil, nil, fmt.Errorf("undefined label %q", r.jmpTo)
|
||||
return nil, nil, nil, nil, nil, nil, fmt.Errorf("undefined label %q", r.jmpTo)
|
||||
}
|
||||
offset := int32(targetOff - pc)
|
||||
if err := riscvCheckJumpOffset(r.jmpTo, offset); err != nil {
|
||||
return nil, nil, nil, nil, nil, err
|
||||
return nil, nil, nil, nil, nil, nil, err
|
||||
}
|
||||
word := riscvJType(0, offset)
|
||||
code = []byte{byte(word), byte(word >> 8), byte(word >> 16), byte(word >> 24)}
|
||||
@@ -233,7 +242,7 @@ func assembleRISCV(t *ast.Text) ([]byte, map[string]int, []Reloc, []LineEntry, [
|
||||
// JMP, always the very next instruction (offset 4).
|
||||
enc, rs1, rs2, ok := riscvInvertedBranchEnc(strings.ToUpper(r.instr.Mnemonic.Text), r.instr.Operands)
|
||||
if !ok {
|
||||
return nil, nil, nil, nil, nil, fmt.Errorf("%s: cannot relax branch", r.instr.Mnemonic.Text)
|
||||
return nil, nil, nil, nil, nil, nil, fmt.Errorf("%s: cannot relax branch", r.instr.Mnemonic.Text)
|
||||
}
|
||||
word := riscvBType(enc, rs1, rs2, 4)
|
||||
code = []byte{byte(word), byte(word >> 8), byte(word >> 16), byte(word >> 24)}
|
||||
@@ -241,9 +250,9 @@ func assembleRISCV(t *ast.Text) ([]byte, map[string]int, []Reloc, []LineEntry, [
|
||||
code = r.code
|
||||
default:
|
||||
var err error
|
||||
code, err = encodeRISCVInstr(r.instr, pc, offsets, fi, &relocs, pcRelPcs)
|
||||
code, err = encodeRISCVInstr(r.instr, pc, offsets, fi, &relocs, pcRelPcs, lits)
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, nil, err
|
||||
return nil, nil, nil, nil, nil, nil, err
|
||||
}
|
||||
if c16, ok := tryCompressRVC(r.instr, fi); ok {
|
||||
code = []byte{byte(c16), byte(c16 >> 8)}
|
||||
@@ -270,7 +279,7 @@ func assembleRISCV(t *ast.Text) ([]byte, map[string]int, []Reloc, []LineEntry, [
|
||||
if fi.needSplit {
|
||||
relocs = append(relocs, guardReloc)
|
||||
}
|
||||
return out, offsets, relocs, lines, spadj, nil
|
||||
return out, offsets, relocs, lines, spadj, lits.list(), nil
|
||||
}
|
||||
|
||||
// riscvImmAlias maps the R-type ALU mnemonics onto their I-type immediate
|
||||
@@ -348,6 +357,11 @@ func riscvPadBytes(pad int) []byte {
|
||||
func riscvInstrSize(instr *ast.Instr, fi riscvFrameInfo) int {
|
||||
mnem := instr.Mnemonic.Text
|
||||
ops := instr.Operands
|
||||
mnem = riscvNormalisePseudo(mnem)
|
||||
if mnem == "FUNCDATA" || mnem == "PCDATA" {
|
||||
// The bookkeeping statements contribute no bytes.
|
||||
return 0
|
||||
}
|
||||
var immNeg bool
|
||||
mnem, immNeg = riscvNormaliseImmAlias(mnem, ops)
|
||||
if mnem == "RET" {
|
||||
@@ -368,7 +382,25 @@ func riscvInstrSize(instr *ast.Instr, fi riscvFrameInfo) int {
|
||||
}
|
||||
// MOV $imm, rd → size depends on the immediate and RVC compression.
|
||||
if isImmOperand(ops[0]) && ops[0].Imm.Sym == nil {
|
||||
return riscvMovImmSize(regFromOperand(ops[1]), immFromOperand(ops[0]))
|
||||
imm := riscvOperandImm64(ops[0])
|
||||
if int64(int32(imm)) != imm {
|
||||
return riscvMovImm64Size(regFromOperand(ops[1]), imm)
|
||||
}
|
||||
return riscvMovImmSize(regFromOperand(ops[1]), int32(imm))
|
||||
}
|
||||
// MOV $sym+off(FP|SP), rd → the frame-adjusted offset as an ADDI,
|
||||
// compressed like riscvSPAddiBytes encodes it.
|
||||
if isImmOperand(ops[0]) && ops[0].Imm.Sym != nil &&
|
||||
(ops[0].Imm.Sym.Pseudo == "FP" || ops[0].Imm.Sym.Pseudo == "SP") {
|
||||
rd := regFromOperand(ops[1])
|
||||
_, off := riscvResolvePseudo(ops[0].Imm.Sym, fi)
|
||||
if rd > 0 && off == 0 {
|
||||
return 2 // C.MV rd, SP
|
||||
}
|
||||
if isRVCIntReg(rd) && off > 0 && off < 1024 && off%4 == 0 {
|
||||
return 2 // C.ADDI4SPN
|
||||
}
|
||||
return riscvItypeImmediateSize("ADDI", off)
|
||||
}
|
||||
// Frame-relative loads and stores: a frame offset beyond the signed
|
||||
// 12-bit range materialises the address in X31 first.
|
||||
@@ -664,9 +696,10 @@ func riscvCheckJumpOffset(target string, off int32) error {
|
||||
}
|
||||
|
||||
// encodeRISCVInstr encodes a single RISC-V instruction.
|
||||
func encodeRISCVInstr(instr *ast.Instr, pc int, offsets map[string]int, fi riscvFrameInfo, relocs *[]Reloc, pcRelPcs map[*ast.Instr]int) ([]byte, error) {
|
||||
func encodeRISCVInstr(instr *ast.Instr, pc int, offsets map[string]int, fi riscvFrameInfo, relocs *[]Reloc, pcRelPcs map[*ast.Instr]int, lits *riscvLiterals) ([]byte, error) {
|
||||
mnem := instr.Mnemonic.Text
|
||||
ops := instr.Operands
|
||||
mnem = riscvNormalisePseudo(mnem)
|
||||
var immNeg bool
|
||||
mnem, immNeg = riscvNormaliseImmAlias(mnem, ops)
|
||||
var word uint32
|
||||
@@ -677,6 +710,22 @@ func encodeRISCVInstr(instr *ast.Instr, pc int, offsets map[string]int, fi riscv
|
||||
// RET = epilogue (restore LR and close the frame when present) +
|
||||
// uncompressed JALR X0, 0(X1) (the toolchain never compresses RET).
|
||||
return riscvReturn(fi), nil
|
||||
case "FUNCDATA":
|
||||
// The assembler's bookkeeping statement, the expanded form of the
|
||||
// GO_ARGS and NO_LOCAL_POINTERS macros: FUNCDATA $n, sym(SB)
|
||||
// contributes no bytes, exactly as the toolchain's listing shows
|
||||
// (the FUNCDATA entries and the instruction after them share a PC).
|
||||
if len(ops) != 2 || !isImmOperand(ops[0]) {
|
||||
return nil, fmt.Errorf("FUNCDATA expects $n, sym(SB)")
|
||||
}
|
||||
return nil, nil
|
||||
case "PCDATA":
|
||||
// The other bookkeeping statement, the expanded form of
|
||||
// GO_RESULTS_INITIALIZED: PCDATA $n, $m contributes no bytes too.
|
||||
if len(ops) != 2 || !isImmOperand(ops[0]) || !isImmOperand(ops[1]) {
|
||||
return nil, fmt.Errorf("PCDATA expects $n, $m")
|
||||
}
|
||||
return nil, nil
|
||||
case "WORD":
|
||||
// WORD $w lays down a raw 32-bit little-endian word.
|
||||
if len(ops) != 1 {
|
||||
@@ -739,6 +788,22 @@ 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
|
||||
}
|
||||
target = labelFromOperand(ops[0])
|
||||
// JMP N(PC): the PC-relative slot form, resolved like the
|
||||
// branches (the toolchain counts source instructions at a
|
||||
// uniform 4 bytes, so JMP 0(PC) is a self-loop and JMP -3(PC)
|
||||
// reaches twelve bytes back). It must be recognised before the
|
||||
// indirect-register form, whose operand it resembles.
|
||||
if off, isPCRel, err := riscvPCRelTargetOff(instr, pc, pcRelPcs); isPCRel {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
offset := int32(off - pc)
|
||||
if err := riscvCheckJumpOffset("", offset); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
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).
|
||||
if ops[0].Addr.Sym == nil && ops[0].Addr.Base != "" {
|
||||
if ops[0].Addr.Offset != 0 || ops[0].Addr.Index != "" {
|
||||
@@ -751,17 +816,6 @@ func encodeRISCVInstr(instr *ast.Instr, pc int, offsets map[string]int, fi riscv
|
||||
word = riscvIType(riscvEnc{0x67, 0x0, 0x00}, 0, rs1, 0)
|
||||
return []byte{byte(word), byte(word >> 8), byte(word >> 16), byte(word >> 24)}, nil
|
||||
}
|
||||
if off, isPCRel, err := riscvPCRelTargetOff(instr, pc, pcRelPcs); isPCRel {
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
offset := int32(off - pc)
|
||||
if err := riscvCheckJumpOffset("", offset); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
word = riscvJType(0, offset)
|
||||
return []byte{byte(word), byte(word >> 8), byte(word >> 16), byte(word >> 24)}, nil
|
||||
}
|
||||
}
|
||||
targetOff, ok := offsets[target]
|
||||
if !ok {
|
||||
@@ -810,7 +864,7 @@ func encodeRISCVInstr(instr *ast.Instr, pc int, offsets map[string]int, fi riscv
|
||||
// (MOVB/MOVH/MOVW and unsigned forms) select the access width, and
|
||||
// MOVD/MOVF address the FP registers.
|
||||
case "MOV", "MOVB", "MOVBU", "MOVH", "MOVHU", "MOVW", "MOVWU", "MOVF", "MOVD":
|
||||
return encodeRISCVMov(instr, fi, relocs)
|
||||
return encodeRISCVMov(instr, fi, relocs, lits)
|
||||
|
||||
// JALR: indirect jump/call. Plan 9: JALR rs1, rd or JALR offset(rs1).
|
||||
case "JALR":
|
||||
@@ -1316,7 +1370,7 @@ func isImmOperand(op *ast.Operand) bool {
|
||||
// - MOV Rs, (Rd) register-relative store
|
||||
// - MOV Rs, Rd register-to-register move (ADDI $0)
|
||||
// - MOV $imm, Rd load immediate (ADDI or LUI+ADDIW)
|
||||
func encodeRISCVMov(instr *ast.Instr, fi riscvFrameInfo, relocs *[]Reloc) ([]byte, error) {
|
||||
func encodeRISCVMov(instr *ast.Instr, fi riscvFrameInfo, relocs *[]Reloc, lits *riscvLiterals) ([]byte, error) {
|
||||
ops := instr.Operands
|
||||
if len(ops) != 2 {
|
||||
return nil, fmt.Errorf("MOV expects 2 operands, got %d", len(ops))
|
||||
@@ -1335,8 +1389,21 @@ func encodeRISCVMov(instr *ast.Instr, fi riscvFrameInfo, relocs *[]Reloc) ([]byt
|
||||
}
|
||||
return encodeRISCVSBAddr(src.Imm.Sym, rd, relocs), nil
|
||||
}
|
||||
// MOV $sym+off(FP|SP), rd: the address of a frame slot as an
|
||||
// immediate is the frame-adjusted offset against the hardware SP,
|
||||
// the toolchain's ADDI $adj, SP, rd (argframe+0(FP) in the runtime's
|
||||
// reflect trampolines is the spelling).
|
||||
if src.Imm.Sym != nil && (src.Imm.Sym.Pseudo == "FP" || src.Imm.Sym.Pseudo == "SP") {
|
||||
rd := regFromOperand(dst)
|
||||
if rd < 0 {
|
||||
return nil, fmt.Errorf("MOV $%s(%s): invalid destination register", src.Imm.Sym.Name, src.Imm.Sym.Pseudo)
|
||||
}
|
||||
_, off := riscvResolvePseudo(src.Imm.Sym, fi)
|
||||
return riscvSPAddiBytes(rd, off), nil
|
||||
}
|
||||
// MOV $sym(FP/SP), rd, not supported: immediate symbol references
|
||||
// other than SB cannot be encoded as a simple immediate.
|
||||
// other than the frame pseudos cannot be encoded as a simple
|
||||
// immediate.
|
||||
if src.Imm.Sym != nil && src.Imm.Sym.Pseudo != "" {
|
||||
return nil, fmt.Errorf("MOV $%s(%s): unsupported immediate symbol reference (only SB is supported)", src.Imm.Sym.Name, src.Imm.Sym.Pseudo)
|
||||
}
|
||||
@@ -1344,11 +1411,14 @@ func encodeRISCVMov(instr *ast.Instr, fi riscvFrameInfo, relocs *[]Reloc) ([]byt
|
||||
if rd < 0 {
|
||||
return nil, fmt.Errorf("MOV $imm: invalid destination register")
|
||||
}
|
||||
imm, err := riscvImm32FromOperand(src, false)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
imm := riscvOperandImm64(src)
|
||||
if int64(int32(imm)) != imm {
|
||||
// Beyond the signed 32-bit span the toolchain either builds the
|
||||
// value from a shifted 32-bit part or loads it from the pooled
|
||||
// $i64 constant it synthesises for the purpose.
|
||||
return riscvLoadImm64(rd, imm, lits, relocs), nil
|
||||
}
|
||||
return encodeRISCVLoadImm(rd, imm), nil
|
||||
return encodeRISCVLoadImm(rd, int32(imm)), nil
|
||||
}
|
||||
|
||||
// Memory → register (load).
|
||||
@@ -1557,6 +1627,186 @@ func splitRISCV32Imm(imm int32) (low, high int32) {
|
||||
return low, high
|
||||
}
|
||||
|
||||
// riscvNormalisePseudo rewrites the toolchain's UNDEF spelling onto EBREAK:
|
||||
// the assembler accepts UNDEF where the hardware wants the trap instruction
|
||||
// and emits ebreak (compressed to C.EBREAK under RVC), so every pass sees the
|
||||
// canonical name.
|
||||
func riscvNormalisePseudo(mnem string) string {
|
||||
if strings.EqualFold(mnem, "UNDEF") {
|
||||
return "EBREAK"
|
||||
}
|
||||
return mnem
|
||||
}
|
||||
|
||||
// riscvOperandImm64 reads an immediate operand as a full signed 64-bit value,
|
||||
// where immFromOperand would truncate to int32; the MOV immediate path uses
|
||||
// it to classify the wide constants.
|
||||
func riscvOperandImm64(op *ast.Operand) int64 {
|
||||
if !op.Imm.HasVal {
|
||||
return 0
|
||||
}
|
||||
v := op.Imm.Val
|
||||
if op.Imm.Neg {
|
||||
v = -v
|
||||
}
|
||||
return v
|
||||
}
|
||||
|
||||
// riscvSplitShiftConst mirrors cmd/internal/obj/riscv's splitShiftConst: it
|
||||
// looks for the signed 32-bit integer a constant can be rebuilt from with a
|
||||
// left shift, a left-and-right shift pair (a run of ones), or a zero-extended
|
||||
// 32-bit pattern. A constant that fits none of the shapes is materialised
|
||||
// from the pooled $i64 data symbol instead.
|
||||
func riscvSplitShiftConst(v int64) (imm int64, lsh int, rsh int, ok bool) {
|
||||
// Rebuild from a signed 32-bit integer shifted left.
|
||||
lsh = bits.TrailingZeros64(uint64(v))
|
||||
c := v >> lsh
|
||||
if int64(int32(c)) == c {
|
||||
return c, lsh, 0, true
|
||||
}
|
||||
|
||||
// Rebuild from a small negative constant: shift left into place, then
|
||||
// shift the sign-extended ones run right.
|
||||
rsh = bits.LeadingZeros64(uint64(v))
|
||||
ones := bits.OnesCount64((uint64(v) >> lsh) >> 11)
|
||||
if rsh+ones+lsh+11 == 64 {
|
||||
c = (1<<11 | ((v >> lsh) & 0x7ff)) << 52 >> 52 // sign extend 12 bits
|
||||
if lsh > 0 || c != -1 {
|
||||
lsh += rsh
|
||||
}
|
||||
return c, lsh, rsh, true
|
||||
}
|
||||
|
||||
// Rebuild from a zero-extended signed 32-bit integer.
|
||||
if int64(uint32(c)) == c {
|
||||
c = int64(int32(c))
|
||||
lsh, rsh = 32, 32-lsh
|
||||
return c, lsh, rsh, true
|
||||
}
|
||||
|
||||
return 0, 0, 0, false
|
||||
}
|
||||
|
||||
// riscvSPAddiBytes encodes ADDI rd, SP, imm for the frame-address immediates
|
||||
// (the MOV $sym+off(FP|SP) form), using the compressed forms the toolchain
|
||||
// picks under RVC: C.ADDI4SPN for a positive 4-byte multiple that fits, C.MV
|
||||
// for the zero offset, the plain ADDI otherwise.
|
||||
func riscvSPAddiBytes(rd int, imm int32) []byte {
|
||||
if rd != 0 && imm == 0 {
|
||||
return word16(rvcCR(0x8, uint32(rd), 2)) // C.MV rd, SP
|
||||
}
|
||||
if isRVCIntReg(rd) && imm > 0 && imm < 1024 && imm%4 == 0 {
|
||||
return word16(rvcCIW(0x0, rvcReg3(rd), uint32(imm)))
|
||||
}
|
||||
return wordLE(riscvIType(riscvInstrTable["ADDI"], rd, 2, imm))
|
||||
}
|
||||
|
||||
// riscvMovImm64Size returns the encoded byte length of MOV $imm, rd when the
|
||||
// immediate sits outside the signed 32-bit span: the shifted-part sequences
|
||||
// of riscvLoadImm64, or the 8-byte AUIPC+LD pool load.
|
||||
func riscvMovImm64Size(rd int, imm int64) int {
|
||||
c, lsh, rsh, ok := riscvSplitShiftConst(imm)
|
||||
if !ok {
|
||||
return 8 // AUIPC + LD against the $i64 pool symbol
|
||||
}
|
||||
size := riscvMovImmSize(rd, int32(c))
|
||||
if lsh > 0 {
|
||||
size += riscvShiftImmSize(rd, true)
|
||||
}
|
||||
if rsh > 0 {
|
||||
size += riscvShiftImmSize(rd, false)
|
||||
}
|
||||
return size
|
||||
}
|
||||
|
||||
// riscvShiftImmSize returns the encoded size of one SLLI/SRLI expansion
|
||||
// part: two bytes under RVC when the destination can carry a compressed
|
||||
// shift (C.SLLI admits every register but X0, C.SRLI only X8 to X15), four
|
||||
// otherwise.
|
||||
func riscvShiftImmSize(rd int, left bool) int {
|
||||
if rd != 0 && (left || isRVCIntReg(rd)) {
|
||||
return 2
|
||||
}
|
||||
return 4
|
||||
}
|
||||
|
||||
// riscvLoadImm64 encodes MOV $imm, rd for an immediate beyond the signed
|
||||
// 32-bit span, mirroring the toolchain's instructionsForMOVConst: when a
|
||||
// shifted 32-bit part rebuilds the value it emits that part (compressed like
|
||||
// any written MOV) followed by the SLLI and SRLI shifts; otherwise it loads
|
||||
// the constant from the pooled read-only $i64.<hex> symbol via AUIPC + LD
|
||||
// and registers the literal so the data section carries its bytes.
|
||||
func riscvLoadImm64(rd int, imm int64, lits *riscvLiterals, relocs *[]Reloc) []byte {
|
||||
c, lsh, rsh, ok := riscvSplitShiftConst(imm)
|
||||
if !ok {
|
||||
name := fmt.Sprintf("$i64.%016x", uint64(imm))
|
||||
if lits != nil {
|
||||
lits.add(name, riscvLiteralBytes(imm))
|
||||
}
|
||||
return encodeRISCVSBLoad(&ast.Symbol{Name: name}, rd, relocs)
|
||||
}
|
||||
out := encodeRISCVLoadImm(rd, int32(c))
|
||||
if lsh > 0 {
|
||||
out = append(out, riscvShiftImmBytes(rd, lsh, true)...)
|
||||
}
|
||||
if rsh > 0 {
|
||||
out = append(out, riscvShiftImmBytes(rd, rsh, false)...)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// riscvShiftImmBytes encodes one SLLI (left) or SRLI expansion part, using
|
||||
// the compressed form the toolchain picks under RVC: C.SLLI admits every
|
||||
// register but X0, C.SRLI only X8 to X15.
|
||||
func riscvShiftImmBytes(rd, shamt int, left bool) []byte {
|
||||
if rd != 0 && shamt >= 1 && shamt <= 63 && (left || isRVCIntReg(rd)) {
|
||||
if left {
|
||||
return word16(rvcSLLI(uint32(rd), uint32(shamt)&0x3F))
|
||||
}
|
||||
return word16(rvcCBShift(0x0, rvcReg3(rd), uint32(shamt)&0x3F))
|
||||
}
|
||||
enc := riscvEnc{0x13, 0x1, 0x00} // SLLI
|
||||
imm := int32(shamt)
|
||||
if !left {
|
||||
enc = riscvEnc{0x13, 0x5, 0x00} // SRLI: funct6 000000, funct3 101
|
||||
}
|
||||
return wordLE(riscvIType(enc, rd, rd, imm))
|
||||
}
|
||||
|
||||
// riscvLiteralBytes renders a 64-bit constant as the little-endian bytes the
|
||||
// $i64 pool symbol holds.
|
||||
func riscvLiteralBytes(v int64) []byte {
|
||||
return []byte{byte(v), byte(v >> 8), byte(v >> 16), byte(v >> 24),
|
||||
byte(v >> 32), byte(v >> 40), byte(v >> 48), byte(v >> 56)}
|
||||
}
|
||||
|
||||
// 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.
|
||||
type RiscvLiteral struct {
|
||||
Name string
|
||||
Data []byte
|
||||
}
|
||||
|
||||
// riscvLiterals collects the pooled constants the MOV expansions refer to,
|
||||
// deduplicated by name, in first-use order.
|
||||
type riscvLiterals struct {
|
||||
order []RiscvLiteral
|
||||
seen map[string]bool
|
||||
}
|
||||
|
||||
func (l *riscvLiterals) add(name string, data []byte) {
|
||||
if l.seen == nil {
|
||||
l.seen = map[string]bool{}
|
||||
}
|
||||
if !l.seen[name] {
|
||||
l.seen[name] = true
|
||||
l.order = append(l.order, RiscvLiteral{Name: name, Data: data})
|
||||
}
|
||||
}
|
||||
|
||||
func (l *riscvLiterals) list() []RiscvLiteral { return l.order }
|
||||
|
||||
// encodeRISCVItypeImmediate encodes an I-type arithmetic instruction, expanding
|
||||
// large immediates for ADDI/ANDI/ORI/XORI into LUI+ADDIW+op (or two ADDIs for
|
||||
// ADDI), matching the Go assembler.
|
||||
@@ -1734,6 +1984,7 @@ func encodeRISCVJALR(instr *ast.Instr, fi riscvFrameInfo) ([]byte, error) {
|
||||
// RVC form. It returns the compressed instruction word and true on success.
|
||||
func tryCompressRVC(instr *ast.Instr, fi riscvFrameInfo) (uint16, bool) {
|
||||
mnem := riscvCompressMnem(instr)
|
||||
mnem = riscvNormalisePseudo(mnem)
|
||||
ops := instr.Operands
|
||||
// The immediate aliases fold onto their I-type mnemonics before
|
||||
// compression: the toolchain compresses ADD $imm, rd as c.addi, exactly
|
||||
|
||||
+1
-1
@@ -63,7 +63,7 @@ func riscvRegNum(name string) int {
|
||||
return 24
|
||||
case "X25", "S9":
|
||||
return 25
|
||||
case "X26", "S10":
|
||||
case "X26", "S10", "CTXT":
|
||||
return 26
|
||||
case "X27", "S11", "g":
|
||||
return 27
|
||||
|
||||
+152
-19
@@ -33,7 +33,7 @@ func firstTextRISCV(t *testing.T, src string) *ast.Text {
|
||||
// assembleRISCVHelper assembles one TEXT function and returns its code bytes.
|
||||
func assembleRISCVHelper(t *testing.T, fn *ast.Text) []byte {
|
||||
t.Helper()
|
||||
code, _, _, _, _, err := assembleRISCV(fn)
|
||||
code, _, _, _, _, _, err := assembleRISCV(fn)
|
||||
if err != nil {
|
||||
t.Fatalf("assemble: %v", err)
|
||||
}
|
||||
@@ -785,16 +785,140 @@ TEXT ·sys(SB), NOSPLIT, $0
|
||||
}
|
||||
}
|
||||
|
||||
func TestRISCV_MOV_sym_FP_error(t *testing.T) {
|
||||
// MOV $sym(FP), rd should return an error (unsupported).
|
||||
func TestRISCV_MOV_sym_FP(t *testing.T) {
|
||||
// MOV $sym(FP), rd lowers to the frame-adjusted ADDI against SP: the
|
||||
// toolchain's argframe spelling. A zero frame leaves the offset at the
|
||||
// 8-byte link slot, compressed to C.ADDI4SPN.
|
||||
fn := firstTextRISCV(t, `#include "textflag.h"
|
||||
TEXT ·badfp(SB), NOSPLIT, $0
|
||||
TEXT ·argfp(SB), NOSPLIT, $0
|
||||
MOV $arg(FP), X10
|
||||
RET
|
||||
`)
|
||||
_, _, _, _, _, err := assembleRISCV(fn)
|
||||
if err == nil {
|
||||
t.Error("expected error for MOV $arg(FP), got nil")
|
||||
code, _, _, _, _, _, err := assembleRISCV(fn)
|
||||
if err != nil {
|
||||
t.Fatalf("assemble: %v", err)
|
||||
}
|
||||
// prologue (0: leaf, zero frame) + C.ADDI4SPN (2) + RET (4) = 6
|
||||
want := []byte{0x28, 0x00, 0x67, 0x80, 0x00, 0x00}
|
||||
if string(code) != string(want) {
|
||||
t.Errorf("got % x, want % x", code, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRISCV_Bookkeeping(t *testing.T) {
|
||||
// FUNCDATA and PCDATA contribute no bytes; UNDEF is the toolchain's
|
||||
// ebreak, compressed to C.EBREAK under RVC.
|
||||
fn := firstTextRISCV(t, `#include "textflag.h"
|
||||
TEXT ·book(SB), NOSPLIT, $0-8
|
||||
FUNCDATA $0, marks<>(SB)
|
||||
PCDATA $1, $1
|
||||
UNDEF
|
||||
MOV $1, X10
|
||||
MOV X10, ret+0(FP)
|
||||
RET
|
||||
`)
|
||||
code, _, _, _, _, _, err := assembleRISCV(fn)
|
||||
if err != nil {
|
||||
t.Fatalf("assemble: %v", err)
|
||||
}
|
||||
// C.EBREAK (2) + C.LI X10, 1 (2) + C.SWSP (2) + RET (4) = 10: the
|
||||
// FUNCDATA and PCDATA statements contribute nothing.
|
||||
want := []byte{0x02, 0x90, 0x05, 0x45, 0x2a, 0xe4, 0x67, 0x80, 0x00, 0x00}
|
||||
if string(code) != string(want) {
|
||||
t.Errorf("got % x, want % x", code, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRISCV_JMPPCRel(t *testing.T) {
|
||||
// JMP N(PC): the displacement tracks the instruction N source slots
|
||||
// away in the final layout (0 the jump itself, negative backwards).
|
||||
fn := firstTextRISCV(t, `#include "textflag.h"
|
||||
TEXT ·slots(SB), NOSPLIT, $0-0
|
||||
JMP 2(PC)
|
||||
MOV $1, X11
|
||||
MOV $2, X12
|
||||
MOV X12, X11
|
||||
JMP -3(PC)
|
||||
RET
|
||||
`)
|
||||
code, _, _, _, _, _, err := assembleRISCV(fn)
|
||||
if err != nil {
|
||||
t.Fatalf("assemble: %v", err)
|
||||
}
|
||||
// JMP 2(PC) lands on the C.MV six bytes ahead; JMP -3(PC) lands back on
|
||||
// the first C.LI, six bytes behind.
|
||||
want := []byte{
|
||||
0x6f, 0x00, 0x60, 0x00, // JAL X0, 6
|
||||
0x85, 0x45, // C.LI X11, 1
|
||||
0x09, 0x46, // C.LI X12, 2
|
||||
0xb2, 0x85, // C.MV X11, X12
|
||||
0x6f, 0xf0, 0xbf, 0xff, // JAL X0, -6
|
||||
0x67, 0x80, 0x00, 0x00, // RET
|
||||
}
|
||||
if string(code) != string(want) {
|
||||
t.Errorf("got % x, want % x", code, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRISCV_MOVWideImm(t *testing.T) {
|
||||
// Shift-sequence constants compress like the toolchain's expansion.
|
||||
fn := firstTextRISCV(t, `#include "textflag.h"
|
||||
TEXT ·wide(SB), NOSPLIT, $0-0
|
||||
MOV $0x8000000000000000, X5
|
||||
MOV $0x100000000, X5
|
||||
MOV $0x000fffffffffffda, X5
|
||||
RET
|
||||
`)
|
||||
code, _, _, _, _, _, err := assembleRISCV(fn)
|
||||
if err != nil {
|
||||
t.Fatalf("assemble: %v", err)
|
||||
}
|
||||
// C.LI -1, C.SLLI 63; C.LI 1, C.SLLI 32; C.LI -19, C.SLLI 13, SRLI 12.
|
||||
want := []byte{
|
||||
0xfd, 0x52, 0xfe, 0x12,
|
||||
0x85, 0x42, 0x82, 0x12,
|
||||
0xb5, 0x52, 0xb6, 0x02, 0x93, 0xd2, 0xc2, 0x00,
|
||||
0x67, 0x80, 0x00, 0x00,
|
||||
}
|
||||
if string(code) != string(want) {
|
||||
t.Errorf("got % x, want % x", code, want)
|
||||
}
|
||||
}
|
||||
|
||||
func TestRISCV_MOVImmPool(t *testing.T) {
|
||||
// A constant outside the shift shapes loads from the pooled $i64 data
|
||||
// symbol via AUIPC+LD, named like the toolchain's pool.
|
||||
src := `#include "textflag.h"
|
||||
TEXT ·pool(SB), NOSPLIT, $0-8
|
||||
MOV $0x0101010101010101, X16
|
||||
MOV X16, ret+0(FP)
|
||||
RET
|
||||
`
|
||||
f, errs := parser.Parse("pool_riscv64.s", src)
|
||||
if len(errs) > 0 {
|
||||
t.Fatalf("parse: %v", errs)
|
||||
}
|
||||
img, err := AssembleFileRISCV(f)
|
||||
if err != nil {
|
||||
t.Fatalf("AssembleFileRISCV: %v", err)
|
||||
}
|
||||
// AUIPC X16, 0 + LD X16, 0(X16): the relocation pair carries the symbol.
|
||||
wantCode := []byte{0x17, 0x08, 0x00, 0x00, 0x03, 0x38, 0x08, 0x00}
|
||||
if string(img.Code[0:8]) != string(wantCode) {
|
||||
t.Errorf("pool load: got % x", img.Code[0:8])
|
||||
}
|
||||
var lit *DataSymbol
|
||||
for i := range img.DataSyms {
|
||||
if img.DataSyms[i].Name == "$i64.0101010101010101" {
|
||||
lit = &img.DataSyms[i]
|
||||
}
|
||||
}
|
||||
if lit == nil {
|
||||
t.Fatalf("pool symbol missing: %v", img.DataSyms)
|
||||
}
|
||||
wantData := []byte{0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01, 0x01}
|
||||
if string(img.Data[lit.Offset:lit.Offset+8]) != string(wantData) {
|
||||
t.Errorf("pool bytes: got % x", img.Data[lit.Offset:lit.Offset+8])
|
||||
}
|
||||
}
|
||||
|
||||
@@ -805,7 +929,7 @@ TEXT ·calltest(SB), NOSPLIT, $0
|
||||
CALL ext(SB)
|
||||
RET
|
||||
`)
|
||||
code, _, relocs, _, _, err := assembleRISCV(fn)
|
||||
code, _, relocs, _, _, _, err := assembleRISCV(fn)
|
||||
if err != nil {
|
||||
t.Fatalf("assemble: %v", err)
|
||||
}
|
||||
@@ -834,7 +958,7 @@ TEXT ·calllocal(SB), NOSPLIT, $0
|
||||
sub:
|
||||
RET
|
||||
`)
|
||||
_, _, _, _, _, err := assembleRISCV(fn)
|
||||
_, _, _, _, _, _, err := assembleRISCV(fn)
|
||||
if err == nil {
|
||||
t.Error("expected error for CALL to local label, got nil")
|
||||
}
|
||||
@@ -868,7 +992,7 @@ func encodeOneInstrRISCV(t *testing.T, src string, pc int, offsets map[string]in
|
||||
t.Helper()
|
||||
fn := firstTextRISCV(t, "#include \"textflag.h\"\n"+src)
|
||||
instr := fn.Body[0].(*ast.Instr)
|
||||
return encodeRISCVInstr(instr, pc, offsets, riscvFrameInfo{}, nil, nil)
|
||||
return encodeRISCVInstr(instr, pc, offsets, riscvFrameInfo{}, nil, nil, nil)
|
||||
}
|
||||
|
||||
// TestRISCVBranchJumpRange checks that displacements beyond the B-type span
|
||||
@@ -917,7 +1041,7 @@ func TestRISCVBranchFarBody(t *testing.T) {
|
||||
}
|
||||
sb.WriteString("done:\n\tRET\n")
|
||||
fn := firstTextRISCV(t, sb.String())
|
||||
out, _, _, _, _, err := assembleRISCV(fn)
|
||||
out, _, _, _, _, _, err := assembleRISCV(fn)
|
||||
if err != nil {
|
||||
t.Fatalf("unexpected error: %v", err)
|
||||
}
|
||||
@@ -943,7 +1067,7 @@ TEXT ·csrhi(SB), NOSPLIT, $0
|
||||
CSRRW $4096, X10, X11
|
||||
RET
|
||||
`)
|
||||
if _, _, _, _, _, err := assembleRISCV(fn); err == nil {
|
||||
if _, _, _, _, _, _, err := assembleRISCV(fn); err == nil {
|
||||
t.Error("expected an out-of-range error for CSR $4096, got none")
|
||||
}
|
||||
fn = firstTextRISCV(t, `#include "textflag.h"
|
||||
@@ -951,25 +1075,24 @@ TEXT ·csrmax(SB), NOSPLIT, $0
|
||||
CSRRW $4095, X10, X11
|
||||
RET
|
||||
`)
|
||||
if _, _, _, _, _, err := assembleRISCV(fn); err != nil {
|
||||
if _, _, _, _, _, _, err := assembleRISCV(fn); err != nil {
|
||||
t.Errorf("CSR $4095 must assemble: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestRISCV_Imm64Rejected checks that immediates outside the signed 32-bit
|
||||
// span are diagnosed instead of silently truncated to their low 32 bits (the
|
||||
// toolchain materialises such constants via SLLI expansion, which this
|
||||
// assembler does not implement).
|
||||
// span are diagnosed instead of silently truncated to their low 32 bits for
|
||||
// the I-type arithmetic; the MOV forms materialise the wide constant instead
|
||||
// (shift sequence or pooled load), like the toolchain.
|
||||
func TestRISCV_Imm64Rejected(t *testing.T) {
|
||||
cases := []string{
|
||||
"MOV $0x123456789, X10",
|
||||
"ADDI $0x100000000, X10, X11",
|
||||
"ANDI $-0x800000001, X10, X11",
|
||||
"SUB $0x100000000, X10, X11",
|
||||
}
|
||||
for _, src := range cases {
|
||||
fn := firstTextRISCV(t, "#include \"textflag.h\"\nTEXT ·wide(SB), NOSPLIT, $0\n\t"+src+"\n\tRET\n")
|
||||
if _, _, _, _, _, err := assembleRISCV(fn); err == nil {
|
||||
if _, _, _, _, _, _, err := assembleRISCV(fn); err == nil {
|
||||
t.Errorf("%s: expected an out-of-range error, got none", src)
|
||||
}
|
||||
}
|
||||
@@ -982,9 +1105,19 @@ TEXT ·edge(SB), NOSPLIT, $0
|
||||
SUB $0x80000000, X12, X13
|
||||
RET
|
||||
`)
|
||||
if _, _, _, _, _, err := assembleRISCV(fn); err != nil {
|
||||
if _, _, _, _, _, _, err := assembleRISCV(fn); err != nil {
|
||||
t.Errorf("int32-span immediates must assemble: %v", err)
|
||||
}
|
||||
// Beyond the span the MOV forms materialise the constant like the
|
||||
// toolchain instead of diagnosing it.
|
||||
fn = firstTextRISCV(t, `#include "textflag.h"
|
||||
TEXT ·pool(SB), NOSPLIT, $0
|
||||
MOV $0x123456789, X10
|
||||
RET
|
||||
`)
|
||||
if _, _, _, _, _, _, err := assembleRISCV(fn); err != nil {
|
||||
t.Errorf("MOV with a 64-bit immediate must assemble: %v", err)
|
||||
}
|
||||
}
|
||||
|
||||
// riscvWants decodes code as little-endian words and pins each one; the
|
||||
|
||||
Reference in New Issue
Block a user