Skip to content

Commit ea2429f

Browse files
committed
Add DispatchMethodMissingNode
1 parent 41ec137 commit ea2429f

File tree

3 files changed

+160
-101
lines changed

3 files changed

+160
-101
lines changed
Lines changed: 112 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,112 @@
1+
/*
2+
* Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. This
3+
* code is released under a tri EPL/GPL/LGPL license. You can use it,
4+
* redistribute it and/or modify it under the terms of the:
5+
*
6+
* Eclipse Public License version 2.0, or
7+
* GNU General Public License version 2, or
8+
* GNU Lesser General Public License version 2.1.
9+
*/
10+
package org.truffleruby.language.dispatch;
11+
12+
import com.oracle.truffle.api.HostCompilerDirectives.InliningCutoff;
13+
import com.oracle.truffle.api.dsl.Bind;
14+
import com.oracle.truffle.api.dsl.Cached;
15+
import com.oracle.truffle.api.dsl.GenerateInline;
16+
import com.oracle.truffle.api.dsl.GenerateUncached;
17+
import com.oracle.truffle.api.dsl.Specialization;
18+
import com.oracle.truffle.api.frame.Frame;
19+
import com.oracle.truffle.api.nodes.Node;
20+
import com.oracle.truffle.api.profiles.InlinedBranchProfile;
21+
import org.truffleruby.core.cast.ToSymbolNode;
22+
import org.truffleruby.core.exception.ExceptionOperations;
23+
import org.truffleruby.core.symbol.RubySymbol;
24+
import org.truffleruby.language.RubyBaseNode;
25+
import org.truffleruby.language.RubyGuards;
26+
import org.truffleruby.language.arguments.RubyArguments;
27+
import org.truffleruby.language.control.RaiseException;
28+
import org.truffleruby.language.methods.CallForeignMethodNode;
29+
30+
import static org.truffleruby.language.dispatch.DispatchNode.MISSING;
31+
32+
@GenerateInline(false)
33+
@GenerateUncached
34+
public abstract class DispatchMethodMissingNode extends RubyBaseNode {
35+
36+
37+
public abstract Object execute(Frame frame, Object receiver, String methodName, Object[] rubyArgs,
38+
DispatchConfiguration config, LiteralCallNode literalCallNode);
39+
40+
41+
@Specialization(guards = "isReturnMissing(config)")
42+
protected static Object dispatchReturnMissing(
43+
Frame frame,
44+
Object receiver,
45+
String methodName,
46+
Object[] rubyArgs,
47+
DispatchConfiguration config,
48+
LiteralCallNode literalCallNode) {
49+
return MISSING;
50+
}
51+
52+
@InliningCutoff
53+
@Specialization(guards = "isForeignObject(config, receiver)")
54+
protected static Object dispatchForeign(
55+
Frame frame,
56+
Object receiver,
57+
String methodName,
58+
Object[] rubyArgs,
59+
DispatchConfiguration config,
60+
LiteralCallNode literalCallNode,
61+
@Cached CallForeignMethodNode callForeign) {
62+
final Object block = RubyArguments.getBlock(rubyArgs);
63+
final Object[] arguments = RubyArguments.getPositionalArguments(rubyArgs);
64+
return callForeign.execute(receiver, methodName, block, arguments);
65+
}
66+
67+
@InliningCutoff
68+
@Specialization(guards = "isMethodMissing(config, receiver)")
69+
protected static Object dispatchMissingMethod(
70+
Frame frame,
71+
Object receiver,
72+
String methodName,
73+
Object[] rubyArgs,
74+
DispatchConfiguration config,
75+
LiteralCallNode literalCallNode,
76+
@Cached ToSymbolNode toSymbol,
77+
@Cached DispatchNode callMethodMissing,
78+
@Cached InlinedBranchProfile methodMissingMissingProfile,
79+
@Bind("this") Node node) {
80+
final RubySymbol symbolName = toSymbol.execute(node, methodName);
81+
82+
final Object[] newArgs = RubyArguments.repack(rubyArgs, receiver, 0, 1);
83+
84+
RubyArguments.setArgument(newArgs, 0, symbolName);
85+
final Object result = callMethodMissing.execute(frame, receiver, "method_missing", newArgs,
86+
DispatchConfiguration.PRIVATE_RETURN_MISSING_IGNORE_REFINEMENTS, literalCallNode);
87+
88+
if (result == MISSING) {
89+
methodMissingMissingProfile.enter(node);
90+
throw new RaiseException(getContext(node), coreExceptions(node).noMethodErrorFromMethodMissing(
91+
ExceptionOperations.ExceptionFormatter.NO_METHOD_ERROR,
92+
receiver,
93+
methodName,
94+
RubyArguments.getPositionalArguments(rubyArgs),
95+
node));
96+
}
97+
98+
return result;
99+
}
100+
101+
protected static boolean isReturnMissing(DispatchConfiguration config) {
102+
return config.missingBehavior == MissingBehavior.RETURN_MISSING;
103+
}
104+
105+
protected static boolean isForeignObject(DispatchConfiguration config, Object receiver) {
106+
return config.missingBehavior == MissingBehavior.CALL_METHOD_MISSING && RubyGuards.isForeignObject(receiver);
107+
}
108+
109+
protected static boolean isMethodMissing(DispatchConfiguration config, Object receiver) {
110+
return config.missingBehavior == MissingBehavior.CALL_METHOD_MISSING && !RubyGuards.isForeignObject(receiver);
111+
}
112+
}

