view src/main/gov/nasa/jpf/vm/GenericHeap.java @ 2:b920e6b1be83

second part of the jpf-statechart motivated event interface overhaul, providing dynamic (context specific) expansion of EventTrees from within EventChoiceGenerators. This adds a EventContext mechanism that can replace events on-the-fly during advance() (e.g. expand wildcard patterns) this also included the refined 'vm.extend.transitions' property, which is now a list of TypeSpecs (glob notation plus bounds) for CG types that should be subject to transition extension. We also support CheckExtendTransition attrs for CGs, which can be used to dynamically mark CGs. Note that each matching CG is still tested for non-rescheduling single choices small Type/FeatureSpec extension to make it applicable to java.lang.Class instances. There is no reason why we can't make use of this for native types
author Peter Mehlitz <Peter.C.Mehlitz@nasa.gov>
date Sat, 24 Jan 2015 18:19:08 -0800
parents 61d41facf527
children
line wrap: on
line source

/*
 * Copyright (C) 2014, United States Government, as represented by the
 * Administrator of the National Aeronautics and Space Administration.
 * All rights reserved.
 *
 * The Java Pathfinder core (jpf-core) platform is licensed under the
 * Apache License, Version 2.0 (the "License"); you may not use this file except
 * in compliance with the License. You may obtain a copy of the License at
 * 
 *        http://www.apache.org/licenses/LICENSE-2.0. 
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and 
 * limitations under the License.
 */

package gov.nasa.jpf.vm;

import java.util.ArrayList;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;

import gov.nasa.jpf.Config;
import gov.nasa.jpf.JPFException;
import gov.nasa.jpf.util.ArrayObjectQueue;
import gov.nasa.jpf.util.IntTable;
import gov.nasa.jpf.util.IntVector;
import gov.nasa.jpf.util.ObjectQueue;
import gov.nasa.jpf.util.Processor;

/**
 * this is an abstract root for Heap implementations, providing a standard
 * mark&sweep collector, change attribute management, and generic pinDownList,
 * weakReference and internString handling
 * 
 * The concrete Heap implementors have to provide the ElementInfo collection
 * and associated getters, allocators and iterators
 */
public abstract class GenericHeap implements Heap, Iterable<ElementInfo> {
  
  static abstract class GenericHeapMemento implements Memento<Heap> {
    // those can be simply copied
    int attributes;
    IntVector pinDownList;
    Map<Integer,IntTable<String>> internStringsMap;
    
    protected GenericHeapMemento (GenericHeap heap){
      // these are copy-on-first-write, so we don't have to clone
      pinDownList = heap.pinDownList;
      internStringsMap = heap.internStringsMap;
      attributes = heap.attributes & ATTR_STORE_MASK;
      
      heap.setStored();
    }
    
    @Override
    public Heap restore (Heap inSitu) {
      GenericHeap heap = (GenericHeap) inSitu;
      heap.pinDownList = pinDownList;
      heap.internStringsMap = internStringsMap;
      heap.attributes = attributes;
      heap.liveBitValue = false; // always start with false after a restore
      return inSitu;
    }
  }
  
  
  protected class ElementInfoMarker implements Processor<ElementInfo>{
    @Override
	public void process (ElementInfo ei) {
      ei.markRecursive( GenericHeap.this); // this might in turn call queueMark
    }
  }
  
  protected VM vm;

  // list of pinned down references (this is only efficient for a small number of objects)
  // this is copy-on-first-write
  protected IntVector pinDownList;

  // interned Strings
  // this is copy-on-first-write, it is created on demand upon adding the first interned string,
  // and it includes IntTable per process.
  protected Map<Integer,IntTable<String>> internStringsMap;

  // the usual drill - the lower 2 bytes are sticky, the upper two ones 
  // hold change status and transient (transition local) flags
  protected int attributes;

  static final int ATTR_GC            = 0x0001;
  static final int ATTR_OUT_OF_MEMORY = 0x0002;
  static final int ATTR_RUN_FINALIZER = 0x0004;

