feat(asm): add the EVEX floating-point and conversion set
Assisted-by: Qwen 3.8 Max Preview
This commit is contained in:
+84
@@ -43,6 +43,13 @@ const (
|
||||
// in ModRM.reg and the destination in r/m — the layout of the EVEX
|
||||
// narrowing stores (VPMOVDW, VPMOVQD).
|
||||
vexRMRev
|
||||
// vexRMSrcLen is the two-operand conversion form `OP src, dst` whose
|
||||
// vector length follows the source: the packed-double → dword
|
||||
// conversions (VCVTPD2DQ/VCVTTPD2DQ and their X/Y spellings) narrow into
|
||||
// an XMM destination, so the L bit rides with the wider source. The
|
||||
// mnemonic's spelling fixes the length (X = 128, Y = 256), which also
|
||||
// covers a memory source. ModRM.reg = dst, ModRM.rm = src, no vvvv.
|
||||
vexRMSrcLen
|
||||
// vexZero is the no-operand form (VZEROUPPER).
|
||||
vexZero
|
||||
)
|
||||
@@ -86,12 +93,29 @@ var vexTable = map[string]vexSpec{
|
||||
// VEX.128/256.66.0F.WIG — packed double-precision arithmetic / logic.
|
||||
"VADDPD": {1, 0x58, 0, 1, -1, vexNDS3},
|
||||
"VMULPD": {1, 0x59, 0, 1, -1, vexNDS3},
|
||||
"VSUBPD": {1, 0x5C, 0, 1, -1, vexNDS3},
|
||||
"VDIVPD": {1, 0x5E, 0, 1, -1, vexNDS3},
|
||||
"VMINPD": {1, 0x5D, 0, 1, -1, vexNDS3},
|
||||
"VMAXPD": {1, 0x5F, 0, 1, -1, vexNDS3},
|
||||
"VXORPD": {1, 0x57, 0, 1, -1, vexNDS3},
|
||||
"VUNPCKHPD": {1, 0x15, 0, 1, -1, vexNDS3},
|
||||
"VUNPCKLPD": {1, 0x14, 0, 1, -1, vexNDS3},
|
||||
// VEX.128.F2.0F.WIG — scalar double-precision arithmetic (the packed
|
||||
// opcodes with an F2 pp).
|
||||
"VADDSD": {1, 0x58, 0, 3, -1, vexNDS3},
|
||||
"VSUBSD": {1, 0x5C, 0, 3, -1, vexNDS3},
|
||||
"VMULSD": {1, 0x59, 0, 3, -1, vexNDS3},
|
||||
"VDIVSD": {1, 0x5E, 0, 3, -1, vexNDS3},
|
||||
"VMINSD": {1, 0x5D, 0, 3, -1, vexNDS3},
|
||||
"VMAXSD": {1, 0x5F, 0, 3, -1, vexNDS3},
|
||||
// VEX.128.F3.0F.WIG — scalar single-precision arithmetic (the packed
|
||||
// opcodes with an F3 pp).
|
||||
"VADDSS": {1, 0x58, 0, 2, -1, vexNDS3},
|
||||
"VSUBSS": {1, 0x5C, 0, 2, -1, vexNDS3},
|
||||
"VMULSS": {1, 0x59, 0, 2, -1, vexNDS3},
|
||||
"VDIVSS": {1, 0x5E, 0, 2, -1, vexNDS3},
|
||||
"VMINSS": {1, 0x5D, 0, 2, -1, vexNDS3},
|
||||
"VMAXSS": {1, 0x5F, 0, 2, -1, vexNDS3},
|
||||
// VEX.128/256.66.0F38.W1 — fused multiply-add (NDS form).
|
||||
"VFMADD231PD": {2, 0xB8, 1, 1, -1, vexNDS3},
|
||||
|
||||
@@ -105,6 +129,19 @@ var vexTable = map[string]vexSpec{
|
||||
// VEX.128/256.F3.0F.WIG — signed dword to packed double conversion
|
||||
// (reg=dst, rm=src, no vvvv; the length follows the destination).
|
||||
"VCVTDQ2PD": {1, 0xE6, 0, 2, -1, vexRM},
|
||||
// VEX.128/256.0F.WIG — signed dword to packed single conversion
|
||||
// (reg=dst, rm=src, no vvvv, no mandatory prefix).
|
||||
"VCVTDQ2PS": {1, 0x5B, 0, 0, -1, vexRM},
|
||||
// VEX.128/256.0F.WIG — packed single to packed double conversion
|
||||
// (reg=dst, rm=src; the destination is the wide operand and sets the
|
||||
// length). Intel's maps prescribe the F3 prefix here (VEX.pp = 10), but
|
||||
// the Go assembler emits the instruction with pp = 00, and gasm follows
|
||||
// the Go assembler's bytes — its machine code is the oracle, not the
|
||||
// manual.
|
||||
"VCVTPS2PD": {1, 0x5A, 0, 0, -1, vexRM},
|
||||
// VEX.128.F2.0F.WIG — duplicate the low double of each 128-bit lane
|
||||
// (reg=dst, rm=src, no vvvv; the length follows the destination).
|
||||
"VMOVDDUP": {1, 0x12, 0, 3, -1, vexRM},
|
||||
// VEX.128/256.66.0F.WIG — move mask to a GPR (reg=gpr dst, rm=vec src).
|
||||
"VPMOVMSKB": {1, 0xD7, 0, 1, -1, vexRM},
|
||||
"VMOVMSKPS": {1, 0x50, 0, 0, -1, vexRM}, // no 66 prefix (that would be VMOVMSKPD)
|
||||
@@ -138,6 +175,25 @@ var vexTable = map[string]vexSpec{
|
||||
|
||||
// VEX.128.0F.W0 — mask-register test (KTESTW k1, k2: reg = dst, rm = src).
|
||||
"KTESTW": {1, 0x99, 0, 0, -1, vexRM},
|
||||
|
||||
// VEX.F2.0F — packed double to packed dword conversions, truncating and
|
||||
// non-truncating. The destination is always XMM; the X/Y spellings fix
|
||||
// the source length (XMM/YMM), and VEX.L follows it — see vexSrcLen.
|
||||
"VCVTPD2DQX": {1, 0xE6, 0, 3, -1, vexRMSrcLen},
|
||||
"VCVTPD2DQY": {1, 0xE6, 0, 3, -1, vexRMSrcLen},
|
||||
"VCVTTPD2DQX": {1, 0xE6, 0, 1, -1, vexRMSrcLen},
|
||||
"VCVTTPD2DQY": {1, 0xE6, 0, 1, -1, vexRMSrcLen},
|
||||
}
|
||||
|
||||
// vexSrcLen maps a source-length conversion mnemonic (the X/Y spellings of
|
||||
// the packed-double → dword conversions) to its fixed vector length:
|
||||
// X = 128 (L = 0), Y = 256 (L = 1). The spelling fixes the length even for
|
||||
// a memory source, matching the Go assembler's ytab.
|
||||
var vexSrcLen = map[string]int{
|
||||
"VCVTPD2DQX": 0,
|
||||
"VCVTPD2DQY": 1,
|
||||
"VCVTTPD2DQX": 0,
|
||||
"VCVTTPD2DQY": 1,
|
||||
}
|
||||
|
||||
// vexVarShift maps the shift mnemonics to their variable-count opcode — the
|
||||
@@ -230,6 +286,8 @@ func (e *enc) encodeVex(mnemUpper string, ops []Operand) error {
|
||||
return e.encodeVexNDS3Imm(spec, ops)
|
||||
case vexExtract:
|
||||
return e.encodeVexExtract(spec, ops)
|
||||
case vexRMSrcLen:
|
||||
return e.encodeVexRMSrcLen(mnemUpper, spec, ops)
|
||||
case vexZero:
|
||||
return e.encodeVexZero(mnemUpper, spec, ops)
|
||||
}
|
||||
@@ -295,6 +353,32 @@ func (e *enc) encodeVexRM(spec vexSpec, ops []Operand) error {
|
||||
return e.emitVexFields(spec, l, regField, rBit, 15, src)
|
||||
}
|
||||
|
||||
// encodeVexRMSrcLen encodes a length-narrowing conversion: OP src, dst with
|
||||
// the destination always XMM and the VEX.L bit following the source — fixed
|
||||
// by the mnemonic's spelling (VCVTPD2DQX = 128, VCVTPD2DQY = 256) even when
|
||||
// the source is memory.
|
||||
func (e *enc) encodeVexRMSrcLen(mnem string, spec vexSpec, ops []Operand) error {
|
||||
if len(ops) != 2 {
|
||||
return fmt.Errorf("conversion expects 2 operands, got %d", len(ops))
|
||||
}
|
||||
src, dst := ops[0], ops[1]
|
||||
dstReg, ok := dst.(Reg)
|
||||
if !ok || !dstReg.isVec() {
|
||||
return fmt.Errorf("VEX destination must be a vector register")
|
||||
}
|
||||
ll, ok := vexSrcLen[mnem]
|
||||
if !ok {
|
||||
return fmt.Errorf("no fixed vector length for %s", mnem)
|
||||
}
|
||||
regField := dstReg.idx & 7
|
||||
rBit := 0
|
||||
if dstReg.idx >= 8 {
|
||||
rBit = 1
|
||||
}
|
||||
// An unused vvvv field must be stored as all ones (v̄vvv = 1111).
|
||||
return e.emitVexFields(spec, ll, regField, rBit, 15, src)
|
||||
}
|
||||
|
||||
// encodeVexShiftImm encodes an immediate-shift instruction: OP $imm, src, dst.
|
||||
// The destination is carried in VEX.vvvv, the source in ModRM.rm, and the
|
||||
// shift kind in the ModRM.reg /digit.
|
||||
|
||||
Reference in New Issue
Block a user