| OLD | NEW |
| 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2012, the Dart project authors. Please see the AUTHORS file |
| 2 // for details. All rights reserved. Use of this source code is governed by a | 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. | 3 // BSD-style license that can be found in the LICENSE file. |
| 4 | 4 |
| 5 #include "vm/code_generator.h" | 5 #include "vm/code_generator.h" |
| 6 | 6 |
| 7 #include "vm/assembler_macros.h" | 7 #include "vm/assembler_macros.h" |
| 8 #include "vm/ast.h" | 8 #include "vm/ast.h" |
| 9 #include "vm/code_patcher.h" | 9 #include "vm/code_patcher.h" |
| 10 #include "vm/compiler.h" | 10 #include "vm/compiler.h" |
| (...skipping 130 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 141 static intptr_t GetCallerLocation() { | 141 static intptr_t GetCallerLocation() { |
| 142 DartFrameIterator iterator; | 142 DartFrameIterator iterator; |
| 143 StackFrame* caller_frame = iterator.NextFrame(); | 143 StackFrame* caller_frame = iterator.NextFrame(); |
| 144 ASSERT(caller_frame != NULL); | 144 ASSERT(caller_frame != NULL); |
| 145 const Code& code = Code::Handle(caller_frame->LookupDartCode()); | 145 const Code& code = Code::Handle(caller_frame->LookupDartCode()); |
| 146 const PcDescriptors& descriptors = | 146 const PcDescriptors& descriptors = |
| 147 PcDescriptors::Handle(code.pc_descriptors()); | 147 PcDescriptors::Handle(code.pc_descriptors()); |
| 148 ASSERT(!descriptors.IsNull()); | 148 ASSERT(!descriptors.IsNull()); |
| 149 for (int i = 0; i < descriptors.Length(); i++) { | 149 for (int i = 0; i < descriptors.Length(); i++) { |
| 150 if (static_cast<uword>(descriptors.PC(i)) == caller_frame->pc()) { | 150 if (static_cast<uword>(descriptors.PC(i)) == caller_frame->pc()) { |
| 151 return descriptors.TokenIndex(i); | 151 return descriptors.TokenPos(i); |
| 152 } | 152 } |
| 153 } | 153 } |
| 154 return -1; | 154 return -1; |
| 155 } | 155 } |
| 156 | 156 |
| 157 | 157 |
| 158 // Allocate a new object of a generic type and check that the instantiated type | 158 // Allocate a new object of a generic type and check that the instantiated type |
| 159 // arguments are within the declared bounds or throw a dynamic type error. | 159 // arguments are within the declared bounds or throw a dynamic type error. |
| 160 // Arg0: class of the object that needs to be allocated. | 160 // Arg0: class of the object that needs to be allocated. |
| 161 // Arg1: type arguments of the object that needs to be allocated. | 161 // Arg1: type arguments of the object that needs to be allocated. |
| (...skipping 265 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 427 | 427 |
| 428 | 428 |
| 429 // This updates the type test cache, an array containing 4-value elements | 429 // This updates the type test cache, an array containing 4-value elements |
| 430 // (instance class, instance type arguments, instantiator type arguments and | 430 // (instance class, instance type arguments, instantiator type arguments and |
| 431 // test_result). It can be applied to classes with type arguments in which | 431 // test_result). It can be applied to classes with type arguments in which |
| 432 // case it contains just the result of the class subtype test, not including | 432 // case it contains just the result of the class subtype test, not including |
| 433 // the evaluation of type arguments. | 433 // the evaluation of type arguments. |
| 434 // This operation is currently very slow (lookup of code is not efficient yet). | 434 // This operation is currently very slow (lookup of code is not efficient yet). |
| 435 // 'instantiator' can be null, in which case inst_targ | 435 // 'instantiator' can be null, in which case inst_targ |
| 436 static void UpdateTypeTestCache( | 436 static void UpdateTypeTestCache( |
| 437 intptr_t node_id, | |
| 438 const Instance& instance, | 437 const Instance& instance, |
| 439 const AbstractType& type, | 438 const AbstractType& type, |
| 440 const Instance& instantiator, | 439 const Instance& instantiator, |
| 441 const AbstractTypeArguments& incoming_instantiator_type_arguments, | 440 const AbstractTypeArguments& incoming_instantiator_type_arguments, |
| 442 const Bool& result, | 441 const Bool& result, |
| 443 const SubtypeTestCache& new_cache) { | 442 const SubtypeTestCache& new_cache) { |
| 444 // Since the test is expensive, don't do it unless necessary. | 443 // Since the test is expensive, don't do it unless necessary. |
| 445 // The list of disallowed cases will decrease as they are implemented in | 444 // The list of disallowed cases will decrease as they are implemented in |
| 446 // inlined assembly. | 445 // inlined assembly. |
| 447 if (new_cache.IsNull()) return; | 446 if (new_cache.IsNull()) return; |
| (...skipping 91 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 539 Class::Handle(test_type.type_class()).id(), | 538 Class::Handle(test_type.type_class()).id(), |
| 540 instantiator_type_arguments.raw(), | 539 instantiator_type_arguments.raw(), |
| 541 instantiator_type_arguments.ToCString(), | 540 instantiator_type_arguments.ToCString(), |
| 542 result.ToCString()); | 541 result.ToCString()); |
| 543 } | 542 } |
| 544 } | 543 } |
| 545 | 544 |
| 546 | 545 |
| 547 // Check that the given instance is an instance of the given type. | 546 // Check that the given instance is an instance of the given type. |
| 548 // Tested instance may not be null, because the null test is inlined. | 547 // Tested instance may not be null, because the null test is inlined. |
| 549 // Arg0: node id of the instanceof node. | 548 // Arg0: instance being checked. |
| 550 // Arg1: instance being checked. | 549 // Arg1: type. |
| 551 // Arg2: type. | 550 // Arg2: instantiator (or null). |
| 552 // Arg3: instantiator (or null). | 551 // Arg3: type arguments of the instantiator of the type. |
| 553 // Arg4: type arguments of the instantiator of the type. | 552 // Arg4: SubtypeTestCache. |
| 554 // Arg5: SubtypeTestCache. | |
| 555 // Return value: true or false, or may throw a type error in checked mode. | 553 // Return value: true or false, or may throw a type error in checked mode. |
| 556 DEFINE_RUNTIME_ENTRY(Instanceof, 6) { | 554 DEFINE_RUNTIME_ENTRY(Instanceof, 5) { |
| 557 ASSERT(arguments.Count() == kInstanceofRuntimeEntry.argument_count()); | 555 ASSERT(arguments.Count() == kInstanceofRuntimeEntry.argument_count()); |
| 558 intptr_t node_id = Smi::CheckedHandle(arguments.At(0)).Value(); | 556 const Instance& instance = Instance::CheckedHandle(arguments.At(0)); |
| 559 const Instance& instance = Instance::CheckedHandle(arguments.At(1)); | 557 const AbstractType& type = AbstractType::CheckedHandle(arguments.At(1)); |
| 560 const AbstractType& type = AbstractType::CheckedHandle(arguments.At(2)); | 558 const Instance& instantiator = Instance::CheckedHandle(arguments.At(2)); |
| 561 const Instance& instantiator = Instance::CheckedHandle(arguments.At(3)); | |
| 562 const AbstractTypeArguments& instantiator_type_arguments = | 559 const AbstractTypeArguments& instantiator_type_arguments = |
| 563 AbstractTypeArguments::CheckedHandle(arguments.At(4)); | 560 AbstractTypeArguments::CheckedHandle(arguments.At(3)); |
| 564 const SubtypeTestCache& cache = | 561 const SubtypeTestCache& cache = |
| 565 SubtypeTestCache::CheckedHandle(arguments.At(5)); | 562 SubtypeTestCache::CheckedHandle(arguments.At(4)); |
| 566 ASSERT(type.IsFinalized()); | 563 ASSERT(type.IsFinalized()); |
| 567 Error& malformed_error = Error::Handle(); | 564 Error& malformed_error = Error::Handle(); |
| 568 const Bool& result = Bool::Handle( | 565 const Bool& result = Bool::Handle( |
| 569 instance.IsInstanceOf(type, | 566 instance.IsInstanceOf(type, |
| 570 instantiator_type_arguments, | 567 instantiator_type_arguments, |
| 571 &malformed_error) ? | 568 &malformed_error) ? |
| 572 Bool::True() : Bool::False()); | 569 Bool::True() : Bool::False()); |
| 573 if (FLAG_trace_type_checks) { | 570 if (FLAG_trace_type_checks) { |
| 574 PrintTypeCheck("InstanceOf", | 571 PrintTypeCheck("InstanceOf", |
| 575 instance, type, instantiator_type_arguments, result); | 572 instance, type, instantiator_type_arguments, result); |
| 576 } | 573 } |
| 577 if (!result.value() && !malformed_error.IsNull()) { | 574 if (!result.value() && !malformed_error.IsNull()) { |
| 578 // Throw a dynamic type error only if the instanceof test fails. | 575 // Throw a dynamic type error only if the instanceof test fails. |
| 579 const intptr_t location = GetCallerLocation(); | 576 const intptr_t location = GetCallerLocation(); |
| 580 String& malformed_error_message = String::Handle( | 577 String& malformed_error_message = String::Handle( |
| 581 String::New(malformed_error.ToErrorCString())); | 578 String::New(malformed_error.ToErrorCString())); |
| 582 const String& no_name = String::Handle(Symbols::Empty()); | 579 const String& no_name = String::Handle(Symbols::Empty()); |
| 583 Exceptions::CreateAndThrowTypeError( | 580 Exceptions::CreateAndThrowTypeError( |
| 584 location, no_name, no_name, no_name, malformed_error_message); | 581 location, no_name, no_name, no_name, malformed_error_message); |
| 585 UNREACHABLE(); | 582 UNREACHABLE(); |
| 586 } | 583 } |
| 587 UpdateTypeTestCache(node_id, instance, type, instantiator, | 584 UpdateTypeTestCache(instance, type, instantiator, |
| 588 instantiator_type_arguments, result, cache); | 585 instantiator_type_arguments, result, cache); |
| 589 arguments.SetReturn(result); | 586 arguments.SetReturn(result); |
| 590 } | 587 } |
| 591 | 588 |
| 592 | 589 |
| 593 // Check that the type of the given instance is a subtype of the given type and | 590 // Check that the type of the given instance is a subtype of the given type and |
| 594 // can therefore be assigned. | 591 // can therefore be assigned. |
| 595 // Arg0: node-id of the assignment. | 592 // Arg0: instance being assigned. |
| 596 // Arg1: instance being assigned. | 593 // Arg1: type being assigned to. |
| 597 // Arg2: type being assigned to. | 594 // Arg2: instantiator (or null). |
| 598 // Arg3: instantiator (or null). | 595 // Arg3: type arguments of the instantiator of the type being assigned to. |
| 599 // Arg4: type arguments of the instantiator of the type being assigned to. | 596 // Arg4: name of variable being assigned to. |
| 600 // Arg5: name of variable being assigned to. | 597 // Arg5: SubtypeTestCache. |
| 601 // Arg6: SubtypeTestCache. | |
| 602 // Return value: instance if a subtype, otherwise throw a TypeError. | 598 // Return value: instance if a subtype, otherwise throw a TypeError. |
| 603 DEFINE_RUNTIME_ENTRY(TypeCheck, 7) { | 599 DEFINE_RUNTIME_ENTRY(TypeCheck, 6) { |
| 604 ASSERT(arguments.Count() == kTypeCheckRuntimeEntry.argument_count()); | 600 ASSERT(arguments.Count() == kTypeCheckRuntimeEntry.argument_count()); |
| 605 intptr_t node_id = Smi::CheckedHandle(arguments.At(0)).Value(); | 601 const Instance& src_instance = Instance::CheckedHandle(arguments.At(0)); |
| 606 const Instance& src_instance = Instance::CheckedHandle(arguments.At(1)); | 602 const AbstractType& dst_type = AbstractType::CheckedHandle(arguments.At(1)); |
| 607 const AbstractType& dst_type = AbstractType::CheckedHandle(arguments.At(2)); | 603 const Instance& dst_instantiator = Instance::CheckedHandle(arguments.At(2)); |
| 608 const Instance& dst_instantiator = Instance::CheckedHandle(arguments.At(3)); | |
| 609 const AbstractTypeArguments& instantiator_type_arguments = | 604 const AbstractTypeArguments& instantiator_type_arguments = |
| 610 AbstractTypeArguments::CheckedHandle(arguments.At(4)); | 605 AbstractTypeArguments::CheckedHandle(arguments.At(3)); |
| 611 const String& dst_name = String::CheckedHandle(arguments.At(5)); | 606 const String& dst_name = String::CheckedHandle(arguments.At(4)); |
| 612 const SubtypeTestCache& cache = | 607 const SubtypeTestCache& cache = |
| 613 SubtypeTestCache::CheckedHandle(arguments.At(6)); | 608 SubtypeTestCache::CheckedHandle(arguments.At(5)); |
| 614 ASSERT(!dst_type.IsDynamicType()); // No need to check assignment. | 609 ASSERT(!dst_type.IsDynamicType()); // No need to check assignment. |
| 615 ASSERT(!dst_type.IsMalformed()); // Already checked in code generator. | 610 ASSERT(!dst_type.IsMalformed()); // Already checked in code generator. |
| 616 ASSERT(!src_instance.IsNull()); // Already checked in inlined code. | 611 ASSERT(!src_instance.IsNull()); // Already checked in inlined code. |
| 617 | 612 |
| 618 Error& malformed_error = Error::Handle(); | 613 Error& malformed_error = Error::Handle(); |
| 619 const bool is_instance_of = src_instance.IsInstanceOf( | 614 const bool is_instance_of = src_instance.IsInstanceOf( |
| 620 dst_type, instantiator_type_arguments, &malformed_error); | 615 dst_type, instantiator_type_arguments, &malformed_error); |
| 621 | 616 |
| 622 if (FLAG_trace_type_checks) { | 617 if (FLAG_trace_type_checks) { |
| 623 PrintTypeCheck("TypeCheck", | 618 PrintTypeCheck("TypeCheck", |
| (...skipping 16 matching lines...) Expand all Loading... |
| 640 } | 635 } |
| 641 String& malformed_error_message = String::Handle(); | 636 String& malformed_error_message = String::Handle(); |
| 642 if (!malformed_error.IsNull()) { | 637 if (!malformed_error.IsNull()) { |
| 643 ASSERT(FLAG_enable_type_checks); | 638 ASSERT(FLAG_enable_type_checks); |
| 644 malformed_error_message = String::New(malformed_error.ToErrorCString()); | 639 malformed_error_message = String::New(malformed_error.ToErrorCString()); |
| 645 } | 640 } |
| 646 Exceptions::CreateAndThrowTypeError(location, src_type_name, dst_type_name, | 641 Exceptions::CreateAndThrowTypeError(location, src_type_name, dst_type_name, |
| 647 dst_name, malformed_error_message); | 642 dst_name, malformed_error_message); |
| 648 UNREACHABLE(); | 643 UNREACHABLE(); |
| 649 } | 644 } |
| 650 UpdateTypeTestCache(node_id, src_instance, dst_type, | 645 UpdateTypeTestCache(src_instance, dst_type, |
| 651 dst_instantiator, instantiator_type_arguments, | 646 dst_instantiator, instantiator_type_arguments, |
| 652 Bool::ZoneHandle(Bool::True()), cache); | 647 Bool::ZoneHandle(Bool::True()), cache); |
| 653 arguments.SetReturn(src_instance); | 648 arguments.SetReturn(src_instance); |
| 654 } | 649 } |
| 655 | 650 |
| 656 | 651 |
| 657 // Report that the type of the given object is not bool in conditional context. | 652 // Report that the type of the given object is not bool in conditional context. |
| 658 // Arg0: bad object. | 653 // Arg0: bad object. |
| 659 // Return value: none, throws a TypeError. | 654 // Return value: none, throws a TypeError. |
| 660 DEFINE_RUNTIME_ENTRY(ConditionTypeError, 1) { | 655 DEFINE_RUNTIME_ENTRY(ConditionTypeError, 1) { |
| (...skipping 733 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1394 } | 1389 } |
| 1395 | 1390 |
| 1396 | 1391 |
| 1397 static intptr_t GetDeoptInfo(const Code& code, uword pc) { | 1392 static intptr_t GetDeoptInfo(const Code& code, uword pc) { |
| 1398 const PcDescriptors& descriptors = | 1393 const PcDescriptors& descriptors = |
| 1399 PcDescriptors::Handle(code.pc_descriptors()); | 1394 PcDescriptors::Handle(code.pc_descriptors()); |
| 1400 ASSERT(!descriptors.IsNull()); | 1395 ASSERT(!descriptors.IsNull()); |
| 1401 // Locate deopt id at deoptimization point inside optimized code. | 1396 // Locate deopt id at deoptimization point inside optimized code. |
| 1402 for (int i = 0; i < descriptors.Length(); i++) { | 1397 for (int i = 0; i < descriptors.Length(); i++) { |
| 1403 if (static_cast<uword>(descriptors.PC(i)) == pc) { | 1398 if (static_cast<uword>(descriptors.PC(i)) == pc) { |
| 1404 return descriptors.NodeId(i); | 1399 return descriptors.DeoptId(i); |
| 1405 } | 1400 } |
| 1406 } | 1401 } |
| 1407 return Computation::kNoCid; | 1402 return Isolate::kNoDeoptId; |
| 1408 } | 1403 } |
| 1409 | 1404 |
| 1410 | 1405 |
| 1411 // Copy saved registers and caller's frame into temporary buffers. | 1406 // Copy saved registers and caller's frame into temporary buffers. |
| 1412 // Access the deopt information for the deoptimization point. | 1407 // Access the deopt information for the deoptimization point. |
| 1413 // Return the new stack size (including PC marker and deopt return address, | 1408 // Return the new stack size (including PC marker and deopt return address, |
| 1414 // excluding FP). | 1409 // excluding FP). |
| 1415 DEFINE_LEAF_RUNTIME_ENTRY(intptr_t, DeoptimizeCopyFrame, | 1410 DEFINE_LEAF_RUNTIME_ENTRY(intptr_t, DeoptimizeCopyFrame, |
| 1416 intptr_t deopt_reason, | 1411 intptr_t deopt_reason, |
| 1417 intptr_t* saved_registers_address) { | 1412 intptr_t* saved_registers_address) { |
| (...skipping 13 matching lines...) Expand all Loading... |
| 1431 } | 1426 } |
| 1432 isolate->set_deopt_registers_copy(registers_copy); | 1427 isolate->set_deopt_registers_copy(registers_copy); |
| 1433 ASSERT(reinterpret_cast<uword>(saved_registers_address) == last_fp); | 1428 ASSERT(reinterpret_cast<uword>(saved_registers_address) == last_fp); |
| 1434 DartFrameIterator iterator(last_fp); | 1429 DartFrameIterator iterator(last_fp); |
| 1435 StackFrame* caller_frame = iterator.NextFrame(); | 1430 StackFrame* caller_frame = iterator.NextFrame(); |
| 1436 ASSERT(caller_frame != NULL); | 1431 ASSERT(caller_frame != NULL); |
| 1437 const Code& optimized_code = Code::Handle(caller_frame->LookupDartCode()); | 1432 const Code& optimized_code = Code::Handle(caller_frame->LookupDartCode()); |
| 1438 ASSERT(optimized_code.is_optimized()); | 1433 ASSERT(optimized_code.is_optimized()); |
| 1439 | 1434 |
| 1440 const intptr_t deopt_id = GetDeoptInfo(optimized_code, caller_frame->pc()); | 1435 const intptr_t deopt_id = GetDeoptInfo(optimized_code, caller_frame->pc()); |
| 1441 ASSERT(deopt_id != Computation::kNoCid); | 1436 ASSERT(deopt_id != Isolate::kNoDeoptId); |
| 1442 | 1437 |
| 1443 // Add incoming arguments. | 1438 // Add incoming arguments. |
| 1444 const Function& function = Function::Handle(optimized_code.function()); | 1439 const Function& function = Function::Handle(optimized_code.function()); |
| 1445 // Think of copied arguments. | 1440 // Think of copied arguments. |
| 1446 const intptr_t num_args = (function.num_optional_parameters() > 0) ? | 1441 const intptr_t num_args = (function.num_optional_parameters() > 0) ? |
| 1447 0 : function.num_fixed_parameters(); | 1442 0 : function.num_fixed_parameters(); |
| 1448 // FP, PC marker and return address will all be copied. | 1443 // FP, PC marker and return address will all be copied. |
| 1449 const intptr_t frame_copy_size = | 1444 const intptr_t frame_copy_size = |
| 1450 1 // Deoptimized function's return address: caller_frame->pc(). | 1445 1 // Deoptimized function's return address: caller_frame->pc(). |
| 1451 + ((caller_frame->fp() - caller_frame->sp()) / kWordSize) | 1446 + ((caller_frame->fp() - caller_frame->sp()) / kWordSize) |
| (...skipping 39 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1491 const Function& function = Function::Handle(optimized_code.function()); | 1486 const Function& function = Function::Handle(optimized_code.function()); |
| 1492 ASSERT(!function.IsNull()); | 1487 ASSERT(!function.IsNull()); |
| 1493 const Code& unoptimized_code = Code::Handle(function.unoptimized_code()); | 1488 const Code& unoptimized_code = Code::Handle(function.unoptimized_code()); |
| 1494 ASSERT(!optimized_code.IsNull() && optimized_code.is_optimized()); | 1489 ASSERT(!optimized_code.IsNull() && optimized_code.is_optimized()); |
| 1495 ASSERT(!unoptimized_code.IsNull() && !unoptimized_code.is_optimized()); | 1490 ASSERT(!unoptimized_code.IsNull() && !unoptimized_code.is_optimized()); |
| 1496 | 1491 |
| 1497 intptr_t* frame_copy = isolate->deopt_frame_copy(); | 1492 intptr_t* frame_copy = isolate->deopt_frame_copy(); |
| 1498 intptr_t* registers_copy = isolate->deopt_registers_copy(); | 1493 intptr_t* registers_copy = isolate->deopt_registers_copy(); |
| 1499 | 1494 |
| 1500 intptr_t deopt_id = GetDeoptInfo(optimized_code, caller_frame->pc()); | 1495 intptr_t deopt_id = GetDeoptInfo(optimized_code, caller_frame->pc()); |
| 1501 ASSERT(deopt_id != Computation::kNoCid); | 1496 ASSERT(deopt_id != Isolate::kNoDeoptId); |
| 1502 uword continue_at_pc = unoptimized_code.GetDeoptPcAtNodeId(deopt_id); | 1497 uword continue_at_pc = unoptimized_code.GetDeoptPcAtDeoptId(deopt_id); |
| 1503 if (FLAG_trace_deopt) { | 1498 if (FLAG_trace_deopt) { |
| 1504 OS::Print(" -> continue at 0x%x\n", continue_at_pc); | 1499 OS::Print(" -> continue at 0x%x\n", continue_at_pc); |
| 1505 // TODO(srdjan): If we could allow GC, we could print the line where | 1500 // TODO(srdjan): If we could allow GC, we could print the line where |
| 1506 // deoptimization occured. | 1501 // deoptimization occured. |
| 1507 } | 1502 } |
| 1508 const intptr_t deopt_frame_copy_size = isolate->deopt_frame_copy_size(); | 1503 const intptr_t deopt_frame_copy_size = isolate->deopt_frame_copy_size(); |
| 1509 // TODO(srdjan): Use deopt info to copy the values to right place. | 1504 // TODO(srdjan): Use deopt info to copy the values to right place. |
| 1510 const intptr_t pc_marker_index = | 1505 const intptr_t pc_marker_index = |
| 1511 ((caller_frame->fp() - caller_frame->sp()) / kWordSize); | 1506 ((caller_frame->fp() - caller_frame->sp()) / kWordSize); |
| 1512 // Patch the return PC and saved PC marker in frame to point to the | 1507 // Patch the return PC and saved PC marker in frame to point to the |
| (...skipping 103 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 1616 } | 1611 } |
| 1617 } | 1612 } |
| 1618 } | 1613 } |
| 1619 // The cache is null terminated, therefore the loop above should never | 1614 // The cache is null terminated, therefore the loop above should never |
| 1620 // terminate by itself. | 1615 // terminate by itself. |
| 1621 UNREACHABLE(); | 1616 UNREACHABLE(); |
| 1622 return Code::null(); | 1617 return Code::null(); |
| 1623 } | 1618 } |
| 1624 | 1619 |
| 1625 } // namespace dart | 1620 } // namespace dart |
| OLD | NEW |