/* * Copyright (c) 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.oracle.max.vm.ext.maxri; import static com.oracle.max.vm.ext.maxri.MaxTargetMethod.*; import static com.oracle.max.vm.ext.maxri.ValueCodec.*; import static com.sun.cri.ci.CiUtil.*; import static com.sun.max.platform.Platform.*; import static com.sun.max.vm.MaxineVM.*; import java.io.*; import java.util.*; import com.oracle.max.asm.target.amd64.*; import com.sun.cri.ci.*; import com.sun.cri.ri.*; import com.sun.max.annotate.*; import com.sun.max.lang.*; import com.sun.max.unsafe.*; import com.sun.max.vm.actor.holder.*; import com.sun.max.vm.actor.member.*; import com.sun.max.vm.compiler.*; import com.sun.max.vm.compiler.target.TargetMethod.CodePosClosure; import com.sun.max.vm.compiler.target.TargetMethod.FrameAccess; import com.sun.max.vm.reference.*; import com.sun.max.vm.runtime.*; /** * The debug info for the safepoints in a {@link MaxTargetMethod}. */ public final class DebugInfo { static final int FIRST_FRAME = 1; static final int NO_FRAME = 0; /** * Encoded debug info. This array has three ordered sections: * <ol> * <li>The frame and register reference maps for the associated target method. * The format of this section is described by the following pseudo C declaration: * <pre> * referenceMaps { * { * u1 frameMap[frameRefMapSize]; * u1 regMap[regRefMapSize]; * } directCallMaps[numberOfDirectCalls] * { * u1 frameMap[frameRefMapSize]; * u1 regMap[regRefMapSize]; * } indirectCallMaps[numberOfIndirectCalls] * { * u1 frameMap[frameRefMapSize]; * u1 regMap[regRefMapSize]; * } safepointMaps[numberOfSafepoints] * } * </pre> * </li> * <li>Frame position table (FPT). This is a table mapping frame indexes to the position of the frame encoded (in the following section) * for the safepoint. The prefix of the table is the safepoint indexes. Following that are indexes of the caller frames. If {@code data.length <= 0xFFFF} * then each entry in this table is an unsigned short otherwise each entry is an integer. The size of this * table is stored in {@link #fptSize}.</li> * <li>Encoded frames. * The format of this section is described by {@code frames} in the following pseudo C declarations. * All {@code uint}s are encoded with {@link EncodingStream#encodeUInt(int)} and the encoding * of {@code value} is specified by {@link ValueCodec#writeValue(EncodingStream, CiValue)}.</li> * <pre> * { * uint caller; // index in FPT of caller frame + 1 (0 means no caller) * uint holder; // class ID of method holder * union { * method_with_bci; * method_no_bci; * } method // the ID of method (within holder) and BCI * uint num_locals; * uint num_stack; * uint num_locks; * value[num_locals + num_stack + num_locks] values; * } frames[fptSize] * * method_with_bci { * uint method; // the ID of method << 1 | 0 * uint bci; // BCI * } * * method_no_bci { * uint method; // the ID of method << 1 | 1 (BCI is -1) * } * </pre> * </li> * </ol> */ final byte[] data; /** * The number of entries in the FPT. */ final int fptSize; /** * The target method associated with this debug info. */ final MaxTargetMethod tm; /** * Encodes an array of debug infos. * * @param debugInfos an array of debug infos correlated with each safepoint index * @param tm the target method associated with the debug infos */ public DebugInfo(CiDebugInfo[] debugInfos, MaxTargetMethod tm) { this.tm = tm; final HashMap<CiFrame, int[]> framesMap = new HashMap<CiFrame, int[]>(); final EncodingStream out = new EncodingStream(1024); // Reserve space for the reference maps int totalRefMapsSize = (tm.totalRefMapSize()) * debugInfos.length; out.skip(totalRefMapsSize); int index = 0; for (CiDebugInfo info : debugInfos) { if (info != null) { int refmapIndex = index * (tm.totalRefMapSize()); initRefMap(out.buf, index, refmapIndex, info, tm.frameRefMapSize(), regRefMapSize()); CiFrame frame = info.frame(); if (frame != null) { int[] indexes = framesMap.get(frame); if (indexes == null) { framesMap.put(frame, new int[] {index}); } else { indexes = Arrays.copyOf(indexes, indexes.length + 1); indexes[indexes.length - 1] = index; framesMap.put(frame, indexes); } } } index++; } for (CiDebugInfo info : debugInfos) { if (info != null && info.frame() != null) { index = gatherCallers(info.frame().caller(), framesMap, index); } } fptSize = index; final int fpt = out.pos; if (!encodeFrames(framesMap, out, 2)) { out.seek(fpt, true); encodeFrames(framesMap, out, 4); } this.data = out.toByteArray(); if (isHosted()) { // Test encoding & decoding while offline for (int i = 0; i < debugInfos.length; ++i) { if (debugInfos[i] != null && debugInfos[i].frame() != null) { CiDebugInfo info = infoAt(i, null, false); CiFrame frame = info.frame(); CiFrame originalFrame = debugInfos[i].frame(); assert frame.equals(originalFrame, true, true); } forEachCodePos(new TestCPC(), i); } } totalDebugInfos++; totalDebugInfoBytes += this.data.length; totalCode += tm.codeLength(); } /** * Encodes a given set of frames and records their positions in the FPT. * * @param framesMap map from frames to indexes in the FPT * @param out the underlying encoding buffer currently positioned where the FPT is to be written * @param fps the frame position size (i.e. the size of an entry in the FPT) * @return {@code true} if {@code fps != 2} or every element in the underlying buffer can be addressed by an unsigned 16-bit index */ boolean encodeFrames(final HashMap<CiFrame, int[]> framesMap, final EncodingStream out, int fps) { // Reserve space for the FPT int fpt = out.pos; out.skip(fptSize * fps); for (CiFrame frame : framesMap.keySet()) { int framePos = out.pos; // Write the frame position in the FPT int[] indexes = framesMap.get(frame); for (int frameIndex : indexes) { int fptEntry = fpt + (frameIndex * fps); out.seek(fptEntry, false); if (fps == 2) { out.writeShort(framePos); } else { out.writeInt(framePos); } } // Write the frame itself out.seek(framePos, false); CiFrame caller = frame.caller(); if (caller == null) { out.encodeUInt(NO_FRAME); } else { out.encodeUInt(framesMap.get(caller)[0] + FIRST_FRAME); } out.encodeUInt(((ClassActor) frame.method.holder()).id); int m = ((MethodActor) frame.method).memberIndex() << 1; if (frame.bci < 0) { out.encodeUInt(m | 1); } else { out.encodeUInt(m); out.encodeUInt(frame.bci); } out.encodeUInt(frame.numLocals); out.encodeUInt(frame.numStack); out.encodeUInt(frame.numLocks); for (CiValue value : frame.values) { if (isHosted()) { // Test codec while offline CiValue v = testCodec(value); if (!value.equalsIgnoringKind(v)) { writeValue(out, value); assert false : "value: " + value + ", v: " + v; } } writeValue(out, value); } if (fps == 2 && (out.pos & 0xFFFF) != out.pos) { return false; } } return true; } private static int gatherCallers(CiFrame callerFrame, HashMap<CiFrame, int[]> framesMap, int nextIndex) { if (callerFrame == null || framesMap.containsKey(callerFrame)) { return nextIndex; } nextIndex = gatherCallers(callerFrame.caller(), framesMap, nextIndex); framesMap.put(callerFrame, new int[] {nextIndex}); return ++nextIndex; } private static void initRefMap(byte[] data, int index, int refmapIndex, CiDebugInfo debugInfo, int frameRefMapSize, int regRefMapSize) { if (debugInfo != null) { // copy the stack map int frameRefMapBytes; if (debugInfo.hasStackRefMap()) { frameRefMapBytes = debugInfo.frameRefMap.copyTo(data, refmapIndex, -1); assert new CiBitMap(data, refmapIndex, frameRefMapSize).equals(debugInfo.frameRefMap); } else { frameRefMapBytes = 0; } // copy the register map if (debugInfo.hasRegisterRefMap()) { debugInfo.registerRefMap.copyTo(data, refmapIndex + frameRefMapBytes, regRefMapSize); assert new CiBitMap(data, refmapIndex + frameRefMapBytes, regRefMapSize).equals(debugInfo.registerRefMap); } } } /** * Gets the index in {@code #data} at which the register reference map for safepoint {@code index} starts. */ public int regRefMapStart(int index) { return index * tm.totalRefMapSize() + tm.frameRefMapSize(); } /** * Gets the index in {@code #data} at which the frame reference map for safepoint {@code index} starts. */ public int frameRefMapStart(int index) { return index * tm.totalRefMapSize(); } /** * Gets the frame reference map for a given safepoint index. */ public CiBitMap frameRefMapAt(int index) { return new CiBitMap(data, index * tm.totalRefMapSize(), tm.frameRefMapSize()); } /** * Gets the register reference map for a given safepoint index. */ public CiBitMap regRefMapAt(int index) { return new CiBitMap(data, index * tm.totalRefMapSize() + tm.frameRefMapSize(), regRefMapSize()); } /** * Iterates over the code positions encoded for a given stsafepointop index. * * @param cpc a closure called for each bytecode location in the inlining chain rooted * at {@code index} (inner most callee first) * @param index the index of a frame * * @return the number of code positions iterated over (i.e. the number of times * {@link CodePosClosure#doCodePos(ClassMethodActor, int)} was called */ public int forEachCodePos(CodePosClosure cpc, int index) { int count = 0; final DecodingStream in = new DecodingStream(data); int fpt = (tm.totalRefMapSize()) * tm.safepoints().size(); int frameIndex = index; while (true) { in.pos = framePos(fpt, frameIndex); if (in.pos == 0) { return count; } count++; int encCallerIndex = in.decodeUInt(); int holderID = in.decodeUInt(); ClassActor holder = ClassIDManager.toClassActor(holderID); int m = in.decodeUInt(); MethodActor method; int bci; if ((m & 1) == 1) { bci = -1; int memberIndex = m >>> 1; method = holder.getLocalMethodActor(memberIndex); } else { int memberIndex = m >>> 1; method = holder.getLocalMethodActor(memberIndex); bci = in.decodeUInt(); } assert method != null; assert bci == -1 || (bci >= 0 && bci < method.code().length); if (!cpc.doCodePos((ClassMethodActor) method, bci)) { return count; } if (encCallerIndex == NO_FRAME) { return count; } int callerIndex = encCallerIndex - FIRST_FRAME; assert frameIndex != callerIndex; frameIndex = callerIndex; } } /** * Decodes the debug info at a given safepoint index. * * @param index a safepoint index * @param fa access to a live frame (may be {@code null}) * @param stackSlotAsAddress translate stack slots to stack addresses * @return the frame(s) at {@code index} */ public CiDebugInfo infoAt(int index, FrameAccess fa, boolean stackSlotAsAddress) { final DecodingStream in = new DecodingStream(data); int fpt = (tm.totalRefMapSize()) * tm.safepoints().size(); CiBitMap regRefMap = regRefMapAt(index); CiBitMap frameRefMap = frameRefMapAt(index); CiFrame frame = decodeFrame(in, fpt, index, fa, regRefMap, frameRefMap, stackSlotAsAddress); return new CiDebugInfo(frame, regRefMap, frameRefMap); } /** * Gets the position at which a given frame is encoded in {@link #data}. * * @param fpt the position of the FPT in {@link #data} * @param index the index of an entry in the FPT * @return the value of entry {@code index} in the FPT */ int framePos(int fpt, int index) { if ((data.length & 0xFFFF) == data.length) { int pos = fpt + (index * 2); // entry is an unsigned short int hi = data[pos] & 0xff; int lo = data[pos + 1] & 0xff; return (hi << 8) | (lo << 0); } else { // entry is an int int pos = fpt + (index * 4); int b0 = data[pos] & 0xff; int b1 = data[pos + 1] & 0xff; int b2 = data[pos + 2] & 0xff; int b3 = data[pos + 3] & 0xff; return (b0 << 24) | (b1 << 16) | (b2 << 8) | (b3 << 0); } } /** * Decodes a frame denoted by a given frame index. * @param fpt the position of the FPT in {@link #data} * @param frameIndex the index of an entry in the FPT * @param stackSlotAsAddress translate stack slots to stack addresses * @return the decoded frame */ CiFrame decodeFrame(DecodingStream in, int fpt, int frameIndex, FrameAccess fa, CiBitMap regRefMap, CiBitMap frameRefMap, boolean stackSlotAsAddress) { int framePos = framePos(fpt, frameIndex); if (framePos == 0) { return null; } in.pos = framePos; int encCallerIndex = in.decodeUInt(); int holderID = in.decodeUInt(); ClassActor holder = ClassIDManager.toClassActor(holderID); int m = in.decodeUInt(); RiResolvedMethod method; int bci; if ((m & 1) == 1) { bci = -1; int memberIndex = m >>> 1; method = holder.getLocalMethodActor(memberIndex); } else { int memberIndex = m >>> 1; method = holder.getLocalMethodActor(memberIndex); bci = in.decodeUInt(); } assert method != null; assert bci == -1 || (bci >= 0 && bci < method.code().length); int numLocals = in.decodeUInt(); int numStack = in.decodeUInt(); int numLocks = in.decodeUInt(); int n = numLocals + numStack + numLocks; CiValue[] values = new CiValue[n]; for (int i = 0; i < n; i++) { CiValue value = readValue(in, regRefMap, frameRefMap); if (fa != null) { value = toLiveSlot(fa, value); } else { if (stackSlotAsAddress && value != null && value.isStackSlot()) { CiStackSlot ss = (CiStackSlot) value; CiRegister fp; if (platform().isa == ISA.AMD64) { fp = AMD64.rsp; } else { throw FatalError.unimplemented(); } final int offsetInFrame = ss.index() * target().spillSlotSize; if (ss.inCallerFrame()) { int callerFrame = tm.frameSize() + target().arch.returnAddressSize; int offset = callerFrame + offsetInFrame; value = new CiAddress(ss.kind, fp.asValue(), offset); } else { value = new CiAddress(ss.kind, fp.asValue(), offsetInFrame); } } } values[i] = value; } CiFrame caller = null; if (encCallerIndex != NO_FRAME) { int callerIndex = encCallerIndex - FIRST_FRAME; assert frameIndex != callerIndex; caller = decodeFrame(in, fpt, callerIndex, fa, regRefMap, frameRefMap, stackSlotAsAddress); } return new CiFrame(caller, method, bci, false, values, numLocals, numStack, numLocks); } private static CiValue toLiveSlot(FrameAccess fa, CiValue value) { if (value.isRegister()) { CiRegister reg = value.asRegister(); CiCalleeSaveLayout csl = fa.csl; assert csl != null : "cannot recover value for " + reg; int offset = csl.offsetOf(reg); if (value.kind.isObject()) { Reference ref = fa.csa.readReference(offset); value = CiConstant.forObject(ref.toJava()); } else if (value.kind.isLong()) { long l = fa.csa.readLong(offset); value = CiConstant.forLong(l); } else if (value.kind.isDouble()) { double d = fa.csa.readDouble(offset); value = CiConstant.forDouble(d); } else { Word w = fa.csa.readWord(offset); value = WordUtil.archConstant(w); } } else if (value.isStackSlot()) { CiStackSlot ss = (CiStackSlot) value; Pointer base = ss.inCallerFrame() ? fa.callerSP : fa.sp; if (value.kind.isObject()) { Reference ref = base.readReference(ss.index() * Word.size()); value = CiConstant.forObject(ref.toJava()); } else if (value.kind.isLong()) { long l = base.readLong(ss.index() * Word.size()); value = CiConstant.forLong(l); } else if (value.kind.isDouble()) { double d = base.readDouble(ss.index() * Word.size()); value = CiConstant.forDouble(d); } else { Word w = base.readWord(ss.index() * Word.size()); value = WordUtil.archConstant(w); } } else if (value.isIllegal()) { value = WordUtil.ZERO; } else { assert value.isConstant(); } return value; } @Override public String toString() { if (data == null) { // Still somewhere in the constructor return ""; } StringBuilder sb = new StringBuilder(1024); for (int i = 0; i < tm.safepoints().size(); i++) { CiDebugInfo info = infoAt(i, null, true); if (sb.length() != 0) { sb.append(NEW_LINE); } sb.append("==== safepoint " + i + " [" + tm + "+" + tm.safepoints().posAt(i) + "] ====").append(NEW_LINE).append(info); } return sb.toString(); } static int totalDebugInfos; static long totalDebugInfoBytes; static long totalCode; public static void dumpStats(PrintStream out) { out.println("-- DebugInfo stats --"); out.println(" " + totalDebugInfos + " DebugInfo objects"); out.println(" " + totalDebugInfoBytes + " DebugInfo bytes (avg per target method: " + (float) ((double) totalDebugInfoBytes / totalDebugInfos) + ")"); out.println(" " + totalCode + " code bytes"); out.println(" " + objectConstants.size() + " object constants"); out.println(" " + nonObjectConstants.size() + " non-object constants"); out.println(" " + valuesEncoded + " values encoded (avg bytes per value: " + (float) ((double) valuesEncodedSize / valuesEncoded) + ")"); } @HOSTED_ONLY static class TestCPC implements CodePosClosure { public boolean doCodePos(ClassMethodActor method, int bci) { assert method != null; assert bci == -1 || (bci >= 0 && bci < method.code().length); return true; } } }