github.com/bananabytelabs/wazero@v0.0.0-20240105073314-54b22a776da8/internal/engine/compiler/impl_vec_arm64_test.go (about)

     1  package compiler
     2  
     3  import (
     4  	"encoding/binary"
     5  	"testing"
     6  
     7  	"github.com/bananabytelabs/wazero/internal/asm"
     8  	"github.com/bananabytelabs/wazero/internal/asm/arm64"
     9  	"github.com/bananabytelabs/wazero/internal/testing/require"
    10  	"github.com/bananabytelabs/wazero/internal/wasm"
    11  	"github.com/bananabytelabs/wazero/internal/wazeroir"
    12  )
    13  
    14  // TestArm64Compiler_V128Shuffle_ConstTable_MiddleOfFunction ensures that flushing constant table in the middle of
    15  // function works well by intentionally setting arm64.AssemblerImpl MaxDisplacementForConstantPool = 0.
    16  func TestArm64Compiler_V128Shuffle_ConstTable_MiddleOfFunction(t *testing.T) {
    17  	env := newCompilerEnvironment()
    18  	compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler,
    19  		&wazeroir.CompilationResult{HasMemory: true})
    20  
    21  	err := compiler.compilePreamble()
    22  	require.NoError(t, err)
    23  
    24  	lanes := []uint64{1, 1, 1, 1, 0, 0, 0, 0, 10, 10, 10, 10, 0, 0, 0, 0}
    25  	v := [16]byte{0: 0xa, 1: 0xb, 10: 0xc}
    26  	w := [16]byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff}
    27  	exp := [16]byte{
    28  		0xb, 0xb, 0xb, 0xb,
    29  		0xa, 0xa, 0xa, 0xa,
    30  		0xc, 0xc, 0xc, 0xc,
    31  		0xa, 0xa, 0xa, 0xa,
    32  	}
    33  
    34  	err = compiler.compileV128Const(operationPtr(wazeroir.NewOperationV128Const(binary.LittleEndian.Uint64(v[:8]), binary.LittleEndian.Uint64(v[8:]))))
    35  	require.NoError(t, err)
    36  
    37  	err = compiler.compileV128Const(operationPtr(wazeroir.NewOperationV128Const(binary.LittleEndian.Uint64(w[:8]), binary.LittleEndian.Uint64(w[8:]))))
    38  	require.NoError(t, err)
    39  
    40  	err = compiler.compileV128Shuffle(operationPtr(wazeroir.NewOperationV128Shuffle(lanes)))
    41  	require.NoError(t, err)
    42  
    43  	assembler := compiler.(*arm64Compiler).assembler.(*arm64.AssemblerImpl)
    44  	assembler.MaxDisplacementForConstantPool = 0 // Ensures that constant table for shuffle will be flushed immediately.
    45  
    46  	err = compiler.compileReturnFunction()
    47  	require.NoError(t, err)
    48  
    49  	code := asm.CodeSegment{}
    50  	defer func() { require.NoError(t, code.Unmap()) }()
    51  
    52  	// Generate and run the code under test.
    53  	_, err = compiler.compile(code.NextCodeSection())
    54  	require.NoError(t, err)
    55  
    56  	env.exec(code.Bytes())
    57  
    58  	lo, hi := env.stackTopAsV128()
    59  	var actual [16]byte
    60  	binary.LittleEndian.PutUint64(actual[:8], lo)
    61  	binary.LittleEndian.PutUint64(actual[8:], hi)
    62  	require.Equal(t, exp, actual)
    63  }
    64  
    65  func TestArm64Compiler_V128Shuffle_combinations(t *testing.T) {
    66  	movValueRegisterToRegister := func(t *testing.T, c *arm64Compiler, src *runtimeValueLocation, dst asm.Register) {
    67  		c.assembler.CompileTwoVectorRegistersToVectorRegister(arm64.VORR, src.register, src.register, dst,
    68  			arm64.VectorArrangement16B)
    69  		c.locationStack.markRegisterUnused(src.register)
    70  		src.setRegister(dst)
    71  		// We have to set the lower 64-bits' location as well.
    72  		c.locationStack.stack[src.stackPointer-1].setRegister(dst)
    73  		c.locationStack.markRegisterUsed(dst)
    74  	}
    75  
    76  	tests := []struct {
    77  		name                        string
    78  		init                        func(t *testing.T, c *arm64Compiler)
    79  		wReg, vReg                  asm.Register
    80  		verifyFnc                   func(t *testing.T, env *compilerEnv)
    81  		expStackPointerAfterShuffle uint64
    82  	}{
    83  		{
    84  			name:                        "w=v1, v=v2",
    85  			wReg:                        arm64.RegV1,
    86  			vReg:                        arm64.RegV2,
    87  			init:                        func(t *testing.T, c *arm64Compiler) {},
    88  			verifyFnc:                   func(t *testing.T, env *compilerEnv) {},
    89  			expStackPointerAfterShuffle: 2,
    90  		},
    91  		{
    92  			name:                        "w=v2, v=v1",
    93  			wReg:                        arm64.RegV2,
    94  			vReg:                        arm64.RegV1,
    95  			init:                        func(t *testing.T, c *arm64Compiler) {},
    96  			verifyFnc:                   func(t *testing.T, env *compilerEnv) {},
    97  			expStackPointerAfterShuffle: 2,
    98  		},
    99  		{
   100  			name:                        "w=v29, v=v30",
   101  			wReg:                        arm64.RegV29, // will be moved to v30.
   102  			vReg:                        arm64.RegV30, // will be moved to v29.
   103  			init:                        func(t *testing.T, c *arm64Compiler) {},
   104  			verifyFnc:                   func(t *testing.T, env *compilerEnv) {},
   105  			expStackPointerAfterShuffle: 2,
   106  		},
   107  		{
   108  			name: "w=v12, v=v30",
   109  			wReg: arm64.RegV12, // will be moved to v30.
   110  			vReg: arm64.RegV30, // will be moved to v29.
   111  			init: func(t *testing.T, c *arm64Compiler) {
   112  				// Set up the previous value on the v3 register.
   113  				err := c.compileV128Const(operationPtr(wazeroir.NewOperationV128Const(1234, 5678)))
   114  				require.NoError(t, err)
   115  				movValueRegisterToRegister(t, c, c.locationStack.peek(), arm64.RegV29)
   116  			},
   117  			verifyFnc: func(t *testing.T, env *compilerEnv) {
   118  				// Previous value on the V3 register must be saved onto the stack.
   119  				lo, hi := env.stack()[callFrameDataSizeInUint64], env.stack()[callFrameDataSizeInUint64+1]
   120  				require.Equal(t, uint64(1234), lo)
   121  				require.Equal(t, uint64(5678), hi)
   122  			},
   123  			expStackPointerAfterShuffle: 4,
   124  		},
   125  		{
   126  			name: "w=v29, v=v12",
   127  			wReg: arm64.RegV29, // will be moved to v30.
   128  			vReg: arm64.RegV12, // will be moved to v29.
   129  			init: func(t *testing.T, c *arm64Compiler) {
   130  				// Set up the previous value on the v3 register.
   131  				err := c.compileV128Const(operationPtr(wazeroir.NewOperationV128Const(1234, 5678)))
   132  				require.NoError(t, err)
   133  				movValueRegisterToRegister(t, c, c.locationStack.peek(), arm64.RegV30)
   134  			},
   135  			verifyFnc: func(t *testing.T, env *compilerEnv) {
   136  				// Previous value on the V3 register must be saved onto the stack.
   137  				lo, hi := env.stack()[callFrameDataSizeInUint64], env.stack()[callFrameDataSizeInUint64+1]
   138  				require.Equal(t, uint64(1234), lo)
   139  				require.Equal(t, uint64(5678), hi)
   140  			},
   141  			expStackPointerAfterShuffle: 4,
   142  		},
   143  	}
   144  
   145  	lanes := []uint64{1, 1, 1, 1, 0, 0, 0, 0, 10, 10, 10, 10, 0, 0, 0, 31}
   146  	v := [16]byte{0: 0xa, 1: 0xb, 10: 0xc}
   147  	w := [16]byte{0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 0xff, 1}
   148  	exp := [16]byte{
   149  		0xb, 0xb, 0xb, 0xb,
   150  		0xa, 0xa, 0xa, 0xa,
   151  		0xc, 0xc, 0xc, 0xc,
   152  		0xa, 0xa, 0xa, 1,
   153  	}
   154  
   155  	for _, tc := range tests {
   156  		tc := tc
   157  		t.Run(tc.name, func(t *testing.T) {
   158  			env := newCompilerEnvironment()
   159  			compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler,
   160  				&wazeroir.CompilationResult{HasMemory: true})
   161  
   162  			err := compiler.compilePreamble()
   163  			require.NoError(t, err)
   164  
   165  			ac := compiler.(*arm64Compiler)
   166  			tc.init(t, ac)
   167  
   168  			err = compiler.compileV128Const(operationPtr(wazeroir.NewOperationV128Const(binary.LittleEndian.Uint64(v[:8]), binary.LittleEndian.Uint64(v[8:]))))
   169  			require.NoError(t, err)
   170  
   171  			vLocation := compiler.runtimeValueLocationStack().peek()
   172  			movValueRegisterToRegister(t, ac, vLocation, tc.vReg)
   173  
   174  			err = compiler.compileV128Const(operationPtr(wazeroir.NewOperationV128Const(binary.LittleEndian.Uint64(w[:8]), binary.LittleEndian.Uint64(w[8:]))))
   175  			require.NoError(t, err)
   176  
   177  			wLocation := compiler.runtimeValueLocationStack().peek()
   178  			movValueRegisterToRegister(t, ac, wLocation, tc.wReg)
   179  
   180  			err = compiler.compileV128Shuffle(operationPtr(wazeroir.NewOperationV128Shuffle(lanes)))
   181  			require.NoError(t, err)
   182  
   183  			requireRuntimeLocationStackPointerEqual(t, tc.expStackPointerAfterShuffle, compiler)
   184  			require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters.list()))
   185  
   186  			err = compiler.compileReturnFunction()
   187  			require.NoError(t, err)
   188  
   189  			code := asm.CodeSegment{}
   190  			defer func() { require.NoError(t, code.Unmap()) }()
   191  
   192  			// Generate and run the code under test.
   193  			_, err = compiler.compile(code.NextCodeSection())
   194  			require.NoError(t, err)
   195  
   196  			env.exec(code.Bytes())
   197  
   198  			lo, hi := env.stackTopAsV128()
   199  			var actual [16]byte
   200  			binary.LittleEndian.PutUint64(actual[:8], lo)
   201  			binary.LittleEndian.PutUint64(actual[8:], hi)
   202  			require.Equal(t, exp, actual)
   203  
   204  			tc.verifyFnc(t, env)
   205  		})
   206  	}
   207  }