OLD | NEW |
---|---|
(Empty) | |
1 // Copyright (c) 2014, the Dart project authors. Please see the AUTHORS file | |
2 // for details. All rights reserved. Use of this source code is governed by a | |
3 // BSD-style license that can be found in the LICENSE file. | |
4 | |
5 #include "vm/ast_transformer.h" | |
6 | |
7 #include "vm/parser.h" | |
8 | |
9 namespace dart { | |
10 | |
11 // Quick access to the locally defined isolate() method. | |
12 #define I (isolate()) | |
13 | |
14 // Nodes that are unreachable from already parsed expressions. | |
15 #define FOR_EACH_UNREACHABLE_NODE(V) \ | |
16 V(Case) \ | |
17 V(CatchClause) \ | |
18 V(CloneContext) \ | |
19 V(ClosureCall) \ | |
20 V(DoWhile) \ | |
21 V(If) \ | |
22 V(InlinedFinally) \ | |
23 V(For) \ | |
24 V(Jump) \ | |
25 V(LoadInstanceField) \ | |
26 V(NativeBody) \ | |
27 V(Primary) \ | |
28 V(Return) \ | |
29 V(Sequence) \ | |
30 V(StoreInstanceField) \ | |
31 V(Switch) \ | |
32 V(TryCatch) \ | |
33 V(While) | |
34 | |
35 #define DEFINE_UNREACHABLE(BaseName) \ | |
36 void AwaitTransformer::Visit##BaseName##Node(BaseName##Node* node) { \ | |
37 UNREACHABLE(); \ | |
38 } | |
39 | |
40 FOR_EACH_UNREACHABLE_NODE(DEFINE_UNREACHABLE) | |
41 #undef DEFINE_UNREACHABLE | |
42 | |
43 | |
44 void AwaitTransformer::Transform(AstNode* expr) { | |
45 expr->Visit(this); | |
46 } | |
47 | |
48 | |
49 LocalVariable* AwaitTransformer::EnsureCurrentTempVar() { | |
50 const char* await_temp_prefix = ":await_temp_var_"; | |
51 const String& cnt_str = String::Handle( | |
srdjan
2014/08/13 18:41:05
I,
Michael Lippautz (Google)
2014/08/13 20:32:23
Done.
| |
52 String::NewFormatted( | |
53 "%s%" Pd "", await_temp_prefix, temp_cnt_)); | |
54 const String& symbol = String::ZoneHandle(I, Symbols::New(cnt_str)); | |
55 ASSERT(!symbol.IsNull()); | |
56 LocalVariable* await_tmp = | |
57 parsed_function_->await_temps_scope()->LookupVariable(symbol, false); | |
58 if (await_tmp == NULL) { | |
59 await_tmp = new(I) LocalVariable( | |
60 Scanner::kNoSourcePos, | |
61 symbol, | |
62 Type::ZoneHandle(I, Type::DynamicType())); | |
63 parsed_function_->await_temps_scope()->AddVariable(await_tmp); | |
64 } | |
65 return await_tmp; | |
66 } | |
67 | |
68 | |
69 LocalVariable* AwaitTransformer::AddToPreambleNewTempVar(AstNode* node) { | |
70 LocalVariable* tmp_var = EnsureCurrentTempVar(); | |
71 preamble_->Add(new(I) StoreLocalNode(Scanner::kNoSourcePos, tmp_var, node)); | |
72 NextTempVar(); | |
73 return tmp_var; | |
74 } | |
75 | |
76 | |
77 void AwaitTransformer::VisitLiteralNode(LiteralNode* node) { | |
78 result_ = node; | |
79 } | |
80 | |
81 | |
82 void AwaitTransformer::VisitTypeNode(TypeNode* node) { | |
83 result_ = new(I) TypeNode(node->token_pos(), node->type()); | |
84 } | |
85 | |
86 | |
87 | |
88 void AwaitTransformer::VisitAwaitNode(AwaitNode* node) { | |
89 // Await transformation: | |
90 // | |
91 // :await_temp_var_X = <expr>; | |
92 // :result_param = :await_temp_var_X; | |
93 // if (:result_param is Future) { | |
94 // // :result_param.then(:async_op); | |
95 // } | |
96 // :await_temp_var_(X+1) = :result_param; | |
97 | |
98 LocalVariable* async_op = preamble_->scope()->LookupVariable( | |
99 Symbols::AsyncOperation(), false); | |
100 ASSERT(async_op != NULL); | |
101 LocalVariable* result_param = preamble_->scope()->LookupVariable( | |
102 Symbols::AsyncOperationParam(), false); | |
103 ASSERT(result_param != NULL); | |
104 | |
105 node->expr()->Visit(this); | |
106 preamble_->Add(new(I) StoreLocalNode(Scanner::kNoSourcePos, | |
107 result_param, | |
108 result_)); | |
109 LoadLocalNode* load_result_param = new(I) LoadLocalNode( | |
110 Scanner::kNoSourcePos, result_param); | |
111 SequenceNode* is_future_branch = new(I) SequenceNode( | |
112 Scanner::kNoSourcePos, preamble_->scope()); | |
113 ArgumentListNode* args = new(I) ArgumentListNode(Scanner::kNoSourcePos); | |
114 args->Add(new(I) LoadLocalNode(Scanner::kNoSourcePos, async_op)); | |
115 // TODO(mlippautz): Once continuations are supported, just call .then(). | |
116 // is_future_branch->Add(new(I) InstanceCallNode( | |
117 // Scanner::kNoSourcePos, load_result_param, Symbols::FutureThen(), args)); | |
118 // | |
119 // For now, throw an exception. | |
120 const String& exception = String::ZoneHandle( | |
121 I, String::New("awaitable futures not yet supported", Heap::kOld)); | |
122 is_future_branch->Add(new(I) ThrowNode( | |
123 Scanner::kNoSourcePos, | |
124 new(I) LiteralNode( | |
125 Scanner::kNoSourcePos, | |
126 String::ZoneHandle(I, Symbols::New(exception))), | |
127 NULL)); | |
128 const Class& cls = Class::Handle(library_.LookupClass(Symbols::Future())); | |
srdjan
2014/08/13 18:41:06
I,
Michael Lippautz (Google)
2014/08/13 20:32:23
Done.
| |
129 const AbstractType& future_type = AbstractType::ZoneHandle(I, | |
130 cls.RareType()); | |
131 ASSERT(!future_type.IsNull()); | |
132 TypeNode* future_type_node = new(I) TypeNode( | |
133 Scanner::kNoSourcePos, future_type); | |
134 IfNode* is_future_if = new(I) IfNode( | |
135 Scanner::kNoSourcePos, | |
136 new(I) ComparisonNode(Scanner::kNoSourcePos, | |
137 Token::kIS, | |
138 load_result_param, | |
139 future_type_node), | |
140 is_future_branch, | |
141 NULL); | |
142 preamble_->Add(is_future_if); | |
143 | |
144 // TODO(mlippautz): Join for await needs to happen here. | |
145 | |
146 LocalVariable* result = AddToPreambleNewTempVar(new(I) LoadLocalNode( | |
147 Scanner::kNoSourcePos, result_param)); | |
148 result_ = new(I) LoadLocalNode(Scanner::kNoSourcePos, result); | |
149 } | |
150 | |
151 | |
152 // Transforms boolean expressions into a sequence of evaluatons that only lazily | |
153 // evaluate subexpressions. | |
154 // | |
155 // Example: | |
156 // | |
157 // (a || b) only evaluates b if a is true | |
158 // | |
159 // Transformation (roughly): | |
160 // | |
161 // t_1 = a; | |
162 // if (t_1) { | |
163 // t_2 = b; | |
164 // } | |
165 // t_3 = t_1 || t_2; // Compiler takes care that lazy evaluation takes place | |
166 // on this level. | |
167 AstNode* AwaitTransformer::LazyTransform(const Token::Kind logical_op, | |
168 AstNode* new_left, | |
169 AstNode* right) { | |
170 ASSERT(logical_op == Token::kAND || logical_op == Token::kOR); | |
171 AstNode* result = NULL; | |
172 const Token::Kind compare_logical_op = (logical_op == Token::kAND) ? | |
173 Token::kEQ : Token::kNE; | |
174 SequenceNode* eval = new SequenceNode( | |
srdjan
2014/08/13 18:41:06
new (I)
Michael Lippautz (Google)
2014/08/13 20:32:23
Done.
| |
175 Scanner::kNoSourcePos, preamble_->scope()); | |
176 SequenceNode* saved_preamble = preamble_; | |
177 preamble_ = eval; | |
178 right->Visit(this); | |
179 result = result_; | |
180 preamble_ = saved_preamble; | |
181 IfNode* right_body = new(I) IfNode( | |
182 Scanner::kNoSourcePos, | |
183 new(I) ComparisonNode( | |
184 Scanner::kNoSourcePos, | |
185 compare_logical_op, | |
186 new_left, | |
187 new(I) LiteralNode(Scanner::kNoSourcePos, Bool::True())), | |
188 eval, | |
189 NULL); | |
190 preamble_->Add(right_body); | |
191 return result; | |
192 } | |
193 | |
194 | |
195 void AwaitTransformer::VisitBinaryOpNode(BinaryOpNode* node) { | |
196 node->left()->Visit(this); | |
197 AstNode* new_left = result_; | |
198 AstNode* new_right = NULL; | |
199 // Preserve lazy evaluaton. | |
200 if (node->kind() == Token::kAND || node->kind() == Token::kOR) { | |
srdjan
2014/08/13 18:41:05
Use more parentheses.
Michael Lippautz (Google)
2014/08/13 20:32:23
Done.
| |
201 new_right = LazyTransform(node->kind(), new_left, node->right()); | |
202 } else { | |
203 node->right()->Visit(this); | |
204 new_right = result_; | |
205 } | |
206 LocalVariable* result = AddToPreambleNewTempVar( | |
207 new(I) BinaryOpNode(node->token_pos(), | |
208 node->kind(), | |
209 new_left, | |
210 new_right)); | |
211 result_ = new(I) LoadLocalNode(Scanner::kNoSourcePos, result); | |
212 } | |
213 | |
214 | |
215 void AwaitTransformer::VisitBinaryOpWithMask32Node( | |
216 BinaryOpWithMask32Node* node) { | |
217 node->left()->Visit(this); | |
218 AstNode* new_left = result_; | |
219 AstNode* new_right = NULL; | |
220 // Preserve lazy evaluaton. | |
221 if (node->kind() == Token::kAND || node->kind() == Token::kOR) { | |
srdjan
2014/08/13 18:41:05
Use more parentheses
Michael Lippautz (Google)
2014/08/13 20:32:23
Done.
| |
222 new_right = LazyTransform(node->kind(), new_left, node->right()); | |
223 } else { | |
224 node->right()->Visit(this); | |
225 new_right = result_; | |
226 } | |
227 LocalVariable* result = AddToPreambleNewTempVar( | |
228 new(I) BinaryOpWithMask32Node(node->token_pos(), | |
229 node->kind(), | |
230 new_left, | |
231 new_right, | |
232 node->mask32())); | |
233 result_ = new(I) LoadLocalNode(Scanner::kNoSourcePos, result); | |
234 } | |
235 | |
236 | |
237 void AwaitTransformer::VisitComparisonNode(ComparisonNode* node) { | |
238 node->left()->Visit(this); | |
239 AstNode* new_left = result_; | |
240 node->right()->Visit(this); | |
241 AstNode* new_right = result_; | |
242 LocalVariable* result = AddToPreambleNewTempVar( | |
243 new(I) ComparisonNode(node->token_pos(), | |
244 node->kind(), | |
245 new_left, | |
246 new_right)); | |
247 result_ = new(I) LoadLocalNode(Scanner::kNoSourcePos, result); | |
248 } | |
249 | |
250 | |
251 void AwaitTransformer::VisitUnaryOpNode(UnaryOpNode* node) { | |
252 node->operand()->Visit(this); | |
253 AstNode* new_operand = result_; | |
254 | |
255 LocalVariable* result = AddToPreambleNewTempVar( | |
256 new(I) UnaryOpNode(node->token_pos(), | |
257 node->kind(), | |
258 new_operand)); | |
259 result_ = new(I) LoadLocalNode(Scanner::kNoSourcePos, result); | |
260 } | |
261 | |
srdjan
2014/08/13 18:41:05
two empty lines
Michael Lippautz (Google)
2014/08/13 20:32:23
Done.
| |
262 // ::= (<condition>) ? <true-branch> : <false-branch> | |
263 // | |
264 void AwaitTransformer::VisitConditionalExprNode(ConditionalExprNode* node) { | |
265 // TODO(mlippautz): Optimize variable assingments and branching. | |
srdjan
2014/08/13 18:41:06
s/assingments/assignments/
Michael Lippautz (Google)
2014/08/13 20:32:23
Got rid of the comment as it doesn't make too much
| |
266 node->condition()->Visit(this); | |
267 AstNode* new_condition = result_; | |
268 SequenceNode* new_true = new(I) SequenceNode( | |
269 Scanner::kNoSourcePos, preamble_->scope()); | |
270 SequenceNode* saved_preamble = preamble_; | |
271 preamble_ = new_true; | |
272 node->true_expr()->Visit(this); | |
273 AstNode* new_true_result = result_; | |
274 SequenceNode* new_false = new(I) SequenceNode( | |
275 Scanner::kNoSourcePos, preamble_->scope()); | |
276 preamble_ = new_false; | |
277 node->false_expr()->Visit(this); | |
278 AstNode* new_false_result = result_; | |
279 preamble_ = saved_preamble; | |
280 IfNode* new_if = new(I) IfNode(Scanner::kNoSourcePos, | |
281 new_condition, | |
282 new_true, | |
283 new_false); | |
284 preamble_->Add(new_if); | |
285 LocalVariable* result = AddToPreambleNewTempVar( | |
286 new(I) ConditionalExprNode(Scanner::kNoSourcePos, | |
287 new_condition, | |
288 new_true_result, | |
289 new_false_result)); | |
290 result_ = new(I) LoadLocalNode(Scanner::kNoSourcePos, result); | |
291 } | |
292 | |
293 | |
294 void AwaitTransformer::VisitArgumentListNode(ArgumentListNode* node) { | |
295 ArgumentListNode* new_args = new(I) ArgumentListNode(node->token_pos()); | |
296 for (intptr_t i = 0; i < node->length(); i++) { | |
297 node->NodeAt(i)->Visit(this); | |
298 new_args->Add(result_); | |
299 } | |
300 result_ = new_args; | |
301 } | |
302 | |
303 | |
304 void AwaitTransformer::VisitArrayNode(ArrayNode* node) { | |
305 GrowableArray<AstNode*> new_elements; | |
306 for (intptr_t i = 0; i < node->length(); i++) { | |
307 node->ElementAt(i)->Visit(this); | |
308 new_elements.Add(result_); | |
309 } | |
310 result_ = new(I) ArrayNode(node->token_pos(), node->type(), new_elements); | |
311 } | |
312 | |
313 | |
314 void AwaitTransformer::VisitStringInterpolateNode(StringInterpolateNode* node) { | |
315 node->value()->Visit(this); | |
316 ArrayNode* new_value = result_->AsArrayNode(); | |
317 LocalVariable* result = AddToPreambleNewTempVar( | |
318 new(I) StringInterpolateNode(node->token_pos(), | |
319 new_value)); | |
320 result_ = new(I) LoadLocalNode(Scanner::kNoSourcePos, result); | |
321 } | |
322 | |
323 | |
324 void AwaitTransformer::VisitClosureNode(ClosureNode* node) { | |
325 AstNode* new_receiver = node->receiver(); | |
326 if (new_receiver != NULL) { | |
327 new_receiver->Visit(this); | |
328 new_receiver = result_; | |
329 } | |
330 LocalVariable* result = AddToPreambleNewTempVar( | |
331 new(I) ClosureNode(node->token_pos(), | |
332 node->function(), | |
333 new_receiver, | |
334 node->scope())); | |
335 result_ = new(I) LoadLocalNode(Scanner::kNoSourcePos, result); | |
336 } | |
337 | |
338 | |
339 void AwaitTransformer::VisitInstanceCallNode(InstanceCallNode* node) { | |
340 node->receiver()->Visit(this); | |
341 AstNode* new_receiver = result_; | |
342 node->arguments()->Visit(this); | |
343 ArgumentListNode* new_args = result_->AsArgumentListNode(); | |
344 | |
345 LocalVariable* result = AddToPreambleNewTempVar( | |
346 new(I) InstanceCallNode(node->token_pos(), | |
347 new_receiver, | |
348 node->function_name(), | |
349 new_args)); | |
350 result_ = new(I) LoadLocalNode(Scanner::kNoSourcePos, result); | |
351 } | |
352 | |
353 | |
354 void AwaitTransformer::VisitStaticCallNode(StaticCallNode* node) { | |
355 node->arguments()->Visit(this); | |
356 ArgumentListNode* new_args = result_->AsArgumentListNode(); | |
357 | |
358 LocalVariable* result = AddToPreambleNewTempVar( | |
359 new(I) StaticCallNode(node->token_pos(), | |
360 node->function(), | |
361 new_args)); | |
362 result_ = new(I) LoadLocalNode(Scanner::kNoSourcePos, result); | |
363 } | |
364 | |
365 | |
366 void AwaitTransformer::VisitConstructorCallNode(ConstructorCallNode* node) { | |
367 node->arguments()->Visit(this); | |
368 ArgumentListNode* new_args = result_->AsArgumentListNode(); | |
369 | |
370 LocalVariable* result = AddToPreambleNewTempVar( | |
371 new(I) ConstructorCallNode(node->token_pos(), | |
372 node->type_arguments(), | |
373 node->constructor(), | |
374 new_args)); | |
375 result_ = new(I) LoadLocalNode(Scanner::kNoSourcePos, result); | |
376 } | |
377 | |
378 | |
379 void AwaitTransformer::VisitInstanceGetterNode(InstanceGetterNode* node) { | |
380 node->receiver()->Visit(this); | |
381 AstNode* new_receiver = result_; | |
382 | |
383 LocalVariable* result = AddToPreambleNewTempVar( | |
384 new(I) InstanceGetterNode(node->token_pos(), | |
385 new_receiver, | |
386 node->field_name())); | |
387 result_ = new(I) LoadLocalNode(Scanner::kNoSourcePos, result); | |
388 } | |
389 | |
390 | |
391 void AwaitTransformer::VisitInstanceSetterNode(InstanceSetterNode* node) { | |
392 AstNode* new_receiver = node->receiver(); | |
393 if (new_receiver != NULL) { | |
394 new_receiver->Visit(this); | |
395 new_receiver = result_; | |
396 } | |
397 | |
398 node->value()->Visit(this); | |
399 AstNode* new_value = result_; | |
400 | |
401 LocalVariable* result = AddToPreambleNewTempVar( | |
402 new(I) InstanceSetterNode(node->token_pos(), | |
403 new_receiver, | |
404 node->field_name(), | |
405 new_value)); | |
406 result_ = new(I) LoadLocalNode(Scanner::kNoSourcePos, result); | |
407 } | |
408 | |
409 | |
410 void AwaitTransformer::VisitStaticGetterNode(StaticGetterNode* node) { | |
411 AstNode* new_receiver = node->receiver(); | |
412 if (new_receiver != NULL) { | |
413 new_receiver->Visit(this); | |
414 new_receiver = result_; | |
415 } | |
416 | |
417 LocalVariable* result = AddToPreambleNewTempVar( | |
418 new(I) StaticGetterNode(node->token_pos(), | |
419 new_receiver, | |
420 node->is_super_getter(), | |
421 node->cls(), | |
422 node->field_name())); | |
423 result_ = new(I) LoadLocalNode(Scanner::kNoSourcePos, result); | |
424 } | |
425 | |
426 | |
427 void AwaitTransformer::VisitStaticSetterNode(StaticSetterNode* node) { | |
428 AstNode* new_receiver = node->receiver(); | |
429 if (new_receiver != NULL) { | |
430 new_receiver->Visit(this); | |
431 new_receiver = result_; | |
432 } | |
433 node->value()->Visit(this); | |
434 AstNode* new_value = result_; | |
435 | |
436 LocalVariable* result = AddToPreambleNewTempVar( | |
437 new(I) StaticSetterNode(node->token_pos(), | |
438 new_receiver, | |
439 node->cls(), | |
440 node->field_name(), | |
441 new_value)); | |
442 result_ = new(I) LoadLocalNode(Scanner::kNoSourcePos, result); | |
443 } | |
444 | |
445 | |
446 void AwaitTransformer::VisitLoadLocalNode(LoadLocalNode* node) { | |
447 LocalVariable* result = AddToPreambleNewTempVar( | |
448 new(I) LoadLocalNode(node->token_pos(), &node->local())); | |
449 result_ = new(I) LoadLocalNode(Scanner::kNoSourcePos, result); | |
450 } | |
451 | |
452 | |
453 void AwaitTransformer::VisitStoreLocalNode(StoreLocalNode* node) { | |
454 node->value()->Visit(this); | |
455 AstNode* new_value = result_; | |
456 LocalVariable* result = AddToPreambleNewTempVar( | |
457 new(I) StoreLocalNode(node->token_pos(), | |
458 &node->local(), | |
459 new_value)); | |
460 result_ = new(I) LoadLocalNode(Scanner::kNoSourcePos, result); | |
461 } | |
462 | |
463 | |
464 void AwaitTransformer::VisitLoadStaticFieldNode(LoadStaticFieldNode* node) { | |
465 LocalVariable* result = AddToPreambleNewTempVar( | |
466 new(I) LoadStaticFieldNode(node->token_pos(), | |
467 node->field())); | |
468 result_ = new(I) LoadLocalNode(Scanner::kNoSourcePos, result); | |
469 } | |
470 | |
471 | |
472 void AwaitTransformer::VisitStoreStaticFieldNode(StoreStaticFieldNode* node) { | |
473 node->value()->Visit(this); | |
474 AstNode* new_value = result_; | |
475 LocalVariable* result = AddToPreambleNewTempVar( | |
476 new(I) StoreStaticFieldNode(node->token_pos(), | |
477 node->field(), | |
478 new_value)); | |
479 result_ = new(I) LoadLocalNode(Scanner::kNoSourcePos, result); | |
480 } | |
481 | |
482 | |
483 void AwaitTransformer::VisitLoadIndexedNode(LoadIndexedNode* node) { | |
484 node->array()->Visit(this); | |
485 AstNode* new_array = result_; | |
486 node->index_expr()->Visit(this); | |
487 AstNode* new_index = result_; | |
488 LocalVariable* result = AddToPreambleNewTempVar( | |
489 new(I) LoadIndexedNode(node->token_pos(), | |
490 new_array, | |
491 new_index, | |
492 node->super_class())); | |
493 result_ = new(I) LoadLocalNode(Scanner::kNoSourcePos, result); | |
494 } | |
495 | |
496 | |
497 void AwaitTransformer::VisitStoreIndexedNode(StoreIndexedNode* node) { | |
498 node->array()->Visit(this); | |
499 AstNode* new_array = result_; | |
500 node->index_expr()->Visit(this); | |
501 AstNode* new_index = result_; | |
502 node->value()->Visit(this); | |
503 AstNode* new_value = result_; | |
504 LocalVariable* result = AddToPreambleNewTempVar( | |
505 new(I) StoreIndexedNode(node->token_pos(), | |
506 new_array, | |
507 new_index, | |
508 new_value, | |
509 node->super_class())); | |
510 result_ = new(I) LoadLocalNode(Scanner::kNoSourcePos, result); | |
511 } | |
512 | |
513 | |
514 void AwaitTransformer::VisitAssignableNode(AssignableNode* node) { | |
515 node->expr()->Visit(this); | |
516 AstNode* new_expr = result_; | |
517 LocalVariable* result = AddToPreambleNewTempVar( | |
518 new(I) AssignableNode(node->token_pos(), | |
519 new_expr, | |
520 node->type(), | |
521 node->dst_name())); | |
522 result_ = new(I) LoadLocalNode(Scanner::kNoSourcePos, result); | |
523 } | |
524 | |
525 | |
526 void AwaitTransformer::VisitLetNode(LetNode* node) { | |
527 // TODO(mlippautz): Check initializers and their temps. | |
528 LetNode* result = new(I) LetNode(node->token_pos()); | |
529 for (intptr_t i = 0; i < node->nodes().length(); i++) { | |
530 node->nodes()[i]->Visit(this); | |
531 result->AddNode(result_); | |
532 } | |
533 result_ = result; | |
534 } | |
535 | |
536 | |
537 void AwaitTransformer::VisitThrowNode(ThrowNode* node) { | |
538 // TODO(mlippautz): Check if relevant. | |
539 node->exception()->Visit(this); | |
540 AstNode* new_exception = result_; | |
541 node->stacktrace()->Visit(this); | |
542 AstNode* new_stacktrace = result_; | |
543 result_ = new(I) ThrowNode(node->token_pos(), | |
544 new_exception, | |
545 new_stacktrace); | |
546 } | |
547 | |
548 } // namespace dart | |
OLD | NEW |