/****************************************************************************/
/*                                                                          */
/*                         GNAT COMPILER COMPONENTS                         */
/*                                                                          */
/*                              A - R A I S E                               */
/*                                                                          */
/*                          C Implementation File                           */
/*                                                                          */
/*                            $Revision: 1.63 $
/*                                                                          */
/*             Copyright (C) 1992-2000, Free Software Foundation, Inc.      */
/*                                                                          */
/* GNAT is free software;  you can  redistribute it  and/or modify it under */
/* terms of the  GNU General Public License as published  by the Free Soft- */
/* ware  Foundation;  either version 2,  or (at your option) any later ver- */
/* sion.  GNAT is distributed in the hope that it will be useful, but WITH- */
/* OUT 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  distributed with GNAT;  see file COPYING.  If not, write */
/* to  the Free Software Foundation,  59 Temple Place - Suite 330,  Boston, */
/* MA 02111-1307, USA.                                                      */
/*                                                                          */
/* As a  special  exception,  if you  link  this file  with other  files to */
/* produce an executable,  this file does not by itself cause the resulting */
/* executable to be covered by the GNU General Public License. This except- */
/* ion does not  however invalidate  any other reasons  why the  executable */
/* file might be covered by the  GNU Public License.                        */
/*                                                                          */
/* GNAT was originally developed  by the GNAT team at  New York University. */
/* It is now maintained by Ada Core Technologies Inc (http://www.gnat.com). */
/*                                                                          */
/****************************************************************************/

/*  This file contains allocation tracking capability that is inactive by
    default; to switch it on, symbol GMEM must be defined.

    The object file memtrack.o is the version of this file with GMEM sections
    enabled, this object is to be found in libgmem.a.

    When enabled, the subsystem logs all the calls to __gnat_malloc and
    __gnat_free. This log can then be processed by GNATMEM tool to detect
    dynamic memory leaks.

    To use this functionality, the user's program must be compiled with -g
    and then linked with this object file:

       gnatmake -g program -largs -lgmem

    After the compilation, the user may use his program as usual except
    that upon its completion it will generate in the current directory
    the file gmem.out.

    The user can then investigate his program for possible memory leaks
    by calling GNATMEM tool with this file as an input:

      gnatmem -i gmem.out program

    See GNATMEM section in GNAT User's Guide for more details).

    NOTE: This capability is currently supported on the following targets:

      All x86 ports
      AiX PowerPC
      HP-UX
      Irix
      Solaris sparc
      Tru64
      VxWorks PowerPC
      VxWorks Alpha

*/

/* Routines to support runtime exception handling */

/* ??? We need this to define malloc on those machines that need it, but
   this is the wrong file when this is built for libgnat.a.  */

#ifdef __alpha_vxworks
#include "vxWorks.h"
#endif

#include "config.h"
#include "a-ada.h"
#include "a-types.h"
#include "a-raise.h"
#include <stdio.h>


#if defined GMEM

/* This is a variable generated by the binder and it is set to 1 when in
   elaboration or finalisation code. In this case the memory tracking feature
   is disabled */

extern int __gnat_elab_final_code;

static void gmem_malloc (char *addr, int size);
static void gmem_free (char *addr);

#endif

/* Routines to protect non thread-safe and non async abort-safe malloc
   implementations.
   If you do not use asynchronous operations (abort statements
   and asynchronous transfer of control constructs) and are using an OS that
   provides a thread-safe implementation of malloc/free, then compiling this
   file with -DNO_LOCK will remove the calls to Lock/Unlock_Task,
   providing a more efficient implementation. */

#ifdef NO_LOCK
#define LOCK_TASK
#define UNLOCK_TASK

#else

#define Lock_Task system__soft_links__lock_task
extern void (*Lock_Task) (void);

#define Unlock_Task system__soft_links__unlock_task
extern void (*Unlock_Task) (void);

#define LOCK_TASK (*Lock_Task) ()
#define UNLOCK_TASK (*Unlock_Task) ()
#endif

/* Routines to support runtime exception handling */

#define Print_Unhandled_Exception ada__exceptions__print_unhandled_exception
extern void Print_Unhandled_Exception (void);

#define Raise_With_C_Msg ada__exceptions__raise_with_c_msg
extern void Raise_With_C_Msg (Exception_Id, char *);

extern struct Exception_Data constraint_error;
extern struct Exception_Data program_error;
extern struct Exception_Data storage_error;

/*  We have not yet figured out how to import this directly */

void
_gnat_builtin_longjmp (ptr, flag)
     void *ptr;
     int flag;
{
   __builtin_longjmp (ptr, 1);
}

/* When an exception is raised for which no handler exists, the procedure
   Ada.Exceptions.Unhandled_Exception is called, which performs the call
   to adafinal to complete finalization, and then prints out the error
   messages for the unhandled exception. The final step is to call this
   routine, which performs any system dependent cleanup required */

