Files
wazero/internal/engine/compiler/impl_amd64_test.go
Nuno Cruces 90f58bce75 compiler: fix compiledModule leak (#1608)
Signed-off-by: Nuno Cruces <ncruces@users.noreply.github.com>
Co-authored-by: Achille Roussel <achille.roussel@gmail.com>
2023-08-02 09:14:49 +08:00

745 lines
26 KiB
Go

package compiler
import (
"encoding/hex"
"testing"
"unsafe"
"github.com/tetratelabs/wazero/internal/asm"
"github.com/tetratelabs/wazero/internal/asm/amd64"
"github.com/tetratelabs/wazero/internal/platform"
"github.com/tetratelabs/wazero/internal/testing/require"
"github.com/tetratelabs/wazero/internal/wasm"
"github.com/tetratelabs/wazero/internal/wazeroir"
)
// TestAmd64Compiler_indirectCallWithTargetOnCallingConvReg is the regression test for #526.
// In short, the offset register for call_indirect might be the same as amd64CallingConventionDestinationFunctionModuleInstanceAddressRegister
// and that must not be a failure.
func TestAmd64Compiler_indirectCallWithTargetOnCallingConvReg(t *testing.T) {
code := asm.CodeSegment{}
defer func() { require.NoError(t, code.Unmap()) }()
env := newCompilerEnvironment()
table := make([]wasm.Reference, 1)
env.addTable(&wasm.TableInstance{References: table})
// Ensure that the module instance has the type information for targetOperation.TypeIndex,
// and the typeID matches the table[targetOffset]'s type ID.
operation := operationPtr(wazeroir.NewOperationCallIndirect(0, 0))
env.module().TypeIDs = []wasm.FunctionTypeID{0}
env.module().Engine = &moduleEngine{functions: []function{}}
me := env.moduleEngine()
{ // Compiling call target.
compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler, nil)
err := compiler.compilePreamble()
require.NoError(t, err)
err = compiler.compileReturnFunction()
require.NoError(t, err)
_, err = compiler.compile(code.NextCodeSection())
require.NoError(t, err)
executable := code.Bytes()
makeExecutable(executable)
f := function{
parent: &compiledFunction{parent: &compiledCode{executable: code}},
codeInitialAddress: code.Addr(),
moduleInstance: env.moduleInstance,
typeID: 0,
}
me.functions = append(me.functions, f)
table[0] = uintptr(unsafe.Pointer(&f))
}
compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler, &wazeroir.CompilationResult{
Types: []wasm.FunctionType{{}},
HasTable: true,
}).(*amd64Compiler)
err := compiler.compilePreamble()
require.NoError(t, err)
// Place the offset into the calling-convention reserved register.
offsetLoc := compiler.pushRuntimeValueLocationOnRegister(amd64CallingConventionDestinationFunctionModuleInstanceAddressRegister,
runtimeValueTypeI32)
compiler.assembler.CompileConstToRegister(amd64.MOVQ, 0, offsetLoc.register)
require.NoError(t, compiler.compileCallIndirect(operation))
err = compiler.compileReturnFunction()
require.NoError(t, err)
// Generate the code under test and run.
_, err = compiler.compile(code.NextCodeSection())
require.NoError(t, err)
env.exec(code.Bytes())
}
func TestAmd64Compiler_compile_Mul_Div_Rem(t *testing.T) {
for _, kind := range []wazeroir.OperationKind{
wazeroir.OperationKindMul,
wazeroir.OperationKindDiv,
wazeroir.OperationKindRem,
} {
kind := kind
t.Run(kind.String(), func(t *testing.T) {
t.Run("int32", func(t *testing.T) {
tests := []struct {
name string
x1Reg, x2Reg asm.Register
}{
{
name: "x1:ax,x2:random_reg",
x1Reg: amd64.RegAX,
x2Reg: amd64.RegR10,
},
{
name: "x1:ax,x2:stack",
x1Reg: amd64.RegAX,
x2Reg: asm.NilRegister,
},
{
name: "x1:random_reg,x2:ax",
x1Reg: amd64.RegR10,
x2Reg: amd64.RegAX,
},
{
name: "x1:stack,x2:ax",
x1Reg: asm.NilRegister,
x2Reg: amd64.RegAX,
},
{
name: "x1:random_reg,x2:random_reg",
x1Reg: amd64.RegR10,
x2Reg: amd64.RegR9,
},
{
name: "x1:stack,x2:random_reg",
x1Reg: asm.NilRegister,
x2Reg: amd64.RegR9,
},
{
name: "x1:random_reg,x2:stack",
x1Reg: amd64.RegR9,
x2Reg: asm.NilRegister,
},
{
name: "x1:stack,x2:stack",
x1Reg: asm.NilRegister,
x2Reg: asm.NilRegister,
},
}
for _, tt := range tests {
tc := tt
t.Run(tc.name, func(t *testing.T) {
env := newCompilerEnvironment()
const x1Value uint32 = 1 << 11
const x2Value uint32 = 51
const dxValue uint64 = 111111
compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newAmd64Compiler, nil).(*amd64Compiler)
// To make the assertion below stable, we preallocate the underlying stack,
// so that the pointer to the entry will be stale.
compiler.runtimeValueLocationStack().stack = make([]runtimeValueLocation, 100)
err := compiler.compilePreamble()
require.NoError(t, err)
// Pretend there was an existing value on the DX register. We expect compileMul to save this to the stack.
// Here, we put it just before two operands as ["any value used by DX", x1, x2]
// but in reality, it can exist in any position of stack.
compiler.assembler.CompileConstToRegister(amd64.MOVQ, int64(dxValue), amd64.RegDX)
prevOnDX := compiler.pushRuntimeValueLocationOnRegister(amd64.RegDX, runtimeValueTypeI32)
// Setup values.
if tc.x1Reg != asm.NilRegister {
compiler.assembler.CompileConstToRegister(amd64.MOVQ, int64(x1Value), tc.x1Reg)
compiler.pushRuntimeValueLocationOnRegister(tc.x1Reg, runtimeValueTypeI32)
} else {
loc := compiler.runtimeValueLocationStack().pushRuntimeValueLocationOnStack()
loc.valueType = runtimeValueTypeI32
env.stack()[loc.stackPointer] = uint64(x1Value)
}
if tc.x2Reg != asm.NilRegister {
compiler.assembler.CompileConstToRegister(amd64.MOVQ, int64(x2Value), tc.x2Reg)
compiler.pushRuntimeValueLocationOnRegister(tc.x2Reg, runtimeValueTypeI32)
} else {
loc := compiler.runtimeValueLocationStack().pushRuntimeValueLocationOnStack()
loc.valueType = runtimeValueTypeI32
env.stack()[loc.stackPointer] = uint64(x2Value)
}
switch kind {
case wazeroir.OperationKindDiv:
err = compiler.compileDiv(operationPtr(wazeroir.NewOperationDiv(wazeroir.SignedTypeUint32)))
case wazeroir.OperationKindMul:
err = compiler.compileMul(operationPtr(wazeroir.NewOperationMul(wazeroir.UnsignedTypeI32)))
case wazeroir.OperationKindRem:
err = compiler.compileRem(operationPtr(wazeroir.NewOperationRem(wazeroir.SignedUint32)))
}
require.NoError(t, err)
require.Equal(t, registerTypeGeneralPurpose, compiler.runtimeValueLocationStack().peek().getRegisterType())
requireRuntimeLocationStackPointerEqual(t, uint64(2), compiler)
require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters.list()))
// At this point, the previous value on the DX register is saved to the stack.
require.True(t, prevOnDX.onStack())
// We add the value previously on the DX with the multiplication result
// in order to ensure that not saving existing DX value would cause
// the failure in a subsequent instruction.
err = compiler.compileAdd(operationPtr(wazeroir.NewOperationAdd(wazeroir.UnsignedTypeI32)))
require.NoError(t, err)
require.NoError(t, compiler.compileReturnFunction())
code := asm.CodeSegment{}
defer func() { require.NoError(t, code.Unmap()) }()
// Generate the code under test.
_, err = compiler.compile(code.NextCodeSection())
require.NoError(t, err)
// Run code.
env.exec(code.Bytes())
// Verify the stack is in the form of ["any value previously used by DX" + the result of operation]
require.Equal(t, uint64(1), env.stackPointer())
switch kind {
case wazeroir.OperationKindDiv:
require.Equal(t, x1Value/x2Value+uint32(dxValue), env.stackTopAsUint32())
case wazeroir.OperationKindMul:
require.Equal(t, x1Value*x2Value+uint32(dxValue), env.stackTopAsUint32())
case wazeroir.OperationKindRem:
require.Equal(t, x1Value%x2Value+uint32(dxValue), env.stackTopAsUint32())
}
})
}
})
t.Run("int64", func(t *testing.T) {
tests := []struct {
name string
x1Reg, x2Reg asm.Register
}{
{
name: "x1:ax,x2:random_reg",
x1Reg: amd64.RegAX,
x2Reg: amd64.RegR10,
},
{
name: "x1:ax,x2:stack",
x1Reg: amd64.RegAX,
x2Reg: asm.NilRegister,
},
{
name: "x1:random_reg,x2:ax",
x1Reg: amd64.RegR10,
x2Reg: amd64.RegAX,
},
{
name: "x1:stack,x2:ax",
x1Reg: asm.NilRegister,
x2Reg: amd64.RegAX,
},
{
name: "x1:random_reg,x2:random_reg",
x1Reg: amd64.RegR10,
x2Reg: amd64.RegR9,
},
{
name: "x1:stack,x2:random_reg",
x1Reg: asm.NilRegister,
x2Reg: amd64.RegR9,
},
{
name: "x1:random_reg,x2:stack",
x1Reg: amd64.RegR9,
x2Reg: asm.NilRegister,
},
{
name: "x1:stack,x2:stack",
x1Reg: asm.NilRegister,
x2Reg: asm.NilRegister,
},
}
for _, tt := range tests {
tc := tt
t.Run(tc.name, func(t *testing.T) {
const x1Value uint64 = 1 << 35
const x2Value uint64 = 51
const dxValue uint64 = 111111
env := newCompilerEnvironment()
compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newAmd64Compiler, nil).(*amd64Compiler)
// To make the assertion below stable, we preallocate the underlying stack,
// so that the pointer to the entry will be stale.
compiler.runtimeValueLocationStack().stack = make([]runtimeValueLocation, 100)
err := compiler.compilePreamble()
require.NoError(t, err)
// Pretend there was an existing value on the DX register. We expect compileMul to save this to the stack.
// Here, we put it just before two operands as ["any value used by DX", x1, x2]
// but in reality, it can exist in any position of stack.
compiler.assembler.CompileConstToRegister(amd64.MOVQ, int64(dxValue), amd64.RegDX)
prevOnDX := compiler.pushRuntimeValueLocationOnRegister(amd64.RegDX, runtimeValueTypeI64)
// Setup values.
if tc.x1Reg != asm.NilRegister {
compiler.assembler.CompileConstToRegister(amd64.MOVQ, int64(x1Value), tc.x1Reg)
compiler.pushRuntimeValueLocationOnRegister(tc.x1Reg, runtimeValueTypeI64)
} else {
loc := compiler.runtimeValueLocationStack().pushRuntimeValueLocationOnStack()
loc.valueType = runtimeValueTypeI64
env.stack()[loc.stackPointer] = uint64(x1Value)
}
if tc.x2Reg != asm.NilRegister {
compiler.assembler.CompileConstToRegister(amd64.MOVQ, int64(x2Value), tc.x2Reg)
compiler.pushRuntimeValueLocationOnRegister(tc.x2Reg, runtimeValueTypeI64)
} else {
loc := compiler.runtimeValueLocationStack().pushRuntimeValueLocationOnStack()
loc.valueType = runtimeValueTypeI64
env.stack()[loc.stackPointer] = uint64(x2Value)
}
switch kind {
case wazeroir.OperationKindDiv:
err = compiler.compileDiv(operationPtr(wazeroir.NewOperationDiv(wazeroir.SignedTypeInt64)))
case wazeroir.OperationKindMul:
err = compiler.compileMul(operationPtr(wazeroir.NewOperationMul(wazeroir.UnsignedTypeI64)))
case wazeroir.OperationKindRem:
err = compiler.compileRem(operationPtr(wazeroir.NewOperationRem(wazeroir.SignedUint64)))
}
require.NoError(t, err)
require.Equal(t, registerTypeGeneralPurpose, compiler.runtimeValueLocationStack().peek().getRegisterType())
requireRuntimeLocationStackPointerEqual(t, uint64(2), compiler)
require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters.list()))
// At this point, the previous value on the DX register is saved to the stack.
require.True(t, prevOnDX.onStack())
// We add the value previously on the DX with the multiplication result
// in order to ensure that not saving existing DX value would cause
// the failure in a subsequent instruction.
err = compiler.compileAdd(operationPtr(wazeroir.NewOperationAdd(wazeroir.UnsignedTypeI64)))
require.NoError(t, err)
require.NoError(t, compiler.compileReturnFunction())
code := asm.CodeSegment{}
defer func() { require.NoError(t, code.Unmap()) }()
// Generate the code under test.
_, err = compiler.compile(code.NextCodeSection())
require.NoError(t, err)
// Run code.
env.exec(code.Bytes())
// Verify the stack is in the form of ["any value previously used by DX" + the result of operation]
switch kind {
case wazeroir.OperationKindDiv:
require.Equal(t, uint64(1), env.stackPointer())
require.Equal(t, uint64(x1Value/x2Value)+dxValue, env.stackTopAsUint64())
case wazeroir.OperationKindMul:
require.Equal(t, uint64(1), env.stackPointer())
require.Equal(t, uint64(x1Value*x2Value)+dxValue, env.stackTopAsUint64())
case wazeroir.OperationKindRem:
require.Equal(t, uint64(1), env.stackPointer())
require.Equal(t, x1Value%x2Value+dxValue, env.stackTopAsUint64())
}
})
}
})
})
}
}
func TestAmd64Compiler_readInstructionAddress(t *testing.T) {
t.Run("invalid", func(t *testing.T) {
env := newCompilerEnvironment()
compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newAmd64Compiler, nil).(*amd64Compiler)
err := compiler.compilePreamble()
require.NoError(t, err)
// Set the acquisition target instruction to the one after JMP.
compiler.assembler.CompileReadInstructionAddress(amd64.RegAX, amd64.JMP)
code := asm.CodeSegment{}
defer func() { require.NoError(t, code.Unmap()) }()
// If generate the code without JMP after readInstructionAddress,
// the call back added must return error.
_, err = compiler.compile(code.NextCodeSection())
require.Error(t, err)
})
t.Run("ok", func(t *testing.T) {
env := newCompilerEnvironment()
compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newAmd64Compiler, nil).(*amd64Compiler)
err := compiler.compilePreamble()
require.NoError(t, err)
const destinationRegister = amd64.RegAX
// Set the acquisition target instruction to the one after RET,
// and read the absolute address into destinationRegister.
compiler.assembler.CompileReadInstructionAddress(destinationRegister, amd64.RET)
// Jump to the instruction after RET below via the absolute
// address stored in destinationRegister.
compiler.assembler.CompileJumpToRegister(amd64.JMP, destinationRegister)
compiler.assembler.CompileStandAlone(amd64.RET)
// This could be the read instruction target as this is the
// right after RET. Therefore, the jmp instruction above
// must target here.
const expectedReturnValue uint32 = 10000
err = compiler.compileConstI32(operationPtr(wazeroir.NewOperationConstI32(expectedReturnValue)))
require.NoError(t, err)
err = compiler.compileReturnFunction()
require.NoError(t, err)
code := asm.CodeSegment{}
defer func() { require.NoError(t, code.Unmap()) }()
// Generate the code under test.
_, err = compiler.compile(code.NextCodeSection())
require.NoError(t, err)
// Run code.
env.exec(code.Bytes())
require.Equal(t, nativeCallStatusCodeReturned, env.compilerStatus())
require.Equal(t, uint64(1), env.stackPointer())
require.Equal(t, expectedReturnValue, env.stackTopAsUint32())
})
}
func TestAmd64Compiler_preventCrossedTargetdRegisters(t *testing.T) {
env := newCompilerEnvironment()
compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newAmd64Compiler, nil).(*amd64Compiler)
tests := []struct {
initial []*runtimeValueLocation
desired, expected []asm.Register
}{
{
initial: []*runtimeValueLocation{{register: amd64.RegAX}, {register: amd64.RegCX}, {register: amd64.RegDX}},
desired: []asm.Register{amd64.RegDX, amd64.RegCX, amd64.RegAX},
expected: []asm.Register{amd64.RegDX, amd64.RegCX, amd64.RegAX},
},
{
initial: []*runtimeValueLocation{{register: amd64.RegAX}, {register: amd64.RegCX}, {register: amd64.RegDX}},
desired: []asm.Register{amd64.RegDX, amd64.RegAX, amd64.RegCX},
expected: []asm.Register{amd64.RegDX, amd64.RegAX, amd64.RegCX},
},
{
initial: []*runtimeValueLocation{{register: amd64.RegR8}, {register: amd64.RegR9}, {register: amd64.RegR10}},
desired: []asm.Register{amd64.RegR8, amd64.RegR9, amd64.RegR10},
expected: []asm.Register{amd64.RegR8, amd64.RegR9, amd64.RegR10},
},
{
initial: []*runtimeValueLocation{{register: amd64.RegBX}, {register: amd64.RegDX}, {register: amd64.RegCX}},
desired: []asm.Register{amd64.RegR8, amd64.RegR9, amd64.RegR10},
expected: []asm.Register{amd64.RegBX, amd64.RegDX, amd64.RegCX},
},
{
initial: []*runtimeValueLocation{{register: amd64.RegR8}, {register: amd64.RegR9}, {register: amd64.RegR10}},
desired: []asm.Register{amd64.RegAX, amd64.RegCX, amd64.RegR9},
expected: []asm.Register{amd64.RegR8, amd64.RegR10, amd64.RegR9},
},
}
for _, tt := range tests {
initialRegisters := collectRegistersFromRuntimeValues(tt.initial)
restoreCrossing := compiler.compilePreventCrossedTargetRegisters(tt.initial, tt.desired)
// Required expected state after prevented crossing.
require.Equal(t, tt.expected, collectRegistersFromRuntimeValues(tt.initial))
restoreCrossing()
// Require initial state after restoring.
require.Equal(t, initialRegisters, collectRegistersFromRuntimeValues(tt.initial))
}
}
// mockCpuFlags implements platform.CpuFeatureFlags
type mockCpuFlags struct {
flags uint64
extraFlags uint64
}
// Has implements the method of the same name in platform.CpuFeatureFlags
func (f *mockCpuFlags) Has(flag uint64) bool {
return (f.flags & flag) != 0
}
// HasExtra implements the method of the same name in platform.CpuFeatureFlags
func (f *mockCpuFlags) HasExtra(flag uint64) bool {
return (f.extraFlags & flag) != 0
}
// Relates to #1111 (Clz): older AMD64 CPUs do not support the LZCNT instruction
// CPUID should be used instead. We simulate presence/absence of the feature
// by overriding the field in the corresponding struct.
func TestAmd64Compiler_ensureClz_ABM(t *testing.T) {
tests := []struct {
name string
cpuFeatures platform.CpuFeatureFlags
expectedCode string
}{
{
name: "with ABM",
expectedCode: "b80a000000f3480fbdc0",
cpuFeatures: &mockCpuFlags{
flags: 0,
extraFlags: platform.CpuExtraFeatureABM,
},
},
{
name: "without ABM",
expectedCode: "b80a0000004885c07507b840000000eb08480fbdc04883f03f",
cpuFeatures: &mockCpuFlags{
flags: 0,
extraFlags: 0, // no flags, thus no ABM, i.e. no LZCNT
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
env := newCompilerEnvironment()
newCompiler := func() compiler {
c := newCompiler().(*amd64Compiler)
// override auto-detected CPU features with the test case
c.cpuFeatures = tt.cpuFeatures
return c
}
compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler, nil)
err := compiler.compileConstI32(operationPtr(wazeroir.NewOperationConstI32(10)))
require.NoError(t, err)
err = compiler.compileClz(operationPtr(wazeroir.NewOperationClz(wazeroir.UnsignedInt64)))
require.NoError(t, err)
compiler.compileNOP() // pad for jump target (when no ABM)
code := asm.CodeSegment{}
defer func() { require.NoError(t, code.Unmap()) }()
buf := code.NextCodeSection()
_, err = compiler.compile(buf)
require.NoError(t, err)
require.Equal(t, tt.expectedCode, hex.EncodeToString(buf.Bytes()))
})
}
}
// Relates to #1111 (Ctz): older AMD64 CPUs do not support the LZCNT instruction
// CPUID should be used instead. We simulate presence/absence of the feature
// by overriding the field in the corresponding struct.
func TestAmd64Compiler_ensureCtz_ABM(t *testing.T) {
tests := []struct {
name string
cpuFeatures platform.CpuFeatureFlags
expectedCode string
}{
{
name: "with ABM",
expectedCode: "b80a000000f3480fbcc0",
cpuFeatures: &mockCpuFlags{
flags: 0,
extraFlags: platform.CpuExtraFeatureABM,
},
},
{
name: "without ABM",
expectedCode: "b80a0000004885c07507b840000000eb05f3480fbcc0",
cpuFeatures: &mockCpuFlags{
flags: 0,
extraFlags: 0, // no flags, thus no ABM, i.e. no LZCNT
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
env := newCompilerEnvironment()
newCompiler := func() compiler {
c := newCompiler().(*amd64Compiler)
// override auto-detected CPU features with the test case
c.cpuFeatures = tt.cpuFeatures
return c
}
compiler := env.requireNewCompiler(t, &wasm.FunctionType{}, newCompiler, nil)
err := compiler.compileConstI32(operationPtr(wazeroir.NewOperationConstI32(10)))
require.NoError(t, err)
err = compiler.compileCtz(operationPtr(wazeroir.NewOperationCtz(wazeroir.UnsignedInt64)))
require.NoError(t, err)
compiler.compileNOP() // pad for jump target (when no ABM)
code := asm.CodeSegment{}
defer func() { require.NoError(t, code.Unmap()) }()
buf := code.NextCodeSection()
_, err = compiler.compile(buf)
require.NoError(t, err)
require.Equal(t, tt.expectedCode, hex.EncodeToString(buf.Bytes()))
})
}
}
// collectRegistersFromRuntimeValues returns the registers occupied by locs.
func collectRegistersFromRuntimeValues(locs []*runtimeValueLocation) []asm.Register {
out := make([]asm.Register, len(locs))
for i := range locs {
out[i] = locs[i].register
}
return out
}
// compile implements compilerImpl.setStackPointerCeil for the amd64 architecture.
func (c *amd64Compiler) setStackPointerCeil(v uint64) {
c.stackPointerCeil = v
}
// compile implements compilerImpl.setRuntimeValueLocationStack for the amd64 architecture.
func (c *amd64Compiler) setRuntimeValueLocationStack(s *runtimeValueLocationStack) {
c.locationStack = s
}
func TestAmd64Compiler_label(t *testing.T) {
c := &amd64Compiler{}
c.label(wazeroir.NewLabel(wazeroir.LabelKindContinuation, 100))
require.Equal(t, 100, c.frameIDMax)
require.Equal(t, 101, len(c.labels[wazeroir.LabelKindContinuation]))
// frameIDMax is for all LabelKind, so this shouldn't change frameIDMax.
c.label(wazeroir.NewLabel(wazeroir.LabelKindHeader, 2))
require.Equal(t, 100, c.frameIDMax)
require.Equal(t, 3, len(c.labels[wazeroir.LabelKindHeader]))
}
func TestAmd64Compiler_Init(t *testing.T) {
c := &amd64Compiler{
locationStackForEntrypoint: newRuntimeValueLocationStack(),
assembler: amd64.NewAssembler(),
}
const stackCap = 12345
c.locationStackForEntrypoint.stack = make([]runtimeValueLocation, stackCap)
c.locationStackForEntrypoint.sp = 5555
c.Init(&wasm.FunctionType{}, nil, false)
// locationStack is the pointer to locationStackForEntrypoint after init.
require.Equal(t, c.locationStack, &c.locationStackForEntrypoint)
// And the underlying stack must be reused (the capacity preserved).
require.Equal(t, stackCap, cap(c.locationStack.stack))
require.Equal(t, stackCap, cap(c.locationStackForEntrypoint.stack))
}
func TestAmd64Compiler_resetLabels(t *testing.T) {
c := newAmd64Compiler().(*amd64Compiler)
nop := c.compileNOP()
const (
frameIDMax = 50
capacity = 12345
)
c.frameIDMax = frameIDMax
for i := range c.labels {
ifs := make([]amd64LabelInfo, frameIDMax*2)
c.labels[i] = ifs
for j := 0; j <= frameIDMax; j++ {
ifs[j].stackInitialized = true
ifs[j].initialInstruction = nop
ifs[j].initialStack = newRuntimeValueLocationStack()
ifs[j].initialStack.sp = 5555 // should be cleared via runtimeLocationStack.Reset().
ifs[j].initialStack.stack = make([]runtimeValueLocation, 0, capacity)
}
}
c.resetLabels()
for i := range c.labels {
for j := 0; j < len(c.labels[i]); j++ {
l := &c.labels[i][j]
require.False(t, l.stackInitialized)
require.Nil(t, l.initialInstruction)
require.Equal(t, 0, len(l.initialStack.stack))
if j > frameIDMax {
require.Equal(t, 0, cap(l.initialStack.stack))
} else {
require.Equal(t, capacity, cap(l.initialStack.stack))
}
require.Equal(t, uint64(0), l.initialStack.sp)
}
}
}
func TestAmd64Compiler_getSavedTemporaryLocationStack(t *testing.T) {
t.Run("len(brTableTmp)<len(current)", func(t *testing.T) {
st := newRuntimeValueLocationStack()
c := &amd64Compiler{locationStack: &st}
c.locationStack.sp = 3
c.locationStack.stack = []runtimeValueLocation{{stackPointer: 150}, {stackPointer: 200}, {stackPointer: 300}}
actual := c.getSavedTemporaryLocationStack()
require.Equal(t, uint64(3), actual.sp)
require.Equal(t, 3, len(actual.stack))
require.Equal(t, c.locationStack.stack[:3], actual.stack)
})
t.Run("len(brTableTmp)==len(current)", func(t *testing.T) {
st := newRuntimeValueLocationStack()
c := &amd64Compiler{locationStack: &st, brTableTmp: make([]runtimeValueLocation, 3)}
initSlicePtr := &c.brTableTmp
c.locationStack.sp = 3
c.locationStack.stack = []runtimeValueLocation{{stackPointer: 150}, {stackPointer: 200}, {stackPointer: 300}}
actual := c.getSavedTemporaryLocationStack()
require.Equal(t, uint64(3), actual.sp)
require.Equal(t, 3, len(actual.stack))
require.Equal(t, c.locationStack.stack[:3], actual.stack)
// The underlying temporary slice shouldn't be changed.
require.Equal(t, initSlicePtr, &c.brTableTmp)
})
t.Run("len(brTableTmp)>len(current)", func(t *testing.T) {
const temporarySliceSize = 100
st := newRuntimeValueLocationStack()
c := &amd64Compiler{locationStack: &st, brTableTmp: make([]runtimeValueLocation, temporarySliceSize)}
c.locationStack.sp = 3
c.locationStack.stack = []runtimeValueLocation{
{stackPointer: 150},
{stackPointer: 200},
{stackPointer: 300},
{},
{},
{},
{},
{stackPointer: 1231455}, // Entries here shouldn't be copied as they are avobe sp.
}
actual := c.getSavedTemporaryLocationStack()
require.Equal(t, uint64(3), actual.sp)
require.Equal(t, temporarySliceSize, len(actual.stack))
require.Equal(t, c.locationStack.stack[:3], actual.stack[:3])
for i := int(actual.sp); i < len(actual.stack); i++ {
// Above the stack pointer, the values must not be copied.
require.Zero(t, actual.stack[i].stackPointer)
}
})
}