C 64位整数的有符号饱和加法?

C 64位整数的有符号饱和加法?,c,optimization,x86-64,addition,saturation-arithmetic,C,Optimization,X86 64,Addition,Saturation Arithmetic,我正在寻找一些用于签名饱和64位加法的C代码,它可以使用gcc优化器编译成高效的x86-64代码。虽然必要时可以使用asm解决方案,但可移植代码是理想的 static const int64 kint64max = 0x7fffffffffffffffll; static const int64 kint64min = 0x8000000000000000ll; int64 signed_saturated_add(int64 x, int64 y) { bool x_is_negativ

我正在寻找一些用于签名饱和64位加法的C代码,它可以使用gcc优化器编译成高效的x86-64代码。虽然必要时可以使用asm解决方案,但可移植代码是理想的

static const int64 kint64max = 0x7fffffffffffffffll;
static const int64 kint64min = 0x8000000000000000ll;

int64 signed_saturated_add(int64 x, int64 y) {
  bool x_is_negative = (x & kint64min) != 0;
  bool y_is_negative = (y & kint64min) != 0;
  int64 sum = x+y;
  bool sum_is_negative = (sum & kint64min) != 0;
  if (x_is_negative != y_is_negative) return sum;  // can't overflow
  if (x_is_negative && !sum_is_negative) return kint64min;
  if (!x_is_negative && sum_is_negative) return kint64max;
  return sum;
}

所编写的函数生成一个相当长的程序集输出,其中包含几个分支。关于优化有什么建议吗?似乎它应该只需要一个
ADD
和一些
CMOV
指令就可以实现,但我对这些东西有点生疏了。

我仍然在寻找一个像样的便携式解决方案,但这是我迄今为止提出的最好的解决方案:

改进建议

int64 saturated_add(int64 x, int64 y) {
#if __GNUC__ && __X86_64__
  asm("add %1, %0\n\t"
      "jno 1f\n\t"
      "cmovge %3, %0\n\t"
      "cmovl %2, %0\n"
      "1:" : "+r"(x) : "r"(y), "r"(kint64min), "r"(kint64max));
  return x;
#else
  return portable_saturated_add(x, y);
#endif
}

这可能会进一步优化,但这里有一个可移植的解决方案。它不会调用未定义的行为,并在发生整数溢出之前检查整数溢出

#include <stdint.h>

int64_t sadd64(int64_t a, int64_t b)
{
    if (a > 0) {
        if (b > INT64_MAX - a) {
            return INT64_MAX;
        }
    } else if (b < INT64_MIN - a) {
            return INT64_MIN;
    }

    return a + b;
}
#包括
int64_t sadd64(int64_t a,int64_t b)
{
如果(a>0){
如果(b>INT64_MAX-a){
返回INT64_MAX;
}
}否则如果(b
这是一个延续其中一条评论中所述脉络的解决方案,也已在ouah的解决方案中使用。这里生成的代码应该没有条件跳转

int64_t signed_saturated_add(int64_t x, int64_t y) {
  // determine the lower or upper bound of the result
  int64_t ret =  (x < 0) ? INT64_MIN : INT64_MAX;
  // this is always well defined:
  // if x < 0 this adds a positive value to INT64_MIN
  // if x > 0 this subtracts a positive value from INT64_MAX
  int64_t comp = ret - x;
  // the condition is equivalent to
  // ((x < 0) && (y > comp)) || ((x >=0) && (y <= comp))
  if ((x < 0) == (y > comp)) ret = x + y;
  return ret;
}
int64\u t签名\u饱和\u添加(int64\u t x,int64\u t y){
//确定结果的下限或上限
int64_t ret=(x<0)?int64_MIN:int64_MAX;
//这一点总是很明确的:
//如果x<0,则会向INT64_MIN添加一个正值
//如果x>0,则从INT64_MAX中减去一个正值
int64_t comp=ret-x;
//该条件等价于
//(x<0)和&(y>comp)| |((x>=0)和&(y>comp))ret=x+y;
返回ret;
}
第一个看起来像是有条件的移动,但是由于特殊的值,我的编译器得到了一个附加值:在2的补码中
INT64_MIN
INT64_MAX+1
。 如果一切正常,那么只有一个条件移动用于分配和


所有这些都没有UB,因为在抽象状态机中,只有在没有溢出的情况下才进行求和

相关:
无符号
饱和在纯ISO C中更容易、更有效地实现:


编译器在目前提出的所有纯C选项中都很糟糕

他们看不到可以使用
add
指令中的有符号溢出标志结果来检测是否需要将饱和度设置为INT64_MIN/MAX。另外,编译器没有识别为读取
add
的结果标志的纯C模式

// clang8.0 -O3 -target aarch64
signed_sat_add64_gnuc:
    orr     x9, xzr, #0x7fffffffffffffff      // mov constant = OR with zero reg
    adds    x8, x0, x1                        // add and set flags
    add     x9, x9, x1, lsr #63               // sat = (b shr 63) + MAX
    csel    x0, x9, x8, vs                    // conditional-select, condition = VS = oVerflow flag Set
    ret
        // an earlier version of this answer used this
        int64_t mask = (b<0) ? ~0ULL : 0;  // compiles to sar with good compilers, but is not implementation-defined.
        return mask ^ INT64_MAX;
内联asm在这里不是一个坏主意,但我们可以通过GCC的内置代码避免这种情况,该内置代码使用布尔溢出结果公开UB-safe包装签名加法

(如果您打算使用GNU C内联asm,这将限制您与这些GNU C内置组件一样多。而且这些内置组件不是特定于arch的。它们确实需要gcc5或更高版本,但gcc4.9及更高版本基本上已经过时。-它会阻止不断传播,并且很难维护。)



此版本使用
INT64_MIN=INT64_MAX+1all
(用于2的补码)的事实,根据
b
的符号选择INT64_MIN/MAX。通过对该加法使用
uint64\u t
可以避免有符号溢出,GNU C定义了将无符号整数转换为不能表示其值的有符号类型的行为(使用的位模式不变)。当前的gcc/clang受益于这种手持方式,因为他们无法从三元模式(如
)中找到这一窍门(我们计算值符号的方法太复杂了,为什么不直接使用
(x<0)
,例如,为了便于携带使用
[u]int64_t
。然后您可以免费使用
int64_MAX
int64_MIN
,而不必使用自己的常量。gcc的可能副本可以优化128位数字上的操作。请尝试类似于
钳制((int128_t)x+y,int64_MIN,int64_MAX))的操作
并查看它是否可接受。此实现调用未定义的行为。一个好的实现应该在溢出发生之前检查它。即使计算总和也是未定义的行为,如果它溢出。编译器可以并且确实在优化时利用这一事实(例如,将表达式
x+1>x
编译为常量
1
)。仅出于这个原因,我不认为这个问题应该结束,因为参考答案中没有一个考虑到这个事实……我同意这是可移植的、优雅的、100%正确的。一个潜在的优化:不要使用
返回INT64_MAX
,而是尝试
b=INT64_MAX-a
。而不是
返回INT64_MIN
,而是尝试
b=INT64_MIN-a
。在我的编译器(GCC4.7.3)上,这会生成稍微紧凑的代码,用条件移动替换两个条件分支。(另一方面,它引入了更多的数据依赖性,因此速度可能较慢……)我同意这是正确的“直接”解决方案@尼莫,事实上有一种可能只会导致一次有条件的移动,见下面我的答案。这些解决方案中哪一个更有效,只有基准测试才能显示。请参阅我的答案,了解只生成一个有条件移动的解决方案。无论这是否更好,您都必须进行基准测试。我想知道您是否可以执行类似于
asm(“添加%[y],%%[x]\n\t”“jno 1f\n\t”“xor%%rax,%%%rax\n\t”“mov%[MAX],%%[x]\n\t”“setge%%al\n\t”“添加%%rax,%%[x]\n\t”“1:[x]“+r”(x):[y]“r”(y),[MAX]“I”(INT64\u MAX):“eax”,“cc”)。乍一看,这可能比代码长,但请记住,在调用asm之前,代码需要将值加载到%2和%3中,即使它不打算使用它们。矿山仅在溢流时进行装载(可能是较不常见的情况)。NB:已经很晚了,我还没有运行这个。正如@JensGustedt所说,基准测试。假设饱和很少,那么您需要无溢出的情况
// clang8.0 -O3 -target aarch64
signed_sat_add64_gnuc:
    orr     x9, xzr, #0x7fffffffffffffff      // mov constant = OR with zero reg
    adds    x8, x0, x1                        // add and set flags
    add     x9, x9, x1, lsr #63               // sat = (b shr 63) + MAX
    csel    x0, x9, x8, vs                    // conditional-select, condition = VS = oVerflow flag Set
    ret
        // an earlier version of this answer used this
        int64_t mask = (b<0) ? ~0ULL : 0;  // compiles to sar with good compilers, but is not implementation-defined.
        return mask ^ INT64_MAX;
int64_t signed_sat_add64_gnuc(int64_t a, int64_t b) {
    long long res;
    bool overflow = __builtin_saddll_overflow(a, b, &res);
    if (overflow) {
            // overflow is only possible in one direction for a given `b`
            return (b<0) ? INT64_MIN : INT64_MAX;
    }
    return res;
}
# gcc9.1 -O3   (clang chooses branchless)
signed_sat_add64_gnuc:
        add     rdi, rsi                   # the actual add
        jo      .L3                        # jump on signed overflow
        mov     rax, rdi                   # retval = the non-overflowing add result
        ret
.L3:
        movabs  rdx, 9223372036854775807
        test    rsi, rsi                      # one of the addends is still available after
        movabs  rax, -9223372036854775808     # missed optimization: lea rdx, [rax+1]
        cmovns  rax, rdx                      # branchless selection of which saturation limit
        ret