github.com/wasilibs/wazerox@v0.0.0-20240124024944-4923be63ab5f/internal/engine/compiler/compiler_drop_test.go (about)

     1  package compiler
     2  
     3  import (
     4  	"fmt"
     5  	"testing"
     6  
     7  	"github.com/wasilibs/wazerox/internal/asm"
     8  	"github.com/wasilibs/wazerox/internal/testing/require"
     9  	"github.com/wasilibs/wazerox/internal/wasm"
    10  	"github.com/wasilibs/wazerox/internal/wazeroir"
    11  )
    12  
    13  func Test_compileDropRange(t *testing.T) {
    14  	t.Run("nop range", func(t *testing.T) {
    15  		c := newCompiler()
    16  
    17  		err := compileDropRange(c, wazeroir.NopInclusiveRange.AsU64())
    18  		require.NoError(t, err)
    19  	})
    20  
    21  	t.Run("start at the top", func(t *testing.T) {
    22  		c := newCompiler()
    23  		c.Init(&wasm.FunctionType{}, nil, false)
    24  
    25  		// Use up all unreserved registers.
    26  		for _, reg := range unreservedGeneralPurposeRegisters {
    27  			c.pushRuntimeValueLocationOnRegister(reg, runtimeValueTypeI32)
    28  		}
    29  		for i, vreg := range unreservedVectorRegisters {
    30  			// Mix and match scalar float and vector values.
    31  			if i%2 == 0 {
    32  				c.pushVectorRuntimeValueLocationOnRegister(vreg)
    33  			} else {
    34  				c.pushRuntimeValueLocationOnRegister(vreg, runtimeValueTypeF32)
    35  			}
    36  		}
    37  
    38  		unreservedRegisterTotal := len(unreservedGeneralPurposeRegisters) + len(unreservedVectorRegisters)
    39  		ls := c.runtimeValueLocationStack()
    40  		require.Equal(t, unreservedRegisterTotal, len(ls.usedRegisters.list()))
    41  
    42  		// Drop all the values.
    43  		err := compileDropRange(c, wazeroir.InclusiveRange{Start: 0, End: int32(ls.sp - 1)}.AsU64())
    44  		require.NoError(t, err)
    45  
    46  		// All the registers must be marked unused.
    47  		require.Equal(t, 0, len(ls.usedRegisters.list()))
    48  		// Also, stack pointer must be zero.
    49  		require.Equal(t, 0, int(ls.sp))
    50  	})
    51  }
    52  
    53  func TestRuntimeValueLocationStack_dropsLivesForInclusiveRange(t *testing.T) {
    54  	tests := []struct {
    55  		v            *runtimeValueLocationStack
    56  		ir           wazeroir.InclusiveRange
    57  		lives, drops []runtimeValueLocation
    58  	}{
    59  		{
    60  			v: &runtimeValueLocationStack{
    61  				stack: []runtimeValueLocation{{register: 0}, {register: 1} /* drop target */, {register: 2}},
    62  				sp:    3,
    63  			},
    64  			ir:    wazeroir.InclusiveRange{Start: 1, End: 1},
    65  			drops: []runtimeValueLocation{{register: 1}},
    66  			lives: []runtimeValueLocation{{register: 2}},
    67  		},
    68  		{
    69  			v: &runtimeValueLocationStack{
    70  				stack: []runtimeValueLocation{
    71  					{register: 0},
    72  					{register: 1},
    73  					{register: 2}, // drop target
    74  					{register: 3}, // drop target
    75  					{register: 4}, // drop target
    76  					{register: 5},
    77  					{register: 6},
    78  				},
    79  				sp: 7,
    80  			},
    81  			ir:    wazeroir.InclusiveRange{Start: 2, End: 4},
    82  			drops: []runtimeValueLocation{{register: 2}, {register: 3}, {register: 4}},
    83  			lives: []runtimeValueLocation{{register: 5}, {register: 6}},
    84  		},
    85  	}
    86  
    87  	for _, tc := range tests {
    88  		actualDrops, actualLives := tc.v.dropsLivesForInclusiveRange(tc.ir)
    89  		require.Equal(t, tc.drops, actualDrops)
    90  		require.Equal(t, tc.lives, actualLives)
    91  	}
    92  }
    93  
    94  func Test_getTemporariesForStackedLiveValues(t *testing.T) {
    95  	t.Run("no stacked values", func(t *testing.T) {
    96  		liveValues := []runtimeValueLocation{{register: 1}, {register: 2}}
    97  		c := newCompiler()
    98  		c.Init(&wasm.FunctionType{}, nil, false)
    99  
   100  		gpTmp, vecTmp, err := getTemporariesForStackedLiveValues(c, liveValues)
   101  		require.NoError(t, err)
   102  
   103  		require.Equal(t, asm.NilRegister, gpTmp)
   104  		require.Equal(t, asm.NilRegister, vecTmp)
   105  	})
   106  	t.Run("general purpose needed", func(t *testing.T) {
   107  		for _, freeRegisterExists := range []bool{false, true} {
   108  			freeRegisterExists := freeRegisterExists
   109  			t.Run(fmt.Sprintf("free register exists=%v", freeRegisterExists), func(t *testing.T) {
   110  				liveValues := []runtimeValueLocation{
   111  					// Even multiple integer values are alive and on stack,
   112  					// only one general purpose register should be chosen.
   113  					{valueType: runtimeValueTypeI32},
   114  					{valueType: runtimeValueTypeI64},
   115  				}
   116  				c := newCompiler()
   117  				c.Init(&wasm.FunctionType{}, nil, false)
   118  
   119  				if !freeRegisterExists {
   120  					// Use up all the unreserved gp registers.
   121  					for _, reg := range unreservedGeneralPurposeRegisters {
   122  						c.pushRuntimeValueLocationOnRegister(reg, runtimeValueTypeI32)
   123  					}
   124  					// Ensures actually we used them up all.
   125  					require.Equal(t, len(c.runtimeValueLocationStack().usedRegisters.list()),
   126  						len(unreservedGeneralPurposeRegisters))
   127  				}
   128  
   129  				gpTmp, vecTmp, err := getTemporariesForStackedLiveValues(c, liveValues)
   130  				require.NoError(t, err)
   131  
   132  				if !freeRegisterExists {
   133  					// At this point, one register should be marked as unused.
   134  					require.Equal(t, len(c.runtimeValueLocationStack().usedRegisters.list()),
   135  						len(unreservedGeneralPurposeRegisters)-1)
   136  				}
   137  
   138  				require.NotEqual(t, asm.NilRegister, gpTmp)
   139  				require.Equal(t, asm.NilRegister, vecTmp)
   140  			})
   141  		}
   142  	})
   143  
   144  	t.Run("vector needed", func(t *testing.T) {
   145  		for _, freeRegisterExists := range []bool{false, true} {
   146  			freeRegisterExists := freeRegisterExists
   147  			t.Run(fmt.Sprintf("free register exists=%v", freeRegisterExists), func(t *testing.T) {
   148  				liveValues := []runtimeValueLocation{
   149  					// Even multiple vectors are alive and on stack,
   150  					// only one vector register should be chosen.
   151  					{valueType: runtimeValueTypeF32},
   152  					{valueType: runtimeValueTypeV128Lo},
   153  					{valueType: runtimeValueTypeV128Hi},
   154  					{valueType: runtimeValueTypeV128Lo},
   155  					{valueType: runtimeValueTypeV128Hi},
   156  				}
   157  				c := newCompiler()
   158  				c.Init(&wasm.FunctionType{}, nil, false)
   159  
   160  				if !freeRegisterExists {
   161  					// Use up all the unreserved gp registers.
   162  					for _, reg := range unreservedVectorRegisters {
   163  						c.pushVectorRuntimeValueLocationOnRegister(reg)
   164  					}
   165  					// Ensures actually we used them up all.
   166  					require.Equal(t, len(c.runtimeValueLocationStack().usedRegisters.list()),
   167  						len(unreservedVectorRegisters))
   168  				}
   169  
   170  				gpTmp, vecTmp, err := getTemporariesForStackedLiveValues(c, liveValues)
   171  				require.NoError(t, err)
   172  
   173  				if !freeRegisterExists {
   174  					// At this point, one register should be marked as unused.
   175  					require.Equal(t, len(c.runtimeValueLocationStack().usedRegisters.list()),
   176  						len(unreservedVectorRegisters)-1)
   177  				}
   178  
   179  				require.Equal(t, asm.NilRegister, gpTmp)
   180  				require.NotEqual(t, asm.NilRegister, vecTmp)
   181  			})
   182  		}
   183  	})
   184  }
   185  
   186  func Test_migrateLiveValue(t *testing.T) {
   187  	t.Run("v128.hi", func(t *testing.T) {
   188  		migrateLiveValue(nil, &runtimeValueLocation{valueType: runtimeValueTypeV128Hi}, asm.NilRegister, asm.NilRegister)
   189  	})
   190  	t.Run("already on register", func(t *testing.T) {
   191  		// This case, we don't use tmp registers.
   192  		c := newCompiler()
   193  		c.Init(&wasm.FunctionType{}, nil, false)
   194  
   195  		// Push the dummy values.
   196  		for i := 0; i < 10; i++ {
   197  			_ = c.runtimeValueLocationStack().pushRuntimeValueLocationOnStack()
   198  		}
   199  
   200  		gpReg := unreservedGeneralPurposeRegisters[0]
   201  		vReg := unreservedVectorRegisters[0]
   202  		c.pushRuntimeValueLocationOnRegister(gpReg, runtimeValueTypeI64)
   203  		c.pushVectorRuntimeValueLocationOnRegister(vReg)
   204  
   205  		// Emulate the compileDrop
   206  		ls := c.runtimeValueLocationStack()
   207  		vLive, gpLive := ls.popV128(), ls.pop()
   208  		const dropNum = 5
   209  		ls.sp -= dropNum
   210  
   211  		// Migrate these two values.
   212  		migrateLiveValue(c, gpLive, asm.NilRegister, asm.NilRegister)
   213  		migrateLiveValue(c, vLive, asm.NilRegister, asm.NilRegister)
   214  
   215  		// Check the new stack location.
   216  		vectorMigrated, gpMigrated := ls.popV128(), ls.pop()
   217  		require.Equal(t, uint64(5), gpMigrated.stackPointer)
   218  		require.Equal(t, uint64(6), vectorMigrated.stackPointer)
   219  
   220  		require.Equal(t, gpLive.register, gpMigrated.register)
   221  		require.Equal(t, vLive.register, vectorMigrated.register)
   222  	})
   223  }