  static final int ATTR_ELEMENTS_CHANGED  = 0x10000;
  static final int ATTR_PINDOWN_CHANGED   = 0x20000;
  static final int ATTR_INTERN_CHANGED    = 0x40000;
  static final int ATTR_ATTRIBUTE_CHANGED = 0x80000;

  // masks and sets
  static final int ATTR_STORE_MASK = 0x0000ffff;
  static final int ATTR_ANY_CHANGED = (ATTR_ELEMENTS_CHANGED | ATTR_PINDOWN_CHANGED | ATTR_INTERN_CHANGED | ATTR_ATTRIBUTE_CHANGED);


  //--- these objects are only used during gc

  // used to keep track of marked WeakRefs that might have to be updated (no need to restore, only transient use during gc)
  protected ArrayList<ElementInfo> weakRefs;

  protected ObjectQueue<ElementInfo> markQueue = new ArrayObjectQueue<ElementInfo>();

  // this is set to false upon backtrack/restore
  protected boolean liveBitValue;
  
  protected ElementInfoMarker elementInfoMarker = new ElementInfoMarker();
  
  // the number of live objects
  // <2do> currently only defined after gc
  protected int nLiveObjects;
  
  //--- constructors

  public GenericHeap (Config config, KernelState ks){
    vm = VM.getVM();

    pinDownList = new IntVector(256);
    attributes |= ATTR_PINDOWN_CHANGED; // no need to clone on next add

    if (config.getBoolean("vm.finalize", true)){
      attributes |= ATTR_RUN_FINALIZER;
    }

    if (config.getBoolean("vm.sweep",true)){
      attributes |= ATTR_GC;
    }
  }


  protected DynamicElementInfo createElementInfo (int objref, ClassInfo ci, Fields f, Monitor m, ThreadInfo ti){
    return new DynamicElementInfo( objref,ci,f,m,ti);
  }
  
  //--- pinDown handling
  protected void addToPinDownList (int objref){
    if ((attributes & ATTR_PINDOWN_CHANGED) == 0) {
      pinDownList = pinDownList.clone();
      attributes |= ATTR_PINDOWN_CHANGED;
    }
    pinDownList.add(objref);
  }
  
  protected void removeFromPinDownList (int objref){
    if ((attributes & ATTR_PINDOWN_CHANGED) == 0) {
      pinDownList = pinDownList.clone();
      attributes |= ATTR_PINDOWN_CHANGED;
    }
    pinDownList.removeFirst(objref);    
  }

  @Override
  public void registerPinDown(int objref){
    ElementInfo ei = getModifiable(objref);
    if (ei != null) {
      if (ei.incPinDown()){
        addToPinDownList(objref);
      }
    } else {
      throw new JPFException("pinDown reference not a live object: " + objref);
    }
  }

  @Override
  public void releasePinDown(int objref){
    ElementInfo ei = getModifiable(objref);
    if (ei != null) {
      if (ei.decPinDown()){
        removeFromPinDownList(objref);
      }
    } else {
      throw new JPFException("pinDown reference not a live object: " + objref);
    }
  }  
  void markPinDownList (){
    if (pinDownList != null){
      int len = pinDownList.size();
      for (int i=0; i<len; i++){
        int objref = pinDownList.get(i);
        queueMark(objref);
      }
    }
  }
  
  //--- weak reference handling
  
  @Override
  public void registerWeakReference (ElementInfo ei) {
    if (weakRefs == null) {
      weakRefs = new ArrayList<ElementInfo>();
    }

    weakRefs.add(ei);
  }
  
  /**
   * reset all weak references that now point to collected objects to 'null'
   * NOTE: this implementation requires our own Reference/WeakReference implementation, to
   * make sure the 'ref' field is the first one
   */
  protected void cleanupWeakRefs () {
    if (weakRefs != null) {
      for (ElementInfo ei : weakRefs) {
        Fields f = ei.getFields();
        int    ref = f.getIntValue(0); // watch out, the 0 only works with our own WeakReference impl
        if (ref != MJIEnv.NULL) {
          ElementInfo refEi = get(ref);
          if ((refEi == null) || (refEi.isNull())) {
            ei = ei.getModifiableInstance();
            // we need to make sure the Fields are properly state managed
            ei.setReferenceField(ei.getFieldInfo(0), MJIEnv.NULL);
          }
        }
      }

      weakRefs = null;
    }
  }
  
