/* Copyright (c) 1997-2009
   Ewgenij Gawrilow, Michael Joswig (Technische Universitaet Darmstadt, Germany)
   http://www.polymake.de

   This program 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 2, or (at your option) any
   later version: http://www.gnu.org/licenses/gpl.txt.

   This program 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.
--------------------------------------------------------------------------------
   $Project: polymake $$Id: gmp_init.cc 9298 2009-09-02 23:10:56Z gawrilow $
*/

#include <cstring>
#include <memory>
#include <cstdio>
#include <gmp.h>
#include <plausible_checks.h>

namespace pm {

#if defined(__GNUC__)
#if __GNUC__==3 && __GNUC_MINOR__==3
namespace {
   void* pm_gmp_allocate(size_t n)
   {
      return __builtin_expect(n==0,0) ? 0 : std::__alloc::allocate(n);
   }
}
# define pm_gmp_deallocate std::__alloc::deallocate
# define pm_gmp_reallocate std::__alloc::reallocate
#endif // gcc 3.3

#if __GNUC__==3 && __GNUC_MINOR__==4 || __GNUC__==4
# include <ext/pool_allocator.h>

namespace {
# if __GNUC__==3 && __GNUC_PATCHLEVEL__ == 0
   typedef __gnu_cxx::__pool_alloc<true,0> raw_alloc;

   void* pm_gmp_allocate(size_t n)
   {
      return __builtin_expect(n==0,0) ? 0 : raw_alloc::allocate(n);
   }

   void pm_gmp_deallocate(void* p, size_t n) { raw_alloc::deallocate(p,n); }
# else  // >3.4.0
   __gnu_cxx::__pool_alloc<char> gmp_allocator;

   void* pm_gmp_allocate(size_t n) { return gmp_allocator.allocate(n); }
   void pm_gmp_deallocate(void* p, size_t n) { gmp_allocator.deallocate(reinterpret_cast<char*>(p), n); }
# endif

   void* pm_gmp_reallocate(void* p, size_t old_sz, size_t new_sz)
   {
      static const bool use_new=getenv("GLIBCPP_FORCE_NEW") || getenv("GLIBCXX_FORCE_NEW");
      const size_t align=8, limit=128;
      if (!use_new && ((old_sz+align-1)&~(align-1)) == ((new_sz+align-1)&~(align-1)) && new_sz < limit)
	 return p;
      void* new_p=pm_gmp_allocate(new_sz);
      if (new_p) {
	 std::memcpy(new_p, p, old_sz < new_sz ? old_sz : new_sz);
	 pm_gmp_deallocate(p,old_sz);
      }
      return new_p;
   }
}
#endif // gcc >=3.4
#elif defined(__INTEL_COMPILER)
namespace {
   std::allocator<char> gmp_allocator;
   void* pm_gmp_allocate(size_t n) { return gmp_allocator.allocate(n); }
   void pm_gmp_deallocate(void* p, size_t n) { gmp_allocator.deallocate(reinterpret_cast<char*>(p), n); }
   void* pm_gmp_reallocate(void* p, size_t old_sz, size_t new_sz)
   {
      void* new_p=pm_gmp_allocate(new_sz);
      if (new_p) {
	 std::memcpy(new_p, p, old_sz < new_sz ? old_sz : new_sz);
	 pm_gmp_deallocate(p,old_sz);
      }
      return new_p;
   }
}
#endif // __INTEL_COMPILER

namespace {
   class gmp_alloc_init {
   public:
      gmp_alloc_init();
   };

#ifdef POLY_DEBUG
   void announce_bad_alloc() throw(std::bad_alloc)
   {
      break_on_throw("bad alloc");
      throw std::bad_alloc();
   }
#endif

   gmp_alloc_init::gmp_alloc_init()
   {
      mp_set_memory_functions(pm_gmp_allocate, pm_gmp_reallocate, pm_gmp_deallocate);
#ifdef POLY_DEBUG
      std::set_new_handler(announce_bad_alloc);
#endif
   }

   void do_alloc_init() __attribute__((constructor));

   void do_alloc_init()
   {
      static gmp_alloc_init init;
   }
}

} // end namespace pm
