/* ztfm.c: Transforms test
 *
 * $Id$
 * Copyright (c) 2011-2022 Ravenbrook Limited.  See end of file for license.
 *
 * .overview: This test creates data structures and then applies MPS
 * transforms (see design.mps.transform) to them and verifies the
 * result.  It uses various checksums to verify that the structures
 * are equivalent before and after the transform.
 *
 * .issues: TODO: This test has issues and needs refactoring:
 *
 * - GitHub issue #242 <https://github.com/Ravenbrook/mps/issues/242>
 *   "Testing of transforms is unclear and overengineered"
 *
 * - GitHub issue #243 <https://github.com/Ravenbrook/mps/issues/243>
 *   "ztfm.c fails to meet its requirement to test transforms on all
 *   platforms"
 */

#include "fmtdy.h"
#include "fmtdytst.h"
#include "mps.h"
#include "mpsavm.h"
#include "mpscamc.h"
#include "mpslib.h"
#include "testlib.h"

#include <stdio.h> /* printf */

/* Hacky progress output with switches. */
/* TODO: Tidy this up by extending testlib if necessary. */
#define progressf(args) \
    printf args;
#define Xprogressf(args) \
    do{if(0)printf args;}while(FALSE)

/* testChain -- generation parameters for the test */
#define genCOUNT 2
static mps_gen_param_s testChain[genCOUNT] = {
  { 100, 0.85 }, { 170, 0.45 } };


/* myroot -- arrays of references that are the root */
#define myrootAmbigCOUNT 30000
static void *myrootAmbig[myrootAmbigCOUNT];
#define myrootExactCOUNT 1000000
static void *myrootExact[myrootExactCOUNT];

static mps_root_t root_stackreg;
static void *stack_start;
static mps_thr_t stack_thr;


/* ===========  Transform ==============*/
static void get(mps_arena_t arena);

static ulongest_t serial = 0;


/* Tree nodes
 *
 * The node structure is punned with a Dylan vector.  The first two
 * fields map to the Dylan wrapper and vector length.  The fields with
 * suffix "dyi" will be tagged as Dylan integers.  TODO: Fix this
 * somewhat unsafe punning.
 *
 * TODO: Is this actually a tree or a graph?
 *
 * To make a node:
 *   - use next unique serial;
 *   - choose an id (eg. next in sequence);
 *   - choose a version (eg. 0).
 *
 * To make a New node from (or in connection with) an Old:
 *   - use next unique serial;
 *   - copy id of Old;
 *   - ver = ver(Old) + 1.
 *
 * To invalidate an Old node, when adding an OldNew pair for it:
 *   - ver = -1
 */

struct node_t {
  mps_word_t     word0; /* Dylan wrapper pointer */
  mps_word_t     word1; /* Dylan vector length */
  mps_word_t     serial_dyi;  /* unique across every node ever */
  mps_word_t     id_dyi;  /* .id: replacement nodes copy this */
  mps_word_t     ver_dyi;  /* .version: distinguish new from old */
  struct node_t  *left;
  struct node_t  *right;
  mps_word_t     tour_dyi;  /* latest tour that visited this node */
  mps_word_t     tourIdHash_dyi;  /* hash of node ids, computed last tour */
};


/* Tour -- a every node in the world calculating report with hash
 *
 * A tour starts with the node at world[0], tours the graph reachable
 * from it, then any further bits of graph reachable from world[1],
 * and so on.
 *
 * As it does so, the tour computes a tourReport, characterising the
 * state of everything reachable (from world) in the form a few numbers.
 *
 * Each tour explores the subgraph rooted at each node exactly once.
 * That is: if the tour re-encounters a node already visited on that
 * tour, it uses the values already computed for that node.
 *
 * The tourIdHash deliberately depends on the order in which nodes are
 * encountered.  Therefore, the tourIdHash of a tour depends on the
 * entirety of the state of the world and all the world-reachable nodes.
 *
 * The tourVerSum, being a simple sum, does not depend on the order of
 * visiting.
 */

/* Maximum count of node versions.  TODO: Should not be an enum. */
enum {
  cVer = 10
};

typedef struct tourReportStruct {
  ulongest_t  tour;  /* tour serial */
  ulongest_t  tourIdHash;  /* hash of node ids, computed last tour */
  ulongest_t  acNodesVer[cVer];  /* count of nodes of each Ver */
} *tourReport;

static void tour_subgraph(tourReport tr_o, struct node_t *node);

static ulongest_t tourSerial = 0;

static void tourWorld(tourReport tr_o, mps_addr_t *world, ulongest_t countWorld)
{
  ulongest_t i;

  tourSerial += 1;
  tr_o->tour = tourSerial;
  tr_o->tourIdHash = 0;
  for(i = 0; i < cVer; i++) {
    tr_o->acNodesVer[i] = 0;
  }
  Xprogressf(( "[tour %"SCNuLONGEST"] BEGIN, world: %p, countWorld: %"SCNuLONGEST"\n",
    tourSerial, (void *)world, countWorld));

  for(i = 0; i < countWorld; i++) {
    struct node_t *node;
    node = (struct node_t *)world[i];
    tour_subgraph(tr_o, node);
    tr_o->tourIdHash += i * (node ? DYI_INT(node->tourIdHash_dyi) : 0);
  }
}

static void tour_subgraph(tourReport tr_o, struct node_t *node)
{
  ulongest_t tour;
  ulongest_t ver;
  ulongest_t id;
  struct node_t *left;
  struct node_t *right;
  ulongest_t tourIdHashLeft;
  ulongest_t tourIdHashRight;
  ulongest_t tourIdHash;

  Insist(tr_o != NULL);

  /* node == NULL is permitted */
  if(node == NULL)
    return;

  tour = tr_o->tour;
  if(DYI_INT(node->tour_dyi) == tour)
    return;  /* already visited */

  /* this is a newly discovered node */
  Insist(DYI_INT(node->tour_dyi) < tour);

  /* mark as visited */
  node->tour_dyi = INT_DYI(tour);

  /* 'local' idHash = id, used for any re-encounters while computing the computed idHash */
  node->tourIdHash_dyi = node->id_dyi;

  /* record this node in the array of ver counts */
  ver = DYI_INT(node->ver_dyi);
  Insist(ver < cVer);
  tr_o->acNodesVer[ver] += 1;

  /* tour the subgraphs (NULL is permitted) */
  left = node->left;
  right = node->right;
  tour_subgraph(tr_o, left);
  tour_subgraph(tr_o, right);

  /* computed idHash of subgraph at this node */
  id = DYI_INT(node->id_dyi);
  tourIdHashLeft = left ? DYI_INT(left->tourIdHash_dyi) : 0;
  tourIdHashRight = right ? DYI_INT(right->tourIdHash_dyi) : 0;
  tourIdHash = (13*id + 17*tourIdHashLeft + 19*tourIdHashRight) & DYLAN_UINT_MASK;
  Insist(tourIdHash <= DYLAN_UINT_MAX);
  node->tourIdHash_dyi = INT_DYI(tourIdHash);

  Insist(DYI_INT(node->tour_dyi) == tour);
  Xprogressf(( "[tour %"SCNuLONGEST"] new completed node: %p, ver: %"SCNuLONGEST", tourIdHash: %"SCNuLONGEST"\n",
    tour, (void*)node, ver, tourIdHash ));
}

