为 GCC 复制 clang 的 __builtin_assume

Jan*_*tke 6 c++ gcc clang built-in assumption

最近,我发现void __builtin_assume(bool)了 clang,它可以向编译器提供有关程序状态的附加信息。这可以产生巨大的差异,例如

#include <cstddef>

// compiles to about 80 instructions at -O3
unsigned sum(unsigned data[], size_t count) {
    unsigned sum = 0;
    for (size_t i = 0; i < count; ++i) {
        sum += data[i];
    }
    return sum;
}

// compiles to about 10 instructions at -O3
unsigned sum_small(unsigned data[], size_t count) {
    __builtin_assume(count <= 4);
    unsigned sum = 0;
    for (size_t i = 0; i < count; ++i) {
        sum += data[i];
    }
    return sum;
}
Run Code Online (Sandbox Code Playgroud)

我现在被迫使用 GCC,我很好奇是否存在等效的内置函数。__builtin_assume不幸的是我在GCC 文档中找不到。也许存在一个内置函数,但它只是有一个不同的名称?

如果不存在等效的内置函数,是否有可能在没有 的情况下产生相同的结果__builtin_assume,例如在条件不成立时故意调用未定义的行为?

理想情况下,我想要一个始终可以安全调用的宏,例如:

#if ... // detect clang
#define MY_ASSUME(condition) __builtin_assume(condition)
#elif ... // detect GCC
#define MY_ASSUME(condition) __gcc_builtin_assume_equivalent(condition)
#else
#define MY_ASSUME(condition)
#endif
Run Code Online (Sandbox Code Playgroud)

无论解决方案是什么,它也应该在constexpr函数中工作。

Fra*_*eux 6

我使用了__builtin_unreachable(),这表明控制流到达这里是未定义的行为。您可以将其包装在if本质上编写一个断言。该条件可以是任何不变量false,因此在您的情况下您将设置相反的条件。

例子:

// Basically __builtin_assume(count <= 4),
// except that !(count <= 4) is evaluated.
if ( !(count <= 4) ) {
    __builtin_unreachable();
}
Run Code Online (Sandbox Code Playgroud)

您可以将其转换为断言宏,如下所示:

// Line break for readability
#define my_assert(...) \
   { if(!(__VA_ARGS__)) __builtin_unreachable(); }
Run Code Online (Sandbox Code Playgroud)

根据问题中的代码,您将像这样使用它:

unsigned sum_small(unsigned data[], size_t count) {
    my_assert(count <= 4); // <--- Changed here
    unsigned sum = 0;
    for (size_t i = 0; i < count; ++i) {
        sum += data[i];
    }
    return sum;
}
Run Code Online (Sandbox Code Playgroud)


Jan*_*tke 2

从 C++23 开始,可以使用[[assume]]属性来实现这一点。这就像 clang 的一样工作__builtin_assume。还有一个__attribute__((__assume__(...))适用于 C 和 C++ 的程序。

假设宏的定义

// define an ASSUME(...) function-style macro so we only need to detect compilers
// in one place

// Comment this out if you don't want assumptions to possibly evaluate.
// This may happen for implementations based on unreachable() functions.
#define DANGEROUS_BEHAVIOR_ASSUMPTIONS_ALLOWED_TO_EVALUATE 1

// preferred option: C++ standard attribute
#ifdef __has_cpp_attribute
  #if __has_cpp_attribute(assume) >= 202207L
    #define ASSUME(...) [[assume(__VA_ARGS__)]]
  #endif
#endif
// first fallback: compiler intrinsics/attributes for assumptions
#ifndef ASSUME
  #if defined(__clang__)
    #define ASSUME(...) do { __builtin_assume(__VA_ARGS__); } while(0)
  #elif defined(_MSC_VER)
    #define ASSUME(...) do { __assume(__VA_ARGS__); } while(0)
  #elif defined(__GNUC__)
    #if __GNUC__ >= 13
      #define ASSUME(...) __attribute__((__assume__(__VA_ARGS__)))
    #endif
  #endif
#endif
// second fallback: possibly evaluating uses of unreachable()
#if !defined(ASSUME) && defined(DANGEROUS_BEHAVIOR_ASSUMPTIONS_ALLOWED_TO_EVALUATE)
  #if defined(__GNUC__)
    #define ASSUME(...) do { if (!bool(__VA_ARGS__)) __builtin_unreachable(); } while(0)
  #elif __cpp_lib_unreachable >= 202202L
    #include <utility>
    #define ASSUME(...) do { if (!bool(__VA_ARGS__)) ::std::unreachable(); ) while(0)
  #endif
#endif
// last fallback: define macro as doing nothing
#ifndef ASSUME
  #define ASSUME(...)
#endif
Run Code Online (Sandbox Code Playgroud)

使用示例

unsigned sum_small(unsigned data[], size_t count) {
    ASSUME(count <= 4);

    unsigned sum = 0;
    for (size_t i = 0; i < count; ++i) {
        sum += data[i];
    }
    return sum;
}
Run Code Online (Sandbox Code Playgroud)

所有编译器可能需要一些时间才能实现[[assume]],但正如您所看到的,有很多后备选项。截至撰写本文时,只有 GCC 13 支持此功能。

另请参阅:C++23 编译器支持