/* tmalloc.c (emx+gcc) -- Copyright (c) 1995 by Eberhard Mattes */

#include <stdlib.h>
#include <os2thunk.h>
#include "tmalloc.h"

typedef unsigned long ULONG;

void *_tmalloc (size_t size)
{
  void *p, *u;
  struct tmh *h;
  size_t parent_size;
  unsigned attempt;
  char ok;

  if (size <= 0x8000)
    {
      /* If the size is reasonable small, try to just allocate a block
         big enough.  If we are lucky, the block does not cross a
         64KByte boundary. */

      p = malloc (size + TM_OVERHEAD);
      if (p == NULL)
        return NULL;
      h = p; u = TM_USER (h);
      if (_THUNK_PTR_SIZE_OK (u, size))
        {
          h->parent = p;
          h->size = size;
          return u;
        }
      free (p);
    }

  /* Compute the size of a block which can hold SIZE bytes without
     crossing a 64KByte boundary, however the block gets aligned.  We
     allocate extra SIZE or 0x10000 bytes, whatever is smaller. */

  if (size < 0x10000)
    parent_size = 2 * size + TM_OVERHEAD;
  else
    parent_size = size + 0x10000 + TM_OVERHEAD;

  /* Make two attempts.  The second attempt allocates another 0x10000
     bytes by increasing parent_size. */

  for (attempt = 0;; ++attempt)
    {
      /* Check for overflow. */

      if (parent_size < size)
        return NULL;

      /* Allocate the parent block. */

      p = malloc (parent_size);
      if (p == NULL)
        return NULL;

      /* If the first SIZE bytes (after our header) of the block don't
         cross a 64KByte boundary, we use SIZE bytes (plus our header)
         at the beginning of the block. */

      h = p; u = TM_USER (h);
      if (size < 0x10000)
        ok = _THUNK_PTR_SIZE_OK (u, size);
      else
        ok = (((unsigned long)u & 0xffff) == 0);
      if (ok)
        {
          h->parent = p;
          h->size = size;
          return u;
        }

      /* Skip to the (attempt+1)th 64KByte boundary for the user block. */

      u = (void *)(((unsigned long)p + (attempt + 1) * 0x10000) & ~0xffff);

      if ((char *)u - (char *)p + size > parent_size)
        abort ();

      /* Use that user block if there is enough space for the
         header. */

      if ((char *)u - (char *)p >= TM_OVERHEAD)
        {
          h = TM_HEADER (u);
          h->parent = p;
          h->size = size;
          return u;
        }

      /* In the first pass, there may be not enough space for
         TM_OVERHEAD bytes just preceding the first 64KByte boundary.
         Retry by allocating an additional 0x10000 bytes to ensure the
         existence of a suitable 64KByte boundary. */

      if (attempt >= 1)
        abort ();
      free (p);
      parent_size += 0x10000;
    }
}
