// vim: ts=4 sw=4 expandtab ft=c

// Copyright (C) 2017-2018 The Mercury team.
// This file is distributed under the terms specified in COPYING.LIB.

// mercury_int.h - integer type handling

#ifndef MERCURY_INT_H
#define MERCURY_INT_H

#include "mercury_conf.h"       // for MR_BOXED_INT64S, MR_CONSERVATIVE_GC
#include "mercury_types.h"      // for `MR_Word'
#include "mercury_std.h"        // for `MR_bool'
#include <stdint.h>

#define MR_INT64_WORDS          ((sizeof(int64_t) + sizeof(MR_Word) - 1)     \
                                        / sizeof(MR_Word))
#define MR_UINT64_WORDS         ((sizeof(uint64_t) + sizeof(MR_Word) - 1)    \
                                        / sizeof(MR_Word))

// See the comment on the definitions of MR_Float_Aligned in mercury_float.h.
//
// Any changes here will probably also need to be made for floats
// in mercury_float.h.

#if defined(MR_GNUC) || defined(MR_CLANG)
  typedef int64_t   MR_Int64Aligned  __attribute__((aligned(sizeof(MR_Word))));
  typedef uint64_t  MR_Uint64Aligned __attribute__((aligned(sizeof(MR_Word))));
#elif defined(MR_MSVC)
  typedef __declspec(align(MR_BYTES_PER_WORD)) int64_t  MR_Int64Aligned;
  typedef __declspec(align(MR_BYTES_PER_WORD)) uint64_t MR_Uint64Aligned;
#else
  typedef int64_t   MR_Int64Aligned;
  typedef uint64_t  MR_Uint64Aligned;
#endif

