feat: gasm-devkit 0.1.0 — GAsm lexer, parser, linter, formatter, LSP and amd64 assembler
Assisted-by: Qwen 3.8 Max Preview
This commit is contained in:
+169
@@ -0,0 +1,169 @@
|
||||
// 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:])
|
||||
}
|
||||
Reference in New Issue
Block a user