feat(riscv64,loong64): PCALIGN, branch relaxation and operand shapes
Assisted-by: GLM 5.3 Flash
This commit is contained in:
+392
-45
@@ -46,30 +46,70 @@ func assembleLOONG64(t *ast.Text) ([]byte, map[string]int, []Reloc, []LineEntry,
|
||||
spadj = append(spadj, SpadjStep{PC: guardLen + (loong64StoreWords(fi.autosize)+loong64AdjustWords(-int64(fi.autosize)))*4, Value: fi.autosize})
|
||||
}
|
||||
|
||||
// Pass 1: label offsets from the instruction sizes.
|
||||
offsets := map[string]int{}
|
||||
pos := guardLen + len(prologue)
|
||||
// The toolchain's parser counts N(PC) displacements over the source
|
||||
// instructions at a uniform 4 bytes each, so a PC-relative branch
|
||||
// resolves to the instruction N slots away in body order; the resolved
|
||||
// target then participates in layout and loop-head padding like any
|
||||
// branch target.
|
||||
instrs := make([]*ast.Instr, 0, len(t.Body))
|
||||
for _, stmt := range t.Body {
|
||||
switch s := stmt.(type) {
|
||||
case *ast.Label:
|
||||
offsets[s.Name.Text] = pos
|
||||
case *ast.Instr:
|
||||
pos += loong64InstrSize(s, fi)
|
||||
if in, ok := stmt.(*ast.Instr); ok && strings.ToUpper(in.Mnemonic.Text) != "PCALIGN" {
|
||||
instrs = append(instrs, in)
|
||||
}
|
||||
}
|
||||
|
||||
// Pass 2: encode. The guard prefix precedes the prologue; its branches
|
||||
// target the morestack block at the end of the function, which the first
|
||||
// pass has sized.
|
||||
bodyLen := 0
|
||||
{
|
||||
p := guardLen + len(prologue)
|
||||
for _, stmt := range t.Body {
|
||||
if in, ok := stmt.(*ast.Instr); ok {
|
||||
p += loong64InstrSize(in, fi)
|
||||
}
|
||||
parseIndex := make(map[*ast.Instr]int, len(instrs))
|
||||
for i, in := range instrs {
|
||||
parseIndex[in] = i
|
||||
}
|
||||
pcRelTarget := make(map[*ast.Instr]*ast.Instr)
|
||||
for _, in := range instrs {
|
||||
off, ok := loong64PCRelOffset(in)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
bodyLen = p - (guardLen + len(prologue))
|
||||
tgt := parseIndex[in] + off
|
||||
if tgt < 0 || tgt >= len(instrs) {
|
||||
continue
|
||||
}
|
||||
pcRelTarget[in] = instrs[tgt]
|
||||
}
|
||||
|
||||
// Pass 1: label offsets from the instruction sizes. PCALIGN contributes
|
||||
// only its padding. On top of the explicit PCALIGNs, the toolchain pads
|
||||
// every backward-branch target (loop head) to a 16-byte boundary, so the
|
||||
// layout runs to a fixpoint over the alignment set.
|
||||
loopAligns := map[string]bool{}
|
||||
alignInstrs := map[*ast.Instr]bool{}
|
||||
for {
|
||||
offsets, _, pcs, _ := loong64Layout(t, guardLen+len(prologue), fi, loopAligns, alignInstrs)
|
||||
changed := false
|
||||
for _, in := range instrs {
|
||||
// A backward PC-relative target is the resolved instruction.
|
||||
if tgt, ok := pcRelTarget[in]; ok && pcs[tgt] < pcs[in] && !alignInstrs[tgt] {
|
||||
alignInstrs[tgt] = true
|
||||
changed = true
|
||||
}
|
||||
target, ok := loong64BranchTarget(in)
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
tOff, ok := offsets[target]
|
||||
if !ok || tOff >= pcs[in] || loopAligns[target] {
|
||||
continue
|
||||
}
|
||||
loopAligns[target] = true
|
||||
changed = true
|
||||
}
|
||||
if !changed {
|
||||
break
|
||||
}
|
||||
}
|
||||
// Final layout with the complete alignment set.
|
||||
offsets, alignPad, pcs, bodyEnd := loong64Layout(t, guardLen+len(prologue), fi, loopAligns, alignInstrs)
|
||||
bodyLen := bodyEnd - (guardLen + len(prologue))
|
||||
pcRelPcs := make(map[*ast.Instr]int, len(pcRelTarget))
|
||||
for in, tgt := range pcRelTarget {
|
||||
pcRelPcs[in] = pcs[tgt]
|
||||
}
|
||||
var out []byte
|
||||
if fi.needSplit {
|
||||
@@ -84,7 +124,20 @@ func assembleLOONG64(t *ast.Text) ([]byte, map[string]int, []Reloc, []LineEntry,
|
||||
if !ok {
|
||||
continue
|
||||
}
|
||||
code, err := encodeLOONG64Instr(in, pc, offsets, fi, &relocs, resolve)
|
||||
// PCALIGN pads to the requested boundary with andi $0, $0, 0, the
|
||||
// architecture's NOP, and encodes to nothing itself.
|
||||
if strings.ToUpper(in.Mnemonic.Text) == "PCALIGN" {
|
||||
pad := loong64PCAlignPad(pc, in)
|
||||
out = append(out, loong64PadBytes(pad)...)
|
||||
pc += pad
|
||||
continue
|
||||
}
|
||||
// Loop-head alignment padding precedes the instruction.
|
||||
if pad := alignPad[in]; pad > 0 {
|
||||
out = append(out, loong64PadBytes(pad)...)
|
||||
pc += pad
|
||||
}
|
||||
code, err := encodeLOONG64Instr(in, pc, offsets, fi, &relocs, resolve, pcRelPcs)
|
||||
if err != nil {
|
||||
return nil, nil, nil, nil, nil, fmt.Errorf("%s: %w", in.Mnemonic.Text, err)
|
||||
}
|
||||
@@ -115,6 +168,115 @@ func assembleLOONG64(t *ast.Text) ([]byte, map[string]int, []Reloc, []LineEntry,
|
||||
return out, offsets, relocs, lines, spadj, nil
|
||||
}
|
||||
|
||||
// loong64PCRelOffset reports the N of a branch operand spelled N(PC): the
|
||||
// displacement counted in source instructions from the branch itself.
|
||||
func loong64PCRelOffset(instr *ast.Instr) (int, bool) {
|
||||
mnem := strings.ToUpper(instr.Mnemonic.Text)
|
||||
branch := false
|
||||
switch mnem {
|
||||
case "JMP":
|
||||
branch = len(instr.Operands) == 1
|
||||
case "JAL", "CALL", "BL":
|
||||
branch = len(instr.Operands) == 1 || len(instr.Operands) == 2
|
||||
case "BFPT", "BFPF":
|
||||
branch = len(instr.Operands) == 1
|
||||
case "BEQ", "BNE", "BLT", "BGE", "BLTU", "BGEU",
|
||||
"BEQZ", "BNEZ", "BLTZ", "BGEZ", "BLEZ", "BGTZ":
|
||||
branch = len(instr.Operands) >= 2
|
||||
}
|
||||
if !branch {
|
||||
return 0, false
|
||||
}
|
||||
op := instr.Operands[len(instr.Operands)-1]
|
||||
if op.Kind == ast.OpAddr && op.Addr.Sym == nil && op.Addr.Base == "PC" {
|
||||
return int(op.Addr.Offset), true
|
||||
}
|
||||
return 0, false
|
||||
}
|
||||
|
||||
// loong64Layout walks the function body once and returns the label offsets,
|
||||
// the loop-alignment padding due before each instruction (a pad of 0 needs
|
||||
// nothing), the pc each instruction starts at (its padding included) and the
|
||||
// first pc past the body. Explicit PCALIGN pads, the alignment pads for the
|
||||
// labels in aligns and those for the instructions in alignInstrs (backward
|
||||
// PC-relative targets) all contribute, mirroring the toolchain's layout
|
||||
// pass.
|
||||
func loong64Layout(t *ast.Text, start int, fi loong64FrameInfo, aligns map[string]bool, alignInstrs map[*ast.Instr]bool) (map[string]int, map[*ast.Instr]int, map[*ast.Instr]int, int) {
|
||||
offsets := map[string]int{}
|
||||
alignPad := map[*ast.Instr]int{}
|
||||
pcs := map[*ast.Instr]int{}
|
||||
pos := start
|
||||
pendingAlign := false
|
||||
var pendingNames []string
|
||||
explicit := false
|
||||
for _, stmt := range t.Body {
|
||||
switch s := stmt.(type) {
|
||||
case *ast.Label:
|
||||
if aligns[s.Name.Text] {
|
||||
pendingAlign = true
|
||||
}
|
||||
pendingNames = append(pendingNames, s.Name.Text)
|
||||
// Provisional: a branch to the label lands here unless a loop
|
||||
// alignment pad follows, in which case the label resolves to the
|
||||
// padded instruction (the toolchain's labels bind to the branch
|
||||
// target instruction, which the padding pass precedes).
|
||||
offsets[s.Name.Text] = pos
|
||||
case *ast.Instr:
|
||||
if strings.ToUpper(s.Mnemonic.Text) == "PCALIGN" {
|
||||
pos += loong64PCAlignPad(pos, s)
|
||||
explicit = true
|
||||
continue
|
||||
}
|
||||
if pendingAlign {
|
||||
pendingAlign = false
|
||||
if pos&15 != 0 {
|
||||
alignPad[s] = 16 - pos&15
|
||||
}
|
||||
}
|
||||
if alignInstrs[s] && pos&15 != 0 {
|
||||
alignPad[s] = 16 - pos&15
|
||||
}
|
||||
if !explicit {
|
||||
for _, n := range pendingNames {
|
||||
offsets[n] = pos + alignPad[s]
|
||||
}
|
||||
}
|
||||
pendingNames = nil
|
||||
explicit = false
|
||||
pcs[s] = pos + alignPad[s]
|
||||
pos += alignPad[s] + loong64InstrSize(s, fi)
|
||||
}
|
||||
}
|
||||
return offsets, alignPad, pcs, pos
|
||||
}
|
||||
|
||||
// loong64BranchTarget reports the local label a branch-like instruction
|
||||
// transfers to, the loop-head signal the toolchain derives from backward
|
||||
// branch targets.
|
||||
func loong64BranchTarget(instr *ast.Instr) (string, bool) {
|
||||
mnem := strings.ToUpper(instr.Mnemonic.Text)
|
||||
ops := instr.Operands
|
||||
var op *ast.Operand
|
||||
switch {
|
||||
case mnem == "JMP" || mnem == "JAL" || mnem == "BFPT" || mnem == "BFPF":
|
||||
if len(ops) != 1 {
|
||||
return "", false
|
||||
}
|
||||
op = ops[0]
|
||||
case mnem == "TEQ" || mnem == "TNE":
|
||||
return "", false
|
||||
case len(ops) >= 2:
|
||||
op = ops[len(ops)-1]
|
||||
default:
|
||||
return "", false
|
||||
}
|
||||
if op.Kind == ast.OpAddr && op.Addr.Sym != nil && op.Addr.Sym.Pseudo == "" &&
|
||||
op.Addr.Base == "" && op.Addr.Sym.Name != "" {
|
||||
return op.Addr.Sym.Name, true
|
||||
}
|
||||
return "", false
|
||||
}
|
||||
|
||||
// loong64JumpChain precomputes jump-to-jump folding, mirroring the linker's
|
||||
// branch-chasing pass: a label whose first instruction is an unconditional
|
||||
// local jump redirects its own jumpers to the ultimate target. The Go
|
||||
@@ -177,21 +339,51 @@ func l64LabelOK(op *ast.Operand) (string, bool) {
|
||||
return "", false
|
||||
}
|
||||
|
||||
// l64SubToAdd rewrites the SUB family with an immediate first operand onto
|
||||
// its ADD counterpart with the negated immediate: LoongArch has no
|
||||
// subtract-immediate instructions, and the toolchain folds SUB $v into the
|
||||
// ADD immediate form through the same optab matching (the $0 fold into 3R
|
||||
// and the large-constant materialisations included). The negation is the
|
||||
// second result; the operand is left untouched because the size pass
|
||||
// normalises the same instruction.
|
||||
func l64SubToAdd(mnem string, ops []*ast.Operand) (string, bool) {
|
||||
if len(ops) >= 2 && isImmOperand(ops[0]) {
|
||||
switch mnem {
|
||||
case "SUB":
|
||||
return "ADD", true
|
||||
case "SUBW":
|
||||
return "ADDW", true
|
||||
case "SUBV", "SUBVU":
|
||||
return "ADDV", true
|
||||
}
|
||||
}
|
||||
return mnem, false
|
||||
}
|
||||
|
||||
// loong64InstrSize returns the encoded size of an instruction: 4 bytes for
|
||||
// most, more for the multi-instruction expansions.
|
||||
func loong64InstrSize(instr *ast.Instr, fi loong64FrameInfo) int {
|
||||
mnem := strings.ToUpper(instr.Mnemonic.Text)
|
||||
ops := instr.Operands
|
||||
var neg bool
|
||||
mnem, neg = l64SubToAdd(mnem, ops)
|
||||
|
||||
if mnem == "RET" {
|
||||
return len(loong64Return(fi))
|
||||
}
|
||||
switch mnem {
|
||||
case "TEQ", "TNE":
|
||||
return 8 // bne/beq over the BREAK, then BREAK
|
||||
case "PRELDX":
|
||||
return 20 // the four-instruction constant materialisation + preldx
|
||||
case "MOV", "MOVB", "MOVH", "MOVW", "MOVV", "MOVBU", "MOVHU", "MOVWU", "MOVF", "MOVD":
|
||||
return loong64MovSize(mnem, ops, fi)
|
||||
case "ADD", "ADDW", "ADDV", "ADDVU", "AND", "OR", "XOR", "SGT", "SGTU":
|
||||
if len(ops) >= 2 && isImmOperand(ops[0]) {
|
||||
v := l64Imm64(ops[0])
|
||||
if neg {
|
||||
v = -v
|
||||
}
|
||||
if v == 0 {
|
||||
return 4 // folds into the 3R form (rk = R0)
|
||||
}
|
||||
@@ -229,11 +421,50 @@ func loong64InstrSize(instr *ast.Instr, fi loong64FrameInfo) int {
|
||||
return 4
|
||||
}
|
||||
|
||||
// loong64PCAlignPad returns the padding PCALIGN inserts before the next
|
||||
// instruction so that it starts at the requested boundary relative to the
|
||||
// function start. The boundary must be a power of two between 8 and 2048, as
|
||||
// the toolchain requires; anything else pads nothing.
|
||||
func loong64PCAlignPad(pos int, instr *ast.Instr) int {
|
||||
if len(instr.Operands) != 1 || !isImmOperand(instr.Operands[0]) {
|
||||
return 0
|
||||
}
|
||||
align := int(immFromOperand(instr.Operands[0]))
|
||||
if align < 8 || align > 2048 || align&(align-1) != 0 {
|
||||
return 0
|
||||
}
|
||||
return (align - pos%align) % align
|
||||
}
|
||||
|
||||
// loong64PadBytes renders PCALIGN padding: the toolchain emits andi $0, $0, 0
|
||||
// (the architecture's NOP) for every full 4 bytes of pad.
|
||||
func loong64PadBytes(pad int) []byte {
|
||||
nop := l64wordLE(l64irr(l64DualTable["AND"].imm, 0, 0, 0))
|
||||
out := make([]byte, 0, pad/4*len(nop))
|
||||
for i := 0; i < pad/4; i++ {
|
||||
out = append(out, nop...)
|
||||
}
|
||||
return out
|
||||
}
|
||||
|
||||
// encodeLOONG64Instr encodes a single LoongArch instruction.
|
||||
func encodeLOONG64Instr(instr *ast.Instr, pc int, offsets map[string]int, fi loong64FrameInfo, relocs *[]Reloc, resolve func(string) string) ([]byte, error) {
|
||||
func encodeLOONG64Instr(instr *ast.Instr, pc int, offsets map[string]int, fi loong64FrameInfo, relocs *[]Reloc, resolve func(string) string, pcRelPcs map[*ast.Instr]int) ([]byte, error) {
|
||||
mnem := strings.ToUpper(instr.Mnemonic.Text)
|
||||
ops := instr.Operands
|
||||
|
||||
// The SUB family with an immediate first operand folds onto the ADD
|
||||
// immediate form with the negated immediate; the negation happens on a
|
||||
// copy of the operand, never on the shared syntax tree.
|
||||
mnem, neg := l64SubToAdd(mnem, ops)
|
||||
if neg {
|
||||
c := *ops[0]
|
||||
c.Imm.Val = -c.Imm.Val
|
||||
ops2 := make([]*ast.Operand, len(ops))
|
||||
ops2[0] = &c
|
||||
copy(ops2[1:], ops[1:])
|
||||
ops = ops2
|
||||
}
|
||||
|
||||
// Pseudo-instructions and the branches first.
|
||||
switch mnem {
|
||||
case "RET":
|
||||
@@ -249,10 +480,80 @@ func encodeLOONG64Instr(instr *ast.Instr, pc int, offsets map[string]int, fi loo
|
||||
return nil, fmt.Errorf("WORD expects 1 operand, got %d", len(ops))
|
||||
}
|
||||
return l64wordLE(uint32(immFromOperand(ops[0]))), nil
|
||||
case "NEGW", "NEGV":
|
||||
// The integer negation pseudo is a subtract from zero:
|
||||
// NEGW src, dst → sub.w r0, src, dst.
|
||||
if len(ops) != 2 {
|
||||
return nil, fmt.Errorf("%s expects 2 operands, got %d", mnem, len(ops))
|
||||
}
|
||||
src, dst := l64Reg(ops[0]), l64Reg(ops[1])
|
||||
if src < 0 || dst < 0 {
|
||||
return nil, fmt.Errorf("invalid register operand")
|
||||
}
|
||||
sub := l64InstrTable["SUBW"].op
|
||||
if mnem == "NEGV" {
|
||||
sub = l64InstrTable["SUBV"].op
|
||||
}
|
||||
return l64wordLE(l64rrr(sub, src, 0, dst)), nil
|
||||
case "TEQ", "TNE":
|
||||
// The trap pseudo expands to two instructions: bne/beq rj, rd over
|
||||
// the BREAK (offset 2 instruction units), then BREAK $code.
|
||||
if len(ops) != 2 && len(ops) != 3 {
|
||||
return nil, fmt.Errorf("%s expects 2 or 3 operands, got %d", mnem, len(ops))
|
||||
}
|
||||
code := int(immFromOperand(ops[0]))
|
||||
rj, rd := 0, l64Reg(ops[len(ops)-1])
|
||||
if len(ops) == 3 {
|
||||
rj = l64Reg(ops[1])
|
||||
}
|
||||
if rj < 0 || rd < 0 {
|
||||
return nil, fmt.Errorf("invalid register operand")
|
||||
}
|
||||
bop := l64branchTable["BNE"]
|
||||
if mnem == "TNE" {
|
||||
bop = l64branchTable["BEQ"]
|
||||
}
|
||||
return l64WordsLE(
|
||||
l64irr16(bop, 2, rj, rd),
|
||||
l64i15(l64InstrTable["BREAK"].op, code),
|
||||
), nil
|
||||
case "PRELDX":
|
||||
// preldx offset(Rbase), $n, $hint: the 64-bit descriptor n packs
|
||||
// (addrSeq, blockSize, blockNums, stride); the constant v built from
|
||||
// it materialises in R30 across four instructions, then the preldx.
|
||||
if len(ops) != 3 || !isMemOperand(ops[0]) || !isImmOperand(ops[1]) || !isImmOperand(ops[2]) {
|
||||
return nil, fmt.Errorf("PRELDX expects offset(reg), $n, $hint")
|
||||
}
|
||||
rj := loong64RegNum(ops[0].Addr.Base)
|
||||
if rj < 0 {
|
||||
return nil, fmt.Errorf("invalid register operand")
|
||||
}
|
||||
n := uint64(l64Imm64(ops[1]))
|
||||
hint := int(l64Imm64(ops[2]))
|
||||
addrSeq := (n >> 0) & 0x1
|
||||
blkSize := (n >> 1) & 0x7ff
|
||||
blkNums := (n >> 12) & 0x1ff
|
||||
stride := (n >> 21) & 0xffff
|
||||
v := uint64(ops[0].Addr.Offset)&0xffff + addrSeq<<16 +
|
||||
((blkSize/16)-1)<<20 + (blkNums-1)<<32 + stride<<44
|
||||
const (
|
||||
lu12iw = 0x0a << 25
|
||||
lu32id = 0x0b << 25
|
||||
lu52id = 0x00c << 22
|
||||
ori = 0x00e << 22
|
||||
preldx = 0x7058 << 15
|
||||
)
|
||||
return l64WordsLE(
|
||||
l64ir(lu12iw, int(uint32(v>>12)), 30),
|
||||
l64irr(ori, int(uint32(v)), 30, 30),
|
||||
l64ir(lu32id, int(uint32(v>>32)), 30),
|
||||
l64irr(lu52id, int(uint32(v>>52)), 30, 30),
|
||||
l64rrr(preldx, 30, rj, hint),
|
||||
), nil
|
||||
case "JMP", "B":
|
||||
return encodeLOONG64Branch(instr, mnem, pc, offsets, false, resolve, relocs)
|
||||
return encodeLOONG64Branch(instr, mnem, pc, offsets, false, resolve, relocs, pcRelPcs)
|
||||
case "JAL", "CALL", "BL":
|
||||
return encodeLOONG64Branch(instr, mnem, pc, offsets, true, resolve, relocs)
|
||||
return encodeLOONG64Branch(instr, mnem, pc, offsets, true, resolve, relocs, pcRelPcs)
|
||||
case "MOV", "MOVB", "MOVH", "MOVW", "MOVV", "MOVBU", "MOVHU", "MOVWU", "MOVF", "MOVD":
|
||||
return encodeLOONG64Mov(instr, mnem, fi, relocs)
|
||||
}
|
||||
@@ -262,12 +563,12 @@ func encodeLOONG64Instr(instr *ast.Instr, pc int, offsets map[string]int, fi loo
|
||||
if mnem == "JIRL" {
|
||||
return encodeLOONG64Jirl(op, ops)
|
||||
}
|
||||
return encodeLOONG64Branch16(mnem, op, ops, pc, offsets, resolve)
|
||||
return encodeLOONG64Branch16(instr, mnem, op, ops, pc, offsets, resolve, pcRelPcs)
|
||||
}
|
||||
// Single-register branches with 21-bit offsets (BLTZ/BGEZ/BLEZ/BGTZ,
|
||||
// BFPT/BFPF; BEQZ/BNEZ are reached through BEQ/BNE with R0).
|
||||
if op, ok := l64branch21Table[mnem]; ok {
|
||||
return encodeLOONG64Branch21(mnem, op, ops, pc, offsets, resolve)
|
||||
return encodeLOONG64Branch21(instr, mnem, op, ops, pc, offsets, resolve, pcRelPcs)
|
||||
}
|
||||
// B/BL aliases reached only via JMP/JAL above.
|
||||
|
||||
@@ -533,11 +834,23 @@ func encodeLOONG64Instr(instr *ast.Instr, pc int, offsets map[string]int, fi loo
|
||||
//
|
||||
// JMP/B label → b label JMP/B (rj) → jirl r0, rj, 0
|
||||
// JAL/CALL/BL label → bl label JAL/CALL/BL (rj) → jirl r1, rj, 0
|
||||
func encodeLOONG64Branch(instr *ast.Instr, mnem string, pc int, offsets map[string]int, link bool, resolve func(string) string, relocs *[]Reloc) ([]byte, error) {
|
||||
func encodeLOONG64Branch(instr *ast.Instr, mnem string, pc int, offsets map[string]int, link bool, resolve func(string) string, relocs *[]Reloc, pcRelPcs map[*ast.Instr]int) ([]byte, error) {
|
||||
if len(instr.Operands) != 1 {
|
||||
return nil, fmt.Errorf("%s expects 1 operand, got %d", mnem, len(instr.Operands))
|
||||
}
|
||||
op := instr.Operands[0]
|
||||
// PC-relative displacement: N(PC) resolves to the instruction N slots
|
||||
// away in source order (the toolchain's parse-time count), and the field
|
||||
// carries the final pc distance in instruction units.
|
||||
if op.Addr.Sym == nil && op.Addr.Base == "PC" {
|
||||
targetPc, ok := pcRelPcs[instr]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("%s: PC-relative target %d out of range", mnem, op.Addr.Offset)
|
||||
}
|
||||
v := (targetPc - pc) >> 2
|
||||
opc := l64jumpTable[mnem]
|
||||
return l64wordLE(l64bbl(opc, v)), nil
|
||||
}
|
||||
if isMemOperand(op) && op.Addr.Base != "" && op.Addr.Index == "" && op.Addr.Sym == nil {
|
||||
// Indirect: (rj) → jirl.
|
||||
rj := loong64RegNum(op.Addr.Base)
|
||||
@@ -620,16 +933,28 @@ func l64offsetOperand(op *ast.Operand) (int32, bool) {
|
||||
// encodeLOONG64Branch16 encodes a 16-bit branch (BEQ/BNE/BLT/BGE/BLTU/BGEU):
|
||||
// INSTR rj, rd, label, or INSTR rj, label with rd = R0, which the toolchain
|
||||
// turns into the 21-bit BEQZ/BNEZ form when the register is the only operand.
|
||||
func encodeLOONG64Branch16(mnem string, op uint32, ops []*ast.Operand, pc int, offsets map[string]int, resolve func(string) string) ([]byte, error) {
|
||||
func encodeLOONG64Branch16(instr *ast.Instr, mnem string, op uint32, ops []*ast.Operand, pc int, offsets map[string]int, resolve func(string) string, pcRelPcs map[*ast.Instr]int) ([]byte, error) {
|
||||
if len(ops) != 2 && len(ops) != 3 {
|
||||
return nil, fmt.Errorf("%s expects 2 or 3 operands, got %d", mnem, len(ops))
|
||||
}
|
||||
target := resolve(l64Label(ops[len(ops)-1]))
|
||||
targetOff, ok := offsets[target]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("undefined label %q%s", target, suggestLabel(target, offsets))
|
||||
var target string
|
||||
var v int
|
||||
lastOp := ops[len(ops)-1]
|
||||
if lastOp.Kind == ast.OpAddr && lastOp.Addr.Sym == nil && lastOp.Addr.Base == "PC" {
|
||||
// N(PC) resolves to the instruction N slots away in source order.
|
||||
targetPc, ok := pcRelPcs[instr]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("%s: PC-relative target %d out of range", mnem, lastOp.Addr.Offset)
|
||||
}
|
||||
v = (targetPc - pc) >> 2
|
||||
} else {
|
||||
target = resolve(l64Label(lastOp))
|
||||
targetOff, ok := offsets[target]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("undefined label %q%s", target, suggestLabel(target, offsets))
|
||||
}
|
||||
v = (targetOff - pc) >> 2
|
||||
}
|
||||
v := (targetOff - pc) >> 2
|
||||
if len(ops) == 2 {
|
||||
// Single register: BEQ rj, label → beqz (21-bit), and the BLTZ/
|
||||
// BGEZ-family aliases encoded with rj in the rj field.
|
||||
@@ -690,33 +1015,55 @@ func encodeLOONG64Branch16(mnem string, op uint32, ops []*ast.Operand, pc int, o
|
||||
// BFPT/BFPF use the 21-bit offset form (register in the rj field), while
|
||||
// BGTZ/BLEZ, which the toolchain encodes with the register in the rd field
|
||||
// and a 16-bit offset, are handled separately.
|
||||
func encodeLOONG64Branch21(mnem string, op uint32, ops []*ast.Operand, pc int, offsets map[string]int, resolve func(string) string) ([]byte, error) {
|
||||
if len(ops) != 2 {
|
||||
func encodeLOONG64Branch21(instr *ast.Instr, mnem string, op uint32, ops []*ast.Operand, pc int, offsets map[string]int, resolve func(string) string, pcRelPcs map[*ast.Instr]int) ([]byte, error) {
|
||||
isBF := mnem == "BFPT" || mnem == "BFPF"
|
||||
if len(ops) != 2 && !(isBF && (len(ops) == 1 || len(ops) == 2)) {
|
||||
return nil, fmt.Errorf("%s expects 2 operands, got %d", mnem, len(ops))
|
||||
}
|
||||
target := resolve(l64Label(ops[1]))
|
||||
targetOff, ok := offsets[target]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("undefined label %q%s", target, suggestLabel(target, offsets))
|
||||
}
|
||||
v := (targetOff - pc) >> 2
|
||||
rj := 0 // BFPT/BFPF default to FCC0
|
||||
if mnem != "BFPT" && mnem != "BFPF" {
|
||||
var rj int
|
||||
tgtOp := ops[len(ops)-1]
|
||||
if isBF {
|
||||
// BFPT/BFPF test an FCC condition register, defaulting to FCC0 when
|
||||
// spelled without one.
|
||||
rj = 0
|
||||
if len(ops) == 2 {
|
||||
rj = l64Reg(ops[0])
|
||||
if rj < 0 {
|
||||
return nil, fmt.Errorf("invalid register operand")
|
||||
}
|
||||
}
|
||||
} else {
|
||||
rj = l64Reg(ops[0])
|
||||
if rj < 0 {
|
||||
return nil, fmt.Errorf("invalid register operand")
|
||||
}
|
||||
}
|
||||
var v int
|
||||
if tgtOp.Kind == ast.OpAddr && tgtOp.Addr.Sym == nil && tgtOp.Addr.Base == "PC" {
|
||||
// N(PC) resolves to the instruction N slots away in source order.
|
||||
targetPc, ok := pcRelPcs[instr]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("%s: PC-relative target %d out of range", mnem, tgtOp.Addr.Offset)
|
||||
}
|
||||
v = (targetPc - pc) >> 2
|
||||
} else {
|
||||
target := resolve(l64Label(tgtOp))
|
||||
targetOff, ok := offsets[target]
|
||||
if !ok {
|
||||
return nil, fmt.Errorf("undefined label %q%s", target, suggestLabel(target, offsets))
|
||||
}
|
||||
v = (targetOff - pc) >> 2
|
||||
}
|
||||
if mnem == "BGTZ" || mnem == "BLEZ" {
|
||||
// The toolchain swaps the register into the rd field and keeps the
|
||||
// 16-bit offset form.
|
||||
if (v<<16)>>16 != v {
|
||||
return nil, fmt.Errorf("branch to %q too far (16-bit range)", target)
|
||||
return nil, fmt.Errorf("branch %d too far (16-bit range)", v)
|
||||
}
|
||||
return l64wordLE(l64irr16(op, v, 0, rj)), nil
|
||||
}
|
||||
if (v<<11)>>11 != v {
|
||||
return nil, fmt.Errorf("branch to %q too far (21-bit range)", target)
|
||||
return nil, fmt.Errorf("branch %d too far (21-bit range)", v)
|
||||
}
|
||||
return l64wordLE(l64ir21(op, v, rj)), nil
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user