feat(asm): take the loong64 register-pair spellings the toolchain parses
Assisted-by: GLM 5.3 Flash
This commit is contained in:
1 parent
1031cd9ae7
commit
a3eaa82f87
5 files changed
+237
-18
No files matched your search
+105
-18
@@ -360,6 +360,59 @@ func l64SubToAdd(mnem string, ops []*ast.Operand) (string, bool) {
|
|||||||
return mnem, false
|
return mnem, false
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// l64PairRegister reports whether name names a register the pair sugar may
|
||||||
|
// carry: the general, FP, FCC and FCSR spellings loong64RegNum resolves, or
|
||||||
|
// a bare LSX/LASX vector register (V0-V31, X0-X31), which lives outside
|
||||||
|
// that table and parses only through the vector reader.
|
||||||
|
func l64PairRegister(name string) bool {
|
||||||
|
if loong64RegNum(name) >= 0 {
|
||||||
|
return true
|
||||||
|
}
|
||||||
|
v, ok := l64ParseVecOperand(&ast.Operand{Kind: ast.OpAddr, Raw: name})
|
||||||
|
return ok && !v.hasSuf
|
||||||
|
}
|
||||||
|
|
||||||
|
// l64ExpandPairs rewrites the toolchain's register-pair spellings into the
|
||||||
|
// operand list its own parser produces: a top-level colon between two
|
||||||
|
// registers splits the operand in two with the halves swapped (the old x86
|
||||||
|
// "register pair" syntax the shared grammar keeps on every GOARCH), so
|
||||||
|
// INSTR R4:R5, R6 encodes exactly as INSTR R5, R4, R6, bytes included.
|
||||||
|
// The sugar is pure syntax: every acceptance question the reordered list
|
||||||
|
// raises is answered by the ordinary operand matching. changed reports
|
||||||
|
// whether any operand carried it; the operand objects are copied, never
|
||||||
|
// edited in the shared syntax tree.
|
||||||
|
func l64ExpandPairs(ops []*ast.Operand) ([]*ast.Operand, bool) {
|
||||||
|
changed := false
|
||||||
|
var out []*ast.Operand
|
||||||
|
for i, op := range ops {
|
||||||
|
sfx := strings.Join(strings.Fields(op.Addr.Shift), "")
|
||||||
|
if op.Addr.Base != "" || !strings.HasPrefix(sfx, ":") || !l64PairRegister(sfx[1:]) {
|
||||||
|
if changed {
|
||||||
|
out = append(out, op)
|
||||||
|
}
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if !changed {
|
||||||
|
out = make([]*ast.Operand, 0, len(ops)+1)
|
||||||
|
out = append(out, ops[:i]...)
|
||||||
|
changed = true
|
||||||
|
}
|
||||||
|
name := sfx[1:]
|
||||||
|
// The second register lands first. Both halves keep the operand
|
||||||
|
// shape a plain register spelling parses to (a bare symbol
|
||||||
|
// reference), which is what every operand consumer reads.
|
||||||
|
second := *op
|
||||||
|
second.Addr.Shift = ""
|
||||||
|
second.Addr.Sym = &ast.Symbol{Raw: name, Name: name}
|
||||||
|
second.Raw = name
|
||||||
|
first := *op
|
||||||
|
first.Addr.Shift = ""
|
||||||
|
first.Raw = operandRegName(op)
|
||||||
|
out = append(out, &second, &first)
|
||||||
|
}
|
||||||
|
return out, changed
|
||||||
|
}
|
||||||
|
|
||||||
// loong64InstrSize returns the encoded size of an instruction: 4 bytes for
|
// loong64InstrSize returns the encoded size of an instruction: 4 bytes for
|
||||||
// most, more for the multi-instruction expansions.
|
// most, more for the multi-instruction expansions.
|
||||||
func loong64InstrSize(instr *ast.Instr, fi loong64FrameInfo) int {
|
func loong64InstrSize(instr *ast.Instr, fi loong64FrameInfo) int {
|
||||||
@@ -367,6 +420,11 @@ func loong64InstrSize(instr *ast.Instr, fi loong64FrameInfo) int {
|
|||||||
ops := instr.Operands
|
ops := instr.Operands
|
||||||
var neg bool
|
var neg bool
|
||||||
mnem, neg = l64SubToAdd(mnem, ops)
|
mnem, neg = l64SubToAdd(mnem, ops)
|
||||||
|
// The register-pair sugar widens the operand list exactly as the encode
|
||||||
|
// pass sees it, so both passes count the same instruction.
|
||||||
|
if pairOps, changed := l64ExpandPairs(ops); changed {
|
||||||
|
ops = pairOps
|
||||||
|
}
|
||||||
|
|
||||||
if mnem == "RET" {
|
if mnem == "RET" {
|
||||||
return len(loong64Return(fi))
|
return len(loong64Return(fi))
|
||||||
@@ -485,6 +543,13 @@ func encodeLOONG64Instr(instr *ast.Instr, pc int, offsets map[string]int, fi loo
|
|||||||
copy(ops2[1:], ops[1:])
|
copy(ops2[1:], ops[1:])
|
||||||
ops = ops2
|
ops = ops2
|
||||||
}
|
}
|
||||||
|
// The toolchain's register-pair sugar: INSTR R4:R5, R6 encodes exactly
|
||||||
|
// as INSTR R5, R4, R6. The expansion is a fresh operand list; the
|
||||||
|
// instruction pointer stays the original one, because the PC-relative
|
||||||
|
// layout tables are keyed on it.
|
||||||
|
if pairOps, changed := l64ExpandPairs(ops); changed {
|
||||||
|
ops = pairOps
|
||||||
|
}
|
||||||
|
|
||||||
// Pseudo-instructions and the branches first.
|
// Pseudo-instructions and the branches first.
|
||||||
// Before any of them: the toolchain's loong64 operand grammar has no
|
// Before any of them: the toolchain's loong64 operand grammar has no
|
||||||
@@ -494,13 +559,28 @@ func encodeLOONG64Instr(instr *ast.Instr, pc int, offsets map[string]int, fi loo
|
|||||||
// the composition and emit the bare register, so every operand whose
|
// the composition and emit the bare register, so every operand whose
|
||||||
// verbatim suffix is not the element-selector index (V1.B[3] records
|
// verbatim suffix is not the element-selector index (V1.B[3] records
|
||||||
// the name as V1.B and the suffix "[3]", which is a real loong64
|
// the name as V1.B and the suffix "[3]", which is a real loong64
|
||||||
// form) is rejected outright.
|
// form) or the register-pair sugar expanded above is rejected
|
||||||
|
// outright, with the toolchain's own wording where the shape is one
|
||||||
|
// it diagnoses.
|
||||||
for _, op := range ops {
|
for _, op := range ops {
|
||||||
sfx := strings.Join(strings.Fields(op.Addr.Shift), "")
|
sfx := strings.Join(strings.Fields(op.Addr.Shift), "")
|
||||||
if sfx != "" && !strings.HasPrefix(sfx, "[") {
|
if sfx == "" || strings.HasPrefix(sfx, "[") {
|
||||||
return nil, fmt.Errorf("%s: shifted register operand %s%s is not a loong64 form",
|
continue
|
||||||
mnem, operandRegName(op), sfx)
|
|
||||||
}
|
}
|
||||||
|
if strings.HasPrefix(sfx, ":") {
|
||||||
|
if op.Addr.Base != "" {
|
||||||
|
// (Rj:Rk) inside an address: the pair never splits there.
|
||||||
|
return nil, fmt.Errorf("%s: indirect through register pair", mnem)
|
||||||
|
}
|
||||||
|
// A register-pair spelling with a register right half was
|
||||||
|
// expanded above, so whatever survives carries a right half
|
||||||
|
// outside the register table: the toolchain's parse-stage
|
||||||
|
// objection (R4:label).
|
||||||
|
return nil, fmt.Errorf("%s: illegal or missing addressing mode for symbol %s",
|
||||||
|
mnem, strings.TrimPrefix(sfx, ":"))
|
||||||
|
}
|
||||||
|
return nil, fmt.Errorf("%s: shifted register operand %s%s is not a loong64 form",
|
||||||
|
mnem, operandRegName(op), sfx)
|
||||||
}
|
}
|
||||||
switch mnem {
|
switch mnem {
|
||||||
case "RET":
|
case "RET":
|
||||||
@@ -629,11 +709,11 @@ func encodeLOONG64Instr(instr *ast.Instr, pc int, offsets map[string]int, fi loo
|
|||||||
l64rrr(preldx, 30, rj, hint),
|
l64rrr(preldx, 30, rj, hint),
|
||||||
), nil
|
), nil
|
||||||
case "JMP", "B":
|
case "JMP", "B":
|
||||||
return encodeLOONG64Branch(instr, mnem, pc, offsets, false, resolve, relocs, pcRelPcs)
|
return encodeLOONG64Branch(instr, ops, mnem, pc, offsets, false, resolve, relocs, pcRelPcs)
|
||||||
case "JAL", "CALL", "BL":
|
case "JAL", "CALL", "BL":
|
||||||
return encodeLOONG64Branch(instr, mnem, pc, offsets, true, resolve, relocs, pcRelPcs)
|
return encodeLOONG64Branch(instr, ops, mnem, pc, offsets, true, resolve, relocs, pcRelPcs)
|
||||||
case "MOV", "MOVB", "MOVH", "MOVW", "MOVV", "MOVBU", "MOVHU", "MOVWU", "MOVF", "MOVD":
|
case "MOV", "MOVB", "MOVH", "MOVW", "MOVV", "MOVBU", "MOVHU", "MOVWU", "MOVF", "MOVD":
|
||||||
return encodeLOONG64Mov(instr, mnem, fi, relocs)
|
return encodeLOONG64Mov(ops, mnem, fi, relocs)
|
||||||
}
|
}
|
||||||
|
|
||||||
// 16-bit branches (BEQ/BNE/BLT/BGE/BLTU/BGEU) and JIRL.
|
// 16-bit branches (BEQ/BNE/BLT/BGE/BLTU/BGEU) and JIRL.
|
||||||
@@ -703,7 +783,7 @@ func encodeLOONG64Instr(instr *ast.Instr, pc int, offsets map[string]int, fi loo
|
|||||||
// The LSX/LASX vector slice and the VMOVQ/XVMOVQ move family, before
|
// The LSX/LASX vector slice and the VMOVQ/XVMOVQ move family, before
|
||||||
// the integer/FP table (their mnemonics overlap the table's 2R format
|
// the integer/FP table (their mnemonics overlap the table's 2R format
|
||||||
// but resolve vector-bank registers).
|
// but resolve vector-bank registers).
|
||||||
if code, handled, err := encodeLOONG64Vector(instr, mnem, fi); handled {
|
if code, handled, err := encodeLOONG64Vector(mnem, ops, fi); handled {
|
||||||
if err != nil {
|
if err != nil {
|
||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
@@ -960,11 +1040,14 @@ 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
|
// 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
|
// 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, pcRelPcs map[*ast.Instr]int) ([]byte, error) {
|
//
|
||||||
if len(instr.Operands) != 1 {
|
// instr is the original instruction, the key the PC-relative layout table
|
||||||
return nil, fmt.Errorf("%s expects 1 operand, got %d", mnem, len(instr.Operands))
|
// is keyed on; ops are the operands to encode, the pair expansion included.
|
||||||
|
func encodeLOONG64Branch(instr *ast.Instr, ops []*ast.Operand, 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(ops) != 1 {
|
||||||
|
return nil, fmt.Errorf("%s expects 1 operand, got %d", mnem, len(ops))
|
||||||
}
|
}
|
||||||
op := instr.Operands[0]
|
op := ops[0]
|
||||||
// PC-relative displacement: N(PC) resolves to the instruction N slots
|
// PC-relative displacement: N(PC) resolves to the instruction N slots
|
||||||
// away in source order (the toolchain's parse-time count), and the field
|
// away in source order (the toolchain's parse-time count), and the field
|
||||||
// carries the final pc distance in instruction units.
|
// carries the final pc distance in instruction units.
|
||||||
@@ -1376,8 +1459,7 @@ func l64ImmMem(op *ast.Operand) (off int32, base int, ok bool) {
|
|||||||
// MOVx $sym(SB), rd address of a static symbol (pcalau12i+addi.d)
|
// MOVx $sym(SB), rd address of a static symbol (pcalau12i+addi.d)
|
||||||
// MOVx sym(SB), rd load from a static symbol (pcalau12i+ld)
|
// MOVx sym(SB), rd load from a static symbol (pcalau12i+ld)
|
||||||
// MOVx rd, sym(SB) store to a static symbol (pcalau12i+st)
|
// MOVx rd, sym(SB) store to a static symbol (pcalau12i+st)
|
||||||
func encodeLOONG64Mov(instr *ast.Instr, mnem string, fi loong64FrameInfo, relocs *[]Reloc) ([]byte, error) {
|
func encodeLOONG64Mov(ops []*ast.Operand, mnem string, fi loong64FrameInfo, relocs *[]Reloc) ([]byte, error) {
|
||||||
ops := instr.Operands
|
|
||||||
if len(ops) != 2 {
|
if len(ops) != 2 {
|
||||||
return nil, fmt.Errorf("%s expects 2 operands, got %d", mnem, len(ops))
|
return nil, fmt.Errorf("%s expects 2 operands, got %d", mnem, len(ops))
|
||||||
}
|
}
|
||||||
@@ -2008,8 +2090,14 @@ func operandRegName(op *ast.Operand) string {
|
|||||||
return ""
|
return ""
|
||||||
}
|
}
|
||||||
|
|
||||||
// l64Reg returns the register number of an operand, or -1.
|
// l64Reg returns the register number of a register operand, or -1. A
|
||||||
|
// memory reference is not a register, however register-shaped its base:
|
||||||
|
// the toolchain's class match refuses one wherever a C_REG is required,
|
||||||
|
// and reading the base's number here would encode it silently.
|
||||||
func l64Reg(op *ast.Operand) int {
|
func l64Reg(op *ast.Operand) int {
|
||||||
|
if isMemOperand(op) {
|
||||||
|
return -1
|
||||||
|
}
|
||||||
return loong64RegNum(operandRegName(op))
|
return loong64RegNum(operandRegName(op))
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -2228,16 +2316,15 @@ func l64VecElementBase(lasx bool, v l64VecOperand) (int, bool) {
|
|||||||
// vector plus the VMOVQ/XVMOVQ move family. handled reports whether the
|
// vector plus the VMOVQ/XVMOVQ move family. handled reports whether the
|
||||||
// mnemonic belongs to the vector slice; the operand shapes and opcode
|
// mnemonic belongs to the vector slice; the operand shapes and opcode
|
||||||
// constants reproduce GOARCH=loong64 `go tool asm` exactly.
|
// constants reproduce GOARCH=loong64 `go tool asm` exactly.
|
||||||
func encodeLOONG64Vector(instr *ast.Instr, mnem string, fi loong64FrameInfo) ([]byte, bool, error) {
|
func encodeLOONG64Vector(mnem string, ops []*ast.Operand, fi loong64FrameInfo) ([]byte, bool, error) {
|
||||||
if mnem == "VMOVQ" || mnem == "XVMOVQ" {
|
if mnem == "VMOVQ" || mnem == "XVMOVQ" {
|
||||||
code, err := encodeLOONG64Vmovq(mnem == "XVMOVQ", instr.Operands, fi)
|
code, err := encodeLOONG64Vmovq(mnem == "XVMOVQ", ops, fi)
|
||||||
return code, true, err
|
return code, true, err
|
||||||
}
|
}
|
||||||
lasx, ok := l64VecBank[mnem]
|
lasx, ok := l64VecBank[mnem]
|
||||||
if !ok {
|
if !ok {
|
||||||
return nil, false, nil
|
return nil, false, nil
|
||||||
}
|
}
|
||||||
ops := instr.Operands
|
|
||||||
bank := "V"
|
bank := "V"
|
||||||
if lasx {
|
if lasx {
|
||||||
bank = "X"
|
bank = "X"
|
||||||
|
|||||||
@@ -1058,3 +1058,50 @@ TEXT ·bytes(SB), NOSPLIT, $0
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestLOONG64PairSugar pins the toolchain's register-pair spellings: a
|
||||||
|
// top-level colon between two registers splits the operand in two with the
|
||||||
|
// halves swapped, so INSTR R4:R5, R6 encodes exactly as INSTR R5, R4, R6.
|
||||||
|
// Each row is pinned two ways: against the word GOARCH=loong64 go tool asm
|
||||||
|
// emits for the pair spelling, and against gasm's own encoding of the
|
||||||
|
// direct spelling, which must be the same bytes.
|
||||||
|
func TestLOONG64PairSugar(t *testing.T) {
|
||||||
|
for _, tt := range []struct {
|
||||||
|
pair, direct string
|
||||||
|
want uint32 // the word go tool asm emits for the pair
|
||||||
|
}{
|
||||||
|
{"MULV R4:R5, R6", "MULV R5, R4, R6", 0x001D9486},
|
||||||
|
{"MULV R4:R5", "MULV R5, R4", 0x001D9484},
|
||||||
|
{"MULV R6, R4:R5", "MULV R6, R5, R4", 0x001D98A4},
|
||||||
|
{"DIVV R4:R5, R6", "DIVV R5, R4, R6", 0x00221486},
|
||||||
|
{"MULHV R4:R5, R6", "MULHV R5, R4, R6", 0x001E1486},
|
||||||
|
{"ADDV R4:R5, R6", "ADDV R5, R4, R6", 0x00109486},
|
||||||
|
{"AND R4:R5, R6", "AND R5, R4, R6", 0x00149486},
|
||||||
|
{"MOVV R4:R5", "MOVV R5, R4", 0x001500A4},
|
||||||
|
{"MULD F4:F5, F6", "MULD F5, F4, F6", 0x01051486},
|
||||||
|
{"VADDW V1:V2, V3", "VADDW V2, V1, V3", 0x700B0823},
|
||||||
|
} {
|
||||||
|
t.Run(tt.pair, func(t *testing.T) {
|
||||||
|
pair := assembleLOONG64Helper(t, firstTextLOONG64(t, `#include "textflag.h"
|
||||||
|
TEXT ·p(SB), NOSPLIT, $0
|
||||||
|
`+tt.pair+`
|
||||||
|
RET
|
||||||
|
`))
|
||||||
|
direct := assembleLOONG64Helper(t, firstTextLOONG64(t, `#include "textflag.h"
|
||||||
|
TEXT ·d(SB), NOSPLIT, $0
|
||||||
|
`+tt.direct+`
|
||||||
|
RET
|
||||||
|
`))
|
||||||
|
if len(pair) < 4 {
|
||||||
|
t.Fatalf("pair spelling produced %d bytes", len(pair))
|
||||||
|
}
|
||||||
|
got := binary.LittleEndian.Uint32(pair)
|
||||||
|
if got != tt.want {
|
||||||
|
t.Errorf("pair word = %08x, want %08x (go tool asm)", got, tt.want)
|
||||||
|
}
|
||||||
|
if len(direct) < 4 || !bytes.Equal(pair[:4], direct[:4]) {
|
||||||
|
t.Errorf("pair bytes % x differ from the direct spelling's % x", pair[:4], direct[:4])
|
||||||
|
}
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -215,6 +215,62 @@ func TestLoong64ForeignErrorParity(t *testing.T) {
|
|||||||
// and the pair of rejections is the parity this catalogue asserts.
|
// and the pair of rejections is the parity this catalogue asserts.
|
||||||
var loong64RejectedNames = []string{"RFE", "DUFFCOPY", "DUFFZERO", "PCALIGNMAX"}
|
var loong64RejectedNames = []string{"RFE", "DUFFCOPY", "DUFFZERO", "PCALIGNMAX"}
|
||||||
|
|
||||||
|
// loong64PairErrorShapes walks the register-pair sugar's negative shapes,
|
||||||
|
// every one measured against GOARCH=loong64 go tool asm. want carries the
|
||||||
|
// toolchain's diagnostic where gasm's wording matches it (checked through
|
||||||
|
// the same equivalence as the catalogue above); an empty want pins the
|
||||||
|
// rejection alone, and the row's comment records the toolchain's own words
|
||||||
|
// for the divergence.
|
||||||
|
var loong64PairErrorShapes = []struct {
|
||||||
|
body string
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
// A pair never splits inside an address; the toolchain's parse stage
|
||||||
|
// refuses it outright.
|
||||||
|
{"MULV (R4:R5), R6", "indirect through register pair"},
|
||||||
|
// The reordered list fails the instruction's operand shape. The
|
||||||
|
// toolchain words these "illegal combination ..." at the class match;
|
||||||
|
// gasm words them at the operand count.
|
||||||
|
{"MOVV R4:R5, R6", ""},
|
||||||
|
{"BEQ R4:R5, R6", ""},
|
||||||
|
{"JMP R4:R5", ""},
|
||||||
|
{"MULV R4:R5, R7:R8, R6", ""},
|
||||||
|
{"MULV R4:R5, (R6)", ""},
|
||||||
|
{"BEQ R4:R5, 2(PC)", ""},
|
||||||
|
// A right half outside the register table; the toolchain's parse stage
|
||||||
|
// again ("illegal or missing addressing mode for symbol label").
|
||||||
|
{"MULV R4:label", "illegal or missing addressing mode for symbol label"},
|
||||||
|
// An immediate half on either side. The toolchain reorders the pair
|
||||||
|
// first and rejects at the class match ("illegal combination MULV
|
||||||
|
// U3CON ...", "MULV: expected register; found $4"); gasm rejects the
|
||||||
|
// shape without the reorder.
|
||||||
|
{"MOVV $4:R5", ""},
|
||||||
|
{"MULV R4:$5", ""},
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestLoong64PairErrorParity requires gasm to refuse every register-pair
|
||||||
|
// spelling the toolchain refuses, with an equivalent diagnostic where the
|
||||||
|
// row carries one. The toolchain's accept side of the sugar is pinned
|
||||||
|
// byte for byte by TestLOONG64PairSugar; the empty-want rows are documented
|
||||||
|
// wording divergences, not acceptance divergences.
|
||||||
|
func TestLoong64PairErrorParity(t *testing.T) {
|
||||||
|
for _, tt := range loong64PairErrorShapes {
|
||||||
|
src := "#include \"textflag.h\"\n\nTEXT ·f(SB), NOSPLIT, $0-0\n\t" + tt.body + "\n\tRET\n"
|
||||||
|
f, perr := parser.Parse("pairparity.s", src)
|
||||||
|
if len(perr) > 0 {
|
||||||
|
continue // the parser already rejects the spelling
|
||||||
|
}
|
||||||
|
_, aerr := AssembleFileLOONG64(f)
|
||||||
|
if aerr == nil {
|
||||||
|
t.Errorf("gasm accepts what the toolchain rejects: %s", tt.body)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if tt.want != "" && !loong64DiagEquivalent(tt.want, aerr.Error()) {
|
||||||
|
t.Errorf("gasm rejects %s with an inequivalent diagnostic:\n toolchain: %s\n gasm: %s", tt.body, tt.want, aerr)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// TestLoong64BacklogNameParity walks the audit's known-but-unencodable
|
// TestLoong64BacklogNameParity walks the audit's known-but-unencodable
|
||||||
// backlog: the names go tool asm recognises on loong64 yet refuses to
|
// backlog: the names go tool asm recognises on loong64 yet refuses to
|
||||||
// encode must be refused by gasm as well, and the one name the audit
|
// encode must be refused by gasm as well, and the one name the audit
|
||||||
|
|||||||
Vendored
+28
@@ -0,0 +1,28 @@
|
|||||||
|
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
// Differential kernel for the loong64 register-pair spellings: a top-level
|
||||||
|
// colon between two registers splits the operand in two with the halves
|
||||||
|
// swapped, so R4:R5 encodes exactly as R5, R4. The integer multiply and
|
||||||
|
// divide families, the FP pair and the vector pair, each the way
|
||||||
|
// GOARCH=loong64 go tool asm takes them.
|
||||||
|
|
||||||
|
#include "textflag.h"
|
||||||
|
|
||||||
|
TEXT ·pairs(SB), NOSPLIT, $0-0
|
||||||
|
MULV R4 : R5, R6
|
||||||
|
MULV R4 : R5
|
||||||
|
MULV R6, R4 : R5
|
||||||
|
DIVV R4 : R5, R6
|
||||||
|
DIVVU R4 : R5, R6
|
||||||
|
MULHV R4 : R5, R6
|
||||||
|
MULHVU R4 : R5, R6
|
||||||
|
ADDV R4 : R5, R6
|
||||||
|
ADDW R4 : R5, R6
|
||||||
|
AND R4 : R5, R6
|
||||||
|
OR R4 : R5, R6
|
||||||
|
XOR R4 : R5, R6
|
||||||
|
MOVV R4 : R5
|
||||||
|
MULD F4 : F5, F6
|
||||||
|
VADDW V1 : V2, V3
|
||||||
|
RET
|
||||||
@@ -28,6 +28,7 @@ func TestGroundTruthLOONG64(t *testing.T) {
|
|||||||
"../testdata/verify/movwfp_loong64.s",
|
"../testdata/verify/movwfp_loong64.s",
|
||||||
"../testdata/verify/branchu_loong64.s",
|
"../testdata/verify/branchu_loong64.s",
|
||||||
"../testdata/verify/atomics_loong64.s",
|
"../testdata/verify/atomics_loong64.s",
|
||||||
|
"../testdata/verify/pairs_loong64.s",
|
||||||
"../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",
|
||||||
|
|||||||
Reference in new issue
Block a user