github.com/bananabytelabs/wazero@v0.0.0-20240105073314-54b22a776da8/internal/engine/wazevo/backend/regalloc/regalloc_test.go (about)

     1  package regalloc
     2  
     3  import (
     4  	"fmt"
     5  	"testing"
     6  
     7  	"github.com/bananabytelabs/wazero/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  }