//===-- common.h ------------------------------------------------*- C++ -*-===//
//
// Part of the LLVM Project, under the Apache License v2.0 with LLVM Exceptions.
// See https://llvm.org/LICENSE.txt for license information.
// SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
//
//===----------------------------------------------------------------------===//

#ifndef SCUDO_COMMON_H_
#define SCUDO_COMMON_H_

#include "internal_defs.h"

#include "fuchsia.h"
#include "linux.h"
#include "trusty.h"

#include <stddef.h>
#include <string.h>
#include <unistd.h>

namespace scudo {

template <class Dest, class Source> inline Dest bit_cast(const Source &S) {
  static_assert(sizeof(Dest) == sizeof(Source), "");
  Dest D;
  memcpy(&D, &S, sizeof(D));
  return D;
}

inline constexpr bool isPowerOfTwo(uptr X) {
  if (X == 0)
    return false;
  return (X & (X - 1)) == 0;
}

inline constexpr uptr roundUp(uptr X, uptr Boundary) {
  DCHECK(isPowerOfTwo(Boundary));
  return (X + Boundary - 1) & ~(Boundary - 1);
}
inline constexpr uptr roundUpSlow(uptr X, uptr Boundary) {
  return ((X + Boundary - 1) / Boundary) * Boundary;
}

inline constexpr uptr roundDown(uptr X, uptr Boundary) {
  DCHECK(isPowerOfTwo(Boundary));
  return X & ~(Boundary - 1);
}
inline constexpr uptr roundDownSlow(uptr X, uptr Boundary) {
  return (X / Boundary) * Boundary;
}

inline constexpr bool isAligned(uptr X, uptr Alignment) {
  DCHECK(isPowerOfTwo(Alignment));
  return (X & (Alignment - 1)) == 0;
}
inline constexpr bool isAlignedSlow(uptr X, uptr Alignment) {
  return X % Alignment == 0;
}

template <class T> constexpr T Min(T A, T B) { return A < B ? A : B; }

template <class T> constexpr T Max(T A, T B) { return A > B ? A : B; }

template <class T> void Swap(T &A, T &B) {
  T Tmp = A;
  A = B;
  B = Tmp;
}

inline uptr getMostSignificantSetBitIndex(uptr X) {
  DCHECK_NE(X, 0U);
  return SCUDO_WORDSIZE - 1U - static_cast<uptr>(__builtin_clzl(X));
}

inline uptr roundUpPowerOfTwo(uptr Size) {
  DCHECK(Size);
  if (isPowerOfTwo(Size))
    return Size;
  const uptr Up = getMostSignificantSetBitIndex(Size);
  DCHECK_LT(Size, (1UL << (Up + 1)));
  DCHECK_GT(Size, (1UL << Up));
  return 1UL << (Up + 1);
}

inline uptr getLeastSignificantSetBitIndex(uptr X) {
  DCHECK_NE(X, 0U);
  return static_cast<uptr>(__builtin_ctzl(X));
}

inline uptr getLog2(uptr X) {
  DCHECK(isPowerOfTwo(X));
  return getLeastSignificantSetBitIndex(X);
}

inline u32 getRandomU32(u32 *State) {
  // ANSI C linear congruential PRNG (16-bit output).
  // return (*State = *State * 1103515245 + 12345) >> 16;
  // XorShift (32-bit output).
  *State ^= *State << 13;
  *State ^= *State >> 17;
  *State ^= *State << 5;
  return *State;
}

inline u32 getRandomModN(u32 *State, u32 N) {
  return getRandomU32(State) % N; // [0, N)
}

template <typename T> inline void shuffle(T *A, u32 N, u32 *RandState) {
  if (N <= 1)
    return;
  u32 State = *RandState;
  for (u32 I = N - 1; I > 0; I--)
    Swap(A[I], A[getRandomModN(&State, I + 1)]);
  *RandState = State;
}

inline void computePercentage(uptr Numerator, uptr Denominator, uptr *Integral,
                              uptr *Fractional) {
  constexpr uptr Digits = 100;
  if (Denominator == 0) {
    *Integral = 100;
    *Fractional = 0;
    return;
  }

  *Integral = Numerator * Digits / Denominator;
  *Fractional =
      (((Numerator * Digits) % Denominator) * Digits + Denominator / 2) /
      Denominator;
}

// Platform specific functions.

#if defined(SCUDO_PAGE_SIZE)

inline constexpr uptr getPageSizeCached() { return SCUDO_PAGE_SIZE; }

inline constexpr uptr getPageSizeSlow() { return getPageSizeCached(); }

inline constexpr uptr getPageSizeLogCached() {
  return static_cast<uptr>(__builtin_ctzl(SCUDO_PAGE_SIZE));
}

#else

extern uptr PageSizeCached;
extern uptr PageSizeLogCached;

uptr getPageSizeSlow();

inline uptr getPageSizeCached() {
  if (LIKELY(PageSizeCached))
    return PageSizeCached;
  return getPageSizeSlow();
}

inline uptr getPageSizeLogCached() {
  if (LIKELY(PageSizeLogCached))
    return PageSizeLogCached;
  // PageSizeLogCached and PageSizeCached are both set in getPageSizeSlow()
  getPageSizeSlow();
  DCHECK_NE(PageSizeLogCached, 0);
  return PageSizeLogCached;
}

#endif

// Returns 0 if the number of CPUs could not be determined.
u32 getNumberOfCPUs();

const char *getEnv(const char *Name);

u64 getMonotonicTime();
// Gets the time faster but with less accuracy. Can call getMonotonicTime
// if no fast version is available.
u64 getMonotonicTimeFast();

u32 getThreadID();

// Our randomness gathering function is limited to 256 bytes to ensure we get
// as many bytes as requested, and avoid interruptions (on Linux).
constexpr uptr MaxRandomLength = 256U;
bool getRandom(void *Buffer, uptr Length, bool Blocking = false);

// Platform memory mapping functions.

#define MAP_ALLOWNOMEM (1U << 0)
#define MAP_NOACCESS (1U << 1)
#define MAP_RESIZABLE (1U << 2)
#define MAP_MEMTAG (1U << 3)
#define MAP_PRECOMMIT (1U << 4)

// Our platform memory mapping use is restricted to 3 scenarios:
// - reserve memory at a random address (MAP_NOACCESS);
// - commit memory in a previously reserved space;
// - commit memory at a random address.
// As such, only a subset of parameters combinations is valid, which is checked
// by the function implementation. The Data parameter allows to pass opaque
// platform specific data to the function.
// Returns nullptr on error or dies if MAP_ALLOWNOMEM is not specified.
void *map(void *Addr, uptr Size, const char *Name, uptr Flags = 0,
          MapPlatformData *Data = nullptr);

// Indicates that we are getting rid of the whole mapping, which might have
// further consequences on Data, depending on the platform.
#define UNMAP_ALL (1U << 0)

void unmap(void *Addr, uptr Size, uptr Flags = 0,
           MapPlatformData *Data = nullptr);

void setMemoryPermission(uptr Addr, uptr Size, uptr Flags,
                         MapPlatformData *Data = nullptr);

void releasePagesToOS(uptr BaseAddress, uptr Offset, uptr Size,
                      MapPlatformData *Data = nullptr);

// Logging related functions.

void setAbortMessage(const char *Message);

struct BlockInfo {
  uptr BlockBegin;
  uptr BlockSize;
  uptr RegionBegin;
  uptr RegionEnd;
};

enum class Option : u8 {
  ReleaseInterval,      // Release to OS interval in milliseconds.
  MemtagTuning,         // Whether to tune tagging for UAF or overflow.
  ThreadDisableMemInit, // Whether to disable automatic heap initialization and,
                        // where possible, memory tagging, on this thread.
  MaxCacheEntriesCount, // Maximum number of blocks that can be cached.
  MaxCacheEntrySize,    // Maximum size of a block that can be cached.
  MaxTSDsCount,         // Number of usable TSDs for the shared registry.
};

enum class ReleaseToOS : u8 {
  Normal, // Follow the normal rules for releasing pages to the OS
  Force,  // Force release pages to the OS, but avoid cases that take too long.
  ForceAll, // Force release every page possible regardless of how long it will
            // take.
};

constexpr unsigned char PatternFillByte = 0xAB;

enum FillContentsMode {
  NoFill = 0,
  ZeroFill = 1,
  PatternOrZeroFill = 2 // Pattern fill unless the memory is known to be
                        // zero-initialized already.
};

} // namespace scudo

#endif // SCUDO_COMMON_H_
