This implements various SIMD instructions related to load, store, and lane manipulations for all engines. Notablely, now our engines pass the following specification tests: * simd_address.wast * simd_const.wast * simd_align.wast * simd_laod16_lane.wast * simd_laod32_lane.wast * simd_laod64_lane.wast * simd_laod8_lane.wast * simd_lane.wast * simd_load_extend.wast * simd_load_splat.wast * simd_load_zero.wast * simd_store.wast * simd_store16_lane.wast * simd_store32_lane.wast * simd_store64_lane.wast * simd_store8_lane.wast part of #484 Signed-off-by: Takeshi Yoneda <takeshi@tetrate.io> Co-authored-by: Adrian Cole <adrian@tetrate.io>
739 lines
26 KiB
Go
739 lines
26 KiB
Go
package compiler
|
|
|
|
import (
|
|
"fmt"
|
|
"math"
|
|
"testing"
|
|
|
|
"github.com/tetratelabs/wazero/internal/asm"
|
|
"github.com/tetratelabs/wazero/internal/testing/require"
|
|
"github.com/tetratelabs/wazero/internal/wazeroir"
|
|
)
|
|
|
|
func TestCompiler_releaseRegisterToStack(t *testing.T) {
|
|
const val = 10000
|
|
tests := []struct {
|
|
name string
|
|
stackPointer uint64
|
|
isFloat bool
|
|
}{
|
|
{name: "int", stackPointer: 10, isFloat: false},
|
|
{name: "float", stackPointer: 10, isFloat: true},
|
|
{name: "int-huge-height", stackPointer: math.MaxInt16 + 1, isFloat: false},
|
|
{name: "float-huge-height", stackPointer: math.MaxInt16 + 1, isFloat: true},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
tc := tt
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
env := newCompilerEnvironment()
|
|
|
|
// Compile code.
|
|
compiler := env.requireNewCompiler(t, newCompiler, nil)
|
|
err := compiler.compilePreamble()
|
|
require.NoError(t, err)
|
|
|
|
// Setup the location stack so that we push the const on the specified height.
|
|
s := &runtimeValueLocationStack{
|
|
sp: tc.stackPointer,
|
|
stack: make([]*runtimeValueLocation, tc.stackPointer),
|
|
usedRegisters: map[asm.Register]struct{}{},
|
|
}
|
|
// Peek must be non-nil. Otherwise, compileConst* would fail.
|
|
s.stack[s.sp-1] = &runtimeValueLocation{}
|
|
compiler.setRuntimeValueLocationStack(s)
|
|
|
|
if tc.isFloat {
|
|
err = compiler.compileConstF64(&wazeroir.OperationConstF64{Value: math.Float64frombits(val)})
|
|
} else {
|
|
err = compiler.compileConstI64(&wazeroir.OperationConstI64{Value: val})
|
|
}
|
|
require.NoError(t, err)
|
|
// Release the register allocated value to the memory stack so that we can see the value after exiting.
|
|
compiler.compileReleaseRegisterToStack(s.peek())
|
|
compiler.compileExitFromNativeCode(nativeCallStatusCodeReturned)
|
|
|
|
// Generate the code under test.
|
|
code, _, _, err := compiler.compile()
|
|
require.NoError(t, err)
|
|
|
|
// Run native code after growing the value stack.
|
|
env.callEngine().builtinFunctionGrowValueStack(tc.stackPointer)
|
|
env.exec(code)
|
|
|
|
// Compiler status must be returned and stack pointer must end up the specified one.
|
|
require.Equal(t, nativeCallStatusCodeReturned, env.compilerStatus())
|
|
require.Equal(t, tc.stackPointer+1, env.stackPointer())
|
|
|
|
if tc.isFloat {
|
|
require.Equal(t, math.Float64frombits(val), env.stackTopAsFloat64())
|
|
} else {
|
|
require.Equal(t, uint64(val), env.stackTopAsUint64())
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCompiler_compileLoadValueOnStackToRegister(t *testing.T) {
|
|
const val = 123
|
|
tests := []struct {
|
|
name string
|
|
stackPointer uint64
|
|
isFloat bool
|
|
}{
|
|
{name: "int", stackPointer: 10, isFloat: false},
|
|
{name: "float", stackPointer: 10, isFloat: true},
|
|
{name: "int-huge-height", stackPointer: math.MaxInt16 + 1, isFloat: false},
|
|
{name: "float-huge-height", stackPointer: math.MaxInt16 + 1, isFloat: true},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
tc := tt
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
env := newCompilerEnvironment()
|
|
|
|
// Compile code.
|
|
compiler := env.requireNewCompiler(t, newCompiler, nil)
|
|
err := compiler.compilePreamble()
|
|
require.NoError(t, err)
|
|
|
|
// Setup the location stack so that we push the const on the specified height.
|
|
compiler.runtimeValueLocationStack().sp = tc.stackPointer
|
|
compiler.runtimeValueLocationStack().stack = make([]*runtimeValueLocation, tc.stackPointer)
|
|
|
|
require.Zero(t, len(compiler.runtimeValueLocationStack().usedRegisters))
|
|
loc := compiler.runtimeValueLocationStack().pushRuntimeValueLocationOnStack()
|
|
if tc.isFloat {
|
|
loc.valueType = runtimeValueTypeF64
|
|
} else {
|
|
loc.valueType = runtimeValueTypeI64
|
|
}
|
|
// At this point the value must be recorded as being on stack.
|
|
require.True(t, loc.onStack())
|
|
|
|
// Release the stack-allocated value to register.
|
|
err = compiler.compileEnsureOnGeneralPurposeRegister(loc)
|
|
require.NoError(t, err)
|
|
require.Equal(t, 1, len(compiler.runtimeValueLocationStack().usedRegisters))
|
|
require.True(t, loc.onRegister())
|
|
|
|
// To verify the behavior, increment the value on the register.
|
|
if tc.isFloat {
|
|
err = compiler.compileConstF64(&wazeroir.OperationConstF64{Value: 1})
|
|
require.NoError(t, err)
|
|
err = compiler.compileAdd(&wazeroir.OperationAdd{Type: wazeroir.UnsignedTypeF64})
|
|
require.NoError(t, err)
|
|
} else {
|
|
err = compiler.compileConstI64(&wazeroir.OperationConstI64{Value: 1})
|
|
require.NoError(t, err)
|
|
err = compiler.compileAdd(&wazeroir.OperationAdd{Type: wazeroir.UnsignedTypeI64})
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
// Release the value to the memory stack so that we can see the value after exiting.
|
|
compiler.compileReleaseRegisterToStack(loc)
|
|
require.NoError(t, err)
|
|
compiler.compileExitFromNativeCode(nativeCallStatusCodeReturned)
|
|
require.NoError(t, err)
|
|
|
|
// Generate the code under test.
|
|
code, _, _, err := compiler.compile()
|
|
require.NoError(t, err)
|
|
|
|
// Run native code after growing the value stack, and place the original value.
|
|
env.callEngine().builtinFunctionGrowValueStack(tc.stackPointer)
|
|
env.stack()[tc.stackPointer] = val
|
|
env.exec(code)
|
|
|
|
// Compiler status must be returned and stack pointer must end up the specified one.
|
|
require.Equal(t, nativeCallStatusCodeReturned, env.compilerStatus())
|
|
require.Equal(t, tc.stackPointer+1, env.stackPointer())
|
|
|
|
if tc.isFloat {
|
|
require.Equal(t, math.Float64frombits(val)+1, env.stackTopAsFloat64())
|
|
} else {
|
|
require.Equal(t, uint64(val)+1, env.stackTopAsUint64())
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCompiler_compilePick_v128(t *testing.T) {
|
|
const pickTargetLo, pickTargetHi uint64 = 12345, 6789
|
|
|
|
op := &wazeroir.OperationPick{Depth: 2, IsTargetVector: true}
|
|
tests := []struct {
|
|
name string
|
|
isPickTargetOnRegister bool
|
|
}{
|
|
{name: "target on register", isPickTargetOnRegister: false},
|
|
{name: "target on stack", isPickTargetOnRegister: true},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
tc := tt
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
env := newCompilerEnvironment()
|
|
compiler := env.requireNewCompiler(t, newCompiler, nil)
|
|
err := compiler.compilePreamble()
|
|
require.NoError(t, err)
|
|
|
|
// Set up the stack before picking.
|
|
if tc.isPickTargetOnRegister {
|
|
err = compiler.compileV128Const(&wazeroir.OperationV128Const{
|
|
Lo: pickTargetLo, Hi: pickTargetHi,
|
|
})
|
|
require.NoError(t, err)
|
|
} else {
|
|
lo := compiler.runtimeValueLocationStack().pushRuntimeValueLocationOnStack() // lo
|
|
lo.valueType = runtimeValueTypeV128Lo
|
|
env.stack()[lo.stackPointer] = pickTargetLo
|
|
hi := compiler.runtimeValueLocationStack().pushRuntimeValueLocationOnStack() // hi
|
|
hi.valueType = runtimeValueTypeV128Hi
|
|
env.stack()[hi.stackPointer] = pickTargetHi
|
|
}
|
|
|
|
// Push the unused median value.
|
|
_ = compiler.runtimeValueLocationStack().pushRuntimeValueLocationOnStack()
|
|
require.Equal(t, uint64(3), compiler.runtimeValueLocationStack().sp)
|
|
|
|
// Now ready to compile Pick operation.
|
|
err = compiler.compilePick(op)
|
|
require.NoError(t, err)
|
|
require.Equal(t, uint64(5), compiler.runtimeValueLocationStack().sp)
|
|
|
|
hiLoc := compiler.runtimeValueLocationStack().peek()
|
|
loLoc := compiler.runtimeValueLocationStack().stack[hiLoc.stackPointer-1]
|
|
require.True(t, hiLoc.onRegister())
|
|
require.Equal(t, runtimeValueTypeV128Hi, hiLoc.valueType)
|
|
require.Equal(t, runtimeValueTypeV128Lo, loLoc.valueType)
|
|
|
|
err = compiler.compileReturnFunction()
|
|
require.NoError(t, err)
|
|
|
|
// Compile and execute the code under test.
|
|
code, _, _, err := compiler.compile()
|
|
require.NoError(t, err)
|
|
env.exec(code)
|
|
|
|
// Check the returned status and stack pointer.
|
|
require.Equal(t, nativeCallStatusCodeReturned, env.compilerStatus())
|
|
require.Equal(t, uint64(5), env.stackPointer())
|
|
|
|
// Verify the top value is the picked one and the pick target's value stays the same.
|
|
lo, hi := env.stackTopAsV128()
|
|
require.Equal(t, pickTargetLo, lo)
|
|
require.Equal(t, pickTargetHi, hi)
|
|
require.Equal(t, pickTargetLo, env.stack()[loLoc.stackPointer])
|
|
require.Equal(t, pickTargetHi, env.stack()[hiLoc.stackPointer])
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCompiler_compilePick(t *testing.T) {
|
|
const pickTargetValue uint64 = 12345
|
|
op := &wazeroir.OperationPick{Depth: 1}
|
|
tests := []struct {
|
|
name string
|
|
pickTargetSetupFunc func(compiler compilerImpl, ce *callEngine) error
|
|
isPickTargetFloat, isPickTargetOnRegister bool
|
|
}{
|
|
{
|
|
name: "float on register",
|
|
pickTargetSetupFunc: func(compiler compilerImpl, _ *callEngine) error {
|
|
return compiler.compileConstF64(&wazeroir.OperationConstF64{Value: math.Float64frombits(pickTargetValue)})
|
|
},
|
|
isPickTargetFloat: true,
|
|
isPickTargetOnRegister: true,
|
|
},
|
|
{
|
|
name: "int on register",
|
|
pickTargetSetupFunc: func(compiler compilerImpl, _ *callEngine) error {
|
|
return compiler.compileConstI64(&wazeroir.OperationConstI64{Value: pickTargetValue})
|
|
},
|
|
isPickTargetFloat: false,
|
|
isPickTargetOnRegister: true,
|
|
},
|
|
{
|
|
name: "float on stack",
|
|
pickTargetSetupFunc: func(compiler compilerImpl, ce *callEngine) error {
|
|
pickTargetLocation := compiler.runtimeValueLocationStack().pushRuntimeValueLocationOnStack()
|
|
pickTargetLocation.valueType = runtimeValueTypeF64
|
|
ce.valueStack[pickTargetLocation.stackPointer] = pickTargetValue
|
|
return nil
|
|
},
|
|
isPickTargetFloat: true,
|
|
isPickTargetOnRegister: false,
|
|
},
|
|
{
|
|
name: "int on stack",
|
|
pickTargetSetupFunc: func(compiler compilerImpl, ce *callEngine) error {
|
|
pickTargetLocation := compiler.runtimeValueLocationStack().pushRuntimeValueLocationOnStack()
|
|
pickTargetLocation.valueType = runtimeValueTypeI64
|
|
ce.valueStack[pickTargetLocation.stackPointer] = pickTargetValue
|
|
return nil
|
|
},
|
|
isPickTargetFloat: false,
|
|
isPickTargetOnRegister: false,
|
|
},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
tc := tt
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
env := newCompilerEnvironment()
|
|
compiler := env.requireNewCompiler(t, newCompiler, nil)
|
|
err := compiler.compilePreamble()
|
|
require.NoError(t, err)
|
|
|
|
// Set up the stack before picking.
|
|
err = tc.pickTargetSetupFunc(compiler, env.callEngine())
|
|
require.NoError(t, err)
|
|
pickTargetLocation := compiler.runtimeValueLocationStack().peek()
|
|
|
|
// Push the unused median value.
|
|
_ = compiler.runtimeValueLocationStack().pushRuntimeValueLocationOnStack()
|
|
require.Equal(t, uint64(2), compiler.runtimeValueLocationStack().sp)
|
|
|
|
// Now ready to compile Pick operation.
|
|
err = compiler.compilePick(op)
|
|
require.NoError(t, err)
|
|
require.Equal(t, uint64(3), compiler.runtimeValueLocationStack().sp)
|
|
|
|
pickedLocation := compiler.runtimeValueLocationStack().peek()
|
|
require.True(t, pickedLocation.onRegister())
|
|
require.Equal(t, pickTargetLocation.getRegisterType(), pickedLocation.getRegisterType())
|
|
|
|
err = compiler.compileReturnFunction()
|
|
require.NoError(t, err)
|
|
|
|
// Compile and execute the code under test.
|
|
code, _, _, err := compiler.compile()
|
|
require.NoError(t, err)
|
|
env.exec(code)
|
|
|
|
// Check the returned status and stack pointer.
|
|
require.Equal(t, nativeCallStatusCodeReturned, env.compilerStatus())
|
|
require.Equal(t, uint64(3), env.stackPointer())
|
|
|
|
// Verify the top value is the picked one and the pick target's value stays the same.
|
|
if tc.isPickTargetFloat {
|
|
require.Equal(t, math.Float64frombits(pickTargetValue), env.stackTopAsFloat64())
|
|
require.Equal(t, math.Float64frombits(pickTargetValue), math.Float64frombits(env.stack()[pickTargetLocation.stackPointer]))
|
|
} else {
|
|
require.Equal(t, pickTargetValue, env.stackTopAsUint64())
|
|
require.Equal(t, pickTargetValue, env.stack()[pickTargetLocation.stackPointer])
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCompiler_compileDrop(t *testing.T) {
|
|
t.Run("range nil", func(t *testing.T) {
|
|
env := newCompilerEnvironment()
|
|
compiler := env.requireNewCompiler(t, newCompiler, nil)
|
|
|
|
err := compiler.compilePreamble()
|
|
require.NoError(t, err)
|
|
|
|
// Put existing contents on stack.
|
|
liveNum := 10
|
|
for i := 0; i < liveNum; i++ {
|
|
compiler.runtimeValueLocationStack().pushRuntimeValueLocationOnStack()
|
|
}
|
|
require.Equal(t, uint64(liveNum), compiler.runtimeValueLocationStack().sp)
|
|
|
|
err = compiler.compileDrop(&wazeroir.OperationDrop{Depth: nil})
|
|
require.NoError(t, err)
|
|
|
|
// After the nil range drop, the stack must remain the same.
|
|
require.Equal(t, uint64(liveNum), compiler.runtimeValueLocationStack().sp)
|
|
|
|
err = compiler.compileReturnFunction()
|
|
require.NoError(t, err)
|
|
|
|
code, _, _, err := compiler.compile()
|
|
require.NoError(t, err)
|
|
|
|
env.exec(code)
|
|
require.Equal(t, nativeCallStatusCodeReturned, env.compilerStatus())
|
|
})
|
|
t.Run("start top", func(t *testing.T) {
|
|
r := &wazeroir.InclusiveRange{Start: 0, End: 2}
|
|
dropTargetNum := r.End - r.Start + 1 // +1 as the range is inclusive!
|
|
liveNum := 5
|
|
|
|
env := newCompilerEnvironment()
|
|
compiler := env.requireNewCompiler(t, newCompiler, nil)
|
|
|
|
err := compiler.compilePreamble()
|
|
require.NoError(t, err)
|
|
|
|
// Put existing contents on stack.
|
|
const expectedTopLiveValue = 100
|
|
for i := 0; i < liveNum+dropTargetNum; i++ {
|
|
if i == liveNum-1 {
|
|
err := compiler.compileConstI64(&wazeroir.OperationConstI64{Value: expectedTopLiveValue})
|
|
require.NoError(t, err)
|
|
} else {
|
|
compiler.runtimeValueLocationStack().pushRuntimeValueLocationOnStack()
|
|
}
|
|
}
|
|
require.Equal(t, uint64(liveNum+dropTargetNum), compiler.runtimeValueLocationStack().sp)
|
|
|
|
err = compiler.compileDrop(&wazeroir.OperationDrop{Depth: r})
|
|
require.NoError(t, err)
|
|
|
|
// After the drop operation, the stack contains only live contents.
|
|
require.Equal(t, uint64(liveNum), compiler.runtimeValueLocationStack().sp)
|
|
// Plus, the top value must stay on a register.
|
|
top := compiler.runtimeValueLocationStack().peek()
|
|
require.True(t, top.onRegister())
|
|
|
|
err = compiler.compileReturnFunction()
|
|
require.NoError(t, err)
|
|
|
|
code, _, _, err := compiler.compile()
|
|
require.NoError(t, err)
|
|
|
|
env.exec(code)
|
|
require.Equal(t, nativeCallStatusCodeReturned, env.compilerStatus())
|
|
require.Equal(t, uint64(5), env.stackPointer())
|
|
require.Equal(t, uint64(expectedTopLiveValue), env.stackTopAsUint64())
|
|
})
|
|
|
|
t.Run("start from middle", func(t *testing.T) {
|
|
r := &wazeroir.InclusiveRange{Start: 2, End: 3}
|
|
liveAboveDropStartNum := 3
|
|
dropTargetNum := r.End - r.Start + 1 // +1 as the range is inclusive!
|
|
liveBelowDropEndNum := 5
|
|
total := liveAboveDropStartNum + dropTargetNum + liveBelowDropEndNum
|
|
liveTotal := liveAboveDropStartNum + liveBelowDropEndNum
|
|
|
|
env := newCompilerEnvironment()
|
|
ce := env.callEngine()
|
|
compiler := env.requireNewCompiler(t, newCompiler, nil)
|
|
|
|
err := compiler.compilePreamble()
|
|
require.NoError(t, err)
|
|
|
|
// Put existing contents except the top on stack
|
|
for i := 0; i < total-1; i++ {
|
|
loc := compiler.runtimeValueLocationStack().pushRuntimeValueLocationOnStack()
|
|
ce.valueStack[loc.stackPointer] = uint64(i) // Put the initial value.
|
|
}
|
|
|
|
// Place the top value.
|
|
const expectedTopLiveValue = 100
|
|
err = compiler.compileConstI64(&wazeroir.OperationConstI64{Value: expectedTopLiveValue})
|
|
require.NoError(t, err)
|
|
|
|
require.Equal(t, uint64(total), compiler.runtimeValueLocationStack().sp)
|
|
|
|
err = compiler.compileDrop(&wazeroir.OperationDrop{Depth: r})
|
|
require.NoError(t, err)
|
|
|
|
// After the drop operation, the stack contains only live contents.
|
|
require.Equal(t, uint64(liveTotal), compiler.runtimeValueLocationStack().sp)
|
|
// Plus, the top value must stay on a register.
|
|
require.True(t, compiler.runtimeValueLocationStack().peek().onRegister())
|
|
|
|
err = compiler.compileReturnFunction()
|
|
require.NoError(t, err)
|
|
|
|
code, _, _, err := compiler.compile()
|
|
require.NoError(t, err)
|
|
|
|
env.exec(code)
|
|
require.Equal(t, nativeCallStatusCodeReturned, env.compilerStatus())
|
|
require.Equal(t, uint64(liveTotal), env.stackPointer())
|
|
|
|
stack := env.stack()[:env.stackPointer()]
|
|
for i, val := range stack {
|
|
if i <= liveBelowDropEndNum {
|
|
require.Equal(t, uint64(i), val)
|
|
} else if i == liveTotal-1 {
|
|
require.Equal(t, uint64(expectedTopLiveValue), val)
|
|
} else {
|
|
require.Equal(t, uint64(i+dropTargetNum), val)
|
|
}
|
|
}
|
|
})
|
|
}
|
|
|
|
func TestCompiler_compileSelect(t *testing.T) {
|
|
// There are mainly 8 cases we have to test:
|
|
// - [x1 = reg, x2 = reg] select x1
|
|
// - [x1 = reg, x2 = reg] select x2
|
|
// - [x1 = reg, x2 = stack] select x1
|
|
// - [x1 = reg, x2 = stack] select x2
|
|
// - [x1 = stack, x2 = reg] select x1
|
|
// - [x1 = stack, x2 = reg] select x2
|
|
// - [x1 = stack, x2 = stack] select x1
|
|
// - [x1 = stack, x2 = stack] select x2
|
|
// And for each case, we have to test with
|
|
// three conditional value location: stack, gp register, conditional register.
|
|
// So in total we have 24 cases.
|
|
tests := []struct {
|
|
x1OnRegister, x2OnRegister bool
|
|
selectX1 bool
|
|
condlValueOnStack, condValueOnGPRegister, condValueOnCondRegister bool
|
|
}{
|
|
// Conditional value on stack.
|
|
{x1OnRegister: true, x2OnRegister: true, selectX1: true, condlValueOnStack: true},
|
|
{x1OnRegister: true, x2OnRegister: true, selectX1: false, condlValueOnStack: true},
|
|
{x1OnRegister: true, x2OnRegister: false, selectX1: true, condlValueOnStack: true},
|
|
{x1OnRegister: true, x2OnRegister: false, selectX1: false, condlValueOnStack: true},
|
|
{x1OnRegister: false, x2OnRegister: true, selectX1: true, condlValueOnStack: true},
|
|
{x1OnRegister: false, x2OnRegister: true, selectX1: false, condlValueOnStack: true},
|
|
{x1OnRegister: false, x2OnRegister: false, selectX1: true, condlValueOnStack: true},
|
|
{x1OnRegister: false, x2OnRegister: false, selectX1: false, condlValueOnStack: true},
|
|
// Conditional value on register.
|
|
{x1OnRegister: true, x2OnRegister: true, selectX1: true, condValueOnGPRegister: true},
|
|
{x1OnRegister: true, x2OnRegister: true, selectX1: false, condValueOnGPRegister: true},
|
|
{x1OnRegister: true, x2OnRegister: false, selectX1: true, condValueOnGPRegister: true},
|
|
{x1OnRegister: true, x2OnRegister: false, selectX1: false, condValueOnGPRegister: true},
|
|
{x1OnRegister: false, x2OnRegister: true, selectX1: true, condValueOnGPRegister: true},
|
|
{x1OnRegister: false, x2OnRegister: true, selectX1: false, condValueOnGPRegister: true},
|
|
{x1OnRegister: false, x2OnRegister: false, selectX1: true, condValueOnGPRegister: true},
|
|
{x1OnRegister: false, x2OnRegister: false, selectX1: false, condValueOnGPRegister: true},
|
|
// Conditional value on conditional register.
|
|
{x1OnRegister: true, x2OnRegister: true, selectX1: true, condValueOnCondRegister: true},
|
|
{x1OnRegister: true, x2OnRegister: true, selectX1: false, condValueOnCondRegister: true},
|
|
{x1OnRegister: true, x2OnRegister: false, selectX1: true, condValueOnCondRegister: true},
|
|
{x1OnRegister: true, x2OnRegister: false, selectX1: false, condValueOnCondRegister: true},
|
|
{x1OnRegister: false, x2OnRegister: true, selectX1: true, condValueOnCondRegister: true},
|
|
{x1OnRegister: false, x2OnRegister: true, selectX1: false, condValueOnCondRegister: true},
|
|
{x1OnRegister: false, x2OnRegister: false, selectX1: true, condValueOnCondRegister: true},
|
|
{x1OnRegister: false, x2OnRegister: false, selectX1: false, condValueOnCondRegister: true},
|
|
}
|
|
|
|
for i, tt := range tests {
|
|
tc := tt
|
|
t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
|
|
for _, vals := range [][2]uint64{
|
|
{1, 2}, {0, 1}, {1, 0},
|
|
{math.Float64bits(-1), math.Float64bits(-1)},
|
|
{math.Float64bits(-1), math.Float64bits(1)},
|
|
{math.Float64bits(1), math.Float64bits(-1)},
|
|
} {
|
|
x1Value, x2Value := vals[0], vals[1]
|
|
t.Run(fmt.Sprintf("x1=0x%x,x2=0x%x", vals[0], vals[1]), func(t *testing.T) {
|
|
env := newCompilerEnvironment()
|
|
compiler := env.requireNewCompiler(t, newCompiler, nil)
|
|
err := compiler.compilePreamble()
|
|
require.NoError(t, err)
|
|
|
|
x1 := compiler.runtimeValueLocationStack().pushRuntimeValueLocationOnStack()
|
|
env.stack()[x1.stackPointer] = x1Value
|
|
if tc.x1OnRegister {
|
|
err = compiler.compileEnsureOnGeneralPurposeRegister(x1)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
x2 := compiler.runtimeValueLocationStack().pushRuntimeValueLocationOnStack()
|
|
env.stack()[x2.stackPointer] = x2Value
|
|
if tc.x2OnRegister {
|
|
err = compiler.compileEnsureOnGeneralPurposeRegister(x2)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
var c *runtimeValueLocation
|
|
if tc.condlValueOnStack {
|
|
c = compiler.runtimeValueLocationStack().pushRuntimeValueLocationOnStack()
|
|
if tc.selectX1 {
|
|
env.stack()[c.stackPointer] = 1
|
|
} else {
|
|
env.stack()[c.stackPointer] = 0
|
|
}
|
|
} else if tc.condValueOnGPRegister {
|
|
c = compiler.runtimeValueLocationStack().pushRuntimeValueLocationOnStack()
|
|
if tc.selectX1 {
|
|
env.stack()[c.stackPointer] = 1
|
|
} else {
|
|
env.stack()[c.stackPointer] = 0
|
|
}
|
|
err = compiler.compileEnsureOnGeneralPurposeRegister(c)
|
|
require.NoError(t, err)
|
|
} else if tc.condValueOnCondRegister {
|
|
err = compiler.compileConstI32(&wazeroir.OperationConstI32{Value: 0})
|
|
require.NoError(t, err)
|
|
err = compiler.compileConstI32(&wazeroir.OperationConstI32{Value: 0})
|
|
require.NoError(t, err)
|
|
if tc.selectX1 {
|
|
err = compiler.compileEq(&wazeroir.OperationEq{Type: wazeroir.UnsignedTypeI32})
|
|
} else {
|
|
err = compiler.compileNe(&wazeroir.OperationNe{Type: wazeroir.UnsignedTypeI32})
|
|
}
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
// Now emit code for select.
|
|
err = compiler.compileSelect()
|
|
require.NoError(t, err)
|
|
|
|
// x1 should be top of the stack.
|
|
require.Equal(t, x1, compiler.runtimeValueLocationStack().peek())
|
|
|
|
err = compiler.compileReturnFunction()
|
|
require.NoError(t, err)
|
|
|
|
// Run code.
|
|
code, _, _, err := compiler.compile()
|
|
require.NoError(t, err)
|
|
env.exec(code)
|
|
|
|
// Check the selected value.
|
|
require.Equal(t, uint64(1), env.stackPointer())
|
|
if tc.selectX1 {
|
|
require.Equal(t, env.stack()[x1.stackPointer], x1Value)
|
|
} else {
|
|
require.Equal(t, env.stack()[x1.stackPointer], x2Value)
|
|
}
|
|
})
|
|
}
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCompiler_compileSwap_v128(t *testing.T) {
|
|
const x1Lo, x1Hi uint64 = 100000, 200000
|
|
const x2Lo, x2Hi uint64 = 1, 2
|
|
|
|
tests := []struct {
|
|
x1OnRegister, x2OnRegister bool
|
|
}{
|
|
{x1OnRegister: true, x2OnRegister: true},
|
|
{x1OnRegister: true, x2OnRegister: false},
|
|
{x1OnRegister: false, x2OnRegister: true},
|
|
{x1OnRegister: false, x2OnRegister: false},
|
|
}
|
|
|
|
for _, tt := range tests {
|
|
tc := tt
|
|
t.Run(fmt.Sprintf("x1_register=%v, x2_register=%v", tc.x1OnRegister, tc.x2OnRegister), func(t *testing.T) {
|
|
env := newCompilerEnvironment()
|
|
compiler := env.requireNewCompiler(t, newCompiler, nil)
|
|
err := compiler.compilePreamble()
|
|
require.NoError(t, err)
|
|
|
|
if tc.x1OnRegister {
|
|
err = compiler.compileV128Const(&wazeroir.OperationV128Const{Lo: x1Lo, Hi: x1Hi})
|
|
require.NoError(t, err)
|
|
} else {
|
|
lo := compiler.runtimeValueLocationStack().pushRuntimeValueLocationOnStack() // lo
|
|
lo.valueType = runtimeValueTypeV128Lo
|
|
env.stack()[lo.stackPointer] = x1Lo
|
|
hi := compiler.runtimeValueLocationStack().pushRuntimeValueLocationOnStack() // hi
|
|
hi.valueType = runtimeValueTypeV128Hi
|
|
env.stack()[hi.stackPointer] = x1Hi
|
|
}
|
|
|
|
_ = compiler.runtimeValueLocationStack().pushRuntimeValueLocationOnStack() // Dummy value!
|
|
|
|
if tc.x2OnRegister {
|
|
err = compiler.compileV128Const(&wazeroir.OperationV128Const{Lo: x2Lo, Hi: x2Hi})
|
|
require.NoError(t, err)
|
|
} else {
|
|
lo := compiler.runtimeValueLocationStack().pushRuntimeValueLocationOnStack() // lo
|
|
lo.valueType = runtimeValueTypeV128Lo
|
|
env.stack()[lo.stackPointer] = x2Lo
|
|
hi := compiler.runtimeValueLocationStack().pushRuntimeValueLocationOnStack() // hi
|
|
hi.valueType = runtimeValueTypeV128Hi
|
|
env.stack()[hi.stackPointer] = x2Hi
|
|
}
|
|
|
|
// Swap x1 and x2.
|
|
err = compiler.compileSwap(&wazeroir.OperationSwap{Depth: 4, IsTargetVector: true})
|
|
require.NoError(t, err)
|
|
|
|
require.NoError(t, compiler.compileReturnFunction())
|
|
|
|
// Generate the code under test.
|
|
code, _, _, err := compiler.compile()
|
|
require.NoError(t, err)
|
|
|
|
// Run code.
|
|
env.exec(code)
|
|
|
|
require.Equal(t, nativeCallStatusCodeReturned, env.compilerStatus())
|
|
require.Equal(t, uint64(5), env.stackPointer())
|
|
|
|
st := env.stack()
|
|
require.Equal(t, x2Lo, st[0])
|
|
require.Equal(t, x2Hi, st[1])
|
|
require.Equal(t, x1Lo, st[3])
|
|
require.Equal(t, x1Hi, st[4])
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestCompiler_compileSwap(t *testing.T) {
|
|
var x1Value, x2Value int64 = 100, 200
|
|
tests := []struct {
|
|
x1OnConditionalRegister, x1OnRegister, x2OnRegister bool
|
|
}{
|
|
{x1OnRegister: true, x2OnRegister: true},
|
|
{x1OnRegister: true, x2OnRegister: false},
|
|
{x1OnRegister: false, x2OnRegister: true},
|
|
{x1OnRegister: false, x2OnRegister: false},
|
|
// x1 on conditional register
|
|
{x1OnConditionalRegister: true, x2OnRegister: false},
|
|
{x1OnConditionalRegister: true, x2OnRegister: true},
|
|
}
|
|
|
|
for i, tt := range tests {
|
|
tc := tt
|
|
t.Run(fmt.Sprintf("%d", i), func(t *testing.T) {
|
|
env := newCompilerEnvironment()
|
|
compiler := env.requireNewCompiler(t, newCompiler, nil)
|
|
err := compiler.compilePreamble()
|
|
require.NoError(t, err)
|
|
|
|
x2 := compiler.runtimeValueLocationStack().pushRuntimeValueLocationOnStack()
|
|
env.stack()[x2.stackPointer] = uint64(x2Value)
|
|
if tc.x2OnRegister {
|
|
err = compiler.compileEnsureOnGeneralPurposeRegister(x2)
|
|
require.NoError(t, err)
|
|
}
|
|
|
|
_ = compiler.runtimeValueLocationStack().pushRuntimeValueLocationOnStack() // Dummy value!
|
|
if tc.x1OnRegister && !tc.x1OnConditionalRegister {
|
|
x1 := compiler.runtimeValueLocationStack().pushRuntimeValueLocationOnStack()
|
|
env.stack()[x1.stackPointer] = uint64(x1Value)
|
|
err = compiler.compileEnsureOnGeneralPurposeRegister(x1)
|
|
require.NoError(t, err)
|
|
} else if !tc.x1OnConditionalRegister {
|
|
x1 := compiler.runtimeValueLocationStack().pushRuntimeValueLocationOnStack()
|
|
env.stack()[x1.stackPointer] = uint64(x1Value)
|
|
} else {
|
|
err = compiler.compileConstI32(&wazeroir.OperationConstI32{Value: 0})
|
|
require.NoError(t, err)
|
|
err = compiler.compileConstI32(&wazeroir.OperationConstI32{Value: 0})
|
|
require.NoError(t, err)
|
|
err = compiler.compileEq(&wazeroir.OperationEq{Type: wazeroir.UnsignedTypeI32})
|
|
require.NoError(t, err)
|
|
x1Value = 1
|
|
}
|
|
|
|
// Swap x1 and x2.
|
|
err = compiler.compileSwap(&wazeroir.OperationSwap{Depth: 2})
|
|
require.NoError(t, err)
|
|
|
|
require.NoError(t, compiler.compileReturnFunction())
|
|
|
|
// Generate the code under test.
|
|
code, _, _, err := compiler.compile()
|
|
require.NoError(t, err)
|
|
|
|
// Run code.
|
|
env.exec(code)
|
|
|
|
require.Equal(t, uint64(3), env.stackPointer())
|
|
// Check values are swapped.
|
|
require.Equal(t, uint64(x1Value), env.stack()[0])
|
|
require.Equal(t, uint64(x2Value), env.stack()[2])
|
|
})
|
|
}
|
|
}
|