1
2
3
4
5 package ssacompile
6
7
8
9
10
11
12
13
14
15
16
17
18 import (
19 "cmd/compile/internal/base"
20 "cmd/compile/internal/ir"
21 "cmd/compile/internal/ssa"
22 "cmd/compile/internal/ssa/ssaop"
23 "cmd/compile/internal/types"
24 "internal/buildcfg"
25 "slices"
26 )
27
28 type useType int
29
30 const (
31 useLoad useType = 1 << iota
32 useStore
33 useOffset
34 useCopy
35 useZero
36 useMove
37 useOther
38 )
39
40
41
42
43
44 type useTable struct {
45 useInfo
46 }
47
48
49 func (u *useTable) of(v *ssa.Value) []*ssa.Value {
50 if int(v.ID) >= len(u.starts) {
51 return nil
52 }
53 return u.get(v)
54 }
55
56
57 func classifyUses(v *ssa.Value, uses *useTable) useType {
58 q := []*ssa.Value{v}
59 var u useType
60 for len(q) > 0 {
61 curr := q[len(q)-1]
62 q = q[:len(q)-1]
63 for _, use := range uses.of(curr) {
64 switch use.Op {
65 case ssaop.OpCopy:
66 u |= useCopy
67 q = append(q, use)
68 case ssaop.OpOffPtr:
69 u |= useOffset
70 q = append(q, use)
71 case ssaop.OpLoad:
72 u |= useLoad
73 case ssaop.OpStore:
74 if curr == use.Args[1] {
75
76 u |= useOther
77 } else {
78 u |= useStore
79 }
80 case ssaop.OpZero:
81 u |= useZero
82 case ssaop.OpMove:
83 u |= useMove
84 default:
85
86
87 u |= useOther
88 }
89 }
90 }
91 return u
92 }
93
94
95
96 type variableDemographic struct {
97
98
99 ls bool
100
101
102
103 lszmco bool
104 varDefs []*ssa.Value
105 }
106
107
108
109
110
111
112
113
114
115 func variableDemographics(f *ssa.Func) (
116 demographics map[ssa.Aux]*variableDemographic,
117 localAddrs []*ssa.Value,
118 u *useTable) {
119
120
121
122 found := false
123 for _, b := range f.Blocks {
124 for _, c := range b.ControlValues() {
125 if c.Op == ssaop.OpOffPtr || c.Op == ssaop.OpLocalAddr {
126 f.Fatalf("unexpected pointer value in block control")
127 }
128 }
129 for _, v := range b.Values {
130 if v.Op == ssaop.OpLocalAddr {
131 if n := v.Aux.(*ir.Name); n.Class == ir.PAUTO || isABIInternalParam(f, n) {
132 found = true
133 break
134 }
135 }
136 }
137 if found {
138 break
139 }
140 }
141 if !found {
142 return nil, nil, nil
143 }
144
145
146
147 u = &useTable{uses(f)}
148 varLives := map[ssa.Aux]bool{}
149 demographics = make(map[ssa.Aux]*variableDemographic)
150 for _, b := range f.Blocks {
151 for _, v := range b.Values {
152 switch v.Op {
153 case ssaop.OpVarLive:
154 varLives[v.Aux] = true
155 case ssaop.OpVarDef:
156 d := demographics[v.Aux]
157 if d == nil {
158 d = &variableDemographic{ls: true, lszmco: true}
159 demographics[v.Aux] = d
160 }
161 d.varDefs = append(d.varDefs, v)
162 }
163 }
164 }
165
166 for _, b := range f.Blocks {
167 for _, v := range b.Values {
168 if v.Op == ssaop.OpLocalAddr {
169 if n := v.Aux.(*ir.Name); n.Class == ir.PAUTO || isABIInternalParam(f, n) {
170 ut := classifyUses(v, u)
171 d := demographics[n]
172 if d == nil {
173 d = &variableDemographic{ls: true, lszmco: true}
174 demographics[n] = d
175 }
176 if ut&^(useLoad|useStore) != 0 {
177 d.ls = false
178 }
179 if ut&useOther != 0 {
180 d.lszmco = false
181 }
182 localAddrs = append(localAddrs, v)
183 }
184 }
185 }
186 }
187
188
189
190 for n := range varLives {
191 if d := demographics[n]; d != nil {
192 d.ls = false
193 d.lszmco = false
194 }
195 }
196 return
197 }
198
199 var reinterpretOpMap = map[[2]types.Kind]ssaop.Op{
200 {types.TUINT32, types.TFLOAT32}: ssaop.OpI32AsF32,
201 {types.TINT32, types.TFLOAT32}: ssaop.OpI32AsF32,
202 {types.TFLOAT32, types.TUINT32}: ssaop.OpF32AsI32,
203 {types.TFLOAT32, types.TINT32}: ssaop.OpF32AsI32,
204 {types.TUINT64, types.TFLOAT64}: ssaop.OpI64AsF64,
205 {types.TINT64, types.TFLOAT64}: ssaop.OpI64AsF64,
206 {types.TFLOAT64, types.TUINT64}: ssaop.OpF64AsI64,
207 {types.TFLOAT64, types.TINT64}: ssaop.OpF64AsI64,
208 }
209
210
211
212 func reinterpretOp(t1, t2 *types.Type) ssaop.Op {
213 if buildcfg.GOARCH != "amd64" {
214
215 return ssaop.OpInvalid
216 }
217 if op, ok := reinterpretOpMap[[2]types.Kind{t1.Kind(), t2.Kind()}]; ok {
218 return op
219 }
220
221 return ssaop.OpInvalid
222 }
223
224
225
226 func copyCompatibleType(t1, t2 *types.Type) bool {
227 if t1.Size() != t2.Size() {
228 return false
229 }
230 if t1.IsInteger() {
231 return t2.IsInteger()
232 }
233 if ssa.IsPtr(t1) {
234 return ssa.IsPtr(t2)
235 }
236 if t1.IsSIMD() {
237
238
239 return t2.IsSIMD()
240 }
241 return t1.Compare(t2) == types.CMPeq
242 }
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259 func sortLoadStores(bb *ssa.Block, loadStores []*ssa.Value, memoryOrders map[ssa.ID]map[ssa.ID]int) []*ssa.Value {
260 memOrder, ok := memoryOrders[bb.ID]
261 if !ok {
262 memOrder = make(map[ssa.ID]int)
263 var computeDepth func(v *ssa.Value) int
264 computeDepth = func(v *ssa.Value) int {
265 if d, ok := memOrder[v.ID]; ok {
266 return d
267 }
268
269 if v.Block != bb || v.Op == ssaop.OpInitMem || v.Op == ssaop.OpPhi {
270 memOrder[v.ID] = 0
271 return 0
272 }
273
274 d := computeDepth(v.MemoryArg()) + 2
275 memOrder[v.ID] = d
276 return d
277 }
278 for _, v := range bb.Values {
279 if v.Type.IsMemory() {
280 computeDepth(v)
281 }
282 }
283 memoryOrders[bb.ID] = memOrder
284 }
285 key := func(v *ssa.Value) int {
286 if v.Op == ssaop.OpLoad {
287 return memOrder[v.MemoryArg().ID] + 1
288 }
289 return memOrder[v.ID]
290 }
291 slices.SortFunc(loadStores, func(a, b *ssa.Value) int {
292 return key(a) - key(b)
293 })
294 return loadStores
295 }
296
297 func mem2reg(f *ssa.Func) {
298 changed := false
299 if base.Flag.N != 0 {
300 return
301 }
302 st := f.NewStats("mem2reg")
303
304
305 demographics, localAddrs, uses := variableDemographics(f)
306 if uses == nil {
307
308 return
309 }
310 defer uses.free(f)
311 memoryOrders := make(map[ssa.ID]map[ssa.ID]int)
312
313
314
315 varGrouped := make(map[*ir.Name][]*ssa.Value)
316 for _, v := range localAddrs {
317
318
319
320
321 if d, ok := demographics[v.Aux]; !ok || !d.ls {
322 continue
323 }
324 n := v.Aux.(*ir.Name)
325 if n.Class != ir.PAUTO {
326
327 continue
328 }
329 varGrouped[n] = append(varGrouped[n], v)
330 }
331 namesOrdered := []*ir.Name{}
332 for n, vag := range varGrouped {
333 namesOrdered = append(namesOrdered, n)
334 slices.SortFunc(vag, func(a, b *ssa.Value) int { return int(a.ID - b.ID) })
335 }
336 slices.SortFunc(namesOrdered, func(a, b *ir.Name) int {
337 return int(varGrouped[a][0].ID - varGrouped[b][0].ID)
338 })
339
340 removeStore := func(v *ssa.Value) {
341 changed = true
342 v.SetArgs1(v.MemoryArg())
343 v.Aux = nil
344 v.AuxInt = 0
345 v.Op = ssaop.OpCopy
346 }
347
348
349
350
351 addNamedValue := func(n *ir.Name, v *ssa.Value) {
352 if ir.IsAutoTmp(n) {
353 return
354 }
355 loc := ssa.LocalSlot{N: n, Type: n.Type(), Off: 0}
356 values, ok := f.NamedValues[loc]
357 if !ok {
358 f.Names = append(f.Names, loc)
359 }
360 f.NamedValues[loc] = append(values, v)
361 }
362 type loadCandidate struct {
363 l *ssa.Value
364 v *ssa.Value
365
366
367 reinterpret ssaop.Op
368 }
369 replaceLoad := func(n *ir.Name, lc loadCandidate) {
370 changed = true
371 if lc.reinterpret == ssaop.OpInvalid {
372 if !copyCompatibleType(lc.l.Type, lc.v.Type) {
373 f.Fatalf("mem2reg: load is being replaced by a value of an incompatible type")
374 }
375 lc.l.SetArgs1(lc.v)
376 } else {
377
378
379 lc.l.SetArgs1(lc.l.Block.NewValue1(lc.l.Pos, lc.reinterpret, lc.l.Type, lc.v))
380 }
381 lc.l.Aux = nil
382 lc.l.AuxInt = 0
383 lc.l.Op = ssaop.OpCopy
384
385 addNamedValue(n, lc.l)
386 }
387
388
389 storeCands := []*ssa.Value{}
390 loadCands := []loadCandidate{}
391 vaUses := []*ssa.Value{}
392 NextVar:
393 for _, n := range namesOrdered {
394 vag := varGrouped[n]
395 var block *ssa.Block
396 for _, va := range vag {
397
398 for _, use := range uses.of(va) {
399 if block == nil {
400 block = use.Block
401 }
402 if use.Block != block {
403
404 continue NextVar
405 }
406 }
407 }
408
409 storeCands = storeCands[:0]
410 loadCands = loadCands[:0]
411 vaUses = vaUses[:0]
412 for _, va := range vag {
413 vaUses = append(vaUses, uses.of(va)...)
414 }
415 vaUses = sortLoadStores(block, vaUses, memoryOrders)
416 var curV *ssa.Value
417 for _, v := range vaUses {
418 switch v.Op {
419 case ssaop.OpLoad:
420 if curV == nil {
421 f.Fatalf("mem2reg sees a load from an auto variable before any store")
422 }
423 reinter := ssaop.OpInvalid
424 if !copyCompatibleType(v.Type, curV.Type) {
425 reinter = reinterpretOp(curV.Type, v.Type)
426 if reinter == ssaop.OpInvalid {
427
428
429 st.Record("incompatible types in single block case", 1)
430 delete(varGrouped, n)
431 continue NextVar
432 }
433 }
434 loadCands = append(loadCands, loadCandidate{
435 l: v,
436 v: curV,
437 reinterpret: reinter,
438 })
439 case ssaop.OpStore:
440 curV = v.Args[1]
441 storeCands = append(storeCands, v)
442 default:
443 f.Fatalf("should only has load or store uses")
444 }
445 }
446
447 for _, v := range storeCands {
448
449 addNamedValue(n, v.Args[1])
450 removeStore(v)
451 }
452 for _, v := range demographics[n].varDefs {
453
454 removeStore(v)
455 }
456 for _, lc := range loadCands {
457
458 replaceLoad(n, lc)
459 }
460
461
462
463 n.SetAddrtaken(false)
464
465 if f.Pass.Debug > 1 {
466 f.Warnl(n.Pos(), "promoted %v in single block case", n)
467 }
468 delete(varGrouped, n)
469 st.Record("promoted variable in single block case", 1)
470 }
471
472 if len(varGrouped) == 0 {
473 if changed {
474 deadcode(f)
475 }
476 return
477 }
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510 crossBlockBudget := f.Pass.Val("crossblockbudget", 20)
511
512
513 phiBlocksByName := map[*ir.Name][]*ssa.Block{}
514 {
515 var seeds []*ssa.Block
516 seen := map[*ssa.Block]struct{}{}
517 PlacementLoop:
518 for _, n := range namesOrdered {
519 vag, ok := varGrouped[n]
520 if !ok {
521 continue
522 }
523 seeds = seeds[:0]
524 clear(seen)
525 for _, va := range vag {
526 for _, use := range uses.of(va) {
527 if use.Op != ssaop.OpStore {
528 continue
529 }
530 if _, dup := seen[use.Block]; dup {
531 continue
532 }
533 seen[use.Block] = struct{}{}
534 seeds = append(seeds, use.Block)
535 if int64(len(seeds)) >= crossBlockBudget {
536
537 st.Record("too many def blocks", 1)
538 delete(varGrouped, n)
539 continue PlacementLoop
540 }
541 }
542 }
543
544
545
546 slices.SortFunc(seeds, func(a, b *ssa.Block) int { return int(a.ID - b.ID) })
547 var phiBlocks []*ssa.Block
548 for y := range f.IterDomFrontierPlus(slices.Values(seeds)) {
549 if int64(len(phiBlocks)) >= crossBlockBudget {
550
551 st.Record("too many cascading def blocks", 1)
552 delete(varGrouped, n)
553 continue PlacementLoop
554 }
555 phiBlocks = append(phiBlocks, y)
556 }
557 phiBlocksByName[n] = phiBlocks
558 }
559 }
560
561
562
563
564 virtualPhi := &ssa.Value{}
565
566
567
568 type changeLogEnt struct {
569 name *ir.Name
570 valBefore *ssa.Value
571 }
572
573
574 badNames := map[*ir.Name]struct{}{}
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589 renameWalk := func(
590 phiAt func(b *ssa.Block, n *ir.Name) *ssa.Value,
591 onPhiArg func(n *ir.Name, phi *ssa.Value, predIdx int, v *ssa.Value),
592 onLoad func(n *ir.Name, use, incoming *ssa.Value) bool,
593 onStore func(n *ir.Name, use *ssa.Value) bool,
594 ) {
595 incomingV := map[*ir.Name]*ssa.Value{}
596 changeLog := []changeLogEnt{}
597 type work struct {
598
599
600 e ssa.Edge
601
602
603 changeLogSnapShot int
604 }
605 worklist := []work{{ssa.Edge{B: f.Entry, I: -1}, 0}}
606 visited := map[*ssa.Block]struct{}{}
607 for len(worklist) > 0 {
608 curWork := worklist[len(worklist)-1]
609 worklist = worklist[:len(worklist)-1]
610
611 for len(changeLog) > curWork.changeLogSnapShot {
612 curLog := changeLog[len(changeLog)-1]
613 changeLog = changeLog[:len(changeLog)-1]
614 incomingV[curLog.name] = curLog.valBefore
615 }
616 curB := curWork.e.B
617
618 for _, n := range namesOrdered {
619 if _, ok := varGrouped[n]; !ok {
620 continue
621 }
622 if _, bad := badNames[n]; bad {
623 continue
624 }
625 phi := phiAt(curB, n)
626 if phi == nil {
627 continue
628 }
629 if curWork.e.I == -1 {
630 f.Fatalf("phi placed at entry in mem2reg")
631 }
632 if iV := incomingV[n]; iV != nil {
633
634
635 onPhiArg(n, phi, curWork.e.I, iV)
636 }
637 changeLog = append(changeLog, changeLogEnt{n, incomingV[n]})
638 incomingV[n] = phi
639 }
640
641 if _, ok := visited[curB]; ok {
642 continue
643 }
644 visited[curB] = struct{}{}
645 NameLoop:
646 for _, n := range namesOrdered {
647 vag, ok := varGrouped[n]
648 if !ok {
649 continue
650 }
651 if _, bad := badNames[n]; bad {
652 continue
653 }
654 vaUses := []*ssa.Value{}
655 for _, va := range vag {
656 for _, u := range uses.of(va) {
657 if u.Block == curB {
658 vaUses = append(vaUses, u)
659 }
660 }
661 }
662 vaUses = sortLoadStores(curB, vaUses, memoryOrders)
663 for _, use := range vaUses {
664 switch use.Op {
665 case ssaop.OpLoad:
666 if !onLoad(n, use, incomingV[n]) {
667 continue NameLoop
668 }
669 case ssaop.OpStore:
670 val := use.Args[1]
671 if !onStore(n, use) {
672 continue NameLoop
673 }
674 changeLog = append(changeLog, changeLogEnt{n, incomingV[n]})
675 incomingV[n] = val
676 default:
677 f.Fatalf("should not have uses other than load and store")
678 }
679 }
680 }
681 for _, e := range curB.Succs {
682 worklist = append(worklist, work{e, len(changeLog)})
683 }
684 }
685 }
686
687
688 {
689 phiBlockSet := map[*ir.Name]map[*ssa.Block]struct{}{}
690 for n, bs := range phiBlocksByName {
691 m := make(map[*ssa.Block]struct{}, len(bs))
692 for _, b := range bs {
693 m[b] = struct{}{}
694 }
695 phiBlockSet[n] = m
696 }
697 renameWalk(
698
699 func(b *ssa.Block, n *ir.Name) *ssa.Value {
700 if _, ok := phiBlockSet[n][b]; ok {
701 return virtualPhi
702 }
703 return nil
704 },
705
706 func(n *ir.Name, phi *ssa.Value, predIdx int, v *ssa.Value) {
707 t := v.Type
708 if v == virtualPhi {
709 t = n.Type()
710 }
711 if !copyCompatibleType(n.Type(), t) {
712 st.Record("incompatible phi and arg types in cross block cases", 1)
713 badNames[n] = struct{}{}
714 }
715 },
716
717 func(n *ir.Name, use, incoming *ssa.Value) bool {
718 if incoming == nil {
719 f.Fatalf("no incoming value for a load in mem2reg")
720 }
721 t := incoming.Type
722 if incoming == virtualPhi {
723 t = n.Type()
724 }
725 if !copyCompatibleType(use.Type, t) {
726 st.Record("incompatible types in cross block cases", 1)
727 badNames[n] = struct{}{}
728 return false
729 }
730 return true
731 },
732
733 func(n *ir.Name, use *ssa.Value) bool {
734 if !copyCompatibleType(use.Args[1].Type, n.Type()) {
735 st.Record("incompatible store types in cross block cases", 1)
736 badNames[n] = struct{}{}
737 return false
738 }
739 return true
740 },
741 )
742 }
743
744
745 phiToName := map[*ssa.Value]*ir.Name{}
746
747 newPhisByBlockByName := make([]map[*ir.Name]*ssa.Value, f.NumBlocks())
748 for _, n := range namesOrdered {
749 if _, ok := varGrouped[n]; !ok {
750 continue
751 }
752 if _, bad := badNames[n]; bad {
753 continue
754 }
755 for _, y := range phiBlocksByName[n] {
756 if newPhisByBlockByName[y.ID] == nil {
757 newPhisByBlockByName[y.ID] = make(map[*ir.Name]*ssa.Value)
758 }
759 p := y.NewValue0(y.Pos, ssaop.OpPhi, n.Type())
760 changed = true
761 phiToName[p] = n
762 newPhisByBlockByName[y.ID][n] = p
763 for range len(y.Preds) {
764
765 p.AddArg(virtualPhi)
766 }
767 addNamedValue(n, p)
768 }
769 }
770
771
772
773 renameWalk(
774
775 func(b *ssa.Block, n *ir.Name) *ssa.Value {
776 if m := newPhisByBlockByName[b.ID]; m != nil {
777 return m[n]
778 }
779 return nil
780 },
781
782 func(n *ir.Name, phi *ssa.Value, predIdx int, v *ssa.Value) {
783 if !copyCompatibleType(n.Type(), v.Type) {
784 f.Fatalf("phi of %v decided promotable but has incompatible type", n)
785 }
786 phi.SetArg(predIdx, v)
787 },
788
789 func(n *ir.Name, use, incoming *ssa.Value) bool {
790 if incoming == nil {
791 f.Fatalf("no incoming value for a load in mem2reg")
792 }
793 if !copyCompatibleType(use.Type, incoming.Type) {
794 f.Fatalf("load of %v decided promotable but has incompatible type", n)
795 }
796 replaceLoad(n, loadCandidate{
797 l: use,
798 v: incoming,
799 reinterpret: ssaop.OpInvalid,
800 })
801 return true
802 },
803
804 func(n *ir.Name, use *ssa.Value) bool {
805 val := use.Args[1]
806 if !copyCompatibleType(val.Type, n.Type()) {
807 f.Fatalf("store of %v decided promotable but has incompatible type", n)
808 }
809 removeStore(use)
810 addNamedValue(n, val)
811 return true
812 },
813 )
814
815
816
817
818
819 for _, n := range namesOrdered {
820 if _, ok := varGrouped[n]; !ok {
821 continue
822 }
823 if _, bad := badNames[n]; bad {
824 continue
825 }
826 n.SetAddrtaken(false)
827 for _, v := range demographics[n].varDefs {
828
829 removeStore(v)
830 }
831
832 if f.Pass.Debug > 1 {
833 f.Warnl(n.Pos(), "promoted %v in cross block case", n)
834 }
835 st.Record("promoted variable in cross block cases", 1)
836 }
837
838 if changed {
839 deadcode(f)
840 }
841
842
843 if virtualPhi.Uses != 0 {
844 f.Fatalf("some new phis have incomplete arg lists")
845 }
846 }
847
View as plain text