/* trans.c: RAVENBROOK MEMORY POOL SYSTEM TRANSFORMS
 *
 * $Id: //info.ravenbrook.com/project/mps/custom/cet/version/1.114/code/trans.c#1 $
 * Copyright 2011 Ravenbrook Limited.  See end of file for license.
 *
 * This code is specific to Configura.
 *
 * A transform is a special kind of garbage collection that replaces references
 * to a set of objects.  The transform is piggybacked onto a garbage
 * collection by overriding the fix method for a trace.  The mapping used to
 * replace the references is built up in a hash table by the client.
 *
 *
 * Rationale
 *
 * This design was arrived at after some pain.  The MPS isn't really designed
 * for this kind of thing, and the pools generally assume that they're doing
 * a garbage collection when they're asked to condemn, scan, fix, and reclaim
 * stuff.  This makes it very hard to apply the transform without also doing
 * a garbage collection.  Changing this would require a significant reworking
 * of the MPS to generalise its ideas, and would bloat the pool classes.
 *
 *
 * Assumptions:
 *
 *   - Single-threaded mutator.  In fact this code might work if other mutator
 *     threads are running, since the shield ought to operate correctly.
 *     However, in this case there can only be one trace running so that the
 *     correct fix method is chosen by ScanStateInit.
 */

#include "trans.h"
#include "table.h"


#define TransformSig         ((Sig)0x51926A45) /* SIGnature TRANSform */

typedef struct mps_transform_s {
  Sig sig;                      /* <design/sig/> */
  Arena arena;                  /* owning arena */
  Table oldToNew;               /* map to apply to refs */
  Epoch epoch;                  /* epoch in which transform was created */
  Bool aborted;                 /* no longer transforming, just GCing */
} TransformStruct;


Bool TransformCheck(Transform transform)
{
  CHECKS(Transform, transform);
  CHECKU(Arena, transform->arena);
  /* .check.boot: avoid bootstrap problem in transformTableAlloc where
     transformTableFree checks the transform while the table is being
     destroyed */
  if (transform->oldToNew != NULL)
    CHECKD(Table, transform->oldToNew);
  CHECKL(BoolCheck(transform->aborted));
  CHECKL(transform->epoch <= ArenaEpoch(transform->arena));
  return TRUE;
}


/* Allocator functions for the Table oldToNew */

static void *transformTableAlloc(void *closure, size_t size)
{
  Transform transform = (Transform)closure;
  Res res;
  void *p;

  AVERT(Transform, transform);

  res = ControlAlloc(&p, transform->arena, size, FALSE);
  if (res != ResOK)
    return NULL;

  return p;
}

static void transformTableFree(void *closure, void *p, size_t size)
{
  Transform transform = (Transform)closure;
  AVERT(Transform, transform);
  ControlFree(transform->arena, p, size);
}


Res TransformCreate(Transform *transformReturn, Arena arena)
{
  Transform transform;
  Res res;
  void *p;

  AVER(transformReturn != NULL);
  AVERT(Arena, arena);

  res = ControlAlloc(&p, arena, sizeof(TransformStruct), FALSE);
  if (res != ResOK)
    goto failAlloc;
  transform = (Transform)p;
  
  transform->oldToNew = NULL;
  transform->arena = arena;
  transform->epoch = ArenaEpoch(arena);
  transform->aborted = FALSE;

  transform->sig = TransformSig;

  AVERT(Transform, transform);

  res = TableCreate(&transform->oldToNew,
                    0, /* don't grow table until TransformAddOldNew */
                    transformTableAlloc,
                    transformTableFree,
                    transform,
                    0, 1); /* these can't be old references */
  if (res != ResOK)
    goto failTable;

  *transformReturn = transform;
  return ResOK;

failTable:
  ControlFree(arena, transform, sizeof(TransformStruct));
failAlloc:
  return res;
}


void TransformDestroy(Transform transform)
{
  Arena arena;
  Table oldToNew;

  AVERT(Transform, transform);

  /* TODO: Log some transform statistics. */

  /* Workaround bootstrap problem, see .check.boot */
  oldToNew = transform->oldToNew;
  transform->oldToNew = NULL;
  TableDestroy(oldToNew);

  arena = TransformArena(transform);
  transform->sig = SigInvalid;
  ControlFree(arena, transform, sizeof(TransformStruct));
}


Arena TransformArena(Transform transform)
{
  Arena arena;
  AVERT(Transform, transform);
  arena = transform->arena;
  AVERT(Arena, arena);
  return arena;
}


Res TransformAddOldNew(Transform transform,
                       Ref old_list[],
                       Ref new_list[],
                       Count count)
{
  Res res;
  Index i;
  Count added = 0;

  AVERT(Transform, transform);
  AVER(old_list);
  AVER(new_list);
  /* count: cannot check */
  
  res = TableGrow(transform->oldToNew, count);
  if (res != ResOK)
    return res;

  for(i = 0; i < count; ++i) {
    /* NOTE: If the mutator isn't adding references while the arena is parked,
       we might need to access the client-provided lists, using ArenaPeek. */
    if(old_list[i] == NULL)
      continue;  /* permitted, but no transform to do */
    if(old_list[i] == new_list[i])
      continue;  /* ignore identity-transforms */

    /* Old refs must be in managed memory. */
    {
      Seg seg;
      AVER(SegOfAddr(&seg, transform->arena, old_list[i]));
    }

    res = TableDefine(transform->oldToNew, (Word)old_list[i], new_list[i]);
    AVER(res != ResFAIL); /* It's a static error to add the same old twice. */
    if (res != ResOK)
      return res;
    
    ++added;
  }

  AVERT(Transform, transform);
  
  return ResOK;
}


