feat(asm): encode the amd64 and loong64 tails of the corpus testdata
Assisted-by: GLM 5.3
This commit is contained in:
+200
-7
@@ -90,6 +90,40 @@ var noOperandTable = map[string][]byte{
|
||||
"LOCK": {0xF0},
|
||||
"REP": {0xF3},
|
||||
"REPN": {0xF2},
|
||||
"ENDBR64": {0xF3, 0x0F, 0x1E, 0xFA},
|
||||
}
|
||||
|
||||
// sysUnaryTable maps the one-operand system instructions to their bytes:
|
||||
// the prefix, the opcode and the /digit the reg field carries. The operand
|
||||
// is a register or memory in r/m.
|
||||
var sysUnaryTable = map[string]struct {
|
||||
prefix byte
|
||||
opcode []byte
|
||||
digit int
|
||||
}{
|
||||
"CLWB": {0x66, []byte{0x0F, 0xAE}, 6},
|
||||
"TPAUSE": {0x66, []byte{0x0F, 0xAE}, 6},
|
||||
"UMONITOR": {0xF3, []byte{0x0F, 0xAE}, 6},
|
||||
"UMWAIT": {0xF2, []byte{0x0F, 0xAE}, 6},
|
||||
"RDPID": {0xF3, []byte{0x0F, 0xC7}, 7},
|
||||
"CLDEMOTE": {0x00, []byte{0x0F, 0x1C}, 0},
|
||||
}
|
||||
|
||||
// encodeSysUnary emits a one-operand system instruction: the operand in r/m
|
||||
// under the fixed /digit, no REX.W.
|
||||
func (e *enc) encodeSysUnary(mnem string, m struct {
|
||||
prefix byte
|
||||
opcode []byte
|
||||
digit int
|
||||
}, ops []Operand) error {
|
||||
if len(ops) != 1 {
|
||||
return fmt.Errorf("%s expects 1 operand, got %d", mnem, len(ops))
|
||||
}
|
||||
i := &instr{prefix: m.prefix, opcode: m.opcode, modrm: -1, sib: -1}
|
||||
if err := setRMDigit(i, m.digit, ops[0], 8); err != nil {
|
||||
return err
|
||||
}
|
||||
return e.emit(i)
|
||||
}
|
||||
|
||||
// --- MOV --------------------------------------------------------------------
|
||||
@@ -111,6 +145,76 @@ func (e *enc) encodeMov(ops []Operand, size int) error {
|
||||
// silently emit REX.W 8B with the wrong operand meaning.
|
||||
_, srcVec := vecReg(src)
|
||||
dstReg, dstVec := vecReg(dst)
|
||||
|
||||
// Control and debug register moves: 0F 20 (CRn→r64), 0F 22 (r64→CRn),
|
||||
// 0F 21 (DRn→r64) and 0F 23 (r64→DRn). The CR/DR number rides the reg
|
||||
// field, the general register r/m; CR8+/DR8+ take REX.R.
|
||||
if c, ok := src.(Reg); ok && c.ctl != 0 {
|
||||
g, ok := dst.(Reg)
|
||||
if !ok || g.isVec() || g.ctl != 0 {
|
||||
return fmt.Errorf("MOV: control/debug register load needs a general register destination")
|
||||
}
|
||||
opc := byte(0x20)
|
||||
if c.ctl == 2 {
|
||||
opc = 0x21
|
||||
}
|
||||
return e.emit(&instr{
|
||||
opcode: []byte{0x0F, opc},
|
||||
modrm: 0xC0 | (c.idx&7)<<3 | (g.idx & 7),
|
||||
sib: -1, rexR: c.idx >= 8, rexB: g.idx >= 8,
|
||||
})
|
||||
}
|
||||
if c, ok := dst.(Reg); ok && c.ctl != 0 {
|
||||
g, ok := src.(Reg)
|
||||
if !ok || g.isVec() || g.ctl != 0 {
|
||||
return fmt.Errorf("MOV: control/debug register store needs a general register source")
|
||||
}
|
||||
opc := byte(0x22)
|
||||
if c.ctl == 2 {
|
||||
opc = 0x23
|
||||
}
|
||||
return e.emit(&instr{
|
||||
opcode: []byte{0x0F, opc},
|
||||
modrm: 0xC0 | (c.idx&7)<<3 | (g.idx & 7),
|
||||
sib: -1, rexR: c.idx >= 8, rexB: g.idx >= 8,
|
||||
})
|
||||
}
|
||||
|
||||
// MMX register moves: MOVQ M0, mem and MOVQ mem, M0 are the MMX
|
||||
// load/store pair 0F 6F/0F 7F (no prefix); a register pair takes the
|
||||
// load opcode. The XMM MOVQ forms follow below.
|
||||
if m, ok := src.(Reg); ok && m.mmx {
|
||||
switch d := dst.(type) {
|
||||
case Reg:
|
||||
if !d.mmx {
|
||||
return fmt.Errorf("MOV: MMX register moves stay inside the M bank")
|
||||
}
|
||||
i := &instr{opcode: []byte{0x0F, 0x6F}, modrm: -1, sib: -1}
|
||||
if err := setRM(i, d, src, 8); err != nil {
|
||||
return err
|
||||
}
|
||||
return e.emit(i)
|
||||
case Mem:
|
||||
i := &instr{opcode: []byte{0x0F, 0x7F}, modrm: -1, sib: -1}
|
||||
if err := setRM(i, m, d, 8); err != nil {
|
||||
return err
|
||||
}
|
||||
return e.emit(i)
|
||||
}
|
||||
return fmt.Errorf("MOV: invalid MMX destination")
|
||||
}
|
||||
if m, ok := dst.(Reg); ok && m.mmx {
|
||||
srcM, ok := src.(Mem)
|
||||
if !ok {
|
||||
return fmt.Errorf("MOV: MMX load takes a memory source")
|
||||
}
|
||||
i := &instr{opcode: []byte{0x0F, 0x6F}, modrm: -1, sib: -1}
|
||||
if err := setRM(i, m, srcM, 8); err != nil {
|
||||
return err
|
||||
}
|
||||
return e.emit(i)
|
||||
}
|
||||
|
||||
if srcVec || dstVec {
|
||||
if dstVec {
|
||||
if g, ok := src.(Reg); ok && !g.isVec() {
|
||||
@@ -518,6 +622,14 @@ func (e *enc) encodeLea(ops []Operand, size int) error {
|
||||
default:
|
||||
return fmt.Errorf("LEA: source must be a memory operand")
|
||||
}
|
||||
// LEA accepts the full unsigned 32-bit displacement span where the
|
||||
// loads and stores reject it beyond the signed one; the wide values
|
||||
// ride the same disp32 bytes as their two's-complement bit pattern.
|
||||
if m, ok := src.(Mem); ok && m.Disp >= 1<<31 && m.Disp <= (1<<32)-1 {
|
||||
c := m
|
||||
c.Disp = int64(int32(uint32(m.Disp)))
|
||||
src = c
|
||||
}
|
||||
i := newInstr(size, []byte{0x8D})
|
||||
if err := setRM(i, dstReg, src, size); err != nil {
|
||||
return err
|
||||
@@ -663,6 +775,18 @@ func (e *enc) encodeDoubleShift(base string, ops []Operand, size int) error {
|
||||
|
||||
func (e *enc) encodeImul(ops []Operand, size int) error {
|
||||
switch len(ops) {
|
||||
case 1:
|
||||
// The one-operand form, IMUL r/m: F6/F7 /5 with AL/AX/EAX/RAX as the
|
||||
// implied destination (the toolchain's one-register shape).
|
||||
opc := byte(0xF7)
|
||||
if size == 1 {
|
||||
opc = 0xF6
|
||||
}
|
||||
i := newInstr(size, []byte{opc})
|
||||
if err := setRMDigit(i, 5, ops[0], size); err != nil {
|
||||
return err
|
||||
}
|
||||
return e.emit(i)
|
||||
case 2:
|
||||
// Two shapes. The leading-immediate spelling IMUL $imm, r multiplies
|
||||
// r in place (dst = rm = r): the shape GOROOT's clock code writes.
|
||||
@@ -697,7 +821,7 @@ func (e *enc) encodeImul(ops []Operand, size int) error {
|
||||
// r/m operand (setRM takes registers and memory alike).
|
||||
return e.encodeImulImm(imm, ops[1], dstReg, size)
|
||||
}
|
||||
return fmt.Errorf("IMUL expects 2 or 3 operands, got %d", len(ops))
|
||||
return fmt.Errorf("IMUL expects 1, 2 or 3 operands, got %d", len(ops))
|
||||
}
|
||||
|
||||
// encodeImulImm emits the immediate multiply: 0x6B with a sign-extended imm8
|
||||
@@ -742,6 +866,27 @@ func (e *enc) encodePushPop(ops []Operand, size int, push bool) error {
|
||||
w16 := size == 2
|
||||
switch op := ops[0].(type) {
|
||||
case Reg:
|
||||
// Segment registers: FS and GS carry their own one-byte opcodes
|
||||
// under 0F (A0/A8 push, A1/A9 pop); the other four spellings are
|
||||
// not pushable in 64-bit mode.
|
||||
if n, isSeg := op.segNumber(); isSeg {
|
||||
switch n {
|
||||
case 4: // FS
|
||||
if push {
|
||||
return e.emit(&instr{opcode: []byte{0x0F, 0xA0}, modrm: -1, sib: -1})
|
||||
}
|
||||
return e.emit(&instr{opcode: []byte{0x0F, 0xA1}, modrm: -1, sib: -1})
|
||||
case 5: // GS
|
||||
if push {
|
||||
return e.emit(&instr{opcode: []byte{0x0F, 0xA8}, modrm: -1, sib: -1})
|
||||
}
|
||||
return e.emit(&instr{opcode: []byte{0x0F, 0xA9}, modrm: -1, sib: -1})
|
||||
}
|
||||
return fmt.Errorf("PUSH/POP: only FS and GS are encodable in 64-bit mode")
|
||||
}
|
||||
if op.mmx || op.isVec() || op.fp || op.ctl != 0 {
|
||||
return fmt.Errorf("PUSH/POP: invalid register operand")
|
||||
}
|
||||
base := byte(0x50) // PUSH r; POP is 0x58
|
||||
if !push {
|
||||
base = 0x58
|
||||
@@ -1093,6 +1238,38 @@ var sseMoveTable = map[string]sseMove{
|
||||
"MOVSS": {0xF3, 0x10, 0x11}, // scalar single
|
||||
}
|
||||
|
||||
// sseStoreOnly holds the store-only SSE forms, OP xmm, mem: the XMM register
|
||||
// rides the reg field and memory r/m (the non-temporal store).
|
||||
var sseStoreOnly = map[string]struct {
|
||||
prefix byte
|
||||
op byte
|
||||
}{
|
||||
"MOVNTDQ": {0x66, 0xE7},
|
||||
}
|
||||
|
||||
// encodeSSEStoreOnly encodes OP xmm, mem (reg = the XMM source, r/m = the
|
||||
// destination memory).
|
||||
func (e *enc) encodeSSEStoreOnly(mnem string, m struct {
|
||||
prefix byte
|
||||
op byte
|
||||
}, ops []Operand) error {
|
||||
if len(ops) != 2 {
|
||||
return fmt.Errorf("%s expects 2 operands, got %d", mnem, len(ops))
|
||||
}
|
||||
srcReg, ok := ops[0].(Reg)
|
||||
if !ok || !srcReg.isVec() {
|
||||
return fmt.Errorf("%s source must be a vector register", mnem)
|
||||
}
|
||||
if !isX86Mem(ops[1]) {
|
||||
return fmt.Errorf("%s destination must be a memory operand", mnem)
|
||||
}
|
||||
i := &instr{prefix: m.prefix, opcode: []byte{0x0F, m.op}, modrm: -1, sib: -1}
|
||||
if err := setRM(i, srcReg, ops[1], 8); err != nil {
|
||||
return err
|
||||
}
|
||||
return e.emit(i)
|
||||
}
|
||||
|
||||
// encodeSSEMove encodes a legacy SSE move: a vector-to-vector move uses the
|
||||
// load form (reg = destination), matching the Go assembler.
|
||||
func (e *enc) encodeSSEMove(m sseMove, ops []Operand) error {
|
||||
@@ -1308,14 +1485,23 @@ func (e *enc) encodeSSEBin(m sseBin, ops []Operand) error {
|
||||
}
|
||||
src, dst := ops[0], ops[1]
|
||||
dstReg, ok := dst.(Reg)
|
||||
if !ok || !dstReg.isVec() {
|
||||
if !ok || (!dstReg.isVec() && !dstReg.mmx) {
|
||||
return fmt.Errorf("SSE binary destination must be a vector register")
|
||||
}
|
||||
// The MMX twins of the packed-integer SSE2 ops drop the 0x66 prefix:
|
||||
// PADDD M2, M1 is 0F FE where the XMM form is 66 0F FE.
|
||||
prefix := m.prefix
|
||||
if dstReg.mmx {
|
||||
if prefix != 0x66 {
|
||||
return fmt.Errorf("SSE binary: this form takes no MMX register operand")
|
||||
}
|
||||
prefix = 0
|
||||
}
|
||||
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}
|
||||
i := &instr{prefix: prefix, opcode: opcode, modrm: -1, sib: -1}
|
||||
if err := setRM(i, dstReg, src, 8); err != nil {
|
||||
return err
|
||||
}
|
||||
@@ -1791,12 +1977,19 @@ func (e *enc) encodeSSEShift(name string, ops []Operand) error {
|
||||
// immediate LAST in Plan 9 order (src, dst, $imm), unlike the shuffle family:
|
||||
// F2 0F C2 with reg = dst, rm = src.
|
||||
func (e *enc) encodeCmpsd(ops []Operand) error {
|
||||
return e.encodeSSECmp("CMPSD", 0xF2, ops)
|
||||
}
|
||||
|
||||
// encodeSSECmp encodes the SSE compare family (CMPSD/CMPSS/CMPPS/CMPPD):
|
||||
// 0F C2 /r ib with the predicate immediate last in Plan 9 order
|
||||
// (src, dst, $imm) and the packed forms' prefixes.
|
||||
func (e *enc) encodeSSECmp(mnem string, prefix byte, ops []Operand) error {
|
||||
if len(ops) != 3 {
|
||||
return fmt.Errorf("CMPSD expects 3 operands (src, dst, $imm), got %d", len(ops))
|
||||
return fmt.Errorf("%s expects 3 operands (src, dst, $imm), got %d", mnem, len(ops))
|
||||
}
|
||||
imm, ok := ops[2].(Imm)
|
||||
if !ok {
|
||||
return fmt.Errorf("CMPSD predicate must be an immediate")
|
||||
return fmt.Errorf("%s predicate must be an immediate", mnem)
|
||||
}
|
||||
immByte, err := imm8(int64(imm))
|
||||
if err != nil {
|
||||
@@ -1804,9 +1997,9 @@ func (e *enc) encodeCmpsd(ops []Operand) error {
|
||||
}
|
||||
dstReg, ok2 := ops[1].(Reg)
|
||||
if !ok2 || !dstReg.isVec() {
|
||||
return fmt.Errorf("CMPSD destination must be a vector register")
|
||||
return fmt.Errorf("%s destination must be a vector register", mnem)
|
||||
}
|
||||
i := &instr{prefix: 0xF2, opcode: []byte{0x0F, 0xC2}, modrm: -1, sib: -1}
|
||||
i := &instr{prefix: prefix, opcode: []byte{0x0F, 0xC2}, modrm: -1, sib: -1}
|
||||
if err := setRM(i, dstReg, ops[0], 8); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user