This is the mail archive of the gcc@gcc.gnu.org mailing list for the GCC project.


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]
Other format: [Raw text]

Re: Restricting arguments to intrinsic functions


First off, I apologize for resurrecting such an old thread, but I feel this needs a bit of clarity and to add my "two cents".

On 10/23/2014 12:52 PM, Charles Baylis wrote:
Hi

( tl;dr: How do I handle intrinsic or builtin functions where there
are restrictions on the arguments which can't be represented in a C
function prototype? Do other ports have this problem, how do they
solve it? Language extension for C++98 to provide static_assert?)

I'm trying to resolve some problems with error reporting for NEON (ARM
SIMD/vector) intrinsics.
eg https://bugs.linaro.org/show_bug.cgi?id=418

The NEON intrinsics are defined in a header file, arm_neon.h, which
includes type definitions and inline functions which implement the
intrinsics in terms of __builtin functions provided by gcc.

A number of these intrinsics (eg shift by a constant, set/get Nth lane
of vector) are defined to take a compile-time integer constant as one
of their arguments. For example:

The vshrn_n_s16 (narrowing vector shift right by a constant) intrinsic
is defined as:
     __extension__ static __inline int8x8_t __attribute__ ((__always_inline__))
     vshrn_n_s16 (int16x8_t __a, const int __b)
     {
       return (int8x8_t)__builtin_neon_vshrn_nv8hi (__a, __b, 1);
     }

These examples demonstrate some valid and invalid use
     int8x16_t v = vshrn_n_u16(v1, 5); // valid

     int8x16_t v = vshrn_n_u16(v1, 20); // invalid, constant is out of range

     int x = 5;
     int8x16_t v = vshrn_n_u16(v1, x); // invalid, shift amount is not
a compile-time constant

We have a distinct (human) language problem here. Going by the C standard, there's no such thing as a "compile-time constant". What you appear to be referring to in your example is an "integer constant expression" as per Â6.6. In the real world, if you compile your example with -O1 or better, gcc is going to replace that "x" with 5 because it clearly will always be 5 when that line of code is reached and __builtin_constant_p(x) will always return 1. This is what I generally call a "compile-time constant" (exists in the real world in optimized builds, but not yet in the C standard). So in fact, that last line shouldn't be invalid per your specifications, unless it compiled w/o optimizations, but again, it depends on what you mean by "compile-time constant".

Presently, the ARM/Aarch64 backends include some checks, but this
doesn't work very well, as it allows invalid operations to persist too
long. By the time the error is raised, the original source position is
lost. Therefore, I would like to add some error checking at an earlier
stage, with various options coming to mind:

1. Somehow arrange for __builtin_neon_vshrn_nv8hi to require a
constant integer in the prescribed range as the 2nd argument.

2. Redefine the intrinsic function in arm_neon.h to include
    _Static_assert(__b >= 1 && __b <=8, "Shift amount out of range");

3. Put a static assert into a macro
#define vshrn_n_s16(a,b) \
    ({ _Static_assert(__b >= 1 && __b <=8, "Shift amount out of
range"); _vshrn_n_s16(a,b); })

_Static_assert doesn't care about what the optimizer determines is constant or not, so both options 2 and 3 here will enforce that b is an "integer constant expression". If you were to use this in an inline (or always_inline) function, the _Static_assert would always fail.

4. Some sort of language extension so that inline functions can
require compile-time constants for suitably annotated arguments.
eg
     static __inline int8x8_t __attribute__ ((__always_inline__))
     vshrn_n_s16 (int16x8_t __a, const int
__attribute((constant_range(1,8))) __b)


Pros/cons of each approach:
1. somewhat complex to implement, errors are reported in arm_neon.h,
rather than at the source line in the user's code.

2. Easy to implement. Can be adapted to support C++11 using
static_assert, but doesn't work for C++98 (maybe a language extension
is possible to provide the same functionality as __static_assert in
C++98?) . Errors are reported in arm_neon.h, rather than user's code.

3. Easy to implement, the compiler will indicate the source line where
the macro was used, so that the user can find the location of their
error easily. Can also be adapted to support C++11 using
static_assert, but doesn't work for C++98.

4. Results in high quality error messages, but is complex to implement.

In my view, options 1&4 are potentially complex to implement. I favour
one of 2 or 3, with an extension to C++98 to provide
static_assert-like functionality. This is not unprecedented, as
_Static_assert is provided in all C language dialects.

I am interested in views on the relative merits of these approaches.

Thanks
Charles

I'm working on a C metaprogramming library for (currently supporting only gcc) and this is an everyday dilemma for me. I have a macros for this (see https://github.com/daniel-santos/gboing/blob/master/include/gboing/compiler.h#L62). So if written as such(and compiled with optimizations):

__extension__ static __inline int8x8_t __attribute__ ((__always_inline__))
    vshrn_n_s16 (int16x8_t __a, const int __b)
    {
      gboing_assert_msg(__b >= 1 && __b <= 8, "Shift amount out ofrange");
      return (int8x8_t)__builtin_neon_vshrn_nv8hi (__a, __b, 1);
    }

...it will expand into something like:

__extension__ static __inline int8x8_t __attribute__ ((__always_inline__))
    vshrn_n_s16 (int16x8_t __a, const int __b)
    {
      do {
extern void __gboing_assert_5(void) __attribute__((error("Shift amount out of range")));
        if (!(__b >= 1 && __b <= 8))
          __gboing_assert_5();
      } while (0);
      return (int8x8_t)__builtin_neon_vshrn_nv8hi (__a, __b, 1);
    }

Through constant folding and propagation and dead code removal, the error will only be invoked if the condition isn't met. When building -O0, this mechanism doesn't work and the macro expands into a do {} while(0). gcc will display a full backtrace of all macro expansions and inline functions if built with -g (I wish it did it always, but oh well).

Daniel


Index Nav: [Date Index] [Subject Index] [Author Index] [Thread Index]
Message Nav: [Date Prev] [Date Next] [Thread Prev] [Thread Next]