  // NOTE - this is where to assert if this index isn't occupied yet, since only concrete classes know
  // if there can be collisions, and how elements are stored
  
  protected abstract AllocationContext getSUTAllocationContext (ClassInfo ci, ThreadInfo ti);
  protected abstract AllocationContext getSystemAllocationContext (ClassInfo ci, ThreadInfo ti, int anchor);
  
  /**
   * this is called for newXX(..) allocations that are SUT thread specific, i.e. in response to
   * a explicit NEW or xNEWARRAY instruction that should take the allocating thread into account 
   */
  protected abstract int getNewElementInfoIndex (AllocationContext ctx);
  
  //--- allocators
    
  protected ElementInfo createObject (ClassInfo ci, ThreadInfo ti, int objref) {
    // create the thing itself
    Fields f = ci.createInstanceFields();
    Monitor m = new Monitor();
    ElementInfo ei = createElementInfo( objref, ci, f, m, ti);
    
    set(objref, ei);

    attributes |= ATTR_ELEMENTS_CHANGED;

    // and do the default (const) field initialization
    ci.initializeInstanceData(ei, ti);

    vm.notifyObjectCreated(ti, ei);
    
    // note that we don't return -1 if 'outOfMemory' (which is handled in
    // the NEWxx bytecode) because our allocs are used from within the
    // exception handling of the resulting OutOfMemoryError (and we would
    // have to override it, since the VM should guarantee proper exceptions)
    
    return ei;    
  }
    
  @Override
  public ElementInfo newObject(ClassInfo ci, ThreadInfo ti) {
    AllocationContext ctx = getSUTAllocationContext( ci, ti);
    int index = getNewElementInfoIndex( ctx);
    ElementInfo ei = createObject( ci, ti, index);

    return ei;
  }

  @Override
  public ElementInfo newSystemObject (ClassInfo ci, ThreadInfo ti, int anchor) {
    AllocationContext ctx = getSystemAllocationContext( ci, ti, anchor);
    int index = getNewElementInfoIndex( ctx);
    ElementInfo ei = createObject( ci, ti, index);
    return ei;
  }
  
  protected ElementInfo createArray (String elementType, int nElements, ClassInfo ci, ThreadInfo ti, int objref) {

    Fields f = ci.createArrayFields(ci.getName(), nElements, Types.getTypeSize(elementType), Types.isReference(elementType));
    Monitor m = new Monitor();
    DynamicElementInfo ei = createElementInfo( objref, ci, f, m, ti);

    set(objref, ei);

    attributes |= ATTR_ELEMENTS_CHANGED;

    vm.notifyObjectCreated(ti, ei);

    return ei;
  }
  
  protected ClassInfo getArrayClassInfo (ThreadInfo ti, String elementType) {
    String type = "[" + elementType;
    SystemClassLoaderInfo sysCl = ti.getSystemClassLoaderInfo();
    ClassInfo ciArray = sysCl.getResolvedClassInfo(type);

    if (!ciArray.isInitialized()) {
      // we do this explicitly here since there are no clinits for array classes
      ciArray.registerClass(ti);
      ciArray.setInitialized();
    }

    return ciArray;
  }
  
  @Override
  public ElementInfo newArray(String elementType, int nElements, ThreadInfo ti) {
    // see newObject for OOM simulation
    ClassInfo ci = getArrayClassInfo(ti, elementType);
    AllocationContext ctx = getSUTAllocationContext( ci, ti);
    
    int index = getNewElementInfoIndex( ctx);
    ElementInfo ei = createArray( elementType, nElements, ci, ti, index);

    return ei;
  }

  @Override
  public ElementInfo newSystemArray(String elementType, int nElements, ThreadInfo ti, int anchor) {
    // see newObject for OOM simulation
    ClassInfo ci = getArrayClassInfo(ti, elementType);
    AllocationContext ctx = getSystemAllocationContext( ci, ti, anchor);
    
    int index = getNewElementInfoIndex( ctx);
    ElementInfo ei = createArray( elementType, nElements, ci, ti, index);

    return ei;
  }

  
  
