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

     1  package regalloc
     2  
     3  import (
     4  	"fmt"
     5  	"sort"
     6  	"testing"
     7  
     8  	"github.com/tetratelabs/wazero/internal/engine/wazevo/wazevoapi"
     9  	"github.com/tetratelabs/wazero/internal/testing/require"
    10  )
    11  
    12  func TestAllocator_livenessAnalysis(t *testing.T) {
    13  	const realRegID, realRegID2 = 50, 100
    14  	realReg, realReg2 := FromRealReg(realRegID, RegTypeInt), FromRealReg(realRegID2, RegTypeInt)
    15  	phiVReg := VReg(12345).SetRegType(RegTypeInt)
    16  	for _, tc := range []struct {
    17  		name  string
    18  		setup func() Function
    19  		exp   map[int]*blockState
    20  	}{
    21  		{
    22  			name: "single block",
    23  			setup: func() Function {
    24  				return newMockFunction(
    25  					newMockBlock(0,
    26  						newMockInstr().def(1),
    27  						newMockInstr().use(1).def(2),
    28  					).entry(),
    29  				)
    30  			},
    31  			exp: map[int]*blockState{
    32  				0: {},
    33  			},
    34  		},
    35  		{
    36  			name: "single block with real reg",
    37  			setup: func() Function {
    38  				realVReg := FromRealReg(10, RegTypeInt)
    39  				param := VReg(1)
    40  				ret := VReg(2)
    41  				blk := newMockBlock(0,
    42  					newMockInstr().def(param).use(realVReg),
    43  					newMockInstr().def(ret).use(param, param),
    44  					newMockInstr().def(realVReg).use(ret),
    45  				).entry()
    46  				blk.blockParam(param)
    47  				return newMockFunction(blk)
    48  			},
    49  			exp: map[int]*blockState{
    50  				0: {},
    51  			},
    52  		},
    53  		{
    54  			name: "straight",
    55  			// b0 -> b1 -> b2
    56  			setup: func() Function {
    57  				b0 := newMockBlock(0,
    58  					newMockInstr().def(1000, 1, 2),
    59  					newMockInstr().use(1000),
    60  					newMockInstr().use(1, 2).def(3),
    61  				).entry()
    62  				b1 := newMockBlock(1,
    63  					newMockInstr().def(realReg),
    64  					newMockInstr().use(3).def(4, 5),
    65  					newMockInstr().use(realReg),
    66  				)
    67  				b2 := newMockBlock(2,
    68  					newMockInstr().use(3, 4, 5),
    69  				)
    70  				b2.addPred(b1)
    71  				b1.addPred(b0)
    72  				return newMockFunction(b0, b1, b2)
    73  			},
    74  			exp: map[int]*blockState{
    75  				0: {},
    76  				1: {
    77  					liveIns: []VRegID{3},
    78  				},
    79  				2: {liveIns: []VRegID{3, 4, 5}},
    80  			},
    81  		},
    82  		{
    83  			name: "diamond",
    84  			//  0   v1000<-, v1<-, v2<-
    85  			// / \
    86  			// 1   2
    87  			// \ /
    88  			//  3
    89  			setup: func() Function {
    90  				b0 := newMockBlock(0,
    91  					newMockInstr().def(1000),
    92  					newMockInstr().def(1),
    93  					newMockInstr().def(2),
    94  				).entry()
    95  				b1 := newMockBlock(1,
    96  					newMockInstr().def(realReg).use(1),
    97  					newMockInstr().use(realReg),
    98  					newMockInstr().def(realReg2),
    99  					newMockInstr().use(realReg2),
   100  					newMockInstr().def(realReg),
   101  					newMockInstr().use(realReg),
   102  				)
   103  				b2 := newMockBlock(2,
   104  					newMockInstr().use(2, realReg2),
   105  				)
   106  				b3 := newMockBlock(3,
   107  					newMockInstr().use(1000),
   108  				)
   109  				b3.addPred(b1)
   110  				b3.addPred(b2)
   111  				b1.addPred(b0)
   112  				b2.addPred(b0)
   113  				return newMockFunction(b0, b1, b2, b3)
   114  			},
   115  			exp: map[int]*blockState{
   116  				0: {},
   117  				1: {liveIns: []VRegID{1000, 1}},
   118  				2: {
   119  					liveIns: []VRegID{1000, 2},
   120  				},
   121  				3: {
   122  					liveIns: []VRegID{1000},
   123  				},
   124  			},
   125  		},
   126  
   127  		{
   128  			name: "phis",
   129  			//   0
   130  			// /  \
   131  			// 1   \
   132  			// |   |
   133  			// 2   3
   134  			//  \ /
   135  			//   4  use v5 (phi node) defined at both 1 and 3.
   136  			setup: func() Function {
   137  				b0 := newMockBlock(0,
   138  					newMockInstr().def(1000, 2000, 3000),
   139  				).entry()
   140  				b1 := newMockBlock(1,
   141  					newMockInstr().def(phiVReg).use(2000),
   142  				)
   143  				b2 := newMockBlock(2)
   144  				b3 := newMockBlock(3,
   145  					newMockInstr().def(phiVReg).use(1000),
   146  				)
   147  				b4 := newMockBlock(
   148  					4, newMockInstr().use(phiVReg, 3000),
   149  				)
   150  				b4.addPred(b2)
   151  				b4.addPred(b3)
   152  				b3.addPred(b0)
   153  				b2.addPred(b1)
   154  				b1.addPred(b0)
   155  				return newMockFunction(b0, b1, b2, b3, b4)
   156  			},
   157  			exp: map[int]*blockState{
   158  				0: {},
   159  				1: {
   160  					liveIns: []VRegID{2000, 3000},
   161  				},
   162  				2: {
   163  					liveIns: []VRegID{phiVReg.ID(), 3000},
   164  				},
   165  				3: {
   166  					liveIns: []VRegID{1000, 3000},
   167  				},
   168  				4: {
   169  					liveIns: []VRegID{phiVReg.ID(), 3000},
   170  				},
   171  			},
   172  		},
   173  
   174  		{
   175  			name: "loop",
   176  			// 0 -> 1 -> 2
   177  			//      ^    |
   178  			//      |    v
   179  			//      4 <- 3 -> 5
   180  			setup: func() Function {
   181  				b0 := newMockBlock(0,
   182  					newMockInstr().def(1),
   183  					newMockInstr().def(phiVReg).use(1),
   184  				).entry()
   185  				b1 := newMockBlock(1,
   186  					newMockInstr().def(9999),
   187  				)
   188  				b1.blockParam(phiVReg)
   189  				b2 := newMockBlock(2,
   190  					newMockInstr().def(100).use(phiVReg, 9999),
   191  				)
   192  				b3 := newMockBlock(3,
   193  					newMockInstr().def(54321),
   194  					newMockInstr().use(100),
   195  				)
   196  				b4 := newMockBlock(4,
   197  					newMockInstr().def(phiVReg).use(54321).
   198  						// Make sure this is the PHI defining instruction.
   199  						asCopy(),
   200  				)
   201  				b5 := newMockBlock(
   202  					5, newMockInstr().use(54321),
   203  				)
   204  				b1.addPred(b0)
   205  				b1.addPred(b4)
   206  				b2.addPred(b1)
   207  				b3.addPred(b2)
   208  				b4.addPred(b3)
   209  				b5.addPred(b3)
   210  				b1.loop(b2, b3, b4, b5)
   211  				f := newMockFunction(b0, b1, b2, b3, b4, b5)
   212  				f.loopNestingForestRoots(b1)
   213  				return f
   214  			},
   215  			exp: map[int]*blockState{
   216  				0: {
   217  					liveIns: []VRegID{},
   218  				},
   219  				1: {
   220  					liveIns: []VRegID{phiVReg.ID()},
   221  				},
   222  				2: {
   223  					liveIns: []VRegID{phiVReg.ID(), 9999},
   224  				},
   225  				3: {
   226  					liveIns: []VRegID{100},
   227  				},
   228  				4: {
   229  					liveIns: []VRegID{54321},
   230  				},
   231  				5: {liveIns: []VRegID{54321}},
   232  			},
   233  		},
   234  		{
   235  			name: "multiple pass alive",
   236  			setup: func() Function {
   237  				v := VReg(9999)
   238  				b0 := newMockBlock(0, newMockInstr().def(v)).entry()
   239  
   240  				b1, b2, b3, b4, b5, b6 := newMockBlock(1), newMockBlock(2),
   241  					newMockBlock(3, newMockInstr().use(v)),
   242  					newMockBlock(4), newMockBlock(5), newMockBlock(6)
   243  
   244  				b1.addPred(b0)
   245  				b4.addPred(b0)
   246  				b2.addPred(b1)
   247  				b5.addPred(b2)
   248  				b2.addPred(b5)
   249  				b6.addPred(b2)
   250  				b3.addPred(b6)
   251  				b3.addPred(b4)
   252  				f := newMockFunction(b0, b1, b2, b4, b5, b6, b3)
   253  				f.loopNestingForestRoots(b2)
   254  				return f
   255  			},
   256  			exp: map[int]*blockState{
   257  				0: {},
   258  				1: {
   259  					liveIns: []VRegID{9999},
   260  				},
   261  				2: {
   262  					liveIns: []VRegID{9999},
   263  				},
   264  				3: {
   265  					liveIns: []VRegID{9999},
   266  				},
   267  				4: {
   268  					liveIns: []VRegID{9999},
   269  				},
   270  				5: {},
   271  				6: {
   272  					liveIns: []VRegID{9999},
   273  				},
   274  			},
   275  		},
   276  		{
   277  			//           -----+
   278  			//           v    |
   279  			// 0 -> 1 -> 2 -> 3 -> 4
   280  			//      ^    |
   281  			//      +----+
   282  			name: "Fig. 9.2 in paper",
   283  			setup: func() Function {
   284  				b0 := newMockBlock(0,
   285  					newMockInstr().def(99999),
   286  					newMockInstr().def(phiVReg).use(111).asCopy(),
   287  				).entry()
   288  				b1 := newMockBlock(1, newMockInstr().use(99999))
   289  				b1.blockParam(phiVReg)
   290  				b2 := newMockBlock(2, newMockInstr().def(88888).use(phiVReg, phiVReg))
   291  				b3 := newMockBlock(3, newMockInstr().def(phiVReg).use(88888).asCopy())
   292  				b4 := newMockBlock(4)
   293  				b1.addPred(b0)
   294  				b1.addPred(b2)
   295  				b2.addPred(b1)
   296  				b2.addPred(b3)
   297  				b3.addPred(b2)
   298  				b4.addPred(b3)
   299  
   300  				b1.loop(b2)
   301  				b2.loop(b3)
   302  				f := newMockFunction(b0, b1, b2, b3, b4)
   303  				f.loopNestingForestRoots(b1)
   304  				return f
   305  			},
   306  			exp: map[int]*blockState{
   307  				0: {
   308  					liveIns: []VRegID{111},
   309  				},
   310  				1: {
   311  					liveIns: []VRegID{99999, phiVReg.ID()},
   312  				},
   313  				2: {
   314  					liveIns: []VRegID{99999, phiVReg.ID()},
   315  				},
   316  				3: {
   317  					liveIns: []VRegID{99999, phiVReg.ID(), 88888},
   318  				},
   319  				4: {},
   320  			},
   321  		},
   322  	} {
   323  		tc := tc
   324  		t.Run(tc.name, func(t *testing.T) {
   325  			f := tc.setup()
   326  			a := NewAllocator(&RegisterInfo{
   327  				RealRegName: func(r RealReg) string {
   328  					return fmt.Sprintf("r%d", r)
   329  				},
   330  			})
   331  			a.livenessAnalysis(f)
   332  			for blockID := 0; blockID <= a.blockStates.MaxIDEncountered(); blockID++ {
   333  				actual := a.blockStates.Get(blockID)
   334  				if actual == nil {
   335  					continue
   336  				}
   337  				t.Run(fmt.Sprintf("block_id=%d", blockID), func(t *testing.T) {
   338  					exp := tc.exp[blockID]
   339  					if len(exp.liveIns) == 0 {
   340  						require.Nil(t, actual.liveIns, "live ins")
   341  					} else {
   342  						sort.Slice(actual.liveIns, func(i, j int) bool {
   343  							return actual.liveIns[i] < actual.liveIns[j]
   344  						})
   345  						sort.Slice(exp.liveIns, func(i, j int) bool {
   346  							return exp.liveIns[i] < exp.liveIns[j]
   347  						})
   348  						require.Equal(t, exp.liveIns, actual.liveIns, "live ins")
   349  					}
   350  				})
   351  			}
   352  		})
   353  	}
   354  }
   355  
   356  func TestAllocator_livenessAnalysis_copy(t *testing.T) {
   357  	f := newMockFunction(
   358  		newMockBlock(0,
   359  			newMockInstr().def(1),
   360  			newMockInstr().use(1).def(2).asCopy(),
   361  		).entry(),
   362  	)
   363  	a := NewAllocator(&RegisterInfo{})
   364  	a.livenessAnalysis(f)
   365  }
   366  
   367  func Test_findOrSpillAllocatable_prefersSpill(t *testing.T) {
   368  	t.Run("ok", func(t *testing.T) {
   369  		s := &state{}
   370  		s.regsInUse.add(RealReg(1), VReg(2222222))
   371  		got := s.findOrSpillAllocatable(&Allocator{}, []RealReg{3}, 0, 3)
   372  		require.Equal(t, RealReg(3), got)
   373  	})
   374  	t.Run("preferred but in use", func(t *testing.T) {
   375  		s := &state{vrStates: wazevoapi.NewIDedPool[vrState](resetVrState)}
   376  		s.regsInUse.add(RealReg(3), VReg(1).SetRealReg(3))
   377  		got := s.findOrSpillAllocatable(&Allocator{}, []RealReg{3, 4}, 0, 3)
   378  		require.Equal(t, RealReg(4), got)
   379  	})
   380  	t.Run("preferred but forbidden", func(t *testing.T) {
   381  		s := &state{vrStates: wazevoapi.NewIDedPool[vrState](resetVrState)}
   382  		got := s.findOrSpillAllocatable(&Allocator{}, []RealReg{3, 4}, RegSet(0).add(3), 3)
   383  		require.Equal(t, RealReg(4), got)
   384  	})
   385  }