/*****************************************************************************\
*                                                                             *
*  Name   : memory_pool                                                       *
*  Author : Chris Koeritz                                                     *
*                                                                             *
*******************************************************************************
* Copyright (c) 1998-$now By Author.  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 2 of   *
* the License or (at your option) any later version.  This is online at:      *
*     http://www.fsf.org/copyleft/gpl.html                                    *
* Please send any updates to: fred@gruntose.com                               *
\*****************************************************************************/

// A note regarding the algorithm employed here...
//
//     "Upper Bits Bins" is a short name for the memory pooling algorithm.  
// Consider an allocation that has a size S, which is currently interpreted as
// a 32 bit number (we do not yet support larger chunks of memory).  We will
// take some number of upper bits from that number to use as our hashing bin.
// so, let's say UB (upper bits) is defined as 6.  That means that we will use
// the numerically leftmost bits in the binary number of the size as the
// bucket for storing and retrieving allocations.  Let's call the prefix bits
// PB; given a specific PB, our algorithm will store all allocations between
// (PB)00000000000000000000000000 and (PB)11111111111111111111111111 in size
// into the same bin.  The goal here is to make lookups for memory fairly fast,
// since each allocation's bin is easy to compute; all it takes is some bit
// manipulations.
//     The real implementation does the upper bit slicing in three different
// places within 32 bit numbers.  This allows us to provide more reasonable
// groupings of memory, where our re-use is done on small allocations, middling
// sized ones and very large allocations.  The bit slicing approach is defined
// below and three example models are provided.

#ifndef OMIT_MEMORY_POOL

#include "definitions.h"
#include "log_base.h"
#include "memory_pool.h"
#include "mutex.h"
#include "utility.h"

#include <stdio.h>
#include <stdlib.h>
#include <string.h>

//#define CLEAR_ALLOCATED_MEMORY
  // uncomment this to ensure that new memory gets its contents set to zero.
  // neither new nor malloc does this, but it can help finding bugs from
  // people re-using deallocated memory.

//#define MEMORY_POOL_STATISTICS
  // uncomment to enable code that analyzes how many allocations were new and
  // so forth.  this will make the object run a bit slower.

//#define MEMORY_POOL_CHECKING
  // uncomment to verify the state of the memory lists.

//#define DEBUG_MEMORY_POOL
  // uncomment for super noisy version.

//hmmm: would be super handy to have analytic hooks here so that we can track
//      addresses for memory leaks and so forth.

//hmmm: eventually we may want to provide a way to ask how much real space is
//      available in the allocation, so that we can use all that was actually
//      allocated.  realloc, as it were.

//hmmm: may need to set the limit to zero for highest bin on firmware.  make
//      sure the implementation would handle a zero limit, just in case.

////////////////////////////////////////////////////////////////////////////

// bit slicing models for upper bits bins algorithm...
//
// the parameters below specify which portions of the binary representation
// of the requested allocation size are used for hashing indices.  they also
// limit the number of chunks of memory allowed per bin.  these numbers need
// careful tuning if you intend to modify them.  together, they represent the
// maximum memory that a program can hold onto that it's not actually using.
// we recommend adjusting mainly the limit values, since the others must be
// matched properly in bit-wise ways.  any request larger than Z3_TOP is
// passed directly to the OS.
// semantics:
//   let us call the size of the allocation N.  N is examined in binary.
//   we break up N into an arbitrary number of zones, which we choose as three.
//   each zone is described by five numbers...
//     MASK is the bit mask applied to N to calculate a hash slot pattern.
//     SHIFT is the number of bits that the masked size must be shifted to
//         become a simple hash slot number.
//     BINS is the number of hash slots in a zone.  note that:
//         (N % MASK) >> SHIFT must never be larger than BINS
//     TOP is the highest number that a zone can represent; higher values must
//         be serviced by a zone with greater SHIFT.  TOP can be computed from
//         the mask, but providing it avoids a little calculation.
//     LIMIT is the number of allocations allowed per hash slot in the zone.

// implementation note:
//    the items in each bin are currently allocated as _max_size for that bin.
// this avoids gradually working up to those sizes and causing more memory
// fragementation.  plus it means a request should always be able to be granted
// if a bin has even one item in it.
//    even so, the original code that keeps the bin sorted is still in place.
// it costs very little to keep it, and would be useful in the future if other
// bin approaches are being tested out.

#define UBB_MODEL_3
  // UBB model three is the current favorite.

