|
9 | 9 | */
|
10 | 10 | package org.truffleruby.language;
|
11 | 11 |
|
12 |
| -import com.oracle.truffle.api.CompilerDirectives; |
13 | 12 | import com.oracle.truffle.api.frame.Frame;
|
14 |
| -import com.oracle.truffle.api.nodes.InvalidAssumptionException; |
15 | 13 | import org.truffleruby.SuppressFBWarnings;
|
16 | 14 | import org.truffleruby.core.binding.RubyBinding;
|
17 | 15 | import org.truffleruby.core.kernel.TruffleKernelNodes.GetSpecialVariableStorage;
|
18 |
| -import org.truffleruby.language.arguments.ReadCallerDataNode; |
19 |
| -import org.truffleruby.language.arguments.ReadCallerFrameAndVariablesNode; |
20 | 16 | import org.truffleruby.language.arguments.ReadCallerFrameNode;
|
21 | 17 | import org.truffleruby.language.arguments.ReadCallerVariablesNode;
|
22 | 18 |
|
23 |
| -import com.oracle.truffle.api.Assumption; |
24 |
| -import com.oracle.truffle.api.CompilerDirectives.CompilationFinal; |
25 |
| -import com.oracle.truffle.api.nodes.Node; |
26 |
| -import com.oracle.truffle.api.utilities.AlwaysValidAssumption; |
27 | 19 | import org.truffleruby.language.methods.DeclarationContext;
|
28 | 20 | import org.truffleruby.language.threadlocal.SpecialVariableStorage;
|
29 | 21 |
|
30 | 22 | /** Some Ruby methods need access to the caller frame (the frame active when the method call was made) or to the storage
|
31 | 23 | * of special variables within that frame: see usages of {@link ReadCallerFrameNode} and {@link ReadCallerVariablesNode}
|
32 | 24 | * . This is notably used to get hold of instances of {@link DeclarationContext} and {@link RubyBinding} and methods
|
33 |
| - * which need to access the last regexp match or the last io line. |
| 25 | + * which need to access the last regexp MatchData or the last IO line. |
34 | 26 | *
|
35 | 27 | * <p>
|
36 | 28 | * This means that when making a method call, we might need to pass down its {@link Frame} or
|
37 | 29 | * {@link SpecialVariableStorage} active when the method call was made.
|
38 | 30 | *
|
39 | 31 | * <p>
|
40 |
| - * When retrieving the frame or special variable storage in a method called through the Ruby {@code #send} method, we |
41 |
| - * must not retrieve the frame of the actual call (made by {@code #send}) but the frame of the {@code #send} call |
42 |
| - * itself. |
43 |
| - * |
44 |
| - * <p> |
45 | 32 | * Materializing a frame is expensive, and the point of this parent node is to only materialize the frame when we know
|
46 | 33 | * for sure it has been requested by the callee. It is also possible to walk the stack to retrieve the frame to
|
47 | 34 | * materialize - but this is even slower and causes a deoptimization in the callee every time we walk the stack.
|
48 | 35 | *
|
49 | 36 | * <p>
|
50 |
| - * This class works in tandem with {@link ReadCallerFrameNode} for this purpose. At first, we don't send down the frame. |
51 |
| - * If the callee needs it, it will de-optimize and walk the stack to retrieve it (slow). It will also call |
| 37 | + * This class works in tandem with {@link FrameOrVariablesReadingNode} for this purpose. At first, we don't send down |
| 38 | + * the frame. If the callee needs it, it will de-optimize and walk the stack to retrieve it (slow). It will also call |
52 | 39 | * {@link #startSendingOwnFrame()}}, so that the next time the method is called, the frame will be passed down and the
|
53 |
| - * method does not need further de-optimizations. (Note in the case of {@code #send} calls, we need to recursively call |
54 |
| - * {@link ReadCallerFrameNode} to get the parent frame!) {@link ReadCallerVariablesNode} is used similarly to access |
55 |
| - * special variable storage, but for child nodes that only require access to this storage ensures they receive an object |
56 |
| - * that will not require node splitting to be accessed efficiently. |
| 40 | + * method does not need further de-optimizations. |
57 | 41 | *
|
58 | 42 | * <p>
|
59 |
| - * This class is the sole consumer of {@link RubyRootNode#getNeedsCallerAssumption()}, which is used to optimize |
60 |
| - * {@link #getFrameOrStorageIfRequired(Frame)} (called by subclasses in order to pass down the frame or not). Starting |
61 |
| - * to send the frame invalidates the assumption. In other words, the assumption guards the fact that {@link #sendsFrame} |
62 |
| - * is a compilation constant, and is invalidated whenever it needs to change. */ |
| 43 | + * {@link ReadCallerVariablesNode} is used similarly to access special variable storage, but for child nodes that only |
| 44 | + * require access to this storage ensures they receive an object that will not require node splitting to be accessed |
| 45 | + * efficiently. */ |
63 | 46 | @SuppressFBWarnings("IS")
|
64 | 47 | public abstract class FrameAndVariablesSendingNode extends RubyContextNode {
|
65 | 48 |
|
66 |
| - private enum SendsFrame { |
67 |
| - NO_FRAME, // callees don't need to read the frame |
68 |
| - MY_FRAME, // for most calls |
69 |
| - CALLER_FRAME; // for `send` calls |
70 |
| - } |
71 |
| - |
72 |
| - @CompilationFinal protected SendsFrame sendsFrame = SendsFrame.NO_FRAME; |
73 |
| - @CompilationFinal protected Assumption needsCallerAssumption; |
74 |
| - @CompilationFinal protected SendsFrame sendsVariables = SendsFrame.NO_FRAME; |
75 |
| - |
76 |
| - @Child protected ReadCallerDataNode readCaller; |
77 |
| - @Child protected GetSpecialVariableStorage readMyVariables; |
78 |
| - |
79 |
| - /** Whether we are sending down the frame (because the called method reads it). */ |
80 |
| - protected boolean sendingFrames() { |
81 |
| - return sendsFrame != SendsFrame.NO_FRAME; |
82 |
| - } |
83 |
| - |
84 |
| - protected boolean sendingVariables() { |
85 |
| - return sendsVariables != SendsFrame.NO_FRAME; |
86 |
| - } |
87 |
| - |
88 |
| - public void startSendingOwnFrame() { |
89 |
| - if (getContext().getCallStack().callerIsSend()) { |
90 |
| - startSendingFrame(SendsFrame.CALLER_FRAME); |
91 |
| - } else { |
92 |
| - startSendingFrame(SendsFrame.MY_FRAME); |
93 |
| - } |
94 |
| - } |
95 |
| - |
96 |
| - public void startSendingOwnVariables() { |
97 |
| - if (getContext().getCallStack().callerIsSend()) { |
98 |
| - startSendingVariables(SendsFrame.CALLER_FRAME); |
99 |
| - } else { |
100 |
| - startSendingVariables(SendsFrame.MY_FRAME); |
101 |
| - } |
102 |
| - } |
103 |
| - |
104 |
| - public void startSendingOwnFrameAndVariables() { |
105 |
| - if (getContext().getCallStack().callerIsSend()) { |
106 |
| - startSendingFrameAndVariables(SendsFrame.CALLER_FRAME); |
107 |
| - } else { |
108 |
| - startSendingFrameAndVariables(SendsFrame.MY_FRAME); |
109 |
| - } |
110 |
| - } |
111 |
| - |
112 |
| - private synchronized void startSendingFrame(SendsFrame frameToSend) { |
113 |
| - if (sendingFrames()) { |
114 |
| - assert sendsFrame == frameToSend; |
115 |
| - return; |
116 |
| - } |
117 |
| - |
118 |
| - if (sendingVariables()) { |
119 |
| - assert sendsVariables == frameToSend; |
120 |
| - } |
121 |
| - |
122 |
| - // We'd only get AlwaysValidAssumption if the root node isn't Ruby (in which case this shouldn't be called), |
123 |
| - // or when we already know to send the frame (in which case we'd have exited above). |
124 |
| - assert needsCallerAssumption != AlwaysValidAssumption.INSTANCE; |
| 49 | + @Child protected FrameOrVariablesReadingNode readingNode; |
125 | 50 |
|
126 |
| - this.sendsFrame = frameToSend; |
127 |
| - if (frameToSend == SendsFrame.CALLER_FRAME) { |
128 |
| - if (!sendingVariables()) { |
129 |
| - this.readCaller = insert(new ReadCallerFrameNode()); |
130 |
| - } else { |
131 |
| - this.readCaller = readCaller.replace(new ReadCallerFrameAndVariablesNode()); |
132 |
| - } |
133 |
| - } |
134 |
| - Node root = getRootNode(); |
135 |
| - if (root instanceof RubyRootNode) { |
136 |
| - ((RubyRootNode) root).invalidateNeedsCallerAssumption(); |
| 51 | + public boolean sendingFrames() { |
| 52 | + if (readingNode == null) { |
| 53 | + return false; |
137 | 54 | } else {
|
138 |
| - throw new Error(); |
| 55 | + return readingNode.sendingFrame(); |
139 | 56 | }
|
140 | 57 | }
|
141 | 58 |
|
142 |
| - private synchronized void startSendingVariables(SendsFrame variablesToSend) { |
143 |
| - if (sendingFrames()) { |
144 |
| - assert sendsFrame == variablesToSend; |
145 |
| - } |
146 |
| - |
147 |
| - if (sendingVariables()) { |
148 |
| - assert sendsVariables == variablesToSend; |
149 |
| - return; |
150 |
| - } |
151 |
| - |
152 |
| - // We'd only get AlwaysValidAssumption if the root node isn't Ruby (in which case this shouldn't be called), |
153 |
| - // or when we already know to send the frame (in which case we'd have exited above). |
154 |
| - assert needsCallerAssumption != AlwaysValidAssumption.INSTANCE; |
155 |
| - |
156 |
| - this.sendsVariables = variablesToSend; |
157 |
| - if (variablesToSend == SendsFrame.CALLER_FRAME) { |
158 |
| - if (!sendingFrames()) { |
159 |
| - this.readCaller = insert(new ReadCallerVariablesNode()); |
160 |
| - } else { |
161 |
| - this.readCaller = readCaller.replace(new ReadCallerFrameAndVariablesNode()); |
162 |
| - } |
163 |
| - } else { |
164 |
| - this.readMyVariables = insert(GetSpecialVariableStorage.create()); |
165 |
| - } |
166 |
| - Node root = getRootNode(); |
167 |
| - if (root instanceof RubyRootNode) { |
168 |
| - ((RubyRootNode) root).invalidateNeedsVariablesAssumption(); |
169 |
| - } else { |
170 |
| - throw new Error(); |
| 59 | + private synchronized void startSending(boolean variables, boolean frame) { |
| 60 | + if (readingNode != null) { |
| 61 | + readingNode.startSending(variables, frame); |
| 62 | + } else if (variables && !frame) { |
| 63 | + readingNode = insert(GetSpecialVariableStorage.create()); |
| 64 | + } else if (!variables && frame) { |
| 65 | + readingNode = insert(new ReadOwnFrameNode()); |
171 | 66 | }
|
172 | 67 | }
|
173 | 68 |
|
174 |
| - private synchronized void startSendingFrameAndVariables(SendsFrame dataToSend) { |
175 |
| - if (sendingFrames()) { |
176 |
| - assert sendsFrame == dataToSend; |
177 |
| - return; |
178 |
| - } |
179 |
| - |
180 |
| - if (sendingVariables()) { |
181 |
| - assert sendsFrame == dataToSend; |
182 |
| - return; |
183 |
| - } |
184 |
| - |
185 |
| - // We'd only get AlwaysValidAssumption if the root node isn't Ruby (in which case this shouldn't be called), |
186 |
| - // or when we already know to send the frame (in which case we'd have exited above). |
187 |
| - assert needsCallerAssumption != AlwaysValidAssumption.INSTANCE; |
188 |
| - |
189 |
| - this.sendsFrame = dataToSend; |
190 |
| - this.sendsVariables = dataToSend; |
191 |
| - if (dataToSend == SendsFrame.CALLER_FRAME) { |
192 |
| - if (sendingFrames() || sendingVariables()) { |
193 |
| - this.readCaller = readCaller.replace(new ReadCallerFrameAndVariablesNode()); |
194 |
| - } else { |
195 |
| - this.readCaller = insert(new ReadCallerFrameNode()); |
196 |
| - } |
197 |
| - } |
198 |
| - Node root = getRootNode(); |
199 |
| - if (root instanceof RubyRootNode) { |
200 |
| - ((RubyRootNode) root).invalidateNeedsCallerAssumption(); |
201 |
| - } else { |
202 |
| - throw new Error(); |
203 |
| - } |
| 69 | + /** Whether we are sending down the frame (because the called method reads it). */ |
| 70 | + public void startSendingOwnFrame() { |
| 71 | + startSending(false, true); |
204 | 72 | }
|
205 | 73 |
|
206 |
| - private synchronized void resetNeedsCallerAssumption() { |
207 |
| - Node root = getRootNode(); |
208 |
| - if (root instanceof RubyRootNode && (!sendingFrames() || !sendingVariables())) { |
209 |
| - needsCallerAssumption = ((RubyRootNode) root).getNeedsCallerAssumption(); |
210 |
| - } else { |
211 |
| - needsCallerAssumption = AlwaysValidAssumption.INSTANCE; |
212 |
| - } |
| 74 | + public void startSendingOwnVariables() { |
| 75 | + startSending(true, false); |
213 | 76 | }
|
214 | 77 |
|
215 | 78 | public Object getFrameOrStorageIfRequired(Frame frame) {
|
216 |
| - if (frame == null) { // the frame should be proved null or non-null at PE time |
217 |
| - return null; |
218 |
| - } |
219 |
| - |
220 |
| - if (needsCallerAssumption == null) { |
221 |
| - CompilerDirectives.transferToInterpreterAndInvalidate(); |
222 |
| - resetNeedsCallerAssumption(); |
223 |
| - } |
224 |
| - try { |
225 |
| - needsCallerAssumption.check(); |
226 |
| - } catch (InvalidAssumptionException e) { |
227 |
| - CompilerDirectives.transferToInterpreterAndInvalidate(); |
228 |
| - resetNeedsCallerAssumption(); |
229 |
| - } |
230 |
| - |
231 |
| - if (sendsVariables == SendsFrame.NO_FRAME && sendsFrame == SendsFrame.NO_FRAME) { |
232 |
| - return null; |
233 |
| - } else if (sendsVariables == SendsFrame.MY_FRAME && sendsFrame == SendsFrame.NO_FRAME) { |
234 |
| - return readMyVariables.execute(frame); |
235 |
| - } else if (sendsVariables == SendsFrame.NO_FRAME && sendsFrame == SendsFrame.MY_FRAME) { |
236 |
| - return frame.materialize(); |
237 |
| - } else if (sendsVariables == SendsFrame.MY_FRAME && sendsFrame == SendsFrame.MY_FRAME) { |
238 |
| - return new FrameAndVariables(readMyVariables.execute(frame), frame.materialize()); |
239 |
| - } else if ((sendsVariables == SendsFrame.CALLER_FRAME && sendsFrame == SendsFrame.MY_FRAME) || |
240 |
| - (sendsVariables == SendsFrame.MY_FRAME && sendsFrame == SendsFrame.CALLER_FRAME)) { |
241 |
| - CompilerDirectives.transferToInterpreterAndInvalidate(); |
242 |
| - CompilerDirectives.shouldNotReachHere(); |
| 79 | + if (readingNode == null) { |
243 | 80 | return null;
|
244 | 81 | } else {
|
245 |
| - return readCaller.execute(frame); |
| 82 | + return readingNode.execute(frame); |
246 | 83 | }
|
247 | 84 | }
|
248 | 85 |
|
|
0 commit comments