feat(asm): add vpcmp compare, full opmask set, legacy sse integers and bswap
Test / vet (push) Successful in 47s
Test / test (push) Successful in 2m35s
Test / build (push) Successful in 41s

Assisted-by: GLM 5.3
This commit is contained in:
2026-08-27 22:41:03 +02:00
parent 163480e283
commit 19a26e049b
5 changed files with 170 additions and 32 deletions
+71 -27
View File
@@ -48,15 +48,21 @@ func (e *enc) encodeMov(ops []Operand, size int) error {
}
src, dst := ops[0], ops[1]
// MOVQ with an XMM operand is the SSE2 packed-quadword move, NOT a
// GPR move: mem→xmm and xmm↔xmm encode as F3 0F 7E (reg = dst),
// xmm→mem as 66 0F D6 (rm = xmm). A GPR-move fallback would
// silently emit REX.W 8B with the wrong operand meaning.
// Integer scalar XMM moves: MOVQ with an XMM operand is the SSE2
// packed-quadword move, NOT a GPR move: mem→xmm encodes as F3 0F 7E
// (reg = dst, no REX.W — the Go assembler's form), xmm→mem as
// 66 0F D6 (rm = xmm). MOVL is the packed-dword move instead:
// 66 0F 6E load, 66 0F 7E store. A GPR-move fallback would silently
// emit REX.W 8B with the wrong operand meaning.
_, srcVec := vecReg(src)
dstReg, dstVec := vecReg(dst)
if srcVec || dstVec {
if dstVec {
i := &instr{prefix: 0xF3, opcode: []byte{0x0F, 0x7E}, modrm: -1, sib: -1}
if size == 4 {
i.prefix = 0x66
i.opcode = []byte{0x0F, 0x6E}
}
if err := setRM(i, dstReg, src, 8); err != nil {
return err
}
@@ -64,9 +70,12 @@ func (e *enc) encodeMov(ops []Operand, size int) error {
}
srcXMM, srcIsXMM := src.(Reg)
if !srcIsXMM || !srcXMM.isVec() {
return fmt.Errorf("MOVQ: store needs an XMM source")
return fmt.Errorf("MOV: store needs an XMM source")
}
i := &instr{prefix: 0x66, opcode: []byte{0x0F, 0xD6}, modrm: -1, sib: -1}
if size == 4 {
i.opcode = []byte{0x0F, 0x7E}
}
if err := setRM(i, srcXMM, dst, 8); err != nil {
return err
}
@@ -682,6 +691,21 @@ func (e *enc) encodeCount(base string, ops []Operand, size int) error {
return e.emit(i)
}
// encodeBswap encodes BSWAP: the single register operand is encoded in the
// opcode byte (0F C8+r), with REX.B for R8-R15 and REX.W for the quad form.
func (e *enc) encodeBswap(ops []Operand, size int) error {
if len(ops) != 1 {
return fmt.Errorf("BSWAP expects 1 operand, got %d", len(ops))
}
reg, ok := ops[0].(Reg)
if !ok {
return fmt.Errorf("BSWAP operand must be a register")
}
i := newInstr(size, []byte{0x0F, 0xC8 + byte(reg.idx&7)})
i.rexB = reg.idx >= 8
return e.emit(i)
}
// --- mixed-width sign/zero-extending moves -----------------------------------
// movExtendOp maps Go's mixed-width move names to their opcode and destination
@@ -784,33 +808,49 @@ func (e *enc) encodeSSEMove(m sseMove, ops []Operand) error {
// --- 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.
// optional mandatory prefix plus the 0F-prefixed opcode (0F38 for the
// SSSE3 integer shuffles). 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
map38 bool // opcode lives under 0F38 instead of 0F
}
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},
"CVTSS2SD": {0xF3, 0x5A}, "CVTSD2SS": {0xF2, 0x5A},
"CVTPS2PD": {0, 0x5A}, "CVTPD2PS": {0x66, 0x5A},
"ADDPS": {0, 0x58, false}, "ADDPD": {0x66, 0x58, false},
"MULPS": {0, 0x59, false}, "MULPD": {0x66, 0x59, false},
"SUBPS": {0, 0x5C, false}, "SUBPD": {0x66, 0x5C, false},
"DIVPS": {0, 0x5E, false}, "DIVPD": {0x66, 0x5E, false},
"ANDPS": {0, 0x54, false}, "ANDPD": {0x66, 0x54, false},
"ORPS": {0, 0x56, false}, "ORPD": {0x66, 0x56, false},
"XORPS": {0, 0x57, false}, "XORPD": {0x66, 0x57, false},
"MINPS": {0, 0x5D, false}, "MINPD": {0x66, 0x5D, false},
"MAXPS": {0, 0x5F, false}, "MAXPD": {0x66, 0x5F, false},
"ADDSS": {0xF3, 0x58, false}, "ADDSD": {0xF2, 0x58, false},
"MULSS": {0xF3, 0x59, false}, "MULSD": {0xF2, 0x59, false},
"SUBSS": {0xF3, 0x5C, false}, "SUBSD": {0xF2, 0x5C, false},
"DIVSS": {0xF3, 0x5E, false}, "DIVSD": {0xF2, 0x5E, false},
"MINSS": {0xF3, 0x5D, false}, "MINSD": {0xF2, 0x5D, false},
"MAXSS": {0xF3, 0x5F, false}, "MAXSD": {0xF2, 0x5F, false},
"UNPCKLPS": {0, 0x14, false}, "UNPCKHPS": {0, 0x15, false},
"UNPCKLPD": {0x66, 0x14, false}, "UNPCKHPD": {0x66, 0x15, false},
"CVTSS2SD": {0xF3, 0x5A, false}, "CVTSD2SS": {0xF2, 0x5A, false},
"CVTPS2PD": {0, 0x5A, false}, "CVTPD2PS": {0x66, 0x5A, false},
// SSE2 packed integers (reg = reg op rm) and the SSSE3 byte shuffle.
"PXOR": {0x66, 0xEF, false},
"POR": {0x66, 0xEB, false},
"PAND": {0x66, 0xDB, false},
"PANDN": {0x66, 0xDF, false},
"PADDB": {0x66, 0xFC, false}, "PADDW": {0x66, 0xFD, false},
"PADDD": {0x66, 0xFE, false}, "PADDQ": {0x66, 0xD4, false},
"PSUBB": {0x66, 0xF8, false}, "PSUBW": {0x66, 0xF9, false},
"PSUBD": {0x66, 0xFA, false}, "PSUBQ": {0x66, 0xFB, false},
"PCMPEQB": {0x66, 0x74, false}, "PCMPEQW": {0x66, 0x75, false},
"PCMPEQD": {0x66, 0x76, false},
"PCMPGTB": {0x66, 0x64, false}, "PCMPGTW": {0x66, 0x65, false},
"PCMPGTD": {0x66, 0x66, false},
"PSHUFB": {0x66, 0x00, true},
}
// sseShuf describes a legacy SSE shuffle taking a trailing imm8
@@ -835,7 +875,11 @@ func (e *enc) encodeSSEBin(m sseBin, ops []Operand) error {
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}
opcode := []byte{0x0F, m.op}
if m.map38 {
opcode = []byte{0x0F, 0x38, m.op}
}
i := &instr{prefix: m.prefix, opcode: opcode, modrm: -1, sib: -1}
if err := setRM(i, dstReg, src, 8); err != nil {
return err
}