void
__gnat_unhandled_terminate ()
{
/* Special termination handling for VMS */

#ifdef VMS
    {
      long prvhnd;

      /* Remove the exception vector so it won't intercept any errors
	 in the call to exit, and go into and endless loop */

      SYS$SETEXV (1, 0, 3, &prvhnd);
      os_exit (1);
    }

/* Termination handling for all other systems. */

#elif !defined (__RT__)
    os_exit (1);
#endif
}

#if defined (WINNT)
/* The following defines functions to limit the amount
   of used memory for systems that do not have OS support
   for that. The amount of available memory available for
   dynamic allocation is limited by setting the environment
   variable GNAT_MEMORY_LIMIT to the number of kilobytes
   that can be used.
*/

static __SIZE_TYPE__ available_memory = 0;
/*  Amount of memory that is available for heap allocations.
    A value of 0 means that the amount is not yet initialized */

#define MSIZE_ACCURACY 4096
/* This defines the amount of memory to add to requested allocation
   sizes, because malloc may return a bigger block than requested.
   As msize is used when free-ing, it must be used on allocation as
   well. To prevent underflow of available_memory we need to use
   a reserve.
*/

/* This routine must be called while having the task lock.
   When the memory limit is not yet initialized, it will
   be set to the value of the GNAT_MEMORY_LIMIT environment
   variable or to unlimited if that does not exist.

   If the size is larger than the amount of available memory,
   the task lock will be freed and a storage_error exception will
   be raised.
*/

static void
check_available_memory (size)
   __SIZE_TYPE__ size;
{
   if (available_memory == 0)
   {
      /* The amount of available memory hasn't been initialized yet */
      char *gnat_memory_limit;
      gnat_memory_limit = (char *) getenv ("GNAT_MEMORY_LIMIT");
      if (gnat_memory_limit != 0) {
         available_memory = atoi (gnat_memory_limit) * 1024 + MSIZE_ACCURACY;
      } else {
         available_memory = (__SIZE_TYPE__) -1;
      }
   }

   if (size >= available_memory)
   {
      /* There is a memory overflow */
      UNLOCK_TASK;
      Raise_With_C_Msg (&storage_error, "heap memory limit exceeded");
   }
}

#endif

/* Linux modules don't have malloc/free but provide a similar interface with
   kmalloc/kfree. */

#ifdef __RT__

void *
malloc (size)
     __SIZE_TYPE__ size;
{
  extern void *kmalloc();

  return kmalloc (size);
}

void
free (void *ptr)
{
  kfree (ptr);
}

void *
realloc (void *ptr, __SIZE_TYPE__ size)
{
  /* Currently do not support realloc. */
  return 0;
}

#endif

/* malloc for use by GNAT, with error checking and task lockout,
   as well as allocation tracking. */

void *
__gnat_malloc (size)
     __SIZE_TYPE__ size;
{
  void *result;

  if (size == (__SIZE_TYPE__) -1)
    Raise_With_C_Msg (&storage_error, "object too large");

  /* Change size from zero to non-zero. We still want a proper pointer
     for the zero case because pointers to zero length objects have to
     be distinct, but we can't just go ahead and allocate zero bytes,
     since some malloc's return zero for a zero argument. */

  if (size == 0)
    size = 1;

  LOCK_TASK;

#if defined (WINNT)
  if (size + MSIZE_ACCURACY >= available_memory)
     check_available_memory (size + MSIZE_ACCURACY);
#endif

  result = (char *) malloc (size);

#if defined (WINNT)
  if (result != 0)
     available_memory = available_memory - _msize (result);
#endif

#if defined (GMEM)

  /* Allocation tracking */
  if (__gnat_elab_final_code == 0)
    gmem_malloc (result, size);

#endif

  UNLOCK_TASK;

  if (result == 0)
    Raise_With_C_Msg (&storage_error, "heap exhausted");

  return result;
}

/* free for use by GNAT, with task lockout and allocation tracking. */

void
__gnat_free (void *ptr)
{
  LOCK_TASK;

#if defined (WINNT)
  if (ptr != 0)
     available_memory = available_memory + _msize (ptr);
#endif

#if defined (GMEM)

  /* Allocation tracking */
  if (__gnat_elab_final_code == 0)
    gmem_free (ptr);

#endif

  free (ptr);

  UNLOCK_TASK;
}

/* realloc for use by GNAT, with error checking and task lockout. */

void *
__gnat_realloc (void *ptr, __SIZE_TYPE__ size)
{
  void *result;
#if defined (WINNT)
  __SIZE_TYPE__ old_size;
#endif

  if (size == (__SIZE_TYPE__) -1)
    Raise_With_C_Msg (&storage_error, "object too large");

  LOCK_TASK;

#if defined (WINNT)
  old_size = _msize (ptr);

  /* conservative check - no need to try to be precise here */
  if (size + MSIZE_ACCURACY >= available_memory)
     check_available_memory (size + MSIZE_ACCURACY);
#endif

  result = (void *) realloc (ptr, size);

#if defined (WINNT)
    if (result != 0)
       available_memory = available_memory + old_size - _msize (ptr);
#endif

  UNLOCK_TASK;

  if (result == 0)
    Raise_With_C_Msg (&storage_error, "heap exhausted");

  return result;
}


