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

     1  package arm64
     2  
     3  import (
     4  	"testing"
     5  
     6  	"github.com/bananabytelabs/wazero/internal/engine/wazevo/backend/regalloc"
     7  	"github.com/bananabytelabs/wazero/internal/engine/wazevo/ssa"
     8  	"github.com/bananabytelabs/wazero/internal/testing/require"
     9  )
    10  
    11  func TestRegAllocFunctionImpl_addBlock(t *testing.T) {
    12  	ssab := ssa.NewBuilder()
    13  	sb1, sb2 := ssab.AllocateBasicBlock(), ssab.AllocateBasicBlock()
    14  	p1, p2 := &labelPosition{}, &labelPosition{}
    15  
    16  	f := &regAllocFunctionImpl{labelToRegAllocBlockIndex: map[label]int{}}
    17  	f.addBlock(sb1, label(10), p1)
    18  	f.addBlock(sb2, label(20), p2)
    19  
    20  	require.Equal(t, 2, len(f.labelToRegAllocBlockIndex))
    21  	require.Equal(t, 2, len(f.reversePostOrderBlocks))
    22  
    23  	rb1, rb2 := &f.reversePostOrderBlocks[0], &f.reversePostOrderBlocks[1]
    24  	require.Equal(t, f, rb1.f)
    25  	require.Equal(t, f, rb2.f)
    26  
    27  	require.Equal(t, p1, rb1.pos)
    28  	require.Equal(t, p2, rb2.pos)
    29  
    30  	require.Equal(t, label(10), rb1.l)
    31  	require.Equal(t, label(20), rb2.l)
    32  
    33  	require.Equal(t, sb1, rb1.sb)
    34  	require.Equal(t, sb2, rb2.sb)
    35  }
    36  
    37  func TestRegAllocFunctionImpl_PostOrderBlockIterator(t *testing.T) {
    38  	f := &regAllocFunctionImpl{reversePostOrderBlocks: []regAllocBlockImpl{{}, {}, {}}}
    39  	blk := f.PostOrderBlockIteratorBegin()
    40  	require.Equal(t, blk, &f.reversePostOrderBlocks[2])
    41  	blk = f.PostOrderBlockIteratorNext()
    42  	require.Equal(t, blk, &f.reversePostOrderBlocks[1])
    43  	blk = f.PostOrderBlockIteratorNext()
    44  	require.Equal(t, blk, &f.reversePostOrderBlocks[0])
    45  	blk = f.PostOrderBlockIteratorNext()
    46  	require.Nil(t, blk)
    47  }
    48  
    49  func TestRegAllocFunctionImpl_ReversePostOrderBlockIterator(t *testing.T) {
    50  	f := &regAllocFunctionImpl{reversePostOrderBlocks: []regAllocBlockImpl{{}, {}, {}}}
    51  	blk := f.ReversePostOrderBlockIteratorBegin()
    52  	require.Equal(t, blk, &f.reversePostOrderBlocks[0])
    53  	blk = f.ReversePostOrderBlockIteratorNext()
    54  	require.Equal(t, blk, &f.reversePostOrderBlocks[1])
    55  	blk = f.ReversePostOrderBlockIteratorNext()
    56  	require.Equal(t, blk, &f.reversePostOrderBlocks[2])
    57  	blk = f.ReversePostOrderBlockIteratorNext()
    58  	require.Nil(t, blk)
    59  }
    60  
    61  func TestRegAllocFunctionImpl_ReloadRegisterAfter(t *testing.T) {
    62  	ctx, _, m := newSetupWithMockContext()
    63  
    64  	ctx.typeOf = map[regalloc.VRegID]ssa.Type{x1VReg.ID(): ssa.TypeI64, v1VReg.ID(): ssa.TypeF64}
    65  	i1, i2 := m.allocateNop(), m.allocateNop()
    66  	i1.next = i2
    67  	i2.prev = i1
    68  
    69  	f := &regAllocFunctionImpl{m: m}
    70  	f.ReloadRegisterAfter(x1VReg, i1)
    71  	f.ReloadRegisterAfter(v1VReg, i1)
    72  
    73  	require.NotEqual(t, i1, i2.prev)
    74  	require.NotEqual(t, i1.next, i2)
    75  	fload, iload := i1.next, i1.next.next
    76  	require.Equal(t, fload.prev, i1)
    77  	require.Equal(t, i1, fload.prev)
    78  	require.Equal(t, iload.next, i2)
    79  	require.Equal(t, iload, i2.prev)
    80  
    81  	require.Equal(t, iload.kind, uLoad64)
    82  	require.Equal(t, fload.kind, fpuLoad64)
    83  
    84  	m.executableContext.RootInstr = i1
    85  	require.Equal(t, `
    86  	ldr d1, [sp, #0x18]
    87  	ldr x1, [sp, #0x10]
    88  `, m.Format())
    89  }
    90  
    91  func TestRegAllocFunctionImpl_StoreRegisterBefore(t *testing.T) {
    92  	ctx, _, m := newSetupWithMockContext()
    93  
    94  	ctx.typeOf = map[regalloc.VRegID]ssa.Type{x1VReg.ID(): ssa.TypeI64, v1VReg.ID(): ssa.TypeF64}
    95  	i1, i2 := m.allocateNop(), m.allocateNop()
    96  	i1.next = i2
    97  	i2.prev = i1
    98  
    99  	f := &regAllocFunctionImpl{m: m}
   100  	f.StoreRegisterBefore(x1VReg, i2)
   101  	f.StoreRegisterBefore(v1VReg, i2)
   102  
   103  	require.NotEqual(t, i1, i2.prev)
   104  	require.NotEqual(t, i1.next, i2)
   105  	iload, fload := i1.next, i1.next.next
   106  	require.Equal(t, iload.prev, i1)
   107  	require.Equal(t, i1, iload.prev)
   108  	require.Equal(t, fload.next, i2)
   109  	require.Equal(t, fload, i2.prev)
   110  
   111  	require.Equal(t, iload.kind, store64)
   112  	require.Equal(t, fload.kind, fpuStore64)
   113  
   114  	m.executableContext.RootInstr = i1
   115  	require.Equal(t, `
   116  	str x1, [sp, #0x10]
   117  	str d1, [sp, #0x18]
   118  `, m.Format())
   119  }
   120  
   121  func TestMachine_insertStoreRegisterAt(t *testing.T) {
   122  	for _, tc := range []struct {
   123  		spillSlotSize int64
   124  		expected      string
   125  	}{
   126  		{
   127  			spillSlotSize: 0,
   128  			expected: `
   129  	udf
   130  	str x1, [sp, #0x10]
   131  	str d1, [sp, #0x18]
   132  	exit_sequence x30
   133  `,
   134  		},
   135  		{
   136  			spillSlotSize: 0xffff,
   137  			expected: `
   138  	udf
   139  	movz x27, #0xf, lsl 0
   140  	movk x27, #0x1, lsl 16
   141  	str x1, [sp, x27]
   142  	movz x27, #0x17, lsl 0
   143  	movk x27, #0x1, lsl 16
   144  	str d1, [sp, x27]
   145  	exit_sequence x30
   146  `,
   147  		},
   148  		{
   149  			spillSlotSize: 0xffff_00,
   150  			expected: `
   151  	udf
   152  	movz x27, #0xff10, lsl 0
   153  	movk x27, #0xff, lsl 16
   154  	str x1, [sp, x27]
   155  	movz x27, #0xff18, lsl 0
   156  	movk x27, #0xff, lsl 16
   157  	str d1, [sp, x27]
   158  	exit_sequence x30
   159  `,
   160  		},
   161  	} {
   162  		t.Run(tc.expected, func(t *testing.T) {
   163  			ctx, _, m := newSetupWithMockContext()
   164  			m.spillSlotSize = tc.spillSlotSize
   165  
   166  			for _, after := range []bool{false, true} {
   167  				var name string
   168  				if after {
   169  					name = "after"
   170  				} else {
   171  					name = "before"
   172  				}
   173  				t.Run(name, func(t *testing.T) {
   174  					ctx.typeOf = map[regalloc.VRegID]ssa.Type{x1VReg.ID(): ssa.TypeI64, v1VReg.ID(): ssa.TypeF64}
   175  					i1, i2 := m.allocateInstr().asUDF(), m.allocateInstr().asExitSequence(x30VReg)
   176  					i1.next = i2
   177  					i2.prev = i1
   178  
   179  					if after {
   180  						m.insertStoreRegisterAt(v1VReg, i1, after)
   181  						m.insertStoreRegisterAt(x1VReg, i1, after)
   182  					} else {
   183  						m.insertStoreRegisterAt(x1VReg, i2, after)
   184  						m.insertStoreRegisterAt(v1VReg, i2, after)
   185  					}
   186  					m.executableContext.RootInstr = i1
   187  					require.Equal(t, tc.expected, m.Format())
   188  				})
   189  			}
   190  		})
   191  	}
   192  }
   193  
   194  func TestMachine_insertReloadRegisterAt(t *testing.T) {
   195  	for _, tc := range []struct {
   196  		spillSlotSize int64
   197  		expected      string
   198  	}{
   199  		{
   200  			spillSlotSize: 0,
   201  			expected: `
   202  	udf
   203  	ldr x1, [sp, #0x10]
   204  	ldr d1, [sp, #0x18]
   205  	exit_sequence x30
   206  `,
   207  		},
   208  		{
   209  			spillSlotSize: 0xffff,
   210  			expected: `
   211  	udf
   212  	movz x27, #0xf, lsl 0
   213  	movk x27, #0x1, lsl 16
   214  	ldr x1, [sp, x27]
   215  	movz x27, #0x17, lsl 0
   216  	movk x27, #0x1, lsl 16
   217  	ldr d1, [sp, x27]
   218  	exit_sequence x30
   219  `,
   220  		},
   221  		{
   222  			spillSlotSize: 0xffff_00,
   223  			expected: `
   224  	udf
   225  	movz x27, #0xff10, lsl 0
   226  	movk x27, #0xff, lsl 16
   227  	ldr x1, [sp, x27]
   228  	movz x27, #0xff18, lsl 0
   229  	movk x27, #0xff, lsl 16
   230  	ldr d1, [sp, x27]
   231  	exit_sequence x30
   232  `,
   233  		},
   234  	} {
   235  		t.Run(tc.expected, func(t *testing.T) {
   236  			ctx, _, m := newSetupWithMockContext()
   237  			m.spillSlotSize = tc.spillSlotSize
   238  
   239  			for _, after := range []bool{false, true} {
   240  				var name string
   241  				if after {
   242  					name = "after"
   243  				} else {
   244  					name = "before"
   245  				}
   246  				t.Run(name, func(t *testing.T) {
   247  					ctx.typeOf = map[regalloc.VRegID]ssa.Type{x1VReg.ID(): ssa.TypeI64, v1VReg.ID(): ssa.TypeF64}
   248  					i1, i2 := m.allocateInstr().asUDF(), m.allocateInstr().asExitSequence(x30VReg)
   249  					i1.next = i2
   250  					i2.prev = i1
   251  
   252  					if after {
   253  						m.insertReloadRegisterAt(v1VReg, i1, after)
   254  						m.insertReloadRegisterAt(x1VReg, i1, after)
   255  					} else {
   256  						m.insertReloadRegisterAt(x1VReg, i2, after)
   257  						m.insertReloadRegisterAt(v1VReg, i2, after)
   258  					}
   259  					m.executableContext.RootInstr = i1
   260  
   261  					require.Equal(t, tc.expected, m.Format())
   262  				})
   263  			}
   264  		})
   265  	}
   266  }
   267  
   268  func TestRegAllocFunctionImpl_ClobberedRegisters(t *testing.T) {
   269  	_, _, m := newSetupWithMockContext()
   270  	f := &regAllocFunctionImpl{m: m}
   271  	f.ClobberedRegisters([]regalloc.VReg{v19VReg, v19VReg, v19VReg, v19VReg})
   272  	require.Equal(t, []regalloc.VReg{v19VReg, v19VReg, v19VReg, v19VReg}, m.clobberedRegs)
   273  }
   274  
   275  func TestMachine_swap(t *testing.T) {
   276  	for _, tc := range []struct {
   277  		x1, x2, tmp regalloc.VReg
   278  		expected    string
   279  	}{
   280  		{
   281  			x1:  x18VReg,
   282  			x2:  x19VReg,
   283  			tmp: x20VReg,
   284  			expected: `
   285  	udf
   286  	mov x20, x18
   287  	mov x18, x19
   288  	mov x19, x20
   289  	exit_sequence x30
   290  `,
   291  		},
   292  		{
   293  			x1: x18VReg,
   294  			x2: x19VReg,
   295  			// Tmp not given.
   296  			expected: `
   297  	udf
   298  	mov x27, x18
   299  	mov x18, x19
   300  	mov x19, x27
   301  	exit_sequence x30
   302  `,
   303  		},
   304  		{
   305  			x1:  v18VReg,
   306  			x2:  v19VReg,
   307  			tmp: v11VReg,
   308  			expected: `
   309  	udf
   310  	mov v11.16b, v18.16b
   311  	mov v18.16b, v19.16b
   312  	mov v19.16b, v11.16b
   313  	exit_sequence x30
   314  `,
   315  		},
   316  		{
   317  			x1: v18VReg,
   318  			x2: v19VReg,
   319  			// Tmp not given.
   320  			expected: `
   321  	udf
   322  	str d18, [sp, #0x10]
   323  	mov v18.16b, v19.16b
   324  	ldr d19, [sp, #0x10]
   325  	exit_sequence x30
   326  `,
   327  		},
   328  	} {
   329  		t.Run(tc.expected, func(t *testing.T) {
   330  			ctx, _, m := newSetupWithMockContext()
   331  
   332  			ctx.typeOf = map[regalloc.VRegID]ssa.Type{
   333  				x18VReg.ID(): ssa.TypeI64, x19VReg.ID(): ssa.TypeI64,
   334  				v18VReg.ID(): ssa.TypeF64, v19VReg.ID(): ssa.TypeF64,
   335  			}
   336  			cur, i2 := m.allocateInstr().asUDF(), m.allocateInstr().asExitSequence(x30VReg)
   337  			cur.next = i2
   338  			i2.prev = cur
   339  
   340  			m.swap(cur, tc.x1, tc.x2, tc.tmp)
   341  			m.executableContext.RootInstr = cur
   342  
   343  			require.Equal(t, tc.expected, m.Format())
   344  		})
   345  	}
   346  }