static struct tourReportStruct trBefore;
static struct tourReportStruct trAfter;

static void before(mps_addr_t *world, ulongest_t countWorld)
{
  tourWorld(&trBefore, world, countWorld);
}

static void after(mps_addr_t *world, ulongest_t countWorld,
  ulongest_t verOld,
  longest_t deltaCVerOld,
  ulongest_t verNew,
  longest_t deltaCVerNew)
{
  longest_t dCVerOld;
  longest_t dCVerNew;

  tourWorld(&trAfter, world, countWorld);

  dCVerOld = ((long)trAfter.acNodesVer[verOld] - (long)trBefore.acNodesVer[verOld]);
  dCVerNew = ((long)trAfter.acNodesVer[verNew] - (long)trBefore.acNodesVer[verNew]);

  progressf(("tourWorld: (%"PRIuLONGEST"  %"PRIuLONGEST":%"PRIuLONGEST"/%"PRIuLONGEST":%"PRIuLONGEST") -> (%"PRIuLONGEST"  %"PRIuLONGEST":%+"PRIdLONGEST"/%"PRIuLONGEST":%+"PRIdLONGEST"), %s\n",
    trBefore.tourIdHash,
    verOld,
    trBefore.acNodesVer[verOld],
    verNew,
    trBefore.acNodesVer[verNew],

    trAfter.tourIdHash,
    verOld,
    dCVerOld,
    verNew,
    dCVerNew,

    trBefore.tourIdHash == trAfter.tourIdHash ? "same" : "XXXXX DIFFERENT XXXXX"
  ));
  Insist(trBefore.tourIdHash == trAfter.tourIdHash);
  Insist(dCVerOld == deltaCVerOld);
  Insist(dCVerNew == deltaCVerNew);
}


static mps_res_t mps_arena_transform_objects_list(mps_bool_t *transform_done_o,
                                                  mps_arena_t mps_arena,
                                                  mps_addr_t  *old_list,
                                                  size_t      old_list_count,
                                                  mps_addr_t  *new_list,
                                                  size_t      new_list_count)
{
  mps_res_t res;
  mps_transform_t transform;
  mps_bool_t applied = FALSE;

  Insist(old_list_count == new_list_count);

  res = mps_transform_create(&transform, mps_arena);
  if(res == MPS_RES_OK) {
    /* We have a transform */
    res = mps_transform_add_oldnew(transform, old_list, new_list, old_list_count);
    if(res == MPS_RES_OK) {
      res = mps_transform_apply(&applied, transform);
    }
    Insist(!applied || res == MPS_RES_OK);
    mps_transform_destroy(transform);
  }

  /* Always set *transform_done_o (even if there is also a non-ResOK */
  /* return code): it is a status report, not a material return. */
  *transform_done_o = applied;
  return res;
}

