feat(asm): encode the arm64 FP immediate moves
Assisted-by: GLM 5.3 Flash
This commit is contained in:
1 parent
a9b54b6868
commit
fbdad8424f
4 files changed
+181
No files matched your search
@@ -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
|
||||
|
||||
@@ -2069,3 +2069,53 @@ func TestArm64ConRnRejections(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestArm64FPImmediate pins the FP immediate moves against `go tool asm`
|
||||
// words: the FMOV (immediate) instruction for the 8-bit encodable values and
|
||||
// the FMOV-from-ZR move for zero, plus the rejections the toolchain raises
|
||||
// (integer immediates to FP registers, FMOVQ immediates) and the values that
|
||||
// only the toolchain's $f64 pool reaches.
|
||||
func TestArm64FPImmediate(t *testing.T) {
|
||||
got := arm64Words(t,
|
||||
"\tFMOVS $(4.0), F0\n"+
|
||||
"\tFMOVD $(4.0), F0\n"+
|
||||
"\tFMOVS $(0.265625), F1\n"+
|
||||
"\tFMOVD $(0.1796875), F2\n"+
|
||||
"\tFMOVD $(28.0), F4\n"+
|
||||
"\tFMOVD $(-4.0), F6\n"+
|
||||
"\tFMOVD $0, F0\n"+
|
||||
"\tFMOVD $(0.0), F5\n")
|
||||
want := []uint32{
|
||||
0x1e221000, // FMOV S0, #4.0
|
||||
0x1e621000, // FMOV D0, #4.0
|
||||
0x1e2a3001, // FMOV S1, #0.265625
|
||||
0x1e68f002, // FMOV D2, #0.1796875
|
||||
0x1e679004, // FMOV D4, #28.0
|
||||
0x1e721006, // FMOV D6, #-4.0
|
||||
0x9e6703e0, // FMOV D0, XZR
|
||||
0x9e6703e5, // FMOV D5, XZR
|
||||
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])
|
||||
}
|
||||
}
|
||||
for _, src := range []string{
|
||||
"\tFMOVD\t$5, F0\n",
|
||||
"\tFMOVS\t$4, F0\n",
|
||||
"\tFMOVQ\t$(4.0), F0\n",
|
||||
"\tFMOVD\t$(2.0), F0\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 // a parse rejection is a rejection
|
||||
}
|
||||
if _, err := AssembleFileARM64(f); err == nil {
|
||||
t.Errorf("expected rejection for %q, got nil", strings.TrimSpace(src))
|
||||
}
|
||||
}
|
||||
}
|
||||
Vendored
+34
@@ -0,0 +1,34 @@
|
||||
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
// Differential kernel for the arm64 FP immediate moves: the FMOV (immediate)
|
||||
// instruction for the 8-bit encodable values and the FMOV-from-ZR move for
|
||||
// zero. The values outside the 8-bit field pool through the toolchain's
|
||||
// $f64 symbols with a PC-relative relocation; gasm carries no such pool and
|
||||
// rejects the spelling instead (its previous encoding was wrong code).
|
||||
// Every function is byte-compared against go tool asm.
|
||||
|
||||
#include "textflag.h"
|
||||
|
||||
// func encodable()
|
||||
TEXT ·encodable(SB), NOSPLIT, $0-0
|
||||
FMOVS $ (4.0), F0
|
||||
FMOVD $ (4.0), F0
|
||||
FMOVS $ (0.265625), F1
|
||||
FMOVD $ (0.1796875), F2
|
||||
FMOVS $ (0.96875), F3
|
||||
FMOVD $ (28.0), F4
|
||||
FMOVD $ (-4.0), F6
|
||||
FMOVD $ (1.0), F7
|
||||
FMOVD $ (0.5), F8
|
||||
RET
|
||||
|
||||
// func zeroes()
|
||||
TEXT ·zeroes(SB), NOSPLIT, $0-0
|
||||
FMOVD $0, F0
|
||||
FMOVS $0, F1
|
||||
FMOVD $ (0.0), F5
|
||||
FMOVS $ (0.0), F6
|
||||
FMOVD ZR, F2
|
||||
FMOVS ZR, F3
|
||||
RET
|
||||
@@ -39,6 +39,7 @@ func TestGroundTruthARM64(t *testing.T) {
|
||||
"../testdata/verify/splits_arm64.s",
|
||||
"../testdata/verify/regoffset_arm64.s",
|
||||
"../testdata/verify/simdarr_arm64.s",
|
||||
"../testdata/verify/fpimm_arm64.s",
|
||||
"../testdata/verify/crypto_arm64.s",
|
||||
"../testdata/verify/integer_arm64.s",
|
||||
"../testdata/verify/simd_arm64.s",
|
||||
|
||||
Reference in new issue
Block a user