github.com/tetratelabs/wazero@v1.7.3-0.20240513003603-48f702e154b5/internal/engine/wazevo/ssa/pass_blk_layouts_test.go (about)

     1  package ssa
     2  
     3  import (
     4  	"testing"
     5  
     6  	"github.com/tetratelabs/wazero/internal/testing/require"
     7  )
     8  
     9  func Test_maybeInvertBranch(t *testing.T) {
    10  	insertJump := func(b *builder, src, dst *basicBlock) {
    11  		b.SetCurrentBlock(src)
    12  		jump := b.AllocateInstruction()
    13  		jump.AsJump(ValuesNil, dst)
    14  		b.InsertInstruction(jump)
    15  	}
    16  
    17  	insertBrz := func(b *builder, src, dst *basicBlock) {
    18  		b.SetCurrentBlock(src)
    19  		vinst := b.AllocateInstruction()
    20  		vinst.AsIconst32(0)
    21  		b.InsertInstruction(vinst)
    22  		v := vinst.Return()
    23  		brz := b.AllocateInstruction()
    24  		brz.AsBrz(v, ValuesNil, dst)
    25  		b.InsertInstruction(brz)
    26  	}
    27  
    28  	for _, tc := range []struct {
    29  		name  string
    30  		setup func(b *builder) (now, next *basicBlock, verify func(t *testing.T))
    31  		exp   bool
    32  	}{
    33  		{
    34  			name: "ends with br_table",
    35  			setup: func(b *builder) (now, next *basicBlock, verify func(t *testing.T)) {
    36  				now, next = b.allocateBasicBlock(), b.allocateBasicBlock()
    37  				inst := b.AllocateInstruction()
    38  				// TODO: we haven't implemented AsBrTable on Instruction.
    39  				inst.opcode = OpcodeBrTable
    40  				now.currentInstr = inst
    41  				verify = func(t *testing.T) { require.Equal(t, OpcodeBrTable, inst.opcode) }
    42  				return
    43  			},
    44  		},
    45  		{
    46  			name: "no conditional branch without previous instruction",
    47  			setup: func(b *builder) (now, next *basicBlock, verify func(t *testing.T)) {
    48  				now, next = b.allocateBasicBlock(), b.allocateBasicBlock()
    49  				insertJump(b, now, next)
    50  				verify = func(t *testing.T) {
    51  					tail := now.currentInstr
    52  					require.Equal(t, OpcodeJump, tail.opcode)
    53  				}
    54  				return
    55  			},
    56  		},
    57  		{
    58  			name: "no conditional branch with previous instruction",
    59  			setup: func(b *builder) (now, next *basicBlock, verify func(t *testing.T)) {
    60  				now, next = b.allocateBasicBlock(), b.allocateBasicBlock()
    61  				b.SetCurrentBlock(now)
    62  				prev := b.AllocateInstruction()
    63  				prev.AsIconst64(1)
    64  				b.InsertInstruction(prev)
    65  				insertJump(b, now, next)
    66  				verify = func(t *testing.T) {
    67  					tail := now.currentInstr
    68  					require.Equal(t, OpcodeJump, tail.opcode)
    69  					require.Equal(t, prev, tail.prev)
    70  				}
    71  				return
    72  			},
    73  		},
    74  		{
    75  			name: "tail target is already loop",
    76  			setup: func(b *builder) (now, next *basicBlock, verify func(t *testing.T)) {
    77  				now, next, loopHeader, dummy := b.allocateBasicBlock(), b.allocateBasicBlock(), b.allocateBasicBlock(), b.allocateBasicBlock()
    78  				loopHeader.loopHeader = true
    79  				insertBrz(b, now, dummy)
    80  				insertJump(b, now, loopHeader)
    81  				verify = func(t *testing.T) {
    82  					tail := now.currentInstr
    83  					conditionalBr := tail.prev
    84  					require.Equal(t, OpcodeJump, tail.opcode)
    85  					require.Equal(t, OpcodeBrz, conditionalBr.opcode) // intact.
    86  					require.Equal(t, conditionalBr, tail.prev)
    87  				}
    88  				return
    89  			},
    90  		},
    91  		{
    92  			name: "tail target is already the next block",
    93  			setup: func(b *builder) (now, next *basicBlock, verify func(t *testing.T)) {
    94  				now, next, dummy := b.allocateBasicBlock(), b.allocateBasicBlock(), b.allocateBasicBlock()
    95  				insertBrz(b, now, dummy)
    96  				insertJump(b, now, next)
    97  				verify = func(t *testing.T) {
    98  					tail := now.currentInstr
    99  					conditionalBr := tail.prev
   100  					require.Equal(t, OpcodeJump, tail.opcode)
   101  					require.Equal(t, OpcodeBrz, conditionalBr.opcode) // intact.
   102  					require.Equal(t, conditionalBr, tail.prev)
   103  				}
   104  				return
   105  			},
   106  		},
   107  		{
   108  			name: "conditional target is loop",
   109  			setup: func(b *builder) (now, next *basicBlock, verify func(t *testing.T)) {
   110  				now, next, loopHeader := b.allocateBasicBlock(), b.allocateBasicBlock(), b.allocateBasicBlock()
   111  				loopHeader.loopHeader = true
   112  				insertBrz(b, now, loopHeader) // jump to loop, which needs inversion.
   113  				insertJump(b, now, next)
   114  
   115  				tail := now.currentInstr
   116  				conditionalBr := tail.prev
   117  
   118  				// Sanity check before inversion.
   119  				require.Equal(t, conditionalBr, loopHeader.preds[0].branch)
   120  				require.Equal(t, tail, next.preds[0].branch)
   121  				verify = func(t *testing.T) {
   122  					require.Equal(t, OpcodeJump, tail.opcode)
   123  					require.Equal(t, OpcodeBrnz, conditionalBr.opcode) // inversion.
   124  					require.Equal(t, loopHeader, tail.blk)             // swapped.
   125  					require.Equal(t, next, conditionalBr.blk)          // swapped.
   126  					require.Equal(t, conditionalBr, tail.prev)
   127  
   128  					// Predecessor info should correctly point to the inverted jump instruction.
   129  					require.Equal(t, tail, loopHeader.preds[0].branch)
   130  					require.Equal(t, conditionalBr, next.preds[0].branch)
   131  				}
   132  				return
   133  			},
   134  			exp: true,
   135  		},
   136  		{
   137  			name: "conditional target is the next block",
   138  			setup: func(b *builder) (now, next *basicBlock, verify func(t *testing.T)) {
   139  				now, next = b.allocateBasicBlock(), b.allocateBasicBlock()
   140  				nowTarget := b.allocateBasicBlock()
   141  				insertBrz(b, now, next) // jump to the next block in conditional, which needs inversion.
   142  				insertJump(b, now, nowTarget)
   143  
   144  				tail := now.currentInstr
   145  				conditionalBr := tail.prev
   146  
   147  				// Sanity check before inversion.
   148  				require.Equal(t, tail, nowTarget.preds[0].branch)
   149  				require.Equal(t, conditionalBr, next.preds[0].branch)
   150  
   151  				verify = func(t *testing.T) {
   152  					require.Equal(t, OpcodeJump, tail.opcode)
   153  					require.Equal(t, OpcodeBrnz, conditionalBr.opcode) // inversion.
   154  					require.Equal(t, next, tail.blk)                   // swapped.
   155  					require.Equal(t, nowTarget, conditionalBr.blk)     // swapped.
   156  					require.Equal(t, conditionalBr, tail.prev)
   157  
   158  					require.Equal(t, conditionalBr, nowTarget.preds[0].branch)
   159  					require.Equal(t, tail, next.preds[0].branch)
   160  				}
   161  				return
   162  			},
   163  			exp: true,
   164  		},
   165  	} {
   166  		t.Run(tc.name, func(t *testing.T) {
   167  			b := NewBuilder().(*builder)
   168  			now, next, verify := tc.setup(b)
   169  			actual := maybeInvertBranches(now, next)
   170  			verify(t)
   171  			require.Equal(t, tc.exp, actual)
   172  		})
   173  	}
   174  }
   175  
   176  func TestBuilder_splitCriticalEdge(t *testing.T) {
   177  	b := NewBuilder().(*builder)
   178  	predBlk, dummyBlk, dummyBlk2 := b.allocateBasicBlock(), b.allocateBasicBlock(), b.allocateBasicBlock()
   179  	predBlk.reversePostOrder = 100
   180  	b.SetCurrentBlock(predBlk)
   181  	inst := b.AllocateInstruction()
   182  	inst.AsIconst32(1)
   183  	b.InsertInstruction(inst)
   184  	v := inst.Return()
   185  	originalBrz := b.AllocateInstruction() // This is the split edge.
   186  	originalBrz.AsBrz(v, ValuesNil, dummyBlk)
   187  	b.InsertInstruction(originalBrz)
   188  	dummyJump := b.AllocateInstruction()
   189  	dummyJump.AsJump(ValuesNil, dummyBlk2)
   190  	b.InsertInstruction(dummyJump)
   191  
   192  	predInfo := &basicBlockPredecessorInfo{blk: predBlk, branch: originalBrz}
   193  	trampoline := b.splitCriticalEdge(predBlk, dummyBlk, predInfo)
   194  	require.NotNil(t, trampoline)
   195  	require.Equal(t, 100, trampoline.reversePostOrder)
   196  
   197  	require.Equal(t, trampoline, predInfo.blk)
   198  	require.Equal(t, originalBrz, predInfo.branch)
   199  	require.Equal(t, trampoline.rootInstr, predInfo.branch)
   200  	require.Equal(t, trampoline.currentInstr, predInfo.branch)
   201  	require.Equal(t, trampoline.success[0], dummyBlk)
   202  
   203  	replacedBrz := predBlk.rootInstr.next
   204  	require.Equal(t, OpcodeBrz, replacedBrz.opcode)
   205  	require.Equal(t, trampoline, replacedBrz.blk)
   206  }
   207  
   208  func Test_swapInstruction(t *testing.T) {
   209  	t.Run("swap root", func(t *testing.T) {
   210  		b := NewBuilder().(*builder)
   211  		blk := b.allocateBasicBlock()
   212  
   213  		dummy := b.AllocateInstruction()
   214  
   215  		old := b.AllocateInstruction()
   216  		old.next, dummy.prev = dummy, old
   217  		newi := b.AllocateInstruction()
   218  		blk.rootInstr = old
   219  		swapInstruction(blk, old, newi)
   220  
   221  		require.Equal(t, newi, blk.rootInstr)
   222  		require.Equal(t, dummy, newi.next)
   223  		require.Equal(t, dummy.prev, newi)
   224  		require.Nil(t, old.next)
   225  		require.Nil(t, old.prev)
   226  	})
   227  	t.Run("swap middle", func(t *testing.T) {
   228  		b := NewBuilder().(*builder)
   229  		blk := b.allocateBasicBlock()
   230  		b.SetCurrentBlock(blk)
   231  		i1, i2, i3 := b.AllocateInstruction(), b.AllocateInstruction(), b.AllocateInstruction()
   232  		i1.AsIconst32(1)
   233  		i2.AsIconst32(2)
   234  		i3.AsIconst32(3)
   235  		b.InsertInstruction(i1)
   236  		b.InsertInstruction(i2)
   237  		b.InsertInstruction(i3)
   238  
   239  		newi := b.AllocateInstruction()
   240  		newi.AsIconst32(100)
   241  		swapInstruction(blk, i2, newi)
   242  
   243  		require.Equal(t, i1, blk.rootInstr)
   244  		require.Equal(t, newi, i1.next)
   245  		require.Equal(t, i3, newi.next)
   246  		require.Equal(t, i1, newi.prev)
   247  		require.Equal(t, newi, i3.prev)
   248  		require.Nil(t, i2.next)
   249  		require.Nil(t, i2.prev)
   250  	})
   251  	t.Run("swap tail", func(t *testing.T) {
   252  		b := NewBuilder().(*builder)
   253  		blk := b.allocateBasicBlock()
   254  		b.SetCurrentBlock(blk)
   255  		i1, i2 := b.AllocateInstruction(), b.AllocateInstruction()
   256  		i1.AsIconst32(1)
   257  		i2.AsIconst32(2)
   258  		b.InsertInstruction(i1)
   259  		b.InsertInstruction(i2)
   260  
   261  		newi := b.AllocateInstruction()
   262  		newi.AsIconst32(100)
   263  		swapInstruction(blk, i2, newi)
   264  
   265  		require.Equal(t, i1, blk.rootInstr)
   266  		require.Equal(t, newi, blk.currentInstr)
   267  		require.Equal(t, newi, i1.next)
   268  		require.Equal(t, i1, newi.prev)
   269  		require.Nil(t, newi.next)
   270  		require.Nil(t, i2.next)
   271  		require.Nil(t, i2.prev)
   272  	})
   273  }
   274  
   275  func TestBuilder_LayoutBlocks(t *testing.T) {
   276  	insertJump := func(b *builder, src, dst *basicBlock, vs ...Value) {
   277  		b.SetCurrentBlock(src)
   278  		jump := b.AllocateInstruction()
   279  		args := b.varLengthPool.Allocate(len(vs))
   280  		args = args.Append(&b.varLengthPool, vs...)
   281  		jump.AsJump(args, dst)
   282  		b.InsertInstruction(jump)
   283  	}
   284  
   285  	insertBrz := func(b *builder, src, dst *basicBlock, condVal Value, vs ...Value) {
   286  		b.SetCurrentBlock(src)
   287  		vinst := b.AllocateInstruction().AsIconst32(0)
   288  		b.InsertInstruction(vinst)
   289  		brz := b.AllocateInstruction()
   290  		args := b.varLengthPool.Allocate(len(vs))
   291  		args = args.Append(&b.varLengthPool, vs...)
   292  		brz.AsBrz(condVal, args, dst)
   293  		b.InsertInstruction(brz)
   294  	}
   295  
   296  	for _, tc := range []struct {
   297  		name  string
   298  		setup func(b *builder)
   299  		exp   []BasicBlockID
   300  	}{
   301  		{
   302  			name: "sequential - no critical edge",
   303  			setup: func(b *builder) {
   304  				b1, b2, b3, b4 := b.allocateBasicBlock(), b.allocateBasicBlock(), b.allocateBasicBlock(), b.allocateBasicBlock()
   305  				insertJump(b, b1, b2)
   306  				insertJump(b, b2, b3)
   307  				insertJump(b, b3, b4)
   308  				b.Seal(b1)
   309  				b.Seal(b2)
   310  				b.Seal(b3)
   311  				b.Seal(b4)
   312  			},
   313  			exp: []BasicBlockID{0, 1, 2, 3},
   314  		},
   315  		{
   316  			name: "sequential with unreachable predecessor",
   317  			setup: func(b *builder) {
   318  				b0, unreachable, b2 := b.allocateBasicBlock(), b.allocateBasicBlock(), b.allocateBasicBlock()
   319  				insertJump(b, b0, b2)
   320  				insertJump(b, unreachable, b2)
   321  				unreachable.invalid = true
   322  				b.Seal(b0)
   323  				b.Seal(unreachable)
   324  				b.Seal(b2)
   325  			},
   326  			exp: []BasicBlockID{0, 2},
   327  		},
   328  		{
   329  			name: "merge - no critical edge",
   330  			// 0 -> 1 -> 3
   331  			// |         ^
   332  			// v         |
   333  			// 2 ---------
   334  			setup: func(b *builder) {
   335  				b0, b1, b2, b3 := b.allocateBasicBlock(), b.allocateBasicBlock(), b.allocateBasicBlock(), b.allocateBasicBlock()
   336  				b.SetCurrentBlock(b0)
   337  				c := b.AllocateInstruction().AsIconst32(0)
   338  				b.InsertInstruction(c)
   339  				insertBrz(b, b0, b2, c.Return())
   340  				insertJump(b, b0, b1)
   341  				insertJump(b, b1, b3)
   342  				insertJump(b, b2, b3)
   343  				b.Seal(b0)
   344  				b.Seal(b1)
   345  				b.Seal(b2)
   346  				b.Seal(b3)
   347  			},
   348  			exp: []BasicBlockID{0, 1, 2, 3},
   349  		},
   350  		{
   351  			name: "loop towards loop header in fallthrough",
   352  			//    0
   353  			//    v
   354  			//    1<--+
   355  			//    |   | <---- critical
   356  			//    2---+
   357  			//    v
   358  			//    3
   359  			//
   360  			// ==>
   361  			//
   362  			//    0
   363  			//    v
   364  			//    1<---+
   365  			//    |    |
   366  			//    2--->4
   367  			//    v
   368  			//    3
   369  			setup: func(b *builder) {
   370  				b0, b1, b2, b3 := b.allocateBasicBlock(), b.allocateBasicBlock(), b.allocateBasicBlock(), b.allocateBasicBlock()
   371  				insertJump(b, b0, b1)
   372  				insertJump(b, b1, b2)
   373  				b.SetCurrentBlock(b2)
   374  				c := b.AllocateInstruction().AsIconst32(0)
   375  				b.InsertInstruction(c)
   376  				insertBrz(b, b2, b1, c.Return())
   377  				insertJump(b, b2, b3)
   378  				b.Seal(b0)
   379  				b.Seal(b1)
   380  				b.Seal(b2)
   381  				b.Seal(b3)
   382  			},
   383  			// The trampoline 4 is placed right after 2, which is the hot path of the loop.
   384  			exp: []BasicBlockID{0, 1, 2, 4, 3},
   385  		},
   386  		{
   387  			name: "loop - towards loop header in conditional branch",
   388  			//    0
   389  			//    v
   390  			//    1<--+
   391  			//    |   | <---- critical
   392  			//    2---+
   393  			//    v
   394  			//    3
   395  			//
   396  			// ==>
   397  			//
   398  			//    0
   399  			//    v
   400  			//    1<---+
   401  			//    |    |
   402  			//    2--->4
   403  			//    v
   404  			//    3
   405  			setup: func(b *builder) {
   406  				b0, b1, b2, b3 := b.allocateBasicBlock(), b.allocateBasicBlock(), b.allocateBasicBlock(), b.allocateBasicBlock()
   407  				insertJump(b, b0, b1)
   408  				insertJump(b, b1, b2)
   409  				b.SetCurrentBlock(b2)
   410  				c := b.AllocateInstruction().AsIconst32(0)
   411  				b.InsertInstruction(c)
   412  				insertBrz(b, b2, b3, c.Return())
   413  				insertJump(b, b2, b1)
   414  				b.Seal(b0)
   415  				b.Seal(b1)
   416  				b.Seal(b2)
   417  				b.Seal(b3)
   418  			},
   419  			// The trampoline 4 is placed right after 2, which is the hot path of the loop.
   420  			exp: []BasicBlockID{0, 1, 2, 4, 3},
   421  		},
   422  		{
   423  			name: "loop with header is critical backward edge",
   424  			//    0
   425  			//    v
   426  			//    1<--+
   427  			//  / |   |
   428  			// 3  2   | <--- critical
   429  			//  \ |   |
   430  			//    4---+
   431  			//    v
   432  			//    5
   433  			//
   434  			// ==>
   435  			//
   436  			//    0
   437  			//    v
   438  			//    1<----+
   439  			//  / |     |
   440  			// 3  2     |
   441  			//  \ |     |
   442  			//    4---->6
   443  			//    v
   444  			//    5
   445  			setup: func(b *builder) {
   446  				b0, b1, b2, b3, b4, b5 := b.allocateBasicBlock(), b.allocateBasicBlock(), b.allocateBasicBlock(),
   447  					b.allocateBasicBlock(), b.allocateBasicBlock(), b.allocateBasicBlock()
   448  				insertJump(b, b0, b1)
   449  				b.SetCurrentBlock(b0)
   450  				c1 := b.AllocateInstruction().AsIconst32(0)
   451  				b.InsertInstruction(c1)
   452  				insertBrz(b, b1, b2, c1.Return())
   453  				insertJump(b, b1, b3)
   454  				insertJump(b, b3, b4)
   455  				insertJump(b, b2, b4)
   456  				b.SetCurrentBlock(b4)
   457  				c2 := b.AllocateInstruction().AsIconst32(0)
   458  				b.InsertInstruction(c2)
   459  				insertBrz(b, b4, b1, c2.Return())
   460  				insertJump(b, b4, b5)
   461  				b.Seal(b0)
   462  				b.Seal(b1)
   463  				b.Seal(b2)
   464  				b.Seal(b3)
   465  				b.Seal(b4)
   466  				b.Seal(b5)
   467  			},
   468  			// The trampoline 6 is placed right after 4, which is the hot path of the loop.
   469  			exp: []BasicBlockID{0, 1, 3, 2, 4, 6, 5},
   470  		},
   471  		{
   472  			name: "multiple critical edges",
   473  			//                   0
   474  			//                   v
   475  			//               +---1<--+
   476  			//               |   v   | <---- critical
   477  			// critical ---->|   2 --+
   478  			//               |   | <-------- critical
   479  			//               |   v
   480  			//               +-->3--->4
   481  			//
   482  			// ==>
   483  			//
   484  			//                   0
   485  			//                   v
   486  			//               +---1<---+
   487  			//               |   v    |
   488  			//               5   2 -->6
   489  			//               |   v
   490  			//               |   7
   491  			//               |   v
   492  			//               +-->3--->4
   493  			setup: func(b *builder) {
   494  				b0, b1, b2, b3, b4 := b.allocateBasicBlock(), b.allocateBasicBlock(), b.allocateBasicBlock(),
   495  					b.allocateBasicBlock(), b.allocateBasicBlock()
   496  				insertJump(b, b0, b1)
   497  				b.SetCurrentBlock(b1)
   498  				c1 := b.AllocateInstruction().AsIconst32(0)
   499  				b.InsertInstruction(c1)
   500  				insertBrz(b, b1, b2, c1.Return())
   501  				insertJump(b, b1, b3)
   502  
   503  				b.SetCurrentBlock(b2)
   504  				c2 := b.AllocateInstruction().AsIconst32(0)
   505  				b.InsertInstruction(c2)
   506  				insertBrz(b, b2, b1, c2.Return())
   507  				insertJump(b, b2, b3)
   508  				insertJump(b, b3, b4)
   509  
   510  				b.Seal(b0)
   511  				b.Seal(b1)
   512  				b.Seal(b2)
   513  				b.Seal(b3)
   514  				b.Seal(b4)
   515  			},
   516  			exp: []BasicBlockID{
   517  				0, 1,
   518  				// block 2 has loop header (1) as the conditional branch target, so it's inverted,
   519  				// and the split edge trampoline is placed right after 2 which is the hot path of the loop.
   520  				2, 6,
   521  				// Then the placement iteration goes to 3, which has two (5, 7) unplaced trampolines as predecessors,
   522  				// so they are placed before 3.
   523  				5, 7, 3,
   524  				// Then the final block.
   525  				4,
   526  			},
   527  		},
   528  		{
   529  			name: "brz with arg",
   530  			setup: func(b *builder) {
   531  				b0, b1, b2 := b.allocateBasicBlock(), b.allocateBasicBlock(), b.allocateBasicBlock()
   532  				p := b0.AddParam(b, TypeI32)
   533  				retval := b1.AddParam(b, TypeI32)
   534  
   535  				b.SetCurrentBlock(b0)
   536  				{
   537  					arg := b.AllocateInstruction().AsIconst32(1000).Insert(b).Return()
   538  					insertBrz(b, b0, b1, p, arg)
   539  					insertJump(b, b0, b2)
   540  				}
   541  				b.SetCurrentBlock(b1)
   542  				{
   543  					args := b.varLengthPool.Allocate(1)
   544  					args = args.Append(&b.varLengthPool, retval)
   545  					b.AllocateInstruction().AsReturn(args).Insert(b)
   546  				}
   547  				b.SetCurrentBlock(b2)
   548  				{
   549  					arg := b.AllocateInstruction().AsIconst32(1).Insert(b).Return()
   550  					insertJump(b, b2, b1, arg)
   551  				}
   552  
   553  				b.Seal(b0)
   554  				b.Seal(b1)
   555  				b.Seal(b2)
   556  			},
   557  			exp: []BasicBlockID{0x0, 0x3, 0x1, 0x2},
   558  		},
   559  		{
   560  			name: "loop with output",
   561  			exp:  []BasicBlockID{0x0, 0x2, 0x4, 0x1, 0x3, 0x6, 0x5},
   562  			setup: func(b *builder) {
   563  				b.currentSignature = &Signature{Results: []Type{TypeI32}}
   564  				b0, b1, b2, b3 := b.allocateBasicBlock(), b.allocateBasicBlock(), b.allocateBasicBlock(), b.allocateBasicBlock()
   565  
   566  				b.SetCurrentBlock(b0)
   567  				funcParam := b0.AddParam(b, TypeI32)
   568  				b2Param := b2.AddParam(b, TypeI32)
   569  				insertJump(b, b0, b2, funcParam)
   570  
   571  				b.SetCurrentBlock(b1)
   572  				{
   573  					returnParam := b1.AddParam(b, TypeI32)
   574  					insertJump(b, b1, b.returnBlk, returnParam)
   575  				}
   576  
   577  				b.SetCurrentBlock(b2)
   578  				{
   579  					c := b.AllocateInstruction().AsIconst32(100).Insert(b)
   580  					cmp := b.AllocateInstruction().
   581  						AsIcmp(b2Param, c.Return(), IntegerCmpCondUnsignedLessThan).
   582  						Insert(b)
   583  					insertBrz(b, b2, b1, cmp.Return(), b2Param)
   584  					insertJump(b, b2, b3)
   585  				}
   586  
   587  				b.SetCurrentBlock(b3)
   588  				{
   589  					one := b.AllocateInstruction().AsIconst32(1).Insert(b)
   590  					minusOned := b.AllocateInstruction().AsIsub(b2Param, one.Return()).Insert(b)
   591  					c := b.AllocateInstruction().AsIconst32(150).Insert(b)
   592  					cmp := b.AllocateInstruction().
   593  						AsIcmp(b2Param, c.Return(), IntegerCmpCondEqual).
   594  						Insert(b)
   595  					insertBrz(b, b3, b1, cmp.Return(), minusOned.Return())
   596  					insertJump(b, b3, b2, minusOned.Return())
   597  				}
   598  
   599  				b.Seal(b0)
   600  				b.Seal(b1)
   601  				b.Seal(b2)
   602  				b.Seal(b3)
   603  			},
   604  		},
   605  	} {
   606  		tc := tc
   607  		t.Run(tc.name, func(t *testing.T) {
   608  			b := NewBuilder().(*builder)
   609  			tc.setup(b)
   610  
   611  			b.runPreBlockLayoutPasses() // LayoutBlocks() must be called after RunPasses().
   612  			b.runBlockLayoutPass()
   613  			var actual []BasicBlockID
   614  			for blk := b.BlockIteratorReversePostOrderBegin(); blk != nil; blk = b.BlockIteratorReversePostOrderNext() {
   615  				actual = append(actual, blk.(*basicBlock).id)
   616  			}
   617  			require.Equal(t, tc.exp, actual)
   618  		})
   619  	}
   620  }