/*
* Copyright (c) 2013, 2020, 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. Oracle designates this
* particular file as subject to the "Classpath" exception as provided
* by Oracle in the LICENSE file that accompanied this code.
*
* 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.truffle.runtime;
import java.lang.ref.WeakReference;
import java.util.function.Consumer;
import java.util.logging.Level;
import org.graalvm.compiler.truffle.common.OptimizedAssumptionDependency;
import org.graalvm.compiler.truffle.options.PolyglotCompilerOptions;
import org.graalvm.options.OptionValues;
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
import com.oracle.truffle.api.TruffleLogger;
import com.oracle.truffle.api.impl.AbstractAssumption;
import com.oracle.truffle.api.nodes.InvalidAssumptionException;
import jdk.vm.ci.meta.JavaKind.FormatWithToString;
An assumption that when invalidated will cause all registered dependencies to be invalidated. /**
* An assumption that when {@linkplain #invalidate() invalidated} will cause all
* {@linkplain #registerDependency() registered} dependencies to be invalidated.
*/
public final class OptimizedAssumption extends AbstractAssumption implements FormatWithToString {
Reference to machine code that is dependent on an assumption.
/**
* Reference to machine code that is dependent on an assumption.
*/
static class Entry implements Consumer<OptimizedAssumptionDependency> {
A machine code reference that must be kept reachable as long as the machine code itself
is valid.
/**
* A machine code reference that must be kept reachable as long as the machine code itself
* is valid.
*/
OptimizedAssumptionDependency dependency;
Machine code that is guaranteed to be invalid once the OptimizedAssumptionDependency
object becomes unreachable. /**
* Machine code that is guaranteed to be invalid once the
* {@link OptimizedAssumptionDependency} object becomes unreachable.
*/
WeakReference<OptimizedAssumptionDependency> weakDependency;
Entry next;
@Override
public synchronized void accept(OptimizedAssumptionDependency dep) {
if (dep == null || dep.soleExecutionEntryPoint()) {
this.weakDependency = new WeakReference<>(dep);
} else {
this.dependency = dep;
}
this.notifyAll();
}
synchronized OptimizedAssumptionDependency awaitDependency() {
boolean interrupted = false;
while (dependency == null && weakDependency == null) {
try {
this.wait();
} catch (InterruptedException e) {
interrupted = true;
}
}
if (interrupted) {
Thread.currentThread().interrupt();
}
if (dependency != null) {
return dependency;
}
return weakDependency.get();
}
synchronized boolean isValid() {
if (dependency != null) {
return dependency.isValid();
}
if (weakDependency != null) {
OptimizedAssumptionDependency dep = weakDependency.get();
return dep != null && dep.isValid();
}
// A pending dependency is treated as valid
return true;
}
@Override
public synchronized String toString() {
if (dependency != null) {
return String.format("%x[%s]", hashCode(), dependency);
}
if (weakDependency != null) {
OptimizedAssumptionDependency dep = weakDependency.get();
return String.format("%x[%s]", hashCode(), dep);
}
return String.format("%x", hashCode());
}
}
Linked list of registered dependencies.
/**
* Linked list of registered dependencies.
*/
private Entry dependencies;
Number of entries in dependencies
. /**
* Number of entries in {@link #dependencies}.
*/
private int size;
Number of entries in dependencies
after most recent call to removeInvalidEntries()
. /**
* Number of entries in {@link #dependencies} after most recent call to
* {@link #removeInvalidEntries()}.
*/
private int sizeAfterLastRemove;
public OptimizedAssumption(String name) {
super(name);
}
@Override
public void check() throws InvalidAssumptionException {
if (!this.isValid()) {
CompilerDirectives.transferToInterpreterAndInvalidate();
throw new InvalidAssumptionException();
}
}
@Override
public void invalidate() {
if (isValid) {
invalidateImpl("");
}
}
@Override
public void invalidate(String message) {
if (isValid) {
invalidateImpl(message);
}
}
@TruffleBoundary
private synchronized void invalidateImpl(String message) {
/*
* Check again, now that we are holding the lock. Since isValid is defined volatile,
* double-checked locking is allowed.
*/
if (!isValid) {
return;
}
OptionValues engineOptions = null;
TruffleLogger logger = null;
boolean logStackTrace = false;
Entry e = dependencies;
CharSequence reason = null;
while (e != null) {
OptimizedAssumptionDependency dependency = e.awaitDependency();
if (dependency != null) {
if (reason == null) {
String useName = name != null ? name : "";
String useMessage = message != null ? message : "";
if (useName.isEmpty() && useMessage.isEmpty()) {
reason = "assumption invalidated";
} else if (useName.isEmpty()) {
reason = useMessage;
} else if (useMessage.isEmpty()) {
reason = useName;
} else {
reason = new LazyReason(useName, useMessage);
}
}
dependency.onAssumptionInvalidated(this, reason);
if (engineOptions == null) {
OptimizedCallTarget callTarget = (OptimizedCallTarget) dependency.getCompilable();
if (callTarget != null) {
engineOptions = callTarget.getOptionValues();
logger = callTarget.engine.getEngineLogger();
} else {
EngineData engineData = GraalTVMCI.getEngineData(null);
engineOptions = engineData.engineOptions;
logger = engineData.getEngineLogger();
}
}
if (engineOptions.get(PolyglotCompilerOptions.TraceAssumptions)) {
logStackTrace = true;
logInvalidatedDependency(dependency, message, logger);
}
}
e = e.next;
}
dependencies = null;
size = 0;
sizeAfterLastRemove = 0;
isValid = false;
if (logStackTrace) {
logStackTrace(engineOptions, logger);
}
}
private void removeInvalidEntries() {
Entry last = null;
Entry e = dependencies;
dependencies = null;
while (e != null) {
if (e.isValid()) {
if (last == null) {
dependencies = e;
} else {
last.next = e;
}
last = e;
} else {
size--;
}
e = e.next;
}
if (last != null) {
last.next = null;
}
sizeAfterLastRemove = size;
}
Removes all invalid dependencies. /**
* Removes all {@linkplain OptimizedAssumptionDependency#isValid() invalid} dependencies.
*/
public synchronized void removeInvalidDependencies() {
removeInvalidEntries();
}
Gets the number of dependencies registered with this assumption.
/**
* Gets the number of dependencies registered with this assumption.
*/
public synchronized int countDependencies() {
return size;
}
Registers some dependent code with this assumption. As the dependent code may not yet be available, a Consumer
is returned that must be notified when the code becomes available. If there is an error while compiling or installing the code, the returned consumer must be called with a null
argument. If this assumption is already invalid, then null
is returned in which case the caller (e.g., the compiler) must ensure the dependent code is never executed. /**
* Registers some dependent code with this assumption.
*
* As the dependent code may not yet be available, a {@link Consumer} is returned that must be
* {@linkplain Consumer#accept(Object) notified} when the code becomes available. If there is an
* error while compiling or installing the code, the returned consumer must be called with a
* {@code null} argument.
*
* If this assumption is already invalid, then {@code null} is returned in which case the caller
* (e.g., the compiler) must ensure the dependent code is never executed.
*/
public synchronized Consumer<OptimizedAssumptionDependency> registerDependency() {
if (isValid) {
if (size >= 2 * sizeAfterLastRemove) {
removeInvalidEntries();
}
Entry e = new Entry();
e.next = dependencies;
dependencies = e;
size++;
return e;
} else {
return null;
}
}
@Override
public boolean isValid() {
return isValid;
}
private void logInvalidatedDependency(OptimizedAssumptionDependency dependency, String message, TruffleLogger logger) {
final StringBuilder sb = new StringBuilder("assumption '").append(name).append("' invalidated installed code '").append(dependency);
if (message != null && !message.isEmpty()) {
sb.append("' with message '").append(message);
}
logger.log(Level.INFO, sb.toString());
}
private static void logStackTrace(OptionValues engineOptions, TruffleLogger logger) {
final int skip = 1;
final int limit = engineOptions.get(PolyglotCompilerOptions.TraceStackTraceLimit);
StackTraceElement[] stackTrace = new Throwable().getStackTrace();
StringBuilder strb = new StringBuilder();
String sep = "";
for (int i = skip; i < stackTrace.length && i < skip + limit; i++) {
strb.append(sep).append(" ").append(stackTrace[i].toString());
sep = "\n";
}
if (stackTrace.length > skip + limit) {
strb.append("\n ...");
}
logger.log(Level.INFO, strb.toString());
}
private static final class LazyReason implements CharSequence {
private final String assumptionName;
private final String message;
private String strValue;
LazyReason(String assumptionName, String message) {
this.assumptionName = assumptionName;
this.message = message;
}
@Override
public int length() {
return toString().length();
}
@Override
public char charAt(int index) {
return toString().charAt(index);
}
@Override
public CharSequence subSequence(int start, int end) {
return toString().subSequence(start, end);
}
@Override
public String toString() {
if (strValue == null) {
strValue = assumptionName + ' ' + message;
}
return strValue;
}
}
}