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

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