  protected ElementInfo initializeStringObject( String str, int index, int vref) {
    ElementInfo ei = getModifiable(index);
    ei.setReferenceField("value", vref);

    ElementInfo eVal = getModifiable(vref);
    CharArrayFields cf = (CharArrayFields)eVal.getFields();
    cf.setCharValues(str.toCharArray());
    
    return ei;
  }
  
  protected ElementInfo newString (ClassInfo ciString, ClassInfo ciChars, String str, ThreadInfo ti, AllocationContext ctx) {
    
    //--- the string object itself
    int sRef = getNewElementInfoIndex( ctx);
    createObject( ciString, ti, sRef);
    
    //--- its char[] array
    ctx = ctx.extend(ciChars, sRef);
    int vRef = getNewElementInfoIndex( ctx);
    createArray( "C", str.length(), ciChars, ti, vRef);
    
    ElementInfo ei = initializeStringObject(str, sRef, vRef);      
    return ei;
  }

  @Override
  public ElementInfo newString(String str, ThreadInfo ti){
    if (str != null) {
      SystemClassLoaderInfo sysCl = ti.getSystemClassLoaderInfo();
      ClassInfo ciString = sysCl.getStringClassInfo();
      ClassInfo ciChars = sysCl.getCharArrayClassInfo();
      
      AllocationContext ctx = getSUTAllocationContext( ciString, ti);
      return newString( ciString, ciChars, str, ti, ctx);
      
    } else {
      return null;
    }
  }
  
  @Override
  public ElementInfo newSystemString (String str, ThreadInfo ti, int anchor) {
    if (str != null) {
      SystemClassLoaderInfo sysCl = ti.getSystemClassLoaderInfo();
      ClassInfo ciString = sysCl.getStringClassInfo();
      ClassInfo ciChars = sysCl.getCharArrayClassInfo();
      
      AllocationContext ctx = getSystemAllocationContext( ciString, ti, anchor);
      return newString( ciString, ciChars, str, ti, ctx);
      
    } else {
      return null;
    }    
  }

  @Override
  public ElementInfo newInternString (String str, ThreadInfo ti) {
    if(internStringsMap==null) {
      internStringsMap = vm.getInitialInternStringsMap();
    }
    
    int prcId = ti.getApplicationContext().getId();
    IntTable.Entry<String> e = internStringsMap.get(prcId).get(str);
    
    if (e == null){
      if (str != null) {
        ElementInfo ei = newString( str, ti);
        int index = ei.getObjectRef();
        
        // new interned Strings are always pinned down
        ei.incPinDown();
        addToPinDownList(index);
        addToInternStrings(str, index, prcId);

        return ei;
      
      } else {
        return null;
      }

    } else {
      return get(e.val);
    }
  }

  protected void addToInternStrings (String str, int objref, int prcId) {
    if ((attributes & ATTR_INTERN_CHANGED) == 0){
      // shallow copy all interned strings tables
      internStringsMap = new HashMap<Integer,IntTable<String>>(internStringsMap);
      
      // only clone the interned strings table of the current process
      internStringsMap.put(prcId, internStringsMap.get(prcId).clone());
      
      // just cloned, no need to clone on the next add
      attributes |= ATTR_INTERN_CHANGED;
    }
    internStringsMap.get(prcId).add(str, objref);
  }
  
  
  @Override
  public ElementInfo newSystemThrowable (ClassInfo ciThrowable, String details, int[] stackSnapshot, int causeRef,
                                 ThreadInfo ti, int anchor) {
    SystemClassLoaderInfo sysCl = ti.getSystemClassLoaderInfo(); 
    ClassInfo ciString = sysCl.getStringClassInfo();
    ClassInfo ciChars = sysCl.getCharArrayClassInfo();
    
    //--- the Throwable object itself
    AllocationContext ctx = getSystemAllocationContext( ciThrowable, ti, anchor);
    int xRef = getNewElementInfoIndex( ctx);
    ElementInfo eiThrowable = createObject( ciThrowable, ti, xRef);
    
    //--- the detailMsg field
    if (details != null) {
      AllocationContext ctxString = ctx.extend( ciString, xRef);
      ElementInfo eiMsg = newString( ciString, ciChars, details, ti, ctxString);
      eiThrowable.setReferenceField("detailMessage", eiMsg.getObjectRef());
    }

    //--- the stack snapshot field
    ClassInfo ciSnap = getArrayClassInfo(ti, "I");
    AllocationContext ctxSnap = ctx.extend(ciSnap, xRef);
    int snapRef = getNewElementInfoIndex( ctxSnap);
    ElementInfo eiSnap = createArray( "I", stackSnapshot.length, ciSnap, ti, snapRef);
    int[] snap = eiSnap.asIntArray();
    System.arraycopy( stackSnapshot, 0, snap, 0, stackSnapshot.length);
    eiThrowable.setReferenceField("snapshot", snapRef);

    //--- the cause field
    eiThrowable.setReferenceField("cause", (causeRef != MJIEnv.NULL)? causeRef : xRef);

    return eiThrowable;
  }

  
  //--- abstract accessors