#ifdef UBB_MODEL_1
  // original version; too many in the low end.
  const int Z1_MASK = 0xF0,    Z1_SHIFT = 4,  Z1_BINS = 16, Z1_TOP = 0x100;
  const int Z2_MASK = 0xF000,  Z2_SHIFT = 12, Z2_BINS = 16, Z2_TOP = 0x10000;
  const int Z3_MASK = 0xF0000, Z3_SHIFT = 16, Z3_BINS = 16, Z3_TOP = 0x100000;
  #ifndef EMBEDDED_BUILD
    // parameters for general purpose computers.
    const int Z1_LIMIT = 40,  Z2_LIMIT = 14, Z3_LIMIT = 4;
  #else
    // embedded parameters are tinier.
    const int Z1_LIMIT = 10,  Z2_LIMIT = 5,  Z3_LIMIT = 1;
  #endif
#endif

#ifdef UBB_MODEL_2
  // next try, scooting the low range up a bit.
  // this one seems to be too fat and slow.
  const int Z1_MASK = 0x3C0,    Z1_SHIFT = 6,  Z1_BINS = 16, Z1_TOP = 0x400;
  const int Z2_MASK = 0x3C000,  Z2_SHIFT = 14, Z2_BINS = 16, Z2_TOP = 0x40000;
  const int Z3_MASK = 0x300000, Z3_SHIFT = 20, Z3_BINS = 4,  Z3_TOP = 0x400000;
  #ifndef EMBEDDED_BUILD
    const int Z1_LIMIT = 20,  Z2_LIMIT = 10, Z3_LIMIT = 2;
  #else
    const int Z1_LIMIT = 10,  Z2_LIMIT = 5,  Z3_LIMIT = 1;
  #endif
#endif

#ifdef UBB_MODEL_3
  // now trying to move the low range back down and cover the midrange better.
  const int Z1_MASK =   0x1C0, Z1_SHIFT =  6, Z1_BINS =  8, Z1_TOP = 0x200;
  const int Z2_MASK =  0x7800, Z2_SHIFT = 11, Z2_BINS = 16, Z2_TOP = 0x8000;
  const int Z3_MASK = 0xF8000, Z3_SHIFT = 15, Z3_BINS = 32, Z3_TOP = 0x100000;
  #ifndef EMBEDDED_BUILD
    const int Z1_LIMIT = 40,  Z2_LIMIT = 20, Z3_LIMIT = 10;
  #else
    const int Z1_LIMIT = 10,  Z2_LIMIT = 5, Z3_LIMIT = 1;
  #endif
#endif

////////////////////////////////////////////////////////////////////////////

// these methods implement our guarded, sized memory allocations.  we will
// remember the size of the allocation as the first N bytes and then have a
// sentinel byte that gives us a simple way to check that we probably did
// allocate that memory.  it would be easy for someone to intentionally
// corrupt this scheme, but more difficult to trick it accidentally.  and
// there are so many other potential memory shenanigans possible that we
// cannot worry that much about intentionally making one's own program crash.

const byte GUARDIAN = 0x3e;
  // secret code that we check to be more sure that we allocated the memory.
  // there is no surety in the world however... we might still be fooled if
  // someone is giving us chunks of garbage memory, but they can also corrupt
  // any other pointer they feel like, so this is not useful to worry about.

const int FOOLIO_POINT = sizeof(int) + 1;  // number of extra bytes.

void *allocate_hidden_sized_chunk(int size)
{
  int true_size = size + FOOLIO_POINT;
  void *to_return = malloc(true_size);
    // extra is allocated for the stored size and guardian byte.
#ifdef CLEAR_ALLOCATED_MEMORY
  memset(to_return, 0, true_size);  // clear the memory.
#endif
  *(int *)to_return = size;
  *((byte *)to_return + sizeof(int)) = GUARDIAN;
  return (void *) ((byte *)to_return + FOOLIO_POINT);
}

bool verify_created_here(void *to_check, int &size, void * &real_addr)
{
  size = 0;
  real_addr = NIL;
  if (!to_check) return false;  // no use looking at that.
  if ( *((byte *)to_check - 1) != GUARDIAN) return false;
  real_addr = (void *) ((byte *)to_check - FOOLIO_POINT);
  size = *(int *)real_addr;
  return true;
}

void really_free_memory(void *maybe_our_pointer)
{
  void *real_address = NIL;
  int true_size;
  if (!verify_created_here(maybe_our_pointer, true_size, real_address)) {
    return;  // bad news; not memory that we created.  should never happen.
  }
  free(real_address);
}

