feat(amd64): encode legacy SSE packed binaries and imm8 shuffles
This commit is contained in:
@@ -270,6 +270,8 @@ func amd64Curated() []Instr {
|
|||||||
"VMINPD", "VMINPS", "VMINSD", "VMINSS", "VMAXPD", "VMAXPS", "VMAXSD", "VMAXSS",
|
"VMINPD", "VMINPS", "VMINSD", "VMINSS", "VMAXPD", "VMAXPS", "VMAXSD", "VMAXSS",
|
||||||
"VXORPD", "VXORPS", "VANDPD", "VANDPS", "VANDNPD", "VANDNPS", "VORPD", "VORPS",
|
"VXORPD", "VXORPS", "VANDPD", "VANDPS", "VANDNPD", "VANDNPS", "VORPD", "VORPS",
|
||||||
"VUNPCKHPD", "VUNPCKLPD", "VUNPCKHPS", "VUNPCKLPS",
|
"VUNPCKHPD", "VUNPCKLPD", "VUNPCKHPS", "VUNPCKLPS",
|
||||||
|
"PSHUFD", "PSHUFHW", "PSHUFLW", "SHUFPS", "SHUFPD",
|
||||||
|
"UNPCKLPS", "UNPCKHPS", "UNPCKLPD", "UNPCKHPD",
|
||||||
"VSQRTPD", "VSQRTPS", "VSQRTSD", "VSQRTSS", "VRSQRTPS", "VRCPPS",
|
"VSQRTPD", "VSQRTPS", "VSQRTSD", "VSQRTSS", "VRSQRTPS", "VRCPPS",
|
||||||
"VCMPPD", "VCMPPS", "VCMPSD", "VCMPSS",
|
"VCMPPD", "VCMPPS", "VCMPSD", "VCMPSS",
|
||||||
} {
|
} {
|
||||||
|
|||||||
@@ -76,6 +76,17 @@ func (e *enc) encode(mnem string, ops []Operand) error {
|
|||||||
if size == 0 {
|
if size == 0 {
|
||||||
size = 8 // default operand size in 64-bit mode (e.g. PUSHQ)
|
size = 8 // default operand size in 64-bit mode (e.g. PUSHQ)
|
||||||
}
|
}
|
||||||
|
// Legacy SSE imm8 shuffles whose names end in W/H (PSHUFLW,
|
||||||
|
// PSHUFHW) must dispatch BEFORE the size-suffix split, and the
|
||||||
|
// others ride along.
|
||||||
|
if m, ok := sseShufTable[upper]; ok {
|
||||||
|
return e.encodeSSEShuf(m, ops)
|
||||||
|
}
|
||||||
|
// Legacy SSE packed binary ops and imm8 shuffles (ADDPS/MULPS/
|
||||||
|
// SHUFPS/PSHUFD/...): no size suffix, dispatch on the full name.
|
||||||
|
if m, ok := sseBinTable[base]; ok {
|
||||||
|
return e.encodeSSEBin(m, ops)
|
||||||
|
}
|
||||||
switch base {
|
switch base {
|
||||||
case "MOV":
|
case "MOV":
|
||||||
return e.encodeMov(ops, size)
|
return e.encodeMov(ops, size)
|
||||||
|
|||||||
@@ -4,6 +4,7 @@
|
|||||||
package asm
|
package asm
|
||||||
|
|
||||||
import (
|
import (
|
||||||
|
"fmt"
|
||||||
"strings"
|
"strings"
|
||||||
"testing"
|
"testing"
|
||||||
|
|
||||||
@@ -312,3 +313,59 @@ func TestScalarErrors(t *testing.T) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// TestSSEBinGroundTruth checks the legacy packed/scalar binary family
|
||||||
|
// byte for byte (no prefix / 66 / F2 / F3 variants).
|
||||||
|
func TestSSEBinGroundTruth(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
mnem string
|
||||||
|
ops []Operand
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{"MULPS X0,X1", "MULPS", []Operand{vreg(t, "X0"), vreg(t, "X1")}, "0f59c8"},
|
||||||
|
{"MULPS (DI),X1", "MULPS", []Operand{Ptr(DI, 0, 16), vreg(t, "X1")}, "0f590f"},
|
||||||
|
{"ADDPD X1,X2", "ADDPD", []Operand{vreg(t, "X1"), vreg(t, "X2")}, "660f58d1"},
|
||||||
|
{"XORPS X0,X0", "XORPS", []Operand{vreg(t, "X0"), vreg(t, "X0")}, "0f57c0"},
|
||||||
|
{"UNPCKLPS X0,X0", "UNPCKLPS", []Operand{vreg(t, "X0"), vreg(t, "X0")}, "0f14c0"},
|
||||||
|
{"MULSD X1,X2", "MULSD", []Operand{vreg(t, "X1"), vreg(t, "X2")}, "f20f59d1"},
|
||||||
|
{"ADDSS (DI),X0", "ADDSS", []Operand{Ptr(DI, 0, 4), vreg(t, "X0")}, "f30f5807"},
|
||||||
|
}
|
||||||
|
for _, c := range cases {
|
||||||
|
code, err := Encode(c.mnem, c.ops...)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%s: %v", c.name, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if got := fmt.Sprintf("%x", code); got != c.want {
|
||||||
|
t.Errorf("%s = %s, want %s", c.name, got, c.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// TestSSEShuffleGroundTruth checks the imm8 shuffle family: immediate
|
||||||
|
// first in Plan 9 order, encoded last on the wire.
|
||||||
|
func TestSSEShuffleGroundTruth(t *testing.T) {
|
||||||
|
cases := []struct {
|
||||||
|
name string
|
||||||
|
mnem string
|
||||||
|
ops []Operand
|
||||||
|
want string
|
||||||
|
}{
|
||||||
|
{"SHUFPS $0,X0,X0", "SHUFPS", []Operand{Imm(0), vreg(t, "X0"), vreg(t, "X0")}, "0fc6c000"},
|
||||||
|
{"SHUFPS $27,X1,X2", "SHUFPS", []Operand{Imm(27), vreg(t, "X1"), vreg(t, "X2")}, "0fc6d11b"},
|
||||||
|
{"PSHUFD $0,X0,X0", "PSHUFD", []Operand{Imm(0), vreg(t, "X0"), vreg(t, "X0")}, "660f70c000"},
|
||||||
|
{"PSHUFLW $3,(DI),X1", "PSHUFLW", []Operand{Imm(3), Ptr(DI, 0, 8), vreg(t, "X1")}, "f20f700f03"},
|
||||||
|
{"PSHUFHW $2,X1,X2", "PSHUFHW", []Operand{Imm(2), vreg(t, "X1"), vreg(t, "X2")}, "f30f70d102"},
|
||||||
|
}
|
||||||
|
for _, c := range cases {
|
||||||
|
code, err := Encode(c.mnem, c.ops...)
|
||||||
|
if err != nil {
|
||||||
|
t.Errorf("%s: %v", c.name, err)
|
||||||
|
continue
|
||||||
|
}
|
||||||
|
if got := fmt.Sprintf("%x", code); got != c.want {
|
||||||
|
t.Errorf("%s = %s, want %s", c.name, got, c.want)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@@ -756,6 +756,90 @@ func (e *enc) encodeSSEMove(m sseMove, ops []Operand) error {
|
|||||||
return e.emit(i)
|
return e.emit(i)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// --- legacy SSE packed binary and shuffles -----------------------------------
|
||||||
|
|
||||||
|
// sseBin describes a legacy (non-VEX) SSE packed/scalar binary op: an
|
||||||
|
// optional mandatory prefix plus the 0F-prefixed opcode. Plan 9 asm
|
||||||
|
// lists the source operand first, so MULPS X0, X1 computes X1 = X1 * X0.
|
||||||
|
type sseBin struct {
|
||||||
|
prefix byte // 0, 0x66, 0xF2 or 0xF3
|
||||||
|
op byte
|
||||||
|
}
|
||||||
|
|
||||||
|
var sseBinTable = map[string]sseBin{
|
||||||
|
"ADDPS": {0, 0x58}, "ADDPD": {0x66, 0x58},
|
||||||
|
"MULPS": {0, 0x59}, "MULPD": {0x66, 0x59},
|
||||||
|
"SUBPS": {0, 0x5C}, "SUBPD": {0x66, 0x5C},
|
||||||
|
"DIVPS": {0, 0x5E}, "DIVPD": {0x66, 0x5E},
|
||||||
|
"ANDPS": {0, 0x54}, "ANDPD": {0x66, 0x54},
|
||||||
|
"ORPS": {0, 0x56}, "ORPD": {0x66, 0x56},
|
||||||
|
"XORPS": {0, 0x57}, "XORPD": {0x66, 0x57},
|
||||||
|
"MINPS": {0, 0x5D}, "MINPD": {0x66, 0x5D},
|
||||||
|
"MAXPS": {0, 0x5F}, "MAXPD": {0x66, 0x5F},
|
||||||
|
"ADDSS": {0xF3, 0x58}, "ADDSD": {0xF2, 0x58},
|
||||||
|
"MULSS": {0xF3, 0x59}, "MULSD": {0xF2, 0x59},
|
||||||
|
"SUBSS": {0xF3, 0x5C}, "SUBSD": {0xF2, 0x5C},
|
||||||
|
"DIVSS": {0xF3, 0x5E}, "DIVSD": {0xF2, 0x5E},
|
||||||
|
"MINSS": {0xF3, 0x5D}, "MINSD": {0xF2, 0x5D},
|
||||||
|
"MAXSS": {0xF3, 0x5F}, "MAXSD": {0xF2, 0x5F},
|
||||||
|
"UNPCKLPS": {0, 0x14}, "UNPCKHPS": {0, 0x15},
|
||||||
|
"UNPCKLPD": {0x66, 0x14}, "UNPCKHPD": {0x66, 0x15},
|
||||||
|
}
|
||||||
|
|
||||||
|
// sseShuf describes a legacy SSE shuffle taking a trailing imm8
|
||||||
|
// (PSHUFD/PSHUFHW/PSHUFLW also carry the packed-int 0x66/F3/F2 prefixes).
|
||||||
|
type sseShuf struct {
|
||||||
|
prefix byte
|
||||||
|
op byte
|
||||||
|
}
|
||||||
|
|
||||||
|
var sseShufTable = map[string]sseShuf{
|
||||||
|
"SHUFPS": {0, 0xC6}, "SHUFPD": {0x66, 0xC6},
|
||||||
|
"PSHUFD": {0x66, 0x70}, "PSHUFHW": {0xF3, 0x70}, "PSHUFLW": {0xF2, 0x70},
|
||||||
|
}
|
||||||
|
|
||||||
|
// encodeSSEBin encodes reg = reg op rm (memory allowed for rm).
|
||||||
|
func (e *enc) encodeSSEBin(m sseBin, ops []Operand) error {
|
||||||
|
if len(ops) != 2 {
|
||||||
|
return fmt.Errorf("SSE binary expects 2 operands, got %d", len(ops))
|
||||||
|
}
|
||||||
|
src, dst := ops[0], ops[1]
|
||||||
|
dstReg, ok := dst.(Reg)
|
||||||
|
if !ok || !dstReg.isVec() {
|
||||||
|
return fmt.Errorf("SSE binary destination must be a vector register")
|
||||||
|
}
|
||||||
|
i := &instr{prefix: m.prefix, opcode: []byte{0x0F, m.op}, modrm: -1, sib: -1}
|
||||||
|
if err := setRM(i, dstReg, src, 8); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
return e.emit(i)
|
||||||
|
}
|
||||||
|
|
||||||
|
// encodeSSEShuf encodes an imm8 shuffle: SHUFPS $imm, src, dst.
|
||||||
|
func (e *enc) encodeSSEShuf(m sseShuf, ops []Operand) error {
|
||||||
|
if len(ops) != 3 {
|
||||||
|
return fmt.Errorf("SSE shuffle expects 3 operands, got %d", len(ops))
|
||||||
|
}
|
||||||
|
imm, ok := ops[0].(Imm)
|
||||||
|
if !ok {
|
||||||
|
return fmt.Errorf("SSE shuffle needs an imm8 first operand")
|
||||||
|
}
|
||||||
|
if imm < -128 || imm > 255 {
|
||||||
|
return fmt.Errorf("SSE shuffle imm8 %d out of range", imm)
|
||||||
|
}
|
||||||
|
src, dst := ops[1], ops[2]
|
||||||
|
dstReg, ok2 := dst.(Reg)
|
||||||
|
if !ok2 || !dstReg.isVec() {
|
||||||
|
return fmt.Errorf("SSE shuffle destination must be a vector register")
|
||||||
|
}
|
||||||
|
i := &instr{prefix: m.prefix, opcode: []byte{0x0F, m.op}, modrm: -1, sib: -1}
|
||||||
|
if err := setRM(i, dstReg, src, 8); err != nil {
|
||||||
|
return err
|
||||||
|
}
|
||||||
|
i.imm = []byte{byte(int8(imm))}
|
||||||
|
return e.emit(i)
|
||||||
|
}
|
||||||
|
|
||||||
// --- CVTSL2SD / CVTSQ2SD -----------------------------------------------------
|
// --- CVTSL2SD / CVTSQ2SD -----------------------------------------------------
|
||||||
|
|
||||||
// encodeCvtsi2sd encodes a signed integer to scalar double conversion
|
// encodeCvtsi2sd encodes a signed integer to scalar double conversion
|
||||||
|
|||||||
Reference in New Issue
Block a user