fix(asm): enforce the arm64 VLD/VST post-index contract

Assisted-by: GLM 5.3
This commit is contained in:
petrbalvin committed 2026-10-07 13:49:49 +02:00
1 parent 7bc80ccb54
commit 2385bb7069
3 files changed
+83 -12

No files matched your search

+33 -1
View File
@@ -4978,11 +4978,22 @@ func encodeARM64VLDST(mnem string, post uint32, ops []*ast.Operand) ([]byte, err
if off != 0 && post == 0 {
return nil, fmt.Errorf("%s: offset %d not supported, plain and list accesses take a plain (Rn) operand", mnem, off)
}
// The toolchain's addressing contract for the structure forms (asm7.go's
// class ladder): an index register exists only as the post-index
// increment, so it is illegal without .P and takes neither an offset nor
// an extend nor a shift beside it.
idx := ops[memIdx].Addr.Index
if idx != "" && post == 0 {
return nil, fmt.Errorf("%s: illegal combination: the register index is a post-index, it needs the .P spelling", mnem)
}
if idx != "" && (ops[memIdx].Addr.Shift != "" || ops[memIdx].Addr.HasOff) {
return nil, fmt.Errorf("%s: invalid extended register op: the post-index register takes no offset, extend or shift", mnem)
}
// The post-index increment: 11111 for an immediate offset, else the
// spelled (Rn)(Rm) register.
rm := 31
if post != 0 {
if idx := ops[memIdx].Addr.Index; idx != "" {
if idx != "" {
if rm = arm64RegNum(idx); rm < 0 {
return nil, fmt.Errorf("%s: invalid post-index register %q", mnem, idx)
}
@@ -5000,6 +5011,14 @@ func encodeARM64VLDST(mnem string, post uint32, ops []*ast.Operand) ([]byte, err
if !ok {
return nil, fmt.Errorf("%s: invalid arrangement %q", mnem, vs[0].arr)
}
// The immediate post-increment transfers one element per register,
// not a whole register (VLD3R.P 6(R15), [V15.H4,V16.H4,V17.H4]).
// An unspelled offset (or a spelled zero, the same class) is the
// implicit by-size increment the Rm=11111 encoding carries; any
// other spelled value must match.
if post != 0 && idx == "" && off != 0 && off != int64(n)*(1<<uint(size)) {
return nil, fmt.Errorf("%s: invalid post-increment offset %d, want %d", mnem, off, n*(1<<uint(size)))
}
w := a64VLDNReplicate[n] | q<<30 | size<<10 | uint32(rn)<<5 | uint32(vs[0].reg)
if post != 0 {
w |= 1<<23 | uint32(rm)<<16
@@ -5024,6 +5043,19 @@ func encodeARM64VLDST(mnem string, post uint32, ops []*ast.Operand) ([]byte, err
return nil, fmt.Errorf("%s: invalid arrangement %q", mnem, vs[0].arr)
}
n := len(vs)
// The immediate post-increment moves the whole list: count times the
// register width (16 bytes in the Q forms, 8 otherwise). An unspelled
// offset (or a spelled zero) is the implicit by-size increment
// (Rm=11111); any other spelled value must match.
if post != 0 && idx == "" && off != 0 {
regBytes := 8
if q != 0 {
regBytes = 16
}
if off != int64(n)*int64(regBytes) {
return nil, fmt.Errorf("%s: invalid post-increment offset %d, want %d", mnem, off, n*regBytes)
}
}
base := a64VLD1Base[n]
if !load {
base = a64VST1Base[n]
+50
View File
@@ -2267,6 +2267,56 @@ func TestArm64NoopVsNop(t *testing.T) {
}
}
// TestArm64VLDSTPostIndexContract pins the structure-load post-index rules
// against `go tool asm` (Go 1.27, arm64): the implicit by-size increment of
// an unspelled or zero offset (both load and replicate forms, the words
// taken from the toolchain's own listing), the register post-index that
// takes a bare register only, and the spelled immediate that must match the
// transferred bytes.
func TestArm64VLDSTPostIndexContract(t *testing.T) {
got := arm64Words(t,
"\tVLD1.P (R3), [V31.H8, V0.H8]\n"+ // implicit 2*16
"\tVLD1R.P (R1), [V9.B8]\n"+ // implicit 1*1
"\tVLD3R.P 6(R15), [V15.H4,V16.H4,V17.H4]\n"+ // 3*2 spelled
"\tVLD1.P (R8)(R20), [V21.B16, V22.B16]\n") // register post-index
want := []uint32{
0x4cdfa47f, // VLD1.P (R3), [V31.H8, V0.H8]
0x0ddfc029, // VLD1R.P (R1), [V9.B8]
0x0ddfe5ef, // VLD3R.P 6(R15), [V15.H4,V16.H4,V17.H4]
0x4cd4a115, // VLD1.P (R8)(R20), [V21.B16, V22.B16]
0xd65f03c0, // RET
}
if len(got) != len(want) {
t.Fatalf("word count = %d, want %d (got %08x)", len(got), len(want), got)
}
for i := range want {
if got[i] != want[i] {
t.Errorf("word %d = %08x, want %08x", i, got[i], want[i])
}
}
reject := []string{
"\tVLD1\t(R8)(R13), [V2.B16]\n", // register index without .P
"\tVLD1\t8(R9), [V2.B16]\n", // offset without .P
"\tVLD1.P\t8(R8)(R13), [V2.B16]\n", // offset beside the register index
"\tVLD1.P\t(R8)(R9.UXTW), [V2.B16]\n", // extended post-index register
"\tVLD1.P\t(R8)(R9<<2), [V2.B16]\n", // shifted post-index register
"\tVST1.P\t[V1.B16], (R8)(R9.UXTW)\n", // extended, store side
"\tVLD1.P\t17(R1), [V2.B16]\n", // 16 bytes transferred
"\tVLD1.P\t-16(R1), [V2.B16]\n", // negative increment
"\tVST1.P\t[V4.S4,V5.S4], 48(R1)\n", // 2*16, not 48
"\tVLD3R.P\t24(R15), [V15.H4,V16.H4,V17.H4]\n", // 3*2, not 24
}
for _, src := range reject {
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))
}
}
}
// 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
-11
View File
@@ -17,18 +17,7 @@ import (
// only shrinks: every tightening of the encoder moves spellings out of it,
// and a spelling reappearing here means a regression.
var arm64AcceptedErrorShapes = []string{
// VLD1/VST1 post-index shapes: the register post-index without the .P
// spelling, the .P immediate against the aggregate register size, the
// scaled register increment and the replicating register count.
"VLD1 (R8)(R13), [V2.B16]",
"VST1 [V1.B16], (R8)(R13)",
"VST1.P [V4.S4,V5.S4], 48(R1)",
"VST1.P [V4.S4], 8(R1)",
"VLD1.P 32(R1), [V8.S4, V9.S4, V10.S4]",
"VLD1.P 48(R1), [V7.S4, V8.S4, V9.S4, V10.S4]",
"VLD1.P (R8)(R9<<2), [V2.B16]",
"VST1.P [V1.B16], (R8)(R9<<1)",
"VLD3R.P 24(R15), [V15.H4,V16.H4,V17.H4]",
// REGTMP (R27) as an explicit operand the toolchain refuses, the pair
// split paths included.
"ADD $0x1234567, R27, R3",