Mercurial > hg > truffle
diff truffle/com.oracle.truffle.api.dsl.test/src/com/oracle/truffle/api/dsl/test/examples/RubyCall.java @ 21951:9c8c0937da41
Moving all sources into truffle subdirectory
author | Jaroslav Tulach <jaroslav.tulach@oracle.com> |
---|---|
date | Wed, 17 Jun 2015 10:58:08 +0200 |
parents | graal/com.oracle.truffle.api.dsl.test/src/com/oracle/truffle/api/dsl/test/examples/RubyCall.java@476374f3fe9a |
children | dc83cc1f94f2 |
line wrap: on
line diff
--- /dev/null Thu Jan 01 00:00:00 1970 +0000 +++ b/truffle/com.oracle.truffle.api.dsl.test/src/com/oracle/truffle/api/dsl/test/examples/RubyCall.java Wed Jun 17 10:58:08 2015 +0200 @@ -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.test.examples; + +import static com.oracle.truffle.api.dsl.test.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.internal.*; +import com.oracle.truffle.api.dsl.test.examples.RubyCallFactory.RubyDispatchNodeGen; +import com.oracle.truffle.api.dsl.test.examples.RubyCallFactory.RubyHeadNodeGen; +import com.oracle.truffle.api.dsl.test.examples.RubyCallFactory.RubyLookupNodeGen; +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(); + @Child private RubyDispatchNode dispatch = RubyDispatchNodeGen.create(); + + @Specialization + public Object doCall(VirtualFrame frame, RubyObject receiverObject, Object methodName, Object blockObject, Object... argumentsObjects) { + InternalMethod method = lookup.executeLookup(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 Node { + + public abstract InternalMethod executeLookup(RubyObject receiver, Object method); + + @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 Node { + + 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<Object, InternalMethod> 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() + "]"; + } + + } + +}