1
2
3
4
5 package ssacompile
6
7 import (
8 "fmt"
9 "sort"
10 "testing"
11
12 "cmd/compile/internal/ssa/ssaop"
13 "cmd/compile/internal/types"
14 "cmd/internal/src"
15 )
16
17 func TestDeadStore(t *testing.T) {
18 c := testConfig(t)
19 ptrType := c.config.Types.BytePtr
20 t.Logf("PTRTYPE %v", ptrType)
21 fun := c.Fun("entry",
22 Bloc("entry",
23 Valu("start", ssaop.OpInitMem, types.TypeMem, 0, nil),
24 Valu("sb", ssaop.OpSB, c.config.Types.Uintptr, 0, nil),
25 Valu("v", ssaop.OpConstBool, c.config.Types.Bool, 1, nil),
26 Valu("addr1", ssaop.OpAddr, ptrType, 0, nil, "sb"),
27 Valu("addr2", ssaop.OpAddr, ptrType, 0, nil, "sb"),
28 Valu("addr3", ssaop.OpAddr, ptrType, 0, nil, "sb"),
29 Valu("zero1", ssaop.OpZero, types.TypeMem, 1, c.config.Types.Bool, "addr3", "start"),
30 Valu("store1", ssaop.OpStore, types.TypeMem, 0, c.config.Types.Bool, "addr1", "v", "zero1"),
31 Valu("store2", ssaop.OpStore, types.TypeMem, 0, c.config.Types.Bool, "addr2", "v", "store1"),
32 Valu("store3", ssaop.OpStore, types.TypeMem, 0, c.config.Types.Bool, "addr1", "v", "store2"),
33 Valu("store4", ssaop.OpStore, types.TypeMem, 0, c.config.Types.Bool, "addr3", "v", "store3"),
34 Goto("exit")),
35 Bloc("exit",
36 Exit("store3")))
37
38 CheckFunc(fun.f)
39 dse(fun.f)
40 CheckFunc(fun.f)
41
42 v1 := fun.values["store1"]
43 if v1.Op != ssaop.OpCopy {
44 t.Errorf("dead store not removed")
45 }
46
47 v2 := fun.values["zero1"]
48 if v2.Op != ssaop.OpCopy {
49 t.Errorf("dead store (zero) not removed")
50 }
51 }
52
53 func TestDeadStorePhi(t *testing.T) {
54
55 c := testConfig(t)
56 ptrType := c.config.Types.BytePtr
57 fun := c.Fun("entry",
58 Bloc("entry",
59 Valu("start", ssaop.OpInitMem, types.TypeMem, 0, nil),
60 Valu("sb", ssaop.OpSB, c.config.Types.Uintptr, 0, nil),
61 Valu("v", ssaop.OpConstBool, c.config.Types.Bool, 1, nil),
62 Valu("addr", ssaop.OpAddr, ptrType, 0, nil, "sb"),
63 Goto("loop")),
64 Bloc("loop",
65 Valu("phi", ssaop.OpPhi, types.TypeMem, 0, nil, "start", "store"),
66 Valu("store", ssaop.OpStore, types.TypeMem, 0, c.config.Types.Bool, "addr", "v", "phi"),
67 If("v", "loop", "exit")),
68 Bloc("exit",
69 Exit("store")))
70
71 CheckFunc(fun.f)
72 dse(fun.f)
73 CheckFunc(fun.f)
74 }
75
76 func TestDeadStoreTypes(t *testing.T) {
77
78
79
80
81 c := testConfig(t)
82 t1 := c.config.Types.UInt64.PtrTo()
83 t2 := c.config.Types.UInt32.PtrTo()
84 fun := c.Fun("entry",
85 Bloc("entry",
86 Valu("start", ssaop.OpInitMem, types.TypeMem, 0, nil),
87 Valu("sb", ssaop.OpSB, c.config.Types.Uintptr, 0, nil),
88 Valu("v", ssaop.OpConstBool, c.config.Types.Bool, 1, nil),
89 Valu("addr1", ssaop.OpAddr, t1, 0, nil, "sb"),
90 Valu("addr2", ssaop.OpAddr, t2, 0, nil, "sb"),
91 Valu("store1", ssaop.OpStore, types.TypeMem, 0, c.config.Types.Bool, "addr1", "v", "start"),
92 Valu("store2", ssaop.OpStore, types.TypeMem, 0, c.config.Types.Bool, "addr2", "v", "store1"),
93 Goto("exit")),
94 Bloc("exit",
95 Exit("store2")))
96
97 CheckFunc(fun.f)
98 cse(fun.f)
99 dse(fun.f)
100 CheckFunc(fun.f)
101
102 v := fun.values["store1"]
103 if v.Op == ssaop.OpCopy {
104 t.Errorf("store %s incorrectly removed", v)
105 }
106 }
107
108 func TestDeadStoreUnsafe(t *testing.T) {
109
110
111
112 c := testConfig(t)
113 ptrType := c.config.Types.UInt64.PtrTo()
114 fun := c.Fun("entry",
115 Bloc("entry",
116 Valu("start", ssaop.OpInitMem, types.TypeMem, 0, nil),
117 Valu("sb", ssaop.OpSB, c.config.Types.Uintptr, 0, nil),
118 Valu("v", ssaop.OpConstBool, c.config.Types.Bool, 1, nil),
119 Valu("addr1", ssaop.OpAddr, ptrType, 0, nil, "sb"),
120 Valu("store1", ssaop.OpStore, types.TypeMem, 0, c.config.Types.Int64, "addr1", "v", "start"),
121 Valu("store2", ssaop.OpStore, types.TypeMem, 0, c.config.Types.Bool, "addr1", "v", "store1"),
122 Goto("exit")),
123 Bloc("exit",
124 Exit("store2")))
125
126 CheckFunc(fun.f)
127 cse(fun.f)
128 dse(fun.f)
129 CheckFunc(fun.f)
130
131 v := fun.values["store1"]
132 if v.Op == ssaop.OpCopy {
133 t.Errorf("store %s incorrectly removed", v)
134 }
135 }
136
137 func TestDeadStoreSmallStructInit(t *testing.T) {
138 c := testConfig(t)
139 ptrType := c.config.Types.BytePtr
140 typ := types.NewStruct([]*types.Field{
141 types.NewField(src.NoXPos, &types.Sym{Name: "A"}, c.config.Types.Int),
142 types.NewField(src.NoXPos, &types.Sym{Name: "B"}, c.config.Types.Int),
143 })
144 name := c.Temp(typ)
145 fun := c.Fun("entry",
146 Bloc("entry",
147 Valu("start", ssaop.OpInitMem, types.TypeMem, 0, nil),
148 Valu("sp", ssaop.OpSP, c.config.Types.Uintptr, 0, nil),
149 Valu("zero", ssaop.OpConst64, c.config.Types.Int, 0, nil),
150 Valu("v6", ssaop.OpLocalAddr, ptrType, 0, name, "sp", "start"),
151 Valu("v3", ssaop.OpOffPtr, ptrType, 8, nil, "v6"),
152 Valu("v22", ssaop.OpOffPtr, ptrType, 0, nil, "v6"),
153 Valu("zerostore1", ssaop.OpStore, types.TypeMem, 0, c.config.Types.Int, "v22", "zero", "start"),
154 Valu("zerostore2", ssaop.OpStore, types.TypeMem, 0, c.config.Types.Int, "v3", "zero", "zerostore1"),
155 Valu("v8", ssaop.OpLocalAddr, ptrType, 0, name, "sp", "zerostore2"),
156 Valu("v23", ssaop.OpOffPtr, ptrType, 8, nil, "v8"),
157 Valu("v25", ssaop.OpOffPtr, ptrType, 0, nil, "v8"),
158 Valu("zerostore3", ssaop.OpStore, types.TypeMem, 0, c.config.Types.Int, "v25", "zero", "zerostore2"),
159 Valu("zerostore4", ssaop.OpStore, types.TypeMem, 0, c.config.Types.Int, "v23", "zero", "zerostore3"),
160 Goto("exit")),
161 Bloc("exit",
162 Exit("zerostore4")))
163
164 fun.f.Name = "smallstructinit"
165 CheckFunc(fun.f)
166 cse(fun.f)
167 dse(fun.f)
168 CheckFunc(fun.f)
169
170 v1 := fun.values["zerostore1"]
171 if v1.Op != ssaop.OpCopy {
172 t.Errorf("dead store not removed")
173 }
174 v2 := fun.values["zerostore2"]
175 if v2.Op != ssaop.OpCopy {
176 t.Errorf("dead store not removed")
177 }
178 }
179
180 func TestDeadStoreArrayGap(t *testing.T) {
181 c := testConfig(t)
182 ptr := c.config.Types.BytePtr
183 i64 := c.config.Types.Int64
184
185 typ := types.NewArray(i64, 5)
186 tmp := c.Temp(typ)
187
188 fun := c.Fun("entry",
189 Bloc("entry",
190 Valu("start", ssaop.OpInitMem, types.TypeMem, 0, nil),
191 Valu("sp", ssaop.OpSP, c.config.Types.Uintptr, 0, nil),
192
193 Valu("base", ssaop.OpLocalAddr, ptr, 0, tmp, "sp", "start"),
194
195 Valu("p0", ssaop.OpOffPtr, ptr, 0, nil, "base"),
196 Valu("p1", ssaop.OpOffPtr, ptr, 8, nil, "base"),
197 Valu("p2", ssaop.OpOffPtr, ptr, 16, nil, "base"),
198 Valu("p3", ssaop.OpOffPtr, ptr, 24, nil, "base"),
199 Valu("p4", ssaop.OpOffPtr, ptr, 32, nil, "base"),
200
201 Valu("one", ssaop.OpConst64, i64, 1, nil),
202 Valu("seven", ssaop.OpConst64, i64, 7, nil),
203 Valu("zero", ssaop.OpConst64, i64, 0, nil),
204
205 Valu("mem0", ssaop.OpZero, types.TypeMem, 40, typ, "base", "start"),
206
207 Valu("s0", ssaop.OpStore, types.TypeMem, 0, i64, "p0", "one", "mem0"),
208 Valu("s1", ssaop.OpStore, types.TypeMem, 0, i64, "p1", "seven", "s0"),
209 Valu("s2", ssaop.OpStore, types.TypeMem, 0, i64, "p3", "one", "s1"),
210 Valu("s3", ssaop.OpStore, types.TypeMem, 0, i64, "p4", "one", "s2"),
211 Valu("s4", ssaop.OpStore, types.TypeMem, 0, i64, "p2", "zero", "s3"),
212
213 Goto("exit")),
214 Bloc("exit",
215 Exit("s4")))
216
217 CheckFunc(fun.f)
218 dse(fun.f)
219 CheckFunc(fun.f)
220
221 if op := fun.values["mem0"].Op; op != ssaop.OpCopy {
222 t.Fatalf("dead Zero not removed: got %s, want OpCopy", op)
223 }
224 }
225
226 func TestShadowRanges(t *testing.T) {
227 t.Run("simple insert & contains", func(t *testing.T) {
228 var sr shadowRanges
229 sr.add(10, 20)
230
231 wantRanges(t, sr.ranges, [][2]uint16{{10, 20}})
232 if !sr.contains(12, 18) || !sr.contains(10, 20) {
233 t.Fatalf("contains failed after simple add")
234 }
235 if sr.contains(9, 11) || sr.contains(11, 21) {
236 t.Fatalf("contains erroneously true for non-contained range")
237 }
238 })
239
240 t.Run("merge overlapping", func(t *testing.T) {
241 var sr shadowRanges
242 sr.add(10, 20)
243 sr.add(15, 25)
244
245 wantRanges(t, sr.ranges, [][2]uint16{{10, 25}})
246 if !sr.contains(13, 24) {
247 t.Fatalf("contains should be true after merge")
248 }
249 })
250
251 t.Run("merge touching boundary", func(t *testing.T) {
252 var sr shadowRanges
253 sr.add(100, 150)
254
255 sr.add(150, 180)
256
257 wantRanges(t, sr.ranges, [][2]uint16{{100, 180}})
258 })
259
260 t.Run("union across several ranges", func(t *testing.T) {
261 var sr shadowRanges
262 sr.add(10, 20)
263 sr.add(30, 40)
264
265 sr.add(25, 35)
266
267 wantRanges(t, sr.ranges, [][2]uint16{{10, 20}, {25, 40}})
268
269
270 sr.add(5, 50)
271 wantRanges(t, sr.ranges, [][2]uint16{{5, 50}})
272 })
273
274 t.Run("disjoint intervals stay separate", func(t *testing.T) {
275 var sr shadowRanges
276 sr.add(10, 20)
277 sr.add(22, 30)
278
279 wantRanges(t, sr.ranges, [][2]uint16{{10, 20}, {22, 30}})
280
281 if sr.contains(15, 25) {
282 t.Fatalf("contains across two disjoint ranges should be false")
283 }
284 })
285
286 t.Run("large uint16 offsets still work", func(t *testing.T) {
287 var sr shadowRanges
288 sr.add(40000, 45000)
289
290 if !sr.contains(42000, 43000) {
291 t.Fatalf("contains failed for large uint16 values")
292 }
293 })
294
295 t.Run("out-of-bounds inserts ignored", func(t *testing.T) {
296 var sr shadowRanges
297 sr.add(10, 20)
298 sr.add(-5, 5)
299 sr.add(70000, 70010)
300
301 wantRanges(t, sr.ranges, [][2]uint16{{10, 20}})
302 })
303 }
304
305
306 func sortRanges(r []shadowRange) {
307 sort.Slice(r, func(i, j int) bool { return r[i].lo < r[j].lo })
308 }
309
310
311 func wantRanges(t *testing.T, got []shadowRange, want [][2]uint16) {
312 t.Helper()
313 sortRanges(got)
314
315 if len(got) != len(want) {
316 t.Fatalf("len(ranges)=%d, want %d (got=%v)", len(got), len(want), got)
317 }
318
319 for i, w := range want {
320 if got[i].lo != w[0] || got[i].hi != w[1] {
321 t.Fatalf("range %d = [%d,%d], want [%d,%d] (full=%v)",
322 i, got[i].lo, got[i].hi, w[0], w[1], got)
323 }
324 }
325 }
326
327 func BenchmarkDeadStore(b *testing.B) {
328 cfg := testConfig(b)
329 ptr := cfg.config.Types.BytePtr
330
331 f := cfg.Fun("entry",
332 Bloc("entry",
333 Valu("start", ssaop.OpInitMem, types.TypeMem, 0, nil),
334 Valu("sb", ssaop.OpSB, cfg.config.Types.Uintptr, 0, nil),
335 Valu("v", ssaop.OpConstBool, cfg.config.Types.Bool, 1, nil),
336 Valu("a1", ssaop.OpAddr, ptr, 0, nil, "sb"),
337 Valu("a2", ssaop.OpAddr, ptr, 0, nil, "sb"),
338 Valu("a3", ssaop.OpAddr, ptr, 0, nil, "sb"),
339 Valu("z1", ssaop.OpZero, types.TypeMem, 1, cfg.config.Types.Bool, "a3", "start"),
340 Valu("s1", ssaop.OpStore, types.TypeMem, 0, cfg.config.Types.Bool, "a1", "v", "z1"),
341 Valu("s2", ssaop.OpStore, types.TypeMem, 0, cfg.config.Types.Bool, "a2", "v", "s1"),
342 Valu("s3", ssaop.OpStore, types.TypeMem, 0, cfg.config.Types.Bool, "a1", "v", "s2"),
343 Valu("s4", ssaop.OpStore, types.TypeMem, 0, cfg.config.Types.Bool, "a3", "v", "s3"),
344 Goto("exit")),
345 Bloc("exit",
346 Exit("s3")))
347
348 runBench(b, func() {
349 dse(f.f)
350 })
351 }
352
353 func BenchmarkDeadStorePhi(b *testing.B) {
354 cfg := testConfig(b)
355 ptr := cfg.config.Types.BytePtr
356
357 f := cfg.Fun("entry",
358 Bloc("entry",
359 Valu("start", ssaop.OpInitMem, types.TypeMem, 0, nil),
360 Valu("sb", ssaop.OpSB, cfg.config.Types.Uintptr, 0, nil),
361 Valu("v", ssaop.OpConstBool, cfg.config.Types.Bool, 1, nil),
362 Valu("addr", ssaop.OpAddr, ptr, 0, nil, "sb"),
363 Goto("loop")),
364 Bloc("loop",
365 Valu("phi", ssaop.OpPhi, types.TypeMem, 0, nil, "start", "store"),
366 Valu("store", ssaop.OpStore, types.TypeMem, 0, cfg.config.Types.Bool, "addr", "v", "phi"),
367 If("v", "loop", "exit")),
368 Bloc("exit",
369 Exit("store")))
370
371 runBench(b, func() {
372 dse(f.f)
373 })
374 }
375
376 func BenchmarkDeadStoreTypes(b *testing.B) {
377 cfg := testConfig(b)
378
379 t1 := cfg.config.Types.UInt64.PtrTo()
380 t2 := cfg.config.Types.UInt32.PtrTo()
381
382 f := cfg.Fun("entry",
383 Bloc("entry",
384 Valu("start", ssaop.OpInitMem, types.TypeMem, 0, nil),
385 Valu("sb", ssaop.OpSB, cfg.config.Types.Uintptr, 0, nil),
386 Valu("v", ssaop.OpConstBool, cfg.config.Types.Bool, 1, nil),
387 Valu("a1", ssaop.OpAddr, t1, 0, nil, "sb"),
388 Valu("a2", ssaop.OpAddr, t2, 0, nil, "sb"),
389 Valu("s1", ssaop.OpStore, types.TypeMem, 0, cfg.config.Types.Bool, "a1", "v", "start"),
390 Valu("s2", ssaop.OpStore, types.TypeMem, 0, cfg.config.Types.Bool, "a2", "v", "s1"),
391 Goto("exit")),
392 Bloc("exit",
393 Exit("s2")))
394 cse(f.f)
395
396 runBench(b, func() {
397 dse(f.f)
398 })
399 }
400
401 func BenchmarkDeadStoreUnsafe(b *testing.B) {
402 cfg := testConfig(b)
403 ptr := cfg.config.Types.UInt64.PtrTo()
404 f := cfg.Fun("entry",
405 Bloc("entry",
406 Valu("start", ssaop.OpInitMem, types.TypeMem, 0, nil),
407 Valu("sb", ssaop.OpSB, cfg.config.Types.Uintptr, 0, nil),
408 Valu("v", ssaop.OpConstBool, cfg.config.Types.Bool, 1, nil),
409 Valu("a1", ssaop.OpAddr, ptr, 0, nil, "sb"),
410 Valu("s1", ssaop.OpStore, types.TypeMem, 0, cfg.config.Types.Int64, "a1", "v", "start"),
411 Valu("s2", ssaop.OpStore, types.TypeMem, 0, cfg.config.Types.Bool, "a1", "v", "s1"),
412 Goto("exit")),
413 Bloc("exit",
414 Exit("s2")))
415 cse(f.f)
416 runBench(b, func() {
417 dse(f.f)
418 })
419 }
420
421 func BenchmarkDeadStoreSmallStructInit(b *testing.B) {
422 cfg := testConfig(b)
423 ptr := cfg.config.Types.BytePtr
424
425 typ := types.NewStruct([]*types.Field{
426 types.NewField(src.NoXPos, &types.Sym{Name: "A"}, cfg.config.Types.Int),
427 types.NewField(src.NoXPos, &types.Sym{Name: "B"}, cfg.config.Types.Int),
428 })
429 tmp := cfg.Temp(typ)
430
431 f := cfg.Fun("entry",
432 Bloc("entry",
433 Valu("start", ssaop.OpInitMem, types.TypeMem, 0, nil),
434 Valu("sp", ssaop.OpSP, cfg.config.Types.Uintptr, 0, nil),
435 Valu("zero", ssaop.OpConst64, cfg.config.Types.Int, 0, nil),
436
437 Valu("v6", ssaop.OpLocalAddr, ptr, 0, tmp, "sp", "start"),
438 Valu("v3", ssaop.OpOffPtr, ptr, 8, nil, "v6"),
439 Valu("v22", ssaop.OpOffPtr, ptr, 0, nil, "v6"),
440 Valu("s1", ssaop.OpStore, types.TypeMem, 0, cfg.config.Types.Int, "v22", "zero", "start"),
441 Valu("s2", ssaop.OpStore, types.TypeMem, 0, cfg.config.Types.Int, "v3", "zero", "s1"),
442
443 Valu("v8", ssaop.OpLocalAddr, ptr, 0, tmp, "sp", "s2"),
444 Valu("v23", ssaop.OpOffPtr, ptr, 8, nil, "v8"),
445 Valu("v25", ssaop.OpOffPtr, ptr, 0, nil, "v8"),
446 Valu("s3", ssaop.OpStore, types.TypeMem, 0, cfg.config.Types.Int, "v25", "zero", "s2"),
447 Valu("s4", ssaop.OpStore, types.TypeMem, 0, cfg.config.Types.Int, "v23", "zero", "s3"),
448 Goto("exit")),
449 Bloc("exit",
450 Exit("s4")))
451 cse(f.f)
452
453 runBench(b, func() {
454 dse(f.f)
455 })
456 }
457
458 func BenchmarkDeadStoreLargeBlock(b *testing.B) {
459
460 const (
461 addrCount = 128
462
463 storesPerAddr = 8
464 )
465 cfg := testConfig(b)
466 ptrType := cfg.config.Types.BytePtr
467 boolType := cfg.config.Types.Bool
468
469 items := []interface{}{
470 Valu("start", ssaop.OpInitMem, types.TypeMem, 0, nil),
471 Valu("sb", ssaop.OpSB, cfg.config.Types.Uintptr, 0, nil),
472 Valu("v", ssaop.OpConstBool, boolType, 1, nil),
473 }
474
475 for i := 0; i < addrCount; i++ {
476 items = append(items,
477 Valu(fmt.Sprintf("addr%d", i), ssaop.OpAddr, ptrType, 0, nil, "sb"),
478 )
479 }
480
481 prev := "start"
482 for round := 0; round < storesPerAddr; round++ {
483 for i := 0; i < addrCount; i++ {
484 store := fmt.Sprintf("s_%03d_%d", i, round)
485 addr := fmt.Sprintf("addr%d", i)
486 items = append(items,
487 Valu(store, ssaop.OpStore, types.TypeMem, 0, boolType, addr, "v", prev),
488 )
489 prev = store
490 }
491 }
492
493 items = append(items, Goto("exit"))
494 entryBlk := Bloc("entry", items...)
495 exitBlk := Bloc("exit", Exit(prev))
496
497 f := cfg.Fun("stress", entryBlk, exitBlk)
498
499 runBench(b, func() {
500 dse(f.f)
501 })
502 }
503
504 func runBench(b *testing.B, build func()) {
505 b.ReportAllocs()
506 b.ResetTimer()
507 for i := 0; i < b.N; i++ {
508 build()
509 }
510 }
511
View as plain text