From dbe503776d1833b9d1c9a6968fcccefa123b4604 Mon Sep 17 00:00:00 2001 From: Christopher Haster Date: Wed, 1 May 2024 11:04:17 -0500 Subject: [PATCH] Added lfs_parity intrinsic We're using parity a lot more than popc now (actually, now that we don't use CTZ skip-lists, do we use popc at all?), so it makes sense to the compiler's __builtin_parity intrinsic when possible. On some processors parity can be much cheaper than popc. Notably, the 8080 family just includes a parity flag in the set of carry flags that are implicitly updated on most ALU operations. Though I think this approach didn't scale, you don't really see parity flags on most >8-bit architectures... Unfortunately, ARM thumb, our test arch, does not have a popc or parity instruction. I guess because thanks to implicit shifts in most instructions, the tree-reduction solution is surprisingly cheap: ea80 4010 eor.w r0, r0, r0, lsr #16 ea80 2010 eor.w r0, r0, r0, lsr #8 ea80 1010 eor.w r0, r0, r0, lsr #4 ea80 00c0 eor.w r0, r0, r0, lsr #2 ea80 0050 eor.w r0, r0, r0, lsr #1 f000 0001 and.w r0, r0, #1 Both popc and parity benefit from this (GCC 11): code __popcountsi2: 40 __paritysi2: 32 (-20.0%) So, thumb is not an arch where we see much benefit: code stack before: 33908 2824 after: 33924 (+0.0%) 2824 (+0.0%) Not really sure where the +16 bytes come from, we removed several masks, so I guess it's just bool vs in compiler noise? Still, this may be useful for other archs with parity instructions/ hardware. --- lfs.c | 15 +++++++-------- lfs_util.h | 9 +++++++++ 2 files changed, 16 insertions(+), 8 deletions(-) diff --git a/lfs.c b/lfs.c index e9399407..71c3f219 100644 --- a/lfs.c +++ b/lfs.c @@ -2223,7 +2223,7 @@ static int lfsr_rbyd_fetch(lfs_t *lfs, lfsr_rbyd_t *rbyd, // temporary state until we validate a cksum uint32_t cksum_ = cksum; - bool parity_ = lfs_popc(cksum) & 1; + bool parity_ = lfs_parity(cksum); lfs_size_t off = sizeof(uint32_t); lfs_size_t trunk_ = 0; lfs_size_t trunk__ = 0; @@ -2255,7 +2255,7 @@ static int lfsr_rbyd_fetch(lfs_t *lfs, lfsr_rbyd_t *rbyd, break; } tag &= 0x7fff; - parity_ ^= lfs_popc(cksum_ ^ cksum__) & 1; + parity_ ^= lfs_parity(cksum_ ^ cksum__); cksum_ = cksum__; // tag goes out of range? @@ -2277,7 +2277,7 @@ static int lfsr_rbyd_fetch(lfs_t *lfs, lfsr_rbyd_t *rbyd, } return err; } - parity_ ^= lfs_popc(cksum_ ^ cksum__) & 1; + parity_ ^= lfs_parity(cksum_ ^ cksum__); cksum_ = cksum__; // found an ecksum? save for later @@ -2631,7 +2631,7 @@ static int lfsr_rbyd_appendrev(lfs_t *lfs, lfsr_rbyd_t *rbyd, uint32_t rev) { // update eoff, xor cksum parity rbyd->eoff - += ((lfs_popc(rbyd->cksum ^ cksum_) & 1) + += ((lfs_size_t)lfs_parity(rbyd->cksum ^ cksum_) << (8*sizeof(lfs_size_t)-1)) + sizeof(uint32_t); rbyd->cksum = cksum_; @@ -2655,7 +2655,7 @@ static int lfsr_rbyd_appendtag(lfs_t *lfs, lfsr_rbyd_t *rbyd, // update eoff, xor cksum parity rbyd->eoff - += ((lfs_popc(rbyd->cksum ^ cksum_) & 1) + += ((lfs_size_t)lfs_parity(rbyd->cksum ^ cksum_) << (8*sizeof(lfs_size_t)-1)) + d; rbyd->cksum = cksum_; @@ -2674,7 +2674,7 @@ static int lfsr_rbyd_appenddata(lfs_t *lfs, lfsr_rbyd_t *rbyd, // update eoff, xor cksum parity rbyd->eoff - += ((lfs_popc(rbyd->cksum ^ cksum_) & 1) + += ((lfs_size_t)lfs_parity(rbyd->cksum ^ cksum_) << (8*sizeof(lfs_size_t)-1)) + lfsr_data_size(data); rbyd->cksum = cksum_; @@ -3506,8 +3506,7 @@ static int lfsr_rbyd_appendcksum(lfs_t *lfs, lfsr_rbyd_t *rbyd) { // xor in the tag parity cksum_buf[0] ^= (uint8_t)lfsr_rbyd_parity(rbyd) << 7; // find the new parity - bool parity_ = lfsr_rbyd_parity(rbyd) - ^ (lfs_popc(rbyd->cksum ^ cksum_) & 1); + bool parity_ = lfsr_rbyd_parity(rbyd) ^ lfs_parity(rbyd->cksum ^ cksum_); // and intentionally perturb the commit so the next tag appears invalid if ((e >> 7) == parity_) { cksum_buf[1] ^= 0x01; diff --git a/lfs_util.h b/lfs_util.h index 13fa6d01..8dc801e2 100644 --- a/lfs_util.h +++ b/lfs_util.h @@ -274,6 +274,15 @@ static inline uint32_t lfs_popc(uint32_t a) { #endif } +// Returns true if there is an odd number of binary ones in a +static inline bool lfs_parity(uint32_t a) { +#if !defined(LFS_NO_INTRINSICS) && (defined(__GNUC__) || defined(__CC_ARM)) + return __builtin_parity(a); +#else + return lfs_popc(a) & 1; +#endif +} + // Find the sequence comparison of a and b, this is the distance // between a and b ignoring overflow static inline int lfs_scmp(uint32_t a, uint32_t b) {