Skip to content

winch x86: unspecified behavior in clz/ctz on hosts without lzcnt/bmi1 #13746

Description

@gramseyer

winch/codegen/isa/x64/masm.rs, line 1061+:

    fn ctz(&mut self, dst: WritableReg, src: Reg, size: OperandSize) -> Result<()> {
        if self.flags.has_bmi1() {
            self.asm.tzcnt(src, dst, size);
        } else {
            self.with_scratch::<IntScratch, _>(|masm, scratch| {
                // Use the following approach:
                // dst = bsf(src) + (is_zero * size.num_bits())
                //     = bsf(src) + (is_zero << size.log2()).
                // BSF outputs the correct value for every value except 0.
                // When the value is 0, BSF outputs 0, correct output for ctz is
                // the number of bits.
                masm.asm.bsf(src, dst, size);
                masm.asm.setcc(IntCmpKind::Eq, scratch.writable());
                masm.asm
                    .shift_ir(size.log2(), scratch.writable(), ShiftKind::Shl, size);
                masm.asm.add_rr(scratch.inner(), dst, size);
            });
        }

        Ok(())
    }

The fallback path when !flags.has_bmi1() assumes BSF outputs 0 on 0 input, which conflicts with Intel manual.
"If the content of the source operand is 0, the content of the destination operand is undefined."
(https://www.intel.com/content/dam/www/public/us/en/documents/manuals/64-ia-32-architectures-software-developer-vol-2a-manual.pdf -- page 210 of the PDF).

Similar issue applies to bsr, used in the fallback path of clz() when !flags.has_lzcnt()

Metadata

Metadata

Assignees

No one assigned

    Labels

    winchWinch issues or pull requests

    Type

    No type
    No fields configured for issues without a type.

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions