fix(asm): encode the CMOV condition the renderer prints
The renderer spells a conditional move CMOV plus the condition alone (CMOVLE, CMOVG), the width carried by the operand registers, so CMOVLE parsed as the size L and the condition E and encoded CMOVE. A suffix that is itself a condition name now reads as that condition with the width from the destination register, and the Plan 9 size-prefixed spellings keep their parse. Assisted-by: GLM 5.3 Flash
This commit is contained in:
1 parent
11cac26508
commit
abc2d32b83
3 files changed
+64
-30
No files matched your search
+8
-10
@@ -62,16 +62,14 @@ func Encodable(mnemonic string) bool {
|
|||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
// CMOV carries size then condition (CMOVLGT); SET carries the condition
|
// CMOV carries size then condition (CMOVLGT), or the renderer's
|
||||||
// alone (SETNE). The size letter is checked exactly as encodeCmov does,
|
// condition alone (CMOVLE) with the width from the operand; SET carries
|
||||||
// so a spelling like CMOVBGT is not reported encodable when Encode
|
// the condition alone (SETNE). cmovCondition checks the suffix exactly
|
||||||
// would reject it.
|
// as encodeCmov does, so a spelling like CMOVBGT is not reported
|
||||||
if rest, ok := strings.CutPrefix(upper, "CMOV"); ok && len(rest) >= 2 {
|
// encodable when Encode would reject it.
|
||||||
switch rest[0] {
|
if rest, ok := strings.CutPrefix(upper, "CMOV"); ok {
|
||||||
case 'W', 'L', 'Q':
|
if _, _, ok := cmovCondition(rest); ok {
|
||||||
if _, ok := jccMap[rest[1:]]; ok {
|
return true
|
||||||
return true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if rest, ok := strings.CutPrefix(upper, "SET"); ok {
|
if rest, ok := strings.CutPrefix(upper, "SET"); ok {
|
||||||
|
|||||||
+14
-3
@@ -411,6 +411,16 @@ func TestScalarGroundTruth(t *testing.T) {
|
|||||||
{"CMOVLEQ CX,AX", "CMOVLEQ", []Operand{CX, AX}, "0f44c1", "CMOVE"},
|
{"CMOVLEQ CX,AX", "CMOVLEQ", []Operand{CX, AX}, "0f44c1", "CMOVE"},
|
||||||
{"CMOVQGT R9,R8", "CMOVQGT", []Operand{r9, r8}, "4d0f4fc1", "CMOVG"},
|
{"CMOVQGT R9,R8", "CMOVQGT", []Operand{r9, r8}, "4d0f4fc1", "CMOVG"},
|
||||||
{"CMOVWLS R9W,R8W", "CMOVWLS", []Operand{r9w, r8w}, "66450f46c1", "CMOVBE"},
|
{"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 AL", "SETNE", []Operand{AL}, "0f95c0", "SETNE"},
|
||||||
{"SETNE (AX)", "SETNE", []Operand{Ptr(AX, 0, 1)}, "0f9500", "SETNE"},
|
{"SETNE (AX)", "SETNE", []Operand{Ptr(AX, 0, 1)}, "0f9500", "SETNE"},
|
||||||
{"MOVBLZX AL,CX", "MOVBLZX", []Operand{AL, CX}, "0fb6c8", "MOVZX"},
|
{"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
|
// TestEncodableCmovSize pins the linter contract for CMOVcc: Encodable must
|
||||||
// reject the spellings Encode rejects, so a mnemonic like CMOVBGT (no size
|
// 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) {
|
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) {
|
if Encodable(m) {
|
||||||
t.Errorf("Encodable(%q) = true, want false", 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) {
|
if !Encodable(m) {
|
||||||
t.Errorf("Encodable(%q) = false, want true", m)
|
t.Errorf("Encodable(%q) = false, want true", m)
|
||||||
}
|
}
|
||||||
|
|||||||
+42
-17
@@ -1068,29 +1068,46 @@ func immediate(v int64, size int, full64 bool) ([]byte, error) {
|
|||||||
|
|
||||||
// --- CMOVcc / SETcc ---------------------------------------------------------
|
// --- 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
|
// encodeCmov encodes a conditional move: CMOV + size (W/L/Q) + condition
|
||||||
// (CMOVLGT, CMOVQEQ, …). The condition reads exactly like the Jcc spellings;
|
// (CMOVLGT, CMOVQEQ, …), or the renderer's condition alone (CMOVLE) with the
|
||||||
// the instruction is 0F 40+cc with reg = dst, rm = src.
|
// 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 {
|
func (e *enc) encodeCmov(upper string, ops []Operand) error {
|
||||||
if len(ops) != 2 {
|
if len(ops) != 2 {
|
||||||
return fmt.Errorf("CMOVcc expects 2 operands, got %d", len(ops))
|
return fmt.Errorf("CMOVcc expects 2 operands, got %d", len(ops))
|
||||||
}
|
}
|
||||||
rest := upper[len("CMOV"):]
|
rest := upper[len("CMOV"):]
|
||||||
if len(rest) < 2 {
|
size, cc, ok := cmovCondition(rest)
|
||||||
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:]]
|
|
||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("unsupported instruction %q", upper)
|
return fmt.Errorf("unsupported instruction %q", upper)
|
||||||
}
|
}
|
||||||
@@ -1099,6 +1116,14 @@ func (e *enc) encodeCmov(upper string, ops []Operand) error {
|
|||||||
if !ok {
|
if !ok {
|
||||||
return fmt.Errorf("CMOVcc destination must be a register")
|
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)})
|
i := newInstr(size, []byte{0x0F, byte(0x40 + cc)})
|
||||||
if err := setRM(i, dstReg, src, size); err != nil {
|
if err := setRM(i, dstReg, src, size); err != nil {
|
||||||
return err
|
return err
|
||||||
|
|||||||
Reference in new issue
Block a user