10
10
package org .truffleruby .language ;
11
11
12
12
import com .oracle .truffle .api .CompilerDirectives ;
13
+ import com .oracle .truffle .api .frame .Frame ;
13
14
import com .oracle .truffle .api .nodes .InvalidAssumptionException ;
15
+ import org .truffleruby .core .binding .RubyBinding ;
14
16
import org .truffleruby .language .arguments .ReadCallerFrameNode ;
15
17
16
18
import com .oracle .truffle .api .Assumption ;
19
21
import com .oracle .truffle .api .frame .VirtualFrame ;
20
22
import com .oracle .truffle .api .nodes .Node ;
21
23
import com .oracle .truffle .api .utilities .AlwaysValidAssumption ;
24
+ import org .truffleruby .language .methods .DeclarationContext ;
22
25
26
+ /** Some Ruby methods need access to the caller frame (the frame active when the method call was made): see usages of
27
+ * {@link ReadCallerFrameNode}. This is notably used to get hold of instances of {@link DeclarationContext} and
28
+ * {@link RubyBinding}.
29
+ *
30
+ * <p>
31
+ * This means that when making a method call, we might need to pass down its {@link Frame} active when the method call
32
+ * was made.
33
+ *
34
+ * <p>
35
+ * When retrieving the frame in a method called through the Ruby {@code #send} method, we must not retrieve the frame of
36
+ * the actual call (made by {@code #send}) but the frame of the {@code #send} call itself.
37
+ *
38
+ * <p>
39
+ * Materializing a frame is expensive, and the point of this parent node is to only materialize the frame when we know
40
+ * for sure it has been requested by the callee. It is also possible to walk the stack to retrieve the frame to
41
+ * materialize - but this is even slower and causes a deoptimization in the callee.
42
+ *
43
+ * <p>
44
+ * This class works in tandem with {@link ReadCallerFrameNode} for this purpose. At first, we don't send down the frame.
45
+ * If the callee needs it, it will de-optimize and walk the stack to retrieve it (slow). It will also call
46
+ * {@link #startSendingOwnFrame()}}, so that the next time the method is called, the frame will be passed down and the
47
+ * method does not need further de-optimizations. (Note in the case of {@code #send} calls, we need to recursively call
48
+ * {@link ReadCallerFrameNode} to get the parent frame!)
49
+ *
50
+ * <p>
51
+ * This class is the sole consumer of {@link RubyRootNode#getNeedsCallerAssumption()}, which is used to optimize
52
+ * {@link #getFrameIfRequired(VirtualFrame)} (called by subclasses in order to pass down the frame or not). Starting to
53
+ * send the frame invalidates the assumption. In other words, the assumption guards the fact that {@link #sendsFrame} is
54
+ * a compilation constant, and is invalidated whenever it needs to change. */
23
55
public abstract class FrameSendingNode extends RubyContextNode {
24
56
25
- protected enum SendsFrame {
26
- NO_FRAME ,
27
- MY_FRAME ,
28
- CALLER_FRAME ;
57
+ private enum SendsFrame {
58
+ NO_FRAME , // callees don't need to read the frame
59
+ MY_FRAME , // for most calls
60
+ CALLER_FRAME ; // for `send` calls
29
61
}
30
62
31
63
@ CompilationFinal protected SendsFrame sendsFrame = SendsFrame .NO_FRAME ;
32
64
@ CompilationFinal protected Assumption needsCallerAssumption ;
33
65
34
66
@ Child protected ReadCallerFrameNode readCaller ;
35
67
68
+ /** Whether we are sending down the frame (because the called method reads it). */
36
69
protected boolean sendingFrames () {
37
70
return sendsFrame != SendsFrame .NO_FRAME ;
38
71
}
@@ -52,7 +85,7 @@ private synchronized void startSendingFrame(SendsFrame frameToSend) {
52
85
}
53
86
54
87
// We'd only get AlwaysValidAssumption if the root node isn't Ruby (in which case this shouldn't be called),
55
- // or when we already know to send the frame (in which case we'd have exited above.
88
+ // or when we already know to send the frame (in which case we'd have exited above) .
56
89
assert needsCallerAssumption != AlwaysValidAssumption .INSTANCE ;
57
90
58
91
this .sendsFrame = frameToSend ;
@@ -67,7 +100,7 @@ private synchronized void startSendingFrame(SendsFrame frameToSend) {
67
100
}
68
101
}
69
102
70
- protected synchronized void resetNeedsCallerAssumption () {
103
+ private synchronized void resetNeedsCallerAssumption () {
71
104
Node root = getRootNode ();
72
105
if (root instanceof RubyRootNode && !sendingFrames ()) {
73
106
needsCallerAssumption = ((RubyRootNode ) root ).getNeedsCallerAssumption ();
@@ -76,9 +109,8 @@ protected synchronized void resetNeedsCallerAssumption() {
76
109
}
77
110
}
78
111
79
- public MaterializedFrame getFrameIfRequiredNew (VirtualFrame frame ) {
80
- // TODO(norswap, 07 Aug 2020): worth moving up to the dispatch node & profiling?
81
- if (frame == null ) {
112
+ public MaterializedFrame getFrameIfRequired (VirtualFrame frame ) {
113
+ if (frame == null ) { // the frame should be proved null or non-null at PE time
82
114
return null ;
83
115
}
84
116
@@ -102,15 +134,4 @@ public MaterializedFrame getFrameIfRequiredNew(VirtualFrame frame) {
102
134
return null ;
103
135
}
104
136
}
105
-
106
- public MaterializedFrame getFrameIfRequired (VirtualFrame frame ) {
107
- switch (sendsFrame ) {
108
- case MY_FRAME :
109
- return frame .materialize ();
110
- case CALLER_FRAME :
111
- return readCaller .execute (frame );
112
- default :
113
- return null ;
114
- }
115
- }
116
137
}
0 commit comments