Files
gasm-sdk/asm/encode_test.go
T

137 lines
5.0 KiB
Go
Raw Normal View History

// 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"
)
// decode encodes an instruction and decodes it back, returning the decoded
// instruction and its Intel-syntax rendering.
func decode(t *testing.T, mnemonic string, ops ...Operand) (x86asm.Inst, string) {
t.Helper()
code, err := Encode(mnemonic, ops...)
if err != nil {
t.Fatalf("Encode(%s): %v", mnemonic, err)
}
inst, err := x86asm.Decode(code, 64)
if err != nil {
t.Fatalf("Decode(%x) of %s: %v", code, mnemonic, err)
}
if inst.Len != len(code) {
t.Fatalf("Decode consumed %d of %d bytes for %s (%x)", inst.Len, len(code), mnemonic, code)
}
return inst, x86asm.IntelSyntax(inst, 0, nil)
}
// checkSyntax asserts an instruction encodes and decodes to the expected
// Intel-syntax string.
func checkSyntax(t *testing.T, want, mnemonic string, ops ...Operand) {
t.Helper()
_, got := decode(t, mnemonic, ops...)
if got != want {
t.Errorf("%s: got %q, want %q", mnemonic, got, want)
}
}
// checkOp asserts the decoded opcode (used for relative jumps, whose rendered
// target depends on the program counter).
func checkOp(t *testing.T, want x86asm.Op, mnemonic string, ops ...Operand) {
t.Helper()
inst, _ := decode(t, mnemonic, ops...)
if inst.Op != want {
t.Errorf("%s: got op %v, want %v", mnemonic, inst.Op, want)
}
}
func TestMov(t *testing.T) {
checkSyntax(t, "mov rbx, rax", "MOVQ", AX, BX)
checkSyntax(t, "mov ebx, eax", "MOVL", AX, BX)
checkSyntax(t, "mov bl, al", "MOVB", AL, BL)
checkSyntax(t, "mov rax, rbx", "MOVQ", BX, AX)
checkSyntax(t, "mov rbx, qword ptr [rax]", "MOVQ", Ptr(AX, 0, 8), BX)
checkSyntax(t, "mov qword ptr [rbx], rax", "MOVQ", AX, Ptr(BX, 0, 8))
checkSyntax(t, "mov rbx, qword ptr [rax+0x10]", "MOVQ", Ptr(AX, 0x10, 8), BX)
checkSyntax(t, "mov rbx, qword ptr [rsi+4*rbx]", "MOVQ", Idx(SI, BX, 4, 0, 8), BX)
checkSyntax(t, "mov rax, 0x5", "MOVQ", Imm(5), AX)
checkSyntax(t, "mov r8, 0x5", "MOVQ", Imm(5), Reg{idx: 8, size: 8})
checkSyntax(t, "mov qword ptr [rax], 0x5", "MOVQ", Imm(5), Ptr(AX, 0, 8))
checkSyntax(t, "mov r12, r13", "MOVQ", Reg{idx: 13, size: 8}, Reg{idx: 12, size: 8})
}
func TestALU(t *testing.T) {
checkSyntax(t, "add rbx, rax", "ADDQ", AX, BX)
checkSyntax(t, "add rax, 0x1", "ADDQ", Imm(1), AX)
checkSyntax(t, "add rax, 0x12c", "ADDQ", Imm(300), AX)
checkSyntax(t, "sub rdx, rcx", "SUBQ", CX, DX)
checkSyntax(t, "and rbx, 0x7", "ANDQ", Imm(7), BX)
checkSyntax(t, "or rcx, rbx", "ORQ", BX, CX)
checkSyntax(t, "xor rax, rax", "XORQ", AX, AX)
checkSyntax(t, "cmp r10, rsi", "CMPQ", SI, Reg{idx: 10, size: 8})
checkSyntax(t, "add rbx, qword ptr [rax]", "ADDQ", Ptr(AX, 0, 8), BX)
checkSyntax(t, "add qword ptr [rax], rbx", "ADDQ", BX, Ptr(AX, 0, 8))
checkSyntax(t, "cmp rbx, -0x20", "CMPQ", Imm(-32), BX)
// The Go assembler's own spelling: immediate second.
checkSyntax(t, "cmp ecx, 0x1f", "CMPL", CX, Imm(31))
checkSyntax(t, "cmp ecx, -0x80000000", "CMPL", CX, Imm(-2147483648))
checkSyntax(t, "cmp r9, -0x80000000", "CMPQ", Reg{idx: 9, size: 8}, Imm(-2147483648))
}
func TestLea(t *testing.T) {
checkSyntax(t, "lea r9, ptr [rsi+4*rbx]", "LEAQ", Idx(SI, BX, 4, 0, 8), Reg{idx: 9, size: 8})
checkSyntax(t, "lea rax, ptr [rbx+0x8]", "LEAQ", Ptr(BX, 0x8, 8), AX)
}
func TestTest(t *testing.T) {
checkSyntax(t, "test rax, rax", "TESTQ", AX, AX)
checkSyntax(t, "test rbx, 0x7", "TESTQ", Imm(7), BX)
}
func TestPushPop(t *testing.T) {
checkSyntax(t, "push rbx", "PUSHQ", BX)
checkSyntax(t, "pop r12", "POPQ", Reg{idx: 12, size: 8})
checkSyntax(t, "push 0x5", "PUSHQ", Imm(5))
}
func TestUnary(t *testing.T) {
checkSyntax(t, "inc rax", "INCQ", AX)
checkSyntax(t, "dec rbx", "DECQ", BX)
checkSyntax(t, "neg rcx", "NEGQ", CX)
checkSyntax(t, "not rdx", "NOTQ", DX)
}
func TestShift(t *testing.T) {
checkSyntax(t, "shl rdx, 0x2", "SHLQ", Imm(2), DX)
checkSyntax(t, "shl rdx, cl", "SHLQ", CL, DX)
checkSyntax(t, "shl rdx, 0x1", "SHLQ", Imm(1), DX)
checkSyntax(t, "sar rcx, 0x1f", "SARQ", Imm(31), CX)
}
func TestImul(t *testing.T) {
checkSyntax(t, "imul rdx, rcx", "IMULQ", CX, DX)
checkSyntax(t, "imul edx, edx, 0x3", "IMULL", Imm(3), DX, DX)
checkSyntax(t, "imul rdx, rcx, 0x100", "IMULQ", Imm(256), CX, DX)
}
func TestControl(t *testing.T) {
checkSyntax(t, "ret", "RET")
checkSyntax(t, "nop", "NOP")
checkOp(t, x86asm.JMP, "JMP", Imm(0))
checkOp(t, x86asm.CALL, "CALL", Imm(0))
checkOp(t, x86asm.JGE, "JGE", Imm(0))
checkOp(t, x86asm.JNE, "JNE", Imm(0))
checkOp(t, x86asm.JBE, "JLS", Imm(0))
}
// TestGoFlacScalarTail encodes the scalar tail of an analyze kernel to confirm
// the encoder handles a realistic instruction sequence.
func TestGoFlacScalarTail(t *testing.T) {
// MOVQ swin_base+0(FP), SI — modelled as MOVQ disp(reg), reg.
checkSyntax(t, "mov rsi, qword ptr [rax+0x10]", "MOVQ", Ptr(AX, 0x10, 8), SI)
checkSyntax(t, "lea r9, ptr [rsi+4*rbx]", "LEAQ", Idx(SI, BX, 4, 0, 8), Reg{idx: 9, size: 8})
checkSyntax(t, "and r10, -0x8", "ANDQ", Imm(-8), Reg{idx: 10, size: 8})
}