/* Copyright (C) 2021-2026 Free Software Foundation, Inc.
   Contributed by Oracle.

   This file is part of GNU Binutils.

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

   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.

   You should have received a copy of the GNU General Public License
   along with this program; if not, write to the Free Software
   Foundation, 51 Franklin Street - Fifth Floor, Boston,
   MA 02110-1301, USA.  */

#include "config.h"
#include <sys/mman.h>
#include <unistd.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>

#include "collector.h"
#include "libcol_util.h"
#include "gp-experiment.h"
#include "memmgr.h"

/*
 * Memory allocation.
 *
 * Heap:
 *    chain[0] - linked list of chunks;
 *    chain[1] - linked list of free 16-byte objects;
 *    chain[2] - linked list of free 32-byte objects;
 *    ...
 *
 * Chunk:
 *
 * base               lo        hi
 * V                  V         V
 * +------------------+---------+-------------------+--+--+-----+
 * | Var size object  | ->    <-| Const size objects|  |  |Chunk|
 * +------------------+---------+-------------------+--+--+-----+
 *
 * Limitations:
 *   - one var size object per chunk
 *   - can't allocate const size objects larger than 2^MAXCHAIN
 */

#define MAXCHAIN    32
#define ALIGNMENT    4   /* 2^ALIGNMENT == minimal size and alignment */
#define ALIGN(x)    ((((x) - 1)/(1 << ALIGNMENT) + 1) * (1 << ALIGNMENT))

struct Heap
{
  collector_mutex_t lock;   /* master lock */
  void *chain[MAXCHAIN];    /* chain[0] - chunks */
			    /* chain[i] - structs of size 2^i */
};

typedef struct Chunk
{
  size_t size;
  char *base;
  char *lo;
  char *hi;
  struct Chunk *next;
} Chunk;

static void
not_implemented ()
{
  __collector_log_write ("<event kind=\"%s\" id=\"%d\">error memmgr not_implemented()</event>\n",
			 SP_JCMD_CERROR, COL_ERROR_NOZMEM);
  return;
}

/*
 * void __collector_mmgr_init_mutex_locks( Heap *heap )
 *      Iinitialize mmgr mutex locks.
 */
void
__collector_mmgr_init_mutex_locks (Heap *heap)
{
  if (heap == NULL)
    return;
  if (__collector_mutex_trylock (&heap->lock))
    {
      /*
       * We are in a child process immediately after the fork().
       * Parent process was in the middle of critical section when the fork() happened.
       * This is a placeholder for the cleanup.
       * See CR 6997020 for details.
       */
      __collector_mutex_init (&heap->lock);
    }
  __collector_mutex_init (&heap->lock);
}

/*
 * alloc_chunk( unsigned sz ) allocates a chunk of at least sz bytes.
 * If sz == 0, allocates a chunk of the default size.
 */
static Chunk *
alloc_chunk (unsigned sz, int log)
{
  static long pgsz = 0;
  char *ptr;
  Chunk *chnk;
  size_t chunksz;
  if (pgsz == 0)
    {
      pgsz = CALL_UTIL (sysconf)(_SC_PAGESIZE);
      Tprintf (DBG_LT2, "memmgr: pgsz = %ld (0x%lx)\n", pgsz, pgsz);
    }
  /* Allocate 2^n >= sz bytes */
  unsigned nsz = ALIGN (sizeof (Chunk)) + sz;
  for (chunksz = pgsz; chunksz < nsz; chunksz *= 2);
  if (log == 1)
    Tprintf (DBG_LT2, "alloc_chunk mapping %u, rounded up from %u\n", (unsigned int) chunksz, sz);
  /* mmap64 is only in 32-bits; this call goes to mmap in 64-bits */
  ptr = (char*) CALL_UTIL (mmap64_)(0, chunksz, PROT_READ | PROT_WRITE,
				   MAP_PRIVATE | MAP_ANON, (int) -1, (off64_t) 0);
  if (ptr == MAP_FAILED)
    {
      Tprintf (0, "alloc_chunk mapping failed COL_ERROR_NOZMEMMAP: %s\n", CALL_UTIL (strerror)(errno));
      __collector_log_write ("<event kind=\"%s\" id=\"%d\" ec=\"%d\">%s</event>\n",
			     SP_JCMD_CERROR, COL_ERROR_NOZMEMMAP, errno, "0");
      return NULL;
    }
  /* Put the chunk descriptor at the end of the chunk */
  chnk = (Chunk*) (ptr + chunksz - ALIGN (sizeof (Chunk)));
  chnk->size = chunksz;
  chnk->base = ptr;
  chnk->lo = chnk->base;
  chnk->hi = (char*) chnk;
  chnk->next = (Chunk*) NULL;
  if (log == 1)
    Tprintf (DBG_LT2, "memmgr: returning new chunk @%p, chunksx=%ld sz=%ld\n",
	     ptr, (long) chunksz, (long) sz);
  return chnk;
}

