--- /dev/null 2017-01-22 10:16:57.869617664 -0800 +++ new/src/jdk.internal.vm.compiler/share/classes/org.graalvm.compiler.debug/src/org/graalvm/compiler/debug/internal/DebugScope.java 2017-02-15 16:58:26.121163188 -0800 @@ -0,0 +1,576 @@ +/* + * 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 org.graalvm.compiler.debug.internal; + +import java.io.PrintStream; +import java.util.Iterator; +import java.util.concurrent.Callable; +import java.util.concurrent.atomic.AtomicLong; + +import org.graalvm.compiler.debug.Debug; +import org.graalvm.compiler.debug.DebugConfig; +import org.graalvm.compiler.debug.DebugDumpHandler; +import org.graalvm.compiler.debug.DebugVerifyHandler; +import org.graalvm.compiler.debug.DelegatingDebugConfig; +import org.graalvm.compiler.debug.Indent; +import org.graalvm.compiler.debug.JavaMethodContext; +import org.graalvm.compiler.debug.TTY; +import org.graalvm.compiler.debug.TopLevelDebugConfig; + +import jdk.vm.ci.meta.JavaMethod; + +public final class DebugScope implements Debug.Scope { + + private final class IndentImpl implements Indent { + + private static final String INDENTATION_INCREMENT = " "; + + final String indent; + final IndentImpl parentIndent; + + IndentImpl(IndentImpl parentIndent) { + this.parentIndent = parentIndent; + this.indent = (parentIndent == null ? "" : parentIndent.indent + INDENTATION_INCREMENT); + } + + private boolean logScopeName() { + return logScopeName; + } + + private void printScopeName(StringBuilder str, boolean isCurrent) { + if (logScopeName) { + boolean parentPrinted = false; + if (parentIndent != null) { + parentPrinted = parentIndent.logScopeName(); + parentIndent.printScopeName(str, false); + } + /* + * Always print the current scope, scopes with context and the any scope whose + * parent didn't print. This ensure the first new scope always shows up. + */ + if (isCurrent || printContext(null) != 0 || !parentPrinted) { + str.append(indent).append("[thread:").append(Thread.currentThread().getId()).append("] scope: ").append(getQualifiedName()).append(System.lineSeparator()); + } + printContext(str); + logScopeName = false; + } + } + + /** + * Print or count the context objects for the current scope. + */ + private int printContext(StringBuilder str) { + int count = 0; + if (context != null && context.length > 0) { + // Include some context in the scope output + for (Object contextObj : context) { + if (contextObj instanceof JavaMethodContext || contextObj instanceof JavaMethod) { + if (str != null) { + str.append(indent).append("Context: ").append(contextObj).append(System.lineSeparator()); + } + count++; + } + } + } + return count; + } + + public void log(int logLevel, String msg, Object... args) { + if (isLogEnabled(logLevel)) { + StringBuilder str = new StringBuilder(); + printScopeName(str, true); + str.append(indent); + String result = args.length == 0 ? msg : String.format(msg, args); + String lineSep = System.lineSeparator(); + str.append(result.replace(lineSep, lineSep.concat(indent))); + str.append(lineSep); + output.append(str); + lastUsedIndent = this; + } + } + + IndentImpl indent() { + lastUsedIndent = new IndentImpl(this); + return lastUsedIndent; + } + + @Override + public void close() { + if (parentIndent != null) { + lastUsedIndent = parentIndent; + } + } + } + + /** + * Interface for an additional information object per scope. The information object will be + * given to child scopes, but can be explicitly set with + * {@link DebugScope#enhanceWithExtraInfo(CharSequence, ExtraInfo, boolean, Object...)} + */ + public interface ExtraInfo { + + } + + private static final ThreadLocal instanceTL = new ThreadLocal<>(); + private static final ThreadLocal lastClosedTL = new ThreadLocal<>(); + private static final ThreadLocal configTL = new ThreadLocal<>(); + private static final ThreadLocal lastExceptionThrownTL = new ThreadLocal<>(); + + private final DebugScope parent; + private final DebugConfig parentConfig; + private final boolean sandbox; + private IndentImpl lastUsedIndent; + private boolean logScopeName; + + private final Object[] context; + + private DebugValueMap valueMap; + + private String qualifiedName; + private final String unqualifiedName; + + private final ExtraInfo extraInfo; + + private static final AtomicLong uniqueScopeId = new AtomicLong(); + private final long scopeId; + + private static final char SCOPE_SEP = '.'; + + private boolean countEnabled; + private boolean timeEnabled; + private boolean memUseTrackingEnabled; + private boolean verifyEnabled; + private boolean methodMetricsEnabled; + + private int currentDumpLevel; + private int currentLogLevel; + + private PrintStream output; + + public static long getCurrentGlobalScopeId() { + return uniqueScopeId.get(); + } + + public static DebugScope getInstance() { + DebugScope result = instanceTL.get(); + if (result == null) { + DebugScope topLevelDebugScope = new DebugScope(Thread.currentThread()); + instanceTL.set(topLevelDebugScope); + return topLevelDebugScope; + } else { + return result; + } + } + + public static DebugConfig getConfig() { + return configTL.get(); + } + + static final Object[] EMPTY_CONTEXT = new Object[0]; + + private DebugScope(Thread thread) { + this(thread.getName(), null, uniqueScopeId.incrementAndGet(), null, false); + computeValueMap(thread.getName()); + DebugValueMap.registerTopLevel(getValueMap()); + } + + private DebugScope(String unqualifiedName, DebugScope parent, long scopeId, ExtraInfo metaInfo, boolean sandbox, Object... context) { + this.parent = parent; + this.sandbox = sandbox; + this.parentConfig = getConfig(); + this.context = context; + this.scopeId = scopeId; + this.unqualifiedName = unqualifiedName; + this.extraInfo = metaInfo; + if (parent != null) { + logScopeName = !unqualifiedName.equals(""); + } else { + logScopeName = true; + } + + this.output = TTY.out; + assert context != null; + } + + private void computeValueMap(String name) { + if (parent != null) { + for (DebugValueMap child : parent.getValueMap().getChildren()) { + if (child.getName().equals(name)) { + this.valueMap = child; + return; + } + } + this.valueMap = new DebugValueMap(name); + parent.getValueMap().addChild(this.valueMap); + } else { + this.valueMap = new DebugValueMap(name); + } + } + + @Override + public void close() { + instanceTL.set(parent); + configTL.set(parentConfig); + lastClosedTL.set(this); + } + + public boolean isDumpEnabled(int dumpLevel) { + assert dumpLevel > 0; + return currentDumpLevel >= dumpLevel; + } + + /** + * Enable dumping at the new {@code dumpLevel} for the remainder of enclosing scopes. This only + * works if a {@link TopLevelDebugConfig} was installed at a higher scope. + * + * @param dumpLevel + */ + public static void setDumpLevel(int dumpLevel) { + TopLevelDebugConfig config = fetchTopLevelDebugConfig("setDebugLevel"); + if (config != null) { + config.override(DelegatingDebugConfig.Level.DUMP, dumpLevel); + recursiveUpdateFlags(); + } + } + + /** + * Enable logging at the new {@code logLevel} for the remainder of enclosing scopes. This only + * works if a {@link TopLevelDebugConfig} was installed at a higher scope. + * + * @param logLevel + */ + public static void setLogLevel(int logLevel) { + TopLevelDebugConfig config = fetchTopLevelDebugConfig("setLogLevel"); + if (config != null) { + config.override(DelegatingDebugConfig.Level.LOG, logLevel); + config.delegate(DelegatingDebugConfig.Feature.LOG_METHOD); + recursiveUpdateFlags(); + } + } + + private static void recursiveUpdateFlags() { + DebugScope c = DebugScope.getInstance(); + while (c != null) { + c.updateFlags(); + c = c.parent; + } + } + + private static TopLevelDebugConfig fetchTopLevelDebugConfig(String msg) { + DebugConfig config = getConfig(); + if (config instanceof TopLevelDebugConfig) { + return (TopLevelDebugConfig) config; + } else { + if (config == null) { + TTY.println("DebugScope.%s ignored because debugging is disabled", msg); + } else { + TTY.println("DebugScope.%s ignored because top level delegate config missing", msg); + } + return null; + } + } + + public boolean isVerifyEnabled() { + return verifyEnabled; + } + + public boolean isLogEnabled(int logLevel) { + assert logLevel > 0; + return currentLogLevel >= logLevel; + } + + public boolean isCountEnabled() { + return countEnabled; + } + + public boolean isTimeEnabled() { + return timeEnabled; + } + + public boolean isMethodMeterEnabled() { + return methodMetricsEnabled; + } + + public boolean isMemUseTrackingEnabled() { + return memUseTrackingEnabled; + } + + public void log(int logLevel, String msg, Object... args) { + if (isLogEnabled(logLevel)) { + getLastUsedIndent().log(logLevel, msg, args); + } + } + + public ExtraInfo getExtraInfo() { + return extraInfo; + } + + public long scopeId() { + return scopeId; + } + + public void dump(int dumpLevel, Object object, String formatString, Object... args) { + if (isDumpEnabled(dumpLevel)) { + DebugConfig config = getConfig(); + if (config != null) { + String message = String.format(formatString, args); + for (DebugDumpHandler dumpHandler : config.dumpHandlers()) { + dumpHandler.dump(object, message); + } + } + } + } + + /** + * This method exists mainly to allow a debugger (e.g., Eclipse) to force dump a graph. + */ + public static void forceDump(Object object, String format, Object... args) { + DebugConfig config = getConfig(); + if (config != null) { + String message = String.format(format, args); + for (DebugDumpHandler dumpHandler : config.dumpHandlers()) { + dumpHandler.dump(object, message); + } + } else { + TTY.println("Forced dump ignored because debugging is disabled - use -Dgraal.Dump=xxx"); + } + } + + /** + * @see Debug#verify(Object, String) + */ + public void verify(Object object, String formatString, Object... args) { + if (isVerifyEnabled()) { + DebugConfig config = getConfig(); + if (config != null) { + String message = String.format(formatString, args); + for (DebugVerifyHandler handler : config.verifyHandlers()) { + handler.verify(object, message); + } + } + } + } + + /** + * Creates and enters a new debug scope which is either a child of the current scope or a + * disjoint top level scope. + * + * @param name the name of the new scope + * @param sandboxConfig the configuration to use for a new top level scope, or null if the new + * scope should be a child scope + * @param newContextObjects objects to be appended to the debug context + * @return the new scope which will be exited when its {@link #close()} method is called + */ + public DebugScope scope(CharSequence name, DebugConfig sandboxConfig, Object... newContextObjects) { + DebugScope newScope = null; + if (sandboxConfig != null) { + newScope = new DebugScope(name.toString(), this, uniqueScopeId.incrementAndGet(), null, true, newContextObjects); + configTL.set(sandboxConfig); + } else { + newScope = this.createChild(name.toString(), this.extraInfo, newContextObjects); + } + instanceTL.set(newScope); + newScope.updateFlags(); + return newScope; + } + + public DebugScope enhanceWithExtraInfo(CharSequence name, ExtraInfo newInfo, boolean newId, Object... newContext) { + DebugScope newScope = createChild(name.toString(), newInfo, newId ? uniqueScopeId.incrementAndGet() : this.scopeId, newContext); + instanceTL.set(newScope); + newScope.updateFlags(); + return newScope; + } + + public RuntimeException handle(Throwable e) { + DebugScope lastClosed = lastClosedTL.get(); + assert lastClosed.parent == this : "Debug.handle() used with no matching Debug.scope(...) or Debug.sandbox(...)"; + if (e != lastExceptionThrownTL.get()) { + RuntimeException newException = null; + instanceTL.set(lastClosed); + try (DebugScope s = lastClosed) { + newException = s.interceptException(e); + } + assert instanceTL.get() == this; + assert lastClosed == lastClosedTL.get(); + if (newException == null) { + lastExceptionThrownTL.set(e); + } else { + lastExceptionThrownTL.set(newException); + throw newException; + } + } + if (e instanceof Error) { + throw (Error) e; + } + if (e instanceof RuntimeException) { + throw (RuntimeException) e; + } + throw new RuntimeException(e); + } + + private void updateFlags() { + DebugConfig config = getConfig(); + if (config == null) { + countEnabled = false; + memUseTrackingEnabled = false; + timeEnabled = false; + verifyEnabled = false; + currentDumpLevel = 0; + methodMetricsEnabled = false; + // Be pragmatic: provide a default log stream to prevent a crash if the stream is not + // set while logging + output = TTY.out; + } else { + countEnabled = config.isCountEnabled(); + memUseTrackingEnabled = config.isMemUseTrackingEnabled(); + timeEnabled = config.isTimeEnabled(); + verifyEnabled = config.isVerifyEnabled(); + output = config.output(); + currentDumpLevel = config.getDumpLevel(); + currentLogLevel = config.getLogLevel(); + methodMetricsEnabled = config.isMethodMeterEnabled(); + } + } + + @SuppressWarnings("try") + private RuntimeException interceptException(final Throwable e) { + final DebugConfig config = getConfig(); + if (config != null) { + try (DebugScope s = scope("InterceptException", null, e)) { + return config.interceptException(e); + } catch (Throwable t) { + return new RuntimeException("Exception while intercepting exception", t); + } + } + return null; + } + + private DebugValueMap getValueMap() { + if (valueMap == null) { + computeValueMap(unqualifiedName); + } + return valueMap; + } + + long getCurrentValue(int index) { + return getValueMap().getCurrentValue(index); + } + + void setCurrentValue(int index, long l) { + getValueMap().setCurrentValue(index, l); + } + + private DebugScope createChild(String newName, ExtraInfo newInfo, Object[] newContext) { + return new DebugScope(newName, this, this.scopeId, newInfo, false, newContext); + } + + private DebugScope createChild(String newName, ExtraInfo newInfo, long newId, Object[] newContext) { + return new DebugScope(newName, this, newId, newInfo, false, newContext); + } + + public Iterable getCurrentContext() { + final DebugScope scope = this; + return new Iterable() { + + @Override + public Iterator iterator() { + return new Iterator() { + + DebugScope currentScope = scope; + int objectIndex; + + @Override + public boolean hasNext() { + selectScope(); + return currentScope != null; + } + + private void selectScope() { + while (currentScope != null && currentScope.context.length <= objectIndex) { + currentScope = currentScope.sandbox ? null : currentScope.parent; + objectIndex = 0; + } + } + + @Override + public Object next() { + selectScope(); + if (currentScope != null) { + return currentScope.context[objectIndex++]; + } + throw new IllegalStateException("May only be called if there is a next element."); + } + + @Override + public void remove() { + throw new UnsupportedOperationException("This iterator is read only."); + } + }; + } + }; + } + + public static T call(Callable callable) { + try { + return callable.call(); + } catch (Exception e) { + if (e instanceof RuntimeException) { + throw (RuntimeException) e; + } else { + throw new RuntimeException(e); + } + } + } + + public void setConfig(DebugConfig newConfig) { + configTL.set(newConfig); + updateFlags(); + } + + public String getQualifiedName() { + if (qualifiedName == null) { + if (parent == null) { + qualifiedName = unqualifiedName; + } else { + qualifiedName = parent.getQualifiedName() + SCOPE_SEP + unqualifiedName; + } + } + return qualifiedName; + } + + public Indent pushIndentLogger() { + lastUsedIndent = getLastUsedIndent().indent(); + return lastUsedIndent; + } + + public IndentImpl getLastUsedIndent() { + if (lastUsedIndent == null) { + if (parent != null) { + lastUsedIndent = new IndentImpl(parent.getLastUsedIndent()); + } else { + lastUsedIndent = new IndentImpl(null); + } + } + return lastUsedIndent; + } +}