Malloc

Malloc test source.

/******************************************************************************
* Copyright (c) Intel Corporation - All rights reserved.                      *
* This file is part of the LIBXS library.                                     *
*                                                                             *
* For information on the license, see the LICENSE file.                       *
* Further information: https://github.com/hfp/libxs/                          *
* SPDX-License-Identifier: BSD-3-Clause                                       *
******************************************************************************/
#include <libxs_malloc.h>

#if !defined(LIBXS_MALLOC_UPSIZE)
# define LIBXS_MALLOC_UPSIZE (2 << 20)
#endif
#if !defined(LIBXS_MALLOC_EVICTSIZE)
# define LIBXS_MALLOC_EVICTSIZE (8 * LIBXS_MALLOC_UPSIZE)
#endif
#if !defined(LIBXS_MALLOC_EVICTWARMUP)
# define LIBXS_MALLOC_EVICTWARMUP 4
#endif

#if defined(_DEBUG)
# define FPRINTF(STREAM, ...) do { fprintf(STREAM, __VA_ARGS__); } while(0)
#else
# define FPRINTF(STREAM, ...) do {} while(0)
#endif


static void* test_xmalloc(size_t size, const void* extra)
{
  LIBXS_UNUSED(extra);
  return malloc(size);
}

static void test_xfree(void* pointer, const void* extra)
{
  LIBXS_UNUSED(extra);
  free(pointer);
}


