Instruction
TZCNT
Counts the trailing zero bits of an operand; APX adds a form that leaves the flags unchanged.
TZCNT counts the zero bits below the lowest set bit, which is that bit’s index: for 0x28 it gives 3. For a zero source it returns the operand size, 16, 32 or 64, and sets CF, so every input has a defined result. Code walks the set bits of a mask with it: TZCNT for the index of the lowest one, BLSR to clear that bit, and again until the mask is 0.
TZCNT and BSF
TZCNT is encoded as BSF with an F3 prefix, and a processor without BMI1 ignores the prefix and runs BSF. Both give the same index for a nonzero source; they part for zero and in the flags:
| Source | TZCNT | BSF |
|---|---|---|
| nonzero | the index; CF = 0, ZF = 1 if it is 0 | the index; ZF = 0 |
| zero | the operand size; CF = 1, ZF = 0 | the destination unchanged; ZF = 1 |
Both vendors’ manuals now say that BSF leaves the destination unchanged; Intel’s adds that some older processors clear bits 63:32 of a 64-bit register for a 32-bit BSF.
Compilers use the shared encoding. Without -mbmi, GCC 14 and Clang 23
at -O2 compile __builtin_ctz(x), which C leaves undefined for 0, to
rep bsf: TZCNT on processors with BMI1, BSF on the others, and the
same result for every nonzero x. For x ? __builtin_ctz(x) : 32,
Clang loads 32 into the destination first, which TZCNT returns and BSF
keeps; GCC adds a TEST and a CMOVE. With -mbmi both emit one TZCNT.
Code that tests ZF after rep bsf to catch a zero source, however,
gets different answers on the two kinds of processors.
LZCNT is different
The trick does not carry over to LZCNT and
BSR. BSR returns the index of the highest set bit,
LZCNT the number of zeros above it, which for 32 bits is 31 minus that
index; a processor without LZCNT runs its encoding as BSR and returns
the other number. Without -mlzcnt, both compilers therefore compile
__builtin_clz to BSR and an XOR with 31, not to rep bsr.
TZCNT belongs to BMI1, part of x86-64-v3; APX adds an EVEX form and one that leaves the flags unchanged (NF).
In the optimization guide
What the optimization guide says about TZCNT, a line per claim. Each links to the claim in the guide, with its sources and where they differ.
Out of order
- Zeroing and dependency-breaking idioms: Some instructions that overwrite their whole destination still wait for its old value, a flaw of particular cores: LLVM’s tunings mark
popcntfrom Sandy Bridge to the Skylake family andlzcntandtzcnton Haswell, AMD namestzcntand three BMI1 instructions on some versions of Zen 5, and Intel several AVX-512 permutations and multiplications on Golden Cove (Skylake, Zen 5, Golden Cove).
Haswell and Broadwell
- Integer execution and bypass: LLVM’s tuning marks POPCNT, LZCNT and TZCNT on both cores as waiting for whatever last wrote the register they overwrite, though they never use its value, as on Skylake.
Jaguar and Puma
- Integer execution: LZCNT and POPCNT take one cycle on either ALU and TZCNT two, while BSF and BSR are microcoded, of seven and eight macro-ops and 4 and 5 cycles, so the newer instructions are the better choice where the zero case allows.
Skylake to Cooper Lake
- Integer execution: Port 1 alone runs integer multiplication, PDEP and PEXT,
popcnt,lzcnt,tzcnt,bsf,bsrand the double shifts, with a latency of three cycles and one a cycle in LLVM’s models, as on Golden Cove.
Zen and Zen+
- Integer execution: BSF and BSR are six macro-ops with a latency of three and four cycles, while LZCNT and POPCNT are one macro-op of one cycle and TZCNT two of two cycles. sources differ
Zen 2
- Integer execution: BSF and BSR are six macro-ops with a latency of three and four cycles, while LZCNT and POPCNT are one macro-op of one cycle and TZCNT two; LLVM’s model agrees.
Zen 5
- Integer execution: Some versions of Zen 5 make TZCNT, BLSI, BLSR and BLSMSK wait for the old value of their destination.
Forms
| Instruction | Encoding | Requires |
|---|---|---|
TZCNT r16/32/64, m16/32/64
TZCNT_GPRv_MEMv | F3 0F BC /r | BMI1 |
TZCNT r16/32/64, r16/32/64
TZCNT_GPRv_GPRv | F3 0F BC /r | BMI1 |
TZCNT r16/32/64, r16/32/64
TZCNT_GPRv_GPRv_APX | EVEX.LLZ.NP.MAP4.SCALABLE F4 /r | APX_F + BMI1 |
TZCNT r16/32/64, m16/32/64
TZCNT_GPRv_MEMv_APX | EVEX.LLZ.NP.MAP4.SCALABLE F4 /r | APX_F + BMI1 |
TZCNT r16/32/64, r16/32/64 NF
TZCNT_GPRv_GPRv_APX_N3 | EVEX.LLZ.NP.MAP4.SCALABLE F4 /r NF=1 | APX_F + APX_NCI_NDD_NF + BMI1 |
TZCNT r16/32/64, m16/32/64 NF
TZCNT_GPRv_MEMv_APX_N3 | EVEX.LLZ.NP.MAP4.SCALABLE F4 /r NF=1 | APX_F + APX_NCI_NDD_NF + BMI1 |
Latency and throughput
| Model | r16, m16r32, m32r64, m64 | r16, r16 | r32, r32r64, r64 |
|---|---|---|---|
| Haswell, Broadwell, Skylake, Skylake-SP, Ice Lake, Alder Lake, Sapphire Rapids | 8 / 1p1 | 3 / 1p1 | 3 / 1p1 |
| Lunar Lake | 7 / 1.33p135 | 3 / 0.33p135 | 3 / 0.33p135 |
| Zen | 6 / 0.5ALU0 ALU1 ALU2 ALU3 | 2 / 0.5ALU0 ALU1 ALU2 ALU3 | 2 / 0.5ALU0 ALU1 ALU2 ALU3 |
| Zen 3 | 6 / 0.5ALU1 ALU2 | 2 / 1ALU0 ALU1 ALU2 ALU3 | 2 / 0.5ALU1 ALU2 |
| Zen 4 | 5 / 0.5ALU1 ALU2 | 1 / 0.25ALU0 ALU1 ALU2 ALU3 | 1 / 0.5ALU1 ALU2 |
| Jaguar | 5 / 1ALU0 ALU1 | 2 / 1ALU0 ALU1 | 2 / 1ALU0 ALU1 |
Operation
What the forms do, in pseudocode that our tests run against results recorded on real processors. How to read.
s = src.read()
n = 0
for i in range(OSIZE):
if s >> i & 1:
break
n += 1 # the 0 bits below the lowest 1 bit
if not nf: # NF=1 (APX) leaves the flags as they were
cf = s == 0 # and then n is OSIZE
zf = n == 0
of = sf = af = pf = UNDEFINED
dest.write(n) # a 32-bit register: bits 63:32 cleared
# without BMI1, a processor runs the legacy encoding (F3 0F BC) as BSFRun on AMD Zen 5 (EPYC 9645): 78 cases. The APX forms were not run: none of these processors has APX.
Operands
| Operand | Kind | Action |
|---|---|---|
TZCNT r16/32/64, r/m16/32/64 | ||
r16/32/64 | explicit | write |
r/m16/32/64 | explicit | read |
RFLAGS | implicit | write |
TZCNT r16/32/64, r/m16/32/64 NF | ||
r16/32/64 | explicit | write |
r/m16/32/64 | explicit | read |
Flags
| OF | SF | ZF | AF | PF | CF |
|---|---|---|---|---|---|
| NF forms | |||||
| Not affected | |||||
| Other forms | |||||
| U | U | M | U | U | M |
Exceptions
The exception class is XED's name for the set of exception conditions that a form follows. How to read.
- Legacy forms
- none in XED
- EVEX forms
APX-EVEX-INT
CPUID and processors
Each form belongs to an XED ISA set. A form can be used when the processor reports every CPUID bit of one of its ISA set's alternatives.
| ISA set | CPUID | Processors |
|---|---|---|
BMI1 | BMI1 | Haswell, Broadwell, Skylake, Comet Lake, Cannon Lake, Ice Lake (client), Tiger Lake, Alder Lake, Arrow Lake, Panther Lake, Nova Lake, Skylake-SP, Cascade Lake, Cooper Lake, Ice Lake (server), Sapphire Rapids, Emerald Rapids, Granite Rapids, Diamond Rapids, Sierra Forest, Clearwater Forest, Knights Landing, Knights Mill, Zen 3, Zen 4, Zen 5, Zen 6 Note: XED's Piledriver adds TBM and FMA to Bulldozer but not BMI1, and its Zen chips inherit that gap; GCC's -march=bdver2 and later and -march=znver1 and later include BMI1. |
APX_F_BMI1 | APX_F + BMI1 | Nova Lake, Diamond Rapids |
APX_F_BMI1_N3 | APX_F + APX_NCI_NDD_NF + BMI1 | Same as APX_F_BMI1 |
More BMI1 instructions
More APX instructions
Sources
- Intel XED v2026.08.23 (commit
0bcb6237345c): forms, encodings, operands, flags, exception classes, attributes, ISA sets, CPUID bits and chip model. - The CPU data of x86-64.net: Zen 3 to Zen 6, from GCC’s
-marchfeature sets and AMD’s Processor Programming References, which the page of each processor cites. - LLVM 23.1.2's x86 scheduling models (commit
85ac56026243): the latency, reciprocal throughput and ports of each form, as llvm-mca reports them. - Intel 64 and IA-32 Architectures Software Developer's Manual, Volume 2 (325383-093, September 2026): Volume 2B, page 4-731 (TZCNT)
- Intel Advanced Performance Extensions (Intel APX) Architecture Specification, revision 9.0 (355828-009, August 2026)
- AMD64 Architecture Programmer's Manual, Volume 3: General-Purpose and System Instructions (24594, rev. 3.39, October 2026)
The tables are derived from Intel XED, Copyright Intel Corporation, licensed under the Apache License 2.0; x86-64.net converted and reformatted them. The timings come from LLVM's scheduling models, part of the LLVM Project, licensed under the Apache License 2.0 with LLVM Exceptions. The text is our own.