#if defined(MR_BOXED_INT64S)

  #define MR_word_to_int64(w)   (* (int64_t *) (w))
  #define MR_word_to_uint64(w)  (* (uint64_t *) (w))

  #if defined(MR_CONSERVATIVE_GC)
    #define MR_make_hp_int64_aligned()  ((void) 0)
    #define MR_make_hp_uint64_aligned() ((void) 0)

    #define MR_int64_to_word(i)                                              \
      (                                                                      \
        MR_hp_alloc_atomic_msg(MR_INT64_WORDS, MR_ALLOC_SITE_INT64, NULL),   \
        * (int64_t *) (void *) (MR_hp - MR_INT64_WORDS) = (i),               \
        /* return */ (MR_Word) (MR_hp - MR_INT64_WORDS)                      \
      )
    #define MR_uint64_to_word(u)                                             \
      (                                                                      \
        MR_hp_alloc_atomic_msg(MR_UINT64_WORDS, MR_ALLOC_SITE_UINT64, NULL), \
        * (uint64_t *) (void *) (MR_hp - MR_UINT64_WORDS) = (u),             \
        /* return */ (MR_Word) (MR_hp - MR_UINT64_WORDS)                     \
      )
  #else // ! defined(MR_CONSERVATIVE_GC)
    // We need to ensure that what we allocated on the heap is properly
    // aligned for a 64-bit integer value, by rounding MR_hp up to the
    // nearest int64-aligned boundary.
    #define MR_make_hp_int64_aligned()                                       \
      ( ((MR_Word) MR_hp & (sizeof(int64_t) - 1)) ?                          \
            MR_hp_alloc_atomic_msg(1, MR_ALLOC_SITE_INT64, NULL)             \
      :                                                                      \
            (void) 0                                                         \
      )
    #define MR_make_hp_uint64_aligned()                                      \
      ( ((MR_Word) MR_hp & (sizeof(uint64_t) - 1)) ?                         \
            MR_hp_alloc_atomic_msg(1, MR_ALLOC_SITE_UINT64, NULL)            \
      :                                                                      \
            (void) 0                                                         \
      )

    #define MR_int64_to_word(i)                                              \
      (                                                                      \
        MR_make_hp_int64_aligned(),                                          \
        MR_hp_alloc_atomic_msg(MR_INT64_WORDS, MR_ALLOC_SITE_INT64, NULL),   \
        * (int64_t *) (void *) (MR_hp - MR_INT64_WORDS) = (i),               \
        /* return */ (MR_Word) (MR_hp - MR_INT64_WORDS)                      \
      )
    #define MR_uint64_to_word(u)                                             \
      (                                                                      \
        MR_make_hp_uint64_aligned(),                                         \
        MR_hp_alloc_atomic_msg(MR_UINT64_WORDS, MR_ALLOC_SITE_UINT64, NULL), \
        * (uint64_t *) (void *) (MR_hp - MR_UINT64_WORDS) = (u),             \
        /* return  */ (MR_Word) (MR_hp - MR_UINT64_WORDS)                    \
      )
  #endif // MR_CONSERVATIVE_GC

  #ifdef MR_GNUC
    #define MR_int64_const(i)  ({ static const int64_t  d = i; (MR_Word) &d; })
    #define MR_uint64_const(u) ({ static const uint64_t d = u; (MR_Word) &d; })
  #else
    #define MR_int64_const(i)  MR_int64_to_word(i)    // inefficient
    #define MR_uint64_const(u) MR_uint64_to_word(u)   // inefficient
  #endif

  union MR_Int64Dword {
        int64_t     i64;
        MR_Word     i64w[2];
  };
  union MR_Uint64Dword {
        uint64_t    ui64;
        MR_Word     ui64w[2];
  };

  #if defined(MR_GNUC) || defined(MR_CLANG)
    #define MR_dword_int64_get_word0(i64)                                    \
        (((union MR_Int64Dword) (int64_t) (i64)).i64w[0])
    #define MR_dword_int64_get_word1(i64)                                    \
        (((union MR_Int64Dword) (int64_t) (i64)).i64w[1])
    #define MR_dword_uint64_get_word0(ui64)                                  \
        (((union MR_Uint64Dword) (uint64_t) (ui64)).ui64w[0])
    #define MR_dword_uint64_get_word1(ui64)                                  \
        (((union MR_Uint64Dword) (uint64_t) (ui64)).ui64w[1])
  #else // ! (defined(MR_GNUC) || defined(MR_CLANG))
    MR_EXTERN_INLINE MR_Word    MR_dword_int64_get_word0(int64_t i64);
    MR_EXTERN_INLINE MR_Word    MR_dword_int64_get_word1(int64_t i64);
    MR_EXTERN_INLINE MR_Word    MR_dword_uint64_get_word0(uint64_t ui64);
    MR_EXTERN_INLINE MR_Word    MR_dword_uint64_get_word1(uint64_t ui64);

    MR_EXTERN_INLINE MR_Word
    MR_dword_int64_get_word0(int64_t i64)
    {
        union MR_Int64Dword __i64dw;
        __i64dw.i64 = i64;
        return __i64dw.i64w[0];
    }
    MR_EXTERN_INLINE MR_Word
    MR_dword_int64_get_word1(int64_t i64)
    {
        union MR_Int64Dword __i64dw;
        __i64dw.i64 = i64;
        return __i64dw.i64w[1];
    }

    MR_EXTERN_INLINE MR_Word
    MR_dword_uint64_get_word0(uint64_t ui64)
    {
        union MR_Uint64Dword __ui64dw;
        __ui64dw.ui64 = ui64;
        return __ui64dw.ui64w[0];
    }
    MR_EXTERN_INLINE MR_Word
    MR_dword_uint64_get_word1(uint64_t ui64)
    {
        union MR_Uint64Dword __ui64dw;
        __ui64dw.ui64 = ui64;
        return __ui64dw.ui64w[1];
    }
  #endif // defined(MR_GNUC) || defined(MR_CLANG)

  #define MR_int64_from_dword_ptr(ptr)                                       \
        (((union MR_Int64Dword *) (ptr))->i64)
  #define MR_uint64_from_dword_ptr(ptr)                                      \
        (((union MR_Uint64Dword *) (ptr))->ui64)

  #if defined(MR_GNUC) || defined(MR_CLANG)
    #define MR_int64_from_dword(w0, w1)                                      \
      ({                                                                     \
        union MR_Int64Dword __i64dw;                                         \
        __i64dw.i64w[0] = (MR_Word) (w0);                                    \
        __i64dw.i64w[1] = (MR_Word) (w1);                                    \
        __i64dw.i64;                                                         \
      })
    #define MR_uint64_from_dword(w0, w1)                                     \
      ({                                                                     \
        union MR_Uint64Dword __ui64dw;                                       \
        __ui64dw.ui64w[0] = (MR_Word) (w0);                                  \
        __ui64dw.ui64w[1] = (MR_Word) (w1);                                  \
        __ui64dw.ui64;                                                       \
      })
  #else // ! (defined(MR_GNUC) || defined(MR_CLANG))
    #define MR_int64_from_dword(w0, w1)                                      \
        MR_int64_from_dword_func((MR_Word) (w0), (MR_Word) (w1))
    #define MR_uint64_from_dword(w0, w1)                                     \
        MR_uint64_from_dword_func((MR_Word) (w0), (MR_Word) (w1))

    MR_EXTERN_INLINE int64_t
    MR_int64_from_dword_func(MR_Word w0, MR_Word w1);
    MR_EXTERN_INLINE uint64_t
    MR_uint64_from_dword_func(MR_Word w0, MR_Word w1);

    MR_EXTERN_INLINE int64_t
    MR_int64_from_dword_func(MR_Word w0, MR_Word w1)
    {
        union MR_Int64Dword __i64dw;
        __i64dw.i64w[0] = (MR_Word) (w0);
        __i64dw.i64w[1] = (MR_Word) (w1);
        return __i64dw.i64;
    }

    MR_EXTERN_INLINE uint64_t
    MR_uint64_from_dword_func(MR_Word w0, MR_Word w1)
    {
        union MR_Uint64Dword __ui64dw;
        __ui64dw.ui64w[0] = (MR_Word) (w0);
        __ui64dw.ui64w[1] = (MR_Word) (w1);
        return __ui64dw.ui64;
    }
  #endif // defined(MR_GNUC) || defined(MR_CLANG)

