// Copyright (c) 2026 Petr Balvín (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:]) }