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 (
|
import (
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
"fmt"
|
||||||
|
"math/bits"
|
||||||
"slices"
|
"slices"
|
||||||
"strings"
|
"strings"
|
||||||
|
|
||||||
@@ -14,13 +15,14 @@ import (
|
|||||||
|
|
||||||
// assembleRISCV assembles a RISC-V TEXT function body into machine code.
|
// assembleRISCV assembles a RISC-V TEXT function body into machine code.
|
||||||
// It handles the full RV64IMAFDC instruction set including RVC compression.
|
// 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)
|
fi := riscvComputeFrame(t)
|
||||||
prologue := riscvPrologue(fi)
|
prologue := riscvPrologue(fi)
|
||||||
guardLen, err := riscvGuardLen(fi)
|
guardLen, err := riscvGuardLen(fi)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, nil, nil, nil, err
|
return nil, nil, nil, nil, nil, nil, err
|
||||||
}
|
}
|
||||||
|
lits := &riscvLiterals{}
|
||||||
|
|
||||||
var relocs []Reloc
|
var relocs []Reloc
|
||||||
var spadj []SpadjStep
|
var spadj []SpadjStep
|
||||||
@@ -74,9 +76,9 @@ func assembleRISCV(t *ast.Text) ([]byte, map[string]int, []Reloc, []LineEntry, [
|
|||||||
pc := len(prologue)
|
pc := len(prologue)
|
||||||
for i := range recs {
|
for i := range recs {
|
||||||
branchLike := isBranchLike(recs[i].instr.Mnemonic.Text) || riscvIsCondBranch(recs[i].instr.Mnemonic.Text)
|
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)) {
|
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 {
|
if err != nil {
|
||||||
code = make([]byte, 4)
|
code = make([]byte, 4)
|
||||||
@@ -178,13 +180,20 @@ func assembleRISCV(t *ast.Text) ([]byte, map[string]int, []Reloc, []LineEntry, [
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if !changed {
|
if !changed {
|
||||||
// Capture the final pcs for the N(PC) branch forms: their target
|
// Capture the final pcs for the N(PC) branch and jump forms: the
|
||||||
// is the instruction N source slots away, resolved by index.
|
// 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{}
|
pcRelPcs = map[*ast.Instr]int{}
|
||||||
for i := range recs {
|
for i := range recs {
|
||||||
if _, ok := riscvPCRelOffset(recs[i].instr); ok {
|
n, ok := riscvPCRelOffset(recs[i].instr)
|
||||||
pcRelPcs[recs[i].instr] = pcs[i]
|
if !ok {
|
||||||
|
continue
|
||||||
}
|
}
|
||||||
|
if i+n < 0 || i+n >= len(recs) {
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
pcRelPcs[recs[i].instr] = pcs[i+n]
|
||||||
}
|
}
|
||||||
break
|
break
|
||||||
}
|
}
|
||||||
@@ -198,7 +207,7 @@ func assembleRISCV(t *ast.Text) ([]byte, map[string]int, []Reloc, []LineEntry, [
|
|||||||
var out []byte
|
var out []byte
|
||||||
guardBytes, guardReloc, err := riscvGuard(fi)
|
guardBytes, guardReloc, err := riscvGuard(fi)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, nil, nil, nil, nil, err
|
return nil, nil, nil, nil, nil, nil, err
|
||||||
}
|
}
|
||||||
if fi.needSplit {
|
if fi.needSplit {
|
||||||
out = append(out, guardBytes...)
|
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.
|
// The JMP a relaxation inserted: JAL X0 to the original target.
|
||||||
targetOff, ok := offsets[r.jmpTo]
|
targetOff, ok := offsets[r.jmpTo]
|
||||||
if !ok {
|
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)
|
offset := int32(targetOff - pc)
|
||||||
if err := riscvCheckJumpOffset(r.jmpTo, offset); err != nil {
|
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)
|
word := riscvJType(0, offset)
|
||||||
code = []byte{byte(word), byte(word >> 8), byte(word >> 16), byte(word >> 24)}
|
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).
|
// JMP, always the very next instruction (offset 4).
|
||||||
enc, rs1, rs2, ok := riscvInvertedBranchEnc(strings.ToUpper(r.instr.Mnemonic.Text), r.instr.Operands)
|
enc, rs1, rs2, ok := riscvInvertedBranchEnc(strings.ToUpper(r.instr.Mnemonic.Text), r.instr.Operands)
|
||||||
if !ok {
|
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)
|
word := riscvBType(enc, rs1, rs2, 4)
|
||||||
code = []byte{byte(word), byte(word >> 8), byte(word >> 16), byte(word >> 24)}
|
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
|
code = r.code
|
||||||
default:
|
default:
|
||||||
var err error
|
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 {
|
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 {
|
if c16, ok := tryCompressRVC(r.instr, fi); ok {
|
||||||
code = []byte{byte(c16), byte(c16 >> 8)}
|
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 {
|
if fi.needSplit {
|
||||||
relocs = append(relocs, guardReloc)
|
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
|
// 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 {
|
func riscvInstrSize(instr *ast.Instr, fi riscvFrameInfo) int {
|
||||||
mnem := instr.Mnemonic.Text
|
mnem := instr.Mnemonic.Text
|
||||||
ops := instr.Operands
|
ops := instr.Operands
|
||||||
|
mnem = riscvNormalisePseudo(mnem)
|
||||||
|
if mnem == "FUNCDATA" || mnem == "PCDATA" {
|
||||||
|
// The bookkeeping statements contribute no bytes.
|
||||||
|
return 0
|
||||||
|
}
|
||||||
var immNeg bool
|
var immNeg bool
|
||||||
mnem, immNeg = riscvNormaliseImmAlias(mnem, ops)
|
mnem, immNeg = riscvNormaliseImmAlias(mnem, ops)
|
||||||
if mnem == "RET" {
|
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.
|
// MOV $imm, rd → size depends on the immediate and RVC compression.
|
||||||
if isImmOperand(ops[0]) && ops[0].Imm.Sym == nil {
|
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
|
// Frame-relative loads and stores: a frame offset beyond the signed
|
||||||
// 12-bit range materialises the address in X31 first.
|
// 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.
|
// 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
|
mnem := instr.Mnemonic.Text
|
||||||
ops := instr.Operands
|
ops := instr.Operands
|
||||||
|
mnem = riscvNormalisePseudo(mnem)
|
||||||
var immNeg bool
|
var immNeg bool
|
||||||
mnem, immNeg = riscvNormaliseImmAlias(mnem, ops)
|
mnem, immNeg = riscvNormaliseImmAlias(mnem, ops)
|
||||||
var word uint32
|
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) +
|
// RET = epilogue (restore LR and close the frame when present) +
|
||||||
// uncompressed JALR X0, 0(X1) (the toolchain never compresses RET).
|
// uncompressed JALR X0, 0(X1) (the toolchain never compresses RET).
|
||||||
return riscvReturn(fi), nil
|
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":
|
case "WORD":
|
||||||
// WORD $w lays down a raw 32-bit little-endian word.
|
// WORD $w lays down a raw 32-bit little-endian word.
|
||||||
if len(ops) != 1 {
|
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
|
return []byte{byte(word), byte(word >> 8), byte(word >> 16), byte(word >> 24)}, nil
|
||||||
}
|
}
|
||||||
target = labelFromOperand(ops[0])
|
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).
|
// 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.Sym == nil && ops[0].Addr.Base != "" {
|
||||||
if ops[0].Addr.Offset != 0 || ops[0].Addr.Index != "" {
|
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)
|
word = riscvIType(riscvEnc{0x67, 0x0, 0x00}, 0, rs1, 0)
|
||||||
return []byte{byte(word), byte(word >> 8), byte(word >> 16), byte(word >> 24)}, nil
|
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]
|
targetOff, ok := offsets[target]
|
||||||
if !ok {
|
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
|
// (MOVB/MOVH/MOVW and unsigned forms) select the access width, and
|
||||||
// MOVD/MOVF address the FP registers.
|
// MOVD/MOVF address the FP registers.
|
||||||
case "MOV", "MOVB", "MOVBU", "MOVH", "MOVHU", "MOVW", "MOVWU", "MOVF", "MOVD":
|
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).
|
// JALR: indirect jump/call. Plan 9: JALR rs1, rd or JALR offset(rs1).
|
||||||
case "JALR":
|
case "JALR":
|
||||||
@@ -1316,7 +1370,7 @@ func isImmOperand(op *ast.Operand) bool {
|
|||||||
// - MOV Rs, (Rd) register-relative store
|
// - MOV Rs, (Rd) register-relative store
|
||||||
// - MOV Rs, Rd register-to-register move (ADDI $0)
|
// - MOV Rs, Rd register-to-register move (ADDI $0)
|
||||||
// - MOV $imm, Rd load immediate (ADDI or LUI+ADDIW)
|
// - 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
|
ops := instr.Operands
|
||||||
if len(ops) != 2 {
|
if len(ops) != 2 {
|
||||||
return nil, fmt.Errorf("MOV expects 2 operands, got %d", len(ops))
|
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
|
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
|
// 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 != "" {
|
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)
|
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 {
|
if rd < 0 {
|
||||||
return nil, fmt.Errorf("MOV $imm: invalid destination register")
|
return nil, fmt.Errorf("MOV $imm: invalid destination register")
|
||||||
}
|
}
|
||||||
imm, err := riscvImm32FromOperand(src, false)
|
imm := riscvOperandImm64(src)
|
||||||
if err != nil {
|
if int64(int32(imm)) != imm {
|
||||||
return nil, err
|
// 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).
|
// Memory → register (load).
|
||||||
@@ -1557,6 +1627,186 @@ func splitRISCV32Imm(imm int32) (low, high int32) {
|
|||||||
return low, high
|
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
|
// encodeRISCVItypeImmediate encodes an I-type arithmetic instruction, expanding
|
||||||
// large immediates for ADDI/ANDI/ORI/XORI into LUI+ADDIW+op (or two ADDIs for
|
// large immediates for ADDI/ANDI/ORI/XORI into LUI+ADDIW+op (or two ADDIs for
|
||||||
// ADDI), matching the Go assembler.
|
// 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.
|
// RVC form. It returns the compressed instruction word and true on success.
|
||||||
func tryCompressRVC(instr *ast.Instr, fi riscvFrameInfo) (uint16, bool) {
|
func tryCompressRVC(instr *ast.Instr, fi riscvFrameInfo) (uint16, bool) {
|
||||||
mnem := riscvCompressMnem(instr)
|
mnem := riscvCompressMnem(instr)
|
||||||
|
mnem = riscvNormalisePseudo(mnem)
|
||||||
ops := instr.Operands
|
ops := instr.Operands
|
||||||
// The immediate aliases fold onto their I-type mnemonics before
|
// The immediate aliases fold onto their I-type mnemonics before
|
||||||
// compression: the toolchain compresses ADD $imm, rd as c.addi, exactly
|
// compression: the toolchain compresses ADD $imm, rd as c.addi, exactly
|
||||||
|
|||||||
+1
-1
@@ -63,7 +63,7 @@ func riscvRegNum(name string) int {
|
|||||||
return 24
|
return 24
|
||||||
case "X25", "S9":
|
case "X25", "S9":
|
||||||
return 25
|
return 25
|
||||||
case "X26", "S10":
|
case "X26", "S10", "CTXT":
|
||||||
return 26
|
return 26
|
||||||
case "X27", "S11", "g":
|
case "X27", "S11", "g":
|
||||||
return 27
|
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.
|
// assembleRISCVHelper assembles one TEXT function and returns its code bytes.
|
||||||
func assembleRISCVHelper(t *testing.T, fn *ast.Text) []byte {
|
func assembleRISCVHelper(t *testing.T, fn *ast.Text) []byte {
|
||||||
t.Helper()
|
t.Helper()
|
||||||
code, _, _, _, _, err := assembleRISCV(fn)
|
code, _, _, _, _, _, err := assembleRISCV(fn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("assemble: %v", err)
|
t.Fatalf("assemble: %v", err)
|
||||||
}
|
}
|
||||||
@@ -785,16 +785,140 @@ TEXT ·sys(SB), NOSPLIT, $0
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func TestRISCV_MOV_sym_FP_error(t *testing.T) {
|
func TestRISCV_MOV_sym_FP(t *testing.T) {
|
||||||
// MOV $sym(FP), rd should return an error (unsupported).
|
// 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"
|
fn := firstTextRISCV(t, `#include "textflag.h"
|
||||||
TEXT ·badfp(SB), NOSPLIT, $0
|
TEXT ·argfp(SB), NOSPLIT, $0
|
||||||
MOV $arg(FP), X10
|
MOV $arg(FP), X10
|
||||||
RET
|
RET
|
||||||
`)
|
`)
|
||||||
_, _, _, _, _, err := assembleRISCV(fn)
|
code, _, _, _, _, _, err := assembleRISCV(fn)
|
||||||
if err == nil {
|
if err != nil {
|
||||||
t.Error("expected error for MOV $arg(FP), got 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)
|
CALL ext(SB)
|
||||||
RET
|
RET
|
||||||
`)
|
`)
|
||||||
code, _, relocs, _, _, err := assembleRISCV(fn)
|
code, _, relocs, _, _, _, err := assembleRISCV(fn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("assemble: %v", err)
|
t.Fatalf("assemble: %v", err)
|
||||||
}
|
}
|
||||||
@@ -834,7 +958,7 @@ TEXT ·calllocal(SB), NOSPLIT, $0
|
|||||||
sub:
|
sub:
|
||||||
RET
|
RET
|
||||||
`)
|
`)
|
||||||
_, _, _, _, _, err := assembleRISCV(fn)
|
_, _, _, _, _, _, err := assembleRISCV(fn)
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Error("expected error for CALL to local label, got 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()
|
t.Helper()
|
||||||
fn := firstTextRISCV(t, "#include \"textflag.h\"\n"+src)
|
fn := firstTextRISCV(t, "#include \"textflag.h\"\n"+src)
|
||||||
instr := fn.Body[0].(*ast.Instr)
|
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
|
// TestRISCVBranchJumpRange checks that displacements beyond the B-type span
|
||||||
@@ -917,7 +1041,7 @@ func TestRISCVBranchFarBody(t *testing.T) {
|
|||||||
}
|
}
|
||||||
sb.WriteString("done:\n\tRET\n")
|
sb.WriteString("done:\n\tRET\n")
|
||||||
fn := firstTextRISCV(t, sb.String())
|
fn := firstTextRISCV(t, sb.String())
|
||||||
out, _, _, _, _, err := assembleRISCV(fn)
|
out, _, _, _, _, _, err := assembleRISCV(fn)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
t.Fatalf("unexpected error: %v", err)
|
t.Fatalf("unexpected error: %v", err)
|
||||||
}
|
}
|
||||||
@@ -943,7 +1067,7 @@ TEXT ·csrhi(SB), NOSPLIT, $0
|
|||||||
CSRRW $4096, X10, X11
|
CSRRW $4096, X10, X11
|
||||||
RET
|
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")
|
t.Error("expected an out-of-range error for CSR $4096, got none")
|
||||||
}
|
}
|
||||||
fn = firstTextRISCV(t, `#include "textflag.h"
|
fn = firstTextRISCV(t, `#include "textflag.h"
|
||||||
@@ -951,25 +1075,24 @@ TEXT ·csrmax(SB), NOSPLIT, $0
|
|||||||
CSRRW $4095, X10, X11
|
CSRRW $4095, X10, X11
|
||||||
RET
|
RET
|
||||||
`)
|
`)
|
||||||
if _, _, _, _, _, err := assembleRISCV(fn); err != nil {
|
if _, _, _, _, _, _, err := assembleRISCV(fn); err != nil {
|
||||||
t.Errorf("CSR $4095 must assemble: %v", err)
|
t.Errorf("CSR $4095 must assemble: %v", err)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TestRISCV_Imm64Rejected checks that immediates outside the signed 32-bit
|
// TestRISCV_Imm64Rejected checks that immediates outside the signed 32-bit
|
||||||
// span are diagnosed instead of silently truncated to their low 32 bits (the
|
// span are diagnosed instead of silently truncated to their low 32 bits for
|
||||||
// toolchain materialises such constants via SLLI expansion, which this
|
// the I-type arithmetic; the MOV forms materialise the wide constant instead
|
||||||
// assembler does not implement).
|
// (shift sequence or pooled load), like the toolchain.
|
||||||
func TestRISCV_Imm64Rejected(t *testing.T) {
|
func TestRISCV_Imm64Rejected(t *testing.T) {
|
||||||
cases := []string{
|
cases := []string{
|
||||||
"MOV $0x123456789, X10",
|
|
||||||
"ADDI $0x100000000, X10, X11",
|
"ADDI $0x100000000, X10, X11",
|
||||||
"ANDI $-0x800000001, X10, X11",
|
"ANDI $-0x800000001, X10, X11",
|
||||||
"SUB $0x100000000, X10, X11",
|
"SUB $0x100000000, X10, X11",
|
||||||
}
|
}
|
||||||
for _, src := range cases {
|
for _, src := range cases {
|
||||||
fn := firstTextRISCV(t, "#include \"textflag.h\"\nTEXT ·wide(SB), NOSPLIT, $0\n\t"+src+"\n\tRET\n")
|
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)
|
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
|
SUB $0x80000000, X12, X13
|
||||||
RET
|
RET
|
||||||
`)
|
`)
|
||||||
if _, _, _, _, _, err := assembleRISCV(fn); err != nil {
|
if _, _, _, _, _, _, err := assembleRISCV(fn); err != nil {
|
||||||
t.Errorf("int32-span immediates must assemble: %v", err)
|
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
|
// riscvWants decodes code as little-endian words and pins each one; the
|
||||||
|
|||||||
+2
-2
@@ -475,8 +475,8 @@ type corpusStats struct {
|
|||||||
// set, even when gasm does not support the architecture.
|
// set, even when gasm does not support the architecture.
|
||||||
var goPortSuffixes = []string{
|
var goPortSuffixes = []string{
|
||||||
"386", "amd64", "arm", "arm64", "loong64", "mips", "mips64",
|
"386", "amd64", "arm", "arm64", "loong64", "mips", "mips64",
|
||||||
"mips64le", "mipsle", "ppc64", "ppc64le", "riscv", "riscv64",
|
"mips64le", "mipsle", "mips64x", "mipsx", "ppc64", "ppc64le",
|
||||||
"s390x", "wasm",
|
"ppc64x", "riscv", "riscv64", "s390x", "wasm",
|
||||||
}
|
}
|
||||||
|
|
||||||
// otherPortFile reports whether the file belongs to a build no supported
|
// otherPortFile reports whether the file belongs to a build no supported
|
||||||
|
|||||||
Vendored
+40
@@ -0,0 +1,40 @@
|
|||||||
|
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
// Differential kernel for the riscv64 bookkeeping statements and the
|
||||||
|
// slot-relative branches: FUNCDATA and PCDATA (the expanded forms of the
|
||||||
|
// funcdata.h macros, contributing no bytes), UNDEF (the toolchain's ebreak),
|
||||||
|
// and the JMP N(PC) slot jumps including the self-loop and the backward form.
|
||||||
|
|
||||||
|
#include "textflag.h"
|
||||||
|
|
||||||
|
TEXT ·bookkeep(SB), NOSPLIT, $8-8
|
||||||
|
FUNCDATA $1, marks<>(SB)
|
||||||
|
PCDATA $1, $-1
|
||||||
|
MOV ZERO, ret+0(FP)
|
||||||
|
PCDATA $1, $1
|
||||||
|
UNDEF
|
||||||
|
MOV $1, X10
|
||||||
|
RET
|
||||||
|
|
||||||
|
TEXT ·slots(SB), NOSPLIT, $0-0
|
||||||
|
MOV $1, X10
|
||||||
|
JMP 2(PC)
|
||||||
|
MOV $64, X11
|
||||||
|
MOV $128, X12
|
||||||
|
MOV $2, X11
|
||||||
|
MOV $3, X12
|
||||||
|
BEQ X10, X11, skip
|
||||||
|
JMP -2(PC)
|
||||||
|
|
||||||
|
skip:
|
||||||
|
JMP 0(PC)
|
||||||
|
|
||||||
|
TEXT ·marksreader(SB), NOSPLIT, $0-8
|
||||||
|
MOV $marks<>(SB), X10
|
||||||
|
MOV (X10), X11
|
||||||
|
MOV X11, ret+0(FP)
|
||||||
|
RET
|
||||||
|
|
||||||
|
GLOBL marks<>(SB), RODATA, $8
|
||||||
|
DATA marks<>+0(SB)/8, $1234605616436508552
|
||||||
Vendored
+21
@@ -0,0 +1,21 @@
|
|||||||
|
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
// Differential kernel for the loong64 two-operand BEQ/BNE spellings the
|
||||||
|
// msan trampolines use: BEQ Rj, target compares against R0 (the beqz form).
|
||||||
|
|
||||||
|
#include "textflag.h"
|
||||||
|
|
||||||
|
TEXT ·branch2(SB), NOSPLIT, $0-8
|
||||||
|
MOVV arg+0(FP), R4
|
||||||
|
BEQ R4, zero
|
||||||
|
ADDV $1, R4, R4
|
||||||
|
|
||||||
|
zero:
|
||||||
|
MOVV $16, R5
|
||||||
|
BNE R4, done
|
||||||
|
ADDV $2, R4, R4
|
||||||
|
|
||||||
|
done:
|
||||||
|
MOVV R4, ret+0(FP)
|
||||||
|
RET
|
||||||
Vendored
+27
@@ -0,0 +1,27 @@
|
|||||||
|
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
// Differential kernel for the loong64 DATA value forms the runtime's exp and
|
||||||
|
// asm files use: floating-point initialisers stored as IEEE-754 bits and
|
||||||
|
// string initialisers zero-padded within their declared width.
|
||||||
|
|
||||||
|
#include "textflag.h"
|
||||||
|
|
||||||
|
TEXT ·floatbits(SB), NOSPLIT, $0-8
|
||||||
|
MOVV $floats<>(SB), R12
|
||||||
|
MOVD 8(R12), F0
|
||||||
|
MOVD F0, ret+0(FP)
|
||||||
|
RET
|
||||||
|
|
||||||
|
TEXT ·stringhead(SB), NOSPLIT, $0-8
|
||||||
|
MOVV $msg<>(SB), R12
|
||||||
|
MOVV (R12), R13
|
||||||
|
MOVV R13, ret+0(FP)
|
||||||
|
RET
|
||||||
|
|
||||||
|
GLOBL floats<>(SB), RODATA, $16
|
||||||
|
DATA floats<>+0(SB)/8, $0.0
|
||||||
|
DATA floats<>+8(SB)/8, $0.5
|
||||||
|
|
||||||
|
GLOBL msg<>(SB), RODATA, $20
|
||||||
|
DATA msg<>+0(SB)/20, $"call frame too large"
|
||||||
Vendored
+40
@@ -0,0 +1,40 @@
|
|||||||
|
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
// Differential kernel for the riscv64 wide MOV immediates: the 64-bit
|
||||||
|
// constants the toolchain materialises through shifted 32-bit parts or loads
|
||||||
|
// from its pooled $i64 data symbols, and the frame-address immediates
|
||||||
|
// ($name+off(FP)) lowered to ADDI against the hardware stack pointer.
|
||||||
|
|
||||||
|
#include "textflag.h"
|
||||||
|
|
||||||
|
TEXT ·poolload(SB), NOSPLIT, $0-16
|
||||||
|
MOV arg+0(FP), X10
|
||||||
|
MOV $0x0101010101010101, X16
|
||||||
|
MUL X10, X16, X17
|
||||||
|
MOV $0x123456789, X18
|
||||||
|
ADD X18, X17, X17
|
||||||
|
MOV X17, ret+8(FP)
|
||||||
|
RET
|
||||||
|
|
||||||
|
TEXT ·shiftload(SB), NOSPLIT, $0-16
|
||||||
|
MOV arg+0(FP), X10
|
||||||
|
MOV $0x8000000000000000, X20
|
||||||
|
MOV $0x100000000, X21
|
||||||
|
ADD X21, X20, X20
|
||||||
|
MOV $0x000fffffffffffda, X22
|
||||||
|
XOR X22, X20, X20
|
||||||
|
ADD X20, X10, X10
|
||||||
|
MOV X10, ret+8(FP)
|
||||||
|
RET
|
||||||
|
|
||||||
|
TEXT ·frameaddr(SB), NOSPLIT, $32-16
|
||||||
|
MOV $ret+8(FP), X12
|
||||||
|
MOV $scratch+0(FP), X13
|
||||||
|
ADD X12, X13, X13
|
||||||
|
MOV $slot+16(SP), X14
|
||||||
|
SUB X14, X13, X13
|
||||||
|
MOV X13, ret+8(FP)
|
||||||
|
RET
|
||||||
|
|
||||||
|
GLOBL scratch<>(SB), NOPTR, $8
|
||||||
@@ -31,6 +31,8 @@ func TestGroundTruthLOONG64(t *testing.T) {
|
|||||||
"../testdata/verify/vector_loong64.s",
|
"../testdata/verify/vector_loong64.s",
|
||||||
"../testdata/verify/pcalign_loong64.s",
|
"../testdata/verify/pcalign_loong64.s",
|
||||||
"../testdata/verify/l64forms_loong64.s",
|
"../testdata/verify/l64forms_loong64.s",
|
||||||
|
"../testdata/verify/datainit_loong64.s",
|
||||||
|
"../testdata/verify/branch2_loong64.s",
|
||||||
"../testdata/verify/vector_arith_add_loong64.s",
|
"../testdata/verify/vector_arith_add_loong64.s",
|
||||||
"../testdata/verify/vector_arith_sub_loong64.s",
|
"../testdata/verify/vector_arith_sub_loong64.s",
|
||||||
"../testdata/verify/vector_arith_sat_loong64.s",
|
"../testdata/verify/vector_arith_sat_loong64.s",
|
||||||
|
|||||||
@@ -35,6 +35,8 @@ func TestGroundTruthRISCV(t *testing.T) {
|
|||||||
"../testdata/verify/bitmanip_riscv64.s",
|
"../testdata/verify/bitmanip_riscv64.s",
|
||||||
"../testdata/verify/pcalign_riscv64.s",
|
"../testdata/verify/pcalign_riscv64.s",
|
||||||
"../testdata/verify/branch_far_riscv64.s",
|
"../testdata/verify/branch_far_riscv64.s",
|
||||||
|
"../testdata/verify/wideimm_riscv64.s",
|
||||||
|
"../testdata/verify/bookkeep_riscv64.s",
|
||||||
"trampoline_riscv64.s",
|
"trampoline_riscv64.s",
|
||||||
} {
|
} {
|
||||||
t.Run(path, func(t *testing.T) {
|
t.Run(path, func(t *testing.T) {
|
||||||
|
|||||||
Reference in New Issue
Block a user