The x87 stack controls, the D8/DC arithmetic pair, the conditional moves, the register compares, FADDDP, the memory loads and the FXSAVE pair, each pinned byte for byte against go tool asm through every corpus line the toolchain's own amd64enc.s carries for the family (78 lines). Assisted-by: GLM 5.3 Flash
238 lines
7.5 KiB
Go
238 lines
7.5 KiB
Go
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
|
|
|
package asm
|
|
|
|
import "fmt"
|
|
|
|
// This file implements the x87 floating-point family the Go assembler
|
|
// carries: the no-operand stack controls, the two-register arithmetic pair,
|
|
// the register compares, the memory loads and stores and the FXSAVE pair.
|
|
// Every encoding here is pinned byte for byte against go tool asm through
|
|
// the corpus lines in amd64_x87_test.go.
|
|
|
|
// x87NoOperand maps the no-operand x87 instruction to its postfix byte
|
|
// inside the D9 escape: D9 <postfix>, no ModR/M, no operand.
|
|
var x87NoOperand = map[string]byte{
|
|
"F2XM1": 0xF0,
|
|
"FABS": 0xE1,
|
|
"FCHS": 0xE0,
|
|
"FCOS": 0xFF,
|
|
"FDECSTP": 0xF6,
|
|
"FINCSTP": 0xF7,
|
|
"FLD1": 0xE8,
|
|
"FLDL2E": 0xEA,
|
|
"FLDL2T": 0xE9,
|
|
"FLDLG2": 0xEC,
|
|
"FLDPI": 0xEB,
|
|
"FNOP": 0xD0,
|
|
"FPATAN": 0xF3,
|
|
"FPREM": 0xF8,
|
|
"FPREM1": 0xF5,
|
|
"FPTAN": 0xF2,
|
|
"FRNDINT": 0xFC,
|
|
"FSCALE": 0xFD,
|
|
"FSIN": 0xFE,
|
|
"FSINCOS": 0xFB,
|
|
"FSQRT": 0xFA,
|
|
"FTST": 0xE4,
|
|
"FXAM": 0xE5,
|
|
"FXTRACT": 0xF4,
|
|
"FYL2X": 0xF1,
|
|
"FYL2XP1": 0xF9,
|
|
}
|
|
|
|
// x87ArithSpec describes one member of the D8/DC two-register arithmetic
|
|
// pair. The D8 form reads ST(0) as its second operand (FADDD F2, F0), the
|
|
// DC form ST(0) as its first (FADDD F0, F2) or a memory source (FADDD (BX),
|
|
// F0). FDIV is the odd member: with ST(0) as the first operand the toolchain
|
|
// assembles the reversed register form (DC F8+i, the FDIVR digit), so the DC
|
|
// digit differs from the memory digit there.
|
|
type x87ArithSpec struct {
|
|
d8Digit int // the /digit of the D8 form (dst = ST(0))
|
|
dcDigit int // the /digit of the DC register form (src = ST(0))
|
|
memDigit int // the /digit of the DC memory form (dst = ST(0))
|
|
}
|
|
|
|
// x87Arith maps the arithmetic mnemonics to their digits.
|
|
var x87Arith = map[string]x87ArithSpec{
|
|
"FADDD": {0, 0, 0},
|
|
"FCOMD": {2, 2, 2},
|
|
"FDIVD": {6, 7, 6},
|
|
}
|
|
|
|
// x87FCmov maps the conditional x87 moves to their escape byte and postfix
|
|
// base (the C0/C8/D0/D8 group the condition selects); the compared register
|
|
// rides the postfix's low three bits.
|
|
var x87FCmov = map[string][2]byte{
|
|
"FCMOVB": {0xDA, 0xC0},
|
|
"FCMOVBE": {0xDA, 0xD0},
|
|
"FCMOVE": {0xDA, 0xC8},
|
|
"FCMOVNB": {0xDB, 0xC0},
|
|
"FCMOVNBE": {0xDB, 0xD0},
|
|
"FCMOVNE": {0xDB, 0xC8},
|
|
"FCMOVNU": {0xDB, 0xD8},
|
|
"FCMOVU": {0xDA, 0xD8},
|
|
}
|
|
|
|
// x87Compare maps the register compare pair to their escape byte; the
|
|
// register form is escape F0+i (mod 11, reg 110, rm = the compared stack
|
|
// register), ST(0) fixed as the second operand.
|
|
var x87Compare = map[string]byte{
|
|
"FCOMI": 0xDB,
|
|
"FCOMIP": 0xDF,
|
|
}
|
|
|
|
// x87MemUnary maps the one-memory-operand x87 controls to their escape byte
|
|
// and /digit.
|
|
var x87MemUnary = map[string]struct {
|
|
escape byte
|
|
digit int
|
|
}{
|
|
"FBLD": {0xDF, 4},
|
|
"FBSTP": {0xDF, 6},
|
|
"FLDCW": {0xD9, 5},
|
|
}
|
|
|
|
// x87Fxsav maps the FXSAVE pair to their /digit in the 0F AE group; the 64
|
|
// spellings carry REX.W.
|
|
var x87Fxsav = map[string]struct {
|
|
digit int
|
|
rexW bool
|
|
}{
|
|
"FXSAVE": {0, false},
|
|
"FXSAVE64": {0, true},
|
|
"FXRSTOR": {1, false},
|
|
"FXRSTOR64": {1, true},
|
|
}
|
|
|
|
// encodeX87 encodes the x87 family. It reports whether the mnemonic belongs
|
|
// to the family; a false result hands the mnemonic back to the caller, an
|
|
// error result a failed attempt to encode it.
|
|
func (e *enc) encodeX87(upper string, ops []Operand) (bool, error) {
|
|
if post, ok := x87NoOperand[upper]; ok {
|
|
if len(ops) != 0 {
|
|
return true, fmt.Errorf("%s takes no operands, got %d", upper, len(ops))
|
|
}
|
|
return true, e.emit(&instr{opcode: []byte{0xD9, post}, modrm: -1, sib: -1})
|
|
}
|
|
if spec, ok := x87Arith[upper]; ok {
|
|
return true, e.encodeX87Arith(upper, spec, ops)
|
|
}
|
|
if esc, ok := x87FCmov[upper]; ok {
|
|
src, _, err := x87PairOperands(upper, ops)
|
|
if err != nil {
|
|
return true, err
|
|
}
|
|
// The condition applies between the named register and ST(0), so the
|
|
// second operand is always F0; the first rides the postfix's low bits.
|
|
return true, e.emit(&instr{opcode: []byte{esc[0], esc[1] | byte(src.idx&7)}, modrm: -1, sib: -1})
|
|
}
|
|
if escape, ok := x87Compare[upper]; ok {
|
|
if _, _, err := x87PairOperands(upper, ops); err != nil {
|
|
return true, err
|
|
}
|
|
src, _ := ops[0].(Reg)
|
|
return true, e.emit(&instr{opcode: []byte{escape, 0xF0 | byte(src.idx&7)}, modrm: -1, sib: -1})
|
|
}
|
|
if upper == "FADDDP" {
|
|
if len(ops) != 2 {
|
|
return true, fmt.Errorf("FADDDP expects 2 operands, got %d", len(ops))
|
|
}
|
|
first, ok1 := ops[0].(Reg)
|
|
second, ok2 := ops[1].(Reg)
|
|
if !ok1 || !ok2 || !first.fp || !second.fp {
|
|
return true, fmt.Errorf("FADDDP takes two x87 stack registers")
|
|
}
|
|
if first.idx != 0 {
|
|
return true, fmt.Errorf("FADDDP: the first operand must be F0")
|
|
}
|
|
return true, e.emit(&instr{opcode: []byte{0xDE, 0xC0 | byte(second.idx&7)}, modrm: -1, sib: -1})
|
|
}
|
|
if m, ok := x87MemUnary[upper]; ok {
|
|
if len(ops) != 1 {
|
|
return true, fmt.Errorf("%s expects 1 memory operand, got %d", upper, len(ops))
|
|
}
|
|
if !isX86Mem(ops[0]) {
|
|
return true, fmt.Errorf("%s requires a memory operand", upper)
|
|
}
|
|
i := &instr{opcode: []byte{m.escape}, modrm: -1, sib: -1}
|
|
if err := setRMDigit(i, m.digit, ops[0], 8); err != nil {
|
|
return true, err
|
|
}
|
|
return true, e.emit(i)
|
|
}
|
|
if m, ok := x87Fxsav[upper]; ok {
|
|
if len(ops) != 1 {
|
|
return true, fmt.Errorf("%s expects 1 memory operand, got %d", upper, len(ops))
|
|
}
|
|
if !isX86Mem(ops[0]) {
|
|
return true, fmt.Errorf("%s requires a memory operand", upper)
|
|
}
|
|
i := newInstr(0, []byte{0x0F, 0xAE})
|
|
i.rexW = m.rexW
|
|
if err := setRMDigit(i, m.digit, ops[0], 8); err != nil {
|
|
return true, err
|
|
}
|
|
return true, e.emit(i)
|
|
}
|
|
return false, nil
|
|
}
|
|
|
|
// encodeX87Arith encodes one member of the D8/DC arithmetic pair. The tool-
|
|
// chain's shape set: (Fn, F0) rides D8, (F0, Fn) rides DC, ((m), F0) rides
|
|
// the DC memory digit; every other pairing is an error.
|
|
func (e *enc) encodeX87Arith(mnem string, spec x87ArithSpec, ops []Operand) error {
|
|
if len(ops) != 2 {
|
|
return fmt.Errorf("%s expects 2 operands, got %d", mnem, len(ops))
|
|
}
|
|
src, dst := ops[0], ops[1]
|
|
dstReg, dstIsF := dst.(Reg)
|
|
if !dstIsF || !dstReg.fp {
|
|
return fmt.Errorf("%s: the destination must be an x87 stack register", mnem)
|
|
}
|
|
switch s := src.(type) {
|
|
case Reg:
|
|
if !s.fp {
|
|
return fmt.Errorf("%s: the source must be an x87 stack register", mnem)
|
|
}
|
|
switch {
|
|
case dstReg.idx == 0:
|
|
return e.emit(&instr{opcode: []byte{0xD8, 0xC0 | byte(spec.d8Digit)<<3 | byte(s.idx&7)}, modrm: -1, sib: -1})
|
|
case s.idx == 0:
|
|
return e.emit(&instr{opcode: []byte{0xDC, 0xC0 | byte(spec.dcDigit)<<3 | byte(dstReg.idx&7)}, modrm: -1, sib: -1})
|
|
default:
|
|
return fmt.Errorf("%s: one operand must be F0", mnem)
|
|
}
|
|
default:
|
|
if !isX86Mem(src) {
|
|
return fmt.Errorf("%s: the source must be an x87 stack register or memory", mnem)
|
|
}
|
|
if dstReg.idx != 0 {
|
|
return fmt.Errorf("%s: the destination must be F0 with a memory source", mnem)
|
|
}
|
|
i := &instr{opcode: []byte{0xDC}, modrm: -1, sib: -1}
|
|
if err := setRMDigit(i, spec.memDigit, src, 8); err != nil {
|
|
return err
|
|
}
|
|
return e.emit(i)
|
|
}
|
|
}
|
|
|
|
// x87PairOperands validates the (register, F0) shape the conditional moves
|
|
// and compares take and returns the two registers.
|
|
func x87PairOperands(mnem string, ops []Operand) (Reg, Reg, error) {
|
|
if len(ops) != 2 {
|
|
return Reg{}, Reg{}, fmt.Errorf("%s expects 2 operands, got %d", mnem, len(ops))
|
|
}
|
|
src, ok1 := ops[0].(Reg)
|
|
dst, ok2 := ops[1].(Reg)
|
|
if !ok1 || !ok2 || !src.fp || !dst.fp {
|
|
return Reg{}, Reg{}, fmt.Errorf("%s takes two x87 stack registers", mnem)
|
|
}
|
|
if dst.idx != 0 {
|
|
return Reg{}, Reg{}, fmt.Errorf("%s: the second operand must be F0", mnem)
|
|
}
|
|
return src, dst, nil
|
|
}
|