github.com/tetratelabs/wazero@v1.7.3-0.20240513003603-48f702e154b5/internal/engine/wazevo/backend/regalloc/regalloc_test.go (about) 1 package regalloc 2 3 import ( 4 "fmt" 5 "sort" 6 "testing" 7 8 "github.com/tetratelabs/wazero/internal/engine/wazevo/wazevoapi" 9 "github.com/tetratelabs/wazero/internal/testing/require" 10 ) 11 12 func TestAllocator_livenessAnalysis(t *testing.T) { 13 const realRegID, realRegID2 = 50, 100 14 realReg, realReg2 := FromRealReg(realRegID, RegTypeInt), FromRealReg(realRegID2, RegTypeInt) 15 phiVReg := VReg(12345).SetRegType(RegTypeInt) 16 for _, tc := range []struct { 17 name string 18 setup func() Function 19 exp map[int]*blockState 20 }{ 21 { 22 name: "single block", 23 setup: func() Function { 24 return newMockFunction( 25 newMockBlock(0, 26 newMockInstr().def(1), 27 newMockInstr().use(1).def(2), 28 ).entry(), 29 ) 30 }, 31 exp: map[int]*blockState{ 32 0: {}, 33 }, 34 }, 35 { 36 name: "single block with real reg", 37 setup: func() Function { 38 realVReg := FromRealReg(10, RegTypeInt) 39 param := VReg(1) 40 ret := VReg(2) 41 blk := newMockBlock(0, 42 newMockInstr().def(param).use(realVReg), 43 newMockInstr().def(ret).use(param, param), 44 newMockInstr().def(realVReg).use(ret), 45 ).entry() 46 blk.blockParam(param) 47 return newMockFunction(blk) 48 }, 49 exp: map[int]*blockState{ 50 0: {}, 51 }, 52 }, 53 { 54 name: "straight", 55 // b0 -> b1 -> b2 56 setup: func() Function { 57 b0 := newMockBlock(0, 58 newMockInstr().def(1000, 1, 2), 59 newMockInstr().use(1000), 60 newMockInstr().use(1, 2).def(3), 61 ).entry() 62 b1 := newMockBlock(1, 63 newMockInstr().def(realReg), 64 newMockInstr().use(3).def(4, 5), 65 newMockInstr().use(realReg), 66 ) 67 b2 := newMockBlock(2, 68 newMockInstr().use(3, 4, 5), 69 ) 70 b2.addPred(b1) 71 b1.addPred(b0) 72 return newMockFunction(b0, b1, b2) 73 }, 74 exp: map[int]*blockState{ 75 0: {}, 76 1: { 77 liveIns: []VRegID{3}, 78 }, 79 2: {liveIns: []VRegID{3, 4, 5}}, 80 }, 81 }, 82 { 83 name: "diamond", 84 // 0 v1000<-, v1<-, v2<- 85 // / \ 86 // 1 2 87 // \ / 88 // 3 89 setup: func() Function { 90 b0 := newMockBlock(0, 91 newMockInstr().def(1000), 92 newMockInstr().def(1), 93 newMockInstr().def(2), 94 ).entry() 95 b1 := newMockBlock(1, 96 newMockInstr().def(realReg).use(1), 97 newMockInstr().use(realReg), 98 newMockInstr().def(realReg2), 99 newMockInstr().use(realReg2), 100 newMockInstr().def(realReg), 101 newMockInstr().use(realReg), 102 ) 103 b2 := newMockBlock(2, 104 newMockInstr().use(2, realReg2), 105 ) 106 b3 := newMockBlock(3, 107 newMockInstr().use(1000), 108 ) 109 b3.addPred(b1) 110 b3.addPred(b2) 111 b1.addPred(b0) 112 b2.addPred(b0) 113 return newMockFunction(b0, b1, b2, b3) 114 }, 115 exp: map[int]*blockState{ 116 0: {}, 117 1: {liveIns: []VRegID{1000, 1}}, 118 2: { 119 liveIns: []VRegID{1000, 2}, 120 }, 121 3: { 122 liveIns: []VRegID{1000}, 123 }, 124 }, 125 }, 126 127 { 128 name: "phis", 129 // 0 130 // / \ 131 // 1 \ 132 // | | 133 // 2 3 134 // \ / 135 // 4 use v5 (phi node) defined at both 1 and 3. 136 setup: func() Function { 137 b0 := newMockBlock(0, 138 newMockInstr().def(1000, 2000, 3000), 139 ).entry() 140 b1 := newMockBlock(1, 141 newMockInstr().def(phiVReg).use(2000), 142 ) 143 b2 := newMockBlock(2) 144 b3 := newMockBlock(3, 145 newMockInstr().def(phiVReg).use(1000), 146 ) 147 b4 := newMockBlock( 148 4, newMockInstr().use(phiVReg, 3000), 149 ) 150 b4.addPred(b2) 151 b4.addPred(b3) 152 b3.addPred(b0) 153 b2.addPred(b1) 154 b1.addPred(b0) 155 return newMockFunction(b0, b1, b2, b3, b4) 156 }, 157 exp: map[int]*blockState{ 158 0: {}, 159 1: { 160 liveIns: []VRegID{2000, 3000}, 161 }, 162 2: { 163 liveIns: []VRegID{phiVReg.ID(), 3000}, 164 }, 165 3: { 166 liveIns: []VRegID{1000, 3000}, 167 }, 168 4: { 169 liveIns: []VRegID{phiVReg.ID(), 3000}, 170 }, 171 }, 172 }, 173 174 { 175 name: "loop", 176 // 0 -> 1 -> 2 177 // ^ | 178 // | v 179 // 4 <- 3 -> 5 180 setup: func() Function { 181 b0 := newMockBlock(0, 182 newMockInstr().def(1), 183 newMockInstr().def(phiVReg).use(1), 184 ).entry() 185 b1 := newMockBlock(1, 186 newMockInstr().def(9999), 187 ) 188 b1.blockParam(phiVReg) 189 b2 := newMockBlock(2, 190 newMockInstr().def(100).use(phiVReg, 9999), 191 ) 192 b3 := newMockBlock(3, 193 newMockInstr().def(54321), 194 newMockInstr().use(100), 195 ) 196 b4 := newMockBlock(4, 197 newMockInstr().def(phiVReg).use(54321). 198 // Make sure this is the PHI defining instruction. 199 asCopy(), 200 ) 201 b5 := newMockBlock( 202 5, newMockInstr().use(54321), 203 ) 204 b1.addPred(b0) 205 b1.addPred(b4) 206 b2.addPred(b1) 207 b3.addPred(b2) 208 b4.addPred(b3) 209 b5.addPred(b3) 210 b1.loop(b2, b3, b4, b5) 211 f := newMockFunction(b0, b1, b2, b3, b4, b5) 212 f.loopNestingForestRoots(b1) 213 return f 214 }, 215 exp: map[int]*blockState{ 216 0: { 217 liveIns: []VRegID{}, 218 }, 219 1: { 220 liveIns: []VRegID{phiVReg.ID()}, 221 }, 222 2: { 223 liveIns: []VRegID{phiVReg.ID(), 9999}, 224 }, 225 3: { 226 liveIns: []VRegID{100}, 227 }, 228 4: { 229 liveIns: []VRegID{54321}, 230 }, 231 5: {liveIns: []VRegID{54321}}, 232 }, 233 }, 234 { 235 name: "multiple pass alive", 236 setup: func() Function { 237 v := VReg(9999) 238 b0 := newMockBlock(0, newMockInstr().def(v)).entry() 239 240 b1, b2, b3, b4, b5, b6 := newMockBlock(1), newMockBlock(2), 241 newMockBlock(3, newMockInstr().use(v)), 242 newMockBlock(4), newMockBlock(5), newMockBlock(6) 243 244 b1.addPred(b0) 245 b4.addPred(b0) 246 b2.addPred(b1) 247 b5.addPred(b2) 248 b2.addPred(b5) 249 b6.addPred(b2) 250 b3.addPred(b6) 251 b3.addPred(b4) 252 f := newMockFunction(b0, b1, b2, b4, b5, b6, b3) 253 f.loopNestingForestRoots(b2) 254 return f 255 }, 256 exp: map[int]*blockState{ 257 0: {}, 258 1: { 259 liveIns: []VRegID{9999}, 260 }, 261 2: { 262 liveIns: []VRegID{9999}, 263 }, 264 3: { 265 liveIns: []VRegID{9999}, 266 }, 267 4: { 268 liveIns: []VRegID{9999}, 269 }, 270 5: {}, 271 6: { 272 liveIns: []VRegID{9999}, 273 }, 274 }, 275 }, 276 { 277 // -----+ 278 // v | 279 // 0 -> 1 -> 2 -> 3 -> 4 280 // ^ | 281 // +----+ 282 name: "Fig. 9.2 in paper", 283 setup: func() Function { 284 b0 := newMockBlock(0, 285 newMockInstr().def(99999), 286 newMockInstr().def(phiVReg).use(111).asCopy(), 287 ).entry() 288 b1 := newMockBlock(1, newMockInstr().use(99999)) 289 b1.blockParam(phiVReg) 290 b2 := newMockBlock(2, newMockInstr().def(88888).use(phiVReg, phiVReg)) 291 b3 := newMockBlock(3, newMockInstr().def(phiVReg).use(88888).asCopy()) 292 b4 := newMockBlock(4) 293 b1.addPred(b0) 294 b1.addPred(b2) 295 b2.addPred(b1) 296 b2.addPred(b3) 297 b3.addPred(b2) 298 b4.addPred(b3) 299 300 b1.loop(b2) 301 b2.loop(b3) 302 f := newMockFunction(b0, b1, b2, b3, b4) 303 f.loopNestingForestRoots(b1) 304 return f 305 }, 306 exp: map[int]*blockState{ 307 0: { 308 liveIns: []VRegID{111}, 309 }, 310 1: { 311 liveIns: []VRegID{99999, phiVReg.ID()}, 312 }, 313 2: { 314 liveIns: []VRegID{99999, phiVReg.ID()}, 315 }, 316 3: { 317 liveIns: []VRegID{99999, phiVReg.ID(), 88888}, 318 }, 319 4: {}, 320 }, 321 }, 322 } { 323 tc := tc 324 t.Run(tc.name, func(t *testing.T) { 325 f := tc.setup() 326 a := NewAllocator(&RegisterInfo{ 327 RealRegName: func(r RealReg) string { 328 return fmt.Sprintf("r%d", r) 329 }, 330 }) 331 a.livenessAnalysis(f) 332 for blockID := 0; blockID <= a.blockStates.MaxIDEncountered(); blockID++ { 333 actual := a.blockStates.Get(blockID) 334 if actual == nil { 335 continue 336 } 337 t.Run(fmt.Sprintf("block_id=%d", blockID), func(t *testing.T) { 338 exp := tc.exp[blockID] 339 if len(exp.liveIns) == 0 { 340 require.Nil(t, actual.liveIns, "live ins") 341 } else { 342 sort.Slice(actual.liveIns, func(i, j int) bool { 343 return actual.liveIns[i] < actual.liveIns[j] 344 }) 345 sort.Slice(exp.liveIns, func(i, j int) bool { 346 return exp.liveIns[i] < exp.liveIns[j] 347 }) 348 require.Equal(t, exp.liveIns, actual.liveIns, "live ins") 349 } 350 }) 351 } 352 }) 353 } 354 } 355 356 func TestAllocator_livenessAnalysis_copy(t *testing.T) { 357 f := newMockFunction( 358 newMockBlock(0, 359 newMockInstr().def(1), 360 newMockInstr().use(1).def(2).asCopy(), 361 ).entry(), 362 ) 363 a := NewAllocator(&RegisterInfo{}) 364 a.livenessAnalysis(f) 365 } 366 367 func Test_findOrSpillAllocatable_prefersSpill(t *testing.T) { 368 t.Run("ok", func(t *testing.T) { 369 s := &state{} 370 s.regsInUse.add(RealReg(1), VReg(2222222)) 371 got := s.findOrSpillAllocatable(&Allocator{}, []RealReg{3}, 0, 3) 372 require.Equal(t, RealReg(3), got) 373 }) 374 t.Run("preferred but in use", func(t *testing.T) { 375 s := &state{vrStates: wazevoapi.NewIDedPool[vrState](resetVrState)} 376 s.regsInUse.add(RealReg(3), VReg(1).SetRealReg(3)) 377 got := s.findOrSpillAllocatable(&Allocator{}, []RealReg{3, 4}, 0, 3) 378 require.Equal(t, RealReg(4), got) 379 }) 380 t.Run("preferred but forbidden", func(t *testing.T) { 381 s := &state{vrStates: wazevoapi.NewIDedPool[vrState](resetVrState)} 382 got := s.findOrSpillAllocatable(&Allocator{}, []RealReg{3, 4}, RegSet(0).add(3), 3) 383 require.Equal(t, RealReg(4), got) 384 }) 385 }