/* Copyright (C) 2019-2022 Free Software Foundation, Inc.

   This file is part of LIBF7, which is part of GCC.

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

   GCC 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/>.  */

#ifndef LIBF7_H
#define LIBF7_H
#define IN_LIBF7_H

#include "f7-renames.h"

#define F7_MANT_BYTES 7
#define F7_MANT_BITS (8 * F7_MANT_BYTES)

/*  Using the following GCC features:
    --  Unnamed structs / unions (GNU-C)
    --  Fixed-point types (GNU-C)
    --  Inline asm
    --  Setting assembler names by means of __asm (GNU-C).
    --  Attributes: alias, always_inline, const, noinline, unused,
                    progmem, pure, weak, warning
    --  GCC built-ins: __builtin_abort, __builtin_constant_p
    --  AVR built-ins: __builtin_avr_bitsr, __builtin_avr_rbits
*/

/* We have 2 kinds of flags:

   A)  The flags that are stored in f7_t.flags:
       --  f7_t.is_nan (NaN)
       --  f7_t.is_inf (+Inf or -Inf)
       --  f7_t.sign (negative or -Inf).

   B)  The flags that are returned by f7_classify().  This are the
       flags from A) together with
       --  _zero: indicate that a number is zero.
*/

#define F7_FLAGNO_sign  0
#define F7_FLAGNO_zero  1
#define F7_FLAGNO_nan   2
#define F7_FLAGNO_inf   7

#define F7_HAVE_Inf 1

// Flags that might be set by f7_classify().
#define F7_FLAG_sign            (1 << F7_FLAGNO_sign)
#define F7_FLAG_zero            (1 << F7_FLAGNO_zero)
#define F7_FLAG_nan             (1 << F7_FLAGNO_nan)
#define F7_FLAG_inf   (F7_HAVE_Inf << F7_FLAGNO_inf)

// Flags that might be set in f7_t.flags.
#define F7_FLAGS (F7_FLAG_inf | F7_FLAG_nan | F7_FLAG_sign)

#if !defined __ASSEMBLER__

#ifndef IN_LIBGCC2
#include <stdint.h>
#include <stdbool.h>
#include <stdlib.h>
#include <stdio.h>
#else
/* Do not assume that we have std headers when we build libgcc.  */

typedef __UINT64_TYPE__ uint64_t;
typedef __UINT32_TYPE__ uint32_t;
typedef __UINT16_TYPE__ uint16_t;
typedef __UINT8_TYPE__  uint8_t;
typedef __INT64_TYPE__ int64_t;
typedef __INT32_TYPE__ int32_t;
typedef __INT16_TYPE__ int16_t;
typedef __INT8_TYPE__  int8_t;
typedef _Bool bool;
#define false 0
#define true  1
#define INT8_MIN  (-1 - __INT8_MAX__)
#define INT16_MAX __INT16_MAX__
#define NULL ((void*) 0)
#endif /* IN_LIBGCC2 */

#include "asm-defs.h"