#else // ! MR_BOXED_INT64S

  // Unboxed int64s means we can assume sizeof(int64_t) <= sizeof(MR_Word)
  // and sizeof(uint64_t) <= sizeof(MR_Word).

  #define MR_make_hp_int64_aligned()  ((void) 0)
  #define MR_make_hp_uint64_aligned() ((void) 0)

  #define MR_int64_to_word(i)  ((MR_Word) (i))
  #define MR_uint64_to_word(u) ((MR_Word) (u))

  #define MR_word_to_int64(w)  ((int64_t) (w))
  #define MR_word_to_uint64(w) ((uint64_t) (w))

#endif // not MR_BOXED_INT64S

extern MR_Integer MR_hash_int64(int64_t);
extern MR_Integer MR_hash_uint64(uint64_t);

#if defined(MR_HAVE_BUILTIN_BSWAP16)
  #define MR_uint16_reverse_bytes(U) __builtin_bswap16((U))
#elif defined(MR_MSVC)
  #define MR_uint16_reverse_bytes(U) _byteswap_ushort((U))
#else
  #define MR_uint16_reverse_bytes(U) (((U & 0xff00) >> 8) | \
                                      ((U & 0x00ff) << 8))
#endif

#if defined(MR_HAVE_BUILTIN_BSWAP32)
  #define MR_uint32_reverse_bytes(U) __builtin_bswap32((U))
#elif defined(MR_MSVC)
  #define MR_uint32_reverse_bytes(U) _byteswap_ulong((U))
#else
  #define MR_uint32_reverse_bytes(U) ((U & UINT32_C(0x000000ff)) << 24 | \
                                      (U & UINT32_C(0x0000ff00)) << 8  | \
                                      (U & UINT32_C(0x00ff0000)) >> 8  | \
                                      (U & UINT32_C(0xff000000)) >> 24 )
#endif

#if defined(MR_HAVE_BUILTIN_BSWAP64)
  #define MR_uint64_reverse_bytes(U) __builtin_bswap64((U))
#elif defined(MR_MSVC)
  #define MR_uint64_reverse_bytes(U) _byteswap_uint64((U))
#else
  #define MR_uint64_reverse_bytes(U) \
        ((U & UINT64_C(0x00000000000000ff)) << 56  | \
         (U & UINT64_C(0x000000000000ff00)) << 40  | \
         (U & UINT64_C(0x0000000000ff0000)) << 24  | \
         (U & UINT64_C(0x00000000ff000000)) << 8   | \
         (U & UINT64_C(0x000000ff00000000)) >> 8   | \
         (U & UINT64_C(0x0000ff0000000000)) >> 24  | \
         (U & UINT64_C(0x00ff000000000000)) >> 40  | \
         (U & UINT64_C(0xff00000000000000)) >> 56)
#endif

#endif // not MERCURY_INT_H
