1
2
3
4
5 package arm64
6
7 import (
8 "cmd/internal/obj"
9 "cmd/internal/obj/arm64"
10 "cmd/internal/src"
11 "testing"
12 )
13
14 func TestPairSpills(t *testing.T) {
15 movdLoad := func(dst, base int16, off int64) *obj.Prog {
16 return &obj.Prog{
17 As: arm64.AMOVD,
18 From: obj.Addr{Type: obj.TYPE_MEM, Reg: base, Offset: off, Name: obj.NAME_AUTO},
19 To: obj.Addr{Type: obj.TYPE_REG, Reg: dst},
20 }
21 }
22 movdStore := func(src, base int16, off int64) *obj.Prog {
23 return &obj.Prog{
24 As: arm64.AMOVD,
25 From: obj.Addr{Type: obj.TYPE_REG, Reg: src},
26 To: obj.Addr{Type: obj.TYPE_MEM, Reg: base, Offset: off, Name: obj.NAME_AUTO},
27 }
28 }
29
30 param := func(p *obj.Prog) *obj.Prog {
31 if p.From.Type == obj.TYPE_MEM {
32 p.From.Name = obj.NAME_PARAM
33 } else {
34 p.To.Name = obj.NAME_PARAM
35 }
36 return p
37 }
38 chain := func(progs ...*obj.Prog) *obj.Prog {
39 for i := 0; i < len(progs)-1; i++ {
40 progs[i].Link = progs[i+1]
41 }
42 return progs[0]
43 }
44 countAs := func(head *obj.Prog) map[obj.As]int {
45 m := map[obj.As]int{}
46 for p := head; p != nil; p = p.Link {
47 m[p.As]++
48 }
49 return m
50 }
51
52
53
54
55 type pairWant struct {
56 as obj.As
57 base int16
58 off int64
59 lo, hi int16
60 name obj.AddrName
61 }
62
63 tests := []struct {
64 name string
65 framesize int64
66 setup func() (*obj.Prog, []obj.JumpTable)
67 wantLDP int
68 wantSTP int
69 wantMOVD int
70 wantNOP int
71 wantPair *pairWant
72 }{
73 {
74 name: "adjacent LDRs fuse to LDP",
75 setup: func() (*obj.Prog, []obj.JumpTable) {
76 return chain(
77 movdLoad(arm64.REG_R0, arm64.REGSP, 16),
78 movdLoad(arm64.REG_R1, arm64.REGSP, 24),
79 ), nil
80 },
81 wantLDP: 1, wantNOP: 1,
82 wantPair: &pairWant{arm64.ALDP, arm64.REGSP, 16, arm64.REG_R0, arm64.REG_R1, obj.NAME_AUTO},
83 },
84 {
85 name: "adjacent STRs fuse to STP",
86 setup: func() (*obj.Prog, []obj.JumpTable) {
87 return chain(
88 movdStore(arm64.REG_R0, arm64.REGSP, 16),
89 movdStore(arm64.REG_R1, arm64.REGSP, 24),
90 ), nil
91 },
92 wantSTP: 1, wantNOP: 1,
93 wantPair: &pairWant{arm64.ASTP, arm64.REGSP, 16, arm64.REG_R0, arm64.REG_R1, obj.NAME_AUTO},
94 },
95 {
96 name: "reverse-order LDRs fuse to LDP",
97 setup: func() (*obj.Prog, []obj.JumpTable) {
98
99 return chain(
100 movdLoad(arm64.REG_R0, arm64.REGSP, 24),
101 movdLoad(arm64.REG_R1, arm64.REGSP, 16),
102 ), nil
103 },
104 wantLDP: 1, wantNOP: 1,
105 wantPair: &pairWant{arm64.ALDP, arm64.REGSP, 16, arm64.REG_R1, arm64.REG_R0, obj.NAME_AUTO},
106 },
107 {
108 name: "reverse-order STRs fuse to STP",
109 setup: func() (*obj.Prog, []obj.JumpTable) {
110
111 return chain(
112 movdStore(arm64.REG_R0, arm64.REGSP, 24),
113 movdStore(arm64.REG_R1, arm64.REGSP, 16),
114 ), nil
115 },
116 wantSTP: 1, wantNOP: 1,
117 wantPair: &pairWant{arm64.ASTP, arm64.REGSP, 16, arm64.REG_R1, arm64.REG_R0, obj.NAME_AUTO},
118 },
119 {
120 name: "PARAM pair fuses",
121 setup: func() (*obj.Prog, []obj.JumpTable) {
122 return chain(
123 param(movdLoad(arm64.REG_R0, arm64.REGSP, 16)),
124 param(movdLoad(arm64.REG_R1, arm64.REGSP, 24)),
125 ), nil
126 },
127 wantLDP: 1, wantNOP: 1,
128 wantPair: &pairWant{arm64.ALDP, arm64.REGSP, 16, arm64.REG_R0, arm64.REG_R1, obj.NAME_PARAM},
129 },
130 {
131 name: "mixed AUTO and PARAM do not fuse",
132 setup: func() (*obj.Prog, []obj.JumpTable) {
133 return chain(
134 movdLoad(arm64.REG_R0, arm64.REGSP, 16),
135 param(movdLoad(arm64.REG_R1, arm64.REGSP, 24)),
136 ), nil
137 },
138 wantMOVD: 2,
139 },
140 {
141 name: "non-adjacent offsets do not fuse",
142 setup: func() (*obj.Prog, []obj.JumpTable) {
143 return chain(
144 movdLoad(arm64.REG_R0, arm64.REGSP, 16),
145 movdLoad(arm64.REG_R1, arm64.REGSP, 32),
146 ), nil
147 },
148 wantMOVD: 2,
149 },
150 {
151 name: "different base registers do not fuse",
152 setup: func() (*obj.Prog, []obj.JumpTable) {
153 return chain(
154 movdLoad(arm64.REG_R0, arm64.REGSP, 16),
155 movdLoad(arm64.REG_R1, arm64.REG_R28, 24),
156 ), nil
157 },
158 wantMOVD: 2,
159 },
160 {
161 name: "load followed by store does not fuse",
162 setup: func() (*obj.Prog, []obj.JumpTable) {
163 return chain(
164 movdLoad(arm64.REG_R0, arm64.REGSP, 16),
165 movdStore(arm64.REG_R1, arm64.REGSP, 24),
166 ), nil
167 },
168 wantMOVD: 2,
169 },
170 {
171 name: "same destination register does not fuse",
172 setup: func() (*obj.Prog, []obj.JumpTable) {
173 return chain(
174 movdLoad(arm64.REG_R0, arm64.REGSP, 16),
175 movdLoad(arm64.REG_R0, arm64.REGSP, 24),
176 ), nil
177 },
178 wantMOVD: 2,
179 },
180 {
181 name: "first load writing the second's base does not fuse",
182 setup: func() (*obj.Prog, []obj.JumpTable) {
183
184
185
186
187 return chain(
188 movdLoad(arm64.REG_R1, arm64.REG_R1, 16),
189 movdLoad(arm64.REG_R2, arm64.REG_R1, 24),
190 ), nil
191 },
192 wantMOVD: 2,
193 },
194 {
195 name: "second load writing the base fuses",
196 setup: func() (*obj.Prog, []obj.JumpTable) {
197
198
199 return chain(
200 movdLoad(arm64.REG_R0, arm64.REG_R5, 16),
201 movdLoad(arm64.REG_R5, arm64.REG_R5, 24),
202 ), nil
203 },
204 wantLDP: 1, wantNOP: 1,
205 wantPair: &pairWant{arm64.ALDP, arm64.REG_R5, 16, arm64.REG_R0, arm64.REG_R5, obj.NAME_AUTO},
206 },
207 {
208 name: "store whose source is the base fuses",
209 setup: func() (*obj.Prog, []obj.JumpTable) {
210
211
212 return chain(
213 movdStore(arm64.REG_R5, arm64.REG_R5, 16),
214 movdStore(arm64.REG_R1, arm64.REG_R5, 24),
215 ), nil
216 },
217 wantSTP: 1, wantNOP: 1,
218 wantPair: &pairWant{arm64.ASTP, arm64.REG_R5, 16, arm64.REG_R5, arm64.REG_R1, obj.NAME_AUTO},
219 },
220 {
221 name: "skip when second instruction is a branch target",
222 setup: func() (*obj.Prog, []obj.JumpTable) {
223 p1 := movdLoad(arm64.REG_R0, arm64.REGSP, 16)
224 p2 := movdLoad(arm64.REG_R1, arm64.REGSP, 24)
225 br := &obj.Prog{As: arm64.AB, To: obj.Addr{Type: obj.TYPE_BRANCH}}
226 br.To.SetTarget(p2)
227 return chain(br, p1, p2), nil
228 },
229 wantMOVD: 2,
230 },
231 {
232 name: "skip when second instruction is a backward-branch target",
233 setup: func() (*obj.Prog, []obj.JumpTable) {
234
235
236
237 p1 := movdLoad(arm64.REG_R0, arm64.REGSP, 16)
238 p2 := movdLoad(arm64.REG_R1, arm64.REGSP, 24)
239 br := &obj.Prog{As: arm64.AB, To: obj.Addr{Type: obj.TYPE_BRANCH}}
240 br.To.SetTarget(p2)
241 return chain(p1, p2, br), nil
242 },
243 wantMOVD: 2,
244 },
245 {
246 name: "skip when second instruction is a jump-table target",
247 setup: func() (*obj.Prog, []obj.JumpTable) {
248 p1 := movdLoad(arm64.REG_R0, arm64.REGSP, 16)
249 p2 := movdLoad(arm64.REG_R1, arm64.REGSP, 24)
250 return chain(p1, p2), []obj.JumpTable{{Targets: []*obj.Prog{p2}}}
251 },
252 wantMOVD: 2,
253 },
254 {
255 name: "skip when second instruction is a statement boundary",
256 setup: func() (*obj.Prog, []obj.JumpTable) {
257
258
259
260
261 var tab src.PosTable
262 pos := tab.XPos(src.MakePos(src.NewFileBase("f.go", "f.go"), 1, 1))
263 p1 := movdLoad(arm64.REG_R0, arm64.REGSP, 16)
264 p2 := movdLoad(arm64.REG_R1, arm64.REGSP, 24)
265 p2.Pos = pos.WithIsStmt()
266 return chain(p1, p2), nil
267 },
268 wantMOVD: 2,
269 },
270 {
271 name: "at the encodable bound fuses",
272 setup: func() (*obj.Prog, []obj.JumpTable) {
273
274 return chain(
275 movdLoad(arm64.REG_R0, arm64.REGSP, 496),
276 movdLoad(arm64.REG_R1, arm64.REGSP, 504),
277 ), nil
278 },
279 wantLDP: 1, wantNOP: 1,
280 wantPair: &pairWant{arm64.ALDP, arm64.REGSP, 496, arm64.REG_R0, arm64.REG_R1, obj.NAME_AUTO},
281 },
282 {
283 name: "resolved offset out of LDP range does not fuse",
284 setup: func() (*obj.Prog, []obj.JumpTable) {
285
286 return chain(
287 movdLoad(arm64.REG_R0, arm64.REGSP, 504),
288 movdLoad(arm64.REG_R1, arm64.REGSP, 512),
289 ), nil
290 },
291 wantMOVD: 2,
292 },
293 {
294 name: "large frame pushes resolved offset out of range",
295 framesize: 1024,
296 setup: func() (*obj.Prog, []obj.JumpTable) {
297
298
299
300 return chain(
301 movdLoad(arm64.REG_R0, arm64.REGSP, 16),
302 movdLoad(arm64.REG_R1, arm64.REGSP, 24),
303 ), nil
304 },
305 wantMOVD: 2,
306 },
307 {
308 name: "deep spill slots fuse in a large frame",
309 framesize: 1024,
310 setup: func() (*obj.Prog, []obj.JumpTable) {
311
312
313 return chain(
314 movdLoad(arm64.REG_R0, arm64.REGSP, -528),
315 movdLoad(arm64.REG_R1, arm64.REGSP, -520),
316 ), nil
317 },
318 wantLDP: 1, wantNOP: 1,
319 wantPair: &pairWant{arm64.ALDP, arm64.REGSP, -528, arm64.REG_R0, arm64.REG_R1, obj.NAME_AUTO},
320 },
321 {
322 name: "misaligned offsets do not fuse",
323 setup: func() (*obj.Prog, []obj.JumpTable) {
324 return chain(
325 movdLoad(arm64.REG_R0, arm64.REGSP, -12),
326 movdLoad(arm64.REG_R1, arm64.REGSP, -4),
327 ), nil
328 },
329 wantMOVD: 2,
330 },
331 {
332 name: "three adjacent loads fuse greedily",
333 setup: func() (*obj.Prog, []obj.JumpTable) {
334 return chain(
335 movdLoad(arm64.REG_R0, arm64.REGSP, 16),
336 movdLoad(arm64.REG_R1, arm64.REGSP, 24),
337 movdLoad(arm64.REG_R2, arm64.REGSP, 32),
338 ), nil
339 },
340 wantLDP: 1, wantNOP: 1, wantMOVD: 1,
341 },
342 {
343 name: "intervening instruction blocks fusion",
344 setup: func() (*obj.Prog, []obj.JumpTable) {
345 return chain(
346 movdLoad(arm64.REG_R0, arm64.REGSP, 16),
347 &obj.Prog{As: arm64.AHINT},
348 movdLoad(arm64.REG_R1, arm64.REGSP, 24),
349 ), nil
350 },
351 wantMOVD: 2,
352 },
353 {
354 name: "pre-indexed addressing does not fuse",
355 setup: func() (*obj.Prog, []obj.JumpTable) {
356 p1 := movdLoad(arm64.REG_R0, arm64.REGSP, 16)
357 p1.Scond = arm64.C_XPRE
358 return chain(p1, movdLoad(arm64.REG_R1, arm64.REGSP, 24)), nil
359 },
360 wantMOVD: 2,
361 },
362 {
363 name: "post-indexed addressing does not fuse",
364 setup: func() (*obj.Prog, []obj.JumpTable) {
365 p1 := movdLoad(arm64.REG_R0, arm64.REGSP, 16)
366 p1.Scond = arm64.C_XPOST
367 return chain(p1, movdLoad(arm64.REG_R1, arm64.REGSP, 24)), nil
368 },
369 wantMOVD: 2,
370 },
371 }
372
373 for _, tt := range tests {
374 t.Run(tt.name, func(t *testing.T) {
375 head, jumpTables := tt.setup()
376 pairSpills(head, tt.framesize, jumpTables)
377 got := countAs(head)
378 if got[arm64.ALDP] != tt.wantLDP {
379 t.Errorf("ALDP count = %d, want %d", got[arm64.ALDP], tt.wantLDP)
380 }
381 if got[arm64.ASTP] != tt.wantSTP {
382 t.Errorf("ASTP count = %d, want %d", got[arm64.ASTP], tt.wantSTP)
383 }
384 if got[arm64.AMOVD] != tt.wantMOVD {
385 t.Errorf("AMOVD count = %d, want %d", got[arm64.AMOVD], tt.wantMOVD)
386 }
387 if got[obj.ANOP] != tt.wantNOP {
388 t.Errorf("ANOP count = %d, want %d", got[obj.ANOP], tt.wantNOP)
389 }
390 if w := tt.wantPair; w != nil {
391 var fused *obj.Prog
392 for p := head; p != nil; p = p.Link {
393 if p.As == arm64.ALDP || p.As == arm64.ASTP {
394 fused = p
395 break
396 }
397 }
398 if fused == nil {
399 t.Fatal("no LDP/STP emitted")
400 }
401 mem, regs := &fused.From, &fused.To
402 if fused.As == arm64.ASTP {
403 regs, mem = &fused.From, &fused.To
404 }
405 if fused.As != w.as {
406 t.Errorf("fused As = %v, want %v", fused.As, w.as)
407 }
408 if mem.Type != obj.TYPE_MEM || mem.Reg != w.base || mem.Offset != w.off || mem.Name != w.name {
409 t.Errorf("memory operand = {Type %v, Reg %v, Offset %d, Name %v}, want {TYPE_MEM, %v, %d, %v}",
410 mem.Type, mem.Reg, mem.Offset, mem.Name, w.base, w.off, w.name)
411 }
412 if regs.Type != obj.TYPE_REGREG || regs.Reg != w.lo || regs.Offset != int64(w.hi) {
413 t.Errorf("register pair = {Type %v, Reg %v, Offset %v}, want {TYPE_REGREG, %v, %v}",
414 regs.Type, regs.Reg, regs.Offset, w.lo, w.hi)
415 }
416 }
417 })
418 }
419 }
420
View as plain text