/*
* Copyright (c) 2016, 2016, 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.truffle.test;
import static org.graalvm.compiler.truffle.TruffleCompilerOptions.TruffleCompilationThreshold;
import static org.graalvm.compiler.truffle.TruffleCompilerOptions.TruffleCompileOnly;
import static org.graalvm.compiler.truffle.TruffleCompilerOptions.TruffleFunctionInlining;
import static org.graalvm.compiler.truffle.TruffleCompilerOptions.TruffleOSRCompilationThreshold;
import static org.graalvm.compiler.truffle.TruffleCompilerOptions.TruffleReplaceReprofileCount;
import static org.junit.Assert.assertEquals;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertNotNull;
import static org.junit.Assert.assertNotSame;
import static org.junit.Assert.assertNull;
import static org.junit.Assert.assertTrue;
import java.lang.reflect.Field;
import java.util.List;
import java.util.stream.Collectors;
import java.util.stream.IntStream;
import org.graalvm.compiler.core.common.util.Util;
import org.graalvm.compiler.truffle.GraalTruffleRuntime;
import org.graalvm.compiler.truffle.OptimizedCallTarget;
import org.graalvm.compiler.truffle.OptimizedOSRLoopNode;
import org.graalvm.compiler.truffle.TruffleCompilerOptions;
import org.graalvm.compiler.truffle.TruffleCompilerOptions.TruffleOptionsOverrideScope;
import org.graalvm.compiler.truffle.test.nodes.AbstractTestNode;
import org.graalvm.compiler.truffle.test.nodes.ConstantTestNode;
import org.graalvm.compiler.truffle.test.nodes.RootTestNode;
import org.junit.Ignore;
import org.junit.Test;
import com.oracle.truffle.api.Assumption;
import com.oracle.truffle.api.CallTarget;
import com.oracle.truffle.api.CompilerDirectives;
import com.oracle.truffle.api.Truffle;
import com.oracle.truffle.api.frame.FrameDescriptor;
import com.oracle.truffle.api.frame.VirtualFrame;
import com.oracle.truffle.api.nodes.DirectCallNode;
import com.oracle.truffle.api.nodes.LoopNode;
import com.oracle.truffle.api.nodes.Node;
import com.oracle.truffle.api.nodes.RepeatingNode;
import com.oracle.truffle.api.nodes.RootNode;
@SuppressWarnings("try")
public class OptimizedCallTargetTest extends TestWithSynchronousCompiling {
private static final GraalTruffleRuntime runtime = (GraalTruffleRuntime) Truffle.getRuntime();
private static final Field nodeRewritingAssumptionField;
static {
try {
nodeRewritingAssumptionField = OptimizedCallTarget.class.getDeclaredField("nodeRewritingAssumption");
Util.setAccessible(nodeRewritingAssumptionField, true);
} catch (NoSuchFieldException | SecurityException e) {
throw new AssertionError(e);
}
}
private static Assumption getRewriteAssumption(OptimizedCallTarget target) {
try {
return (Assumption) nodeRewritingAssumptionField.get(target);
} catch (IllegalArgumentException | IllegalAccessException e) {
throw new AssertionError(e);
}
}
private static final class CallTestNode extends AbstractTestNode {
@Child private DirectCallNode callNode;
CallTestNode(CallTarget ct) {
this.callNode = runtime.createDirectCallNode(ct);
}
@Override
public int execute(VirtualFrame frame) {
return (int) callNode.call(frame.getArguments());
}
}
volatile int testInvalidationCounterCompiled = 0;
volatile int testInvalidationCounterInterpreted = 0;
volatile boolean doInvalidate;
/*
* GR-1328
*/
@Test
@Ignore("Fails non deterministically")
public void testCompilationHeuristics() {
testInvalidationCounterCompiled = 0;
testInvalidationCounterInterpreted = 0;
doInvalidate = false;
OptimizedCallTarget target = (OptimizedCallTarget) runtime.createCallTarget(new RootNode(null) {
@Override
public Object execute(VirtualFrame frame) {
if (CompilerDirectives.inInterpreter()) {
testInvalidationCounterInterpreted++;
} else {
testInvalidationCounterCompiled++;
}
// doInvalidate needs to be volatile otherwise it floats up.
if (doInvalidate) {
CompilerDirectives.transferToInterpreterAndInvalidate();
}
return null;
}
});
final int compilationThreshold = TruffleCompilerOptions.getValue(TruffleCompilationThreshold);
final int reprofileCount = TruffleCompilerOptions.getValue(TruffleReplaceReprofileCount);
assertTrue(compilationThreshold >= 2);
int expectedCompiledCount = 0;
int expectedInterpreterCount = 0;
for (int i = 0; i < compilationThreshold; i++) {
assertNotCompiled(target);
target.call();
}
assertCompiled(target);
expectedInterpreterCount += compilationThreshold;
assertEquals(expectedCompiledCount, testInvalidationCounterCompiled);
assertEquals(expectedInterpreterCount, testInvalidationCounterInterpreted);
for (int j = 1; j < 100; j++) {
target.invalidate();
for (int i = 0; i < reprofileCount; i++) {
assertNotCompiled(target);
target.call();
}
assertCompiled(target);
expectedInterpreterCount += reprofileCount;
assertEquals(expectedCompiledCount, testInvalidationCounterCompiled);
assertEquals(expectedInterpreterCount, testInvalidationCounterInterpreted);
doInvalidate = true;
expectedCompiledCount++;
target.call();
assertNotCompiled(target);
doInvalidate = false;
assertEquals(expectedCompiledCount, testInvalidationCounterCompiled);
assertEquals(expectedInterpreterCount, testInvalidationCounterInterpreted);
for (int i = 0; i < reprofileCount; i++) {
assertNotCompiled(target);
target.call();
}
assertCompiled(target);
expectedInterpreterCount += reprofileCount;
assertEquals(expectedCompiledCount, testInvalidationCounterCompiled);
assertEquals(expectedInterpreterCount, testInvalidationCounterInterpreted);
for (int i = 0; i < compilationThreshold; i++) {
assertCompiled(target);
target.call();
}
assertCompiled(target);
expectedCompiledCount += compilationThreshold;
assertEquals(expectedCompiledCount, testInvalidationCounterCompiled);
assertEquals(expectedInterpreterCount, testInvalidationCounterInterpreted);
}
}
@Test
public void testRewriteAssumption() {
String testName = "testRewriteAssumption";
final int compilationThreshold = TruffleCompilerOptions.getValue(TruffleCompilationThreshold);
assertTrue(compilationThreshold >= 2);
assertTrue("test only works with inlining enabled", TruffleCompilerOptions.getValue(TruffleFunctionInlining));
IntStream.range(0, 8).parallel().forEach(i -> {
// We need to restate the option override for each thread individually.
try (TruffleOptionsOverrideScope s = TruffleCompilerOptions.overrideOptions(TruffleCompilerOptions.TruffleBackgroundCompilation, false)) {
OptimizedCallTarget innermostCallTarget = (OptimizedCallTarget) runtime.createCallTarget(new RootTestNode(new FrameDescriptor(), testName + 0, new AbstractTestNode() {
@Child private AbstractTestNode child = new ConstantTestNode(42);
@Child private AbstractTestNode dummy = new ConstantTestNode(17);
@Override
public int execute(VirtualFrame frame) {
int k = (int) frame.getArguments()[0];
if (k > compilationThreshold) {
CompilerDirectives.transferToInterpreter();
dummy.replace(new ConstantTestNode(k));
}
return child.execute(frame);
}
}));
OptimizedCallTarget ct = innermostCallTarget;
ct = (OptimizedCallTarget) runtime.createCallTarget(new RootTestNode(new FrameDescriptor(), testName + 1, new CallTestNode(ct)));
ct = (OptimizedCallTarget) runtime.createCallTarget(new RootTestNode(new FrameDescriptor(), testName + 2, new CallTestNode(ct)));
final OptimizedCallTarget outermostCallTarget = ct;
assertNull("assumption is initially null", getRewriteAssumption(innermostCallTarget));
IntStream.range(0, compilationThreshold / 2).parallel().forEach(k -> {
assertEquals(42, outermostCallTarget.call(k));
assertNull("assumption stays null in the interpreter", getRewriteAssumption(innermostCallTarget));
});
outermostCallTarget.compile();
assertCompiled(outermostCallTarget);
Assumption firstRewriteAssumption = getRewriteAssumption(innermostCallTarget);
assertNotNull("assumption must not be null after compilation", firstRewriteAssumption);
assertTrue(firstRewriteAssumption.isValid());
List<Assumption> rewriteAssumptions = IntStream.range(0, 2 * compilationThreshold).parallel().mapToObj(k -> {
assertEquals(42, outermostCallTarget.call(k));
Assumption rewriteAssumptionAfter = getRewriteAssumption(innermostCallTarget);
assertNotNull("assumption must not be null after compilation", rewriteAssumptionAfter);
return rewriteAssumptionAfter;
}).collect(Collectors.toList());
Assumption finalRewriteAssumption = getRewriteAssumption(innermostCallTarget);
assertNotNull("assumption must not be null after compilation", finalRewriteAssumption);
assertNotSame(firstRewriteAssumption, finalRewriteAssumption);
assertFalse(firstRewriteAssumption.isValid());
assertTrue(finalRewriteAssumption.isValid());
assertFalse(rewriteAssumptions.stream().filter(a -> a != finalRewriteAssumption).anyMatch(Assumption::isValid));
}
});
}
private static class NamedRootNode extends RootNode {
private String name;
NamedRootNode(String name) {
super(null);
this.name = name;
}
@Override
public Object execute(VirtualFrame frame) {
return name;
}
@Override
public String getName() {
return name;
}
}
@Test
public void testCompileOnly1() {
final int compilationThreshold = TruffleCompilerOptions.getValue(TruffleCompilationThreshold);
// test single include
try (TruffleOptionsOverrideScope scope = TruffleCompilerOptions.overrideOptions(TruffleCompileOnly, "foobar")) {
OptimizedCallTarget target = (OptimizedCallTarget) runtime.createCallTarget(new NamedRootNode("foobar"));
for (int i = 0; i < compilationThreshold; i++) {
assertNotCompiled(target);
target.call();
}
assertCompiled(target);
target = (OptimizedCallTarget) runtime.createCallTarget(new NamedRootNode("baz"));
for (int i = 0; i < compilationThreshold; i++) {
assertNotCompiled(target);
target.call();
}
assertNotCompiled(target);
}
}
@Test
public void testCompileOnly2() {
final int compilationThreshold = TruffleCompilerOptions.getValue(TruffleCompilationThreshold);
// test single exclude
try (TruffleOptionsOverrideScope scope = TruffleCompilerOptions.overrideOptions(TruffleCompileOnly, "~foobar")) {
OptimizedCallTarget target = (OptimizedCallTarget) runtime.createCallTarget(new NamedRootNode("foobar"));
for (int i = 0; i < compilationThreshold; i++) {
assertNotCompiled(target);
target.call();
}
assertNotCompiled(target);
target = (OptimizedCallTarget) runtime.createCallTarget(new NamedRootNode("baz"));
for (int i = 0; i < compilationThreshold; i++) {
assertNotCompiled(target);
target.call();
}
assertCompiled(target);
}
}
@Test
public void testCompileOnly3() {
final int compilationThreshold = TruffleCompilerOptions.getValue(TruffleCompilationThreshold);
// test two includes/excludes
try (TruffleOptionsOverrideScope scope = TruffleCompilerOptions.overrideOptions(TruffleCompileOnly, "foo,baz")) {
OptimizedCallTarget target = (OptimizedCallTarget) runtime.createCallTarget(new NamedRootNode("foobar"));
for (int i = 0; i < compilationThreshold; i++) {
assertNotCompiled(target);
target.call();
}
assertCompiled(target);
target = (OptimizedCallTarget) runtime.createCallTarget(new NamedRootNode("baz"));
for (int i = 0; i < compilationThreshold; i++) {
assertNotCompiled(target);
target.call();
}
assertCompiled(target);
}
}
private static class OSRRepeatingNode extends Node implements RepeatingNode {
int count = 0;
final int osrCompilationThreshold;
OSRRepeatingNode(int osrCompilationThreshold) {
this.osrCompilationThreshold = osrCompilationThreshold;
}
@Override
public boolean executeRepeating(VirtualFrame frame) {
count++;
return count < (osrCompilationThreshold + 10);
}
}
@Test
public void testCompileOnly4() {
// OSR should not trigger for compile-only includes
try (TruffleOptionsOverrideScope scope = TruffleCompilerOptions.overrideOptions(TruffleCompileOnly, "foobar")) {
final OSRRepeatingNode repeating = new OSRRepeatingNode(TruffleCompilerOptions.getValue(TruffleOSRCompilationThreshold));
final LoopNode loop = runtime.createLoopNode(repeating);
OptimizedCallTarget target = (OptimizedCallTarget) runtime.createCallTarget(new NamedRootNode("foobar") {
@Child LoopNode loopChild = loop;
@Override
public Object execute(VirtualFrame frame) {
loopChild.executeLoop(frame);
return super.execute(frame);
}
});
target.call();
OptimizedCallTarget osrTarget = findOSRTarget(loop);
if (osrTarget != null) {
assertNotCompiled(osrTarget);
}
}
}
private static OptimizedCallTarget findOSRTarget(Node loopNode) {
if (loopNode instanceof OptimizedOSRLoopNode) {
return ((OptimizedOSRLoopNode) loopNode).getCompiledOSRLoop();
}
for (Node child : loopNode.getChildren()) {
OptimizedCallTarget target = findOSRTarget(child);
if (target != null) {
return target;
}
}
return null;
}
@Test
public void testCompileOnly5() {
// OSR should trigger if compile-only with excludes
try (TruffleOptionsOverrideScope scope = TruffleCompilerOptions.overrideOptions(TruffleCompileOnly, "~foobar")) {
final OSRRepeatingNode repeating = new OSRRepeatingNode(TruffleCompilerOptions.getValue(TruffleOSRCompilationThreshold));
final LoopNode loop = runtime.createLoopNode(repeating);
OptimizedCallTarget target = (OptimizedCallTarget) runtime.createCallTarget(new NamedRootNode("foobar") {
@Child LoopNode loopChild = loop;
@Override
public Object execute(VirtualFrame frame) {
loopChild.executeLoop(frame);
return super.execute(frame);
}
});
target.call();
OptimizedCallTarget osrTarget = findOSRTarget(loop);
assertCompiled(osrTarget);
}
}
}