@@ -6,6 +6,7 @@ import 'package:analyzer/dart/element/type.dart';
6
6
import 'package:analyzer/src/wolf/ir/call_descriptor.dart' ;
7
7
import 'package:analyzer/src/wolf/ir/coded_ir.dart' ;
8
8
import 'package:analyzer/src/wolf/ir/ir.dart' ;
9
+ import 'package:analyzer/src/wolf/ir/scope_analyzer.dart' ;
9
10
import 'package:meta/meta.dart' ;
10
11
11
12
/// Evaluates [ir] , passing in [args] , and returns the result.
@@ -22,14 +23,35 @@ import 'package:meta/meta.dart';
22
23
/// that an instruction sequence behaves as it's expected to.
23
24
@visibleForTesting
24
25
Object ? interpret (CodedIRContainer ir, List <Object ?> args,
25
- {required CallHandler Function (CallDescriptor ) callDispatcher}) {
26
- return _IRInterpreter (ir, callDispatcher: callDispatcher).run (args);
26
+ {required Scopes scopes, required CallDispatcher callDispatcher}) {
27
+ return _IRInterpreter (ir, scopes: scopes, callDispatcher: callDispatcher)
28
+ .run (args);
27
29
}
28
30
29
31
/// Function type invoked by [interpret] to execute a `call` instruction.
30
32
typedef CallHandler = Object ? Function (
31
33
List <Object ?> positionalArguments, Map <String , Object ?> namedArguments);
32
34
35
+ /// Interface used by [interpret] to query the behavior of calls to external
36
+ /// code.
37
+ abstract interface class CallDispatcher {
38
+ /// Evaluates a call to `operator==` , using virtual dispatch on [firstValue] ,
39
+ /// and passing [secondValue] as the parameter to `operator==` .
40
+ ///
41
+ /// In accordance with Dart semantics, this method is only called if both
42
+ /// [firstValue] and [secondValue] are non-null.
43
+ bool equals (Object firstValue, Object secondValue);
44
+
45
+ /// Looks up the function that can be used to evaluate calls to
46
+ /// [callDescriptor] .
47
+ ///
48
+ /// The interpreter may invoke this method for any [CallDescriptor] in the
49
+ /// IR's call descriptor table (whether or not it's invoked), and it may cache
50
+ /// the results. However, it is guaranteed to call the [CallHandler] exactly
51
+ /// once for each `call` instruction that is interpreted.
52
+ CallHandler lookupCallDescriptor (CallDescriptor callDescriptor);
53
+ }
54
+
33
55
/// Interpreter representation of a heap object.
34
56
///
35
57
/// This class should not be used for the types [int] , [double] , [String] , or
@@ -62,29 +84,101 @@ class SoundnessError extends Error {
62
84
'Soundness error at $address ($instructionString ): $message ' ;
63
85
}
64
86
87
+ /// An entry on the control flow stack, representing a control flow construct
88
+ /// (such as a `block` ) that the interpreter is currently executing.
89
+ class _ControlFlowStackEntry {
90
+ /// The index into [_IRInterpreter.stack] before the first input to control
91
+ /// flow construct.
92
+ ///
93
+ /// This is called a "fence" because it represents the dividing line between
94
+ /// stack values that belong to the instructions inside the control flow
95
+ /// construct and stack values that belong to the instructions outside the
96
+ /// control flow construct. If a branch instruction targets the control flow
97
+ /// construct, this helps to determine which stack values should be discarded
98
+ /// (see [outputCount] ).
99
+ final int stackFence;
100
+
101
+ /// The length of [_IRInterpreter.locals] at the time the control flow
102
+ /// construct was entered.
103
+ ///
104
+ /// This is called a "fence" because it represents the dividing line between
105
+ /// locals that belong to the instructions inside the control flow construct
106
+ /// and locals that belong to the instructions outside the control flow
107
+ /// construct. If a branch instruction targets the control flow construct,
108
+ /// then locals whose index is greater than equal to this value will
109
+ /// automatically be released.
110
+ final int localFence;
111
+
112
+ /// The number of outputs of the control flow construct.
113
+ ///
114
+ /// If a branch instruction targets the control flow construct, this is the
115
+ /// number of entries at the top of [_IRInterpreter.stack] that will remain on
116
+ /// the stack after the branch is taken. Any other stack entries belonging to
117
+ /// the instructions inside the control flow construct will be discarded (see
118
+ /// [stackFence] ).
119
+ final int outputCount;
120
+
121
+ /// The scope index (as defined by [Scopes] ) corresponding to the instructions
122
+ /// that delimit the control flow construct.
123
+ final int scope;
124
+
125
+ _ControlFlowStackEntry (
126
+ {required this .stackFence,
127
+ required this .localFence,
128
+ required this .outputCount,
129
+ required this .scope});
130
+ }
131
+
65
132
class _IRInterpreter {
133
+ static const keepGoing = _KeepGoing ();
66
134
final CodedIRContainer ir;
135
+ final Scopes scopes;
136
+ final CallDispatcher callDispatcher;
67
137
final List <CallHandler > callHandlers;
68
138
final stack = < Object ? > [];
69
139
final locals = < _LocalSlot > [];
140
+ final controlFlowStack = < _ControlFlowStackEntry > [];
70
141
var address = 1 ;
71
142
72
- _IRInterpreter (this .ir,
73
- {required CallHandler Function (CallDescriptor ) callDispatcher})
74
- : callHandlers = ir.mapCallDescriptors (callDispatcher);
143
+ /// The scope index (as defined by [Scopes] ) corresponding to the last begin
144
+ /// instruction preceding [address] .
145
+ var mostRecentScope = 0 ;
146
+
147
+ _IRInterpreter (this .ir, {required this .scopes, required this .callDispatcher})
148
+ : callHandlers =
149
+ ir.mapCallDescriptors (callDispatcher.lookupCallDescriptor);
75
150
76
151
/// Performs the necessary logic for a `br` , `brIf` , or `brIndex` instruction.
77
152
///
78
153
/// [nesting] indicates which enclosing control flow construct is targeted by
79
154
/// the branch (where 0 means the innermost).
80
155
///
81
- /// The returned value is the value that should be returned to the caller.
156
+ /// The returned value is either:
157
+ /// - [keepGoing] , indicating that interpretation should continue from the
158
+ /// instruction following [address] , or
159
+ /// - Some other value, indicating that the code being interpreted has
160
+ /// finished executing, and this value should be returned to the caller.
82
161
Object ? branch (int nesting) {
83
- if (nesting != 0 ) {
84
- throw UnimplementedError ('TODO(paulberry): nonzero branch nesting' );
162
+ while (nesting-- > 0 ) {
163
+ controlFlowStack.removeLast ();
164
+ }
165
+ if (controlFlowStack.isNotEmpty) {
166
+ var stackEntry = controlFlowStack.removeLast ();
167
+ var stackFence = stackEntry.stackFence;
168
+ var outputCount = stackEntry.outputCount;
169
+ var newStackLength = stackFence + outputCount;
170
+ stack.setRange (stackFence, newStackLength, stack,
171
+ stack.length - stackEntry.outputCount);
172
+ stack.length = stackFence + outputCount;
173
+ locals.length = stackEntry.localFence;
174
+ var scope = stackEntry.scope;
175
+ address = scopes.endAddress (scope);
176
+ mostRecentScope = scopes.lastDescendant (scope);
177
+ return keepGoing;
178
+ } else {
179
+ // Branch targets the function, so return from the code being interpreted.
180
+ return stack.last;
85
181
}
86
- // Branch targets the function, so return from the code being interpreted.
87
- return stack.last;
88
182
}
89
183
90
184
Object ? run (List <Object ?> args) {
@@ -98,15 +192,37 @@ class _IRInterpreter {
98
192
}
99
193
stack.addAll (args);
100
194
while (true ) {
195
+ assert (scopes.mostRecentScope (address - 1 ) == mostRecentScope);
101
196
switch (ir.opcodeAt (address)) {
102
197
case Opcode .alloc:
103
198
var count = Opcode .alloc.decodeCount (ir, address);
104
199
for (var i = 0 ; i < count; i++ ) {
105
200
locals.add (_LocalSlot ());
106
201
}
202
+ case Opcode .block:
203
+ var inputCount = Opcode .block.decodeInputCount (ir, address);
204
+ var outputCount = Opcode .block.decodeOutputCount (ir, address);
205
+ var scope = ++ mostRecentScope;
206
+ assert (scopes.beginAddress (scope) == address);
207
+ controlFlowStack.add (_ControlFlowStackEntry (
208
+ stackFence: stack.length - inputCount,
209
+ localFence: locals.length,
210
+ outputCount: outputCount,
211
+ scope: scope));
107
212
case Opcode .br:
108
213
var nesting = Opcode .br.decodeNesting (ir, address);
109
- return branch (nesting);
214
+ var result = branch (nesting);
215
+ if (! identical (result, keepGoing)) {
216
+ return result;
217
+ }
218
+ case Opcode .brIf:
219
+ var nesting = Opcode .brIf.decodeNesting (ir, address);
220
+ if (stack.removeLast () as bool ) {
221
+ var result = branch (nesting);
222
+ if (! identical (result, keepGoing)) {
223
+ return result;
224
+ }
225
+ }
110
226
case Opcode .call:
111
227
var argumentNames = ir.decodeArgumentNames (
112
228
Opcode .call.decodeArgumentNames (ir, address));
@@ -130,8 +246,27 @@ class _IRInterpreter {
130
246
case Opcode .dup:
131
247
stack.add (stack.last);
132
248
case Opcode .end:
133
- assert (stack.length == 1 );
134
- return stack.last;
249
+ if (controlFlowStack.isEmpty) {
250
+ assert (stack.length == 1 );
251
+ return stack.last;
252
+ } else {
253
+ var stackEntry = controlFlowStack.last;
254
+ assert (
255
+ stack.length == stackEntry.stackFence + stackEntry.outputCount);
256
+ assert (locals.length == stackEntry.localFence);
257
+ // Continue with the code following the block.
258
+ controlFlowStack.removeLast ();
259
+ }
260
+ case Opcode .eq:
261
+ var secondValue = stack.removeLast ();
262
+ var firstValue = stack.removeLast ();
263
+ if (firstValue == null ) {
264
+ stack.add (null == secondValue);
265
+ } else if (secondValue == null ) {
266
+ stack.add (false );
267
+ } else {
268
+ stack.add (callDispatcher.equals (firstValue, secondValue));
269
+ }
135
270
case Opcode .literal:
136
271
var value = Opcode .literal.decodeValue (ir, address);
137
272
stack.add (ir.decodeLiteral (value));
@@ -173,6 +308,12 @@ class _IRInterpreter {
173
308
message: message);
174
309
}
175
310
311
+ /// Sentinel value used by [_IRInterpreter.branch] to indicate that the
312
+ /// interpreter should keep executing instructions.
313
+ class _KeepGoing {
314
+ const _KeepGoing ();
315
+ }
316
+
176
317
/// Storage for a single local variable.
177
318
class _LocalSlot {
178
319
/// The contents of the local variable, or [_NoValue] if the slot is empty.
0 commit comments