src/main/java/org/truffleruby/language/dispatch/DispatchNode.java

Lines changed: 12 additions & 101 deletions
Original file line numberDiff line numberDiff line change
@@ -10,7 +10,6 @@
1010
package org.truffleruby.language.dispatch;
1111

1212
import com.oracle.truffle.api.Assumption;
13-
import com.oracle.truffle.api.HostCompilerDirectives.InliningCutoff;
1413
import com.oracle.truffle.api.RootCallTarget;
1514
import com.oracle.truffle.api.dsl.Cached;
1615
import com.oracle.truffle.api.dsl.GenerateUncached;
@@ -19,22 +18,16 @@
1918
import com.oracle.truffle.api.frame.Frame;
2019
import com.oracle.truffle.api.interop.TruffleObject;
2120
import com.oracle.truffle.api.nodes.DirectCallNode;
22-
import com.oracle.truffle.api.profiles.ConditionProfile;
23-
import org.truffleruby.core.cast.ToSymbolNode;
24-
import org.truffleruby.core.exception.ExceptionOperations.ExceptionFormatter;
21+
import com.oracle.truffle.api.profiles.InlinedConditionProfile;
2522
import org.truffleruby.core.hash.RubyHash;
2623
import org.truffleruby.core.kernel.TruffleKernelNodes.GetSpecialVariableStorage;
2724
import org.truffleruby.core.klass.RubyClass;
28-
import org.truffleruby.core.symbol.RubySymbol;
29-
import org.truffleruby.language.RubyGuards;
3025
import org.truffleruby.language.RubyRootNode;
3126
import org.truffleruby.language.SpecialVariablesSendingNode;
3227
import org.truffleruby.language.arguments.ArgumentsDescriptor;
3328
import org.truffleruby.language.arguments.EmptyArgumentsDescriptor;
3429
import org.truffleruby.language.arguments.KeywordArgumentsDescriptorManager;
3530
import org.truffleruby.language.arguments.RubyArguments;
36-
import org.truffleruby.language.control.RaiseException;
37-
import org.truffleruby.language.methods.CallForeignMethodNode;
3831
import org.truffleruby.language.methods.CallInternalMethodNode;
3932
import org.truffleruby.language.methods.InternalMethod;
4033
import org.truffleruby.language.methods.LookupMethodNode;
@@ -290,46 +283,33 @@ protected Object dispatch(
290283
uncached = "getValidAssumption()") Assumption specialVariableAssumption,
291284
@Cached MetaClassNode metaclassNode,
292285
@Cached LookupMethodNode methodLookup,
293-
@Cached ConditionProfile methodMissing,
286+
@Cached InlinedConditionProfile methodMissing,
294287
@Cached CallInternalMethodNode callNode,
295-
@Cached CallForeignMethodNode callForeign,
296-
@Cached DispatchNode callMethodMissing,
297-
@Cached ToSymbolNode toSymbol,
298-
@Cached GetSpecialVariableStorage readingNode) {
288+
@Cached GetSpecialVariableStorage readingNode,
289+
@Cached LazyDispatchMethodMissingNode lazyDispatchMethodMissingNode) {
299290
return dispatchInternal(frame, receiver, methodName, rubyArgs, config, literalCallNode,
300-
metaclassNode, methodLookup, methodMissing, callNode, readingNode, callForeign, callMethodMissing,
301-
toSymbol, specialVariableAssumption);
291+
metaclassNode, methodLookup, methodMissing, callNode, readingNode, specialVariableAssumption,
292+
lazyDispatchMethodMissingNode);
302293
}
303294

