feat(asm): extend arm64 encoder with FP, conditional select, CRC32 and tests
Assisted-by: MiMo V2.5 Pro
This commit is contained in:
@@ -0,0 +1,59 @@
|
|||||||
|
// 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/parser"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestGOObjectAARCH64Structure checks the basic structure of the emitted
|
||||||
|
// AArch64 GOOBJ: the preamble, the magic, the block offsets and the
|
||||||
|
// non-package symbol definitions.
|
||||||
|
func TestGOObjectAARCH64Structure(t *testing.T) {
|
||||||
|
f, errs := parser.Parse("k_arm64.s", `
|
||||||
|
#include "textflag.h"
|
||||||
|
|
||||||
|
TEXT ·add(SB), NOSPLIT, $0-24
|
||||||
|
MOVD a+0(FP), R4
|
||||||
|
MOVD b+8(FP), R5
|
||||||
|
ADD R5, R4, R4
|
||||||
|
MOVD R4, ret+16(FP)
|
||||||
|
RET
|
||||||
|
`)
|
||||||
|
if len(errs) > 0 {
|
||||||
|
t.Fatalf("parse: %v", errs)
|
||||||
|
}
|
||||||
|
img, err := AssembleFileARM64(f)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("AssembleFileARM64: %v", err)
|
||||||
|
}
|
||||||
|
obj, err := img.GOObjectAARCH64("testpkg", "k_arm64.s")
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("GOObjectAARCH64: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check preamble.
|
||||||
|
idx := strings.Index(string(obj), "\n!\n")
|
||||||
|
if idx < 0 {
|
||||||
|
t.Fatal("missing preamble separator")
|
||||||
|
}
|
||||||
|
preamble := string(obj[:idx])
|
||||||
|
if !strings.HasPrefix(preamble, "go object") {
|
||||||
|
t.Errorf("preamble = %q, want 'go object ...'", preamble)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check GOOBJ magic.
|
||||||
|
magicIdx := idx + 3
|
||||||
|
if magicIdx+8 > len(obj) || string(obj[magicIdx:magicIdx+8]) != "\x00go120ld" {
|
||||||
|
t.Error("missing GOOBJ magic")
|
||||||
|
}
|
||||||
|
|
||||||
|
// The object should contain the function's code.
|
||||||
|
if len(img.Code) == 0 {
|
||||||
|
t.Error("no code generated")
|
||||||
|
}
|
||||||
|
}
|
||||||
+302
-9
@@ -218,6 +218,51 @@ func encodeARM64Instr(instr *ast.Instr, pc int, offsets map[string]int, fi arm64
|
|||||||
return encodeARM64DPSR(mnem, enc.op, ops)
|
return encodeARM64DPSR(mnem, enc.op, ops)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// FP 3-operand (Rm, Rn, Rd).
|
||||||
|
if enc, ok := a64InstrTable[mnem]; ok && enc.format == a64FFP3 {
|
||||||
|
return encodeARM64FP3(mnem, enc.op, ops)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FP unary (Rn, Rd).
|
||||||
|
if enc, ok := a64InstrTable[mnem]; ok && enc.format == a64FFPUnary {
|
||||||
|
return encodeARM64FPUnary(mnem, enc.op, ops)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FP 4-operand FMA (Ra, Rm, Rn, Rd).
|
||||||
|
if enc, ok := a64InstrTable[mnem]; ok && enc.format == a64FFP4 {
|
||||||
|
return encodeARM64FP4(mnem, enc.op, ops)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FP compare (Rm, Rn or #0, Rn).
|
||||||
|
if enc, ok := a64InstrTable[mnem]; ok && enc.format == a64FFPCmp {
|
||||||
|
return encodeARM64FPCmp(mnem, enc.op, ops)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FP conditional compare (Rm, Rn, #nzcv, cond).
|
||||||
|
if enc, ok := a64InstrTable[mnem]; ok && enc.format == a64FFPCCmp {
|
||||||
|
return encodeARM64FPCCmp(mnem, enc.op, ops)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FP conditional select (Rm, Rn, Rd, cond).
|
||||||
|
if enc, ok := a64InstrTable[mnem]; ok && enc.format == a64FFPSel {
|
||||||
|
return encodeARM64FPSel(mnem, enc.op, ops)
|
||||||
|
}
|
||||||
|
|
||||||
|
// FP ↔ integer conversion.
|
||||||
|
if enc, ok := a64InstrTable[mnem]; ok && enc.format == a64FFPCvt {
|
||||||
|
return encodeARM64FPCvt(mnem, enc.op, ops)
|
||||||
|
}
|
||||||
|
|
||||||
|
// Conditional select (CSEL, CSINC, CSINV, CSNEG, CSET, CSETM, CINC, CINV, CNEG).
|
||||||
|
if enc, ok := a64InstrTable[mnem]; ok && enc.format == a64FCSEL {
|
||||||
|
return encodeARM64CSEL(mnem, enc.op, ops)
|
||||||
|
}
|
||||||
|
|
||||||
|
// CRC32.
|
||||||
|
if enc, ok := a64InstrTable[mnem]; ok && enc.format == a64FCRC32 {
|
||||||
|
return encodeARM64CRC32(mnem, enc.op, ops)
|
||||||
|
}
|
||||||
|
|
||||||
return nil, fmt.Errorf("unsupported arm64 instruction %q", mnem)
|
return nil, fmt.Errorf("unsupported arm64 instruction %q", mnem)
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -621,7 +666,11 @@ func arm64Bitmask(v uint64, sf int) (N, immr, imms uint32, ok bool) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
// encodeARM64RegMove encodes a register-to-register move as ORR Rd, ZR, Rs.
|
// encodeARM64RegMove encodes a register-to-register move.
|
||||||
|
// Integer → integer: ORR Rd, ZR, Rs.
|
||||||
|
// FP → FP: FMOV Fd, Fn (FP data processing).
|
||||||
|
// FP ↔ GP: FMOV general (FPCVTI encoding).
|
||||||
|
// Go Plan 9 syntax: MOV dst, src (first operand = destination).
|
||||||
func encodeARM64RegMove(mnem string, src, dst *ast.Operand) ([]byte, error) {
|
func encodeARM64RegMove(mnem string, src, dst *ast.Operand) ([]byte, error) {
|
||||||
rs := arm64RegNum(operandRegName(src))
|
rs := arm64RegNum(operandRegName(src))
|
||||||
rd := arm64RegNum(operandRegName(dst))
|
rd := arm64RegNum(operandRegName(dst))
|
||||||
@@ -631,21 +680,37 @@ func encodeARM64RegMove(mnem string, src, dst *ast.Operand) ([]byte, error) {
|
|||||||
sc := arm64RegClassOf(operandRegName(src))
|
sc := arm64RegClassOf(operandRegName(src))
|
||||||
dc := arm64RegClassOf(operandRegName(dst))
|
dc := arm64RegClassOf(operandRegName(dst))
|
||||||
|
|
||||||
// FP → FP: FMOV Rd, Rs
|
// FP → FP: FMOV Fd, Fn (FP data processing unary form).
|
||||||
if sc == arm64ClsFP && dc == arm64ClsFP {
|
if sc == arm64ClsFP && dc == arm64ClsFP {
|
||||||
sf := uint32(1) // 64-bit
|
|
||||||
if mnem == "FMOVS" {
|
|
||||||
sf = 0
|
|
||||||
}
|
|
||||||
// FMOV: 0x1E<<24 | type<<22 | 1<<21 | 0x10<<10 | Rm<<5 | Rd
|
|
||||||
typ := uint32(1) // 64-bit double
|
typ := uint32(1) // 64-bit double
|
||||||
if mnem == "FMOVS" {
|
if mnem == "FMOVS" {
|
||||||
typ = 0 // 32-bit float
|
typ = 0 // 32-bit float
|
||||||
}
|
}
|
||||||
return a64wordLE(sf<<31 | 0x1E<<24 | typ<<22 | 1<<21 | 0x10<<10 | uint32(rs)<<5 | uint32(rd)), nil
|
// FPOP1S encoding: 0x1E204000 | type<<22 | Rn<<5 | Rd
|
||||||
|
return a64wordLE(0x1E<<24 | typ<<22 | 1<<21 | 0x10<<10 | uint32(rs)<<5 | uint32(rd)), nil
|
||||||
}
|
}
|
||||||
|
|
||||||
// Integer → integer: ORR Rd, ZR, Rs
|
// GP ↔ FP: FMOV general (FPCVTI encoding).
|
||||||
|
// Go syntax: FMOV FPdst, GPsrc or FMOV GPdst, FPsrc.
|
||||||
|
// First operand = destination, second = source.
|
||||||
|
if sc == arm64ClsFP && dc == arm64ClsGR {
|
||||||
|
// FP → GP: FMOV Wd/Xd, Sn/Dn. opcode bits[20:16]=6.
|
||||||
|
sf, typ := uint32(0), uint32(0)
|
||||||
|
if mnem == "FMOVD" {
|
||||||
|
sf, typ = 1, 1
|
||||||
|
}
|
||||||
|
return a64wordLE(sf<<31 | 0x1E<<24 | typ<<22 | 1<<21 | 6<<16 | uint32(rs)<<5 | uint32(rd)), nil
|
||||||
|
}
|
||||||
|
if sc == arm64ClsGR && dc == arm64ClsFP {
|
||||||
|
// GP → FP: FMOV Vd, Wn/Xn. opcode bits[20:16]=7.
|
||||||
|
sf, typ := uint32(0), uint32(0)
|
||||||
|
if mnem == "FMOVD" {
|
||||||
|
sf, typ = 1, 1
|
||||||
|
}
|
||||||
|
return a64wordLE(sf<<31 | 0x1E<<24 | typ<<22 | 1<<21 | 7<<16 | uint32(rs)<<5 | uint32(rd)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// Integer → integer: ORR Rd, ZR, Rs.
|
||||||
sf := uint32(1) // 64-bit
|
sf := uint32(1) // 64-bit
|
||||||
if mnem == "MOVW" || mnem == "MOVWU" || mnem == "MOVB" || mnem == "MOVBU" ||
|
if mnem == "MOVW" || mnem == "MOVWU" || mnem == "MOVB" || mnem == "MOVBU" ||
|
||||||
mnem == "MOVH" || mnem == "MOVHU" {
|
mnem == "MOVH" || mnem == "MOVHU" {
|
||||||
@@ -775,6 +840,234 @@ func arm64Label(op *ast.Operand) string {
|
|||||||
return op.Raw
|
return op.Raw
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// ---- FP instruction encoding ----
|
||||||
|
|
||||||
|
// encodeARM64FP3 encodes a FP 3-operand instruction (Rm, Rn, Rd).
|
||||||
|
// FADD, FSUB, FMUL, FDIV, FMAX, FMIN, FNMUL.
|
||||||
|
func encodeARM64FP3(mnem string, baseOp uint32, ops []*ast.Operand) ([]byte, error) {
|
||||||
|
if len(ops) != 3 {
|
||||||
|
return nil, fmt.Errorf("%s expects 3 operands, got %d", mnem, len(ops))
|
||||||
|
}
|
||||||
|
rm := arm64RegNum(operandRegName(ops[0]))
|
||||||
|
rn := arm64RegNum(operandRegName(ops[1]))
|
||||||
|
rd := arm64RegNum(operandRegName(ops[2]))
|
||||||
|
if rm < 0 || rn < 0 || rd < 0 {
|
||||||
|
return nil, fmt.Errorf("invalid register operand in %s", mnem)
|
||||||
|
}
|
||||||
|
return a64wordLE(baseOp | uint32(rm)<<16 | uint32(rn)<<5 | uint32(rd)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// encodeARM64FPUnary encodes a FP unary instruction (Rn, Rd).
|
||||||
|
// FMOV reg-reg, FABS, FNEG, FSQRT, FCVT cross-precision, FRINT*.
|
||||||
|
func encodeARM64FPUnary(mnem string, baseOp uint32, ops []*ast.Operand) ([]byte, error) {
|
||||||
|
if len(ops) != 2 {
|
||||||
|
return nil, fmt.Errorf("%s expects 2 operands, got %d", mnem, len(ops))
|
||||||
|
}
|
||||||
|
rn := arm64RegNum(operandRegName(ops[0]))
|
||||||
|
rd := arm64RegNum(operandRegName(ops[1]))
|
||||||
|
if rn < 0 || rd < 0 {
|
||||||
|
return nil, fmt.Errorf("invalid register operand in %s", mnem)
|
||||||
|
}
|
||||||
|
return a64wordLE(baseOp | uint32(rn)<<5 | uint32(rd)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// encodeARM64FP4 encodes a FP 4-operand FMA instruction (Ra, Rm, Rn, Rd).
|
||||||
|
// FMADD, FMSUB, FNMADD, FNMSUB.
|
||||||
|
func encodeARM64FP4(mnem string, baseOp uint32, ops []*ast.Operand) ([]byte, error) {
|
||||||
|
var ra, rm, rn, rd int
|
||||||
|
switch len(ops) {
|
||||||
|
case 4:
|
||||||
|
ra = arm64RegNum(operandRegName(ops[0]))
|
||||||
|
rm = arm64RegNum(operandRegName(ops[1]))
|
||||||
|
rn = arm64RegNum(operandRegName(ops[2]))
|
||||||
|
rd = arm64RegNum(operandRegName(ops[3]))
|
||||||
|
case 3:
|
||||||
|
// 3-operand form: Fa, Fm, Fd → Fd = Fa ± Fd*Fm (Rn = Rd)
|
||||||
|
ra = arm64RegNum(operandRegName(ops[0]))
|
||||||
|
rm = arm64RegNum(operandRegName(ops[1]))
|
||||||
|
rd = arm64RegNum(operandRegName(ops[2]))
|
||||||
|
rn = rd
|
||||||
|
default:
|
||||||
|
return nil, fmt.Errorf("%s expects 3 or 4 operands, got %d", mnem, len(ops))
|
||||||
|
}
|
||||||
|
if ra < 0 || rm < 0 || rn < 0 || rd < 0 {
|
||||||
|
return nil, fmt.Errorf("invalid register operand in %s", mnem)
|
||||||
|
}
|
||||||
|
return a64wordLE(baseOp | uint32(ra)<<16 | uint32(rm)<<10 | uint32(rn)<<5 | uint32(rd)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// encodeARM64FPCmp encodes a FP compare instruction.
|
||||||
|
// Go assembler syntax: FCMP Fn, Fm (register) or FCMP $0.0, Fn (compare with zero).
|
||||||
|
// ARM64 encoding: Rm in bits[20:16], Rn in bits[9:5].
|
||||||
|
// Go puts first operand → Rm, second → Rn.
|
||||||
|
func encodeARM64FPCmp(mnem string, baseOp uint32, ops []*ast.Operand) ([]byte, error) {
|
||||||
|
if len(ops) != 2 {
|
||||||
|
return nil, fmt.Errorf("%s expects 2 operands, got %d", mnem, len(ops))
|
||||||
|
}
|
||||||
|
// Check if first operand is #0 (compare with zero): FCMP $0.0, Fn.
|
||||||
|
if isImmOperand(ops[0]) && immFromOperand(ops[0]) == 0 {
|
||||||
|
rn := arm64RegNum(operandRegName(ops[1]))
|
||||||
|
if rn < 0 {
|
||||||
|
return nil, fmt.Errorf("invalid register operand in %s", mnem)
|
||||||
|
}
|
||||||
|
// For compare with zero: Rm=0, op2 bit 3 set (|= 8).
|
||||||
|
return a64wordLE((baseOp | 8) | 0<<16 | uint32(rn)<<5), nil
|
||||||
|
}
|
||||||
|
// Register compare: FCMP Fn, Fm.
|
||||||
|
// Go puts first operand in Rm field, second in Rn field.
|
||||||
|
rm := arm64RegNum(operandRegName(ops[0]))
|
||||||
|
rn := arm64RegNum(operandRegName(ops[1]))
|
||||||
|
if rm < 0 || rn < 0 {
|
||||||
|
return nil, fmt.Errorf("invalid register operand in %s", mnem)
|
||||||
|
}
|
||||||
|
return a64wordLE(baseOp | uint32(rm)<<16 | uint32(rn)<<5), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// encodeARM64FPCCmp encodes a FP conditional compare.
|
||||||
|
// Go assembler syntax: FCCMP cond, Fn, Fm, $nzcv
|
||||||
|
// ARM64 encoding: Rm in bits[20:16], Rn in bits[9:5].
|
||||||
|
// Go puts ops[1] in Rm field, ops[2] in Rn field.
|
||||||
|
func encodeARM64FPCCmp(mnem string, baseOp uint32, ops []*ast.Operand) ([]byte, error) {
|
||||||
|
if len(ops) != 4 {
|
||||||
|
return nil, fmt.Errorf("%s expects 4 operands, got %d", mnem, len(ops))
|
||||||
|
}
|
||||||
|
condName := operandRegName(ops[0])
|
||||||
|
cond, ok := arm64CondMap[condName]
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("invalid condition code %q in %s", condName, mnem)
|
||||||
|
}
|
||||||
|
// Go puts ops[1] in Rm (bits 20:16), ops[2] in Rn (bits 9:5).
|
||||||
|
rm := arm64RegNum(operandRegName(ops[1]))
|
||||||
|
rn := arm64RegNum(operandRegName(ops[2]))
|
||||||
|
if rm < 0 || rn < 0 {
|
||||||
|
return nil, fmt.Errorf("invalid register operand in %s", mnem)
|
||||||
|
}
|
||||||
|
nzcv := uint32(immFromOperand(ops[3]))
|
||||||
|
return a64wordLE(baseOp | uint32(rm)<<16 | cond<<12 | uint32(rn)<<5 | nzcv&0xF), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// encodeARM64FPSel encodes a FP conditional select.
|
||||||
|
// Go assembler syntax: FCSEL cond, Fn, Fm, Fd
|
||||||
|
func encodeARM64FPSel(mnem string, baseOp uint32, ops []*ast.Operand) ([]byte, error) {
|
||||||
|
if len(ops) != 4 {
|
||||||
|
return nil, fmt.Errorf("%s expects 4 operands, got %d", mnem, len(ops))
|
||||||
|
}
|
||||||
|
// Operand order: cond, Fn, Fm, Fd
|
||||||
|
condName := operandRegName(ops[0])
|
||||||
|
cond, ok := arm64CondMap[condName]
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("invalid condition code %q in %s", condName, mnem)
|
||||||
|
}
|
||||||
|
rn := arm64RegNum(operandRegName(ops[1]))
|
||||||
|
rm := arm64RegNum(operandRegName(ops[2]))
|
||||||
|
rd := arm64RegNum(operandRegName(ops[3]))
|
||||||
|
if rn < 0 || rm < 0 || rd < 0 {
|
||||||
|
return nil, fmt.Errorf("invalid register operand in %s", mnem)
|
||||||
|
}
|
||||||
|
return a64wordLE(baseOp | uint32(rm)<<16 | cond<<12 | uint32(rn)<<5 | uint32(rd)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// encodeARM64FPCvt encodes a FP ↔ integer conversion instruction.
|
||||||
|
// The operand order depends on direction: FCVTZS Fd, Rn (FP→int) or SCVTF Rd, Fn (int→FP).
|
||||||
|
func encodeARM64FPCvt(mnem string, baseOp uint32, ops []*ast.Operand) ([]byte, error) {
|
||||||
|
if len(ops) != 2 {
|
||||||
|
return nil, fmt.Errorf("%s expects 2 operands, got %d", mnem, len(ops))
|
||||||
|
}
|
||||||
|
src := arm64RegNum(operandRegName(ops[0]))
|
||||||
|
dst := arm64RegNum(operandRegName(ops[1]))
|
||||||
|
if src < 0 || dst < 0 {
|
||||||
|
return nil, fmt.Errorf("invalid register operand in %s", mnem)
|
||||||
|
}
|
||||||
|
return a64wordLE(baseOp | uint32(src)<<5 | uint32(dst)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// encodeARM64CSEL encodes a conditional select instruction.
|
||||||
|
// CSEL Rm, Rn, Rd, cond (4 operands) or CSET Rd, cond (2 operands).
|
||||||
|
func encodeARM64CSEL(mnem string, baseOp uint32, ops []*ast.Operand) ([]byte, error) {
|
||||||
|
isAlias := mnem == "CSET" || mnem == "CSETW" || mnem == "CSETM" || mnem == "CSETMW" ||
|
||||||
|
mnem == "CINC" || mnem == "CINCW" || mnem == "CINV" || mnem == "CINVW" ||
|
||||||
|
mnem == "CNEG" || mnem == "CNEGW"
|
||||||
|
|
||||||
|
if isAlias {
|
||||||
|
is2op := mnem == "CSET" || mnem == "CSETW" || mnem == "CSETM" || mnem == "CSETMW"
|
||||||
|
if is2op {
|
||||||
|
// CSET cond, Rd → CSEL XZR, XZR, Rd, inverted_cond
|
||||||
|
if len(ops) != 2 {
|
||||||
|
return nil, fmt.Errorf("%s expects 2 operands, got %d", mnem, len(ops))
|
||||||
|
}
|
||||||
|
condName := operandRegName(ops[0])
|
||||||
|
cond, ok := arm64CondMap[condName]
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("invalid condition code %q in %s", condName, mnem)
|
||||||
|
}
|
||||||
|
rd := arm64RegNum(operandRegName(ops[1]))
|
||||||
|
if rd < 0 {
|
||||||
|
return nil, fmt.Errorf("invalid register operand in %s", mnem)
|
||||||
|
}
|
||||||
|
invCond := cond ^ 1
|
||||||
|
return a64wordLE(baseOp | 31<<16 | invCond<<12 | 31<<5 | uint32(rd)), nil
|
||||||
|
}
|
||||||
|
// CINC cond, Rn, Rd → CSINC Rn, Rn, Rd, inverted_cond
|
||||||
|
if len(ops) != 3 {
|
||||||
|
return nil, fmt.Errorf("%s expects 3 operands, got %d", mnem, len(ops))
|
||||||
|
}
|
||||||
|
condName := operandRegName(ops[0])
|
||||||
|
cond, ok := arm64CondMap[condName]
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("invalid condition code %q in %s", condName, mnem)
|
||||||
|
}
|
||||||
|
rn := arm64RegNum(operandRegName(ops[1]))
|
||||||
|
rd := arm64RegNum(operandRegName(ops[2]))
|
||||||
|
if rn < 0 || rd < 0 {
|
||||||
|
return nil, fmt.Errorf("invalid register operand in %s", mnem)
|
||||||
|
}
|
||||||
|
invCond := cond ^ 1
|
||||||
|
return a64wordLE(baseOp | uint32(rn)<<16 | invCond<<12 | uint32(rn)<<5 | uint32(rd)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// CSEL cond, Rn, Rm, Rd (4 operands) — condition first.
|
||||||
|
// Go assembler syntax: CSEL cond, Rn, Rm, Rd
|
||||||
|
// ARM64 encoding: Rm in bits[20:16], Rn in bits[9:5], Rd in bits[4:0].
|
||||||
|
if len(ops) != 4 {
|
||||||
|
return nil, fmt.Errorf("%s expects 4 operands, got %d", mnem, len(ops))
|
||||||
|
}
|
||||||
|
condName := operandRegName(ops[0])
|
||||||
|
cond, ok := arm64CondMap[condName]
|
||||||
|
if !ok {
|
||||||
|
return nil, fmt.Errorf("invalid condition code %q in %s", condName, mnem)
|
||||||
|
}
|
||||||
|
rn := arm64RegNum(operandRegName(ops[1]))
|
||||||
|
rm := arm64RegNum(operandRegName(ops[2]))
|
||||||
|
rd := arm64RegNum(operandRegName(ops[3]))
|
||||||
|
if rn < 0 || rm < 0 || rd < 0 {
|
||||||
|
return nil, fmt.Errorf("invalid register operand in %s", mnem)
|
||||||
|
}
|
||||||
|
return a64wordLE(baseOp | uint32(rm)<<16 | cond<<12 | uint32(rn)<<5 | uint32(rd)), nil
|
||||||
|
}
|
||||||
|
|
||||||
|
// encodeARM64CRC32 encodes a CRC32 instruction.
|
||||||
|
// Go assembler syntax: CRC32B Rm, Rd (2 operands, Rn=Rd).
|
||||||
|
func encodeARM64CRC32(mnem string, baseOp uint32, ops []*ast.Operand) ([]byte, error) {
|
||||||
|
if len(ops) == 3 {
|
||||||
|
// 3-operand form: CRC32B Rm, Rn, Rd → use Rm and Rd, Rn=Rd.
|
||||||
|
rm := arm64RegNum(operandRegName(ops[0]))
|
||||||
|
rd := arm64RegNum(operandRegName(ops[2]))
|
||||||
|
if rm < 0 || rd < 0 {
|
||||||
|
return nil, fmt.Errorf("invalid register operand in %s", mnem)
|
||||||
|
}
|
||||||
|
return a64wordLE(baseOp | uint32(rm)<<16 | uint32(rd)<<5 | uint32(rd)), nil
|
||||||
|
}
|
||||||
|
if len(ops) != 2 {
|
||||||
|
return nil, fmt.Errorf("%s expects 2 or 3 operands, got %d", mnem, len(ops))
|
||||||
|
}
|
||||||
|
rm := arm64RegNum(operandRegName(ops[0]))
|
||||||
|
rd := arm64RegNum(operandRegName(ops[1]))
|
||||||
|
if rm < 0 || rd < 0 {
|
||||||
|
return nil, fmt.Errorf("invalid register operand in %s", mnem)
|
||||||
|
}
|
||||||
|
return a64wordLE(baseOp | uint32(rm)<<16 | uint32(rd)<<5 | uint32(rd)), nil
|
||||||
|
}
|
||||||
|
|
||||||
// AssembleFileARM64 assembles every TEXT function of a parsed arm64 file
|
// AssembleFileARM64 assembles every TEXT function of a parsed arm64 file
|
||||||
// and lays out its static symbols (GLOBL/DATA) in a data section behind the
|
// and lays out its static symbols (GLOBL/DATA) in a data section behind the
|
||||||
// code. SB references in the code are encoded as ADRP pairs with zero
|
// code. SB references in the code are encoded as ADRP pairs with zero
|
||||||
|
|||||||
@@ -339,6 +339,16 @@ const (
|
|||||||
a64FEXTR // EXTR
|
a64FEXTR // EXTR
|
||||||
a64FBitfield // bitfield: BFI/BFXIL/SBFM/UBFM/BFM
|
a64FBitfield // bitfield: BFI/BFXIL/SBFM/UBFM/BFM
|
||||||
a64FSystem // system: NOP, BRK, etc.
|
a64FSystem // system: NOP, BRK, etc.
|
||||||
|
a64FFP3 // FP 3-operand (Rm, Rn, Rd): FADD, FSUB, FMUL, FDIV, etc.
|
||||||
|
a64FFPUnary // FP unary (Rn, Rd): FMOV, FABS, FNEG, FSQRT, FCVT, FRINT*
|
||||||
|
a64FFP4 // FP 4-operand FMA (Ra, Rm, Rn, Rd): FMADD, FMSUB, etc.
|
||||||
|
a64FFPCmp // FP compare (Rm, Rn): FCMP, FCMPE
|
||||||
|
a64FFPCCmp // FP conditional compare (Rm, Rn, nzcv, cond): FCCMP, FCCMPE
|
||||||
|
a64FFPCvt // FP↔integer conversion: FCVTZS, SCVTF, etc.
|
||||||
|
a64FFPSel // FP conditional select (Rm, Rn, Rd, cond): FCSEL
|
||||||
|
a64FFMovGR // FMOV between GP and FP registers
|
||||||
|
a64FCRC32 // CRC32
|
||||||
|
a64FCSEL // conditional select: CSEL, CSINC, CSINV, CSNEG
|
||||||
)
|
)
|
||||||
|
|
||||||
// a64Enc is one instruction's encoding: its bit layout (format) and the
|
// a64Enc is one instruction's encoding: its bit layout (format) and the
|
||||||
@@ -517,6 +527,141 @@ func init() {
|
|||||||
a64InstrTable["BFIW"] = a64Enc{format: a64FBitfield, op: 0<<31 | 2<<29 | 0x26<<23 | 0<<22}
|
a64InstrTable["BFIW"] = a64Enc{format: a64FBitfield, op: 0<<31 | 2<<29 | 0x26<<23 | 0<<22}
|
||||||
a64InstrTable["BFXIL"] = a64Enc{format: a64FBitfield, op: 1<<31 | 1<<29 | 0x26<<23 | 1<<22}
|
a64InstrTable["BFXIL"] = a64Enc{format: a64FBitfield, op: 1<<31 | 1<<29 | 0x26<<23 | 1<<22}
|
||||||
a64InstrTable["BFXILW"] = a64Enc{format: a64FBitfield, op: 0<<31 | 1<<29 | 0x26<<23 | 0<<22}
|
a64InstrTable["BFXILW"] = a64Enc{format: a64FBitfield, op: 0<<31 | 1<<29 | 0x26<<23 | 0<<22}
|
||||||
|
|
||||||
|
// ---- FP 3-operand (Rm, Rn, Rd): FADD, FSUB, FMUL, FDIV, FMAX, FMIN, FNMUL ----
|
||||||
|
fp3 := map[string]uint32{
|
||||||
|
"FADDS": 0x1e202800, "FADDD": 0x1e602800,
|
||||||
|
"FSUBS": 0x1e203800, "FSUBD": 0x1e603800,
|
||||||
|
"FMULS": 0x1e200800, "FMULD": 0x1e600800,
|
||||||
|
"FDIVS": 0x1e201800, "FDIVD": 0x1e601800,
|
||||||
|
"FMAXS": 0x1e204800, "FMAXD": 0x1e604800,
|
||||||
|
"FMINS": 0x1e205800, "FMIND": 0x1e605800,
|
||||||
|
"FMAXNMS": 0x1e206800, "FMAXNMD": 0x1e606800,
|
||||||
|
"FMINNMS": 0x1e207800, "FMINNMD": 0x1e607800,
|
||||||
|
"FNMULS": 0x1e208800, "FNMULD": 0x1e608800,
|
||||||
|
}
|
||||||
|
for m, op := range fp3 {
|
||||||
|
a64InstrTable[m] = a64Enc{format: a64FFP3, op: op}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- FP unary (Rn, Rd): FMOV reg-reg, FABS, FNEG, FSQRT, FCVT, FRINT* ----
|
||||||
|
fp1 := map[string]uint32{
|
||||||
|
"FMOVS": 0x1e204000, "FMOVD": 0x1e604000,
|
||||||
|
"FABSS": 0x1e20c000, "FABSD": 0x1e60c000,
|
||||||
|
"FNEGS": 0x1e214000, "FNEGD": 0x1e614000,
|
||||||
|
"FSQRTS": 0x1e21c000, "FSQRTD": 0x1e61c000,
|
||||||
|
"FCVTSD": 0x1e22c000, "FCVTDS": 0x1e624000,
|
||||||
|
"FRINTNS": 0x1e244000, "FRINTND": 0x1e644000,
|
||||||
|
"FRINTPS": 0x1e24c000, "FRINTPD": 0x1e64c000,
|
||||||
|
"FRINTMS": 0x1e254000, "FRINTMD": 0x1e654000,
|
||||||
|
"FRINTZS": 0x1e25c000, "FRINTZD": 0x1e65c000,
|
||||||
|
"FRINTAS": 0x1e264000, "FRINTAD": 0x1e664000,
|
||||||
|
"FRINTXS": 0x1e274000, "FRINTXD": 0x1e674000,
|
||||||
|
"FRINTIS": 0x1e27c000, "FRINTID": 0x1e67c000,
|
||||||
|
}
|
||||||
|
for m, op := range fp1 {
|
||||||
|
a64InstrTable[m] = a64Enc{format: a64FFPUnary, op: op}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- FP 4-operand FMA (Ra, Rm, Rn, Rd) ----
|
||||||
|
fp4 := map[string]uint32{
|
||||||
|
"FMADDS": 0x1f000000, "FMADDD": 0x1f400000,
|
||||||
|
"FMSUBS": 0x1f008000, "FMSUBD": 0x1f408000,
|
||||||
|
"FNMADDS": 0x1f200000, "FNMADDD": 0x1f600000,
|
||||||
|
"FNMSUBS": 0x1f208000, "FNMSUBD": 0x1f608000,
|
||||||
|
}
|
||||||
|
for m, op := range fp4 {
|
||||||
|
a64InstrTable[m] = a64Enc{format: a64FFP4, op: op}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- FP compare (Rm, Rn or #0, Rn) ----
|
||||||
|
fpcmp := map[string]uint32{
|
||||||
|
"FCMPS": 0x1e202000, "FCMPD": 0x1e602000,
|
||||||
|
"FCMPES": 0x1e202010, "FCMPED": 0x1e602010,
|
||||||
|
}
|
||||||
|
for m, op := range fpcmp {
|
||||||
|
a64InstrTable[m] = a64Enc{format: a64FFPCmp, op: op}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- FP conditional compare (Rm, Rn, #nzcv, cond) ----
|
||||||
|
fpccmp := map[string]uint32{
|
||||||
|
"FCCMPS": 0x1e200400, "FCCMPD": 0x1e600400,
|
||||||
|
"FCCMPES": 0x1e200410, "FCCMPED": 0x1e600410,
|
||||||
|
}
|
||||||
|
for m, op := range fpccmp {
|
||||||
|
a64InstrTable[m] = a64Enc{format: a64FFPCCmp, op: op}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- FP conditional select (Rm, Rn, Rd, cond) ----
|
||||||
|
a64InstrTable["FCSELS"] = a64Enc{format: a64FFPSel, op: 0x1e200c00}
|
||||||
|
a64InstrTable["FCSELD"] = a64Enc{format: a64FFPSel, op: 0x1e600c00}
|
||||||
|
|
||||||
|
// ---- FP ↔ integer conversion ----
|
||||||
|
fpcvt := map[string]uint32{
|
||||||
|
"FCVTZSD": 0x9e780000, "FCVTZSDW": 0x1e780000,
|
||||||
|
"FCVTZSS": 0x9e380000, "FCVTZSSW": 0x1e380000,
|
||||||
|
"FCVTZUD": 0x9e790000, "FCVTZUDW": 0x1e790000,
|
||||||
|
"FCVTZUS": 0x9e390000, "FCVTZUSW": 0x1e390000,
|
||||||
|
"SCVTFD": 0x9e620000, "SCVTFS": 0x9e220000,
|
||||||
|
"SCVTFWD": 0x1e620000, "SCVTFWS": 0x1e220000,
|
||||||
|
"UCVTFD": 0x9e630000, "UCVTFS": 0x9e230000,
|
||||||
|
"UCVTFWD": 0x1e630000, "UCVTFWS": 0x1e230000,
|
||||||
|
}
|
||||||
|
for m, op := range fpcvt {
|
||||||
|
a64InstrTable[m] = a64Enc{format: a64FFPCvt, op: op}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- FMOV between GP and FP registers ----
|
||||||
|
a64InstrTable["FMOVGR"] = a64Enc{format: a64FFMovGR, op: 0x1e260000} // placeholder, actual encoding depends on direction
|
||||||
|
|
||||||
|
// ---- conditional select: CSEL, CSINC, CSINV, CSNEG ----
|
||||||
|
csel := map[string]uint32{
|
||||||
|
"CSEL": 0x9a800000, "CSELW": 0x1a800000,
|
||||||
|
"CSINC": 0x9a800400, "CSINCW": 0x1a800400,
|
||||||
|
"CSINV": 0xda800000, "CSINVW": 0x5a800000,
|
||||||
|
"CSNEG": 0xda800400, "CSNEGW": 0x5a800400,
|
||||||
|
}
|
||||||
|
for m, op := range csel {
|
||||||
|
a64InstrTable[m] = a64Enc{format: a64FCSEL, op: op}
|
||||||
|
}
|
||||||
|
// Aliases
|
||||||
|
a64InstrTable["CSET"] = a64Enc{format: a64FCSEL, op: 0x9a800400}
|
||||||
|
a64InstrTable["CSETW"] = a64Enc{format: a64FCSEL, op: 0x1a800400}
|
||||||
|
a64InstrTable["CSETM"] = a64Enc{format: a64FCSEL, op: 0xda800000}
|
||||||
|
a64InstrTable["CSETMW"] = a64Enc{format: a64FCSEL, op: 0x5a800000}
|
||||||
|
a64InstrTable["CINC"] = a64Enc{format: a64FCSEL, op: 0x9a800400}
|
||||||
|
a64InstrTable["CINCW"] = a64Enc{format: a64FCSEL, op: 0x1a800400}
|
||||||
|
a64InstrTable["CINV"] = a64Enc{format: a64FCSEL, op: 0xda800000}
|
||||||
|
a64InstrTable["CINVW"] = a64Enc{format: a64FCSEL, op: 0x5a800000}
|
||||||
|
a64InstrTable["CNEG"] = a64Enc{format: a64FCSEL, op: 0xda800400}
|
||||||
|
a64InstrTable["CNEGW"] = a64Enc{format: a64FCSEL, op: 0x5a800400}
|
||||||
|
|
||||||
|
// ---- CRC32 ----
|
||||||
|
crc32 := map[string]uint32{
|
||||||
|
"CRC32B": 0x1ac04000, "CRC32H": 0x1ac04400,
|
||||||
|
"CRC32W": 0x1ac04800, "CRC32X": 0x9ac04c00,
|
||||||
|
"CRC32CB": 0x1ac05000, "CRC32CH": 0x1ac05400,
|
||||||
|
"CRC32CW": 0x1ac05800, "CRC32CX": 0x9ac05c00,
|
||||||
|
}
|
||||||
|
for m, op := range crc32 {
|
||||||
|
a64InstrTable[m] = a64Enc{format: a64FCRC32, op: op}
|
||||||
|
}
|
||||||
|
|
||||||
|
// ---- exclusive load/store ----
|
||||||
|
// LDXR/STXR and variants
|
||||||
|
a64InstrTable["LDXR"] = a64Enc{format: a64FLSU, op: 0xc85f7c00}
|
||||||
|
a64InstrTable["LDXRB"] = a64Enc{format: a64FLSU, op: 0x085f7c00}
|
||||||
|
a64InstrTable["LDXRH"] = a64Enc{format: a64FLSU, op: 0x485f7c00}
|
||||||
|
a64InstrTable["LDXRW"] = a64Enc{format: a64FLSU, op: 0x885f7c00}
|
||||||
|
a64InstrTable["LDAXR"] = a64Enc{format: a64FLSU, op: 0xc85ffc00}
|
||||||
|
a64InstrTable["LDAXRB"] = a64Enc{format: a64FLSU, op: 0x085ffc00}
|
||||||
|
a64InstrTable["LDAXRH"] = a64Enc{format: a64FLSU, op: 0x485ffc00}
|
||||||
|
a64InstrTable["LDAXRW"] = a64Enc{format: a64FLSU, op: 0x885ffc00}
|
||||||
|
|
||||||
|
// ---- SIMD basics (VADD, VSUB, VMUL, VMOV) ----
|
||||||
|
a64InstrTable["VADD"] = a64Enc{format: a64FFP3, op: 0x0e208400}
|
||||||
|
a64InstrTable["VSUB"] = a64Enc{format: a64FFP3, op: 0x2e208400}
|
||||||
|
a64InstrTable["VMUL"] = a64Enc{format: a64FFP3, op: 0x0e209c00}
|
||||||
}
|
}
|
||||||
|
|
||||||
// ---- load/store helper tables ----
|
// ---- load/store helper tables ----
|
||||||
|
|||||||
@@ -0,0 +1,142 @@
|
|||||||
|
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
package asm
|
||||||
|
|
||||||
|
import (
|
||||||
|
"bytes"
|
||||||
|
"debug/elf"
|
||||||
|
"testing"
|
||||||
|
|
||||||
|
"sourcedock.dev/petrbalvin/gasm-devkit/parser"
|
||||||
|
)
|
||||||
|
|
||||||
|
// TestELFAARCH64Object checks the structure of the emitted AArch64 ELF64
|
||||||
|
// relocatable object: sections, the symbol table (bindings, types, values,
|
||||||
|
// sizes) and the .rela.text relocation pair for the static-symbol load,
|
||||||
|
// parsed back with debug/elf.
|
||||||
|
func TestELFAARCH64Object(t *testing.T) {
|
||||||
|
f, errs := parser.Parse("k_arm64.s", `
|
||||||
|
#include "textflag.h"
|
||||||
|
|
||||||
|
TEXT ·add(SB), NOSPLIT, $0-24
|
||||||
|
MOVD a+0(FP), R4
|
||||||
|
MOVD b+8(FP), R5
|
||||||
|
ADD R5, R4, R4
|
||||||
|
MOVD R4, ret+16(FP)
|
||||||
|
RET
|
||||||
|
|
||||||
|
TEXT ·getanswer(SB), NOSPLIT, $0-8
|
||||||
|
MOVD answer<>(SB), R4
|
||||||
|
MOVD R4, ret+0(FP)
|
||||||
|
RET
|
||||||
|
|
||||||
|
GLOBL answer<>(SB), RODATA, $8
|
||||||
|
DATA answer<>+0(SB)/8, $42
|
||||||
|
`)
|
||||||
|
if len(errs) > 0 {
|
||||||
|
t.Fatalf("parse: %v", errs)
|
||||||
|
}
|
||||||
|
img, err := AssembleFileARM64(f)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("AssembleFileARM64: %v", err)
|
||||||
|
}
|
||||||
|
obj, err := img.ELFAARCH64Object()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ELFAARCH64Object: %v", err)
|
||||||
|
}
|
||||||
|
ef, err := elf.NewFile(bytes.NewReader(obj))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("parse emitted object: %v", err)
|
||||||
|
}
|
||||||
|
defer ef.Close()
|
||||||
|
|
||||||
|
if ef.Type != elf.ET_REL || ef.Machine != elf.EM_AARCH64 {
|
||||||
|
t.Errorf("type/machine = %v/%v, want ET_REL/EM_AARCH64", ef.Type, ef.Machine)
|
||||||
|
}
|
||||||
|
|
||||||
|
text := ef.Section(".text")
|
||||||
|
data := ef.Section(".data")
|
||||||
|
if text == nil || data == nil {
|
||||||
|
t.Fatal("missing .text or .data section")
|
||||||
|
}
|
||||||
|
if text.Size == 0 {
|
||||||
|
t.Error(".text section is empty")
|
||||||
|
}
|
||||||
|
|
||||||
|
syms, err := ef.Symbols()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("symbols: %v", err)
|
||||||
|
}
|
||||||
|
|
||||||
|
foundAdd, foundGetanswer, foundAnswer := false, false, false
|
||||||
|
for _, s := range syms {
|
||||||
|
switch s.Name {
|
||||||
|
case "add":
|
||||||
|
foundAdd = true
|
||||||
|
if elf.SymType(s.Info&0xf) != elf.STT_FUNC || elf.SymBind(s.Info>>4) != elf.STB_GLOBAL {
|
||||||
|
t.Errorf("add: info=0x%02x, want STT_FUNC|STB_GLOBAL", s.Info)
|
||||||
|
}
|
||||||
|
case "getanswer":
|
||||||
|
foundGetanswer = true
|
||||||
|
if elf.SymType(s.Info&0xf) != elf.STT_FUNC || elf.SymBind(s.Info>>4) != elf.STB_GLOBAL {
|
||||||
|
t.Errorf("getanswer: info=0x%02x, want STT_FUNC|STB_GLOBAL", s.Info)
|
||||||
|
}
|
||||||
|
case "answer":
|
||||||
|
foundAnswer = true
|
||||||
|
if elf.SymType(s.Info&0xf) != elf.STT_OBJECT || elf.SymBind(s.Info>>4) != elf.STB_LOCAL {
|
||||||
|
t.Errorf("answer: info=0x%02x, want STT_OBJECT|STB_LOCAL", s.Info)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if !foundAdd {
|
||||||
|
t.Error("symbol 'add' not found")
|
||||||
|
}
|
||||||
|
if !foundGetanswer {
|
||||||
|
t.Error("symbol 'getanswer' not found")
|
||||||
|
}
|
||||||
|
if !foundAnswer {
|
||||||
|
t.Error("symbol 'answer' not found")
|
||||||
|
}
|
||||||
|
|
||||||
|
// Check that .rela.text exists (getanswer has SB reference).
|
||||||
|
relaText := ef.Section(".rela.text")
|
||||||
|
if relaText == nil {
|
||||||
|
t.Error("missing .rela.text section")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestELFAARCH64ObjectNoRelocations checks the ELF output when there are no
|
||||||
|
// static-symbol references (no .rela.text section).
|
||||||
|
func TestELFAARCH64ObjectNoRelocations(t *testing.T) {
|
||||||
|
f, errs := parser.Parse("k_arm64.s", `
|
||||||
|
#include "textflag.h"
|
||||||
|
|
||||||
|
TEXT ·add(SB), NOSPLIT, $0-24
|
||||||
|
MOVD a+0(FP), R4
|
||||||
|
MOVD b+8(FP), R5
|
||||||
|
ADD R5, R4, R4
|
||||||
|
MOVD R4, ret+16(FP)
|
||||||
|
RET
|
||||||
|
`)
|
||||||
|
if len(errs) > 0 {
|
||||||
|
t.Fatalf("parse: %v", errs)
|
||||||
|
}
|
||||||
|
img, err := AssembleFileARM64(f)
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("AssembleFileARM64: %v", err)
|
||||||
|
}
|
||||||
|
obj, err := img.ELFAARCH64Object()
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("ELFAARCH64Object: %v", err)
|
||||||
|
}
|
||||||
|
ef, err := elf.NewFile(bytes.NewReader(obj))
|
||||||
|
if err != nil {
|
||||||
|
t.Fatalf("parse emitted object: %v", err)
|
||||||
|
}
|
||||||
|
defer ef.Close()
|
||||||
|
|
||||||
|
if ef.Section(".rela.text") != nil {
|
||||||
|
t.Error("unexpected .rela.text section when there are no relocations")
|
||||||
|
}
|
||||||
|
}
|
||||||
Vendored
+119
@@ -0,0 +1,119 @@
|
|||||||
|
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
||||||
|
// SPDX-License-Identifier: BSD-3-Clause
|
||||||
|
|
||||||
|
#include "textflag.h"
|
||||||
|
|
||||||
|
// fparith exercises the FP arithmetic set.
|
||||||
|
TEXT ·fparith(SB), NOSPLIT, $0-0
|
||||||
|
FADDD F0, F1, F2
|
||||||
|
FSUBD F3, F4, F5
|
||||||
|
FMULD F6, F7, F8
|
||||||
|
FDIVD F9, F10, F11
|
||||||
|
FADDS F12, F13, F14
|
||||||
|
FSUBS F15, F16, F17
|
||||||
|
FMULS F18, F19, F20
|
||||||
|
FDIVS F21, F22, F23
|
||||||
|
FSQRTD F24, F25
|
||||||
|
FSQRTS F26, F27
|
||||||
|
FNEGD F28, F29
|
||||||
|
FNEGS F30, F31
|
||||||
|
FABSD F0, F1
|
||||||
|
FABSS F2, F3
|
||||||
|
FNMULD F4, F5, F6
|
||||||
|
FNMULS F7, F8, F9
|
||||||
|
FMIND F10, F11, F12
|
||||||
|
FMAXD F13, F14, F15
|
||||||
|
FMINS F16, F17, F18
|
||||||
|
FMAXS F19, F20, F21
|
||||||
|
RET
|
||||||
|
|
||||||
|
// fpfma exercises fused multiply-add.
|
||||||
|
TEXT ·fpfma(SB), NOSPLIT, $0-0
|
||||||
|
FMADDD F0, F1, F2, F3
|
||||||
|
FMSUBD F4, F5, F6, F7
|
||||||
|
FNMADDD F8, F9, F10, F11
|
||||||
|
FNMSUBD F12, F13, F14, F15
|
||||||
|
FMADDS F16, F17, F18, F19
|
||||||
|
FMSUBS F20, F21, F22, F23
|
||||||
|
FNMADDS F24, F25, F26, F27
|
||||||
|
FNMSUBS F28, F29, F30, F0
|
||||||
|
RET
|
||||||
|
|
||||||
|
// fpconv exercises FP↔integer conversion and cross-precision.
|
||||||
|
// Syntax: FCVTZSD Fd, Rn (float→int: FP source first, int dest second)
|
||||||
|
// SCVTFD Rn, Fd (int→float: int source first, FP dest second)
|
||||||
|
TEXT ·fpconv(SB), NOSPLIT, $0-0
|
||||||
|
FCVTSD F0, F1
|
||||||
|
FCVTDS F2, F3
|
||||||
|
FCVTZSD F4, R0
|
||||||
|
FCVTZSS F5, R1
|
||||||
|
FCVTZUD F6, R2
|
||||||
|
FCVTZUS F7, R3
|
||||||
|
SCVTFD R4, F8
|
||||||
|
SCVTFS R5, F9
|
||||||
|
UCVTFD R6, F10
|
||||||
|
UCVTFS R7, F11
|
||||||
|
SCVTFWD R0, F12
|
||||||
|
SCVTFWS R1, F13
|
||||||
|
UCVTFWD R2, F14
|
||||||
|
UCVTFWS R3, F15
|
||||||
|
FMOVS F14, R20
|
||||||
|
FMOVS R21, F15
|
||||||
|
FMOVD F16, R22
|
||||||
|
FMOVD R23, F17
|
||||||
|
RET
|
||||||
|
|
||||||
|
// fpcmp exercises FP compare and conditional compare.
|
||||||
|
// FCCMP syntax: FCCMP cond, Fn, Fm, $nzcv
|
||||||
|
// FCSEL syntax: FCSEL cond, Fn, Fm, Fd
|
||||||
|
TEXT ·fpcmp(SB), NOSPLIT, $0-0
|
||||||
|
FCMPS F0, F1
|
||||||
|
FCMPD F2, F3
|
||||||
|
FCMPS $0.0, F4
|
||||||
|
FCMPD $0.0, F5
|
||||||
|
FCCMPS EQ, F6, F7, $0
|
||||||
|
FCCMPD NE, F8, F9, $0
|
||||||
|
FCSELS GE, F10, F11, F12
|
||||||
|
FCSELD LT, F13, F14, F15
|
||||||
|
RET
|
||||||
|
|
||||||
|
// frint exercises FP rounding.
|
||||||
|
TEXT ·frint(SB), NOSPLIT, $0-0
|
||||||
|
FRINTND F0, F1
|
||||||
|
FRINTNS F2, F3
|
||||||
|
FRINTPD F4, F5
|
||||||
|
FRINTPS F6, F7
|
||||||
|
FRINTMD F8, F9
|
||||||
|
FRINTMS F10, F11
|
||||||
|
FRINTZD F12, F13
|
||||||
|
FRINTZS F14, F15
|
||||||
|
FRINTAD F16, F17
|
||||||
|
FRINTAS F18, F19
|
||||||
|
FRINTXD F20, F21
|
||||||
|
FRINTXS F22, F23
|
||||||
|
FRINTID F24, F25
|
||||||
|
FRINTIS F26, F27
|
||||||
|
FMOVD F0, F1
|
||||||
|
FMOVS F2, F3
|
||||||
|
RET
|
||||||
|
|
||||||
|
// condsel exercises conditional select and CRC32.
|
||||||
|
TEXT ·condsel(SB), NOSPLIT, $0-0
|
||||||
|
CSEL EQ, R0, R1, R2
|
||||||
|
CSINC NE, R3, R4, R5
|
||||||
|
CSINV GE, R6, R7, R8
|
||||||
|
CSNEG LT, R9, R10, R11
|
||||||
|
CSET EQ, R12
|
||||||
|
CSETM NE, R13
|
||||||
|
CINC EQ, R14, R15
|
||||||
|
CINV NE, R16, R17
|
||||||
|
CNEG GE, R19, R20
|
||||||
|
CRC32B R0, R2
|
||||||
|
CRC32H R3, R5
|
||||||
|
CRC32W R6, R8
|
||||||
|
CRC32X R9, R11
|
||||||
|
CRC32CB R12, R14
|
||||||
|
CRC32CH R15, R0
|
||||||
|
CRC32CW R1, R3
|
||||||
|
CRC32CX R4, R6
|
||||||
|
RET
|
||||||
@@ -19,6 +19,7 @@ import (
|
|||||||
func TestGroundTruthARM64(t *testing.T) {
|
func TestGroundTruthARM64(t *testing.T) {
|
||||||
for _, path := range []string{
|
for _, path := range []string{
|
||||||
"../testdata/verify/basic_arm64.s",
|
"../testdata/verify/basic_arm64.s",
|
||||||
|
"../testdata/verify/fp_arm64.s",
|
||||||
} {
|
} {
|
||||||
t.Run(path, func(t *testing.T) {
|
t.Run(path, func(t *testing.T) {
|
||||||
src, err := os.ReadFile(path)
|
src, err := os.ReadFile(path)
|
||||||
|
|||||||
Reference in New Issue
Block a user