wa-lang.org/wazero@v1.0.2/internal/engine/compiler/compiler_stack_test.go (about)

     1  package compiler
     2  
     3  import (
     4  	"fmt"
     5  	"math"
     6  	"testing"
     7  
     8  	"wa-lang.org/wazero/internal/asm"
     9  	"wa-lang.org/wazero/internal/testing/require"
    10  	"wa-lang.org/wazero/internal/wazeroir"
    11  )
    12  
    13  func TestCompiler_releaseRegisterToStack(t *testing.T) {
    14  	const val = 10000
    15  	tests := []struct {
    16  		name         string
    17  		stackPointer uint64
    18  		isFloat      bool
    19  	}{
    20  		{name: "int", stackPointer: 10, isFloat: false},
    21  		{name: "float", stackPointer: 10, isFloat: true},
    22  		{name: "int-huge-height", stackPointer: math.MaxInt16 + 1, isFloat: false},
    23  		{name: "float-huge-height", stackPointer: math.MaxInt16 + 1, isFloat: true},
    24  	}
    25  
    26  	for _, tt := range tests {
    27  		tc := tt
    28  		t.Run(tc.name, func(t *testing.T) {
    29  			env := newCompilerEnvironment()
    30  
    31  			// Compile code.
    32  			compiler := env.requireNewCompiler(t, newCompiler, nil)
    33  			err := compiler.compilePreamble()
    34  			require.NoError(t, err)
    35  
    36  			// Set up the location stack so that we push the const on the specified height.
    37  			s := &runtimeValueLocationStack{
    38  				sp:                                tc.stackPointer,
    39  				stack:                             make([]*runtimeValueLocation, tc.stackPointer),
    40  				usedRegisters:                     map[asm.Register]struct{}{},
    41  				unreservedVectorRegisters:         unreservedVectorRegisters,
    42  				unreservedGeneralPurposeRegisters: unreservedGeneralPurposeRegisters,
    43  			}
    44  			// Peek must be non-nil. Otherwise, compileConst* would fail.
    45  			s.stack[s.sp-1] = &runtimeValueLocation{}
    46  			compiler.setRuntimeValueLocationStack(s)
    47  
    48  			if tc.isFloat {
    49  				err = compiler.compileConstF64(&wazeroir.OperationConstF64{Value: math.Float64frombits(val)})
    50  			} else {
    51  				err = compiler.compileConstI64(&wazeroir.OperationConstI64{Value: val})
    52  			}
    53  			require.NoError(t, err)
    54  			// Release the register allocated value to the memory stack so that we can see the value after exiting.
    55  			compiler.compileReleaseRegisterToStack(s.peek())
    56  			compiler.compileExitFromNativeCode(nativeCallStatusCodeReturned)
    57  
    58  			// Generate the code under test.
    59  			code, _, err := compiler.compile()
    60  			require.NoError(t, err)
    61  
    62  			// Run native code after growing the value stack.
    63  			env.callEngine().builtinFunctionGrowStack(tc.stackPointer)
    64  			env.exec(code)
    65  
    66  			// Compiler status must be returned and stack pointer must end up the specified one.
    67  			require.Equal(t, nativeCallStatusCodeReturned, env.compilerStatus())
    68  			require.Equal(t, tc.stackPointer+1, env.ce.stackPointer)
    69  
    70  			if tc.isFloat {
    71  				require.Equal(t, math.Float64frombits(val), env.stackTopAsFloat64())
    72  			} else {
    73  				require.Equal(t, uint64(val), env.stackTopAsUint64())
    74  			}
    75  		})
    76  	}
    77  }
    78  
    79  func TestCompiler_compileLoadValueOnStackToRegister(t *testing.T) {
    80  	const val = 123
    81  	tests := []struct {
    82  		name         string
    83  		stackPointer uint64
    84  		isFloat      bool
    85  	}{
    86  		{name: "int", stackPointer: 10, isFloat: false},
    87  		{name: "float", stackPointer: 10, isFloat: true},
    88  		{name: "int-huge-height", stackPointer: math.MaxInt16 + 1, isFloat: false},
    89  		{name: "float-huge-height", stackPointer: math.MaxInt16 + 1, isFloat: true},
    90  	}
    91  
    92  	for _, tt := range tests {
    93  		tc := tt
    94  		t.Run(tc.name, func(t *testing.T) {
    95  			env := newCompilerEnvironment()
    96  
    97  			// Compile code.
    98  			compiler := env.requireNewCompiler(t, newCompiler, nil)
    99  			err := compiler.compilePreamble()
   100  			require.NoError(t, err)
   101  
   102  			// Setup the location stack so that we push the const on the specified height.
   103  			compiler.runtimeValueLocationStack().sp = tc.stackPointer
   104  			compiler.runtimeValueLocationStack().stack = make([]*runtimeValueLocation, tc.stackPointer)
   105  
   106  			require.Zero(t, len(compiler.runtimeValueLocationStack().usedRegisters))
   107  			loc := compiler.runtimeValueLocationStack().pushRuntimeValueLocationOnStack()
   108  			if tc.isFloat {
   109  				loc.valueType = runtimeValueTypeF64
   110  			} else {
   111  				loc.valueType = runtimeValueTypeI64
   112  			}
   113  			// At this point the value must be recorded as being on stack.
   114  			require.True(t, loc.onStack())
   115  
   116  			// Release the stack-allocated value to register.
   117  			err = compiler.compileEnsureOnRegister(loc)
   118  			require.NoError(t, err)
   119  			require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters))
   120  			require.True(t, loc.onRegister())
   121  
   122  			// To verify the behavior, increment the value on the register.
   123  			if tc.isFloat {
   124  				err = compiler.compileConstF64(&wazeroir.OperationConstF64{Value: 1})
   125  				require.NoError(t, err)
   126  				err = compiler.compileAdd(&wazeroir.OperationAdd{Type: wazeroir.UnsignedTypeF64})
   127  				require.NoError(t, err)
   128  			} else {
   129  				err = compiler.compileConstI64(&wazeroir.OperationConstI64{Value: 1})
   130  				require.NoError(t, err)
   131  				err = compiler.compileAdd(&wazeroir.OperationAdd{Type: wazeroir.UnsignedTypeI64})
   132  				require.NoError(t, err)
   133  			}
   134  
   135  			// Release the value to the memory stack so that we can see the value after exiting.
   136  			compiler.compileReleaseRegisterToStack(loc)
   137  			require.NoError(t, err)
   138  			compiler.compileExitFromNativeCode(nativeCallStatusCodeReturned)
   139  			require.NoError(t, err)
   140  
   141  			// Generate the code under test.
   142  			code, _, err := compiler.compile()
   143  			require.NoError(t, err)
   144  
   145  			// Run native code after growing the value stack, and place the original value.
   146  			env.callEngine().builtinFunctionGrowStack(tc.stackPointer)
   147  			env.stack()[tc.stackPointer] = val
   148  			env.exec(code)
   149  
   150  			// Compiler status must be returned and stack pointer must end up the specified one.
   151  			require.Equal(t, nativeCallStatusCodeReturned, env.compilerStatus())
   152  			require.Equal(t, tc.stackPointer+1, env.ce.stackPointer)
   153  
   154  			if tc.isFloat {
   155  				require.Equal(t, math.Float64frombits(val)+1, env.stackTopAsFloat64())
   156  			} else {
   157  				require.Equal(t, uint64(val)+1, env.stackTopAsUint64())
   158  			}
   159  		})
   160  	}
   161  }
   162  
   163  func TestCompiler_compilePick_v128(t *testing.T) {
   164  	const pickTargetLo, pickTargetHi uint64 = 12345, 6789
   165  
   166  	op := &wazeroir.OperationPick{Depth: 2, IsTargetVector: true}
   167  	tests := []struct {
   168  		name                   string
   169  		isPickTargetOnRegister bool
   170  	}{
   171  		{name: "target on register", isPickTargetOnRegister: false},
   172  		{name: "target on stack", isPickTargetOnRegister: true},
   173  	}
   174  
   175  	for _, tt := range tests {
   176  		tc := tt
   177  		t.Run(tc.name, func(t *testing.T) {
   178  			env := newCompilerEnvironment()
   179  			compiler := env.requireNewCompiler(t, newCompiler, nil)
   180  			err := compiler.compilePreamble()
   181  			require.NoError(t, err)
   182  
   183  			// Set up the stack before picking.
   184  			if tc.isPickTargetOnRegister {
   185  				err = compiler.compileV128Const(&wazeroir.OperationV128Const{
   186  					Lo: pickTargetLo, Hi: pickTargetHi,
   187  				})
   188  				require.NoError(t, err)
   189  			} else {
   190  				lo := compiler.runtimeValueLocationStack().pushRuntimeValueLocationOnStack() // lo
   191  				lo.valueType = runtimeValueTypeV128Lo
   192  				env.stack()[lo.stackPointer] = pickTargetLo
   193  				hi := compiler.runtimeValueLocationStack().pushRuntimeValueLocationOnStack() // hi
   194  				hi.valueType = runtimeValueTypeV128Hi
   195  				env.stack()[hi.stackPointer] = pickTargetHi
   196  			}
   197  
   198  			// Push the unused median value.
   199  			_ = compiler.runtimeValueLocationStack().pushRuntimeValueLocationOnStack()
   200  			requireRuntimeLocationStackPointerEqual(t, uint64(3), compiler)
   201  
   202  			// Now ready to compile Pick operation.
   203  			err = compiler.compilePick(op)
   204  			require.NoError(t, err)
   205  			requireRuntimeLocationStackPointerEqual(t, uint64(5), compiler)
   206  
   207  			hiLoc := compiler.runtimeValueLocationStack().peek()
   208  			loLoc := compiler.runtimeValueLocationStack().stack[hiLoc.stackPointer-1]
   209  			require.True(t, hiLoc.onRegister())
   210  			require.Equal(t, runtimeValueTypeV128Hi, hiLoc.valueType)
   211  			require.Equal(t, runtimeValueTypeV128Lo, loLoc.valueType)
   212  
   213  			err = compiler.compileReturnFunction()
   214  			require.NoError(t, err)
   215  
   216  			// Compile and execute the code under test.
   217  			code, _, err := compiler.compile()
   218  			require.NoError(t, err)
   219  			env.exec(code)
   220  
   221  			// Check the returned status and stack pointer.
   222  			require.Equal(t, nativeCallStatusCodeReturned, env.compilerStatus())
   223  			require.Equal(t, uint64(5), env.stackPointer())
   224  
   225  			// Verify the top value is the picked one and the pick target's value stays the same.
   226  			lo, hi := env.stackTopAsV128()
   227  			require.Equal(t, pickTargetLo, lo)
   228  			require.Equal(t, pickTargetHi, hi)
   229  			require.Equal(t, pickTargetLo, env.stack()[loLoc.stackPointer])
   230  			require.Equal(t, pickTargetHi, env.stack()[hiLoc.stackPointer])
   231  		})
   232  	}
   233  }
   234  
   235  func TestCompiler_compilePick(t *testing.T) {
   236  	const pickTargetValue uint64 = 12345
   237  	op := &wazeroir.OperationPick{Depth: 1}
   238  	tests := []struct {
   239  		name                                      string
   240  		pickTargetSetupFunc                       func(compiler compilerImpl, ce *callEngine) error
   241  		isPickTargetFloat, isPickTargetOnRegister bool
   242  	}{
   243  		{
   244  			name: "float on register",
   245  			pickTargetSetupFunc: func(compiler compilerImpl, _ *callEngine) error {
   246  				return compiler.compileConstF64(&wazeroir.OperationConstF64{Value: math.Float64frombits(pickTargetValue)})
   247  			},
   248  			isPickTargetFloat:      true,
   249  			isPickTargetOnRegister: true,
   250  		},
   251  		{
   252  			name: "int on register",
   253  			pickTargetSetupFunc: func(compiler compilerImpl, _ *callEngine) error {
   254  				return compiler.compileConstI64(&wazeroir.OperationConstI64{Value: pickTargetValue})
   255  			},
   256  			isPickTargetFloat:      false,
   257  			isPickTargetOnRegister: true,
   258  		},
   259  		{
   260  			name: "float on stack",
   261  			pickTargetSetupFunc: func(compiler compilerImpl, ce *callEngine) error {
   262  				pickTargetLocation := compiler.runtimeValueLocationStack().pushRuntimeValueLocationOnStack()
   263  				pickTargetLocation.valueType = runtimeValueTypeF64
   264  				ce.stack[pickTargetLocation.stackPointer] = pickTargetValue
   265  				return nil
   266  			},
   267  			isPickTargetFloat:      true,
   268  			isPickTargetOnRegister: false,
   269  		},
   270  		{
   271  			name: "int on stack",
   272  			pickTargetSetupFunc: func(compiler compilerImpl, ce *callEngine) error {
   273  				pickTargetLocation := compiler.runtimeValueLocationStack().pushRuntimeValueLocationOnStack()
   274  				pickTargetLocation.valueType = runtimeValueTypeI64
   275  				ce.stack[pickTargetLocation.stackPointer] = pickTargetValue
   276  				return nil
   277  			},
   278  			isPickTargetFloat:      false,
   279  			isPickTargetOnRegister: false,
   280  		},
   281  	}
   282  
   283  	for _, tt := range tests {
   284  		tc := tt
   285  		t.Run(tc.name, func(t *testing.T) {
   286  			env := newCompilerEnvironment()
   287  			compiler := env.requireNewCompiler(t, newCompiler, nil)
   288  			err := compiler.compilePreamble()
   289  			require.NoError(t, err)
   290  
   291  			// Set up the stack before picking.
   292  			err = tc.pickTargetSetupFunc(compiler, env.callEngine())
   293  			require.NoError(t, err)
   294  			pickTargetLocation := compiler.runtimeValueLocationStack().peek()
   295  
   296  			// Push the unused median value.
   297  			_ = compiler.runtimeValueLocationStack().pushRuntimeValueLocationOnStack()
   298  			requireRuntimeLocationStackPointerEqual(t, uint64(2), compiler)
   299  
   300  			// Now ready to compile Pick operation.
   301  			err = compiler.compilePick(op)
   302  			require.NoError(t, err)
   303  			requireRuntimeLocationStackPointerEqual(t, uint64(3), compiler)
   304  
   305  			pickedLocation := compiler.runtimeValueLocationStack().peek()
   306  			require.True(t, pickedLocation.onRegister())
   307  			require.Equal(t, pickTargetLocation.getRegisterType(), pickedLocation.getRegisterType())
   308  
   309  			err = compiler.compileReturnFunction()
   310  			require.NoError(t, err)
   311  
   312  			// Compile and execute the code under test.
   313  			code, _, err := compiler.compile()
   314  			require.NoError(t, err)
   315  			env.exec(code)
   316  
   317  			// Check the returned status and stack pointer.
   318  			require.Equal(t, nativeCallStatusCodeReturned, env.compilerStatus())
   319  			require.Equal(t, uint64(3), env.stackPointer())
   320  
   321  			// Verify the top value is the picked one and the pick target's value stays the same.
   322  			if tc.isPickTargetFloat {
   323  				require.Equal(t, math.Float64frombits(pickTargetValue), env.stackTopAsFloat64())
   324  				require.Equal(t, math.Float64frombits(pickTargetValue), math.Float64frombits(env.stack()[pickTargetLocation.stackPointer]))
   325  			} else {
   326  				require.Equal(t, pickTargetValue, env.stackTopAsUint64())
   327  				require.Equal(t, pickTargetValue, env.stack()[pickTargetLocation.stackPointer])
   328  			}
   329  		})
   330  	}
   331  }
   332  
   333  func TestCompiler_compileDrop(t *testing.T) {
   334  	t.Run("range nil", func(t *testing.T) {
   335  		env := newCompilerEnvironment()
   336  		compiler := env.requireNewCompiler(t, newCompiler, nil)
   337  
   338  		err := compiler.compilePreamble()
   339  		require.NoError(t, err)
   340  
   341  		// Put existing contents on stack.
   342  		liveNum := 10
   343  		for i := 0; i < liveNum; i++ {
   344  			compiler.runtimeValueLocationStack().pushRuntimeValueLocationOnStack()
   345  		}
   346  		requireRuntimeLocationStackPointerEqual(t, uint64(liveNum), compiler)
   347  
   348  		err = compiler.compileDrop(&wazeroir.OperationDrop{Depth: nil})
   349  		require.NoError(t, err)
   350  
   351  		// After the nil range drop, the stack must remain the same.
   352  		requireRuntimeLocationStackPointerEqual(t, uint64(liveNum), compiler)
   353  
   354  		err = compiler.compileReturnFunction()
   355  		require.NoError(t, err)
   356  
   357  		code, _, err := compiler.compile()
   358  		require.NoError(t, err)
   359  
   360  		env.exec(code)
   361  		require.Equal(t, nativeCallStatusCodeReturned, env.compilerStatus())
   362  	})
   363  	t.Run("start top", func(t *testing.T) {
   364  		r := &wazeroir.InclusiveRange{Start: 0, End: 2}
   365  		dropTargetNum := r.End - r.Start + 1 // +1 as the range is inclusive!
   366  		liveNum := 5
   367  
   368  		env := newCompilerEnvironment()
   369  		compiler := env.requireNewCompiler(t, newCompiler, nil)
   370  
   371  		err := compiler.compilePreamble()
   372  		require.NoError(t, err)
   373  
   374  		// Put existing contents on stack.
   375  		const expectedTopLiveValue = 100
   376  		for i := 0; i < liveNum+dropTargetNum; i++ {
   377  			if i == liveNum-1 {
   378  				err := compiler.compileConstI64(&wazeroir.OperationConstI64{Value: expectedTopLiveValue})
   379  				require.NoError(t, err)
   380  			} else {
   381  				compiler.runtimeValueLocationStack().pushRuntimeValueLocationOnStack()
   382  			}
   383  		}
   384  		requireRuntimeLocationStackPointerEqual(t, uint64(liveNum+dropTargetNum), compiler)
   385  
   386  		err = compiler.compileDrop(&wazeroir.OperationDrop{Depth: r})
   387  		require.NoError(t, err)
   388  
   389  		// After the drop operation, the stack contains only live contents.
   390  		requireRuntimeLocationStackPointerEqual(t, uint64(liveNum), compiler)
   391  		// Plus, the top value must stay on a register.
   392  		top := compiler.runtimeValueLocationStack().peek()
   393  		require.True(t, top.onRegister())
   394  
   395  		err = compiler.compileReturnFunction()
   396  		require.NoError(t, err)
   397  
   398  		code, _, err := compiler.compile()
   399  		require.NoError(t, err)
   400  
   401  		env.exec(code)
   402  		require.Equal(t, nativeCallStatusCodeReturned, env.compilerStatus())
   403  		require.Equal(t, uint64(5), env.stackPointer())
   404  		require.Equal(t, uint64(expectedTopLiveValue), env.stackTopAsUint64())
   405  	})
   406  
   407  	t.Run("start from middle", func(t *testing.T) {
   408  		r := &wazeroir.InclusiveRange{Start: 2, End: 3}
   409  		liveAboveDropStartNum := 3
   410  		dropTargetNum := r.End - r.Start + 1 // +1 as the range is inclusive!
   411  		liveBelowDropEndNum := 5
   412  		total := liveAboveDropStartNum + dropTargetNum + liveBelowDropEndNum
   413  		liveTotal := liveAboveDropStartNum + liveBelowDropEndNum
   414  
   415  		env := newCompilerEnvironment()
   416  		ce := env.callEngine()
   417  		compiler := env.requireNewCompiler(t, newCompiler, nil)
   418  
   419  		err := compiler.compilePreamble()
   420  		require.NoError(t, err)
   421  
   422  		// We don't need call frame in this test case, so simply pop them out!
   423  		for i := 0; i < callFrameDataSizeInUint64; i++ {
   424  			compiler.runtimeValueLocationStack().pop()
   425  		}
   426  
   427  		// Put existing contents except the top on stack
   428  		for i := 0; i < total-1; i++ {
   429  			loc := compiler.runtimeValueLocationStack().pushRuntimeValueLocationOnStack()
   430  			ce.stack[loc.stackPointer] = uint64(i) // Put the initial value.
   431  		}
   432  
   433  		// Place the top value.
   434  		const expectedTopLiveValue = 100
   435  		err = compiler.compileConstI64(&wazeroir.OperationConstI64{Value: expectedTopLiveValue})
   436  		require.NoError(t, err)
   437  
   438  		require.Equal(t, uint64(total), compiler.runtimeValueLocationStack().sp)
   439  
   440  		err = compiler.compileDrop(&wazeroir.OperationDrop{Depth: r})
   441  		require.NoError(t, err)
   442  
   443  		// After the drop operation, the stack contains only live contents.
   444  		require.Equal(t, uint64(liveTotal), compiler.runtimeValueLocationStack().sp)
   445  		// Plus, the top value must stay on a register.
   446  		require.True(t, compiler.runtimeValueLocationStack().peek().onRegister())
   447  
   448  		err = compiler.compileReturnFunction()
   449  		require.NoError(t, err)
   450  
   451  		code, _, err := compiler.compile()
   452  		require.NoError(t, err)
   453  
   454  		env.exec(code)
   455  		require.Equal(t, nativeCallStatusCodeReturned, env.compilerStatus())
   456  		require.Equal(t, uint64(liveTotal), env.ce.stackPointer)
   457  
   458  		stack := env.stack()[:env.stackPointer()]
   459  		for i, val := range stack {
   460  			if i <= liveBelowDropEndNum {
   461  				require.Equal(t, uint64(i), val)
   462  			} else if i == liveTotal-1 {
   463  				require.Equal(t, uint64(expectedTopLiveValue), val)
   464  			} else {
   465  				require.Equal(t, uint64(i+dropTargetNum), val)
   466  			}
   467  		}
   468  	})
   469  }
   470  
   471  func TestCompiler_compileSelect(t *testing.T) {
   472  	// There are mainly 8 cases we have to test:
   473  	// - [x1 = reg, x2 = reg] select x1
   474  	// - [x1 = reg, x2 = reg] select x2
   475  	// - [x1 = reg, x2 = stack] select x1
   476  	// - [x1 = reg, x2 = stack] select x2
   477  	// - [x1 = stack, x2 = reg] select x1
   478  	// - [x1 = stack, x2 = reg] select x2
   479  	// - [x1 = stack, x2 = stack] select x1
   480  	// - [x1 = stack, x2 = stack] select x2
   481  	// And for each case, we have to test with
   482  	// three conditional value location: stack, gp register, conditional register.
   483  	// So in total we have 24 cases.
   484  	tests := []struct {
   485  		x1OnRegister, x2OnRegister                                        bool
   486  		selectX1                                                          bool
   487  		condlValueOnStack, condValueOnGPRegister, condValueOnCondRegister bool
   488  	}{
   489  		// Conditional value on stack.
   490  		{x1OnRegister: true, x2OnRegister: true, selectX1: true, condlValueOnStack: true},
   491  		{x1OnRegister: true, x2OnRegister: true, selectX1: false, condlValueOnStack: true},
   492  		{x1OnRegister: true, x2OnRegister: false, selectX1: true, condlValueOnStack: true},
   493  		{x1OnRegister: true, x2OnRegister: false, selectX1: false, condlValueOnStack: true},
   494  		{x1OnRegister: false, x2OnRegister: true, selectX1: true, condlValueOnStack: true},
   495  		{x1OnRegister: false, x2OnRegister: true, selectX1: false, condlValueOnStack: true},
   496  		{x1OnRegister: false, x2OnRegister: false, selectX1: true, condlValueOnStack: true},
   497  		{x1OnRegister: false, x2OnRegister: false, selectX1: false, condlValueOnStack: true},
   498  		// Conditional value on register.
   499  		{x1OnRegister: true, x2OnRegister: true, selectX1: true, condValueOnGPRegister: true},
   500  		{x1OnRegister: true, x2OnRegister: true, selectX1: false, condValueOnGPRegister: true},
   501  		{x1OnRegister: true, x2OnRegister: false, selectX1: true, condValueOnGPRegister: true},
   502  		{x1OnRegister: true, x2OnRegister: false, selectX1: false, condValueOnGPRegister: true},
   503  		{x1OnRegister: false, x2OnRegister: true, selectX1: true, condValueOnGPRegister: true},
   504  		{x1OnRegister: false, x2OnRegister: true, selectX1: false, condValueOnGPRegister: true},
   505  		{x1OnRegister: false, x2OnRegister: false, selectX1: true, condValueOnGPRegister: true},
   506  		{x1OnRegister: false, x2OnRegister: false, selectX1: false, condValueOnGPRegister: true},
   507  		// Conditional value on conditional register.
   508  		{x1OnRegister: true, x2OnRegister: true, selectX1: true, condValueOnCondRegister: true},
   509  		{x1OnRegister: true, x2OnRegister: true, selectX1: false, condValueOnCondRegister: true},
   510  		{x1OnRegister: true, x2OnRegister: false, selectX1: true, condValueOnCondRegister: true},
   511  		{x1OnRegister: true, x2OnRegister: false, selectX1: false, condValueOnCondRegister: true},
   512  		{x1OnRegister: false, x2OnRegister: true, selectX1: true, condValueOnCondRegister: true},
   513  		{x1OnRegister: false, x2OnRegister: true, selectX1: false, condValueOnCondRegister: true},
   514  		{x1OnRegister: false, x2OnRegister: false, selectX1: true, condValueOnCondRegister: true},
   515  		{x1OnRegister: false, x2OnRegister: false, selectX1: false, condValueOnCondRegister: true},
   516  	}
   517  
   518  	for i, tt := range tests {
   519  		tc := tt
   520  		t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
   521  			for _, vals := range [][2]uint64{
   522  				{1, 2},
   523  				{0, 1},
   524  				{1, 0},
   525  				{math.Float64bits(-1), math.Float64bits(-1)},
   526  				{math.Float64bits(-1), math.Float64bits(1)},
   527  				{math.Float64bits(1), math.Float64bits(-1)},
   528  			} {
   529  				x1Value, x2Value := vals[0], vals[1]
   530  				t.Run(fmt.Sprintf("x1=0x%x,x2=0x%x", vals[0], vals[1]), func(t *testing.T) {
   531  					env := newCompilerEnvironment()
   532  					compiler := env.requireNewCompiler(t, newCompiler, nil)
   533  					err := compiler.compilePreamble()
   534  					require.NoError(t, err)
   535  
   536  					x1 := compiler.runtimeValueLocationStack().pushRuntimeValueLocationOnStack()
   537  					x1.valueType = runtimeValueTypeI64
   538  					env.stack()[x1.stackPointer] = x1Value
   539  					if tc.x1OnRegister {
   540  						err = compiler.compileEnsureOnRegister(x1)
   541  						require.NoError(t, err)
   542  					}
   543  
   544  					x2 := compiler.runtimeValueLocationStack().pushRuntimeValueLocationOnStack()
   545  					x2.valueType = runtimeValueTypeI64
   546  					env.stack()[x2.stackPointer] = x2Value
   547  					if tc.x2OnRegister {
   548  						err = compiler.compileEnsureOnRegister(x2)
   549  						require.NoError(t, err)
   550  					}
   551  
   552  					var c *runtimeValueLocation
   553  					if tc.condlValueOnStack {
   554  						c = compiler.runtimeValueLocationStack().pushRuntimeValueLocationOnStack()
   555  						if tc.selectX1 {
   556  							env.stack()[c.stackPointer] = 1
   557  						} else {
   558  							env.stack()[c.stackPointer] = 0
   559  						}
   560  					} else if tc.condValueOnGPRegister {
   561  						c = compiler.runtimeValueLocationStack().pushRuntimeValueLocationOnStack()
   562  						if tc.selectX1 {
   563  							env.stack()[c.stackPointer] = 1
   564  						} else {
   565  							env.stack()[c.stackPointer] = 0
   566  						}
   567  						err = compiler.compileEnsureOnRegister(c)
   568  						require.NoError(t, err)
   569  					} else if tc.condValueOnCondRegister {
   570  						err = compiler.compileConstI32(&wazeroir.OperationConstI32{Value: 0})
   571  						require.NoError(t, err)
   572  						err = compiler.compileConstI32(&wazeroir.OperationConstI32{Value: 0})
   573  						require.NoError(t, err)
   574  						if tc.selectX1 {
   575  							err = compiler.compileEq(&wazeroir.OperationEq{Type: wazeroir.UnsignedTypeI32})
   576  						} else {
   577  							err = compiler.compileNe(&wazeroir.OperationNe{Type: wazeroir.UnsignedTypeI32})
   578  						}
   579  						require.NoError(t, err)
   580  					}
   581  
   582  					// Now emit code for select.
   583  					err = compiler.compileSelect(&wazeroir.OperationSelect{})
   584  					require.NoError(t, err)
   585  
   586  					// x1 should be top of the stack.
   587  					require.Equal(t, x1, compiler.runtimeValueLocationStack().peek())
   588  
   589  					err = compiler.compileReturnFunction()
   590  					require.NoError(t, err)
   591  
   592  					// Run code.
   593  					code, _, err := compiler.compile()
   594  					require.NoError(t, err)
   595  					env.exec(code)
   596  
   597  					// Check the selected value.
   598  					require.Equal(t, uint64(1), env.stackPointer())
   599  					if tc.selectX1 {
   600  						require.Equal(t, env.stack()[x1.stackPointer], x1Value)
   601  					} else {
   602  						require.Equal(t, env.stack()[x1.stackPointer], x2Value)
   603  					}
   604  				})
   605  			}
   606  		})
   607  	}
   608  }
   609  
   610  func TestCompiler_compileSwap_v128(t *testing.T) {
   611  	const x1Lo, x1Hi uint64 = 100000, 200000
   612  	const x2Lo, x2Hi uint64 = 1, 2
   613  
   614  	tests := []struct {
   615  		x1OnRegister, x2OnRegister bool
   616  	}{
   617  		{x1OnRegister: true, x2OnRegister: true},
   618  		{x1OnRegister: true, x2OnRegister: false},
   619  		{x1OnRegister: false, x2OnRegister: true},
   620  		{x1OnRegister: false, x2OnRegister: false},
   621  	}
   622  
   623  	for _, tt := range tests {
   624  		tc := tt
   625  		t.Run(fmt.Sprintf("x1_register=%v, x2_register=%v", tc.x1OnRegister, tc.x2OnRegister), func(t *testing.T) {
   626  			env := newCompilerEnvironment()
   627  			compiler := env.requireNewCompiler(t, newCompiler, nil)
   628  			err := compiler.compilePreamble()
   629  			require.NoError(t, err)
   630  
   631  			if tc.x1OnRegister {
   632  				err = compiler.compileV128Const(&wazeroir.OperationV128Const{Lo: x1Lo, Hi: x1Hi})
   633  				require.NoError(t, err)
   634  			} else {
   635  				lo := compiler.runtimeValueLocationStack().pushRuntimeValueLocationOnStack() // lo
   636  				lo.valueType = runtimeValueTypeV128Lo
   637  				env.stack()[lo.stackPointer] = x1Lo
   638  				hi := compiler.runtimeValueLocationStack().pushRuntimeValueLocationOnStack() // hi
   639  				hi.valueType = runtimeValueTypeV128Hi
   640  				env.stack()[hi.stackPointer] = x1Hi
   641  			}
   642  
   643  			_ = compiler.runtimeValueLocationStack().pushRuntimeValueLocationOnStack() // Dummy value!
   644  
   645  			if tc.x2OnRegister {
   646  				err = compiler.compileV128Const(&wazeroir.OperationV128Const{Lo: x2Lo, Hi: x2Hi})
   647  				require.NoError(t, err)
   648  			} else {
   649  				lo := compiler.runtimeValueLocationStack().pushRuntimeValueLocationOnStack() // lo
   650  				lo.valueType = runtimeValueTypeV128Lo
   651  				env.stack()[lo.stackPointer] = x2Lo
   652  				hi := compiler.runtimeValueLocationStack().pushRuntimeValueLocationOnStack() // hi
   653  				hi.valueType = runtimeValueTypeV128Hi
   654  				env.stack()[hi.stackPointer] = x2Hi
   655  			}
   656  
   657  			// Swap x1 and x2.
   658  			err = compiler.compileSet(&wazeroir.OperationSet{Depth: 4, IsTargetVector: true})
   659  			require.NoError(t, err)
   660  
   661  			require.NoError(t, compiler.compileReturnFunction())
   662  
   663  			// Generate the code under test.
   664  			code, _, err := compiler.compile()
   665  			require.NoError(t, err)
   666  
   667  			// Run code.
   668  			env.exec(code)
   669  
   670  			require.Equal(t, nativeCallStatusCodeReturned, env.compilerStatus())
   671  			require.Equal(t, uint64(3), env.stackPointer())
   672  
   673  			// The first variable is above the call frame.
   674  			st := env.stack()
   675  			require.Equal(t, x2Lo, st[callFrameDataSizeInUint64])
   676  			require.Equal(t, x2Hi, st[callFrameDataSizeInUint64+1])
   677  		})
   678  	}
   679  }
   680  
   681  func TestCompiler_compileSet(t *testing.T) {
   682  	var x1Value, x2Value int64 = 100, 200
   683  	tests := []struct {
   684  		x1OnConditionalRegister, x1OnRegister, x2OnRegister bool
   685  	}{
   686  		{x1OnRegister: true, x2OnRegister: true},
   687  		{x1OnRegister: true, x2OnRegister: false},
   688  		{x1OnRegister: false, x2OnRegister: true},
   689  		{x1OnRegister: false, x2OnRegister: false},
   690  		// x1 on conditional register
   691  		{x1OnConditionalRegister: true, x2OnRegister: false},
   692  		{x1OnConditionalRegister: true, x2OnRegister: true},
   693  	}
   694  
   695  	for i, tt := range tests {
   696  		tc := tt
   697  		t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
   698  			env := newCompilerEnvironment()
   699  			compiler := env.requireNewCompiler(t, newCompiler, nil)
   700  			err := compiler.compilePreamble()
   701  			require.NoError(t, err)
   702  
   703  			x2 := compiler.runtimeValueLocationStack().pushRuntimeValueLocationOnStack()
   704  			env.stack()[x2.stackPointer] = uint64(x2Value)
   705  			if tc.x2OnRegister {
   706  				err = compiler.compileEnsureOnRegister(x2)
   707  				require.NoError(t, err)
   708  			}
   709  
   710  			_ = compiler.runtimeValueLocationStack().pushRuntimeValueLocationOnStack() // Dummy value!
   711  			if tc.x1OnRegister && !tc.x1OnConditionalRegister {
   712  				x1 := compiler.runtimeValueLocationStack().pushRuntimeValueLocationOnStack()
   713  				env.stack()[x1.stackPointer] = uint64(x1Value)
   714  				err = compiler.compileEnsureOnRegister(x1)
   715  				require.NoError(t, err)
   716  			} else if !tc.x1OnConditionalRegister {
   717  				x1 := compiler.runtimeValueLocationStack().pushRuntimeValueLocationOnStack()
   718  				env.stack()[x1.stackPointer] = uint64(x1Value)
   719  			} else {
   720  				err = compiler.compileConstI32(&wazeroir.OperationConstI32{Value: 0})
   721  				require.NoError(t, err)
   722  				err = compiler.compileConstI32(&wazeroir.OperationConstI32{Value: 0})
   723  				require.NoError(t, err)
   724  				err = compiler.compileEq(&wazeroir.OperationEq{Type: wazeroir.UnsignedTypeI32})
   725  				require.NoError(t, err)
   726  				x1Value = 1
   727  			}
   728  
   729  			// Set x2 into the x1.
   730  			err = compiler.compileSet(&wazeroir.OperationSet{Depth: 2})
   731  			require.NoError(t, err)
   732  
   733  			require.NoError(t, compiler.compileReturnFunction())
   734  
   735  			// Generate the code under test.
   736  			code, _, err := compiler.compile()
   737  			require.NoError(t, err)
   738  
   739  			// Run code.
   740  			env.exec(code)
   741  
   742  			require.Equal(t, uint64(2), env.stackPointer())
   743  			// Check the value was set. Note that it is placed above the call frame.
   744  			require.Equal(t, uint64(x1Value), env.stack()[callFrameDataSizeInUint64])
   745  		})
   746  	}
   747  }