> mov edx, 0 ; Зачем? Скорее всего (спекуляция), потому что на этапе промежуточного кода (gimple) оно выглядит так:
popc (unsigned int d)
{
unsigned int D.1910;
unsigned int n; n = 0;
goto <D.1907>;
<D.1906>:
n = n + 1;
_1 = d + 4294967295;
d = d & _1;
<D.1907>:
if (d != 0) goto <D.1906>; else goto <D.1908>;
<D.1908>:
D.1910 = n;
return D.1910;
}
На финальном этапе RTL
(insn:TI # 0 0 2 (parallel [
(set (reg:SI 0 ax [orig:82 _2 ] [82])
(popcount:SI (reg/v:SI 5 di [orig:84 d ] [84])))
(clobber (reg:CC 17 flags))
...
(insn:TI # 0 0 2 (set (reg/v:SI 0 ax [orig:83 <retval> ] [83])
(if_then_else:SI (ne (reg:CCZ 17 flags)
(const_int 0 [0]))
(reg:SI 0 ax [orig:82 _2 ] [82])
(reg:SI 1 dx [86])))# {*movsicc_noc}
(expr_list:REG_DEAD (reg:CCZ 17 flags)
(expr_list:REG_DEAD (reg:SI 1 dx [86])
(nil))))
Т.е. получилось "мухи" (while(d) - модифицируем n, потом возвращаем) отдельно, "котлеты" (control flow, если "не d", возвращаем 0) отдельно, т.е. семантика popcnt не учитывается до такой степени
(возможно, потому что на целевой платформе popcnt "как у интеля" может и не быть, возможно, потому что основная оптимизация делается на более высоком уровне).
clang -S -emit-llvm
define dso_local i32 @popc(i32 %0) local_unnamed_addr #0 {
%2 = call i32 @llvm.ctpop.i32(i32 %0), !range !2
ret i32 %2
}
The 'llvm.ctpop' family of intrinsics counts the number of bits set in a value.
Получается, высокоуровневый код транслируется в одну "виртуальную" инструкцию подсчета, которая на "конкретных" интелях реализуется простым popcnt.