949 lines
25 KiB
Go
949 lines
25 KiB
Go
package arm64
|
|
|
|
import (
|
|
"encoding/hex"
|
|
"fmt"
|
|
"strings"
|
|
"testing"
|
|
|
|
"github.com/tetratelabs/wazero/internal/engine/wazevo/backend"
|
|
"github.com/tetratelabs/wazero/internal/engine/wazevo/backend/regalloc"
|
|
"github.com/tetratelabs/wazero/internal/engine/wazevo/ssa"
|
|
"github.com/tetratelabs/wazero/internal/engine/wazevo/wazevoapi"
|
|
"github.com/tetratelabs/wazero/internal/testing/require"
|
|
)
|
|
|
|
func TestMachine_LowerConditionalBranch(t *testing.T) {
|
|
cmpInSameGroupFromParams := func(
|
|
brz bool, intCond ssa.IntegerCmpCond, floatCond ssa.FloatCmpCond,
|
|
ctx *mockCompiler, builder ssa.Builder, m *machine,
|
|
) (instr *ssa.Instruction, verify func(t *testing.T)) {
|
|
m.StartLoweringFunction(10)
|
|
entry := builder.CurrentBlock()
|
|
isInt := intCond != ssa.IntegerCmpCondInvalid
|
|
|
|
var val1, val2 ssa.Value
|
|
if isInt {
|
|
val1 = entry.AddParam(builder, ssa.TypeI64)
|
|
val2 = entry.AddParam(builder, ssa.TypeI64)
|
|
ctx.vRegMap[val1], ctx.vRegMap[val2] = regToVReg(x1).SetRegType(regalloc.RegTypeInt), regToVReg(x2).SetRegType(regalloc.RegTypeInt)
|
|
} else {
|
|
val1 = entry.AddParam(builder, ssa.TypeF64)
|
|
val2 = entry.AddParam(builder, ssa.TypeF64)
|
|
ctx.vRegMap[val1], ctx.vRegMap[val2] = regToVReg(v1).SetRegType(regalloc.RegTypeFloat), regToVReg(v2).SetRegType(regalloc.RegTypeFloat)
|
|
}
|
|
|
|
var cmpInstr *ssa.Instruction
|
|
if isInt {
|
|
cmpInstr = builder.AllocateInstruction()
|
|
cmpInstr.AsIcmp(val1, val2, intCond)
|
|
builder.InsertInstruction(cmpInstr)
|
|
} else {
|
|
cmpInstr = builder.AllocateInstruction()
|
|
cmpInstr.AsFcmp(val1, val2, floatCond)
|
|
builder.InsertInstruction(cmpInstr)
|
|
}
|
|
|
|
cmpVal := cmpInstr.Return()
|
|
ctx.vRegMap[cmpVal] = 3
|
|
|
|
ctx.definitions[val1] = &backend.SSAValueDefinition{BlkParamVReg: ctx.vRegMap[val1], BlockParamValue: val1}
|
|
ctx.definitions[val2] = &backend.SSAValueDefinition{BlkParamVReg: ctx.vRegMap[val2], BlockParamValue: val2}
|
|
ctx.definitions[cmpVal] = &backend.SSAValueDefinition{Instr: cmpInstr}
|
|
b := builder.AllocateInstruction()
|
|
if brz {
|
|
b.AsBrz(cmpVal, nil, builder.AllocateBasicBlock())
|
|
} else {
|
|
b.AsBrnz(cmpVal, nil, builder.AllocateBasicBlock())
|
|
}
|
|
builder.InsertInstruction(b)
|
|
return b, func(t *testing.T) {
|
|
_, ok := ctx.lowered[cmpInstr]
|
|
require.True(t, ok)
|
|
}
|
|
}
|
|
|
|
icmpInSameGroupFromParamAndImm12 := func(brz bool, ctx *mockCompiler, builder ssa.Builder, m *machine) (instr *ssa.Instruction, verify func(t *testing.T)) {
|
|
m.StartLoweringFunction(10)
|
|
entry := builder.CurrentBlock()
|
|
v1 := entry.AddParam(builder, ssa.TypeI32)
|
|
|
|
iconst := builder.AllocateInstruction()
|
|
iconst.AsIconst32(0x4d2)
|
|
builder.InsertInstruction(iconst)
|
|
v2 := iconst.Return()
|
|
|
|
// Constant can be referenced from different groups because we inline it.
|
|
builder.SetCurrentBlock(builder.AllocateBasicBlock())
|
|
|
|
icmp := builder.AllocateInstruction()
|
|
icmp.AsIcmp(v1, v2, ssa.IntegerCmpCondEqual)
|
|
builder.InsertInstruction(icmp)
|
|
icmpVal := icmp.Return()
|
|
ctx.definitions[v1] = &backend.SSAValueDefinition{BlkParamVReg: intToVReg(1), BlockParamValue: v1}
|
|
ctx.definitions[v2] = &backend.SSAValueDefinition{Instr: iconst}
|
|
ctx.definitions[icmpVal] = &backend.SSAValueDefinition{Instr: icmp}
|
|
ctx.vRegMap[v1], ctx.vRegMap[v2], ctx.vRegMap[icmpVal] = intToVReg(1), intToVReg(2), intToVReg(3)
|
|
b := builder.AllocateInstruction()
|
|
if brz {
|
|
b.AsBrz(icmpVal, nil, builder.AllocateBasicBlock())
|
|
} else {
|
|
b.AsBrnz(icmpVal, nil, builder.AllocateBasicBlock())
|
|
}
|
|
builder.InsertInstruction(b)
|
|
return b, func(t *testing.T) {
|
|
_, ok := ctx.lowered[icmp]
|
|
require.True(t, ok)
|
|
}
|
|
}
|
|
|
|
for _, tc := range []struct {
|
|
name string
|
|
setup func(*mockCompiler, ssa.Builder, *machine) (instr *ssa.Instruction, verify func(t *testing.T))
|
|
instructions []string
|
|
}{
|
|
{
|
|
name: "icmp in different group",
|
|
setup: func(ctx *mockCompiler, builder ssa.Builder, m *machine) (instr *ssa.Instruction, verify func(t *testing.T)) {
|
|
m.StartLoweringFunction(10)
|
|
entry := builder.CurrentBlock()
|
|
v1, v2 := entry.AddParam(builder, ssa.TypeI64), entry.AddParam(builder, ssa.TypeI64)
|
|
|
|
icmp := builder.AllocateInstruction()
|
|
icmp.AsIcmp(v1, v2, ssa.IntegerCmpCondEqual)
|
|
builder.InsertInstruction(icmp)
|
|
icmpVal := icmp.Return()
|
|
ctx.definitions[icmpVal] = &backend.SSAValueDefinition{Instr: icmp}
|
|
ctx.vRegMap[v1], ctx.vRegMap[v2], ctx.vRegMap[icmpVal] = intToVReg(1), intToVReg(2), intToVReg(3)
|
|
|
|
brz := builder.AllocateInstruction()
|
|
brz.AsBrz(icmpVal, nil, builder.AllocateBasicBlock())
|
|
builder.InsertInstruction(brz)
|
|
|
|
// Indicate that currently compiling in the different group.
|
|
ctx.currentGID = 1000
|
|
return brz, func(t *testing.T) {
|
|
_, ok := ctx.lowered[icmp]
|
|
require.False(t, ok)
|
|
}
|
|
},
|
|
instructions: []string{"cbz w3?, (L1)"},
|
|
},
|
|
{
|
|
name: "brz / icmp in the same group / params",
|
|
setup: func(ctx *mockCompiler, builder ssa.Builder, m *machine) (instr *ssa.Instruction, verify func(t *testing.T)) {
|
|
return cmpInSameGroupFromParams(true, ssa.IntegerCmpCondUnsignedGreaterThan, ssa.FloatCmpCondInvalid, ctx, builder, m)
|
|
},
|
|
instructions: []string{
|
|
"subs xzr, x1, x2",
|
|
"b.ls L1",
|
|
},
|
|
},
|
|
{
|
|
name: "brnz / icmp in the same group / params",
|
|
setup: func(ctx *mockCompiler, builder ssa.Builder, m *machine) (instr *ssa.Instruction, verify func(t *testing.T)) {
|
|
return cmpInSameGroupFromParams(false, ssa.IntegerCmpCondEqual, ssa.FloatCmpCondInvalid, ctx, builder, m)
|
|
},
|
|
instructions: []string{
|
|
"subs xzr, x1, x2",
|
|
"b.eq L1",
|
|
},
|
|
},
|
|
{
|
|
name: "brz / fcmp in the same group / params",
|
|
setup: func(ctx *mockCompiler, builder ssa.Builder, m *machine) (instr *ssa.Instruction, verify func(t *testing.T)) {
|
|
return cmpInSameGroupFromParams(true, ssa.IntegerCmpCondInvalid, ssa.FloatCmpCondEqual, ctx, builder, m)
|
|
},
|
|
instructions: []string{
|
|
"fcmp d1, d2",
|
|
"b.ne L1",
|
|
},
|
|
},
|
|
{
|
|
name: "brnz / fcmp in the same group / params",
|
|
setup: func(ctx *mockCompiler, builder ssa.Builder, m *machine) (instr *ssa.Instruction, verify func(t *testing.T)) {
|
|
return cmpInSameGroupFromParams(false, ssa.IntegerCmpCondInvalid, ssa.FloatCmpCondGreaterThan, ctx, builder, m)
|
|
},
|
|
instructions: []string{
|
|
"fcmp d1, d2",
|
|
"b.gt L1",
|
|
},
|
|
},
|
|
{
|
|
name: "brz / icmp in the same group / params",
|
|
setup: func(ctx *mockCompiler, builder ssa.Builder, m *machine) (instr *ssa.Instruction, verify func(t *testing.T)) {
|
|
return icmpInSameGroupFromParamAndImm12(true, ctx, builder, m)
|
|
},
|
|
instructions: []string{
|
|
"subs wzr, w1?, #0x4d2",
|
|
"b.ne L1",
|
|
},
|
|
},
|
|
{
|
|
name: "brz / icmp in the same group / params",
|
|
setup: func(ctx *mockCompiler, builder ssa.Builder, m *machine) (instr *ssa.Instruction, verify func(t *testing.T)) {
|
|
return icmpInSameGroupFromParamAndImm12(false, ctx, builder, m)
|
|
},
|
|
instructions: []string{
|
|
"subs wzr, w1?, #0x4d2",
|
|
"b.eq L1",
|
|
},
|
|
},
|
|
} {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
ctx, b, m := newSetupWithMockContext()
|
|
instr, verify := tc.setup(ctx, b, m)
|
|
m.LowerConditionalBranch(instr)
|
|
verify(t)
|
|
require.Equal(t, strings.Join(tc.instructions, "\n"),
|
|
formatEmittedInstructionsInCurrentBlock(m))
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestMachine_LowerSingleBranch(t *testing.T) {
|
|
for _, tc := range []struct {
|
|
name string
|
|
setup func(*mockCompiler, ssa.Builder, *machine) (instr *ssa.Instruction)
|
|
instructions []string
|
|
}{
|
|
{
|
|
name: "jump-fallthrough",
|
|
setup: func(ctx *mockCompiler, builder ssa.Builder, m *machine) (instr *ssa.Instruction) {
|
|
jump := builder.AllocateInstruction()
|
|
jump.AsJump(nil, builder.AllocateBasicBlock())
|
|
builder.InsertInstruction(jump)
|
|
jump.AsFallthroughJump()
|
|
return jump
|
|
},
|
|
instructions: []string{}, // Fallthrough jump should be optimized out.
|
|
},
|
|
{
|
|
name: "b",
|
|
setup: func(ctx *mockCompiler, builder ssa.Builder, m *machine) (instr *ssa.Instruction) {
|
|
m.StartLoweringFunction(10)
|
|
jump := builder.AllocateInstruction()
|
|
jump.AsJump(nil, builder.AllocateBasicBlock())
|
|
builder.InsertInstruction(jump)
|
|
return jump
|
|
},
|
|
instructions: []string{"b L1"},
|
|
},
|
|
{
|
|
name: "ret",
|
|
setup: func(ctx *mockCompiler, builder ssa.Builder, m *machine) (instr *ssa.Instruction) {
|
|
m.StartLoweringFunction(10)
|
|
jump := builder.AllocateInstruction()
|
|
jump.AsJump(nil, builder.ReturnBlock())
|
|
builder.InsertInstruction(jump)
|
|
return jump
|
|
},
|
|
// Jump which targets the return block should be translated as "ret".
|
|
instructions: []string{"ret"},
|
|
},
|
|
} {
|
|
tc := tc
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
ctx, b, m := newSetupWithMockContext()
|
|
instr := tc.setup(ctx, b, m)
|
|
m.LowerSingleBranch(instr)
|
|
require.Equal(t, strings.Join(tc.instructions, "\n"), formatEmittedInstructionsInCurrentBlock(m))
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestMachine_InsertMove(t *testing.T) {
|
|
for _, tc := range []struct {
|
|
name string
|
|
src, dst regalloc.VReg
|
|
typ ssa.Type
|
|
instruction string
|
|
}{
|
|
{
|
|
name: "int",
|
|
src: regalloc.VReg(1).SetRegType(regalloc.RegTypeInt),
|
|
dst: regalloc.VReg(2).SetRegType(regalloc.RegTypeInt),
|
|
instruction: "mov x1?, x2?",
|
|
typ: ssa.TypeI64,
|
|
},
|
|
{
|
|
name: "float",
|
|
src: regalloc.VReg(1).SetRegType(regalloc.RegTypeFloat),
|
|
dst: regalloc.VReg(2).SetRegType(regalloc.RegTypeFloat),
|
|
instruction: "mov v1?.8b, v2?.8b",
|
|
typ: ssa.TypeF64,
|
|
},
|
|
{
|
|
name: "vector",
|
|
src: regalloc.VReg(1).SetRegType(regalloc.RegTypeFloat),
|
|
dst: regalloc.VReg(2).SetRegType(regalloc.RegTypeFloat),
|
|
instruction: "mov v1?.16b, v2?.16b",
|
|
typ: ssa.TypeV128,
|
|
},
|
|
} {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
_, _, m := newSetupWithMockContext()
|
|
m.InsertMove(tc.src, tc.dst, tc.typ)
|
|
require.Equal(t, tc.instruction, formatEmittedInstructionsInCurrentBlock(m))
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestMachine_lowerIDiv(t *testing.T) {
|
|
for _, tc := range []struct {
|
|
name string
|
|
_64bit bool
|
|
signed bool
|
|
exp string
|
|
}{
|
|
{
|
|
name: "32bit unsigned", _64bit: false, signed: false,
|
|
exp: `
|
|
udiv w1?, w2?, w3?
|
|
cbnz w3?, L1
|
|
movz x1?, #0xa, lsl 0
|
|
str w1?, [x65535?]
|
|
mov x2?, sp
|
|
str x2?, [x65535?, #0x38]
|
|
adr x3?, #0x0
|
|
str x3?, [x65535?, #0x30]
|
|
exit_sequence x65535?
|
|
L1:
|
|
`,
|
|
},
|
|
{name: "32bit signed", _64bit: false, signed: true, exp: `
|
|
sdiv w1?, w2?, w3?
|
|
cbnz w3?, L1
|
|
movz x1?, #0xa, lsl 0
|
|
str w1?, [x65535?]
|
|
mov x2?, sp
|
|
str x2?, [x65535?, #0x38]
|
|
adr x3?, #0x0
|
|
str x3?, [x65535?, #0x30]
|
|
exit_sequence x65535?
|
|
L1:
|
|
adds wzr, w3?, #0x1
|
|
ccmp w2?, #0x1, #0x0, eq
|
|
b.vc L2
|
|
movz x4?, #0xb, lsl 0
|
|
str w4?, [x65535?]
|
|
mov x5?, sp
|
|
str x5?, [x65535?, #0x38]
|
|
adr x6?, #0x0
|
|
str x6?, [x65535?, #0x30]
|
|
exit_sequence x65535?
|
|
L2:
|
|
`},
|
|
{name: "64bit unsigned", _64bit: true, signed: false, exp: `
|
|
udiv x1?, x2?, x3?
|
|
cbnz x3?, L1
|
|
movz x1?, #0xa, lsl 0
|
|
str w1?, [x65535?]
|
|
mov x2?, sp
|
|
str x2?, [x65535?, #0x38]
|
|
adr x3?, #0x0
|
|
str x3?, [x65535?, #0x30]
|
|
exit_sequence x65535?
|
|
L1:
|
|
`},
|
|
{name: "64bit signed", _64bit: true, signed: true, exp: `
|
|
sdiv x1?, x2?, x3?
|
|
cbnz x3?, L1
|
|
movz x1?, #0xa, lsl 0
|
|
str w1?, [x65535?]
|
|
mov x2?, sp
|
|
str x2?, [x65535?, #0x38]
|
|
adr x3?, #0x0
|
|
str x3?, [x65535?, #0x30]
|
|
exit_sequence x65535?
|
|
L1:
|
|
adds xzr, x3?, #0x1
|
|
ccmp x2?, #0x1, #0x0, eq
|
|
b.vc L2
|
|
movz x4?, #0xb, lsl 0
|
|
str w4?, [x65535?]
|
|
mov x5?, sp
|
|
str x5?, [x65535?, #0x38]
|
|
adr x6?, #0x0
|
|
str x6?, [x65535?, #0x30]
|
|
exit_sequence x65535?
|
|
L2:
|
|
`},
|
|
} {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
execCtx := regalloc.VReg(0xffff).SetRegType(regalloc.RegTypeInt)
|
|
rd, rn, rm := regalloc.VReg(1).SetRegType(regalloc.RegTypeInt),
|
|
regalloc.VReg(2).SetRegType(regalloc.RegTypeInt),
|
|
regalloc.VReg(3).SetRegType(regalloc.RegTypeInt)
|
|
_, _, m := newSetupWithMockContext()
|
|
m.lowerIDiv(execCtx, operandNR(rd), operandNR(rn), operandNR(rm), tc._64bit, tc.signed)
|
|
require.Equal(t, tc.exp, "\n"+formatEmittedInstructionsInCurrentBlock(m)+"\n")
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestMachine_exitWithCode(t *testing.T) {
|
|
_, _, m := newSetupWithMockContext()
|
|
m.lowerExitWithCode(x1VReg, wazevoapi.ExitCodeGrowStack)
|
|
m.FlushPendingInstructions()
|
|
m.encode(m.perBlockHead)
|
|
require.Equal(t, `
|
|
movz x1?, #0x1, lsl 0
|
|
str w1?, [x1]
|
|
mov x2?, sp
|
|
str x2?, [x1, #0x38]
|
|
adr x3?, #0x0
|
|
str x3?, [x1, #0x30]
|
|
exit_sequence x1
|
|
`, "\n"+formatEmittedInstructionsInCurrentBlock(m)+"\n")
|
|
}
|
|
|
|
func TestMachine_lowerFpuToInt(t *testing.T) {
|
|
for _, tc := range []struct {
|
|
name string
|
|
nontrapping bool
|
|
expectedAsm string
|
|
}{
|
|
{
|
|
name: "trapping",
|
|
nontrapping: false,
|
|
expectedAsm: `
|
|
msr fpsr, xzr
|
|
fcvtzu w1, s2
|
|
mrs x1? fpsr
|
|
subs xzr, x1?, #0x1
|
|
b.ne L2
|
|
fcmp w2, w2
|
|
b.vc L1
|
|
movz x2?, #0xc, lsl 0
|
|
str w2?, [x15]
|
|
mov x3?, sp
|
|
str x3?, [x15, #0x38]
|
|
adr x4?, #0x0
|
|
str x4?, [x15, #0x30]
|
|
exit_sequence x15
|
|
L1:
|
|
movz x5?, #0xb, lsl 0
|
|
str w5?, [x15]
|
|
mov x6?, sp
|
|
str x6?, [x15, #0x38]
|
|
adr x7?, #0x0
|
|
str x7?, [x15, #0x30]
|
|
exit_sequence x15
|
|
L2:
|
|
`,
|
|
},
|
|
{
|
|
name: "nontrapping",
|
|
nontrapping: true,
|
|
expectedAsm: `
|
|
fcvtzu w1, s2
|
|
`,
|
|
},
|
|
} {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
_, _, m := newSetupWithMockContext()
|
|
m.lowerFpuToInt(operandNR(x1VReg), operandNR(x2VReg), x15VReg, false, false, false, tc.nontrapping)
|
|
require.Equal(t, tc.expectedAsm, "\n"+formatEmittedInstructionsInCurrentBlock(m)+"\n")
|
|
|
|
m.FlushPendingInstructions()
|
|
m.encode(m.perBlockHead)
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestMachine_lowerVIMul(t *testing.T) {
|
|
for _, tc := range []struct {
|
|
name string
|
|
expectedAsm string
|
|
arrangement vecArrangement
|
|
expectedBytes string
|
|
}{
|
|
{
|
|
name: "2D",
|
|
arrangement: vecArrangement2D,
|
|
expectedAsm: `
|
|
rev64 v2?.4s, x15.4s
|
|
mul v2?.4s, v2?.4s, x2.4s
|
|
xtn v1?.2s, x2.2s
|
|
addp v2?.4s, v2?.4s, v2?.4s
|
|
xtn v3?.2s, x15.2s
|
|
shll x1.2s, v2?.2s
|
|
umlal x1.2s, v3?.2s, v1?.2s
|
|
`,
|
|
expectedBytes: "e009a04e009ca24e4028a10e00bca04ee029a10e0138a12e0180a02e",
|
|
},
|
|
{
|
|
name: "8B",
|
|
arrangement: vecArrangement8B,
|
|
expectedAsm: `
|
|
mul x1.8b, x2.8b, x15.8b
|
|
`,
|
|
expectedBytes: "419c2f0e",
|
|
},
|
|
{
|
|
name: "16B",
|
|
arrangement: vecArrangement16B,
|
|
expectedAsm: `
|
|
mul x1.16b, x2.16b, x15.16b
|
|
`,
|
|
expectedBytes: "419c2f4e",
|
|
},
|
|
{
|
|
name: "4H",
|
|
arrangement: vecArrangement4H,
|
|
expectedAsm: `
|
|
mul x1.4h, x2.4h, x15.4h
|
|
`,
|
|
expectedBytes: "419c6f0e",
|
|
},
|
|
{
|
|
name: "8H",
|
|
arrangement: vecArrangement8H,
|
|
expectedAsm: `
|
|
mul x1.8h, x2.8h, x15.8h
|
|
`,
|
|
expectedBytes: "419c6f4e",
|
|
},
|
|
{
|
|
name: "2S",
|
|
arrangement: vecArrangement2S,
|
|
expectedAsm: `
|
|
mul x1.2s, x2.2s, x15.2s
|
|
`,
|
|
expectedBytes: "419caf0e",
|
|
},
|
|
{
|
|
name: "4S",
|
|
arrangement: vecArrangement4S,
|
|
expectedAsm: `
|
|
mul x1.4s, x2.4s, x15.4s
|
|
`,
|
|
expectedBytes: "419caf4e",
|
|
},
|
|
} {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
_, _, m := newSetupWithMockContext()
|
|
m.lowerVIMul(operandNR(x1VReg), operandNR(x2VReg), operandNR(x15VReg), tc.arrangement)
|
|
require.Equal(t, tc.expectedAsm, "\n"+formatEmittedInstructionsInCurrentBlock(m)+"\n")
|
|
|
|
m.FlushPendingInstructions()
|
|
m.encode(m.perBlockHead)
|
|
buf := m.compiler.Buf()
|
|
require.Equal(t, tc.expectedBytes, hex.EncodeToString(buf))
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestMachine_lowerVcheckTrue(t *testing.T) {
|
|
for _, tc := range []struct {
|
|
name string
|
|
op ssa.Opcode
|
|
expectedAsm string
|
|
arrangement vecArrangement
|
|
expectedBytes string
|
|
}{
|
|
{
|
|
name: "anyTrue",
|
|
op: ssa.OpcodeVanyTrue,
|
|
expectedAsm: `
|
|
umaxp v1?.16b, x1.16b, x1.16b
|
|
mov x15, v1?.d[0]
|
|
ccmp x15, #0x0, #0x0, al
|
|
cset x15, ne
|
|
`,
|
|
expectedBytes: "20a4216e0f3c084ee0e940faef079f9a",
|
|
},
|
|
{
|
|
name: "allTrue 2D",
|
|
op: ssa.OpcodeVallTrue,
|
|
arrangement: vecArrangement2D,
|
|
expectedAsm: `
|
|
cmeq x15.2d, x1.2d, #0
|
|
addp x15.2d, x15.2d, x15.2d
|
|
fcmp x15, x15
|
|
cset x15, eq
|
|
`,
|
|
expectedBytes: "2f98e04eefbdef4ee0216f1eef179f9a",
|
|
},
|
|
{
|
|
name: "allTrue 8B",
|
|
arrangement: vecArrangement8B,
|
|
op: ssa.OpcodeVallTrue,
|
|
expectedAsm: `
|
|
uminv h1?, x1.8b
|
|
mov x15, v1?.d[0]
|
|
ccmp x15, #0x0, #0x0, al
|
|
cset x15, ne
|
|
`,
|
|
expectedBytes: "20a8312e0f3c084ee0e940faef079f9a",
|
|
},
|
|
{
|
|
name: "allTrue 16B",
|
|
arrangement: vecArrangement16B,
|
|
op: ssa.OpcodeVallTrue,
|
|
expectedAsm: `
|
|
uminv h1?, x1.16b
|
|
mov x15, v1?.d[0]
|
|
ccmp x15, #0x0, #0x0, al
|
|
cset x15, ne
|
|
`,
|
|
expectedBytes: "20a8316e0f3c084ee0e940faef079f9a",
|
|
},
|
|
{
|
|
name: "allTrue 4H",
|
|
arrangement: vecArrangement4H,
|
|
op: ssa.OpcodeVallTrue,
|
|
expectedAsm: `
|
|
uminv s1?, x1.4h
|
|
mov x15, v1?.d[0]
|
|
ccmp x15, #0x0, #0x0, al
|
|
cset x15, ne
|
|
`,
|
|
expectedBytes: "20a8712e0f3c084ee0e940faef079f9a",
|
|
},
|
|
{
|
|
name: "allTrue 8H",
|
|
arrangement: vecArrangement8H,
|
|
op: ssa.OpcodeVallTrue,
|
|
expectedAsm: `
|
|
uminv s1?, x1.8h
|
|
mov x15, v1?.d[0]
|
|
ccmp x15, #0x0, #0x0, al
|
|
cset x15, ne
|
|
`,
|
|
expectedBytes: "20a8716e0f3c084ee0e940faef079f9a",
|
|
},
|
|
{
|
|
name: "allTrue 4S",
|
|
arrangement: vecArrangement4S,
|
|
op: ssa.OpcodeVallTrue,
|
|
expectedAsm: `
|
|
uminv d1?, x1.4s
|
|
mov x15, v1?.d[0]
|
|
ccmp x15, #0x0, #0x0, al
|
|
cset x15, ne
|
|
`,
|
|
expectedBytes: "20a8b16e0f3c084ee0e940faef079f9a",
|
|
},
|
|
} {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
_, _, m := newSetupWithMockContext()
|
|
m.lowerVcheckTrue(tc.op, operandNR(x1VReg), operandNR(x15VReg), tc.arrangement)
|
|
require.Equal(t, tc.expectedAsm, "\n"+formatEmittedInstructionsInCurrentBlock(m)+"\n")
|
|
|
|
m.FlushPendingInstructions()
|
|
m.encode(m.perBlockHead)
|
|
buf := m.compiler.Buf()
|
|
require.Equal(t, tc.expectedBytes, hex.EncodeToString(buf))
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestMachine_lowerVhighBits(t *testing.T) {
|
|
for _, tc := range []struct {
|
|
name string
|
|
expectedAsm string
|
|
arrangement vecArrangement
|
|
expectedBytes string
|
|
}{
|
|
{
|
|
name: "16B",
|
|
arrangement: vecArrangement16B,
|
|
expectedAsm: `
|
|
sshr v3?.16b, x1.16b, #7
|
|
movz x1?, #0x201, lsl 0
|
|
movk x1?, #0x804, lsl 16
|
|
movk x1?, #0x2010, lsl 32
|
|
movk x1?, #0x8040, lsl 48
|
|
dup v2?.2d, x1?
|
|
and v3?.16b, v3?.16b, v2?.16b
|
|
ext v2?.16b, v3?.16b, v3?.16b, #8
|
|
zip1 v2?.16b, v3?.16b, v2?.16b
|
|
addv s2?, v2?.8h
|
|
umov w15, v2?.h[0]
|
|
`,
|
|
expectedBytes: "2004094f204080d28000a1f20002c4f20008f0f2000c084e001c204e0040006e0038004e00b8714e0f3c020e",
|
|
},
|
|
{
|
|
name: "8H",
|
|
arrangement: vecArrangement8H,
|
|
expectedAsm: `
|
|
sshr v3?.8h, x1.8h, #15
|
|
movz x1?, #0x1, lsl 0
|
|
movk x1?, #0x2, lsl 16
|
|
movk x1?, #0x4, lsl 32
|
|
movk x1?, #0x8, lsl 48
|
|
dup v2?.2d, x1?
|
|
lsl x1?, x1?, 0x4
|
|
ins v2?.d[1], x1?
|
|
and v2?.16b, v3?.16b, v2?.16b
|
|
addv s2?, v2?.8h
|
|
umov w15, v2?.h[0]
|
|
`,
|
|
expectedBytes: "2004114f200080d24000a0f28000c0f20001e0f2000c084e00ec7cd3001c184e001c204e00b8714e0f3c020e",
|
|
},
|
|
{
|
|
name: "4S",
|
|
arrangement: vecArrangement4S,
|
|
expectedAsm: `
|
|
sshr v3?.4s, x1.4s, #31
|
|
movz x1?, #0x1, lsl 0
|
|
movk x1?, #0x2, lsl 32
|
|
dup v2?.2d, x1?
|
|
lsl x1?, x1?, 0x2
|
|
ins v2?.d[1], x1?
|
|
and v2?.16b, v3?.16b, v2?.16b
|
|
addv d2?, v2?.4s
|
|
umov w15, v2?.s[0]
|
|
`,
|
|
expectedBytes: "2004214f200080d24000c0f2000c084e00f47ed3001c184e001c204e00b8b14e0f3c040e",
|
|
},
|
|
{
|
|
name: "2D",
|
|
arrangement: vecArrangement2D,
|
|
expectedAsm: `
|
|
mov x15, x1.d[0]
|
|
mov x1?, x1.d[1]
|
|
lsr x1?, x1?, 0x3f
|
|
lsr x15, x15, 0x3f
|
|
add w15, w15, w1?, lsl #1
|
|
`,
|
|
expectedBytes: "2f3c084e203c184e00fc7fd3effd7fd3ef05000b",
|
|
},
|
|
} {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
_, _, m := newSetupWithMockContext()
|
|
m.lowerVhighBits(operandNR(x1VReg), operandNR(x15VReg), tc.arrangement)
|
|
require.Equal(t, tc.expectedAsm, "\n"+formatEmittedInstructionsInCurrentBlock(m)+"\n")
|
|
|
|
m.FlushPendingInstructions()
|
|
m.encode(m.perBlockHead)
|
|
buf := m.compiler.Buf()
|
|
require.Equal(t, tc.expectedBytes, hex.EncodeToString(buf))
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestMachine_lowerShuffle(t *testing.T) {
|
|
for _, tc := range []struct {
|
|
name string
|
|
lanes []uint64
|
|
expectedAsm string
|
|
expectedBytes string
|
|
}{
|
|
{
|
|
name: "lanes 0..15",
|
|
lanes: []uint64{0, 1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15},
|
|
expectedAsm: `
|
|
mov v29.16b, x2.16b
|
|
mov v30.16b, x15.16b
|
|
ldr q1?, #8; b 32; data.v128 0706050403020100 0f0e0d0c0b0a0908
|
|
tbl x1.16b, { v29.16b, v30.16b }, v1?.16b
|
|
`,
|
|
expectedBytes: "5d1ca24efe1daf4e4000009c05000014000102030405060708090a0b0c0d0e0fa123004e",
|
|
},
|
|
{
|
|
name: "lanes 0101...",
|
|
lanes: []uint64{0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1, 0, 1},
|
|
expectedAsm: `
|
|
mov v29.16b, x2.16b
|
|
mov v30.16b, x15.16b
|
|
ldr q1?, #8; b 32; data.v128 0100010001000100 0100010001000100
|
|
tbl x1.16b, { v29.16b, v30.16b }, v1?.16b
|
|
`,
|
|
expectedBytes: "5d1ca24efe1daf4e4000009c0500001400010001000100010001000100010001a123004e",
|
|
},
|
|
} {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
_, _, m := newSetupWithMockContext()
|
|
lanes := tc.lanes
|
|
|
|
// Encode the 16 bytes as 8 bytes in u1, and 8 bytes in u2.
|
|
lane1 := lanes[7]<<56 | lanes[6]<<48 | lanes[5]<<40 | lanes[4]<<32 | lanes[3]<<24 | lanes[2]<<16 | lanes[1]<<8 | lanes[0]
|
|
lane2 := lanes[15]<<56 | lanes[14]<<48 | lanes[13]<<40 | lanes[12]<<32 | lanes[11]<<24 | lanes[10]<<16 | lanes[9]<<8 | lanes[8]
|
|
|
|
m.lowerShuffle(operandNR(x1VReg), operandNR(x2VReg), operandNR(x15VReg), lane1, lane2)
|
|
require.Equal(t, tc.expectedAsm, "\n"+formatEmittedInstructionsInCurrentBlock(m)+"\n")
|
|
|
|
m.FlushPendingInstructions()
|
|
m.encode(m.perBlockHead)
|
|
buf := m.compiler.Buf()
|
|
require.Equal(t, tc.expectedBytes, hex.EncodeToString(buf))
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestMachine_lowerVShift(t *testing.T) {
|
|
for _, tc := range []struct {
|
|
name string
|
|
expectedAsm string
|
|
op ssa.Opcode
|
|
arrangement vecArrangement
|
|
expectedBytes string
|
|
}{
|
|
{
|
|
name: "VIshl",
|
|
op: ssa.OpcodeVIshl,
|
|
arrangement: vecArrangement16B,
|
|
expectedAsm: `
|
|
and s1?, w15, #0x7
|
|
dup x1.16b, d1?
|
|
sshl x1.16b, x2.16b, x1.16b
|
|
`,
|
|
expectedBytes: "e0090012010c014e4144214e",
|
|
},
|
|
{
|
|
name: "VSshr",
|
|
op: ssa.OpcodeVSshr,
|
|
arrangement: vecArrangement16B,
|
|
expectedAsm: `
|
|
and s1?, w15, #0x7
|
|
sub s1?, wzr, s1?
|
|
dup x1.16b, d1?
|
|
sshl x1.16b, x2.16b, x1.16b
|
|
`,
|
|
expectedBytes: "e0090012e003004b010c014e4144214e",
|
|
},
|
|
{
|
|
name: "VUshr",
|
|
op: ssa.OpcodeVUshr,
|
|
arrangement: vecArrangement16B,
|
|
expectedAsm: `
|
|
and s1?, w15, #0x7
|
|
sub s1?, wzr, s1?
|
|
dup x1.16b, d1?
|
|
ushl x1.16b, x2.16b, x1.16b
|
|
`,
|
|
expectedBytes: "e0090012e003004b010c014e4144216e",
|
|
},
|
|
} {
|
|
t.Run(tc.name, func(t *testing.T) {
|
|
_, _, m := newSetupWithMockContext()
|
|
m.lowerVShift(tc.op, operandNR(x1VReg), operandNR(x2VReg), operandNR(x15VReg), tc.arrangement)
|
|
require.Equal(t, tc.expectedAsm, "\n"+formatEmittedInstructionsInCurrentBlock(m)+"\n")
|
|
|
|
m.FlushPendingInstructions()
|
|
m.encode(m.perBlockHead)
|
|
buf := m.compiler.Buf()
|
|
require.Equal(t, tc.expectedBytes, hex.EncodeToString(buf))
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestMachine_lowerSelectVec(t *testing.T) {
|
|
_, _, m := newSetupWithMockContext()
|
|
c := operandNR(m.compiler.AllocateVReg(ssa.TypeI32))
|
|
rn := operandNR(m.compiler.AllocateVReg(ssa.TypeV128))
|
|
rm := operandNR(m.compiler.AllocateVReg(ssa.TypeV128))
|
|
rd := operandNR(m.compiler.AllocateVReg(ssa.TypeV128))
|
|
|
|
require.Equal(t, 1, int(c.reg().ID()))
|
|
require.Equal(t, 2, int(rn.reg().ID()))
|
|
require.Equal(t, 3, int(rm.reg().ID()))
|
|
require.Equal(t, 4, int(rd.reg().ID()))
|
|
|
|
m.lowerSelectVec(c, rn, rm, rd)
|
|
require.Equal(t, `
|
|
sub x5?, xzr, x1?
|
|
dup v4?.2d, x5?
|
|
bsl v4?.16b, v2?.16b, v3?.16b
|
|
`, "\n"+formatEmittedInstructionsInCurrentBlock(m)+"\n")
|
|
}
|
|
|
|
func TestMachine_lowerFcopysign(t *testing.T) {
|
|
for _, tc := range []struct {
|
|
_64bit bool
|
|
exp string
|
|
}{
|
|
{
|
|
_64bit: false,
|
|
exp: `
|
|
movz w1?, #0x8000, lsl 16
|
|
ins v2?.s[0], w1?
|
|
mov v5?.8b, v3?.8b
|
|
bit v5?.8b, v4?.8b, v2?.8b
|
|
`,
|
|
},
|
|
{
|
|
_64bit: true,
|
|
exp: `
|
|
movz x1?, #0x8000, lsl 48
|
|
ins v2?.d[0], x1?
|
|
mov v5?.8b, v3?.8b
|
|
bit v5?.8b, v4?.8b, v2?.8b
|
|
`,
|
|
},
|
|
} {
|
|
t.Run(fmt.Sprintf("64bit=%v", tc._64bit), func(t *testing.T) {
|
|
_, _, m := newSetupWithMockContext()
|
|
var typ, ftyp ssa.Type
|
|
if tc._64bit {
|
|
typ = ssa.TypeI64
|
|
ftyp = ssa.TypeF64
|
|
} else {
|
|
typ = ssa.TypeI32
|
|
ftyp = ssa.TypeF32
|
|
}
|
|
tmpI := operandNR(m.compiler.AllocateVReg(typ))
|
|
tmpF := operandNR(m.compiler.AllocateVReg(ftyp))
|
|
rn := operandNR(m.compiler.AllocateVReg(ftyp))
|
|
rm := operandNR(m.compiler.AllocateVReg(ftyp))
|
|
rd := operandNR(m.compiler.AllocateVReg(ftyp))
|
|
|
|
require.Equal(t, 1, int(tmpI.reg().ID()))
|
|
require.Equal(t, 2, int(tmpF.reg().ID()))
|
|
require.Equal(t, 3, int(rn.reg().ID()))
|
|
require.Equal(t, 4, int(rm.reg().ID()))
|
|
require.Equal(t, 5, int(rd.reg().ID()))
|
|
|
|
m.lowerFcopysignImpl(rd, rn, rm, tmpI, tmpF, tc._64bit)
|
|
require.Equal(t, tc.exp, "\n"+formatEmittedInstructionsInCurrentBlock(m)+"\n")
|
|
})
|
|
}
|
|
}
|
|
|
|
func TestMachine_lowerRotl(t *testing.T) {
|
|
for _, tc := range []struct {
|
|
_64bit bool
|
|
exp string
|
|
}{
|
|
{
|
|
_64bit: false,
|
|
exp: `
|
|
sub w1?, wzr, w3?
|
|
ror w4?, w2?, w1?
|
|
`,
|
|
},
|
|
{
|
|
_64bit: true,
|
|
exp: `
|
|
sub x1?, xzr, x3?
|
|
ror x4?, x2?, x1?
|
|
`,
|
|
},
|
|
} {
|
|
t.Run(fmt.Sprintf("64bit=%v", tc._64bit), func(t *testing.T) {
|
|
_, _, m := newSetupWithMockContext()
|
|
var typ ssa.Type
|
|
if tc._64bit {
|
|
typ = ssa.TypeI64
|
|
} else {
|
|
typ = ssa.TypeI32
|
|
}
|
|
tmpI := operandNR(m.compiler.AllocateVReg(typ))
|
|
rn := operandNR(m.compiler.AllocateVReg(typ))
|
|
rm := operandNR(m.compiler.AllocateVReg(typ))
|
|
rd := operandNR(m.compiler.AllocateVReg(typ))
|
|
|
|
require.Equal(t, 1, int(tmpI.reg().ID()))
|
|
require.Equal(t, 2, int(rn.reg().ID()))
|
|
require.Equal(t, 3, int(rm.reg().ID()))
|
|
require.Equal(t, 4, int(rd.reg().ID()))
|
|
|
|
m.lowerRotlImpl(rd, rn, rm, tmpI, tc._64bit)
|
|
require.Equal(t, tc.exp, "\n"+formatEmittedInstructionsInCurrentBlock(m)+"\n")
|
|
})
|
|
}
|
|
}
|