package org.graalvm.compiler.hotspot.test;
import static org.graalvm.compiler.debug.DebugOptions.DumpOnError;
import org.graalvm.compiler.api.directives.GraalDirectives;
import org.graalvm.compiler.api.replacements.ClassSubstitution;
import org.graalvm.compiler.api.replacements.MethodSubstitution;
import org.graalvm.compiler.api.test.Graal;
import org.graalvm.compiler.core.common.spi.ForeignCallDescriptor;
import org.graalvm.compiler.core.test.GraalCompilerTest;
import org.graalvm.compiler.debug.GraalError;
import org.graalvm.compiler.debug.TTY;
import org.graalvm.compiler.hotspot.nodes.CurrentJavaThreadNode;
import org.graalvm.compiler.nodes.StructuredGraph;
import org.graalvm.compiler.nodes.StructuredGraph.AllowAssumptions;
import org.graalvm.compiler.nodes.ValueNode;
import org.graalvm.compiler.nodes.extended.ForeignCallNode;
import org.graalvm.compiler.nodes.graphbuilderconf.GraphBuilderContext;
import org.graalvm.compiler.nodes.graphbuilderconf.InvocationPlugin;
import org.graalvm.compiler.nodes.graphbuilderconf.InvocationPlugins;
import org.graalvm.compiler.nodes.graphbuilderconf.InvocationPlugins.Registration;
import org.graalvm.compiler.options.OptionValues;
import org.graalvm.compiler.phases.tiers.Suites;
import org.graalvm.compiler.replacements.ReplacementsImpl;
import org.graalvm.compiler.replacements.classfile.ClassfileBytecodeProvider;
import org.graalvm.compiler.runtime.RuntimeProvider;
import org.graalvm.word.LocationIdentity;
import org.junit.Assert;
import org.junit.Test;
import jdk.vm.ci.meta.JavaKind;
import jdk.vm.ci.meta.MetaAccessProvider;
import jdk.vm.ci.meta.ResolvedJavaMethod;
public class MethodSubstitutionForeignCallTest extends GraalCompilerTest {
public static final ForeignCallDescriptor TEST_CALL_DEOPT = new ForeignCallDescriptor("test", int.class, new Class<?>[]{int.class}, false, new LocationIdentity[0], true, true);
public static final ForeignCallDescriptor TEST_CALL_REEXECUTABLE = new ForeignCallDescriptor("test", int.class, new Class<?>[]{int.class}, true, new LocationIdentity[0], false, false);
public static final ForeignCallDescriptor TEST_CALL_NON_DEOPT = new ForeignCallDescriptor("test", int.class, new Class<?>[]{int.class}, false, new LocationIdentity[0], false, false);
public static class A {
static void invalidConsecutiveForeignCall1(@SuppressWarnings("unused") int phi) {
}
static void invalidConsecutiveForeignCall2(@SuppressWarnings("unused") int phi) {
}
static void validConsecutiveForeignCallReexecutable(@SuppressWarnings("unused") int phi) {
}
static void splitForeignCallInvalid(@SuppressWarnings("unused") int phi) {
}
}
@ClassSubstitution(A.class)
public static class ASubstitutions {
@MethodSubstitution
static void invalidConsecutiveForeignCall1(int phi) {
testDeopt(phi);
testDeopt(phi);
}
@MethodSubstitution
static void invalidConsecutiveForeignCall2(int phi) {
testNonDeopting(phi);
testNonDeopting(phi);
}
@MethodSubstitution
static void validConsecutiveForeignCallReexecutable(int phi) {
testPureReexectuable(phi);
testPureReexectuable(phi);
}
@MethodSubstitution
static void splitForeignCallInvalid(int phi) {
if (SideEffect == 0) {
testDeopt(phi);
} else {
CurrentJavaThreadNode.get().writeByte(0, (byte) 0);
testDeopt(phi);
}
}
}
@Override
protected void registerInvocationPlugins(InvocationPlugins invocationPlugins) {
invocationPlugins.register(new InvocationPlugin() {
@Override
public boolean apply(GraphBuilderContext b, ResolvedJavaMethod targetMethod, Receiver receiver, ValueNode arg) {
ForeignCallNode node = new ForeignCallNode(TEST_CALL_DEOPT, arg);
node.setBci(b.bci());
b.addPush(JavaKind.Int, node);
return true;
}
}, MethodSubstitutionForeignCallTest.class, "testDeopt", int.class);
invocationPlugins.register(new InvocationPlugin() {
@Override
public boolean apply(GraphBuilderContext b, ResolvedJavaMethod targetMethod, Receiver receiver, ValueNode arg) {
ForeignCallNode node = new ForeignCallNode(TEST_CALL_NON_DEOPT, arg);
node.setBci(b.bci());
b.addPush(JavaKind.Int, node);
return true;
}
}, MethodSubstitutionForeignCallTest.class, "testNonDeopting", int.class);
invocationPlugins.register(new InvocationPlugin() {
@Override
public boolean apply(GraphBuilderContext b, ResolvedJavaMethod targetMethod, Receiver receiver, ValueNode arg) {
ForeignCallNode node = new ForeignCallNode(TEST_CALL_REEXECUTABLE, arg);
node.setBci(b.bci());
b.addPush(JavaKind.Int, node);
return true;
}
}, MethodSubstitutionForeignCallTest.class, "testPureReexectuable", int.class);
ClassfileBytecodeProvider bytecodeProvider = getSystemClassLoaderBytecodeProvider();
Registration r = new Registration(invocationPlugins, A.class, getReplacements(), bytecodeProvider);
r.registerMethodSubstitution(ASubstitutions.class, "invalidConsecutiveForeignCall1", int.class);
r.registerMethodSubstitution(ASubstitutions.class, "invalidConsecutiveForeignCall2", int.class);
r.registerMethodSubstitution(ASubstitutions.class, "validConsecutiveForeignCallReexecutable", int.class);
r.registerMethodSubstitution(ASubstitutions.class, "splitForeignCallInvalid", int.class);
super.registerInvocationPlugins(invocationPlugins);
}
private ClassfileBytecodeProvider getSystemClassLoaderBytecodeProvider() {
ReplacementsImpl d = (ReplacementsImpl) getReplacements();
MetaAccessProvider metaAccess = d.getProviders().getMetaAccess();
ClassfileBytecodeProvider bytecodeProvider = new ClassfileBytecodeProvider(metaAccess, d.snippetReflection, ClassLoader.getSystemClassLoader());
return bytecodeProvider;
}
public static int SideEffect;
public static int testDeopt(int value) {
SideEffect = value;
return value;
}
public static int testNonDeopting(int value) {
return value;
}
public static int testPureReexectuable(int value) {
return value;
}
public static void testSnippetInvalidSequential() {
A.invalidConsecutiveForeignCall1(SideEffect);
if (SideEffect == 1) {
GraalDirectives.deoptimize();
}
}
public static void testNonDeoptingInvalid() {
A.invalidConsecutiveForeignCall2(SideEffect);
if (SideEffect == 1) {
GraalDirectives.deoptimize();
}
}
public static void testNonDeoptingSplit() {
A.splitForeignCallInvalid(SideEffect);
if (SideEffect == 1) {
GraalDirectives.deoptimize();
}
}
public static void testNonDeoptingReexectuable() {
A.validConsecutiveForeignCallReexecutable(SideEffect);
if (SideEffect == 1) {
GraalDirectives.deoptimize();
}
}
@Test
@SuppressWarnings("try")
public void test1() {
try (AutoCloseable c = new TTY.Filter()) {
OptionValues options = new OptionValues(getInitialOptions(), DumpOnError, false);
StructuredGraph g = parseEager(getResolvedJavaMethod("testSnippetInvalidSequential"), AllowAssumptions.NO, options);
Suites s = Graal.getRequiredCapability(RuntimeProvider.class).getHostBackend().getSuites().getDefaultSuites(getInitialOptions());
s.getHighTier().apply(g, getDefaultHighTierContext());
s.getMidTier().apply(g, getDefaultMidTierContext());
Assert.fail("Compilation should not reach this point, must throw an exception before");
} catch (Throwable t) {
if ((t.getCause() instanceof GraalError || t instanceof GraalError) && t.getMessage().contains("invalid framestate")) {
return;
}
throw new AssertionError(t);
}
}
@Test
@SuppressWarnings("try")
public void test2() {
try (AutoCloseable c = new TTY.Filter()) {
OptionValues options = new OptionValues(getInitialOptions(), DumpOnError, false);
StructuredGraph g = parseEager(getResolvedJavaMethod("testSnippetInvalidSequential"), AllowAssumptions.NO, options);
Suites s = Graal.getRequiredCapability(RuntimeProvider.class).getHostBackend().getSuites().getDefaultSuites(getInitialOptions());
s.getHighTier().apply(g, getDefaultHighTierContext());
s.getMidTier().apply(g, getDefaultMidTierContext());
Assert.fail("Compilation should not reach this point, must throw an exception before");
} catch (Throwable t) {
if ((t.getCause() instanceof GraalError || t instanceof GraalError) && t.getMessage().contains("invalid framestate")) {
return;
}
throw new AssertionError(t);
}
}
@Test
@SuppressWarnings("try")
public void test3() {
try (AutoCloseable c = new TTY.Filter()) {
OptionValues options = new OptionValues(getInitialOptions(), DumpOnError, false);
StructuredGraph g = parseEager(getResolvedJavaMethod("testNonDeoptingSplit"), AllowAssumptions.NO, options);
Suites s = Graal.getRequiredCapability(RuntimeProvider.class).getHostBackend().getSuites().getDefaultSuites(getInitialOptions());
s.getHighTier().apply(g, getDefaultHighTierContext());
s.getMidTier().apply(g, getDefaultMidTierContext());
Assert.fail("Compilation should not reach this point, must throw an exception before");
} catch (Throwable t) {
if ((t.getCause() instanceof GraalError || t instanceof GraalError) && t.getMessage().contains("invalid framestate")) {
return;
}
throw new AssertionError(t);
}
}
@Test
public void test4() {
StructuredGraph g = parseEager(getResolvedJavaMethod("testNonDeoptingReexectuable"), AllowAssumptions.NO);
Suites s = Graal.getRequiredCapability(RuntimeProvider.class).getHostBackend().getSuites().getDefaultSuites(getInitialOptions());
s.getHighTier().apply(g, getDefaultHighTierContext());
s.getMidTier().apply(g, getDefaultMidTierContext());
}
}