#if defined (GMEM)

/* Some of the stack frames must be skipped, this is dependant of the stack
   backtrace implementation. The goal here is to skip the GNAT runtime frame
   which are of no use for the user. It is hard to count the number of frames
   from the allocation/deallocation routine in the user code down to the
   __gnat_malloc/__gnat_free routines. 

   To adjust the [ALLOC/FREE]_STACK_FRAME_SKIPPED for a platforms use the
   following program.

      with Ada.Unchecked_Deallocation;
      with Interfaces.C.Strings; use Interfaces.C.Strings;

      procedure Bt is

         type SA is access String;

         procedure Free is new Ada.Unchecked_Deallocation (String, SA);

         A, B, C : SA;
      begin
         A := new String'("test");
         B := new String'("test");
         C := B;
         Free (B);
         Free (C);
      end Bt;

   Check it through gnatmem using GMEM mode:

      $ gnatmake -g bt -largs -lgmem
      $ bt
      $ gnatmem 1 -i gmem.out bt[.exe]

   Note that one some platforms (Linux for example) the bt executable could
   raise storage error during execution of "Free (C);" above. But in anycase
   the gmem.out file should be correct.

   The deallocated memory check should read (FREE_STACK_FRAME_SKIPPED must be
   adjusted):

      Releasing deallocated memory at :
      --------------------------------
         bt.adb:16 bt

   And the output for the first allocation root should read
   (ALLOC_STACK_FRAME_SKIPPED must be adjusted):

      Allocation Root # 1
      -------------------
       Number of non freed allocations    :   1
       Final Water Mark (non freed mem)   :  12 Bytes
       High Water Mark                    :  12 Bytes
       Backtrace                          :
         bt.adb:12 bt

   The important point is that the backtrace line *must* be pointing to
   user's code and not some GNAT runtime frame. This is in fact the line (in
   the user's code) where the leak or release of deallocated memory occurs.

 */

#if defined (_WIN32) || defined (linux)
#define ALLOC_STACK_FRAME_SKIPPED 4
#define FREE_STACK_FRAME_SKIPPED 4
#elif defined (sun)
#define ALLOC_STACK_FRAME_SKIPPED 2
#define FREE_STACK_FRAME_SKIPPED 2
#else
#define ALLOC_STACK_FRAME_SKIPPED 0
#define FREE_STACK_FRAME_SKIPPED 0
#endif

/* Allocation tracking for detection of memory usage errors */

/* Some macros to call the OS machine state operations from System.Traceback */

#define CALL_CHAIN(traceback,max_len) \
  system__traceback__c_call_chain(traceback, max_len)

/* tb_len is the call level supported by this module */
#define tb_len 200

static char *tracebk [tb_len];
static int cur_tb_len;

/* Allocation log of a program is saved in a file gmem.out */
/* ??? what about argv[0].out instead of static gmem.out */

static char *gmemfname = "gmem.out";
static FILE *gmemfile;

static int needs_init = 1;

/* Initialization routine; opens the file and writes a header string. This
   header string is used as a magic-tag to know if the .out file is to be
   handled by GDB or by the GMEM (instrumented malloc/free) implementation.
*/

static void
gmem_initialize ()
{
  needs_init = 0;
  gmemfile = fopen (gmemfname, "wb");
  fwrite ("GMEM DUMP\n", 10, 1, gmemfile);
}

/* Obtains the call backtrace chain and stores it in tracebk array */

static void
gmem_call_chain ()
{
  cur_tb_len = CALL_CHAIN (tracebk, tb_len);
}

/* Writes the backtrace chain to a file */

static void
gmem_save_backtrace (int nb_skipped_frame)
{
  int saved_tb = cur_tb_len - nb_skipped_frame;
  fwrite (&saved_tb, sizeof (int), 1, gmemfile);
  fwrite (tracebk+nb_skipped_frame, sizeof (char *), saved_tb, gmemfile);
}

/* Logs allocation call */
/* format is:
   'A' <mem addr> <size chunk> <len backtrace> <addr1> ... <addrn> */

static void
gmem_malloc (char *addr, int size)
{
  if (needs_init) gmem_initialize();
  gmem_call_chain ();
  fputc ('A', gmemfile);
  fwrite (&addr, sizeof (char *), 1, gmemfile);
  fwrite (&size, sizeof (int), 1, gmemfile);
  gmem_save_backtrace (ALLOC_STACK_FRAME_SKIPPED);
}

/* Logs deallocation call */
/* format is:
   'D' <mem addr> <len backtrace> <addr1> ... <addrn> */

static void
gmem_free (char *addr)
{
  if (needs_init) gmem_initialize();
  gmem_call_chain ();
  fputc ('D', gmemfile);
  fwrite (&addr, sizeof (char *), 1, gmemfile);
  gmem_save_backtrace (FREE_STACK_FRAME_SKIPPED);
}

/* Replaces the __gnat_finalize to properly close the log file */

void
__gnat_finalize ()
{
  if (!needs_init)
    fclose (gmemfile);
}

/* End of allocation tracking section */

#endif
