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