Files
gasm-sdk/asm/loong64_assemble.go
T

1386 lines
43 KiB
Go
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
// SPDX-License-Identifier: BSD-3-Clause
package asm
import (
"fmt"
"math/bits"
"strings"
"sourcedock.dev/petrbalvin/gasm-devkit/ast"
)
// assembleLOONG64 assembles a LoongArch (loong64) TEXT function body into
// machine code. Every instruction is 4 bytes; the MOV pseudo-instruction and
// the immediate-arithmetic forms expand to 2–5 instructions when the
// immediate does not fit, so the layout is computed in two passes (sizes,
// then encoding with resolved branch targets).
//
// The emitted bytes match the Go toolchain's loong64 assembler, which is the
// ground-truth oracle: prologue/epilogue, FP/SP frame mapping, branch
// encodings and the MOV immediate expansions all follow cmd/internal/obj/
// loong64's asmout cases.
func assembleLOONG64(t *ast.Text) ([]byte, map[string]int, []Reloc, []LineEntry, []SpadjStep, error) {
fi := loong64ComputeFrame(t)
prologue := loong64Prologue(fi)
chain := loong64JumpChain(t)
resolve := func(name string) string {
if r, ok := chain[name]; ok {
return r
}
return name
}
var relocs []Reloc
var spadj []SpadjStep
// The prologue (3 instructions when a frame is present) raises the SP
// delta by autosize; the boundary is reported at the third instruction's
// pc, exactly as the toolchain's pctospadj does.
if fi.autosize != 0 {
spadj = append(spadj, SpadjStep{PC: 8, Value: fi.autosize})
}
// Pass 1: label offsets from the instruction sizes.
offsets := map[string]int{}
pos := len(prologue)
for _, stmt := range t.Body {
switch s := stmt.(type) {
case *ast.Label:
offsets[s.Name.Text] = pos
case *ast.Instr:
pos += loong64InstrSize(s, fi)
}
}
// Pass 2: encode. Relocation offsets are recorded function-relative.
out := append([]byte(nil), prologue...)
pc := len(prologue)
preCount := len(relocs)
var lines []LineEntry
for _, stmt := range t.Body {
in, ok := stmt.(*ast.Instr)
if !ok {
continue
}
code, err := encodeLOONG64Instr(in, pc, offsets, fi, &relocs, resolve)
if err != nil {
return nil, nil, nil, nil, nil, fmt.Errorf("%s: %w", in.Mnemonic.Text, err)
}
for j := preCount; j < len(relocs); j++ {
relocs[j].Off += pc - len(prologue)
}
preCount = len(relocs)
lines = append(lines, LineEntry{Offset: pc, Line: in.Pos().Line})
// The RET's epilogue closes the frame: the SP delta returns to zero
// after the addi.d (one instruction for a leaf, two for a non-leaf
// with the LR restore).
if strings.ToUpper(in.Mnemonic.Text) == "RET" && fi.autosize != 0 {
epi := 4
if !fi.leaf {
epi = 8
}
spadj = append(spadj, SpadjStep{PC: pc + epi, Value: 0})
}
out = append(out, code...)
pc += len(code)
}
return out, offsets, relocs, lines, spadj, nil
}
// 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
// toolchain chases these chains before it encodes branches, so matching its
// bytes requires the same redirection.
func loong64JumpChain(t *ast.Text) map[string]string {
leadsTo := map[string]string{}
for i, stmt := range t.Body {
l, ok := stmt.(*ast.Label)
if !ok {
continue
}
j := i + 1
for j < len(t.Body) {
if _, isLabel := t.Body[j].(*ast.Label); !isLabel {
break
}
j++
}
if j >= len(t.Body) {
continue
}
in, ok := t.Body[j].(*ast.Instr)
if !ok {
continue
}
mnem := strings.ToUpper(in.Mnemonic.Text)
if (mnem != "JMP" && mnem != "B") || len(in.Operands) != 1 {
continue
}
if name, ok := l64LabelOK(in.Operands[0]); ok {
leadsTo[l.Name.Text] = name
}
}
chain := map[string]string{}
for name := range leadsTo {
visited := map[string]bool{name: true}
cur := name
for {
next, ok := leadsTo[cur]
if !ok || visited[next] {
break
}
visited[next] = true
cur = next
}
if cur != name {
chain[name] = cur
}
}
return chain
}
// l64LabelOK returns the local label name of a jump operand.
func l64LabelOK(op *ast.Operand) (string, bool) {
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
}
// 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
if mnem == "RET" {
return len(loong64Return(fi))
}
switch mnem {
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 v == 0 {
return 4 // folds into the 3R form (rk = R0)
}
switch mnem {
case "ADD", "ADDW", "ADDV", "ADDVU", "SGT", "SGTU":
// C_US12CON (−2048..0x7ff) encodes directly as addi/slti.
if v >= -2048 && v <= 0x7ff {
return 4
}
// C_U12CON (0x800..0xfff) → ori r30, r0, v; op rd, rj, r30.
if v >= 0x800 && v <= 0xfff {
return 8
}
default: // AND/OR/XOR
// C_UU12CON (0..0x7ff) encodes directly as andi/ori/xori.
if v >= 0 && v <= 0x7ff {
return 4
}
// C_S12CON (−2048..−1) → addi.d r30, r0, v; op rd, rj, r30.
if v >= -2048 && v < 0 {
return 8
}
}
// 0x800..0xfff for AND/OR/XOR and the 32/64-bit ranges go through
// the lu12i.w materialisation.
if v == int64(int32(v)) {
if v&0xfff == 0 && (v < 0x800 || v > 0xfff) {
return 8 // lu12i.w r30, v>>12; op rd, rj, r30
}
return 12 // lu12i.w r30, v>>12; ori r30, r30, v; op rd, rj, r30
}
return 4 * (len(l64DconMovWords(0, v)) + 1) // dcon materialisation + op
}
}
return 4
}
// 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) {
mnem := strings.ToUpper(instr.Mnemonic.Text)
ops := instr.Operands
// Pseudo-instructions and the branches first.
switch mnem {
case "RET":
return loong64Return(fi), nil
case "NOP", "NOOP":
// andi r0, r0, 0
return l64wordLE(l64irr(l64DualTable["AND"].imm, 0, 0, 0)), nil
case "UNDEF":
// break 0
return l64wordLE(l64i15(l64InstrTable["BREAK"].op, 0)), nil
case "WORD":
if len(ops) != 1 {
return nil, fmt.Errorf("WORD expects 1 operand, got %d", len(ops))
}
return l64wordLE(uint32(immFromOperand(ops[0]))), nil
case "JMP", "B":
return encodeLOONG64Branch(instr, mnem, pc, offsets, false, resolve)
case "JAL", "CALL", "BL":
return encodeLOONG64Branch(instr, mnem, pc, offsets, true, resolve)
case "MOV", "MOVB", "MOVH", "MOVW", "MOVV", "MOVBU", "MOVHU", "MOVWU", "MOVF", "MOVD":
return encodeLOONG64Mov(instr, mnem, fi, relocs)
}
// 16-bit branches (BEQ/BNE/BLT/BGE/BLTU/BGEU) and JIRL.
if op, ok := l64branchTable[mnem]; ok {
return encodeLOONG64Branch16(mnem, op, ops, pc, offsets, resolve)
}
// 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)
}
// B/BL aliases reached only via JMP/JAL above.
// The dual-form arithmetic mnemonics: register (3R) or immediate (2RI12).
if de, ok := l64DualTable[mnem]; ok {
if len(ops) >= 2 && isImmOperand(ops[0]) {
if de.shift {
// INSTR $shamt, rd or INSTR $shamt, rj, rd.
if len(ops) != 2 && len(ops) != 3 {
return nil, fmt.Errorf("%s expects 2 or 3 operands, got %d", mnem, len(ops))
}
shamt := int(immFromOperand(ops[0]))
rd := l64Reg(ops[len(ops)-1])
rj := rd
if len(ops) == 3 {
rj = l64Reg(ops[1])
}
if rj < 0 || rd < 0 {
return nil, fmt.Errorf("invalid register operand")
}
// $0 folds into the register form (the toolchain matches the
// zero constant against the 3R optab entry first).
if shamt == 0 {
return l64wordLE(l64rrr(de.rrr, 0, rj, rd)), nil
}
// The .d variants take a 6-bit amount, the .w variants 5 bits.
if isLoong64ShiftD(de.imm) {
shamt &= 0x3f
} else {
shamt &= 0x1f
}
return l64wordLE(l64irr(de.imm, shamt, rj, rd)), nil
}
return encodeLOONG64ImmArith(mnem, de, ops)
}
// Register form: 3R.
if len(ops) == 3 {
rk, rj, rd := l64Reg(ops[0]), l64Reg(ops[1]), l64Reg(ops[2])
if rk < 0 || rj < 0 || rd < 0 {
return nil, fmt.Errorf("invalid register operand")
}
return l64wordLE(l64rrr(de.rrr, rk, rj, rd)), nil
}
if len(ops) == 2 {
rk, rd := l64Reg(ops[0]), l64Reg(ops[1])
if rk < 0 || rd < 0 {
return nil, fmt.Errorf("invalid register operand")
}
return l64wordLE(l64rrr(de.rrr, rk, rd, rd)), nil
}
return nil, fmt.Errorf("%s expects 2 or 3 operands, got %d", mnem, len(ops))
}
enc, ok := l64InstrTable[mnem]
if !ok {
return nil, fmt.Errorf("unsupported loong64 instruction %q", mnem)
}
switch enc.format {
case l64Frrr:
// INSTR rk, rj, rd (3 operands) or INSTR rk, rd (rj = rd).
switch len(ops) {
case 3:
rk, rj, rd := l64Reg(ops[0]), l64Reg(ops[1]), l64Reg(ops[2])
if rk < 0 || rj < 0 || rd < 0 {
return nil, fmt.Errorf("invalid register operand")
}
return l64wordLE(l64rrr(enc.op, rk, rj, rd)), nil
case 2:
rk, rd := l64Reg(ops[0]), l64Reg(ops[1])
if rk < 0 || rd < 0 {
return nil, fmt.Errorf("invalid register operand")
}
return l64wordLE(l64rrr(enc.op, rk, rd, rd)), nil
}
return nil, fmt.Errorf("%s expects 2 or 3 register operands, got %d", mnem, len(ops))
case l64Frr:
// INSTR rj, rd.
if len(ops) != 2 {
return nil, fmt.Errorf("%s expects 2 operands, got %d", mnem, len(ops))
}
rj, rd := l64Reg(ops[0]), l64Reg(ops[1])
if rj < 0 || rd < 0 {
return nil, fmt.Errorf("invalid register operand")
}
return l64wordLE(l64rr(enc.op, rj, rd)), nil
case l64Firr:
// LU52ID: INSTR $imm, rd or INSTR $imm, rj, rd.
if len(ops) < 2 || !isImmOperand(ops[0]) {
return nil, fmt.Errorf("%s expects an immediate operand", mnem)
}
imm := int(immFromOperand(ops[0]))
rd := l64Reg(ops[len(ops)-1])
rj := rd
if len(ops) == 3 {
rj = l64Reg(ops[1])
}
if rj < 0 || rd < 0 {
return nil, fmt.Errorf("invalid register operand")
}
return l64wordLE(l64irr(enc.op, imm, rj, rd)), nil
case l64Firr16:
// ADDV16: INSTR $imm, rd or INSTR $imm, rj, rd; the immediate must be
// a multiple of 65536 and is shifted right by 16.
if len(ops) < 2 || !isImmOperand(ops[0]) {
return nil, fmt.Errorf("%s expects an immediate operand", mnem)
}
v := int(immFromOperand(ops[0]))
if v&0xFFFF != 0 {
return nil, fmt.Errorf("%s: the constant must be a multiple of 65536", mnem)
}
rd := l64Reg(ops[len(ops)-1])
rj := rd
if len(ops) == 3 {
rj = l64Reg(ops[1])
}
if rj < 0 || rd < 0 {
return nil, fmt.Errorf("invalid register operand")
}
return l64wordLE(l64irr16(enc.op, v>>16, rj, rd)), nil
case l64Firr14:
// LL/SC/MOVWP: INSTR mem, rd (load) or INSTR rd, mem (store); the
// 14-bit offset is scaled by 4 (byte offset >> 2).
rd, rj, off, load, err := l64MemOperands(ops, fi)
if err != nil {
return nil, err
}
op := enc.op
if load && (mnem == "MOVWP" || mnem == "MOVVP") {
// ldptr.{w,d} = stptr.{w,d} minus the LSB of the opcode field.
op -= 1 << 24
}
return l64wordLE(l64irr14(op, int(off)>>2, rj, rd)), nil
case l64Fir20:
// LU12IW/LU32ID/PCALAU12I/PCADDU12I: INSTR rd, $imm.
if len(ops) != 2 {
return nil, fmt.Errorf("%s expects 2 operands, got %d", mnem, len(ops))
}
rd := l64Reg(ops[0])
if rd < 0 {
return nil, fmt.Errorf("invalid register operand")
}
return l64wordLE(l64ir(enc.op, int(immFromOperand(ops[1])), rd)), nil
case l64Frrrr:
// FMADD/FMSUB/FNMADD/FNMSUB: INSTR fa, fk, fj, fd (4 operands) or
// INSTR fa, fk, fd (fj = fd).
fa, fk, fj, fd, err := l64FmaOperands(ops)
if err != nil {
return nil, err
}
return l64wordLE(l64rrrr(enc.op, fa, fk, fj, fd)), nil
case l64Firir:
// BSTRINS/BSTRPICK: INSTR $msb, rj, $lsb, rd (or $msb, rj, rd with
// lsb = 0).
if len(ops) != 4 && len(ops) != 3 {
return nil, fmt.Errorf("%s expects 3 or 4 operands, got %d", mnem, len(ops))
}
msb := int(immFromOperand(ops[0]))
lsb := 0
rj := l64Reg(ops[1])
rd := l64Reg(ops[len(ops)-1])
if len(ops) == 4 {
lsb = int(immFromOperand(ops[2]))
}
if rj < 0 || rd < 0 {
return nil, fmt.Errorf("invalid register operand")
}
return l64wordLE(l64irir(enc.op, msb, rj, lsb, rd)), nil
case l64Firrr:
// ALSL: INSTR $sa, rj, rk, rd (the toolchain's optab places rj in
// the second register position); the source amount is 1–4, encoded
// as sa-1.
if len(ops) != 4 {
return nil, fmt.Errorf("%s expects 4 operands, got %d", mnem, len(ops))
}
sa := int(immFromOperand(ops[0])) - 1
rj, rk, rd := l64Reg(ops[1]), l64Reg(ops[2]), l64Reg(ops[3])
if sa < 0 || sa > 3 {
return nil, fmt.Errorf("shift amount out of range [1, 4]")
}
if rk < 0 || rj < 0 || rd < 0 {
return nil, fmt.Errorf("invalid register operand")
}
return l64wordLE(l64irrr(enc.op, sa, rk, rj, rd)), nil
case l64Fi15:
// SYSCALL/BREAK/DBAR: no operands, or SYSCALL $code / BREAK $code.
code := 0
if len(ops) == 1 {
code = int(immFromOperand(ops[0]))
} else if len(ops) > 1 {
return nil, fmt.Errorf("%s expects at most 1 operand, got %d", mnem, len(ops))
}
return l64wordLE(l64i15(enc.op, code)), nil
case l64Fam:
// AM* val, (addr), result.
if len(ops) != 3 {
return nil, fmt.Errorf("%s expects 3 operands, got %d", mnem, len(ops))
}
rk := l64Reg(ops[0])
rj, _ := l64Mem(ops[1])
rd := l64Reg(ops[2])
if rk < 0 || rj < 0 || rd < 0 {
return nil, fmt.Errorf("invalid operand in %s", mnem)
}
return l64wordLE(l64rrr(enc.op, rk, rj, rd)), nil
case l64Frdtime:
// RDTIME* rd, rj (rd at bits [9:5], rj at bits [4:0]).
if len(ops) != 2 {
return nil, fmt.Errorf("%s expects 2 operands, got %d", mnem, len(ops))
}
rd, rj := l64Reg(ops[0]), l64Reg(ops[1])
if rd < 0 || rj < 0 {
return nil, fmt.Errorf("invalid register operand")
}
return l64wordLE(l64rr(enc.op, rd, rj)), nil
case l64Fpreld:
// PRELD off(rj), $hint.
if len(ops) != 2 {
return nil, fmt.Errorf("%s expects 2 operands, got %d", mnem, len(ops))
}
rj, off := l64Mem(ops[0])
hint := int(immFromOperand(ops[1]))
if rj < 0 {
return nil, fmt.Errorf("invalid operand in %s", mnem)
}
return l64wordLE(l64irr5i(enc.op, int(off), rj, hint)), nil
}
return nil, fmt.Errorf("cannot encode %s with %d operands", mnem, len(ops))
}
// encodeLOONG64Branch encodes a label or indirect jump/call:
//
// 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) ([]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]
if isMemOperand(op) && op.Addr.Base != "" && op.Addr.Index == "" && op.Addr.Sym == nil {
// Indirect: (rj) → jirl.
rj := loong64RegNum(op.Addr.Base)
if rj < 0 {
return nil, fmt.Errorf("invalid register operand")
}
rd := 0
if link {
rd = 1 // link register
}
return l64wordLE(l64irr16(l64branchTable["JIRL"], 0, rj, rd)), nil
}
// Direct: label → b/bl.
target := resolve(l64Label(op))
targetOff, ok := offsets[target]
if !ok {
return nil, fmt.Errorf("undefined label %q%s", target, suggestLabel(target, offsets))
}
v := (targetOff - pc) >> 2
if v < -1<<25 || v >= 1<<25 {
return nil, fmt.Errorf("branch to %q too far (26-bit range)", target)
}
opc := l64jumpTable[mnem]
return l64wordLE(l64bbl(opc, v)), nil
}
// 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) {
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))
}
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.
rj := l64Reg(ops[0])
if rj < 0 {
return nil, fmt.Errorf("invalid register operand")
}
if (v<<11)>>11 != v {
return nil, fmt.Errorf("branch to %q too far (21-bit range)", target)
}
zop := l64branch21Table["BEQZ"]
if mnem == "BNE" {
zop = l64branch21Table["BNEZ"]
}
if mnem == "BLT" || mnem == "BLTZ" || mnem == "BGTZ" {
zop = l64branch21Table["BLTZ"]
}
if mnem == "BGE" || mnem == "BGEZ" || mnem == "BLEZ" {
zop = l64branch21Table["BGEZ"]
}
return l64wordLE(l64ir21(zop, v, rj)), nil
}
// Two registers: BEQ rj, rd, label. When one is R0 the toolchain
// re-encodes as the 21-bit BEQZ/BNEZ form.
rj, rd := l64Reg(ops[0]), l64Reg(ops[1])
if rj < 0 || rd < 0 {
return nil, fmt.Errorf("invalid register operand")
}
if rj == 0 {
rj, rd = rd, 0
}
if rd == 0 {
if (v<<11)>>11 != v {
return nil, fmt.Errorf("branch to %q too far (21-bit range)", target)
}
zop := l64branch21Table["BEQZ"]
if mnem == "BNE" {
zop = l64branch21Table["BNEZ"]
}
return l64wordLE(l64ir21(zop, v, rj)), nil
}
if (v<<16)>>16 != v {
return nil, fmt.Errorf("branch to %q too far (16-bit range)", target)
}
return l64wordLE(l64irr16(op, v, rj, rd)), nil
}
// encodeLOONG64Branch21 encodes a single-register branch: BLTZ/BGEZ and
// 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 {
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" {
rj = l64Reg(ops[0])
if rj < 0 {
return nil, fmt.Errorf("invalid register operand")
}
}
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 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 l64wordLE(l64ir21(op, v, rj)), nil
}
// encodeLOONG64ImmArith encodes an immediate arithmetic/logic instruction,
// expanding the immediate exactly as the toolchain's aclass classifies it:
//
// ADD/SGT family: −2048..0x7ff → addi/slti directly (4 bytes)
// 0x800..0xfff → ori r30, r0, v; op rd, rj, r30 (8)
// AND/OR/XOR: 0..0x7ff → andi/ori/xori directly (4)
// −2048..−1 → addi.d r30, r0, v; op rd, rj, r30 (8)
// 32-bit: lu12i.w r30, v>>12 [; ori r30, r30, v]; op (8/12)
// 64-bit: lu12i.w + ori + lu32i.d + lu52i.d + op (20)
func encodeLOONG64ImmArith(mnem string, de l64DualEnc, ops []*ast.Operand) ([]byte, error) {
if len(ops) != 2 && len(ops) != 3 {
return nil, fmt.Errorf("%s expects 2 or 3 operands, got %d", mnem, len(ops))
}
v := l64Imm64(ops[0])
rd := l64Reg(ops[len(ops)-1])
rj := rd
if len(ops) == 3 {
rj = l64Reg(ops[1])
}
if rj < 0 || rd < 0 {
return nil, fmt.Errorf("invalid register operand")
}
// The two immediate families classify differently.
additive := mnem == "ADD" || mnem == "ADDW" || mnem == "ADDV" || mnem == "ADDVU" || mnem == "SGT" || mnem == "SGTU"
if additive {
if v == 0 {
// $0 folds into the 3R form (rk = R0), matching the toolchain's
// optab matching of the zero constant against the register form.
return l64wordLE(l64rrr(de.rrr, 0, rj, rd)), nil
}
if v >= -2048 && v <= 0x7ff {
return l64wordLE(l64irr(de.imm, int(v), rj, rd)), nil
}
if v >= 0x800 && v <= 0xfff {
return l64WordsLE(
l64irr(0x00e<<22, int(v), 0, 30), // ori r30, r0, v
l64rrr(de.rrr, 30, rj, rd),
), nil
}
} else {
if v == 0 {
return l64wordLE(l64rrr(de.rrr, 0, rj, rd)), nil
}
if v >= 0 && v <= 0x7ff {
return l64wordLE(l64irr(de.imm, int(v), rj, rd)), nil
}
if v >= -2048 && v < 0 {
return l64WordsLE(
l64irr(0x00b<<22, int(v), 0, 30), // addi.d r30, r0, v
l64rrr(de.rrr, 30, rj, rd),
), nil
}
}
// 32/64-bit constants are materialised in R30 (the assembler temp),
// using the same dcon classification as the toolchain's case 24/60/70/
// 71/72 sequences.
const (
lu12iw = 0x0a << 25
ori = 0x00e << 22
lu32id = 0x0b << 25
lu52id = 0x00c << 22
)
if v == int64(int32(v)) {
if v&0xfff == 0 && (v < 0x800 || v > 0xfff) {
return l64WordsLE(
l64ir(lu12iw, int(int32(v)>>12), 30),
l64rrr(de.rrr, 30, rj, rd),
), nil
}
return l64WordsLE(
l64ir(lu12iw, int(int32(v)>>12), 30),
l64irr(ori, int(v), 30, 30),
l64rrr(de.rrr, 30, rj, rd),
), nil
}
words := l64DconMovWords(30, v)
words = append(words, l64rrr(de.rrr, 30, rj, rd))
return l64WordsLE(words...), nil
}
// isLoong64ShiftD reports whether a shift-immediate opcode constant is one of
// the 6-bit (.d) variants — the toolchain distinguishes them by the bit
// position of the opcode field (bits [25:16]).
func isLoong64ShiftD(op uint32) bool {
return op&0x03ff0000 != 0 && op>>25 == 0
}
// l64FmaOperands extracts the four fused-multiply-add operands:
// INSTR fa, fk, fj, fd, or INSTR fa, fk, fd with fj = fd.
func l64FmaOperands(ops []*ast.Operand) (fa, fk, fj, fd int, err error) {
switch len(ops) {
case 4:
fa, fk, fj, fd = l64Reg(ops[0]), l64Reg(ops[1]), l64Reg(ops[2]), l64Reg(ops[3])
case 3:
fa, fk, fd = l64Reg(ops[0]), l64Reg(ops[1]), l64Reg(ops[2])
fj = fd
default:
return 0, 0, 0, 0, fmt.Errorf("expected 3 or 4 operands, got %d", len(ops))
}
if fa < 0 || fk < 0 || fj < 0 || fd < 0 {
return 0, 0, 0, 0, fmt.Errorf("invalid register operand")
}
return fa, fk, fj, fd, nil
}
// l64MemOperands extracts (rd, rj, off, load) from a load/store instruction:
// INSTR mem, rd is a load, INSTR rd, mem a store.
func l64MemOperands(ops []*ast.Operand, fi loong64FrameInfo) (rd, rj int, off int32, load bool, err error) {
if len(ops) != 2 {
return 0, 0, 0, false, fmt.Errorf("expected 2 operands, got %d", len(ops))
}
if isMemOperand(ops[0]) {
rd = l64Reg(ops[1])
rj, off = l64MemWithFrame(ops[0], fi)
load = true
} else if isMemOperand(ops[1]) {
rd = l64Reg(ops[0])
rj, off = l64MemWithFrame(ops[1], fi)
} else {
return 0, 0, 0, false, fmt.Errorf("expected a memory operand")
}
if rd < 0 || rj < 0 {
return 0, 0, 0, false, fmt.Errorf("invalid operand")
}
return rd, rj, off, load, nil
}
// ---- the MOV pseudo-instruction ----
// encodeLOONG64Mov encodes the MOV family — the load/store/immediate
// workhorse of Go's loong64 assembly. MOV is an alias of MOVV (the width
// mnemonics MOVB/MOVH/MOVW/MOVV/MOVBU/MOVHU/MOVWU/MOVF/MOVD select the
// access width). The forms, mirroring the toolchain:
//
// MOVx $imm, rd load immediate (addi/lu12i+ori/lu32i/lu52i)
// MOVx mem, rd load from memory
// MOVx rd, mem store to memory
// MOVx rs, rd register move (incl. the FP-bank specials)
// MOVx $sym(SB), rd address of a static symbol (pcalau12i+addi.d)
// MOVx sym(SB), rd load from a static symbol (pcalau12i+ld)
// MOVx rd, sym(SB) store to a static symbol (pcalau12i+st)
func encodeLOONG64Mov(instr *ast.Instr, mnem string, fi loong64FrameInfo, relocs *[]Reloc) ([]byte, error) {
ops := instr.Operands
if len(ops) != 2 {
return nil, fmt.Errorf("%s expects 2 operands, got %d", mnem, len(ops))
}
if mnem == "MOV" {
mnem = "MOVV"
}
src, dst := ops[0], ops[1]
// Immediate → register.
if isImmOperand(src) && !isMemOperand(src) {
if src.Imm.Sym != nil && src.Imm.Sym.Pseudo == "SB" {
rd := l64Reg(dst)
if rd < 0 {
return nil, fmt.Errorf("%s $sym(SB): invalid destination register", mnem)
}
return encodeLOONG64SBAddr(src.Imm.Sym, rd, mnem, relocs), nil
}
rd := l64Reg(dst)
if rd < 0 {
return nil, fmt.Errorf("%s $imm: invalid destination register", mnem)
}
// MOVF/MOVD $imm, Fd → materialise in R30, then movgr2fr.{w,d}.
if (mnem == "MOVF" || mnem == "MOVD") && loong64RegClass(operandRegName(dst)) == l64ClsFP {
return encodeLOONG64ImmToFp(rd, l64Imm64(src), mnem), nil
}
return encodeLOONG64LoadImm(rd, l64Imm64(src), mnem), nil
}
// Static symbol load/store via pcalau12i.
if src.Addr.Sym != nil && src.Addr.Sym.Pseudo == "SB" && isMemOperand(src) {
rd := l64Reg(dst)
if rd < 0 {
return nil, fmt.Errorf("%s sym(SB): invalid destination register", mnem)
}
return encodeLOONG64SBLoad(src.Addr.Sym, rd, mnem, relocs), nil
}
if dst.Addr.Sym != nil && dst.Addr.Sym.Pseudo == "SB" && isMemOperand(dst) {
rs := l64Reg(src)
if rs < 0 {
return nil, fmt.Errorf("%s rd, sym(SB): invalid source register", mnem)
}
return encodeLOONG64SBStore(dst.Addr.Sym, rs, mnem, relocs), nil
}
// Register-offset addressing: MOVx (rj)(rk), rd / MOVx rd, (rj)(rk).
if src.Addr.Index != "" && !isMemOperand(dst) {
rd := l64Reg(dst)
rj, rk := loong64RegNum(src.Addr.Base), loong64RegNum(src.Addr.Index)
if rd < 0 || rj < 0 || rk < 0 {
return nil, fmt.Errorf("%s (rj)(rk): invalid register operand", mnem)
}
op, ok := l64IndexedTable[mnem]
if !ok {
return nil, fmt.Errorf("%s: no register-indexed form", mnem)
}
return l64wordLE(l64rrr(op.ld, rk, rj, rd)), nil
}
if dst.Addr.Index != "" && !isMemOperand(src) {
rs := l64Reg(src)
rj, rk := loong64RegNum(dst.Addr.Base), loong64RegNum(dst.Addr.Index)
if rs < 0 || rj < 0 || rk < 0 {
return nil, fmt.Errorf("%s rd, (rj)(rk): invalid register operand", mnem)
}
op, ok := l64IndexedTable[mnem]
if !ok {
return nil, fmt.Errorf("%s: no register-indexed form", mnem)
}
return l64wordLE(l64rrr(op.st, rk, rj, rs)), nil
}
// Memory load/store with a 12-bit (or larger, via expansion) offset.
if isMemOperand(src) && !isMemOperand(dst) {
rd := l64Reg(dst)
if rd < 0 {
return nil, fmt.Errorf("%s: invalid destination register", mnem)
}
return encodeLOONG64MemOp(mnem, ops[0], rd, true, fi, relocs)
}
if !isMemOperand(src) && isMemOperand(dst) {
rs := l64Reg(src)
if rs < 0 {
return nil, fmt.Errorf("%s: invalid source register", mnem)
}
return encodeLOONG64MemOp(mnem, ops[1], rs, false, fi, relocs)
}
// Register → register.
return encodeLOONG64RegMove(mnem, src, dst)
}
// loong64MovSize returns the encoded size of a MOV instruction.
func loong64MovSize(mnem string, ops []*ast.Operand, fi loong64FrameInfo) int {
if mnem == "MOV" {
mnem = "MOVV"
}
if len(ops) != 2 {
return 4
}
src, dst := ops[0], ops[1]
switch {
case isImmOperand(src):
if src.Imm.Sym != nil && src.Imm.Sym.Pseudo == "SB" {
return 8 // pcalau12i + addi.d
}
if (mnem == "MOVF" || mnem == "MOVD") && loong64RegClass(operandRegName(dst)) == l64ClsFP {
return 8 // addi/ori r30 + movgr2fr
}
v := l64Imm64(src)
if v == 0 {
return 4
}
if v > 0 && v <= 0xfff {
return 4 // ori rd, r0, v
}
if v >= -2048 && v < 0 {
return 4 // addi.d rd, r0, v
}
if v == int64(int32(v)) {
if v&0xfff == 0 {
return 4 // lu12i.w
}
return 8 // lu12i.w + ori
}
return 4 * len(l64DconMovWords(0, v))
case src.Addr.Sym != nil && src.Addr.Sym.Pseudo == "SB":
return 8 // pcalau12i + ld
case dst.Addr.Sym != nil && dst.Addr.Sym.Pseudo == "SB":
return 8 // pcalau12i + st
case src.Addr.Index != "" || dst.Addr.Index != "":
return 4 // ldx/stx
case isMemOperand(src) || isMemOperand(dst):
// A 12-bit offset fits in one instruction; larger offsets expand
// to lu12i.w + add.d + the access.
mem := src
if !isMemOperand(src) {
mem = dst
}
if l64MemOffset(mem, fi) >= -2048 && l64MemOffset(mem, fi) < 2048 {
return 4
}
return 12
default:
return 4 // register move
}
}
// encodeLOONG64ImmToFp materialises a 12-bit immediate in R30 and moves it to
// an F register (the toolchain's case 34: movgr2fr.w/movgr2fr.d).
func encodeLOONG64ImmToFp(fd int, v int64, mnem string) []byte {
// ori for positive constants, addi.d for zero/negative.
op := uint32(0x00b << 22)
if v > 0 {
op = 0x00e << 22
}
mov := uint32(0x452a << 10) // movgr2fr.d
if mnem == "MOVF" {
mov = 0x4529 << 10 // movgr2fr.w
}
return l64WordsLE(
l64irr(op, int(v), 0, 30),
l64rr(mov, 30, fd),
)
}
// ---- 64-bit immediate classification ----
// The dcon classes classify a 64-bit constant by which of the four
// materialisation instructions (lu12i.w, ori, lu32i.d, lu52i.d) can be
// dropped, mirroring the toolchain's dconClass: a field is ALL1/ALL0 when
// it is all ones/zeros (fillable by sign/zero extension) or ST1/ST0 when it
// starts with a 1/0 but is mixed.
const (
l64All1 = iota
l64All0
l64St1
l64St0
l64Dcon12_0
l64Dcon12_20S
l64Dcon20S_20
l64Dcon12_12S
l64Dcon20S_12S
l64Dcon20S_0
l64Dcon12_12U
l64Dcon20S_12U
l64Dcon32_12S
l64Dcon32_0
l64Dcon32_20
l64Dcon12_32S
l64Dcon20S_32
l64Dcon32_12U
l64Dcon
)
// l64BitField classifies the bit field of v at [suf+len-1 : suf].
func l64BitField(v int64, suf, ln int8) int {
var mask1, mask2 uint64
if ln == 12 {
if suf == 0 {
mask1, mask2 = 0xfff, 0x800
} else {
mask1, mask2 = 0xfff0000000000000, 0x8000000000000000
}
} else {
if suf == 12 {
mask1, mask2 = 0xfffff000, 0x80000000
} else {
mask1, mask2 = 0xfffff00000000, 0x8000000000000
}
}
u := uint64(v)
switch {
case u&mask1 == mask1:
return l64All1
case u&mask1 == 0:
return l64All0
case u&mask2 == mask2:
return l64St1
}
return l64St0
}
// l64DconClass returns the materialisation class of a 64-bit constant,
// transcribed from cmd/internal/obj/loong64's dconClass.
func l64DconClass(v int64) int {
tzb := bits.TrailingZeros64(uint64(v))
hi12 := l64BitField(v, 52, 12)
hi20 := l64BitField(v, 32, 20)
lo20 := l64BitField(v, 12, 20)
lo12 := l64BitField(v, 0, 12)
if tzb >= 52 {
return l64Dcon12_0
}
if tzb >= 32 {
if ((hi20 == l64All1 || hi20 == l64St1) && hi12 == l64All1) || ((hi20 == l64All0 || hi20 == l64St0) && hi12 == l64All0) {
return l64Dcon20S_0
}
return l64Dcon32_0
}
if tzb >= 12 {
if lo20 == l64St1 || lo20 == l64All1 {
if hi20 == l64All1 {
return l64Dcon12_20S
}
if (hi20 == l64St1 && hi12 == l64All1) || ((hi20 == l64St0 || hi20 == l64All0) && hi12 == l64All0) {
return l64Dcon20S_20
}
return l64Dcon32_20
}
if hi20 == l64All0 {
return l64Dcon12_20S
}
if (hi20 == l64St0 && hi12 == l64All0) || ((hi20 == l64St1 || hi20 == l64All1) && hi12 == l64All1) {
return l64Dcon20S_20
}
return l64Dcon32_20
}
if lo12 == l64St1 || lo12 == l64All1 {
if lo20 == l64All1 {
if hi20 == l64All1 {
return l64Dcon12_12S
}
if (hi20 == l64St1 && hi12 == l64All1) || ((hi20 == l64St0 || hi20 == l64All0) && hi12 == l64All0) {
return l64Dcon20S_12S
}
return l64Dcon32_12S
}
if lo20 == l64St1 {
if hi20 == l64All1 {
return l64Dcon12_32S
}
if (hi20 == l64St1 && hi12 == l64All1) || ((hi20 == l64St0 || hi20 == l64All0) && hi12 == l64All0) {
return l64Dcon20S_32
}
return l64Dcon
}
if lo20 == l64All0 {
if hi20 == l64All0 {
return l64Dcon12_12U
}
if ((hi20 == l64St1 || hi20 == l64All1) && hi12 == l64All1) || (hi20 == l64St0 && hi12 == l64All0) {
return l64Dcon20S_12U
}
return l64Dcon32_12U
}
if hi20 == l64All0 {
return l64Dcon12_32S
}
if ((hi20 == l64St1 || hi20 == l64All1) && hi12 == l64All1) || (hi20 == l64St0 && hi12 == l64All0) {
return l64Dcon20S_32
}
return l64Dcon
}
if lo20 == l64All0 {
if hi20 == l64All0 {
return l64Dcon12_12U
}
if ((hi20 == l64St1 || hi20 == l64All1) && hi12 == l64All1) || (hi20 == l64St0 && hi12 == l64All0) {
return l64Dcon20S_12U
}
return l64Dcon32_12U
}
if lo20 == l64St1 || lo20 == l64All1 {
if hi20 == l64All1 {
return l64Dcon12_32S
}
if (hi20 == l64St1 && hi12 == l64All1) || ((hi20 == l64St0 || hi20 == l64All0) && hi12 == l64All0) {
return l64Dcon20S_32
}
return l64Dcon
}
if hi20 == l64All0 {
return l64Dcon12_32S
}
if ((hi20 == l64St1 || hi20 == l64All1) && hi12 == l64All1) || (hi20 == l64St0 && hi12 == l64All0) {
return l64Dcon20S_32
}
return l64Dcon
}
// l64DconMovWords returns the materialisation words for a 64-bit constant
// into rd, per the toolchain's case 67/68/69/59 sequences.
func l64DconMovWords(rd int, v int64) []uint32 {
const (
lu12iw = 0x0a << 25
lu32id = 0x0b << 25
lu52id = 0x00c << 22
addiw = 0x00a << 22
addid = 0x00b << 22
ori = 0x00e << 22
)
switch l64DconClass(v) {
case l64Dcon12_0:
return []uint32{l64irr(lu52id, int(v>>52), 0, rd)}
case l64Dcon12_20S:
return []uint32{l64ir(lu12iw, int(v>>12), rd), l64irr(lu52id, int(v>>52), rd, rd)}
case l64Dcon20S_20:
return []uint32{l64ir(lu12iw, int(v>>12), rd), l64ir(lu32id, int(v>>32), rd)}
case l64Dcon12_12S:
return []uint32{l64irr(addid, int(v), 0, rd), l64irr(lu52id, int(v>>52), rd, rd)}
case l64Dcon20S_12S, l64Dcon20S_0:
return []uint32{l64irr(addiw, int(v), 0, rd), l64ir(lu32id, int(v>>32), rd)}
case l64Dcon12_12U:
return []uint32{l64irr(ori, int(v), 0, rd), l64irr(lu52id, int(v>>52), rd, rd)}
case l64Dcon20S_12U:
return []uint32{l64irr(ori, int(v), 0, rd), l64ir(lu32id, int(v>>32), rd)}
case l64Dcon32_12S, l64Dcon32_0:
return []uint32{l64irr(addiw, int(v), 0, rd), l64ir(lu32id, int(v>>32), rd), l64irr(lu52id, int(v>>52), rd, rd)}
case l64Dcon32_20:
return []uint32{l64ir(lu12iw, int(v>>12), rd), l64ir(lu32id, int(v>>32), rd), l64irr(lu52id, int(v>>52), rd, rd)}
case l64Dcon12_32S:
return []uint32{l64ir(lu12iw, int(v>>12), rd), l64irr(ori, int(v), rd, rd), l64irr(lu52id, int(v>>52), rd, rd)}
case l64Dcon20S_32:
return []uint32{l64ir(lu12iw, int(v>>12), rd), l64irr(ori, int(v), rd, rd), l64ir(lu32id, int(v>>32), rd)}
case l64Dcon32_12U:
return []uint32{l64irr(ori, int(v), 0, rd), l64ir(lu32id, int(v>>32), rd), l64irr(lu52id, int(v>>52), rd, rd)}
default:
return []uint32{l64ir(lu12iw, int(v>>12), rd), l64irr(ori, int(v), rd, rd), l64ir(lu32id, int(v>>32), rd), l64irr(lu52id, int(v>>52), rd, rd)}
}
}
// encodeLOONG64LoadImm loads an immediate into a register, matching the
// toolchain's MOVV/MOVW case 3/19/25/59 expansion:
//
// $0: or rd, r0, r0 (MOVW: sll.w rd, r0, r0)
// 1..0xfff: ori rd, r0, imm
// −2048..−1: addi.d rd, r0, imm
// 32-bit (low 12 zero): lu12i.w rd, imm>>12
// 32-bit: lu12i.w rd, imm>>12; ori rd, rd, imm
// 64-bit: lu12i.w rd, imm>>12; ori rd, rd, imm;
// lu32i.d rd, imm>>32; lu52i.d rd, rd, imm>>52
func encodeLOONG64LoadImm(rd int, v int64, mnem string) []byte {
if v == 0 {
// The zero constant matches the register-form optab entry: MOVV →
// or rd, r0, r0, MOVW → sll.w rd, r0, r0.
op := l64movRegTable["MOVV"].op
if mnem == "MOVW" {
op = l64movRegTable["MOVW"].op
}
return l64wordLE(l64rrr(op, 0, 0, rd))
}
if v > 0 && v <= 0xfff {
return l64wordLE(l64irr(l64DualTable["OR"].imm, int(v), 0, rd))
}
if v >= -2048 && v < 0 {
// Both MOVV and MOVW use addi.d for negative constants.
return l64wordLE(l64irr(l64DualTable["ADDV"].imm, int(v), 0, rd))
}
if v == int64(int32(v)) {
if v&0xfff == 0 {
return l64wordLE(l64ir(l64InstrTable["LU12IW"].op, int(int32(v)>>12), rd))
}
return l64WordsLE(
l64ir(l64InstrTable["LU12IW"].op, int(int32(v)>>12), rd),
l64irr(l64DualTable["OR"].imm, int(v), rd, rd),
)
}
// 64-bit constants use the shortest materialisation the bit pattern
// admits (dcon classification).
return l64WordsLE(l64DconMovWords(rd, v)...)
}
// encodeLOONG64MemOp encodes a memory load (load = true) or store with a
// 12-bit offset, or the 3-instruction expansion for larger offsets:
// lu12i.w r30, (off+0x800)>>12; add.d r30, rj, r30; ld/st rd, off(r30).
func encodeLOONG64MemOp(mnem string, mem *ast.Operand, reg int, load bool, fi loong64FrameInfo, relocs *[]Reloc) ([]byte, error) {
rj, off := l64MemWithFrame(mem, fi)
if rj < 0 {
return nil, fmt.Errorf("invalid memory operand")
}
ls, ok := l64loadStoreTable[mnem]
if !ok {
return nil, fmt.Errorf("unsupported MOV width %q", mnem)
}
op := ls.st
if load {
op = ls.ld
}
if off >= -2048 && off < 2048 {
return l64wordLE(l64irr(op, int(off), rj, reg)), nil
}
// Large offset: materialise the base in R30 (the assembler temp).
return l64WordsLE(
l64ir(l64InstrTable["LU12IW"].op, int((off+0x800)>>12), 30),
l64rrr(l64DualTable["ADDV"].rrr, rj, 30, 30),
l64irr(op, int(off), 30, reg),
), nil
}
// encodeLOONG64RegMove encodes a register-to-register move: the width
// extensions (ext.w.b, ext.w.h, sll.w, or, andi, bstrpick.d) between GPRs,
// fmov between F registers, and the special moves across the GPR/FP/FCC/FCSR
// banks.
func encodeLOONG64RegMove(mnem string, src, dst *ast.Operand) ([]byte, error) {
rs, rd := l64Reg(src), l64Reg(dst)
if rs < 0 || rd < 0 {
return nil, fmt.Errorf("invalid register operand")
}
sc, dc := loong64RegClass(operandRegName(src)), loong64RegClass(operandRegName(dst))
// FP-bank specials (MOVV/MOVW between GPR/FCC/FCSR and F registers).
if key, ok := l64FpMoveKey(mnem, sc, dc); ok {
op, ok := l64FpMovTable[key]
if !ok {
return nil, fmt.Errorf("unsupported %s register move %s → %s", mnem, operandRegName(src), operandRegName(dst))
}
return l64wordLE(l64rr(op, rs, rd)), nil
}
// GPR → GPR.
if sc == l64ClsGR && dc == l64ClsGR {
switch mnem {
case "MOVHU":
// bstrpick.d rd, rj, $15, $0
return l64wordLE(l64irir(0x3<<22, 15, rs, 0, rd)), nil
case "MOVWU":
// bstrpick.d rd, rj, $31, $0
return l64wordLE(l64irir(0x3<<22, 31, rs, 0, rd)), nil
}
if e, ok := l64movRegTable[mnem]; ok {
if e.rr {
return l64wordLE(l64rr(e.op, rs, rd)), nil
}
if e.imm != 0 {
return l64wordLE(l64irr(e.op, e.imm, rs, rd)), nil
}
// 3R with rk = r0: or rd, rj, r0 / sll.w rd, rj, r0.
return l64wordLE(l64rrr(e.op, 0, rs, rd)), nil
}
}
// F → F.
if sc == l64ClsFP && dc == l64ClsFP {
if op, ok := l64movFpRegTable[mnem]; ok {
return l64wordLE(l64rr(op, rs, rd)), nil
}
}
return nil, fmt.Errorf("unsupported %s register move %s → %s", mnem, operandRegName(src), operandRegName(dst))
}
// l64FpMoveKey builds the l64FpMovTable key for a cross-bank move, reporting
// whether the move is a cross-bank special at all.
func l64FpMoveKey(mnem string, sc, dc l64RegClass) (string, bool) {
bank := func(c l64RegClass) string {
switch c {
case l64ClsFP:
return "F"
case l64ClsFCC:
return "FCC"
case l64ClsFCSR:
return "FCSR"
default:
return "R"
}
}
if sc == dc {
return "", false
}
if mnem != "MOVV" && mnem != "MOVW" {
return "", false
}
key := mnem + "." + bank(sc) + "." + bank(dc)
_, ok := l64FpMovTable[key]
return key, ok
}
// ---- static symbol references (pcalau12i + offset) ----
// encodeLOONG64SBAddr emits pcalau12i rd, 0; addi.d rd, rd, 0 with the
// R_LOONG64_ADDR_HI/LO relocation pair, loading a symbol's address.
func encodeLOONG64SBAddr(sym *ast.Symbol, rd int, mnem string, relocs *[]Reloc) []byte {
if relocs != nil {
*relocs = append(*relocs,
Reloc{Off: 0, After: 0, Name: sym.Name, Kind: RelLoong64AddrHi, Addend: sym.Offset},
Reloc{Off: 4, After: 4, Name: sym.Name, Kind: RelLoong64AddrLo, Addend: sym.Offset},
)
}
return l64WordsLE(
l64ir(l64InstrTable["PCALAU12I"].op, 0, rd),
l64irr(l64DualTable["ADDV"].imm, 0, rd, rd),
)
}
// encodeLOONG64SBLoad emits pcalau12i r30, 0; ld rd, 0(r30) with the
// R_LOONG64_ADDR_HI/LO pair, loading from a static symbol.
func encodeLOONG64SBLoad(sym *ast.Symbol, rd int, mnem string, relocs *[]Reloc) []byte {
ls := l64loadStoreTable[mnem]
if relocs != nil {
*relocs = append(*relocs,
Reloc{Off: 0, After: 0, Name: sym.Name, Kind: RelLoong64AddrHi, Addend: sym.Offset},
Reloc{Off: 4, After: 4, Name: sym.Name, Kind: RelLoong64AddrLo, Addend: sym.Offset},
)
}
return l64WordsLE(
l64ir(l64InstrTable["PCALAU12I"].op, 0, 30),
l64irr(ls.ld, 0, 30, rd),
)
}
// encodeLOONG64SBStore emits pcalau12i r30, 0; st rd, 0(r30) with the
// R_LOONG64_ADDR_HI/LO pair, storing to a static symbol.
func encodeLOONG64SBStore(sym *ast.Symbol, rs int, mnem string, relocs *[]Reloc) []byte {
ls := l64loadStoreTable[mnem]
if relocs != nil {
*relocs = append(*relocs,
Reloc{Off: 0, After: 0, Name: sym.Name, Kind: RelLoong64AddrHi, Addend: sym.Offset},
Reloc{Off: 4, After: 4, Name: sym.Name, Kind: RelLoong64AddrLo, Addend: sym.Offset},
)
}
return l64WordsLE(
l64ir(l64InstrTable["PCALAU12I"].op, 0, 30),
l64irr(ls.st, 0, 30, rs),
)
}
// ---- operand helpers ----
// l64IndexedTable holds the register-indexed load/store (ldx/stx) opcodes.
var l64IndexedTable = map[string]struct{ ld, st uint32 }{
"MOVB": {0x07000 << 15, 0x07020 << 15},
"MOVH": {0x07008 << 15, 0x07028 << 15},
"MOVW": {0x07010 << 15, 0x07030 << 15},
"MOVV": {0x07018 << 15, 0x07038 << 15},
"MOVBU": {0x07040 << 15, 0x07020 << 15},
"MOVHU": {0x07048 << 15, 0x07028 << 15},
"MOVWU": {0x07050 << 15, 0x07030 << 15},
"MOVF": {0x07060 << 15, 0x07070 << 15},
"MOVD": {0x07068 << 15, 0x07078 << 15},
}
// operandRegName returns the register name of an operand, or "".
func operandRegName(op *ast.Operand) string {
if op.Addr.Base != "" {
return op.Addr.Base
}
if op.Addr.Sym != nil && op.Addr.Sym.Name != "" {
return op.Addr.Sym.Name
}
return ""
}
// l64Reg returns the register number of an operand, or -1.
func l64Reg(op *ast.Operand) int {
return loong64RegNum(operandRegName(op))
}
// l64Imm64 returns the full 64-bit immediate value of an operand.
func l64Imm64(op *ast.Operand) int64 {
if op.Imm.HasVal {
v := op.Imm.Val
if op.Imm.Neg {
v = -v
}
return v
}
return 0
}
// l64Mem returns the base register and byte offset of a memory operand.
func l64Mem(op *ast.Operand) (rj int, off int32) {
rj = loong64RegNum(op.Addr.Base)
off = int32(op.Addr.Offset)
return
}
// l64MemWithFrame resolves a memory operand, translating FP/SP pseudo-
// registers via the frame mapping.
func l64MemWithFrame(op *ast.Operand, fi loong64FrameInfo) (rj int, off int32) {
if op.Addr.Sym != nil && op.Addr.Sym.Pseudo != "" {
return loong64ResolvePseudo(op.Addr.Sym, fi)
}
return l64Mem(op)
}
// l64MemOffset returns the resolved byte offset of a memory operand.
func l64MemOffset(op *ast.Operand, fi loong64FrameInfo) int32 {
_, off := l64MemWithFrame(op, fi)
return off
}
// l64Label returns the label name of an operand.
func l64Label(op *ast.Operand) string {
if op.Addr.Sym != nil {
return op.Addr.Sym.Name
}
return op.Raw
}