@@ -25,7 +25,7 @@ import (
25
25
// It starts with optimistically assuming that all SSA values are initially Top
26
26
// and then propagates constant facts only along reachable control flow paths.
27
27
// Since some basic blocks are not visited yet, corresponding inputs of phi become
28
- // Top, we use the meet(args...) for phi to compute its lattice.
28
+ // Top, we use the meet(phi) to compute its lattice.
29
29
//
30
30
// Top ∩ any = any
31
31
// Bottom ∩ any = Bottom
@@ -60,6 +60,67 @@ type worklist struct {
60
60
defBlock map [* Value ][]* Block // use blocks of def
61
61
}
62
62
63
+ // sccp stands for sparse conditional constant propagation, it propagates constants
64
+ // through CFG conditionally and applies constant folding, constant replacement and
65
+ // dead code elimination all together.
66
+ func sccp (f * Func ) {
67
+ var t worklist
68
+ t .f = f
69
+ t .edges = make ([]Edge , 0 )
70
+ t .visited = make (map [Edge ]bool )
71
+ t .edges = append (t .edges , Edge {f .Entry , 0 })
72
+ t .defUse = make (map [* Value ][]* Value )
73
+ t .defBlock = make (map [* Value ][]* Block )
74
+ t .latticeCells = make (map [* Value ]lattice )
75
+
76
+ // build it early since we rely heavily on the def-use chain later
77
+ t .buildDefUses ()
78
+
79
+ visitedBlock := f .newSparseSet (f .NumBlocks ())
80
+ defer f .retSparseSet (visitedBlock )
81
+
82
+ // pick up either an edge or SSA value from worklilst, process it
83
+ for {
84
+ if len (t .edges ) > 0 {
85
+ edge := t .edges [0 ]
86
+ t .edges = t .edges [1 :]
87
+ if _ , exist := t .visited [edge ]; ! exist {
88
+ dest := edge .b
89
+ destVisited := visitedBlock .contains (dest .ID )
90
+
91
+ // mark edge as visited
92
+ t .visited [edge ] = true
93
+ visitedBlock .add (dest .ID )
94
+ for _ , val := range dest .Values {
95
+ if val .Op == OpPhi || ! destVisited {
96
+ t .visitValue (val )
97
+ }
98
+ }
99
+ // propagates constants facts through CFG, taking condition test into account
100
+ if ! destVisited {
101
+ t .propagate (dest )
102
+ }
103
+ }
104
+ continue
105
+ }
106
+ if len (t .uses ) > 0 {
107
+ use := t .uses [0 ]
108
+ t .uses = t .uses [1 :]
109
+ t .visitValue (use )
110
+ continue
111
+ }
112
+ break
113
+ }
114
+
115
+ // apply optimizations based on discovered constants
116
+ constCnt , rewireCnt := t .replaceConst ()
117
+ if f .pass .debug > 0 {
118
+ if constCnt > 0 || rewireCnt > 0 {
119
+ fmt .Printf ("Phase SCCP for %v : %v constants, %v dce\n " , f .Name , constCnt , rewireCnt )
120
+ }
121
+ }
122
+ }
123
+
63
124
// possibleConst checks if Value can be fold to const. For those Values that can never become
64
125
// constants(e.g. StaticCall), we don't make futile efforts.
65
126
func possibleConst (val * Value ) bool {
@@ -68,9 +129,9 @@ func possibleConst(val *Value) bool {
68
129
}
69
130
switch val .Op {
70
131
case OpCopy :
71
- fallthrough
132
+ return true
72
133
case OpPhi :
73
- fallthrough
134
+ return true
74
135
case
75
136
// negate
76
137
OpNeg8 , OpNeg16 , OpNeg32 , OpNeg64 , OpNeg32F , OpNeg64F ,
@@ -94,7 +155,7 @@ func possibleConst(val *Value) bool {
94
155
OpIsNonNil ,
95
156
// not
96
157
OpNot :
97
- fallthrough
158
+ return true
98
159
case
99
160
// add
100
161
OpAdd64 , OpAdd32 , OpAdd16 , OpAdd8 ,
@@ -162,7 +223,8 @@ func isConst(val *Value) bool {
162
223
// buildDefUses builds def-use chain for some values early, because once the lattice of
163
224
// a value is changed, we need to update lattices of use. But we don't need all uses of
164
225
// it, only uses that can become constants would be added into re-visit worklist since
165
- // no matter how many times they are revisited, their lattice remains unchanged(Bottom)
226
+ // no matter how many times they are revisited, uses which can't become constants lattice
227
+ // remains unchanged, i.e. Bottom.
166
228
func (t * worklist ) buildDefUses () {
167
229
for _ , block := range t .f .Blocks {
168
230
for _ , val := range block .Values {
@@ -177,11 +239,13 @@ func (t *worklist) buildDefUses() {
177
239
}
178
240
}
179
241
for _ , ctl := range block .ControlValues () {
180
- // for every control values, find their use blocks
181
- if _ , exist := t .defBlock [ctl ]; ! exist {
182
- t .defBlock [ctl ] = make ([]* Block , 0 )
242
+ // for control values that can become constants, find their use blocks
243
+ if possibleConst (ctl ) {
244
+ if _ , exist := t .defBlock [ctl ]; ! exist {
245
+ t .defBlock [ctl ] = make ([]* Block , 0 )
246
+ }
247
+ t .defBlock [ctl ] = append (t .defBlock [ctl ], block )
183
248
}
184
- t .defBlock [ctl ] = append (t .defBlock [ctl ], block )
185
249
}
186
250
}
187
251
}
@@ -200,50 +264,46 @@ func (t *worklist) addUses(val *Value) {
200
264
}
201
265
}
202
266
203
- func meet (arr []lattice ) lattice {
204
- var lt = lattice {top , nil }
205
- for _ , t := range arr {
206
- if t .tag == bottom {
207
- return lattice {bottom , nil }
208
- } else if t .tag == constant {
209
- if lt .tag == top {
210
- lt .tag = constant
211
- lt .val = t .val
212
- } else {
213
- if lt .val != t .val {
214
- return lattice {bottom , nil }
267
+ // meet meets all of phi arguments and computes result lattice
268
+ func (t * worklist ) meet (val * Value ) lattice {
269
+ optimisticLt := lattice {top , nil }
270
+ for i := 0 ; i < len (val .Args ); i ++ {
271
+ edge := Edge {val .Block , i }
272
+ // If incoming edge for phi is not visited, assume top optimistically.
273
+ // According to rules of meet:
274
+ // Top ∩ any = any
275
+ // Top participates in meet() but does not affect the result, so here
276
+ // we will ignore Top and only take other lattices into consideration.
277
+ if _ , exist := t .visited [edge ]; exist {
278
+ lt := t .getLatticeCell (val .Args [i ])
279
+ if lt .tag == constant {
280
+ if optimisticLt .tag == top {
281
+ optimisticLt = lt
282
+ } else {
283
+ if optimisticLt .val != lt .val {
284
+ // ConstantA ∩ ConstantB = Bottom
285
+ return lattice {bottom , nil }
286
+ }
215
287
}
288
+ } else if lt .tag == bottom {
289
+ // Bottom ∩ any = Bottom
290
+ return lattice {bottom , nil }
291
+ } else {
292
+ // Top ∩ any = any
216
293
}
217
294
} else {
218
295
// Top ∩ any = any
219
296
}
220
297
}
221
- return lt
222
- }
223
298
224
- func (t * worklist ) visitPhi (val * Value ) {
225
- var argLattice = make ([]lattice , 0 )
226
- for i := 0 ; i < len (val .Args ); i ++ {
227
- var edge = Edge {val .Block , i }
228
- // If incoming edge for phi is not visited, assume top optimistically. According to rules
229
- // of meet:
230
- // Top ∩ any = any
231
- // Top participates in meet() but does not affect the result, so here we will ignore Top
232
- // and only take Bottom and Constant lattices into consideration.
233
- if _ , exist := t .visited [edge ]; exist {
234
- argLattice = append (argLattice , t .getLatticeCell (val .Args [i ]))
235
- } else {
236
- // ignore Top intentionally
237
- }
238
- }
239
- // meet all of phi arguments
240
- t .latticeCells [val ] = meet (argLattice )
299
+ // ConstantA ∩ ConstantA = ConstantA or Top ∩ any = any
300
+ return optimisticLt
241
301
}
242
302
243
303
func computeLattice (f * Func , val * Value , args ... * Value ) lattice {
244
304
// In general, we need to perform constant evaluation based on two constant lattices:
245
305
//
246
- // var res = lattice{constant, nil}
306
+ // res : = lattice{constant, nil}
247
307
// switch op {
248
308
// case OpAdd16:
249
309
// res.val = newConst(argLt1.val.AuxInt16() + argLt2.val.AuxInt16())
@@ -265,9 +325,9 @@ func computeLattice(f *Func, val *Value, args ...*Value) lattice {
265
325
// change it permanently, which can lead to errors. For example, We cannot change its value
266
326
// immediately after visiting Phi, because some of its input edges may still not be visited
267
327
// at this moment.
268
- var constValue = f .newValue (val .Op , val .Type , f .Entry , val .Pos )
328
+ constValue : = f .newValue (val .Op , val .Type , f .Entry , val .Pos )
269
329
constValue .AddArgs (args ... )
270
- var matched = rewriteValuegeneric (constValue )
330
+ matched : = rewriteValuegeneric (constValue )
271
331
if matched {
272
332
if isConst (constValue ) {
273
333
return lattice {constant , constValue }
@@ -284,16 +344,16 @@ func (t *worklist) visitValue(val *Value) {
284
344
return
285
345
}
286
346
287
- var oldLt = t .getLatticeCell (val )
347
+ oldLt : = t .getLatticeCell (val )
288
348
defer func () {
289
349
// re-visit all uses of value if its lattice is changed
290
- var newLt = t .getLatticeCell (val )
350
+ newLt : = t .getLatticeCell (val )
291
351
if newLt != oldLt {
292
352
t .addUses (val )
293
353
}
294
354
}()
295
355
296
- var worstLt = lattice {bottom , nil }
356
+ worstLt : = lattice {bottom , nil }
297
357
switch val .Op {
298
358
// they are constant values, aren't they?
299
359
case OpConst64 , OpConst32 , OpConst16 , OpConst8 ,
@@ -304,7 +364,7 @@ func (t *worklist) visitValue(val *Value) {
304
364
t .latticeCells [val ] = t .getLatticeCell (val .Args [0 ])
305
365
// phi should be processed specially
306
366
case OpPhi :
307
- t .visitPhi (val )
367
+ t .latticeCells [ val ] = t . meet (val )
308
368
// fold 1-input operations:
309
369
case
310
370
// negate
@@ -329,7 +389,7 @@ func (t *worklist) visitValue(val *Value) {
329
389
OpIsNonNil ,
330
390
// not
331
391
OpNot :
332
- var lt1 = t .getLatticeCell (val .Args [0 ])
392
+ lt1 : = t .getLatticeCell (val .Args [0 ])
333
393
if lt1 .tag != constant {
334
394
t .latticeCells [val ] = worstLt
335
395
return
@@ -374,8 +434,8 @@ func (t *worklist) visitValue(val *Value) {
374
434
OpAnd8 , OpAnd16 , OpAnd32 , OpAnd64 ,
375
435
OpOr8 , OpOr16 , OpOr32 , OpOr64 ,
376
436
OpXor8 , OpXor16 , OpXor32 , OpXor64 :
377
- var lt1 = t .getLatticeCell (val .Args [0 ])
378
- var lt2 = t .getLatticeCell (val .Args [1 ])
437
+ lt1 : = t .getLatticeCell (val .Args [0 ])
438
+ lt2 : = t .getLatticeCell (val .Args [1 ])
379
439
if lt1 .tag != constant || lt2 .tag != constant {
380
440
t .latticeCells [val ] = worstLt
381
441
return
@@ -404,8 +464,8 @@ func (t *worklist) propagate(block *Block) {
404
464
case BlockPlain :
405
465
t .edges = append (t .edges , block .Succs [0 ])
406
466
case BlockIf , BlockJumpTable :
407
- var cond = block .ControlValues ()[0 ]
408
- var condLattice = t .getLatticeCell (cond )
467
+ cond : = block .ControlValues ()[0 ]
468
+ condLattice : = t .getLatticeCell (cond )
409
469
if condLattice .tag == bottom {
410
470
// we know nothing about control flow, add all branch destinations
411
471
t .edges = append (t .edges , block .Succs ... )
@@ -438,8 +498,8 @@ func rewireSuccessor(block *Block, constVal *Value) bool {
438
498
block .ResetControls ()
439
499
return true
440
500
case BlockJumpTable :
441
- var idx = int (constVal .AuxInt )
442
- var targetBlock = block .Succs [idx ].b
501
+ idx : = int (constVal .AuxInt )
502
+ targetBlock : = block .Succs [idx ].b
443
503
for len (block .Succs ) > 0 {
444
504
block .removeEdge (0 )
445
505
}
@@ -456,7 +516,7 @@ func rewireSuccessor(block *Block, constVal *Value) bool {
456
516
// replaceConst will replace non-constant values that have been proven by sccp to be
457
517
// constants.
458
518
func (t * worklist ) replaceConst () (int , int ) {
459
- var constCnt , rewireCnt = 0 , 0
519
+ constCnt , rewireCnt : = 0 , 0
460
520
for val , lt := range t .latticeCells {
461
521
if lt .tag == constant {
462
522
if ! isConst (val ) {
@@ -468,7 +528,7 @@ func (t *worklist) replaceConst() (int, int) {
468
528
constCnt ++
469
529
}
470
530
// If const value controls this block, rewires successors according to its value
471
- var ctrlBlock = t .defBlock [val ]
531
+ ctrlBlock : = t .defBlock [val ]
472
532
for _ , block := range ctrlBlock {
473
533
if rewireSuccessor (block , lt .val ) {
474
534
rewireCnt ++
@@ -481,61 +541,3 @@ func (t *worklist) replaceConst() (int, int) {
481
541
}
482
542
return constCnt , rewireCnt
483
543
}
484
-
485
- func sccp (f * Func ) {
486
- var t worklist
487
- t .f = f
488
- t .edges = make ([]Edge , 0 )
489
- t .visited = make (map [Edge ]bool )
490
- t .edges = append (t .edges , Edge {f .Entry , 0 })
491
- t .defUse = make (map [* Value ][]* Value )
492
- t .defBlock = make (map [* Value ][]* Block )
493
- t .latticeCells = make (map [* Value ]lattice )
494
-
495
- // build it early since we rely heavily on the def-use chain later
496
- t .buildDefUses ()
497
-
498
- var visitedBlock = f .newSparseSet (f .NumBlocks ())
499
- defer f .retSparseSet (visitedBlock )
500
-
501
- // pick up either an edge or SSA value from worklilst, process it
502
- for {
503
- if len (t .edges ) > 0 {
504
- var edge = t .edges [0 ]
505
- t .edges = t .edges [1 :]
506
- if _ , exist := t .visited [edge ]; ! exist {
507
- var dest = edge .b
508
- var destVisited = visitedBlock .contains (dest .ID )
509
-
510
- // mark edge as visited
511
- t .visited [edge ] = true
512
- visitedBlock .add (dest .ID )
513
- for _ , val := range dest .Values {
514
- if val .Op == OpPhi || ! destVisited {
515
- t .visitValue (val )
516
- }
517
- }
518
- // propagates constants facts through CFG, taking condition test into account
519
- if ! destVisited {
520
- t .propagate (dest )
521
- }
522
- }
523
- continue
524
- }
525
- if len (t .uses ) > 0 {
526
- var use = t .uses [0 ]
527
- t .uses = t .uses [1 :]
528
- t .visitValue (use )
529
- continue
530
- }
531
- break
532
- }
533
-
534
- // apply optimizations based on discovered constants
535
- var constCnt , rewireCnt = t .replaceConst ()
536
- if f .pass .debug > 0 {
537
- if constCnt > 0 || rewireCnt > 0 {
538
- fmt .Printf ("Phase SCCP for %v : %v constants, %v dce\n " , f .Name , constCnt , rewireCnt )
539
- }
540
- }
541
- }
0 commit comments