github.com/bananabytelabs/wazero@v0.0.0-20240105073314-54b22a776da8/internal/engine/wazevo/ssa/pass_test.go (about) 1 package ssa 2 3 import ( 4 "testing" 5 6 "github.com/bananabytelabs/wazero/internal/testing/require" 7 ) 8 9 func TestBuilder_passes(t *testing.T) { 10 for _, tc := range []struct { 11 name string 12 // pass is the optimization pass to run. 13 pass, 14 // postPass is run after the pass is executed, and can be used to test a pass that depends on another pass. 15 postPass func(b *builder) 16 // setup creates the SSA function in the given *builder. 17 // TODO: when we have the text SSA IR parser, we can eliminate this `setup`, 18 // we could directly decode the *builder from the `before` string. I am still 19 // constantly changing the format, so let's keep setup for now. 20 // `verifier` is executed after executing pass, and can be used to 21 // do the additional verification of the state of SSA function in addition to `after` text result. 22 setup func(*builder) (verifier func(t *testing.T)) 23 // before is the expected SSA function after `setup` is executed. 24 before, 25 // after is the expected output after optimization pass. 26 after string 27 }{ 28 { 29 name: "dead block", 30 pass: passDeadBlockEliminationOpt, 31 setup: func(b *builder) func(*testing.T) { 32 entry := b.AllocateBasicBlock() 33 value := entry.AddParam(b, TypeI32) 34 35 middle1, middle2 := b.AllocateBasicBlock(), b.AllocateBasicBlock() 36 end := b.AllocateBasicBlock() 37 38 b.SetCurrentBlock(entry) 39 { 40 brz := b.AllocateInstruction() 41 brz.AsBrz(value, nil, middle1) 42 b.InsertInstruction(brz) 43 44 jmp := b.AllocateInstruction() 45 jmp.AsJump(nil, middle2) 46 b.InsertInstruction(jmp) 47 } 48 49 b.SetCurrentBlock(middle1) 50 { 51 jmp := b.AllocateInstruction() 52 jmp.AsJump(nil, end) 53 b.InsertInstruction(jmp) 54 } 55 56 b.SetCurrentBlock(middle2) 57 { 58 jmp := b.AllocateInstruction() 59 jmp.AsJump(nil, end) 60 b.InsertInstruction(jmp) 61 } 62 63 { 64 unreachable := b.AllocateBasicBlock() 65 b.SetCurrentBlock(unreachable) 66 jmp := b.AllocateInstruction() 67 jmp.AsJump(nil, end) 68 b.InsertInstruction(jmp) 69 } 70 71 b.SetCurrentBlock(end) 72 { 73 jmp := b.AllocateInstruction() 74 jmp.AsJump(nil, middle1) 75 b.InsertInstruction(jmp) 76 } 77 78 b.Seal(entry) 79 b.Seal(middle1) 80 b.Seal(middle2) 81 b.Seal(end) 82 return nil 83 }, 84 before: ` 85 blk0: (v0:i32) 86 Brz v0, blk1 87 Jump blk2 88 89 blk1: () <-- (blk0,blk3) 90 Jump blk3 91 92 blk2: () <-- (blk0) 93 Jump blk3 94 95 blk3: () <-- (blk1,blk2,blk4) 96 Jump blk1 97 98 blk4: () 99 Jump blk3 100 `, 101 after: ` 102 blk0: (v0:i32) 103 Brz v0, blk1 104 Jump blk2 105 106 blk1: () <-- (blk0,blk3) 107 Jump blk3 108 109 blk2: () <-- (blk0) 110 Jump blk3 111 112 blk3: () <-- (blk1,blk2) 113 Jump blk1 114 `, 115 }, 116 { 117 name: "redundant phis", 118 pass: passRedundantPhiEliminationOpt, 119 setup: func(b *builder) func(*testing.T) { 120 entry, loopHeader, end := b.AllocateBasicBlock(), b.AllocateBasicBlock(), b.AllocateBasicBlock() 121 122 loopHeader.AddParam(b, TypeI32) 123 var1 := b.DeclareVariable(TypeI32) 124 125 b.SetCurrentBlock(entry) 126 { 127 constInst := b.AllocateInstruction() 128 constInst.AsIconst32(0xff) 129 b.InsertInstruction(constInst) 130 iConst := constInst.Return() 131 b.DefineVariable(var1, iConst, entry) 132 133 jmp := b.AllocateInstruction() 134 jmp.AsJump([]Value{iConst}, loopHeader) 135 b.InsertInstruction(jmp) 136 } 137 b.Seal(entry) 138 139 b.SetCurrentBlock(loopHeader) 140 { 141 // At this point, loop is not sealed, so PHI will be added to this header. However, the only 142 // input to the PHI is iConst above, so there must be an alias to iConst from the PHI value. 143 value := b.MustFindValue(var1) 144 145 tmpInst := b.AllocateInstruction() 146 tmpInst.AsIconst32(0xff) 147 b.InsertInstruction(tmpInst) 148 tmp := tmpInst.Return() 149 150 brz := b.AllocateInstruction() 151 brz.AsBrz(value, []Value{tmp}, loopHeader) // Loop to itself. 152 b.InsertInstruction(brz) 153 154 jmp := b.AllocateInstruction() 155 jmp.AsJump(nil, end) 156 b.InsertInstruction(jmp) 157 } 158 b.Seal(loopHeader) 159 160 b.SetCurrentBlock(end) 161 { 162 ret := b.AllocateInstruction() 163 ret.AsReturn(nil) 164 b.InsertInstruction(ret) 165 } 166 return nil 167 }, 168 before: ` 169 blk0: () 170 v1:i32 = Iconst_32 0xff 171 Jump blk1, v1, v1 172 173 blk1: (v0:i32,v2:i32) <-- (blk0,blk1) 174 v3:i32 = Iconst_32 0xff 175 Brz v2, blk1, v3, v2 176 Jump blk2 177 178 blk2: () <-- (blk1) 179 Return 180 `, 181 after: ` 182 blk0: () 183 v1:i32 = Iconst_32 0xff 184 Jump blk1, v1 185 186 blk1: (v0:i32) <-- (blk0,blk1) 187 v3:i32 = Iconst_32 0xff 188 Brz v2, blk1, v3 189 Jump blk2 190 191 blk2: () <-- (blk1) 192 Return 193 `, 194 }, 195 { 196 name: "dead code", 197 pass: passDeadCodeEliminationOpt, 198 setup: func(b *builder) func(*testing.T) { 199 entry, end := b.AllocateBasicBlock(), b.AllocateBasicBlock() 200 201 b.SetCurrentBlock(entry) 202 iconstRefThriceInst := b.AllocateInstruction() 203 iconstRefThriceInst.AsIconst32(3) 204 b.InsertInstruction(iconstRefThriceInst) 205 refThriceVal := iconstRefThriceInst.Return() 206 207 // This has side effect. 208 store := b.AllocateInstruction() 209 store.AsStore(OpcodeStore, refThriceVal, refThriceVal, 0) 210 b.InsertInstruction(store) 211 212 iconstDeadInst := b.AllocateInstruction() 213 iconstDeadInst.AsIconst32(0) 214 b.InsertInstruction(iconstDeadInst) 215 216 iconstRefOnceInst := b.AllocateInstruction() 217 iconstRefOnceInst.AsIconst32(1) 218 b.InsertInstruction(iconstRefOnceInst) 219 refOnceVal := iconstRefOnceInst.Return() 220 221 jmp := b.AllocateInstruction() 222 jmp.AsJump(nil, end) 223 b.InsertInstruction(jmp) 224 225 b.SetCurrentBlock(end) 226 aliasedRefOnceVal := b.allocateValue(refOnceVal.Type()) 227 b.alias(aliasedRefOnceVal, refOnceVal) 228 229 add := b.AllocateInstruction() 230 add.AsIadd(aliasedRefOnceVal, refThriceVal) 231 b.InsertInstruction(add) 232 233 addRes := add.Return() 234 235 ret := b.AllocateInstruction() 236 ret.AsReturn([]Value{addRes}) 237 b.InsertInstruction(ret) 238 return func(t *testing.T) { 239 // Group IDs. 240 const gid0, gid1, gid2 InstructionGroupID = 0, 1, 2 241 require.Equal(t, gid0, iconstRefThriceInst.gid) 242 require.Equal(t, gid0, store.gid) 243 require.Equal(t, gid1, iconstDeadInst.gid) 244 require.Equal(t, gid1, iconstRefOnceInst.gid) 245 require.Equal(t, gid1, jmp.gid) 246 // Different blocks have different gids. 247 require.Equal(t, gid2, add.gid) 248 require.Equal(t, gid2, ret.gid) 249 250 // Dead or Alive... 251 require.False(t, iconstDeadInst.live) 252 require.True(t, iconstRefOnceInst.live) 253 require.True(t, iconstRefThriceInst.live) 254 require.True(t, add.live) 255 require.True(t, jmp.live) 256 require.True(t, ret.live) 257 258 require.Equal(t, 1, b.valueRefCounts[refOnceVal.ID()]) 259 require.Equal(t, 1, b.valueRefCounts[addRes.ID()]) 260 require.Equal(t, 3, b.valueRefCounts[refThriceVal.ID()]) 261 } 262 }, 263 before: ` 264 blk0: () 265 v0:i32 = Iconst_32 0x3 266 Store v0, v0, 0x0 267 v1:i32 = Iconst_32 0x0 268 v2:i32 = Iconst_32 0x1 269 Jump blk1 270 271 blk1: () <-- (blk0) 272 v4:i32 = Iadd v3, v0 273 Return v4 274 `, 275 after: ` 276 blk0: () 277 v0:i32 = Iconst_32 0x3 278 Store v0, v0, 0x0 279 v2:i32 = Iconst_32 0x1 280 Jump blk1 281 282 blk1: () <-- (blk0) 283 v4:i32 = Iadd v2, v0 284 Return v4 285 `, 286 }, 287 { 288 name: "nop elimination", 289 pass: passNopInstElimination, 290 postPass: passDeadCodeEliminationOpt, 291 setup: func(b *builder) (verifier func(t *testing.T)) { 292 entry := b.AllocateBasicBlock() 293 b.SetCurrentBlock(entry) 294 295 i32Param := entry.AddParam(b, TypeI32) 296 i64Param := entry.AddParam(b, TypeI64) 297 298 // 32-bit shift. 299 moduleZeroI32 := b.AllocateInstruction().AsIconst32(32 * 245).Insert(b).Return() 300 nopIshl := b.AllocateInstruction().AsIshl(i32Param, moduleZeroI32).Insert(b).Return() 301 302 // 64-bit shift. 303 moduleZeroI64 := b.AllocateInstruction().AsIconst64(64 * 245).Insert(b).Return() 304 nopUshr := b.AllocateInstruction().AsUshr(i64Param, moduleZeroI64).Insert(b).Return() 305 306 // Non zero shift amount should not be eliminated. 307 nonZeroI32 := b.AllocateInstruction().AsIconst32(32*245 + 1).Insert(b).Return() 308 nonZeroIshl := b.AllocateInstruction().AsIshl(i32Param, nonZeroI32).Insert(b).Return() 309 310 nonZeroI64 := b.AllocateInstruction().AsIconst64(64*245 + 1).Insert(b).Return() 311 nonZeroSshr := b.AllocateInstruction().AsSshr(i64Param, nonZeroI64).Insert(b).Return() 312 313 ret := b.AllocateInstruction() 314 ret.AsReturn([]Value{nopIshl, nopUshr, nonZeroIshl, nonZeroSshr}) 315 b.InsertInstruction(ret) 316 return nil 317 }, 318 before: ` 319 blk0: (v0:i32, v1:i64) 320 v2:i32 = Iconst_32 0x1ea0 321 v3:i32 = Ishl v0, v2 322 v4:i64 = Iconst_64 0x3d40 323 v5:i64 = Ushr v1, v4 324 v6:i32 = Iconst_32 0x1ea1 325 v7:i32 = Ishl v0, v6 326 v8:i64 = Iconst_64 0x3d41 327 v9:i64 = Sshr v1, v8 328 Return v3, v5, v7, v9 329 `, 330 after: ` 331 blk0: (v0:i32, v1:i64) 332 v6:i32 = Iconst_32 0x1ea1 333 v7:i32 = Ishl v0, v6 334 v8:i64 = Iconst_64 0x3d41 335 v9:i64 = Sshr v1, v8 336 Return v0, v1, v7, v9 337 `, 338 }, 339 } { 340 tc := tc 341 t.Run(tc.name, func(t *testing.T) { 342 b := NewBuilder().(*builder) 343 verifier := tc.setup(b) 344 require.Equal(t, tc.before, b.Format()) 345 tc.pass(b) 346 if verifier != nil { 347 verifier(t) 348 } 349 if tc.postPass != nil { 350 tc.postPass(b) 351 } 352 require.Equal(t, tc.after, b.Format()) 353 }) 354 } 355 }