/*=========================================================================
 * Copyright (C) GemTalk Systems 2023-2026.  All Rights Reserved.
 *
 *   bit utilities header file
 *
 *========================================================================
 */

extern void gs_checkBitUtils(void);

/* Return whether the given (un)signed 64 bit integer is an (un)signed
 * integer of the given bit width.
 */
extern int sisks(int64_t, uint32_t);
extern int sisku(int64_t, uint32_t);
extern int uisks(uint64_t, uint32_t);
extern int uisku(uint64_t, uint32_t);

#define i64IsSigned(x, k) sisks(x, k)
#define i64IsUnsigned(x, k) sisku(x, k)
#define u64IsSigned(x, k) uisks(x, k)
#define u64IsUnsigned(x, k) uisku(x, k)

#define i64IsObjSize(x) sisku(x, OOP_NUM_MAX_BITS)
#define i64IsPositiveSmallInteger(x) sisku(x, 64 - OOP_NUM_TAG_BITS - 1)
#define u64IsPositiveSmallInteger(x) uisku(x, 64 - OOP_NUM_TAG_BITS - 1)

/* Return the index of the least significant bit set in the argument, starting
 * at index zero, or -1 if none.
 */
extern int _gs_ffs(uint32_t);
extern int _gs_ffs8(uint64_t);

/* Return the index of the most significant bit set in the argument, starting
 * at index zero, or 0 if none.
 */
extern int _gs_msb(uint32_t);
extern int _gs_msb8(uint64_t);

/* Return the number of bits set in the argument, or 0 if none */
extern int _gs_popcnt8(uint64_t);

/* Swap endianness, casting arguments to the restricted size type first */
extern uint16_t _gs_bswap2(uint16_t);
extern uint32_t _gs_bswap4(uint32_t);
extern uint64_t _gs_bswap8(uint64_t);

/* Replace with builtins if available */
#if defined(FLG_LINUX_UNIX)

/* Use GCC's builtins */
#define GS_BSWAP_BUILTIN
#define gs_bswap2(x) __builtin_bswap16((uint16_t)(x))
#define gs_bswap4(x) __builtin_bswap32((uint32_t)(x))
#define gs_bswap8(x) __builtin_bswap64((uint64_t)(x))


#if defined(FLG_INTEL_LINUX)
/* Sadly, gcc's x86 builtins are problematic here for a multitude of reasons.
 *
 *   1.  Intel updated its manual entries for bsf and bsr between 2022 and 2026
 *   to define what was otherwise undefined behavior of those instructions when
 *   given a zero source operand.  Note the manual states "some [unspecified]
 *   older processors" may clear out the upper 32 bits of a 64 bit register when
 *   bsf and bsr are given the corresponding 32 bit register as a destination
 *   even if the instructions should leave the destination register unmodified.
 *
 *   2.  Given this, gcc has been rightfully conservative on how it implements
 *   the corresponding builtins, so in older versions of gcc they are not as
 *   fast as possible.  Newer versions of gcc, notably gcc 14 and above, still
 *   do not generate the most efficient code for the corresponding builtins,
 *   including __builtin_ctzg() and __builtin_clzg().
 *
 *   3.  The bsf and bsr instructions behave as cmov, and thus are susceptible
 *   to stalls due to dependency chains on their destination register.  The
 *   interaction of this dependency with compiler optimization choices such as
 *   register allocation and loop unrolling greatly obfuscate performance
 *   measurements.
 *
 *   4.  In part because of this complexity, there is the tzcnt instruction one
 *   could use, but that requires -mbmi.  Also, for this particular use, bsf
 *   is a better choice because tzcnt will require a separate cmov instruction.
 */

extern int _gs_asm_ffs(uint32_t);
extern int _gs_asm_ffs8(uint64_t);

#define GS_FFS_BUILTIN
#define gs_ffs(x) _gs_asm_ffs(x)
#define gs_ffs8(x) _gs_asm_ffs8(x)

extern int _gs_asm_msb(uint32_t);
extern int _gs_asm_msb8(uint64_t);

#define GS_MSBIT_BUILTIN
#define gs_msb(x) _gs_asm_msb(x)
#define gs_msb8(x) _gs_asm_msb8(x)

#else

#define GS_FFS_BUILTIN
/* With gcc 14 or newer, this might be improved with __builtin_ctzg() */
#define gs_ffs(x) (__builtin_ffs(x) - 1)
#define gs_ffs8(x) (__builtin_ffsll(x) - 1)

/* With gcc 14 or newer, this might be improved with __builtin_clzg() */
#define GS_MSBIT_BUILTIN
#define gs_msb(x) ((x) == 0 ? 0 : 31 - __builtin_clz(x))
#define gs_msb8(x) ((x) == 0 ? 0 : 63 - __builtin_clzll(x))

#endif


#define GS_POPCNT_BUILTIN
#define gs_popcnt(x) __builtin_popcount(x)
#define gs_popcnt8(x) __builtin_popcountll(x)

#elif defined(FLG_AIX_UNIX)

/* Use XLC's builtins, but not for popcnt or bswap */
#define GS_FFS_BUILTIN
#define gs_ffs(x) ((x) == 0 ? -1 : __cnttz4(x))
#define gs_ffs8(x) ((x) == 0 ? -1LL : __cnttz8(x))

#define GS_MSBIT_BUILTIN
#define gs_msb(x) ((x) == 0 ? 0 : 31 - __cntlz4(x))
#define gs_msb8(x) ((x) == 0 ? 0 : 63 - __cntlz8(x))

#define gs_popcnt(x) _gs_popcnt8(x)
#define gs_popcnt8(x) _gs_popcnt8(x)
#define gs_bswap2(x) _gs_bswap2((uint16_t)(x))
#define gs_bswap4(x) _gs_bswap4((uint32_t)(x))
#define gs_bswap8(x) _gs_bswap8((uint64_t)(x))

#else

/* Use our functions */
#define gs_ffs(x) _gs_ffs(x)
#define gs_ffs8(x) _gs_ffs8(x)
#define gs_msb(x) _gs_msb(x)
#define gs_msb8(x) _gs_msb8(x)
#define gs_popcnt(x) _gs_popcnt8(x)
#define gs_popcnt8(x) _gs_popcnt8(x)
#define gs_bswap2(x) _gs_bswap2((uint16_t)(x))
#define gs_bswap4(x) _gs_bswap4((uint32_t)(x))
#define gs_bswap8(x) _gs_bswap8((uint64_t)(x))

#endif

/* For the sake of convenience using bswap */
#define gs_bswap4i(x) (int32_t)gs_bswap4(x)

#ifdef FLG_LITTLE_ENDIAN
#define gs_bswap2_ifle(x) gs_bswap2(x)
#define gs_bswap4_ifle(x) gs_bswap4(x)
#define gs_bswap8_ifle(x) gs_bswap8(x)
#else
#define gs_bswap2_ifle(x) (x)
#define gs_bswap4_ifle(x) (x)
#define gs_bswap8_ifle(x) (x)
#endif

