/* Copyright (C) 2012-2023 Free Software Foundation, Inc.
   Contributed by Richard Henderson <rth@redhat.com>.

   This file is part of the GNU Atomic Library (libatomic).

   Libatomic 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 3, or (at your option)
   any later version.

   Libatomic 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.

   Under Section 7 of GPL version 3, you are granted additional
   permissions described in the GCC Runtime Library Exception, version
   3.1, as published by the Free Software Foundation.

   You should have received a copy of the GNU General Public License and
   a copy of the GCC Runtime Library Exception along with this program;
   see the files COPYING3 and COPYING.RUNTIME respectively.  If not, see
   <http://www.gnu.org/licenses/>.  */

/* This file contains data types and function declarations that are
   private to the implementation of libatomic.  */

#ifndef LIBATOMIC_H
#define LIBATOMIC_H 1

#include "auto-config.h"
#include <stdbool.h>
#include <stdint.h>
#include <stddef.h>
#include <limits.h>
#include <string.h>


/* Symbol concatenation macros.  */
#define C2_(X,Y)	X ## Y
#define C2(X,Y)		C2_(X,Y)
#define C3_(X,Y,Z)	X ## Y ## Z
#define C3(X,Y,Z)	C3_(X,Y,Z)
#define C4_(W,X,Y,Z)	W ## X ## Y ## Z
#define C4(W,X,Y,Z)	C4_(W,X,Y,Z)

/* Stringification macros.  */
#define S2(X)		#X
#define S(X)		S2(X)

/* All of the primitive types on which we operate.  */
typedef unsigned U_1 __attribute__((mode(QI)));
#if HAVE_INT2
typedef unsigned U_2 __attribute__((mode(HI)));
#endif
#if HAVE_INT4
typedef unsigned U_4 __attribute__((mode(SI)));
#endif
#if HAVE_INT8
typedef unsigned U_8 __attribute__((mode(DI)));
#endif
#if HAVE_INT16
typedef unsigned U_16 __attribute__((mode(TI)));
#endif

/* The widest type that we support.  */
#if HAVE_INT16
# define MAX_SIZE	16
#elif HAVE_INT8
# define MAX_SIZE	8
#elif HAVE_INT4
# define MAX_SIZE	4
#elif HAVE_INT2
# define MAX_SIZE	2
#else
# define MAX_SIZE	1
#endif
typedef C2(U_,MAX_SIZE) U_MAX;

/* Provide dummy fallback types so that stuff is syntactically correct
   without having to overdo the ifdefs.  The code using these should
   always be protected with the HAVE_INT{n} macros.  */
#if !HAVE_INT2
typedef U_MAX U_2;
#endif
#if !HAVE_INT4
typedef U_MAX U_4;
#endif
#if !HAVE_INT8
typedef U_MAX U_8;
#endif
#if !HAVE_INT16
typedef U_MAX U_16;
#endif

union max_size_u
{
  U_1 b[MAX_SIZE];
  U_2 i2;
  U_4 i4;
  U_8 i8;
  U_16 i16;
};

/* The "word" size of the machine.  */
typedef unsigned UWORD __attribute__((mode(word)));

/* Macros for handing sub-word sized quantities.  */
#define MASK_1		((UWORD)0xff)
#define MASK_2		((UWORD)0xffff)
#define MASK_4		((UWORD)0xffffffff)
#define MASK_8		((UWORD)0xffffffffffffffff)
#define INVERT_MASK_1	((UWORD)WORDS_BIGENDIAN << ((WORDSIZE - 1) * CHAR_BIT))
#define INVERT_MASK_2	((UWORD)WORDS_BIGENDIAN << ((WORDSIZE - 2) * CHAR_BIT))
#define INVERT_MASK_4	((UWORD)WORDS_BIGENDIAN << ((WORDSIZE - 4) * CHAR_BIT))
#define INVERT_MASK_8	((UWORD)WORDS_BIGENDIAN << ((WORDSIZE - 8) * CHAR_BIT))

/* Most of the files in this library are compiled multiple times with
   N defined to be a power of 2 between 1 and 16.  The SIZE macro is
   then used to append _N to the symbol being manipulated.  */
