diff --git a/asm/encodable.go b/asm/encodable.go index d73837f..65f0d18 100644 --- a/asm/encodable.go +++ b/asm/encodable.go @@ -62,16 +62,14 @@ func Encodable(mnemonic string) bool { return true } - // CMOV carries size then condition (CMOVLGT); SET carries the condition - // alone (SETNE). The size letter is checked exactly as encodeCmov does, - // so a spelling like CMOVBGT is not reported encodable when Encode - // would reject it. - if rest, ok := strings.CutPrefix(upper, "CMOV"); ok && len(rest) >= 2 { - switch rest[0] { - case 'W', 'L', 'Q': - if _, ok := jccMap[rest[1:]]; ok { - return true - } + // CMOV carries size then condition (CMOVLGT), or the renderer's + // condition alone (CMOVLE) with the width from the operand; SET carries + // the condition alone (SETNE). cmovCondition checks the suffix exactly + // as encodeCmov does, so a spelling like CMOVBGT is not reported + // encodable when Encode would reject it. + if rest, ok := strings.CutPrefix(upper, "CMOV"); ok { + if _, _, ok := cmovCondition(rest); ok { + return true } } if rest, ok := strings.CutPrefix(upper, "SET"); ok { diff --git a/asm/encode_test.go b/asm/encode_test.go index 83d93a2..4a14627 100644 --- a/asm/encode_test.go +++ b/asm/encode_test.go @@ -411,6 +411,16 @@ func TestScalarGroundTruth(t *testing.T) { {"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"}, + // The renderer's condition spellings carry no size letter; the width + // rides the operand registers and the bytes match the toolchain's own + // size-prefixed spellings (CMOVQLE/CMOVLLE/CMOVWLE pinned from go tool + // asm). CMOVLE with the 16-bit registers reproduces the disasm + // fixture's 660f4e13 row byte for byte. + {"CMOVLE (BX),DX", "CMOVLE", []Operand{Ptr(BX, 0, 2), DX}, "660f4e13", "CMOVLE"}, + {"CMOVQLE AX,BX", "CMOVQLE", []Operand{Reg{idx: 0, size: 8}, Reg{idx: 3, size: 8}}, "480f4ed8", "CMOVLE"}, + {"CMOVLLE AX,BX", "CMOVLLE", []Operand{Reg{idx: 0, size: 4}, Reg{idx: 3, size: 4}}, "0f4ed8", "CMOVLE"}, + {"CMOVWLE AX,BX", "CMOVWLE", []Operand{AX, BX}, "660f4ed8", "CMOVLE"}, + {"CMOVB AL,CL", "CMOVB", []Operand{AL, CL}, "0f42c8", "CMOVB"}, {"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"}, @@ -595,14 +605,15 @@ func TestImmediateTruncation(t *testing.T) { // 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. +// letter) is not reported as encodable. The condition-name spellings the +// renderer prints (CMOVB, CMOVLE) encode with the width from the operands. func TestEncodableCmovSize(t *testing.T) { - for _, m := range []string{"CMOVBGT", "CMOVXEQ", "CMOVB", "CMOV", "CMOVWXX"} { + for _, m := range []string{"CMOVBGT", "CMOVXEQ", "CMOV", "CMOVWXX"} { if Encodable(m) { t.Errorf("Encodable(%q) = true, want false", m) } } - for _, m := range []string{"CMOVLGT", "CMOVQGT", "CMOVWLS", "CMOVLEQ"} { + for _, m := range []string{"CMOVLGT", "CMOVQGT", "CMOVWLS", "CMOVLEQ", "CMOVB", "CMOVLE"} { if !Encodable(m) { t.Errorf("Encodable(%q) = false, want true", m) } diff --git a/asm/instrs.go b/asm/instrs.go index 82dae29..a223488 100644 --- a/asm/instrs.go +++ b/asm/instrs.go @@ -1068,29 +1068,46 @@ func immediate(v int64, size int, full64 bool) ([]byte, error) { // --- CMOVcc / SETcc --------------------------------------------------------- +// cmovCondition splits a CMOVcc suffix into an optional size letter and the +// condition code. Two vocabularies meet here: the Plan 9 spellings prefix +// the condition with a size letter (CMOVLGT, CMOVQEQ), while the toolchain's +// renderer prints the condition alone (CMOVLE, CMOVG) and leaves the width +// to the operand registers. A suffix that is itself a condition name reads +// as that condition, so the renderer's text re-encodes; CMOVBGT, CMOVWXX and +// the bare CMOV still find no condition and stay rejected. +func cmovCondition(rest string) (size int, cc int, ok bool) { + if cc, ok := jccMap[rest]; ok { + return 0, cc, true + } + if len(rest) >= 2 { + switch rest[0] { + case 'W': + if cc, ok := jccMap[rest[1:]]; ok { + return 2, cc, true + } + case 'L': + if cc, ok := jccMap[rest[1:]]; ok { + return 4, cc, true + } + case 'Q': + if cc, ok := jccMap[rest[1:]]; ok { + return 8, cc, true + } + } + } + return 0, 0, false +} + // encodeCmov encodes a conditional move: CMOV + size (W/L/Q) + condition -// (CMOVLGT, CMOVQEQ, …). The condition reads exactly like the Jcc spellings; -// the instruction is 0F 40+cc with reg = dst, rm = src. +// (CMOVLGT, CMOVQEQ, …), or the renderer's condition alone (CMOVLE) with the +// width taken from the destination register. The instruction is 0F 40+cc +// with reg = dst, rm = src. func (e *enc) encodeCmov(upper string, ops []Operand) error { if len(ops) != 2 { return fmt.Errorf("CMOVcc expects 2 operands, got %d", len(ops)) } rest := upper[len("CMOV"):] - if len(rest) < 2 { - return fmt.Errorf("unsupported instruction %q", upper) - } - var size int - switch rest[0] { - case 'W': - size = 2 - case 'L': - size = 4 - case 'Q': - size = 8 - default: - return fmt.Errorf("unsupported instruction %q", upper) - } - cc, ok := jccMap[rest[1:]] + size, cc, ok := cmovCondition(rest) if !ok { return fmt.Errorf("unsupported instruction %q", upper) } @@ -1099,6 +1116,14 @@ func (e *enc) encodeCmov(upper string, ops []Operand) error { if !ok { return fmt.Errorf("CMOVcc destination must be a register") } + if size == 0 { + // The renderer's spelling carries no size letter: the width rides + // the destination register's own size class. + size = dstReg.size + if size != 1 && size != 2 && size != 4 && size != 8 { + return fmt.Errorf("CMOVcc destination must be a general register") + } + } i := newInstr(size, []byte{0x0F, byte(0x40 + cc)}) if err := setRM(i, dstReg, src, size); err != nil { return err