170 lines
4.5 KiB
Go
170 lines
4.5 KiB
Go
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
||||
|
|
// SPDX-License-Identifier: BSD-3-Clause
|
|||
|
|
|
|||
|
|
// Package asm is a standalone assembler: it encodes Plan 9 assembly
|
|||
|
|
// instructions into machine code without the Go toolchain. Phase 2 begins
|
|||
|
|
// with an amd64 (x86-64) scalar instruction encoder; the encoding is validated
|
|||
|
|
// by round-tripping through golang.org/x/arch's decoder in the tests.
|
|||
|
|
package asm
|
|||
|
|
|
|||
|
|
import "strings"
|
|||
|
|
|
|||
|
|
// Reg is an x86-64 register. In Plan 9 assembly the classic names (AX, BX, …)
|
|||
|
|
// are size-agnostic — the instruction suffix (MOVQ vs MOVL) fixes the width —
|
|||
|
|
// so the encoder keys off the register's index and lets the mnemonic supply the
|
|||
|
|
// size. The high flag marks the legacy high-byte registers AH/CH/DH/BH, which
|
|||
|
|
// occupy indices 4–7 yet take no REX prefix, unlike SPL/BPL/SIL/DIL that share
|
|||
|
|
// those indices but require one.
|
|||
|
|
type Reg struct {
|
|||
|
|
idx int
|
|||
|
|
size int // informational width implied by the name; the mnemonic decides
|
|||
|
|
high bool // AH/CH/DH/BH
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Index returns the register number (0–15).
|
|||
|
|
func (r Reg) Index() int { return r.idx }
|
|||
|
|
|
|||
|
|
// Size returns the width in bytes implied by the register's name.
|
|||
|
|
func (r Reg) Size() int { return r.size }
|
|||
|
|
|
|||
|
|
func (r Reg) isOperand() {}
|
|||
|
|
|
|||
|
|
// needsREX reports whether this register forces a REX prefix at the given
|
|||
|
|
// operand size: the extended registers R8–R15 always do, and at byte size the
|
|||
|
|
// low registers SPL/BPL/SIL/DIL (indices 4–7, not high) do as well.
|
|||
|
|
func (r Reg) needsREX(opSize int) bool {
|
|||
|
|
if r.idx >= 8 {
|
|||
|
|
return true
|
|||
|
|
}
|
|||
|
|
return opSize == 1 && r.idx >= 4 && !r.high
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Register constants (the size is the width the name implies).
|
|||
|
|
var (
|
|||
|
|
AL = Reg{0, 1, false}
|
|||
|
|
CL = Reg{1, 1, false}
|
|||
|
|
DL = Reg{2, 1, false}
|
|||
|
|
BL = Reg{3, 1, false}
|
|||
|
|
AH = Reg{4, 1, true}
|
|||
|
|
CH = Reg{5, 1, true}
|
|||
|
|
DH = Reg{6, 1, true}
|
|||
|
|
BH = Reg{7, 1, true}
|
|||
|
|
SPL = Reg{4, 1, false}
|
|||
|
|
BPL = Reg{5, 1, false}
|
|||
|
|
SIL = Reg{6, 1, false}
|
|||
|
|
DIL = Reg{7, 1, false}
|
|||
|
|
|
|||
|
|
AX = Reg{0, 2, false}
|
|||
|
|
CX = Reg{1, 2, false}
|
|||
|
|
DX = Reg{2, 2, false}
|
|||
|
|
BX = Reg{3, 2, false}
|
|||
|
|
SP = Reg{4, 2, false}
|
|||
|
|
BP = Reg{5, 2, false}
|
|||
|
|
SI = Reg{6, 2, false}
|
|||
|
|
DI = Reg{7, 2, false}
|
|||
|
|
|
|||
|
|
EAX = Reg{0, 4, false}
|
|||
|
|
ECX = Reg{1, 4, false}
|
|||
|
|
EDX = Reg{2, 4, false}
|
|||
|
|
EBX = Reg{3, 4, false}
|
|||
|
|
ESP = Reg{4, 4, false}
|
|||
|
|
EBP = Reg{5, 4, false}
|
|||
|
|
ESI = Reg{6, 4, false}
|
|||
|
|
EDI = Reg{7, 4, false}
|
|||
|
|
|
|||
|
|
RAX = Reg{0, 8, false}
|
|||
|
|
RCX = Reg{1, 8, false}
|
|||
|
|
RDX = Reg{2, 8, false}
|
|||
|
|
RBX = Reg{3, 8, false}
|
|||
|
|
RSP = Reg{4, 8, false}
|
|||
|
|
RBP = Reg{5, 8, false}
|
|||
|
|
RSI = Reg{6, 8, false}
|
|||
|
|
RDI = Reg{7, 8, false}
|
|||
|
|
)
|
|||
|
|
|
|||
|
|
// regByName maps an assembly register name (case-insensitive) to a Reg.
|
|||
|
|
var regByName = buildRegByName()
|
|||
|
|
|
|||
|
|
func buildRegByName() map[string]Reg {
|
|||
|
|
m := map[string]Reg{}
|
|||
|
|
|
|||
|
|
// 64-bit: RAX..RDI, R8..R15.
|
|||
|
|
r64 := []string{"RAX", "RCX", "RDX", "RBX", "RSP", "RBP", "RSI", "RDI"}
|
|||
|
|
for i, n := range r64 {
|
|||
|
|
m[n] = Reg{i, 8, false}
|
|||
|
|
}
|
|||
|
|
for i := 8; i <= 15; i++ {
|
|||
|
|
m["R"+itoa(i)] = Reg{i, 8, false}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 32-bit: EAX..EDI, R8D..R15D.
|
|||
|
|
e32 := []string{"EAX", "ECX", "EDX", "EBX", "ESP", "EBP", "ESI", "EDI"}
|
|||
|
|
for i, n := range e32 {
|
|||
|
|
m[n] = Reg{i, 4, false}
|
|||
|
|
}
|
|||
|
|
for i := 8; i <= 15; i++ {
|
|||
|
|
m["R"+itoa(i)+"D"] = Reg{i, 4, false}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 16-bit: AX..DI, R8W..R15W.
|
|||
|
|
w16 := []string{"AX", "CX", "DX", "BX", "SP", "BP", "SI", "DI"}
|
|||
|
|
for i, n := range w16 {
|
|||
|
|
m[n] = Reg{i, 2, false}
|
|||
|
|
}
|
|||
|
|
for i := 8; i <= 15; i++ {
|
|||
|
|
m["R"+itoa(i)+"W"] = Reg{i, 2, false}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// 8-bit: AL..BH, SPL..DIL, R8B..R15B.
|
|||
|
|
for n, r := range map[string]Reg{
|
|||
|
|
"AL": AL, "CL": CL, "DL": DL, "BL": BL,
|
|||
|
|
"AH": AH, "CH": CH, "DH": DH, "BH": BH,
|
|||
|
|
"SPL": SPL, "BPL": BPL, "SIL": SIL, "DIL": DIL,
|
|||
|
|
} {
|
|||
|
|
m[n] = r
|
|||
|
|
}
|
|||
|
|
for i := 8; i <= 15; i++ {
|
|||
|
|
m["R"+itoa(i)+"B"] = Reg{i, 1, false}
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// Vector: X0..X15 (128-bit, encoded size 16), Y0..Y15 (256-bit, size 32).
|
|||
|
|
// Z (512-bit) and K (mask) registers arrive with EVEX/AVX-512 support.
|
|||
|
|
for i := 0; i <= 15; i++ {
|
|||
|
|
m["X"+itoa(i)] = Reg{i, 16, false}
|
|||
|
|
m["Y"+itoa(i)] = Reg{i, 32, false}
|
|||
|
|
}
|
|||
|
|
return m
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// isVec reports whether r is an XMM/YMM vector register.
|
|||
|
|
func (r Reg) isVec() bool { return r.size == 16 || r.size == 32 }
|
|||
|
|
|
|||
|
|
// vecLenBit returns the VEX.L bit for a vector register (X=0/128-bit,
|
|||
|
|
// Y=1/256-bit).
|
|||
|
|
func (r Reg) vecLenBit() int {
|
|||
|
|
if r.size == 32 {
|
|||
|
|
return 1
|
|||
|
|
}
|
|||
|
|
return 0
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
// ParseReg resolves an assembly register name to a Reg.
|
|||
|
|
func ParseReg(name string) (Reg, bool) {
|
|||
|
|
r, ok := regByName[strings.ToUpper(name)]
|
|||
|
|
return r, ok
|
|||
|
|
}
|
|||
|
|
|
|||
|
|
func itoa(n int) string {
|
|||
|
|
if n == 0 {
|
|||
|
|
return "0"
|
|||
|
|
}
|
|||
|
|
var buf [3]byte
|
|||
|
|
i := len(buf)
|
|||
|
|
for n > 0 {
|
|||
|
|
i--
|
|||
|
|
buf[i] = byte('0' + n%10)
|
|||
|
|
n /= 10
|
|||
|
|
}
|
|||
|
|
return string(buf[i:])
|
|||
|
|
}
|