/* * Copyright (c) 2014, 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. */ package org.graalvm.compiler.replacements.test; import static org.graalvm.compiler.java.BytecodeParserOptions.InlinePartialIntrinsicExitDuringParsing; import java.util.function.Function; 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.bytecode.BytecodeProvider; import org.graalvm.compiler.debug.Debug; import org.graalvm.compiler.debug.DebugConfigScope; import org.graalvm.compiler.graph.GraalGraphError; import org.graalvm.compiler.graph.Node.ConstantNodeParameter; import org.graalvm.compiler.graph.Node.NodeIntrinsic; import org.graalvm.compiler.nodes.PiNode; import org.graalvm.compiler.nodes.StructuredGraph; import org.graalvm.compiler.nodes.ValueNode; import org.graalvm.compiler.nodes.graphbuilderconf.GraphBuilderContext; import org.graalvm.compiler.nodes.graphbuilderconf.InlineInvokePlugin; import org.graalvm.compiler.nodes.graphbuilderconf.InvocationPlugin.Receiver; import org.graalvm.compiler.nodes.graphbuilderconf.InvocationPlugins; import org.graalvm.compiler.nodes.graphbuilderconf.InvocationPlugins.Registration; import org.graalvm.compiler.options.OptionValues; import org.junit.Assert; import org.junit.BeforeClass; import org.junit.Test; import jdk.vm.ci.code.InstalledCode; import jdk.vm.ci.meta.ResolvedJavaMethod; /** * Tests for expected behavior when parsing snippets and intrinsics. */ public class ReplacementsParseTest extends ReplacementsTest { private static final String IN_COMPILED_HANDLER_MARKER = "*** in compiled handler ***"; private InlineInvokePlugin.InlineInfo inlineInvokeDecision; @SuppressWarnings("serial") static class CustomError extends Error { CustomError(String message) { super(message); } } static final Object THROW_EXCEPTION_MARKER = new Object() { @Override public String toString() { return "THROW_EXCEPTION_MARKER"; } }; static int copyFirstBody(byte[] left, byte[] right, boolean left2right) { if (left2right) { byte e = left[0]; right[0] = e; return e; } else { byte e = right[0]; left[0] = e; return e; } } static int copyFirstL2RBody(byte[] left, byte[] right) { byte e = left[0]; right[0] = e; return e; } static class TestObject { static double next(double v) { return Math.nextAfter(v, 1.0); } static double next2(double v) { return Math.nextAfter(v, 1.0); } static double nextAfter(double x, double d) { return Math.nextAfter(x, d); } TestObject() { this(null); } TestObject(Object id) { this.id = id; } final Object id; String stringizeId() { Object res = id; if (res == THROW_EXCEPTION_MARKER) { // Tests exception throwing from partial intrinsification throw new CustomError("ex"); } return String.valueOf(res); } static String stringize(Object obj) { Object res = obj; if (res == THROW_EXCEPTION_MARKER) { // Tests exception throwing from partial intrinsification throw new CustomError("ex"); } return String.valueOf(res); } static String identity(String s) { return s; } /** * @see TestObjectSubstitutions#copyFirst(byte[], byte[], boolean) */ static int copyFirst(byte[] left, byte[] right, boolean left2right) { return copyFirstBody(left, right, left2right); } /** * @see TestObjectSubstitutions#copyFirstL2R(byte[], byte[]) */ static int copyFirstL2R(byte[] left, byte[] right) { return copyFirstL2RBody(left, right); } } @ClassSubstitution(TestObject.class) static class TestObjectSubstitutions { @MethodSubstitution(isStatic = true) static double nextAfter(double x, double d) { double xx = (x == -0.0 ? 0.0 : x); return Math.nextAfter(xx, d); } /** * Tests conditional intrinsification of a static method. */ @MethodSubstitution static String stringize(Object obj) { if (obj != null && obj.getClass() == String.class) { return asNonNullString(obj); } else { // A recursive call denotes exiting/deoptimizing // out of the partial intrinsification to the // slow/uncommon case. return stringize(obj); } } /** * Tests conditional intrinsification of a non-static method. */ @MethodSubstitution(isStatic = false) static String stringizeId(TestObject thisObj) { if (thisObj.id != null && thisObj.id.getClass() == String.class) { return asNonNullString(thisObj.id); } else { // A recursive call denotes exiting/deoptimizing // out of the partial intrinsification to the // slow/uncommon case. return outOfLinePartialIntrinsification(thisObj); } } static String outOfLinePartialIntrinsification(TestObject thisObj) { return stringizeId(thisObj); } public static String asNonNullString(Object object) { return asNonNullStringIntrinsic(object, String.class, true, true); } @NodeIntrinsic(PiNode.class) private static native String asNonNullStringIntrinsic(Object object, @ConstantNodeParameter Class<?> toType, @ConstantNodeParameter boolean exactType, @ConstantNodeParameter boolean nonNull); /** * An valid intrinsic as the frame state associated with the merge should prevent the frame * states associated with the array stores from being associated with subsequent * deoptimizing nodes. */ @MethodSubstitution static int copyFirst(byte[] left, byte[] right, boolean left2right) { return copyFirstBody(left, right, left2right); } /** * An invalid intrinsic as the frame state associated with the array assignment can leak out * to subsequent deoptimizing nodes. */ @MethodSubstitution static int copyFirstL2R(byte[] left, byte[] right) { return copyFirstL2RBody(left, right); } /** * Tests that non-capturing lambdas are folded away. */ @MethodSubstitution static String identity(String value) { return apply(s -> s, value); } private static String apply(Function<String, String> f, String value) { return f.apply(value); } } @Override protected void registerInvocationPlugins(InvocationPlugins invocationPlugins) { BytecodeProvider replacementBytecodeProvider = getSystemClassLoaderBytecodeProvider(); Registration r = new Registration(invocationPlugins, TestObject.class, replacementBytecodeProvider); new PluginFactory_ReplacementsParseTest().registerPlugins(invocationPlugins, null); r.registerMethodSubstitution(TestObjectSubstitutions.class, "nextAfter", double.class, double.class); r.registerMethodSubstitution(TestObjectSubstitutions.class, "stringize", Object.class); r.registerMethodSubstitution(TestObjectSubstitutions.class, "stringizeId", Receiver.class); r.registerMethodSubstitution(TestObjectSubstitutions.class, "copyFirst", byte[].class, byte[].class, boolean.class); r.registerMethodSubstitution(TestObjectSubstitutions.class, "copyFirstL2R", byte[].class, byte[].class); if (replacementBytecodeProvider.supportsInvokedynamic()) { r.registerMethodSubstitution(TestObjectSubstitutions.class, "identity", String.class); } super.registerInvocationPlugins(invocationPlugins); } @BeforeClass public static void warmupProfiles() { for (int i = 0; i < 40000; i++) { callCopyFirst(new byte[16], new byte[16], true); callCopyFirstL2R(new byte[16], new byte[16]); } } /** * Ensure that calling the original method from the substitution binds correctly. */ @Test public void test1() { test("test1Snippet", 1.0); } public double test1Snippet(double d) { return TestObject.next(d); } /** * Ensure that calling the substitution method binds to the original method properly. */ @Test public void test2() { test("test2Snippet", 1.0); } public double test2Snippet(double d) { return TestObject.next2(d); } /** * Ensure that substitution methods with assertions in them don't complain when the exception * constructor is deleted. */ @Test public void testNextAfter() { double[] inArray = new double[1024]; double[] outArray = new double[1024]; for (int i = 0; i < inArray.length; i++) { inArray[i] = -0.0; } test("doNextAfter", inArray, outArray); } public void doNextAfter(double[] outArray, double[] inArray) { for (int i = 0; i < inArray.length; i++) { double direction = (i & 1) == 0 ? Double.POSITIVE_INFINITY : -Double.NEGATIVE_INFINITY; outArray[i] = TestObject.nextAfter(inArray[i], direction); } } private void testWithDifferentReturnValues(OptionValues options, String standardReturnValue, String compiledReturnValue, String name, Object... args) { ResolvedJavaMethod method = getResolvedJavaMethod(name); Object receiver = null; Result expect = executeExpected(method, receiver, args); Assert.assertEquals(standardReturnValue, expect.returnValue); expect = new Result(compiledReturnValue, null); testAgainstExpected(options, method, expect, receiver, args); } @Override protected InstalledCode getCode(final ResolvedJavaMethod installedCodeOwner, StructuredGraph graph, boolean forceCompile, boolean installAsDefault, OptionValues options) { return super.getCode(installedCodeOwner, graph, forceCompileOverride, installAsDefault, options); } boolean forceCompileOverride; @Test public void testCallStringize() { test("callStringize", "a string"); test("callStringize", Boolean.TRUE); // Unset 'exception seen' bit if testCallStringizeWithoutInlinePartialIntrinsicExit // is executed before this test getResolvedJavaMethod("callStringize").reprofile(); forceCompileOverride = true; String standardReturnValue = new CustomError("ex").toString(); String compiledReturnValue = IN_COMPILED_HANDLER_MARKER; testWithDifferentReturnValues(getInitialOptions(), standardReturnValue, compiledReturnValue, "callStringize", THROW_EXCEPTION_MARKER); } @Test public void testCallStringizeWithoutInlinePartialIntrinsicExit() { OptionValues options = new OptionValues(getInitialOptions(), InlinePartialIntrinsicExitDuringParsing, false); test(options, "callStringize", "a string"); test(options, "callStringize", Boolean.TRUE); String standardReturnValue = new CustomError("ex").toString(); String compiledReturnValue = IN_COMPILED_HANDLER_MARKER; for (int i = 0; i < 1000; i++) { // Ensures 'exception seen' bit is set for call to stringize callStringize(THROW_EXCEPTION_MARKER); } forceCompileOverride = true; testWithDifferentReturnValues(options, standardReturnValue, compiledReturnValue, "callStringize", THROW_EXCEPTION_MARKER); } @Test public void testCallStringizeId() { test("callStringizeId", new TestObject("a string")); test("callStringizeId", new TestObject(Boolean.TRUE)); // Unset 'exception seen' bit if testCallStringizeIdWithoutInlinePartialIntrinsicExit // is executed before this test getResolvedJavaMethod("callStringize").reprofile(); forceCompileOverride = true; String standardReturnValue = new CustomError("ex").toString(); String compiledReturnValue = IN_COMPILED_HANDLER_MARKER; testWithDifferentReturnValues(getInitialOptions(), standardReturnValue, compiledReturnValue, "callStringizeId", new TestObject(THROW_EXCEPTION_MARKER)); } @Test public void testCallStringizeIdWithoutInlinePartialIntrinsicExit() { OptionValues options = new OptionValues(getInitialOptions(), InlinePartialIntrinsicExitDuringParsing, false); test(options, "callStringizeId", new TestObject("a string")); test(options, "callStringizeId", new TestObject(Boolean.TRUE)); TestObject exceptionTestObject = new TestObject(THROW_EXCEPTION_MARKER); for (int i = 0; i < 1000; i++) { // Ensures 'exception seen' bit is set for call to stringizeId callStringizeId(exceptionTestObject); } String standardReturnValue = new CustomError("ex").toString(); String compiledReturnValue = IN_COMPILED_HANDLER_MARKER; forceCompileOverride = true; testWithDifferentReturnValues(options, standardReturnValue, compiledReturnValue, "callStringizeId", exceptionTestObject); } public static Object callStringize(Object obj) { try { return TestObject.stringize(obj); } catch (CustomError e) { if (GraalDirectives.inCompiledCode()) { return IN_COMPILED_HANDLER_MARKER; } return e.toString(); } } public static Object callStringizeId(TestObject testObj) { try { return testObj.stringizeId(); } catch (CustomError e) { if (GraalDirectives.inCompiledCode()) { return IN_COMPILED_HANDLER_MARKER; } return e.toString(); } } @Test public void testRootCompileStringize() { ResolvedJavaMethod method = getResolvedJavaMethod(TestObject.class, "stringize"); test(method, null, "a string"); test(method, null, Boolean.TRUE); test(method, null, THROW_EXCEPTION_MARKER); } @Test public void testLambda() { test("callLambda", (String) null); test("callLambda", "a string"); } public static String callLambda(String value) { return TestObject.identity(value); } public static int callCopyFirst(byte[] in, byte[] out, boolean left2right) { int res = TestObject.copyFirst(in, out, left2right); if (res == 17) { // A node after the intrinsic that needs a frame state. GraalDirectives.deoptimize(); } return res; } public static int callCopyFirstWrapper(byte[] in, byte[] out, boolean left2right) { return callCopyFirst(in, out, left2right); } public static int callCopyFirstL2R(byte[] in, byte[] out) { int res = TestObject.copyFirstL2R(in, out); if (res == 17) { // A node after the intrinsic that needs a frame state. GraalDirectives.deoptimize(); } return res; } @Test public void testCallCopyFirst() { byte[] in = {0, 1, 2, 3, 4}; byte[] out = new byte[in.length]; test("callCopyFirst", in, out, true); test("callCopyFirst", in, out, false); } @SuppressWarnings("try") @Test public void testCallCopyFirstL2R() { byte[] in = {0, 1, 2, 3, 4}; byte[] out = new byte[in.length]; try { try (DebugConfigScope s = Debug.setConfig(Debug.silentConfig())) { test("callCopyFirstL2R", in, out); } } catch (GraalGraphError e) { assertTrue(e.getMessage().startsWith("Invalid frame state")); } } @Override protected InlineInvokePlugin.InlineInfo bytecodeParserShouldInlineInvoke(GraphBuilderContext b, ResolvedJavaMethod method, ValueNode[] args) { return inlineInvokeDecision; } @Test public void testCallCopyFirstWithoutInlinePartialIntrinsicExit() { OptionValues options = new OptionValues(getInitialOptions(), InlinePartialIntrinsicExitDuringParsing, false); inlineInvokeDecision = InlineInvokePlugin.InlineInfo.DO_NOT_INLINE_WITH_EXCEPTION; try { byte[] in = {0, 1, 2, 3, 4}; byte[] out = new byte[in.length]; test(options, "callCopyFirstWrapper", in, out, true); test(options, "callCopyFirstWrapper", in, out, false); } finally { inlineInvokeDecision = null; } } }