Files
gasm-sdk/asm/amd64_x87.go
T

237 lines
7.5 KiB
Go
Raw Normal View History

2026-10-06 20:08:34 +02:00
// 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
}