#define SIZE(X)		C3(X,_,N)
#define WSIZE(X)	C3(X,_,WORDSIZE)
#define PTR(N,X)	((C2(U_,N) *)X)

/* And thus, the type on which this compilation will be operating.  */
#define ITYPE		SIZE(I)
#define UTYPE		SIZE(U)

/* Utility macros for GCC attributes.  */
#define UNUSED		__attribute__((unused))
#ifdef HAVE_ATTRIBUTE_VISIBILITY
# define HIDDEN		__attribute__((visibility("hidden")))
#else
# define HIDDEN
#endif

/* Occasionally we have to play games with internal and external symbol
   names, in order to work around builtin functions of the same name.
   This macro sets the external name of the function appropriately.  */
#define ASMNAME(X)	__asm__(S(C2(__USER_LABEL_PREFIX__,X)))

/* Locking for a "small" operation.  In the bare-metal single processor
   cases this could be implemented by disabling interrupts.  Thus the extra
   word passed between the two functions, saving the interrupt level.
   It is assumed that the object being locked does not cross the locking
   granularity.

   Not actually declared here so that they can be defined static inline
   in a target-specfic <host-config.h>.

UWORD protect_start (void *ptr);
void protect_end (void *ptr, UWORD);
*/

/* Locking for a "large' operation.  This should always be some sort of
   test-and-set operation, as we assume that the interrupt latency would
   be unreasonably large.  */
void libat_lock_n (void *ptr, size_t n);
void libat_unlock_n (void *ptr, size_t n);

/* We'll need to declare all of the sized functions a few times...  */
#define DECLARE_ALL_SIZED(N)  DECLARE_ALL_SIZED_(N,C2(U_,N))
#define DECLARE_ALL_SIZED_(N,T)						\
  DECLARE_1(T,    C2(load_,N), (T *mptr, int));				\
  DECLARE_1(void, C2(store_,N), (T *mptr, T val, int));			\
  DECLARE_1(T,    C2(exchange_,N), (T *mptr, T, int));			\
  DECLARE_1(bool, C2(compare_exchange_,N), (T *mptr, T *, T, int, int)); \
  DECLARE_1(bool, C2(test_and_set_,N), (T *mptr, int));			\
  DECLARE_1(T,    C2(fetch_add_,N), (T *mptr, T, int));			\
  DECLARE_1(T,    C2(fetch_sub_,N), (T *mptr, T, int));			\
  DECLARE_1(T,    C2(fetch_and_,N), (T *mptr, T, int));			\
  DECLARE_1(T,    C2(fetch_xor_,N), (T *mptr, T, int));			\
  DECLARE_1(T,    C2(fetch_or_,N), (T *mptr, T, int));			\
  DECLARE_1(T,    C2(fetch_nand_,N), (T *mptr, T, int));		\
  DECLARE_1(T,    C2(add_fetch_,N), (T *mptr, T, int));			\
  DECLARE_1(T,    C2(sub_fetch_,N), (T *mptr, T, int));			\
  DECLARE_1(T,    C2(and_fetch_,N), (T *mptr, T, int));			\
  DECLARE_1(T,    C2(xor_fetch_,N), (T *mptr, T, int));			\
  DECLARE_1(T,    C2(or_fetch_,N), (T *mptr, T, int));			\
  DECLARE_1(T,    C2(nand_fetch_,N), (T *mptr, T, int))

/* All sized operations are implemented in hidden functions prefixed with
   "libat_".  These are either renamed or aliased to the expected prefix
   of "__atomic".  Some amount of renaming is required to avoid hiding or
   conflicting with the builtins of the same name, but this additional
   use of hidden symbols (where appropriate) avoids unnecessary PLT entries
   on relevant targets.  */

#if IFUNC_ALT
# define MAN(X)			ASMNAME(C4(libat_,X,_i,IFUNC_ALT)) HIDDEN
#elif defined(HAVE_ATTRIBUTE_ALIAS)
# define MAN(X)			HIDDEN
#else
# define MAN(X)			ASMNAME(C2(__atomic_,X))
#endif

#if !defined(N) && HAVE_IFUNC
# define DECLARE_1(RET,NAME,ARGS) \
	RET C2(libat_,NAME) ARGS MAN(NAME); \
	RET C2(ifunc_,NAME) ARGS ASMNAME(C2(__atomic_,NAME))
