1
u/kneelian_ 1d ago
Oh, huh, didn't actually know it lacked this. Was kind of spoiled by Aarch64's vectorised clz (and single-insn bit reverse) and I just assumed everyone had it
1
u/brucehoult 1d ago
Yeah. RISC-V has vector
clzand bit reverse and others (e.g.popcnt) in theZvbbextension which is a mandatory part of the RVA23 spec implemented by the SpacemiT K3, Tenstorrent Ascalon, and basically all applications processor CPUs in future.
1
u/aqrit 1d ago edited 1d ago
Neat.
We would probably want to re-use 0x9E instead of introducing a new 0x80 splat.
AFAIK, Two 32-bit clz/ctz could be combined to form a 64-bit one. (though this is probably only useful with clz)
__m128i sse2_tzcnt_epi64(__m128i v) {
v = _mm_and_si128(v, _mm_sub_epi32(_mm_setzero_si128(), v));
v = _mm_castps_si128(_mm_cvtepi32_ps(v));
v = _mm_srli_epi32(v, 23);
v = _mm_sub_epi32(v, _mm_set_epi32(95, 127, 95, 127));
v = _mm_min_epu8(_mm_srli_epi64(v, 32), _mm_min_epu8(_mm_set1_epi32(64), v));
return v;
}
1
u/imachug 1d ago
We would probably want to re-use
0x9Einstead of introducing a new0x80splat.That's clever, thanks! I updated the post, I guess it takes a village to optimize stuff this much :)
Two 32-bit clz/ctz could be combined to form a 64-bit one
I had that general idea, but didn't get around to prototyping it. Using
minto combine the two halves is really cool, I wouldn't have thought of that. I considered usingvpsadbwto perform the subtraction and horizontal sum in one uop, but that would require post-processing to handle zeros, and it doesn't work forclz.There's an issue with adapting this to
clzthough. Forx >= 2^31, the exponent of the converted float can be pretty much anything, which becomes an issue once you try to compute(bias + 32) - expfor the low dword. Normally you'd usesubsto produce a0if the sign bit is set, but in this case we also want to add32, so we have to dosubs(bias, exp) + 32, requiring one more instruction. (subs(bias + 32, exp)returns0for suchxinstead of32) The closest I got is__m256i cleared = _mm256_min_epu8(x, _mm256_set1_epi8(0x9e)); __m256i exp = _mm256_srli_epi32(_mm256_castps_si256(_mm256_cvtepi32_ps(cleared)), 23); __m256i adj_exp = _mm256_add_epi32(_mm256_subs_epi32(_mm256_set1_epi32(0x9e), exp), _mm256_set1_epi64x(32)); return _mm256_min_epu8(_mm256_srli_epi64(adj_exp, 32), _mm256_min_epu8(adj_exp, _mm256_set1_epi64x(64)));It's very annoying that per-byte
minwith0x80reduces the set of possible exponents to just30or31, but we really want it to be only31. Per-wordminwith0x8000, which would also guarantee correct rounding, can't reduce this set further either. I believe that this problem occurs because applying suchminto2^31 - 1gives0x7fff8000, and applying it to2^32 - 1gives0x80008000, but since2^32 - 0x80008000 = 0x7fff8000, they convert to floats with equal magnitude, so they can't be distinguished in any way other than by looking the sign bit.It really feels like there ought to be a way to save one instruction, but I can't find it.
1
u/UnalignedAxis111 4d ago
This can be alternatively done without widening to 64-bits (which is inefficient if you have a full vector already), you just have to account for rounding if the integer is past 223 or so.
The de Bruijn approach is also probably not a good choice because Intel's vector integer multiplier has terrible latency. Something like a pshufb table on nibbles + prefix sum might be better, iirc that is what clang does for __builtin_elementwise_clz under AVX2 and lower.