////////////////////////////////////////////////////////////////////////////

// memlink is one link in a chain of memories.  it's singly linked, so our
// algorithms have to explicitly remember the parent.

class memlink
{
public:
  void *_chunk;  // the chunk of memory held (not real address).
    // NOTE: we store the chunk as it looks to the outside world, rather than
    // using its real address.  this eliminates a bit of ambiguity in the code.
  memlink *_next;  // the next memory wrapper in the list.
  int _size;  // the size of the chunk we're holding.
};

////////////////////////////////////////////////////////////////////////////

#ifdef MEMORY_POOL_STATISTICS
  // simple stats: the methods below will tweak these numbers if memory_pool
  // statistics are enabled.  ints won't do here, due to the number of
  // operations in a long-running program easily overflowing that size.

  // this bank of statistics counts the number of times memory was treated
  // a certain way.
  double _stat_reused_memory = 0;  // this many allocations were re-used.
  double _stat_new_allocations = 0;  // this many new allocations occurred.
  double _stat_stowed_memory = 0;  // this many freed blocks were stored.
  double _stat_limit_freed = 0;  // number freed blocks dropped due to limits.
  // next bank of stats are the sizes of the memory that were stowed, etc.
  double _stat_reused_memory_size = 0;
  double _stat_new_allocations_size = 0;
  double _stat_stowed_memory_size = 0;
  double _stat_limit_freed_size = 0;
#endif

////////////////////////////////////////////////////////////////////////////

// the memory bin remembers a list of chunks of memory held in memlink objects.

class memory_bin
{
public:
  void construct(int limit, int max_size) {
    _head = NIL; _limit = limit; _max_size = max_size;
    _count = 0; _unused = NIL;
    _lock = (mutex_base *)malloc(sizeof(mutex_base));
    _lock->construct();

    // allocate limit's worth of memlinks in the unused links bin.
    for (int i = 0; i < _limit; i++) {
      memlink *new_link = (memlink *)malloc(sizeof(memlink));
      new_link->_next = _unused;
      _unused = new_link;
    }
  }
  void destruct() {
    flush_all();
    // deallocate all the unused links.
    while (_unused) {
      memlink *goner = _unused;
      _unused = _unused->_next;
      free(goner);
    }
    _lock->destruct();
    free(_lock);
  }

  void flush_all() {
    _lock->lock();
    memlink *current = _head;
    // zip down our list ripping out the nodes.
    while (current) {
      really_free_memory(current->_chunk);  // free contained memory first.
      memlink *to_whack = current;
      current = current->_next;  // set iterator to next place.
      // dump the one that's dead back in unused bin.
      to_whack->_next = _unused;
      _unused = to_whack;
    }
    _head = NIL;
    _count = 0;
    _lock->unlock();
  }

#ifdef MEMORY_POOL_CHECKING
  // the list should already be locked when calling this method.
  void locked_verify_list_order() {
    memlink *current = _head;
    while (current) {
      if (current->_next && (current->_size > current->_next->_size) ) {
        printf("Serious error!  ordering of memory lists is wrong.\n");
        break;
      }
      current = current->_next;
    }
  }
#endif

  void *provide_memory(int size_needed) {
#ifdef MEMORY_POOL_CHECKING
    if (size_needed > _max_size) {
      printf("got a request for %d but our max size is %d!\n", size_needed,
          _max_size);
    }
#endif
    _lock->lock();
    // search the bin and hand out first chunk that meets size requirement.
    memlink *current = _head;  // current will scoot through the list.
    memlink *previous = NIL;  // previous remembers the parent node.
    while (current) {
      if (current->_size >= size_needed) {
        // unlink this one and hand it out.
        void *to_return = current->_chunk;
        if (!previous) {
          // this is the head we're modifying.
          _head = current->_next;
        } else {
          // not the head, so there was a valid previous element.
          previous->_next = current->_next;
        }
        // dump it back in the unused bin.
        current->_next = _unused;
        _unused = current;
        _count--;
#ifdef DEBUG_MEMORY_POOL
        printf("found an existing chunk to return.\n");
#endif
#ifdef MEMORY_POOL_STATISTICS
        _stat_reused_memory += 1.0;
        _stat_reused_memory_size += size_needed;
//hmmm: an over-allocation stat would be nice also.
#endif
#ifdef MEMORY_POOL_CHECKING
        locked_verify_list_order();
#endif
        _lock->unlock();
        return to_return;
      }
      // the current node isn't big enough; jump to next node.
      previous = current;
      current = current->_next;
    }
    _lock->unlock();
    return NIL;  // nothing suitable exists.
  }