  /*
   * these methods abstract away the container type used in GenericHeap subclasses
   */
  
  /**
   * internal setter used during allocation
   * @param index
   * @param ei
   */  
  protected abstract void set (int index, ElementInfo ei);

  /**
   * public getter to access but not change ElementInfos
   */
  @Override
  public abstract ElementInfo get (int ref);
  
  
  /**
   * public getter to access modifiable ElementInfos;
   */
  @Override
  public abstract ElementInfo getModifiable (int ref);
  
  
  /**
   * internal remover used by generic sweep
   */
  protected abstract void remove (int ref);

  
  //--- iterators
  
  /**
   * return Iterator for all non-null ElementInfo entries
   */
  @Override
  public abstract Iterator<ElementInfo> iterator();
  
  @Override
  public abstract Iterable<ElementInfo> liveObjects();
  
  
  //--- garbage collection
  
  public boolean isGcEnabled (){
    return (attributes & ATTR_GC) != 0;
  }

  public void setGcEnabled (boolean doGC) {
    if (doGC != isGcEnabled()) {
      if (doGC) {
        attributes |= ATTR_GC;
      } else {
        attributes &= ~ATTR_GC;
      }
      attributes |= ATTR_ATTRIBUTE_CHANGED;
    }
  }
  
  @Override
  public void unmarkAll(){
    for (ElementInfo ei : liveObjects()){
      ei.setUnmarked();
    }
  }
  
  /**
   * add a non-null, not yet marked reference to the markQueue
   *  
   * called from ElementInfo.markRecursive(). We don't want to expose the
   * markQueue since a copying collector might not have it
   */
  @Override
  public void queueMark (int objref){
    if (objref == MJIEnv.NULL) {
      return;
    }

    ElementInfo ei = get(objref);
    if (!ei.isMarked()){ // only add objects once
      ei.setMarked();
      markQueue.add(ei);
    }
  }
  
  /**
   * called during non-recursive phase1 marking of all objects reachable
   * from static fields
   * @aspects: gc
   */
  @Override
  public void markStaticRoot (int objref) {
    if (objref != MJIEnv.NULL) {
      queueMark(objref);
    }
  }

  /**
   * called during non-recursive phase1 marking of all objects reachable
   * from Thread roots
   * @aspects: gc
   */
  @Override
  public void markThreadRoot (int objref, int tid) {
    if (objref != MJIEnv.NULL) {
      queueMark(objref);
    }
  }
  
  /**
   * this implementation uses a generic ElementInfo iterator, it can be replaced
   * with a more efficient container specific version
   */
  protected void sweep () {
    ThreadInfo ti = vm.getCurrentThread();
    int tid = ti.getId();
    boolean isThreadTermination = ti.isTerminated();
    int n = 0;
    
    if(vm.finalizersEnabled()) {
      markFinalizableObjects();
    }
    
    // now go over all objects, purge the ones that are not live and reset attrs for rest
    for (ElementInfo ei : this){
      
      if (ei.isMarked()){ // live object, prepare for next transition & gc cycle
        ei.setUnmarked();
        ei.setAlive(liveBitValue);
        
        ei.cleanUp(this, isThreadTermination, tid);
        n++;
        
      } else {
        ei.processReleaseActions();
        
        vm.notifyObjectReleased(ti, ei);
        remove(ei.getObjectRef());
      }
    }
    
    nLiveObjects = n;
  }
  