Heap *
__collector_newHeap ()
{
  Heap *heap;
  Chunk *chnk;
  Tprintf (DBG_LT2, "__collector_newHeap calling alloc_chunk(0)\n");
  chnk = alloc_chunk (0, 1);
  if (chnk == NULL)
    return NULL;

  /* A bit of hackery: allocate heap from its own chunk */
  chnk->hi -= ALIGN (sizeof (Heap));
  heap = (Heap*) chnk->hi;
  heap->chain[0] = (void*) chnk;
  __collector_mutex_init (&heap->lock);
  return heap;
}

void
__collector_deleteHeap (Heap *heap)
{
  if (heap == NULL)
    return;
  /* Note: heap itself is in the last chunk */
  for (Chunk *chnk = heap->chain[0]; chnk;)
    {
      Chunk *next = chnk->next;
      CALL_UTIL (munmap)((void*) chnk->base, chnk->size);
      chnk = next;
    }
}

void *
__collector_allocCSize (Heap *heap, unsigned sz, int log)
{
  void *res;
  Chunk *chnk;
  if (heap == NULL)
    return NULL;

  /* block all signals and acquire lock */
  sigset_t old_mask, new_mask;
  CALL_UTIL (sigfillset)(&new_mask);
  CALL_UTIL (sigprocmask)(SIG_SETMASK, &new_mask, &old_mask);
  __collector_mutex_lock (&heap->lock);

  /* Allocate nsz = 2^idx >= sz bytes */
  unsigned idx = ALIGNMENT;
  unsigned nsz = 1 << idx;
  while (nsz < sz)
    nsz = 1 << ++idx;

  /* Look in the corresponding chain first */
  if (idx < MAXCHAIN)
    {
      if (heap->chain[idx] != NULL)
	{
	  res = heap->chain[idx];
	  heap->chain[idx] = *(void**) res;
	  __collector_mutex_unlock (&heap->lock);
	  CALL_UTIL (sigprocmask)(SIG_SETMASK, &old_mask, NULL);
	  if (log == 1)
	    Tprintf (DBG_LT2, "memmgr: allocCSize %p sz %d (0x%x) req = 0x%x, from chain idx = %d\n", res, nsz, nsz, sz, idx);
	  return res;
	}
    }
  else
    {
      not_implemented ();
      __collector_mutex_unlock (&heap->lock);
      CALL_UTIL (sigprocmask)(SIG_SETMASK, &old_mask, NULL);
      return NULL;
    }

  /* Chain is empty, allocate from chunks */
  for (chnk = (Chunk*) heap->chain[0]; chnk; chnk = chnk->next)
    if (chnk->lo + nsz < chnk->hi)
      break;
  if (chnk == NULL)
    {
      /* Get a new chunk */
      if (log == 1)
	Tprintf (DBG_LT2, "__collector_allocCSize (%u) calling alloc_chunk(%u)\n", sz, nsz);
      chnk = alloc_chunk (nsz, 1);
      if (chnk == NULL)
	{
	  __collector_mutex_unlock (&heap->lock);
	  CALL_UTIL (sigprocmask)(SIG_SETMASK, &old_mask, NULL);
	  return NULL;
	}
      chnk->next = (Chunk*) heap->chain[0];
      heap->chain[0] = chnk;
    }

  /* Allocate from the chunk */
  chnk->hi -= nsz;
  res = (void*) chnk->hi;
  __collector_mutex_unlock (&heap->lock);
  CALL_UTIL (sigprocmask)(SIG_SETMASK, &old_mask, NULL);
  if (log == 1)
    Tprintf (DBG_LT2, "memmgr: allocCSize %p sz %d (0x%x) req = 0x%x, new chunk\n", res, nsz, nsz, sz);
  return res;
}

