GCC __builtin_clz and smaller-than-int arguments - Page 1
Forum section: electronics / general

author: HwAoRrDk
author_rank: Super Contributor
timestamp: February 19, 2023, 02:56:29 pm
content: Can anyone explain to me why GCC's__builtin_clzfunction behaves weirdly and doesn't give correct results when passed an argument that is smaller thanint?Is it perhaps something to do with it not being a real function, and maybe smaller args do not get extended tointwidth like they normally would?What I encountered trouble with was trying to use it for determining how many bits to shift a register field's value over to the least-significant position by, using only the field's mask define. But the mask define includes a typecast of the constant touint16_t. So I end up with an incorrect value because__builtin_clzreturns the wrong result and the shift is too far or not far enough.

author: Kalvin
author_rank: Super Contributor
timestamp: February 19, 2023, 03:09:05 pm
content: Which GCC compiler and compiler version you are using? Can you show a code snippet that will generate invalid code?

author: TheCalligrapher
author_rank: Regular Contributor
timestamp: February 19, 2023, 06:19:46 pm
content: Quote from: HwAoRrDk on February 19, 2023, 02:56:29 pmYou need to provide an example. It is completely unclear what problem you are talking aboiut.It is also not clear what does__builtin_clzhave to do with shifting bits to theleast-significant position.__builtin_clzcountsmost-significant zero bits. If you wanted to countleast-significant zero bits, that would be__builtin_ctz.

author: SiliconWizard
author_rank: Super Contributor
timestamp: February 19, 2023, 08:04:48 pm
content: As said above. __builtin_clz() counts the number of leading zeros, make sure that's what you want to get.Also tell us what GCC version (or Clang which also supports this builtin) and what target.Now a quick check on godbolt shows that: with GCC 12.2 for x86_64, what it will do is:- sign extend the parameter (if the parameter is int16_t) or zero extend the parameter (if the parameter is uint16_t) to 32-bit,- use BSR to get the index of the MS bit set,- XOR it with 31 to get the number of leading zeros.So it does actually promote the integer to int and implicitely casts it to unsigned int. The side-effect is that __builtin_clz() will get you the number of leading zeros for a 32-bit integer if you pass a narrower integer to it - 32-bit being the width of int/unsigned int on x86_64 . On a given platform, it will be relative to the width of int's.Up to you to subtract the number of extra zeros for your use case. To get the width of 'unsigned int' in a portable way, use 'UINT_WIDTH' (include limits.h).Which makes sense since the function signature is this: 'int __builtin_clz (unsigned int x)'

author: HwAoRrDk
author_rank: Super Contributor
timestamp: February 19, 2023, 09:57:10 pm
content: Agh, I'm a dumbass - I got leading and trailing mixed up!For some reason I was associating 'leading' with the least-significant position, because when you're looking at values expressed in conventional binary notation, you start counting bits from the right. I should have checked the GCC documentation, where of course it explicitly says "Returns the number of leading 0-bits in x, starting at the most significant bit position".When I use__builtin_ctz, it does actually work properly.What I was trying to do was something like this:Code:#define SOME_REG_VAL_MASK ((uint16_t)0xFFF0)foo = lookup_table[(PERIPH->SOME_REG & SOME_REG_VAL_MASK) >> __builtin_ctz(SOME_REG_VAL_MASK)];It was only working accidentally in one place with__builtin_clz(and a differentuint32_tmask value) because while it was also shifting there too many places and the result was zero, that happened to be the appropriate value at index zero of the array. But in another place with the aforementioneduint16_tmask, it all went wrong.For those wondering "why not just add another define with the bit position of the register field?", I'm dealing with third-party headers, and I thought this was a clever solution to use what's already available, because GCC is able to resolve the call to the builtin with a constant argument to a constant value.