/*
* 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.jikesrvm.runtime;
import static org.jikesrvm.VM.NOT_REACHED;
import static org.jikesrvm.runtime.JavaSizeConstants.BYTES_IN_BYTE;
import static org.jikesrvm.runtime.JavaSizeConstants.BYTES_IN_CHAR;
import static org.jikesrvm.runtime.JavaSizeConstants.BYTES_IN_INT;
import static org.jikesrvm.runtime.JavaSizeConstants.BYTES_IN_LONG;
import static org.jikesrvm.runtime.UnboxedSizeConstants.LOG_BYTES_IN_ADDRESS;
import org.jikesrvm.VM;
import org.jikesrvm.architecture.AbstractRegisters;
import org.jikesrvm.architecture.StackFrameLayout;
import org.jikesrvm.classloader.DynamicTypeCheck;
import org.jikesrvm.classloader.MemberReference;
import org.jikesrvm.classloader.RVMArray;
import org.jikesrvm.classloader.RVMClass;
import org.jikesrvm.classloader.RVMField;
import org.jikesrvm.classloader.RVMMethod;
import org.jikesrvm.classloader.RVMType;
import org.jikesrvm.classloader.TypeReference;
import org.jikesrvm.compilers.common.CompiledMethod;
import org.jikesrvm.compilers.common.CompiledMethods;
import org.jikesrvm.mm.mminterface.Barriers;
import org.jikesrvm.mm.mminterface.MemoryManager;
import org.jikesrvm.objectmodel.ObjectModel;
import org.jikesrvm.objectmodel.TIB;
import org.jikesrvm.scheduler.RVMThread;
import org.jikesrvm.util.Services;
import org.vmmagic.pragma.Entrypoint;
import org.vmmagic.pragma.Inline;
import org.vmmagic.pragma.NoInline;
import org.vmmagic.pragma.Pure;
import org.vmmagic.pragma.Uninterruptible;
import org.vmmagic.pragma.Unpreemptible;
import org.vmmagic.pragma.UnpreemptibleNoWarn;
import org.vmmagic.unboxed.Address;
import org.vmmagic.unboxed.Offset;
import org.vmmagic.unboxed.Word;
/**
* Entrypoints into the runtime of the virtual machine.
*
* <p> These are "helper functions" called from machine code
* emitted by BaselineCompilerImpl.
* They implement functionality that cannot be mapped directly
* into a small inline
* sequence of machine instructions. See also: Linker.
*
* <p> Note #1: If you add, remove, or change the signature of
* any of these methods you may need to change Entrypoints to match.
*
* <p> Note #2: Code here must be carefully written to be gc-safe
* while manipulating
* stackframe and instruction addresses.
*
* <p> Any time we are holding interior pointers to objects that
* could be moved by a garbage
* collection cycle we must either avoid passing through gc-sites
* (by writing
* straight line code with no "non-magic" method invocations) or we
* must turn off the
* collector (so that a gc request initiated by another thread will
* not run until we're
* done manipulating the bare pointers). Furthermore, while
* the collector is turned off,
* we must be careful not to make any allocation requests ("new").
*
* <p> The interior pointers that we must worry about are:
* <ul>
* <li> "ip" values that point to interiors of "code" objects
* <li> "fp" values that point to interior of "stack" objects
* </ul>
*/
public class RuntimeEntrypoints {
private static final boolean traceAthrow = false;
// Trap codes for communication with C trap handler.
//
public static final int TRAP_UNKNOWN = -1;
public static final int TRAP_NULL_POINTER = 0;
public static final int TRAP_ARRAY_BOUNDS = 1;
public static final int TRAP_DIVIDE_BY_ZERO = 2;
public static final int TRAP_STACK_OVERFLOW = 3;
public static final int TRAP_CHECKCAST = 4; // opt-compiler
public static final int TRAP_REGENERATE = 5; // opt-compiler
public static final int TRAP_JNI_STACK = 6; // jni
public static final int TRAP_MUST_IMPLEMENT = 7;
public static final int TRAP_STORE_CHECK = 8; // opt-compiler
public static final int TRAP_UNREACHABLE_BYTECODE = 9; // IA32 baseline compiler assertion
private static final String UNREACHABLE_BC_MESSAGE = "Attempted to execute " +
"a bytecode that was determined to be unreachable!";
//---------------------------------------------------------------//
// Type Checking. //
//---------------------------------------------------------------//
/**
* Test if object is instance of target class/array or
* implements target interface.
* @param object object to be tested
* @param targetID type reference id corresponding to target
* class/array/interface
* @return true iff is object instance of target type?
*/
@Entrypoint
static boolean instanceOf(Object object, int targetID) throws NoClassDefFoundError {
/* Here, LHS and RHS refer to the way we would treat these if they were
arguments to an assignment operator and we were testing for
assignment-compatibility. In Java, "rhs instanceof lhs" means that
the operation "lhs = rhs" would succeed. This of course is backwards
if one is looking at it from the point of view of the "instanceof"
operator. */
TypeReference tRef = TypeReference.getTypeRef(targetID);
RVMType lhsType = tRef.peekType();
if (lhsType == null) {
lhsType = tRef.resolve();
}
if (!lhsType.isResolved()) {
lhsType.resolve(); // forces loading/resolution of super class/interfaces
}
/* Test for null only AFTER we have resolved the type of targetID. */
if (object == null) {
return false; // null is not an instance of any type
}
RVMType rhsType = ObjectModel.getObjectType(object);
/* RHS must already be resolved, since we have a non-null object that is
an instance of RHS */
if (VM.VerifyAssertions) VM._assert(rhsType.isResolved());
if (VM.VerifyAssertions) VM._assert(lhsType.isResolved());
return lhsType == rhsType || DynamicTypeCheck.instanceOfResolved(lhsType, rhsType);
}
/**
* Throw exception unless object is instance of target
* class/array or implements target interface.
* @param object object to be tested
* @param id of type reference corresponding to target class/array/interface
*/
@Entrypoint
static void checkcast(Object object, int id) throws ClassCastException, NoClassDefFoundError {
if (object == null) {
return; // null may be cast to any type
}
TypeReference tRef = TypeReference.getTypeRef(id);
RVMType lhsType = tRef.peekType();
if (lhsType == null) {
lhsType = tRef.resolve();
}
RVMType rhsType = ObjectModel.getObjectType(object);
if (lhsType == rhsType) {
return; // exact match
}
// not an exact match, do more involved lookups
//
if (!isAssignableWith(lhsType, rhsType)) {
throw new ClassCastException("Cannot cast a(n) " + rhsType + " to a(n) " + lhsType);
}
}
@Entrypoint
static void aastore(Object[] arrayRef, int index, Object value) throws ArrayStoreException, ArrayIndexOutOfBoundsException {
checkstore(arrayRef, value);
int nelts = ObjectModel.getArrayLength(arrayRef);
if (index >= 0 && index < nelts) {
Services.setArrayUninterruptible(arrayRef, index, value);
} else {
throw new ArrayIndexOutOfBoundsException(index);
}
}
@Entrypoint
@Uninterruptible
static void aastoreUninterruptible(Object[] arrayRef, int index, Object value) {
if (VM.VerifyAssertions) {
int nelts = ObjectModel.getArrayLength(arrayRef);
VM._assert(index >= 0 && index < nelts);
}
Services.setArrayUninterruptible(arrayRef, index, value);
}
@Entrypoint
@Inline
static void checkstore(Object array, Object arrayElement) throws ArrayStoreException {
if (arrayElement == null) {
return; // null may be assigned to any type
}
RVMType lhsType = Magic.getObjectType(array);
RVMType elmType = lhsType.asArray().getElementType();
if (elmType == RVMType.JavaLangObjectType) {
return; // array of Object can receive anything
}
RVMType rhsType = Magic.getObjectType(arrayElement);
if (elmType == rhsType) {
return; // exact type match
}
if (isAssignableWith(elmType, rhsType)) {
return;
}
throw new ArrayStoreException();
}
/**
* May a variable of type "lhs" be assigned a value of type "rhs"?
* @param lhs type of variable
* @param rhs type of value
* @return true --> assignment is legal
* false --> assignment is illegal
* <strong>Assumption</strong>: caller has already tested "trivial" case
* (exact type match)
* so we need not repeat it here
*/
@Pure
@Inline(value = Inline.When.AllArgumentsAreConstant)
public static boolean isAssignableWith(RVMType lhs, RVMType rhs) {
if (!lhs.isResolved()) {
lhs.resolve();
}
if (!rhs.isResolved()) {
rhs.resolve();
}
return DynamicTypeCheck.instanceOfResolved(lhs, rhs);
}
//---------------------------------------------------------------//
// Object Allocation. //
//---------------------------------------------------------------//
/**
* Allocate something like "new Foo()".
* @param id id of type reference of class to create
* @param site the site id of the calling allocation site
* @return object with header installed and all fields set to zero/null
* (ready for initializer to be run on it)
* See also: bytecode 0xbb ("new")
*/
@Entrypoint
static Object unresolvedNewScalar(int id, int site) throws NoClassDefFoundError, OutOfMemoryError {
TypeReference tRef = TypeReference.getTypeRef(id);
RVMType t = tRef.peekType();
if (t == null) {
t = tRef.resolve();
}
RVMClass cls = t.asClass();
if (!cls.isInitialized()) {
initializeClassForDynamicLink(cls);
}
int allocator = MemoryManager.pickAllocator(cls);
int align = ObjectModel.getAlignment(cls);
int offset = ObjectModel.getOffsetForAlignment(cls, false);
return resolvedNewScalar(cls.getInstanceSize(),
cls.getTypeInformationBlock(),
cls.hasFinalizer(),
allocator,
align,
offset,
site);
}
/**
* Allocate something like "new Foo()".
* @param cls RVMClass of array to create
* @return object with header installed and all fields set to zero/null
* (ready for initializer to be run on it)
* See also: bytecode 0xbb ("new")
*/
public static Object resolvedNewScalar(RVMClass cls) {
int allocator = MemoryManager.pickAllocator(cls);
int site = MemoryManager.getAllocationSite(false);
int align = ObjectModel.getAlignment(cls);
int offset = ObjectModel.getOffsetForAlignment(cls, false);
return resolvedNewScalar(cls.getInstanceSize(),
cls.getTypeInformationBlock(),
cls.hasFinalizer(),
allocator,
align,
offset,
site);
}
/**
* Allocate something like "new Foo()".
* @param size size of object (including header), in bytes
* @param tib type information block for object
* @param hasFinalizer does this type have a finalizer?
* @param allocator int that encodes which allocator should be used
* @param align the alignment requested; must be a power of 2.
* @param offset the offset at which the alignment is desired.
* @param site the site id of the calling allocation site
* @return object with header installed and all fields set to zero/null
* (ready for initializer to be run on it)
* See also: bytecode 0xbb ("new")
*/
@Entrypoint
public static Object resolvedNewScalar(int size, TIB tib, boolean hasFinalizer, int allocator, int align,
int offset, int site) throws OutOfMemoryError {
// GC stress testing
if (VM.ForceFrequentGC) checkAllocationCountDownToGC();
// Allocate the object and initialize its header
Object newObj = MemoryManager.allocateScalar(size, tib, allocator, align, offset, site);
// Deal with finalization
if (hasFinalizer) MemoryManager.addFinalizer(newObj);
return newObj;
}
/**
* Allocate something like "new Foo[]".
* @param numElements number of array elements
* @param id id of type reference of array to create.
* @param site the site id of the calling allocation site
* @return array with header installed and all fields set to zero/null
* See also: bytecode 0xbc ("anewarray")
*/
@Entrypoint
public static Object unresolvedNewArray(int numElements, int id, int site)
throws NoClassDefFoundError, OutOfMemoryError, NegativeArraySizeException {
TypeReference tRef = TypeReference.getTypeRef(id);
RVMType t = tRef.peekType();
if (t == null) {
t = tRef.resolve();
}
RVMArray array = t.asArray();
if (!array.isInitialized()) {
array.resolve();
array.instantiate();
}
return resolvedNewArray(numElements, array, site);
}
/**
* Allocate something like "new Foo[]".
* @param numElements number of array elements
* @param array RVMArray of array to create
* @return array with header installed and all fields set to zero/null
* See also: bytecode 0xbc ("anewarray")
*/
public static Object resolvedNewArray(int numElements, RVMArray array)
throws OutOfMemoryError, NegativeArraySizeException {
return resolvedNewArray(numElements, array, MemoryManager.getAllocationSite(false));
}
public static Object resolvedNewArray(int numElements, RVMArray array, int site)
throws OutOfMemoryError, NegativeArraySizeException {
return resolvedNewArray(numElements,
array.getLogElementSize(),
ObjectModel.computeArrayHeaderSize(array),
array.getTypeInformationBlock(),
MemoryManager.pickAllocator(array),
ObjectModel.getAlignment(array),
ObjectModel.getOffsetForAlignment(array, false),
site);
}
/**
* Allocate something like "new int[cnt]" or "new Foo[cnt]".
* @param numElements number of array elements
* @param logElementSize size in bytes of an array element, log base 2.
* @param headerSize size in bytes of array header
* @param tib type information block for array object
* @param allocator int that encodes which allocator should be used
* @param align the alignment requested; must be a power of 2.
* @param offset the offset at which the alignment is desired.
* @param site the site id of the calling allocation site
* @return array object with header installed and all elements set
* to zero/null
* See also: bytecode 0xbc ("newarray") and 0xbd ("anewarray")
*/
@Entrypoint
public static Object resolvedNewArray(int numElements, int logElementSize, int headerSize, TIB tib,
int allocator, int align, int offset, int site)
throws OutOfMemoryError, NegativeArraySizeException {
if (numElements < 0) raiseNegativeArraySizeException();
// GC stress testing
if (VM.ForceFrequentGC) checkAllocationCountDownToGC();
// Allocate the array and initialize its header
return MemoryManager.allocateArray(numElements, logElementSize, headerSize, tib, allocator, align, offset, site);
}
/**
* Clone a Scalar or Array Object.
* called from java/lang/Object.clone().
* <p>
* For simplicity, we just code this more or less in Java using
* internal reflective operations and some magic.
* This is inefficient for large scalar objects, but until that
* is proven to be a performance problem, we won't worry about it.
* By keeping this in Java instead of dropping into Memory.copy,
* we avoid having to add special case code to deal with write barriers,
* and other such things.
* <p>
* This method calls specific cloning routines based on type to help
* guide the inliner (which won't inline a single large method).
*
* @param obj the object to clone
* @return the cloned object
* @throws CloneNotSupportedException when the object does not support cloning
*/
public static Object clone(Object obj) throws OutOfMemoryError, CloneNotSupportedException {
RVMType type = Magic.getObjectType(obj);
if (type.isArrayType()) {
return cloneArray(obj, type);
} else {
return cloneClass(obj, type);
}
}
/**
* Clone an array
*
* @param obj the array to clone
* @param type the type information for the array
* @return the cloned object
*/
private static Object cloneArray(Object obj, RVMType type) throws OutOfMemoryError {
RVMArray ary = type.asArray();
int nelts = ObjectModel.getArrayLength(obj);
Object newObj = resolvedNewArray(nelts, ary);
System.arraycopy(obj, 0, newObj, 0, nelts);
return newObj;
}
/**
* Clone an object implementing a class - check that the class is cloneable
* (we make this a small method with just a test so that the inliner will
* inline it and hopefully eliminate the instanceof test).
*
* @param obj the object to clone
* @param type the type information for the class
* @return the cloned object
* @throws CloneNotSupportedException when the object does not support cloning
*/
private static Object cloneClass(Object obj, RVMType type) throws OutOfMemoryError, CloneNotSupportedException {
if (!(obj instanceof Cloneable)) {
throw new CloneNotSupportedException();
} else {
return cloneClass2(obj, type);
}
}
/**
* Clone an object implementing a class - the actual clone
*
* @param obj the object to clone
* @param type the type information for the class
* @return the cloned object
*/
private static Object cloneClass2(Object obj, RVMType type) throws OutOfMemoryError {
RVMClass cls = type.asClass();
Object newObj = resolvedNewScalar(cls);
for (RVMField f : cls.getInstanceFields()) {
if (f.isReferenceType()) {
// Writing a reference
// Do via slower "VM-internal reflection" to enable
// collectors to do the right thing wrt reference counting
// and write barriers.
f.setObjectValueUnchecked(newObj, f.getObjectValueUnchecked(obj));
} else {
// Primitive type
// Check if we need to go via the slower barried path
TypeReference fieldType = f.getType();
if (Barriers.NEEDS_BOOLEAN_PUTFIELD_BARRIER && fieldType.isBooleanType()) {
f.setBooleanValueUnchecked(newObj, f.getBooleanValueUnchecked(obj));
continue;
} else if (Barriers.NEEDS_BYTE_PUTFIELD_BARRIER && fieldType.isByteType()) {
f.setByteValueUnchecked(newObj, f.getByteValueUnchecked(obj));
continue;
} else if (Barriers.NEEDS_CHAR_PUTFIELD_BARRIER && fieldType.isCharType()) {
f.setCharValueUnchecked(newObj, f.getCharValueUnchecked(obj));
continue;
} else if (Barriers.NEEDS_DOUBLE_PUTFIELD_BARRIER && fieldType.isDoubleType()) {
f.setDoubleValueUnchecked(newObj, f.getDoubleValueUnchecked(obj));
continue;
} else if (Barriers.NEEDS_FLOAT_PUTFIELD_BARRIER && fieldType.isFloatType()) {
f.setFloatValueUnchecked(newObj, f.getFloatValueUnchecked(obj));
continue;
} else if (Barriers.NEEDS_INT_PUTFIELD_BARRIER && fieldType.isIntType()) {
f.setIntValueUnchecked(newObj, f.getIntValueUnchecked(obj));
continue;
} else if (Barriers.NEEDS_LONG_PUTFIELD_BARRIER && fieldType.isLongType()) {
f.setLongValueUnchecked(newObj, f.getLongValueUnchecked(obj));
continue;
} else if (Barriers.NEEDS_SHORT_PUTFIELD_BARRIER && fieldType.isShortType()) {
f.setShortValueUnchecked(newObj, f.getShortValueUnchecked(obj));
continue;
} else if (Barriers.NEEDS_WORD_PUTFIELD_BARRIER && fieldType.isWordType()) {
f.setWordValueUnchecked(newObj, f.getWordValueUnchecked(obj));
continue;
} else if (Barriers.NEEDS_ADDRESS_PUTFIELD_BARRIER && fieldType.isAddressType()) {
f.setAddressValueUnchecked(newObj, f.getAddressValueUnchecked(obj));
continue;
} else if (Barriers.NEEDS_EXTENT_PUTFIELD_BARRIER && fieldType.isExtentType()) {
f.setExtentValueUnchecked(newObj, f.getExtentValueUnchecked(obj));
continue;
} else if (Barriers.NEEDS_OFFSET_PUTFIELD_BARRIER && fieldType.isOffsetType()) {
f.setOffsetValueUnchecked(newObj, f.getOffsetValueUnchecked(obj));
continue;
} else {
// Can perform raw copy of field
int size = f.getSize();
Offset offset = f.getOffset();
if (VM.BuildFor32Addr) {
// As per pre primitive write barrier code we test the most likely
// case first
if (size == BYTES_IN_INT) {
int bits = Magic.getIntAtOffset(obj, offset);
Magic.setIntAtOffset(newObj, offset, bits);
continue;
} else if (size == BYTES_IN_LONG) {
long bits = Magic.getLongAtOffset(obj, offset);
Magic.setLongAtOffset(newObj, offset, bits);
continue;
}
} else {
// BuildFor64Addr
// As per pre primitive write barrier code we test the most likely
// case first
if (size == BYTES_IN_LONG) {
long bits = Magic.getLongAtOffset(obj, offset);
Magic.setLongAtOffset(newObj, offset, bits);
continue;
} else if (size == BYTES_IN_INT) {
int bits = Magic.getIntAtOffset(obj, offset);
Magic.setIntAtOffset(newObj, offset, bits);
continue;
}
}
if (size == BYTES_IN_CHAR) {
char bits = Magic.getCharAtOffset(obj, offset);
Magic.setCharAtOffset(newObj, offset, bits);
} else {
if (VM.VerifyAssertions) VM._assert(size == BYTES_IN_BYTE);
byte bits = Magic.getByteAtOffset(obj, offset);
Magic.setByteAtOffset(newObj, offset, bits);
}
}
}
}
return newObj;
}
/**
* Helper function to actually throw the required exception.
* Keep out of line to mitigate code space when quickNewArray is inlined.
*/
@NoInline
private static void raiseNegativeArraySizeException() throws NegativeArraySizeException {
throw new NegativeArraySizeException();
}
/**
* Get an object's "hashcode" value.
*
* Side effect: hash value is generated and stored into object's
* status word.
* @param object the object to hash
* @return object's hashcode.
* @see java.lang.Object#hashCode
*/
public static int getObjectHashCode(Object object) {
return ObjectModel.getObjectHashCode(object);
}
//---------------------------------------------------------------//
// Dynamic linking. //
//---------------------------------------------------------------//
/**
* Prepare a class for use prior to first allocation,
* field access, or method invocation.
* Made public so that it is accessible from java.lang.reflect.*.
*
* @param cls the class to prepare for dynamic link
* @see MemberReference#needsDynamicLink
*/
public static void initializeClassForDynamicLink(RVMClass cls) {
if (VM.TraceClassLoading) {
VM.sysWriteln("RuntimeEntrypoints.initializeClassForDynamicLink: (begin) " + cls);
}
cls.prepareForFirstUse();
if (VM.TraceClassLoading) {
VM.sysWriteln("RuntimeEntrypoints.initializeClassForDynamicLink: (end) " + cls);
}
}
//---------------------------------------------------------------//
// Implementation Errors. //
//---------------------------------------------------------------//
/**
* Report unexpected method call: interface method
* (virtual machine dispatching error, shouldn't happen).
*/
static void unexpectedInterfaceMethodCall() {
VM.sysFail("interface method dispatching error");
}
/**
* Report unexpected method call: abstract method (verification error).
*/
@Entrypoint
static void unexpectedAbstractMethodCall() {
VM.sysWriteln("RuntimeEntrypoints.unexpectedAbstractMethodCall");
throw new AbstractMethodError();
}
//---------------------------------------------------------------//
// Exception Handling. //
//---------------------------------------------------------------//
/**
* Deliver a software exception to current java thread.
* @param exceptionObject exception object to deliver
* (null --> deliver NullPointerException).
* does not return
* (stack is unwound and execution resumes in a catch block)
*
* This method is public so that it can be invoked by java.lang.VMClass.
*/
@NoInline
@Entrypoint
@Unpreemptible("Deliver exception possibly from unpreemptible code")
public static void athrow(Throwable exceptionObject) {
if (traceAthrow) {
VM.sysWriteln("in athrow.");
RVMThread.dumpStack();
}
RVMThread myThread = RVMThread.getCurrentThread();
AbstractRegisters exceptionRegisters = myThread.getExceptionRegisters();
VM.disableGC(); // VM.enableGC() is called when the exception is delivered.
Magic.saveThreadState(exceptionRegisters);
exceptionRegisters.setInUse(true);
deliverException(exceptionObject, exceptionRegisters);
}
/**
* Deliver a hardware exception to current java thread.
* <p>
* Does not return.
* (stack is unwound, starting at trap site, and
* execution resumes in a catch block somewhere up the stack)
* /or/ execution resumes at instruction following trap
* (for TRAP_STACK_OVERFLOW)
*
* <p> Note: Control reaches here by the actions of an
* external "C" signal handler
* which saves the register state of the trap site into the
* "exceptionRegisters" field of the current
* Thread object.
* The signal handler also inserts a <hardware trap> frame
* onto the stack immediately above this frame, for use by
* HardwareTrapGCMapIterator during garbage collection.
*
* @param trapCode code indicating kind of exception that was trapped
* (see TRAP_xxx, above)
* @param trapInfo array subscript (for array bounds trap, only), marker
* (for stack overflow traps on PPC) or
*/
@Entrypoint
@UnpreemptibleNoWarn
static void deliverHardwareException(int trapCode, Word trapInfo) {
if (VM.verboseSignalHandling) VM.sysWriteln("delivering hardware exception");
RVMThread myThread = RVMThread.getCurrentThread();
if (VM.verboseSignalHandling) VM.sysWriteln("we have a thread = ",Magic.objectAsAddress(myThread));
if (VM.verboseSignalHandling) VM.sysWriteln("it's in state = ",myThread.getExecStatus());
AbstractRegisters exceptionRegisters = myThread.getExceptionRegisters();
if (VM.verboseSignalHandling) VM.sysWriteln("we have exception registers = ",Magic.objectAsAddress(exceptionRegisters));
if ((trapCode == TRAP_STACK_OVERFLOW || trapCode == TRAP_JNI_STACK) &&
myThread.getStack().length < (StackFrameLayout.getMaxStackSize() >> LOG_BYTES_IN_ADDRESS) &&
!myThread.hasNativeStackFrame()) {
// expand stack by the size appropriate for normal or native frame
// and resume execution at successor to trap instruction
// (C trap handler has set register.ip to the instruction following the trap).
if (trapCode == TRAP_JNI_STACK) {
RVMThread.resizeCurrentStack(myThread.getStackLength() + StackFrameLayout.getJNIStackGrowthSize(), exceptionRegisters);
} else {
RVMThread.resizeCurrentStack(myThread.getStackLength() + StackFrameLayout.getStackGrowthSize(), exceptionRegisters);
}
if (VM.VerifyAssertions) VM._assert(exceptionRegisters.getInUse());
exceptionRegisters.setInUse(false);
Magic.restoreHardwareExceptionState(exceptionRegisters);
if (VM.VerifyAssertions) VM._assert(NOT_REACHED);
}
// GC stress testing
if (canForceGC()) {
//VM.sysWriteln("FORCING GC: in deliverHardwareException");
System.gc();
}
// Sanity checking.
// Hardware traps in uninterruptible code should be considered hard failures.
if (!VM.sysFailInProgress()) {
Address fp = exceptionRegisters.getInnermostFramePointer();
int compiledMethodId = Magic.getCompiledMethodID(fp);
if (compiledMethodId != StackFrameLayout.getInvisibleMethodID()) {
CompiledMethod compiledMethod = CompiledMethods.getCompiledMethod(compiledMethodId);
Address ip = exceptionRegisters.getInnermostInstructionAddress();
Offset instructionOffset = compiledMethod.getInstructionOffset(ip);
if (compiledMethod.isWithinUninterruptibleCode(instructionOffset)) {
VM.sysWriteln();
switch (trapCode) {
case TRAP_NULL_POINTER:
VM.sysWriteln("Fatal error: NullPointerException within uninterruptible region.");
break;
case TRAP_ARRAY_BOUNDS:
VM.sysWriteln("Fatal error: ArrayIndexOutOfBoundsException within uninterruptible region (index was ", trapInfo.toInt(), ").");
break;
case TRAP_DIVIDE_BY_ZERO:
VM.sysWriteln("Fatal error: DivideByZero within uninterruptible region.");
break;
case TRAP_STACK_OVERFLOW:
case TRAP_JNI_STACK:
VM.sysWriteln("Fatal error: StackOverflowError within uninterruptible region.");
break;
case TRAP_CHECKCAST:
VM.sysWriteln("Fatal error: ClassCastException within uninterruptible region.");
break;
case TRAP_MUST_IMPLEMENT:
VM.sysWriteln("Fatal error: IncompatibleClassChangeError within uninterruptible region.");
break;
case TRAP_STORE_CHECK:
VM.sysWriteln("Fatal error: ArrayStoreException within uninterruptible region.");
break;
case TRAP_UNREACHABLE_BYTECODE:
VM.sysWriteln("Fatal error: Reached a bytecode that was determined to be unreachable within uninterruptible region.");
break;
default:
VM.sysWriteln("Fatal error: Unknown hardware trap within uninterruptible region.");
break;
}
VM.sysWriteln("trapCode = ", trapCode);
VM.sysWriteln("trapInfo = ", trapInfo.toAddress());
VM.sysFail("Exiting virtual machine due to uninterruptibility violation.");
}
}
}
Throwable exceptionObject;
switch (trapCode) {
case TRAP_NULL_POINTER:
exceptionObject = new java.lang.NullPointerException();
break;
case TRAP_ARRAY_BOUNDS:
exceptionObject = new java.lang.ArrayIndexOutOfBoundsException(trapInfo.toInt());
break;
case TRAP_DIVIDE_BY_ZERO:
exceptionObject = new java.lang.ArithmeticException();
break;
case TRAP_STACK_OVERFLOW:
case TRAP_JNI_STACK:
exceptionObject = new java.lang.StackOverflowError();
break;
case TRAP_CHECKCAST:
exceptionObject = new java.lang.ClassCastException();
break;
case TRAP_MUST_IMPLEMENT:
exceptionObject = new java.lang.IncompatibleClassChangeError();
break;
case TRAP_STORE_CHECK:
exceptionObject = new java.lang.ArrayStoreException();
break;
case TRAP_UNREACHABLE_BYTECODE:
exceptionObject = new java.lang.InternalError(UNREACHABLE_BC_MESSAGE);
break;
default:
exceptionObject = new java.lang.UnknownError();
RVMThread.traceback("UNKNOWN ERROR");
break;
}
VM.disableGC(); // VM.enableGC() is called when the exception is delivered.
deliverException(exceptionObject, exceptionRegisters);
}
/**
* Unlock an object and then deliver a software exception
* to current java thread.
* <p>
* Does not return (stack is unwound and execution resumes in a catch block).
*
* @param objToUnlock object to unlock
* @param objToThrow exception object to deliver
* ({@code null} --> deliver NullPointerException).
*/
@NoInline
@Entrypoint
static void unlockAndThrow(Object objToUnlock, Throwable objToThrow) {
ObjectModel.genericUnlock(objToUnlock);
athrow(objToThrow);
}
/**
* Create and throw a java.lang.ArrayIndexOutOfBoundsException.
* Only used in some configurations where it is easier to make a call
* then recover the array index from a trap instruction.
*
* @param index the failing index
*/
@NoInline
@Entrypoint
static void raiseArrayIndexOutOfBoundsException(int index) {
throw new java.lang.ArrayIndexOutOfBoundsException(index);
}
/**
* Create and throw a java.lang.ArrayIndexOutOfBoundsException.
* Used (rarely) by the opt compiler when it has determined that
* an array access will unconditionally raise an array bounds check
* error, but it has lost track of exactly what the index is going to be.
*/
@NoInline
static void raiseArrayIndexOutOfBoundsException() {
throw new java.lang.ArrayIndexOutOfBoundsException();
}
/**
* Create and throw a java.lang.NullPointerException.
* Used in a few circumstances to reduce code space costs
* of inlining (see java.lang.System.arraycopy()). Could also
* be used to raise a null pointer exception without going through
* the hardware trap handler; currently this is only done when the
* opt compiler has determined that an instruction will unconditionally
* raise a null pointer exception.
*/
@NoInline
@Entrypoint
public static void raiseNullPointerException() {
throw new java.lang.NullPointerException();
}
/**
* Create and throw a java.lang.ArrayStoreException.
* Used in a few circumstances to reduce code space costs
* of inlining (see java.lang.System.arraycopy()).
*/
@NoInline
public static void raiseArrayStoreException() {
throw new java.lang.ArrayStoreException();
}
/**
* Create and throw a java.lang.ArithmeticException.
* Used to raise an arithmetic exception without going through
* the hardware trap handler; currently this is only done when the
* opt compiler has determined that an instruction will unconditionally
* raise an arithmetic exception.
*/
@NoInline
@Entrypoint
static void raiseArithmeticException() {
throw new java.lang.ArithmeticException();
}
/**
* Create and throw a java.lang.AbstractMethodError.
* Used to handle error cases in invokeinterface dispatching.
*/
@NoInline
@Entrypoint
static void raiseAbstractMethodError() {
throw new java.lang.AbstractMethodError();
}
/**
* Create and throw a java.lang.IllegalAccessError.
* Used to handle error cases in invokeinterface dispatching.
*/
@NoInline
@Entrypoint
static void raiseIllegalAccessError() {
throw new java.lang.IllegalAccessError();
}
//----------------//
// implementation //
//----------------//
public static void init() {
// tell the bootloader (sysSignal*.c) to pass control to
// "RuntimeEntrypoints.deliverHardwareException()"
// whenever the host operating system detects a hardware trap.
//
BootRecord.the_boot_record.hardwareTrapMethodId = CompiledMethods.createHardwareTrapCompiledMethod().getId();
BootRecord.the_boot_record.deliverHardwareExceptionOffset =
Entrypoints.deliverHardwareExceptionMethod.getOffset();
// tell the bootloader (sysSignal.c) to set "RVMThread.debugRequested" flag
// whenever the host operating system detects a debug request signal
//
BootRecord.the_boot_record.debugRequestedOffset = Entrypoints.debugRequestedField.getOffset();
}
/**
* Build a multi-dimensional array.
* @param methodId TODO document me
* @param numElements number of elements to allocate for each dimension
* @param arrayType type of array that will result
* @return array object
*/
public static Object buildMultiDimensionalArray(int methodId, int[] numElements, RVMArray arrayType) {
RVMMethod method = MemberReference.getMethodRef(methodId).peekResolvedMethod();
if (VM.VerifyAssertions) VM._assert(method != null);
return buildMDAHelper(method, numElements, 0, arrayType);
}
/**
* Build a two-dimensional array.
* @param methodId TODO document me
* @param dim0 the arraylength for arrays in dimension 0
* @param dim1 the arraylength for arrays in dimension 1
* @param arrayType type of array that will result
* @return array object
*/
public static Object buildTwoDimensionalArray(int methodId, int dim0, int dim1, RVMArray arrayType) {
RVMMethod method = MemberReference.getMethodRef(methodId).peekResolvedMethod();
if (VM.VerifyAssertions) VM._assert(method != null);
if (!arrayType.isInstantiated()) {
arrayType.resolve();
arrayType.instantiate();
}
Object[] newArray = (Object[])resolvedNewArray(dim0, arrayType);
RVMArray innerArrayType = arrayType.getElementType().asArray();
if (!innerArrayType.isInstantiated()) {
innerArrayType.resolve();
innerArrayType.instantiate();
}
for (int i = 0; i < dim0; i++) {
newArray[i] = resolvedNewArray(dim1, innerArrayType);
}
return newArray;
}
/**
* @param method Apparently unused (!)
* @param numElements Number of elements to allocate for each dimension
* @param dimIndex Current dimension to build
* @param arrayType type of array that will result
* @return a multi-dimensional array
*/
public static Object buildMDAHelper(RVMMethod method, int[] numElements, int dimIndex, RVMArray arrayType) {
if (!arrayType.isInstantiated()) {
arrayType.resolve();
arrayType.instantiate();
}
int nelts = numElements[dimIndex];
Object newObject = resolvedNewArray(nelts, arrayType);
if (++dimIndex == numElements.length) {
return newObject; // all dimensions have been built
}
Object[] newArray = (Object[]) newObject;
RVMArray newArrayType = arrayType.getElementType().asArray();
for (int i = 0; i < nelts; ++i) {
newArray[i] = buildMDAHelper(method, numElements, dimIndex, newArrayType);
}
return newArray;
}
/**
* Deliver an exception to current java thread.
* <STRONG> Precondition: </STRONG> VM.disableGC has already been called.
* <ol>
* <li> exceptionRegisters may not match any reasonable stack
* frame at this point.
* <li> we're going to be playing with raw addresses (fp, ip).
* </ol>
* <p>
* Does not return:
* <ul>
* <li> stack is unwound and execution resumes in a catch block
* <li> <em> or </em> current thread is terminated if no catch block is found
* </ul>
* @param exceptionObject exception object to deliver
* @param exceptionRegisters register state corresponding to exception site
*/
@Unpreemptible("Deliver exception trying to avoid preemption")
private static void deliverException(Throwable exceptionObject, AbstractRegisters exceptionRegisters) {
if (VM.TraceExceptionDelivery) {
VM.sysWriteln("RuntimeEntrypoints.deliverException() entered; just got an exception object.");
}
//VM.sysWriteln("throwing exception!");
//RVMThread.dumpStack();
// walk stack and look for a catch block
//
if (VM.TraceExceptionDelivery) {
VM.sysWrite("Hunting for a catch block...");
}
RVMType exceptionType = Magic.getObjectType(exceptionObject);
Address fp = exceptionRegisters.getInnermostFramePointer();
Address hijackedCalleeFp = RVMThread.getCurrentThread().getHijackedReturnCalleeFp();
boolean leapfroggedReturnBarrier = false;
if (VM.VerifyAssertions) VM._assert(hijackedCalleeFp.isZero() || hijackedCalleeFp.GE(fp));
while (Magic.getCallerFramePointer(fp).NE(StackFrameLayout.getStackFrameSentinelFP())) {
if (!hijackedCalleeFp.isZero() && hijackedCalleeFp.LE(fp)) {
leapfroggedReturnBarrier = true;
}
int compiledMethodId = Magic.getCompiledMethodID(fp);
if (compiledMethodId != StackFrameLayout.getInvisibleMethodID()) {
CompiledMethod compiledMethod = CompiledMethods.getCompiledMethod(compiledMethodId);
ExceptionDeliverer exceptionDeliverer = compiledMethod.getExceptionDeliverer();
Address ip = exceptionRegisters.getInnermostInstructionAddress();
Offset ipOffset = compiledMethod.getInstructionOffset(ip);
int catchBlockOffset = compiledMethod.findCatchBlockForInstruction(ipOffset, exceptionType);
if (catchBlockOffset >= 0) {
// found an appropriate catch block
if (VM.TraceExceptionDelivery) {
VM.sysWriteln("found one; delivering.");
}
if (leapfroggedReturnBarrier) {
RVMThread t = RVMThread.getCurrentThread();
if (RVMThread.DEBUG_STACK_TRAMPOLINE) VM.sysWriteln("leapfrogged...");
t.deInstallStackTrampoline();
}
Address catchBlockStart = compiledMethod.getInstructionAddress(Offset.fromIntSignExtend(catchBlockOffset));
exceptionDeliverer.deliverException(compiledMethod, catchBlockStart, exceptionObject, exceptionRegisters);
if (VM.VerifyAssertions) VM._assert(NOT_REACHED);
}
exceptionDeliverer.unwindStackFrame(compiledMethod, exceptionRegisters);
} else {
unwindInvisibleStackFrame(exceptionRegisters);
}
fp = exceptionRegisters.getInnermostFramePointer();
}
if (VM.TraceExceptionDelivery) {
VM.sysWriteln("Nope.");
VM.sysWriteln("RuntimeEntrypoints.deliverException() found no catch block.");
}
/* No appropriate catch block found. */
if (RVMThread.DEBUG_STACK_TRAMPOLINE && leapfroggedReturnBarrier) VM.sysWriteln("Leapfrogged, and unhandled!");
handleUncaughtException(exceptionObject);
}
@UnpreemptibleNoWarn("Uncaught exception handling that may cause preemption")
private static void handleUncaughtException(Throwable exceptionObject) {
RVMThread.getCurrentThread().handleUncaughtException(exceptionObject);
}
/**
* Skip over all frames below currfp with saved code pointers outside of heap
* (C frames), stopping at the native frame immediately preceding the glue
* frame which contains the method ID of the native method (this is necessary
* to allow retrieving the return address of the glue frame).
*
* @param currfp The current frame is expected to be one of the JNI functions
* called from C, below which is one or more native stack frames
* @return the frame pointer for the appropriate frame
*/
@Uninterruptible
public static Address unwindNativeStackFrame(Address currfp) {
if (VM.BuildForIA32) {
return currfp;
}
// Remembered address of previous FP
Address callee_fp;
// Address of native frame
Address fp = Magic.getCallerFramePointer(currfp);
// Instruction pointer for current native frame
Address ip;
// Loop until either we fall off the stack or we find an instruction address
// in one of our heaps
do {
callee_fp = fp;
ip = Magic.getReturnAddressUnchecked(fp);
fp = Magic.getCallerFramePointer(fp);
} while (!MemoryManager.addressInVM(ip) && fp.NE(StackFrameLayout.getStackFrameSentinelFP()));
if (VM.BuildForPowerPC) {
// We want to return fp, not callee_fp because we want the stack walkers
// to see the "mini-frame" which has the RVM information, not the "main frame"
// pointed to by callee_fp which is where the saved ip was actually stored.
return fp;
} else {
return callee_fp;
}
}
/**
* The current frame is expected to be one of the JNI functions
* called from C,
* below which is one or more native stack frames.
* Skip over all frames below which do not contain any object
* references.
*
* @param currfp the frame pointer of the current frame
* @return the frame pointer for the appropriate frame
*/
@Uninterruptible
public static Address unwindNativeStackFrameForGC(Address currfp) {
return unwindNativeStackFrame(currfp);
}
/**
* Unwind stack frame for an <invisible method>.
* See also: ExceptionDeliverer.unwindStackFrame()
* <p>
* !!TODO: Could be a reflective method invoker frame.
* Does it clobber any non-volatiles?
* If so, how do we restore them?
* (I don't think our current implementations of reflective method
* invokers save/restore any nonvolatiles, so we're probably ok.
* --dave 6/29/01
*
* @param registers exception registers
*/
@Uninterruptible
private static void unwindInvisibleStackFrame(AbstractRegisters registers) {
registers.unwindStackFrame();
}
/**
* Number of allocations left before a GC is forced. Only used if VM.StressGCAllocationInterval is not 0.
*/
static int allocationCountDownToGC = VM.StressGCAllocationInterval;
/**
* Number of c-to-java jni calls left before a GC is forced. Only used if VM.StressGCAllocationInterval is not 0.
*/
static int jniCountDownToGC = VM.StressGCAllocationInterval;
/**
* Check to see if we are stress testing garbage collector and if another JNI call should
* trigger a gc then do so.
*/
@Inline
public static void checkJNICountDownToGC() {
// Temporarily disabled as it will causes nightly to take too long to run
// There should be a mechanism to optionally enable this countdown in Configuration
if (false && canForceGC()) {
if (jniCountDownToGC-- <= 0) {
jniCountDownToGC = VM.StressGCAllocationInterval;
System.gc();
}
}
}
/**
* Check to see if we are stress testing garbage collector and if another allocation should
* trigger a GC then do so.
*/
@Inline
private static void checkAllocationCountDownToGC() {
if (canForceGC()) {
if (allocationCountDownToGC-- <= 0) {
allocationCountDownToGC = VM.StressGCAllocationInterval;
System.gc();
}
}
}
/**
* @return {@code true} if we are stress testing garbage collector and the
* system is in state where we can force a garbage collection.
*/
@Inline
@Uninterruptible
private static boolean canForceGC() {
return VM.ForceFrequentGC && RVMThread.safeToForceGCs() && MemoryManager.collectionEnabled();
}
}