static void Transform(mps_arena_t arena, mps_ap_t ap)
{
  ulongest_t i;
  ulongest_t keepCount = 0;
  mps_word_t v;
  struct node_t *node;
  mps_res_t res;
  mps_bool_t transform_done;
  ulongest_t old, new;
  ulongest_t perset;

  mps_arena_park(arena);

  {
    /* Test with sets of pre-built nodes, a known distance apart.
     *
     * This gives control over whether new nodes are on the same
     * segment as the olds or not.
     */

    ulongest_t iPerset;
    ulongest_t aPerset[] = {0, 1, 1, 10, 10, 1000, 1000};
    ulongest_t cPerset = NELEMS(aPerset);
    ulongest_t stepPerset;
    ulongest_t countWorld = 0;

    /* randomize the order of set sizes from aPerset */
    stepPerset = 1 + (rnd() % (cPerset - 1));

    progressf(("INT_DYI(1): %"PRIuLONGEST"; DYI_INT(5): %"PRIuLONGEST"\n",
              (ulongest_t)INT_DYI(1), (ulongest_t)DYI_INT(5) ));
    progressf(("Will make and transform sets of old nodes into new nodes.  Set sizes: "));
    for(iPerset = stepPerset;
        aPerset[iPerset] != 0;
        iPerset = (iPerset + stepPerset) % cPerset) {
      countWorld += aPerset[iPerset] * 2;  /* 2: old + new */
      progressf(("%"PRIuLONGEST", ", aPerset[iPerset]));
    }
    progressf(("total: %"PRIuLONGEST".\n", countWorld));
    Insist(countWorld <= myrootExactCOUNT);

    keepCount = 0;

    for(iPerset = stepPerset;
        aPerset[iPerset] != 0;
        iPerset = (iPerset + stepPerset) % cPerset) {
      ulongest_t j;
      ulongest_t first;
      ulongest_t skip;
      ulongest_t count;

      perset = aPerset[iPerset];
      first = keepCount;
      skip = 0;
      count = perset;
      progressf(("perset: %"PRIuLONGEST", first: %"PRIuLONGEST"\n", perset, first));

      /* Make a set of olds, and a set of news */
      for(j = 0; j < 2 * perset; j++) {
        ulongest_t slots = (sizeof(struct node_t) / sizeof(mps_word_t)) - 2;
        /* make_dylan_vector: fills slots with INT_DYI(0) */
        die(make_dylan_vector(&v, ap, slots), "make_dylan_vector");
        node = (struct node_t *)v;
        node->serial_dyi = INT_DYI(serial++);
        node->id_dyi = INT_DYI(j % perset);
        node->ver_dyi = INT_DYI(1 + (j >= perset));
        node->left = NULL;
        node->right = NULL;
        node->tour_dyi = INT_DYI(tourSerial);
        node->tourIdHash_dyi = INT_DYI(0);
        myrootExact[keepCount++ % myrootExactCOUNT] = (void*)v;
        get(arena);
        /*printf("Object %"PRIuLONGEST" at %p.\n", keepCount, (void*)v);*/
      }
      v = 0;

      /* >=10? pick subset */
      if(perset >= 10) {
        /* subset of [first..first+perset) */

        skip = (rnd() % (2 * perset));
        if(skip > (perset - 1))
          skip = 0;

        count = 1 + rnd() % (2 * (perset - skip));
        if(skip + count > perset)
          count = perset - skip;

        Insist(skip < perset);
        Insist(count >= 1);
        Insist(skip + count <= perset);
      }

      /* >=10? sometimes build tree */
      if(perset >= 10 && count >= 4 && rnd() % 2 == 0) {
        void **oldNodes = &myrootExact[first + skip];
        void **newNodes = &myrootExact[first + skip + perset];
        progressf(("Building tree in %"PRIuLONGEST" nodes.\n", count));
        for(j = 1; (2 * j) + 1 < count; j++) {
          /* You might be tempted to lift some of these gnarly casts
             into local variables, but if you do you will probably
             create ambiguous references in stack slots and prevent
             the transform from succeeding, as observed in Git commit
             a7ebcbdf0. */
          ((struct node_t *)oldNodes[j])->left = oldNodes[2 * j];
          ((struct node_t *)oldNodes[j])->right = oldNodes[(2 * j) + 1];
          ((struct node_t *)newNodes[j])->left = newNodes[2 * j];
          ((struct node_t *)newNodes[j])->right = newNodes[(2 * j) + 1];
        }
      }

      /* transform {count} olds into {count} news */
      before(myrootExact, countWorld);
      /* after(myrootExact, countWorld, 1, 0, 2, 0); */
      progressf(("Transform [%"PRIuLONGEST"..%"PRIuLONGEST") to [%"PRIuLONGEST"..%"PRIuLONGEST").\n",
        first + skip, first + skip + count, first + skip + perset, first + skip + count + perset));
      res = mps_arena_transform_objects_list(&transform_done, arena,
        &myrootExact[first + skip], count,
        &myrootExact[first + skip + perset], count);
      Insist(res == MPS_RES_OK);
      Insist(transform_done);
      /* Olds decrease; news were in world already so don't increase. */
      after(myrootExact, countWorld, 1, -(longest_t)count, 2, 0);
    }
  }

  {
    /* Transforming in various situations
     *
     * First, make two sets of 1024 nodes.
     */

    perset = 1024;
    Insist(2*perset < myrootExactCOUNT);
    for(keepCount = 0; keepCount < 2*perset; keepCount++) {
      ulongest_t slots = (sizeof(struct node_t) / sizeof(mps_word_t)) - 2;
      /* make_dylan_vector: fills slots with INT_DYI(0) */
      die(make_dylan_vector(&v, ap, slots), "make_dylan_vector");
      node = (struct node_t *)v;
      node->serial_dyi = INT_DYI(serial++);
      node->id_dyi = INT_DYI(keepCount % perset);
      node->ver_dyi = INT_DYI(1 + (keepCount >= perset));
      node->left = NULL;
      node->right = NULL;
      myrootExact[keepCount % myrootExactCOUNT] = (void*)v;
      get(arena);
      /* printf("Object %u at %p.\n", keepCount, (void*)v); */
    }
    v = 0;


    /* Functions before() and after() checksum the world, and verify
     * that the expected transform occurred.
     */
    before(myrootExact, perset);
    after(myrootExact, perset, 1, 0, 2, 0);

    /* Don't transform node 0: its ref coincides with a segbase, so
     * there are probably ambiguous refs to it on the stack.
     * Don't transform last node either: this test code may leave an
     * ambiguous reference to it on the stack.
     */

    /* Refs in root */
    /* ============ */
    old = 1;
    new = 1 + perset;
    Insist(myrootExact[old] != myrootExact[new]);
    before(myrootExact, perset);
    res = mps_arena_transform_objects_list(&transform_done, arena, &myrootExact[old], 1, &myrootExact[new], 1);
    Insist(res == MPS_RES_OK);
    Insist(transform_done);
    Insist(myrootExact[old] == myrootExact[new]);
    after(myrootExact, perset, 1, -1, 2, +1);

    /* Refs in root: ambiguous ref causes failure */
    /* ========================================== */
    old = 2;
    new = 2 + perset;
    Insist(myrootExact[old] != myrootExact[new]);
    /* Make an ambiguous reference.  This must make the transform fail. */
    myrootAmbig[1] = myrootExact[old];
    before(myrootExact, perset);
    res = mps_arena_transform_objects_list(&transform_done, arena, &myrootExact[old], 1, &myrootExact[new], 1);
    Insist(res == MPS_RES_OK);
    Insist(!transform_done);
    Insist(myrootExact[old] != myrootExact[new]);
    after(myrootExact, perset, 1, 0, 2, 0);

    /* Ref in an object */
    /* ================ */
    old = 3;
    new = 3 + perset;
    node = myrootExact[4];
    progressf(("node: %p\n", (void *)node));
    node->left = myrootExact[old];
    Insist(myrootExact[old] != myrootExact[new]);
    before(myrootExact, perset);
    res = mps_arena_transform_objects_list(&transform_done, arena, &myrootExact[old], 1, &myrootExact[new], 1);
    Insist(res == MPS_RES_OK);
    Insist(transform_done);
    Insist(myrootExact[old] == myrootExact[new]);
    after(myrootExact, perset, 1, -1, 2, +1);
  }

  {
    /* Tests with mps_transform_t
     *
     * **** USES OBJECTS CREATED IN PREVIOUS TEST GROUP ****
     */

    mps_transform_t t1;
    mps_transform_t t2;
    mps_bool_t applied = FALSE;
    ulongest_t k, l;
    mps_addr_t nullref1 = NULL;
    mps_addr_t nullref2 = NULL;

    k = 9;  /* start with this object (in set of 1024) */

    /* Destroy */
    before(myrootExact, perset);
    res = mps_transform_create(&t1, arena);
    Insist(res == MPS_RES_OK);
    mps_transform_destroy(t1);
    t1 = NULL;

    /* Empty (no add) */
    before(myrootExact, perset);
    res = mps_transform_create(&t1, arena);
    Insist(res == MPS_RES_OK);
    res = mps_transform_apply(&applied, t1);
    Insist(res == MPS_RES_OK);
    Insist(applied);
    mps_transform_destroy(t1);
    t1 = NULL;
    after(myrootExact, perset, 1, 0, 2, 0);

    /* Identity-transform */
    before(myrootExact, perset);
    res = mps_transform_create(&t1, arena);
    Insist(res == MPS_RES_OK);
    for(l = k + 4; k < l; k++) {
      mps_transform_add_oldnew(t1, &myrootExact[k], &myrootExact[k], 1);
    }
    mps_transform_add_oldnew(t1, &myrootExact[k], &myrootExact[k], 10);
    k += 10;
    res = mps_transform_apply(&applied, t1);
    Insist(res == MPS_RES_OK);
    Insist(applied);
    mps_transform_destroy(t1);
    t1 = NULL;
    after(myrootExact, perset, 1, 0, 2, 0);

    /* Mixed non-trivial, NULL- and identity-transforms */
    before(myrootExact, perset);
    res = mps_transform_create(&t1, arena);
    Insist(res == MPS_RES_OK);
    {
      mps_transform_add_oldnew(t1, &myrootExact[k], &myrootExact[k + perset], 1);
      k += 1;
      /* NULL */
      mps_transform_add_oldnew(t1, &nullref1, &myrootExact[k + perset], 1);
      k += 1;
      /* identity */
      mps_transform_add_oldnew(t1, &myrootExact[k], &myrootExact[k], 1);
      k += 1;
      /* NULL */
      mps_transform_add_oldnew(t1, &nullref2, &myrootExact[k + perset], 1);
      k += 1;
    }
    mps_transform_add_oldnew(t1, &myrootExact[k], &myrootExact[k + perset], 10);
    k += 10;
    res = mps_transform_apply(&applied, t1);
    Insist(res == MPS_RES_OK);
    Insist(applied);
    mps_transform_destroy(t1);
    t1 = NULL;
    after(myrootExact, perset, 1, -11, 2, +11);

    /* Non-trivial transform */
    before(myrootExact, perset);
    res = mps_transform_create(&t1, arena);
    Insist(res == MPS_RES_OK);
    for(l = k + 4; k < l; k++) {
      mps_transform_add_oldnew(t1, &myrootExact[k], &myrootExact[k + perset], 1);
    }
    mps_transform_add_oldnew(t1, &myrootExact[k], &myrootExact[k + perset], 10);
    k += 10;
    res = mps_transform_apply(&applied, t1);
    Insist(res == MPS_RES_OK);
    Insist(applied);
    mps_transform_destroy(t1);
    t1 = NULL;
    after(myrootExact, perset, 1, -14, 2, +14);

    /* Two transforms, first destroyed unused */
    before(myrootExact, perset);
    res = mps_transform_create(&t1, arena);
    Insist(res == MPS_RES_OK);
    mps_transform_add_oldnew(t1, &myrootExact[k], &myrootExact[k + perset], 10);
    k += 10;
    l = k;
    res = mps_transform_create(&t2, arena);
    Insist(res == MPS_RES_OK);
    mps_transform_add_oldnew(t2, &myrootExact[l], &myrootExact[l + perset], 10);
    l += 10;
    res = mps_transform_apply(&applied, t2);
    Insist(res == MPS_RES_OK);
    Insist(applied);
    mps_transform_destroy(t2);
    t2 = NULL;
    mps_transform_destroy(t1);
    t1 = NULL;
    after(myrootExact, perset, 1, -10, 2, +10);

    /* Two transforms, both live [-- not supported yet.  RHSK 2010-12-16] */
    before(myrootExact, perset);
    res = mps_transform_create(&t1, arena);
    Insist(res == MPS_RES_OK);
    mps_transform_add_oldnew(t1, &myrootExact[k], &myrootExact[k + perset], 10);
    k += 10;
    l = k;
    res = mps_transform_create(&t2, arena);
    Insist(res == MPS_RES_OK);
    mps_transform_add_oldnew(t2, &myrootExact[l], &myrootExact[l + perset], 10);
    l += 10;
    res = mps_transform_apply(&applied, t2);
    Insist(res == MPS_RES_OK);
    Insist(applied);
    mps_transform_destroy(t2);
    t2 = NULL;
    /* TODO: This test block does not destroy t1.  Why is that?  RB 2023-06-16 */
    k = l;
    after(myrootExact, perset, 1, -10, 2, +10);
  }

  /* Large number of objects */
  {
    ulongest_t count;
    mps_transform_t t;
    mps_bool_t applied;

    /* LARGE! */
    perset = myrootExactCOUNT / 2;

    Insist(2*perset <= myrootExactCOUNT);
    for(keepCount = 0; keepCount < 2*perset; keepCount++) {
      ulongest_t slots = (sizeof(struct node_t) / sizeof(mps_word_t)) - 2;
      /* make_dylan_vector: fills slots with INT_DYI(0) */
      die(make_dylan_vector(&v, ap, slots), "make_dylan_vector");
      node = (struct node_t *)v;
      node->serial_dyi = INT_DYI(serial++);
      node->id_dyi = INT_DYI(keepCount % perset);
      node->ver_dyi = INT_DYI(1 + (keepCount >= perset));
      node->left = NULL;
      node->right = NULL;
      myrootExact[keepCount % myrootExactCOUNT] = (void*)v;
      get(arena);
      /* printf("Object %u at %p.\n", keepCount, (void*)v); */
    }
    v = 0;

    /* Refs in root */
    /* ============ */
    /* don't transform 0: its ref coincides with a segbase, so causes ambig refs on stack */
    /* don't transform last: its ambig ref may be left on the stack */
    old = 1;
    new = 1 + perset;
    count = perset - 2;
    Insist(myrootExact[old] != myrootExact[new]);
    before(myrootExact, perset);
    res = mps_transform_create(&t, arena);
    Insist(res == MPS_RES_OK);
    for(i = 0; i < count; i++) {
      res = mps_transform_add_oldnew(t, &myrootExact[old + i], &myrootExact[new + i], 1);
      Insist(res == MPS_RES_OK);
    }
    res = mps_transform_apply(&applied, t);
    Insist(applied);
    mps_transform_destroy(t);
    Insist(myrootExact[old] == myrootExact[new]);
    after(myrootExact, perset, 1, -(longest_t)count, 2, +(longest_t)count);
  }

  printf("  ...made and kept: %"PRIuLONGEST" objects.\n",
         keepCount);
}


