Stream: git-wasmtime

Topic: wasmtime / issue #13746 winch x86: unspecified behavior i...


view this post on Zulip Wasmtime GitHub notifications bot (Jun 28 2026 at 23:53):

gramseyer opened issue #13746:

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 is incorrect per the Intel manualm which states
"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()

view this post on Zulip Wasmtime GitHub notifications bot (Jun 28 2026 at 23:54):

gramseyer edited issue #13746:

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()

view this post on Zulip Wasmtime GitHub notifications bot (Jun 29 2026 at 12:47):

alexcrichton commented on issue #13746:

cc @saulecabrera, would you be able to take a look at this?

view this post on Zulip Wasmtime GitHub notifications bot (Jun 29 2026 at 12:47):

alexcrichton added the winch label to Issue #13746.

view this post on Zulip Wasmtime GitHub notifications bot (Jun 29 2026 at 17:12):

saulecabrera commented on issue #13746:

I'll take a look.

view this post on Zulip Wasmtime GitHub notifications bot (Jul 06 2026 at 20:26):

fitzgen closed issue #13746:

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()


Last updated: Jul 29 2026 at 05:03 UTC