13
0
livetrax/libs/pbd/pbd/int62.h
Paul Davis 4137271188 int62_t: use explicit memory order for all loads and stores
Also, improve implementation of CAS loops for operator <X>=.

Ideally, we ought to test this on ARM, both before and after.
2023-03-24 14:19:15 -06:00

228 lines
11 KiB
C++

/*
* Copyright (C) 2020 Paul Davis <paul@linuxaudiosystems.com>
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License along
* with this program; if not, write to the Free Software Foundation, Inc.,
* 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA.
*/
#ifndef __libpbd_int62_h__
#define __libpbd_int62_h__
#include <atomic>
#include <cstdint>
#include <cstdlib>
#include <iostream>
#include <exception>
#include <limits>
/* int62_t is a class the functions as a 62 bit signed integer complete with a flag that can be used to indicate a boolean property of
* the object. The flag is stored inside the 64 bit integer used by the object (as a single bit), and all operations on the object that
* change either the flag or the value are atomic.
*
* this was written to function as a base class for a timeline positional/distance type which needs to indicate whether it represents
* audio time or musical time.
*/
class alignas(16) int62_t {
protected:
/* std::atomic<> takes care of memory barriers for us; the actual load and stores
are atomic on architectures that we're likely to care about.
*/
std::atomic<int64_t> v;
/* this defines the bit used to indicate "flag" or not */
static const int64_t flagbit_mask = (1LL<<62);
protected:
/* the "flagbit" follows 2's complement logic. It is "set" if the value is positive and the bit is 1; it is also set if the
* value is negative and bit is 0.
*/
static int64_t int62 (int64_t v) { if (v >= 0) { return v & ~flagbit_mask; } return (v | flagbit_mask); }
static bool flagged (int64_t v) { if (v >= 0) { return v & flagbit_mask; } return ((v & flagbit_mask) == 0); }
public:
/* this is really a private method but is useful to construct the int64_t value when building tests. It is static anyway, so
providing public access doesn't hurt.
*/
static int64_t build (bool flag, int64_t v) { if (v >= 0) { return (flag ? flagbit_mask : 0) | v; } return flag ? (v & ~flagbit_mask) : v; }
int62_t () : v (0) {}
int62_t (bool bc, int64_t vc) : v (build (bc, vc)) {}
int62_t (int62_t const & other) { v.store (other.v.load(std::memory_order_acquire), std::memory_order_release); }
static const int64_t max = 4611686018427387903; /* 2^62 - 1 */
static const int64_t min = -2305843009213693952;
bool flagged() const { return flagged (v); }
int64_t val() const { return int62(v); }
int62_t& operator= (int64_t n) { v.store (build (flagged (v.load(std::memory_order_acquire)), n), std::memory_order_release); return *this; }
int62_t& operator= (int62_t const & other) { v.store (other.v.load(std::memory_order_acquire), std::memory_order_release); return *this; }
/* there's a pattern to many of these operators:
1) atomically load the current in64_t into "tmp". This value has
both the flag bit and the values bits of this int62_t.
2) constructor a new int62_t from
(a) is the flag bit set (using ::flagged (tmp))
(b) the result of applying the operator (plus arg) to the value
bits (obtained using ::int62 (tmp))
Note that we need to ensure that we're atomically determining both
the flag bit and values bit, hence the initial load into "tmp"
rather than two separate loads for each "part".
*/
int62_t operator- () const { const int64_t vv = v.load(std::memory_order_acquire); return int62_t (flagged (vv), -int62(vv)); }
int62_t operator+ (int64_t n) const { const int64_t vv = v.load(std::memory_order_acquire); return int62_t (flagged (vv), int62 (vv) + n); }
int62_t operator- (int64_t n) const { const int64_t vv = v.load(std::memory_order_acquire); return int62_t (flagged (vv), int62 (vv) - n); }
int62_t operator* (int64_t n) const { const int64_t vv = v.load(std::memory_order_acquire); return int62_t (flagged (vv), int62 (vv) * n); }
int62_t operator/ (int64_t n) const { const int64_t vv = v.load(std::memory_order_acquire); return int62_t (flagged (vv), int62 (vv) / n); }
int62_t operator% (int64_t n) const { const int64_t vv = v.load(std::memory_order_acquire); return int62_t (flagged (vv), int62 (vv) % n); }
int62_t operator+ (int62_t n) const { const int64_t vv = v.load(std::memory_order_acquire); return int62_t (flagged (vv), int62 (vv) + n.val()); }
int62_t operator- (int62_t n) const { const int64_t vv = v.load(std::memory_order_acquire); return int62_t (flagged (vv), int62 (vv) - n.val()); }
int62_t operator* (int62_t n) const { const int64_t vv = v.load(std::memory_order_acquire); return int62_t (flagged (vv), int62 (vv) * n.val()); }
int62_t operator/ (int62_t n) const { const int64_t vv = v.load(std::memory_order_acquire); return int62_t (flagged (vv), int62 (vv) / n.val()); }
int62_t operator% (int62_t n) const { const int64_t vv = v.load(std::memory_order_acquire); return int62_t (flagged (vv), int62 (vv) % n.val()); }
/* comparison operators .. will throw if the two objects have different
* flag settings (which is assumed to indicate that they differ in some
* important respect, and thus should not have their values compared)
*/
struct flag_mismatch : public std::exception {
flag_mismatch () {}
const char* what () const throw () { return "mismatched flags in int62_t"; }
};
bool operator< (int62_t const & other) const { if (flagged() != other.flagged()) throw flag_mismatch(); return val() < other.val(); }
bool operator<= (int62_t const & other) const { if (flagged() != other.flagged()) throw flag_mismatch(); return val() <= other.val(); }
bool operator> (int62_t const & other) const { if (flagged() != other.flagged()) throw flag_mismatch(); return val() > other.val(); }
bool operator>= (int62_t const & other) const { if (flagged() != other.flagged()) throw flag_mismatch(); return val() >= other.val(); }
/* don't throw flag_mismatch for explicit equality checks, since
* the semantics are well defined and the computation cost is trivial
*/
bool operator!= (int62_t const & other) const { const int64_t vv = v.load(std::memory_order_acquire); if (flagged (vv) != other.flagged()) return true; return int62 (vv) != other.val(); }
bool operator== (int62_t const & other) const { const int64_t vv = v.load(std::memory_order_acquire); if (flagged (vv) != other.flagged()) return false; return int62 (vv) == other.val(); }
explicit operator int64_t() const { return int62(v); }
bool operator< (int64_t n) const { return val() < n; }
bool operator<= (int64_t n) const { return val() <= n; }
bool operator> (int64_t n) const { return val() > n; }
bool operator>= (int64_t n) const { return val() >= n; }
bool operator!= (int64_t n) const { return val() != n; }
bool operator== (int64_t n) const { return val() == n; }
int62_t abs() const { const int64_t tmp = v; return int62_t (flagged(tmp), ::llabs(int62(tmp))); }
int62_t& operator+= (int64_t n) {
int64_t oldval = v.load (std::memory_order_acquire);
int64_t newval = build (flagged (oldval), int62 (oldval) + n);
while (!v.compare_exchange_weak (oldval, newval, std::memory_order_release, std::memory_order_relaxed)) {
newval = build (flagged (oldval), int62 (oldval) + n);
}
return *this;
}
int62_t& operator-= (int64_t n) {
int64_t oldval = v.load (std::memory_order_acquire);
int64_t newval = build (flagged (oldval), int62 (oldval) - n);
while (!v.compare_exchange_weak (oldval, newval, std::memory_order_release, std::memory_order_relaxed)) {
newval = build (flagged (oldval), int62 (oldval) - n);
}
return *this;
}
int62_t& operator*= (int64_t n) {
int64_t oldval = v.load (std::memory_order_acquire);
int64_t newval = build (flagged (oldval), int62 (oldval) * n);
while (!v.compare_exchange_weak (oldval, newval, std::memory_order_release, std::memory_order_relaxed)) {
newval = build (flagged (oldval), int62 (oldval) * n);
}
return *this;
}
int62_t& operator/= (int64_t n) {
int64_t oldval = v.load (std::memory_order_acquire);
int64_t newval = build (flagged (oldval), int62 (oldval) / n);
while (!v.compare_exchange_weak (oldval, newval, std::memory_order_release, std::memory_order_relaxed)) {
newval = build (flagged (oldval), int62 (oldval) / n);
}
return *this;
}
int62_t& operator%= (int64_t n) {
int64_t oldval = v.load (std::memory_order_acquire);
int64_t newval = build (flagged (oldval), int62 (oldval) % n);
while (!v.compare_exchange_weak (oldval, newval, std::memory_order_release, std::memory_order_relaxed)) {
newval = build (flagged (oldval), int62 (oldval) % n);
}
return *this;
}
int62_t& operator+= (int62_t n) {
int64_t oldval = v.load (std::memory_order_acquire);
int64_t newval = build (flagged (oldval), int62 (oldval) + n.val());
while (!v.compare_exchange_weak (oldval, newval, std::memory_order_release, std::memory_order_relaxed)) {
newval = build (flagged (oldval), int62 (oldval) + n.val());
}
return *this;
}
int62_t& operator-= (int62_t n) {
int64_t oldval = v.load (std::memory_order_acquire);
int64_t newval = build (flagged (oldval), int62 (oldval) - n.val());
while (!v.compare_exchange_weak (oldval, newval, std::memory_order_release, std::memory_order_relaxed)) {
newval = build (flagged (oldval), int62 (oldval) - n.val());
}
return *this;
}
int62_t& operator*= (int62_t n) {
int64_t oldval = v.load (std::memory_order_acquire);
int64_t newval = build (flagged (oldval), int62 (oldval) * n.val());
while (!v.compare_exchange_weak (oldval, newval, std::memory_order_release, std::memory_order_relaxed)) {
newval = build (flagged (oldval), int62 (oldval) * n.val());
}
return *this;
}
int62_t& operator/= (int62_t n) {
int64_t oldval = v.load (std::memory_order_acquire);
int64_t newval = build (flagged (oldval), int62 (oldval) / n.val());
while (!v.compare_exchange_weak (oldval, newval, std::memory_order_release, std::memory_order_relaxed)) {
newval = build (flagged (oldval), int62 (oldval) / n.val());
}
return *this;
}
int62_t& operator%= (int62_t n) {
int64_t oldval = v.load (std::memory_order_acquire);
int64_t newval = build (flagged (oldval), int62 (oldval) % n.val());
while (!v.compare_exchange_weak (oldval, newval, std::memory_order_release, std::memory_order_relaxed)) {
newval = build (flagged (oldval), int62 (oldval) % n.val());
}
return *this;
}
};
namespace std {
template<>
struct numeric_limits<int62_t> {
static int62_t min() { return int62_t (false, -2305843009213693952); }
static int62_t max() { return int62_t (false, 4611686018427387904); }
};
}
#endif /* __libpbd_int62_h__ */