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"]
|
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)
|
scale := a64LSScale(lt)
|
||||||
storeOpc := a64StoreOpc(lt)
|
storeOpc := a64StoreOpc(lt)
|
||||||
var opc int
|
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:
|
// 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.
|
// 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 {
|
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
|
// 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/atomics_arm64.s",
|
||||||
"../testdata/verify/qmov_arm64.s",
|
"../testdata/verify/qmov_arm64.s",
|
||||||
"../testdata/verify/splits_arm64.s",
|
"../testdata/verify/splits_arm64.s",
|
||||||
|
"../testdata/verify/regoffset_arm64.s",
|
||||||
"../testdata/verify/crypto_arm64.s",
|
"../testdata/verify/crypto_arm64.s",
|
||||||
"../testdata/verify/integer_arm64.s",
|
"../testdata/verify/integer_arm64.s",
|
||||||
"../testdata/verify/simd_arm64.s",
|
"../testdata/verify/simd_arm64.s",
|
||||||
|
|||||||
Reference in new issue
Block a user