github.com/wasilibs/wazerox@v0.0.0-20240124024944-4923be63ab5f/internal/engine/wazevo/ssa/builder_test.go (about)

     1  package ssa
     2  
     3  import (
     4  	"testing"
     5  
     6  	"github.com/wasilibs/wazerox/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(nil, 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, nil, 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, nil, dummyBlk)
   187  	b.InsertInstruction(originalBrz)
   188  	dummyJump := b.AllocateInstruction()
   189  	dummyJump.AsJump(nil, 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  		jump.AsJump(vs, dst)
   280  		b.InsertInstruction(jump)
   281  	}
   282  
   283  	insertBrz := func(b *builder, src, dst *basicBlock, condVal Value, vs ...Value) {
   284  		b.SetCurrentBlock(src)
   285  		vinst := b.AllocateInstruction().AsIconst32(0)
   286  		b.InsertInstruction(vinst)
   287  		brz := b.AllocateInstruction()
   288  		brz.AsBrz(condVal, vs, dst)
   289  		b.InsertInstruction(brz)
   290  	}
   291  
   292  	for _, tc := range []struct {
   293  		name  string
   294  		setup func(b *builder)
   295  		exp   []BasicBlockID
   296  	}{
   297  		{
   298  			name: "sequential - no critical edge",
   299  			setup: func(b *builder) {
   300  				b1, b2, b3, b4 := b.allocateBasicBlock(), b.allocateBasicBlock(), b.allocateBasicBlock(), b.allocateBasicBlock()
   301  				insertJump(b, b1, b2)
   302  				insertJump(b, b2, b3)
   303  				insertJump(b, b3, b4)
   304  				b.Seal(b1)
   305  				b.Seal(b2)
   306  				b.Seal(b3)
   307  				b.Seal(b4)
   308  			},
   309  			exp: []BasicBlockID{0, 1, 2, 3},
   310  		},
   311  		{
   312  			name: "sequential with unreachable predecessor",
   313  			setup: func(b *builder) {
   314  				b0, unreachable, b2 := b.allocateBasicBlock(), b.allocateBasicBlock(), b.allocateBasicBlock()
   315  				insertJump(b, b0, b2)
   316  				insertJump(b, unreachable, b2)
   317  				unreachable.invalid = true
   318  				b.Seal(b0)
   319  				b.Seal(unreachable)
   320  				b.Seal(b2)
   321  			},
   322  			exp: []BasicBlockID{0, 2},
   323  		},
   324  		{
   325  			name: "merge - no critical edge",
   326  			// 0 -> 1 -> 3
   327  			// |         ^
   328  			// v         |
   329  			// 2 ---------
   330  			setup: func(b *builder) {
   331  				b0, b1, b2, b3 := b.allocateBasicBlock(), b.allocateBasicBlock(), b.allocateBasicBlock(), b.allocateBasicBlock()
   332  				b.SetCurrentBlock(b0)
   333  				c := b.AllocateInstruction().AsIconst32(0)
   334  				b.InsertInstruction(c)
   335  				insertBrz(b, b0, b2, c.Return())
   336  				insertJump(b, b0, b1)
   337  				insertJump(b, b1, b3)
   338  				insertJump(b, b2, b3)
   339  				b.Seal(b0)
   340  				b.Seal(b1)
   341  				b.Seal(b2)
   342  				b.Seal(b3)
   343  			},
   344  			exp: []BasicBlockID{0, 1, 2, 3},
   345  		},
   346  		{
   347  			name: "loop towards loop header in fallthrough",
   348  			//    0
   349  			//    v
   350  			//    1<--+
   351  			//    |   | <---- critical
   352  			//    2---+
   353  			//    v
   354  			//    3
   355  			//
   356  			// ==>
   357  			//
   358  			//    0
   359  			//    v
   360  			//    1<---+
   361  			//    |    |
   362  			//    2--->4
   363  			//    v
   364  			//    3
   365  			setup: func(b *builder) {
   366  				b0, b1, b2, b3 := b.allocateBasicBlock(), b.allocateBasicBlock(), b.allocateBasicBlock(), b.allocateBasicBlock()
   367  				insertJump(b, b0, b1)
   368  				insertJump(b, b1, b2)
   369  				b.SetCurrentBlock(b2)
   370  				c := b.AllocateInstruction().AsIconst32(0)
   371  				b.InsertInstruction(c)
   372  				insertBrz(b, b2, b1, c.Return())
   373  				insertJump(b, b2, b3)
   374  				b.Seal(b0)
   375  				b.Seal(b1)
   376  				b.Seal(b2)
   377  				b.Seal(b3)
   378  			},
   379  			// The trampoline 4 is placed right after 2, which is the hot path of the loop.
   380  			exp: []BasicBlockID{0, 1, 2, 4, 3},
   381  		},
   382  		{
   383  			name: "loop - towards loop header in conditional branch",
   384  			//    0
   385  			//    v
   386  			//    1<--+
   387  			//    |   | <---- critical
   388  			//    2---+
   389  			//    v
   390  			//    3
   391  			//
   392  			// ==>
   393  			//
   394  			//    0
   395  			//    v
   396  			//    1<---+
   397  			//    |    |
   398  			//    2--->4
   399  			//    v
   400  			//    3
   401  			setup: func(b *builder) {
   402  				b0, b1, b2, b3 := b.allocateBasicBlock(), b.allocateBasicBlock(), b.allocateBasicBlock(), b.allocateBasicBlock()
   403  				insertJump(b, b0, b1)
   404  				insertJump(b, b1, b2)
   405  				b.SetCurrentBlock(b2)
   406  				c := b.AllocateInstruction().AsIconst32(0)
   407  				b.InsertInstruction(c)
   408  				insertBrz(b, b2, b3, c.Return())
   409  				insertJump(b, b2, b1)
   410  				b.Seal(b0)
   411  				b.Seal(b1)
   412  				b.Seal(b2)
   413  				b.Seal(b3)
   414  			},
   415  			// The trampoline 4 is placed right after 2, which is the hot path of the loop.
   416  			exp: []BasicBlockID{0, 1, 2, 4, 3},
   417  		},
   418  		{
   419  			name: "loop with header is critical backward edge",
   420  			//    0
   421  			//    v
   422  			//    1<--+
   423  			//  / |   |
   424  			// 3  2   | <--- critical
   425  			//  \ |   |
   426  			//    4---+
   427  			//    v
   428  			//    5
   429  			//
   430  			// ==>
   431  			//
   432  			//    0
   433  			//    v
   434  			//    1<----+
   435  			//  / |     |
   436  			// 3  2     |
   437  			//  \ |     |
   438  			//    4---->6
   439  			//    v
   440  			//    5
   441  			setup: func(b *builder) {
   442  				b0, b1, b2, b3, b4, b5 := b.allocateBasicBlock(), b.allocateBasicBlock(), b.allocateBasicBlock(),
   443  					b.allocateBasicBlock(), b.allocateBasicBlock(), b.allocateBasicBlock()
   444  				insertJump(b, b0, b1)
   445  				b.SetCurrentBlock(b0)
   446  				c1 := b.AllocateInstruction().AsIconst32(0)
   447  				b.InsertInstruction(c1)
   448  				insertBrz(b, b1, b2, c1.Return())
   449  				insertJump(b, b1, b3)
   450  				insertJump(b, b3, b4)
   451  				insertJump(b, b2, b4)
   452  				b.SetCurrentBlock(b4)
   453  				c2 := b.AllocateInstruction().AsIconst32(0)
   454  				b.InsertInstruction(c2)
   455  				insertBrz(b, b4, b1, c2.Return())
   456  				insertJump(b, b4, b5)
   457  				b.Seal(b0)
   458  				b.Seal(b1)
   459  				b.Seal(b2)
   460  				b.Seal(b3)
   461  				b.Seal(b4)
   462  				b.Seal(b5)
   463  			},
   464  			// The trampoline 6 is placed right after 4, which is the hot path of the loop.
   465  			exp: []BasicBlockID{0, 1, 3, 2, 4, 6, 5},
   466  		},
   467  		{
   468  			name: "multiple critical edges",
   469  			//                   0
   470  			//                   v
   471  			//               +---1<--+
   472  			//               |   v   | <---- critical
   473  			// critical ---->|   2 --+
   474  			//               |   | <-------- critical
   475  			//               |   v
   476  			//               +-->3--->4
   477  			//
   478  			// ==>
   479  			//
   480  			//                   0
   481  			//                   v
   482  			//               +---1<---+
   483  			//               |   v    |
   484  			//               5   2 -->6
   485  			//               |   v
   486  			//               |   7
   487  			//               |   v
   488  			//               +-->3--->4
   489  			setup: func(b *builder) {
   490  				b0, b1, b2, b3, b4 := b.allocateBasicBlock(), b.allocateBasicBlock(), b.allocateBasicBlock(),
   491  					b.allocateBasicBlock(), b.allocateBasicBlock()
   492  				insertJump(b, b0, b1)
   493  				b.SetCurrentBlock(b1)
   494  				c1 := b.AllocateInstruction().AsIconst32(0)
   495  				b.InsertInstruction(c1)
   496  				insertBrz(b, b1, b2, c1.Return())
   497  				insertJump(b, b1, b3)
   498  
   499  				b.SetCurrentBlock(b2)
   500  				c2 := b.AllocateInstruction().AsIconst32(0)
   501  				b.InsertInstruction(c2)
   502  				insertBrz(b, b2, b1, c2.Return())
   503  				insertJump(b, b2, b3)
   504  				insertJump(b, b3, b4)
   505  
   506  				b.Seal(b0)
   507  				b.Seal(b1)
   508  				b.Seal(b2)
   509  				b.Seal(b3)
   510  				b.Seal(b4)
   511  			},
   512  			exp: []BasicBlockID{
   513  				0, 1,
   514  				// block 2 has loop header (1) as the conditional branch target, so it's inverted,
   515  				// and the split edge trampoline is placed right after 2 which is the hot path of the loop.
   516  				2, 6,
   517  				// Then the placement iteration goes to 3, which has two (5, 7) unplaced trampolines as predecessors,
   518  				// so they are placed before 3.
   519  				5, 7, 3,
   520  				// Then the final block.
   521  				4,
   522  			},
   523  		},
   524  		{
   525  			name: "brz with arg",
   526  			setup: func(b *builder) {
   527  				b0, b1, b2 := b.allocateBasicBlock(), b.allocateBasicBlock(), b.allocateBasicBlock()
   528  				p := b0.AddParam(b, TypeI32)
   529  				retval := b1.AddParam(b, TypeI32)
   530  
   531  				b.SetCurrentBlock(b0)
   532  				{
   533  					arg := b.AllocateInstruction().AsIconst32(1000).Insert(b).Return()
   534  					insertBrz(b, b0, b1, p, arg)
   535  					insertJump(b, b0, b2)
   536  				}
   537  				b.SetCurrentBlock(b1)
   538  				{
   539  					b.AllocateInstruction().AsReturn([]Value{retval}).Insert(b)
   540  				}
   541  				b.SetCurrentBlock(b2)
   542  				{
   543  					arg := b.AllocateInstruction().AsIconst32(1).Insert(b).Return()
   544  					insertJump(b, b2, b1, arg)
   545  				}
   546  
   547  				b.Seal(b0)
   548  				b.Seal(b1)
   549  				b.Seal(b2)
   550  			},
   551  			exp: []BasicBlockID{0x0, 0x3, 0x1, 0x2},
   552  		},
   553  		{
   554  			name: "loop with output",
   555  			exp:  []BasicBlockID{0x0, 0x2, 0x4, 0x1, 0x3, 0x6, 0x5},
   556  			setup: func(b *builder) {
   557  				b.currentSignature = &Signature{Results: []Type{TypeI32}}
   558  				b0, b1, b2, b3 := b.allocateBasicBlock(), b.allocateBasicBlock(), b.allocateBasicBlock(), b.allocateBasicBlock()
   559  
   560  				b.SetCurrentBlock(b0)
   561  				funcParam := b0.AddParam(b, TypeI32)
   562  				b2Param := b2.AddParam(b, TypeI32)
   563  				insertJump(b, b0, b2, funcParam)
   564  
   565  				b.SetCurrentBlock(b1)
   566  				{
   567  					returnParam := b1.AddParam(b, TypeI32)
   568  					insertJump(b, b1, b.returnBlk, returnParam)
   569  				}
   570  
   571  				b.SetCurrentBlock(b2)
   572  				{
   573  					c := b.AllocateInstruction().AsIconst32(100).Insert(b)
   574  					cmp := b.AllocateInstruction().
   575  						AsIcmp(b2Param, c.Return(), IntegerCmpCondUnsignedLessThan).
   576  						Insert(b)
   577  					insertBrz(b, b2, b1, cmp.Return(), b2Param)
   578  					insertJump(b, b2, b3)
   579  				}
   580  
   581  				b.SetCurrentBlock(b3)
   582  				{
   583  					one := b.AllocateInstruction().AsIconst32(1).Insert(b)
   584  					minusOned := b.AllocateInstruction().AsIsub(b2Param, one.Return()).Insert(b)
   585  					c := b.AllocateInstruction().AsIconst32(150).Insert(b)
   586  					cmp := b.AllocateInstruction().
   587  						AsIcmp(b2Param, c.Return(), IntegerCmpCondEqual).
   588  						Insert(b)
   589  					insertBrz(b, b3, b1, cmp.Return(), minusOned.Return())
   590  					insertJump(b, b3, b2, minusOned.Return())
   591  				}
   592  
   593  				b.Seal(b0)
   594  				b.Seal(b1)
   595  				b.Seal(b2)
   596  				b.Seal(b3)
   597  			},
   598  		},
   599  	} {
   600  		tc := tc
   601  		t.Run(tc.name, func(t *testing.T) {
   602  			b := NewBuilder().(*builder)
   603  			tc.setup(b)
   604  
   605  			b.RunPasses() // LayoutBlocks() must be called after RunPasses().
   606  			b.LayoutBlocks()
   607  
   608  			var actual []BasicBlockID
   609  			for blk := b.BlockIteratorReversePostOrderBegin(); blk != nil; blk = b.BlockIteratorReversePostOrderNext() {
   610  				actual = append(actual, blk.(*basicBlock).id)
   611  			}
   612  			require.Equal(t, tc.exp, actual)
   613  		})
   614  	}
   615  }