  void stow_memory(void *to_stow, int chunk_size) {
    _lock->lock();

    // if we're over-limit for this bin, destroy a chunk for real.
    if (_count >= _limit) {
#ifdef DEBUG_MEMORY_POOL
      printf("freeing memory since over limit.\n");
#endif
      // we consider the head expendable, since it's the smallest chunk that
      // we have.  we like to make the memory in a bin more valuable as we run,
      // meaning that the trend is towards being big enough for any memory need
      // in that bin.  if we don't do that, a bin could fill up with memory
      // that's too small for most requests, which might make the memory_pool
      // trend towards uselessness.
      memlink *goner = _head;
      if (_head) {
        // if there's a head at all, point at the next element, unless we
        // see that the head is larger than the item to_stow.
        if (_head->_size >= chunk_size) {
          // we'd actually be tossing a bigger chunk instead of dropping
          // this puny one.  so, we'll just drop the punier of the two.
          goner = NIL;
          really_free_memory(to_stow);
#ifdef MEMORY_POOL_STATISTICS
          _stat_limit_freed += 1.0;
          _stat_limit_freed_size += chunk_size;
#endif
          _lock->unlock();
          return;
        } else {
          _head = _head->_next;
        }
      }

      if (goner) {
        // if we had something to remove, which we always should if we're
        // over the limit, then we'll clean it up now.
#ifdef MEMORY_POOL_STATISTICS
        _stat_limit_freed += 1.0;
        _stat_limit_freed_size += goner->_size;
#endif
        really_free_memory(goner->_chunk);  // toss the held memory.
        // dump the goner back in the unused bin.
        goner->_next = _unused;
        _unused = goner;
      }
#ifdef MEMORY_POOL_CHECKING
      locked_verify_list_order();
#endif
    }

    // get the new wrapper from the unused bin.
    memlink *new_guy = _unused;
    if (!new_guy) {
      printf("drastic error!  underflow in unused bin, count is erroneous!\n");
#ifdef DEBUG_MEMORY_POOL
      throw "deadly_error";  // croak now.
#endif
      new_guy = (memlink *)malloc(sizeof(memlink));
        // disaster remediation; should never be necessary.
      new_guy->_next = NIL;
    }
    _unused = new_guy->_next;
    new_guy->_next = NIL;
    _count++;  // we're planning on inserting it and know of no reason not to.
#ifdef MEMORY_POOL_STATISTICS
    _stat_stowed_memory += 1.0;
    _stat_stowed_memory_size += chunk_size;
#endif
    new_guy->_chunk = to_stow;
    new_guy->_size = chunk_size;
    // we want to store it sorted, so just zoom down tree until stowguy is a
    // smaller chunk than the current chunk and splice it in between that
    // chunk and next one.
    memlink *current = _head;
    memlink *previous = NIL;
    while (current) {
      if (current->_size >= chunk_size) {
        // we can insert the memory here because everything after the
        // current node will be bigger than or equal to it.
        if (!previous) {
          // we're inserting it before the head element.
          _head = new_guy;
#ifdef DEBUG_MEMORY_POOL
          printf("not-biggest inserting at head.\n");
#endif
        } else {
          // normal insertion where we have a parent.
#ifdef DEBUG_MEMORY_POOL
          printf("not-biggest inserting in middle.\n");
#endif
          previous->_next = new_guy;
        }
        new_guy->_next = current;          
#ifdef MEMORY_POOL_CHECKING
        locked_verify_list_order();
#endif
        _lock->unlock();
        return;
      }
      current = current->_next;
    }
    // if we got to here, the stowguy is bigger than any existing chunk.
    if (!previous) {
      // this in fact is the only element since the list was empty.
#ifdef DEBUG_MEMORY_POOL
      printf("biggest inserting at head.\n");
#endif
      _head = new_guy;
    } else {
#ifdef DEBUG_MEMORY_POOL
      printf("biggest inserting in middle.\n");
#endif
      previous->_next = new_guy;
    }
    new_guy->_next = current;          
#ifdef MEMORY_POOL_CHECKING
    locked_verify_list_order();
#endif
    _lock->unlock();
  }

