feat(asm): encode the arm64 register-offset addressing forms
Assisted-by: GLM 5.3 Flash
This commit is contained in:
1 parent
a344399b81
commit
458cdd2066
4 files changed
+221
-1
No files matched your search
+80
-1
@@ -2008,6 +2008,14 @@ func encodeARM64MemOp(mnem string, mem *ast.Operand, reg int, load bool, fi arm6
|
||||
lt = a64LoadTable["MOVD"]
|
||||
}
|
||||
|
||||
// Register-offset addressing: (Rn)(Rm), (Rn)(Rm<<k), (Rn)(Rm.UXTW) and
|
||||
// friends. The toolchain accepts the LSL (UXTX), UXTW, SXTW and SXTX
|
||||
// options with a shift amount of either zero or the access size's log2,
|
||||
// rejects every other spelling, and rejects the whole form on FMOVQ.
|
||||
if mem.Addr.Index != "" {
|
||||
return encodeARM64RegOffset(mnem, mem, rn, reg, load, lt, wb)
|
||||
}
|
||||
|
||||
scale := a64LSScale(lt)
|
||||
storeOpc := a64StoreOpc(lt)
|
||||
var opc int
|
||||
@@ -2094,7 +2102,78 @@ func arm64PoolAccess(mnem string, lt a64LSType, opc int, off int64, rn, reg, pc
|
||||
// option the toolchain's register-offset accesses use:
|
||||
// size<<30 | 0x38<<24 | V<<26 | opc<<22 | 1<<21 | Rm<<16 | 011<<13 | 10<<10 | Rn<<5 | Rt.
|
||||
func a64LSReg(size, V, opc, rm, rn, rt uint32) uint32 {
|
||||
return size<<30 | 0x38<<24 | V<<26 | opc<<22 | 1<<21 | rm<<16 | 3<<13 | 2<<10 | rn<<5 | rt
|
||||
return a64LSRegExt(size, V, opc, rm, rn, rt, 3, 0)
|
||||
}
|
||||
|
||||
// a64LSRegExt encodes the extended-register-offset load/store:
|
||||
// size<<30 | 0x38<<24 | V<<26 | opc<<22 | 1<<21 | Rm<<16 | option<<13 |
|
||||
// S<<12 | 10<<10 | Rn<<5 | Rt, with S meaning "scale by the access size".
|
||||
func a64LSRegExt(size, V, opc, rm, rn, rt, option, s uint32) uint32 {
|
||||
return size<<30 | 0x38<<24 | V<<26 | opc<<22 | 1<<21 | rm<<16 | option<<13 | s<<12 | 2<<10 | rn<<5 | rt
|
||||
}
|
||||
|
||||
// encodeARM64RegOffset encodes the register-offset addressing forms of the
|
||||
// MOV family: (Rn)(Rm), (Rn)(Rm*1), (Rn)(Rm<<k) and the extend spellings
|
||||
// (Rn)(Rm.UXTW<<k), (Rn)(Rm.SXTW), (Rn)(Rm.SXTX<<k). The toolchain's
|
||||
// contract, probed against `go tool asm`: the four options UXTX/LSL, UXTW,
|
||||
// SXTW and SXTX exist (the spellings UXTX, UXTB, SXTH and friends are
|
||||
// rejected), the shift amount is either zero or the access size's log2, FMOVQ
|
||||
// carries no register-offset form at all, and writeback does not combine with
|
||||
// an index.
|
||||
func encodeARM64RegOffset(mnem string, mem *ast.Operand, rn, reg int, load bool, lt a64LSType, wb string) ([]byte, error) {
|
||||
if wb != "" {
|
||||
return nil, fmt.Errorf("%s: writeback does not combine with a register offset", mnem)
|
||||
}
|
||||
if mnem == "FMOVQ" {
|
||||
return nil, fmt.Errorf("%s: illegal combination: the register offset form does not exist", mnem)
|
||||
}
|
||||
if mem.Addr.Scale > 1 {
|
||||
return nil, fmt.Errorf("%s: the register offset addressing mode takes no scale factor", mnem)
|
||||
}
|
||||
name, ext, _ := strings.Cut(mem.Addr.Index, ".")
|
||||
var option uint32
|
||||
switch strings.ToUpper(ext) {
|
||||
case "":
|
||||
option = 3 // UXTX / LSL
|
||||
case "UXTW":
|
||||
option = 2
|
||||
case "SXTW":
|
||||
option = 6
|
||||
case "SXTX":
|
||||
option = 7
|
||||
default:
|
||||
return nil, fmt.Errorf("%s: invalid shift for the register offset addressing mode: %s", mnem, ext)
|
||||
}
|
||||
rm := arm64RegNum(name)
|
||||
if rm < 0 {
|
||||
return nil, fmt.Errorf("%s: invalid index register %q", mnem, name)
|
||||
}
|
||||
amount := 0
|
||||
shift := strings.Join(strings.Fields(mem.Addr.Shift), "")
|
||||
shift = strings.TrimSuffix(shift, ")")
|
||||
if shift != "" {
|
||||
if !strings.HasPrefix(shift, "<<") {
|
||||
return nil, fmt.Errorf("%s: invalid shift for the register offset addressing mode: %s", mnem, shift)
|
||||
}
|
||||
v, err := strconv.Atoi(strings.TrimPrefix(shift, "<<"))
|
||||
if err != nil {
|
||||
return nil, fmt.Errorf("%s: invalid shift for the register offset addressing mode: %s", mnem, shift)
|
||||
}
|
||||
amount = v
|
||||
}
|
||||
log2 := bits.TrailingZeros64(uint64(a64LSScale(lt)))
|
||||
if amount != 0 && amount != log2 {
|
||||
return nil, fmt.Errorf("%s: invalid index shift amount %d", mnem, amount)
|
||||
}
|
||||
s := uint32(0)
|
||||
if amount == log2 && log2 > 0 {
|
||||
s = 1
|
||||
}
|
||||
opc := lt.opc
|
||||
if !load {
|
||||
opc = a64StoreOpc(lt)
|
||||
}
|
||||
return a64wordLE(a64LSRegExt(uint32(lt.size), uint32(lt.V), uint32(opc), uint32(rm), uint32(rn), uint32(reg), option, s)), nil
|
||||
}
|
||||
|
||||
// arm64OffsetSplitReach reports whether an offset stays within the band the
|
||||
|
||||
@@ -1825,3 +1825,72 @@ func TestArm64SplitFrameSpadj(t *testing.T) {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestArm64RegOffsetEncodings pins the register-offset addressing forms of
|
||||
// the MOV family against `go tool asm` words (Go 1.27, arm64): the LSL,
|
||||
// UXTW, SXTW and SXTX options, the shift amount equal to the access size's
|
||||
// log2, and the rejections the toolchain raises for the other spellings.
|
||||
func TestArm64RegOffsetEncodings(t *testing.T) {
|
||||
got := arm64Words(t,
|
||||
"\tMOVD (R2)(R6.SXTW), R4\n"+
|
||||
"\tMOVD (R3)(R6), R5\n"+
|
||||
"\tMOVD (R2)(R6<<3), R4\n"+
|
||||
"\tMOVWU (R5)(R4.UXTW), R10\n"+
|
||||
"\tMOVW (R9)(R8.SXTW<<2), R19\n"+
|
||||
"\tMOVD (R3)(R7.SXTX<<3), R8\n"+
|
||||
"\tMOVBU (R10)(R6), R15\n"+
|
||||
"\tFMOVS (R2)(R6<<2), F4\n"+
|
||||
"\tFMOVD (R2)(R6), F4\n"+
|
||||
"\tMOVD R5, (R2)(R6<<3)\n"+
|
||||
"\tMOVW R7, (R3)(R4.SXTW)\n"+
|
||||
"\tMOVD ZR, (R6)(R7.SXTX<<3)\n")
|
||||
want := []uint32{
|
||||
0xf866c844, // MOVD (R2)(R6.SXTW), R4
|
||||
0xf8666865, // MOVD (R3)(R6), R5
|
||||
0xf8667844, // MOVD (R2)(R6<<3), R4
|
||||
0xb86448aa, // MOVWU (R5)(R4.UXTW), R10
|
||||
0xb8a8d933, // MOVW (R9)(R8.SXTW<<2), R19
|
||||
0xf867f868, // MOVD (R3)(R7.SXTX<<3), R8
|
||||
0x3866694f, // MOVBU (R10)(R6), R15
|
||||
0xbc667844, // FMOVS (R2)(R6<<2), F4
|
||||
0xfc666844, // FMOVD (R2)(R6), F4
|
||||
0xf8267845, // MOVD R5, (R2)(R6<<3)
|
||||
0xb824c867, // MOVW R7, (R3)(R4.SXTW)
|
||||
0xf827f8df, // MOVD ZR, (R6)(R7.SXTX<<3)
|
||||
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])
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestArm64RegOffsetRejections pins the spellings the toolchain refuses for
|
||||
// the register-offset addressing: the UXTX/UXTB/SXTH extend names, a shift
|
||||
// amount outside {0, log2(access)}, the scaled *k index, writeback with an
|
||||
// index, and the FMOVQ form the toolchain rejects outright.
|
||||
func TestArm64RegOffsetRejections(t *testing.T) {
|
||||
for _, src := range []string{
|
||||
"\tMOVD\t(R2)(R3.UXTX), R1\n",
|
||||
"\tMOVD\t(R2)(R3.UXTB), R1\n",
|
||||
"\tMOVD\t(R2)(R3.SXTH), R1\n",
|
||||
"\tMOVD\t(R2)(R3<<1), R1\n",
|
||||
"\tMOVD\t(R2)(R3<<2), R1\n",
|
||||
"\tMOVBU\t(R1)(R2<<1), R3\n",
|
||||
"\tMOVD\t(R2)(R3*8), R1\n",
|
||||
"\tMOVD.P\tR5, (R2)(R3)\n",
|
||||
"\tFMOVQ\t(R2)(R6), F4\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
+71
@@ -0,0 +1,71 @@
|
||||
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
// Differential kernel for the arm64 register-offset addressing forms:
|
||||
// (Rn)(Rm), (Rn)(Rm<<k), (Rn)(Rm*1) and the extend spellings
|
||||
// (Rn)(Rm.UXTW<<k), (Rn)(Rm.SXTW), (Rn)(Rm.SXTX<<k). Every function is
|
||||
// byte-compared against go tool asm.
|
||||
|
||||
#include "textflag.h"
|
||||
|
||||
// func loads()
|
||||
TEXT ·loads(SB), NOSPLIT, $0-0
|
||||
MOVD (R2)(R6.SXTW), R4
|
||||
MOVD (R3)(R6), R5
|
||||
MOVD (R3)(R6*1), R5
|
||||
MOVD (R2)(R6), R4
|
||||
MOVWU (R19)(R20<<2), R20
|
||||
MOVD (R2)(R3<<0), R1
|
||||
MOVD (R2)(R6<<3), R4
|
||||
MOVD (R3)(R7.SXTX<<3), R8
|
||||
MOVWU (R5)(R4.UXTW), R10
|
||||
MOVBU (R3)(R9.UXTW), R8
|
||||
MOVBU (R5)(R8), R10
|
||||
MOVHU (R2)(R7.SXTW<<1), R11
|
||||
MOVHU (R1)(R2<<1), R5
|
||||
MOVBU (R9)(R3.UXTW), R6
|
||||
MOVBU (R10)(R6), R15
|
||||
MOVBU (R29)(R30<<0), R14
|
||||
MOVH (R5)(R7.SXTX<<1), R19
|
||||
MOVH (R8)(R4<<1), R10
|
||||
MOVW (R9)(R8.SXTW<<2), R19
|
||||
MOVW (R1)(R4.SXTX), R11
|
||||
MOVW (R1)(R4.SXTX), ZR
|
||||
MOVW (R2)(R5), R12
|
||||
FMOVS (R2)(R6), F4
|
||||
FMOVS (R2)(R6<<2), F4
|
||||
FMOVD (R2)(R6), F4
|
||||
FMOVD (R2)(R6<<3), F4
|
||||
RET
|
||||
|
||||
// func stores()
|
||||
TEXT ·stores(SB), NOSPLIT, $0-0
|
||||
MOVD R5, (R2)(R6<<3)
|
||||
MOVD R9, (R6)(R7.SXTX<<3)
|
||||
MOVD ZR, (R6)(R7.SXTX<<3)
|
||||
MOVW R8, (R2)(R3.UXTW<<2)
|
||||
MOVW R7, (R3)(R4.SXTW)
|
||||
MOVB R4, (R2)(R6.SXTX)
|
||||
MOVB R8, (R3)(R9.UXTW)
|
||||
MOVB R10, (R5)(R8)
|
||||
MOVB R10, (R5)(R8*1)
|
||||
MOVH R11, (R2)(R7.SXTW<<1)
|
||||
MOVH R5, (R1)(R2<<1)
|
||||
MOVH R7, (R2)(R5.SXTX<<1)
|
||||
MOVH R8, (R3)(R6.UXTW)
|
||||
MOVB R4, (R2)(R6.SXTX)
|
||||
FMOVS F4, (R2)(R6)
|
||||
FMOVS F4, (R2)(R6<<2)
|
||||
FMOVD F4, (R2)(R6)
|
||||
FMOVD F4, (R2)(R6<<3)
|
||||
RET
|
||||
|
||||
// func frames()
|
||||
TEXT ·frames(SB), NOSPLIT, $0-0
|
||||
MOVD (RSP)(R3), R1
|
||||
MOVD (R2)(RSP), R1
|
||||
MOVD (R2)(R2), R2
|
||||
MOVW (R2)(R3.SXTW), R1
|
||||
MOVBU (R1)(R2<<0), R3
|
||||
MOVH (R1)(R2<<1), R3
|
||||
RET
|
||||
@@ -37,6 +37,7 @@ func TestGroundTruthARM64(t *testing.T) {
|
||||
"../testdata/verify/atomics_arm64.s",
|
||||
"../testdata/verify/qmov_arm64.s",
|
||||
"../testdata/verify/splits_arm64.s",
|
||||
"../testdata/verify/regoffset_arm64.s",
|
||||
"../testdata/verify/crypto_arm64.s",
|
||||
"../testdata/verify/integer_arm64.s",
|
||||
"../testdata/verify/simd_arm64.s",
|
||||
|
||||
Reference in new issue
Block a user