// https://github.com/32bitmicro/newlib-nano-1.0/blob/master/newlib/libc/machine/xstormy16/tiny-malloc.c

/* A replacement malloc with:
   - Much reduced code size;
   - Smaller RAM footprint;
   - The ability to handle downward-growing heaps;
   but
   - Slower;
   - Probably higher memory fragmentation;
   - Doesn't support threads (but, if it did support threads,
     it wouldn't need a global lock, only a compare-and-swap instruction);
   - Assumes the maximum alignment required is the alignment of a pointer;
   - Assumes that memory is already there and doesn't need to be allocated.

* Synopsis of public routines

  malloc(size_t n);
     Return a pointer to a newly allocated chunk of at least n bytes, or null
     if no space is available.
  free(void* p);
     Release the chunk of memory pointed to by p, or no effect if p is null.
  realloc(void* p, size_t n);
     Return a pointer to a chunk of size n that contains the same data
     as does chunk p up to the minimum of (n, p's size) bytes, or null
     if no space is available. The returned pointer may or may not be
     the same as p. If p is null, equivalent to malloc.  Unless the
     #define REALLOC_ZERO_BYTES_FREES below is set, realloc with a
     size argument of zero (re)allocates a minimum-sized chunk.
  memalign(size_t alignment, size_t n);
     Return a pointer to a newly allocated chunk of n bytes, aligned
     in accord with the alignment argument, which must be a power of
     two.  Will fail if 'alignment' is too large.
  calloc(size_t unit, size_t quantity);
     Returns a pointer to quantity * unit bytes, with all locations
     set to zero.
  cfree(void* p);
     Equivalent to free(p).
  malloc_trim(size_t pad);
     Release all but pad bytes of freed top-most memory back 
     to the system. Return 1 if successful, else 0.
  malloc_usable_size(void* p);
     Report the number usable allocated bytes associated with allocated
     chunk p. This may or may not report more bytes than were requested,
     due to alignment and minimum size constraints.
  malloc_stats();
     Prints brief summary statistics on stderr.
  mallinfo()
     Returns (by copy) a struct containing various summary statistics.
  mallopt(int parameter_number, int parameter_value)
     Changes one of the tunable parameters described below. Returns
     1 if successful in changing the parameter, else 0.  Actually, returns 0
     always, as no parameter can be changed.
*/

#ifdef __xstormy16__
#define MALLOC_DIRECTION -1
#endif

#ifndef MALLOC_DIRECTION
#define MALLOC_DIRECTION 1
#endif

#include <stddef.h>

void* malloc(size_t);
void    free(void*);
void* realloc(void*, size_t);
void* memalign(size_t, size_t);
void* valloc(size_t);
void* pvalloc(size_t);
void* calloc(size_t, size_t);
void    cfree(void*);
int     malloc_trim(size_t);
size_t  malloc_usable_size(void*);
void    malloc_stats(void);
int     mallopt(int, int);
struct mallinfo mallinfo(void);

typedef struct freelist_entry {
  size_t size;
  struct freelist_entry *next;
} *fle;

extern void * __malloc_end;
extern fle __malloc_freelist;

/* Return the number of bytes that need to be added to X to make it
   aligned to an ALIGN boundary.  ALIGN must be a power of 2.  */
#define M_ALIGN(x, align) (-(size_t)(x) & ((align) - 1))

/* Return the number of bytes that need to be subtracted from X to make it
   aligned to an ALIGN boundary.  ALIGN must be a power of 2.  */
#define M_ALIGN_SUB(x, align) ((size_t)(x) & ((align) - 1))

extern char *__malloc_start;

/* This is the minimum gap allowed between __malloc_end and the top of
   the stack.  This is only checked for when __malloc_end is
   decreased; if instead the stack grows into the heap, silent data
   corruption will result.  */
#define MALLOC_MINIMUM_GAP 32

#ifdef __xstormy16__
register void * stack_pointer asm ("r15");
#define MALLOC_LIMIT stack_pointer
#else
#define MALLOC_LIMIT __builtin_frame_address (0)
#endif

#if MALLOC_DIRECTION < 0
#define CAN_ALLOC_P(required)				\
  (((size_t) __malloc_end - (size_t)MALLOC_LIMIT	\
    - MALLOC_MINIMUM_GAP) >= (required))
#else
#define CAN_ALLOC_P(required)				\
  (((size_t)MALLOC_LIMIT - (size_t) __malloc_end	\
    - MALLOC_MINIMUM_GAP) >= (required))