int main(void)
{
  int nerrors = 0;
  void* pool[128];
  char storage[8*sizeof(pool)/sizeof(*pool)];
  void* backup[sizeof(pool)];
  const int npool = sizeof(pool) / sizeof(*pool), nrep = 1024;
  size_t num = npool;
  int i, j;

  libxs_pmalloc_init(sizeof(storage) / num, &num, pool, storage);
  memcpy(backup, pool, sizeof(pool));

  for (i = 0; i < nrep; ++i) {
# if defined(_OPENMP)
#   pragma omp parallel for private(j) schedule(dynamic,1)
# endif
    for (j = 0; j < npool; ++j) {
      void *const p = libxs_pmalloc(pool, &num);
      LIBXS_EXPECT(NULL != p);
    }
# if defined(_OPENMP)
#   pragma omp parallel for private(j) schedule(dynamic,1)
# endif
    for (j = 0; j < npool; ++j) {
      const int k = npool - j - 1;
      void *const p = backup[k];
      libxs_pfree(p, pool, &num);
    }
    if (npool != (int)num) break;
  }
  if (npool == (int)num) {
    for (i = 0, num = 0; i < npool; ++i) {
      const void *const p = backup[i];
      for (j = 0; j < npool; ++j) {
        if (p == pool[j]) {
          ++num; break;
        }
      }
    }
    nerrors += LIBXS_DELTA(npool, (int)num);
  }
  else ++nerrors;

  if (0 == nerrors) {
    const int max_nthreads = 1, max_nactive = 1;
    const int nrep_eviction = LIBXS_MALLOC_EVICTWARMUP + 4;
    const size_t nbytes = (size_t)LIBXS_MALLOC_EVICTSIZE + LIBXS_MALLOC_UPSIZE;
    size_t prev_nmallocs = 0;
    int saw_eviction = 0;
    libxs_malloc_pool_t *mpool = libxs_malloc_pool(NULL, NULL);

    nerrors += (NULL == mpool);
    for (i = 0; i < nrep_eviction && 0 == nerrors; ++i) {
      libxs_malloc_pool_info_t pinfo;
      libxs_malloc_info_t minfo;
      void *p = NULL;

      p = libxs_malloc(mpool, 0/*nbytes*/, LIBXS_MALLOC_AUTO);
      nerrors += (NULL != p); /* zero-size must return NULL */
      libxs_free(p);

      p = libxs_malloc(mpool, nbytes, LIBXS_MALLOC_AUTO);
      if (NULL != p) {
        memset(p, 0xA5, LIBXS_MIN(nbytes, (size_t)4096));
      }
      else {
        ++nerrors; break;
      }
      if (EXIT_SUCCESS != libxs_malloc_info(p, &minfo) || minfo.size < nbytes) {
        ++nerrors; break;
      }
      if (EXIT_SUCCESS != libxs_malloc_pool_info(mpool, &pinfo) || prev_nmallocs > pinfo.nmallocs) {
        ++nerrors; break;
      }
      prev_nmallocs = pinfo.nmallocs;
      libxs_free(p);
      if (EXIT_SUCCESS != libxs_malloc_pool_info(mpool, &pinfo)) {
        ++nerrors; break;
      }
      if ((size_t)(max_nthreads * max_nactive) * LIBXS_MALLOC_EVICTWARMUP <= pinfo.nmallocs
        && pinfo.size < nbytes)
      {
        saw_eviction = 1;
      }
    }
    nerrors += (0 == prev_nmallocs);
    LIBXS_UNUSED(saw_eviction);
    libxs_free_pool(mpool);
  }

  /* Test: multiple independent pools */
  if (0 == nerrors) {
    libxs_malloc_pool_t *pool_a = libxs_malloc_pool(NULL, NULL);
    libxs_malloc_pool_t *pool_b = libxs_malloc_pool(NULL, NULL);
    void *pa, *pb;
    libxs_malloc_pool_info_t info_a, info_b;

    nerrors += (NULL == pool_a || NULL == pool_b);
    pa = libxs_malloc(pool_a, 1024, LIBXS_MALLOC_AUTO);
    pb = libxs_malloc(pool_b, 2048, LIBXS_MALLOC_AUTO);
    nerrors += (NULL == pa || NULL == pb);
    if (0 == nerrors) {
      libxs_malloc_info_t mi;
      nerrors += (EXIT_SUCCESS != libxs_malloc_info(pa, &mi) || mi.size < 1024);
      nerrors += (EXIT_SUCCESS != libxs_malloc_info(pb, &mi) || mi.size < 2048);
      nerrors += (EXIT_SUCCESS != libxs_malloc_pool_info(pool_a, &info_a));
      nerrors += (EXIT_SUCCESS != libxs_malloc_pool_info(pool_b, &info_b));
      nerrors += (1 != info_a.nactive || 1 != info_b.nactive);
    }
    libxs_free(pa);
    libxs_free(pb);
    /* after free, nactive should be 0 for both pools */
    if (0 == nerrors) {
      nerrors += (EXIT_SUCCESS != libxs_malloc_pool_info(pool_a, &info_a));
      nerrors += (EXIT_SUCCESS != libxs_malloc_pool_info(pool_b, &info_b));
      nerrors += (0 != info_a.nactive || 0 != info_b.nactive);
    }
    libxs_free_pool(pool_a);
    libxs_free_pool(pool_b);
  }

  /* Test: custom malloc/free function pointers */
  if (0 == nerrors) {
    libxs_malloc_pool_t *cpool = libxs_malloc_pool(malloc, free);
    void *p;
    libxs_malloc_info_t mi;
    libxs_malloc_pool_info_t pi;

    nerrors += (NULL == cpool);
    p = libxs_malloc(cpool, 4096, LIBXS_MALLOC_AUTO);
    nerrors += (NULL == p);
    if (NULL != p) {
      memset(p, 0xCC, 4096);
      nerrors += (EXIT_SUCCESS != libxs_malloc_info(p, &mi) || mi.size < 4096);
      nerrors += (EXIT_SUCCESS != libxs_malloc_pool_info(cpool, &pi) || 1 != pi.nactive);
      libxs_free(p);
      nerrors += (EXIT_SUCCESS != libxs_malloc_pool_info(cpool, &pi) || 0 != pi.nactive);
    }
    libxs_free_pool(cpool);
  }

  /* Test: explicit alignment (flags > 1) */
  if (0 == nerrors) {
    libxs_malloc_pool_t *apool = libxs_malloc_pool(NULL, NULL);
    void *p;
    libxs_malloc_info_t mi;

    nerrors += (NULL == apool);
    p = libxs_malloc(apool, 4096, 64);
    nerrors += (NULL == p);
    if (NULL != p) {
      nerrors += (0 != ((uintptr_t)p & 63)); /* must be 64-byte aligned */
      memset(p, 0xDD, 4096);
      nerrors += (EXIT_SUCCESS != libxs_malloc_info(p, &mi) || mi.size < 4096);
      libxs_free(p);
    }
    /* larger alignment */
    p = libxs_malloc(apool, 8192, 4096);
    nerrors += (NULL == p);
    if (NULL != p) {
      nerrors += (0 != ((uintptr_t)p & 4095)); /* must be 4096-byte aligned */
      memset(p, 0xEE, 8192);
      nerrors += (EXIT_SUCCESS != libxs_malloc_info(p, &mi) || mi.size < 8192);
      libxs_free(p);
    }
    libxs_free_pool(apool);
  }

  /* Test: LIBXS_MALLOC_NATIVE (registry-based, pointer preserved) */
  if (0 == nerrors) {
    libxs_malloc_pool_t *natpool = libxs_malloc_pool(malloc, free);
    void *p;
    libxs_malloc_info_t mi;
    libxs_malloc_pool_info_t pi;

    nerrors += (NULL == natpool);
    p = libxs_malloc(natpool, 4096, LIBXS_MALLOC_NATIVE);
    nerrors += (NULL == p);
    if (NULL != p) {
      memset(p, 0xAA, 4096);
      nerrors += (EXIT_SUCCESS != libxs_malloc_info(p, &mi) || mi.size < 4096);
      nerrors += (EXIT_SUCCESS != libxs_malloc_pool_info(natpool, &pi) || 1 != pi.nactive);
      libxs_free(p);
      nerrors += (EXIT_SUCCESS != libxs_malloc_pool_info(natpool, &pi) || 0 != pi.nactive);
    }
    /* allocate, free, re-allocate (reuse path) */
    p = libxs_malloc(natpool, 2048, LIBXS_MALLOC_NATIVE);
    nerrors += (NULL == p);
    if (NULL != p) {
      memset(p, 0xBB, 2048);
      libxs_free(p);
    }
    p = libxs_malloc(natpool, 1024, LIBXS_MALLOC_NATIVE);
    nerrors += (NULL == p);
    if (NULL != p) {
      memset(p, 0xCC, 1024);
      nerrors += (EXIT_SUCCESS != libxs_malloc_info(p, &mi) || mi.size < 1024);
      libxs_free(p);
    }
    /* grow: allocate larger than previous chunk */
    p = libxs_malloc(natpool, 8192, LIBXS_MALLOC_NATIVE);
    nerrors += (NULL == p);
    if (NULL != p) {
      memset(p, 0xDD, 8192);
      nerrors += (EXIT_SUCCESS != libxs_malloc_info(p, &mi) || mi.size < 8192);
      libxs_free(p);
    }
    /* multiple concurrent native allocations */
    { void *a, *b, *c;
      a = libxs_malloc(natpool, 512, LIBXS_MALLOC_NATIVE);
      b = libxs_malloc(natpool, 1024, LIBXS_MALLOC_NATIVE);
      c = libxs_malloc(natpool, 2048, LIBXS_MALLOC_NATIVE);
      nerrors += (NULL == a || NULL == b || NULL == c);
      nerrors += (a == b || b == c || a == c); /* must be distinct */
      if (NULL != a) memset(a, 0x11, 512);
      if (NULL != b) memset(b, 0x22, 1024);
      if (NULL != c) memset(c, 0x33, 2048);
      nerrors += (EXIT_SUCCESS != libxs_malloc_pool_info(natpool, &pi) || 3 != pi.nactive);
      libxs_free(a); libxs_free(b); libxs_free(c);
      nerrors += (EXIT_SUCCESS != libxs_malloc_pool_info(natpool, &pi) || 0 != pi.nactive);
    }
    libxs_free_pool(natpool);
  }

  /* Test: extended pool (libxs_malloc_xpool) with per-thread extra arg */
  if (0 == nerrors) {
    const int sentinel = 42;
    libxs_malloc_pool_t *xpool;
    libxs_malloc_pool_info_t pi;
    libxs_malloc_info_t mi;
    void *p;

    xpool = libxs_malloc_xpool(
      /* malloc_xfn */ test_xmalloc,
      /* free_xfn */   test_xfree,
      /* max_nthreads */ 4);
    nerrors += (NULL == xpool);

    /* set per-thread extra for this thread */
    libxs_malloc_arg(xpool, &sentinel);

    p = libxs_malloc(xpool, 2048, LIBXS_MALLOC_AUTO);
    nerrors += (NULL == p);
    if (NULL != p) {
      memset(p, 0xBB, 2048);
      nerrors += (EXIT_SUCCESS != libxs_malloc_info(p, &mi) || mi.size < 2048);
      nerrors += (EXIT_SUCCESS != libxs_malloc_pool_info(xpool, &pi) || 1 != pi.nactive);
      libxs_free(p);
      nerrors += (EXIT_SUCCESS != libxs_malloc_pool_info(xpool, &pi) || 0 != pi.nactive);
    }
    libxs_free_pool(xpool);
  }

  /* Test: libxs_malloc_xpool rejects NULL fn or zero nthreads */
  if (0 == nerrors) {
    nerrors += (NULL != libxs_malloc_xpool(NULL, test_xfree, 4));
    nerrors += (NULL != libxs_malloc_xpool(test_xmalloc, NULL, 4));
    nerrors += (NULL != libxs_malloc_xpool(test_xmalloc, test_xfree, 0));
  }

  /* Test: libxs_malloc_arg on standard pool is a no-op */
  if (0 == nerrors) {
    libxs_malloc_pool_t *spool = libxs_malloc_pool(NULL, NULL);
    int dummy = 0;
    nerrors += (NULL == spool);
    libxs_malloc_arg(spool, &dummy); /* must not crash */
    libxs_free_pool(spool);
  }

  /* Test: NULL pool uses the default pool */
  if (0 == nerrors) {
    libxs_malloc_pool_info_t pinfo;
    libxs_malloc_info_t minfo;
    void *const p = libxs_malloc(NULL, 1024, LIBXS_MALLOC_AUTO);
    nerrors += (NULL == p);
    if (NULL != p) {
      nerrors += (EXIT_SUCCESS != libxs_malloc_info(p, &minfo) || minfo.size < 1024);
      nerrors += (EXIT_SUCCESS != libxs_malloc_pool_info(NULL, &pinfo) || 1 > pinfo.nactive);
      libxs_free(p);
    }
  }

  /* Test: libxs_free(NULL) is safe */
  libxs_free(NULL);

  /* Test: libxs_free_pool(NULL) is safe */
  libxs_free_pool(NULL);

  if (0 == nerrors) {
    return EXIT_SUCCESS;
  }
  else {
    FPRINTF(stderr, "Errors: %i\n", nerrors);
    return EXIT_FAILURE;
  }
}