static ulongest_t cols(size_t bytes)
{
  double M;  /* Mebibytes */
  ulongest_t cM;  /* hundredths of a Mebibyte */

  M = (double)bytes / (1UL<<20);
  cM = (ulongest_t)(M * 100 + 0.5);  /* round to nearest */
  return cM;
}

/* showStatsAscii -- present collection stats, 'graphically'
 *
 */
static void showStatsAscii(size_t notcon, size_t con, size_t live, size_t alimit)
{
  ulongest_t n = cols(notcon);
  ulongest_t c = cols(notcon + con);
  ulongest_t l = cols(notcon + live);  /* a fraction of con */
  ulongest_t a = cols(alimit);
  ulongest_t count;
  ulongest_t i;

  /* if we can show alimit within 200 cols, do so */
  count = (a < 200) ? a + 1 : c;

  for(i = 0; i < count; i++) {
    printf( (i == a)  ? "A"
            : (i < n) ? "n"
            : (i < l) ? "L"
            : (i < c) ? "_"
            : " "
          );
  }
  printf("\n");
}


/* print_M -- print count of bytes as Mebibytes or Megabytes
 *
 * Print as a whole number, "m" for the decimal point, and
 * then the decimal fraction.
 *
 * Input:                208896
 * Output:  (Mebibytes)  0m199
 * Output:  (Megabytes)  0m209
 */
