// This file 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.

// You should have received a copy of the GNU General Public License
// along with GCC; see the file COPYING3.  If not see
// <http://www.gnu.org/licenses/>.

#include "rust-builtins.h"

namespace Rust {
namespace Compile {

static const int builtin_const = 1 << 0;
static const int builtin_noreturn = 1 << 1;
static const int builtin_novops = 1 << 2;

BuiltinsContext &
BuiltinsContext::get ()
{
  static BuiltinsContext instance;
  return instance;
}

bool
BuiltinsContext::lookup_simple_builtin (const std::string &name, tree *builtin)
{
  auto it = rust_intrinsic_to_gcc_builtin.find (name);
  if (it == rust_intrinsic_to_gcc_builtin.end ())
    return false;

  return lookup_gcc_builtin (it->second, builtin);
}

BuiltinsContext::BuiltinsContext () { setup (); }

void
BuiltinsContext::setup_overflow_fns ()
{
  tree overflow_type
    = build_varargs_function_type_list (boolean_type_node, NULL_TREE);

  define_builtin ("add_overflow", BUILT_IN_ADD_OVERFLOW,
		  "__builtin_add_overflow", "add_overflow", overflow_type, 0);
  define_builtin ("sub_overflow", BUILT_IN_SUB_OVERFLOW,
		  "__builtin_sub_overflow", "sub_overflow", overflow_type, 0);
  define_builtin ("mul_overflow", BUILT_IN_MUL_OVERFLOW,
		  "__builtin_mul_overflow", "mul_overflow", overflow_type, 0);
}

void
BuiltinsContext::setup_math_fns ()
{
  tree fn_type_f32_to_f32
    = build_function_type_list (float_type_node, float_type_node, NULL_TREE);
  tree fn_type_f64_to_f64
    = build_function_type_list (double_type_node, double_type_node, NULL_TREE);
  tree fn_type_f32_f32_to_f32
    = build_function_type_list (float_type_node, float_type_node,
				float_type_node, NULL_TREE);
  tree fn_type_f64_f64_to_f64
    = build_function_type_list (double_type_node, double_type_node,
				double_type_node, NULL_TREE);
  tree fn_type_f32_i32_to_f32
    = build_function_type_list (float_type_node, float_type_node,
				integer_type_node, NULL_TREE);
  tree fn_type_f64_i32_to_f64
    = build_function_type_list (double_type_node, double_type_node,
				integer_type_node, NULL_TREE);

  define_builtin ("sqrtf32", BUILT_IN_SQRTF, "__builtin_sqrtf", "sqrtf",
		  fn_type_f32_to_f32, builtin_const);
  define_builtin ("sqrtf64", BUILT_IN_SQRT, "__builtin_sqrt", "sqrt",
		  fn_type_f64_to_f64, builtin_const);

  define_builtin ("powif32", BUILT_IN_POWIF, "__builtin_powif", "powif",
		  fn_type_f32_i32_to_f32, builtin_const);
  define_builtin ("powif64", BUILT_IN_POWI, "__builtin_powi", "powi",
		  fn_type_f64_i32_to_f64, builtin_const);

  define_builtin ("sinf32", BUILT_IN_SINF, "__builtin_sinf", "sinf",
		  fn_type_f32_to_f32, builtin_const);
  define_builtin ("sinf64", BUILT_IN_SIN, "__builtin_sin", "sin",
		  fn_type_f64_to_f64, builtin_const);

  define_builtin ("cosf32", BUILT_IN_COSF, "__builtin_cosf", "cosf",
		  fn_type_f32_to_f32, builtin_const);
  define_builtin ("cosf64", BUILT_IN_COS, "__builtin_cos", "cos",
		  fn_type_f64_to_f64, builtin_const);

  define_builtin ("powf32", BUILT_IN_POWF, "__builtin_powf", "powf",
		  fn_type_f32_f32_to_f32, builtin_const);
  define_builtin ("powf64", BUILT_IN_POW, "__builtin_pow", "pow",
		  fn_type_f64_f64_to_f64, builtin_const);

  define_builtin ("expf32", BUILT_IN_EXPF, "__builtin_expf", "expf",
		  fn_type_f32_to_f32, builtin_const);
  define_builtin ("expf64", BUILT_IN_EXP, "__builtin_exp", "exp",
		  fn_type_f64_to_f64, builtin_const);

  define_builtin ("exp2f32", BUILT_IN_EXP2F, "__builtin_exp2f", "exp2f",
		  fn_type_f32_to_f32, builtin_const);
  define_builtin ("exp2f64", BUILT_IN_EXP2, "__builtin_exp2", "exp2",
		  fn_type_f64_to_f64, builtin_const);

  define_builtin ("logf32", BUILT_IN_LOGF, "__builtin_logf", "logf",
		  fn_type_f32_to_f32, builtin_const);
  define_builtin ("logf64", BUILT_IN_LOG, "__builtin_log", "log",
		  fn_type_f64_to_f64, builtin_const);

  define_builtin ("log10f32", BUILT_IN_LOG10F, "__builtin_log10f", "log10f",
		  fn_type_f32_to_f32, builtin_const);
  define_builtin ("log10f64", BUILT_IN_LOG10, "__builtin_log10", "log10",
		  fn_type_f64_to_f64, builtin_const);

  define_builtin ("log2f32", BUILT_IN_LOG2F, "__builtin_log2f", "log2f",
		  fn_type_f32_to_f32, builtin_const);
  define_builtin ("log2f64", BUILT_IN_LOG2, "__builtin_log2", "log2",
		  fn_type_f64_to_f64, builtin_const);

  define_builtin ("fmaf32", BUILT_IN_FMAF, "__builtin_fmaf", "fmaf",
		  fn_type_f32_f32_to_f32, builtin_const);
  define_builtin ("fmaf64", BUILT_IN_FMA, "__builtin_fma", "fma",
		  fn_type_f64_f64_to_f64, builtin_const);

  define_builtin ("fabsf32", BUILT_IN_FABSF, "__builtin_fabsf", "fabsf",
		  fn_type_f32_to_f32, builtin_const);
  define_builtin ("fabsf64", BUILT_IN_FABS, "__builtin_fabs", "fabs",
		  fn_type_f64_to_f64, builtin_const);

  define_builtin ("minnumf32", BUILT_IN_FMINF, "__builtin_fminf", "fminf",
		  fn_type_f32_f32_to_f32, builtin_const);
  define_builtin ("minnumf64", BUILT_IN_FMIN, "__builtin_fmin", "fmin",
		  fn_type_f64_f64_to_f64, builtin_const);

  define_builtin ("maxnumf32", BUILT_IN_FMAXF, "__builtin_fmaxf", "fmaxf",
		  fn_type_f32_f32_to_f32, builtin_const);
  define_builtin ("maxnumf64", BUILT_IN_FMAX, "__builtin_fmax", "fmax",
		  fn_type_f64_f64_to_f64, builtin_const);

  define_builtin ("copysignf32", BUILT_IN_COPYSIGNF, "__builtin_copysignf",
		  "copysignf", fn_type_f32_f32_to_f32, builtin_const);
  define_builtin ("copysignf64", BUILT_IN_COPYSIGN, "__builtin_copysign",
		  "copysign", fn_type_f64_f64_to_f64, builtin_const);

  define_builtin ("floorf32", BUILT_IN_FLOORF, "__builtin_floorf", "floorf",
		  fn_type_f32_to_f32, builtin_const);
  define_builtin ("floorf64", BUILT_IN_FLOOR, "__builtin_floor", "floor",
		  fn_type_f64_to_f64, builtin_const);

  define_builtin ("ceilf32", BUILT_IN_CEILF, "__builtin_ceilf", "ceilf",
		  fn_type_f32_to_f32, builtin_const);
  define_builtin ("ceilf64", BUILT_IN_CEIL, "__builtin_ceil", "ceil",
		  fn_type_f64_to_f64, builtin_const);

  define_builtin ("truncf32", BUILT_IN_TRUNCF, "__builtin_truncf", "truncf",
		  fn_type_f32_to_f32, builtin_const);
  define_builtin ("truncf64", BUILT_IN_TRUNC, "__builtin_trunc", "trunc",
		  fn_type_f64_to_f64, builtin_const);

  define_builtin ("rintf32", BUILT_IN_RINTF, "__builtin_rintf", "rintf",
		  fn_type_f32_to_f32, builtin_const);
  define_builtin ("rintf64", BUILT_IN_RINT, "__builtin_rint", "rint",
		  fn_type_f64_to_f64, builtin_const);

  define_builtin ("nearbyintf32", BUILT_IN_NEARBYINTF, "__builtin_nearbyintf",
		  "nearbyintf", fn_type_f32_to_f32, builtin_const);
  define_builtin ("nearbyintf64", BUILT_IN_NEARBYINT, "__builtin_nearbyint",
		  "nearbyint", fn_type_f64_to_f64, builtin_const);

  define_builtin ("roundf32", BUILT_IN_ROUNDF, "__builtin_roundf", "roundf",
		  fn_type_f32_to_f32, builtin_const);
  define_builtin ("roundf64", BUILT_IN_ROUND, "__builtin_round", "round",
		  fn_type_f64_to_f64, builtin_const);
}

void
BuiltinsContext::setup_atomic_fns ()
{
  auto atomic_store_type
    = build_varargs_function_type_list (void_type_node, NULL_TREE);
  auto atomic_load_type = [] (tree ret_type_node) {
    return build_function_type_list (ret_type_node,
				     ptr_type_node, // const_ptr_type_node?
				     integer_type_node, NULL_TREE);
  };

  // FIXME: These should be the definition for the generic version of the
  // atomic_store builtins, but I cannot get them to work properly. Revisit
  // later. define_builtin ("atomic_store", BUILT_IN_ATOMIC_STORE,
  // "__atomic_store", NULL,
  //   atomic_store_type, 0);
  // define_builtin ("atomic_store_n", BUILT_IN_ATOMIC_STORE_N,
  // "__atomic_store_n",
  //   NULL, atomic_store_type, 0);

  define_builtin ("atomic_store_1", BUILT_IN_ATOMIC_STORE_1, "__atomic_store_1",
		  NULL, atomic_store_type, 0);
  define_builtin ("atomic_store_2", BUILT_IN_ATOMIC_STORE_2, "__atomic_store_2",
		  NULL, atomic_store_type, 0);
  define_builtin ("atomic_store_4", BUILT_IN_ATOMIC_STORE_4, "__atomic_store_4",
		  NULL, atomic_store_type, 0);
  define_builtin ("atomic_store_8", BUILT_IN_ATOMIC_STORE_8, "__atomic_store_8",
		  NULL, atomic_store_type, 0);
  define_builtin ("atomic_store_16", BUILT_IN_ATOMIC_STORE_16,
		  "__atomic_store_16", NULL, atomic_store_type, 0);

  define_builtin ("atomic_load_1", BUILT_IN_ATOMIC_LOAD_1, "__atomic_load_1",
		  NULL, atomic_load_type (integer_type_node), 0);
  define_builtin ("atomic_load_2", BUILT_IN_ATOMIC_LOAD_2, "__atomic_load_2",
		  NULL, atomic_load_type (integer_type_node), 0);
  define_builtin ("atomic_load_4", BUILT_IN_ATOMIC_LOAD_4, "__atomic_load_4",
		  NULL, atomic_load_type (integer_type_node), 0);
  define_builtin ("atomic_load_8", BUILT_IN_ATOMIC_LOAD_8, "__atomic_load_8",
		  NULL, atomic_load_type (integer_type_node), 0);
}

void
BuiltinsContext::setup ()
{
  setup_math_fns ();
  setup_overflow_fns ();
  setup_atomic_fns ();

  define_builtin ("unreachable", BUILT_IN_UNREACHABLE, "__builtin_unreachable",
		  NULL, build_function_type (void_type_node, void_list_node),
		  builtin_const | builtin_noreturn);

  define_builtin ("abort", BUILT_IN_ABORT, "__builtin_abort", "abort",
		  build_function_type (void_type_node, void_list_node),
		  builtin_const | builtin_noreturn);

  define_builtin ("breakpoint", BUILT_IN_TRAP, "__builtin_trap", "breakpoint",
		  build_function_type (void_type_node, void_list_node),
		  builtin_const | builtin_noreturn);

  define_builtin ("memcpy", BUILT_IN_MEMCPY, "__builtin_memcpy", "memcpy",
		  build_function_type_list (build_pointer_type (void_type_node),
					    build_pointer_type (void_type_node),
					    build_pointer_type (void_type_node),
					    size_type_node, NULL_TREE),
		  0);

  define_builtin ("prefetch", BUILT_IN_PREFETCH, "__builtin_prefetch",
		  "prefetch",
		  build_varargs_function_type_list (
		    build_pointer_type (const_ptr_type_node), NULL_TREE),
		  builtin_const);
}

static void
handle_flags (tree decl, int flags)
{
  if (flags & builtin_const)
    TREE_READONLY (decl) = 1;
  if (flags & builtin_noreturn)
    TREE_READONLY (decl) = 1;
  if (flags & builtin_novops)
    DECL_IS_NOVOPS (decl) = 1;
}

void
BuiltinsContext::define_builtin (const std::string rust_name,
				 built_in_function bcode, const char *name,
				 const char *libname, tree fntype, int flags)
{
  tree decl = add_builtin_function (name, fntype, bcode, BUILT_IN_NORMAL,
				    libname, NULL_TREE);
  handle_flags (decl, flags);
  set_builtin_decl (bcode, decl, true);

  this->builtin_functions_[name] = decl;
  if (libname != NULL)
    {
      decl = add_builtin_function (libname, fntype, bcode, BUILT_IN_NORMAL,
				   NULL, NULL_TREE);
      handle_flags (decl, flags);

      this->builtin_functions_[libname] = decl;
    }

  rust_intrinsic_to_gcc_builtin[rust_name] = name;
}

bool
BuiltinsContext::lookup_gcc_builtin (const std::string &name, tree *builtin)
{
  auto it = builtin_functions_.find (name);
  if (it == builtin_functions_.end ())
    return false;

  *builtin = it->second;
  return true;
}

} // namespace Compile
} // namespace Rust