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 }