Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How can I elide a call if an edge condition is known at compile time?

I have the following situation: there's a huge set of templates like std::vector that will call memmove() to move parts of array. Sometimes they will want to "move" parts of length zero - for example, if the array tail is removed (like std::vector::erase()), they will want to move the remainder of the array which will happen to have length zero and that zero will be known at compile time (I saw the disassembly - the compiler is aware) yet the compiler will still emit a memmove() call.

So basically I could have a wrapper:

inline void callMemmove( void* dest, const void* source, size_t count )
{
   if( count > 0 ) {
       memmove( dest, source, count );
   }
}

but this would introduce an extra runtime check in cases count is not known in compile time that I don't want.

Is it somehow possible to use __assume hint to indicate to the compiler that if it knows for sure that count is zero it should eliminate the memmove()?

like image 854
sharptooth Avatar asked Oct 05 '11 07:10

sharptooth


2 Answers

The point of the __assume is to tell the compiler to skip portions of code when optimizing. In the link you provided the example is given with the default clause of the switch construct - there the hint tells the compiler that the clause will never be reached even though theoretically it could. You're telling the optimizer, basically, "Hey, I know better, throw this code away".

For default you can't not write it in (unless you cover the whole range in cases, which is sometimes problematic) because it would cause compilation error. So you need the hint to optimize the code you know that is unneeded out.

In your case - the code can be reached, but not always, so the __assume hint won't help you much. You have to check if the count is really 0. Unless you're sure it can never be anything but 0, then just don't write it in.

like image 55
littleadv Avatar answered Jan 04 '23 12:01

littleadv


This solution uses a trick described in C++ compile-time constant detection - the trick uses the fact compile time integer zero can be converted to a pointer, and this can be used together with overloading to check for the "compile time known" property.

struct chkconst {
  struct Small {char a;};
  struct Big: Small {char b;};
  struct Temp { Temp( int x ) {} };
  static Small chk2( void* ) { return Small(); }
  static Big chk2( Temp  ) { return Big(); }
};

#define is_const_0(X) (sizeof(chkconst::chk2(X))<sizeof(chkconst::Big))
#define is_const(X) is_const_0( int(X)-int(X) )

#define memmove_smart(dst,src,n) do { \
    if (is_const(n)) {if (n>0) memmove(dst,src,n);} \
    else memmove(dst,src,n); \
  } while (false)

Or, in your case, as you want to check for zero only anyway, one could use is_const_0 directly for maximum simplicity and portability:

#define memmove_smart(dst,src,n) if (is_const_0(n)) {} else memmove(dst,src,n)

Note: the code here used a version of is_const simpler than in the linked question. This is because Visual Studio is more standard conformant than GCC in this case. If targeting gcc, you could use following is_const variant (adapted to handle all possible integral values, including negative and INT_MAX):

#define is_const_0(X) (sizeof(chkconst::chk2(X))<sizeof(chkconst::Big))
#define is_const_pos(X) is_const_0( int(X)^(int(X)&INT_MAX) )
#define is_const(X) (is_const_pos(X)|is_const_pos(-int(X))|is_const_pos(-(int(X)+1)))
like image 26
Suma Avatar answered Jan 04 '23 14:01

Suma