#if 0
#define bPerM (1UL << 20)  /* Mebibytes */
#else
#define bPerM (1000000UL)  /* Megabytes */
#endif
static void print_M(size_t bytes)
{
  size_t M;  /* M thingies */
  double Mfrac;  /* fraction of an M thingy */

  M = bytes / bPerM;
  Mfrac = (double)(bytes % bPerM);
  Mfrac = (Mfrac / bPerM);

  printf("%1"PRIuLONGEST"m%03.f", (ulongest_t)M, Mfrac * 1000);
}


/* showStatsText -- present collection stats
 *
 * prints:
 *   Coll End  0m137[->0m019 14%-live] (0m211-not )
 */
static void showStatsText(size_t notcon, size_t con, size_t live)
{
  double liveFrac = (double)live / (double)con;

  print_M(con);
  printf("[->");
  print_M(live);
  printf("% 3.f%%-live]", liveFrac * 100);
  printf(" (");
  print_M(notcon);
  printf("-not ");
  printf(")\n");
}

/* get -- get messages
 *
 */
static void get(mps_arena_t arena)
{
  mps_message_type_t type;

  while (mps_message_queue_type(&type, arena)) {
    mps_message_t message;
    static mps_clock_t mclockBegin = 0;
    static mps_clock_t mclockEnd = 0;
    mps_word_t *obj;
    mps_word_t objind;
    mps_addr_t objaddr;

    cdie(mps_message_get(&message, arena, type),
         "get");

    switch(type) {
      case mps_message_type_gc_start(): {
        mclockBegin = mps_message_clock(arena, message);
        printf("    %5"PRIuLONGEST": (%5"PRIuLONGEST")",
               mclockBegin, mclockBegin - mclockEnd);
        printf("    Coll Begin                                     (%s)\n",
               mps_message_gc_start_why(arena, message));
        break;
      }
      case mps_message_type_gc(): {
        size_t con = mps_message_gc_condemned_size(arena, message);
        size_t notcon = mps_message_gc_not_condemned_size(arena, message);
        /* size_t other = 0;  -- cannot determine; new method reqd */
        size_t live = mps_message_gc_live_size(arena, message);
        size_t alimit = mps_arena_reserved(arena);

        mclockEnd = mps_message_clock(arena, message);

        printf("    %5"PRIuLONGEST": (%5"PRIuLONGEST")",
               mclockEnd, mclockEnd - mclockBegin);
        printf("    Coll End  ");
        showStatsText(notcon, con, live);
        if(rnd()==0) showStatsAscii(notcon, con, live, alimit);
        break;
      }
      case mps_message_type_finalization(): {
        mps_message_finalization_ref(&objaddr, arena, message);
        obj = objaddr;
        objind = DYLAN_INT_INT(DYLAN_VECTOR_SLOT(obj, 0));
        printf("    Finalization for object %"PRIuLONGEST" at %p\n", (ulongest_t)objind, objaddr);
        break;
      }
      default: {
        cdie(0, "message type");
        break;
      }
    }

    mps_message_discard(arena, message);
  }
}


/* .catalog: The Catalog client:
 *
 * This is an MPS client for testing the MPS.  It simulates
 * converting a multi-page "Catalog" document from a page-description
 * into a bitmap.
 *
 * The intention is that this task will cause memory usage that is
 * fairly realistic (much more so than randomly allocated objects
 * with random interconnections.  The patterns in common with real
 * clients are:
 *   - the program input and its task are 'fractal', with a
 *     self-similar hierarchy;
 *   - object allocation is prompted by each successive element of
 *     the input/task;
 *   - objects are often used to store a transformed version of the
 *     program input;
 *   - there may be several stages of transformation;
 *   - at each stage, the old object (holding the untransformed data)
 *     may become dead;
 *   - sometimes a tree of objects becomes dead once an object at
 *     some level of the hierarchy has been fully processed;
 *   - there is more than one hierarchy, and objects in different
 *     hierarchies interact.
 *
 * The entity-relationship diagram is:
 *        Catalog -< Page -< Article -< Polygon
 *                                        v
 *                                        |
 *        Palette --------------------< Colour
 *
 * The first hierarchy is a Catalog, containing Pages, each
 * containing Articles (bits of artwork etc), each composed of
 * Polygons.  Each polygon has a single colour.
 *
 * The second hierarchy is a top-level Palette, containing Colours.
 * Colours (in this client) are expensive, large objects (perhaps
 * because of complex colour modelling or colour blending).
 *
 * The things that matter for their effect on MPS behaviour are:
 *   - when objects are allocated, and how big they are;
 *   - how the reference graph mutates over time;
 *   - how the mutator accesses objects (barrier hits).
 */

