fix(asm): encode BSF, BSR and POPCNT

Assisted-by: GLM 5.3
This commit is contained in:
2026-08-24 20:19:34 +02:00
parent 5e06d6a6aa
commit b08005753e
3 changed files with 33 additions and 11 deletions
+1 -1
View File
@@ -95,7 +95,7 @@ func (e *enc) encode(mnem string, ops []Operand) error {
return e.encodePushPop(ops, true)
case "POP":
return e.encodePushPop(ops, false)
case "LZCNT", "TZCNT":
case "BSF", "BSR", "LZCNT", "TZCNT", "POPCNT":
return e.encodeCount(base, ops, size)
case "MOVBLZX", "MOVBQZX", "MOVWLZX", "MOVWQZX", "MOVWLSX", "MOVLQSX":
return e.encodeMovExtend(base, ops)
+8
View File
@@ -203,6 +203,14 @@ func TestScalarGroundTruth(t *testing.T) {
{"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"},
// 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"},
{"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"},
+24 -10
View File
@@ -597,25 +597,39 @@ func (e *enc) encodeSet(upper string, ops []Operand) error {
return e.emit(i)
}
// --- LZCNT / TZCNT ----------------------------------------------------------
// --- bit scan / bit count ----------------------------------------------------
// encodeCount encodes LZCNT/TZCNT (leading / trailing zero count): F3 0F BD
// or F3 0F BC, with reg = dst and rm = src. The size suffix selects the
// operand width (LZCNTW/LZCNTL/LZCNTQ).
// countOp maps the bit-scan and bit-count mnemonics to their opcode byte and
// mandatory prefix. TZCNT/LZCNT/POPCNT are the F3-prefixed forms of the
// same map as BSF/BSR's 0F BC/BD; POPCNT is F3 0F B8.
var countOp = map[string]struct {
op byte
prefix byte
}{
"BSF": {0xBC, 0},
"BSR": {0xBD, 0},
"TZCNT": {0xBC, 0xF3},
"LZCNT": {0xBD, 0xF3},
"POPCNT": {0xB8, 0xF3},
}
// encodeCount encodes the bit-scan and bit-count family — BSF (0F BC),
// BSR (0F BD), TZCNT (F3 0F BC), LZCNT (F3 0F BD) and POPCNT (F3 0F B8) —
// with reg = dst and rm = src. The size suffix selects the operand width
// (BSFQ, TZCNTL, …). Note BSF/BSR leave the destination undefined when the
// source is zero (unlike their F3-prefixed counterparts); callers must
// guard non-zero inputs themselves.
func (e *enc) encodeCount(base string, ops []Operand, size int) error {
if len(ops) != 2 {
return fmt.Errorf("%s expects 2 operands, got %d", base, len(ops))
}
op := byte(0xBD)
if base == "TZCNT" {
op = 0xBC
}
spec := countOp[base]
dstReg, ok := ops[1].(Reg)
if !ok {
return fmt.Errorf("%s destination must be a register", base)
}
i := newInstr(size, []byte{0x0F, op})
i.prefix = 0xF3
i := newInstr(size, []byte{0x0F, spec.op})
i.prefix = spec.prefix
if err := setRM(i, dstReg, ops[0], size); err != nil {
return err
}