void
__collector_freeCSize (Heap *heap, void *ptr, unsigned sz)
{
  if (heap == NULL || ptr == NULL)
    return;

  /* block all signals and acquire lock */
  sigset_t old_mask, new_mask;
  CALL_UTIL (sigfillset)(&new_mask);
  CALL_UTIL (sigprocmask)(SIG_SETMASK, &new_mask, &old_mask);
  __collector_mutex_lock (&heap->lock);

  /* Free 2^idx >= sz bytes */
  unsigned idx = ALIGNMENT;
  unsigned nsz = 1 << idx;
  while (nsz < sz)
    nsz = 1 << ++idx;
  if (idx < MAXCHAIN)
    {
      *(void**) ptr = heap->chain[idx];
      heap->chain[idx] = ptr;
    }
  else
    not_implemented ();
  __collector_mutex_unlock (&heap->lock);
  CALL_UTIL (sigprocmask)(SIG_SETMASK, &old_mask, NULL);
  Tprintf (DBG_LT4, "memmgr: freeC %p sz %ld\n", ptr, (long) sz);
}

static void *
allocVSize_nolock (Heap *heap, unsigned sz)
{
  void *res;
  Chunk *chnk;
  if (sz == 0)
    return NULL;

  /* Find a good chunk */
  for (chnk = (Chunk*) heap->chain[0]; chnk; chnk = chnk->next)
    if (chnk->lo == chnk->base && chnk->lo + sz < chnk->hi)
      break;
  if (chnk == NULL)
    {
      /* Get a new chunk */
      Tprintf (DBG_LT2, "allocVsize_nolock calling alloc_chunk(%u)\n", sz);
      chnk = alloc_chunk (sz, 0);
      if (chnk == NULL)
	return NULL;
      chnk->next = (Chunk*) heap->chain[0];
      heap->chain[0] = chnk;
    }
  chnk->lo = chnk->base + sz;
  res = (void*) (chnk->base);
  Tprintf (DBG_LT4, "memmgr: allocV %p for %ld\n", res, (long) sz);
  return res;
}

void *
__collector_allocVSize (Heap *heap, unsigned sz)
{
  void *res;
  if (heap == NULL)
    return NULL;

  /* block all signals and acquire lock */
  sigset_t old_mask, new_mask;
  CALL_UTIL (sigfillset)(&new_mask);
  CALL_UTIL (sigprocmask)(SIG_SETMASK, &new_mask, &old_mask);
  __collector_mutex_lock (&heap->lock);
  res = allocVSize_nolock (heap, sz);
  __collector_mutex_unlock (&heap->lock);
  CALL_UTIL (sigprocmask)(SIG_SETMASK, &old_mask, NULL);
  return res;
}

/*
 *  reallocVSize( Heap *heap, void *ptr, unsigned newsz )
 *  Changes the size of memory pointed by ptr to newsz.
 *  If ptr == NULL, allocates new memory of size newsz.
 *  If newsz == 0, frees ptr and returns NULL.
 */
void *
__collector_reallocVSize (Heap *heap, void *ptr, unsigned newsz)
{
  Chunk *chnk;
  void *res;
  if (heap == NULL)
    return NULL;
  if (ptr == NULL)
    return __collector_allocVSize (heap, newsz);

  /* block all signals and acquire lock */
  sigset_t old_mask, new_mask;
  CALL_UTIL (sigfillset)(&new_mask);
  CALL_UTIL (sigprocmask)(SIG_SETMASK, &new_mask, &old_mask);
  __collector_mutex_lock (&heap->lock);

  /* Find its chunk */
  for (chnk = (Chunk*) heap->chain[0]; chnk; chnk = chnk->next)
    if (ptr == chnk->base)
      break;
  if (chnk == NULL)
    {
      /* memory corrpution */
      not_implemented ();
      __collector_mutex_unlock (&heap->lock);
      CALL_UTIL (sigprocmask)(SIG_SETMASK, &old_mask, NULL);
      return NULL;
    }
  if (chnk->base + newsz < chnk->hi)
    {
      /* easy case */
      chnk->lo = chnk->base + newsz;
      res = newsz ? chnk->base : NULL;
      __collector_mutex_unlock (&heap->lock);
      CALL_UTIL (sigprocmask)(SIG_SETMASK, &old_mask, NULL);
      Tprintf (DBG_LT4, "memmgr: reallocV %p for %ld\n", ptr, (long) newsz);
      return res;
    }
  res = allocVSize_nolock (heap, newsz);
  /* Copy to new location */
  if (res)
    {
      int size = chnk->lo - chnk->base;
      if (newsz < size)
	size = newsz;
      char *s1 = (char*) res;
      char *s2 = chnk->base;
      while (size--)
	*s1++ = *s2++;
    }
  /* Free old memory*/
  chnk->lo = chnk->base;
  __collector_mutex_unlock (&heap->lock);
  CALL_UTIL (sigprocmask)(SIG_SETMASK, &old_mask, NULL);
  return res;
}
