From e7a1c20467cd511e89ad99c9bc3b534d2ff54726 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Balv=C3=ADn?= Date: Wed, 7 Oct 2026 02:10:21 +0200 Subject: [PATCH] fix(asm): prefix MOVQ2DQ with F3 The two bank-crossing quadword moves take the mandatory prefix by direction: the toolchain renders F3 0F D6 as MOVQ2DQ with the MMX source and F2 0F D6 as MOVDQ2Q with the XMM source, and the encoder emitted F2 for both, so MOVQ2DQ encoded MOVDQ2Q. Assisted-by: GLM 5.3 Flash --- asm/encode.go | 13 ++++++++++--- asm/rendered_text_test.go | 3 +++ 2 files changed, 13 insertions(+), 3 deletions(-) diff --git a/asm/encode.go b/asm/encode.go index da976ba..94da113 100644 --- a/asm/encode.go +++ b/asm/encode.go @@ -184,8 +184,11 @@ func (e *enc) encode(mnem string, ops []Operand) error { return fmt.Errorf("RETFL expects an immediate") } return e.emit(&instr{opcode: []byte{0xCA}, modrm: -1, sib: -1, imm: le16(int64(imm))}) - // MOVDQ2Q/MOVQ2DQ cross the MMX and XMM banks (F2 0F D6), the register - // in the reg field, the other bank's in r/m. + // MOVDQ2Q/MOVQ2DQ cross the MMX and XMM banks, and each direction + // carries its own mandatory prefix: the toolchain renders F3 0F D6 as + // MOVQ2DQ (the MMX source, XMM destination) and F2 0F D6 as MOVDQ2Q + // (the XMM source, MMX destination), the register in the reg field, the + // other bank's in r/m. case "MOVDQ2Q", "MOVQ2DQ": if len(ops) != 2 { return fmt.Errorf("%s expects 2 operands, got %d", upper, len(ops)) @@ -199,7 +202,11 @@ func (e *enc) encode(mnem string, ops []Operand) error { upper == "MOVQ2DQ" && (!srcReg.mmx || !dstReg.isVec()) { return fmt.Errorf("%s crosses the XMM and MMX banks in that order", upper) } - i := &instr{prefix: 0xF2, opcode: []byte{0x0F, 0xD6}, modrm: -1, sib: -1} + prefix := byte(0xF2) + if upper == "MOVQ2DQ" { + prefix = 0xF3 + } + i := &instr{prefix: prefix, opcode: []byte{0x0F, 0xD6}, modrm: -1, sib: -1} if err := setRM(i, dstReg, srcReg, 8); err != nil { return err } diff --git a/asm/rendered_text_test.go b/asm/rendered_text_test.go index e3fe9e1..3232043 100644 --- a/asm/rendered_text_test.go +++ b/asm/rendered_text_test.go @@ -24,6 +24,9 @@ func TestRenderedTextEncodes(t *testing.T) { // The segment-absolute rendering lowers to the FS-prefixed disp32 // absolute, exactly what the 0(FS) spelling encodes. {"MOVQ FS:0, DX", "64488b142500000000"}, + // The bank-crossing quadword move takes the prefix by direction: the + // MMX source carries F3, the XMM source F2. + {"MOVQ2DQ M2, X11", "f3440fd6da"}, } { src := "TEXT \u00b7k(SB), NOSPLIT, $0\n\t" + tt.text + "\n\tRET\n" f, errs := parser.Parse("k.s", src)