  inline int max_size() const { return _max_size; }

private:
  memlink *_head;  // our first, if any, item.
  mutex_base *_lock;  // protects our bin from concurrent access.
  int _limit;  // how many we're allowed in this list.
  int _max_size;  // largest chunk that this bin would ever be asked for.
  int _count;  // current count of items held.
  memlink *_unused;  // our list of unused memory links.
};

////////////////////////////////////////////////////////////////////////////

//hmmm: will we want zones with variable limits per bin at some point?

class memory_bin_list
{
public:
  void construct(u_int hash_mask, int shift_by, int num_slots, int limit) {
    _hash_mask = hash_mask;
    _shift_by = shift_by;
    _num_slots = num_slots;
    _bins = (memory_bin *)malloc(num_slots * sizeof(memory_bin));
    for (int i = 0; i < num_slots; i++) {
      u_int max_size = ( (i + 1) << _shift_by) - 1;
      _bins[i].construct(limit, max_size);
    }
  }

  void destruct() {
    // destroy each bin in our list.
    for (int i = 0; i < _num_slots; i++) {
      _bins[i].destruct();
    }
    free(_bins);
    _bins = NIL;
  }

  inline void flush_all() {
    for (int i = 0; i < _num_slots; i++) _bins[i].flush_all();
  }

  int compute_slot(int size) {
    u_int slot = (u_int(size) & _hash_mask) >> _shift_by;
#ifdef DEBUG_MEMORY_POOL
    printf("size %x, mask %x, masked %x, shiftby %d, shifted %d\n",
        size, _hash_mask, u_int(size) & _hash_mask, _shift_by, slot);
#endif
    if (slot >= u_int(_num_slots)) {
      // this condition should never happen in a valid UBB model.
#ifdef DEBUG_MEMORY_POOL
      printf("computed crazy erroneous slot number %d for %d available.\n",
          slot, _num_slots);
#endif
      slot = _num_slots - 1;  // try to repair logic error.
    }
    return slot;
  }

  void *provide_memory(int size_needed) {
    // slice and dice number to get appropriate hash bin.
    int slot = compute_slot(size_needed);
#ifdef DEBUG_MEMORY_POOL
    printf("looking in slot %d for memory.\n", slot);
#endif
    void *to_return = _bins[slot].provide_memory(size_needed);
    if (to_return) return to_return;  // found one in that bin.
    // if not found in normal bin, try next higher bin's first element.
    slot++;
    // make sure we're not past the last slot.
    if (slot < _num_slots) {
      to_return = _bins[slot].provide_memory(size_needed);
    }
    if (!to_return) {
      // if still no match, create a new chunk of memory.
      slot--;  // repair the jump to a higher bin that we attempted above.
#ifdef MEMORY_POOL_STATISTICS
      _stat_new_allocations += 1.0;
      _stat_new_allocations_size += size_needed;
#endif

      // this would provide slower growth:
      //     to_return = allocate_hidden_sized_chunk(size_needed);
      // instead, we use the following to avoid ever having a piece of memory
      // that's not big enough in our bin.
      to_return = allocate_hidden_sized_chunk(_bins[slot].max_size());

#ifdef MEMORY_POOL_CHECKING
      if (_bins[slot].max_size() < size_needed) {
        printf("max size %d is smaller than size needed %d!\n",
            _bins[slot].max_size(), size_needed); 
      }
#endif

#ifdef DEBUG_MEMORY_POOL
      printf("allocated a new chunk to return.\n");
#endif
    }
    return to_return;
  }

  void stow_memory(void *to_stow, int chunk_size) {
    // slice and dice to get bin number.
    int slot = compute_slot(chunk_size);
#ifdef DEBUG_MEMORY_POOL
    printf("stowing mem in slot %d.\n", slot);
#endif
    // make bin stow it for later.
    _bins[slot].stow_memory(to_stow, chunk_size);
  }

private:
  memory_bin *_bins;  // each bin manages one range of chunk sizes.
  u_int _hash_mask;
  u_int _shift_by;
  int _num_slots;
};

////////////////////////////////////////////////////////////////////////////

class ubb_zone_matcher
{
public:
  void construct() {
    // create all three zones with appropriate parameters.
    _first_zone = (memory_bin_list *)malloc(sizeof(memory_bin_list));
    _first_zone ->construct(Z1_MASK, Z1_SHIFT, Z1_BINS, Z1_LIMIT);
    _second_zone = (memory_bin_list *)malloc(sizeof(memory_bin_list));
    _second_zone->construct(Z2_MASK, Z2_SHIFT, Z2_BINS, Z2_LIMIT);
    _third_zone = (memory_bin_list *)malloc(sizeof(memory_bin_list));
    _third_zone ->construct(Z3_MASK, Z3_SHIFT, Z3_BINS, Z3_LIMIT);
  }

