2026-07-06 09:49:50 +02:00
|
|
|
// 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)
|
2026-07-07 13:57:53 +02:00
|
|
|
// 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))
|
2026-07-06 09:49:50 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
|
|
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})
|
|
|
|
|
}
|