| /* |
| * Copyright (c) 2009, 2015, 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. |
| * |
| */ |
| |
| /** |
| * A SAX based parser of LogCompilation output from HotSpot. It takes a complete |
| */ |
| |
| package com.sun.hotspot.tools.compiler; |
| |
| import java.io.FileReader; |
| import java.io.PrintStream; |
| import java.io.Reader; |
| import java.util.ArrayDeque; |
| import java.util.ArrayList; |
| import java.util.Comparator; |
| import java.util.Deque; |
| import java.util.HashMap; |
| import java.util.LinkedHashMap; |
| import java.util.regex.Pattern; |
| |
| import javax.xml.parsers.SAXParser; |
| import javax.xml.parsers.SAXParserFactory; |
| |
| import org.xml.sax.Attributes; |
| import org.xml.sax.ErrorHandler; |
| import org.xml.sax.InputSource; |
| import org.xml.sax.Locator; |
| import org.xml.sax.helpers.DefaultHandler; |
| |
| /** |
| * A SAX parser for HotSpot compilation logs. The bulk of the parsing and event |
| * maintenance work is done in the {@link #startElement(String,String,String,Attributes)} |
| * and {@link #endElement(String,String,String)} methods. |
| */ |
| public class LogParser extends DefaultHandler implements ErrorHandler { |
| |
| static final Pattern spacePattern = Pattern.compile(" "); |
| |
| /** |
| * Map internal array type descriptors to Java names. |
| */ |
| static final HashMap<String, String> type2printableMap; |
| |
| /** |
| * Map Java primitive type names to internal type descriptors. |
| */ |
| static final HashMap<String, String> type2vmtypeMap; |
| |
| static { |
| type2printableMap = new HashMap<>(); |
| type2printableMap.put("[I", "int[]"); |
| type2printableMap.put("[C", "char[]"); |
| type2printableMap.put("[Z", "boolean[]"); |
| type2printableMap.put("[L", "Object[]"); |
| type2printableMap.put("[B", "byte[]"); |
| |
| type2vmtypeMap = new HashMap<>(); |
| type2vmtypeMap.put("void", "V"); |
| type2vmtypeMap.put("boolean", "Z"); |
| type2vmtypeMap.put("byte", "B"); |
| type2vmtypeMap.put("char", "C"); |
| type2vmtypeMap.put("short", "S"); |
| type2vmtypeMap.put("int", "I"); |
| type2vmtypeMap.put("long", "J"); |
| type2vmtypeMap.put("float", "F"); |
| type2vmtypeMap.put("double", "D"); |
| } |
| |
| static String[] bytecodes = new String[] { |
| "nop", |
| "aconst_null", |
| "iconst_m1", |
| "iconst_0", |
| "iconst_1", |
| "iconst_2", |
| "iconst_3", |
| "iconst_4", |
| "iconst_5", |
| "lconst_0", |
| "lconst_1", |
| "fconst_0", |
| "fconst_1", |
| "fconst_2", |
| "dconst_0", |
| "dconst_1", |
| "bipush", |
| "sipush", |
| "ldc", |
| "ldc_w", |
| "ldc2_w", |
| "iload", |
| "lload", |
| "fload", |
| "dload", |
| "aload", |
| "iload_0", |
| "iload_1", |
| "iload_2", |
| "iload_3", |
| "lload_0", |
| "lload_1", |
| "lload_2", |
| "lload_3", |
| "fload_0", |
| "fload_1", |
| "fload_2", |
| "fload_3", |
| "dload_0", |
| "dload_1", |
| "dload_2", |
| "dload_3", |
| "aload_0", |
| "aload_1", |
| "aload_2", |
| "aload_3", |
| "iaload", |
| "laload", |
| "faload", |
| "daload", |
| "aaload", |
| "baload", |
| "caload", |
| "saload", |
| "istore", |
| "lstore", |
| "fstore", |
| "dstore", |
| "astore", |
| "istore_0", |
| "istore_1", |
| "istore_2", |
| "istore_3", |
| "lstore_0", |
| "lstore_1", |
| "lstore_2", |
| "lstore_3", |
| "fstore_0", |
| "fstore_1", |
| "fstore_2", |
| "fstore_3", |
| "dstore_0", |
| "dstore_1", |
| "dstore_2", |
| "dstore_3", |
| "astore_0", |
| "astore_1", |
| "astore_2", |
| "astore_3", |
| "iastore", |
| "lastore", |
| "fastore", |
| "dastore", |
| "aastore", |
| "bastore", |
| "castore", |
| "sastore", |
| "pop", |
| "pop2", |
| "dup", |
| "dup_x1", |
| "dup_x2", |
| "dup2", |
| "dup2_x1", |
| "dup2_x2", |
| "swap", |
| "iadd", |
| "ladd", |
| "fadd", |
| "dadd", |
| "isub", |
| "lsub", |
| "fsub", |
| "dsub", |
| "imul", |
| "lmul", |
| "fmul", |
| "dmul", |
| "idiv", |
| "ldiv", |
| "fdiv", |
| "ddiv", |
| "irem", |
| "lrem", |
| "frem", |
| "drem", |
| "ineg", |
| "lneg", |
| "fneg", |
| "dneg", |
| "ishl", |
| "lshl", |
| "ishr", |
| "lshr", |
| "iushr", |
| "lushr", |
| "iand", |
| "land", |
| "ior", |
| "lor", |
| "ixor", |
| "lxor", |
| "iinc", |
| "i2l", |
| "i2f", |
| "i2d", |
| "l2i", |
| "l2f", |
| "l2d", |
| "f2i", |
| "f2l", |
| "f2d", |
| "d2i", |
| "d2l", |
| "d2f", |
| "i2b", |
| "i2c", |
| "i2s", |
| "lcmp", |
| "fcmpl", |
| "fcmpg", |
| "dcmpl", |
| "dcmpg", |
| "ifeq", |
| "ifne", |
| "iflt", |
| "ifge", |
| "ifgt", |
| "ifle", |
| "if_icmpeq", |
| "if_icmpne", |
| "if_icmplt", |
| "if_icmpge", |
| "if_icmpgt", |
| "if_icmple", |
| "if_acmpeq", |
| "if_acmpne", |
| "goto", |
| "jsr", |
| "ret", |
| "tableswitch", |
| "lookupswitch", |
| "ireturn", |
| "lreturn", |
| "freturn", |
| "dreturn", |
| "areturn", |
| "return", |
| "getstatic", |
| "putstatic", |
| "getfield", |
| "putfield", |
| "invokevirtual", |
| "invokespecial", |
| "invokestatic", |
| "invokeinterface", |
| "invokedynamic", |
| "new", |
| "newarray", |
| "anewarray", |
| "arraylength", |
| "athrow", |
| "checkcast", |
| "instanceof", |
| "monitorenter", |
| "monitorexit", |
| "wide", |
| "multianewarray", |
| "ifnull", |
| "ifnonnull", |
| "goto_w", |
| "jsr_w", |
| "breakpoint" |
| }; |
| |
| /** |
| * Sort log events by start time. |
| */ |
| static Comparator<LogEvent> sortByStart = new Comparator<LogEvent>() { |
| |
| public int compare(LogEvent a, LogEvent b) { |
| double difference = (a.getStart() - b.getStart()); |
| if (difference < 0) { |
| return -1; |
| } |
| if (difference > 0) { |
| return 1; |
| } |
| return 0; |
| } |
| |
| @Override |
| public boolean equals(Object other) { |
| return false; |
| } |
| |
| @Override |
| public int hashCode() { |
| return 7; |
| } |
| }; |
| |
| /** |
| * Sort log events first by the name of the compiled method, then by start |
| * time. In case one of the events has no associated compilation (or the |
| * associated compilation has no method name), the event with a compilation |
| * and/or name is considered the larger one. |
| */ |
| static Comparator<LogEvent> sortByNameAndStart = new Comparator<LogEvent>() { |
| |
| public int compare(LogEvent a, LogEvent b) { |
| Compilation c1 = a.getCompilation(); |
| Compilation c2 = b.getCompilation(); |
| if (c1 != null && c1.getMethod() != null && c2 != null && c2.getMethod() != null) { |
| int result = c1.getMethod().toString().compareTo(c2.getMethod().toString()); |
| if (result != 0) { |
| return result; |
| } |
| } else if ((c1 == null || c1.getMethod() == null) && c2 != null && c2.getMethod() != null) { |
| return -1; |
| } else if ((c2 == null || c2.getMethod() == null) && c1 != null && c1.getMethod() != null) { |
| return 1; |
| } |
| return Double.compare(a.getStart(), b.getStart()); |
| } |
| |
| public boolean equals(Object other) { |
| return false; |
| } |
| |
| @Override |
| public int hashCode() { |
| return 7; |
| } |
| }; |
| |
| /** |
| * Sort log events by duration. |
| */ |
| static Comparator<LogEvent> sortByElapsed = new Comparator<LogEvent>() { |
| |
| public int compare(LogEvent a, LogEvent b) { |
| double difference = (a.getElapsedTime() - b.getElapsedTime()); |
| if (difference < 0) { |
| return -1; |
| } |
| if (difference > 0) { |
| return 1; |
| } |
| return 0; |
| } |
| |
| @Override |
| public boolean equals(Object other) { |
| return false; |
| } |
| |
| @Override |
| public int hashCode() { |
| return 7; |
| } |
| }; |
| |
| /** |
| * Shrink-wrapped representation of a JVMState (tailored to meet this |
| * tool's needs). It only records a method and bytecode instruction index. |
| */ |
| class Jvms { |
| Jvms(Method method, int bci) { |
| this.method = method; |
| this.bci = bci; |
| } |
| final public Method method; |
| final public int bci; |
| final public String toString() { |
| return "@" + bci + " " + method; |
| } |
| } |
| |
| /** |
| * Representation of a lock elimination. Locks, corresponding to |
| * synchronized blocks and method calls, may be eliminated if the object in |
| * question is guaranteed to be used thread-locally. |
| */ |
| class LockElimination extends BasicLogEvent { |
| |
| /** |
| * Track all locations from which this lock was eliminated. |
| */ |
| ArrayList<Jvms> jvms = new ArrayList<>(1); |
| |
| /** |
| * The kind of lock (coarsened, nested, non-escaping, unknown). |
| */ |
| final String kind; |
| |
| /** |
| * The lock class (unlock, lock, unknown). |
| */ |
| final String classId; |
| |
| /** |
| * The precise type of lock. |
| */ |
| final String tagName; |
| |
| LockElimination(String tagName, double start, String id, String kind, String classId) { |
| super(start, id); |
| this.kind = kind; |
| this.classId = classId; |
| this.tagName = tagName; |
| } |
| |
| @Override |
| public void print(PrintStream stream, boolean printID) { |
| if (printID) { |
| stream.printf("%s ", getId()); |
| } |
| stream.printf("%s %s %s %.3f ", tagName, kind, classId, getStart()); |
| stream.print(jvms.toString()); |
| stream.print("\n"); |
| } |
| |
| void addJVMS(Method method, int bci) { |
| jvms.add(new Jvms(method, bci)); |
| } |
| |
| } |
| |
| /** |
| * A list of log events. This is populated with the events found in the |
| * compilation log file during parsing. |
| */ |
| private ArrayList<LogEvent> events = new ArrayList<>(); |
| |
| /** |
| * Map compilation log IDs to type names. |
| */ |
| private HashMap<String, String> types = new HashMap<>(); |
| |
| /** |
| * Map compilation log IDs to methods. |
| */ |
| private HashMap<String, Method> methods = new HashMap<>(); |
| |
| /** |
| * Map compilation IDs ({@see #makeId()}) to newly created nmethods. |
| */ |
| private LinkedHashMap<String, NMethod> nmethods = new LinkedHashMap<>(); |
| |
| /** |
| * Map compilation task IDs {@see #makeId()}) to {@link Compilation} |
| * objects. |
| */ |
| private HashMap<String, Compilation> compiles = new HashMap<>(); |
| |
| /** |
| * Track compilation failure reasons. |
| */ |
| private String failureReason; |
| |
| /** |
| * The current bytecode instruction index. |
| */ |
| private int current_bci; |
| |
| /** |
| * The current bytecode instruction. |
| */ |
| private int current_bytecode; |
| |
| /** |
| * A sequence of {@link CallSite}s representing a call stack. A scope |
| * typically holds several {@link CallSite}s that represent calls |
| * originating from that scope. |
| * |
| * New scopes are typically pushed when parse log events are encountered |
| * ({@see #startElement()}) and popped when parsing of a given Java method |
| * is done ({@see #endElement()}). Parsing events can be nested. Several |
| * other events add information to scopes ({@see #startElement()}). |
| */ |
| private Deque<CallSite> scopes = new ArrayDeque<>(); |
| |
| /** |
| * The current compilation. |
| */ |
| private Compilation compile; |
| |
| /** |
| * The {@linkplain CallSite compilation scope} currently in focus. |
| */ |
| private CallSite site; |
| |
| /** |
| * The {@linkplain CallSite method handle call site} currently under |
| * observation. |
| */ |
| private CallSite methodHandleSite; |
| |
| /** |
| * Keep track of potentially nested compiler {@linkplain Phase phases}. |
| */ |
| private Deque<Phase> phaseStack = new ArrayDeque<>(); |
| |
| /** |
| * The {@linkplain LockElimination lock elimination event} currently being |
| * processed. |
| */ |
| private LockElimination currentLockElimination; |
| |
| /** |
| * The {@linkplain UncommonTrapEvent uncommon trap event} currently being |
| * processed. |
| */ |
| private UncommonTrapEvent currentTrap; |
| |
| /** |
| * During the processing of a late inline event, this stack holds the |
| * {@link CallSite}s that represent the inlining event's call stack. |
| */ |
| private Deque<CallSite> lateInlineScope; |
| |
| /** |
| * Denote whether a late inlining event is currently being processed. |
| */ |
| private boolean lateInlining; |
| |
| /** |
| * A document locator to provide better error messages: this allows the |
| * tool to display in which line of the log file the problem occurred. |
| */ |
| private Locator locator; |
| |
| /** |
| * Callback for the SAX framework to set the document locator. |
| */ |
| @Override |
| public void setDocumentLocator(Locator locator) { |
| this.locator = locator; |
| } |
| |
| /** |
| * Report an internal error explicitly raised, i.e., not derived from an |
| * exception. |
| * |
| * @param msg The error message to report. |
| */ |
| private void reportInternalError(String msg) { |
| reportInternalError(msg, null); |
| } |
| |
| /** |
| * Report an internal error derived from an exception. |
| * |
| * @param msg The beginning of the error message to report. The message |
| * from the exception will be appended to this. |
| * @param e The exception that led to the internal error. |
| */ |
| private void reportInternalError(String msg, Exception e) { |
| if (locator != null) { |
| msg += " at " + locator.getLineNumber() + ":" + locator.getColumnNumber(); |
| if (e != null) { |
| msg += " - " + e.getMessage(); |
| } |
| } |
| if (e != null) { |
| throw new Error(msg, e); |
| } else { |
| throw new Error(msg); |
| } |
| } |
| |
| /** |
| * Parse a long hexadecimal address into a {@code long} value. As Java only |
| * supports positive {@code long} values, extra error handling and parsing |
| * logic is provided. |
| */ |
| long parseLong(String l) { |
| try { |
| return Long.decode(l).longValue(); |
| } catch (NumberFormatException nfe) { |
| int split = l.length() - 8; |
| String s1 = "0x" + l.substring(split); |
| String s2 = l.substring(0, split); |
| long v1 = Long.decode(s1).longValue() & 0xffffffffL; |
| long v2 = (Long.decode(s2).longValue() & 0xffffffffL) << 32; |
| if (!l.equals("0x" + Long.toHexString(v1 + v2))) { |
| System.out.println(l); |
| System.out.println(s1); |
| System.out.println(s2); |
| System.out.println(v1); |
| System.out.println(v2); |
| System.out.println(Long.toHexString(v1 + v2)); |
| reportInternalError("bad conversion"); |
| } |
| return v1 + v2; |
| } |
| } |
| |
| /** |
| * Entry point for log file parsing with a file name. |
| * |
| * @param file The name of the log file to parse. |
| * @param cleanup Whether to perform bad XML cleanup during parsing (this |
| * is relevant for some log files generated by the 1.5 JVM). |
| * @return a list of {@link LogEvent} instances describing the events found |
| * in the log file. |
| */ |
| public static ArrayList<LogEvent> parse(String file, boolean cleanup) throws Exception { |
| return parse(new FileReader(file), cleanup); |
| } |
| |
| /** |
| * Entry point for log file parsing with a file reader. |
| * {@link #parse(String,boolean)} |
| */ |
| public static ArrayList<LogEvent> parse(Reader reader, boolean cleanup) throws Exception { |
| // Create the XML input factory |
| SAXParserFactory factory = SAXParserFactory.newInstance(); |
| |
| // Create the XML LogEvent reader |
| SAXParser p = factory.newSAXParser(); |
| |
| if (cleanup) { |
| // some versions of the log have slightly malformed XML, so clean it |
| // up before passing it to SAX |
| reader = new LogCleanupReader(reader); |
| } |
| |
| LogParser log = new LogParser(); |
| try { |
| p.parse(new InputSource(reader), log); |
| } catch (Throwable th) { |
| th.printStackTrace(); |
| // Carry on with what we've got... |
| } |
| |
| // Associate compilations with their NMethods and other kinds of events |
| for (LogEvent e : log.events) { |
| if (e instanceof BasicLogEvent) { |
| BasicLogEvent ble = (BasicLogEvent) e; |
| Compilation c = log.compiles.get(ble.getId()); |
| if (c == null) { |
| if (!(ble instanceof NMethod)) { |
| throw new InternalError("only nmethods should have a null compilation, here's a " + ble.getClass()); |
| } |
| continue; |
| } |
| ble.setCompilation(c); |
| if (ble instanceof NMethod) { |
| c.setNMethod((NMethod) ble); |
| } |
| } |
| } |
| |
| return log.events; |
| } |
| |
| /** |
| * Retrieve a given attribute's value from a collection of XML tag |
| * attributes. Report an error if the requested attribute is not found. |
| * |
| * @param attr A collection of XML tag attributes. |
| * @param name The name of the attribute the value of which is to be found. |
| * @return The value of the requested attribute, or {@code null} if it was |
| * not found. |
| */ |
| String search(Attributes attr, String name) { |
| String result = attr.getValue(name); |
| if (result != null) { |
| return result; |
| } else { |
| reportInternalError("can't find " + name); |
| return null; |
| } |
| } |
| |
| /** |
| * Retrieve a given attribute's value from a collection of XML tag |
| * attributes. Return a default value if the requested attribute is not |
| * found. |
| * |
| * @param attr A collection of XML tag attributes. |
| * @param name The name of the attribute the value of which is to be found. |
| * @param defaultValue The default value to return if the attribute is not |
| * found. |
| * @return The value of the requested attribute, or the default value if it |
| * was not found. |
| */ |
| String search(Attributes attr, String name, String defaultValue) { |
| String result = attr.getValue(name); |
| if (result != null) { |
| return result; |
| } |
| return defaultValue; |
| } |
| |
| /** |
| * Map a type ID from the compilation log to an actual type name. In case |
| * the type represents an internal array type descriptor, return a |
| * Java-level name. If the type ID cannot be mapped to a name, raise an |
| * error. |
| */ |
| String type(String id) { |
| String result = types.get(id); |
| String remapped = type2printableMap.get(result); |
| if (remapped != null) { |
| return remapped; |
| } |
| return result; |
| } |
| |
| /** |
| * Register a mapping from log file type ID to type name. |
| */ |
| void type(String id, String name) { |
| assert type(id) == null; |
| types.put(id, name); |
| } |
| |
| /** |
| * Map a log file type ID to an internal type declarator. |
| */ |
| String sigtype(String id) { |
| String result = types.get(id); |
| String remapped = type2vmtypeMap.get(result); |
| if (remapped != null) { |
| return remapped; |
| } |
| if (result == null) { |
| reportInternalError(id); |
| } |
| if (result.charAt(0) == '[') { |
| return result; |
| } |
| return "L" + result + ";"; |
| } |
| |
| /** |
| * Retrieve a method based on the log file ID it was registered under. |
| * Raise an error if the ID does not map to a method. |
| */ |
| Method method(String id) { |
| Method result = methods.get(id); |
| if (result == null) { |
| reportInternalError(id); |
| } |
| return result; |
| } |
| |
| /** |
| * From a compilation ID and kind, assemble a compilation ID for inclusion |
| * in the output. |
| * |
| * @param atts A collection of XML attributes from which the required |
| * attributes are retrieved. |
| */ |
| public String makeId(Attributes atts) { |
| String id = atts.getValue("compile_id"); |
| String kind = atts.getValue("kind"); |
| if (kind != null && kind.equals("osr")) { |
| id += "%"; |
| } |
| return id; |
| } |
| |
| /** |
| * Process the start of a compilation log XML element.<ul> |
| * <li><b>phase:</b> record the beginning of a compilation phase, pushing |
| * it on the {@linkplain #phaseStack phase stack} and collecting |
| * information about the compiler graph.</li> |
| * <li><b>phase_done:</b> record the end of a compilation phase, popping it |
| * off the {@linkplain #phaseStack phase stack} and collecting information |
| * about the compiler graph (number of nodes and live nodes).</li> |
| * <li><b>task:</b> register the start of a new compilation.</li> |
| * <li><b>type:</b> register a type.</li> |
| * <li><b>bc:</b> note the current bytecode index and instruction name, |
| * updating {@link #current_bci} and {@link #current_bytecode}.</li> |
| * <li><b>klass:</b> register a type (class).</li> |
| * <li><b>method:</b> register a Java method.</li> |
| * <li><b>call:</b> process a call, populating {@link #site} with the |
| * appropriate data.</li> |
| * <li><b>regalloc:</b> record the register allocator's trip count in the |
| * {@linkplain #compile current compilation}.</li> |
| * <li><b>inline_fail:</b> record the reason for a failed inline |
| * operation.</li> |
| * <li><b>inline_success:</b> record a successful inlining operation, |
| * noting the success reason in the {@linkplain #site call site}.</li> |
| * <li><b>failure:</b> note a compilation failure, storing the reason |
| * description in {@link #failureReason}.</li> |
| * <li><b>task_done:</b> register the end of a compilation, recording time |
| * stamp and success information.</li> |
| * <li><b>make_not_entrant:</b> deal with making a native method |
| * non-callable (e.g., during an OSR compilation, if there are still |
| * activations) or a zombie (when the method can be deleted).</li> |
| * <li><b>uncommon_trap:</b> process an uncommon trap, setting the |
| * {@link #currentTrap} field.</li> |
| * <li><b>eliminate_lock:</b> record the start of a lock elimination, |
| * setting the {@link #currentLockElimination} event.</li> |
| * <li><b>late_inline:</b> start processing a late inline decision: |
| * initialize the {@linkplain #lateInlineScope inline scope stack}, create |
| * an {@linkplain #site initial scope} with a bogus bytecode index and the |
| * right inline ID, and push the scope with the inline ID attached. Note |
| * that most of late inlining processing happens in |
| * {@link #endElement(String,String,String)}.</li> |
| * <li><b>jvms:</b> record a {@linkplain Jvms JVMState}. Depending on the |
| * context in which this event is encountered, this can mean adding |
| * information to the currently being processed trap, lock elimination, or |
| * inlining operation.</li> |
| * <li><b>inline_id:</b> set the inline ID in the |
| * {@linkplain #site current call site}.</li> |
| * <li><b>nmethod:</b> record the creation of a new {@link NMethod} and |
| * store it in the {@link #nmethods} map.</li> |
| * <li><b>parse:</b> begin parsing a Java method's bytecode and |
| * transforming it into an initial compiler IR graph.</li> |
| * <li><b>parse_done:</b> finish parsing a Java method's bytecode.</li> |
| * </ul> |
| */ |
| @Override |
| public void startElement(String uri, String localName, String qname, Attributes atts) { |
| if (qname.equals("phase")) { |
| Phase p = new Phase(search(atts, "name"), |
| Double.parseDouble(search(atts, "stamp")), |
| Integer.parseInt(search(atts, "nodes", "0")), |
| Integer.parseInt(search(atts, "live", "0"))); |
| phaseStack.push(p); |
| } else if (qname.equals("phase_done")) { |
| Phase p = phaseStack.pop(); |
| String phaseName = search(atts, "name", null); |
| if (phaseName != null && !p.getId().equals(phaseName)) { |
| System.out.println("phase: " + p.getId()); |
| reportInternalError("phase name mismatch"); |
| } |
| p.setEnd(Double.parseDouble(search(atts, "stamp"))); |
| p.setEndNodes(Integer.parseInt(search(atts, "nodes", "0"))); |
| p.setEndLiveNodes(Integer.parseInt(search(atts, "live", "0"))); |
| compile.getPhases().add(p); |
| } else if (qname.equals("task")) { |
| String id = makeId(atts); |
| |
| // Create the new Compilation instance and populate it with readily |
| // available data. |
| compile = new Compilation(Integer.parseInt(search(atts, "compile_id", "-1"))); |
| compile.setStart(Double.parseDouble(search(atts, "stamp"))); |
| compile.setICount(search(atts, "count", "0")); |
| compile.setBCount(search(atts, "backedge_count", "0")); |
| compile.setBCI(Integer.parseInt(search(atts, "osr_bci", "-1"))); |
| String compiler = atts.getValue("compiler"); |
| if (compiler == null) { |
| compiler = ""; |
| } |
| compile.setCompiler(compiler); |
| |
| // Extract the name of the compiled method. |
| String[] parts = spacePattern.split(atts.getValue("method")); |
| String methodName = parts[0] + "::" + parts[1]; |
| |
| // Continue collecting compilation meta-data. |
| String kind = atts.getValue("compile_kind"); |
| if (kind == null) { |
| kind = "normal"; |
| } |
| if (kind.equals("osr")) { |
| compile.setOsr(true); |
| } else if (kind.equals("c2i")) { |
| compile.setSpecial("--- adapter " + methodName); |
| } else { |
| compile.setSpecial(compile.getId() + " " + methodName + " (0 bytes)"); |
| } |
| |
| // Build a dummy method to stuff in the Compilation at the |
| // beginning. |
| Method m = new Method(); |
| m.setHolder(parts[0]); |
| m.setName(parts[1]); |
| m.setSignature(parts[2]); |
| m.setFlags("0"); |
| m.setBytes(search(atts, "bytes", "unknown")); |
| compile.setMethod(m); |
| events.add(compile); |
| compiles.put(id, compile); |
| site = compile.getCall(); |
| } else if (qname.equals("type")) { |
| type(search(atts, "id"), search(atts, "name")); |
| } else if (qname.equals("bc")) { |
| current_bci = Integer.parseInt(search(atts, "bci")); |
| current_bytecode = Integer.parseInt(search(atts, "code")); |
| } else if (qname.equals("klass")) { |
| type(search(atts, "id"), search(atts, "name")); |
| } else if (qname.equals("method")) { |
| String id = search(atts, "id"); |
| Method m = new Method(); |
| m.setHolder(type(search(atts, "holder"))); |
| m.setName(search(atts, "name")); |
| m.setReturnType(type(search(atts, "return"))); |
| String arguments = atts.getValue("arguments");; |
| if (arguments == null) { |
| m.setSignature("()" + sigtype(atts.getValue("return"))); |
| } else { |
| String[] args = spacePattern.split(arguments); |
| StringBuilder sb = new StringBuilder("("); |
| for (int i = 0; i < args.length; i++) { |
| sb.append(sigtype(args[i])); |
| } |
| sb.append(")"); |
| sb.append(sigtype(atts.getValue("return"))); |
| m.setSignature(sb.toString()); |
| } |
| |
| if (search(atts, "unloaded", "0").equals("0")) { |
| m.setBytes(search(atts, "bytes")); |
| m.setIICount(search(atts, "iicount")); |
| m.setFlags(search(atts, "flags")); |
| } |
| methods.put(id, m); |
| } else if (qname.equals("call")) { |
| if (methodHandleSite != null) { |
| methodHandleSite = null; |
| } |
| Method m = method(search(atts, "method")); |
| if (lateInlining && scopes.size() == 0) { |
| // re-attempting already seen call site (late inlining for MH invokes) |
| if (m != site.getMethod()) { |
| if (current_bci != site.getBci()) { |
| System.err.println(m + " bci: " + current_bci); |
| System.err.println(site.getMethod() + " bci: " + site.getBci()); |
| reportInternalError("bci mismatch after late inlining"); |
| } |
| site.setMethod(m); |
| } |
| } else { |
| // We're dealing with a new call site; the called method is |
| // likely to be parsed next. |
| site = new CallSite(current_bci, m); |
| } |
| site.setCount(Integer.parseInt(search(atts, "count", "0"))); |
| String receiver = atts.getValue("receiver"); |
| if (receiver != null) { |
| site.setReceiver(type(receiver)); |
| site.setReceiver_count(Integer.parseInt(search(atts, "receiver_count"))); |
| } |
| int methodHandle = Integer.parseInt(search(atts, "method_handle_intrinsic", "0")); |
| if (lateInlining && scopes.size() == 0) { |
| // The call was already added before this round of late |
| // inlining. Ignore. |
| } else if (methodHandle == 0) { |
| scopes.peek().add(site); |
| } else { |
| // method handle call site can be followed by another |
| // call (in case it is inlined). If that happens we |
| // discard the method handle call site. So we keep |
| // track of it but don't add it to the list yet. |
| methodHandleSite = site; |
| } |
| } else if (qname.equals("intrinsic")) { |
| String id = atts.getValue("id"); |
| assert id != null : "intrinsic id is null"; |
| CallSite cs = (site != null) ? site : scopes.peek(); |
| assert cs != null : "no CallSite?"; |
| cs.setIntrinsicName(id); |
| } else if (qname.equals("regalloc")) { |
| compile.setAttempts(Integer.parseInt(search(atts, "attempts"))); |
| } else if (qname.equals("inline_fail")) { |
| if (methodHandleSite != null) { |
| scopes.peek().add(methodHandleSite); |
| methodHandleSite = null; |
| } |
| if (lateInlining && scopes.size() == 0) { |
| site.setReason("fail: " + search(atts, "reason")); |
| lateInlining = false; |
| } else { |
| scopes.peek().last().setReason("fail: " + search(atts, "reason")); |
| } |
| } else if (qname.equals("inline_success")) { |
| if (methodHandleSite != null) { |
| reportInternalError("method handle site should have been replaced"); |
| } |
| site.setReason("succeed: " + search(atts, "reason")); |
| } else if (qname.equals("failure")) { |
| failureReason = search(atts, "reason"); |
| } else if (qname.equals("task_done")) { |
| compile.setEnd(Double.parseDouble(search(atts, "stamp"))); |
| if (Integer.parseInt(search(atts, "success")) == 0) { |
| compile.setFailureReason(failureReason); |
| failureReason = null; |
| } |
| } else if (qname.equals("make_not_entrant")) { |
| String id = makeId(atts); |
| NMethod nm = nmethods.get(id); |
| if (nm == null) reportInternalError("nm == null"); |
| LogEvent e = new MakeNotEntrantEvent(Double.parseDouble(search(atts, "stamp")), id, |
| atts.getValue("zombie") != null, nm); |
| events.add(e); |
| } else if (qname.equals("uncommon_trap")) { |
| String id = atts.getValue("compile_id"); |
| if (id != null) { |
| id = makeId(atts); |
| currentTrap = new UncommonTrapEvent(Double.parseDouble(search(atts, "stamp")), |
| id, |
| atts.getValue("reason"), |
| atts.getValue("action"), |
| Integer.parseInt(search(atts, "count", "0"))); |
| events.add(currentTrap); |
| } else { |
| if (atts.getValue("method") != null) { |
| // These are messages from ciTypeFlow that don't |
| // actually correspond to generated code. |
| return; |
| } |
| try { |
| UncommonTrap unc = new UncommonTrap(Integer.parseInt(search(atts, "bci")), |
| search(atts, "reason"), |
| search(atts, "action"), |
| bytecodes[current_bytecode]); |
| if (scopes.size() == 0) { |
| // There may be a dangling site not yet in scopes after a late_inline |
| if (site != null) { |
| site.add(unc); |
| } else { |
| reportInternalError("scope underflow"); |
| } |
| } else { |
| scopes.peek().add(unc); |
| } |
| } catch (Error e) { |
| e.printStackTrace(); |
| } |
| } |
| } else if (qname.startsWith("eliminate_lock")) { |
| String id = atts.getValue("compile_id"); |
| if (id != null) { |
| id = makeId(atts); |
| String kind = atts.getValue("kind"); |
| String classId = atts.getValue("class_id"); |
| currentLockElimination = new LockElimination(qname, Double.parseDouble(search(atts, "stamp")), id, kind, classId); |
| events.add(currentLockElimination); |
| } |
| } else if (qname.equals("late_inline")) { |
| long inlineId = 0; |
| try { |
| inlineId = Long.parseLong(search(atts, "inline_id")); |
| } catch (InternalError ex) { |
| // Log files from older hotspots may lack inline_id, |
| // and zero is an acceptable substitute that allows processing to continue. |
| } |
| lateInlineScope = new ArrayDeque<>(); |
| Method m = method(search(atts, "method")); |
| site = new CallSite(-999, m); |
| site.setInlineId(inlineId); |
| lateInlineScope.push(site); |
| } else if (qname.equals("jvms")) { |
| // <jvms bci='4' method='java/io/DataInputStream readChar ()C' bytes='40' count='5815' iicount='20815'/> |
| if (currentTrap != null) { |
| String[] parts = spacePattern.split(atts.getValue("method")); |
| currentTrap.addMethodAndBCI(parts[0].replace('/', '.') + '.' + parts[1] + parts[2], Integer.parseInt(atts.getValue("bci"))); |
| } else if (currentLockElimination != null) { |
| currentLockElimination.addJVMS(method(atts.getValue("method")), Integer.parseInt(atts.getValue("bci"))); |
| } else if (lateInlineScope != null) { |
| current_bci = Integer.parseInt(search(atts, "bci")); |
| Method m = method(search(atts, "method")); |
| site = new CallSite(current_bci, m); |
| lateInlineScope.push(site); |
| } else { |
| // Ignore <eliminate_allocation type='667'>, |
| // <replace_string_concat arguments='2' string_alloc='0' multiple='0'> |
| } |
| } else if (qname.equals("inline_id")) { |
| if (methodHandleSite != null) { |
| reportInternalError("method handle site should have been replaced"); |
| } |
| long id = Long.parseLong(search(atts, "id")); |
| site.setInlineId(id); |
| } else if (qname.equals("nmethod")) { |
| String id = makeId(atts); |
| NMethod nm = new NMethod(Double.parseDouble(search(atts, "stamp")), |
| id, |
| parseLong(atts.getValue("address")), |
| parseLong(atts.getValue("size"))); |
| nmethods.put(id, nm); |
| events.add(nm); |
| } else if (qname.equals("parse")) { |
| if (failureReason != null && scopes.size() == 0 && !lateInlining) { |
| // A compilation just failed, and we're back at a top |
| // compilation scope. |
| failureReason = null; |
| compile.reset(); |
| site = compile.getCall(); |
| } |
| |
| // Error checking. |
| if (methodHandleSite != null) { |
| reportInternalError("method handle site should have been replaced"); |
| } |
| Method m = method(search(atts, "method")); // this is the method being parsed |
| if (lateInlining && scopes.size() == 0) { |
| if (site.getMethod() != m) { |
| reportInternalError("Unexpected method mismatch during late inlining (method at call site: " + |
| site.getMethod() + ", method being parsed: " + m + ")"); |
| } |
| } |
| |
| if (scopes.size() == 0 && !lateInlining) { |
| // The method being parsed is actually the method being |
| // compiled; i.e., we're dealing with a compilation top scope, |
| // which we must consequently push to the scopes stack. |
| compile.setMethod(m); |
| scopes.push(site); |
| } else { |
| // The method being parsed is *not* the current compilation's |
| // top scope; i.e., we're dealing with an actual call site |
| // in the top scope or somewhere further down a call stack. |
| if (site.getMethod() == m) { |
| // We're dealing with monomorphic inlining that didn't have |
| // to be narrowed down, because the receiver was known |
| // beforehand. |
| scopes.push(site); |
| } else if (scopes.peek().getCalls().size() > 2 && m == scopes.peek().lastButOne().getMethod()) { |
| // We're dealing with an at least bimorphic call site, and |
| // the compiler has now decided to parse the last-but-one |
| // method. The last one may already have been parsed for |
| // inlining. |
| scopes.push(scopes.peek().lastButOne()); |
| } else { |
| // The method has been narrowed down to the one we're now |
| // going to parse, which is inlined here. It's monomorphic |
| // inlining, but was not immediately clear as such. |
| // |
| // C1 prints multiple method tags during inlining when it |
| // narrows the method being inlined. Example: |
| // ... |
| // <method id="813" holder="694" name="toString" return="695" flags="1" bytes="36" iicount="1"/> |
| // <call method="813" instr="invokevirtual"/> |
| // <inline_success reason="receiver is statically known"/> |
| // <method id="814" holder="792" name="toString" return="695" flags="1" bytes="5" iicount="3"/> |
| // <parse method="814"> |
| // ... |
| site.setMethod(m); |
| scopes.push(site); |
| } |
| } |
| } else if (qname.equals("parse_done")) { |
| // Attach collected information about IR nodes to the current |
| // parsing scope before it's popped off the stack in endElement() |
| // (see where the parse tag is handled). |
| CallSite call = scopes.peek(); |
| call.setEndNodes(Integer.parseInt(search(atts, "nodes", "0"))); |
| call.setEndLiveNodes(Integer.parseInt(search(atts, "live", "0"))); |
| call.setTimeStamp(Double.parseDouble(search(atts, "stamp"))); |
| } |
| } |
| |
| /** |
| * Process the end of a compilation log XML element.<ul> |
| * <li><b>parse:</b> finish transforming a Java method's bytecode |
| * instructions to an initial compiler IR graph.</li> |
| * <li><b>uncommon_trap:</b> record the end of processing an uncommon trap, |
| * resetting {@link #currentTrap}.</li> |
| * <li><b>eliminate_lock:</b> record the end of a lock elimination, |
| * resetting {@link #currentLockElimination}.</li> |
| * <li><b>late_inline:</b> the closing tag for late_inline does not denote |
| * the end of a late inlining operation, but the end of the descriptive log |
| * data given at its beginning. That is, we're now in the position to |
| * assemble details about the inlining chain (bytecode instruction index in |
| * caller, called method). The {@link #lateInlining} flag is set to |
| * {@code true} here. (It will be reset when parsing the inlined methods is |
| * done; this happens for the successful case in this method as well, when |
| * {@code parse} elements are processed; and for inlining failures, in |
| * {@link #startElement(String,String,String,Attributes)}, when {@code inline_fail} elements are |
| * processed.)</li> |
| * <li><b>task:</b> perform cleanup at the end of a compilation. Note that |
| * the explicit {@code task_done} event is handled in |
| * {@link #startElement(String,String,String,Attributes)}.</li> |
| * </ul> |
| */ |
| @Override |
| public void endElement(String uri, String localName, String qname) { |
| try { |
| if (qname.equals("parse")) { |
| // Finish dealing with the current call scope. If no more are |
| // left, no late inlining can be going on. |
| scopes.pop(); |
| if (scopes.size() == 0) { |
| lateInlining = false; |
| } |
| } else if (qname.equals("uncommon_trap")) { |
| currentTrap = null; |
| } else if (qname.startsWith("eliminate_lock")) { |
| currentLockElimination = null; |
| } else if (qname.equals("late_inline")) { |
| // Populate late inlining info. |
| if (scopes.size() != 0) { |
| reportInternalError("scopes should be empty for late inline"); |
| } |
| // late inline scopes are specified in reverse order: |
| // compiled method should be on top of stack. |
| CallSite caller = lateInlineScope.pop(); |
| Method m = compile.getMethod(); |
| if (!m.equals(caller.getMethod())) { |
| reportInternalError(String.format("call site and late_inline info don't match:\n method %s\n caller method %s, bci %d", m, caller.getMethod(), current_bci)); |
| } |
| |
| // Walk down the inlining chain and assemble bci+callee info. |
| // This needs to be converted from caller+bci info contained in |
| // the late_inline data. |
| CallSite lateInlineSite = compile.getLateInlineCall(); |
| ArrayDeque<CallSite> thisCallScopes = new ArrayDeque<>(); |
| do { |
| current_bci = caller.getBci(); |
| // Next inlined call. |
| caller = lateInlineScope.pop(); |
| CallSite callee = new CallSite(current_bci, caller.getMethod()); |
| callee.setInlineId(caller.getInlineId()); |
| thisCallScopes.addLast(callee); |
| lateInlineSite.add(callee); |
| lateInlineSite = callee; |
| } while (!lateInlineScope.isEmpty()); |
| |
| site = compile.getCall().findCallSite(thisCallScopes); |
| if (site == null) { |
| // Call site could not be found - report the problem in detail. |
| System.err.println("call scopes:"); |
| for (CallSite c : thisCallScopes) { |
| System.err.println(c.getMethod() + " " + c.getBci() + " " + c.getInlineId()); |
| } |
| CallSite c = thisCallScopes.getLast(); |
| if (c.getInlineId() != 0) { |
| System.err.println("Looking for call site in entire tree:"); |
| ArrayDeque<CallSite> stack = compile.getCall().findCallSite2(c); |
| for (CallSite c2 : stack) { |
| System.err.println(c2.getMethod() + " " + c2.getBci() + " " + c2.getInlineId()); |
| } |
| } |
| System.err.println(caller.getMethod() + " bci: " + current_bci); |
| reportInternalError("couldn't find call site"); |
| } |
| lateInlining = true; |
| |
| if (caller.getBci() != -999) { |
| System.out.println(caller.getMethod()); |
| reportInternalError("broken late_inline info"); |
| } |
| if (site.getMethod() != caller.getMethod()) { |
| if (site.getInlineId() == caller.getInlineId()) { |
| site.setMethod(caller.getMethod()); |
| } else { |
| System.out.println(site.getMethod()); |
| System.out.println(caller.getMethod()); |
| reportInternalError("call site and late_inline info don't match"); |
| } |
| } |
| // late_inline is followed by parse with scopes.size() == 0, |
| // 'site' will be pushed to scopes. |
| lateInlineScope = null; |
| } else if (qname.equals("task")) { |
| types.clear(); |
| methods.clear(); |
| site = null; |
| } |
| } catch (Exception e) { |
| reportInternalError("exception while processing end element", e); |
| } |
| } |
| |
| // |
| // Handlers for problems that occur in XML parsing itself. |
| // |
| |
| @Override |
| public void warning(org.xml.sax.SAXParseException e) { |
| System.err.println(e.getMessage() + " at line " + e.getLineNumber() + ", column " + e.getColumnNumber()); |
| e.printStackTrace(); |
| } |
| |
| @Override |
| public void error(org.xml.sax.SAXParseException e) { |
| System.err.println(e.getMessage() + " at line " + e.getLineNumber() + ", column " + e.getColumnNumber()); |
| e.printStackTrace(); |
| } |
| |
| @Override |
| public void fatalError(org.xml.sax.SAXParseException e) { |
| System.err.println(e.getMessage() + " at line " + e.getLineNumber() + ", column " + e.getColumnNumber()); |
| e.printStackTrace(); |
| } |
| } |