enum {
  CatalogRootIndex = 0,
  CatalogSig = 0x0000CA2A,  /* CATAlog */
  CatalogFix = 1,
  CatalogVar = 10,
  PageSig =    0x0000BA9E,  /* PAGE */
  PageFix = 1,
  PageVar = 100,
  ArtSig =     0x0000A621,  /* ARTIcle */
  ArtFix = 1,
  ArtVar = 100,
  PolySig =    0x0000B071,  /* POLYgon */
  PolyFix = 1,
  PolyVar = 100
};

static void CatalogCheck(void)
{
  mps_word_t w;
  void *Catalog, *Page, *Art, *Poly;
  ulongest_t Catalogs = 0, Pages = 0, Arts = 0, Polys = 0;
  int i, j, k;

  /* retrieve Catalog from root */
  Catalog = myrootExact[CatalogRootIndex];
  if(!Catalog)
    return;
  Insist(DYLAN_VECTOR_SLOT(Catalog, 0) == DYLAN_INT(CatalogSig));
  Catalogs += 1;

  for(i = 0; i < CatalogVar; i += 1) {
    /* retrieve Page from Catalog */
    w = DYLAN_VECTOR_SLOT(Catalog, CatalogFix + i);
    /* printf("Page = 0x%8x\n", (unsigned int) w); */
    if(w == DYLAN_INT(0))
      break;
    Page = (void *)w;
    Insist(DYLAN_VECTOR_SLOT(Page, 0) == DYLAN_INT(PageSig));
    Pages += 1;

    for(j = 0; j < PageVar; j += 1) {
      /* retrieve Art from Page */
      w = DYLAN_VECTOR_SLOT(Page, PageFix + j);
      if(w == DYLAN_INT(0))
        break;
      Art = (void *)w;
      Insist(DYLAN_VECTOR_SLOT(Art, 0) == DYLAN_INT(ArtSig));
      Arts += 1;

      for(k = 0; k < ArtVar; k += 1) {
        /* retrieve Poly from Art */
        w = DYLAN_VECTOR_SLOT(Art, ArtFix + k);
        if(w == DYLAN_INT(0))
          break;
        Poly = (void *)w;
        Insist(DYLAN_VECTOR_SLOT(Poly, 0) == DYLAN_INT(PolySig));
        Polys += 1;
      }
    }
  }
  printf("Catalog ok with: Catalogs: %"PRIuLONGEST", Pages: %"PRIuLONGEST", Arts: %"PRIuLONGEST", Polys: %"PRIuLONGEST".\n",
         Catalogs, Pages, Arts, Polys);
}


/* CatalogDo -- make a Catalog and its tree of objects
 *
 * .catalog.broken: this code, when compiled with
 * moderate optimization, may have ambiguous interior pointers but
 * lack corresponding ambiguous base pointers to MPS objects.  This
 * means the interior pointers are unmanaged references, and the
 * code goes wrong.  The hack in poolamc.c#4 cures this, but not very
 * nicely.  For further discussion, see:
 *    <http://info.ravenbrook.com/mail/2009/02/05/18-05-52/0.txt>
 */
static void CatalogDo(mps_arena_t arena, mps_ap_t ap)
{
  mps_word_t v;
  void *Catalog, *Page, *Art, *Poly;
  int i, j, k;

  die(make_dylan_vector(&v, ap, CatalogFix + CatalogVar), "Catalog");
  DYLAN_VECTOR_SLOT(v, 0) = DYLAN_INT(CatalogSig);
  Catalog = (void *)v;

  /* store Catalog in root */
  myrootExact[CatalogRootIndex] = Catalog;
  get(arena);

  fflush(stdout);
  CatalogCheck();

  for(i = 0; i < CatalogVar; i += 1) {
    die(make_dylan_vector(&v, ap, PageFix + PageVar), "Page");
    DYLAN_VECTOR_SLOT(v, 0) = DYLAN_INT(PageSig);
    Page = (void *)v;

    /* store Page in Catalog */
    DYLAN_VECTOR_SLOT(Catalog, CatalogFix + i) = (mps_word_t)Page;
    get(arena);

    printf("Page %d: make articles\n", i);
    fflush(stdout);

    for(j = 0; j < PageVar; j += 1) {
      die(make_dylan_vector(&v, ap, ArtFix + ArtVar), "Art");
      DYLAN_VECTOR_SLOT(v, 0) = DYLAN_INT(ArtSig);
      Art = (void *)v;

      /* store Art in Page */
      DYLAN_VECTOR_SLOT(Page, PageFix + j) = (mps_word_t)Art;
      get(arena);

      for(k = 0; k < ArtVar; k += 1) {
        die(make_dylan_vector(&v, ap, PolyFix + PolyVar), "Poly");
        DYLAN_VECTOR_SLOT(v, 0) = DYLAN_INT(PolySig);
        Poly = (void *)v;

        /* store Poly in Art */
        DYLAN_VECTOR_SLOT(Art, ArtFix + k) = (mps_word_t)Poly;
        /* get(arena); */
      }
    }
  }
  fflush(stdout);
  CatalogCheck();
}


/* MakeThing -- make an object of the size requested (in bytes)
 *
 * Any size is accepted.  MakeThing may round it up (MakeThing always
 * makes a dylan vector, which has a minimum size of 8 bytes).  Vector
 * slots, if any, are initialized to DYLAN_INT(0).
 *
 * After making the object, calls get(), to retrieve MPS messages.
 *
 * make_dylan_vector [fmtdytst.c] says:
 *   size = (slots + 2) * sizeof(mps_word_t);
 * That is: a dylan vector has two header words before the first slot.
 */
static void* MakeThing(mps_arena_t arena, mps_ap_t ap, size_t size)
{
  mps_word_t v;
  ulongest_t words;
  ulongest_t slots;

  words = (size + (sizeof(mps_word_t) - 1) ) / sizeof(mps_word_t);
  if(words < 2)
    words = 2;

  slots = words - 2;
  die(make_dylan_vector(&v, ap, slots), "make_dylan_vector");
  get(arena);

  return (void *)v;
}

static void BigdropSmall(mps_arena_t arena, mps_ap_t ap, size_t big, char small_ref)
{
  static ulongest_t keepCount = 0;
  ulongest_t i;

  mps_arena_park(arena);
  for(i = 0; i < 100; i++) {
    (void) MakeThing(arena, ap, big);
    if(small_ref == 'A') {
      myrootAmbig[keepCount++ % myrootAmbigCOUNT] = MakeThing(arena, ap, 1);
    } else if(small_ref == 'E') {
      myrootExact[keepCount++ % myrootExactCOUNT] = MakeThing(arena, ap, 1);
    } else {
      cdie(0, "BigdropSmall: small must be 'A' or 'E'.\n");
    }
  }
}


