From b08005753eccf8b06bf0aa9b5e31996bd1de7eb5 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Petr=20Balv=C3=ADn?= Date: Mon, 24 Aug 2026 20:19:34 +0200 Subject: [PATCH] fix(asm): encode BSF, BSR and POPCNT Assisted-by: GLM 5.3 --- asm/encode.go | 2 +- asm/encode_test.go | 8 ++++++++ asm/instrs.go | 34 ++++++++++++++++++++++++---------- 3 files changed, 33 insertions(+), 11 deletions(-) diff --git a/asm/encode.go b/asm/encode.go index 3f88c48..a8655a7 100644 --- a/asm/encode.go +++ b/asm/encode.go @@ -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) diff --git a/asm/encode_test.go b/asm/encode_test.go index dc74b04..0daf404 100644 --- a/asm/encode_test.go +++ b/asm/encode_test.go @@ -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"}, diff --git a/asm/instrs.go b/asm/instrs.go index 479043f..edd256b 100644 --- a/asm/instrs.go +++ b/asm/instrs.go @@ -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 }