#endif

/* real_size is the size we actually have to allocate, allowing for
   overhead and alignment.  */
#define REAL_SIZE(sz)						\
  ((sz) < sizeof (struct freelist_entry) - sizeof (size_t)	\
   ? sizeof (struct freelist_entry)				\
   : sz + sizeof (size_t) + M_ALIGN(sz, sizeof (size_t)))

#ifdef DEFINE_MALLOC

void * __malloc_end = &__malloc_start;
fle __malloc_freelist;

void *
malloc (size_t sz)
{
  fle *nextfree;
  fle block;

  /* real_size is the size we actually have to allocate, allowing for
     overhead and alignment.  */
  size_t real_size = REAL_SIZE (sz);

  /* Look for the first block on the freelist that is large enough.  */
  for (nextfree = &__malloc_freelist; 
       *nextfree; 
       nextfree = &(*nextfree)->next)  
    {
      block = *nextfree;
      
      if (block->size >= real_size)
	{
	  /* If the block found is just the right size, remove it from
	     the free list.  Otherwise, split it.  */
	  if (block->size < real_size + sizeof (struct freelist_entry))
	    {
	      *nextfree = block->next;
	      return (void *)&block->next;
	    }
	  else
	    {
	      size_t newsize = block->size - real_size;
	      fle newnext = block->next;
	      *nextfree = (fle)((size_t)block + real_size);
	      (*nextfree)->size = newsize;
	      (*nextfree)->next = newnext;
	      goto done;
	    }
	}

      /* If this is the last block on the freelist, and it was too small,
	 enlarge it.  */
      if (! block->next
	  && __malloc_end == (void *)((size_t)block + block->size))
	{
	  size_t moresize = real_size - block->size;
	  if (! CAN_ALLOC_P (moresize))
	    return NULL;
	  
	  *nextfree = NULL;
	  if (MALLOC_DIRECTION < 0)
	    {
	      block = __malloc_end = (void *)((size_t)block - moresize);
	    }
	  else
	    {
	      __malloc_end = (void *)((size_t)block + real_size);
	    }

	  goto done;
	}
    }

  /* No free space at the end of the free list.  Allocate new space
     and use that.  */

  if (! CAN_ALLOC_P (real_size))
    return NULL;

  if (MALLOC_DIRECTION > 0)
    {
      block = __malloc_end;
      __malloc_end = (void *)((size_t)__malloc_end + real_size);
    }
  else
    {
      block = __malloc_end = (void *)((size_t)__malloc_end - real_size);
    }
 done:
  block->size = real_size;
  return (void *)&block->next;
}

#endif

#ifdef DEFINE_FREE

void
free (void *block_p)
{
  fle *nextfree;
  fle block = (fle)((size_t) block_p - offsetof (struct freelist_entry, next));

  if (block_p == NULL)
    return;
  
  /* Look on the freelist to see if there's a free block just before
     or just after this block.  */
  for (nextfree = &__malloc_freelist; 
       *nextfree; 
       nextfree = &(*nextfree)->next)
    {
      fle thisblock = *nextfree;
      if ((size_t)thisblock + thisblock->size == (size_t) block)
	{
	  thisblock->size += block->size;
	  if (MALLOC_DIRECTION > 0
	      && thisblock->next
	      && (size_t) block + block->size == (size_t) thisblock->next)
	    {
	      thisblock->size += thisblock->next->size;
	      thisblock->next = thisblock->next->next;
	    }
	  return;
	}
      else if ((size_t) thisblock == (size_t) block + block->size)
	{
	  if (MALLOC_DIRECTION < 0
	      && thisblock->next
	      && (size_t) block == ((size_t) thisblock->next 
				    + thisblock->next->size))
	    {
	      *nextfree = thisblock->next;
	      thisblock->next->size += block->size + thisblock->size;
	    }
	  else
	    {
	      block->size += thisblock->size;
	      block->next = thisblock->next;
	      *nextfree = block;
	    }
	  return;
	}
      else if ((MALLOC_DIRECTION > 0
		&& (size_t) thisblock > (size_t) block)
	       || (MALLOC_DIRECTION < 0
		   && (size_t) thisblock < (size_t) block))
	break;
    }

  block->next = *nextfree;
  *nextfree = block;
  return;
}
#endif