/* df -- diversity function
 *
 * Either deterministic based on "number", or 'random' (ie. call rnd).
 */

static ulongest_t df(unsigned randm, ulongest_t number)
{
  if(randm == 0) {
    return number;
  } else {
    return rnd();
  }
}

static void Make(mps_arena_t arena, mps_ap_t ap, unsigned randm, unsigned keep1in, unsigned keepTotal, unsigned keepRootspace, unsigned sizemethod)
{
  unsigned keepCount = 0;
  ulongest_t objCount = 0;

  Insist(keepRootspace <= myrootExactCOUNT);

  objCount = 0;
  while(keepCount < keepTotal) {
    mps_word_t v;
    unsigned slots = 2;  /* minimum */
    switch(sizemethod) {
      case 0: {
        /* minimum */
        slots = 2;
        break;
      }
      case 1: {
        slots = 2;
        if(df(randm, objCount) % 10000 == 0) {
          printf("*");
          slots = 300000;
        }
        break;
      }
      case 2: {
        slots = 2;
        if(df(randm, objCount) % 6661 == 0) {  /* prime */
          printf("*");
          slots = 300000;
        }
        break;
      }
      default: {
        printf("bad script command: sizemethod %u unknown.\n", sizemethod);
        cdie(FALSE, "bad script command!");
        break;
      }
    }
    die(make_dylan_vector(&v, ap, slots), "make_dylan_vector");
    DYLAN_VECTOR_SLOT(v, 0) = DYLAN_INT(objCount);
    DYLAN_VECTOR_SLOT(v, 1) = (mps_word_t)NULL;
    objCount++;
    if(df(randm, objCount) % keep1in == 0) {
      /* keep this one */
      myrootExact[df(randm, keepCount) % keepRootspace] = (void*)v;
      keepCount++;
    }
    get(arena);
  }
  printf("  ...made and kept: %u objects, storing cyclically in "
         "first %u roots "
         "(actually created %"PRIuLONGEST" objects, in accord with "
         "keep-1-in %u).\n",
         keepCount, keepRootspace, objCount, keep1in);
}


static void Rootdrop(char rank_char)
{
  ulongest_t i;

  if(rank_char == 'A') {
    for(i = 0; i < myrootAmbigCOUNT; ++i) {
      myrootAmbig[i] = NULL;
    }
  } else if(rank_char == 'E') {
    for(i = 0; i < myrootExactCOUNT; ++i) {
      myrootExact[i] = NULL;
    }
  } else {
    cdie(0, "Rootdrop: rank must be 'A' or 'E'.\n");
  }
}

#if 0
#define stackwipedepth 50000
static void stackwipe(void)
{
  unsigned iw;
  ulongest_t aw[stackwipedepth];

  /* http://xkcd.com/710/ */
  /* I don't want my friends to stop calling; I just want the */
  /* compiler to stop optimising away my code. */

  /* Do you ever get two even numbers next to each other?  Hmmmm :-) */
  for(iw = 0; iw < stackwipedepth; iw++) {
    if((iw & 1) == 0) {
      aw[iw] = 1;
    } else {
      aw[iw] = 0;
    }
  }
  for(iw = 1; iw < stackwipedepth; iw++) {
    if(aw[iw - 1] + aw[iw] != 1) {
      printf("Errrr....\n");
      break;
    }
  }
}
#endif

static void StackScan(mps_arena_t arena, int on)
{
  if(on) {
    Insist(root_stackreg == NULL);
    die(mps_root_create_reg(&root_stackreg, arena,
                            mps_rank_ambig(), (mps_rm_t)0, stack_thr,
                            mps_stack_scan_ambig, stack_start, 0),
        "root_stackreg");
    Insist(root_stackreg != NULL);
  } else {
    Insist(root_stackreg != NULL);
    mps_root_destroy(root_stackreg);
    root_stackreg = NULL;
    Insist(root_stackreg == NULL);
  }
}


/* checksi -- check count of sscanf items is correct
 */

static void checksi(int si, int si_shouldBe, const char *script, const char *scriptAll)
{
  if(si != si_shouldBe) {
    printf("bad script command (sscanf found wrong number of params) %s (full script %s).\n", script, scriptAll);
    cdie(FALSE, "bad script command!");
  }
}

/* testscriptC -- actually runs a test script
 *
 */
static void testscriptC(mps_arena_t arena, mps_ap_t ap, const char *script)
{
  const char *scriptAll = script;
  int si, sb;  /* sscanf items, sscanf bytes */

  while(*script != '\0') {
    switch(*script) {
      case 'C': {
        si = sscanf(script, "Collect%n",
                       &sb);
        checksi(si, 0, script, scriptAll);
        script += sb;
        printf("  Collect\n");
        /* stackwipe(); */
        mps_arena_collect(arena);
        mps_arena_release(arena);
        break;
      }
      case 'T': {
        si = sscanf(script, "Transform%n",
                       &sb);
        checksi(si, 0, script, scriptAll);
        script += sb;
        printf("  Transform\n");
        Transform(arena, ap);
        break;
      }
      case 'K': {
        si = sscanf(script, "Katalog()%n",
                       &sb);
        checksi(si, 0, script, scriptAll);
        script += sb;
        printf("  Katalog()\n");
        CatalogDo(arena, ap);
        break;
      }
      case 'B': {
        ulongest_t big = 0;
        char small_ref = ' ';
        si = sscanf(script, "BigdropSmall(big %"SCNuLONGEST", small %c)%n",
                       &big, &small_ref, &sb);
        checksi(si, 2, script, scriptAll);
        script += sb;
        printf("  BigdropSmall(big %"PRIuLONGEST", small %c)\n", big, small_ref);
        BigdropSmall(arena, ap, big, small_ref);
        break;
      }
      case 'M': {
        unsigned randm = 0;
        unsigned keep1in = 0;
        unsigned keepTotal = 0;
        unsigned keepRootspace = 0;
        unsigned sizemethod = 0;
        si = sscanf(script, "Make(random %u, keep-1-in %u, keep %u, rootspace %u, sizemethod %u)%n",
                    &randm, &keep1in, &keepTotal, &keepRootspace, &sizemethod, &sb);
        checksi(si, 5, script, scriptAll);
        script += sb;
        printf("  Make(random %u, keep-1-in %u, keep %u, rootspace %u, sizemethod %u).\n",
               randm, keep1in, keepTotal, keepRootspace, sizemethod);
        Make(arena, ap, randm, keep1in, keepTotal, keepRootspace, sizemethod);
        break;
      }
      case 'R': {
        char drop_ref = ' ';
        si = sscanf(script, "Rootdrop(rank %c)%n",
                       &drop_ref, &sb);
        checksi(si, 1, script, scriptAll);
        script += sb;
        printf("  Rootdrop(rank %c)\n", drop_ref);
        Rootdrop(drop_ref);
        break;
      }
      case 'S': {
        unsigned on = 0;
        si = sscanf(script, "StackScan(%u)%n",
                       &on, &sb);
        checksi(si, 1, script, scriptAll);
        script += sb;
        printf("  StackScan(%u)\n", on);
        StackScan(arena, on != 0);
        break;
      }
      case 'Z': {
        ulongest_t s0;
        si = sscanf(script, "ZRndStateSet(%"SCNuLONGEST")%n",
                       &s0, &sb);
        checksi(si, 1, script, scriptAll);
        script += sb;
        printf("  ZRndStateSet(%"PRIuLONGEST")\n", s0);
        rnd_state_set((unsigned long)s0);
        break;
      }
      case ' ':
      case ',':
      case '.': {
        script++;
        break;
      }
      default: {
        printf("unknown script command '%c' (script %s).\n",
               *script, scriptAll);
        cdie(FALSE, "unknown script command!");
        return;
      }
    }
    get(arena);
  }

}


