feat(asm): encode the arm64 FP immediate moves

Assisted-by: GLM 5.3 Flash
This commit is contained in:
petrbalvin committed 2026-10-07 02:36:24 +02:00
1 parent a9b54b6868
commit fbdad8424f
4 files changed
+181

No files matched your search

+96
View File
@@ -5,6 +5,7 @@ package asm
import (
"fmt"
"math"
"math/bits"
"slices"
"strconv"
@@ -1603,6 +1604,39 @@ func encodeARM64Mov(instr *ast.Instr, mnem string, wb string, fi arm64FrameInfo,
return encodeARM64SBAddr(src.Imm.Sym, rd, relocs), nil
}
rd := arm64RegNum(operandRegName(dst))
// The FP immediates: FMOVS/FMOVD $f, Fd ride the FMOV (immediate)
// instruction when the 8-bit field carries the value and the
// FMOV-from-ZR move for zero; the toolchain pools anything else
// (obj7.go's preprocess), which needs pool symbols gasm does not
// carry. An integer immediate to an FP register is rejected, the
// zero spelling alone excepted, and FMOVQ has no immediate row.
if mnem == "FMOVS" || mnem == "FMOVD" {
if arm64RegClassOf(operandRegName(dst)) == arm64ClsFP {
f, ok := arm64FloatOperandValue(src)
if !ok {
return nil, fmt.Errorf("%s: illegal combination: an integer immediate needs a real register", mnem)
}
if enc := arm64ChipFloat(f); enc > 0 {
base := uint32(0x1e201000) // FMOV Sd, #imm
if mnem == "FMOVD" {
base = 0x1e601000 // FMOV Dd, #imm
}
return a64wordLE(base | uint32(enc)<<13 | uint32(rd)), nil
}
zero := math.Float64bits(f) == 0
if mnem == "FMOVS" {
zero = math.Float32bits(float32(f)) == 0
}
if zero {
base := uint32(0x1e270000) // FMOV Sd, (W)ZR
if mnem == "FMOVD" {
base = 0x9e670000 // FMOV Dd, XZR
}
return a64wordLE(base | 31<<5 | uint32(rd)), nil
}
return nil, fmt.Errorf("%s $%v: floating-point immediate needs the constant pool", mnem, f)
}
}
// The con(register) form: MOVD $con(Rn), Rd adds the displacement to
// the base register (asm7.go case 4). The toolchain rejects every
// other width and the ZR destination outright (RSP is a real register
@@ -2248,6 +2282,68 @@ func arm64AddSubImmWord(op uint32, v int64, rn, rd uint32) uint32 {
return a64AddSub(1, op, 0, sh, uint32(v), rn, rd)
}
// arm64FloatOperandValue recovers a floating-point immediate: the bare
// spelling fills Imm.Float and the parenthesised one stays in the raw text
// ($ ( 4.0 )), so the token shape comes off the raw string. An integer zero
// rides Imm.HasVal and counts (the toolchain's AFMOVD zero row), any other
// integer immediate does not. ok is false for every other shape.
func arm64FloatOperandValue(op *ast.Operand) (float64, bool) {
if op.Imm.Float != "" {
f, err := strconv.ParseFloat(op.Imm.Float, 64)
if err != nil {
return 0, false
}
if op.Imm.Neg {
f = -f
}
return f, true
}
s := strings.Join(strings.Fields(op.Raw), "")
s = strings.TrimPrefix(s, "$")
if strings.HasPrefix(s, "(") && strings.HasSuffix(s, ")") {
inner := s[1 : len(s)-1]
if strings.ContainsAny(inner, ".eE") {
f, err := strconv.ParseFloat(inner, 64)
if err != nil {
return 0, false
}
return f, true
}
}
if op.Imm.HasVal && op.Imm.Val == 0 && !op.Imm.Neg {
return 0, true
}
return 0, false
}
// arm64ChipFloat ports the toolchain's chipfloat7: the 8-bit FMOV immediate
// field for a double whose low 48 mantissa bits are zero and whose exponent
// sits in the -3..4 band, with bit 7 the sign, bit 6 the negative-exponent
// flag and bits 4:0 the exponent tail plus mantissa top. The toolchain
// takes the encoded value only when this reports above zero; zero itself
// pools (or moves from ZR), so 0 is not a valid encoding here either.
func arm64ChipFloat(e float64) int {
ei := math.Float64bits(e)
l := uint32(ei)
h := uint32(ei >> 32)
if l != 0 || h&0xffff != 0 {
return -1
}
h1 := h & 0x7fc00000
if h1 != 0x40000000 && h1 != 0x3fc00000 {
return -1
}
n := 0
if h&0x80000000 != 0 {
n |= 1 << 7
}
if h1 == 0x3fc00000 {
n |= 1 << 6
}
n |= int((h >> 16) & 0x3f)
return n
}
// arm64ImmWithBase reports whether an immediate operand spells the
// con(register) form, $con(REG): the parser leaves it unstructured (an
// immediate whose raw spelling carries the parenthesised register), so the