#ifdef DEFINE_REALLOC
void *
realloc (void *block_p, size_t sz)
{
  fle block = (fle)((size_t) block_p - offsetof (struct freelist_entry, next));
  size_t real_size = REAL_SIZE (sz);
  size_t old_real_size;

  if (block_p == NULL)
    return malloc (sz);

  old_real_size = block->size;

  /* Perhaps we need to allocate more space.  */
  if (old_real_size < real_size)
    {
      void *result;
      size_t old_size = old_real_size - sizeof (size_t);
      
      /* Need to allocate, copy, and free.  */
      result = malloc (sz);
      if (result == NULL)
	return NULL;
      memcpy (result, block_p, old_size < sz ? old_size : sz);
      free (block_p);
      return result;
    }
  /* Perhaps we can free some space.  */
  if (old_real_size - real_size >= sizeof (struct freelist_entry))
    {
      fle newblock = (fle)((size_t)block + real_size);
      block->size = real_size;
      newblock->size = old_real_size - real_size;
      free (&newblock->next);
    }
  return block_p;
}
#endif

#ifdef DEFINE_CALLOC
void *
calloc (size_t n, size_t elem_size)
{
  void *result;
  size_t sz = n * elem_size;
  result = malloc (sz);
  if (result != NULL)
    memset (result, 0, sz);
  return result;
}
#endif

#ifdef DEFINE_CFREE
void
cfree (void *p)
{
  free (p);
}
#endif

#ifdef DEFINE_MEMALIGN
void *
memalign (size_t align, size_t sz)
{
  fle *nextfree;
  fle block;

  /* real_size is the size we actually have to allocate, allowing for
     overhead and alignment.  */
  size_t real_size = REAL_SIZE (sz);

  /* Some sanity checking on 'align'. */
  if ((align & (align - 1)) != 0
      || align <= 0)
    return NULL;

  /* Look for the first block on the freelist that is large enough.  */
  /* One tricky part is this: We want the result to be a valid pointer
     to free.  That means that there has to be room for a size_t
     before the block.  If there's additional space before the block,
     it should go on the freelist, or it'll be lost---we could add it
     to the size of the block before it in memory, but finding the
     previous block is expensive.  */
  for (nextfree = &__malloc_freelist; 
       ; 
       nextfree = &(*nextfree)->next)  
    {
      size_t before_size;
      size_t old_size;

      /* If we've run out of free blocks, allocate more space.  */
      if (! *nextfree)
	{
	  old_size = real_size;
	  if (MALLOC_DIRECTION < 0)
	    {
	      old_size += M_ALIGN_SUB (((size_t)__malloc_end 
					- old_size + sizeof (size_t)),
				       align);
	      if (! CAN_ALLOC_P (old_size))
		return NULL;
	      block = __malloc_end = (void *)((size_t)__malloc_end - old_size);
	    }
	  else
	    {
	      block = __malloc_end;
	      old_size += M_ALIGN ((size_t)__malloc_end + sizeof (size_t),
				   align);
	      if (! CAN_ALLOC_P (old_size))
		return NULL;
	      __malloc_end = (void *)((size_t)__malloc_end + old_size);
	    }
	  *nextfree = block;
	  block->size = old_size;
	  block->next = NULL;
	}
      else
	{
	  block = *nextfree;
	  old_size = block->size;
	}
      

      before_size = M_ALIGN (&block->next, align);
      if (before_size != 0)
	before_size = sizeof (*block) + M_ALIGN (&(block+1)->next, align);

      /* If this is the last block on the freelist, and it is too small,
	 enlarge it.  */
      if (! block->next
	  && old_size < real_size + before_size
	  && __malloc_end == (void *)((size_t)block + block->size))
	{
	  if (MALLOC_DIRECTION < 0)
	    {
	      size_t moresize = real_size - block->size;
	      moresize += M_ALIGN_SUB ((size_t)&block->next - moresize, align);
	      if (! CAN_ALLOC_P (moresize))
		return NULL;
	      block = __malloc_end = (void *)((size_t)block - moresize);
	      block->next = NULL;
	      block->size = old_size = old_size + moresize;
	      before_size = 0;
	    }
	  else
	    {
	      if (! CAN_ALLOC_P (before_size + real_size - block->size))
		return NULL;
	      __malloc_end = (void *)((size_t)block + before_size + real_size);
	      block->size = old_size = before_size + real_size;
	    }

	  /* Two out of the four cases below will now be possible; which
	     two depends on MALLOC_DIRECTION.  */
	}

      if (old_size >= real_size + before_size)
	{
	  /* This block will do.  If there needs to be space before it, 
	     split the block.  */
	  if (before_size != 0)
	    {
	      fle old_block = block;

	      old_block->size = before_size;
	      block = (fle)((size_t)block + before_size);
	      
	      /* If there's no space after the block, we're now nearly
                 done; just make a note of the size required.  
	         Otherwise, we need to create a new free space block.  */
	      if (old_size - before_size 
		  <= real_size + sizeof (struct freelist_entry))
		{
		  block->size = old_size - before_size;
		  return (void *)&block->next;
		}
	      else 
		{
		  fle new_block;
		  new_block = (fle)((size_t)block + real_size);
		  new_block->size = old_size - before_size - real_size;
		  if (MALLOC_DIRECTION > 0)
		    {
		      new_block->next = old_block->next;
		      old_block->next = new_block;
		    }
		  else
		    {
		      new_block->next = old_block;
		      *nextfree = new_block;
		    }
		  goto done;
		}
	    }
	  else
	    {
	      /* If the block found is just the right size, remove it from
		 the free list.  Otherwise, split it.  */
	      if (old_size <= real_size + sizeof (struct freelist_entry))
		{
		  *nextfree = block->next;
		  return (void *)&block->next;
		}
	      else
		{
		  size_t newsize = old_size - real_size;
		  fle newnext = block->next;
		  *nextfree = (fle)((size_t)block + real_size);
		  (*nextfree)->size = newsize;
		  (*nextfree)->next = newnext;
		  goto done;
		}
	    }
	}
    }

 done:
  block->size = real_size;
  return (void *)&block->next;
}
#endif

