/* * Copyright (c) 2007, 2012, Oracle and/or its affiliates. All rights reserved. * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER. * * This code is free software; you can redistribute it and/or modify it * under the terms of the GNU General Public License version 2 only, as * published by the Free Software Foundation. * * This code is distributed in the hope that it will be useful, but WITHOUT * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License * version 2 for more details (a copy is included in the LICENSE file that * accompanied this code). * * You should have received a copy of the GNU General Public License version * 2 along with this work; if not, write to the Free Software Foundation, * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA. * * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA * or visit www.oracle.com if you need additional information or have any * questions. */ package com.sun.max.tele.object; import java.io.*; import java.util.*; import com.sun.cri.ci.*; import com.sun.max.jdwp.vm.data.*; import com.sun.max.jdwp.vm.proxy.*; import com.sun.max.memory.*; import com.sun.max.program.*; import com.sun.max.tele.*; import com.sun.max.tele.data.*; import com.sun.max.tele.method.*; import com.sun.max.tele.reference.*; import com.sun.max.tele.util.*; import com.sun.max.unsafe.*; import com.sun.max.vm.actor.member.*; import com.sun.max.vm.compiler.RuntimeCompiler.Nature; import com.sun.max.vm.compiler.target.*; import com.sun.max.vm.compiler.target.TargetMethod.FrameAccess; import com.sun.max.vm.type.*; /** * Canonical surrogate for a heap object of type {@link TargetMethod} in the VM. That object extends * {@link MemoryRegion}, by which it describes the area allocated for it in one of the code cache memory regions. This * location can change for compilations that are allocated in managed code cache regions. * <p> * The {@link TargetMethod} stores the results of a method compilation in its allocated area in the form of three arrays * (one or two of which may be omitted) to which the {@link TargetMethod} holds references. These arrays are represented * in standard object format. * <p> * When this surrogate is first created, it records, in addition to the reference to the {@link TargetMethod}, only a * bare minimum of information about the compiled code, mainly just its location in code cache memory. This limitation * keeps the overhead low, since an instance of this class is eagerly created for every compilation discovered in the * VM. It also avoids creating any other instances of {@link TeleObject}, which can lead to infinite regress in the * presence of mutually referential objects, notably with instances of {@link TeleClassMethodActor}. * <p> * The first time this object is refreshed, it gets an instance of {@link TeleClassMethodActor} that refers to the * {@link ClassMethodActor} in the VM that owns the compilation represented by this object. * <p> * The full contents of the compilation (including the three arrays from the code cache) are "loaded" (copied) from the * VM, disassembled, and cached locally only when needed. This operation is relatively expensive because a variety of * summary information about the compilation are derived and included in the class. The local cache is marked * dirty whenever an update determines that the code has been changed (e.g. patched or relocated), and only reloaded as * needed. * <p> * A method compilation is loaded by (restricted) deep copying the {@link TargetMethod} from the VM, and caching the * local instance. * <p> * Clients of target method information should operate with the single thread-safe instance of {@link MaxMachineCodeInfo} * provided by this object, since the information is guaranteed to represent a consistent snapshot of some version of * the compilation. That snapshot should not be cached by clients, however, because the compilation is subject to * change. * * @see VmCodeCacheAccess * @see VmCodeCacheRegion * @see TeleClassMethodActor */ public final class TeleTargetMethod extends TeleMemoryRegion implements TargetMethodAccess { private static final int TRACE_VALUE = 2; /** * The data produced by a compilation is stored into an area of memory allocated from some part of the * {@linkplain VmCodeCacheAccess code cache}, where it is stored in standard VM object format. In particular, it is * stored as three contiguous arrays in the code cache allocation, although two of them might be omitted if not * needed. * <p> * The {@link TargetMethod} holds standard object {@link RemoteReference}s to these three arrays. * <p> * No other kinds of objects should ever appear in a {@linkplain VmCodeCacheRegion code cache region}, so this enum * completely describes the possibilities. */ public static enum CodeCacheReferenceKind { /** * Reference possibly held in a {@link TargetMethod} to an instance of {@code byte[]} in the code cache holding * scalar literals needed by the target code. This will be null in the following situations: * <ul> * <li>During the creation of a new method compilation, until the code cache area is allocated, the method is * compiled, and the scalar literals stored into the code cache;</li> * <li>If there are no scalar literals associated with the target code; and</li> * <li>If the compilation has been evicted.</li> * </ul> * * @see TargetMethod#scalarLiterals() */ SCALAR_LITERALS ("array of scalar literals for a compilation"), /** * Reference possibly held in a {@link TargetMethod} to an instance of {@code Object[]} in the code cache * holding reference literals needed by the target code. This will be null in the following situations: * <ul> * <li>During the creation of a new method compilation, until the code cache area is allocated, the method is * compiled, and the reference literals stored into the code cache;</li> * <li>If there are no reference literals associated with the target code; and</li> * <li>If the compilation has been evicted.</li> * </ul> * * @see TargetMethod#referenceLiterals() */ REFERENCE_LITERALS ("array of reference literals for a compilation"), /** * Reference possibly held in a {@link TargetMethod} to an instance of {@code byte[]} in the code cache holding * the target code. This will be null in the following situations: * <ul> * <li>During the creation of a new method compilation, until the code cache area is allocated, the method is * compiled, and the code stored into the code cache;</li> * <li>If the compilation has been evicted.</li> * </ul> * * @see TargetMethod#code() */ CODE ("byte array holding machine code for a compilation"); private final String label; CodeCacheReferenceKind(String label) { this.label = label; } public String label() { return label; } } /** * A specialized message generator for tracing that incurs no runtime cost unless the trace is actually printed. */ private final class EventTracer { private final String event; public EventTracer(String event) { this.event = event; } @Override public String toString() { final String name = teleClassMethodActor == null ? "<?>" : teleClassMethodActor.classMethodActor().format("%H.%n(%p)"); final String regionName = codeCacheRegion == null ? "<?>" : codeCacheRegion.entityName(); return tracePrefix() + event + ": " + name + " in " + regionName; } } /** * The location in VM memory of the fixed sentinel assigned to the code field of the target method when the code is * evicted. Holds the value {@link Address#zero()} until the sentinel is discovered. * * @see TargetMethod#wipe() */ private static Address codeWipedSentinelAddress = Address.zero(); /** * A representation of the Java language entity, if any, from which this method was compiled. */ private TeleClassMethodActor teleClassMethodActor = null; private Class compilationClass = null; /** * Absolute origin of an array of scalar literals referred to by target code, allocated (if non-empty) in the code * cache allocation for this method The location might change if the code cache allocation is moved, or become * specially marked as <em>wiped</em> if the compilation does not survive an eviction cycle. That special marking is * done by assignment of a distinguished empty array to the field. * <p> * This value is null only until the first successful read of their values from the object in the VM. * * @see CodeCacheReferenceKind#SCALAR_LITERALS * @see TargetMethod * @see TargetMethod#wipe() */ private Address scalarLiteralArrayOrigin = null; /** * Absolute origin of an array of reference literals referred to by target code, allocated (if non-empty) in the * code cache allocation for this method The location might change if the code cache allocation is moved, or become * specially marked as <em>wiped</em> if the compilation does not survive an eviction cycle. That special marking is * done by assignment of a distinguished empty array to the field. * <p> * This value is null only until the first successful read of their values from the object in the VM. * * @see CodeCacheReferenceKind#REFERENCE_LITERALS * @see TargetMethod * @see TargetMethod#wipe() */ private Address referenceLiteralArrayOrigin = null; /** * Absolute origin of a byte array containing target code, allocated in the code cache allocation for this method * The location might change if the code cache allocation is moved, or become specially marked as <em>wiped</em> if * the compilation does not survive an eviction cycle. That special marking is done by assignment of a distinguished * empty array to the field. * <p> * This value is null only until the first successful read of their values from the object in the VM. * * @see CodeCacheReferenceKind#CODE * @see TargetMethod * @see TargetMethod#wipe() */ private Address codeByteArrayOrigin = null; /** * Absolute location in VM code cache memory of the first byte in this compilation's target code. */ private Address codeStartAddress = null; /** * Absolute location in VM code cache memory immediately after the final byte in this compilation's target code. */ private Address codeEndAddress = null; /** * A representation of the the part of the VM's code cache (a code cache region) in which this {@link TargetMethod}'s * compilation data is allocated and possibly managed. */ private VmCodeCacheRegion codeCacheRegion = null; /** * The cache holding the compiled code associated with this {@link TargetMethod}, which can produce a single * immutable summary for thread safety. That summary object (a {@link MaxMachineCodeInfo}) encapsulates a local copy of * the {@link TargetMethod} along with a collection of derived information. * <p> * A version number of the cache is kept. The initial state of the cache is version 0, contains no * {@link TargetMethod}, and is considered to be "unloaded". The initial state of the VM is considered to be version * 1. As soon as the cache object is replaced, the state of the cache is henceforth considered "loaded". * <p> * The cache is intended to include only the kind of detailed information about the method's machine code that is * needed under uncommon circumstances, for example when a user is inspecting the code directly. The cache is not * loaded (and after that reloaded) unless the detailed information is needed. */ private final MachineCodeInfoCache machineCodeInfoCache; /** * A flag that permanently becomes {@code true} when an update detects that the code for this compilation has not * survived an eviction cycle. The actual test depends in the management being used for the code cache region * managing this compilation. */ private boolean isCodeEvicted = false; /** * Starting location of the compilation's allocation in the code cache, the last time we checked. Used to * detect when the code cache allocation has been relocated, but only used when allocated in a managed code cache region. */ private Address previousAllocationStart = Address.zero(); private final Object patchedTracer = new EventTracer("PATCHED"); private final Object relocatedTracer = new EventTracer("RELOCATED"); private final Object evictedTracer = new EventTracer("EVICTED"); protected TeleTargetMethod(TeleVM vm, RemoteReference targetMethodReference) { super(vm, targetMethodReference); machineCodeInfoCache = new MachineCodeInfoCache(vm, this); // Delay initialization of classMethodActor because of circularity: // the compilation history of the classMethodActor refers back to this. // Register every method compilation, so that they can be located by code address. // Note that this depends on the basic location information already being read by // superclass constructors. vm.machineCode().registerCompilation(this); previousAllocationStart = getRegionStart(); } /** * {@inheritDoc} * <p> * Compiled machine code generally doesn't change, so the code and disassembled instructions (once needed) are * cached. This update checks for cases where the code has changed since last seen, i.e. has been patched, and marks * the cache as dirty when this is observed. */ @Override protected boolean updateObjectCache(long epoch, StatsPrinter statsPrinter) { if (!super.updateObjectCache(epoch, statsPrinter)) { return false; } if (isCodeEvicted) { // Once the compilation has been evicted from the code cache it is dead; the cache has been nulled // and no more updates are needed. return true; } try { // Start with some basic attributes we need to capture. if (teleClassMethodActor == null) { // Assumed not to change, once set. final RemoteReference classMethodActorReference = fields().TargetMethod_classMethodActor.readRemoteReference(reference()); teleClassMethodActor = (TeleClassMethodActor) objects().makeTeleObject(classMethodActorReference); } if (codeWipedSentinelAddress.isZero()) { // Static, assumed not to change, once set codeWipedSentinelAddress = fields().TargetMethod_WIPED_CODE.readWord(vm()).asAddress(); } // See if the code cache allocation has been relocated boolean isRelocated = false; if (isRelocatable()) { final Address newAllocationStart = getRegionStart(); if (previousAllocationStart.isNotZero() && !previousAllocationStart.equals(newAllocationStart)) { machineCodeInfoCache.markDirty(); isRelocated = true; previousAllocationStart = newAllocationStart; Trace.line(TRACE_VALUE, relocatedTracer); } } // Read (or re-read if needed) the three fields that might point into the compilation's code cache allocation, in particular at the // arrays in which compilation data is stored. Use low level machinery to avoid circularity with Reference creation. if (scalarLiteralArrayOrigin == null || isRelocated) { scalarLiteralArrayOrigin = reference().readWord(fields().TargetMethod_scalarLiterals.fieldActor().offset()).asAddress(); } if (referenceLiteralArrayOrigin == null || isRelocated) { referenceLiteralArrayOrigin = reference().readWord(fields().TargetMethod_referenceLiterals.fieldActor().offset()).asAddress(); } if (codeByteArrayOrigin == null || isRelocatable()) { // We have to check this one even if not relocated, since we test it to see if the method has been evicted. codeByteArrayOrigin = reference().readWord(fields().TargetMethod_code.fieldActor().offset()).asAddress(); // Get the absolute location of all target code bytes. // Use low level machinery; we don't want to create a {@link TeleObject} for every one of them. final RemoteReference codeByteArrayRef = referenceManager().makeTemporaryRemoteReference(codeByteArrayOrigin); final int length = objects().unsafeReadArrayLength(codeByteArrayRef); codeStartAddress = objects().unsafeArrayIndexToAddress(Kind.BYTE, codeByteArrayOrigin, 0); codeEndAddress = objects().unsafeArrayIndexToAddress(Kind.BYTE, codeByteArrayOrigin, length); } } catch (DataIOError dataIOError) { // If something goes wrong, delay the cache update until next time. } // See if we have been evicted since last cycle by checking if the code pointer has been "wiped". if (isRelocatable() && codeWipedSentinelAddress.isNotZero() && codeByteArrayOrigin != null && codeByteArrayOrigin.equals(codeWipedSentinelAddress)) { markCodeEvicted(); Trace.line(TRACE_VALUE, evictedTracer); return true; } if (!machineCodeInfoCache.isLoaded()) { // Don't update if we've never loaded the code; delay that until actually needed. return true; } if (machineCodeInfoCache.isDirty()) { // If we've already discovered that the loaded copy is not current, don't bother to // check again. It won't be reloaded until needed. return true; } try { // Test for a patch to the target code since the last time we looked. final RemoteReference byteArrayReference = fields().TargetMethod_code.readRemoteReference(reference()); final TeleArrayObject teleByteArrayObject = (TeleArrayObject) objects().makeTeleObject(byteArrayReference); final byte[] codeInVM = (byte[]) teleByteArrayObject.shallowCopy(); if (!Arrays.equals(codeInVM, machineCodeInfoCache.machineCodeInfo().code())) { // The code in the VM is different than in the cache; record that it has changed. machineCodeInfoCache.markDirty(); Trace.line(TRACE_VALUE, patchedTracer); } } catch (DataIOError dataIOError) { // If something goes wrong, delay the cache update until next time. return false; } return true; } /** * Assigns to this representation of a VM {@link TargetMethod} the representation of the code cache region in which * it has been discovered to have been allocated. * <p> * It is assumed that this is only set once, as target methods are assumed not to move among code cache regions. * * @param codeCacheRegion a code cache region in the VM */ public void setCodeCacheRegion(VmCodeCacheRegion codeCacheRegion) { assert this.codeCacheRegion == null; this.codeCacheRegion = codeCacheRegion; } /** * Records (permanently) the observation that the code formerly associated with this compilation has been evicted by * the VM's code cache and is no longer used by the VM. */ private void markCodeEvicted() { assert !isCodeEvicted; isCodeEvicted = true; machineCodeInfoCache.update(null, null); } /** * Has the code associated with this compilation has been evicted by the VM's code cache and therefore become * permanently unused by the VM. */ public boolean isCodeEvicted() { // If the code has been evicted, the first evidence we might see of it could be // that the TargetMethod has been collected. That is sufficient evidence that // the code has been evicted, because we assume that code cache region implementations // keep a list of all currently allocated TargetMethods, and will release that list // only upon eviction. return status().isDead() || isCodeEvicted; } /** * Determines whether there is machine code in this compilation at a specified memory location in the VM, always * {@code false} if this compilation has been evicted. * * @param address an absolute memory location in the VM. * @return whether there is machine code at the address * @throws IllegalArgumentException if the location is not within the code cache memory allocated for this * compilation. */ public boolean isValidCodeLocation(Address address) throws IllegalArgumentException { if (isCodeEvicted()) { return false; } if (!contains(address)) { throw new IllegalArgumentException("Address " + address.to0xHexString() + " not in code cache allocation"); } return address.greaterEqual(codeStartAddress) && address.lessThan(codeEndAddress); } /** * Return the absolute origin location of each of the arrays holding compilation data that are stored in the * compilation's code cache allocation. * <p> * In the current implementation, these pointers in the {@link TargetMethod} are set to special statically allocated * sentinels ("wiped") when the compilation is evicted. */ public Address codeCacheObjectOrigin(CodeCacheReferenceKind kind) { switch (kind) { case SCALAR_LITERALS: return scalarLiteralArrayOrigin; case REFERENCE_LITERALS: return referenceLiteralArrayOrigin; case CODE: return codeByteArrayOrigin; default: TeleError.unknownCase(); return null; } } /** * Determines whether we have ever copied information about the {@link TargetMethod} from the VM, which is done only * on demand. * * @return whether a copy of the {@link TargetMethod} in the VM has been created and cached. */ public boolean isCacheLoaded() { return machineCodeInfoCache.isLoaded(); } /** * Counter for the versions of the code held by a{@link TargetMethod} during its lifetime in the VM, starting with * its original version, which we call 1. Note that the initial (null) instance of the cache is set to version 0, * which means that the cache begins life as dirty, which is how it will remain until detailed information is * needed and the contents of the machine code is loaded into the cache. * <p> * The version can be incremented for different reasons: * <ul> * <li>The machine code has been "patched"</li> * <li>The code has been relocated</li> * </ul> * Note that this number relates only to the <em>observed</em> changes, not necessarily the actual changes that * might have taken place in the VM. */ public int codeVersion() { return machineCodeInfoCache.machineCodeInfo().codeVersion(); } // public boolean isBaseline() { // if (compilationClass == null) { // compilationClass = classActorForObjectType().javaClass(); // } // return compilationClass == T1XTargetMethod.class; // } /** * @see MaxCompilation#shortDesignator() */ public String shortDesignator() { if (teleClassMethodActor == null) { return "?"; } if (teleClassMethodActor.getCompilation(Nature.BASELINE) == this) { return "B"; } if (teleClassMethodActor.getCompilation(Nature.OPT) == this) { return "O"; } return "?"; } /** * @see MaxCompilation#longDesignator() */ public String longDesignator() { if (teleClassMethodActor == null) { return "<?>"; } if (teleClassMethodActor.getCompilation(Nature.BASELINE) == this) { return "BASELINE"; } if (teleClassMethodActor.getCompilation(Nature.OPT) == this) { return "OPTIMIZED"; } return "OTHER"; } /** * @return a local copy of the {@link TargetMethod} in the VM, the most recently observed version. */ public TargetMethod targetMethod() { return machineCodeInfoCache.machineCodeInfo().targetMethod(); } /** * @return surrogate for the {@link ClassMethodActor} in the VM for which this code was compiled. */ public TeleClassMethodActor getTeleClassMethodActor() { return teleClassMethodActor; } public MaxMachineCodeInfo getMachineCodeInfo() { return machineCodeInfoCache.machineCodeInfo(); } /** * Gets VM memory location of the first instruction in the method. * * @see TargetMethod#codeStart() */ public Pointer getCodeStart() { return machineCodeInfoCache.machineCodeInfo().codeStart(); } /** * Gets the call entry memory location in the VM for this method. * * @return {@link Address#zero()} if this target method has not yet been compiled */ public Address callEntryPoint() { return machineCodeInfoCache.machineCodeInfo().callEntryPoint(); } /** * Gets the local mirror of the class method actor associated with this target method. This may be null. */ public ClassMethodActor classMethodActor() { return teleClassMethodActor == null ? null : teleClassMethodActor.classMethodActor(); } public int[] bciToPosMap() { return machineCodeInfoCache.machineCodeInfo().bciToPosMap(); } /** * Gets the debug info available for a given safepoint index. * * @param safepointIndex a safepoint index * @return the debug info available for {@code safepointIndex} or null if there is none * @see TargetMethod#debugInfoAt(int, FrameAccess) */ public CiDebugInfo getDebugInfoAtSafepointIndex(final int safepointIndex) { return machineCodeInfoCache.machineCodeInfo().getDebugInfoAtSafepointIndex(safepointIndex); } /** * Gets the name of the source variable corresponding to a stack slot, if any. * * @param slot a stack slot * @return the Java source name for the frame slot, null if not available. */ public String sourceVariableName(MaxStackFrame.Compiled javaStackFrame, int slot) { return null; } // [tw] Warning: duplicated code! public MachineCodeInstructionArray getTargetCodeInstructions() { final List<TargetCodeInstruction> instructions = machineCodeInfoCache.machineCodeInfo().instructions(); final MachineCodeInstruction[] result = new MachineCodeInstruction[instructions.size()]; for (int i = 0; i < result.length; i++) { final TargetCodeInstruction ins = instructions.get(i); result[i] = new MachineCodeInstruction(ins.mnemonic, ins.position, ins.address.toLong(), ins.label, ins.bytes, ins.operands, ins.getTargetAddressAsLong()); } return new MachineCodeInstructionArray(result); } public MethodProvider getMethodProvider() { return this.teleClassMethodActor; } @Override public ReferenceTypeProvider getReferenceType() { return vm().vmAccess().getReferenceType(getClass()); } /** * {@inheritDoc} * <p> * A {@link TargetMethod} (method compilation) region does not move (see class comment) unless it is in a managed * code cache region; we might not know, however, until we have been told the region in which the code is allocated. */ @Override public boolean isRelocatable() { // This test could in principle be refined so that a relocation check is only needed when the cache region is in an eviction cycle // or an eviction cycle has completed since the last update. But care is needed. This predicate is called by the // superclass to decide whether to update the start and size of the region, and that call happens before this class // has the opportunity to check the status of evictions. if (codeCacheRegion != null && !codeCacheRegion.isManaged()) { // In an unmanaged code region, code is assumed to never move. return false; } return true; } public void writeSummary(PrintStream printStream) { machineCodeInfoCache.writeSummary(printStream); } }