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:
+142
@@ -0,0 +1,142 @@
|
||||
// Copyright (c) 2026 Petr Balvín <opensource@petrbalvin.org> (https://petrbalvin.org)
|
||||
// SPDX-License-Identifier: BSD-3-Clause
|
||||
|
||||
package asm
|
||||
|
||||
import (
|
||||
"testing"
|
||||
|
||||
"golang.org/x/arch/x86/x86asm"
|
||||
)
|
||||
|
||||
func vreg(t *testing.T, name string) Reg {
|
||||
t.Helper()
|
||||
r, ok := ParseReg(name)
|
||||
if !ok {
|
||||
t.Fatalf("unknown register %s", name)
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
// TestVexNDS3 encodes `mnem Y0, Y1, Y2` for every three-operand NDS
|
||||
// instruction and verifies it round-trips through the x86 decoder to the same
|
||||
// mnemonic. A wrong opcode/map/pp surfaces as a different decoded instruction.
|
||||
func TestVexNDS3(t *testing.T) {
|
||||
for mnem, spec := range vexTable {
|
||||
if spec.form != vexNDS3 {
|
||||
continue
|
||||
}
|
||||
code, err := Encode(mnem, vreg(t, "Y0"), vreg(t, "Y1"), vreg(t, "Y2"))
|
||||
if err != nil {
|
||||
t.Errorf("%s: Encode: %v", mnem, err)
|
||||
continue
|
||||
}
|
||||
inst, err := x86asm.Decode(code, 64)
|
||||
if err != nil {
|
||||
t.Errorf("%s: Decode(% x): %v", mnem, code, err)
|
||||
continue
|
||||
}
|
||||
if inst.Op.String() != mnem {
|
||||
t.Errorf("%s: decoded as %s (% x)", mnem, inst.Op.String(), code)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestVexGoFlac checks a representative go-flac instruction sequence encodes
|
||||
// and decodes as expected.
|
||||
func TestVexGoFlac(t *testing.T) {
|
||||
// VPADDD Y5, Y8, Y8 → vpaddd ymm8, ymm8, ymm5.
|
||||
code, err := Encode("VPADDD", vreg(t, "Y5"), vreg(t, "Y8"), vreg(t, "Y8"))
|
||||
if err != nil {
|
||||
t.Fatalf("Encode: %v", err)
|
||||
}
|
||||
inst, err := x86asm.Decode(code, 64)
|
||||
if err != nil {
|
||||
t.Fatalf("Decode(% x): %v", code, err)
|
||||
}
|
||||
if inst.Op != x86asm.VPADDD {
|
||||
t.Fatalf("decoded %s, want VPADDD", inst.Op)
|
||||
}
|
||||
}
|
||||
|
||||
// TestVexXMM checks the 128-bit (XMM) form selects VEX.L=0.
|
||||
func TestVexXMM(t *testing.T) {
|
||||
code, err := Encode("VPXOR", vreg(t, "X7"), vreg(t, "X7"), vreg(t, "X7"))
|
||||
if err != nil {
|
||||
t.Fatalf("Encode: %v", err)
|
||||
}
|
||||
inst, err := x86asm.Decode(code, 64)
|
||||
if err != nil {
|
||||
t.Fatalf("Decode(% x): %v", code, err)
|
||||
}
|
||||
if inst.Op != x86asm.VPXOR {
|
||||
t.Fatalf("decoded %s, want VPXOR", inst.Op)
|
||||
}
|
||||
// vpxor xmm7, xmm7, xmm7 → C5 C9 EF FF (2-byte VEX, L=0).
|
||||
if code[0] != 0xC5 {
|
||||
t.Errorf("expected 2-byte VEX (C5), got % x", code)
|
||||
}
|
||||
}
|
||||
|
||||
// TestVexRM validates the two-operand (reg=dst, rm=src, no vvvv) forms by
|
||||
// round-tripping through the decoder.
|
||||
func TestVexRM(t *testing.T) {
|
||||
cases := []struct {
|
||||
mnem string
|
||||
ops []Operand
|
||||
want x86asm.Op
|
||||
}{
|
||||
{"VPMOVSXWD", []Operand{Ptr(SI, 0, 16), vreg(t, "Y0")}, x86asm.VPMOVSXWD},
|
||||
{"VPMOVSXDQ", []Operand{vreg(t, "X0"), vreg(t, "Y4")}, x86asm.VPMOVSXDQ},
|
||||
{"VPMOVZXDQ", []Operand{vreg(t, "X4"), vreg(t, "Y4")}, x86asm.VPMOVZXDQ},
|
||||
{"VPBROADCASTD", []Operand{vreg(t, "X0"), vreg(t, "Y15")}, x86asm.VPBROADCASTD},
|
||||
{"VPMOVMSKB", []Operand{vreg(t, "X11"), AX}, x86asm.VPMOVMSKB},
|
||||
{"VMOVMSKPS", []Operand{vreg(t, "Y7"), AX}, x86asm.VMOVMSKPS},
|
||||
}
|
||||
for _, c := range cases {
|
||||
code, err := Encode(c.mnem, c.ops...)
|
||||
if err != nil {
|
||||
t.Errorf("%s: Encode: %v", c.mnem, err)
|
||||
continue
|
||||
}
|
||||
inst, err := x86asm.Decode(code, 64)
|
||||
if err != nil {
|
||||
t.Errorf("%s: Decode(% x): %v", c.mnem, code, err)
|
||||
continue
|
||||
}
|
||||
if inst.Op != c.want {
|
||||
t.Errorf("%s: decoded as %s (% x)", c.mnem, inst.Op, code)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// TestVexShiftImm validates the immediate-shift form, checking the destination
|
||||
// (VEX.vvvv) and source (ModRM.rm) land in the right places.
|
||||
func TestVexShiftImm(t *testing.T) {
|
||||
// VPSLLD $1, Y3, Y4 → vpslld ymm4, ymm3, 1.
|
||||
code, err := Encode("VPSLLD", Imm(1), vreg(t, "Y3"), vreg(t, "Y4"))
|
||||
if err != nil {
|
||||
t.Fatalf("Encode: %v", err)
|
||||
}
|
||||
inst, err := x86asm.Decode(code, 64)
|
||||
if err != nil {
|
||||
t.Fatalf("Decode(% x): %v", code, err)
|
||||
}
|
||||
if inst.Op != x86asm.VPSLLD {
|
||||
t.Fatalf("decoded %s, want VPSLLD (% x)", inst.Op, code)
|
||||
}
|
||||
// Intel order: dst, src, imm → "vpslld ymm4, ymm3, 0x1".
|
||||
if got := x86asm.IntelSyntax(inst, 0, nil); got != "vpslld ymm4, ymm3, 0x1" {
|
||||
t.Errorf("VPSLLD syntax = %q, want \"vpslld ymm4, ymm3, 0x1\" (% x)", got, code)
|
||||
}
|
||||
|
||||
// VPSRAD $31, Y3, Y3 → vpsrad ymm3, ymm3, 31.
|
||||
code, err = Encode("VPSRAD", Imm(31), vreg(t, "Y3"), vreg(t, "Y3"))
|
||||
if err != nil {
|
||||
t.Fatalf("Encode VPSRAD: %v", err)
|
||||
}
|
||||
inst, err = x86asm.Decode(code, 64)
|
||||
if err != nil || inst.Op != x86asm.VPSRAD {
|
||||
t.Fatalf("VPSRAD decoded %v (err %v), want VPSRAD", inst.Op, err)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user