github.com/bananabytelabs/wazero@v0.0.0-20240105073314-54b22a776da8/internal/engine/wazevo/backend/isa/arm64/machine_regalloc_test.go (about) 1 package arm64 2 3 import ( 4 "testing" 5 6 "github.com/bananabytelabs/wazero/internal/engine/wazevo/backend/regalloc" 7 "github.com/bananabytelabs/wazero/internal/engine/wazevo/ssa" 8 "github.com/bananabytelabs/wazero/internal/testing/require" 9 ) 10 11 func TestRegAllocFunctionImpl_addBlock(t *testing.T) { 12 ssab := ssa.NewBuilder() 13 sb1, sb2 := ssab.AllocateBasicBlock(), ssab.AllocateBasicBlock() 14 p1, p2 := &labelPosition{}, &labelPosition{} 15 16 f := ®AllocFunctionImpl{labelToRegAllocBlockIndex: map[label]int{}} 17 f.addBlock(sb1, label(10), p1) 18 f.addBlock(sb2, label(20), p2) 19 20 require.Equal(t, 2, len(f.labelToRegAllocBlockIndex)) 21 require.Equal(t, 2, len(f.reversePostOrderBlocks)) 22 23 rb1, rb2 := &f.reversePostOrderBlocks[0], &f.reversePostOrderBlocks[1] 24 require.Equal(t, f, rb1.f) 25 require.Equal(t, f, rb2.f) 26 27 require.Equal(t, p1, rb1.pos) 28 require.Equal(t, p2, rb2.pos) 29 30 require.Equal(t, label(10), rb1.l) 31 require.Equal(t, label(20), rb2.l) 32 33 require.Equal(t, sb1, rb1.sb) 34 require.Equal(t, sb2, rb2.sb) 35 } 36 37 func TestRegAllocFunctionImpl_PostOrderBlockIterator(t *testing.T) { 38 f := ®AllocFunctionImpl{reversePostOrderBlocks: []regAllocBlockImpl{{}, {}, {}}} 39 blk := f.PostOrderBlockIteratorBegin() 40 require.Equal(t, blk, &f.reversePostOrderBlocks[2]) 41 blk = f.PostOrderBlockIteratorNext() 42 require.Equal(t, blk, &f.reversePostOrderBlocks[1]) 43 blk = f.PostOrderBlockIteratorNext() 44 require.Equal(t, blk, &f.reversePostOrderBlocks[0]) 45 blk = f.PostOrderBlockIteratorNext() 46 require.Nil(t, blk) 47 } 48 49 func TestRegAllocFunctionImpl_ReversePostOrderBlockIterator(t *testing.T) { 50 f := ®AllocFunctionImpl{reversePostOrderBlocks: []regAllocBlockImpl{{}, {}, {}}} 51 blk := f.ReversePostOrderBlockIteratorBegin() 52 require.Equal(t, blk, &f.reversePostOrderBlocks[0]) 53 blk = f.ReversePostOrderBlockIteratorNext() 54 require.Equal(t, blk, &f.reversePostOrderBlocks[1]) 55 blk = f.ReversePostOrderBlockIteratorNext() 56 require.Equal(t, blk, &f.reversePostOrderBlocks[2]) 57 blk = f.ReversePostOrderBlockIteratorNext() 58 require.Nil(t, blk) 59 } 60 61 func TestRegAllocFunctionImpl_ReloadRegisterAfter(t *testing.T) { 62 ctx, _, m := newSetupWithMockContext() 63 64 ctx.typeOf = map[regalloc.VRegID]ssa.Type{x1VReg.ID(): ssa.TypeI64, v1VReg.ID(): ssa.TypeF64} 65 i1, i2 := m.allocateNop(), m.allocateNop() 66 i1.next = i2 67 i2.prev = i1 68 69 f := ®AllocFunctionImpl{m: m} 70 f.ReloadRegisterAfter(x1VReg, i1) 71 f.ReloadRegisterAfter(v1VReg, i1) 72 73 require.NotEqual(t, i1, i2.prev) 74 require.NotEqual(t, i1.next, i2) 75 fload, iload := i1.next, i1.next.next 76 require.Equal(t, fload.prev, i1) 77 require.Equal(t, i1, fload.prev) 78 require.Equal(t, iload.next, i2) 79 require.Equal(t, iload, i2.prev) 80 81 require.Equal(t, iload.kind, uLoad64) 82 require.Equal(t, fload.kind, fpuLoad64) 83 84 m.executableContext.RootInstr = i1 85 require.Equal(t, ` 86 ldr d1, [sp, #0x18] 87 ldr x1, [sp, #0x10] 88 `, m.Format()) 89 } 90 91 func TestRegAllocFunctionImpl_StoreRegisterBefore(t *testing.T) { 92 ctx, _, m := newSetupWithMockContext() 93 94 ctx.typeOf = map[regalloc.VRegID]ssa.Type{x1VReg.ID(): ssa.TypeI64, v1VReg.ID(): ssa.TypeF64} 95 i1, i2 := m.allocateNop(), m.allocateNop() 96 i1.next = i2 97 i2.prev = i1 98 99 f := ®AllocFunctionImpl{m: m} 100 f.StoreRegisterBefore(x1VReg, i2) 101 f.StoreRegisterBefore(v1VReg, i2) 102 103 require.NotEqual(t, i1, i2.prev) 104 require.NotEqual(t, i1.next, i2) 105 iload, fload := i1.next, i1.next.next 106 require.Equal(t, iload.prev, i1) 107 require.Equal(t, i1, iload.prev) 108 require.Equal(t, fload.next, i2) 109 require.Equal(t, fload, i2.prev) 110 111 require.Equal(t, iload.kind, store64) 112 require.Equal(t, fload.kind, fpuStore64) 113 114 m.executableContext.RootInstr = i1 115 require.Equal(t, ` 116 str x1, [sp, #0x10] 117 str d1, [sp, #0x18] 118 `, m.Format()) 119 } 120 121 func TestMachine_insertStoreRegisterAt(t *testing.T) { 122 for _, tc := range []struct { 123 spillSlotSize int64 124 expected string 125 }{ 126 { 127 spillSlotSize: 0, 128 expected: ` 129 udf 130 str x1, [sp, #0x10] 131 str d1, [sp, #0x18] 132 exit_sequence x30 133 `, 134 }, 135 { 136 spillSlotSize: 0xffff, 137 expected: ` 138 udf 139 movz x27, #0xf, lsl 0 140 movk x27, #0x1, lsl 16 141 str x1, [sp, x27] 142 movz x27, #0x17, lsl 0 143 movk x27, #0x1, lsl 16 144 str d1, [sp, x27] 145 exit_sequence x30 146 `, 147 }, 148 { 149 spillSlotSize: 0xffff_00, 150 expected: ` 151 udf 152 movz x27, #0xff10, lsl 0 153 movk x27, #0xff, lsl 16 154 str x1, [sp, x27] 155 movz x27, #0xff18, lsl 0 156 movk x27, #0xff, lsl 16 157 str d1, [sp, x27] 158 exit_sequence x30 159 `, 160 }, 161 } { 162 t.Run(tc.expected, func(t *testing.T) { 163 ctx, _, m := newSetupWithMockContext() 164 m.spillSlotSize = tc.spillSlotSize 165 166 for _, after := range []bool{false, true} { 167 var name string 168 if after { 169 name = "after" 170 } else { 171 name = "before" 172 } 173 t.Run(name, func(t *testing.T) { 174 ctx.typeOf = map[regalloc.VRegID]ssa.Type{x1VReg.ID(): ssa.TypeI64, v1VReg.ID(): ssa.TypeF64} 175 i1, i2 := m.allocateInstr().asUDF(), m.allocateInstr().asExitSequence(x30VReg) 176 i1.next = i2 177 i2.prev = i1 178 179 if after { 180 m.insertStoreRegisterAt(v1VReg, i1, after) 181 m.insertStoreRegisterAt(x1VReg, i1, after) 182 } else { 183 m.insertStoreRegisterAt(x1VReg, i2, after) 184 m.insertStoreRegisterAt(v1VReg, i2, after) 185 } 186 m.executableContext.RootInstr = i1 187 require.Equal(t, tc.expected, m.Format()) 188 }) 189 } 190 }) 191 } 192 } 193 194 func TestMachine_insertReloadRegisterAt(t *testing.T) { 195 for _, tc := range []struct { 196 spillSlotSize int64 197 expected string 198 }{ 199 { 200 spillSlotSize: 0, 201 expected: ` 202 udf 203 ldr x1, [sp, #0x10] 204 ldr d1, [sp, #0x18] 205 exit_sequence x30 206 `, 207 }, 208 { 209 spillSlotSize: 0xffff, 210 expected: ` 211 udf 212 movz x27, #0xf, lsl 0 213 movk x27, #0x1, lsl 16 214 ldr x1, [sp, x27] 215 movz x27, #0x17, lsl 0 216 movk x27, #0x1, lsl 16 217 ldr d1, [sp, x27] 218 exit_sequence x30 219 `, 220 }, 221 { 222 spillSlotSize: 0xffff_00, 223 expected: ` 224 udf 225 movz x27, #0xff10, lsl 0 226 movk x27, #0xff, lsl 16 227 ldr x1, [sp, x27] 228 movz x27, #0xff18, lsl 0 229 movk x27, #0xff, lsl 16 230 ldr d1, [sp, x27] 231 exit_sequence x30 232 `, 233 }, 234 } { 235 t.Run(tc.expected, func(t *testing.T) { 236 ctx, _, m := newSetupWithMockContext() 237 m.spillSlotSize = tc.spillSlotSize 238 239 for _, after := range []bool{false, true} { 240 var name string 241 if after { 242 name = "after" 243 } else { 244 name = "before" 245 } 246 t.Run(name, func(t *testing.T) { 247 ctx.typeOf = map[regalloc.VRegID]ssa.Type{x1VReg.ID(): ssa.TypeI64, v1VReg.ID(): ssa.TypeF64} 248 i1, i2 := m.allocateInstr().asUDF(), m.allocateInstr().asExitSequence(x30VReg) 249 i1.next = i2 250 i2.prev = i1 251 252 if after { 253 m.insertReloadRegisterAt(v1VReg, i1, after) 254 m.insertReloadRegisterAt(x1VReg, i1, after) 255 } else { 256 m.insertReloadRegisterAt(x1VReg, i2, after) 257 m.insertReloadRegisterAt(v1VReg, i2, after) 258 } 259 m.executableContext.RootInstr = i1 260 261 require.Equal(t, tc.expected, m.Format()) 262 }) 263 } 264 }) 265 } 266 } 267 268 func TestRegAllocFunctionImpl_ClobberedRegisters(t *testing.T) { 269 _, _, m := newSetupWithMockContext() 270 f := ®AllocFunctionImpl{m: m} 271 f.ClobberedRegisters([]regalloc.VReg{v19VReg, v19VReg, v19VReg, v19VReg}) 272 require.Equal(t, []regalloc.VReg{v19VReg, v19VReg, v19VReg, v19VReg}, m.clobberedRegs) 273 } 274 275 func TestMachine_swap(t *testing.T) { 276 for _, tc := range []struct { 277 x1, x2, tmp regalloc.VReg 278 expected string 279 }{ 280 { 281 x1: x18VReg, 282 x2: x19VReg, 283 tmp: x20VReg, 284 expected: ` 285 udf 286 mov x20, x18 287 mov x18, x19 288 mov x19, x20 289 exit_sequence x30 290 `, 291 }, 292 { 293 x1: x18VReg, 294 x2: x19VReg, 295 // Tmp not given. 296 expected: ` 297 udf 298 mov x27, x18 299 mov x18, x19 300 mov x19, x27 301 exit_sequence x30 302 `, 303 }, 304 { 305 x1: v18VReg, 306 x2: v19VReg, 307 tmp: v11VReg, 308 expected: ` 309 udf 310 mov v11.16b, v18.16b 311 mov v18.16b, v19.16b 312 mov v19.16b, v11.16b 313 exit_sequence x30 314 `, 315 }, 316 { 317 x1: v18VReg, 318 x2: v19VReg, 319 // Tmp not given. 320 expected: ` 321 udf 322 str d18, [sp, #0x10] 323 mov v18.16b, v19.16b 324 ldr d19, [sp, #0x10] 325 exit_sequence x30 326 `, 327 }, 328 } { 329 t.Run(tc.expected, func(t *testing.T) { 330 ctx, _, m := newSetupWithMockContext() 331 332 ctx.typeOf = map[regalloc.VRegID]ssa.Type{ 333 x18VReg.ID(): ssa.TypeI64, x19VReg.ID(): ssa.TypeI64, 334 v18VReg.ID(): ssa.TypeF64, v19VReg.ID(): ssa.TypeF64, 335 } 336 cur, i2 := m.allocateInstr().asUDF(), m.allocateInstr().asExitSequence(x30VReg) 337 cur.next = i2 338 i2.prev = cur 339 340 m.swap(cur, tc.x1, tc.x2, tc.tmp) 341 m.executableContext.RootInstr = cur 342 343 require.Equal(t, tc.expected, m.Format()) 344 }) 345 } 346 }