  void destruct() {
    _first_zone->destruct();
    free(_first_zone);
    _second_zone->destruct();
    free(_second_zone);
    _third_zone->destruct();
    free(_third_zone);
  }

  inline void flush_all() {
    _first_zone ->flush_all();
    _second_zone->flush_all();
    _third_zone ->flush_all();
  }

  void *provide_memory(int size_needed) {
//    if (startup_code::program_is_dying()) return malloc(size_needed);
#ifdef DEBUG_MEMORY_POOL
    printf("using pooled memory.\n");
#endif
    // forward the request to the proper zone.
    if (size_needed < Z1_TOP)
      return _first_zone->provide_memory(size_needed);
    if (size_needed < Z2_TOP)
      return _second_zone->provide_memory(size_needed);
    if (size_needed < Z3_TOP)
      return _third_zone->provide_memory(size_needed);
    // if too big overall, just do an OS allocation.
    return malloc(size_needed);
  }

  void stow_memory(void *to_stow) {
    if (!to_stow) return;  // duhhh.
    // check that we allocated it.
    void *real_addr;
    int chunk_size;
    bool ours = verify_created_here(to_stow, chunk_size, real_addr);
    // verify we created it.  if not, just free it.
    if (!ours) {
      // we have no responsibility here, aside from getting rid of the chunk.
      free(to_stow);
      return;
    } else if (startup_code::program_is_dying()) {
      // program is exiting, so there's no point in storing anything.
      really_free_memory(to_stow);
      return;
    }

    // find zone it belongs to by checking size and hand over responsibility.
    if (chunk_size < Z1_TOP)
      return _first_zone->stow_memory(to_stow, chunk_size);
    if (chunk_size < Z2_TOP)
      return _second_zone->stow_memory(to_stow, chunk_size);
    return _third_zone->stow_memory(to_stow, chunk_size);
  }

  // this is fairly resource intensive, so don't dump the state out that often.
  // also, it certainly may not and must not be used during allocation or
  // deallocation methods in this class itself.
  istring text_form() {
    istring to_return;
#ifdef MEMORY_POOL_STATISTICS
    to_return += "Memory State at ";
    to_return += timestamp(false, true);
    to_return += log_base::platform_ending();
    to_return += istring('-', 78);
    to_return += log_base::platform_ending();
    to_return += "Measurements taken across entire program runtime:";
    to_return += log_base::platform_ending();
    to_return += isprintf("  %f new and %f re-used allocations.",
        _stat_new_allocations, _stat_reused_memory);
    to_return += log_base::platform_ending();
    to_return += isprintf("  %f new Mbytes and %f re-used Mbytes.",
        _stat_new_allocations_size / MEGABYTE,
        _stat_reused_memory_size / MEGABYTE);
    to_return += log_base::platform_ending();
    to_return += isprintf("  %f freed and %f cached deallocations.",
        _stat_limit_freed, _stat_stowed_memory);
    to_return += log_base::platform_ending();
    to_return += isprintf("  %f freed Mbytes and %f cached Mbytes.",
        _stat_limit_freed_size / MEGABYTE,
        _stat_stowed_memory_size / MEGABYTE);
    to_return += log_base::platform_ending();

//hmmm: add reporting of how many chunks are sitting in each queue, what
//      their average size is, how much memory is in use total, what memory
//      is used per bin, per slot, per zone, etc.

#endif
    return to_return;
  }

private:
  memory_bin_list *_first_zone;
  memory_bin_list *_second_zone;
  memory_bin_list *_third_zone;
};

////////////////////////////////////////////////////////////////////////////

void memory_pool::construct()
{
  _matcher = (ubb_zone_matcher *)malloc(sizeof(ubb_zone_matcher));
  _matcher->construct();
}

void memory_pool::destruct() { _matcher->destruct(); free(_matcher); }

void memory_pool::stow_memory(void *ptr) { return _matcher->stow_memory(ptr); }

void memory_pool::flush_all() { _matcher->flush_all(); }

void *memory_pool::provide_memory(size_t size)
{ return _matcher->provide_memory(size); }

istring memory_pool::text_form() { return _matcher->text_form(); }

#endif  // omit memory pool.

