/*
* 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.method;
import static com.sun.max.platform.Platform.*;
import java.io.*;
import java.util.*;
import com.oracle.max.hcfdis.*;
import com.sun.cri.bytecode.*;
import com.sun.cri.bytecode.Bytes;
import com.sun.cri.ci.*;
import com.sun.cri.ci.CiTargetMethod.CodeAnnotation;
import com.sun.cri.ri.*;
import com.sun.max.asm.*;
import com.sun.max.asm.dis.*;
import com.sun.max.io.*;
import com.sun.max.lang.*;
import com.sun.max.platform.*;
import com.sun.max.program.*;
import com.sun.max.tele.*;
import com.sun.max.tele.data.*;
import com.sun.max.tele.method.CodeLocation.MachineCodeLocation;
import com.sun.max.tele.object.*;
import com.sun.max.tele.reference.*;
import com.sun.max.tele.type.*;
import com.sun.max.tele.util.*;
import com.sun.max.unsafe.*;
import com.sun.max.vm.*;
import com.sun.max.vm.actor.member.*;
import com.sun.max.vm.bytecode.*;
import com.sun.max.vm.classfile.*;
import com.sun.max.vm.classfile.constant.*;
import com.sun.max.vm.compiler.*;
import com.sun.max.vm.compiler.target.*;
import com.sun.max.vm.compiler.target.TargetMethod.FrameAccess;
/**
* A cache that encapsulates a {@link TargetMethod} instance copied from the VM, together with derived information that
* is needed when examining the machine code.
* <p>
* The machine code in a {@link TargetMethod} can change in the VM at runtime. There are several cases where the state
* of the {@link TargetMethod} in the VM changes:
* <ol>
* <li>When a method compilation in the VM begins, the {@link TeleTargetMethod}'s {@code code} field is initially null
* and the starting location is {@link Address#zero()}. When the compilation completes, code and related data are copied
* into the appropriate place in the code cache, the {@link TargetMethod}'s {@code code} and other fields are set
* (references into the code cache allocation) and the starting location is assigned.</li>
* <li>The compiled code can be patched in place, for example when a method call is resolved.</li>
* <li>The compiled code may be relocated, for example by eviction in a managed code cache region.</li>
* </ol>
* <p>
* This cache object is effectively immutable, so every method on it is thread-safe. A new cache must be created each
* time the {@link TeleTargetMethod} object in the VM is discovered to have changed. This is done lazily.
* <p>
* There are four states for this cache:
* <ol>
* <li><i>Unloaded, Dirty</i>: the initial state where
* {@code codeVersion == 0 && targetMethod == null && isDirty == true}. This represents the most common case, where a
* {@link TargetMethod} in the code cache is known and registered, but there has been no request to examine the
* details of the compiled code itself.</li>
* <li><i>Loaded, not Dirty</i>: {@code codeVersion > 0 && targetMethod != null && isDirty == false}. In this state, all
* derived information has been computed and is cached; it is available without need to read from VM memory.</li>
* <li><i>Loaded, Dirty</i>: {@code codeVersion > 0 && targetMethod != null && && isDirty == true}. In this state, derived
* cached information is presumed to be out of date (the code in the VM has been patched, relocated or changed in some
* other way). Every attempt to use the cached information should be preceded by an attempt to reload the cache; if the
* reload fails then the old information can be used until the cache successfully refreshes.</li>
* <li><i>Dead</i>: {@code codeVersion > 0 && targetMethod == null}. In this state, the compilation has been declared
* dead, presumably evicted from the VM's code cache.
* </ol>
* Once a {@link TargetMethod} has been loaded, it remains in the "loaded" state, alternating between "not dirty" and
* "dirty" as the cached code is compared with the code in the VM during each update cycle.
* <p>
* This constructor requires locked access to the VM.
*
* @see TargetMethod
* @see TeleVM#tryLock()
*
*/
public final class MachineCodeInfoCache extends AbstractVmHolder {
private static final int TRACE_VALUE = 2;
private static final List<TargetCodeInstruction> EMPTY_TARGET_INSTRUCTIONS = Collections.emptyList();
private static final MachineCodeLocation[] EMPTY_MACHINE_CODE_LOCATIONS = {};
private static final Integer[] EMPTY_SAFEPOINTS = {};
private static final CiDebugInfo[] EMPTY_DEBUG_INFO_MAP = {};
private static final int[] EMPTY_INT_ARRAY = {};
private static final RiMethod[] EMPTY_METHOD_ARRAY = {};
private static final List<Integer> EMPTY_INTEGER_LIST = Collections.emptyList();
/**
* Adapter for bytecode scanning that only knows the constant pool
* index argument of the last method invocation instruction scanned.
*/
private static final class MethodRefIndexFinder extends BytecodeAdapter {
int methodRefIndex = -1;
public MethodRefIndexFinder reset() {
methodRefIndex = -1;
return this;
}
@Override
protected void invokestatic(int index) {
methodRefIndex = index;
}
@Override
protected void invokespecial(int index) {
methodRefIndex = index;
}
@Override
protected void invokevirtual(int index) {
methodRefIndex = index;
}
@Override
protected void invokeinterface(int index, int count) {
methodRefIndex = index;
}
public int methodRefIndex() {
return methodRefIndex;
}
}
public final class TargetMethodMachineCodeInfo implements MaxMachineCodeInfo {
/**
* A limited deep copy of the {@link TargetMethod} in the VM, reflecting its state at some point during
* its lifetime.
*/
private final TargetMethod targetMethodCopy;
/**
* The version number of this cache, with a new one presumed to be created each time the code in the VM is
* discovered to have patched or relocated. Note that this might not agree with the actual number of times the
* code has changed, since it may have changed more than once in a single VM execution cycle.
* <p>
* Version 0 corresponds to the initial state, where the data has not been loaded yet.
* <p>
* Version -1 corresponds to the "evicted" state, where the code has been removed from the code cache and the
* compilation is no longer available for use.
*/
private final int codeVersion;
/**
* A copy of the machine code for the compilation.
*/
private final byte[] code;
/**
* Location of the first byte of the machine code for the compilation in VM memory.
*
* @see TargetMethod#codeStart()
*/
private final Pointer codeStart;
/**
* For decoding inline data in this target method's code.
*/
private final InlineDataDecoder inlineDataDecoder;
/**
* The entry point used for <i>standard</i> calls in this target method to JVM compiled/interpreted code.
*
* @see TargetMethod#callEntryPoint
*/
private final Address callEntryPoint;
/**
* Assembly language instructions that have been disassembled from the method's target code.
*/
private final List<TargetCodeInstruction> instructions;
/**
* The number of disassembled instructions in the method.
*/
private final int instructionCount;
/**
* Map: bytecode positions to target code positions, null map if information not available.
*
* @see TargetMethod#bciToPosMap()
*/
private final int[] bciToPosMap;
/**
* Map: target instruction index -> location of the instruction in VM memory.
*/
private final MachineCodeLocation[] indexToLocation;
/**
* Map: target instruction index -> the safepoint at the instruction, null if not a safepoint.
*/
private final Integer[] indexToSafepoint;
/**
* Map: target instruction index -> debug info, if available; else null.
*/
private final CiDebugInfo[] indexToDebugInfoMap;
/**
* Map: target instruction index -> the specific opcode implemented by the group of instructions starting
* with this one, if known; else empty.
*/
private final int[] indexToOpcode;
/**
* Map: target instruction index -> constant pool index of {@Link MethodRefConstant} if this is a call instruction; else null.
*/
private final RiMethod[] indexToCallee;
/**
* Unmodifiable list of indexes for instructions that are labeled.
*/
private final List<Integer> labelIndexes;
private final MethodRefIndexFinder methodRefIndexFinder = new MethodRefIndexFinder();
/**
* Create a cache that holds a copy of a {@link TargetMethod} at some stage in its life, along
* with information derived from that copy.
* <p>
* This constructor should be called with the vm lock held.
*
* @param targetMethodCopy a local copy of the {@link TargetMethod} from the VM.
* @param codeVersion number of this cache in the sequence of caches: 0 = no information; 1 VM's initial state.
* @param teleClassMethodActor access to the {@link ClassMethodActor} in the VM of which this {@link TargetMethod}
* is a compilation.
*/
private TargetMethodMachineCodeInfo(TargetMethod targetMethodCopy, int codeVersion, TeleClassMethodActor teleClassMethodActor) {
this.targetMethodCopy = targetMethodCopy;
this.codeVersion = codeVersion;
if (targetMethodCopy == null || targetMethodCopy.codeLength() == 0) {
// Create a null cache as a placeholder until loading is required.
// The cache is also null if the target method doesn't have any code yet,
// which can be observed during the allocation part of target method creation.
this.code = null;
this.codeStart = Pointer.zero();
this.inlineDataDecoder = null;
this.callEntryPoint = Address.zero();
this.instructions = EMPTY_TARGET_INSTRUCTIONS;
this.instructionCount = 0;
this.bciToPosMap = null;
this.indexToLocation = EMPTY_MACHINE_CODE_LOCATIONS;
this.indexToSafepoint = EMPTY_SAFEPOINTS;
this.indexToDebugInfoMap = EMPTY_DEBUG_INFO_MAP;
this.indexToOpcode = EMPTY_INT_ARRAY;
this.indexToCallee = EMPTY_METHOD_ARRAY;
this.labelIndexes = EMPTY_INTEGER_LIST;
} else {
// The size of the compilation's machine code in bytes.
final int targetCodeLength = targetMethodCopy.codeLength();
this.code = targetMethodCopy.code();
this.codeStart = targetMethodCopy.codeStart().toPointer();
if (codeStart.isZero()) {
this.callEntryPoint = Address.zero();
} else {
Address callEntryAddress = codeStart;
if (MaxineVM.vm().compilationBroker.needsAdapters()) {
final RemoteReference callEntryPointReference = fields().TargetMethod_callEntryPoint.readRemoteReference(teleTargetMethod.reference());
final TeleObject teleCallEntryPoint = objects().makeTeleObject(callEntryPointReference);
if (teleCallEntryPoint != null) {
final CallEntryPoint callEntryPoint = (CallEntryPoint) teleCallEntryPoint.deepCopy();
if (callEntryPoint != null) {
callEntryAddress = codeStart.plus(callEntryPoint.offset());
}
}
}
this.callEntryPoint = callEntryAddress;
}
CodeAnnotation[] annotations = targetMethodCopy.annotations();
if (annotations != null && annotations.length > 0) {
inlineDataDecoder = HexCodeFileDis.makeInlineDataDecoder(annotations);
} else {
inlineDataDecoder = null;
}
// Disassemble the target code
this.instructions = TeleDisassembler.decode(platform(), codeStart, code, inlineDataDecoder);
this.instructionCount = this.instructions.size();
// Get the raw bytecodes from which the method was compiled, if available.
byte[] bytecodes = null;
CodeAttribute codeAttribute = targetMethodCopy.codeAttribute();
if (codeAttribute != null) {
bytecodes = codeAttribute.code();
}
// Safepoint position locations in compiled code (by byte offset from the start): null if not available
final Safepoints safepoints = targetMethodCopy.safepoints();
// Build map: target instruction position (bytes offset from start) -> the safepoint, 0 if not a safepoint.
int[] posToSafepointMap = null;
if (safepoints != null && safepoints.size() > 0) {
posToSafepointMap = new int[targetCodeLength];
for (int safepointIndex = 0; safepointIndex < safepoints.size(); safepointIndex++) {
posToSafepointMap[safepoints.posAt(safepointIndex)] = safepoints.safepointAt(safepointIndex);
}
}
// Get the precise map between bytecode and machine code instructions, null if not available
this.bciToPosMap = targetMethodCopy.bciToPosMap();
// Build map: target instruction position (bytes offset from start) -> debug infos (as much as can be determined).
final CiDebugInfo[] posToDebugInfoMap = new CiDebugInfo[targetCodeLength];
if (safepoints != null && safepoints.size() > 0) {
for (int safepointIndex = 0; safepointIndex < safepoints.size(); ++safepointIndex) {
posToDebugInfoMap[safepoints.posAt(safepointIndex)] = getDebugInfoAtSafepointIndex(safepointIndex);
}
}
if (bciToPosMap != null) {
int bci = 0; // position cursor in the original bytecode stream, used if we have a bytecode-> machine code map
// Iterate over target code instructions, moving along the bytecode cursor to match
for (int instructionIndex = 0; instructionIndex < instructionCount; instructionIndex++) {
final TargetCodeInstruction instruction = instructions.get(instructionIndex);
// offset in bytes of this target code instruction from beginning of the target code
final int pos = instruction.position;
// To check if we're crossing a bytecode boundary in the machine code,
// compare the offset of the instruction at the current row with the offset recorded
// for the start of bytecode template.
if (bci < bciToPosMap.length && pos == bciToPosMap[bci]) {
// This is the start of the machine code block implementing the next bytecode
CiFrame frame = new CiFrame(null, teleTargetMethod.classMethodActor(), bci, false, new CiValue[0], 0, 0, 0);
posToDebugInfoMap[pos] = new CiDebugInfo(frame, null, null);
do {
++bci;
} while (bci < bciToPosMap.length && (bciToPosMap[bci] == 0 || bciToPosMap[bci] == pos));
}
}
}
// Now build maps based on target instruction index
indexToLocation = new MachineCodeLocation[instructionCount];
indexToSafepoint = new Integer[instructionCount];
indexToDebugInfoMap = new CiDebugInfo[instructionCount];
indexToOpcode = new int[instructionCount];
Arrays.fill(indexToOpcode, -1);
indexToCallee = new RiMethod[instructionCount];
// Also build list of instruction indices where there are labels
final List<Integer> labels = new ArrayList<Integer>();
int bci = 0; // position cursor in the original bytecode stream, used if we have a bytecode -> target code map
try {
for (int instructionIndex = 0; instructionIndex < instructionCount; instructionIndex++) {
final TargetCodeInstruction instruction = instructions.get(instructionIndex);
indexToLocation[instructionIndex] = codeLocations().createMachineCodeLocation(instruction.address, "native target code instruction");
if (instruction.label != null) {
labels.add(instructionIndex);
}
// offset in bytes of this machine code instruction from beginning
final int pos = instruction.position;
// Ensure that the reported instruction position is legitimate.
// The disassembler sometimes seems to report wild positions
// when disassembling random binary; this can happen when
// viewing some unknown native code whose length we must guess.
if (pos < 0 || pos >= targetCodeLength) {
continue;
}
indexToDebugInfoMap[instructionIndex] = posToDebugInfoMap[pos];
if (posToSafepointMap != null) {
final int safepoint = posToSafepointMap[pos];
if (safepoint != 0) {
// We're at a safepoint
indexToSafepoint[instructionIndex] = safepoint;
CiDebugInfo info = indexToDebugInfoMap[instructionIndex];
final CiCodePos codePos = info == null ? null : info.codePos;
if (codePos != null && codePos.bci >= 0) {
ClassMethodActor method = (ClassMethodActor) codePos.method;
RiMethod callee = method.codeAttribute().calleeAt(codePos.bci);
indexToCallee[instructionIndex - 1] = callee;
}
}
}
if (bciToPosMap != null) {
// Add more information if we have a precise map from bytecode to machine code instructions
// To check if we're crossing a bytecode boundary in the JITed code, compare the offset of the instruction at the current row with the offset recorded by the JIT
// for the start of bytecode template.
if (bci < bciToPosMap.length && pos == bciToPosMap[bci]) {
if (bci == bytecodes.length) {
indexToOpcode[instructionIndex] = Integer.MAX_VALUE;
} else {
// This is the start of the machine code block implementing the next bytecode
int opcode = Bytes.beU1(bytecodes, bci);
if (opcode == Bytecodes.WIDE) {
opcode = Bytes.beU1(bytecodes, bci + 1);
}
indexToOpcode[instructionIndex] = opcode;
// Move bytecode position cursor to start of next instruction
do {
++bci;
} while (bci < bciToPosMap.length && (bciToPosMap[bci] == 0 || bciToPosMap[bci] == pos));
}
}
}
}
} catch (InvalidCodeAddressException e) {
TeleError.unexpected("TargetMethod cache loading failed @" + e.getAddressString() + ": " + e.getMessage());
}
labelIndexes = Collections.unmodifiableList(labels);
}
}
public int length() {
return instructionCount;
}
public TargetCodeInstruction instruction(int index) throws IllegalArgumentException {
if (index < 0 || index >= instructionCount) {
throw new IllegalArgumentException();
}
return instructions.get(index);
}
public int findInstructionIndex(Address address) {
if (instructionCount > 0 && address.greaterEqual(instructions.get(0).address)) {
for (int index = 1; index < instructionCount; index++) {
instructions.get(index);
if (address.lessThan(instructions.get(index).address)) {
return index - 1;
}
}
final TargetCodeInstruction lastInstruction = instructions.get(instructionCount - 1);
if (address.lessThan(lastInstruction.address.plus(lastInstruction.bytes.length))) {
return instructionCount - 1;
}
}
return -1;
}
public MachineCodeLocation instructionLocation(int index) {
if (index < 0 || index >= instructionCount) {
throw new IllegalArgumentException();
}
return indexToLocation[index];
}
public boolean isSafepoint(int index) throws IllegalArgumentException {
if (index < 0 || index >= instructionCount) {
throw new IllegalArgumentException();
}
return indexToSafepoint[index] != null;
}
public boolean isCall(int index) throws IllegalArgumentException {
if (index < 0 || index >= instructionCount) {
throw new IllegalArgumentException();
}
if (index + 1 >= instructionCount) {
return false;
}
final Integer safepoint = indexToSafepoint[index + 1];
return safepoint != null && Safepoints.isCall(safepoint);
}
public boolean isNativeCall(int index) throws IllegalArgumentException {
if (index < 0 || index >= instructionCount) {
throw new IllegalArgumentException();
}
if (index + 1 >= instructionCount) {
return false;
}
final Integer safepoint = indexToSafepoint[index + 1];
return safepoint != null && Safepoints.NATIVE_CALL.isSet(safepoint);
}
public boolean isBytecodeBoundary(int index) throws IllegalArgumentException {
assert targetMethodCopy != null;
if (index < 0 || index >= instructionCount) {
throw new IllegalArgumentException();
}
return indexToOpcode[index] >= 0;
}
public CiDebugInfo debugInfoAt(int index) throws IllegalArgumentException {
if (index < 0 || index >= instructionCount) {
throw new IllegalArgumentException();
}
return indexToDebugInfoMap[index];
}
public int opcode(int index) throws IllegalArgumentException {
if (index < 0 || index >= instructionCount) {
throw new IllegalArgumentException();
}
return indexToOpcode[index];
}
public RiMethod calleeAt(int index) {
if (index < 0 || index >= instructionCount) {
throw new IllegalArgumentException();
}
return indexToCallee[index];
}
public List<Integer> labelIndexes() {
return labelIndexes;
}
public int[] bciToMachineCodePositionMap() {
return bciToPosMap;
}
public int codeVersion() {
return codeVersion;
}
public TargetMethod targetMethod() {
return targetMethodCopy;
}
public Pointer codeStart() {
return codeStart;
}
public Address callEntryPoint() {
return callEntryPoint;
}
public int[] bciToPosMap() {
return bciToPosMap;
}
public List<TargetCodeInstruction> instructions() {
return instructions;
}
public byte[] code() {
return code;
}
/**
* Gets the debug info available for a given safepoint.
*
* @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) {
try {
return VmClassAccess.usingTeleClassIDs(new Function<CiDebugInfo>() {
@Override
public CiDebugInfo call() throws Exception {
return targetMethodCopy.debugInfoAt(safepointIndex, null);
}
});
} catch (Error error) {
return null;
}
}
/**
* @param bytecodes
* @param bci byte index into bytecodes
* @return if a call instruction, the index into the constant pool of the called {@link MethodRefConstant}; else -1.
*/
private int findCalleeCPIndex(byte[] bytecodes, int bci) {
if (bytecodes == null || bci >= bytecodes.length) {
return -1;
}
final BytecodeScanner bytecodeScanner = new BytecodeScanner(methodRefIndexFinder.reset());
bytecodeScanner.scanInstruction(bytecodes, bci);
return methodRefIndexFinder.methodRefIndex();
}
}
private final TeleTargetMethod teleTargetMethod;
private volatile TargetMethodMachineCodeInfo machineCodeInfo;
private boolean isDirty;
public MachineCodeInfoCache(TeleVM vm, TeleTargetMethod teleTargetMethod) {
super(vm);
this.teleTargetMethod = teleTargetMethod;
this.machineCodeInfo = new TargetMethodMachineCodeInfo(null, 0, null);
this.isDirty = true;
}
public void update(TargetMethod targetMethodCopy, TeleClassMethodActor teleClassMethodActor) {
machineCodeInfo = new TargetMethodMachineCodeInfo(targetMethodCopy, machineCodeInfo.codeVersion + 1, teleClassMethodActor);
if (machineCodeInfo.codeStart.isNotZero()) {
isDirty = false;
}
}
/**
* Returns an immutable, consistent summary of information about the compilation, attempting
* to update the summary first if it is known to be out of date.
*
* @return the most recent cache of the information that we can get
*/
public TargetMethodMachineCodeInfo machineCodeInfo() {
if (isDirty && vm().tryLock()) {
TeleClassMethodActor teleClassMethodActor = null;
try {
final TargetMethod targetMethodCopy = (TargetMethod) teleTargetMethod.deepCopy();
teleClassMethodActor = teleTargetMethod.getTeleClassMethodActor();
update(targetMethodCopy, teleClassMethodActor);
} catch (DataIOError dataIOError) {
if (teleClassMethodActor == null) {
Trace.line(TRACE_VALUE, "WARNING: failed to update class actor");
} else {
Trace.line(TRACE_VALUE, "WARNING: failed to update TargetMethod for " + teleClassMethodActor.getName());
}
} finally {
vm().unlock();
}
}
return machineCodeInfo;
}
/**
* @return whether the {@link TargetMethod} being cached has been copied from the VM yet.
*/
public boolean isLoaded() {
return machineCodeInfo.targetMethodCopy != null;
}
public void markDirty() {
isDirty = true;
}
public boolean isDirty() {
return isDirty;
}
public void writeSummary(PrintStream printStream) {
final IndentWriter writer = new IndentWriter(new OutputStreamWriter(printStream));
writer.println("code for: " + teleTargetMethod.classMethodActor().format("%H.%n(%p)"));
writer.println("compilation: " + (teleTargetMethod.longDesignator()));
writer.flush();
final Platform platform = platform();
final TargetMethodMachineCodeInfo tmCache = machineCodeInfo;
final InlineDataDecoder inlineDataDecoder = tmCache.inlineDataDecoder;
final Address startAddress = tmCache.codeStart;
final byte[] code = tmCache.code;
final DisassemblyPrinter disassemblyPrinter = new DisassemblyPrinter(false);
com.sun.max.asm.dis.Disassembler.disassemble(printStream, code, platform.isa, platform.wordWidth(), startAddress.toLong(), inlineDataDecoder, disassemblyPrinter);
}
}