feat(asm): extend arm64 encoder with FP, conditional select, CRC32 and tests
Assisted-by: MiMo V2.5 Pro
This commit is contained in:
+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)
|
||||
}
|
||||
|
||||
// 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)
|
||||
}
|
||||
|
||||
@@ -621,7 +666,11 @@ func arm64Bitmask(v uint64, sf int) (N, immr, imms uint32, ok bool) {
|
||||
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) {
|
||||
rs := arm64RegNum(operandRegName(src))
|
||||
rd := arm64RegNum(operandRegName(dst))
|
||||
@@ -631,21 +680,37 @@ func encodeARM64RegMove(mnem string, src, dst *ast.Operand) ([]byte, error) {
|
||||
sc := arm64RegClassOf(operandRegName(src))
|
||||
dc := arm64RegClassOf(operandRegName(dst))
|
||||
|
||||
// FP → FP: FMOV Rd, Rs
|
||||
// FP → FP: FMOV Fd, Fn (FP data processing unary form).
|
||||
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
|
||||
if mnem == "FMOVS" {
|
||||
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
|
||||
if mnem == "MOVW" || mnem == "MOVWU" || mnem == "MOVB" || mnem == "MOVBU" ||
|
||||
mnem == "MOVH" || mnem == "MOVHU" {
|
||||
@@ -775,6 +840,234 @@ func arm64Label(op *ast.Operand) string {
|
||||
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
|
||||
// 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
|
||||
|
||||
Reference in New Issue
Block a user