304295
protected final Object dispatchInternal(Frame frame, Object receiver, String methodName, Object[] rubyArgs,
305296
DispatchConfiguration config,
306297
LiteralCallNode literalCallNode,
307298
MetaClassNode metaClassNode,
308299
LookupMethodNode lookupMethodNode,
309-
ConditionProfile methodMissingProfile,
300+
InlinedConditionProfile methodMissingProfile,
310301
CallInternalMethodNode callNode,
311302
GetSpecialVariableStorage readingNode,
312-
CallForeignMethodNode callForeign,
313-
DispatchNode callMethodMissing,
314-
ToSymbolNode toSymbol, Assumption specialVariableAssumption) {
303+
Assumption specialVariableAssumption,
304+
LazyDispatchMethodMissingNode lazyDispatchMethodMissingNode) {
315305
assert RubyArguments.getSelf(rubyArgs) == receiver;
316306

317307
final RubyClass metaclass = metaClassNode.execute(this, receiver);
318308
final InternalMethod method = lookupMethodNode.execute(frame, metaclass, methodName, config);
319309

320-
if (methodMissingProfile.profile(method == null || method.isUndefined())) {
321-
switch (config.missingBehavior) {
322-
case RETURN_MISSING:
323-
return MISSING;
324-
case CALL_METHOD_MISSING:
325-
// Both branches implicitly profile through lazy node creation
326-
if (RubyGuards.isForeignObject(receiver)) { // TODO (eregon, 16 Aug 2021) maybe use a final boolean on the class to know if foreign
327-
return callForeign(receiver, methodName, rubyArgs, callForeign);
328-
} else {
329-
return callMethodMissing(frame, receiver, methodName, rubyArgs, literalCallNode,
330-
callMethodMissing, toSymbol);
331-
}
332-
}
310+
if (methodMissingProfile.profile(this, method == null || method.isUndefined())) {
311+
return lazyDispatchMethodMissingNode.get(this).execute(frame, receiver, methodName, rubyArgs, config,
312+
literalCallNode);
333313
}
334314

335315
RubyArguments.setMethod(rubyArgs, method);
@@ -343,75 +323,6 @@ protected final Object dispatchInternal(Frame frame, Object receiver, String met
343323
}
344324

345325

346-
@InliningCutoff
347-
private Object callMethodMissing(Frame frame, Object receiver, String methodName, Object[] rubyArgs,
348-
LiteralCallNode literalCallNode, DispatchNode callMethodMissing, ToSymbolNode toSymbol) {
349-
// profiles through lazy node creation
350-
final RubySymbol symbolName = toSymbol.execute(this, methodName);
351-
352-
final Object[] newArgs = RubyArguments.repack(rubyArgs, receiver, 0, 1);
353-
354-
RubyArguments.setArgument(newArgs, 0, symbolName);
355-
final Object result = callMethodMissing.execute(frame, receiver, "method_missing", newArgs,
356-
DispatchConfiguration.PRIVATE_RETURN_MISSING_IGNORE_REFINEMENTS,
357-
literalCallNode);
358-
359-
if (result == MISSING) {
360-
throw new RaiseException(getContext(), coreExceptions().noMethodErrorFromMethodMissing(
361-
ExceptionFormatter.NO_METHOD_ERROR,
362-
receiver,
363-
methodName,
364-
RubyArguments.getPositionalArguments(rubyArgs),
365-
this));
366-
}
367-
368-
return result;
369-
}
370-
371-
@InliningCutoff
372-
protected Object callForeign(Object receiver, String methodName, Object[] rubyArgs,
373-
CallForeignMethodNode callForeignMethodNode) {
374-
// profiles through lazy node creation
375-
//final CallForeignMethodNode callForeignMethodNode = getCallForeignMethodNode();
376-
377-
final Object block = RubyArguments.getBlock(rubyArgs);
378-
final Object[] arguments = RubyArguments.getPositionalArguments(rubyArgs);
379-
return callForeignMethodNode.execute(receiver, methodName, block, arguments);
380-
}
381-
382-
// protected CallForeignMethodNode getCallForeignMethodNode() {
383-
// if (callForeign == null) {
384-
// CompilerDirectives.transferToInterpreterAndInvalidate();
385-
// callForeign = insert(CallForeignMethodNode.create());
386-
// }
387-
// return callForeign;
388-
// }
389-
390-
// protected DispatchNode getMethodMissingNode() {
391-
// if (callMethodMissing == null) {
392-
// CompilerDirectives.transferToInterpreterAndInvalidate();
393-
// // #method_missing ignores refinements on CRuby: https://bugs.ruby-lang.org/issues/13129
394-
// callMethodMissing = insert(
395-
// DispatchNode.create());
396-
// }
397-
// return callMethodMissing;
398-
// }
399-
400-
// protected void methodMissingMissingProfileEnter() {
401-
// if (!methodMissingMissingProfile) {
402-
// CompilerDirectives.transferToInterpreterAndInvalidate();
403-
// methodMissingMissingProfile = true;
404-
// }
405-
// }
406-
407-
// protected RubySymbol nameToSymbol(String methodName) {
408-
// if (toSymbol == null) {
409-
// CompilerDirectives.transferToInterpreterAndInvalidate();
410-
// toSymbol = insert(ToSymbolNodeGen.create());
411-
// }
412-
// return toSymbol.execute(methodName);
413-
// }
414-
415326
/** This will be called from the {@link CallInternalMethodNode} child whenever it creates a new
416327
* {@link DirectCallNode}. */
417328
public final void applySplittingInliningStrategy(RootCallTarget callTarget, String methodName,
Lines changed: 36 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,36 @@
1+
/*
2+
* Copyright (c) 2023 Oracle and/or its affiliates. All rights reserved. This
3+
* code is released under a tri EPL/GPL/LGPL license. You can use it,
4+
* redistribute it and/or modify it under the terms of the:
5+
*
6+
* Eclipse Public License version 2.0, or
7+
* GNU General Public License version 2, or
8+
* GNU Lesser General Public License version 2.1.
9+
*/
10+
package org.truffleruby.language.dispatch;
11+
12+
import com.oracle.truffle.api.dsl.Cached;
13+
import com.oracle.truffle.api.dsl.GenerateCached;
14+
import com.oracle.truffle.api.dsl.GenerateInline;
15+
import com.oracle.truffle.api.dsl.GenerateUncached;
16+
import com.oracle.truffle.api.dsl.Specialization;
17+
import com.oracle.truffle.api.nodes.Node;
18+
import org.truffleruby.language.RubyBaseNode;
19+
20+
@GenerateInline
21+
@GenerateCached(false)
22+
@GenerateUncached
23+
public abstract class LazyDispatchMethodMissingNode extends RubyBaseNode {
24+
25+
public final DispatchMethodMissingNode get(Node node) {
26+
return execute(node);
27+
}
28+
29+
protected abstract DispatchMethodMissingNode execute(Node node);
30+
31+
@Specialization
32+
protected static DispatchMethodMissingNode doLazy(
33+
@Cached(inline = false) DispatchMethodMissingNode dispatchMethodMissingNode) {
34+
return dispatchMethodMissingNode;
35+
}
36+
}

0 commit comments

Comments
 (0)