fix(asm): place the arm64 literal pool the way the toolchain flushes it

Assisted-by: GLM 5.3
This commit is contained in:
petrbalvin committed 2026-10-07 13:49:49 +02:00
1 parent 4fc96decc4
commit 405e2427ed
3 files changed
+215 -68

No files matched your search

+152 -22
View File
@@ -2070,15 +2070,11 @@ func TestArm64ConRnRejections(t *testing.T) {
}
}
// TestArm64LogicalMaterialisationBranch pins a forward branch over the
// three-word logical materialisation against `go tool asm -S` (Go 1.27,
// arm64): the size pass must count the MOVZ/MOVK pair the encoder lays down,
// or the label offsets desynchronise from the bytes and the branch lands a
// word early.
func TestArm64LogicalMaterialisationBranch(t *testing.T) {
// The body closes with its own RET under the end label, so the file is
// parsed as written rather than through arm64Words' appended RET.
f, errs := parser.Parse("test_arm64.s", "#include \"textflag.h\"\n\nTEXT ·f(SB), NOSPLIT, $0-0\n\tCBZ\tR2, end\n\tTST\t$0x4900000049, R0\nend:\tRET\n")
// arm64WordsTail assembles a NOSPLIT leaf body exactly as written, adding no
// RET: the pool guard tests need bodies whose last statement is not a branch.
func arm64WordsTail(t *testing.T, body string) []uint32 {
t.Helper()
f, errs := parser.Parse("test_arm64.s", "#include \"textflag.h\"\n\nTEXT ·f(SB), NOSPLIT, $0-0\n"+body)
if len(errs) > 0 {
t.Fatalf("parse: %v", errs)
}
@@ -2086,21 +2082,155 @@ func TestArm64LogicalMaterialisationBranch(t *testing.T) {
if err != nil {
t.Fatalf("AssembleFileARM64: %v", err)
}
got := leWords(img.Code)
want := []uint32{
0xb4000082, // CBZ R2, +16 (four words ahead)
0xd280093b, // MOVZ $0x49, R27
0xf2c0093b, // MOVK $(0x49<<32), R27
0xea1b001f, // TST R27, R0
0xd65f03c0, // RET
return leWords(img.Code)
}
// TestArm64LiteralPool pins the offset literal pool against `go tool asm -S`
// output (Go 1.27, arm64): the PC-relative literal loads into REGTMP, the
// register-offset accesses, first-use ordering with value-only dedup, the
// entry widths (four-byte words for [0, 0x7FFFFFFF], eight-byte slots for
// the lacon displacements, the negatives and the beyond-32-bit values, with
// no alignment padding between entries) and the shared entries' widths
// following the entry rather than the referrer.
func TestArm64LiteralPool(t *testing.T) {
tests := []struct {
name string
body string
want []uint32
}{
{
name: "dedup and first-use order",
body: "\tMOVD\tR1, 0x1007000(R2)\n\tMOVD\tR1, 0x44332211(R2)\n\tMOVD\tR1, 0x1007000(R2)\n",
want: []uint32{
0x180000fb, 0xf83b6841, // LDR W27, pool0; MOVD R1, (R2)(R27)
0x180000db, 0xf83b6841, // LDR W27, pool1; MOVD R1, (R2)(R27)
0x1800007b, 0xf83b6841, // LDR W27, pool0; MOVD R1, (R2)(R27)
0xd65f03c0, // RET
0x01007000, 0x44332211, // WORD 0x1007000, WORD 0x44332211
},
},
{
name: "mixed widths, no padding, cross-width dedup",
body: "\tMOVB\tR1, 0x1000000(R2)\n\tMOVB\tR1, -0x1000000(R3)\n\tMOVB\tR1, 0x1001000(R4)\n\tMOVD\t$0x1000000(R7), R1\n",
want: []uint32{
0x1800013b, 0x383b6841, // LDR W27, pool0; MOVB R1, (R2)(R27)
0x5800011b, 0x383b6861, // LDR X27, pool1; MOVB R1, (R3)(R27)
0x1800011b, 0x383b6881, // LDR W27, pool2; MOVB R1, (R4)(R27)
0x1800007b, 0x8b3b60e1, // LDR W27, pool0 (lacon reuse); ADD R27.UXTX, R7, R1
0xd65f03c0, // RET
0x01000000, // WORD 0x1000000 (off 0)
0xff000000, 0xffffffff, // DWORD -0x1000000 (off 4, unpadded)
0x01001000, // WORD 0x1001000 (off 12)
},
},
{
name: "lacon entry takes the eight-byte slot",
body: "\tMOVD\t$0x1000000(R7), R1\n",
want: []uint32{
0x5800007b, 0x8b3b60e1, // LDR X27, pool; ADD R27.UXTX, R7, R1
0xd65f03c0, // RET
0x01000000, 0x00000000, // DWORD 0x1000000
},
},
{
name: "negative offsets pool as DWORD with LDR X",
body: "\tMOVB\tR1, -0x1000000(R2)\n\tMOVD\t$-0x1000000(R7), R1\n",
want: []uint32{
0x580000bb, 0x383b6841, // LDR X27, pool; MOVB R1, (R2)(R27)
0x5800007b, 0x8b3b60e1, // LDR X27, pool; ADD R27.UXTX, R7, R1
0xd65f03c0, // RET
0xff000000, 0xffffffff, // DWORD -0x1000000
},
},
{
name: "beyond 32-bit offsets",
body: "\tMOVD\tR1, 0x12345678901(R2)\n\tMOVB\tR2, 0x12345678901(R3)\n",
want: []uint32{
0x580000bb, 0xf83b6841, // LDR X27, pool; MOVD R1, (R2)(R27)
0x5800007b, 0x383b6862, // LDR X27, pool; MOVB R2, (R3)(R27)
0xd65f03c0, // RET
0x45678901, 0x00000123, // DWORD 0x12345678901
},
},
{
name: "pair offsets ride the pool",
body: "\tMOVD\tR1, 0x1000000(R2)\n\tLDP\t0x1000000(R2), (R1, R3)\n",
want: []uint32{
0x917ffc5b, 0xf9080361, // ADD $(4095<<12), R2, R27; MOVD R1, 64(R27)
0x1800009b, 0x8b3b605b, // LDR W27, pool; ADD R27.UXTX, R2, R27
0xa9400f61, // LDP (R27), (R1, R3)
0xd65f03c0, // RET
0x01000000, // WORD 0x1000000
},
},
}
if len(got) != len(want) {
t.Fatalf("word count = %d, want %d", len(got), len(want))
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := arm64Words(t, tt.body)
if len(got) != len(tt.want) {
t.Fatalf("word count = %d, want %d (got %08x)", len(got), len(tt.want), got)
}
for i := range tt.want {
if got[i] != tt.want[i] {
t.Errorf("word %d = %08x, want %08x", i, got[i], tt.want[i])
}
}
})
}
for i := range want {
if got[i] != want[i] {
t.Errorf("word %d = %08x, want %08x", i, got[i], want[i])
}
}
// TestArm64LiteralPoolGuard pins the flushpool guard: the word-zero UNDEF
// that keeps execution from falling into the pool when the last statement is
// not a branch. END closes the body without becoming an instruction, so it
// does not count; a trailing PCDATA is a real statement and takes the guard;
// RET and the morestack block's branch need none.
func TestArm64LiteralPoolGuard(t *testing.T) {
tests := []struct {
name string
body string
want []uint32
}{
{
name: "END without RET still guards",
body: "\tMOVB\tR1, 0x1000000(R2)\n\tEND\n",
want: []uint32{
0x1800007b, 0x383b6841, // LDR W27, pool; MOVB R1, (R2)(R27)
0x00000000, // UNDEF guard
0x01000000, // WORD 0x1000000
},
},
{
name: "trailing PCDATA keeps the guard",
body: "\tMOVD\tR1, 0x1007000(R2)\n\tRET\n\tPCDATA\t$0, $-1\n",
want: []uint32{
0x1800009b, 0xf83b6841, // LDR W27, pool; MOVD R1, (R2)(R27)
0xd65f03c0, // RET
0x00000000, // UNDEF guard
0x01007000, // WORD 0x1007000
},
},
{
name: "RET closes without a guard",
body: "\tMOVD\tR1, 0x1007000(R2)\n\tRET\n\tEND\n",
want: []uint32{
0x1800007b, 0xf83b6841, // LDR W27, pool; MOVD R1, (R2)(R27)
0xd65f03c0, // RET
0x01007000, // WORD 0x1007000
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
got := arm64WordsTail(t, tt.body)
if len(got) != len(tt.want) {
t.Fatalf("word count = %d, want %d (got %08x)", len(got), len(tt.want), got)
}
for i := range tt.want {
if got[i] != tt.want[i] {
t.Errorf("word %d = %08x, want %08x", i, got[i], tt.want[i])
}
}
})
}
}