comparison 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
comparison
equal deleted inserted replaced
21950:2a5011c7e641 21951:9c8c0937da41
1 /*
2 * Copyright (c) 2015, Oracle and/or its affiliates. All rights reserved.
3 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS FILE HEADER.
4 *
5 * This code is free software; you can redistribute it and/or modify it
6 * under the terms of the GNU General Public License version 2 only, as
7 * published by the Free Software Foundation.
8 *
9 * This code is distributed in the hope that it will be useful, but WITHOUT
10 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or
11 * FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License
12 * version 2 for more details (a copy is included in the LICENSE file that
13 * accompanied this code).
14 *
15 * You should have received a copy of the GNU General Public License version
16 * 2 along with this work; if not, write to the Free Software Foundation,
17 * Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA.
18 *
19 * Please contact Oracle, 500 Oracle Parkway, Redwood Shores, CA 94065 USA
20 * or visit www.oracle.com if you need additional information or have any
21 * questions.
22 */
23 package com.oracle.truffle.api.dsl.test.examples;
24
25 import static com.oracle.truffle.api.dsl.test.examples.ExampleNode.*;
26 import static org.junit.Assert.*;
27
28 import java.util.*;
29
30 import org.junit.*;
31
32 import com.oracle.truffle.api.*;
33 import com.oracle.truffle.api.CompilerDirectives.TruffleBoundary;
34 import com.oracle.truffle.api.dsl.*;
35 import com.oracle.truffle.api.dsl.internal.*;
36 import com.oracle.truffle.api.dsl.test.examples.RubyCallFactory.RubyDispatchNodeGen;
37 import com.oracle.truffle.api.dsl.test.examples.RubyCallFactory.RubyHeadNodeGen;
38 import com.oracle.truffle.api.dsl.test.examples.RubyCallFactory.RubyLookupNodeGen;
39 import com.oracle.truffle.api.frame.*;
40 import com.oracle.truffle.api.nodes.*;
41 import com.oracle.truffle.api.utilities.*;
42
43 /**
44 * This example illustrates a simplified version of a Ruby function call semantics (RubyHeadNode).
45 * The example usage shows how methods can be redefined in this implementation.
46 */
47 @SuppressWarnings("unused")
48 public class RubyCall {
49
50 @Test
51 public void testCall() {
52 RubyHeadNode node = RubyHeadNodeGen.create(createArguments(4));
53 CallTarget nodeTarget = createTarget(node);
54 final Object firstArgument = "someArgument";
55
56 // dummyMethod is just going to return the some argument of the function
57 final Object testMethodName = "getSomeArgument";
58 // implementation returns first argument
59 InternalMethod aClassTestMethod = new InternalMethod(ExampleNode.createDummyTarget(3));
60 // implementation returns second argument
61 InternalMethod bClassTestMethod = new InternalMethod(ExampleNode.createDummyTarget(4));
62 // implementation returns third argument
63 InternalMethod cClassTestMethod = new InternalMethod(ExampleNode.createDummyTarget(5));
64
65 // defines hierarchy C extends B extends A
66 RubyClass aClass = new RubyClass("A", null);
67 RubyClass bClass = new RubyClass("B", aClass);
68 RubyClass cClass = new RubyClass("C", bClass);
69
70 RubyObject aInstance = new RubyObject(aClass);
71 RubyObject bInstance = new RubyObject(bClass);
72 RubyObject cInstance = new RubyObject(cClass);
73
74 // undefined method call
75 assertEquals(RubyObject.NIL, nodeTarget.call(cInstance, testMethodName, null, new Object[]{firstArgument}));
76
77 // method defined in a
78 aClass.addMethod(testMethodName, aClassTestMethod);
79 assertEquals(firstArgument, nodeTarget.call(aInstance, testMethodName, null, new Object[]{firstArgument}));
80 assertEquals(firstArgument, nodeTarget.call(bInstance, testMethodName, null, new Object[]{firstArgument}));
81 assertEquals(firstArgument, nodeTarget.call(cInstance, testMethodName, null, new Object[]{firstArgument}));
82
83 // method redefined in b
84 bClass.addMethod(testMethodName, bClassTestMethod);
85 assertEquals(firstArgument, nodeTarget.call(aInstance, testMethodName, null, new Object[]{firstArgument}));
86 assertEquals(firstArgument, nodeTarget.call(bInstance, testMethodName, null, new Object[]{null, firstArgument}));
87 assertEquals(firstArgument, nodeTarget.call(cInstance, testMethodName, null, new Object[]{null, firstArgument}));
88
89 // method redefined in c
90 cClass.addMethod(testMethodName, cClassTestMethod);
91 assertEquals(firstArgument, nodeTarget.call(aInstance, testMethodName, null, new Object[]{firstArgument}));
92 assertEquals(firstArgument, nodeTarget.call(bInstance, testMethodName, null, new Object[]{null, firstArgument}));
93 assertEquals(firstArgument, nodeTarget.call(cInstance, testMethodName, null, new Object[]{null, null, firstArgument}));
94
95 }
96
97 public static class RubyHeadNode extends ExampleNode {
98
99 @Child private RubyLookupNode lookup = RubyLookupNodeGen.create();
100 @Child private RubyDispatchNode dispatch = RubyDispatchNodeGen.create();
101
102 @Specialization
103 public Object doCall(VirtualFrame frame, RubyObject receiverObject, Object methodName, Object blockObject, Object... argumentsObjects) {
104 InternalMethod method = lookup.executeLookup(receiverObject, methodName);
105
106 Object[] packedArguments = new Object[argumentsObjects.length + 3];
107 packedArguments[0] = method;
108 packedArguments[1] = receiverObject;
109 packedArguments[2] = blockObject;
110 System.arraycopy(argumentsObjects, 0, packedArguments, 3, argumentsObjects.length);
111
112 return dispatch.executeDispatch(frame, method, packedArguments);
113 }
114 }
115
116 public abstract static class RubyLookupNode extends Node {
117
118 public abstract InternalMethod executeLookup(RubyObject receiver, Object method);
119
120 @Specialization(guards = "receiver.getRubyClass() == cachedClass", assumptions = "cachedClass.getDependentAssumptions()")
121 protected static InternalMethod cachedLookup(RubyObject receiver, Object name, //
122 @Cached("receiver.getRubyClass()") RubyClass cachedClass, //
123 @Cached("genericLookup(receiver, name)") InternalMethod cachedLookup) {
124 return cachedLookup;
125 }
126
127 @Specialization(contains = "cachedLookup")
128 protected static InternalMethod genericLookup(RubyObject receiver, Object name) {
129 return receiver.getRubyClass().lookup(name);
130 }
131
132 }
133
134 @ImportStatic(InternalMethod.class)
135 public abstract static class RubyDispatchNode extends Node {
136
137 public abstract Object executeDispatch(VirtualFrame frame, InternalMethod function, Object[] packedArguments);
138
139 /*
140 * Please note that cachedMethod != METHOD_MISSING is invoked once at specialization
141 * instantiation. It is never executed on the fast path.
142 */
143 @Specialization(guards = {"method == cachedMethod", "cachedMethod != METHOD_MISSING"})
144 protected static Object directCall(VirtualFrame frame, InternalMethod method, Object[] arguments, //
145 @Cached("method") InternalMethod cachedMethod, //
146 @Cached("create(cachedMethod.getTarget())") DirectCallNode callNode) {
147 return callNode.call(frame, arguments);
148 }
149
150 /*
151 * The method == METHOD_MISSING can fold if the RubyLookup results just in a single entry
152 * returning the constant METHOD_MISSING.
153 */
154 @Specialization(guards = "method == METHOD_MISSING")
155 protected static Object methodMissing(VirtualFrame frame, InternalMethod method, Object[] arguments) {
156 // a real implementation would do a call to a method named method_missing here
157 return RubyObject.NIL;
158 }
159
160 @Specialization(contains = "directCall", guards = "method != METHOD_MISSING")
161 protected static Object indirectCall(VirtualFrame frame, InternalMethod method, Object[] arguments, //
162 @Cached("create()") IndirectCallNode callNode) {
163 return callNode.call(frame, method.getTarget(), arguments);
164 }
165
166 @Override
167 public String toString() {
168 return ((SpecializedNode) this).getSpecializationNode().toString();
169 }
170 }
171
172 public static final class RubyObject {
173
174 public static final RubyObject NIL = new RubyObject(null);
175
176 private final RubyClass rubyClass;
177
178 public RubyObject(RubyClass rubyClass) {
179 this.rubyClass = rubyClass;
180 }
181
182 public RubyClass getRubyClass() {
183 return rubyClass;
184 }
185
186 @Override
187 public String toString() {
188 return "RubyObject[class=" + rubyClass + "]";
189 }
190
191 }
192
193 public static final class RubyClass /* this would extend RubyModule */{
194
195 private final String name;
196 private final RubyClass parent; // this would be a RubyModule
197 private final CyclicAssumption unmodified;
198 private final Map<Object, InternalMethod> methods = new HashMap<>();
199 private Assumption[] cachedDependentAssumptions;
200 private final int depth;
201
202 public RubyClass(String name, RubyClass parent) {
203 this.name = name;
204 this.parent = parent;
205 this.unmodified = new CyclicAssumption("unmodified class " + name);
206
207 // lookup depth for array allocation
208 RubyClass clazz = parent;
209 int currentDepth = 1;
210 while (clazz != null) {
211 currentDepth++;
212 clazz = clazz.parent;
213 }
214 this.depth = currentDepth;
215 }
216
217 @TruffleBoundary
218 public InternalMethod lookup(Object methodName) {
219 InternalMethod method = methods.get(methodName);
220 if (method == null) {
221 if (parent != null) {
222 return parent.lookup(methodName);
223 } else {
224 return InternalMethod.METHOD_MISSING;
225 }
226 } else {
227 return method;
228 }
229 }
230
231 @TruffleBoundary
232 public void addMethod(Object methodName, InternalMethod method) {
233 // check for existing method omitted for simplicity
234 this.methods.put(methodName, method);
235 this.unmodified.invalidate();
236 }
237
238 /*
239 * Method collects all unmodified assumptions in the class hierarchy. The result is cached
240 * per class to void recreation per call site.
241 */
242 @TruffleBoundary
243 public Assumption[] getDependentAssumptions() {
244 Assumption[] dependentAssumptions = cachedDependentAssumptions;
245 if (dependentAssumptions != null) {
246 // we can use the cached dependent assumptions only if they are still valid
247 for (Assumption assumption : cachedDependentAssumptions) {
248 if (!assumption.isValid()) {
249 dependentAssumptions = null;
250 break;
251 }
252 }
253 }
254 if (dependentAssumptions == null) {
255 cachedDependentAssumptions = dependentAssumptions = createDependentAssumptions();
256 }
257 return dependentAssumptions;
258 }
259
260 @Override
261 public String toString() {
262 return "RubyClass[name=" + name + "]";
263 }
264
265 private Assumption[] createDependentAssumptions() {
266 Assumption[] dependentAssumptions;
267 RubyClass clazz = this;
268 dependentAssumptions = new Assumption[depth];
269
270 // populate array
271 int index = 0;
272 do {
273 dependentAssumptions[index] = clazz.unmodified.getAssumption();
274 index++;
275 clazz = clazz.parent;
276 } while (clazz != null);
277 return dependentAssumptions;
278 }
279 }
280
281 public static final class InternalMethod {
282
283 public static final InternalMethod METHOD_MISSING = new InternalMethod(null);
284
285 private final CallTarget target;
286
287 public InternalMethod(CallTarget target) {
288 this.target = target;
289 }
290
291 public CallTarget getTarget() {
292 return target;
293 }
294
295 @Override
296 public String toString() {
297 return "InternalMethod[target=" + getTarget() + "]";
298 }
299
300 }
301
302 }