Commit 5ca44dc4 authored by smasher164's avatar smasher164 Committed by Filippo Valsorda

math/bits: make Add and Sub fallbacks constant time

Make the extended precision add-with-carry and sub-with-carry operations
take a constant amount of time to execute, regardless of input.

name             old time/op  new time/op  delta
Add-4            1.16ns ±11%  1.51ns ± 5%  +30.52%  (p=0.008 n=5+5)
Add32-4          1.08ns ± 0%  1.03ns ± 1%   -4.86%  (p=0.029 n=4+4)
Add64-4          1.09ns ± 1%  1.95ns ± 3%  +79.23%  (p=0.008 n=5+5)
Add64multiple-4  4.03ns ± 1%  4.55ns ±11%  +13.07%  (p=0.008 n=5+5)
Sub-4            1.08ns ± 1%  1.50ns ± 0%  +38.17%  (p=0.016 n=5+4)
Sub32-4          1.09ns ± 2%  1.53ns ±10%  +40.26%  (p=0.008 n=5+5)
Sub64-4          1.10ns ± 1%  1.47ns ± 1%  +33.39%  (p=0.008 n=5+5)
Sub64multiple-4  4.30ns ± 2%  4.08ns ± 4%   -5.07%  (p=0.032 n=5+5)

Fixes #31267

Change-Id: I1824b1b3ab8f09902ce8b5fef84ce2fdb8847ed9
Reviewed-on: https://go-review.googlesource.com/c/go/+/170758Reviewed-by: default avatarFilippo Valsorda <filippo@golang.org>
Reviewed-by: default avatarKeith Randall <khr@golang.org>
Run-TryBot: Filippo Valsorda <filippo@golang.org>
TryBot-Result: Gobot Gobot <gobot@golang.org>
parent 1ab063ce
...@@ -332,23 +332,21 @@ func Len64(x uint64) (n int) { ...@@ -332,23 +332,21 @@ func Len64(x uint64) (n int) {
// The carry input must be 0 or 1; otherwise the behavior is undefined. // The carry input must be 0 or 1; otherwise the behavior is undefined.
// The carryOut output is guaranteed to be 0 or 1. // The carryOut output is guaranteed to be 0 or 1.
func Add(x, y, carry uint) (sum, carryOut uint) { func Add(x, y, carry uint) (sum, carryOut uint) {
yc := y + carry if UintSize == 32 {
sum = x + yc s32, c32 := Add32(uint32(x), uint32(y), uint32(carry))
if sum < x || yc < y { return uint(s32), uint(c32)
carryOut = 1
} }
return s64, c64 := Add64(uint64(x), uint64(y), uint64(carry))
return uint(s64), uint(c64)
} }
// Add32 returns the sum with carry of x, y and carry: sum = x + y + carry. // Add32 returns the sum with carry of x, y and carry: sum = x + y + carry.
// The carry input must be 0 or 1; otherwise the behavior is undefined. // The carry input must be 0 or 1; otherwise the behavior is undefined.
// The carryOut output is guaranteed to be 0 or 1. // The carryOut output is guaranteed to be 0 or 1.
func Add32(x, y, carry uint32) (sum, carryOut uint32) { func Add32(x, y, carry uint32) (sum, carryOut uint32) {
yc := y + carry sum64 := uint64(x) + uint64(y) + uint64(carry)
sum = x + yc sum = uint32(sum64)
if sum < x || yc < y { carryOut = uint32(sum64 >> 32)
carryOut = 1
}
return return
} }
...@@ -356,11 +354,8 @@ func Add32(x, y, carry uint32) (sum, carryOut uint32) { ...@@ -356,11 +354,8 @@ func Add32(x, y, carry uint32) (sum, carryOut uint32) {
// The carry input must be 0 or 1; otherwise the behavior is undefined. // The carry input must be 0 or 1; otherwise the behavior is undefined.
// The carryOut output is guaranteed to be 0 or 1. // The carryOut output is guaranteed to be 0 or 1.
func Add64(x, y, carry uint64) (sum, carryOut uint64) { func Add64(x, y, carry uint64) (sum, carryOut uint64) {
yc := y + carry sum = x + y + carry
sum = x + yc carryOut = ((x & y) | ((x | y) &^ sum)) >> 63
if sum < x || yc < y {
carryOut = 1
}
return return
} }
...@@ -370,23 +365,20 @@ func Add64(x, y, carry uint64) (sum, carryOut uint64) { ...@@ -370,23 +365,20 @@ func Add64(x, y, carry uint64) (sum, carryOut uint64) {
// The borrow input must be 0 or 1; otherwise the behavior is undefined. // The borrow input must be 0 or 1; otherwise the behavior is undefined.
// The borrowOut output is guaranteed to be 0 or 1. // The borrowOut output is guaranteed to be 0 or 1.
func Sub(x, y, borrow uint) (diff, borrowOut uint) { func Sub(x, y, borrow uint) (diff, borrowOut uint) {
yb := y + borrow if UintSize == 32 {
diff = x - yb d32, b32 := Sub32(uint32(x), uint32(y), uint32(borrow))
if diff > x || yb < y { return uint(d32), uint(b32)
borrowOut = 1
} }
return d64, b64 := Sub64(uint64(x), uint64(y), uint64(borrow))
return uint(d64), uint(b64)
} }
// Sub32 returns the difference of x, y and borrow, diff = x - y - borrow. // Sub32 returns the difference of x, y and borrow, diff = x - y - borrow.
// The borrow input must be 0 or 1; otherwise the behavior is undefined. // The borrow input must be 0 or 1; otherwise the behavior is undefined.
// The borrowOut output is guaranteed to be 0 or 1. // The borrowOut output is guaranteed to be 0 or 1.
func Sub32(x, y, borrow uint32) (diff, borrowOut uint32) { func Sub32(x, y, borrow uint32) (diff, borrowOut uint32) {
yb := y + borrow diff = x - y - borrow
diff = x - yb borrowOut = ((^x & y) | (^(x ^ y) & diff)) >> 31
if diff > x || yb < y {
borrowOut = 1
}
return return
} }
...@@ -394,11 +386,8 @@ func Sub32(x, y, borrow uint32) (diff, borrowOut uint32) { ...@@ -394,11 +386,8 @@ func Sub32(x, y, borrow uint32) (diff, borrowOut uint32) {
// The borrow input must be 0 or 1; otherwise the behavior is undefined. // The borrow input must be 0 or 1; otherwise the behavior is undefined.
// The borrowOut output is guaranteed to be 0 or 1. // The borrowOut output is guaranteed to be 0 or 1.
func Sub64(x, y, borrow uint64) (diff, borrowOut uint64) { func Sub64(x, y, borrow uint64) (diff, borrowOut uint64) {
yb := y + borrow diff = x - y - borrow
diff = x - yb borrowOut = ((^x & y) | (^(x ^ y) & diff)) >> 63
if diff > x || yb < y {
borrowOut = 1
}
return return
} }
......
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment