1745 lines
54 KiB
Go
1745 lines
54 KiB
Go
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
||
// SPDX-License-Identifier: BSD-3-Clause
|
||
|
||
package asm
|
||
|
||
import (
|
||
"strings"
|
||
"testing"
|
||
|
||
"sourcedock.dev/petrbalvin/gasm-devkit/ast"
|
||
"sourcedock.dev/petrbalvin/gasm-devkit/parser"
|
||
)
|
||
|
||
func TestArm64LDRSTREncoding(t *testing.T) {
|
||
tests := []struct {
|
||
name string
|
||
got uint32
|
||
want uint32
|
||
}{
|
||
{"LDR X4, [SP, #56]", a64LSU(3, 0, 1, 7, 31, 4), 0xf9401fe4},
|
||
{"STR X4, [SP, #64]", a64LSU(3, 0, 0, 8, 31, 4), 0xf90023e4},
|
||
{"STR X5, [SP, #32]", a64LSU(3, 0, 0, 4, 31, 5), 0xf90013e5},
|
||
{"LDR X6, [SP, #32]", a64LSU(3, 0, 1, 4, 31, 6), 0xf94013e6},
|
||
}
|
||
for _, tt := range tests {
|
||
if tt.got != tt.want {
|
||
t.Errorf("%s: got %08x, want %08x", tt.name, tt.got, tt.want)
|
||
}
|
||
}
|
||
}
|
||
|
||
func TestArm64PrologueEncoding(t *testing.T) {
|
||
fi := arm64FrameInfo{autosize: 48, frame: 32, leaf: false}
|
||
pro := arm64Prologue(fi)
|
||
if len(pro) != 12 {
|
||
t.Fatalf("prologue length: got %d, want 12", len(pro))
|
||
}
|
||
expected := []uint32{0xf81d0ffe, 0xf81f83fd, 0xd10023fd}
|
||
for i, w := range leWords(pro) {
|
||
if w != expected[i] {
|
||
t.Errorf("prologue word %d: got %08x, want %08x", i, w, expected[i])
|
||
}
|
||
}
|
||
}
|
||
|
||
func TestArm64EpilogueSmallEncoding(t *testing.T) {
|
||
fi := arm64FrameInfo{autosize: 48, frame: 32, leaf: false}
|
||
ret := arm64Return(fi)
|
||
if len(ret) != 12 {
|
||
t.Fatalf("epilogue length: got %d, want 12", len(ret))
|
||
}
|
||
// Non-leaf small frame: LDR FP, [SP, #-8]; LDR.P LR, [SP], #48; RET
|
||
expected := []uint32{0xf85f83fd, 0xf84307fe, 0xd65f03c0}
|
||
for i, w := range leWords(ret) {
|
||
if w != expected[i] {
|
||
t.Errorf("epilogue word %d: got %08x, want %08x", i, w, expected[i])
|
||
}
|
||
}
|
||
}
|
||
|
||
func TestArm64LargeFrameEncoding(t *testing.T) {
|
||
fi := arm64FrameInfo{autosize: 272, frame: 256, leaf: false}
|
||
pro := arm64Prologue(fi)
|
||
if len(pro) != 16 {
|
||
t.Fatalf("prologue length: got %d, want 16", len(pro))
|
||
}
|
||
expected := []uint32{0xd10443f4, 0xa93ffa9d, 0x9100029f, 0xd10023fd}
|
||
for i, w := range leWords(pro) {
|
||
if w != expected[i] {
|
||
t.Errorf("prologue word %d: got %08x, want %08x", i, w, expected[i])
|
||
}
|
||
}
|
||
|
||
epi := arm64Return(fi)
|
||
if len(epi) != 12 {
|
||
t.Fatalf("epilogue length: got %d, want 12", len(epi))
|
||
}
|
||
eexpected := []uint32{0xa97ffbfd, 0x910443ff, 0xd65f03c0}
|
||
for i, w := range leWords(epi) {
|
||
if w != eexpected[i] {
|
||
t.Errorf("epilogue word %d: got %08x, want %08x", i, w, eexpected[i])
|
||
}
|
||
}
|
||
}
|
||
|
||
func TestArm64NoFrame(t *testing.T) {
|
||
fi := arm64FrameInfo{autosize: 0, frame: 0, leaf: true}
|
||
pro := arm64Prologue(fi)
|
||
if len(pro) != 0 {
|
||
t.Errorf("no-frame prologue: got %d bytes, want 0", len(pro))
|
||
}
|
||
ret := arm64Return(fi)
|
||
if len(ret) != 4 {
|
||
t.Fatalf("no-frame return: got %d bytes, want 4", len(ret))
|
||
}
|
||
if leWord(ret) != 0xd65f03c0 {
|
||
t.Errorf("no-frame RET: got %08x, want d65f03c0", leWord(ret))
|
||
}
|
||
}
|
||
|
||
func TestArm64RegNum(t *testing.T) {
|
||
tests := []struct {
|
||
name string
|
||
want int
|
||
}{
|
||
{"R0", 0}, {"R4", 4}, {"R29", 29}, {"R30", 30}, {"R31", 31},
|
||
{"FP", 29}, {"LR", 30}, {"LINK", 30}, {"SP", 31}, {"ZR", 31},
|
||
{"R18_PLATFORM", 18},
|
||
{"F0", 0}, {"F4", 4}, {"F31", 31},
|
||
{"INVALID", -1}, {"X0", -1}, {"", -1},
|
||
}
|
||
for _, tt := range tests {
|
||
got := arm64RegNum(tt.name)
|
||
if got != tt.want {
|
||
t.Errorf("arm64RegNum(%q) = %d, want %d", tt.name, got, tt.want)
|
||
}
|
||
}
|
||
}
|
||
|
||
func TestArm64ComputeFrame(t *testing.T) {
|
||
src := "TEXT ·f(SB), NOSPLIT, $32-0\n\tADD\tR4, R5\n\tRET\n"
|
||
f, errs := parser.Parse("test_arm64.s", src)
|
||
if len(errs) > 0 {
|
||
t.Fatalf("parse: %v", errs)
|
||
}
|
||
fi := arm64ComputeFrame(f.Decls[0].(*ast.Text))
|
||
if fi.frame != 32 {
|
||
t.Errorf("frame: got %d, want 32", fi.frame)
|
||
}
|
||
if fi.autosize != 48 { // 32+8=40, aligned to48
|
||
t.Errorf("autosize: got %d, want 48", fi.autosize)
|
||
}
|
||
// ADD + RET with no CALL/BL → leaf
|
||
if !fi.leaf {
|
||
t.Error("expected leaf")
|
||
}
|
||
}
|
||
|
||
func TestArm64IsLeaf(t *testing.T) {
|
||
src := "TEXT ·f(SB), NOSPLIT, $0-0\n\tADD\tR4, R5\n\tRET\n"
|
||
f, errs := parser.Parse("test_arm64.s", src)
|
||
if len(errs) > 0 {
|
||
t.Fatalf("parse: %v", errs)
|
||
}
|
||
if !arm64IsLeaf(f.Decls[0].(*ast.Text)) {
|
||
t.Error("expected leaf")
|
||
}
|
||
|
||
src2 := "TEXT ·f(SB), NOSPLIT, $0-0\n\tBL\tother(SB)\n\tRET\n"
|
||
f2, errs := parser.Parse("test_arm64.s", src2)
|
||
if len(errs) > 0 {
|
||
t.Fatalf("parse: %v", errs)
|
||
}
|
||
if arm64IsLeaf(f2.Decls[0].(*ast.Text)) {
|
||
t.Error("expected non-leaf")
|
||
}
|
||
}
|
||
|
||
func TestArm64Bitmask(t *testing.T) {
|
||
tests := []struct {
|
||
v uint64
|
||
sf int
|
||
N, immr, imms uint32
|
||
ok bool
|
||
}{
|
||
{1, 1, 1, 0, 0, true}, // single bit at pos 0
|
||
{2, 1, 1, 63, 0, true}, // single bit at pos 1 (immr = esize-1)
|
||
{0, 1, 0, 0, 0, false}, // zero is not a bitmask
|
||
{0xFFFFFFFFFFFFFFFF, 1, 0, 0, 0, false}, // all ones is not a bitmask
|
||
{0x5555555555555555, 1, 0, 0, 0x3E, true}, // alternating bits (esize=2, ones=1)
|
||
{0xFFFFFFFF00000000, 1, 1, 32, 31, true}, // upper 32 bits set (esize=64, ones=32)
|
||
}
|
||
for _, tt := range tests {
|
||
N, immr, imms, ok := arm64Bitmask(tt.v, tt.sf)
|
||
if ok != tt.ok {
|
||
t.Errorf("arm64Bitmask(%#x, %d): ok=%v, want %v", tt.v, tt.sf, ok, tt.ok)
|
||
continue
|
||
}
|
||
if ok && (N != tt.N || immr != tt.immr || imms != tt.imms) {
|
||
t.Errorf("arm64Bitmask(%#x, %d): N=%d immr=%d imms=%d, want N=%d immr=%d imms=%d",
|
||
tt.v, tt.sf, N, immr, imms, tt.N, tt.immr, tt.imms)
|
||
}
|
||
}
|
||
}
|
||
|
||
func TestArm64AssembleFile(t *testing.T) {
|
||
src := `#include "textflag.h"
|
||
|
||
TEXT ·simple(SB), NOSPLIT, $0-0
|
||
MOV R4, R5
|
||
ADD R4, R5, R6
|
||
RET
|
||
`
|
||
f, errs := parser.Parse("test_arm64.s", src)
|
||
if len(errs) > 0 {
|
||
t.Fatalf("parse: %v", errs)
|
||
}
|
||
img, err := AssembleFileARM64(f)
|
||
if err != nil {
|
||
t.Fatalf("AssembleFileARM64: %v", err)
|
||
}
|
||
if len(img.Funcs) != 1 {
|
||
t.Fatalf("got %d funcs, want 1", len(img.Funcs))
|
||
}
|
||
fn := img.Funcs[0]
|
||
if fn.Name != "simple" {
|
||
t.Errorf("func name: got %q, want %q", fn.Name, "simple")
|
||
}
|
||
//3 instructions ×4 bytes =12
|
||
if fn.Size != 12 {
|
||
t.Errorf("func size: got %d, want 12", fn.Size)
|
||
}
|
||
}
|
||
|
||
func TestArm64AssembleFileWithFrame(t *testing.T) {
|
||
src := `#include "textflag.h"
|
||
|
||
TEXT ·framed(SB), NOSPLIT, $16-8
|
||
MOVD arg+0(FP), R4
|
||
ADD $1, R4, R4
|
||
MOVD R4, ret+0(FP)
|
||
RET
|
||
`
|
||
f, errs := parser.Parse("test_arm64.s", src)
|
||
if len(errs) > 0 {
|
||
t.Fatalf("parse: %v", errs)
|
||
}
|
||
img, err := AssembleFileARM64(f)
|
||
if err != nil {
|
||
t.Fatalf("AssembleFileARM64: %v", err)
|
||
}
|
||
if len(img.Funcs) != 1 {
|
||
t.Fatalf("got %d funcs, want 1", len(img.Funcs))
|
||
}
|
||
fn := img.Funcs[0]
|
||
if fn.Frame != 16 {
|
||
t.Errorf("frame: got %d, want 16", fn.Frame)
|
||
}
|
||
// Prologue (3×4=12) + body (3×4=12) + RET epilogue (3×4=12) = 36
|
||
if fn.Size != 36 {
|
||
t.Errorf("func size: got %d, want 36", fn.Size)
|
||
}
|
||
}
|
||
|
||
func TestArm64AssembleFileWithBranches(t *testing.T) {
|
||
src := `#include "textflag.h"
|
||
|
||
TEXT ·branch(SB), NOSPLIT, $0-0
|
||
BEQ done
|
||
BNE skip
|
||
skip:
|
||
ADD R4, R5
|
||
done:
|
||
RET
|
||
`
|
||
f, errs := parser.Parse("test_arm64.s", src)
|
||
if len(errs) > 0 {
|
||
t.Fatalf("parse: %v", errs)
|
||
}
|
||
img, err := AssembleFileARM64(f)
|
||
if err != nil {
|
||
t.Fatalf("AssembleFileARM64: %v", err)
|
||
}
|
||
fn := img.Funcs[0]
|
||
if fn.Size != 16 {
|
||
t.Errorf("func size: got %d, want 16", fn.Size)
|
||
}
|
||
}
|
||
|
||
func TestArm64AssembleFileWithJumpChain(t *testing.T) {
|
||
src := `#include "textflag.h"
|
||
|
||
TEXT ·chain(SB), NOSPLIT, $0-0
|
||
BNE skip
|
||
ADD R4, R5
|
||
RET
|
||
skip:
|
||
B target
|
||
target:
|
||
ADD R6, R7
|
||
RET
|
||
`
|
||
f, errs := parser.Parse("test_arm64.s", src)
|
||
if len(errs) > 0 {
|
||
t.Fatalf("parse: %v", errs)
|
||
}
|
||
img, err := AssembleFileARM64(f)
|
||
if err != nil {
|
||
t.Fatalf("AssembleFileARM64: %v", err)
|
||
}
|
||
// BNE should be redirected past skip→target to target directly.
|
||
if img.Funcs[0].Size != 24 {
|
||
t.Errorf("func size: got %d, want 24", img.Funcs[0].Size)
|
||
}
|
||
}
|
||
|
||
func TestArm64AssembleErrors(t *testing.T) {
|
||
tests := []struct {
|
||
name string
|
||
src string
|
||
}{
|
||
{"unsupported", "TEXT ·f(SB), NOSPLIT, $0-0\n\tINVALID\tR4, R5\n\tRET\n"},
|
||
{"undefined label", "TEXT ·f(SB), NOSPLIT, $0-0\n\tB\tnosuch\n\tRET\n"},
|
||
}
|
||
for _, tt := range tests {
|
||
t.Run(tt.name, func(t *testing.T) {
|
||
f, errs := parser.Parse("test_arm64.s", tt.src)
|
||
if len(errs) > 0 {
|
||
return // parse error, that's fine
|
||
}
|
||
_, err := AssembleFileARM64(f)
|
||
if err == nil {
|
||
t.Error("expected error, got nil")
|
||
}
|
||
})
|
||
}
|
||
}
|
||
|
||
func TestArm64Movcon(t *testing.T) {
|
||
tests := []struct {
|
||
v int64
|
||
want int
|
||
}{
|
||
{0, 0}, // 0 fits at shift 0
|
||
{1, 0}, // single bit at shift 0
|
||
{0x10000, 16}, // single bit at shift 16
|
||
{0x100000000, 32}, // single bit at shift 32
|
||
{0xFF, 0}, // 0xFF fits at shift 0
|
||
{0x12345, -1}, // multiple chunks, not movcon
|
||
}
|
||
for _, tt := range tests {
|
||
got := arm64Movcon(tt.v)
|
||
if got != tt.want {
|
||
t.Errorf("arm64Movcon(%#x) = %d, want %d", tt.v, got, tt.want)
|
||
}
|
||
}
|
||
}
|
||
|
||
func TestArm64RegClassOf(t *testing.T) {
|
||
if arm64RegClassOf("R4") != arm64ClsGR {
|
||
t.Error("R4 should be GR")
|
||
}
|
||
if arm64RegClassOf("F4") != arm64ClsFP {
|
||
t.Error("F4 should be FP")
|
||
}
|
||
if arm64RegClassOf("") != arm64ClsNone {
|
||
t.Error("empty should be None")
|
||
}
|
||
}
|
||
|
||
func TestArm64ResolvePseudo(t *testing.T) {
|
||
fi := arm64FrameInfo{autosize: 48, frame: 32}
|
||
// FP: offset = sym.Offset + autosize +8
|
||
base, off := arm64ResolvePseudo(&ast.Symbol{Pseudo: "FP", Offset: 0}, fi)
|
||
if base != 31 || off != 56 {
|
||
t.Errorf("FP: base=%d off=%d, want 31, 56", base, off)
|
||
}
|
||
// SP: offset = sym.Offset + frame +8
|
||
base, off = arm64ResolvePseudo(&ast.Symbol{Pseudo: "SP", Offset: -8}, fi)
|
||
if base != 31 || off != 32 {
|
||
t.Errorf("SP: base=%d off=%d, want 31, 32", base, off)
|
||
}
|
||
// SB: unresolved
|
||
base, _ = arm64ResolvePseudo(&ast.Symbol{Pseudo: "SB"}, fi)
|
||
if base != -1 {
|
||
t.Errorf("SB: base=%d, want -1", base)
|
||
}
|
||
}
|
||
|
||
// TestArm64FPSel tests FP conditional select encoding.
|
||
func TestArm64FPSel(t *testing.T) {
|
||
src := `#include "textflag.h"
|
||
TEXT ·f(SB), NOSPLIT, $0-0
|
||
FCSELD GE, F10, F11, F12
|
||
RET
|
||
`
|
||
f, errs := parser.Parse("test_arm64.s", src)
|
||
if len(errs) > 0 {
|
||
t.Fatalf("parse: %v", errs)
|
||
}
|
||
img, err := AssembleFileARM64(f)
|
||
if err != nil {
|
||
t.Fatalf("AssembleFileARM64: %v", err)
|
||
}
|
||
// FCSELD should be 4 bytes + RET 4 bytes = 8
|
||
if img.Funcs[0].Size != 8 {
|
||
t.Errorf("size: got %d, want 8", img.Funcs[0].Size)
|
||
}
|
||
}
|
||
|
||
// TestArm64FPCvt tests FP conversion encoding.
|
||
func TestArm64FPCvt(t *testing.T) {
|
||
src := `#include "textflag.h"
|
||
TEXT ·f(SB), NOSPLIT, $0-0
|
||
FCVTZSD F4, R0
|
||
SCVTFD R4, F8
|
||
RET
|
||
`
|
||
f, errs := parser.Parse("test_arm64.s", src)
|
||
if len(errs) > 0 {
|
||
t.Fatalf("parse: %v", errs)
|
||
}
|
||
img, err := AssembleFileARM64(f)
|
||
if err != nil {
|
||
t.Fatalf("AssembleFileARM64: %v", err)
|
||
}
|
||
if img.Funcs[0].Size != 12 {
|
||
t.Errorf("size: got %d, want 12", img.Funcs[0].Size)
|
||
}
|
||
}
|
||
|
||
// TestArm64CSEL tests conditional select encoding.
|
||
func TestArm64CSEL(t *testing.T) {
|
||
src := `#include "textflag.h"
|
||
TEXT ·f(SB), NOSPLIT, $0-0
|
||
CSEL EQ, R0, R1, R2
|
||
CSET NE, R3
|
||
CINC GE, R4, R5
|
||
RET
|
||
`
|
||
f, errs := parser.Parse("test_arm64.s", src)
|
||
if len(errs) > 0 {
|
||
t.Fatalf("parse: %v", errs)
|
||
}
|
||
img, err := AssembleFileARM64(f)
|
||
if err != nil {
|
||
t.Fatalf("AssembleFileARM64: %v", err)
|
||
}
|
||
if img.Funcs[0].Size != 16 {
|
||
t.Errorf("size: got %d, want 16", img.Funcs[0].Size)
|
||
}
|
||
}
|
||
|
||
// TestArm64CRC32 tests CRC32 encoding.
|
||
func TestArm64CRC32(t *testing.T) {
|
||
src := `#include "textflag.h"
|
||
TEXT ·f(SB), NOSPLIT, $0-0
|
||
CRC32B R0, R2
|
||
CRC32W R6, R8
|
||
RET
|
||
`
|
||
f, errs := parser.Parse("test_arm64.s", src)
|
||
if len(errs) > 0 {
|
||
t.Fatalf("parse: %v", errs)
|
||
}
|
||
img, err := AssembleFileARM64(f)
|
||
if err != nil {
|
||
t.Fatalf("AssembleFileARM64: %v", err)
|
||
}
|
||
if img.Funcs[0].Size != 12 {
|
||
t.Errorf("size: got %d, want 12", img.Funcs[0].Size)
|
||
}
|
||
}
|
||
|
||
// TestArm64Bitfield tests bitfield/shift encoding.
|
||
func TestArm64Bitfield(t *testing.T) {
|
||
src := `#include "textflag.h"
|
||
TEXT ·f(SB), NOSPLIT, $0-0
|
||
ASR $4, R0, R1
|
||
LSL $12, R4, R5
|
||
EXTR $8, R0, R1, R2
|
||
RET
|
||
`
|
||
f, errs := parser.Parse("test_arm64.s", src)
|
||
if len(errs) > 0 {
|
||
t.Fatalf("parse: %v", errs)
|
||
}
|
||
img, err := AssembleFileARM64(f)
|
||
if err != nil {
|
||
t.Fatalf("AssembleFileARM64: %v", err)
|
||
}
|
||
if img.Funcs[0].Size != 16 {
|
||
t.Errorf("size: got %d, want 16", img.Funcs[0].Size)
|
||
}
|
||
}
|
||
|
||
// TestArm64SIMD tests SIMD encoding (via the arrangement-aware table).
|
||
func TestArm64SIMD(t *testing.T) {
|
||
// Verify SIMD instructions are in the arrangement table.
|
||
for _, mnem := range []string{"VADD", "VSUB", "VMUL", "VAND", "VEOR", "VORR", "VCMEQ", "VZIP1", "VZIP2"} {
|
||
if _, ok := a64SimdVTable[mnem]; !ok {
|
||
t.Errorf("%s not in the SIMD arrangement table", mnem)
|
||
}
|
||
}
|
||
}
|
||
|
||
// TestArm64CarryAndBitOps pins the carry-setting arithmetic, the widening
|
||
// multiplies and the data-processing (1 source) group against go tool asm.
|
||
func TestArm64CarryAndBitOps(t *testing.T) {
|
||
got := arm64Words(t, "\tADC R0, R2, R12\n\tADCS $0, R1\n\tSBCS R5, R9, R5\n\tSBC R25, R10, R26\n"+
|
||
"\tMUL R4, R3, R0\n\tUMULH R24, R20, R24\n\tSMULH R1, R2, R3\n\tMSUB R19, R16, R26, R2\n"+
|
||
"\tRBIT R11, R4\n\tREV R1, R2\n\tCLZ R21, R9\n\tREVW R1, R2\n\tCLSW R1, R2\n")
|
||
want := []uint32{
|
||
0x9a00004c, // ADC R12, R2, R0
|
||
0xba1f0021, // ADCS R1, R1, ZR
|
||
0xfa050125, // SBCS R5, R9, R5
|
||
0xda19015a, // SBC R26, R10, R25
|
||
0x9b047c60, // MUL R0, R3, R4
|
||
0x9bd87e98, // UMULH R24, R20, R24
|
||
0x9b417c43, // SMULH R3, R2, R1
|
||
0x9b13c342, // MSUB R2, R26, R19, R16
|
||
0xdac00164, // RBIT R4, R11
|
||
0xdac00c22, // REV R2, R1
|
||
0xdac012a9, // CLZ R9, R21
|
||
0x5ac00822, // REVW R2, R1
|
||
0x5ac01422, // CLSW R2, R1
|
||
0xd65f03c0, // RET
|
||
}
|
||
if len(got) != len(want) {
|
||
t.Fatalf("word count = %d, want %d", len(got), len(want))
|
||
}
|
||
for i := range want {
|
||
if got[i] != want[i] {
|
||
t.Errorf("word %d = %08x, want %08x", i, got[i], want[i])
|
||
}
|
||
}
|
||
}
|
||
|
||
// TestArm64BitfieldExtract pins UBFX/SBFX: immr wraps to the register
|
||
// width, an out-of-range imms is an error.
|
||
func TestArm64BitfieldExtract(t *testing.T) {
|
||
got := arm64Words(t, "\tUBFX $33, R17, $25, R5\n\tUBFXW $4, R1, $9, R2\n")
|
||
want := []uint32{
|
||
0xd361e625, // UBFX immr=1 (33 wrapped), imms=25
|
||
0x53043022, // UBFXW immr=4, imms=9
|
||
0xd65f03c0,
|
||
}
|
||
if len(got) != len(want) {
|
||
t.Fatalf("word count = %d, want %d", len(got), len(want))
|
||
}
|
||
for i := range want {
|
||
if got[i] != want[i] {
|
||
t.Errorf("word %d = %08x, want %08x", i, got[i], want[i])
|
||
}
|
||
}
|
||
for _, body := range []string{"\tUBFX $33, R17, $70, R5\n", "\tUBFX $-1, R17, $3, R5\n"} {
|
||
f, errs := parser.Parse("test_arm64.s", "#include \"textflag.h\"\n\nTEXT ·f(SB), NOSPLIT, $0-0\n"+body+"\tRET\n")
|
||
if len(errs) > 0 {
|
||
t.Fatalf("parse: %v", errs)
|
||
}
|
||
if _, err := AssembleFileARM64(f); err == nil {
|
||
t.Errorf("%s: expected an error, got none", body)
|
||
}
|
||
}
|
||
}
|
||
|
||
// TestArm64CondCompare pins CCMP/CCMN.
|
||
func TestArm64CondCompare(t *testing.T) {
|
||
got := arm64Words(t, "\tCCMP LE, R7, $19, $3\n\tCCMP LT, R30, R6, $7\n\tCCMN EQ, R1, R2, $3\n\tCCMPW LE, R7, $19, $3\n")
|
||
want := []uint32{
|
||
0xfa53d8e3, // CCMP imm form
|
||
0xfa46b3c7, // CCMP register form
|
||
0xba420023, // CCMN register form
|
||
0x7a53d8e3, // CCMPW
|
||
0xd65f03c0,
|
||
}
|
||
if len(got) != len(want) {
|
||
t.Fatalf("word count = %d, want %d", len(got), len(want))
|
||
}
|
||
for i := range want {
|
||
if got[i] != want[i] {
|
||
t.Errorf("word %d = %08x, want %08x", i, got[i], want[i])
|
||
}
|
||
}
|
||
}
|
||
|
||
// TestArm64CompareBranch pins CBZ/CBNZ/TBZ/TBNZ against a label five and
|
||
// six words ahead, matching go tool asm's own offsets.
|
||
func TestArm64CompareBranch(t *testing.T) {
|
||
// Layout: CBZ(0) TBZ(4) TBNZ(8) CBNZ(12) NOP(16) NOP(17th word...) done.
|
||
src := "#include \"textflag.h\"\n\nTEXT ·f(SB), NOSPLIT, $0-0\n" +
|
||
"\tCBZ R1, done\n\tTBZ $4, R7, done\n\tTBNZ $33, R7, done\n\tCBNZW R2, done\n" +
|
||
"\tNOP\n\tNOP\n\tdone:\tNOP\n\tRET\n"
|
||
f, errs := parser.Parse("test_arm64.s", src)
|
||
if len(errs) > 0 {
|
||
t.Fatalf("parse: %v", errs)
|
||
}
|
||
img, err := AssembleFileARM64(f)
|
||
if err != nil {
|
||
t.Fatalf("AssembleFileARM64: %v", err)
|
||
}
|
||
got := leWords(img.Code)
|
||
// done sits at word 6 from each branch's own pc: CBZ rel 6, TBZ rel 5,
|
||
// TBNZ rel 4, CBNZW rel 3.
|
||
want := []uint32{
|
||
0xb40000c1, // CBZ R1, +6
|
||
0x362000a7, // TBZ $4, R7, +5
|
||
0xb7080087, // TBNZ $33, R7, +4
|
||
0x35000062, // CBNZW R2, +3
|
||
0xd503201f, 0xd503201f, 0xd503201f,
|
||
0xd65f03c0,
|
||
}
|
||
if len(got) != len(want) {
|
||
t.Fatalf("word count = %d, want %d", len(got), len(want))
|
||
}
|
||
for i := range want {
|
||
if got[i] != want[i] {
|
||
t.Errorf("word %d = %08x, want %08x", i, got[i], want[i])
|
||
}
|
||
}
|
||
}
|
||
|
||
// TestArm64ADR pins ADR against a forward label.
|
||
func TestArm64ADR(t *testing.T) {
|
||
src := "#include \"textflag.h\"\n\nTEXT ·f(SB), NOSPLIT, $0-0\n" +
|
||
"\tADR done, R10\n\tNOP\n\tNOP\n\tdone:\tNOP\n\tRET\n"
|
||
f, errs := parser.Parse("test_arm64.s", src)
|
||
if len(errs) > 0 {
|
||
t.Fatalf("parse: %v", errs)
|
||
}
|
||
img, err := AssembleFileARM64(f)
|
||
if err != nil {
|
||
t.Fatalf("AssembleFileARM64: %v", err)
|
||
}
|
||
got := leWords(img.Code)
|
||
// rel = 12 bytes: immlo 0, immhi 3.
|
||
want := []uint32{0x1000006a, 0xd503201f, 0xd503201f, 0xd503201f, 0xd65f03c0}
|
||
if len(got) != len(want) {
|
||
t.Fatalf("word count = %d, want %d", len(got), len(want))
|
||
}
|
||
for i := range want {
|
||
if got[i] != want[i] {
|
||
t.Errorf("word %d = %08x, want %08x", i, got[i], want[i])
|
||
}
|
||
}
|
||
}
|
||
|
||
// TestArm64PairLoadStore pins LDP/STP/LDPW/FLDPD/FSTPD.
|
||
func TestArm64PairLoadStore(t *testing.T) {
|
||
got := arm64Words(t, "\tSTP (R2, R3), 8(R5)\n\tLDP -8(R5), (R2, R3)\n\tLDPW 4(R0), (R1, R2)\n\tSTPW (R1, R2), 4(R0)\n"+
|
||
"\tFLDPD 8(R0), (F1, F2)\n\tFSTPD (F3, F4), -8(R5)\n")
|
||
want := []uint32{
|
||
0xa9008ca2, // STP (R2, R3), 8(R5)
|
||
0xa97f8ca2, // LDP -8(R5), (R2, R3)
|
||
0x29408801, // LDPW 4(R0), (R1, R2)
|
||
0x29008801, // STPW (R1, R2), 4(R0)
|
||
0x6d408801, // FLDPD 8(R0), (F1, F2)
|
||
0x6d3f90a3, // FSTPD (F3, F4), -8(R5)
|
||
0xd65f03c0,
|
||
}
|
||
if len(got) != len(want) {
|
||
t.Fatalf("word count = %d, want %d", len(got), len(want))
|
||
}
|
||
for i := range want {
|
||
if got[i] != want[i] {
|
||
t.Errorf("word %d = %08x, want %08x", i, got[i], want[i])
|
||
}
|
||
}
|
||
}
|
||
|
||
// TestArm64AcquireRelease pins LDAR/STLR and the acquire/release LSE
|
||
// families.
|
||
func TestArm64AcquireRelease(t *testing.T) {
|
||
got := arm64Words(t, "\tLDAR (R27), R22\n\tLDARB (R25), R2\n\tLDARW (R12), R29\n\tSTLR R3, (R24)\n\tSTLRB R11, (R22)\n"+
|
||
"\tCASALD R5, (R6), R7\n\tLDADDALD R5, (R6), R7\n\tLDCLRALB R5, (R6), R7\n\tLDORALD R5, (RSP), R7\n\tSWPALW R5, (R6), R7\n")
|
||
want := []uint32{
|
||
0xc8dfff76, // LDAR R22, (R27)
|
||
0x08dfff22, // LDARB R2, (R25)
|
||
0x88dffd9d, // LDARW R29, (R12)
|
||
0xc89fff03, // STLR R3, (R24)
|
||
0x089ffecb, // STLRB R11, (R22)
|
||
0xc8e5fcc7, // CASALD R7, (R6), R5
|
||
0xf8e500c7, // LDADDALD R7, (R6), R5
|
||
0x38e510c7, // LDCLRALB R7, (R6), R5
|
||
0xf8e533e7, // LDORALD R7, (RSP), R5
|
||
0xb8e580c7, // SWPALW R7, (R6), R5
|
||
0xd65f03c0,
|
||
}
|
||
if len(got) != len(want) {
|
||
t.Fatalf("word count = %d, want %d", len(got), len(want))
|
||
}
|
||
for i := range want {
|
||
if got[i] != want[i] {
|
||
t.Errorf("word %d = %08x, want %08x", i, got[i], want[i])
|
||
}
|
||
}
|
||
}
|
||
|
||
// TestArm64BTI pins the landing-pad family against the toolchain words:
|
||
// only the uppercase C/J/JC spellings assemble, and bare BTI is a
|
||
// diagnostic, never a panic.
|
||
func TestArm64BTI(t *testing.T) {
|
||
got := arm64Words(t, "\tBTI C\n\tBTI J\n\tBTI JC\n")
|
||
want := []uint32{
|
||
0xd503245f, // BTI C
|
||
0xd503249f, // BTI J
|
||
0xd50324df, // BTI JC
|
||
0xd65f03c0, // RET
|
||
}
|
||
if len(got) != len(want) {
|
||
t.Fatalf("got %d words, want %d", len(got), len(want))
|
||
}
|
||
for i := range want {
|
||
if got[i] != want[i] {
|
||
t.Errorf("word %d = %#x, want %#x", i, got[i], want[i])
|
||
}
|
||
}
|
||
for _, src := range []string{"\tBTI\n", "\tBTI c\n", "\tBTI B\n"} {
|
||
f, errs := parser.Parse("test_arm64.s", "#include \"textflag.h\"\n\nTEXT ·f(SB), NOSPLIT, $0-0\n"+src+"\tRET\n")
|
||
if len(errs) > 0 {
|
||
continue
|
||
}
|
||
if _, err := AssembleFileARM64(f); err == nil {
|
||
t.Errorf("BTI spelling %q should be rejected, as go tool asm rejects it", src)
|
||
}
|
||
}
|
||
}
|
||
|
||
// TestArm64System pins BRK, SVC, the barriers, cache maintenance and the
|
||
// system register accesses.
|
||
func TestArm64System(t *testing.T) {
|
||
got := arm64Words(t, "\tBRK $35943\n\tBRK\n\tSVC $7165\n\tDMB $1\n\tDSB $1\n\tISB $15\n"+
|
||
"\tDC ZVA, R4\n\tDC IVAC, R1\n\tMRS DCZID_EL0, R3\n\tMRS CNTVCT_EL0, R0\n\tMSR $9, DAIFSet\n\tMSR $3, SPSel\n"+
|
||
"\tPRFM (R0), PLDL1KEEP\n\tPRFM (R3), PLDL3KEEP\n\tPRFM (R2), $25\n")
|
||
want := []uint32{
|
||
0xd4318ce0, // BRK $35943
|
||
0xd4200000, // BRK
|
||
0xd4037fa1, // SVC $7165
|
||
0xd50331bf, // DMB $1
|
||
0xd503319f, // DSB $1
|
||
0xd5033fdf, // ISB $15
|
||
0xd50b7424, // DC ZVA, R4
|
||
0xd5087621, // DC IVAC, R1
|
||
0xd53b00e3, // MRS DCZID_EL0, R3
|
||
0xd53be040, // MRS CNTVCT_EL0, R0
|
||
0xd50349df, // MSR $9, DAIFSet
|
||
0xd50043bf, // MSR $3, SPSel
|
||
0xf9800000, // PRFM (R0), PLDL1KEEP
|
||
0xf9800064, // PRFM (R3), PLDL3KEEP
|
||
0xf9800059, // PRFM (R2), $25
|
||
0xd65f03c0,
|
||
}
|
||
if len(got) != len(want) {
|
||
t.Fatalf("word count = %d, want %d", len(got), len(want))
|
||
}
|
||
for i := range want {
|
||
if got[i] != want[i] {
|
||
t.Errorf("word %d = %08x, want %08x", i, got[i], want[i])
|
||
}
|
||
}
|
||
}
|
||
|
||
// TestArm64Crypto pins the AES and SHA families.
|
||
func TestArm64Crypto(t *testing.T) {
|
||
got := arm64Words(t, "\tAESE V31.B16, V29.B16\n\tAESD V22.B16, V19.B16\n\tAESIMC V12.B16, V27.B16\n\tAESMC V14.B16, V28.B16\n"+
|
||
"\tSHA1C V8.S4, V8, V2\n\tSHA1H V17, V25\n\tSHA1P V3.S4, V20, V27\n\tSHA1SU0 V17.S4, V13.S4, V16.S4\n\tSHA1SU1 V24.S4, V23.S4\n"+
|
||
"\tSHA256H V4.S4, V2, V11\n\tSHA256H2 V6.S4, V16, V11\n\tSHA256SU0 V0.S4, V16.S4\n\tSHA256SU1 V31.S4, V3.S4, V15.S4\n"+
|
||
"\tSHA512H V2.D2, V1, V0\n\tSHA512H2 V4.D2, V3, V2\n\tSHA512SU0 V9.D2, V8.D2\n\tSHA512SU1 V7.D2, V6.D2, V5.D2\n")
|
||
want := []uint32{
|
||
0x4e284bfd, // AESE
|
||
0x4e285ad3, // AESD
|
||
0x4e28799b, // AESIMC
|
||
0x4e2869dc, // AESMC
|
||
0x5e080102, // SHA1C
|
||
0x5e280a39, // SHA1H
|
||
0x5e03129b, // SHA1P
|
||
0x5e1131b0, // SHA1SU0
|
||
0x5e281b17, // SHA1SU1
|
||
0x5e04404b, // SHA256H
|
||
0x5e06520b, // SHA256H2
|
||
0x5e282810, // SHA256SU0
|
||
0x5e1f606f, // SHA256SU1
|
||
0xce628020, // SHA512H
|
||
0xce648462, // SHA512H2
|
||
0xcec08128, // SHA512SU0
|
||
0xce6788c5, // SHA512SU1
|
||
0xd65f03c0,
|
||
}
|
||
if len(got) != len(want) {
|
||
t.Fatalf("word count = %d, want %d", len(got), len(want))
|
||
}
|
||
for i := range want {
|
||
if got[i] != want[i] {
|
||
t.Errorf("word %d = %08x, want %08x", i, got[i], want[i])
|
||
}
|
||
}
|
||
}
|
||
|
||
// TestArm64SIMDLogical pins the arrangement-aware three- and two-register
|
||
// SIMD paths.
|
||
func TestArm64SIMDLogical(t *testing.T) {
|
||
got := arm64Words(t, "\tVADD V1.B16, V2.B16, V3.B16\n\tVAND V4.B16, V4.B16, V9.B16\n\tVEOR V0.B16, V1.B16, V0.B16\n"+
|
||
"\tVORR V5.B16, V4.B16, V3.B16\n\tVADDP V1.H8, V2.H8, V3.H8\n\tVZIP1 V16.H8, V3.H8, V19.H8\n\tVZIP2 V22.D2, V25.D2, V21.D2\n"+
|
||
"\tVCMEQ V24.S4, V13.S4, V12.S4\n\tVCMEQ $0, V2.H4, V3.H4\n\tVREV32 V2.H8, V1.H8\n\tVREV64 V2.S4, V3.S4\n\tVUADDLV V31.S4, V11\n"+
|
||
"\tVPMULL V2.D1, V1.D1, V3.Q1\n\tVPMULL2 V2.B16, V1.B16, V4.H8\n\tVRAX1 V26.D2, V29.D2, V30.D2\n\tVMOV V2.B16, V4.B16\n")
|
||
want := []uint32{
|
||
0x4e218443, // VADD 16B
|
||
0x4e241c89, // VAND
|
||
0x6e201c20, // VEOR
|
||
0x4ea51c83, // VORR
|
||
0x4e61bc43, // VADDP 8H
|
||
0x4e503873, // VZIP1 8H
|
||
0x4ed67b35, // VZIP2 2D
|
||
0x6eb88dac, // VCMEQ 4S
|
||
0x0e609843, // VCMEQ $0, 4H
|
||
0x6e600841, // VREV32 8H
|
||
0x4ea00843, // VREV64 4S
|
||
0x6eb03beb, // VUADDLV 4S
|
||
0x0ee2e023, // VPMULL D1
|
||
0x4e22e024, // VPMULL2 16B
|
||
0xce7a8fbe, // VRAX1 2D
|
||
0x4ea21c44, // VMOV 16B pair
|
||
0xd65f03c0,
|
||
}
|
||
if len(got) != len(want) {
|
||
t.Fatalf("word count = %d, want %d", len(got), len(want))
|
||
}
|
||
for i := range want {
|
||
if got[i] != want[i] {
|
||
t.Errorf("word %d = %08x, want %08x", i, got[i], want[i])
|
||
}
|
||
}
|
||
}
|
||
|
||
// TestArm64SIMDWide pins the four-register crypto group, VXAR, VEXT and the
|
||
// shift-by-immediate encodings.
|
||
func TestArm64SIMDWide(t *testing.T) {
|
||
got := arm64Words(t, "\tVEOR3 V2.B16, V7.B16, V12.B16, V25.B16\n\tVBCAX V1.B16, V2.B16, V26.B16, V31.B16\n"+
|
||
"\tVXAR $63, V27.D2, V21.D2, V26.D2\n\tVEXT $4, V2.B8, V1.B8, V3.B8\n\tVEXT $8, V2.B16, V1.B16, V3.B16\n"+
|
||
"\tVSHL $7, V22.D2, V25.D2\n\tVUSHR $6, V22.H8, V23.H8\n\tVSRI $24, V1.S4, V2.S4\n")
|
||
want := []uint32{
|
||
0xce070999, // VEOR3
|
||
0xce22075f, // VBCAX
|
||
0xce9bfeba, // VXAR
|
||
0x2e022023, // VEXT B8
|
||
0x6e024023, // VEXT B16
|
||
0x4f4756d9, // VSHL D2 $7
|
||
0x6f1a06d7, // VUSHR H8 $6
|
||
0x6f284422, // VSRI S4 $24
|
||
0xd65f03c0,
|
||
}
|
||
if len(got) != len(want) {
|
||
t.Fatalf("word count = %d, want %d", len(got), len(want))
|
||
}
|
||
for i := range want {
|
||
if got[i] != want[i] {
|
||
t.Errorf("word %d = %08x, want %08x", i, got[i], want[i])
|
||
}
|
||
}
|
||
}
|
||
|
||
// TestArm64SIMDElement pins VDUP and the VMOV element forms.
|
||
func TestArm64SIMDElement(t *testing.T) {
|
||
got := arm64Words(t, "\tVDUP V31.B[15], V18\n\tVDUP V19.S[3], V18.S4\n\tVDUP V1.D[1], V2.D2\n"+
|
||
"\tVMOV V13.S[0], R20\n\tVMOV V11.B[11], V16.B[12]\n\tVMOV R20, V21.B[2]\n")
|
||
want := []uint32{
|
||
0x5e1f07f2, // VDUP element to register
|
||
0x4e1c0672, // VDUP element across S4
|
||
0x4e180422, // VDUP element across D2
|
||
0x0e043db4, // VMOV element to register
|
||
0x6e195d70, // VMOV element to element
|
||
0x4e051e95, // VMOV register into element
|
||
0xd65f03c0,
|
||
}
|
||
if len(got) != len(want) {
|
||
t.Fatalf("word count = %d, want %d", len(got), len(want))
|
||
}
|
||
for i := range want {
|
||
if got[i] != want[i] {
|
||
t.Errorf("word %d = %08x, want %08x", i, got[i], want[i])
|
||
}
|
||
}
|
||
}
|
||
|
||
// TestArm64GPIntoVector pins the whole-vector moves VMOV/VDUP Rs, Vd.<T>
|
||
// against `go tool asm -S` output (Go 1.27, arm64): word = Q | 7<<25 |
|
||
// imm5<<16 | 3<<10 | rs<<5 | rd, shared by both mnemonics, the form
|
||
// sys_windows_arm64.s and the bytealg loops use. The D1 destination is
|
||
// rejected, as the toolchain rejects it.
|
||
func TestArm64GPIntoVector(t *testing.T) {
|
||
got := arm64Words(t, "\tVMOV R5, V5.B16\n\tVMOV R1, V2.B8\n\tVMOV R3, V4.H4\n"+
|
||
"\tVMOV R9, V10.S4\n\tVMOV R7, V31.H8\n\tVMOV R11, V12.D2\n"+
|
||
"\tVDUP R5, V5.B16\n\tVDUP R9, V10.H8\n\tVMOV V4.B16, V20.B16\n")
|
||
want := []uint32{
|
||
0x4e010ca5, // VMOV R5, V5.B16
|
||
0x0e010c22, // VMOV R1, V2.B8
|
||
0x0e020c64, // VMOV R3, V4.H4
|
||
0x4e040d2a, // VMOV R9, V10.S4
|
||
0x4e020cff, // VMOV R7, V31.H8
|
||
0x4e080d6c, // VMOV R11, V12.D2
|
||
0x4e010ca5, // VDUP R5, V5.B16 (same word as VMOV)
|
||
0x4e020d2a, // VDUP R9, V10.H8
|
||
0x4ea41c94, // VMOV V4.B16, V20.B16 (vector to vector stays ORR)
|
||
0xd65f03c0,
|
||
}
|
||
if len(got) != len(want) {
|
||
t.Fatalf("word count = %d, want %d", len(got), len(want))
|
||
}
|
||
for i := range want {
|
||
if got[i] != want[i] {
|
||
t.Errorf("word %d = %08x, want %08x", i, got[i], want[i])
|
||
}
|
||
}
|
||
f, errs := parser.Parse("test_arm64.s", "#include \"textflag.h\"\n\nTEXT ·f(SB), NOSPLIT, $0-0\n\tVMOV R7, V8.D1\n\tRET\n")
|
||
if len(errs) > 0 {
|
||
t.Fatalf("parse: %v", errs)
|
||
}
|
||
if _, err := AssembleFileARM64(f); err == nil {
|
||
t.Errorf("VMOV R7, V8.D1 assembled, want an arrangement error")
|
||
}
|
||
}
|
||
|
||
// TestArm64SimdTwoOperand pins the two-operand accumulate spellings
|
||
// VADD/VSUB Vm, Vn against `go tool asm -S` output (Go 1.27, arm64):
|
||
// word = 5<<28|7<<25|7<<21|1<<15|1<<10 for VADD (7<<28 for VSUB) with
|
||
// rf<<16 | rn<<5 | rn, bare V registers only (asm7.go case 89).
|
||
func TestArm64SimdTwoOperand(t *testing.T) {
|
||
got := arm64Words(t, "\tVADD V7, V8\n\tVSUB V7, V8\n\tVADD V1, V2\n\tVADD V0.B16, V1.B16, V2.B16\n")
|
||
want := []uint32{
|
||
0x5ee78508, // VADD V7, V8
|
||
0x7ee78508, // VSUB V7, V8
|
||
0x5ee18442, // VADD V1, V2
|
||
0x4e208422, // VADD arranged: the ordinary three-register path
|
||
0xd65f03c0,
|
||
}
|
||
if len(got) != len(want) {
|
||
t.Fatalf("word count = %d, want %d", len(got), len(want))
|
||
}
|
||
for i := range want {
|
||
if got[i] != want[i] {
|
||
t.Errorf("word %d = %08x, want %08x", i, got[i], want[i])
|
||
}
|
||
}
|
||
}
|
||
|
||
// TestArm64TruncMove pins the truncating register moves against
|
||
// `go tool asm -S` output (Go 1.27, arm64): the signed forms lower to SXTB,
|
||
// SXTH and SXTW (SBFM), the unsigned byte and halfword forms to UXTB and
|
||
// UXTH (UBFM), MOVWU to a W ORR, and a narrow move out of the zero register
|
||
// drops to the W ORR too (asm7.go case 45).
|
||
func TestArm64TruncMove(t *testing.T) {
|
||
got := arm64Words(t, "\tMOVB R3, R4\n\tMOVH R5, R6\n\tMOVW R9, R10\n"+
|
||
"\tMOVBU R3, R4\n\tMOVHU R3, R4\n\tMOVWU R3, R4\n\tMOVD R3, R4\n"+
|
||
"\tMOVD ZR, R4\n\tMOVB ZR, R4\n\tMOVWU ZR, R5\n")
|
||
want := []uint32{
|
||
0x93401c64, // MOVB = SXTB
|
||
0x93403ca6, // MOVH = SXTH
|
||
0x93407d2a, // MOVW = SXTW
|
||
0xd3401c64, // MOVBU = UXTB
|
||
0xd3403c64, // MOVHU = UXTH
|
||
0x2a0303e4, // MOVWU = ORR W
|
||
0xaa0303e4, // MOVD = ORR X
|
||
0xaa1f03e4, // MOVD ZR, R4 keeps the X form
|
||
0x2a1f03e4, // MOVB ZR, R4 drops to the W form
|
||
0x2a1f03e5, // MOVWU ZR, R5
|
||
0xd65f03c0,
|
||
}
|
||
if len(got) != len(want) {
|
||
t.Fatalf("word count = %d, want %d", len(got), len(want))
|
||
}
|
||
for i := range want {
|
||
if got[i] != want[i] {
|
||
t.Errorf("word %d = %08x, want %08x", i, got[i], want[i])
|
||
}
|
||
}
|
||
}
|
||
|
||
// TestArm64SIMDLoadStore pins the structure loads and stores.
|
||
func TestArm64SIMDLoadStore(t *testing.T) {
|
||
got := arm64Words(t, "\tVLD1 (R2), [V21.B16]\n\tVLD1 (R1), [V2.B16, V3.B16]\n\tVLD1 (R29), [V14.D1, V15.D1, V16.D1, V17.D1]\n"+
|
||
"\tVLD1.P 32(R1), [V2.B16, V3.B16]\n\tVST1 [V2.S4, V3.S4, V4.S4, V5.S4], (R14)\n\tVST1.P [V2.B16], (R1)\n"+
|
||
"\tVLD1R (R1), [V9.B8]\n\tVLD4R (R0), [V0.B8, V1.B8, V2.B8, V3.B8]\n")
|
||
want := []uint32{
|
||
0x4c407055, // VLD1 one register
|
||
0x4c40a022, // VLD1 two registers
|
||
0x0c402fae, // VLD1 four registers D1
|
||
0x4cdfa022, // VLD1.P two registers
|
||
0x4c0029c2, // VST1 four registers S4
|
||
0x4c9f7022, // VST1.P one register
|
||
0x0d40c029, // VLD1R
|
||
0x0d60e000, // VLD4R
|
||
0xd65f03c0,
|
||
}
|
||
if len(got) != len(want) {
|
||
t.Fatalf("word count = %d, want %d", len(got), len(want))
|
||
}
|
||
for i := range want {
|
||
if got[i] != want[i] {
|
||
t.Errorf("word %d = %08x, want %08x", i, got[i], want[i])
|
||
}
|
||
}
|
||
}
|
||
|
||
// TestArm64MoviLiteral pins the VMOVS/VMOVD/VMOVQ constant loads: three
|
||
// words each (ADRP, ADD, wide load) plus the pooled literal in the data
|
||
// section.
|
||
func TestArm64MoviLiteral(t *testing.T) {
|
||
src := "#include \"textflag.h\"\n\nTEXT ·f(SB), NOSPLIT, $0-0\n" +
|
||
"\tVMOVS $0x80402010, V11\n\tVMOVD $0x8040201008040201, V20\n" +
|
||
"\tVMOVQ $0x7040201008040201, $0x8040201008040201, V10\n\tRET\n"
|
||
f, errs := parser.Parse("test_arm64.s", src)
|
||
if len(errs) > 0 {
|
||
t.Fatalf("parse: %v", errs)
|
||
}
|
||
img, err := AssembleFileARM64(f)
|
||
if err != nil {
|
||
t.Fatalf("AssembleFileARM64: %v", err)
|
||
}
|
||
if img.Funcs[0].Size != 12*3+4 {
|
||
t.Errorf("func size = %d, want %d", img.Funcs[0].Size, 12*3+4)
|
||
}
|
||
want := []uint32{
|
||
0x9000001b, 0x9100037b, 0xbd40036b, // VMOVS: ADRP, ADD, LDR S
|
||
0x9000001b, 0x9100037b, 0xfd400374, // VMOVD: ADRP, ADD, LDR D
|
||
0x9000001b, 0x9100037b, 0x3dc0036a, // VMOVQ: ADRP, ADD, LDR Q
|
||
0xd65f03c0,
|
||
}
|
||
got := leWords(img.Code)
|
||
if len(got) != len(want) {
|
||
t.Fatalf("word count = %d, want %d", len(got), len(want))
|
||
}
|
||
for i := range want {
|
||
if got[i] != want[i] {
|
||
t.Errorf("word %d = %08x, want %08x", i, got[i], want[i])
|
||
}
|
||
}
|
||
// The literals sit in the data section.
|
||
var found32, found64, found128 bool
|
||
for _, d := range img.DataSyms {
|
||
switch d.Name {
|
||
case "$i32.80402010":
|
||
found32 = d.Size == 4
|
||
case "$i64.8040201008040201":
|
||
found64 = d.Size == 8
|
||
case "$i128.80402010080402017040201008040201":
|
||
found128 = d.Size == 16
|
||
}
|
||
}
|
||
if !found32 || !found64 || !found128 {
|
||
t.Errorf("literals missing: i32=%v i64=%v i128=%v", found32, found64, found128)
|
||
}
|
||
}
|
||
|
||
// TestArm64MOVK pins standalone MOVK with the hw field derived from the
|
||
// chunk position.
|
||
func TestArm64MOVK(t *testing.T) {
|
||
got := arm64Words(t, "\tMOVK $1234, R5\n\tMOVK $305397760, R5\n\tMOVKW $1234, R5\n")
|
||
want := []uint32{
|
||
0xf2809a45, // MOVK hw=0
|
||
0xf2a24685, // MOVK hw=1
|
||
0x72809a45, // MOVKW hw=0
|
||
0xd65f03c0,
|
||
}
|
||
if len(got) != len(want) {
|
||
t.Fatalf("word count = %d, want %d", len(got), len(want))
|
||
}
|
||
for i := range want {
|
||
if got[i] != want[i] {
|
||
t.Errorf("word %d = %08x, want %08x", i, got[i], want[i])
|
||
}
|
||
}
|
||
}
|
||
|
||
// TestArm64MOVKHighLane pins the shifted high-lane immediate the arm64 test
|
||
// kernels write: $(40000<<48) folds to a negative int64, and the toolchain
|
||
// reads the value as an unsigned 64-bit pattern when it picks the lane.
|
||
func TestArm64MOVKHighLane(t *testing.T) {
|
||
got := arm64Words(t, "\tMOVK $(40000<<48), R0\n\tMOVK $0x9c40000000000000, R1\n")
|
||
want := []uint32{
|
||
0xf2f38800, // MOVK $(40000<<48), R0 (go tool asm: f2f38800)
|
||
0xf2f38801, // MOVK hw=3
|
||
0xd65f03c0,
|
||
}
|
||
if len(got) != len(want) {
|
||
t.Fatalf("word count = %d, want %d", len(got), len(want))
|
||
}
|
||
for i := range want {
|
||
if got[i] != want[i] {
|
||
t.Errorf("word %d = %08x, want %08x", i, got[i], want[i])
|
||
}
|
||
}
|
||
}
|
||
|
||
// TestArm64MoveWideZeroImmediate pins the toolchain's rejection of a zero
|
||
// immediate in the move-wide family (optab case 33: "zero shifts cannot be
|
||
// handled"): every lane is zero, so no hw field can carry it.
|
||
func TestArm64MoveWideZeroImmediate(t *testing.T) {
|
||
for _, mnem := range []string{"MOVK", "MOVZ", "MOVN"} {
|
||
f, errs := parser.Parse("test_arm64.s", "#include \"textflag.h\"\n\nTEXT ·f(SB), NOSPLIT, $0-0\n\t"+mnem+" $0, R0\n\tRET\n")
|
||
if len(errs) > 0 {
|
||
t.Fatalf("%s: parse: %v", mnem, errs)
|
||
}
|
||
if _, err := AssembleFileARM64(f); err == nil {
|
||
t.Errorf("%s $0: expected error, got nil", mnem)
|
||
}
|
||
}
|
||
}
|
||
|
||
// TestArm64LoadImm64 tests 64-bit immediate loading.
|
||
func TestArm64LoadImm64(t *testing.T) {
|
||
src := `#include "textflag.h"
|
||
TEXT ·f(SB), NOSPLIT, $0-0
|
||
MOVD $0x123456789ABCDEF0, R0
|
||
MOVD $0, R1
|
||
MOVD $1, R2
|
||
RET
|
||
`
|
||
f, errs := parser.Parse("test_arm64.s", src)
|
||
if len(errs) > 0 {
|
||
t.Fatalf("parse: %v", errs)
|
||
}
|
||
img, err := AssembleFileARM64(f)
|
||
if err != nil {
|
||
t.Fatalf("AssembleFileARM64: %v", err)
|
||
}
|
||
// $0x123456789ABCDEF0 needs 4 MOVZ/MOVK instructions (16 bytes)
|
||
// $0 is 1 instruction (4 bytes)
|
||
// $1 is 1 bitmask instruction (4 bytes)
|
||
// RET is 1 instruction (4 bytes)
|
||
if img.Funcs[0].Size != 28 {
|
||
t.Errorf("size: got %d, want 28", img.Funcs[0].Size)
|
||
}
|
||
}
|
||
|
||
// TestArm64BranchCond tests conditional branch encoding.
|
||
func TestArm64BranchCond(t *testing.T) {
|
||
src := `#include "textflag.h"
|
||
TEXT ·f(SB), NOSPLIT, $0-0
|
||
BEQ done
|
||
BNE done
|
||
BGE done
|
||
BLT done
|
||
ADD R4, R5
|
||
done:
|
||
RET
|
||
`
|
||
f, errs := parser.Parse("test_arm64.s", src)
|
||
if len(errs) > 0 {
|
||
t.Fatalf("parse: %v", errs)
|
||
}
|
||
img, err := AssembleFileARM64(f)
|
||
if err != nil {
|
||
t.Fatalf("AssembleFileARM64: %v", err)
|
||
}
|
||
// 4 branches + 1 ADD + 1 RET = 24 bytes
|
||
if img.Funcs[0].Size != 24 {
|
||
t.Errorf("size: got %d, want 24", img.Funcs[0].Size)
|
||
}
|
||
}
|
||
|
||
// TestArm64Errors tests error paths.
|
||
func TestArm64Errors(t *testing.T) {
|
||
tests := []struct {
|
||
name string
|
||
src string
|
||
}{
|
||
{"bad mnemonic", "TEXT ·f(SB), NOSPLIT, $0-0\n\tINVALID\tR4\n\tRET\n"},
|
||
{"bad label", "TEXT ·f(SB), NOSPLIT, $0-0\n\tB\tnosuch\n\tRET\n"},
|
||
{"bad register", "TEXT ·f(SB), NOSPLIT, $0-0\n\tADD\tR99, R0\n\tRET\n"},
|
||
}
|
||
for _, tt := range tests {
|
||
t.Run(tt.name, func(t *testing.T) {
|
||
f, errs := parser.Parse("test_arm64.s", tt.src)
|
||
if len(errs) > 0 {
|
||
return
|
||
}
|
||
_, err := AssembleFileARM64(f)
|
||
if err == nil {
|
||
t.Error("expected error, got nil")
|
||
}
|
||
})
|
||
}
|
||
}
|
||
|
||
// leWord reads a little-endian uint32 from b.
|
||
func leWord(b []byte) uint32 {
|
||
return uint32(b[0]) | uint32(b[1])<<8 | uint32(b[2])<<16 | uint32(b[3])<<24
|
||
}
|
||
|
||
// leWords reads all little-endian uint32s from b.
|
||
func leWords(b []byte) []uint32 {
|
||
n := len(b) / 4
|
||
w := make([]uint32, n)
|
||
for i := range w {
|
||
w[i] = leWord(b[i*4:])
|
||
}
|
||
return w
|
||
}
|
||
|
||
// TestArm64IndirectBranch pins the indirect branch forms in a leaf function:
|
||
// JMP (Rn) lowers to BR Rn, matching the toolchain's spelling, and the raw
|
||
// BR/BLR mnemonics encode directly (a gasm superset the toolchain's front
|
||
// end does not accept). CALL (Rn) shares the BLR path and its non-leaf
|
||
// prologue parity is covered by the ground-truth kernel.
|
||
func TestArm64IndirectBranch(t *testing.T) {
|
||
src := `#include "textflag.h"
|
||
|
||
TEXT ·f(SB), NOSPLIT, $0-0
|
||
JMP (R0)
|
||
BR R5
|
||
BLR R6
|
||
RET
|
||
`
|
||
f, errs := parser.Parse("test_arm64.s", src)
|
||
if len(errs) > 0 {
|
||
t.Fatalf("parse: %v", errs)
|
||
}
|
||
img, err := AssembleFileARM64(f)
|
||
if err != nil {
|
||
t.Fatalf("AssembleFileARM64: %v", err)
|
||
}
|
||
want := []uint32{
|
||
0xd61f0000, // BR R0
|
||
0xd61f00a0, // BR R5
|
||
0xd63f00c0, // BLR R6
|
||
0xd65f03c0, // RET (BR LR)
|
||
}
|
||
got := leWords(img.Code)
|
||
if len(got) != len(want) {
|
||
t.Fatalf("word count = %d, want %d", len(got), len(want))
|
||
}
|
||
for i := range want {
|
||
if got[i] != want[i] {
|
||
t.Errorf("word %d = %08x, want %08x", i, got[i], want[i])
|
||
}
|
||
}
|
||
}
|
||
|
||
// arm64Words assembles a single NOSPLIT leaf body and returns its words.
|
||
func arm64Words(t *testing.T, body string) []uint32 {
|
||
t.Helper()
|
||
f, errs := parser.Parse("test_arm64.s", "#include \"textflag.h\"\n\nTEXT ·f(SB), NOSPLIT, $0-0\n"+body+"\tRET\n")
|
||
if len(errs) > 0 {
|
||
t.Fatalf("parse: %v", errs)
|
||
}
|
||
img, err := AssembleFileARM64(f)
|
||
if err != nil {
|
||
t.Fatalf("AssembleFileARM64: %v", err)
|
||
}
|
||
return leWords(img.Code)
|
||
}
|
||
|
||
// TestArm64ShiftEncodings pins the shift words against `go tool asm -S`
|
||
// output (Go 1.27, arm64): immediate forms alias SBFM/UBFM with ROR as EXTR,
|
||
// register forms are the two-source LSLV/LSRV/ASRV/RORV.
|
||
func TestArm64ShiftEncodings(t *testing.T) {
|
||
got := arm64Words(t, "\tLSL $4, R0, R1\n\tLSR $8, R0, R2\n\tASR $4, R0, R3\n\tROR $12, R0, R4\n"+
|
||
"\tLSLW $4, R0, R5\n\tLSRW $8, R0, R6\n\tASRW $4, R0, R7\n\tRORW $12, R0, R8\n")
|
||
want := []uint32{
|
||
0xd37cec01, // LSL $4 = UBFM X1, X0, #60, #59
|
||
0xd348fc02, // LSR $8 = UBFM X2, X0, #8, #63
|
||
0x9344fc03, // ASR $4 = SBFM X3, X0, #4, #63
|
||
0x93c03004, // ROR $12 = EXTR X4, X0, X0, #12
|
||
0x531c6c05, // LSLW $4 = UBFM W5, W0, #28, #27
|
||
0x53087c06, // LSRW $8 = UBFM W6, W0, #8, #31
|
||
0x13047c07, // ASRW $4 = SBFM W7, W0, #4, #31
|
||
0x13803008, // RORW $12 = EXTR W8, W0, W0, #12
|
||
0xd65f03c0, // RET
|
||
}
|
||
if len(got) != len(want) {
|
||
t.Fatalf("word count = %d, want %d", len(got), len(want))
|
||
}
|
||
for i := range want {
|
||
if got[i] != want[i] {
|
||
t.Errorf("imm shift word %d = %08x, want %08x", i, got[i], want[i])
|
||
}
|
||
}
|
||
|
||
got = arm64Words(t, "\tLSL R9, R0, R10\n\tLSR R9, R0, R11\n\tASR R9, R0, R12\n\tROR R9, R0, R13\n"+
|
||
"\tLSLW R9, R0, R14\n\tLSRW R9, R0, R15\n\tASRW R9, R0, R16\n\tRORW R9, R0, R17\n")
|
||
want = []uint32{
|
||
0x9ac9200a, // LSLV X10, X0, X9
|
||
0x9ac9240b, // LSRV X11, X0, X9
|
||
0x9ac9280c, // ASRV X12, X0, X9
|
||
0x9ac92c0d, // RORV X13, X0, X9
|
||
0x1ac9200e, // LSLV W14, W0, W9
|
||
0x1ac9240f, // LSRV W15, W0, W9
|
||
0x1ac92810, // ASRV W16, W0, W9
|
||
0x1ac92c11, // RORV W17, W0, W9
|
||
0xd65f03c0, // RET
|
||
}
|
||
for i := range want {
|
||
if got[i] != want[i] {
|
||
t.Errorf("reg shift word %d = %08x, want %08x", i, got[i], want[i])
|
||
}
|
||
}
|
||
|
||
// Two-operand spellings fold to Rn = Rd.
|
||
got = arm64Words(t, "\tLSL $4, R1\n\tLSR R9, R1\n\tASR $4, R1\n\tROR R9, R1\n\tLSLW $4, R1\n\tRORW R9, R1\n")
|
||
want = []uint32{
|
||
0xd37cec21, // LSL $4, R1 = UBFM X1, X1, #60, #59
|
||
0x9ac92421, // LSRV X1, X1, X9
|
||
0x9344fc21, // ASR $4, R1 = SBFM X1, X1, #4, #63
|
||
0x9ac92c21, // RORV X1, X1, X9
|
||
0x531c6c21, // LSLW $4, R1 = UBFM W1, W1, #28, #27
|
||
0x1ac92c21, // RORV W1, W1, W9
|
||
0xd65f03c0, // RET
|
||
}
|
||
for i := range want {
|
||
if got[i] != want[i] {
|
||
t.Errorf("2op shift word %d = %08x, want %08x", i, got[i], want[i])
|
||
}
|
||
}
|
||
}
|
||
|
||
// TestArm64ShiftRangeErrors: the toolchain reports "illegal bit number" for
|
||
// shift amounts at or above the operand width.
|
||
func TestArm64ShiftRangeErrors(t *testing.T) {
|
||
for _, src := range []string{
|
||
"\tLSL $64, R0, R1\n",
|
||
"\tLSRW $32, R0, R1\n",
|
||
"\tRORW $32, R0, R1\n",
|
||
"\tASR $-1, R0, R1\n",
|
||
} {
|
||
f, errs := parser.Parse("test_arm64.s", "#include \"textflag.h\"\n\nTEXT ·f(SB), NOSPLIT, $0-0\n"+src+"\tRET\n")
|
||
if len(errs) > 0 {
|
||
t.Fatalf("parse: %v", errs)
|
||
}
|
||
if _, err := AssembleFileARM64(f); err == nil {
|
||
t.Errorf("%s: expected an error, got none", src)
|
||
}
|
||
}
|
||
}
|
||
|
||
// TestArm64DivEncodings pins SDIV/UDIV in both widths: the 2-source opcode
|
||
// field (bits 15:10 of the 0xd6<<21 fixed field) is UDIV=0b0010, SDIV=0b0011.
|
||
func TestArm64DivEncodings(t *testing.T) {
|
||
got := arm64Words(t, "\tSDIV R1, R2, R3\n\tUDIV R1, R2, R3\n\tSDIVW R1, R2, R3\n\tUDIVW R1, R2, R3\n")
|
||
want := []uint32{
|
||
0x9ac10c43, // SDIV X3, X2, X1
|
||
0x9ac10843, // UDIV X3, X2, X1
|
||
0x1ac10c43, // SDIV W3, W2, W1
|
||
0x1ac10843, // UDIV W3, W2, W1
|
||
0xd65f03c0, // RET
|
||
}
|
||
if len(got) != len(want) {
|
||
t.Fatalf("word count = %d, want %d", len(got), len(want))
|
||
}
|
||
for i := range want {
|
||
if got[i] != want[i] {
|
||
t.Errorf("div word %d = %08x, want %08x", i, got[i], want[i])
|
||
}
|
||
}
|
||
}
|
||
|
||
// TestArm64MAddSub pins the four-operand MADD/MSUB words (Rm, Ra, Rn, Rd,
|
||
// with Ra in bits 14:10) and rejects the shorter spellings the toolchain
|
||
// also rejects.
|
||
func TestArm64MAddSub(t *testing.T) {
|
||
got := arm64Words(t, "\tMADD R1, R2, R3, R4\n\tMSUB R1, R2, R3, R4\n\tMADDW R1, R2, R3, R5\n\tMSUBW R1, R2, R3, R5\n")
|
||
want := []uint32{
|
||
0x9b010864, // MADD X4, X3, X1, X2 (Rm=1, Ra=2, Rn=3)
|
||
0x9b018864, // MSUB X4, X3, X1, X2
|
||
0x1b010865, // MADD W5, W3, W1, W2
|
||
0x1b018865, // MSUB W5, W3, W1, W2
|
||
0xd65f03c0, // RET
|
||
}
|
||
if len(got) != len(want) {
|
||
t.Fatalf("word count = %d, want %d", len(got), len(want))
|
||
}
|
||
for i := range want {
|
||
if got[i] != want[i] {
|
||
t.Errorf("madd word %d = %08x, want %08x", i, got[i], want[i])
|
||
}
|
||
}
|
||
|
||
// The accumulate operand is mandatory: 2- and 3-operand forms error
|
||
// rather than silently reading R0 or ZR as the accumulator.
|
||
for _, body := range []string{
|
||
"\tMADD R1, R2\n",
|
||
"\tMADD R1, R2, R3\n",
|
||
"\tMSUBW R1, R2, R3\n",
|
||
} {
|
||
f, errs := parser.Parse("test_arm64.s", "#include \"textflag.h\"\n\nTEXT ·f(SB), NOSPLIT, $0-0\n"+body+"\tRET\n")
|
||
if len(errs) > 0 {
|
||
t.Fatalf("parse: %v", errs)
|
||
}
|
||
if _, err := AssembleFileARM64(f); err == nil {
|
||
t.Errorf("%s: expected an error, got none", body)
|
||
}
|
||
}
|
||
}
|
||
|
||
// TestArm64MovImmWidth pins the immediate classifications whose size pass
|
||
// once disagreed with the encoder: negative and 0xFFFFFFFF W values go
|
||
// through MOVN after 32-bit truncation, and 3- to 4-chunk constants expand
|
||
// to one word per non-zero chunk.
|
||
func TestArm64MovImmWidth(t *testing.T) {
|
||
got := arm64Words(t, "\tMOVW $-1, R0\n\tMOVW $0xFFFFFFFF, R3\n")
|
||
want := []uint32{
|
||
0x12800000, // MOVN W0, #0
|
||
0x12800003, // MOVN W3, #0
|
||
0xd65f03c0, // RET
|
||
}
|
||
for i := range want {
|
||
if got[i] != want[i] {
|
||
t.Errorf("movw word %d = %08x, want %08x", i, got[i], want[i])
|
||
}
|
||
}
|
||
|
||
for _, tt := range []struct {
|
||
body string
|
||
words int
|
||
}{
|
||
{"\tMOVD $0x0001000200030000, R2\n", 3}, // three chunks
|
||
{"\tMOVD $0x0001000200030004, R1\n", 4}, // four chunks
|
||
{"\tMOVW $-1, R0\n", 1}, // MOVN after truncation
|
||
} {
|
||
if got := arm64Words(t, tt.body); len(got) != tt.words+1 {
|
||
t.Errorf("%s: %d words, want %d (including RET)", tt.body, len(got), tt.words+1)
|
||
}
|
||
}
|
||
}
|
||
|
||
// TestArm64ExclOffsetErrors: exclusive and atomic encodings carry no
|
||
// immediate field, so a non-zero offset is rejected the way the toolchain
|
||
// reports "illegal combination" for it, never silently dropped.
|
||
func TestArm64ExclOffsetErrors(t *testing.T) {
|
||
for _, body := range []string{
|
||
"\tLDXR 8(R1), R2\n",
|
||
"\tLDAXR 8(R1), R2\n",
|
||
"\tSTXR R3, 8(R1), R4\n",
|
||
"\tSTLXR R3, 8(R1), R4\n",
|
||
"\tCASD R3, 8(R1), R4\n",
|
||
"\tLDADDD R3, 8(R1), R4\n",
|
||
} {
|
||
f, errs := parser.Parse("test_arm64.s", "#include \"textflag.h\"\n\nTEXT ·f(SB), NOSPLIT, $0-0\n"+body+"\tRET\n")
|
||
if len(errs) > 0 {
|
||
t.Fatalf("parse: %v", errs)
|
||
}
|
||
if _, err := AssembleFileARM64(f); err == nil {
|
||
t.Errorf("%s: expected an error, got none", body)
|
||
}
|
||
}
|
||
}
|
||
|
||
// TestArm64ExclNoOffset pins the plain (Rn) forms, byte-for-byte against
|
||
// go tool asm. The toolchain parses the FIRST register of a store as the
|
||
// data register and the LAST as the status register (asm7.go case 59), and
|
||
// the pair forms as (Rt1, Rt2) (case 58/59):
|
||
//
|
||
// STXR R3, (R1), R4 → c8047c23 (Rt=3, Rn=1, Rs=4)
|
||
// STXP (R3, R4), (R1), R5 → c8251023 (Rt=3, Rt2=4, Rn=1, Rs=5)
|
||
// LDXP (R1), (R3, R4) → c87f1023 (Rn=1, Rt=3, Rt2=4)
|
||
func TestArm64ExclNoOffset(t *testing.T) {
|
||
got := arm64Words(t, "\tLDXR (R1), R2\n\tSTXR R3, (R1), R4\n"+
|
||
"\tSTXP (R3, R4), (R1), R5\n\tSTXPW (R3, R4), (R1), R5\n"+
|
||
"\tLDXP (R1), (R3, R4)\n\tLDXPW (R1), (R3, R4)\n"+
|
||
"\tSTXR R3, (RSP), R4\n\tLDXR (RSP), R2\n")
|
||
want := []uint32{
|
||
0xc85f7c22, // LDXR X2, [X1]
|
||
0xc8047c23, // STXR W3, [X1], W4 with Rt = R3, Rs = R4
|
||
0xc8251023, // STXP (R3, R4), [X1], R5
|
||
0x88251023, // STXPW (R3, R4), [X1], R5
|
||
0xc87f1023, // LDXP [X1], (R3, R4)
|
||
0x887f1023, // LDXPW [X1], (R3, R4)
|
||
0xc8047fe3, // STXR R3, [SP], R4
|
||
0xc85f7fe2, // LDXR [SP], R2
|
||
0xd65f03c0, // RET
|
||
}
|
||
for i := range want {
|
||
if got[i] != want[i] {
|
||
t.Errorf("excl word %d = %08x, want %08x", i, got[i], want[i])
|
||
}
|
||
}
|
||
}
|
||
|
||
// TestArm64AddSubImmWide pins the wide-immediate classification the toolchain
|
||
// applies to the ADD/SUB family (asm7.go cases 48, 62, 13): the ADDCON2 split
|
||
// into two imm12 instructions for plain ADD/SUB, the bitmask ORR into REGTMP,
|
||
// and the MOVZ/MOVN/MOVK materialisations followed by the register form.
|
||
// Comparisons never split, and the W forms classify the 32-bit value. Every
|
||
// word is go tool asm's own for the same source.
|
||
func TestArm64AddSubImmWide(t *testing.T) {
|
||
got := arm64Words(t, strings.Join([]string{
|
||
"\tADD $0xaaaaaa, R2, R3",
|
||
"\tSUB $0xaaaaaa, R2",
|
||
"\tADD $0x186a0, R2, R5",
|
||
"\tADD $0x1ffe00, R2, R3",
|
||
"\tADD $0x3fffffffc000, R5",
|
||
"\tADD $-100000, R2, R3",
|
||
"\tADD $-2048, R2, R3",
|
||
"\tCMP $0xaaaaaa, R2",
|
||
"\tCMP $0xffffffffffa0, R3",
|
||
"\tCMPW $27745, R2",
|
||
"\tCMPW $0x60060, R2",
|
||
"\tADDS $0xaaaaaa, R2, R3",
|
||
"\tADD $0x12345678, R2, R3",
|
||
"\tADDW $0x60060, R2",
|
||
"\tSUB $0xe7791f700, R3, R1",
|
||
"\tADDW $0x12345678, R2, R3",
|
||
"\tCMN $0x1000000, R2",
|
||
}, "\n")+"\n")
|
||
want := []uint32{
|
||
0x912aa843, 0x916aa863, // ADD $0xaaaaaa, R2, R3: ADDCON2 split
|
||
0xd12aa842, 0xd16aa842, // SUB $0xaaaaaa, R2: split with Rd = Rn
|
||
0x911a8045, 0x914060a5, // ADD $0x186a0, R2, R5: split
|
||
0xb2772ffb, 0x8b1b0043, // ADD $0x1ffe00: bitmask beats the split
|
||
0xb2727ffb, 0x8b1b00a5, // ADD $0x3fffffffc000: bitmask into REGTMP
|
||
0x9290d3fb, 0xf2bfffdb, 0x8b1b0043, // ADD $-100000: MOVN + MOVK
|
||
0x9280fffb, 0x8b1b0043, // ADD $-2048: single MOVN + ADD
|
||
0xd295555b, 0xf2a0155b, 0xeb1b005f, // CMP: never split, MOVZ + MOVK
|
||
0x92800bfb, 0xf2e0001b, 0xeb1b007f, // CMP $0xffffffffffa0: MOVN + fixup
|
||
0x528d8c3b, 0x6b1b005f, // CMPW $27745: W movcon, single MOVZW
|
||
0x52800c1b, 0x72a000db, 0x6b1b005f, // CMPW $0x60060: S form skips the split
|
||
0xd295555b, 0xf2a0155b, 0xab1b0043, // ADDS $0xaaaaaa: MOVZ + MOVK + ADDS
|
||
0xd28acf1b, 0xf2a2469b, 0x8b1b0043, // ADD $0x12345678: MOVZ + MOVK
|
||
0x11018042, 0x11418042, // ADDW $0x60060: W split
|
||
0xd29ee01b, 0xf2aef23b, 0xf2c001db, 0xcb1b0061, // SUB $0xe7791f700
|
||
0x528acf1b, 0x72a2469b, 0x0b1b0043, // ADDW $0x12345678: MOVZW + MOVKW
|
||
0xd2a0201b, 0xab1b005f, // CMN $0x1000000: single MOVZ + CMN
|
||
0xd65f03c0, // RET
|
||
}
|
||
if len(got) != len(want) {
|
||
t.Fatalf("word count = %d, want %d", len(got), len(want))
|
||
}
|
||
for i := range want {
|
||
if got[i] != want[i] {
|
||
t.Errorf("wide word %d = %08x, want %08x", i, got[i], want[i])
|
||
}
|
||
}
|
||
}
|
||
|
||
// TestArm64CarryImmWide pins the carry family's $0 spellings in two and
|
||
// three operands, the ROR shift on the logical group (and its rejection for
|
||
// the arithmetic forms), the NGC/MNEG zero-register aliases and the vector
|
||
// alias with an element selector. Words are go tool asm's own.
|
||
func TestArm64CarryShiftAlias(t *testing.T) {
|
||
got := arm64Words(t, "\tADC $0, R20\n\tADC $0, R20, R4\n\tSBCS $0, R4, R12\n"+
|
||
"\tSBCS R15, R4, R12\n\tANDW R9@>7, R19, R26\n\tAND R1@>33, R2, R3\n"+
|
||
"\tNEGSW R23<<1, R30\n\tNGC R2, R7\n\tMNEG R14, R27, R23\n")
|
||
want := []uint32{
|
||
0x9a1f0294, // ADC ZR, R20, R20
|
||
0x9a1f0284, // ADC ZR, R20, R4
|
||
0xfa1f008c, // SBCS ZR, R4, R12
|
||
0xfa0f008c, // SBCS R15, R4, R12
|
||
0x0ac91e7a, // ANDW R9 ROR 7, R19, R26
|
||
0x8ac18443, // AND R1 ROR 33, R2, R3
|
||
0x6b1707fe, // SUBSW ZR, R30, R23 LSL 1
|
||
0xda0203e7, // SBC ZR, R7, R2
|
||
0x9b0eff77, // MSUB ZR, R27, R14, R23
|
||
0xd65f03c0, // RET
|
||
}
|
||
if len(got) != len(want) {
|
||
t.Fatalf("word count = %d, want %d", len(got), len(want))
|
||
}
|
||
for i := range want {
|
||
if got[i] != want[i] {
|
||
t.Errorf("carry word %d = %08x, want %08x", i, got[i], want[i])
|
||
}
|
||
}
|
||
|
||
// ROR on an arithmetic form is unallocated: the toolchain reports an
|
||
// unsupported shift operator.
|
||
f, errs := parser.Parse("test_arm64.s", "#include \"textflag.h\"\n\nTEXT ·f(SB), NOSPLIT, $0-0\n\tADD R1@>33, R2, R3\n\tRET\n")
|
||
if len(errs) > 0 {
|
||
t.Fatalf("parse: %v", errs)
|
||
}
|
||
if _, err := AssembleFileARM64(f); err == nil {
|
||
t.Error("ADD R1@>33: expected an error, got none")
|
||
}
|
||
}
|
||
|
||
// TestArm64VecAliasElement pins the register-alias rewrite inside a vector
|
||
// operand with an element selector and inside a split register list: the
|
||
// aliases resolve textually where the parser carries the selector apart from
|
||
// the name. Words are go tool asm's own.
|
||
func TestArm64VecAliasElement(t *testing.T) {
|
||
src := `#include "textflag.h"
|
||
|
||
#define POLY V15
|
||
#define ACC0 V8
|
||
#define ACC1 V9
|
||
|
||
TEXT ·f(SB), NOSPLIT, $0-0
|
||
VMOV R1, POLY.D[0]
|
||
VEOR POLY.B16, POLY.B16, POLY.B16
|
||
VLD1 (R0), [ACC0.B16]
|
||
VLD1.P (R0), [ACC0.B16, ACC1.B16]
|
||
VST1.P [ACC0.B16, ACC1.B16], 32(R1)
|
||
RET
|
||
`
|
||
f, errs := parser.Parse("test_arm64.s", src)
|
||
if len(errs) > 0 {
|
||
t.Fatalf("parse: %v", errs)
|
||
}
|
||
img, err := AssembleFileARM64(f)
|
||
if err != nil {
|
||
t.Fatalf("AssembleFileARM64: %v", err)
|
||
}
|
||
got := leWords(img.Code)
|
||
want := []uint32{
|
||
0x4e081c2f, // INS V15.D[0], R1
|
||
0x6e2f1def, // VEOR V15.B16, V15.B16, V15.B16
|
||
0x4c407008, // VLD1 (R0), [V8.B16]
|
||
0x4cdfa008, // VLD1.P (R0), [V8.B16, V9.B16]
|
||
0x4c9fa028, // VST1.P [V8.B16, V9.B16], 32(R1)
|
||
0xd65f03c0, // RET
|
||
}
|
||
if len(got) != len(want) {
|
||
t.Fatalf("word count = %d, want %d", len(got), len(want))
|
||
}
|
||
for i := range want {
|
||
if got[i] != want[i] {
|
||
t.Errorf("vecalias word %d = %08x, want %08x", i, got[i], want[i])
|
||
}
|
||
}
|
||
}
|
||
|
||
// TestArm64AddSubImmBeyond32 pins the materialisation the toolchain applies
|
||
// once the value leaves every imm12 form: a constant sequence into REGTMP
|
||
// (R27) followed by the register form. SUB $-0x100000000 is a bitmask
|
||
// immediate, so it rides the ORR form; the others take MOVZ. Words are go
|
||
// tool asm's own.
|
||
func TestArm64AddSubImmBeyond32(t *testing.T) {
|
||
got := arm64Words(t, "\tADD $0x100000000, R0, R1\n\tSUB $-0x100000000, R0, R1\n\tCMP $0x100000000, R0\n")
|
||
want := []uint32{
|
||
0xd2c0003b, // MOVZ $(1<<32>>16), R27 (hw=2)
|
||
0x8b1b0001, // ADD R27, R0, R1
|
||
0xb2607ffb, // ORR $-4294967296, ZR, R27 (bitmask)
|
||
0xcb1b0001, // SUB R27, R0, R1
|
||
0xd2c0003b, // MOVZ $(1<<32>>16), R27 (hw=2)
|
||
0xeb1b001f, // CMP R27, R0
|
||
0xd65f03c0, // RET
|
||
}
|
||
if len(got) != len(want) {
|
||
t.Fatalf("word count = %d, want %d", len(got), len(want))
|
||
}
|
||
for i := range want {
|
||
if got[i] != want[i] {
|
||
t.Errorf("word %d = %08x, want %08x", i, got[i], want[i])
|
||
}
|
||
}
|
||
}
|
||
|
||
// TestArm64LargeRegisterOffset pins the large-offset path for a register
|
||
// base: the ADD offsets from the operand's own base, not from SP, matching
|
||
// the toolchain's `ADD $(256<<12), R2, R27; MOVD (R27), R3`.
|
||
func TestArm64LargeRegisterOffset(t *testing.T) {
|
||
got := arm64Words(t, "\tMOVD 0x100000(R2), R3\n\tMOVD R3, 0x100000(R2)\n")
|
||
want := []uint32{
|
||
0x9144005b, // ADD $(256<<12), R2, R27
|
||
0xf9400363, // MOVD (R27), R3
|
||
0x9144005b, // ADD $(256<<12), R2, R27
|
||
0xf9000363, // MOVD R3, (R27)
|
||
0xd65f03c0, // RET
|
||
}
|
||
if len(got) != len(want) {
|
||
t.Fatalf("word count = %d, want %d", len(got), len(want))
|
||
}
|
||
for i := range want {
|
||
if got[i] != want[i] {
|
||
t.Errorf("large offset word %d = %08x, want %08x", i, got[i], want[i])
|
||
}
|
||
}
|
||
}
|
||
|
||
// TestArm64LargeFrameSpadj checks the stack-adjustment boundaries of a frame
|
||
// whose autosize must be materialised into REGTMP: $5000 rounds the autosize
|
||
// to 5024, so the prologue is [MOVD $5024, R27][SUB R27, RSP, R20][STP][ADD
|
||
// R20, SP][SUB $8] and SP moves only at its fourth word, while the RET's
|
||
// epilogue is [LDP][MOVD $5024, R27][ADD R27, RSP, RSP] before the final
|
||
// RET. These PCs feed the DWARF CFA rules and the goobj stack maps.
|
||
func TestArm64LargeFrameSpadj(t *testing.T) {
|
||
f, errs := parser.Parse("frame_arm64.s", "#include \"textflag.h\"\n\nTEXT ·framed(SB), $5000-0\n\tCALL ·other(SB)\n\tRET\n\nTEXT ·other(SB), NOSPLIT, $0\n\tRET\n")
|
||
if len(errs) > 0 {
|
||
t.Fatalf("parse: %v", errs)
|
||
}
|
||
img, err := AssembleFileARM64(f)
|
||
if err != nil {
|
||
t.Fatalf("AssembleFileARM64: %v", err)
|
||
}
|
||
fn := img.Funcs[0]
|
||
// autosize 5024: class-2 guard of 6 words (24 bytes), a 5-word prologue
|
||
// whose ADD R20, SP sits at byte 8 inside it, a one-instruction body,
|
||
// then a 3-word epilogue before the final RET.
|
||
wantSpadj := []SpadjStep{{PC: 24 + 12, Value: 5024}, {PC: 24 + 20 + 4 + 12, Value: 0}}
|
||
if len(fn.Spadj) != len(wantSpadj) {
|
||
t.Fatalf("spadj = %v, want %v", fn.Spadj, wantSpadj)
|
||
}
|
||
for i := range wantSpadj {
|
||
if fn.Spadj[i] != wantSpadj[i] {
|
||
t.Errorf("spadj[%d] = %v, want %v", i, fn.Spadj[i], wantSpadj[i])
|
||
}
|
||
}
|
||
// The words those PCs point between: the prologue's ADD R20, SP at byte
|
||
// 36, and the epilogue's materialised ADD R27, RSP, RSP right before the
|
||
// final RET at byte 60.
|
||
words := leWords(img.Code[fn.Offset : fn.Offset+fn.Size])
|
||
if got := words[(24+12)/4]; got != 0x9100029f {
|
||
t.Errorf("prologue word at byte 36 = %08x, want 9100029f (ADD R20, SP)", got)
|
||
}
|
||
if got := words[(24+20+4+8)/4]; got != 0x8b3b63ff {
|
||
t.Errorf("epilogue word at byte 56 = %08x, want 8b3b63ff (ADD R27, RSP, RSP)", got)
|
||
}
|
||
if got := words[(24+20+4+12)/4]; got != 0xd65f03c0 {
|
||
t.Errorf("final RET word at byte 60 = %08x, want d65f03c0", got)
|
||
}
|
||
}
|
||
|
||
// TestArm64SplitFrameSpadj pins the addcon2 band, where neither imm12 form
|
||
// nor a single MOVZ carries the autosize and the toolchain splits the
|
||
// prologue SUB into two imm12 instructions (asm7.go case 48) while the
|
||
// non-leaf RET still materialises the value into REGTMP (obj7.go ARET,
|
||
// issue 73259). $65664 rounds the autosize to 65680 = 144 + 16<<12:
|
||
//
|
||
// [SUB $144, RSP, R20][SUB $(16<<12), R20, R20][STP][MOVD R20, SP][SUB $8]
|
||
// [CALL]
|
||
// [LDP][MOVD $144, R27][MOVK $(1<<16), R27][ADD R27, RSP, RSP][RET]
|
||
//
|
||
// SP moves at the fourth word (byte 12) and returns to zero at the final
|
||
// RET (byte 40); the words are go tool asm's own for the same source.
|
||
func TestArm64SplitFrameSpadj(t *testing.T) {
|
||
f, errs := parser.Parse("frame_arm64.s", "#include \"textflag.h\"\n\nTEXT ·framed(SB), NOSPLIT, $65664-0\n\tCALL ·other(SB)\n\tRET\n\nTEXT ·other(SB), NOSPLIT, $0\n\tRET\n")
|
||
if len(errs) > 0 {
|
||
t.Fatalf("parse: %v", errs)
|
||
}
|
||
img, err := AssembleFileARM64(f)
|
||
if err != nil {
|
||
t.Fatalf("AssembleFileARM64: %v", err)
|
||
}
|
||
fn := img.Funcs[0]
|
||
wantSpadj := []SpadjStep{{PC: 12, Value: 65680}, {PC: 40, Value: 0}}
|
||
if len(fn.Spadj) != len(wantSpadj) {
|
||
t.Fatalf("spadj = %v, want %v", fn.Spadj, wantSpadj)
|
||
}
|
||
for i := range wantSpadj {
|
||
if fn.Spadj[i] != wantSpadj[i] {
|
||
t.Errorf("spadj[%d] = %v, want %v", i, fn.Spadj[i], wantSpadj[i])
|
||
}
|
||
}
|
||
want := []uint32{
|
||
0xd10243f4, // SUB $144, RSP, R20
|
||
0xd1404294, // SUB $(16<<12), R20, R20
|
||
0xa93ffa9d, // STP (R29, R30), -8(R20)
|
||
0x9100029f, // MOVD R20, RSP
|
||
0xd10023fd, // SUB $8, RSP, R29
|
||
0x94000000, // CALL (relocation masked at link time)
|
||
0xa97ffbfd, // LDP -8(RSP), (R29, R30)
|
||
0xd280121b, // MOVD $144, R27
|
||
0xf2a0003b, // MOVK $(1<<16), R27
|
||
0x8b3b63ff, // ADD R27, RSP, RSP
|
||
0xd65f03c0, // RET
|
||
}
|
||
words := leWords(img.Code[fn.Offset : fn.Offset+fn.Size])
|
||
if len(words) != len(want) {
|
||
t.Fatalf("framed = %d words, want %d", len(words), len(want))
|
||
}
|
||
for i, w := range want {
|
||
if words[i] != w {
|
||
t.Errorf("word %d = %08x, want %08x", i, words[i], w)
|
||
}
|
||
}
|
||
}
|