# HG changeset patch # User Christian Humer # Date 1423653224 -3600 # Node ID bf166845c7d8db9f1a6b216d72f92da43e2ebc18 # Parent 62c43fcf5be2b46cbe19689c63a4acf9a6bc1bc0 Truffle-DSL: new test examples package to show and explain Truffle-DSL features. diff -r 62c43fcf5be2 -r bf166845c7d8 graal/com.oracle.truffle.api.dsl.test/src/com/oracle/truffle/api/dsl/examples/ExampleNode.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/graal/com.oracle.truffle.api.dsl.test/src/com/oracle/truffle/api/dsl/examples/ExampleNode.java Wed Feb 11 12:13:44 2015 +0100 @@ -0,0 +1,123 @@ +/* + * Copyright (c) 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 com.oracle.truffle.api.dsl.examples; + +import com.oracle.truffle.api.*; +import com.oracle.truffle.api.dsl.*; +import com.oracle.truffle.api.dsl.internal.*; +import com.oracle.truffle.api.frame.*; +import com.oracle.truffle.api.nodes.*; + +@TypeSystemReference(ExampleTypes.class) +@NodeChild(value = "args", type = ExampleNode[].class) +public abstract class ExampleNode extends Node { + + public Object execute(@SuppressWarnings("unused") VirtualFrame frame) { + // will get implemented by the DSL. + throw new UnsupportedOperationException(); + } + + @Override + public String toString() { + if (this instanceof SpecializedNode) { + return ((SpecializedNode) this).getSpecializationNode().toString(); + } else { + return super.toString(); + } + } + + public static CallTarget createTarget(ExampleNode node) { + return Truffle.getRuntime().createCallTarget(new ExampleRootNode(node)); + } + + @SuppressWarnings("unchecked") + public static T getNode(CallTarget target) { + return (T) ((ExampleRootNode) ((RootCallTarget) target).getRootNode()).child; + } + + public static ExampleNode[] createArguments(int count) { + ExampleNode[] nodes = new ExampleNode[count]; + for (int i = 0; i < count; i++) { + nodes[i] = new ExampleArgumentNode(i); + } + return nodes; + } + + private static class ExampleRootNode extends RootNode { + + @Child ExampleNode child; + + public ExampleRootNode(ExampleNode child) { + this.child = child; + } + + @Override + public Object execute(VirtualFrame frame) { + return child.execute(frame); + } + + } + + private static class ExampleArgumentNode extends ExampleNode { + + private final int index; + + public ExampleArgumentNode(int index) { + this.index = index; + } + + @Override + public Object execute(VirtualFrame frame) { + Object[] arguments = frame.getArguments(); + if (index < arguments.length) { + return arguments[index]; + } + return null; + } + } + + public static CallTarget createDummyTarget(int argumentIndex) { + return Truffle.getRuntime().createCallTarget(new DummyCallRootNode(argumentIndex)); + } + + private static class DummyCallRootNode extends RootNode { + + private final int argumentIndex; + + public DummyCallRootNode(int argumentIndex) { + this.argumentIndex = argumentIndex; + } + + @Override + public Object execute(VirtualFrame frame) { + return frame.getArguments()[argumentIndex]; + } + + @Override + public String toString() { + return "DummyRootNode[arg = " + argumentIndex + "]"; + } + + } + +} diff -r 62c43fcf5be2 -r bf166845c7d8 graal/com.oracle.truffle.api.dsl.test/src/com/oracle/truffle/api/dsl/examples/ExampleTypes.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/graal/com.oracle.truffle.api.dsl.test/src/com/oracle/truffle/api/dsl/examples/ExampleTypes.java Wed Feb 11 12:13:44 2015 +0100 @@ -0,0 +1,40 @@ +/* + * Copyright (c) 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 com.oracle.truffle.api.dsl.examples; + +import com.oracle.truffle.api.dsl.*; +import com.oracle.truffle.api.dsl.examples.FunctionCall.Function; +import com.oracle.truffle.api.dsl.examples.Interop.TruffleObject; +import com.oracle.truffle.api.dsl.examples.RubyCall.InternalMethod; +import com.oracle.truffle.api.dsl.examples.RubyCall.RubyObject; +import com.oracle.truffle.api.dsl.examples.StableDispatch.SLFunction; + +@TypeSystem({int.class, double.class, boolean.class, TruffleObject.class, SLFunction.class, RubyObject.class, Function.class, InternalMethod.class, int[].class, double[].class, Object[].class}) +public class ExampleTypes { + + @ImplicitCast + public static double castInt(int intValue) { + return intValue; + } + +} diff -r 62c43fcf5be2 -r bf166845c7d8 graal/com.oracle.truffle.api.dsl.test/src/com/oracle/truffle/api/dsl/examples/FunctionCall.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/graal/com.oracle.truffle.api.dsl.test/src/com/oracle/truffle/api/dsl/examples/FunctionCall.java Wed Feb 11 12:13:44 2015 +0100 @@ -0,0 +1,150 @@ +/* + * Copyright (c) 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 com.oracle.truffle.api.dsl.examples; + +import static com.oracle.truffle.api.dsl.examples.ExampleNode.*; +import static org.junit.Assert.*; + +import org.junit.*; + +import com.oracle.truffle.api.*; +import com.oracle.truffle.api.dsl.*; +import com.oracle.truffle.api.dsl.examples.FunctionCallFactory.FunctionCallNodeGen; +import com.oracle.truffle.api.frame.*; +import com.oracle.truffle.api.nodes.*; + +/** + * This example illustrates how {@link Cached} can be used to implement function calls that use + * local state for its guards. If there are always distinct Function objects with distinct + * CallTargets then we can use the directCallFunctionGuard specialization. If there are two Function + * instances cached with the same CallTarget then we use the directCall cache. We do this because + * the directCallFunctionGuard specialization can use a faster guard. + */ +@SuppressWarnings("unused") +public class FunctionCall { + + @Test + public void testFunctionCall() { + assertEquals(2, FunctionCallNode.CACHE_SIZE); + + CallTarget dummyTarget1 = createDummyTarget(0); + CallTarget dummyTarget2 = createDummyTarget(0); + CallTarget dummyTarget3 = createDummyTarget(0); + + Function dummyFunction1 = new Function(dummyTarget1); + Function dummyFunction2 = new Function(dummyTarget2); + Function dummyFunction3 = new Function(dummyTarget2); // same target as dummyFunction2 + Function dummyFunction4 = new Function(dummyTarget3); + + FunctionCallNode node = FunctionCallNodeGen.create(createArguments(2)); + CallTarget target = createTarget(node); + assertEquals(42, target.call(dummyFunction1, 42)); + assertEquals(43, target.call(dummyFunction2, 43)); + assertEquals(44, target.call(dummyFunction3, 44)); // transition to directCall + assertEquals(2, node.directCallFunctionGuard); + assertEquals(1, node.directCall); + + assertEquals(42, target.call(dummyFunction1, 42)); + assertEquals(43, target.call(dummyFunction2, 43)); + assertEquals(2, node.directCallFunctionGuard); + assertEquals(3, node.directCall); + + assertEquals(44, target.call(dummyFunction4, 44)); // transition to indirectCall + assertEquals(2, node.directCallFunctionGuard); + assertEquals(3, node.directCall); + assertEquals(1, node.indirectCall); + + assertEquals(42, target.call(dummyFunction1, 42)); + assertEquals(43, target.call(dummyFunction2, 43)); + assertEquals(44, target.call(dummyFunction3, 44)); + assertEquals(2, node.directCallFunctionGuard); + assertEquals(3, node.directCall); + assertEquals(4, node.indirectCall); + } + + public static class FunctionCallNode extends ExampleNode { + + public static final int CACHE_SIZE = 2; + + private CallTarget[] cachedTargets = new CallTarget[CACHE_SIZE]; + + private int directCallFunctionGuard; + private int directCall; + private int indirectCall; + + @Specialization(limit = "CACHE_SIZE", guards = {"function == cachedFunction", "!cacheFunctionTarget(cachedFunction)"}) + public Object directCallFunctionGuard(VirtualFrame frame, Function function, Object argument, // + @Cached("function") Function cachedFunction, // + @Cached("create(cachedFunction.getTarget())") DirectCallNode callNode) { + directCallFunctionGuard++; + return callNode.call(frame, new Object[]{argument}); + } + + protected final boolean cacheFunctionTarget(Function function) { + CompilerAsserts.neverPartOfCompilation(); + if (cachedTargets != null) { + CallTarget target = function.getTarget(); + for (int i = 0; i < cachedTargets.length; i++) { + CallTarget cachedTarget = cachedTargets[i]; + if (cachedTarget == target) { + cachedTargets = null; + return true; + } else if (cachedTarget == null) { + cachedTargets[i] = target; + return false; + } + } + } + return false; + } + + @Specialization(limit = "CACHE_SIZE", contains = "directCallFunctionGuard", guards = {"function.getTarget() == cachedTarget"}) + protected Object directCall(VirtualFrame frame, Function function, Object argument, // + @Cached("function.getTarget()") CallTarget cachedTarget, // + @Cached("create(cachedTarget)") DirectCallNode callNode) { + directCall++; + return callNode.call(frame, new Object[]{argument}); + } + + @Specialization(contains = "directCall") + protected Object indirectCall(VirtualFrame frame, Function function, Object argument, // + @Cached("create()") IndirectCallNode callNode) { + indirectCall++; + return callNode.call(frame, function.getTarget(), new Object[]{argument}); + } + } + + public static class Function { + + private final CallTarget target; + + public Function(CallTarget target) { + this.target = target; + } + + public CallTarget getTarget() { + return target; + } + } + +} diff -r 62c43fcf5be2 -r bf166845c7d8 graal/com.oracle.truffle.api.dsl.test/src/com/oracle/truffle/api/dsl/examples/Interop.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/graal/com.oracle.truffle.api.dsl.test/src/com/oracle/truffle/api/dsl/examples/Interop.java Wed Feb 11 12:13:44 2015 +0100 @@ -0,0 +1,109 @@ +/* + * Copyright (c) 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 com.oracle.truffle.api.dsl.examples; + +import static com.oracle.truffle.api.dsl.examples.ExampleNode.*; +import static org.junit.Assert.*; + +import org.junit.*; + +import com.oracle.truffle.api.*; +import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; +import com.oracle.truffle.api.dsl.*; +import com.oracle.truffle.api.dsl.examples.InteropFactory.UseInteropNodeGen; +import com.oracle.truffle.api.frame.*; +import com.oracle.truffle.api.nodes.*; + +/** + * This example aims to illustrate how the {@link Cached} annotation can be used to implement a + * cache for a simplified language interoperability pattern. + */ +public class Interop { + + @Test + public void testInterop() { + UseInterop node = UseInteropNodeGen.create(createArguments(2)); + CallTarget target = createTarget(node); + TruffleObject o1 = new TruffleObject(); + TruffleObject o2 = new TruffleObject(); + TruffleObject o3 = new TruffleObject(); + TruffleObject o4 = new TruffleObject(); + assertEquals(42, target.call(o1, 42)); + assertEquals(43, target.call(o2, 43)); + assertEquals(44, target.call(o3, 44)); + assertEquals(3, node.cached); + assertEquals(0, node.generic); + assertEquals(45, target.call(o4, 45)); // operation gets generic + assertEquals(42, target.call(o1, 42)); + assertEquals(43, target.call(o2, 43)); + assertEquals(44, target.call(o3, 44)); + assertEquals(3, node.cached); + assertEquals(4, node.generic); + } + + public static class UseInterop extends ExampleNode { + + int cached = 0; + int generic = 0; + + @Specialization(guards = "operation.accept(target)") + protected Object interopCached(VirtualFrame frame, TruffleObject target, Object value, // + @Cached("target.createOperation()") TruffleObjectOperation operation) { + cached++; + return operation.execute(frame, target, value); + } + + @Specialization(contains = "interopCached") + protected Object interopGeneric(VirtualFrame frame, TruffleObject target, Object value) { + generic++; + return target.createOperation().execute(frame, target, value); + } + } + + public abstract static class TruffleObjectOperation extends Node { + + public abstract boolean accept(TruffleObject object); + + public abstract Object execute(VirtualFrame frame, Object target, Object value); + + } + + public static class TruffleObject { + + @TruffleBoundary + public TruffleObjectOperation createOperation() { + return new TruffleObjectOperation() { + @Override + public Object execute(VirtualFrame frame, Object target, Object value) { + return value; + } + + @Override + public boolean accept(TruffleObject object) { + return TruffleObject.this == object; + } + }; + } + } + +} diff -r 62c43fcf5be2 -r bf166845c7d8 graal/com.oracle.truffle.api.dsl.test/src/com/oracle/truffle/api/dsl/examples/MathPow.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/graal/com.oracle.truffle.api.dsl.test/src/com/oracle/truffle/api/dsl/examples/MathPow.java Wed Feb 11 12:13:44 2015 +0100 @@ -0,0 +1,144 @@ +/* + * Copyright (c) 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 com.oracle.truffle.api.dsl.examples; + +import static com.oracle.truffle.api.dsl.examples.ExampleNode.*; +import static org.junit.Assert.*; + +import org.junit.*; + +import com.oracle.truffle.api.*; +import com.oracle.truffle.api.dsl.*; +import com.oracle.truffle.api.dsl.examples.MathPowFactory.MathPowNodeGen; +import com.oracle.truffle.api.nodes.*; + +/** + * This example shows possible specializations for a simplified math pow node. It demonstrates how + * multiple caches can coexist within in the same node. This example does not show the best possible + * specializations for math.pow. + * + * Note: int values are implicitly casted to double values. + */ +@SuppressWarnings("unused") +public class MathPow extends Node { + + @Test + public void testPow() { + MathPowNode node = MathPowNodeGen.create(createArguments(2)); + CallTarget target = createTarget(node); + + // start with doPowCached + assertEquals(1D, target.call(1D, 1)); + assertEquals(2D, target.call(2D, 1)); + assertEquals(3D, target.call(3D, 1)); + assertEquals(3, node.doPowCached); + assertEquals(0, node.doPowCachedExponent); + + // transition to doPowCachedExponent + assertEquals(4D, target.call(4D, 1)); + assertEquals(5D, target.call(5D, 1)); + assertEquals(6D, target.call(6D, 1)); + assertEquals(16D, target.call(4D, 2)); + assertEquals(125D, target.call(5D, 3)); + assertEquals(5, node.doPowCachedExponent); + assertEquals(0, node.doPowDoubleInt); + + // transition to doPowDoubleInt + assertEquals(4D * 4D * 4D * 4D, target.call(4D, 4)); + assertEquals(5D * 5D * 5D * 5D * 5D, target.call(5D, 5)); + assertEquals(5, node.doPowCachedExponent); + assertEquals(2, node.doPowDoubleInt); + + // transition to doPow + assertEquals(5D, target.call(5D, 1D)); + assertEquals(2D, target.call(2D, 1D)); + + assertEquals(3, node.doPowCached); + assertEquals(5, node.doPowCachedExponent); + assertEquals(2, node.doPowDoubleInt); + assertEquals(2, node.doPow); + } + + public static class MathPowNode extends ExampleNode { + + // test flags + int doPowCached; + int doPowCachedExponent; + int doPowDoubleInt; + int doPow; + + @Specialization(guards = {"base == cachedBase", "exponent == cachedExponent"}) + double doPowCached(double base, int exponent, // + @Cached("base") double cachedBase, // + @Cached("exponent") int cachedExponent, // + @Cached("cachePow(cachedBase, cachedExponent)") double cachedResult) { + doPowCached++; + return cachedResult; + } + + /* + * We could just use the doPow specialization instead. But this makes the number of doPow + * calls more difficult to assert. + */ + protected static double cachePow(double base, int exponent) { + return Math.pow(base, exponent); + } + + @Specialization(contains = "doPowCached", guards = {"exponent == cachedExponent", "cachedExponent <= 10"}) + @ExplodeLoop + double doPowCachedExponent(double base, int exponent, @Cached("exponent") int cachedExponent) { + doPowCachedExponent++; + double result = 1.0; + for (int i = 0; i < cachedExponent; i++) { + result *= base; + } + return result; + } + + @Specialization(contains = "doPowCachedExponent", guards = "exponent >= 0") + double doPowDoubleInt(double base, int exponent) { + doPowDoubleInt++; + // Uses binary decomposition to limit the number of + // multiplications; see the discussion in "Hacker's Delight" by Henry + // S. Warren, Jr., figure 11-6, page 213. + double b = base; + int e = exponent; + double result = 1; + while (e > 0) { + if ((e & 1) == 1) { + result *= b; + } + e >>= 1; + b *= b; + } + return result; + } + + @Specialization(contains = {"doPowCached", "doPowDoubleInt"}) + double doPow(double base, double exponent) { + doPow++; + return Math.pow(base, exponent); + } + } + +} diff -r 62c43fcf5be2 -r bf166845c7d8 graal/com.oracle.truffle.api.dsl.test/src/com/oracle/truffle/api/dsl/examples/RubyCall.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/graal/com.oracle.truffle.api.dsl.test/src/com/oracle/truffle/api/dsl/examples/RubyCall.java Wed Feb 11 12:13:44 2015 +0100 @@ -0,0 +1,302 @@ +/* + * Copyright (c) 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 com.oracle.truffle.api.dsl.examples; + +import static com.oracle.truffle.api.dsl.examples.ExampleNode.*; +import static org.junit.Assert.*; + +import java.util.*; + +import org.junit.*; + +import com.oracle.truffle.api.*; +import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary; +import com.oracle.truffle.api.dsl.*; +import com.oracle.truffle.api.dsl.examples.RubyCallFactory.RubyDispatchNodeGen; +import com.oracle.truffle.api.dsl.examples.RubyCallFactory.RubyHeadNodeGen; +import com.oracle.truffle.api.dsl.examples.RubyCallFactory.RubyLookupNodeGen; +import com.oracle.truffle.api.dsl.internal.*; +import com.oracle.truffle.api.frame.*; +import com.oracle.truffle.api.nodes.*; +import com.oracle.truffle.api.utilities.*; + +/** + * This example illustrates a simplified version of a Ruby function call semantics (RubyHeadNode). + * The example usage shows how methods can be redefined in this implementation. + */ +@SuppressWarnings("unused") +public class RubyCall { + + @Test + public void testCall() { + RubyHeadNode node = RubyHeadNodeGen.create(createArguments(4)); + CallTarget nodeTarget = createTarget(node); + final Object firstArgument = "someArgument"; + + // dummyMethod is just going to return the some argument of the function + final Object testMethodName = "getSomeArgument"; + // implementation returns first argument + InternalMethod aClassTestMethod = new InternalMethod(ExampleNode.createDummyTarget(3)); + // implementation returns second argument + InternalMethod bClassTestMethod = new InternalMethod(ExampleNode.createDummyTarget(4)); + // implementation returns third argument + InternalMethod cClassTestMethod = new InternalMethod(ExampleNode.createDummyTarget(5)); + + // defines hierarchy C extends B extends A + RubyClass aClass = new RubyClass("A", null); + RubyClass bClass = new RubyClass("B", aClass); + RubyClass cClass = new RubyClass("C", bClass); + + RubyObject aInstance = new RubyObject(aClass); + RubyObject bInstance = new RubyObject(bClass); + RubyObject cInstance = new RubyObject(cClass); + + // undefined method call + assertEquals(RubyObject.NIL, nodeTarget.call(cInstance, testMethodName, null, new Object[]{firstArgument})); + + // method defined in a + aClass.addMethod(testMethodName, aClassTestMethod); + assertEquals(firstArgument, nodeTarget.call(aInstance, testMethodName, null, new Object[]{firstArgument})); + assertEquals(firstArgument, nodeTarget.call(bInstance, testMethodName, null, new Object[]{firstArgument})); + assertEquals(firstArgument, nodeTarget.call(cInstance, testMethodName, null, new Object[]{firstArgument})); + + // method redefined in b + bClass.addMethod(testMethodName, bClassTestMethod); + assertEquals(firstArgument, nodeTarget.call(aInstance, testMethodName, null, new Object[]{firstArgument})); + assertEquals(firstArgument, nodeTarget.call(bInstance, testMethodName, null, new Object[]{null, firstArgument})); + assertEquals(firstArgument, nodeTarget.call(cInstance, testMethodName, null, new Object[]{null, firstArgument})); + + // method redefined in c + cClass.addMethod(testMethodName, cClassTestMethod); + assertEquals(firstArgument, nodeTarget.call(aInstance, testMethodName, null, new Object[]{firstArgument})); + assertEquals(firstArgument, nodeTarget.call(bInstance, testMethodName, null, new Object[]{null, firstArgument})); + assertEquals(firstArgument, nodeTarget.call(cInstance, testMethodName, null, new Object[]{null, null, firstArgument})); + + } + + public static class RubyHeadNode extends ExampleNode { + + @Child private RubyLookupNode lookup = RubyLookupNodeGen.create(null); + @Child private RubyDispatchNode dispatch = RubyDispatchNodeGen.create(null); + + @Specialization + public Object doCall(VirtualFrame frame, Object receiverObject, Object methodName, Object blockObject, Object... argumentsObjects) { + InternalMethod method = lookup.executeLookup(frame, receiverObject, methodName); + + Object[] packedArguments = new Object[argumentsObjects.length + 3]; + packedArguments[0] = method; + packedArguments[1] = receiverObject; + packedArguments[2] = blockObject; + System.arraycopy(argumentsObjects, 0, packedArguments, 3, argumentsObjects.length); + + return dispatch.executeDispatch(frame, method, packedArguments); + } + } + + public abstract static class RubyLookupNode extends ExampleNode { + + public abstract InternalMethod executeLookup(VirtualFrame frame, Object receiverObject, Object methodName); + + @Specialization(guards = "receiver.getRubyClass() == cachedClass", assumptions = "cachedClass.getDependentAssumptions()") + protected static InternalMethod cachedLookup(RubyObject receiver, Object name, // + @Cached("receiver.getRubyClass()") RubyClass cachedClass, // + @Cached("genericLookup(receiver, name)") InternalMethod cachedLookup) { + return cachedLookup; + } + + @Specialization(contains = "cachedLookup") + protected static InternalMethod genericLookup(RubyObject receiver, Object name) { + return receiver.getRubyClass().lookup(name); + } + + } + + @ImportStatic(InternalMethod.class) + public abstract static class RubyDispatchNode extends ExampleNode { + + public abstract Object executeDispatch(VirtualFrame frame, InternalMethod function, Object[] packedArguments); + + /* + * Please note that cachedMethod != METHOD_MISSING is invoked once at specialization + * instantiation. It is never executed on the fast path. + */ + @Specialization(guards = {"method == cachedMethod", "cachedMethod != METHOD_MISSING"}) + protected static Object directCall(VirtualFrame frame, InternalMethod method, Object[] arguments, // + @Cached("method") InternalMethod cachedMethod, // + @Cached("create(cachedMethod.getTarget())") DirectCallNode callNode) { + return callNode.call(frame, arguments); + } + + /* + * The method == METHOD_MISSING can fold if the RubyLookup results just in a single entry + * returning the constant METHOD_MISSING. + */ + @Specialization(guards = "method == METHOD_MISSING") + protected static Object methodMissing(VirtualFrame frame, InternalMethod method, Object[] arguments) { + // a real implementation would do a call to a method named method_missing here + return RubyObject.NIL; + } + + @Specialization(contains = "directCall", guards = "method != METHOD_MISSING") + protected static Object indirectCall(VirtualFrame frame, InternalMethod method, Object[] arguments, // + @Cached("create()") IndirectCallNode callNode) { + return callNode.call(frame, method.getTarget(), arguments); + } + + @Override + public String toString() { + return ((SpecializedNode) this).getSpecializationNode().toString(); + } + } + + public static final class RubyObject { + + public static final RubyObject NIL = new RubyObject(null); + + private final RubyClass rubyClass; + + public RubyObject(RubyClass rubyClass) { + this.rubyClass = rubyClass; + } + + public RubyClass getRubyClass() { + return rubyClass; + } + + @Override + public String toString() { + return "RubyObject[class=" + rubyClass + "]"; + } + + } + + public static final class RubyClass /* this would extend RubyModule */{ + + private final String name; + private final RubyClass parent; // this would be a RubyModule + private final CyclicAssumption unmodified; + private final Map methods = new HashMap<>(); + private Assumption[] cachedDependentAssumptions; + private final int depth; + + public RubyClass(String name, RubyClass parent) { + this.name = name; + this.parent = parent; + this.unmodified = new CyclicAssumption("unmodified class " + name); + + // lookup depth for array allocation + RubyClass clazz = parent; + int currentDepth = 1; + while (clazz != null) { + currentDepth++; + clazz = clazz.parent; + } + this.depth = currentDepth; + } + + @TruffleBoundary + public InternalMethod lookup(Object methodName) { + InternalMethod method = methods.get(methodName); + if (method == null) { + if (parent != null) { + return parent.lookup(methodName); + } else { + return InternalMethod.METHOD_MISSING; + } + } else { + return method; + } + } + + @TruffleBoundary + public void addMethod(Object methodName, InternalMethod method) { + // check for existing method omitted for simplicity + this.methods.put(methodName, method); + this.unmodified.invalidate(); + } + + /* + * Method collects all unmodified assumptions in the class hierarchy. The result is cached + * per class to void recreation per call site. + */ + @TruffleBoundary + public Assumption[] getDependentAssumptions() { + Assumption[] dependentAssumptions = cachedDependentAssumptions; + if (dependentAssumptions != null) { + // we can use the cached dependent assumptions only if they are still valid + for (Assumption assumption : cachedDependentAssumptions) { + if (!assumption.isValid()) { + dependentAssumptions = null; + break; + } + } + } + if (dependentAssumptions == null) { + cachedDependentAssumptions = dependentAssumptions = createDependentAssumptions(); + } + return dependentAssumptions; + } + + @Override + public String toString() { + return "RubyClass[name=" + name + "]"; + } + + private Assumption[] createDependentAssumptions() { + Assumption[] dependentAssumptions; + RubyClass clazz = this; + dependentAssumptions = new Assumption[depth]; + + // populate array + int index = 0; + do { + dependentAssumptions[index] = clazz.unmodified.getAssumption(); + index++; + clazz = clazz.parent; + } while (clazz != null); + return dependentAssumptions; + } + } + + public static final class InternalMethod { + + public static final InternalMethod METHOD_MISSING = new InternalMethod(null); + + private final CallTarget target; + + public InternalMethod(CallTarget target) { + this.target = target; + } + + public CallTarget getTarget() { + return target; + } + + @Override + public String toString() { + return "InternalMethod[target=" + getTarget() + "]"; + } + + } + +} diff -r 62c43fcf5be2 -r bf166845c7d8 graal/com.oracle.truffle.api.dsl.test/src/com/oracle/truffle/api/dsl/examples/StableDispatch.java --- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/graal/com.oracle.truffle.api.dsl.test/src/com/oracle/truffle/api/dsl/examples/StableDispatch.java Wed Feb 11 12:13:44 2015 +0100 @@ -0,0 +1,80 @@ +/* + * Copyright (c) 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 com.oracle.truffle.api.dsl.examples; + +import com.oracle.truffle.api.*; +import com.oracle.truffle.api.dsl.*; +import com.oracle.truffle.api.frame.*; +import com.oracle.truffle.api.nodes.*; +import com.oracle.truffle.api.utilities.*; + +/** + * This example is based on the SLDispatchNode of SimpleLanguage. It shows how to implement a simple + * inline cache with an assumption that needs to be checked. + * + * Note that if an assumption is invalidated the specialization instantiation is removed. + */ +@SuppressWarnings("unused") +@NodeChildren({@NodeChild("function"), @NodeChild("arguments")}) +public class StableDispatch { + + public static class StableDispatchNode extends ExampleNode { + + @Specialization(guards = "function == cachedFunction", assumptions = "cachedFunction.getCallTargetStable()") + protected static Object directDispatch(VirtualFrame frame, SLFunction function, Object[] arguments, // + @Cached("function") SLFunction cachedFunction, // + @Cached("create(cachedFunction.getCallTarget())") DirectCallNode callNode) { + return callNode.call(frame, arguments); + } + + @Specialization(contains = "directDispatch") + protected static Object indirectDispatch(VirtualFrame frame, SLFunction function, Object[] arguments, // + @Cached("create()") IndirectCallNode callNode) { + return callNode.call(frame, function.getCallTarget(), arguments); + } + } + + public static final class SLFunction { + + private CallTarget callTarget; + private final CyclicAssumption callTargetStable; + + protected SLFunction(String name) { + this.callTargetStable = new CyclicAssumption(name); + } + + protected void setCallTarget(CallTarget callTarget) { + this.callTarget = callTarget; + this.callTargetStable.invalidate(); + } + + public CallTarget getCallTarget() { + return callTarget; + } + + public Assumption getCallTargetStable() { + return callTargetStable.getAssumption(); + } + } + +}