#else
# define DECLARE_1(RET,NAME,ARGS)	RET C2(libat_,NAME) ARGS MAN(NAME)
#endif

/* Prefix to use when calling internal, possibly ifunc'ed functions.  */
#if HAVE_IFUNC
# define local_ ifunc_
#else
# define local_ libat_
#endif

DECLARE_ALL_SIZED(1);
DECLARE_ALL_SIZED(2);
DECLARE_ALL_SIZED(4);
DECLARE_ALL_SIZED(8);
DECLARE_ALL_SIZED(16);

#undef DECLARE_1
#undef DECLARE_ALL_SIZED
#undef DECLARE_ALL_SIZED_

/* And the generic sized versions.  */
void libat_load (size_t, void *, void *, int) MAN(load);
void libat_store (size_t, void *, void *, int) MAN(store);
void libat_exchange (size_t, void *, void *, void *, int) MAN(exchange);
bool libat_compare_exchange (size_t, void *, void *, void *, int, int)
	MAN(compare_exchange);
bool libat_is_lock_free (size_t, void *) MAN(is_lock_free);

#undef MAN

#include <host-config.h>

/* We don't have IFUNC_NCOND until after host-config.h.  */
#if !HAVE_IFUNC
# define IFUNC_NCOND(N) 0
#endif

#if IFUNC_ALT
# define EXPORT_ALIAS(X)	/* exported symbol in non-alternate file */
#elif defined(N) && IFUNC_NCOND(N)
# if IFUNC_NCOND(N) == 1
#  define GEN_SELECTOR(X)					\
	extern typeof(C2(libat_,X)) C3(libat_,X,_i1) HIDDEN;	\
	static typeof(C2(libat_,X)) * C2(select_,X) (IFUNC_RESOLVER_ARGS) \
	{							\
	  if (IFUNC_COND_1)					\
	    return C3(libat_,X,_i1);				\
	  return C2(libat_,X);					\
	}
# elif IFUNC_NCOND(N) == 2
#  define GEN_SELECTOR(X)					\
	extern typeof(C2(libat_,X)) C3(libat_,X,_i1) HIDDEN;	\
	extern typeof(C2(libat_,X)) C3(libat_,X,_i2) HIDDEN;	\
	static typeof(C2(libat_,X)) * C2(select_,X) (IFUNC_RESOLVER_ARGS) \
	{							\
	  if (IFUNC_COND_1)					\
	    return C3(libat_,X,_i1);				\
	  if (IFUNC_COND_2)					\
	    return C3(libat_,X,_i2);				\
	  return C2(libat_,X);					\
	}
# elif IFUNC_NCOND(N) == 3
#  define GEN_SELECTOR(X)					\
	extern typeof(C2(libat_,X)) C3(libat_,X,_i1) HIDDEN;	\
	extern typeof(C2(libat_,X)) C3(libat_,X,_i2) HIDDEN;	\
	extern typeof(C2(libat_,X)) C3(libat_,X,_i3) HIDDEN;	\
	static typeof(C2(libat_,X)) * C2(select_,X) (IFUNC_RESOLVER_ARGS) \
	{							\
	  if (IFUNC_COND_1)					\
	    return C3(libat_,X,_i1);				\
	  if (IFUNC_COND_2)					\
	    return C3(libat_,X,_i2);				\
	  if (IFUNC_COND_3)					\
	    return C3(libat_,X,_i3);				\
	  return C2(libat_,X);					\
	}
# else
#  error "Unsupported number of ifunc alternatives."
# endif
# define EXPORT_ALIAS(X)					\
	GEN_SELECTOR(X)						\
	typeof(C2(libat_,X)) C2(ifunc_,X)			\
	  ASMNAME(C2(__atomic_,X))				\
	  __attribute__((ifunc(S(C2(select_,X)))))
#elif defined(HAVE_ATTRIBUTE_ALIAS)
# define EXPORT_ALIAS(X)					\
	extern typeof(C2(libat_,X)) C2(export_,X)		\
	  ASMNAME(C2(__atomic_,X))				\
	  __attribute__((alias(S(C2(libat_,X)))))
#else
# define EXPORT_ALIAS(X)	/* original symbol is exported */
#endif

#endif /* LIBATOMIC_H */