  protected void markFinalizableObjects () {
    FinalizerThreadInfo tiFinalizer = vm.getFinalizerThread();
    
    if (tiFinalizer != null){
      for (ElementInfo ei : this) {
        if (!ei.isMarked() && ei.hasFinalizer() && !ei.isFinalized()) {
          ei = tiFinalizer.getFinalizerQueuedInstance(ei);
          ei.setMarked(); // make sure it's not collected before the finalizerQueue has been processed
          ei.markRecursive(this);
        }
      }
    }
  }
  
  protected void mark () {
    markQueue.clear();
    
    //--- mark everything in our root set
    markPinDownList();
    vm.getThreadList().markRoots(this);      // mark thread stacks
    vm.getClassLoaderList().markRoots(this); // mark all static references

    //--- trace all entries - this gets recursive
    markQueue.process(elementInfoMarker);    
  }
  
  @Override
  public void gc() {
    vm.notifyGCBegin();

    weakRefs = null;
    liveBitValue = !liveBitValue;

    mark();
    
    // at this point all live objects are marked
    sweep();

    cleanupWeakRefs(); // for potential nullification

    vm.processPostGcActions();
    vm.notifyGCEnd();
  }

  /**
   * clean up reference values that are stored outside of reference fields 
   * called from KernelState to process live ElementInfos after GC has finished
   * and only live objects remain in the heap.
   * 
   * <2do> full heap enumeration is BAD - check if this can be moved into the sweep loop
   */
  @Override
  public void cleanUpDanglingReferences() {
    ThreadInfo ti = ThreadInfo.getCurrentThread();
    int tid = ti.getId();
    boolean isThreadTermination = ti.isTerminated();
    
    for (ElementInfo e : this) {
      if (e != null) {
        e.cleanUp(this, isThreadTermination, tid);
      }
    }
  }
  
  /**
   * check if object is alive. This is here and not in ElementInfo
   * because we might own the liveness bit. In fact, the generic
   * implementation uses bit-toggle to avoid iteration over all live
   * objects at the end of GC
   */
  @Override
  public boolean isAlive (ElementInfo ei){
    return (ei == null || ei.isMarkedOrAlive(liveBitValue));
  }
  
  //--- state management
  
  // since we can't provide generic implementations, we force concrete subclasses to
  // handle volatile information
  
  @Override
  public abstract void resetVolatiles();

  @Override
  public abstract void restoreVolatiles();
  
  @Override
  public boolean hasChanged() {
    return (attributes & ATTR_ANY_CHANGED) != 0;
  }
  
  @Override
  public void markChanged(int objref) {
    attributes |= ATTR_ELEMENTS_CHANGED;
  }

  public void setStored() {
    attributes &= ~ATTR_ANY_CHANGED;
  }
  
  @Override
  public abstract Memento<Heap> getMemento(MementoFactory factory);

  @Override
  public abstract Memento<Heap> getMemento();

  
  //--- out of memory simulation
  
  @Override
  public boolean isOutOfMemory() {
    return (attributes & ATTR_OUT_OF_MEMORY) != 0;
  }

  @Override
  public void setOutOfMemory(boolean isOutOfMemory) {
    if (isOutOfMemory != isOutOfMemory()) {
      if (isOutOfMemory) {
        attributes |= ATTR_OUT_OF_MEMORY;
      } else {
        attributes &= ~ATTR_OUT_OF_MEMORY;
      }
      attributes |= ATTR_ATTRIBUTE_CHANGED;
    }
  }


  
  //--- debugging

  @Override
  public void checkConsistency(boolean isStateStore) {
    for (ElementInfo ei : this){
      ei.checkConsistency();
    }
  }
}