#ifdef __cplusplus
extern "C" {
#define _Static_assert(X, Y) static_assert (X)
#endif // C++

#define F7_INLINE   inline __attribute__((__always_inline__))
#define F7_NOINLINE __attribute__((__noinline__))
#define F7_WEAK     __attribute__((__weak__))
#define F7_PURE     __attribute__((__pure__))
#define F7_UNUSED   __attribute__((__unused__))
#define F7_CONST    __attribute__((__const__))

#define F7_STRINGY2(X)  #X
#define F7_STRINGY(X)   F7_STRINGY2(X)
#define F7ASM(X)        __asm (F7_STRINGY2(X))

typedef struct f7_t
{
  union
  {
    struct
    {
      uint8_t sign        :1;
      uint8_t reserved1   :1;
      uint8_t is_nan      :1;
      uint8_t reserved2   :4;
      uint8_t is_inf      :1;
    };
    uint8_t flags;
  };

  uint8_t mant[7];
  int16_t expo;
} f7_t;

typedef uint64_t f7_double_t;

#define F7_MANT_HI4(X) \
  (*(uint32_t*) & (X)->mant[F7_MANT_BYTES - 4])

#define F7_MANT_CONST_HI4(X) \
  (*(const uint32_t*) & (X)->mant[F7_MANT_BYTES - 4])

#define F7_MANT_HI2(X) \
  (*(uint16_t*) & (X)->mant[F7_MANT_BYTES - 2])

static F7_INLINE F7_PURE
uint8_t f7_classify (const f7_t *aa)
{
  extern void f7_classify_asm (void);
  register uint8_t rclass __asm ("r24");
  __asm ("%~call %x[f]"
	 : "=r" (rclass)
	 : [f] "i" (f7_classify_asm), "z" (aa));
  return rclass;
}


// +Inf or -Inf
static F7_INLINE
bool f7_class_inf (uint8_t c)
{
#if defined (F7_HAVE_Inf) && F7_HAVE_Inf == 1
  return c >= F7_FLAG_inf;
#elif defined (F7_HAVE_Inf) && F7_HAVE_Inf == 0
  (void) c;
  return false;
#else
#error macro F7_HAVE_Inf must be defined to 0 or to 1.
#endif // Have Inf
}

static F7_INLINE
bool f7_is_inf (const f7_t *aa)
{
  return f7_class_inf (aa->flags);
}

// Not-a-Number (NaN).
static F7_INLINE
bool f7_class_nan (uint8_t c)
{
  return c & F7_FLAG_nan;
}

static F7_INLINE
bool f7_is_nan (const f7_t *aa)
{
  return f7_class_nan (aa->flags);
}

// Some number
static F7_INLINE
bool f7_class_number (uint8_t c)
{
  return c <= (F7_FLAG_sign | F7_FLAG_zero);
}

static F7_INLINE
bool f7_is_number (const f7_t *aa)
{
  return f7_class_number (f7_classify (aa));
}

// Zero
static F7_INLINE
bool f7_class_zero (uint8_t c)
{
  return c & F7_FLAG_zero;
}

static F7_INLINE
bool f7_is_zero (const f7_t *aa)
{
  return f7_class_zero (f7_classify (aa));
}

// A non-zero number.
static F7_INLINE
bool f7_class_nonzero (uint8_t c)
{
  return c <= F7_FLAG_sign;
}

static F7_INLINE
bool f7_is_nonzero (const f7_t *aa)
{
  return f7_class_nonzero (f7_classify (aa));
}

static F7_INLINE
bool f7_class_sign (uint8_t c)
{
  return c & F7_FLAG_sign;
}

static F7_INLINE
bool f7_signbit (const f7_t *aa)
{
  return aa->flags & F7_FLAG_sign;
}

static F7_INLINE
void f7_set_sign (f7_t *cc, bool sign)
{
  _Static_assert (F7_FLAGNO_sign == 0, "");
  cc->flags &= ~F7_FLAG_sign;
  cc->flags |= sign;
}

static F7_INLINE
void f7_set_nan (f7_t *cc)
{
  cc->flags = F7_FLAG_nan;
}

static F7_INLINE
void f7_clr (f7_t *cc)
{
  extern void f7_clr_asm (void);
  __asm ("%~call %x[f]"
	 :
	 : [f] "i" (f7_clr_asm), "z" (cc)
	 : "memory");
}

static F7_INLINE
f7_t* f7_copy (f7_t *cc, const f7_t *aa)
{
  extern void f7_copy_asm (void);
  __asm ("%~call %x[f]"
	 :
	 : [f] "i" (f7_copy_asm), "z" (cc), "x" (aa)
	 : "memory");
  return cc;
}

static F7_INLINE
f7_t* f7_copy_P (f7_t *cc, const f7_t *aa)
{
  extern void f7_copy_P_asm (void);
  __asm ("%~call %x[f]"
	 :
	 : [f] "i" (f7_copy_P_asm), "x" (cc), "z" (aa)
	 : "memory");
  return cc;
}

static F7_INLINE
void f7_copy_mant (f7_t *cc, const f7_t *aa)
{
  extern void f7_copy_mant_asm (void);
  __asm ("%~call %x[f]"
	 :
	 : [f] "i" (f7_copy_mant_asm), "z" (cc), "x" (aa)
	 : "memory");
}

static F7_INLINE
void f7_set_inf (f7_t *cc, bool sign)
{
#if F7_HAVE_Inf == 1
  cc->flags = F7_FLAG_inf | sign;
#else
  (void) sign;
  cc->flags = F7_FLAG_nan;
#endif // Have Inf
}


static F7_INLINE
bool f7_msbit (const f7_t *aa)
{
  return aa->mant[F7_MANT_BYTES - 1] & 0x80;
}

// Quick test against 0 if A is known to be a number (neither NaN nor Inf).
static F7_INLINE
bool f7_is0 (const f7_t *aa)
{
  return 0 == f7_msbit (aa);
}


static F7_INLINE
int8_t f7_cmp_mant (const f7_t *aa, const f7_t *bb)
{
  extern void f7_cmp_mant_asm (void);
  register int8_t r24 __asm ("r24");
  __asm ("%~call %x[f] ;; %1 %3"
	 : "=r" (r24)
	 : [f] "i" (f7_cmp_mant_asm), "x" (aa), "z" (bb));
  return r24;
}

static F7_INLINE
bool f7_store_expo (f7_t *cc, int16_t expo)
{
  extern void f7_store_expo_asm (void);
  register bool r24 __asm ("r24");
  register int16_t rexpo __asm ("r24") = expo;
  __asm ("%~call %x[f] ;; %0 %2 %3"
	 : "=r" (r24)
	 : [f] "i" (f7_store_expo_asm), "z" (cc), "r" (rexpo));
  return r24;
}

static F7_INLINE
f7_t* f7_abs (f7_t *cc, const f7_t *aa)
{
  f7_copy (cc, aa);
  f7_set_sign (cc, 0);

  return cc;
}


F7_PURE extern int8_t f7_cmp (const f7_t*, const f7_t*);
F7_PURE extern bool f7_lt_impl (const f7_t*, const f7_t*);
F7_PURE extern bool f7_le_impl (const f7_t*, const f7_t*);
F7_PURE extern bool f7_gt_impl (const f7_t*, const f7_t*);
F7_PURE extern bool f7_ge_impl (const f7_t*, const f7_t*);
F7_PURE extern bool f7_ne_impl (const f7_t*, const f7_t*);
F7_PURE extern bool f7_eq_impl (const f7_t*, const f7_t*);
F7_PURE extern bool f7_unord_impl (const f7_t*, const f7_t*);

static F7_INLINE
bool f7_lt (const f7_t *aa, const f7_t *bb)
{
  return 2 & f7_cmp (aa, bb);
}

static F7_INLINE
bool f7_gt (const f7_t *aa, const f7_t *bb)
{
  return 1 == f7_cmp (aa, bb);
}

static F7_INLINE
bool f7_le (const f7_t *aa, const f7_t *bb)
{
  int8_t c = f7_cmp (aa, bb);
  return (uint8_t) (c + 1) <= 1;
}

static F7_INLINE
bool f7_ge (const f7_t *aa, const f7_t *bb)
{
  return f7_cmp (aa, bb) >= 0;
}

static F7_INLINE
bool f7_unordered (const f7_t *aa, const f7_t *bb)
{
  return INT8_MIN == f7_cmp (aa, bb);
}

static F7_INLINE
bool f7_ordered (const f7_t *aa, const f7_t *bb)
{
  return INT8_MIN != f7_cmp (aa, bb);
}

static F7_INLINE
bool f7_eq (const f7_t *aa, const f7_t *bb)
{
  return 0 == f7_cmp (aa, bb);
}

static F7_INLINE
bool f7_ne (const f7_t *aa, const f7_t *bb)
{
  return 1 & f7_cmp (aa, bb);
}

extern void f7_clr (f7_t*);

__attribute__((warning ("foo_u16"))) void foo_u16 (void);
__attribute__((warning ("foo_s16"))) void foo_s16 (void);

extern f7_t* f7_set_s16_impl (f7_t*, int16_t);
extern f7_t* f7_set_u16_impl (f7_t*, uint16_t);

static F7_INLINE
f7_t* f7_set_u16_worker (f7_t *cc, uint16_t u16)
{
  if (__builtin_constant_p (u16))
    {
      if (u16 == 0)
	return cc;

      uint8_t off = __builtin_clz (u16);
      if (15 - off)
	* (uint8_t*) & cc->expo = (uint8_t) (15 - off);
      u16 <<= off;
      if (u16 & 0xff)
	cc->mant[5] = (uint8_t) u16;
      if (u16 & 0xff00)
	cc->mant[6] = (uint8_t) (u16 >> 8);

      return cc;
    }
  else
    {
      foo_u16();
      __builtin_abort();
      return NULL;
    }
}

static F7_INLINE
f7_t* f7_set_u16 (f7_t *cc, uint16_t u16)
{
  if (__builtin_constant_p (u16))
    {
      f7_clr (cc);
      return f7_set_u16_worker (cc, u16);
    }

  return f7_set_u16_impl (cc, u16);
}

static F7_INLINE
f7_t* f7_set_s16 (f7_t *cc, int16_t s16)
{
  if (__builtin_constant_p (s16))
    {
      f7_clr (cc);

      uint16_t u16 = (uint16_t) s16;

      if (s16 < 0)
        {
	  u16 = -u16;
	  cc->flags = F7_FLAG_sign;
        }

      return f7_set_u16_worker (cc, u16);
    }

  return f7_set_s16_impl (cc, s16);
}

static F7_INLINE
void f7_set_eps (f7_t *cc, uint8_t eps, bool sign)
{
  cc = f7_set_u16 (cc, 1);
  if (!__builtin_constant_p (sign) || sign)
    cc->flags = sign;
  cc->mant[0] = eps;
}

static F7_INLINE
f7_t* f7_set_1pow2 (f7_t *cc, int16_t expo, bool sign)
{
  cc = f7_set_u16 (cc, 1);
  cc->expo = expo;
  if (!__builtin_constant_p (sign) || sign)
    cc->flags = sign;
  return cc;
}

static F7_INLINE
f7_t* f7_set_u64 (f7_t *cc, uint64_t u64)
{
  extern f7_t* f7_set_u64_asm (uint64_t, f7_t*);
  return f7_set_u64_asm (u64, cc);
}

static F7_INLINE
f7_t* f7_set_s64 (f7_t *cc, int64_t s64)
{
  extern f7_t* f7_set_s64_asm (int64_t, f7_t*);
  return f7_set_s64_asm (s64, cc);
}

extern void f7_set_double_impl (f7_double_t, f7_t*);
static F7_INLINE
void f7_set_double (f7_t *cc, f7_double_t val64)
{
  f7_set_double_impl (val64, cc);
}

extern f7_t* f7_init_impl (uint64_t, uint8_t, f7_t*, int16_t);

static F7_INLINE
f7_t* f7_init (f7_t *cc, uint8_t flags, uint64_t mant, int16_t expo)
{
  return f7_init_impl (mant, flags, cc, expo);
}

extern f7_t* f7_set_s32 (f7_t*, int32_t);
extern f7_t* f7_set_u16 (f7_t*, uint16_t);
extern f7_t* f7_set_u32 (f7_t*, uint32_t);
extern void f7_set_float (f7_t*, float);
extern void f7_set_pdouble (f7_t*, const f7_double_t*);

F7_PURE extern int16_t f7_get_s16 (const f7_t*);
F7_PURE extern int32_t f7_get_s32 (const f7_t*);
F7_PURE extern int64_t f7_get_s64 (const f7_t*);
F7_PURE extern uint16_t f7_get_u16 (const f7_t*);
F7_PURE extern uint32_t f7_get_u32 (const f7_t*);
F7_PURE extern uint64_t f7_get_u64 (const f7_t*);
F7_PURE extern float f7_get_float (const f7_t*);
F7_PURE extern f7_double_t f7_get_double (const f7_t*);

#if USE_LPM == 1
  #define F7_PGMSPACE     __attribute__((__progmem__))
  #define f7_copy_flash   f7_copy_P

  #define f7_const(X, NAME) \
    f7_copy_P ((X), & F7_(const_ ## NAME ## _P))

  #define F7_CONST_DEF(NAME, FLAGS, M0, M1, M2, M3, M4, M5, M6, EXPO) \
    extern const f7_t F7_(const_ ## NAME ## _P);
  #include "libf7-const.def"
  #undef F7_CONST_DEF
#else
  #define F7_PGMSPACE     // Empty
  #define f7_copy_flash   f7_copy

  #define f7_const(X, NAME) \
    f7_copy ((X), & F7_(const_ ## NAME))

  #define F7_CONST_DEF(NAME, FLAGS, M0, M1, M2, M3, M4, M5, M6, EXPO) \
    extern const f7_t F7_(const_ ## NAME);
  #include "libf7-const.def"
  #undef F7_CONST_DEF
#endif // USE_LPM


// Basic floating point arithmetic:
// double output <=> f7_t*
// double input  <=> const f7_t*
extern f7_t* f7_neg (f7_t*, const f7_t*);
extern void f7_add (f7_t*, const f7_t*, const f7_t*);
extern void f7_sub (f7_t*, const f7_t*, const f7_t*);
extern void f7_mul (f7_t*, const f7_t*, const f7_t*);
extern void f7_div (f7_t*, const f7_t*, const f7_t*);

// Analogies of functions from math.h:
// double output <=> f7_t*
// double input  <=> const f7_t*
extern void f7_fabs (f7_t*, const f7_t*);
extern void f7_fmod (f7_t*, const f7_t*, const f7_t*);
extern void f7_frexp (f7_t*, const f7_t*, int*);
extern void f7_exp (f7_t*, const f7_t*);
extern void f7_log (f7_t*, const f7_t*);
extern void f7_pow (f7_t*, const f7_t*, const f7_t*);
extern void f7_sqrt (f7_t*, const f7_t*);
extern void f7_cbrt (f7_t*, const f7_t*);
extern void f7_hypot (f7_t*, const f7_t*, const f7_t*);
extern f7_t* f7_ldexp (f7_t*, const f7_t*, int);
extern f7_t* f7_fmax (f7_t*, const f7_t*, const f7_t*);
extern f7_t* f7_fmin (f7_t*, const f7_t*, const f7_t*);
extern f7_t* f7_trunc (f7_t*, const f7_t*);
extern f7_t* f7_floor (f7_t*, const f7_t*);
extern void f7_ceil (f7_t*, const f7_t*);
extern void f7_round (f7_t*, const f7_t*);
extern void f7_sin (f7_t*, const f7_t*);
extern void f7_cos (f7_t*, const f7_t*);
extern void f7_tan (f7_t*, const f7_t*);
extern void f7_atan (f7_t*, const f7_t*);
extern void f7_asin (f7_t*, const f7_t*);
extern void f7_acos (f7_t*, const f7_t*);
extern void f7_tanh (f7_t*, const f7_t*);
extern void f7_sinh (f7_t*, const f7_t*);
extern void f7_cosh (f7_t*, const f7_t*);
extern void f7_log2 (f7_t*, const f7_t*);
extern void f7_log10 (f7_t*, const f7_t*);
extern void f7_exp10 (f7_t*, const f7_t*);
extern void f7_pow10 (f7_t*, const f7_t*);

// Just prototypes, not implemented yet.
extern void f7_atan2 (f7_t*, const f7_t*, const f7_t*);
extern long f7_lrint (const f7_t*);
extern long f7_lround (const f7_t*);

// Helper functions, aliases, convenience.
extern void f7_div1 (f7_t*, const f7_t*);
extern void f7_square (f7_t*, const f7_t*);

extern void f7_powi (f7_t*, const f7_t*, int);
extern f7_t* f7_max (f7_t*, const f7_t*, const f7_t*);
extern f7_t* f7_min (f7_t*, const f7_t*, const f7_t*);
extern f7_t* f7_truncx (f7_t*, const f7_t*, bool);
extern void f7_cotan (f7_t*, const f7_t*);
extern void f7_sincos (f7_t*, f7_t*, const f7_t*);
extern void f7_asinacos (f7_t*, const f7_t*, uint8_t);
extern void f7_sinhcosh (f7_t*, const f7_t*, bool);

extern void f7_horner (f7_t*, const f7_t*, uint8_t, const f7_t *coeff, f7_t*);
extern void f7_mul_noround (f7_t*, const f7_t*, const f7_t*);
extern void f7_clr_mant_lsbs (f7_t*, const f7_t*, uint8_t) F7ASM(f7_clr_mant_lsbs_asm);

F7_PURE extern int8_t f7_cmp_unordered (const f7_t*, const f7_t*, bool);
F7_PURE extern int8_t f7_cmp_abs (const f7_t*, const f7_t*);

F7_PURE extern bool f7_abscmp_msb_ge (const f7_t*, uint8_t msb, int16_t expo);
extern void f7_addsub (f7_t*, const f7_t*, const f7_t*, bool neg_b);
extern void f7_madd_msub (f7_t*, const f7_t*, const f7_t*, const f7_t*, bool);
extern void f7_madd (f7_t*, const f7_t*, const f7_t*, const f7_t*);
extern void f7_msub (f7_t*, const f7_t*, const f7_t*, const f7_t*);
extern uint8_t f7_mulx (f7_t*, const f7_t*, const f7_t*, bool);
extern void f7_divx (f7_t*, const f7_t*, const f7_t*, uint8_t);
extern void f7_logx (f7_t*, const f7_t*, const f7_t*);
extern f7_t* f7_minmax (f7_t*, const f7_t*, const f7_t*, bool);

// Idem:
//    f7_Ifunc (y)    = f7_func (y, y)
//    f7_Ifunc (y, x) = f7_func (y, y, x)
extern void f7_Iadd (f7_t*, const f7_t*);
extern void f7_Isub (f7_t*, const f7_t*);
extern void f7_Imul (f7_t*, const f7_t*);
extern void f7_Idiv (f7_t*, const f7_t*);
extern void f7_IRsub (f7_t*, const f7_t*);
extern void f7_Ineg (f7_t*);
extern void f7_Isqrt (f7_t*);
extern void f7_Isquare (f7_t*);
extern f7_t* f7_Ildexp (f7_t*, int);

// Protoypes for some functions from libf7-asm.sx.
F7_CONST extern uint16_t f7_sqrt16_round (uint16_t) F7ASM(f7_sqrt16_round_asm);
F7_CONST extern uint8_t  f7_sqrt16_floor (uint16_t) F7ASM(f7_sqrt16_floor_asm);
extern void f7_addsub_mant_scaled_asm (f7_t*, const f7_t*, const f7_t*, uint8_t);
extern uint8_t f7_mul_mant_asm (f7_t*, const f7_t*, const f7_t*, uint8_t);
extern void f7_sqrt_approx_asm (f7_t*, const f7_t*);
extern uint64_t f7_lshrdi3 (uint64_t, uint8_t) F7ASM(f7_lshrdi3_asm);
extern uint64_t f7_ashldi3 (uint64_t, uint8_t) F7ASM(f7_ashldi3_asm);
// Normalize a non-Inf, non-NaN value.  Sets .sign to 0.
extern f7_t* f7_normalize_asm (f7_t*);

// Dumping.
#ifndef IN_LIBGCC2
extern void f7_dump (const f7_t*);
extern void f7_dump_mant (const f7_t*);
extern void f7_put_C (const f7_t*, FILE*);
extern void f7_put_CDEF (const char *name, const f7_t*, FILE*);
#endif /* IN_LIBGCC2 */

#ifdef __cplusplus
} // extern "C"
#include "libf7-class.h"
#endif // C++

#endif /* __ASSEMBLER__ */
#undef IN_LIBF7_H
#endif /* LIBF7_H */
