2026-08-13 11:24:44 +02:00
|
|
|
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
|
|
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
|
|
|
|
|
|
package asm
|
|
|
|
|
|
|
|
|
|
import (
|
|
|
|
|
"bytes"
|
|
|
|
|
"encoding/binary"
|
|
|
|
|
"testing"
|
|
|
|
|
|
|
|
|
|
"sourcedock.dev/petrbalvin/gasm-devkit/ast"
|
|
|
|
|
"sourcedock.dev/petrbalvin/gasm-devkit/parser"
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// firstTextLOONG64 parses assembly source and returns the first TEXT body.
|
|
|
|
|
func firstTextLOONG64(t *testing.T, src string) *ast.Text {
|
|
|
|
|
t.Helper()
|
|
|
|
|
f, errs := parser.Parse("f_loong64.s", src)
|
|
|
|
|
if len(errs) > 0 {
|
|
|
|
|
t.Fatalf("parse: %v", errs)
|
|
|
|
|
}
|
|
|
|
|
for _, d := range f.Decls {
|
|
|
|
|
if fn, ok := d.(*ast.Text); ok {
|
|
|
|
|
return fn
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
t.Fatal("no TEXT found")
|
|
|
|
|
return nil
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// assembleLOONG64Helper assembles one TEXT function and returns its bytes.
|
|
|
|
|
func assembleLOONG64Helper(t *testing.T, fn *ast.Text) []byte {
|
|
|
|
|
t.Helper()
|
|
|
|
|
code, _, _, _, _, err := assembleLOONG64(fn)
|
|
|
|
|
if err != nil {
|
|
|
|
|
t.Fatalf("assemble: %v", err)
|
|
|
|
|
}
|
|
|
|
|
return code
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// wantWords checks that code matches the expected little-endian words.
|
|
|
|
|
func wantWords(t *testing.T, code []byte, want ...uint32) {
|
|
|
|
|
t.Helper()
|
|
|
|
|
got := make([]uint32, 0, len(code)/4)
|
|
|
|
|
for i := 0; i+4 <= len(code); i += 4 {
|
|
|
|
|
got = append(got, binary.LittleEndian.Uint32(code[i:]))
|
|
|
|
|
}
|
|
|
|
|
if len(got) != len(want) {
|
|
|
|
|
t.Fatalf("word count = %d, want %d\ncode: % x", len(got), len(want), code)
|
|
|
|
|
}
|
|
|
|
|
for i := range want {
|
|
|
|
|
if got[i] != want[i] {
|
|
|
|
|
t.Errorf("word %d = %08x, want %08x", i, got[i], want[i])
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestLOONG64_add(t *testing.T) {
|
|
|
|
|
fn := firstTextLOONG64(t, `#include "textflag.h"
|
|
|
|
|
TEXT ·add(SB), NOSPLIT, $0-24
|
|
|
|
|
MOVV a+0(FP), R4
|
|
|
|
|
MOVV b+8(FP), R5
|
|
|
|
|
ADDV R5, R4, R4
|
|
|
|
|
MOVV R4, ret+16(FP)
|
|
|
|
|
RET
|
|
|
|
|
`)
|
|
|
|
|
code := assembleLOONG64Helper(t, fn)
|
|
|
|
|
// 5 instructions: two ld.d, add.d, st.d, jirl r0, r1, 0.
|
|
|
|
|
wantWords(t, code,
|
|
|
|
|
0x28C02064, // ld.d r4, 8(r3)
|
|
|
|
|
0x28C04065, // ld.d r5, 16(r3)
|
|
|
|
|
0x00109484, // add.d r4, r4, r5
|
|
|
|
|
0x29C06064, // st.d r4, 24(r3)
|
|
|
|
|
0x4C000020, // jirl r0, r1, 0
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestLOONG64_arithmetic(t *testing.T) {
|
|
|
|
|
fn := firstTextLOONG64(t, `#include "textflag.h"
|
|
|
|
|
TEXT ·arith(SB), NOSPLIT, $0
|
|
|
|
|
ADDV R4, R5, R6
|
|
|
|
|
SUBV R7, R8, R9
|
|
|
|
|
MULV R10, R11, R12
|
|
|
|
|
DIVV R13, R14, R15
|
|
|
|
|
AND R16, R17, R18
|
|
|
|
|
OR R18, R19, R20
|
|
|
|
|
XOR R20, R21, R2
|
|
|
|
|
SLLV R2, R23, R24
|
|
|
|
|
SRLV R24, R25, R26
|
|
|
|
|
SRAV R26, R27, R28
|
|
|
|
|
RET
|
|
|
|
|
`)
|
|
|
|
|
code := assembleLOONG64Helper(t, fn)
|
|
|
|
|
wantWords(t, code,
|
|
|
|
|
0x001090A6, // add.d r6, r5, r4
|
|
|
|
|
0x00119D09, // sub.d r9, r8, r7
|
|
|
|
|
0x001DA96C, // mul.d r12, r11, r10
|
|
|
|
|
0x002235CF, // div.d r15, r14, r13
|
|
|
|
|
0x0014C232, // and r18, r17, r16
|
|
|
|
|
0x00154A74, // or r20, r19, r18
|
|
|
|
|
0x0015D2A2, // xor r2, r21, r20
|
|
|
|
|
0x00188AF8, // sll.d r24, r23, r2
|
|
|
|
|
0x0019633A, // srl.d r26, r25, r24
|
|
|
|
|
0x0019EB7C, // sra.d r28, r27, r26
|
|
|
|
|
0x4C000020, // jirl r0, r1, 0
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestLOONG64_immediates(t *testing.T) {
|
|
|
|
|
fn := firstTextLOONG64(t, `#include "textflag.h"
|
|
|
|
|
TEXT ·imm(SB), NOSPLIT, $0
|
|
|
|
|
ADDV $42, R4, R5
|
|
|
|
|
ADDV $-8, R6
|
|
|
|
|
AND $0xff, R7, R8
|
|
|
|
|
OR $1, R9, R10
|
|
|
|
|
SGT $100, R13, R14
|
|
|
|
|
SLLV $4, R15, R16
|
|
|
|
|
MOVV $0x12345, R17
|
|
|
|
|
MOVV $0, R18
|
|
|
|
|
MOVW $0, R19
|
|
|
|
|
RET
|
|
|
|
|
`)
|
|
|
|
|
code := assembleLOONG64Helper(t, fn)
|
|
|
|
|
wantWords(t, code,
|
|
|
|
|
0x02C0A885, // addi.d r5, r4, 42
|
|
|
|
|
0x02FFE0C6, // addi.d r6, r6, -8
|
|
|
|
|
0x0343FCE8, // andi r8, r7, 0xff
|
|
|
|
|
0x0380052A, // ori r10, r9, 1
|
|
|
|
|
0x020191AE, // slti r14, r13, 100
|
|
|
|
|
0x004111F0, // slli.d r16, r15, 4
|
|
|
|
|
0x14000251, // lu12i.w r17, 0x12
|
|
|
|
|
0x038D1631, // ori r17, r17, 0x345
|
|
|
|
|
0x00150012, // or r18, r0, r0
|
|
|
|
|
0x00170013, // sll.w r19, r0, r0
|
|
|
|
|
0x4C000020, // jirl r0, r1, 0
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestLOONG64_loadStore(t *testing.T) {
|
|
|
|
|
fn := firstTextLOONG64(t, `#include "textflag.h"
|
|
|
|
|
TEXT ·mem(SB), NOSPLIT, $0
|
|
|
|
|
MOVV (R4), R5
|
|
|
|
|
MOVV R5, (R6)
|
|
|
|
|
MOVW 8(R7), R8
|
|
|
|
|
MOVB R9, -4(R10)
|
|
|
|
|
MOVV (R11)(R12), R13
|
|
|
|
|
MOVV R14, (R15)(R16)
|
|
|
|
|
RET
|
|
|
|
|
`)
|
|
|
|
|
code := assembleLOONG64Helper(t, fn)
|
|
|
|
|
wantWords(t, code,
|
|
|
|
|
0x28C00085, // ld.d r5, 0(r4)
|
|
|
|
|
0x29C000C5, // st.d r5, 0(r6)
|
|
|
|
|
0x288020E8, // ld.w r8, 8(r7)
|
|
|
|
|
0x293FF149, // st.b r9, -4(r10)
|
|
|
|
|
0x380C316D, // ldx.d r13, r11, r12
|
|
|
|
|
0x381C41EE, // stx.d r14, r15, r16
|
|
|
|
|
0x4C000020, // jirl r0, r1, 0
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestLOONG64_branches(t *testing.T) {
|
|
|
|
|
fn := firstTextLOONG64(t, `#include "textflag.h"
|
|
|
|
|
TEXT ·br(SB), NOSPLIT, $0
|
|
|
|
|
BEQ R4, R5, done
|
|
|
|
|
BNE R6, R7, skip
|
|
|
|
|
BLT R8, R9, done
|
|
|
|
|
BGE R10, R11, done
|
|
|
|
|
BLTU R12, R13, done
|
|
|
|
|
BGEU R14, R15, done
|
|
|
|
|
skip:
|
|
|
|
|
JMP done
|
|
|
|
|
done:
|
|
|
|
|
RET
|
|
|
|
|
`)
|
|
|
|
|
code := assembleLOONG64Helper(t, fn)
|
|
|
|
|
// skip is at 0x18 (6 words), done at 0x1c.
|
|
|
|
|
wantWords(t, code,
|
|
|
|
|
0x58001C85, // beq r5, r4, +7
|
|
|
|
|
0x5C0018C7, // bne r7, r6, +6
|
|
|
|
|
0x60001509, // blt r9, r8, +5
|
|
|
|
|
0x6400114B, // bge r11, r10, +4
|
|
|
|
|
0x68000D8D, // bltu r13, r12, +3
|
|
|
|
|
0x6C0009CF, // bgeu r15, r14, +2
|
|
|
|
|
0x50000400, // b done (+1, chain-folded through skip)
|
|
|
|
|
0x4C000020, // jirl r0, r1, 0
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestLOONG64_frame(t *testing.T) {
|
|
|
|
|
fn := firstTextLOONG64(t, `#include "textflag.h"
|
|
|
|
|
TEXT ·f(SB), NOSPLIT, $32-8
|
|
|
|
|
MOVV R4, R5
|
|
|
|
|
MOVV arg+0(FP), R6
|
|
|
|
|
MOVV R7, local-8(SP)
|
|
|
|
|
MOVV local-8(SP), R8
|
|
|
|
|
MOVV R9, ret+0(FP)
|
|
|
|
|
RET
|
|
|
|
|
`)
|
|
|
|
|
code := assembleLOONG64Helper(t, fn)
|
|
|
|
|
// autosize = align8(32+8) = 40; prologue stores LR at -40(SP),
|
|
|
|
|
// opens the frame, stores LR again at 0(SP). The function is a leaf
|
|
|
|
|
// (no calls), so the epilogue skips the LR restore. FP args are at
|
|
|
|
|
// autosize+8; SP locals at autosize+offset.
|
|
|
|
|
wantWords(t, code,
|
|
|
|
|
0x29FF6061, // st.d r1, -40(r3)
|
|
|
|
|
0x02FF6063, // addi.d r3, r3, -40
|
|
|
|
|
0x29C00061, // st.d r1, 0(r3)
|
|
|
|
|
0x00150085, // or r5, r4, r0
|
|
|
|
|
0x28C0C066, // ld.d r6, 48(r3) arg+0(FP) → 0+40+8
|
|
|
|
|
0x29C08067, // st.d r7, 32(r3) local-8(SP) → 40-8
|
|
|
|
|
0x28C08068, // ld.d r8, 32(r3)
|
|
|
|
|
0x29C0C069, // st.d r9, 48(r3) ret+0(FP) → 0+40+8
|
|
|
|
|
0x02C0A063, // addi.d r3, r3, 40
|
|
|
|
|
0x4C000020, // jirl r0, r1, 0
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestLOONG64_jumpChain(t *testing.T) {
|
|
|
|
|
fn := firstTextLOONG64(t, `#include "textflag.h"
|
|
|
|
|
TEXT ·jc(SB), NOSPLIT, $0
|
|
|
|
|
JMP a
|
|
|
|
|
a:
|
|
|
|
|
JMP b
|
|
|
|
|
b:
|
|
|
|
|
RET
|
|
|
|
|
`)
|
|
|
|
|
code := assembleLOONG64Helper(t, fn)
|
|
|
|
|
wantWords(t, code,
|
|
|
|
|
0x50000800, // b +2 (a, chain-folded to b)
|
|
|
|
|
0x50000400, // b +1 (b)
|
|
|
|
|
0x4C000020, // jirl r0, r1, 0
|
|
|
|
|
)
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestLOONG64_dconClasses(t *testing.T) {
|
|
|
|
|
cases := []struct {
|
|
|
|
|
v int64
|
|
|
|
|
word int // expected word count
|
|
|
|
|
}{
|
|
|
|
|
{0x123456789, 3}, // lu12i.w + ori + lu32i.d
|
|
|
|
|
{-1, 2}, // addi.d + lu52i.d (the MOV path handles -1 earlier)
|
|
|
|
|
{0x1000000000000, 2}, // addi.w + lu32i.d
|
|
|
|
|
{0x123456789abcdef0, 4}, // full sequence
|
|
|
|
|
{0xFFFFFFFFF, 2}, // lu12i.w + ori
|
|
|
|
|
{0x1234567800000000, 3}, // addi.w + lu32i.d + lu52i.d
|
|
|
|
|
}
|
|
|
|
|
for _, c := range cases {
|
|
|
|
|
if n := len(l64DconMovWords(0, c.v)); n != c.word {
|
|
|
|
|
t.Errorf("0x%x: %d words, want %d", c.v, n, c.word)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestLOONG64_regNames(t *testing.T) {
|
|
|
|
|
cases := map[string]int{
|
|
|
|
|
"R0": 0, "R31": 31, "F0": 0, "F31": 31, "FCC0": 0, "FCC7": 7,
|
|
|
|
|
"FCSR0": 0, "FCSR3": 3, "ZERO": 0, "RA": 1, "SP": 3, "g": 22, "G": 22,
|
|
|
|
|
"R32": -1, "FCC8": -1, "X0": -1, "R": -1, "TMP": 30, "CTXT": 29,
|
|
|
|
|
}
|
|
|
|
|
for name, want := range cases {
|
|
|
|
|
if got := loong64RegNum(name); got != want {
|
|
|
|
|
t.Errorf("loong64RegNum(%q) = %d, want %d", name, got, want)
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-09-20 06:44:51 +02:00
|
|
|
// The X/V spellings name the LSX/LASX vector banks, a register class of
|
|
|
|
|
// their own: the oracle (GOARCH=loong64 go tool asm) rejects `BEQZ X0`
|
|
|
|
|
// with "unrecognized instruction" while assembling `VADDV V0, V1, V2`
|
|
|
|
|
// and `XVADDV X0, X1, X2`, so loong64RegNum stays strict and the vector
|
|
|
|
|
// operands resolve through loong64VecRegNum only.
|
|
|
|
|
vecCases := map[string]int{
|
|
|
|
|
"V0": 0, "V31": 31, "X0": 0, "X31": 31,
|
|
|
|
|
"R4": -1, "F0": -1, "FCC0": -1, "V32": -1, "X32": -1, "V": -1, "X": -1,
|
|
|
|
|
}
|
|
|
|
|
for name, want := range vecCases {
|
|
|
|
|
if got := loong64VecRegNum(name); got != want {
|
|
|
|
|
t.Errorf("loong64VecRegNum(%q) = %d, want %d", name, got, want)
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-08-13 11:24:44 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
func TestLOONG64_bytesEqualGroundTruth(t *testing.T) {
|
|
|
|
|
// A spot-check that assembleLOONG64 emits the same bytes the Go
|
|
|
|
|
// toolchain does for a small kernel (the full comparison lives in
|
|
|
|
|
// verify's TestGroundTruthLOONG64).
|
|
|
|
|
src := `#include "textflag.h"
|
|
|
|
|
TEXT ·k(SB), NOSPLIT, $0-0
|
|
|
|
|
ADDV R4, R5, R6
|
|
|
|
|
MOVV $0x100000, R7
|
|
|
|
|
BEQ R6, R7, done
|
|
|
|
|
JMP done
|
|
|
|
|
done:
|
|
|
|
|
RET
|
|
|
|
|
`
|
|
|
|
|
fn := firstTextLOONG64(t, src)
|
|
|
|
|
code := assembleLOONG64Helper(t, fn)
|
|
|
|
|
want := []byte{
|
|
|
|
|
0xa6, 0x90, 0x10, 0x00, // add.d r6, r5, r4
|
|
|
|
|
0x07, 0x20, 0x00, 0x14, // lu12i.w r7, 0x100
|
|
|
|
|
0xc7, 0x08, 0x00, 0x58, // beq r7, r6, +2 (done)
|
|
|
|
|
0x00, 0x04, 0x00, 0x50, // b +1 (done)
|
|
|
|
|
0x20, 0x00, 0x00, 0x4c, // jirl r0, r1, 0
|
|
|
|
|
}
|
|
|
|
|
if !bytes.Equal(code, want) {
|
|
|
|
|
t.Errorf("code = % x\nwant % x", code, want)
|
|
|
|
|
}
|
|
|
|
|
}
|
2026-09-19 19:17:07 +02:00
|
|
|
|
|
|
|
|
// TestLOONG64IndirectBranch pins the indirect branch encodings: JMP (Rj) and
|
|
|
|
|
// JAL (Rj) lower to jirl, and the raw JIRL spelling encodes the written
|
|
|
|
|
// offset (the Go loong64 assembler deletes raw JIRL instructions entirely,
|
|
|
|
|
// so this form is a gasm-only superset with faithful semantics).
|
|
|
|
|
func TestLOONG64IndirectBranch(t *testing.T) {
|
|
|
|
|
fn := firstTextLOONG64(t, `#include "textflag.h"
|
|
|
|
|
TEXT ·f(SB), NOSPLIT, $0-0
|
|
|
|
|
JMP (R4)
|
|
|
|
|
JIRL R0, R4, 8
|
|
|
|
|
RET
|
|
|
|
|
`)
|
|
|
|
|
code := assembleLOONG64Helper(t, fn)
|
|
|
|
|
wantWords(t, code,
|
|
|
|
|
0x4C000080, // jirl r0, r4, 0
|
|
|
|
|
0x4C002080, // jirl r0, r4, 8
|
|
|
|
|
0x4C000020, // jirl r0, r1, 0 (RET)
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
// JAL (R5) links, so the toolchain gives the function its autosize-8
|
|
|
|
|
// prologue and epilogue around the call and the closing RET.
|
|
|
|
|
fn = firstTextLOONG64(t, `#include "textflag.h"
|
|
|
|
|
TEXT ·f(SB), NOSPLIT, $0-0
|
|
|
|
|
JAL (R5)
|
|
|
|
|
RET
|
|
|
|
|
`)
|
|
|
|
|
code = assembleLOONG64Helper(t, fn)
|
|
|
|
|
wantWords(t, code,
|
2026-09-19 23:49:13 +02:00
|
|
|
0x29FFE061, // st.d r1, -8(r3) (prologue saves RA below the new SP)
|
|
|
|
|
0x02FFE063, // addi.d r3, r3, -8 (prologue opens the frame)
|
|
|
|
|
0x29C00061, // st.d r1, 0(r3) (prologue saves RA at SP)
|
2026-09-19 19:17:07 +02:00
|
|
|
0x4C0000A1, // jirl r1, r5, 0
|
2026-09-19 23:49:13 +02:00
|
|
|
0x28C00061, // ld.d r1, 0(r3) (epilogue restores RA)
|
2026-09-19 19:17:07 +02:00
|
|
|
0x02C02063, // addi.d r3, r3, 8
|
|
|
|
|
0x4C000020, // jirl r0, r1, 0 (RET)
|
|
|
|
|
)
|
|
|
|
|
}
|
2026-09-20 06:44:51 +02:00
|
|
|
|
|
|
|
|
// TestLOONG64_vector pins the LSX/LASX slice against words read off
|
|
|
|
|
// GOARCH=loong64 go tool asm (cross-checked against the toolchain's own
|
|
|
|
|
// loong64enc1.s): the three-register forms, the immediate forms with their
|
|
|
|
|
// biases, the vector-to-condition forms, lane popcount, the FP conversion,
|
|
|
|
|
// FSEL and the VMOVQ move family.
|
|
|
|
|
func TestLOONG64_vector(t *testing.T) {
|
|
|
|
|
t.Run("three-register and immediate forms", func(t *testing.T) {
|
|
|
|
|
fn := firstTextLOONG64(t, `#include "textflag.h"
|
|
|
|
|
TEXT ·v(SB), NOSPLIT, $0
|
|
|
|
|
VADDV V1, V2, V3
|
|
|
|
|
VADDW V1, V2, V3
|
|
|
|
|
VADDV V2, V1
|
|
|
|
|
VANDV V1, V2
|
|
|
|
|
VXORV V1, V2, V3
|
|
|
|
|
VSEQB V1, V2, V3
|
|
|
|
|
VSEQV V1, V2, V3
|
|
|
|
|
VSRAB V1, V2, V3
|
|
|
|
|
VROTRW V1, V2, V3
|
|
|
|
|
VANDB $0, V2, V3
|
|
|
|
|
VANDB $255, V2
|
|
|
|
|
VSEQB $3, V2, V3
|
|
|
|
|
VSEQV $15, V2, V3
|
|
|
|
|
VSEQV $-15, V2, V3
|
|
|
|
|
VSRAB $7, V1, V2
|
|
|
|
|
VROTRW $16, V1, V2
|
|
|
|
|
VPCNTV V1, V2
|
|
|
|
|
XVADDV X1, X2, X3
|
|
|
|
|
XVXORV X1, X2, X3
|
|
|
|
|
XVSEQB X1, X2, X3
|
|
|
|
|
XVPCNTV X1, X2
|
|
|
|
|
RET
|
|
|
|
|
`)
|
|
|
|
|
code := assembleLOONG64Helper(t, fn)
|
|
|
|
|
wantWords(t, code,
|
|
|
|
|
0x700B8443, // vadd.v v3, v2, v1
|
|
|
|
|
0x700B0443, // vadd.w
|
|
|
|
|
0x700B8821, // vadd.v v1, v1, v2 (two-operand form)
|
|
|
|
|
0x71260442, // vand.v v2, v2, v1
|
|
|
|
|
0x71270443, // vxor.v
|
|
|
|
|
0x70000443, // vseq.b
|
|
|
|
|
0x70018443, // vseq.d
|
|
|
|
|
0x70EC0443, // vsra.b
|
|
|
|
|
0x70EF0443, // vrotr.w
|
|
|
|
|
0x73D00043, // vandi.b v3, v2, 0
|
|
|
|
|
0x73D3FC42, // vandi.b v2, v2, 255 (two-operand form)
|
|
|
|
|
0x72800C43, // vseqi.b v3, v2, 3
|
|
|
|
|
0x7281BC43, // vseqi.d v3, v2, 15
|
|
|
|
|
0x7281C443, // vseqi.d v3, v2, -15 (7-bit two's complement)
|
|
|
|
|
0x73343C22, // vsrai.b v2, v1, 7 (encoded as 7+8)
|
|
|
|
|
0x72A0C022, // vrotri.w v2, v1, 16
|
|
|
|
|
0x729C2C22, // vpcnt.d v2, v1
|
|
|
|
|
0x740B8443, // xvadd.d x3, x2, x1
|
|
|
|
|
0x75270443, // xvxor.d
|
|
|
|
|
0x74000443, // xvseq.b
|
|
|
|
|
0x769C2C22, // xvpcnt.d x2, x1
|
|
|
|
|
0x4C000020,
|
|
|
|
|
)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("vector-to-condition", func(t *testing.T) {
|
|
|
|
|
fn := firstTextLOONG64(t, `#include "textflag.h"
|
|
|
|
|
TEXT ·v(SB), NOSPLIT, $0
|
|
|
|
|
VSETNEV V1, FCC0
|
|
|
|
|
VSETANYEQB V1, FCC0
|
|
|
|
|
VSETANYEQV V2, FCC0
|
|
|
|
|
VSETALLNEV V0, FCC0
|
|
|
|
|
XVSETNEV X1, FCC0
|
|
|
|
|
XVSETALLNEV X1, FCC0
|
|
|
|
|
RET
|
|
|
|
|
`)
|
|
|
|
|
code := assembleLOONG64Helper(t, fn)
|
|
|
|
|
wantWords(t, code,
|
|
|
|
|
0x729C9C20, // vsetnez.d fcc0, v1
|
|
|
|
|
0x729CA020, // vsetanyeqz.b
|
|
|
|
|
0x729CAC40, // vsetanyeqz.d
|
|
|
|
|
0x729CBC00, // vsetallnez.d
|
|
|
|
|
0x769C9C20, // xvsetnez.d
|
|
|
|
|
0x769CBC20, // xvsetallnez.d
|
|
|
|
|
0x4C000020,
|
|
|
|
|
)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("FP convert and FSEL", func(t *testing.T) {
|
|
|
|
|
fn := firstTextLOONG64(t, `#include "textflag.h"
|
|
|
|
|
TEXT ·v(SB), NOSPLIT, $0
|
|
|
|
|
FFINTDV F0, F1
|
|
|
|
|
FSEL FCC0, F3, F4, F3
|
|
|
|
|
FSEL FCC1, F1, F2
|
|
|
|
|
RET
|
|
|
|
|
`)
|
|
|
|
|
code := assembleLOONG64Helper(t, fn)
|
|
|
|
|
wantWords(t, code,
|
|
|
|
|
0x011D2801, // ffint.d.v f1, f0
|
|
|
|
|
0x0D000C83, // fsel f3, f4, f3, fcc0
|
|
|
|
|
0x0D008442, // fsel f2, f2, f1, fcc1
|
|
|
|
|
0x4C000020,
|
|
|
|
|
)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("VMOVQ move family", func(t *testing.T) {
|
|
|
|
|
fn := firstTextLOONG64(t, `#include "textflag.h"
|
|
|
|
|
TEXT ·v(SB), NOSPLIT, $0
|
|
|
|
|
VMOVQ V1, V9
|
|
|
|
|
VMOVQ (R4), V2
|
|
|
|
|
VMOVQ 16(R4), V2
|
|
|
|
|
VMOVQ V0, (R4)
|
|
|
|
|
VMOVQ V0, 32(R4)
|
|
|
|
|
VMOVQ (R4)(R7), V3
|
|
|
|
|
VMOVQ V3, (R4)(R7)
|
|
|
|
|
VMOVQ R6, V0.B16
|
|
|
|
|
VMOVQ R6, V12.W4
|
|
|
|
|
VMOVQ (R4), V4.W4
|
|
|
|
|
XVMOVQ X3, X7
|
|
|
|
|
XVMOVQ (R4), X2
|
|
|
|
|
XVMOVQ X0, (R4)
|
|
|
|
|
XVMOVQ (R4)(R7), X4
|
|
|
|
|
XVMOVQ X0, (R4)(R7)
|
|
|
|
|
XVMOVQ R6, X0.B32
|
|
|
|
|
RET
|
|
|
|
|
`)
|
|
|
|
|
code := assembleLOONG64Helper(t, fn)
|
|
|
|
|
wantWords(t, code,
|
|
|
|
|
0x732D0029, // vori.b v9, v1, 0 (register move)
|
|
|
|
|
0x2C000082, // vld v2, r4, 0
|
|
|
|
|
0x2C004082, // vld v2, r4, 16
|
|
|
|
|
0x2C400080, // vst v0, r4, 0
|
|
|
|
|
0x2C408080, // vst v0, r4, 32
|
|
|
|
|
0x38401C83, // vldx v3, r4, r7
|
|
|
|
|
0x38441C83, // vstx v3, r4, r7
|
|
|
|
|
0x729F00C0, // vreplgr2vr.b v0, r6
|
|
|
|
|
0x729F08CC, // vreplgr2vr.w v12, r6
|
|
|
|
|
0x30200084, // vldrepl.w v4, r4, 0
|
|
|
|
|
0x772D0067, // xvori.b x7, x3, 0
|
|
|
|
|
0x2C800082, // xvld x2, r4, 0
|
|
|
|
|
0x2CC00080, // xvst x0, r4, 0
|
|
|
|
|
0x38481C84, // xvldx x4, r4, r7
|
|
|
|
|
0x384C1C80, // xvstx x0, r4, r7
|
|
|
|
|
0x769F00C0, // xvreplgr2vr.b x0, r6
|
|
|
|
|
0x4C000020,
|
|
|
|
|
)
|
|
|
|
|
})
|
|
|
|
|
|
|
|
|
|
t.Run("element extract and insert", func(t *testing.T) {
|
|
|
|
|
fn := firstTextLOONG64(t, `#include "textflag.h"
|
|
|
|
|
TEXT ·v(SB), NOSPLIT, $0
|
|
|
|
|
VMOVQ V0.V[0], R10
|
|
|
|
|
VMOVQ V6.V[1], R8
|
|
|
|
|
VMOVQ R9, V1.V[0]
|
|
|
|
|
XVMOVQ X0.V[0], R10
|
|
|
|
|
XVMOVQ X5.W[7], R7
|
|
|
|
|
XVMOVQ R4, X7.V[3]
|
|
|
|
|
RET
|
|
|
|
|
`)
|
|
|
|
|
code := assembleLOONG64Helper(t, fn)
|
|
|
|
|
wantWords(t, code,
|
|
|
|
|
0x72EFF00A, // vpickve2gr.d r10, v0, 0
|
|
|
|
|
0x72EFF4C8, // vpickve2gr.d r8, v6, 1
|
|
|
|
|
0x72EBF121, // vinsgr2vr.d v1, r9, 0
|
|
|
|
|
0x76EFE00A, // xvpickve2gr.d r10, x0, 0
|
|
|
|
|
0x76EFDCA7, // xvpickve2gr.w r7, x5, 7
|
|
|
|
|
0x76EBEC87, // xvinsgr2vr.d x7, r4, 3
|
|
|
|
|
0x4C000020,
|
|
|
|
|
)
|
|
|
|
|
})
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TestLOONG64_vectorErrors pins the register-class and range diagnostics of
|
|
|
|
|
// the vector slice; each shape is rejected by the oracle as well
|
|
|
|
|
// (GOARCH=loong64 go tool asm).
|
|
|
|
|
func TestLOONG64_vectorErrors(t *testing.T) {
|
|
|
|
|
cases := []string{
|
|
|
|
|
// Integer registers in vector positions.
|
|
|
|
|
`TEXT ·e(SB), NOSPLIT, $0
|
|
|
|
|
VADDV R4, R5, R6
|
|
|
|
|
RET
|
|
|
|
|
`,
|
|
|
|
|
// Crossed banks: LSX spellings take V, LASX spellings X.
|
|
|
|
|
`TEXT ·e(SB), NOSPLIT, $0
|
|
|
|
|
VADDV X1, X2, X3
|
|
|
|
|
RET
|
|
|
|
|
`,
|
|
|
|
|
`TEXT ·e(SB), NOSPLIT, $0
|
|
|
|
|
XVADDV V1, V2, V3
|
|
|
|
|
RET
|
|
|
|
|
`,
|
|
|
|
|
// The LASX bank has no .b/.h element forms.
|
|
|
|
|
`TEXT ·e(SB), NOSPLIT, $0
|
|
|
|
|
XVMOVQ R4, X2.B[0]
|
|
|
|
|
RET
|
|
|
|
|
`,
|
|
|
|
|
// Immediate ranges.
|
|
|
|
|
`TEXT ·e(SB), NOSPLIT, $0
|
|
|
|
|
VANDB $256, V2
|
|
|
|
|
RET
|
|
|
|
|
`,
|
|
|
|
|
`TEXT ·e(SB), NOSPLIT, $0
|
|
|
|
|
VSEQB $16, V2, V3
|
|
|
|
|
RET
|
|
|
|
|
`,
|
|
|
|
|
`TEXT ·e(SB), NOSPLIT, $0
|
|
|
|
|
VROTRW $32, V1, V2
|
|
|
|
|
RET
|
|
|
|
|
`,
|
|
|
|
|
// VSET* wants an FCC flag, not a vector register.
|
|
|
|
|
`TEXT ·e(SB), NOSPLIT, $0
|
|
|
|
|
VSETNEV V1, V2
|
|
|
|
|
RET
|
|
|
|
|
`,
|
|
|
|
|
}
|
|
|
|
|
for i, src := range cases {
|
|
|
|
|
fn := firstTextLOONG64(t, src)
|
|
|
|
|
if _, _, _, _, _, err := assembleLOONG64(fn); err == nil {
|
|
|
|
|
t.Errorf("case %d: expected an error, got none", i)
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// TestLOONG64_dbarAtomics pins the _dbar (acquire/release) AMO variants.
|
|
|
|
|
// The oracle words come from GOARCH=loong64 go tool objdump of kernels
|
|
|
|
|
// assembled with go tool asm, and match the toolchain's loong64enc1.s.
|
|
|
|
|
func TestLOONG64_dbarAtomics(t *testing.T) {
|
|
|
|
|
fn := firstTextLOONG64(t, `#include "textflag.h"
|
|
|
|
|
TEXT ·atoms(SB), NOSPLIT, $0
|
|
|
|
|
AMADDDBW R14, (R13), R12
|
|
|
|
|
AMADDDBV R14, (R13), R12
|
|
|
|
|
AMANDDBW R5, (R4), R6
|
|
|
|
|
AMANDDBV R5, (R4), R6
|
|
|
|
|
AMORDBW R5, (R4), R0
|
|
|
|
|
AMORDBV R5, (R4), R6
|
|
|
|
|
AMSWAPDBW R5, (R4), R6
|
|
|
|
|
AMCASDBV R6, (R4), R5
|
|
|
|
|
RET
|
|
|
|
|
`)
|
|
|
|
|
code := assembleLOONG64Helper(t, fn)
|
|
|
|
|
wantWords(t, code,
|
|
|
|
|
0x386A39AC, // amadd_db.w r12, r13, r14
|
|
|
|
|
0x386AB9AC, // amadd_db.d
|
|
|
|
|
0x386B1486, // amand_db.w r6, r4, r5
|
|
|
|
|
0x386B9486, // amand_db.d
|
|
|
|
|
0x386C1480, // amor_db.w r0, r4, r5
|
|
|
|
|
0x386C9486, // amor_db.d
|
|
|
|
|
0x38691486, // amswap_db.w
|
|
|
|
|
0x385B9885, // amcas_db.w
|
|
|
|
|
0x4C000020,
|
|
|
|
|
)
|
|
|
|
|
}
|