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

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