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 (
|
2026-08-27 16:36:18 +02:00
|
|
|
|
"fmt"
|
2026-07-08 12:51:35 +02:00
|
|
|
|
"strings"
|
2026-07-06 09:49:50 +02:00
|
|
|
|
"testing"
|
|
|
|
|
|
|
|
|
|
|
|
"golang.org/x/arch/x86/x86asm"
|
2026-09-21 02:04:44 +02:00
|
|
|
|
|
2026-09-26 11:08:43 +02:00
|
|
|
|
"sourcedock.dev/petrbalvin/gasm-sdk/ast"
|
|
|
|
|
|
"sourcedock.dev/petrbalvin/gasm-sdk/parser"
|
2026-07-06 09:49:50 +02:00
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
|
|
// 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)
|
2026-08-24 20:23:39 +02:00
|
|
|
|
// A small positive immediate compresses to the 32-bit zero-extending
|
|
|
|
|
|
// form (matching go tool asm), so the disassembler renders the 32-bit
|
|
|
|
|
|
// register name even for MOVQ.
|
|
|
|
|
|
checkSyntax(t, "mov eax, 0x5", "MOVQ", Imm(5), AX)
|
|
|
|
|
|
checkSyntax(t, "mov r8d, 0x5", "MOVQ", Imm(5), Reg{idx: 8, size: 8})
|
2026-07-06 09:49:50 +02:00
|
|
|
|
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)
|
2026-07-08 12:51:35 +02:00
|
|
|
|
checkSyntax(t, "cmp rsi, r10", "CMPQ", SI, Reg{idx: 10, size: 8})
|
2026-07-06 09:49:50 +02:00
|
|
|
|
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))
|
2026-08-28 19:55:25 +02:00
|
|
|
|
// The Go assembler rejects the immediate-first CMP spelling outright,
|
|
|
|
|
|
// so Encode errors instead of silently emitting the swapped form.
|
|
|
|
|
|
if _, err := Encode("CMPQ", Imm(-32), BX); err == nil {
|
|
|
|
|
|
t.Errorf("Encode(CMPQ imm-first) should error, got success")
|
|
|
|
|
|
}
|
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
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-28 19:55:25 +02:00
|
|
|
|
// TestScalarXmmRegMoves pins the Go-assembler byte forms of scalar
|
|
|
|
|
|
// MOVQ/MOVL between GPRs and XMM registers (66 REX.W 0F 6E/0F 7E) and the
|
|
|
|
|
|
// memory forms (F3 0F 7E load, 66 0F D6 store), all byte-for-byte.
|
|
|
|
|
|
func TestScalarXmmRegMoves(t *testing.T) {
|
|
|
|
|
|
cases := []struct {
|
|
|
|
|
|
name string
|
|
|
|
|
|
mnem string
|
|
|
|
|
|
ops []Operand
|
|
|
|
|
|
want string
|
|
|
|
|
|
}{
|
|
|
|
|
|
{"MOVQ AX,X1", "MOVQ", []Operand{AX, vreg(t, "X1")}, "66480f6ec8"},
|
|
|
|
|
|
{"MOVQ DX,X2", "MOVQ", []Operand{DX, vreg(t, "X2")}, "66480f6ed2"},
|
|
|
|
|
|
{"MOVQ X1,AX", "MOVQ", []Operand{vreg(t, "X1"), AX}, "66480f7ec8"},
|
|
|
|
|
|
{"MOVQ X0,DX", "MOVQ", []Operand{vreg(t, "X0"), DX}, "66480f7ec2"},
|
|
|
|
|
|
{"MOVL AX,X1", "MOVL", []Operand{AX, vreg(t, "X1")}, "660f6ec8"},
|
|
|
|
|
|
{"MOVL X1,AX", "MOVL", []Operand{vreg(t, "X1"), AX}, "660f7ec8"},
|
|
|
|
|
|
{"MOVQ (SI),X1", "MOVQ", []Operand{Ptr(SI, 0, 8), vreg(t, "X1")}, "f30f7e0e"},
|
|
|
|
|
|
{"MOVQ X3,(DI)", "MOVQ", []Operand{vreg(t, "X3"), Ptr(DI, 0, 8)}, "660fd61f"},
|
|
|
|
|
|
}
|
|
|
|
|
|
for _, c := range cases {
|
|
|
|
|
|
code, err := Encode(c.mnem, c.ops...)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
t.Errorf("%s: %v", c.name, err)
|
|
|
|
|
|
continue
|
|
|
|
|
|
}
|
|
|
|
|
|
if got := fmt.Sprintf("%x", code); got != c.want {
|
|
|
|
|
|
t.Errorf("%s: got %s, want %s", c.name, got, c.want)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// TestBadScale pins the go-tool-asm parity of rejecting SIB scales the
|
|
|
|
|
|
// hardware cannot encode.
|
|
|
|
|
|
func TestBadScale(t *testing.T) {
|
|
|
|
|
|
for _, sc := range []int{3, 5, 16, 32} {
|
|
|
|
|
|
if _, err := Encode("LEAQ", Idx(SI, BX, sc, 0, 8), AX); err == nil {
|
|
|
|
|
|
t.Errorf("LEAQ scale %d: expected error, got success", sc)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
for _, sc := range []int{1, 2, 4, 8} {
|
|
|
|
|
|
if _, err := Encode("LEAQ", Idx(SI, BX, sc, 0, 8), AX); err != nil {
|
|
|
|
|
|
t.Errorf("LEAQ scale %d: %v", sc, err)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
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))
|
2026-09-19 23:49:07 +02:00
|
|
|
|
// The W spelling carries the 0x66 operand-size prefix, byte for byte
|
|
|
|
|
|
// with go tool asm; the L and B spellings are illegal in 64-bit mode
|
|
|
|
|
|
// there and rejected here rather than silently widened.
|
|
|
|
|
|
cases := []struct {
|
|
|
|
|
|
name string
|
|
|
|
|
|
mnem string
|
|
|
|
|
|
ops []Operand
|
|
|
|
|
|
want string
|
|
|
|
|
|
}{
|
|
|
|
|
|
{"PUSHW AX", "PUSHW", []Operand{AX}, "6650"},
|
|
|
|
|
|
{"POPW AX", "POPW", []Operand{AX}, "6658"},
|
|
|
|
|
|
{"PUSHW $5", "PUSHW", []Operand{Imm(5)}, "666a05"},
|
|
|
|
|
|
{"PUSHW (AX)", "PUSHW", []Operand{Ptr(AX, 0, 2)}, "66ff30"},
|
|
|
|
|
|
{"PUSHQ AX", "PUSHQ", []Operand{AX}, "50"},
|
|
|
|
|
|
}
|
|
|
|
|
|
for _, c := range cases {
|
|
|
|
|
|
code, err := Encode(c.mnem, c.ops...)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
t.Errorf("%s: %v", c.name, err)
|
|
|
|
|
|
continue
|
|
|
|
|
|
}
|
|
|
|
|
|
if got := fmt.Sprintf("%x", code); got != c.want {
|
|
|
|
|
|
t.Errorf("%s: bytes %s, want %s", c.name, got, c.want)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
for _, c := range []struct {
|
|
|
|
|
|
name string
|
|
|
|
|
|
mnem string
|
|
|
|
|
|
ops []Operand
|
|
|
|
|
|
}{
|
|
|
|
|
|
{"PUSHL AX", "PUSHL", []Operand{AX}},
|
|
|
|
|
|
{"PUSHL R8", "PUSHL", []Operand{Reg{idx: 8, size: 8}}},
|
|
|
|
|
|
{"POPL BX", "POPL", []Operand{BX}},
|
|
|
|
|
|
{"PUSHB AX", "PUSHB", []Operand{AX}},
|
|
|
|
|
|
} {
|
|
|
|
|
|
if _, err := Encode(c.mnem, c.ops...); err == nil {
|
|
|
|
|
|
t.Errorf("%s: expected an error, got none", c.name)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-07-06 09:49:50 +02:00
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
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)
|
2026-09-20 11:40:39 +02:00
|
|
|
|
checkSyntax(t, "shl rdx, cl", "SHLQ", CX, DX)
|
2026-07-06 09:49:50 +02:00
|
|
|
|
checkSyntax(t, "shl rdx, 0x1", "SHLQ", Imm(1), DX)
|
|
|
|
|
|
checkSyntax(t, "sar rcx, 0x1f", "SARQ", Imm(31), CX)
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-20 11:40:39 +02:00
|
|
|
|
// TestDoubleShift pins the three-operand SHL/SHR form, which encodes as
|
|
|
|
|
|
// SHLD/SHRD: go tool asm accepts it for SHL/SHR at W/L/Q widths and rejects
|
|
|
|
|
|
// it for SAR, SAL, the rotates and the B width. The byte pins mirror the
|
|
|
|
|
|
// oracle's objdump output (48 0f a4 fe 0d for the first case, and so on).
|
|
|
|
|
|
func TestDoubleShift(t *testing.T) {
|
|
|
|
|
|
cases := []struct {
|
|
|
|
|
|
name string
|
|
|
|
|
|
mnem string
|
|
|
|
|
|
ops []Operand
|
|
|
|
|
|
want string // hex encoding
|
|
|
|
|
|
}{
|
|
|
|
|
|
{"SHLQ imm", "SHLQ", []Operand{Imm(0x0d), DI, SI}, "480fa4fe0d"},
|
|
|
|
|
|
{"SHLQ CX high regs", "SHLQ", []Operand{CX, Reg{idx: 8, size: 8}, Reg{idx: 9, size: 8}}, "4d0fa5c1"},
|
|
|
|
|
|
{"SHRQ imm", "SHRQ", []Operand{Imm(1), AX, CX}, "480facc101"},
|
|
|
|
|
|
{"SHLW imm", "SHLW", []Operand{Imm(1), AX, CX}, "660fa4c101"},
|
|
|
|
|
|
{"SHRD CL", "SHRQ", []Operand{CL, AX, CX}, "480fadc1"},
|
|
|
|
|
|
{"SHLD imm high regs", "SHLQ", []Operand{Imm(2), Reg{idx: 10, size: 8}, Reg{idx: 11, size: 8}}, "4d0fa4d302"},
|
|
|
|
|
|
{"SHRD imm max", "SHRQ", []Operand{Imm(63), Reg{idx: 9, size: 8}, Reg{idx: 15, size: 8}}, "4d0faccf3f"},
|
|
|
|
|
|
}
|
|
|
|
|
|
for _, c := range cases {
|
|
|
|
|
|
code, err := Encode(c.mnem, c.ops...)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
t.Errorf("%s: Encode: %v", c.name, err)
|
|
|
|
|
|
continue
|
|
|
|
|
|
}
|
|
|
|
|
|
if got := hexCompact(code); got != c.want {
|
|
|
|
|
|
t.Errorf("%s: bytes %s, want %s", c.name, got, c.want)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
// Rejected forms: the oracle rejects every one of these.
|
|
|
|
|
|
rejected := []struct {
|
|
|
|
|
|
name string
|
|
|
|
|
|
mnem string
|
|
|
|
|
|
ops []Operand
|
|
|
|
|
|
}{
|
|
|
|
|
|
{"SARQ three operands", "SARQ", []Operand{Imm(1), AX, CX}},
|
|
|
|
|
|
{"SALQ three operands", "SALQ", []Operand{Imm(1), AX, CX}},
|
|
|
|
|
|
{"ROLQ three operands", "ROLQ", []Operand{Imm(1), AX, CX}},
|
|
|
|
|
|
{"SHLB three operands", "SHLB", []Operand{Imm(1), AL, CL}},
|
|
|
|
|
|
{"SHRQ memory source", "SHRQ", []Operand{Imm(1), Ptr(AX, 0, 8), CX}},
|
|
|
|
|
|
{"SHRQ ECX count", "SHRQ", []Operand{Reg{idx: 1, size: 4}, AX, CX}},
|
|
|
|
|
|
}
|
|
|
|
|
|
for _, c := range rejected {
|
|
|
|
|
|
if _, err := Encode(c.mnem, c.ops...); err == nil {
|
|
|
|
|
|
t.Errorf("%s: Encode succeeded, want rejection", c.name)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-06 09:49:50 +02:00
|
|
|
|
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))
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-19 19:17:07 +02:00
|
|
|
|
// TestIndirectControlFlow pins the indirect JMP/CALL forms: FF /4 for JMP and
|
|
|
|
|
|
// FF /2 for CALL through a register or memory. A REX appears only for the
|
|
|
|
|
|
// extended registers, never REX.W: the branch operand size is fixed at 64
|
|
|
|
|
|
// bits in long mode.
|
|
|
|
|
|
func TestIndirectControlFlow(t *testing.T) {
|
|
|
|
|
|
cases := []struct {
|
|
|
|
|
|
name string
|
|
|
|
|
|
mnem string
|
|
|
|
|
|
ops []Operand
|
|
|
|
|
|
want string
|
|
|
|
|
|
}{
|
|
|
|
|
|
{"JMP AX", "JMP", []Operand{AX}, "ffe0"},
|
|
|
|
|
|
{"CALL AX", "CALL", []Operand{AX}, "ffd0"},
|
|
|
|
|
|
{"JMP (BX)", "JMP", []Operand{Ptr(BX, 0, 8)}, "ff23"},
|
|
|
|
|
|
{"CALL (BX)", "CALL", []Operand{Ptr(BX, 0, 8)}, "ff13"},
|
|
|
|
|
|
{"JMP 8(BX)", "JMP", []Operand{Ptr(BX, 8, 8)}, "ff6308"},
|
|
|
|
|
|
{"CALL -16(BX)", "CALL", []Operand{Ptr(BX, -16, 8)}, "ff53f0"},
|
|
|
|
|
|
{"JMP R8", "JMP", []Operand{Reg{idx: 8, size: 2}}, "41ffe0"},
|
|
|
|
|
|
{"CALL R9", "CALL", []Operand{Reg{idx: 9, size: 2}}, "41ffd1"},
|
|
|
|
|
|
{"JMP R15", "JMP", []Operand{Reg{idx: 15, size: 2}}, "41ffe7"},
|
|
|
|
|
|
}
|
|
|
|
|
|
for _, c := range cases {
|
|
|
|
|
|
code, err := Encode(c.mnem, c.ops...)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
t.Errorf("%s: %v", c.name, err)
|
|
|
|
|
|
continue
|
|
|
|
|
|
}
|
|
|
|
|
|
if got := fmt.Sprintf("%x", code); got != c.want {
|
|
|
|
|
|
t.Errorf("%s: got %s, want %s", c.name, got, c.want)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-11 17:36:52 +02:00
|
|
|
|
// TestSSEMoveGroundTruth checks the legacy (non-VEX) SSE moves byte for byte
|
|
|
|
|
|
// against the Go assembler. wantOp is the decoder's name, which differs from
|
|
|
|
|
|
// the Plan 9 spelling for the octa moves (MOVOU = MOVDQU, MOVO = MOVDQA).
|
|
|
|
|
|
func TestSSEMoveGroundTruth(t *testing.T) {
|
|
|
|
|
|
cases := []struct {
|
|
|
|
|
|
name string
|
|
|
|
|
|
mnem string
|
|
|
|
|
|
ops []Operand
|
|
|
|
|
|
want string
|
|
|
|
|
|
wantOp string
|
|
|
|
|
|
}{
|
|
|
|
|
|
{"MOVOU (SI),X1", "MOVOU", []Operand{Ptr(SI, 0, 16), vreg(t, "X1")}, "f30f6f0e", "MOVDQU"},
|
|
|
|
|
|
{"MOVOU X3,(DI)", "MOVOU", []Operand{vreg(t, "X3"), Ptr(DI, 0, 16)}, "f30f7f1f", "MOVDQU"},
|
|
|
|
|
|
{"MOVOU X1,X2", "MOVOU", []Operand{vreg(t, "X1"), vreg(t, "X2")}, "f30f6fd1", "MOVDQU"},
|
|
|
|
|
|
{"MOVOU (SI)(BX*4),X9", "MOVOU", []Operand{Idx(SI, BX, 4, 0, 16), vreg(t, "X9")}, "f3440f6f0c9e", "MOVDQU"},
|
|
|
|
|
|
{"MOVO (SI),X1", "MOVO", []Operand{Ptr(SI, 0, 16), vreg(t, "X1")}, "660f6f0e", "MOVDQA"},
|
|
|
|
|
|
{"MOVO X3,(DI)", "MOVO", []Operand{vreg(t, "X3"), Ptr(DI, 0, 16)}, "660f7f1f", "MOVDQA"},
|
|
|
|
|
|
{"MOVUPS (SI),X1", "MOVUPS", []Operand{Ptr(SI, 0, 16), vreg(t, "X1")}, "0f100e", "MOVUPS"},
|
|
|
|
|
|
{"MOVAPS X3,(DI)", "MOVAPS", []Operand{vreg(t, "X3"), Ptr(DI, 0, 16)}, "0f291f", "MOVAPS"},
|
|
|
|
|
|
{"MOVUPD (SI),X1", "MOVUPD", []Operand{Ptr(SI, 0, 16), vreg(t, "X1")}, "660f100e", "MOVUPD"},
|
|
|
|
|
|
{"MOVAPD X3,(DI)", "MOVAPD", []Operand{vreg(t, "X3"), Ptr(DI, 0, 16)}, "660f291f", "MOVAPD"},
|
|
|
|
|
|
{"MOVSD (SI),X1", "MOVSD", []Operand{Ptr(SI, 0, 8), vreg(t, "X1")}, "f20f100e", "MOVSD_XMM"},
|
|
|
|
|
|
{"MOVSD X1,X2", "MOVSD", []Operand{vreg(t, "X1"), vreg(t, "X2")}, "f20f10d1", "MOVSD_XMM"},
|
|
|
|
|
|
{"MOVSS X3,(DI)", "MOVSS", []Operand{vreg(t, "X3"), Ptr(DI, 0, 4)}, "f30f111f", "MOVSS"},
|
2026-09-20 11:40:39 +02:00
|
|
|
|
// Static-symbol (SB) references: the GOROOT crypto kernels load and
|
|
|
|
|
|
// store octa constants by name (MOVOU bswapMask<>+0(SB), X0).
|
|
|
|
|
|
{"MOVOU sym,X0", "MOVOU", []Operand{sbMem{size: 16, name: "bswapMask"}, vreg(t, "X0")}, "f30f6f0500000000", "MOVDQU"},
|
|
|
|
|
|
{"MOVOU X0,sym+8", "MOVOU", []Operand{vreg(t, "X0"), sbMem{size: 16, name: "bswapMask", addend: 8}}, "f30f7f0500000000", "MOVDQU"},
|
|
|
|
|
|
{"MOVO sym,X1", "MOVO", []Operand{sbMem{size: 16, name: "gcmPoly"}, vreg(t, "X1")}, "660f6f0d00000000", "MOVDQA"},
|
|
|
|
|
|
{"MOVO X2,sym", "MOVO", []Operand{vreg(t, "X2"), sbMem{size: 16, name: "gcmPoly"}}, "660f7f1500000000", "MOVDQA"},
|
2026-07-11 17:36:52 +02:00
|
|
|
|
}
|
|
|
|
|
|
for _, c := range cases {
|
|
|
|
|
|
code, err := Encode(c.mnem, c.ops...)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
t.Errorf("%s: Encode: %v", c.name, err)
|
|
|
|
|
|
continue
|
|
|
|
|
|
}
|
|
|
|
|
|
if got := hexCompact(code); got != c.want {
|
|
|
|
|
|
t.Errorf("%s: bytes %s, want %s", c.name, got, c.want)
|
|
|
|
|
|
continue
|
|
|
|
|
|
}
|
|
|
|
|
|
inst, err := x86asm.Decode(code, 64)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
t.Errorf("%s: Decode(%x): %v", c.name, code, err)
|
|
|
|
|
|
continue
|
|
|
|
|
|
}
|
|
|
|
|
|
if inst.Op.String() != c.wantOp {
|
|
|
|
|
|
t.Errorf("%s: decoded as %s", c.name, inst.Op.String())
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-07-06 09:49:50 +02:00
|
|
|
|
// TestGoFlacScalarTail encodes the scalar tail of an analyze kernel to confirm
|
|
|
|
|
|
// the encoder handles a realistic instruction sequence.
|
|
|
|
|
|
func TestGoFlacScalarTail(t *testing.T) {
|
2026-09-16 23:12:31 +02:00
|
|
|
|
// MOVQ swin_base+0(FP), SI; modelled as MOVQ disp(reg), reg.
|
2026-07-06 09:49:50 +02:00
|
|
|
|
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})
|
|
|
|
|
|
}
|
2026-07-08 12:51:35 +02:00
|
|
|
|
|
|
|
|
|
|
// TestScalarGroundTruth checks the scalar instruction families the go-flac
|
|
|
|
|
|
// kernels use beyond the basic set, byte for byte against the Go assembler's
|
|
|
|
|
|
// machine code. wantOp is the x86 decoder's name, which differs from the
|
|
|
|
|
|
// Plan 9 spelling for some of these (CMOVLGT → CMOVG, MOVBLZX → MOVZX, …).
|
|
|
|
|
|
func TestScalarGroundTruth(t *testing.T) {
|
|
|
|
|
|
r8 := Reg{idx: 8, size: 8}
|
|
|
|
|
|
r9 := Reg{idx: 9, size: 8}
|
|
|
|
|
|
r9w := Reg{idx: 9, size: 2}
|
|
|
|
|
|
r8w := Reg{idx: 8, size: 2}
|
|
|
|
|
|
r13 := Reg{idx: 13, size: 8}
|
|
|
|
|
|
cases := []struct {
|
|
|
|
|
|
name string
|
|
|
|
|
|
mnem string
|
|
|
|
|
|
ops []Operand
|
|
|
|
|
|
want string
|
|
|
|
|
|
wantOp string
|
|
|
|
|
|
}{
|
|
|
|
|
|
{"LZCNTL AX,CX", "LZCNTL", []Operand{AX, CX}, "f30fbdc8", "LZCNT"},
|
|
|
|
|
|
{"LZCNTQ R8,R9", "LZCNTQ", []Operand{r8, r9}, "f34d0fbdc8", "LZCNT"},
|
|
|
|
|
|
{"LZCNTW AX,CX", "LZCNTW", []Operand{AX, CX}, "66f30fbdc8", "LZCNT"},
|
|
|
|
|
|
{"TZCNTL AX,CX", "TZCNTL", []Operand{AX, CX}, "f30fbcc8", "TZCNT"},
|
2026-08-24 20:19:34 +02:00
|
|
|
|
// Bit scan: BSF/BSR are the unprefixed forms of TZCNT/LZCNT's map.
|
|
|
|
|
|
{"BSFL AX,CX", "BSFL", []Operand{AX, CX}, "0fbcc8", "BSF"},
|
|
|
|
|
|
{"BSFQ R8,R9", "BSFQ", []Operand{r8, r9}, "4d0fbcc8", "BSF"},
|
|
|
|
|
|
{"BSFW AX,CX", "BSFW", []Operand{AX, CX}, "660fbcc8", "BSF"},
|
|
|
|
|
|
{"BSRL AX,CX", "BSRL", []Operand{AX, CX}, "0fbdc8", "BSR"},
|
|
|
|
|
|
{"BSRQ AX,CX", "BSRQ", []Operand{AX, CX}, "480fbdc8", "BSR"},
|
|
|
|
|
|
{"POPCNTL AX,CX", "POPCNTL", []Operand{AX, CX}, "f30fb8c8", "POPCNT"},
|
|
|
|
|
|
{"POPCNTQ R8,R9", "POPCNTQ", []Operand{r8, r9}, "f34d0fb8c8", "POPCNT"},
|
2026-08-24 20:23:39 +02:00
|
|
|
|
// A 64-bit immediate that fits a signed int32 is compressed exactly
|
|
|
|
|
|
// as the Go assembler does: positive via B8+rd without REX.W
|
|
|
|
|
|
// (zero-extended), negative via REX.W C7 /0 (sign-extended).
|
|
|
|
|
|
{"MOVQ $4,BX", "MOVQ", []Operand{Imm(4), BX}, "bb04000000", "MOV"},
|
|
|
|
|
|
{"MOVQ $4,R8", "MOVQ", []Operand{Imm(4), r8}, "41b804000000", "MOV"},
|
|
|
|
|
|
{"MOVQ $-1,BX", "MOVQ", []Operand{Imm(-1), BX}, "48c7c3ffffffff", "MOV"},
|
|
|
|
|
|
{"MOVQ big,BX", "MOVQ", []Operand{Imm(0x1122334455667788), BX}, "48bb8877665544332211", "MOV"},
|
2026-07-08 12:51:35 +02:00
|
|
|
|
{"CMOVLGT CX,AX", "CMOVLGT", []Operand{CX, AX}, "0f4fc1", "CMOVG"},
|
|
|
|
|
|
{"CMOVLEQ CX,AX", "CMOVLEQ", []Operand{CX, AX}, "0f44c1", "CMOVE"},
|
|
|
|
|
|
{"CMOVQGT R9,R8", "CMOVQGT", []Operand{r9, r8}, "4d0f4fc1", "CMOVG"},
|
|
|
|
|
|
{"CMOVWLS R9W,R8W", "CMOVWLS", []Operand{r9w, r8w}, "66450f46c1", "CMOVBE"},
|
|
|
|
|
|
{"SETNE AL", "SETNE", []Operand{AL}, "0f95c0", "SETNE"},
|
|
|
|
|
|
{"SETNE (AX)", "SETNE", []Operand{Ptr(AX, 0, 1)}, "0f9500", "SETNE"},
|
|
|
|
|
|
{"MOVBLZX AL,CX", "MOVBLZX", []Operand{AL, CX}, "0fb6c8", "MOVZX"},
|
|
|
|
|
|
{"MOVBLZX (SI),CX", "MOVBLZX", []Operand{Ptr(SI, 0, 1), CX}, "0fb60e", "MOVZX"},
|
|
|
|
|
|
{"MOVWLSX (SI)(AX*1),CX", "MOVWLSX", []Operand{Idx(SI, AX, 1, 0, 2), CX}, "0fbf0c06", "MOVSX"},
|
|
|
|
|
|
{"MOVLQSX CX,R8", "MOVLQSX", []Operand{CX, r8}, "4c63c1", "MOVSXD"},
|
|
|
|
|
|
{"MOVBQZX AL,R8", "MOVBQZX", []Operand{AL, r8}, "4c0fb6c0", "MOVZX"},
|
|
|
|
|
|
{"MOVWLZX AX,CX", "MOVWLZX", []Operand{AX, CX}, "0fb7c8", "MOVZX"},
|
|
|
|
|
|
{"MOVWQZX AX,R8", "MOVWQZX", []Operand{AX, r8}, "4c0fb7c0", "MOVZX"},
|
2026-09-20 00:38:24 +02:00
|
|
|
|
// The width pairs the toolchain accepts and GOROOT uses; bytes
|
|
|
|
|
|
// pinned from go tool asm (see testdata/verify/widen_amd64.s).
|
|
|
|
|
|
{"MOVBWZX (BX),R11W", "MOVBWZX", []Operand{Ptr(BX, 0, 1), Reg{idx: 11, size: 2}}, "66440fb61b", "MOVZX"},
|
|
|
|
|
|
{"MOVBWSX (BX),R11W", "MOVBWSX", []Operand{Ptr(BX, 0, 1), Reg{idx: 11, size: 2}}, "66440fbe1b", "MOVSX"},
|
|
|
|
|
|
{"MOVBLSX (BX),AX", "MOVBLSX", []Operand{Ptr(BX, 0, 1), AX}, "0fbe03", "MOVSX"},
|
|
|
|
|
|
{"MOVBQSX (BX),R8", "MOVBQSX", []Operand{Ptr(BX, 0, 1), r8}, "4c0fbe03", "MOVSX"},
|
|
|
|
|
|
{"MOVWQSX (BX),R9", "MOVWQSX", []Operand{Ptr(BX, 0, 2), r9}, "4c0fbf0b", "MOVSX"},
|
|
|
|
|
|
// A long to quad zero-extend is a plain 32-bit move.
|
|
|
|
|
|
{"MOVLQZX (BX),DX", "MOVLQZX", []Operand{Ptr(BX, 0, 4), DX}, "8b13", "MOV"},
|
|
|
|
|
|
{"MOVLQZX AX,DX", "MOVLQZX", []Operand{AX, DX}, "8bd0", "MOV"},
|
|
|
|
|
|
{"PMOVMSKB X1,AX", "PMOVMSKB", []Operand{vreg(t, "X1"), AX}, "660fd7c1", "PMOVMSKB"},
|
|
|
|
|
|
{"PMOVMSKB X11,CX", "PMOVMSKB", []Operand{vreg(t, "X11"), CX}, "66410fd7cb", "PMOVMSKB"},
|
2026-07-08 12:51:35 +02:00
|
|
|
|
{"CVTSL2SD R8,X13", "CVTSL2SD", []Operand{r8, vreg(t, "X13")}, "f2450f2ae8", "CVTSI2SD"},
|
|
|
|
|
|
{"CVTSL2SD AX,X0", "CVTSL2SD", []Operand{AX, vreg(t, "X0")}, "f20f2ac0", "CVTSI2SD"},
|
|
|
|
|
|
{"CVTSQ2SD R8,X13", "CVTSQ2SD", []Operand{r8, vreg(t, "X13")}, "f24d0f2ae8", "CVTSI2SD"},
|
|
|
|
|
|
{"INCW (R13)(AX*2)", "INCW", []Operand{Idx(r13, AX, 2, 0, 2)}, "6641ff444500", "INC"},
|
|
|
|
|
|
// The traditional three-operand IMUL spelling.
|
|
|
|
|
|
{"IMUL3L $31,CX,DX", "IMUL3L", []Operand{Imm(31), CX, DX}, "6bd11f", "IMUL"},
|
|
|
|
|
|
{"IMUL3L $256,CX,DX", "IMUL3L", []Operand{Imm(256), CX, DX}, "69d100010000", "IMUL"},
|
|
|
|
|
|
{"IMUL3Q $7,R9,R8", "IMUL3Q", []Operand{Imm(7), r9, r8}, "4d6bc107", "IMUL"},
|
|
|
|
|
|
{"IMUL3W $5,CX,DX", "IMUL3W", []Operand{Imm(5), CX, DX}, "666bd105", "IMUL"},
|
|
|
|
|
|
// Negative displacement with base + index (regression: the parser
|
|
|
|
|
|
// used to drop the whole address).
|
|
|
|
|
|
{"LEAQ -4(DX)(R9*4),R9", "LEAQ", []Operand{Idx(DX, r9, 4, -4, 8), r9}, "4e8d4c8afc", "LEA"},
|
|
|
|
|
|
{"LEAQ 16(SI)(BX*4),R10", "LEAQ", []Operand{Idx(SI, BX, 4, 16, 8), Reg{idx: 10, size: 8}}, "4c8d549e10", "LEA"},
|
|
|
|
|
|
// Register-to-register MOV uses the r/m←r opcode (reg = source), the
|
|
|
|
|
|
// Go assembler's choice.
|
|
|
|
|
|
{"MOVQ BX,R10", "MOVQ", []Operand{BX, Reg{idx: 10, size: 8}}, "4989da", "MOV"},
|
|
|
|
|
|
{"MOVQ AX,BX", "MOVQ", []Operand{AX, BX}, "4889c3", "MOV"},
|
|
|
|
|
|
{"MOVL AX,BX", "MOVL", []Operand{AX, BX}, "89c3", "MOV"},
|
|
|
|
|
|
{"MOVB AL,BL", "MOVB", []Operand{AL, BL}, "88c3", "MOV"},
|
|
|
|
|
|
{"MOVW AX,BX", "MOVW", []Operand{AX, BX}, "6689c3", "MOV"},
|
|
|
|
|
|
{"MOVQ R12,R13", "MOVQ", []Operand{Reg{idx: 12, size: 8}, Reg{idx: 13, size: 8}}, "4d89e5", "MOV"},
|
|
|
|
|
|
// CMP must record first − second: with a register second operand the
|
|
|
|
|
|
// first goes in r/m, with a memory second operand the first goes in reg.
|
|
|
|
|
|
{"CMPQ SI,R10", "CMPQ", []Operand{SI, Reg{idx: 10, size: 8}}, "4c39d6", "CMP"},
|
|
|
|
|
|
{"CMPQ SI,(AX)", "CMPQ", []Operand{SI, Ptr(AX, 0, 8)}, "483b30", "CMP"},
|
|
|
|
|
|
{"CMPQ (AX),SI", "CMPQ", []Operand{Ptr(AX, 0, 8), SI}, "483930", "CMP"},
|
|
|
|
|
|
{"CMPL CX,(AX)", "CMPL", []Operand{CX, Ptr(AX, 0, 4)}, "3b08", "CMP"},
|
|
|
|
|
|
{"CMPB AL,(BX)", "CMPB", []Operand{AL, Ptr(BX, 0, 1)}, "3a03", "CMP"},
|
|
|
|
|
|
{"CMPW AX,BX", "CMPW", []Operand{AX, BX}, "6639d8", "CMP"},
|
|
|
|
|
|
}
|
|
|
|
|
|
for _, c := range cases {
|
|
|
|
|
|
code, err := Encode(c.mnem, c.ops...)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
t.Errorf("%s: Encode: %v", c.name, err)
|
|
|
|
|
|
continue
|
|
|
|
|
|
}
|
|
|
|
|
|
if got := strings.ReplaceAll(hexBytes(code), " ", ""); got != c.want {
|
|
|
|
|
|
t.Errorf("%s: bytes %s, want %s", c.name, got, c.want)
|
|
|
|
|
|
continue
|
|
|
|
|
|
}
|
|
|
|
|
|
inst, err := x86asm.Decode(code, 64)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
t.Errorf("%s: Decode(% x): %v", c.name, code, err)
|
|
|
|
|
|
continue
|
|
|
|
|
|
}
|
|
|
|
|
|
if inst.Op.String() != c.wantOp {
|
|
|
|
|
|
t.Errorf("%s: decoded as %s", c.name, inst.Op.String())
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// TestScalarErrors checks that malformed conditional / extend / convert
|
|
|
|
|
|
// instructions are rejected.
|
|
|
|
|
|
func TestScalarErrors(t *testing.T) {
|
|
|
|
|
|
cases := []struct {
|
|
|
|
|
|
name string
|
|
|
|
|
|
mnem string
|
|
|
|
|
|
ops []Operand
|
|
|
|
|
|
}{
|
|
|
|
|
|
{"CMOV arity", "CMOVLGT", []Operand{AX}},
|
|
|
|
|
|
{"CMOV bare", "CMOV", []Operand{AX, BX}},
|
|
|
|
|
|
{"CMOV bad size", "CMOVBGT", []Operand{AX, BX}},
|
|
|
|
|
|
{"CMOV bad condition", "CMOVLXX", []Operand{AX, BX}},
|
|
|
|
|
|
{"CMOV mem dst", "CMOVLGT", []Operand{AX, Ptr(BX, 0, 4)}},
|
|
|
|
|
|
{"SET arity", "SETNE", []Operand{AL, BL}},
|
|
|
|
|
|
{"SET bad condition", "SETXX", []Operand{AL}},
|
|
|
|
|
|
{"SET bare", "SET", []Operand{AL}},
|
|
|
|
|
|
{"LZCNT arity", "LZCNTL", []Operand{AX}},
|
|
|
|
|
|
{"LZCNT mem dst", "LZCNTL", []Operand{AX, Ptr(BX, 0, 4)}},
|
|
|
|
|
|
{"MOVBLZX mem dst", "MOVBLZX", []Operand{AL, Ptr(BX, 0, 4)}},
|
|
|
|
|
|
{"CVTSL2SD gpr dst", "CVTSL2SD", []Operand{AX, BX}},
|
|
|
|
|
|
}
|
|
|
|
|
|
for _, c := range cases {
|
|
|
|
|
|
if _, err := Encode(c.mnem, c.ops...); err == nil {
|
|
|
|
|
|
t.Errorf("%s: expected an error, got none", c.name)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-08-27 16:36:18 +02:00
|
|
|
|
|
2026-09-19 23:49:07 +02:00
|
|
|
|
// TestImmediateOutOfRange pins the go-tool-asm parity of the immediate and
|
|
|
|
|
|
// displacement spans: a scalar immediate must fit a signed or unsigned 32-bit
|
|
|
|
|
|
// word (only MOVQ reg, $imm takes the full int64), a scalar shift count must
|
|
|
|
|
|
// be an unsigned byte, and a displacement must fit int32. Every rejected
|
|
|
|
|
|
// shape here is rejected by `go tool asm` too; every accepted one encodes the
|
|
|
|
|
|
// same bytes.
|
|
|
|
|
|
func TestImmediateOutOfRange(t *testing.T) {
|
|
|
|
|
|
cases := []struct {
|
|
|
|
|
|
name string
|
|
|
|
|
|
mnem string
|
|
|
|
|
|
ops []Operand
|
|
|
|
|
|
}{
|
|
|
|
|
|
{"SHLQ count 300", "SHLQ", []Operand{Imm(300), AX}},
|
|
|
|
|
|
{"SHLQ count -1", "SHLQ", []Operand{Imm(-1), AX}},
|
|
|
|
|
|
{"SHLW count 256", "SHLW", []Operand{Imm(256), DX}},
|
|
|
|
|
|
{"SHLB count 300", "SHLB", []Operand{Imm(300), BL}},
|
|
|
|
|
|
{"MOVL imm32+", "MOVL", []Operand{Imm(4294967296), AX}},
|
|
|
|
|
|
{"MOVL imm32-", "MOVL", []Operand{Imm(-2147483649), AX}},
|
|
|
|
|
|
{"MOVW imm32+", "MOVW", []Operand{Imm(4294967296), AX}},
|
|
|
|
|
|
{"MOVB imm32+", "MOVB", []Operand{Imm(4294967296), AL}},
|
|
|
|
|
|
{"ADDB imm32+", "ADDB", []Operand{Imm(4294967296), AL}},
|
|
|
|
|
|
{"ADDL imm32+", "ADDL", []Operand{Imm(4294967296), AX}},
|
|
|
|
|
|
{"ADDQ imm32+", "ADDQ", []Operand{Imm(8589934592), AX}},
|
|
|
|
|
|
{"CMPQ imm32+", "CMPQ", []Operand{AX, Imm(4294967296)}},
|
|
|
|
|
|
{"CMPQ imm32-", "CMPQ", []Operand{AX, Imm(-2147483649)}},
|
|
|
|
|
|
{"TESTL imm32+", "TESTL", []Operand{Imm(4294967296), AX}},
|
|
|
|
|
|
{"IMUL3L imm32+", "IMUL3L", []Operand{Imm(4294967296), CX, DX}},
|
|
|
|
|
|
{"PUSHQ imm32+", "PUSHQ", []Operand{Imm(4294967296)}},
|
|
|
|
|
|
{"MOVQ mem imm32+", "MOVQ", []Operand{Imm(4294967296), Ptr(AX, 0, 8)}},
|
|
|
|
|
|
{"disp32+", "MOVQ", []Operand{Ptr(AX, 4294967296, 8), BX}},
|
|
|
|
|
|
{"disp32+ max", "MOVQ", []Operand{Ptr(AX, 2147483648, 8), BX}},
|
|
|
|
|
|
{"disp32-", "MOVQ", []Operand{Ptr(AX, -2147483649, 8), BX}},
|
|
|
|
|
|
{"VEX disp32+", "VMOVDQU", []Operand{Ptr(AX, 4294967296, 32), vreg(t, "Y1")}},
|
|
|
|
|
|
{"EVEX disp32+", "VMOVDQU32", []Operand{Ptr(AX, 4294967296, 64), vreg(t, "Z1")}},
|
|
|
|
|
|
}
|
|
|
|
|
|
for _, c := range cases {
|
|
|
|
|
|
if _, err := Encode(c.mnem, c.ops...); err == nil {
|
|
|
|
|
|
t.Errorf("%s: expected an error, got none", c.name)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// TestImmediateTruncation pins the toolchain-matching truncations inside the
|
|
|
|
|
|
// accepted 32-bit span: the narrower fields take the low bits silently, byte
|
|
|
|
|
|
// for byte with `go tool asm` (which rejects none of these).
|
|
|
|
|
|
func TestImmediateTruncation(t *testing.T) {
|
|
|
|
|
|
cases := []struct {
|
|
|
|
|
|
name string
|
|
|
|
|
|
mnem string
|
|
|
|
|
|
ops []Operand
|
|
|
|
|
|
want string
|
|
|
|
|
|
}{
|
|
|
|
|
|
{"ADDB $256,BL", "ADDB", []Operand{Imm(256), BL}, "80c300"},
|
|
|
|
|
|
{"ADDB $1000,BL", "ADDB", []Operand{Imm(1000), BL}, "80c3e8"},
|
|
|
|
|
|
{"MOVB $256,AL", "MOVB", []Operand{Imm(256), AL}, "b000"},
|
|
|
|
|
|
{"MOVB $-129,AL", "MOVB", []Operand{Imm(-129), AL}, "b07f"},
|
|
|
|
|
|
{"MOVW $65536,AX", "MOVW", []Operand{Imm(65536), AX}, "66b80000"},
|
|
|
|
|
|
{"MOVW $65535,AX", "MOVW", []Operand{Imm(65535), AX}, "66b8ffff"},
|
|
|
|
|
|
{"MOVW $-32769,AX", "MOVW", []Operand{Imm(-32769), AX}, "66b8ff7f"},
|
|
|
|
|
|
{"MOVL $4294967295,AX", "MOVL", []Operand{Imm(4294967295), AX}, "b8ffffffff"},
|
|
|
|
|
|
{"ADDQ $4294967295,AX", "ADDQ", []Operand{Imm(4294967295), AX}, "4805ffffffff"},
|
|
|
|
|
|
{"CMPB BL,$255", "CMPB", []Operand{BL, Imm(255)}, "80fbff"},
|
|
|
|
|
|
{"CMPQ AX,$4294967295", "CMPQ", []Operand{AX, Imm(4294967295)}, "483dffffffff"},
|
|
|
|
|
|
{"MOVQ $4294967295,0(AX)", "MOVQ", []Operand{Imm(4294967295), Ptr(AX, 0, 8)}, "48c700ffffffff"},
|
|
|
|
|
|
{"SHLQ $255,AX", "SHLQ", []Operand{Imm(255), AX}, "48c1e0ff"},
|
|
|
|
|
|
{"SHLQ $0,AX", "SHLQ", []Operand{Imm(0), AX}, "48c1e000"},
|
|
|
|
|
|
// The one form beyond the 32-bit span: the imm64 MOVQ register move.
|
|
|
|
|
|
{"MOVQ $4294967296,AX", "MOVQ", []Operand{Imm(4294967296), AX}, "48b80000000001000000"},
|
|
|
|
|
|
{"MOVQ disp32 max", "MOVQ", []Operand{Ptr(AX, 2147483647, 8), BX}, "488b98ffffff7f"},
|
|
|
|
|
|
}
|
|
|
|
|
|
for _, c := range cases {
|
|
|
|
|
|
code, err := Encode(c.mnem, c.ops...)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
t.Errorf("%s: %v", c.name, err)
|
|
|
|
|
|
continue
|
|
|
|
|
|
}
|
|
|
|
|
|
if got := fmt.Sprintf("%x", code); got != c.want {
|
|
|
|
|
|
t.Errorf("%s: bytes %s, want %s", c.name, got, c.want)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// TestEncodableCmovSize pins the linter contract for CMOVcc: Encodable must
|
|
|
|
|
|
// reject the spellings Encode rejects, so a mnemonic like CMOVBGT (no size
|
|
|
|
|
|
// letter) is not reported as encodable.
|
|
|
|
|
|
func TestEncodableCmovSize(t *testing.T) {
|
|
|
|
|
|
for _, m := range []string{"CMOVBGT", "CMOVXEQ", "CMOVB", "CMOV", "CMOVWXX"} {
|
|
|
|
|
|
if Encodable(m) {
|
|
|
|
|
|
t.Errorf("Encodable(%q) = true, want false", m)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
for _, m := range []string{"CMOVLGT", "CMOVQGT", "CMOVWLS", "CMOVLEQ"} {
|
|
|
|
|
|
if !Encodable(m) {
|
|
|
|
|
|
t.Errorf("Encodable(%q) = false, want true", m)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-09-20 06:44:51 +02:00
|
|
|
|
// TestCarryShiftMulGroundTruth pins the carry-flag ALU family (ADC/SBB with
|
|
|
|
|
|
// their accumulator immediate forms), the rotate family, MUL/DIV/IDIV and the
|
|
|
|
|
|
// bit-test family byte for byte against go tool asm (see
|
|
|
|
|
|
// testdata/verify/scalar_amd64.s).
|
|
|
|
|
|
func TestCarryShiftMulGroundTruth(t *testing.T) {
|
|
|
|
|
|
cases := []struct {
|
|
|
|
|
|
name string
|
|
|
|
|
|
mnem string
|
|
|
|
|
|
ops []Operand
|
|
|
|
|
|
want string
|
|
|
|
|
|
}{
|
|
|
|
|
|
{"ADCQ AX,BX", "ADCQ", []Operand{AX, BX}, "4811c3"},
|
|
|
|
|
|
{"ADCL AX,BX", "ADCL", []Operand{AX, BX}, "11c3"},
|
|
|
|
|
|
{"ADCB AL,BL", "ADCB", []Operand{AL, BL}, "10c3"},
|
|
|
|
|
|
{"ADCW AX,BX", "ADCW", []Operand{AX, BX}, "6611c3"},
|
|
|
|
|
|
{"SBBQ AX,BX", "SBBQ", []Operand{AX, BX}, "4819c3"},
|
|
|
|
|
|
{"ADCQ $5,BX", "ADCQ", []Operand{Imm(5), BX}, "4883d305"},
|
|
|
|
|
|
{"ADCQ $300,BX", "ADCQ", []Operand{Imm(300), BX}, "4881d32c010000"},
|
|
|
|
|
|
{"ADCQ $300,AX", "ADCQ", []Operand{Imm(300), AX}, "48152c010000"},
|
|
|
|
|
|
{"ADCB $5,AL", "ADCB", []Operand{Imm(5), AL}, "1405"},
|
|
|
|
|
|
{"SBBQ $300,AX", "SBBQ", []Operand{Imm(300), AX}, "481d2c010000"},
|
|
|
|
|
|
{"ADCQ AX,(BX)", "ADCQ", []Operand{AX, Ptr(BX, 0, 8)}, "481103"},
|
|
|
|
|
|
{"ROLQ $3,AX", "ROLQ", []Operand{Imm(3), AX}, "48c1c003"},
|
|
|
|
|
|
{"ROLL CX,BX", "ROLL", []Operand{CL, BX}, "d3c3"},
|
|
|
|
|
|
{"RORQ CL,AX", "RORQ", []Operand{CL, AX}, "48d3c8"},
|
|
|
|
|
|
{"RCRQ $1,BX", "RCRQ", []Operand{Imm(1), BX}, "48d1db"},
|
|
|
|
|
|
{"RCLQ $3,AX", "RCLQ", []Operand{Imm(3), AX}, "48c1d003"},
|
|
|
|
|
|
{"RORB CL,BL", "RORB", []Operand{CL, BL}, "d2cb"},
|
|
|
|
|
|
{"SALQ $2,AX", "SALQ", []Operand{Imm(2), AX}, "48c1e002"},
|
|
|
|
|
|
{"ROLW $1,AX", "ROLW", []Operand{Imm(1), AX}, "66d1c0"},
|
|
|
|
|
|
{"MULQ CX", "MULQ", []Operand{CX}, "48f7e1"},
|
|
|
|
|
|
{"MULL CX", "MULL", []Operand{CX}, "f7e1"},
|
|
|
|
|
|
{"MULB CL", "MULB", []Operand{CL}, "f6e1"},
|
|
|
|
|
|
{"DIVL CX", "DIVL", []Operand{CX}, "f7f1"},
|
|
|
|
|
|
{"IDIVQ CX", "IDIVQ", []Operand{CX}, "48f7f9"},
|
|
|
|
|
|
{"MULW CX", "MULW", []Operand{CX}, "66f7e1"},
|
|
|
|
|
|
{"BTQ AX,DX", "BTQ", []Operand{AX, DX}, "480fa3c2"},
|
|
|
|
|
|
{"BTL AX,DX", "BTL", []Operand{AX, DX}, "0fa3c2"},
|
|
|
|
|
|
{"BTW AX,DX", "BTW", []Operand{AX, DX}, "660fa3c2"},
|
|
|
|
|
|
{"BTQ $3,BX", "BTQ", []Operand{Imm(3), BX}, "480fbae303"},
|
|
|
|
|
|
{"BTQ $3,(AX)", "BTQ", []Operand{Imm(3), Ptr(AX, 0, 8)}, "480fba2003"},
|
|
|
|
|
|
{"BTSQ $5,BX", "BTSQ", []Operand{Imm(5), BX}, "480fbaeb05"},
|
|
|
|
|
|
{"BTCQ AX,BX", "BTCQ", []Operand{AX, BX}, "480fbbc3"},
|
|
|
|
|
|
{"BTRQ $7,BX", "BTRQ", []Operand{Imm(7), BX}, "480fbaf307"},
|
|
|
|
|
|
}
|
|
|
|
|
|
for _, c := range cases {
|
|
|
|
|
|
code, err := Encode(c.mnem, c.ops...)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
t.Errorf("%s: Encode: %v", c.name, err)
|
|
|
|
|
|
continue
|
|
|
|
|
|
}
|
|
|
|
|
|
if got := fmt.Sprintf("%x", code); got != c.want {
|
|
|
|
|
|
t.Errorf("%s = %s, want %s", c.name, got, c.want)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
// The bit-test immediate is an unsigned bit index with the negative
|
|
|
|
|
|
// spelling accepted, the shuffle convention: BTQ $300 must be rejected.
|
|
|
|
|
|
if _, err := Encode("BTQ", Imm(300), AX); err == nil {
|
|
|
|
|
|
t.Errorf("BTQ $300: expected an error, got none")
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// TestAtomicSystemGroundTruth pins the exchange/compare-exchange/accumulate
|
|
|
|
|
|
// family, the string primitives, the flag and system instructions, the MXCSR
|
|
|
|
|
|
// pair, the scalar float-to-int conversions and the x87 FMOVD byte for byte
|
|
|
|
|
|
// against go tool asm (see testdata/verify/atomics_amd64.s and
|
|
|
|
|
|
// testdata/verify/system_amd64.s).
|
|
|
|
|
|
func TestAtomicSystemGroundTruth(t *testing.T) {
|
|
|
|
|
|
r8 := Reg{idx: 8, size: 8}
|
|
|
|
|
|
cases := []struct {
|
|
|
|
|
|
name string
|
|
|
|
|
|
mnem string
|
|
|
|
|
|
ops []Operand
|
|
|
|
|
|
want string
|
|
|
|
|
|
}{
|
|
|
|
|
|
{"XCHGQ AX,BX", "XCHGQ", []Operand{AX, BX}, "4893"},
|
|
|
|
|
|
{"XCHGQ BX,AX", "XCHGQ", []Operand{BX, AX}, "4893"},
|
|
|
|
|
|
{"XCHGL AX,BX", "XCHGL", []Operand{AX, BX}, "93"},
|
|
|
|
|
|
{"XCHGB AL,BL", "XCHGB", []Operand{AL, BL}, "86c3"},
|
|
|
|
|
|
{"XCHGW AX,BX", "XCHGW", []Operand{AX, BX}, "6693"},
|
|
|
|
|
|
{"XCHGQ R8,R9", "XCHGQ", []Operand{r8, Reg{idx: 9, size: 8}}, "4d87c1"},
|
|
|
|
|
|
{"XCHGQ BX,(AX)", "XCHGQ", []Operand{BX, Ptr(AX, 0, 8)}, "488718"},
|
|
|
|
|
|
{"XCHGQ (AX),BX", "XCHGQ", []Operand{Ptr(AX, 0, 8), BX}, "488718"},
|
|
|
|
|
|
{"XCHGQ AX,(BX)", "XCHGQ", []Operand{AX, Ptr(BX, 0, 8)}, "488703"},
|
|
|
|
|
|
{"CMPXCHGL AX,BX", "CMPXCHGL", []Operand{AX, BX}, "0fb1c3"},
|
|
|
|
|
|
{"CMPXCHGQ AX,(BX)", "CMPXCHGQ", []Operand{AX, Ptr(BX, 0, 8)}, "480fb103"},
|
|
|
|
|
|
{"CMPXCHGB AL,(BX)", "CMPXCHGB", []Operand{AL, Ptr(BX, 0, 1)}, "0fb003"},
|
|
|
|
|
|
{"CMPXCHGW AX,BX", "CMPXCHGW", []Operand{AX, BX}, "660fb1c3"},
|
|
|
|
|
|
{"XADDL AX,BX", "XADDL", []Operand{AX, BX}, "0fc1c3"},
|
|
|
|
|
|
{"XADDQ AX,(BX)", "XADDQ", []Operand{AX, Ptr(BX, 0, 8)}, "480fc103"},
|
|
|
|
|
|
{"XADDB AL,(BX)", "XADDB", []Operand{AL, Ptr(BX, 0, 1)}, "0fc003"},
|
|
|
|
|
|
{"XADDW AX,BX", "XADDW", []Operand{AX, BX}, "660fc1c3"},
|
|
|
|
|
|
{"ADCXL AX,CX", "ADCXL", []Operand{AX, CX}, "660f38f6c8"},
|
|
|
|
|
|
{"ADCXQ AX,CX", "ADCXQ", []Operand{AX, CX}, "66480f38f6c8"},
|
|
|
|
|
|
{"ADOXL AX,CX", "ADOXL", []Operand{AX, CX}, "f30f38f6c8"},
|
|
|
|
|
|
{"ADOXQ AX,CX", "ADOXQ", []Operand{AX, CX}, "f3480f38f6c8"},
|
|
|
|
|
|
{"CRC32B AX,CX", "CRC32B", []Operand{AX, CX}, "f20f38f0c8"},
|
|
|
|
|
|
{"CRC32W AX,CX", "CRC32W", []Operand{AX, CX}, "66f20f38f1c8"},
|
|
|
|
|
|
{"CRC32L AX,CX", "CRC32L", []Operand{AX, CX}, "f20f38f1c8"},
|
|
|
|
|
|
{"CRC32Q AX,CX", "CRC32Q", []Operand{AX, CX}, "f2480f38f1c8"},
|
|
|
|
|
|
{"CRC32L (AX),CX", "CRC32L", []Operand{Ptr(AX, 0, 4), CX}, "f20f38f108"},
|
|
|
|
|
|
{"MOVSQ", "MOVSQ", []Operand{}, "48a5"},
|
|
|
|
|
|
{"MOVSL", "MOVSL", []Operand{}, "a5"},
|
|
|
|
|
|
{"MOVSB", "MOVSB", []Operand{}, "a4"},
|
|
|
|
|
|
{"MOVSW", "MOVSW", []Operand{}, "66a5"},
|
|
|
|
|
|
{"STOSB", "STOSB", []Operand{}, "aa"},
|
|
|
|
|
|
{"STOSQ", "STOSQ", []Operand{}, "48ab"},
|
|
|
|
|
|
{"STOSL", "STOSL", []Operand{}, "ab"},
|
|
|
|
|
|
{"STOSW", "STOSW", []Operand{}, "66ab"},
|
|
|
|
|
|
{"CLD", "CLD", []Operand{}, "fc"},
|
|
|
|
|
|
{"STD", "STD", []Operand{}, "fd"},
|
|
|
|
|
|
{"POPFQ", "POPFQ", []Operand{}, "9d"},
|
|
|
|
|
|
{"PUSHFQ", "PUSHFQ", []Operand{}, "9c"},
|
|
|
|
|
|
{"CPUID", "CPUID", []Operand{}, "0fa2"},
|
|
|
|
|
|
{"RDTSC", "RDTSC", []Operand{}, "0f31"},
|
|
|
|
|
|
{"RDTSCP", "RDTSCP", []Operand{}, "0f01f9"},
|
|
|
|
|
|
{"SYSCALL", "SYSCALL", []Operand{}, "0f05"},
|
|
|
|
|
|
{"XGETBV", "XGETBV", []Operand{}, "0f01d0"},
|
|
|
|
|
|
{"PAUSE", "PAUSE", []Operand{}, "f390"},
|
|
|
|
|
|
{"LFENCE", "LFENCE", []Operand{}, "0faee8"},
|
|
|
|
|
|
{"MFENCE", "MFENCE", []Operand{}, "0faef0"},
|
|
|
|
|
|
{"SFENCE", "SFENCE", []Operand{}, "0faef8"},
|
|
|
|
|
|
{"UNDEF", "UNDEF", []Operand{}, "0f0b"},
|
|
|
|
|
|
{"INT $3", "INT", []Operand{Imm(3)}, "cd03"},
|
|
|
|
|
|
{"LDMXCSR (AX)", "LDMXCSR", []Operand{Ptr(AX, 0, 4)}, "0fae10"},
|
|
|
|
|
|
{"STMXCSR (AX)", "STMXCSR", []Operand{Ptr(AX, 0, 4)}, "0fae18"},
|
|
|
|
|
|
{"CVTSD2SL X0,AX", "CVTSD2SL", []Operand{vreg(t, "X0"), AX}, "f20f2dc0"},
|
|
|
|
|
|
{"CVTTSD2SQ X0,AX", "CVTTSD2SQ", []Operand{vreg(t, "X0"), AX}, "f2480f2cc0"},
|
|
|
|
|
|
{"CVTTSD2SL X0,AX", "CVTTSD2SL", []Operand{vreg(t, "X0"), AX}, "f20f2cc0"},
|
|
|
|
|
|
{"CVTSS2SQ X0,AX", "CVTSS2SQ", []Operand{vreg(t, "X0"), AX}, "f3480f2dc0"},
|
|
|
|
|
|
{"FMOVD (AX),F0", "FMOVD", []Operand{Ptr(AX, 0, 8), vreg(t, "F0")}, "dd00"},
|
|
|
|
|
|
{"FMOVD F0,(AX)", "FMOVD", []Operand{vreg(t, "F0"), Ptr(AX, 0, 8)}, "dd10"},
|
|
|
|
|
|
{"FMOVD F0,F1", "FMOVD", []Operand{vreg(t, "F0"), vreg(t, "F1")}, "ddd1"},
|
|
|
|
|
|
{"MOVD AX,X0", "MOVD", []Operand{AX, vreg(t, "X0")}, "66480f6ec0"},
|
|
|
|
|
|
{"MOVD X0,AX", "MOVD", []Operand{vreg(t, "X0"), AX}, "66480f7ec0"},
|
|
|
|
|
|
{"MOVD X0,X1", "MOVD", []Operand{vreg(t, "X0"), vreg(t, "X1")}, "f30f7ec8"},
|
|
|
|
|
|
{"MOVD (AX),X0", "MOVD", []Operand{Ptr(AX, 0, 8), vreg(t, "X0")}, "f30f7e00"},
|
|
|
|
|
|
{"MOVD X0,(AX)", "MOVD", []Operand{vreg(t, "X0"), Ptr(AX, 0, 8)}, "660fd600"},
|
|
|
|
|
|
}
|
|
|
|
|
|
for _, c := range cases {
|
|
|
|
|
|
code, err := Encode(c.mnem, c.ops...)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
t.Errorf("%s: Encode: %v", c.name, err)
|
|
|
|
|
|
continue
|
|
|
|
|
|
}
|
|
|
|
|
|
if got := fmt.Sprintf("%x", code); got != c.want {
|
|
|
|
|
|
t.Errorf("%s = %s, want %s", c.name, got, c.want)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
// LDMXCSR/STMXCSR take a memory operand only.
|
|
|
|
|
|
if _, err := Encode("LDMXCSR", AX); err == nil {
|
|
|
|
|
|
t.Errorf("LDMXCSR AX: expected an error, got none")
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// TestSSEGapsGroundTruth pins the legacy SSE gap families: the scalar
|
|
|
|
|
|
// compare and square root, the Plan 9 packed spellings, the imm8-controlled
|
|
|
|
|
|
// shuffles, the lane extracts and inserts, the packed integer shifts and the
|
|
|
|
|
|
// AES/SHA round instructions, byte for byte against go tool asm (see
|
|
|
|
|
|
// testdata/verify/crypto_amd64.s and testdata/verify/sse_amd64.s).
|
|
|
|
|
|
func TestSSEGapsGroundTruth(t *testing.T) {
|
|
|
|
|
|
cases := []struct {
|
|
|
|
|
|
name string
|
|
|
|
|
|
mnem string
|
|
|
|
|
|
ops []Operand
|
|
|
|
|
|
want string
|
|
|
|
|
|
}{
|
|
|
|
|
|
{"ANDNPD X0,X1", "ANDNPD", []Operand{vreg(t, "X0"), vreg(t, "X1")}, "660f55c8"},
|
|
|
|
|
|
{"ANDNPS X0,X1", "ANDNPS", []Operand{vreg(t, "X0"), vreg(t, "X1")}, "0f55c8"},
|
|
|
|
|
|
{"COMISD X0,X1", "COMISD", []Operand{vreg(t, "X0"), vreg(t, "X1")}, "660f2fc8"},
|
|
|
|
|
|
{"SQRTSD X0,X1", "SQRTSD", []Operand{vreg(t, "X0"), vreg(t, "X1")}, "f20f51c8"},
|
|
|
|
|
|
{"PSHUFL $3,X0,X1", "PSHUFL", []Operand{Imm(3), vreg(t, "X0"), vreg(t, "X1")}, "660f70c803"},
|
|
|
|
|
|
{"PALIGNR $2,X0,X1", "PALIGNR", []Operand{Imm(2), vreg(t, "X0"), vreg(t, "X1")}, "660f3a0fc802"},
|
|
|
|
|
|
{"PBLENDW $3,X0,X1", "PBLENDW", []Operand{Imm(3), vreg(t, "X0"), vreg(t, "X1")}, "660f3a0ec803"},
|
|
|
|
|
|
{"PCMPESTRI $1,X0,X1", "PCMPESTRI", []Operand{Imm(1), vreg(t, "X0"), vreg(t, "X1")}, "660f3a61c801"},
|
|
|
|
|
|
{"PCLMULQDQ $0,X0,X1", "PCLMULQDQ", []Operand{Imm(0), vreg(t, "X0"), vreg(t, "X1")}, "660f3a44c800"},
|
|
|
|
|
|
{"PCLMULQDQ $0,(AX),X1", "PCLMULQDQ", []Operand{Imm(0), Ptr(AX, 0, 16), vreg(t, "X1")}, "660f3a440800"},
|
|
|
|
|
|
{"PEXTRB $1,X0,AX", "PEXTRB", []Operand{Imm(1), vreg(t, "X0"), AX}, "660f3a14c001"},
|
|
|
|
|
|
{"PEXTRD $1,X0,AX", "PEXTRD", []Operand{Imm(1), vreg(t, "X0"), AX}, "660f3a16c001"},
|
|
|
|
|
|
{"PEXTRQ $1,X0,AX", "PEXTRQ", []Operand{Imm(1), vreg(t, "X0"), AX}, "66480f3a16c001"},
|
|
|
|
|
|
{"PEXTRW $1,X0,AX", "PEXTRW", []Operand{Imm(1), vreg(t, "X0"), AX}, "660fc5c001"},
|
|
|
|
|
|
{"PEXTRW $1,X0,(AX)", "PEXTRW", []Operand{Imm(1), vreg(t, "X0"), Ptr(AX, 0, 2)}, "660f3a150001"},
|
|
|
|
|
|
{"PINSRB $1,AX,X0", "PINSRB", []Operand{Imm(1), AX, vreg(t, "X0")}, "660f3a20c001"},
|
|
|
|
|
|
{"PINSRD $1,AX,X0", "PINSRD", []Operand{Imm(1), AX, vreg(t, "X0")}, "660f3a22c001"},
|
|
|
|
|
|
{"PINSRQ $1,AX,X0", "PINSRQ", []Operand{Imm(1), AX, vreg(t, "X0")}, "66480f3a22c001"},
|
|
|
|
|
|
{"PINSRW $1,AX,X0", "PINSRW", []Operand{Imm(1), AX, vreg(t, "X0")}, "660fc4c001"},
|
|
|
|
|
|
{"PINSRW $1,(AX),X0", "PINSRW", []Operand{Imm(1), Ptr(AX, 0, 2), vreg(t, "X0")}, "660fc40001"},
|
|
|
|
|
|
{"PSLLL $2,X0", "PSLLL", []Operand{Imm(2), vreg(t, "X0")}, "660f72f002"},
|
|
|
|
|
|
{"PSRAL $2,X0", "PSRAL", []Operand{Imm(2), vreg(t, "X0")}, "660f72e002"},
|
|
|
|
|
|
{"PSRLL $2,X0", "PSRLL", []Operand{Imm(2), vreg(t, "X0")}, "660f72d002"},
|
|
|
|
|
|
{"PSRLQ $2,X0", "PSRLQ", []Operand{Imm(2), vreg(t, "X0")}, "660f73d002"},
|
|
|
|
|
|
{"PSLLQ $2,X0", "PSLLQ", []Operand{Imm(2), vreg(t, "X0")}, "660f73f002"},
|
|
|
|
|
|
{"PSLLW $2,X0", "PSLLW", []Operand{Imm(2), vreg(t, "X0")}, "660f71f002"},
|
|
|
|
|
|
{"PSRLW $2,X0", "PSRLW", []Operand{Imm(2), vreg(t, "X0")}, "660f71d002"},
|
|
|
|
|
|
{"PSRAW $2,X0", "PSRAW", []Operand{Imm(2), vreg(t, "X0")}, "660f71e002"},
|
|
|
|
|
|
{"PSLLDQ $2,X0", "PSLLDQ", []Operand{Imm(2), vreg(t, "X0")}, "660f73f802"},
|
|
|
|
|
|
{"PSRLDQ $2,X0", "PSRLDQ", []Operand{Imm(2), vreg(t, "X0")}, "660f73d802"},
|
|
|
|
|
|
{"PSLLL X0,X1", "PSLLL", []Operand{vreg(t, "X0"), vreg(t, "X1")}, "660ff2c8"},
|
|
|
|
|
|
{"PSRLQ X0,X1", "PSRLQ", []Operand{vreg(t, "X0"), vreg(t, "X1")}, "660fd3c8"},
|
|
|
|
|
|
{"PSLLL (AX),X1", "PSLLL", []Operand{Ptr(AX, 0, 16), vreg(t, "X1")}, "660ff208"},
|
|
|
|
|
|
{"PSUBL X0,X1", "PSUBL", []Operand{vreg(t, "X0"), vreg(t, "X1")}, "660ffac8"},
|
|
|
|
|
|
{"PADDL X0,X1", "PADDL", []Operand{vreg(t, "X0"), vreg(t, "X1")}, "660ffec8"},
|
|
|
|
|
|
{"PCMPEQL X0,X1", "PCMPEQL", []Operand{vreg(t, "X0"), vreg(t, "X1")}, "660f76c8"},
|
|
|
|
|
|
{"PUNPCKLBW X0,X1", "PUNPCKLBW", []Operand{vreg(t, "X0"), vreg(t, "X1")}, "660f60c8"},
|
|
|
|
|
|
{"MOVOA X0,X1", "MOVOA", []Operand{vreg(t, "X0"), vreg(t, "X1")}, "660f6fc8"},
|
|
|
|
|
|
{"MOVOA (AX),X1", "MOVOA", []Operand{Ptr(AX, 0, 16), vreg(t, "X1")}, "660f6f08"},
|
|
|
|
|
|
{"MOVOA X0,(AX)", "MOVOA", []Operand{vreg(t, "X0"), Ptr(AX, 0, 16)}, "660f7f00"},
|
|
|
|
|
|
{"AESIMC X0,X1", "AESIMC", []Operand{vreg(t, "X0"), vreg(t, "X1")}, "660f38dbc8"},
|
|
|
|
|
|
{"AESIMC (AX),X1", "AESIMC", []Operand{Ptr(AX, 0, 16), vreg(t, "X1")}, "660f38db08"},
|
|
|
|
|
|
{"AESENC X0,X1", "AESENC", []Operand{vreg(t, "X0"), vreg(t, "X1")}, "660f38dcc8"},
|
|
|
|
|
|
{"AESENCLAST X0,X1", "AESENCLAST", []Operand{vreg(t, "X0"), vreg(t, "X1")}, "660f38ddc8"},
|
|
|
|
|
|
{"AESDEC X0,X1", "AESDEC", []Operand{vreg(t, "X0"), vreg(t, "X1")}, "660f38dec8"},
|
|
|
|
|
|
{"AESDECLAST X0,X1", "AESDECLAST", []Operand{vreg(t, "X0"), vreg(t, "X1")}, "660f38dfc8"},
|
|
|
|
|
|
{"AESKEYGENASSIST $0,X0,X1", "AESKEYGENASSIST", []Operand{Imm(0), vreg(t, "X0"), vreg(t, "X1")}, "660f3adfc800"},
|
|
|
|
|
|
{"SHA1MSG1 X0,X1", "SHA1MSG1", []Operand{vreg(t, "X0"), vreg(t, "X1")}, "0f38c9c8"},
|
|
|
|
|
|
{"SHA1MSG2 X0,X1", "SHA1MSG2", []Operand{vreg(t, "X0"), vreg(t, "X1")}, "0f38cac8"},
|
|
|
|
|
|
{"SHA1NEXTE X0,X1", "SHA1NEXTE", []Operand{vreg(t, "X0"), vreg(t, "X1")}, "0f38c8c8"},
|
|
|
|
|
|
{"SHA1RNDS4 $0,X0,X1", "SHA1RNDS4", []Operand{Imm(0), vreg(t, "X0"), vreg(t, "X1")}, "0f3accc800"},
|
|
|
|
|
|
{"SHA256MSG1 X0,X1", "SHA256MSG1", []Operand{vreg(t, "X0"), vreg(t, "X1")}, "0f38ccc8"},
|
|
|
|
|
|
{"SHA256MSG2 X0,X1", "SHA256MSG2", []Operand{vreg(t, "X0"), vreg(t, "X1")}, "0f38cdc8"},
|
|
|
|
|
|
{"SHA256RNDS2 X0,X1,X2", "SHA256RNDS2", []Operand{vreg(t, "X0"), vreg(t, "X1"), vreg(t, "X2")}, "0f38cbd1"},
|
|
|
|
|
|
}
|
|
|
|
|
|
for _, c := range cases {
|
|
|
|
|
|
code, err := Encode(c.mnem, c.ops...)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
t.Errorf("%s: Encode: %v", c.name, err)
|
|
|
|
|
|
continue
|
|
|
|
|
|
}
|
|
|
|
|
|
if got := fmt.Sprintf("%x", code); got != c.want {
|
|
|
|
|
|
t.Errorf("%s = %s, want %s", c.name, got, c.want)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
// SHA256RNDS2's first operand must be the literal X0.
|
|
|
|
|
|
if _, err := Encode("SHA256RNDS2", vreg(t, "X1"), vreg(t, "X2"), vreg(t, "X3")); err == nil {
|
|
|
|
|
|
t.Errorf("SHA256RNDS2 X1,...: expected an error, got none")
|
|
|
|
|
|
}
|
|
|
|
|
|
// PSLLDQ has no variable-count form.
|
|
|
|
|
|
if _, err := Encode("PSLLDQ", vreg(t, "X0"), vreg(t, "X1")); err == nil {
|
|
|
|
|
|
t.Errorf("PSLLDQ X0,X1: expected an error, got none")
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
2026-08-27 16:36:18 +02:00
|
|
|
|
// TestSSEBinGroundTruth checks the legacy packed/scalar binary family
|
|
|
|
|
|
// byte for byte (no prefix / 66 / F2 / F3 variants).
|
|
|
|
|
|
func TestSSEBinGroundTruth(t *testing.T) {
|
|
|
|
|
|
cases := []struct {
|
|
|
|
|
|
name string
|
|
|
|
|
|
mnem string
|
|
|
|
|
|
ops []Operand
|
|
|
|
|
|
want string
|
|
|
|
|
|
}{
|
|
|
|
|
|
{"MULPS X0,X1", "MULPS", []Operand{vreg(t, "X0"), vreg(t, "X1")}, "0f59c8"},
|
|
|
|
|
|
{"MULPS (DI),X1", "MULPS", []Operand{Ptr(DI, 0, 16), vreg(t, "X1")}, "0f590f"},
|
|
|
|
|
|
{"ADDPD X1,X2", "ADDPD", []Operand{vreg(t, "X1"), vreg(t, "X2")}, "660f58d1"},
|
|
|
|
|
|
{"XORPS X0,X0", "XORPS", []Operand{vreg(t, "X0"), vreg(t, "X0")}, "0f57c0"},
|
|
|
|
|
|
{"UNPCKLPS X0,X0", "UNPCKLPS", []Operand{vreg(t, "X0"), vreg(t, "X0")}, "0f14c0"},
|
|
|
|
|
|
{"MULSD X1,X2", "MULSD", []Operand{vreg(t, "X1"), vreg(t, "X2")}, "f20f59d1"},
|
|
|
|
|
|
{"ADDSS (DI),X0", "ADDSS", []Operand{Ptr(DI, 0, 4), vreg(t, "X0")}, "f30f5807"},
|
|
|
|
|
|
}
|
|
|
|
|
|
for _, c := range cases {
|
|
|
|
|
|
code, err := Encode(c.mnem, c.ops...)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
t.Errorf("%s: %v", c.name, err)
|
|
|
|
|
|
continue
|
|
|
|
|
|
}
|
|
|
|
|
|
if got := fmt.Sprintf("%x", code); got != c.want {
|
|
|
|
|
|
t.Errorf("%s = %s, want %s", c.name, got, c.want)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// TestSSEShuffleGroundTruth checks the imm8 shuffle family: immediate
|
|
|
|
|
|
// first in Plan 9 order, encoded last on the wire.
|
|
|
|
|
|
func TestSSEShuffleGroundTruth(t *testing.T) {
|
|
|
|
|
|
cases := []struct {
|
|
|
|
|
|
name string
|
|
|
|
|
|
mnem string
|
|
|
|
|
|
ops []Operand
|
|
|
|
|
|
want string
|
|
|
|
|
|
}{
|
|
|
|
|
|
{"SHUFPS $0,X0,X0", "SHUFPS", []Operand{Imm(0), vreg(t, "X0"), vreg(t, "X0")}, "0fc6c000"},
|
|
|
|
|
|
{"SHUFPS $27,X1,X2", "SHUFPS", []Operand{Imm(27), vreg(t, "X1"), vreg(t, "X2")}, "0fc6d11b"},
|
|
|
|
|
|
{"PSHUFD $0,X0,X0", "PSHUFD", []Operand{Imm(0), vreg(t, "X0"), vreg(t, "X0")}, "660f70c000"},
|
|
|
|
|
|
{"PSHUFLW $3,(DI),X1", "PSHUFLW", []Operand{Imm(3), Ptr(DI, 0, 8), vreg(t, "X1")}, "f20f700f03"},
|
|
|
|
|
|
{"PSHUFHW $2,X1,X2", "PSHUFHW", []Operand{Imm(2), vreg(t, "X1"), vreg(t, "X2")}, "f30f70d102"},
|
|
|
|
|
|
}
|
|
|
|
|
|
for _, c := range cases {
|
|
|
|
|
|
code, err := Encode(c.mnem, c.ops...)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
t.Errorf("%s: %v", c.name, err)
|
|
|
|
|
|
continue
|
|
|
|
|
|
}
|
|
|
|
|
|
if got := fmt.Sprintf("%x", code); got != c.want {
|
|
|
|
|
|
t.Errorf("%s = %s, want %s", c.name, got, c.want)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-08-27 17:35:25 +02:00
|
|
|
|
|
|
|
|
|
|
// TestMOVQXMMGroundTruth pins the SSE2 packed-quadword move encodings:
|
2026-09-16 23:12:31 +02:00
|
|
|
|
// loads and register moves on F3 0F 7E, stores on 66 0F D6; the forms
|
2026-08-27 17:35:25 +02:00
|
|
|
|
// the GPR-move fallback silently corrupted.
|
|
|
|
|
|
func TestMOVQXMMGroundTruth(t *testing.T) {
|
|
|
|
|
|
cases := []struct {
|
|
|
|
|
|
name string
|
|
|
|
|
|
mnem string
|
|
|
|
|
|
ops []Operand
|
|
|
|
|
|
want string
|
|
|
|
|
|
}{
|
|
|
|
|
|
{"MOVQ (DI),X0", "MOVQ", []Operand{Ptr(DI, 0, 8), vreg(t, "X0")}, "f30f7e07"},
|
|
|
|
|
|
{"MOVQ X1,X2", "MOVQ", []Operand{vreg(t, "X1"), vreg(t, "X2")}, "f30f7ed1"},
|
|
|
|
|
|
{"MOVQ X0,(DI)", "MOVQ", []Operand{vreg(t, "X0"), Ptr(DI, 0, 8)}, "660fd607"},
|
|
|
|
|
|
}
|
|
|
|
|
|
for _, c := range cases {
|
|
|
|
|
|
code, err := Encode(c.mnem, c.ops...)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
t.Errorf("%s: %v", c.name, err)
|
|
|
|
|
|
continue
|
|
|
|
|
|
}
|
|
|
|
|
|
if got := fmt.Sprintf("%x", code); got != c.want {
|
|
|
|
|
|
t.Errorf("%s = %s, want %s", c.name, got, c.want)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-09-20 14:25:47 +02:00
|
|
|
|
|
|
|
|
|
|
// TestPrefixStatements pins LOCK, REP and REPN. go tool asm encodes each as
|
|
|
|
|
|
// a standalone one-byte instruction with a PC of its own (F0, F3, F2), not a
|
|
|
|
|
|
// prefix field merged into the following instruction, and it validates
|
|
|
|
|
|
// nothing about the pairing (LOCK before NOP assembles). The prefixed
|
|
|
|
|
|
// atomic and string shapes are the bytes the runtime's own kernels need.
|
|
|
|
|
|
func TestPrefixStatements(t *testing.T) {
|
|
|
|
|
|
cases := []struct {
|
|
|
|
|
|
name string
|
|
|
|
|
|
mnem string
|
|
|
|
|
|
ops []Operand
|
|
|
|
|
|
want string
|
|
|
|
|
|
}{
|
|
|
|
|
|
{"LOCK", "LOCK", nil, "f0"},
|
|
|
|
|
|
{"REP", "REP", nil, "f3"},
|
|
|
|
|
|
{"REPN", "REPN", nil, "f2"},
|
|
|
|
|
|
// LOCK; CMPXCHGQ AX, (BX)
|
|
|
|
|
|
{"LOCK CMPXCHGQ", "CMPXCHGQ", []Operand{AX, Ptr(BX, 0, 8)}, "480fb103"},
|
|
|
|
|
|
// REP; MOVSQ
|
|
|
|
|
|
{"REP MOVSQ", "MOVSQ", nil, "48a5"},
|
|
|
|
|
|
// REPN; MOVSB
|
|
|
|
|
|
{"REPN MOVSB", "MOVSB", nil, "a4"},
|
|
|
|
|
|
}
|
|
|
|
|
|
for _, c := range cases {
|
|
|
|
|
|
code, err := Encode(c.mnem, c.ops...)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
t.Errorf("%s: %v", c.name, err)
|
|
|
|
|
|
continue
|
|
|
|
|
|
}
|
|
|
|
|
|
if got := fmt.Sprintf("%x", code); got != c.want {
|
|
|
|
|
|
t.Errorf("%s = %s, want %s", c.name, got, c.want)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
// The prefix statements take no operands, as the toolchain reports for
|
|
|
|
|
|
// LOCK AX.
|
|
|
|
|
|
if _, err := Encode("LOCK", AX); err == nil {
|
|
|
|
|
|
t.Error("LOCK AX assembled, want an error")
|
|
|
|
|
|
}
|
|
|
|
|
|
if _, err := Encode("REP", Imm(1)); err == nil {
|
|
|
|
|
|
t.Error("REP $1 assembled, want an error")
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// TestDataEmission pins BYTE, WORD, LONG and QUAD: the immediate lands in
|
|
|
|
|
|
// the text stream as 1, 2, 4 or 8 little-endian bytes with no opcode
|
|
|
|
|
|
// lookup, truncated to the width rather than range-checked (go tool asm
|
|
|
|
|
|
// emits FF for BYTE $0x1FF and 45 23 for WORD $0x12345, both silently).
|
|
|
|
|
|
func TestDataEmission(t *testing.T) {
|
|
|
|
|
|
cases := []struct {
|
|
|
|
|
|
name string
|
|
|
|
|
|
mnem string
|
|
|
|
|
|
imm Imm
|
|
|
|
|
|
want string
|
|
|
|
|
|
}{
|
|
|
|
|
|
{"BYTE", "BYTE", 0x0f, "0f"},
|
|
|
|
|
|
{"BYTE negative", "BYTE", -1, "ff"},
|
|
|
|
|
|
{"BYTE truncated", "BYTE", 0x1ff, "ff"},
|
|
|
|
|
|
{"WORD", "WORD", 0x1234, "3412"},
|
|
|
|
|
|
{"WORD negative", "WORD", -1, "ffff"},
|
|
|
|
|
|
{"WORD truncated", "WORD", 0x12345, "4523"},
|
|
|
|
|
|
{"LONG", "LONG", 0x11223344, "44332211"},
|
|
|
|
|
|
{"LONG negative", "LONG", -1, "ffffffff"},
|
|
|
|
|
|
{"QUAD", "QUAD", 0x1122334455667788, "8877665544332211"},
|
|
|
|
|
|
{"QUAD negative", "QUAD", -2, "feffffffffffffff"},
|
|
|
|
|
|
}
|
|
|
|
|
|
for _, c := range cases {
|
|
|
|
|
|
code, err := Encode(c.mnem, c.imm)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
t.Errorf("%s: %v", c.name, err)
|
|
|
|
|
|
continue
|
|
|
|
|
|
}
|
|
|
|
|
|
if got := fmt.Sprintf("%x", code); got != c.want {
|
|
|
|
|
|
t.Errorf("%s = %s, want %s", c.name, got, c.want)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
// Exactly one immediate: the toolchain rejects BYTE $1, $2, $3, and a
|
|
|
|
|
|
// register or a missing operand is no immediate at all.
|
|
|
|
|
|
if _, err := Encode("BYTE"); err == nil {
|
|
|
|
|
|
t.Error("BYTE with no operand assembled, want an error")
|
|
|
|
|
|
}
|
|
|
|
|
|
if _, err := Encode("BYTE", Imm(1), Imm(2)); err == nil {
|
|
|
|
|
|
t.Error("BYTE $1, $2 assembled, want an error")
|
|
|
|
|
|
}
|
|
|
|
|
|
if _, err := Encode("WORD", AX); err == nil {
|
|
|
|
|
|
t.Error("WORD AX assembled, want an error")
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// TestEndIgnored pins END: go tool asm drops the statement entirely, so it
|
|
|
|
|
|
// encodes to zero bytes and takes any operands without complaint (the
|
|
|
|
|
|
// toolchain accepts END $0 and END AX alike).
|
|
|
|
|
|
func TestEndIgnored(t *testing.T) {
|
|
|
|
|
|
for _, ops := range [][]Operand{nil, {Imm(0)}, {AX}} {
|
|
|
|
|
|
code, err := Encode("END", ops...)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
t.Errorf("END: %v", err)
|
|
|
|
|
|
continue
|
|
|
|
|
|
}
|
|
|
|
|
|
if len(code) != 0 {
|
|
|
|
|
|
t.Errorf("END = %x, want no bytes", code)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// TestAdjsp pins ADJSP: a positive immediate is SUBQ $imm, SP, a negative
|
|
|
|
|
|
// one ADDQ $-imm, SP, in the imm8 or imm32 form the magnitude picks; $0
|
|
|
|
|
|
// has no encoding (go tool asm refuses ADJSP $0 outright).
|
|
|
|
|
|
func TestAdjsp(t *testing.T) {
|
|
|
|
|
|
cases := []struct {
|
|
|
|
|
|
name string
|
|
|
|
|
|
imm Imm
|
|
|
|
|
|
want string
|
|
|
|
|
|
}{
|
|
|
|
|
|
{"imm8", 112, "4883ec70"},
|
|
|
|
|
|
{"imm8 negative", -112, "4883c470"},
|
|
|
|
|
|
{"imm32", 200, "4881ecc8000000"},
|
|
|
|
|
|
{"imm32 negative", -200, "4881c4c8000000"},
|
|
|
|
|
|
{"small", 8, "4883ec08"},
|
|
|
|
|
|
}
|
|
|
|
|
|
for _, c := range cases {
|
|
|
|
|
|
code, err := Encode("ADJSP", c.imm)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
t.Errorf("%s: %v", c.name, err)
|
|
|
|
|
|
continue
|
|
|
|
|
|
}
|
|
|
|
|
|
if got := fmt.Sprintf("%x", code); got != c.want {
|
|
|
|
|
|
t.Errorf("ADJSP %d = %s, want %s", int64(c.imm), got, c.want)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
if _, err := Encode("ADJSP", Imm(0)); err == nil {
|
|
|
|
|
|
t.Error("ADJSP $0 assembled, want an error")
|
|
|
|
|
|
}
|
|
|
|
|
|
if _, err := Encode("ADJSP"); err == nil {
|
|
|
|
|
|
t.Error("ADJSP with no operand assembled, want an error")
|
|
|
|
|
|
}
|
|
|
|
|
|
if _, err := Encode("ADJSP", AX); err == nil {
|
|
|
|
|
|
t.Error("ADJSP AX assembled, want an error")
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
2026-09-21 02:04:44 +02:00
|
|
|
|
|
|
|
|
|
|
// TestFloatImmediateGroundTruth pins the floating-point immediate rewrite
|
|
|
|
|
|
// byte for byte against go tool asm: the scalar moves and the scalar
|
|
|
|
|
|
// arithmetic read the constant from a synthesised read-only pool symbol
|
|
|
|
|
|
// ($f64.<hex>, $f32.<hex>) RIP-relative with the displacement left to the
|
|
|
|
|
|
// relocation, and a positive zero on the moves collapses to XORPS dst, dst.
|
|
|
|
|
|
func TestFloatImmediateGroundTruth(t *testing.T) {
|
|
|
|
|
|
cases := []struct {
|
|
|
|
|
|
name string
|
|
|
|
|
|
mnem string
|
|
|
|
|
|
ops []Operand
|
|
|
|
|
|
want string
|
|
|
|
|
|
}{
|
|
|
|
|
|
{"MOVSD -1.0", "MOVSD", []Operand{FloatImm{Text: "1.0", Neg: true}, vreg(t, "X2")}, "f20f101500000000"},
|
|
|
|
|
|
{"MOVSD 1.5", "MOVSD", []Operand{FloatImm{Text: "1.5"}, vreg(t, "X3")}, "f20f101d00000000"},
|
|
|
|
|
|
{"MOVSS 2.5", "MOVSS", []Operand{FloatImm{Text: "2.5"}, vreg(t, "X4")}, "f30f102500000000"},
|
|
|
|
|
|
{"MOVSS -0.5", "MOVSS", []Operand{FloatImm{Text: "0.5", Neg: true}, vreg(t, "X5")}, "f30f102d00000000"},
|
|
|
|
|
|
{"MOVSS +0.0 is XORPS", "MOVSS", []Operand{FloatImm{Text: "0.0"}, vreg(t, "X10")}, "450f57d2"},
|
|
|
|
|
|
{"MOVSD +0.0 is XORPS", "MOVSD", []Operand{FloatImm{Text: "0.0"}, vreg(t, "X6")}, "0f57f6"},
|
|
|
|
|
|
{"ADDSD 1.0", "ADDSD", []Operand{FloatImm{Text: "1.0"}, vreg(t, "X0")}, "f20f580500000000"},
|
|
|
|
|
|
{"ADDSS 0.5", "ADDSS", []Operand{FloatImm{Text: "0.5"}, vreg(t, "X1")}, "f30f580d00000000"},
|
|
|
|
|
|
{"SUBSD 2.0", "SUBSD", []Operand{FloatImm{Text: "2.0"}, vreg(t, "X3")}, "f20f5c1d00000000"},
|
|
|
|
|
|
{"MULSD -2.5", "MULSD", []Operand{FloatImm{Text: "2.5", Neg: true}, vreg(t, "X3")}, "f20f591d00000000"},
|
|
|
|
|
|
{"DIVSD 1.0", "DIVSD", []Operand{FloatImm{Text: "1.0"}, vreg(t, "X0")}, "f20f5e0500000000"},
|
|
|
|
|
|
{"COMISD 1.0", "COMISD", []Operand{FloatImm{Text: "1.0"}, vreg(t, "X0")}, "660f2f0500000000"},
|
|
|
|
|
|
}
|
|
|
|
|
|
for _, c := range cases {
|
|
|
|
|
|
code, err := Encode(c.mnem, c.ops...)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
t.Errorf("%s: Encode: %v", c.name, err)
|
|
|
|
|
|
continue
|
|
|
|
|
|
}
|
|
|
|
|
|
if got := hexCompact(code); got != c.want {
|
|
|
|
|
|
t.Errorf("%s: got %s, want %s", c.name, got, c.want)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// The pool names carry the IEEE-754 bits, the float32 narrowing for the
|
|
|
|
|
|
// single spellings; negative zero keeps its sign bit and never takes the
|
|
|
|
|
|
// XORPS shortcut.
|
|
|
|
|
|
for _, c := range []struct {
|
|
|
|
|
|
mnem string
|
|
|
|
|
|
imm FloatImm
|
|
|
|
|
|
want string
|
|
|
|
|
|
}{
|
|
|
|
|
|
{"MOVSD", FloatImm{Text: "1.0", Neg: true}, "$f64.bff0000000000000"},
|
|
|
|
|
|
{"MOVSD", FloatImm{Text: "0.5"}, "$f64.3fe0000000000000"},
|
|
|
|
|
|
{"MOVSS", FloatImm{Text: "2.5"}, "$f32.40200000"},
|
|
|
|
|
|
{"MOVSS", FloatImm{Text: "0.5", Neg: true}, "$f32.bf000000"},
|
|
|
|
|
|
{"MOVSD", FloatImm{Text: "0.0", Neg: true}, "$f64.8000000000000000"},
|
|
|
|
|
|
} {
|
|
|
|
|
|
_, name, err := floatPoolValue(c.mnem, c.imm)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
t.Errorf("%s %s: %v", c.mnem, c.imm.Text, err)
|
|
|
|
|
|
continue
|
|
|
|
|
|
}
|
|
|
|
|
|
if name != c.want {
|
|
|
|
|
|
t.Errorf("%s $%s: pool name %s, want %s", c.mnem, c.imm.Text, name, c.want)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// The shapes the toolchain's parser rejects: the packed and uniform
|
|
|
|
|
|
// forms, a non-vector destination, and the integer spellings.
|
|
|
|
|
|
for _, c := range []struct {
|
|
|
|
|
|
name string
|
|
|
|
|
|
mnem string
|
|
|
|
|
|
ops []Operand
|
|
|
|
|
|
}{
|
|
|
|
|
|
{"MAXSD rejects the immediate", "MAXSD", []Operand{FloatImm{Text: "1.0"}, vreg(t, "X0")}},
|
|
|
|
|
|
{"MINSD rejects the immediate", "MINSD", []Operand{FloatImm{Text: "1.0"}, vreg(t, "X0")}},
|
|
|
|
|
|
{"SQRTSD rejects the immediate", "SQRTSD", []Operand{FloatImm{Text: "1.0"}, vreg(t, "X0")}},
|
|
|
|
|
|
{"integer destination", "MOVSD", []Operand{FloatImm{Text: "1.0"}, AX}},
|
|
|
|
|
|
} {
|
|
|
|
|
|
if _, err := Encode(c.mnem, c.ops...); err == nil {
|
|
|
|
|
|
t.Errorf("%s: expected an error, got none", c.name)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// TestBookkeepingGroundTruth pins FUNCDATA and PCDATA as accept-and-ignore:
|
|
|
|
|
|
// go tool asm emits no text bytes for either, on every architecture.
|
|
|
|
|
|
func TestBookkeepingGroundTruth(t *testing.T) {
|
|
|
|
|
|
for _, c := range []struct {
|
|
|
|
|
|
name string
|
|
|
|
|
|
mnem string
|
|
|
|
|
|
ops []Operand
|
|
|
|
|
|
}{
|
|
|
|
|
|
{"FUNCDATA", "FUNCDATA", []Operand{Imm(3), sbMem{name: "\u00b7f.arginfo0"}}},
|
|
|
|
|
|
{"PCDATA", "PCDATA", []Operand{Imm(1), Imm(-1)}},
|
|
|
|
|
|
} {
|
|
|
|
|
|
code, err := Encode(c.mnem, c.ops...)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
t.Errorf("%s: Encode: %v", c.name, err)
|
|
|
|
|
|
continue
|
|
|
|
|
|
}
|
|
|
|
|
|
if len(code) != 0 {
|
|
|
|
|
|
t.Errorf("%s: emitted %x, want no bytes", c.name, code)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
for _, c := range []struct {
|
|
|
|
|
|
name string
|
|
|
|
|
|
mnem string
|
|
|
|
|
|
ops []Operand
|
|
|
|
|
|
}{
|
|
|
|
|
|
{"FUNCDATA arity", "FUNCDATA", []Operand{Imm(3)}},
|
|
|
|
|
|
{"FUNCDATA missing the count", "FUNCDATA", []Operand{sbMem{name: "x"}}},
|
|
|
|
|
|
{"FUNCDATA integer value", "FUNCDATA", []Operand{Imm(3), Imm(4)}},
|
|
|
|
|
|
{"PCDATA arity", "PCDATA", []Operand{Imm(1)}},
|
|
|
|
|
|
{"PCDATA register value", "PCDATA", []Operand{Imm(1), AX}},
|
|
|
|
|
|
} {
|
|
|
|
|
|
if _, err := Encode(c.mnem, c.ops...); err == nil {
|
|
|
|
|
|
t.Errorf("%s: expected an error, got none", c.name)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// At the statement level the bookkeeping lines sit between real
|
|
|
|
|
|
// instructions and contribute nothing to the body, symbol reference
|
|
|
|
|
|
// included: the FUNCDATA operand never needs file-level resolution.
|
|
|
|
|
|
f, errs := parser.Parse("t_amd64.s", "TEXT \u00b7f(SB), NOSPLIT, $0\n\tNOP\n\tFUNCDATA $3, \u00b7f.arginfo0(SB)\n\tPCDATA $1, $-1\n\tFUNCDATA $0, x<>(SB)\n\tRET\n")
|
|
|
|
|
|
if len(errs) > 0 {
|
|
|
|
|
|
t.Fatalf("parse: %v", errs)
|
|
|
|
|
|
}
|
|
|
|
|
|
img, err := AssembleFile(f)
|
|
|
|
|
|
if err != nil {
|
|
|
|
|
|
t.Fatalf("assemble: %v", err)
|
|
|
|
|
|
}
|
|
|
|
|
|
want := "90c3"
|
|
|
|
|
|
if got := hexCompact(img.Code); got != want {
|
|
|
|
|
|
t.Errorf("body %s, want %s (the bookkeeping lines contribute nothing)", got, want)
|
|
|
|
|
|
}
|
|
|
|
|
|
if _, err := AssembleFile(mustParse(t, "TEXT \u00b7f(SB), NOSPLIT, $0\n\tFUNCDATA $1, X0\n\tRET\n")); err == nil {
|
|
|
|
|
|
t.Error("FUNCDATA $1, X0 assembled, want an error")
|
|
|
|
|
|
}
|
|
|
|
|
|
if _, err := AssembleFile(mustParse(t, "TEXT \u00b7f(SB), NOSPLIT, $0\n\tPCDATA $1, X0\n\tRET\n")); err == nil {
|
|
|
|
|
|
t.Error("PCDATA $1, X0 assembled, want an error")
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// Encodable mirrors Encode for the names this work touched.
|
|
|
|
|
|
for _, mnem := range []string{"FUNCDATA", "PCDATA", "V4FMADDPS", "V4FMADDSS", "V4FNMADDPS", "V4FNMADDSS", "VP4DPWSSD", "VP4DPWSSDS"} {
|
|
|
|
|
|
if !Encodable(mnem) {
|
|
|
|
|
|
t.Errorf("Encodable(%s) = false, want true", mnem)
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
|
|
// mustParse parses src or fails the test.
|
|
|
|
|
|
func mustParse(t *testing.T, src string) *ast.File {
|
|
|
|
|
|
t.Helper()
|
|
|
|
|
|
f, errs := parser.Parse("t_amd64.s", src)
|
|
|
|
|
|
if len(errs) > 0 {
|
|
|
|
|
|
t.Fatalf("parse: %v", errs)
|
|
|
|
|
|
}
|
|
|
|
|
|
return f
|
|
|
|
|
|
}
|