fix(asm): enforce the arm64 VLD/VST post-index contract
Assisted-by: GLM 5.3
This commit is contained in:
1 parent
7bc80ccb54
commit
2385bb7069
3 files changed
+83
-12
No files matched your search
+33
-1
@@ -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]
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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",
|
||||
|
||||
Reference in new issue
Block a user