Author Topic: GCC __builtin_clz and smaller-than-int arguments  (Read 796 times)

0 Members and 1 Guest are viewing this topic.

Offline HwAoRrDkTopic starter

  • Super Contributor
  • ***
  • Posts: 1564
  • Country: gb
GCC __builtin_clz and smaller-than-int arguments
« on: February 19, 2023, 02:56:29 pm »
Can anyone explain to me why GCC's __builtin_clz function behaves weirdly and doesn't give correct results when passed an argument that is smaller than int?

Is it perhaps something to do with it not being a real function, and maybe smaller args do not get extended to int width 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 to uint16_t. So I end up with an incorrect value because __builtin_clz returns the wrong result and the shift is too far or not far enough.
 

Offline Kalvin

  • Super Contributor
  • ***
  • Posts: 2145
  • Country: fi
  • Embedded SW/HW.
Re: GCC __builtin_clz and smaller-than-int arguments
« Reply #1 on: February 19, 2023, 03:09:05 pm »
Which GCC compiler and compiler version you are using? Can you show a code snippet that will generate invalid code?
 

Offline TheCalligrapher

  • Regular Contributor
  • *
  • Posts: 159
  • Country: us
Re: GCC __builtin_clz and smaller-than-int arguments
« Reply #2 on: February 19, 2023, 06:19:46 pm »
Is it perhaps something to do with it not being a real function, and maybe smaller 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 to uint16_t. So I end up with an incorrect value because __builtin_clz returns the wrong result and the shift is too far or not far enough.

You need to provide an example. It is completely unclear what problem you are talking aboiut.

It is also not clear what does __builtin_clz have to do with shifting bits to the least-significant position. __builtin_clz counts most-significant zero bits. If you wanted to count least-significant zero bits, that would be __builtin_ctz.
« Last Edit: February 19, 2023, 06:23:04 pm by TheCalligrapher »
 

Online SiliconWizard

  • Super Contributor
  • ***
  • Posts: 15439
  • Country: fr
Re: GCC __builtin_clz and smaller-than-int arguments
« Reply #3 on: February 19, 2023, 08:04:48 pm »
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)'

« Last Edit: February 19, 2023, 08:07:11 pm by SiliconWizard »
 

Offline HwAoRrDkTopic starter

  • Super Contributor
  • ***
  • Posts: 1564
  • Country: gb
Re: GCC __builtin_clz and smaller-than-int arguments
« Reply #4 on: February 19, 2023, 09:57:10 pm »
Agh, I'm a dumbass - I got leading and trailing mixed up! |O

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: [Select]
#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 different uint32_t mask 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 aforementioned uint16_t mask, 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.
 


Share me

Digg  Facebook  SlashDot  Delicious  Technorati  Twitter  Google  Yahoo
Smf