/* testscriptB -- create pools and objects; call testscriptC
 *
 * Is called via mps_tramp, so matches mps_tramp_t function prototype,
 * and use trampDataStruct to pass parameters.
 */

typedef struct trampDataStruct {
  mps_arena_t arena;
  mps_thr_t thr;
  const char *script;
} trampDataStruct;

static void *testscriptB(void *arg, size_t s)
{
  trampDataStruct trampData;
  mps_arena_t arena;
  mps_thr_t thr;
  const char *script;
  mps_fmt_t fmt;
  mps_chain_t chain;
  mps_pool_t amc;
  int i;
  mps_root_t root_table_Ambig;
  mps_root_t root_table_Exact;
  mps_ap_t ap;
  void *stack_starts_here;  /* stack scanning starts here */

  Insist(s == sizeof(trampDataStruct));
  trampData = *(trampDataStruct*)arg;
  arena = trampData.arena;
  thr = trampData.thr;
  script = trampData.script;

  die(mps_fmt_create_A(&fmt, arena, dylan_fmt_A()), "fmt_create");
  die(mps_chain_create(&chain, arena, genCOUNT, testChain), "chain_create");
  die(mps_pool_create(&amc, arena, mps_class_amc(), fmt, chain),
      "pool_create amc");

  for(i = 0; i < myrootAmbigCOUNT; ++i) {
    myrootAmbig[i] = NULL;
  }
  die(mps_root_create_table(&root_table_Ambig, arena, mps_rank_ambig(), (mps_rm_t)0,
                            myrootAmbig, (size_t)myrootAmbigCOUNT),
      "root_create - ambig");

  for(i = 0; i < myrootExactCOUNT; ++i) {
    myrootExact[i] = NULL;
  }
  die(mps_root_create_table(&root_table_Exact, arena, mps_rank_exact(), (mps_rm_t)0,
                            myrootExact, (size_t)myrootExactCOUNT),
      "root_create - exact");

  die(mps_ap_create(&ap, amc, mps_rank_exact()), "ap_create");

  /* root_stackreg: stack & registers are ambiguous roots = mutator's workspace */
  stack_start = &stack_starts_here;
  stack_thr = thr;
  die(mps_root_create_reg(&root_stackreg, arena,
                          mps_rank_ambig(), (mps_rm_t)0, stack_thr,
                          mps_stack_scan_ambig, stack_start, 0),
      "root_stackreg");


  mps_message_type_enable(arena, mps_message_type_gc_start());
  mps_message_type_enable(arena, mps_message_type_gc());
  mps_message_type_enable(arena, mps_message_type_finalization());

  testscriptC(arena, ap, script);

  printf("  Destroy roots, pools, arena etc.\n\n");
  mps_root_destroy(root_stackreg);
  mps_ap_destroy(ap);
  mps_root_destroy(root_table_Exact);
  mps_root_destroy(root_table_Ambig);
  mps_pool_destroy(amc);
  mps_chain_destroy(chain);
  mps_fmt_destroy(fmt);

  return NULL;
}


/* testscriptA -- create arena, thr; call testscriptB
 */
static void testscriptA(const char *script)
{
  mps_arena_t arena;
  int si, sb;  /* sscanf items, sscanf bytes */
  ulongest_t arenasize = 0;
  mps_thr_t thr;
  trampDataStruct trampData;

  si = sscanf(script, "Arena(size %"SCNuLONGEST")%n", &arenasize, &sb);
  cdie(si == 1, "bad script command: Arena(size %%"PRIuLONGEST")");
  script += sb;
  printf("  Create arena, size = %"PRIuLONGEST".\n", arenasize);

  /* arena */
  die(mps_arena_create(&arena, mps_arena_class_vm(), arenasize),
      "arena_create");

  /* thr: used to stop/restart multiple threads */
  die(mps_thread_reg(&thr, arena), "thread");

  /* call testscriptB! */
  trampData.arena = arena;
  trampData.thr = thr;
  trampData.script = script;
  testscriptB(&trampData, sizeof trampData);

  mps_thread_dereg(thr);
  mps_arena_destroy(arena);
}


/* main -- runs various test scripts
 *
 */
int main(int argc, char *argv[])
{
  randomize(argc, argv);
  mps_lib_assert_fail_install(assert_die);

  /* 1<<19 == 524288 == 1/2 Mebibyte */
  /* 16<<20 == 16777216 == 16 Mebibyte */

  testscriptA("Arena(size 500000000), "
              "Transform"
              /*", Collect, Rootdrop(rank E), Collect, Collect"*/
              ".");

  printf("%s: Conclusion: Failed to find any defects.\n", argv[0]);
  return 0;
}


/* C. COPYRIGHT AND LICENSE
 *
 * Copyright (C) 2011-2022 Ravenbrook Limited <https://www.ravenbrook.com/>.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the
 *    distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
 * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE,
 * DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY
 * THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
 * OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
 */