#ifdef DEFINE_VALLOC
void *
valloc (size_t sz)
{
  return memalign (128, sz);
}
#endif
#ifdef DEFINE_PVALLOC
void *
pvalloc (size_t sz)
{
  return memalign (128, sz + M_ALIGN (sz, 128));
}
#endif

#ifdef DEFINE_MALLINFO
#include "malloc.h"

struct mallinfo 
mallinfo (void)
{
  struct mallinfo r;
  fle fr;
  size_t free_size;
  size_t total_size;
  size_t free_blocks;

  memset (&r, 0, sizeof (r));

  free_size = 0;
  free_blocks = 0;
  for (fr = __malloc_freelist; fr; fr = fr->next)
    {
      free_size += fr->size;
      free_blocks++;
      if (! fr->next)
	{
	  int atend;
	  if (MALLOC_DIRECTION > 0)
	    atend = (void *)((size_t)fr + fr->size) == __malloc_end;
	  else
	    atend = (void *)fr == __malloc_end;
	  if (atend)
	    r.keepcost = fr->size;
	}
    }

  if (MALLOC_DIRECTION > 0)
    total_size = (char *)__malloc_end - (char *)&__malloc_start;
  else
    total_size = (char *)&__malloc_start - (char *)__malloc_end;
  
#ifdef DEBUG
  /* Fixme: should walk through all the in-use blocks and see if
     they're valid.  */
#endif

  r.arena = total_size;
  r.fordblks = free_size;
  r.uordblks = total_size - free_size;
  r.ordblks = free_blocks;
  return r;
}
#endif

#ifdef DEFINE_MALLOC_STATS
#include "malloc.h"
#include <stdio.h>

void 
malloc_stats(void)
{
  struct mallinfo i;
  FILE *fp;
  
  fp = stderr;
  i = mallinfo();
  fprintf (fp, "malloc has reserved %u bytes between %p and %p\n",
	   i.arena, &__malloc_start, __malloc_end);
  fprintf (fp, "there are %u bytes free in %u chunks\n",
	   i.fordblks, i.ordblks);
  fprintf (fp, "of which %u bytes are at the end of the reserved space\n",
	   i.keepcost);
  fprintf (fp, "and %u bytes are in use.\n", i.uordblks);
}
#endif

#ifdef DEFINE_MALLOC_USABLE_SIZE
size_t 
malloc_usable_size (void *block_p)
{
  fle block = (fle)((size_t) block_p - offsetof (struct freelist_entry, next));
  return block->size - sizeof (size_t);
}
#endif

#ifdef DEFINE_MALLOPT
int
mallopt (int n, int v)
{
  (void)n; (void)v;
  return 0;
}
#endif
