270 lines
8.8 KiB
Go
270 lines
8.8 KiB
Go
package compiler
|
|
|
|
import (
|
|
"testing"
|
|
|
|
"github.com/tetratelabs/wazero/internal/asm"
|
|
"github.com/tetratelabs/wazero/internal/testing/require"
|
|
"github.com/tetratelabs/wazero/internal/wasm"
|
|
)
|
|
|
|
func Test_isIntRegister(t *testing.T) {
|
|
for _, r := range unreservedGeneralPurposeRegisters {
|
|
require.True(t, isGeneralPurposeRegister(r))
|
|
}
|
|
}
|
|
|
|
func Test_isVectorRegister(t *testing.T) {
|
|
for _, r := range unreservedVectorRegisters {
|
|
require.True(t, isVectorRegister(r))
|
|
}
|
|
}
|
|
|
|
func TestRuntimeValueLocationStack_basic(t *testing.T) {
|
|
s := newRuntimeValueLocationStack()
|
|
// Push stack value.
|
|
loc := s.pushRuntimeValueLocationOnStack()
|
|
require.Equal(t, uint64(1), s.sp)
|
|
require.Equal(t, uint64(0), loc.stackPointer)
|
|
// Push the register value.
|
|
tmpReg := unreservedGeneralPurposeRegisters[0]
|
|
loc = s.pushRuntimeValueLocationOnRegister(tmpReg, runtimeValueTypeI64)
|
|
require.Equal(t, uint64(2), s.sp)
|
|
require.Equal(t, uint64(1), loc.stackPointer)
|
|
require.Equal(t, tmpReg, loc.register)
|
|
require.Equal(t, loc.valueType, runtimeValueTypeI64)
|
|
// markRegisterUsed.
|
|
tmpReg2 := unreservedGeneralPurposeRegisters[1]
|
|
s.markRegisterUsed(tmpReg2)
|
|
require.True(t, s.usedRegisters.exist(tmpReg2))
|
|
// releaseRegister.
|
|
s.releaseRegister(loc)
|
|
require.False(t, s.usedRegisters.exist(loc.register))
|
|
require.Equal(t, asm.NilRegister, loc.register)
|
|
// Check the max stack pointer.
|
|
for i := 0; i < 1000; i++ {
|
|
s.pushRuntimeValueLocationOnStack()
|
|
}
|
|
for i := 0; i < 1000; i++ {
|
|
s.pop()
|
|
}
|
|
require.Equal(t, uint64(1002), s.stackPointerCeil)
|
|
}
|
|
|
|
func TestRuntimeValueLocationStack_takeFreeRegister(t *testing.T) {
|
|
s := newRuntimeValueLocationStack()
|
|
// For int registers.
|
|
r, ok := s.takeFreeRegister(registerTypeGeneralPurpose)
|
|
require.True(t, ok)
|
|
require.True(t, isGeneralPurposeRegister(r))
|
|
// Mark all the int registers used.
|
|
for _, r := range unreservedGeneralPurposeRegisters {
|
|
s.markRegisterUsed(r)
|
|
}
|
|
// Now we cannot take free ones for int.
|
|
_, ok = s.takeFreeRegister(registerTypeGeneralPurpose)
|
|
require.False(t, ok)
|
|
// But we still should be able to take float regs.
|
|
r, ok = s.takeFreeRegister(registerTypeVector)
|
|
require.True(t, ok)
|
|
require.True(t, isVectorRegister(r))
|
|
// Mark all the float registers used.
|
|
for _, r := range unreservedVectorRegisters {
|
|
s.markRegisterUsed(r)
|
|
}
|
|
// Now we cannot take free ones for floats.
|
|
_, ok = s.takeFreeRegister(registerTypeVector)
|
|
require.False(t, ok)
|
|
}
|
|
|
|
func TestRuntimeValueLocationStack_takeStealTargetFromUsedRegister(t *testing.T) {
|
|
s := newRuntimeValueLocationStack()
|
|
intReg := unreservedGeneralPurposeRegisters[0]
|
|
floatReg := unreservedVectorRegisters[0]
|
|
intLocation := s.push(intReg, asm.ConditionalRegisterStateUnset)
|
|
floatLocation := s.push(floatReg, asm.ConditionalRegisterStateUnset)
|
|
// Take for float.
|
|
target, ok := s.takeStealTargetFromUsedRegister(registerTypeVector)
|
|
require.True(t, ok)
|
|
require.Equal(t, floatLocation, target)
|
|
// Take for ints.
|
|
target, ok = s.takeStealTargetFromUsedRegister(registerTypeGeneralPurpose)
|
|
require.True(t, ok)
|
|
require.Equal(t, intLocation, target)
|
|
// Pop float value.
|
|
popped := s.pop()
|
|
require.Equal(t, floatLocation, popped)
|
|
// Now we cannot find the steal target.
|
|
target, ok = s.takeStealTargetFromUsedRegister(registerTypeVector)
|
|
require.False(t, ok)
|
|
require.Nil(t, target)
|
|
// Pop int value.
|
|
popped = s.pop()
|
|
require.Equal(t, intLocation, popped)
|
|
// Now we cannot find the steal target.
|
|
target, ok = s.takeStealTargetFromUsedRegister(registerTypeGeneralPurpose)
|
|
require.False(t, ok)
|
|
require.Nil(t, target)
|
|
}
|
|
|
|
func TestRuntimeValueLocationStack_setupInitialStack(t *testing.T) {
|
|
const f32 = wasm.ValueTypeF32
|
|
tests := []struct {
|
|
name string
|
|
sig *wasm.FunctionType
|
|
expectedSP uint64
|
|
}{
|
|
{
|
|
name: "no params / no results",
|
|
sig: &wasm.FunctionType{},
|
|
expectedSP: callFrameDataSizeInUint64,
|
|
},
|
|
{
|
|
name: "no results",
|
|
sig: &wasm.FunctionType{
|
|
Params: []wasm.ValueType{f32, f32},
|
|
ParamNumInUint64: 2,
|
|
},
|
|
expectedSP: callFrameDataSizeInUint64 + 2,
|
|
},
|
|
{
|
|
name: "no params",
|
|
sig: &wasm.FunctionType{
|
|
Results: []wasm.ValueType{f32, f32},
|
|
ResultNumInUint64: 2,
|
|
},
|
|
expectedSP: callFrameDataSizeInUint64 + 2,
|
|
},
|
|
{
|
|
name: "params == results",
|
|
sig: &wasm.FunctionType{
|
|
Params: []wasm.ValueType{f32, f32},
|
|
ParamNumInUint64: 2,
|
|
Results: []wasm.ValueType{f32, f32},
|
|
ResultNumInUint64: 2,
|
|
},
|
|
expectedSP: callFrameDataSizeInUint64 + 2,
|
|
},
|
|
{
|
|
name: "params > results",
|
|
sig: &wasm.FunctionType{
|
|
Params: []wasm.ValueType{f32, f32, f32},
|
|
ParamNumInUint64: 3,
|
|
Results: []wasm.ValueType{f32, f32},
|
|
ResultNumInUint64: 2,
|
|
},
|
|
expectedSP: callFrameDataSizeInUint64 + 3,
|
|
},
|
|
{
|
|
name: "params < results",
|
|
sig: &wasm.FunctionType{
|
|
Params: []wasm.ValueType{f32},
|
|
ParamNumInUint64: 1,
|
|
Results: []wasm.ValueType{f32, f32, f32},
|
|
ResultNumInUint64: 3,
|
|
},
|
|
expectedSP: callFrameDataSizeInUint64 + 3,
|
|
},
|
|
}
|
|
|
|
for _, tc := range tests {
|
|
tc := tc
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
s := newRuntimeValueLocationStack()
|
|
s.init(tc.sig)
|
|
require.Equal(t, tc.expectedSP, s.sp)
|
|
|
|
callFrameLocations := s.stack[s.sp-callFrameDataSizeInUint64 : s.sp]
|
|
for _, loc := range callFrameLocations {
|
|
require.Equal(t, runtimeValueTypeI64, loc.valueType)
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestRuntimeValueLocation_pushCallFrame(t *testing.T) {
|
|
for _, sig := range []*wasm.FunctionType{
|
|
{ParamNumInUint64: 0, ResultNumInUint64: 1},
|
|
{ParamNumInUint64: 1, ResultNumInUint64: 0},
|
|
{ParamNumInUint64: 1, ResultNumInUint64: 1},
|
|
{ParamNumInUint64: 0, ResultNumInUint64: 2},
|
|
{ParamNumInUint64: 2, ResultNumInUint64: 0},
|
|
{ParamNumInUint64: 2, ResultNumInUint64: 3},
|
|
} {
|
|
sig := sig
|
|
t.Run(sig.String(), func(t *testing.T) {
|
|
s := newRuntimeValueLocationStack()
|
|
// pushCallFrame assumes that the parameters are already pushed.
|
|
for i := 0; i < sig.ParamNumInUint64; i++ {
|
|
_ = s.pushRuntimeValueLocationOnStack()
|
|
}
|
|
|
|
retAddr, stackBasePointer, fn := s.pushCallFrame(sig)
|
|
|
|
expOffset := uint64(callFrameOffset(sig))
|
|
require.Equal(t, expOffset, retAddr.stackPointer)
|
|
require.Equal(t, expOffset+1, stackBasePointer.stackPointer)
|
|
require.Equal(t, expOffset+2, fn.stackPointer)
|
|
})
|
|
}
|
|
}
|
|
|
|
func Test_usedRegistersMask(t *testing.T) {
|
|
for _, r := range append(unreservedVectorRegisters, unreservedGeneralPurposeRegisters...) {
|
|
mask := usedRegistersMask(0)
|
|
mask.add(r)
|
|
require.False(t, mask == 0)
|
|
require.True(t, mask.exist(r))
|
|
mask.remove(r)
|
|
require.True(t, mask == 0)
|
|
require.False(t, mask.exist(r))
|
|
}
|
|
}
|
|
|
|
func TestRuntimeValueLocation_cloneFrom(t *testing.T) {
|
|
t.Run("sp<cap", func(t *testing.T) {
|
|
v := runtimeValueLocationStack{sp: 7, stack: make([]runtimeValueLocation, 5, 10)}
|
|
orig := v.stack
|
|
v.cloneFrom(runtimeValueLocationStack{sp: 3, usedRegisters: 0xffff, stack: []runtimeValueLocation{
|
|
{register: 3}, {register: 2}, {register: 1},
|
|
}})
|
|
require.Equal(t, uint64(3), v.sp)
|
|
require.Equal(t, usedRegistersMask(0xffff), v.usedRegisters)
|
|
// Underlying stack shouldn't have changed since sp=3 < cap(v.stack).
|
|
require.Equal(t, &orig[0], &v.stack[0])
|
|
require.Equal(t, v.stack[0].register, asm.Register(3))
|
|
require.Equal(t, v.stack[1].register, asm.Register(2))
|
|
require.Equal(t, v.stack[2].register, asm.Register(1))
|
|
})
|
|
t.Run("sp=cap", func(t *testing.T) {
|
|
v := runtimeValueLocationStack{stack: make([]runtimeValueLocation, 0, 3)}
|
|
orig := v.stack[:cap(v.stack)]
|
|
v.cloneFrom(runtimeValueLocationStack{sp: 3, usedRegisters: 0xffff, stack: []runtimeValueLocation{
|
|
{register: 3}, {register: 2}, {register: 1},
|
|
}})
|
|
require.Equal(t, uint64(3), v.sp)
|
|
require.Equal(t, usedRegistersMask(0xffff), v.usedRegisters)
|
|
// Underlying stack shouldn't have changed since sp=3==cap(v.stack).
|
|
require.Equal(t, &orig[0], &v.stack[0])
|
|
require.Equal(t, v.stack[0].register, asm.Register(3))
|
|
require.Equal(t, v.stack[1].register, asm.Register(2))
|
|
require.Equal(t, v.stack[2].register, asm.Register(1))
|
|
})
|
|
t.Run("sp>cap", func(t *testing.T) {
|
|
v := runtimeValueLocationStack{stack: make([]runtimeValueLocation, 0, 3)}
|
|
orig := v.stack[:cap(v.stack)]
|
|
v.cloneFrom(runtimeValueLocationStack{sp: 5, usedRegisters: 0xffff, stack: []runtimeValueLocation{
|
|
{register: 5}, {register: 4}, {register: 3}, {register: 2}, {register: 1},
|
|
}})
|
|
require.Equal(t, uint64(5), v.sp)
|
|
require.Equal(t, usedRegistersMask(0xffff), v.usedRegisters)
|
|
// Underlying stack should have changed since sp=5>cap(v.stack).
|
|
require.NotEqual(t, &orig[0], &v.stack[0])
|
|
require.Equal(t, v.stack[0].register, asm.Register(5))
|
|
require.Equal(t, v.stack[1].register, asm.Register(4))
|
|
require.Equal(t, v.stack[2].register, asm.Register(3))
|
|
require.Equal(t, v.stack[3].register, asm.Register(2))
|
|
require.Equal(t, v.stack[4].register, asm.Register(1))
|
|
})
|
|
}
|