On Fri, 10 Nov 2023, Jakub Jelinek wrote: > On Fri, Nov 10, 2023 at 08:09:26AM +0000, Richard Biener wrote: > > > The following patch adds 6 new type-generic builtins, > > > __builtin_clzg > > > __builtin_ctzg > > > __builtin_clrsbg > > > __builtin_ffsg > > > __builtin_parityg > > > __builtin_popcountg > > > The g at the end stands for generic because the unsuffixed variant > > > of the builtins already have unsigned int or int arguments. > > > > > > The main reason to add these is to support arbitrary unsigned (for > > > clrsb/ffs signed) bit-precise integer types and also __int128 which > > > wasn't supported by the existing builtins, so that e.g. <stdbit.h> > > > type-generic functions could then support not just bit-precise unsigned > > > integer type whose width matches a standard or extended integer type, > > > but others too. > > > > > > None of these new builtins promote their first argument, so the argument > > > can be e.g. unsigned char or unsigned short or unsigned __int20 etc. > > > > But is that a good idea? Is that how type generic functions work in C? > > Most current type generic functions deal just with floating point args and > don't promote there float to double as the ... promotions would normally do: > __builtin_signbit > __builtin_fpclassify > __builtin_isfinite > __builtin_isinf_sign > __builtin_isinf > __builtin_isnan > __builtin_isnormal > __builtin_isgreater > __builtin_isgreaterequal > __builtin_isless > __builtin_islessequal > __builtin_islessgreater > __builtin_isunordered > __builtin_iseqsig > __builtin_issignaling > > __builtin_clear_padding is uninteresting, because the argument must be a > pointer. > > Then > __builtin_add_overflow > __builtin_sub_overflow > __builtin_mul_overflow > do promote the first 2 arguments, but that doesn't really matter, because > all we care about is the argument values, not their type. > > And finally > __builtin_add_overflow_p > __builtin_sub_overflow_p > __builtin_mul_overflow_p > do promote the first 2 arguments, and don't promote the third one, which is > the only one where we care about the type, so that is the behavior that > I've used also for the new builtins. I think if we added e.g. > __builtin_classify_type now and not more than 3 decades ago it would behave > like that too. > Only not promoting the argument will make it directly usable in the > stdc_leading_zeros, stdc_leading_ones, stdc_trailing_zeros, > stdc_trailing_ones, > stdc_first_leading_zero, ..., stdc_count_zeros, stdc_count_ones, ... > C23 stdbit.h type-generic macros, otherwise one would need to play with > _Generic and special-case there unsigned char and unsigned short (which > normally promote to int), but e.g. unsigned _BitInt(8) doesn't.
googling doesn't find me stdc_leading_zeros - are those supposed to work for non-_BitInt types as well and don't promote the argument in that case? If we are spcificially targeting those I wonder why we don't name the builtins after those? But yes, if promotion is undesirable for implementing them then I agree. IIRC _BitInt(n) is not subject to integer promotions. > I expect Joseph will have compatibility version of the macro for when these > builtins aren't supported, but given the standard says that {un,}signed > _BitInt > with width matching standard/extended integer types other than bool needs to > be handled also, either it will not use _Generic at all and just go after > sizeof (argument), or maybe will use _Generic and for the default case will > go after sizeof. Seems clang returns -1 for __builtin_classify_type (0uwb) > rather than 18 GCC returns, so one can't portably distinguish bitints. > > > I think it introduces non-obvious/unexpected behavior in user code. > > If we keep the patch behavior of requiring unsigned > standard/extended/bit-precise types other than bool for the > clz/ctz/parity/popcount cases, the choice is between always erroring on > __builtin_clzg ((unsigned char) 1) - where the promoted argument is signed, > and accepting it as unsigned char case without promotion, so I think users > would be more confused to see an error on that. > If we'd switch to accepting both signed and unsigned > standard/extended/bit-precise integer types other than bool for all the > builtins, whether we promote or not doesn't matter for ctz/parity/popcount > but does for clz. > The clrsb/ffs cases accept signed argument on the other side, so both > promoted and unpromoted argument would mean something and be accepted, > in the ffs case it again doesn't really matter for the result, but for clrsb > is significant. > Would it help to just document it that the argument isn't promoted? > > We document that for __builtin_*_overflow_p: > "The value of the third argument is ignored, just the side effects in the > third argument > are evaluated, and no integral argument promotions are performed on the last > argument." > > > If people do not want to "compensate" for this maybe insted also add > > __builtin_*{8,16} (like we have for the bswap variants)? > > Note, clang has __builtin_clzs and __builtin_ctzs for unsigned short (but > not the other 4), but nothing for the unsigned char cases. > I was just hoping we don't need to add further variants if we have > type-generic ones. > > > Otherwise this looks reasonable. I'm not sure why we need separate > > CFN_CLZ and CFN_BUILT_IN_CLZG? (why CFN_BUILT_IN_CLZG and not CFN_CLZG?) > > That is, I'm confused about > > > > CASE_CFN_CLRSB: > > + case CFN_BUILT_IN_CLRSBG: > > > > why does CASE_CFN_CLRSB not include CLRSBG? It includes IFN_CLRSB, no? > > And IFN_CLRSB already has the two and one arg case and thus encompasses > > some BUILT_IN_CLRSBG cases? > > gencfn-macros.cc is aware of just normal float suffixes (F, nothing, L; > then under different names of the macros other variants for float > suffixes), and int suffixes (nothing, L, LL, IMAX), it doesn't know anything > about the G suffix. We could teach it to under a different suffix add the > G case too, but I didn't think it was necessary because the *G builtins are > meant to be folded away into something else as soon as possible, worst case > during gimplification, so nothing after that ought to care about them. > It is just the fold-const-call.cc case where in constant expressions I think > we want to fold them into constants and having new macros just to use them > once (and don't want to use them in the 2-6 other places depending on the > builtin) seemed unnecessary. > > > Besides the above question I'd say OK (I assume Josephs reply is a > > general ack from his side). > > Joseph, what are your thoughts on the above? > > Incremental patch to document the lack of integral argument promotion: > > --- gcc/doc/extend.texi.jj 2023-11-09 09:17:40.240182342 +0100 > +++ gcc/doc/extend.texi 2023-11-10 09:57:45.396215654 +0100 > @@ -14962,13 +14962,15 @@ Similar to @code{__builtin_parity}, exce > > @defbuiltin{int __builtin_ffsg (...)} > Similar to @code{__builtin_ffs}, except the argument is type-generic > -signed integer (standard, extended or bit-precise). > +signed integer (standard, extended or bit-precise). No integral argument > +promotions are performed on the argument. > @enddefbuiltin > > @defbuiltin{int __builtin_clzg (...)} > Similar to @code{__builtin_clz}, except the argument is type-generic > unsigned integer (standard, extended or bit-precise) and there is > -optional second argument with int type. If two arguments are specified, > +optional second argument with int type. No integral argument promotions > +are performed on the first argument. If two arguments are specified, > and first argument is 0, the result is the second argument. If only > one argument is specified and it is 0, the result is undefined. > @enddefbuiltin > @@ -14976,24 +14978,28 @@ one argument is specified and it is 0, t > @defbuiltin{int __builtin_ctzg (...)} > Similar to @code{__builtin_ctz}, except the argument is type-generic > unsigned integer (standard, extended or bit-precise) and there is > -optional second argument with int type. If two arguments are specified, > +optional second argument with int type. No integral argument promotions > +are performed on the first argument. If two arguments are specified, > and first argument is 0, the result is the second argument. If only > one argument is specified and it is 0, the result is undefined. > @enddefbuiltin > > @defbuiltin{int __builtin_clrsbg (...)} > Similar to @code{__builtin_clrsb}, except the argument is type-generic > -signed integer (standard, extended or bit-precise). > +signed integer (standard, extended or bit-precise). No integral argument > +promotions are performed on the argument. > @enddefbuiltin > > @defbuiltin{int __builtin_popcountg (...)} > Similar to @code{__builtin_popcount}, except the argument is type-generic > -unsigned integer (standard, extended or bit-precise). > +unsigned integer (standard, extended or bit-precise). No integral argument > +promotions are performed on the argument. > @enddefbuiltin > > @defbuiltin{int __builtin_parityg (...)} > Similar to @code{__builtin_parity}, except the argument is type-generic > -unsigned integer (standard, extended or bit-precise). > +unsigned integer (standard, extended or bit-precise). No integral argument > +promotions are performed on the argument. > @enddefbuiltin > > @defbuiltin{double __builtin_powi (double, int)} > > > Jakub > > -- Richard Biener <rguent...@suse.de> SUSE Software Solutions Germany GmbH, Frankenstrasse 146, 90461 Nuernberg, Germany; GF: Ivo Totev, Andrew McDonald, Werner Knoblich; (HRB 36809, AG Nuernberg)