/*
* 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.ins.method;
import java.io.*;
import java.util.*;
import javax.swing.*;
import com.sun.max.ins.*;
import com.sun.max.ins.debug.*;
import com.sun.max.ins.gui.*;
import com.sun.max.ins.util.*;
import com.sun.max.io.*;
import com.sun.max.lang.*;
import com.sun.max.tele.*;
import com.sun.max.tele.method.*;
import com.sun.max.tele.object.*;
import com.sun.max.unsafe.*;
import com.sun.max.vm.actor.member.*;
import com.sun.max.vm.actor.member.MethodKey.MethodActorKey;
import com.sun.max.vm.bytecode.*;
import com.sun.max.vm.classfile.constant.*;
import com.sun.max.vm.type.*;
/**
* Base class for Bytecodes viewers.
*/
public abstract class BytecodeViewer extends CodeViewer {
@Override
public MethodCodeKind codeKind() {
return MethodCodeKind.BYTECODES;
}
@Override
public String codeViewerKindName() {
return "Bytecodes";
}
private final TeleClassMethodActor teleClassMethodActor;
/**
* @return Local {@link TeleClassMethodActor} corresponding to the VM method being viewed.
*/
public TeleClassMethodActor teleClassMethodActor() {
return teleClassMethodActor;
}
/**
* Abstract description of the method being viewed.
*/
private final MethodKey methodKey;
/**
* @return abstract description of the method being viewed.
*/
protected final MethodKey methodKey() {
return methodKey;
}
private final MaxCompilation compilation;
/**
* The compilation associated with this view, if exists.
*/
protected MaxCompilation compilation() {
return compilation;
}
private final byte[] methodBytes;
private final TeleConstantPool teleConstantPool;
/**
* @return local surrogate for the {@link ConstantPool} in the VM that is associated with this method.
*/
protected TeleConstantPool teleConstantPool() {
return teleConstantPool;
}
private final ConstantPool localConstantPool;
/**
* @return local {@link ConstantPool}, should be equivalent to the one in the VM that is associated with this method.
*/
protected ConstantPool localConstantPool() {
return localConstantPool;
}
/**
* Disassembled machine code instructions from the associated compilation of the method, null if none associated.
*/
private List<TargetCodeInstruction> machineCodeInstructions = null;
private boolean haveMachineCodeAddresses = false;
/**
* True if a compiled version of the method is available and if we have a map between bytecode and machine code locations.
*/
protected boolean haveMachineCodeAddresses() {
return haveMachineCodeAddresses;
}
private List<BytecodeInstruction> bytecodeInstructions = null;
protected List<BytecodeInstruction> bytecodeInstructions() {
return bytecodeInstructions;
}
/**
* Base class for bytecode viewers. Machine code is optional, since a method may not yet be compiled, but may appear
* and change as method is compiled and recompiled.
*/
protected BytecodeViewer(Inspection inspection, MethodView parent, TeleClassMethodActor teleClassMethodActor, MaxCompilation compilation) {
super(inspection, parent);
this.teleClassMethodActor = teleClassMethodActor;
this.compilation = compilation;
methodKey = new MethodActorKey(teleClassMethodActor.classMethodActor());
final TeleCodeAttribute teleCodeAttribute = teleClassMethodActor.getTeleCodeAttribute();
// Always use the {@link ConstantPool} taken from the {@link CodeAttribute}; in a substituted method, the
// constant pool for the bytecodes is the one from the origin of the substitution, not the current holder of the method.
teleConstantPool = teleCodeAttribute.getTeleConstantPool();
localConstantPool = teleConstantPool.getTeleHolder().classActor().constantPool();
methodBytes = teleCodeAttribute.readBytecodes();
buildView();
rowToStackFrame = new MaxStackFrame[bytecodeInstructions.size()];
}
private void buildView() {
int[] bciToMachineCodePositionMap = null;
MaxMachineCodeInfo machineCodeInfo = null;
if (compilation != null) {
machineCodeInfo = compilation.getMachineCodeInfo();
bciToMachineCodePositionMap = machineCodeInfo.bciToMachineCodePositionMap();
// TODO (mlvdv) can only map bytecodes to JIT machine code so far
if (bciToMachineCodePositionMap != null) {
haveMachineCodeAddresses = true;
}
}
bytecodeInstructions = new ArrayList<BytecodeInstruction>(10);
int bci = 0;
int bytecodeRow = 0;
int machineCodeRow = 0;
Address machineCodeFirstAddress = Address.zero();
while (bci < methodBytes.length) {
final OutputStream stream = new NullOutputStream();
try {
final InspectorBytecodePrinter bytecodePrinter = new InspectorBytecodePrinter(new PrintStream(stream), localConstantPool);
final BytecodeScanner bytecodeScanner = new BytecodeScanner(bytecodePrinter);
final int nextBCI = bytecodeScanner.scanInstruction(methodBytes, bci);
final byte[] instructionBytes = Bytes.getSection(methodBytes, bci, nextBCI);
if (haveMachineCodeAddresses) {
while (machineCodeInfo.instruction(machineCodeRow).position < bciToMachineCodePositionMap[bci]) {
machineCodeRow++;
}
machineCodeFirstAddress = machineCodeInfo.instruction(machineCodeRow).address;
}
final BytecodeInstruction instruction = new BytecodeInstruction(bytecodeRow, bci, instructionBytes, bytecodePrinter.opcode(), bytecodePrinter.operand1(),
bytecodePrinter.operand2(), machineCodeRow, machineCodeFirstAddress);
bytecodeInstructions.add(instruction);
bytecodeRow++;
bci = nextBCI;
} catch (Throwable throwable) {
InspectorError.unexpected("could not disassemble bytecode", throwable);
}
}
}
/**
* @return Whether the compiled code in the VM for the bytecode at specified row contains the specified address.
*/
protected boolean rowContainsAddress(int row, Address address) {
if (haveMachineCodeAddresses) {
final BytecodeInstruction bytecodeInstruction = bytecodeInstructions.get(row);
if (address.lessThan(bytecodeInstruction.machineCodeFirstAddress)) {
// before the first byte location of the first machine instruction for this bytecode
return false;
}
if (row < (bytecodeInstructions.size() - 1)) {
// All but last bytecode instruction: see if before the first byte location of the first machine code instruction for the next bytecode
return address.lessThan(bytecodeInstructions.get(row + 1).machineCodeFirstAddress);
}
// Last bytecode instruction: see if before the end of the machine code
final MaxMachineCodeInfo machineCodeInfo = compilation.getMachineCodeInfo();
final TargetCodeInstruction lastMachineCodeInstruction = machineCodeInfo.instruction(machineCodeInfo.length() - 1);
return address.lessThan(lastMachineCodeInstruction.address.plus(lastMachineCodeInstruction.bytes.length));
}
return false;
}
/**
* Rebuilds the cache of stack information if needed, based on the thread that is the current focus.
* Identifies for each instruction in the method a stack frame (if any) whose instruction pointer is at the address of the instruction.
*/
@Override
protected void updateStackCache() {
if (haveMachineCodeAddresses()) {
Arrays.fill(rowToStackFrame, null);
final MaxThread thread = focus().thread();
if (thread != null) {
for (int row = 0; row < bytecodeInstructions.size(); row++) {
for (MaxStackFrame frame : thread.stack().frames(StackView.DEFAULT_MAX_FRAMES_DISPLAY)) {
if (frame.codeLocation() != null && rowContainsAddress(row, frame.codeLocation().address())) {
rowToStackFrame[row] = frame;
break;
}
}
}
}
}
}
/**
* Determines if the compiled code for the bytecode has a machine code breakpoint
* set at this location in the VM, in
* situations where we can map between locations.
*/
protected List<MaxBreakpoint> getMachineCodeBreakpointsAtRow(int row) {
final List<MaxBreakpoint> breakpoints = new LinkedList<MaxBreakpoint>();
if (haveMachineCodeAddresses) {
for (MaxBreakpoint breakpoint : vm().breakpointManager().breakpoints()) {
if (!breakpoint.isBytecodeBreakpoint() && rowContainsAddress(row, breakpoint.codeLocation().address())) {
breakpoints.add(breakpoint);
}
}
}
return breakpoints;
}
/**
* @return the bytecode breakpoint, if any, set at the bytecode being displayed in the row.
*/
protected MaxBreakpoint getBytecodeBreakpointAtRow(int row) {
for (MaxBreakpoint breakpoint : vm().breakpointManager().breakpoints()) {
if (breakpoint.isBytecodeBreakpoint()) {
final MaxCodeLocation breakpointLocation = breakpoint.codeLocation();
// the direction of key comparison is significant
if (methodKey.equals(breakpointLocation.methodKey()) && bytecodeInstructions().get(row).bci == breakpointLocation.bci()) {
return breakpoint;
}
}
}
return null;
}
protected final String rowToTagText(int row) {
// Unimplemented
return "";
}
/**
* A representation of a bytecode instruction, suitable for displaying
* different aspects of it.
*
*/
protected class BytecodeInstruction {
/**
* bytecode index: offset from method beginning of the first byte of this instruction.
*/
public final int bci;
/**
* the bytes constituting this instruction.
*/
public final byte[] instructionBytes;
/**
* index of this instruction in the method.
*/
public final int row;
/**
* index of the first machine code instruction implementing this bytecode (Jit only for now).
*/
public final int machineCodeRow;
/**
* address of the first byte in the machine code instructions implementing this bytecode (Jit only for now).
*/
public final Address machineCodeFirstAddress;
/**
* the code of the operation for this instruction.
*/
public final int opcode;
// * Either a rendering component or an index into the constant pool if a reference kind. */
/**
* The first operand of this instruction:
* <ul>
* <li>if the operand is a reference kind, returns an {@link Integer} index into the {@link ConstantPool}</li>
* <li>if the operand is not a reference kind, returns a {@link BytecodeOperandLabel} that can render the operand.</li>
* </ul>
*/
public final Object operand1;
/**
* The second operand of this instruction:
* <ul>
* <li>if the operand is a reference kind, returns an {@link Integer} index into the {@link ConstantPool}</li>
* <li>if the operand is not a reference kind, returns a {@link BytecodeOperandLabel} that can render the operand.</li>
* </ul>
*/
public final Object operand2;
BytecodeInstruction(int bytecodeRow, int bci, byte[] bytes, int opcode, Object operand1, Object operand2, int machineCodeRow, Address machineCodeFirstAddress) {
this.row = bytecodeRow;
this.bci = bci;
this.instructionBytes = bytes;
this.opcode = opcode;
this.operand1 = operand1;
this.operand2 = operand2;
this.machineCodeRow = machineCodeRow;
this.machineCodeFirstAddress = machineCodeFirstAddress;
}
}
private final class InspectorBytecodePrinter extends BytecodePrinter {
public InspectorBytecodePrinter(PrintStream stream, ConstantPool constantPool) {
super(new PrintWriter(stream), constantPool, "", "", 0);
}
@Override
protected void prolog() {
}
private int opcode;
@Override
protected void printOpcode() {
opcode = currentOpcode();
}
public int opcode() {
return opcode;
}
private Object operand1 = new BytecodeOperandLabel(inspection(), "");
public Object operand1() {
return operand1;
}
private JComponent operand2 = new BytecodeOperandLabel(inspection(), "");
public JComponent operand2() {
return operand2;
}
@Override
protected void printImmediate(int immediate) {
operand1 = new BytecodeOperandLabel(inspection(), immediate);
}
@Override
protected void printKind(Kind kind) {
operand1 = new BytecodeOperandLabel(inspection(), kind.name.toString());
}
@Override
protected void printConstant(final int index) {
operand1 = new Integer(index);
}
@Override
public void iinc(int index, int addend) {
printOpcode();
operand1 = new BytecodeOperandLabel(inspection(), index);
operand2 = new BytecodeOperandLabel(inspection(), addend);
}
@Override
public void multianewarray(int index, int nDimensions) {
printOpcode();
printConstant(index);
operand2 = new BytecodeOperandLabel(inspection(), nDimensions);
}
@Override
public void tableswitch(int defaultOffset, int lowMatch, int highMatch, int numberOfCases) {
printOpcode();
operand1 = new BytecodeOperandLabel(inspection(), defaultOffset + ", [" + lowMatch + " - " + highMatch + "] -> ");
final StringBuilder sb = new StringBuilder();
for (int i = 0; i != numberOfCases; ++i) {
sb.append(bytecodeScanner().readSwitchOffset());
if (i != numberOfCases - 1) {
sb.append(' ');
}
}
operand2 = new BytecodeOperandLabel(inspection(), sb.toString());
}
@Override
public void lookupswitch(int defaultOffset, int numberOfCases) {
printOpcode();
printImmediate(defaultOffset);
String s = "";
String separator = ", ";
for (int i = 0; i < numberOfCases; i++) {
s += separator + bytecodeScanner().readSwitchCase() + "->" + bytecodeScanner().readSwitchOffset();
separator = " ";
}
operand2 = new BytecodeOperandLabel(inspection(), s);
}
}
}