/* TransformApply -- transform references on the heap */

static Res transformFix(Pool pool, ScanState ss, Seg seg, Ref *refIO)
{
  Ref ref;
  Transform transform;
  Res res;
        
  UNUSED(pool);
  UNUSED(seg);
  AVERT_CRITICAL(ScanState, ss);
  AVER_CRITICAL(refIO != NULL);

  transform = ss->fixClosure;
  AVERT_CRITICAL(Transform, transform);

  if (!transform->aborted) {
    void *refNew;

    ref = *refIO;

    if (TableLookup(&refNew, transform->oldToNew, (Word)ref)) {
      if (ss->rank == RankAMBIG) {
        /* We rely on the fact that all ambiguous references are fixed before
           any others, so no references will have transformed by the time we
           abort.
           NOTE: Configura CVM prints a message and exits if a transform
           fails.  See Configura's
           //depot/project/cet/kernel/internal/version3.2/cvm/c2/updateHeap2.m#4
           line 133.*/
        transform->aborted = TRUE;
      } else {
        /* NOTE: We could fix refNew in the table before copying it,
           since any summaries etc. collected in the scan state will still
           apply when it's copied.  That could save a few snap-outs. */
        *refIO = refNew;
      }
    }
  }

  /* Now progress to a normal GC fix. */
  /* TODO: Make a clean interface to this kind of dynamic binding. */
  ss->fix = PoolArena(pool)->emergency ? PoolFixEmergency : PoolFix;
  TRACE_SCAN_BEGIN(ss) {
    res = TRACE_FIX(ss, refIO);
  } TRACE_SCAN_END(ss);
  ss->fix = transformFix;

  return res;
}


static void transformWhiten(void *closure, Word key, void *value)
{
  Seg seg;
  Bool b;
  Trace trace;

  UNUSED(value);
  
  trace = (Trace)closure;

  b = SegOfAddr(&seg, trace->arena, (Ref)key);
  AVER(b); /* old refs must be in managed memory, else client param error */
  if (!TraceSetIsMember(SegWhite(seg), trace)) {
    Res res = TraceAddWhite(trace, seg);
    AVER(res == ResOK);
  }
}


Res TransformApply(Bool *appliedReturn, Transform transform)
{
  Res res;
  Arena arena;
  Globals globals;
  Trace trace;

  AVER(appliedReturn != NULL);
  AVERT(Transform, transform);

  arena = TransformArena(transform);

  /* If there have been any flips since the transform was created, the old
     and new pointers will be invalid, since they are not scanned as roots.
     NOTE: Configura CVM parks the arena before adding references.  See
     //depot/project/cet/kernel/internal/version3.2/cvm/c2/updateHeap2.m#4
     line 114. */
  if (transform->epoch != ArenaEpoch(arena))
    return ResPARAM;

  globals = ArenaGlobals(arena);
  AVERT(Globals, globals);

  ArenaPark(globals);
  
  res = TraceCreate(&trace, arena, TraceStartWhyEXTENSION);
  AVER(res == ResOK); /* parking should make a trace available */
  if (res != ResOK)
    return res;

  /* Whiten all segments containing the transform's old objects, so that the
     scanner passes references to them through to transformFix. */
  TableMap(transform->oldToNew, transformWhiten, trace);
  
  trace->fix = transformFix;
  trace->fixClosure = transform;

  res = TraceStart(trace, 1.0, 0.0);
  AVER(res == ResOK); /* transformFix can't fail */
  
  /* If transformFix during traceFlip found ambiguous references and
     aborted the transform then the rest of the trace is just a normal GC. 
     Note that aborting a trace part-way through is pretty much impossible
     without corrupting the mutator graph.  We could safely
         if (transform->aborted) {
           trace->fix = PoolFix;
           trace->fixClosure = NULL;
         }
   */
  
  /* Force the trace to complete now. */
  ArenaRelease(globals);
  ArenaPark(globals);
  
  if (transform->aborted) {
    *appliedReturn = FALSE;
  } else {
    *appliedReturn = TRUE;
    /* I'm not sure why the interface is defined this way. RB 2012-08-03 */
    TransformDestroy(transform);
  }
  
  return ResOK;
}


/* C. COPYRIGHT AND LICENSE
 *
 * Copyright (C) 2011 Ravenbrook Limited <http://www.ravenbrook.com/>.
 * All rights reserved.  This is an open source license.  Contact
 * Ravenbrook for commercial licensing options.
 * 
 * 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.
 * 
 * 3. Redistributions in any form must be accompanied by information on how
 * to obtain complete source code for this software and any accompanying
 * software that uses this software.  The source code must either be
 * included in the distribution or be available for no more than the cost
 * of distribution plus a nominal fee, and must be freely redistributable
 * under reasonable conditions.  For an executable file, complete source
 * code means the source code for all modules it contains. It does not
 * include source code for modules or files that typically accompany the
 * major components of the operating system on which the executable file
 * runs.
 * 
 * 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, FITNESS FOR A PARTICULAR
 * PURPOSE, OR NON-INFRINGEMENT, ARE DISCLAIMED. IN NO EVENT SHALL THE
 * COPYRIGHT HOLDERS AND 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.
 */