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

     1  package compiler
     2  
     3  import (
     4  	"encoding/hex"
     5  	"testing"
     6  	"unsafe"
     7  
     8  	"github.com/bananabytelabs/wazero/internal/asm"
     9  	"github.com/bananabytelabs/wazero/internal/asm/amd64"
    10  	"github.com/bananabytelabs/wazero/internal/platform"
    11  	"github.com/bananabytelabs/wazero/internal/testing/require"
    12  	"github.com/bananabytelabs/wazero/internal/wasm"
    13  	"github.com/bananabytelabs/wazero/internal/wazeroir"
    14  )
    15  
    16  // TestAmd64Compiler_indirectCallWithTargetOnCallingConvReg is the regression test for #526.
    17  // In short, the offset register for call_indirect might be the same as amd64CallingConventionDestinationFunctionModuleInstanceAddressRegister
    18  // and that must not be a failure.
    19  func TestAmd64Compiler_indirectCallWithTargetOnCallingConvReg(t *testing.T) {
    20  	code := asm.CodeSegment{}
    21  	defer func() { require.NoError(t, code.Unmap()) }()
    22  
    23  	env := newCompilerEnvironment()
    24  	table := make([]wasm.Reference, 1)
    25  	env.addTable(&wasm.TableInstance{References: table})
    26  	// Ensure that the module instance has the type information for targetOperation.TypeIndex,
    27  	// and the typeID  matches the table[targetOffset]'s type ID.
    28  	operation := operationPtr(wazeroir.NewOperationCallIndirect(0, 0))
    29  	env.module().TypeIDs = []wasm.FunctionTypeID{0}
    30  	env.module().Engine = &moduleEngine{functions: []function{}}
    31  
    32  	me := env.moduleEngine()
    33  	{ // Compiling call target.
    34  		compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler, nil)
    35  		err := compiler.compilePreamble()
    36  		require.NoError(t, err)
    37  		err = compiler.compileReturnFunction()
    38  		require.NoError(t, err)
    39  
    40  		_, err = compiler.compile(code.NextCodeSection())
    41  		require.NoError(t, err)
    42  
    43  		executable := code.Bytes()
    44  		makeExecutable(executable)
    45  
    46  		f := function{
    47  			parent:             &compiledFunction{parent: &compiledCode{executable: code}},
    48  			codeInitialAddress: code.Addr(),
    49  			moduleInstance:     env.moduleInstance,
    50  			typeID:             0,
    51  		}
    52  		me.functions = append(me.functions, f)
    53  		table[0] = uintptr(unsafe.Pointer(&f))
    54  	}
    55  
    56  	compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler, &wazeroir.CompilationResult{
    57  		Types:    []wasm.FunctionType{{}},
    58  		HasTable: true,
    59  	}).(*amd64Compiler)
    60  	err := compiler.compilePreamble()
    61  	require.NoError(t, err)
    62  
    63  	// Place the offset into the calling-convention reserved register.
    64  	offsetLoc := compiler.pushRuntimeValueLocationOnRegister(amd64CallingConventionDestinationFunctionModuleInstanceAddressRegister,
    65  		runtimeValueTypeI32)
    66  	compiler.assembler.CompileConstToRegister(amd64.MOVQ, 0, offsetLoc.register)
    67  
    68  	require.NoError(t, compiler.compileCallIndirect(operation))
    69  
    70  	err = compiler.compileReturnFunction()
    71  	require.NoError(t, err)
    72  
    73  	// Generate the code under test and run.
    74  	_, err = compiler.compile(code.NextCodeSection())
    75  	require.NoError(t, err)
    76  	env.exec(code.Bytes())
    77  }
    78  
    79  func TestAmd64Compiler_compile_Mul_Div_Rem(t *testing.T) {
    80  	for _, kind := range []wazeroir.OperationKind{
    81  		wazeroir.OperationKindMul,
    82  		wazeroir.OperationKindDiv,
    83  		wazeroir.OperationKindRem,
    84  	} {
    85  		kind := kind
    86  		t.Run(kind.String(), func(t *testing.T) {
    87  			t.Run("int32", func(t *testing.T) {
    88  				tests := []struct {
    89  					name         string
    90  					x1Reg, x2Reg asm.Register
    91  				}{
    92  					{
    93  						name:  "x1:ax,x2:random_reg",
    94  						x1Reg: amd64.RegAX,
    95  						x2Reg: amd64.RegR10,
    96  					},
    97  					{
    98  						name:  "x1:ax,x2:stack",
    99  						x1Reg: amd64.RegAX,
   100  						x2Reg: asm.NilRegister,
   101  					},
   102  					{
   103  						name:  "x1:random_reg,x2:ax",
   104  						x1Reg: amd64.RegR10,
   105  						x2Reg: amd64.RegAX,
   106  					},
   107  					{
   108  						name:  "x1:stack,x2:ax",
   109  						x1Reg: asm.NilRegister,
   110  						x2Reg: amd64.RegAX,
   111  					},
   112  					{
   113  						name:  "x1:random_reg,x2:random_reg",
   114  						x1Reg: amd64.RegR10,
   115  						x2Reg: amd64.RegR9,
   116  					},
   117  					{
   118  						name:  "x1:stack,x2:random_reg",
   119  						x1Reg: asm.NilRegister,
   120  						x2Reg: amd64.RegR9,
   121  					},
   122  					{
   123  						name:  "x1:random_reg,x2:stack",
   124  						x1Reg: amd64.RegR9,
   125  						x2Reg: asm.NilRegister,
   126  					},
   127  					{
   128  						name:  "x1:stack,x2:stack",
   129  						x1Reg: asm.NilRegister,
   130  						x2Reg: asm.NilRegister,
   131  					},
   132  				}
   133  
   134  				for _, tt := range tests {
   135  					tc := tt
   136  					t.Run(tc.name, func(t *testing.T) {
   137  						env := newCompilerEnvironment()
   138  
   139  						const x1Value uint32 = 1 << 11
   140  						const x2Value uint32 = 51
   141  						const dxValue uint64 = 111111
   142  
   143  						compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newAmd64Compiler, nil).(*amd64Compiler)
   144  
   145  						// To make the assertion below stable, we preallocate the underlying stack,
   146  						// so that the pointer to the entry will be stale.
   147  						compiler.runtimeValueLocationStack().stack = make([]runtimeValueLocation, 100)
   148  
   149  						err := compiler.compilePreamble()
   150  						require.NoError(t, err)
   151  
   152  						// Pretend there was an existing value on the DX register. We expect compileMul to save this to the stack.
   153  						// Here, we put it just before two operands as ["any value used by DX", x1, x2]
   154  						// but in reality, it can exist in any position of stack.
   155  						compiler.assembler.CompileConstToRegister(amd64.MOVQ, int64(dxValue), amd64.RegDX)
   156  						prevOnDX := compiler.pushRuntimeValueLocationOnRegister(amd64.RegDX, runtimeValueTypeI32)
   157  
   158  						// Setup values.
   159  						if tc.x1Reg != asm.NilRegister {
   160  							compiler.assembler.CompileConstToRegister(amd64.MOVQ, int64(x1Value), tc.x1Reg)
   161  							compiler.pushRuntimeValueLocationOnRegister(tc.x1Reg, runtimeValueTypeI32)
   162  						} else {
   163  							loc := compiler.runtimeValueLocationStack().pushRuntimeValueLocationOnStack()
   164  							loc.valueType = runtimeValueTypeI32
   165  							env.stack()[loc.stackPointer] = uint64(x1Value)
   166  						}
   167  						if tc.x2Reg != asm.NilRegister {
   168  							compiler.assembler.CompileConstToRegister(amd64.MOVQ, int64(x2Value), tc.x2Reg)
   169  							compiler.pushRuntimeValueLocationOnRegister(tc.x2Reg, runtimeValueTypeI32)
   170  						} else {
   171  							loc := compiler.runtimeValueLocationStack().pushRuntimeValueLocationOnStack()
   172  							loc.valueType = runtimeValueTypeI32
   173  							env.stack()[loc.stackPointer] = uint64(x2Value)
   174  						}
   175  
   176  						switch kind {
   177  						case wazeroir.OperationKindDiv:
   178  							err = compiler.compileDiv(operationPtr(wazeroir.NewOperationDiv(wazeroir.SignedTypeUint32)))
   179  						case wazeroir.OperationKindMul:
   180  							err = compiler.compileMul(operationPtr(wazeroir.NewOperationMul(wazeroir.UnsignedTypeI32)))
   181  						case wazeroir.OperationKindRem:
   182  							err = compiler.compileRem(operationPtr(wazeroir.NewOperationRem(wazeroir.SignedUint32)))
   183  						}
   184  						require.NoError(t, err)
   185  
   186  						require.Equal(t, registerTypeGeneralPurpose, compiler.runtimeValueLocationStack().peek().getRegisterType())
   187  						requireRuntimeLocationStackPointerEqual(t, uint64(2), compiler)
   188  						require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters.list()))
   189  						// At this point, the previous value on the DX register is saved to the stack.
   190  						require.True(t, prevOnDX.onStack())
   191  
   192  						// We add the value previously on the DX with the multiplication result
   193  						// in order to ensure that not saving existing DX value would cause
   194  						// the failure in a subsequent instruction.
   195  						err = compiler.compileAdd(operationPtr(wazeroir.NewOperationAdd(wazeroir.UnsignedTypeI32)))
   196  						require.NoError(t, err)
   197  						require.NoError(t, compiler.compileReturnFunction())
   198  
   199  						code := asm.CodeSegment{}
   200  						defer func() { require.NoError(t, code.Unmap()) }()
   201  
   202  						// Generate the code under test.
   203  						_, err = compiler.compile(code.NextCodeSection())
   204  						require.NoError(t, err)
   205  						// Run code.
   206  						env.exec(code.Bytes())
   207  
   208  						// Verify the stack is in the form of ["any value previously used by DX" + the result of operation]
   209  						require.Equal(t, uint64(1), env.stackPointer())
   210  						switch kind {
   211  						case wazeroir.OperationKindDiv:
   212  							require.Equal(t, x1Value/x2Value+uint32(dxValue), env.stackTopAsUint32())
   213  						case wazeroir.OperationKindMul:
   214  							require.Equal(t, x1Value*x2Value+uint32(dxValue), env.stackTopAsUint32())
   215  						case wazeroir.OperationKindRem:
   216  							require.Equal(t, x1Value%x2Value+uint32(dxValue), env.stackTopAsUint32())
   217  						}
   218  					})
   219  				}
   220  			})
   221  			t.Run("int64", func(t *testing.T) {
   222  				tests := []struct {
   223  					name         string
   224  					x1Reg, x2Reg asm.Register
   225  				}{
   226  					{
   227  						name:  "x1:ax,x2:random_reg",
   228  						x1Reg: amd64.RegAX,
   229  						x2Reg: amd64.RegR10,
   230  					},
   231  					{
   232  						name:  "x1:ax,x2:stack",
   233  						x1Reg: amd64.RegAX,
   234  						x2Reg: asm.NilRegister,
   235  					},
   236  					{
   237  						name:  "x1:random_reg,x2:ax",
   238  						x1Reg: amd64.RegR10,
   239  						x2Reg: amd64.RegAX,
   240  					},
   241  					{
   242  						name:  "x1:stack,x2:ax",
   243  						x1Reg: asm.NilRegister,
   244  						x2Reg: amd64.RegAX,
   245  					},
   246  					{
   247  						name:  "x1:random_reg,x2:random_reg",
   248  						x1Reg: amd64.RegR10,
   249  						x2Reg: amd64.RegR9,
   250  					},
   251  					{
   252  						name:  "x1:stack,x2:random_reg",
   253  						x1Reg: asm.NilRegister,
   254  						x2Reg: amd64.RegR9,
   255  					},
   256  					{
   257  						name:  "x1:random_reg,x2:stack",
   258  						x1Reg: amd64.RegR9,
   259  						x2Reg: asm.NilRegister,
   260  					},
   261  					{
   262  						name:  "x1:stack,x2:stack",
   263  						x1Reg: asm.NilRegister,
   264  						x2Reg: asm.NilRegister,
   265  					},
   266  				}
   267  
   268  				for _, tt := range tests {
   269  					tc := tt
   270  					t.Run(tc.name, func(t *testing.T) {
   271  						const x1Value uint64 = 1 << 35
   272  						const x2Value uint64 = 51
   273  						const dxValue uint64 = 111111
   274  
   275  						env := newCompilerEnvironment()
   276  						compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newAmd64Compiler, nil).(*amd64Compiler)
   277  
   278  						// To make the assertion below stable, we preallocate the underlying stack,
   279  						// so that the pointer to the entry will be stale.
   280  						compiler.runtimeValueLocationStack().stack = make([]runtimeValueLocation, 100)
   281  
   282  						err := compiler.compilePreamble()
   283  						require.NoError(t, err)
   284  
   285  						// Pretend there was an existing value on the DX register. We expect compileMul to save this to the stack.
   286  						// Here, we put it just before two operands as ["any value used by DX", x1, x2]
   287  						// but in reality, it can exist in any position of stack.
   288  						compiler.assembler.CompileConstToRegister(amd64.MOVQ, int64(dxValue), amd64.RegDX)
   289  						prevOnDX := compiler.pushRuntimeValueLocationOnRegister(amd64.RegDX, runtimeValueTypeI64)
   290  
   291  						// Setup values.
   292  						if tc.x1Reg != asm.NilRegister {
   293  							compiler.assembler.CompileConstToRegister(amd64.MOVQ, int64(x1Value), tc.x1Reg)
   294  							compiler.pushRuntimeValueLocationOnRegister(tc.x1Reg, runtimeValueTypeI64)
   295  						} else {
   296  							loc := compiler.runtimeValueLocationStack().pushRuntimeValueLocationOnStack()
   297  							loc.valueType = runtimeValueTypeI64
   298  							env.stack()[loc.stackPointer] = uint64(x1Value)
   299  						}
   300  						if tc.x2Reg != asm.NilRegister {
   301  							compiler.assembler.CompileConstToRegister(amd64.MOVQ, int64(x2Value), tc.x2Reg)
   302  							compiler.pushRuntimeValueLocationOnRegister(tc.x2Reg, runtimeValueTypeI64)
   303  						} else {
   304  							loc := compiler.runtimeValueLocationStack().pushRuntimeValueLocationOnStack()
   305  							loc.valueType = runtimeValueTypeI64
   306  							env.stack()[loc.stackPointer] = uint64(x2Value)
   307  						}
   308  
   309  						switch kind {
   310  						case wazeroir.OperationKindDiv:
   311  							err = compiler.compileDiv(operationPtr(wazeroir.NewOperationDiv(wazeroir.SignedTypeInt64)))
   312  						case wazeroir.OperationKindMul:
   313  							err = compiler.compileMul(operationPtr(wazeroir.NewOperationMul(wazeroir.UnsignedTypeI64)))
   314  						case wazeroir.OperationKindRem:
   315  							err = compiler.compileRem(operationPtr(wazeroir.NewOperationRem(wazeroir.SignedUint64)))
   316  						}
   317  						require.NoError(t, err)
   318  
   319  						require.Equal(t, registerTypeGeneralPurpose, compiler.runtimeValueLocationStack().peek().getRegisterType())
   320  						requireRuntimeLocationStackPointerEqual(t, uint64(2), compiler)
   321  						require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters.list()))
   322  						// At this point, the previous value on the DX register is saved to the stack.
   323  						require.True(t, prevOnDX.onStack())
   324  
   325  						// We add the value previously on the DX with the multiplication result
   326  						// in order to ensure that not saving existing DX value would cause
   327  						// the failure in a subsequent instruction.
   328  						err = compiler.compileAdd(operationPtr(wazeroir.NewOperationAdd(wazeroir.UnsignedTypeI64)))
   329  						require.NoError(t, err)
   330  						require.NoError(t, compiler.compileReturnFunction())
   331  
   332  						code := asm.CodeSegment{}
   333  						defer func() { require.NoError(t, code.Unmap()) }()
   334  
   335  						// Generate the code under test.
   336  						_, err = compiler.compile(code.NextCodeSection())
   337  						require.NoError(t, err)
   338  
   339  						// Run code.
   340  						env.exec(code.Bytes())
   341  
   342  						// Verify the stack is in the form of ["any value previously used by DX" + the result of operation]
   343  						switch kind {
   344  						case wazeroir.OperationKindDiv:
   345  							require.Equal(t, uint64(1), env.stackPointer())
   346  							require.Equal(t, uint64(x1Value/x2Value)+dxValue, env.stackTopAsUint64())
   347  						case wazeroir.OperationKindMul:
   348  							require.Equal(t, uint64(1), env.stackPointer())
   349  							require.Equal(t, uint64(x1Value*x2Value)+dxValue, env.stackTopAsUint64())
   350  						case wazeroir.OperationKindRem:
   351  							require.Equal(t, uint64(1), env.stackPointer())
   352  							require.Equal(t, x1Value%x2Value+dxValue, env.stackTopAsUint64())
   353  						}
   354  					})
   355  				}
   356  			})
   357  		})
   358  	}
   359  }
   360  
   361  func TestAmd64Compiler_readInstructionAddress(t *testing.T) {
   362  	t.Run("invalid", func(t *testing.T) {
   363  		env := newCompilerEnvironment()
   364  		compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newAmd64Compiler, nil).(*amd64Compiler)
   365  
   366  		err := compiler.compilePreamble()
   367  		require.NoError(t, err)
   368  
   369  		// Set the acquisition target instruction to the one after JMP.
   370  		compiler.assembler.CompileReadInstructionAddress(amd64.RegAX, amd64.JMP)
   371  
   372  		code := asm.CodeSegment{}
   373  		defer func() { require.NoError(t, code.Unmap()) }()
   374  
   375  		// If generate the code without JMP after readInstructionAddress,
   376  		// the call back added must return error.
   377  		_, err = compiler.compile(code.NextCodeSection())
   378  		require.Error(t, err)
   379  	})
   380  
   381  	t.Run("ok", func(t *testing.T) {
   382  		env := newCompilerEnvironment()
   383  		compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newAmd64Compiler, nil).(*amd64Compiler)
   384  
   385  		err := compiler.compilePreamble()
   386  		require.NoError(t, err)
   387  
   388  		const destinationRegister = amd64.RegAX
   389  		// Set the acquisition target instruction to the one after RET,
   390  		// and read the absolute address into destinationRegister.
   391  		compiler.assembler.CompileReadInstructionAddress(destinationRegister, amd64.RET)
   392  
   393  		// Jump to the instruction after RET below via the absolute
   394  		// address stored in destinationRegister.
   395  		compiler.assembler.CompileJumpToRegister(amd64.JMP, destinationRegister)
   396  
   397  		compiler.assembler.CompileStandAlone(amd64.RET)
   398  
   399  		// This could be the read instruction target as this is the
   400  		// right after RET. Therefore, the jmp instruction above
   401  		// must target here.
   402  		const expectedReturnValue uint32 = 10000
   403  		err = compiler.compileConstI32(operationPtr(wazeroir.NewOperationConstI32(expectedReturnValue)))
   404  		require.NoError(t, err)
   405  
   406  		err = compiler.compileReturnFunction()
   407  		require.NoError(t, err)
   408  
   409  		code := asm.CodeSegment{}
   410  		defer func() { require.NoError(t, code.Unmap()) }()
   411  
   412  		// Generate the code under test.
   413  		_, err = compiler.compile(code.NextCodeSection())
   414  		require.NoError(t, err)
   415  
   416  		// Run code.
   417  		env.exec(code.Bytes())
   418  
   419  		require.Equal(t, nativeCallStatusCodeReturned, env.compilerStatus())
   420  		require.Equal(t, uint64(1), env.stackPointer())
   421  		require.Equal(t, expectedReturnValue, env.stackTopAsUint32())
   422  	})
   423  }
   424  
   425  func TestAmd64Compiler_preventCrossedTargetdRegisters(t *testing.T) {
   426  	env := newCompilerEnvironment()
   427  	compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newAmd64Compiler, nil).(*amd64Compiler)
   428  
   429  	tests := []struct {
   430  		initial           []*runtimeValueLocation
   431  		desired, expected []asm.Register
   432  	}{
   433  		{
   434  			initial:  []*runtimeValueLocation{{register: amd64.RegAX}, {register: amd64.RegCX}, {register: amd64.RegDX}},
   435  			desired:  []asm.Register{amd64.RegDX, amd64.RegCX, amd64.RegAX},
   436  			expected: []asm.Register{amd64.RegDX, amd64.RegCX, amd64.RegAX},
   437  		},
   438  		{
   439  			initial:  []*runtimeValueLocation{{register: amd64.RegAX}, {register: amd64.RegCX}, {register: amd64.RegDX}},
   440  			desired:  []asm.Register{amd64.RegDX, amd64.RegAX, amd64.RegCX},
   441  			expected: []asm.Register{amd64.RegDX, amd64.RegAX, amd64.RegCX},
   442  		},
   443  		{
   444  			initial:  []*runtimeValueLocation{{register: amd64.RegR8}, {register: amd64.RegR9}, {register: amd64.RegR10}},
   445  			desired:  []asm.Register{amd64.RegR8, amd64.RegR9, amd64.RegR10},
   446  			expected: []asm.Register{amd64.RegR8, amd64.RegR9, amd64.RegR10},
   447  		},
   448  		{
   449  			initial:  []*runtimeValueLocation{{register: amd64.RegBX}, {register: amd64.RegDX}, {register: amd64.RegCX}},
   450  			desired:  []asm.Register{amd64.RegR8, amd64.RegR9, amd64.RegR10},
   451  			expected: []asm.Register{amd64.RegBX, amd64.RegDX, amd64.RegCX},
   452  		},
   453  		{
   454  			initial:  []*runtimeValueLocation{{register: amd64.RegR8}, {register: amd64.RegR9}, {register: amd64.RegR10}},
   455  			desired:  []asm.Register{amd64.RegAX, amd64.RegCX, amd64.RegR9},
   456  			expected: []asm.Register{amd64.RegR8, amd64.RegR10, amd64.RegR9},
   457  		},
   458  	}
   459  
   460  	for _, tt := range tests {
   461  		initialRegisters := collectRegistersFromRuntimeValues(tt.initial)
   462  		restoreCrossing := compiler.compilePreventCrossedTargetRegisters(tt.initial, tt.desired)
   463  		// Required expected state after prevented crossing.
   464  		require.Equal(t, tt.expected, collectRegistersFromRuntimeValues(tt.initial))
   465  		restoreCrossing()
   466  		// Require initial state after restoring.
   467  		require.Equal(t, initialRegisters, collectRegistersFromRuntimeValues(tt.initial))
   468  	}
   469  }
   470  
   471  // mockCpuFlags implements platform.CpuFeatureFlags
   472  type mockCpuFlags struct {
   473  	flags      uint64
   474  	extraFlags uint64
   475  }
   476  
   477  // Has implements the method of the same name in platform.CpuFeatureFlags
   478  func (f *mockCpuFlags) Has(flag uint64) bool {
   479  	return (f.flags & flag) != 0
   480  }
   481  
   482  // HasExtra implements the method of the same name in platform.CpuFeatureFlags
   483  func (f *mockCpuFlags) HasExtra(flag uint64) bool {
   484  	return (f.extraFlags & flag) != 0
   485  }
   486  
   487  // Relates to #1111 (Clz): older AMD64 CPUs do not support the LZCNT instruction
   488  // CPUID should be used instead. We simulate presence/absence of the feature
   489  // by overriding the field in the corresponding struct.
   490  func TestAmd64Compiler_ensureClz_ABM(t *testing.T) {
   491  	tests := []struct {
   492  		name         string
   493  		cpuFeatures  platform.CpuFeatureFlags
   494  		expectedCode string
   495  	}{
   496  		{
   497  			name:         "with ABM",
   498  			expectedCode: "b80a000000f3480fbdc0",
   499  			cpuFeatures: &mockCpuFlags{
   500  				flags:      0,
   501  				extraFlags: platform.CpuExtraFeatureABM,
   502  			},
   503  		},
   504  		{
   505  			name:         "without ABM",
   506  			expectedCode: "b80a0000004885c07507b840000000eb08480fbdc04883f03f",
   507  			cpuFeatures: &mockCpuFlags{
   508  				flags:      0,
   509  				extraFlags: 0, // no flags, thus no ABM, i.e. no LZCNT
   510  			},
   511  		},
   512  	}
   513  	for _, tt := range tests {
   514  		t.Run(tt.name, func(t *testing.T) {
   515  			env := newCompilerEnvironment()
   516  
   517  			newCompiler := func() compiler {
   518  				c := newCompiler().(*amd64Compiler)
   519  				// override auto-detected CPU features with the test case
   520  				c.cpuFeatures = tt.cpuFeatures
   521  				return c
   522  			}
   523  
   524  			compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler, nil)
   525  
   526  			err := compiler.compileConstI32(operationPtr(wazeroir.NewOperationConstI32(10)))
   527  			require.NoError(t, err)
   528  
   529  			err = compiler.compileClz(operationPtr(wazeroir.NewOperationClz(wazeroir.UnsignedInt64)))
   530  			require.NoError(t, err)
   531  
   532  			compiler.compileNOP() // pad for jump target (when no ABM)
   533  
   534  			code := asm.CodeSegment{}
   535  			defer func() { require.NoError(t, code.Unmap()) }()
   536  
   537  			buf := code.NextCodeSection()
   538  			_, err = compiler.compile(buf)
   539  			require.NoError(t, err)
   540  			require.Equal(t, tt.expectedCode, hex.EncodeToString(buf.Bytes()))
   541  		})
   542  	}
   543  }
   544  
   545  // Relates to #1111 (Ctz): older AMD64 CPUs do not support the LZCNT instruction
   546  // CPUID should be used instead. We simulate presence/absence of the feature
   547  // by overriding the field in the corresponding struct.
   548  func TestAmd64Compiler_ensureCtz_ABM(t *testing.T) {
   549  	tests := []struct {
   550  		name         string
   551  		cpuFeatures  platform.CpuFeatureFlags
   552  		expectedCode string
   553  	}{
   554  		{
   555  			name:         "with ABM",
   556  			expectedCode: "b80a000000f3480fbcc0",
   557  			cpuFeatures: &mockCpuFlags{
   558  				flags:      0,
   559  				extraFlags: platform.CpuExtraFeatureABM,
   560  			},
   561  		},
   562  		{
   563  			name:         "without ABM",
   564  			expectedCode: "b80a0000004885c07507b840000000eb05f3480fbcc0",
   565  			cpuFeatures: &mockCpuFlags{
   566  				flags:      0,
   567  				extraFlags: 0, // no flags, thus no ABM, i.e. no LZCNT
   568  			},
   569  		},
   570  	}
   571  	for _, tt := range tests {
   572  		t.Run(tt.name, func(t *testing.T) {
   573  			env := newCompilerEnvironment()
   574  
   575  			newCompiler := func() compiler {
   576  				c := newCompiler().(*amd64Compiler)
   577  				// override auto-detected CPU features with the test case
   578  				c.cpuFeatures = tt.cpuFeatures
   579  				return c
   580  			}
   581  
   582  			compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler, nil)
   583  
   584  			err := compiler.compileConstI32(operationPtr(wazeroir.NewOperationConstI32(10)))
   585  			require.NoError(t, err)
   586  
   587  			err = compiler.compileCtz(operationPtr(wazeroir.NewOperationCtz(wazeroir.UnsignedInt64)))
   588  			require.NoError(t, err)
   589  
   590  			compiler.compileNOP() // pad for jump target (when no ABM)
   591  
   592  			code := asm.CodeSegment{}
   593  			defer func() { require.NoError(t, code.Unmap()) }()
   594  
   595  			buf := code.NextCodeSection()
   596  			_, err = compiler.compile(buf)
   597  			require.NoError(t, err)
   598  			require.Equal(t, tt.expectedCode, hex.EncodeToString(buf.Bytes()))
   599  		})
   600  	}
   601  }
   602  
   603  // collectRegistersFromRuntimeValues returns the registers occupied by locs.
   604  func collectRegistersFromRuntimeValues(locs []*runtimeValueLocation) []asm.Register {
   605  	out := make([]asm.Register, len(locs))
   606  	for i := range locs {
   607  		out[i] = locs[i].register
   608  	}
   609  	return out
   610  }
   611  
   612  // compile implements compilerImpl.setStackPointerCeil for the amd64 architecture.
   613  func (c *amd64Compiler) setStackPointerCeil(v uint64) {
   614  	c.stackPointerCeil = v
   615  }
   616  
   617  // compile implements compilerImpl.setRuntimeValueLocationStack for the amd64 architecture.
   618  func (c *amd64Compiler) setRuntimeValueLocationStack(s *runtimeValueLocationStack) {
   619  	c.locationStack = s
   620  }
   621  
   622  func TestAmd64Compiler_label(t *testing.T) {
   623  	c := &amd64Compiler{}
   624  	c.label(wazeroir.NewLabel(wazeroir.LabelKindContinuation, 100))
   625  	require.Equal(t, 100, c.frameIDMax)
   626  	require.Equal(t, 101, len(c.labels[wazeroir.LabelKindContinuation]))
   627  
   628  	// frameIDMax is for all LabelKind, so this shouldn't change frameIDMax.
   629  	c.label(wazeroir.NewLabel(wazeroir.LabelKindHeader, 2))
   630  	require.Equal(t, 100, c.frameIDMax)
   631  	require.Equal(t, 3, len(c.labels[wazeroir.LabelKindHeader]))
   632  }
   633  
   634  func TestAmd64Compiler_Init(t *testing.T) {
   635  	c := &amd64Compiler{
   636  		locationStackForEntrypoint: newRuntimeValueLocationStack(),
   637  		assembler:                  amd64.NewAssembler(),
   638  	}
   639  	const stackCap = 12345
   640  	c.locationStackForEntrypoint.stack = make([]runtimeValueLocation, stackCap)
   641  	c.locationStackForEntrypoint.sp = 5555
   642  
   643  	c.Init(&wasm.FunctionType{}, nil, false)
   644  
   645  	// locationStack is the pointer to locationStackForEntrypoint after init.
   646  	require.Equal(t, c.locationStack, &c.locationStackForEntrypoint)
   647  	// And the underlying stack must be reused (the capacity preserved).
   648  	require.Equal(t, stackCap, cap(c.locationStack.stack))
   649  	require.Equal(t, stackCap, cap(c.locationStackForEntrypoint.stack))
   650  }
   651  
   652  func TestAmd64Compiler_resetLabels(t *testing.T) {
   653  	c := newAmd64Compiler().(*amd64Compiler)
   654  	nop := c.compileNOP()
   655  
   656  	const (
   657  		frameIDMax = 50
   658  		capacity   = 12345
   659  	)
   660  	c.frameIDMax = frameIDMax
   661  	for i := range c.labels {
   662  		ifs := make([]amd64LabelInfo, frameIDMax*2)
   663  		c.labels[i] = ifs
   664  		for j := 0; j <= frameIDMax; j++ {
   665  			ifs[j].stackInitialized = true
   666  			ifs[j].initialInstruction = nop
   667  			ifs[j].initialStack = newRuntimeValueLocationStack()
   668  			ifs[j].initialStack.sp = 5555 // should be cleared via runtimeLocationStack.Reset().
   669  			ifs[j].initialStack.stack = make([]runtimeValueLocation, 0, capacity)
   670  		}
   671  	}
   672  	c.resetLabels()
   673  	for i := range c.labels {
   674  		for j := 0; j < len(c.labels[i]); j++ {
   675  			l := &c.labels[i][j]
   676  			require.False(t, l.stackInitialized)
   677  			require.Nil(t, l.initialInstruction)
   678  			require.Equal(t, 0, len(l.initialStack.stack))
   679  			if j > frameIDMax {
   680  				require.Equal(t, 0, cap(l.initialStack.stack))
   681  			} else {
   682  				require.Equal(t, capacity, cap(l.initialStack.stack))
   683  			}
   684  			require.Equal(t, uint64(0), l.initialStack.sp)
   685  		}
   686  	}
   687  }
   688  
   689  func TestAmd64Compiler_getSavedTemporaryLocationStack(t *testing.T) {
   690  	t.Run("len(brTableTmp)<len(current)", func(t *testing.T) {
   691  		st := newRuntimeValueLocationStack()
   692  		c := &amd64Compiler{locationStack: &st}
   693  
   694  		c.locationStack.sp = 3
   695  		c.locationStack.stack = []runtimeValueLocation{{stackPointer: 150}, {stackPointer: 200}, {stackPointer: 300}}
   696  
   697  		actual := c.getSavedTemporaryLocationStack()
   698  		require.Equal(t, uint64(3), actual.sp)
   699  		require.Equal(t, 3, len(actual.stack))
   700  		require.Equal(t, c.locationStack.stack[:3], actual.stack)
   701  	})
   702  	t.Run("len(brTableTmp)==len(current)", func(t *testing.T) {
   703  		st := newRuntimeValueLocationStack()
   704  		c := &amd64Compiler{locationStack: &st, brTableTmp: make([]runtimeValueLocation, 3)}
   705  		initSlicePtr := &c.brTableTmp
   706  
   707  		c.locationStack.sp = 3
   708  		c.locationStack.stack = []runtimeValueLocation{{stackPointer: 150}, {stackPointer: 200}, {stackPointer: 300}}
   709  
   710  		actual := c.getSavedTemporaryLocationStack()
   711  		require.Equal(t, uint64(3), actual.sp)
   712  		require.Equal(t, 3, len(actual.stack))
   713  		require.Equal(t, c.locationStack.stack[:3], actual.stack)
   714  		// The underlying temporary slice shouldn't be changed.
   715  		require.Equal(t, initSlicePtr, &c.brTableTmp)
   716  	})
   717  
   718  	t.Run("len(brTableTmp)>len(current)", func(t *testing.T) {
   719  		const temporarySliceSize = 100
   720  		st := newRuntimeValueLocationStack()
   721  		c := &amd64Compiler{locationStack: &st, brTableTmp: make([]runtimeValueLocation, temporarySliceSize)}
   722  
   723  		c.locationStack.sp = 3
   724  		c.locationStack.stack = []runtimeValueLocation{
   725  			{stackPointer: 150},
   726  			{stackPointer: 200},
   727  			{stackPointer: 300},
   728  			{},
   729  			{},
   730  			{},
   731  			{},
   732  			{stackPointer: 1231455}, // Entries here shouldn't be copied as they are avobe sp.
   733  		}
   734  
   735  		actual := c.getSavedTemporaryLocationStack()
   736  		require.Equal(t, uint64(3), actual.sp)
   737  		require.Equal(t, temporarySliceSize, len(actual.stack))
   738  		require.Equal(t, c.locationStack.stack[:3], actual.stack[:3])
   739  		for i := int(actual.sp); i < len(actual.stack); i++ {
   740  			// Above the stack pointer, the values must not be copied.
   741  			require.Zero(t, actual.stack[i].stackPointer)
   742  		}
   743  	})
   744  }