/*
* This file is part of the Jikes RVM project (http://jikesrvm.org).
*
* This file is licensed to You under the Eclipse Public License (EPL);
* You may not use this file except in compliance with the License. You
* may obtain a copy of the License at
*
* http://www.opensource.org/licenses/eclipse-1.0.php
*
* See the COPYRIGHT.txt file distributed with this work for information
* regarding copyright ownership.
*/
package org.mmtk.harness;
import java.util.ArrayDeque;
import java.util.Collections;
import java.util.Deque;
import java.util.HashSet;
import java.util.Set;
import org.mmtk.harness.lang.Trace;
import org.mmtk.harness.lang.Trace.Item;
import org.mmtk.harness.lang.runtime.AllocationSite;
import org.mmtk.harness.lang.runtime.ObjectValue;
import org.mmtk.harness.sanity.Sanity;
import org.mmtk.harness.scheduler.Scheduler;
import org.mmtk.harness.vm.ActivePlan;
import org.mmtk.harness.vm.ObjectModel;
import org.mmtk.harness.vm.Scanning;
import org.mmtk.plan.MutatorContext;
import org.mmtk.plan.Plan;
import org.mmtk.plan.TraceLocal;
import org.mmtk.vm.Collection;
import org.mmtk.vm.VM;
import org.vmmagic.unboxed.Address;
import org.vmmagic.unboxed.ObjectReference;
/**
* This class represents a mutator thread that has memory managed by MMTk.
*
* From within the context of this thread it is possible to call the muXXX methods
* to test MMTk.
*
* To get the current Mutator (from a context where this is valid) it is possible to
* call Mutator.current().
*
* Note that as soon as the mutator is created it is considered active. This means
* that a GC can not occur unless you execute commands on the mutator (or muEnd it).
*/
public abstract class Mutator {
private static boolean gcEveryWB = false;
/**
* Force garbage collection on every write barrier invocation
*/
public static void setGcEveryWB() {
gcEveryWB = true;
}
/**
* @return A mutator context for the current Plan
*/
public static MutatorContext createMutatorContext() {
try {
String prefix = Harness.plan.getValue();
return (MutatorContext)Class.forName(prefix + "Mutator").newInstance();
} catch (Exception ex) {
throw new RuntimeException("Could not create Mutator", ex);
}
}
/**
* Get the currently executing mutator.
* @return the currently executing mutator.
*/
public static Mutator current() {
return Scheduler.currentMutator();
}
/**
* Register a mutator, returning the allocated id.
* @param context The mutator context to register
*/
private static synchronized void register(MutatorContext context) {
context.initMutator(Mutators.registerMutator());
}
/** Is this thread out of memory if the gc cannot free memory */
private boolean outOfMemory;
/** Get the out of memory status
* @return True if we're subject to an out-of-memory condition
*/
public boolean isOutOfMemory() {
return outOfMemory;
}
/**
* Set the out of memory status
* @param value The status
*/
public void setOutOfMemory(boolean value) {
outOfMemory = value;
}
/** The number of collection attempts this thread has had since allocation succeeded */
private int collectionAttempts;
/**
* @return the number of collection attempts
*/
public int getCollectionAttempts() {
return collectionAttempts;
}
/** Report a collection attempt */
public void reportCollectionAttempt() {
collectionAttempts++;
}
/** Clear the collection attempts */
public void clearCollectionAttempts() {
collectionAttempts = 0;
}
/** Was the last failure a physical allocation failure (rather than a budget failure) */
private boolean physicalAllocationFailure;
/**
* Was the last failure a physical allocation failure
* @return Was the last failure a physical allocation failure
*/
public boolean isPhysicalAllocationFailure() {
return physicalAllocationFailure;
}
/** Set if last failure a physical allocation failure
* @param value The new status */
public void setPhysicalAllocationFailure(boolean value) {
physicalAllocationFailure = value;
}
/** The MMTk MutatorContext for this mutator */
protected final MutatorContext context;
/**
* Constructor
*/
public Mutator() {
this.context = createMutatorContext();
register(this.context);
}
/**
* Initial processing of a mutator. Enters the mutator in the mutator table.
*/
public void begin() {
Mutators.set(this);
Scanning.initThreadIteratorTable(this);
}
/**
* @return the MMTk MutatorContext for this mutator.
*/
public MutatorContext getContext() {
return context;
}
/**
* Compute the thread roots for this mutator.
* @param trace The MMTk TraceLocal to receive the roots
*/
public void computeThreadRoots(TraceLocal trace) {
// Nothing to do for the default mutator
}
/**
* Return the roots
* @return The roots for this mutator
*/
public abstract Iterable<ObjectValue> getRoots();
/**
* Print the thread roots and return them for processing.
* @param width Output width
* @return The thread roots
*/
public java.util.Collection<ObjectReference> dumpThreadRoots(int width) {
return Collections.emptyList();
}
/**
* Print the thread roots and add them to a stack for processing.
*/
public static void dumpHeap() {
int width = 80;
Deque<ObjectReference> workStack = new ArrayDeque<ObjectReference>();
Set<ObjectReference> dumped = new HashSet<ObjectReference>();
for(Mutator m: Mutators.getAll()) {
System.err.println("Mutator " + m.context.getId());
workStack.addAll(m.dumpThreadRoots(width));
}
System.err.println("Heap (Depth First)");
while(!workStack.isEmpty()) {
ObjectReference object = workStack.pop();
if (!dumped.contains(object)) {
dumped.add(object);
workStack.addAll(ObjectModel.dumpLogicalObject(width, object));
}
}
}
/**
* A gc safe point for the mutator.
* @return Whether a GC has occurred
*/
public boolean gcSafePoint() {
if (Scheduler.gcTriggered()) {
Scheduler.waitForGC();
return true;
}
return false;
}
/**
* Mark a mutator as no longer active. If a GC has been triggered we must ensure
* that it proceeds before we deactivate.
*/
public void end() {
}
/**
* Request a heap dump (also invokes a garbage collection)
*/
public void heapDump() {
Collector.requestHeapDump();
gc();
}
/**
* Request a garbage collection.
*/
public void gc() {
VM.collection.triggerCollection(Collection.EXTERNAL_GC_TRIGGER);
}
/**
* Fail during execution.
* @param failMessage Message to write
*/
public void fail(String failMessage) {
throw new RuntimeException(failMessage);
}
/**
* Print a message that this code path should be impossible and exit.
*/
public void notReached() {
fail("Unreachable code reached!");
}
/**
* Store a value into the data field of an object.
*
* @param object The object to store the field of.
* @param index The field index.
* @param value The value to store.
*/
public void storeDataField(ObjectReference object, int index, int value) {
if (object.isNull()) fail("Object can not be null in object "+ObjectModel.getString(object));
Sanity.assertValid(object);
int limit = ObjectModel.getDataCount(object);
if (index < 0) fail("Index must be non-negative in object "+ObjectModel.getString(object));
if (index >= limit) fail("Index "+index+" out of bounds "+limit+" in object "+ObjectModel.getString(object));
Address ref = ObjectModel.getDataSlot(object, index);
if (ActivePlan.constraints.needsIntWriteBarrier()) {
context.intWrite(object, ref, value, ref.toWord(), null, Plan.INSTANCE_FIELD);
} else {
ref.store(value);
}
if (Trace.isEnabled(Item.STORE)) {
Trace.trace(Item.STORE,"%s.[%d] = %d", object.toString(), index, value);
}
}
/**
* Store a value into a reference field of an object.
*
* @param object The object to store the field of.
* @param index The field index.
* @param value The value to store.
*/
public void storeReferenceField(ObjectReference object, int index, ObjectReference value) {
if (object.isNull()) fail(("Object can not be null in object "+ObjectModel.getString(object)));
Sanity.assertValid(object);
Sanity.assertValid(value);
int limit = ObjectModel.getRefs(object);
if (Trace.isEnabled(Item.STORE) || ObjectModel.isWatched(object)) {
Trace.printf(Item.STORE,"[%s].object[%d/%d] = %s%n",ObjectModel.getString(object),index,limit,value.toString());
}
if (!(index >= 0)) fail(("Index must be non-negative in object "+ObjectModel.getString(object)));
if (!(index < limit)) fail(("Index "+index+" out of bounds "+limit+" in object "+ObjectModel.getString(object)));
Address referenceSlot = ObjectModel.getRefSlot(object, index);
if (ActivePlan.constraints.needsObjectReferenceWriteBarrier()) {
context.objectReferenceWrite(object, referenceSlot, value, referenceSlot.toWord(), null, Plan.INSTANCE_FIELD);
if (gcEveryWB) {
gc();
}
} else {
referenceSlot.store(value);
}
}
/**
* Load and return the value of a data field of an object.
*
* @param object The object to load the field of.
* @param index The field index.
* @return The contents of the data field
*/
public int loadDataField(ObjectReference object, int index) {
if (object.isNull()) fail(("Object can not be null in object "+ObjectModel.getString(object)));
Sanity.assertValid(object);
int limit = ObjectModel.getDataCount(object);
if (!(index >= 0)) fail(("Index must be non-negative in object "+ObjectModel.getString(object)));
if (!(index < limit)) fail(("Index "+index+" out of bounds "+limit+" in object "+ObjectModel.getString(object)));
Address dataSlot = ObjectModel.getDataSlot(object, index);
int result;
if (ActivePlan.constraints.needsIntReadBarrier()) {
result = context.intRead(object, dataSlot, dataSlot.toWord(), null, Plan.INSTANCE_FIELD);
} else {
result = dataSlot.loadInt();
}
if (Trace.isEnabled(Item.LOAD) || ObjectModel.isWatched(object)) {
Trace.printf(Item.LOAD,"[%s].int[%d] returned [%d]%n",ObjectModel.getString(object),index,result);
}
return result;
}
/**
* Load and return the value of a reference field of an object.
*
* @param object The object to load the field of.
* @param index The field index.
* @return The object reference
*/
public ObjectReference loadReferenceField(ObjectReference object, int index) {
if (object.isNull()) fail(("Object can not be null in object "+ObjectModel.getString(object)));
Sanity.assertValid(object);
int limit = ObjectModel.getRefs(object);
if (!(index >= 0)) fail(("Index must be non-negative in object "+ObjectModel.getString(object)));
if (!(index < limit)) fail(("Index "+index+" out of bounds "+limit+" in object "+ObjectModel.getString(object)));
Address referenceSlot = ObjectModel.getRefSlot(object, index);
ObjectReference result;
if (ActivePlan.constraints.needsObjectReferenceReadBarrier()) {
result = context.objectReferenceRead(object, referenceSlot, referenceSlot.toWord(), null, Plan.INSTANCE_FIELD);
} else {
result = referenceSlot.loadObjectReference();
}
Sanity.assertValid(object);
if (Trace.isEnabled(Item.LOAD) || ObjectModel.isWatched(object)) {
Trace.printf(Item.LOAD,"[%s].object[%d] returned [%s]%n",ObjectModel.getString(object),index,result.toString());
}
return result;
}
/**
* Get the hash code for the given object.
* @param object The object
* @return The hash code
*/
public int hash(ObjectReference object) {
if (object.isNull()) fail("Object can not be null");
int result = ObjectModel.getHashCode(object);
if (Trace.isEnabled(Item.HASH) || ObjectModel.isWatched(object)) {
Trace.printf(Item.HASH,"hash(%s) returned [%d]%n",ObjectModel.getString(object),result);
}
return result;
}
/**
* Allocate an object and return a reference to it.
*
* @param refCount The number of reference fields.
* @param dataCount The number of data fields.
* @param doubleAlign Is this an 8 byte aligned object?
* @param allocSite Allocation site
* @return The object reference.
*/
public ObjectReference alloc(int refCount, int dataCount, boolean doubleAlign, int allocSite) {
if (!(refCount >= 0)) fail("Non-negative reference field count required");
if (!(refCount <= ObjectModel.MAX_REF_FIELDS)) fail(("Maximum of "+ObjectModel.MAX_REF_FIELDS+" reference fields per object"));
if (!(dataCount >= 0)) fail("Non-negative data field count required");
if (!(dataCount <= ObjectModel.MAX_DATA_FIELDS)) fail(("Maximum of "+ObjectModel.MAX_DATA_FIELDS+" data fields per object"));
ObjectReference result = ObjectModel.allocateObject(context, refCount, dataCount, doubleAlign, allocSite);
if (Trace.isEnabled(Item.ALLOC) || ObjectModel.isWatched(result)) {
Trace.printf(Item.ALLOC,"alloc(%d,%d,%b) -> [%s]%n",refCount,dataCount,doubleAlign,ObjectModel.getString(result));
}
if (!(result != null)) fail("Allocation returned null");
return result;
}
/**
* Return a string identifying the allocation site of an object
* @param object The object
* @return Where in the script it was allocated
*/
public static String getSiteName(ObjectReference object) {
int site = ObjectModel.getSite(object);
if (!AllocationSite.isValid(site)) {
return "<invalid>";
}
return AllocationSite.getSite(site).toString();
}
/**
* @return The thread iterator table
*/
public ObjectReference allocThreadIteratorTable() {
return alloc(Scanning.THREAD_ITERATOR_TABLE_ENTRIES,0,false,AllocationSite.INTERNAL_SITE_ID);
}
}