OLD | NEW |
---|---|
1 // Copyright (c) 2013, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2013, 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/deopt_instructions.h" | 5 #include "vm/deopt_instructions.h" |
6 | 6 |
7 #include "vm/assembler.h" | 7 #include "vm/assembler.h" |
8 #include "vm/code_patcher.h" | 8 #include "vm/code_patcher.h" |
9 #include "vm/intermediate_language.h" | 9 #include "vm/intermediate_language.h" |
10 #include "vm/locations.h" | 10 #include "vm/locations.h" |
(...skipping 18 matching lines...) Expand all Loading... | |
29 from_frame_size_(0), | 29 from_frame_size_(0), |
30 registers_copy_(NULL), | 30 registers_copy_(NULL), |
31 fpu_registers_copy_(NULL), | 31 fpu_registers_copy_(NULL), |
32 num_args_(num_args), | 32 num_args_(num_args), |
33 deopt_reason_(deopt_reason), | 33 deopt_reason_(deopt_reason), |
34 isolate_(Isolate::Current()) { | 34 isolate_(Isolate::Current()) { |
35 from_frame_ = isolate_->deopt_frame_copy(); | 35 from_frame_ = isolate_->deopt_frame_copy(); |
36 from_frame_size_ = isolate_->deopt_frame_copy_size(); | 36 from_frame_size_ = isolate_->deopt_frame_copy_size(); |
37 registers_copy_ = isolate_->deopt_cpu_registers_copy(); | 37 registers_copy_ = isolate_->deopt_cpu_registers_copy(); |
38 fpu_registers_copy_ = isolate_->deopt_fpu_registers_copy(); | 38 fpu_registers_copy_ = isolate_->deopt_fpu_registers_copy(); |
39 // The deoptimized frame is filled starting just below the sp of its caller | |
40 // down to kDartFrameFixedSize words below its own sp. | |
41 // The chain of frame pointers is recreated from the fp of the caller. | |
39 caller_fp_ = GetFromFp(); | 42 caller_fp_ = GetFromFp(); |
40 } | 43 } |
41 | 44 |
42 | 45 |
43 intptr_t DeoptimizationContext::GetFromFp() const { | 46 intptr_t DeoptimizationContext::GetFromFp() const { |
44 return from_frame_[from_frame_size_ - num_args_ - 1 - kParamEndSlotFromFp]; | 47 return from_frame_[from_frame_size_ - 1 - num_args_ - kParamEndSlotFromFp]; |
48 } | |
49 | |
50 | |
51 intptr_t DeoptimizationContext::GetFromPp() const { | |
52 // Should not be called if the pool pointer is not used. | |
53 ASSERT(kSavedCallerPpSlotFromFp != kSavedCallerFpSlotFromFp); | |
54 return from_frame_[from_frame_size_ - 1 - num_args_ - kParamEndSlotFromFp + | |
55 kSavedCallerPpSlotFromFp]; | |
45 } | 56 } |
46 | 57 |
47 | 58 |
48 intptr_t DeoptimizationContext::GetFromPc() const { | 59 intptr_t DeoptimizationContext::GetFromPc() const { |
49 return from_frame_[from_frame_size_ - num_args_ + kSavedPcSlotFromSp]; | 60 return from_frame_[from_frame_size_ - num_args_ + kSavedPcSlotFromSp]; |
50 } | 61 } |
51 | 62 |
63 | |
52 intptr_t DeoptimizationContext::GetCallerFp() const { | 64 intptr_t DeoptimizationContext::GetCallerFp() const { |
53 return caller_fp_; | 65 return caller_fp_; |
54 } | 66 } |
55 | 67 |
68 | |
56 void DeoptimizationContext::SetCallerFp(intptr_t caller_fp) { | 69 void DeoptimizationContext::SetCallerFp(intptr_t caller_fp) { |
57 caller_fp_ = caller_fp; | 70 caller_fp_ = caller_fp; |
58 } | 71 } |
59 | 72 |
73 | |
60 // Deoptimization instruction moving value from optimized frame at | 74 // Deoptimization instruction moving value from optimized frame at |
61 // 'from_index' to specified slots in the unoptimized frame. | 75 // 'from_index' to specified slots in the unoptimized frame. |
62 // 'from_index' represents the slot index of the frame (0 being first argument) | 76 // 'from_index' represents the slot index of the frame (0 being first argument) |
63 // and accounts for saved return address, frame pointer and pc marker. | 77 // and accounts for saved return address, frame pointer and pc marker. |
64 class DeoptStackSlotInstr : public DeoptInstr { | 78 class DeoptStackSlotInstr : public DeoptInstr { |
65 public: | 79 public: |
66 explicit DeoptStackSlotInstr(intptr_t from_index) | 80 explicit DeoptStackSlotInstr(intptr_t from_index) |
67 : stack_slot_index_(from_index) { | 81 : stack_slot_index_(from_index) { |
68 ASSERT(stack_slot_index_ >= 0); | 82 ASSERT(stack_slot_index_ >= 0); |
69 } | 83 } |
(...skipping 399 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
469 virtual DeoptInstr::Kind kind() const { return kPcMarker; } | 483 virtual DeoptInstr::Kind kind() const { return kPcMarker; } |
470 | 484 |
471 virtual const char* ToCString() const { | 485 virtual const char* ToCString() const { |
472 return Isolate::Current()->current_zone()->PrintToString( | 486 return Isolate::Current()->current_zone()->PrintToString( |
473 "pcmark oti:%"Pd"", object_table_index_); | 487 "pcmark oti:%"Pd"", object_table_index_); |
474 } | 488 } |
475 | 489 |
476 void Execute(DeoptimizationContext* deopt_context, intptr_t* to_addr) { | 490 void Execute(DeoptimizationContext* deopt_context, intptr_t* to_addr) { |
477 Function& function = Function::Handle(deopt_context->isolate()); | 491 Function& function = Function::Handle(deopt_context->isolate()); |
478 function ^= deopt_context->ObjectAt(object_table_index_); | 492 function ^= deopt_context->ObjectAt(object_table_index_); |
493 if (function.IsNull()) { | |
494 // Callee's PC marker is not used anymore. Set to 0. | |
srdjan
2013/05/14 22:16:39
When can this occur?
regis
2013/05/14 23:23:42
The Deoptimize stub sets up a Dart frame that is c
| |
495 *to_addr = 0; | |
496 return; | |
497 } | |
479 const Code& code = | 498 const Code& code = |
480 Code::Handle(deopt_context->isolate(), function.unoptimized_code()); | 499 Code::Handle(deopt_context->isolate(), function.unoptimized_code()); |
481 ASSERT(!code.IsNull()); | 500 ASSERT(!code.IsNull()); |
482 const intptr_t pc_marker = | 501 const intptr_t pc_marker = |
483 code.EntryPoint() + Assembler::kEntryPointToPcMarkerOffset; | 502 code.EntryPoint() + Assembler::kEntryPointToPcMarkerOffset; |
484 *to_addr = pc_marker; | 503 *to_addr = pc_marker; |
485 // Increment the deoptimization counter. This effectively increments each | 504 // Increment the deoptimization counter. This effectively increments each |
486 // function occurring in the optimized frame. | 505 // function occurring in the optimized frame. |
487 function.set_deoptimization_counter(function.deoptimization_counter() + 1); | 506 function.set_deoptimization_counter(function.deoptimization_counter() + 1); |
488 if (FLAG_trace_deoptimization) { | 507 if (FLAG_trace_deoptimization) { |
489 OS::PrintErr("Deoptimizing %s (count %d)\n", | 508 OS::PrintErr("Deoptimizing %s (count %d)\n", |
490 function.ToFullyQualifiedCString(), | 509 function.ToFullyQualifiedCString(), |
491 function.deoptimization_counter()); | 510 function.deoptimization_counter()); |
492 } | 511 } |
493 // Clear invocation counter so that hopefully the function gets reoptimized | 512 // Clear invocation counter so that hopefully the function gets reoptimized |
494 // only after more feedback has been collected. | 513 // only after more feedback has been collected. |
495 function.set_usage_counter(0); | 514 function.set_usage_counter(0); |
496 if (function.HasOptimizedCode()) function.SwitchToUnoptimizedCode(); | 515 if (function.HasOptimizedCode()) function.SwitchToUnoptimizedCode(); |
497 } | 516 } |
498 | 517 |
499 private: | 518 private: |
500 intptr_t object_table_index_; | 519 intptr_t object_table_index_; |
501 | 520 |
502 DISALLOW_COPY_AND_ASSIGN(DeoptPcMarkerInstr); | 521 DISALLOW_COPY_AND_ASSIGN(DeoptPcMarkerInstr); |
503 }; | 522 }; |
504 | 523 |
505 | 524 |
525 // Deoptimization instruction creating a pool pointer for the code of | |
526 // function at 'object_table_index'. | |
527 class DeoptPpInstr : public DeoptInstr { | |
528 public: | |
529 explicit DeoptPpInstr(intptr_t object_table_index) | |
530 : object_table_index_(object_table_index) { | |
531 ASSERT(object_table_index >= 0); | |
532 } | |
533 | |
534 virtual intptr_t from_index() const { return object_table_index_; } | |
535 virtual DeoptInstr::Kind kind() const { return kPp; } | |
536 | |
537 virtual const char* ToCString() const { | |
538 return Isolate::Current()->current_zone()->PrintToString( | |
539 "pp oti:%"Pd"", object_table_index_); | |
540 } | |
541 | |
542 void Execute(DeoptimizationContext* deopt_context, intptr_t* to_addr) { | |
543 Function& function = Function::Handle(deopt_context->isolate()); | |
544 function ^= deopt_context->ObjectAt(object_table_index_); | |
545 const Code& code = | |
546 Code::Handle(deopt_context->isolate(), function.unoptimized_code()); | |
547 ASSERT(!code.IsNull()); | |
548 const intptr_t pp = reinterpret_cast<intptr_t>(code.ObjectPool()); | |
549 *to_addr = pp; | |
550 } | |
551 | |
552 private: | |
553 intptr_t object_table_index_; | |
554 | |
555 DISALLOW_COPY_AND_ASSIGN(DeoptPpInstr); | |
556 }; | |
557 | |
558 | |
506 // Deoptimization instruction copying the caller saved FP from optimized frame. | 559 // Deoptimization instruction copying the caller saved FP from optimized frame. |
507 class DeoptCallerFpInstr : public DeoptInstr { | 560 class DeoptCallerFpInstr : public DeoptInstr { |
508 public: | 561 public: |
509 DeoptCallerFpInstr() {} | 562 DeoptCallerFpInstr() {} |
510 | 563 |
511 virtual intptr_t from_index() const { return 0; } | 564 virtual intptr_t from_index() const { return 0; } |
512 virtual DeoptInstr::Kind kind() const { return kCallerFp; } | 565 virtual DeoptInstr::Kind kind() const { return kCallerFp; } |
513 | 566 |
514 virtual const char* ToCString() const { | 567 virtual const char* ToCString() const { |
515 return "callerfp"; | 568 return "callerfp"; |
516 } | 569 } |
517 | 570 |
518 void Execute(DeoptimizationContext* deopt_context, intptr_t* to_addr) { | 571 void Execute(DeoptimizationContext* deopt_context, intptr_t* to_addr) { |
519 *to_addr = deopt_context->GetCallerFp(); | 572 *to_addr = deopt_context->GetCallerFp(); |
520 deopt_context->SetCallerFp(reinterpret_cast<intptr_t>(to_addr)); | 573 deopt_context->SetCallerFp(reinterpret_cast<intptr_t>( |
574 to_addr - (kSavedCallerFpSlotFromFp * kWordSize))); | |
521 } | 575 } |
522 | 576 |
523 private: | 577 private: |
524 DISALLOW_COPY_AND_ASSIGN(DeoptCallerFpInstr); | 578 DISALLOW_COPY_AND_ASSIGN(DeoptCallerFpInstr); |
525 }; | 579 }; |
526 | 580 |
527 | 581 |
582 // Deoptimization instruction copying the caller saved PP from optimized frame. | |
583 class DeoptCallerPpInstr : public DeoptInstr { | |
584 public: | |
585 DeoptCallerPpInstr() {} | |
586 | |
587 virtual intptr_t from_index() const { return 0; } | |
588 virtual DeoptInstr::Kind kind() const { return kCallerPp; } | |
589 | |
590 virtual const char* ToCString() const { | |
591 return "callerpp"; | |
592 } | |
593 | |
594 void Execute(DeoptimizationContext* deopt_context, intptr_t* to_addr) { | |
595 *to_addr = deopt_context->GetFromPp(); | |
596 } | |
597 | |
598 private: | |
599 DISALLOW_COPY_AND_ASSIGN(DeoptCallerPpInstr); | |
600 }; | |
601 | |
602 | |
528 // Deoptimization instruction copying the caller return address from optimized | 603 // Deoptimization instruction copying the caller return address from optimized |
529 // frame. | 604 // frame. |
530 class DeoptCallerPcInstr : public DeoptInstr { | 605 class DeoptCallerPcInstr : public DeoptInstr { |
531 public: | 606 public: |
532 DeoptCallerPcInstr() {} | 607 DeoptCallerPcInstr() {} |
533 | 608 |
534 virtual intptr_t from_index() const { return 0; } | 609 virtual intptr_t from_index() const { return 0; } |
535 virtual DeoptInstr::Kind kind() const { return kCallerPc; } | 610 virtual DeoptInstr::Kind kind() const { return kCallerPc; } |
536 | 611 |
537 virtual const char* ToCString() const { | 612 virtual const char* ToCString() const { |
(...skipping 157 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
695 case kRetAddress: return new DeoptRetAddressInstr(from_index); | 770 case kRetAddress: return new DeoptRetAddressInstr(from_index); |
696 case kConstant: return new DeoptConstantInstr(from_index); | 771 case kConstant: return new DeoptConstantInstr(from_index); |
697 case kRegister: return new DeoptRegisterInstr(from_index); | 772 case kRegister: return new DeoptRegisterInstr(from_index); |
698 case kFpuRegister: return new DeoptFpuRegisterInstr(from_index); | 773 case kFpuRegister: return new DeoptFpuRegisterInstr(from_index); |
699 case kInt64FpuRegister: return new DeoptInt64FpuRegisterInstr(from_index); | 774 case kInt64FpuRegister: return new DeoptInt64FpuRegisterInstr(from_index); |
700 case kFloat32x4FpuRegister: | 775 case kFloat32x4FpuRegister: |
701 return new DeoptFloat32x4FpuRegisterInstr(from_index); | 776 return new DeoptFloat32x4FpuRegisterInstr(from_index); |
702 case kUint32x4FpuRegister: | 777 case kUint32x4FpuRegister: |
703 return new DeoptUint32x4FpuRegisterInstr(from_index); | 778 return new DeoptUint32x4FpuRegisterInstr(from_index); |
704 case kPcMarker: return new DeoptPcMarkerInstr(from_index); | 779 case kPcMarker: return new DeoptPcMarkerInstr(from_index); |
780 case kPp: return new DeoptPpInstr(from_index); | |
705 case kCallerFp: return new DeoptCallerFpInstr(); | 781 case kCallerFp: return new DeoptCallerFpInstr(); |
782 case kCallerPp: return new DeoptCallerPpInstr(); | |
706 case kCallerPc: return new DeoptCallerPcInstr(); | 783 case kCallerPc: return new DeoptCallerPcInstr(); |
707 case kSuffix: return new DeoptSuffixInstr(from_index); | 784 case kSuffix: return new DeoptSuffixInstr(from_index); |
708 case kMaterializedObjectRef: | 785 case kMaterializedObjectRef: |
709 return new DeoptMaterializedObjectRefInstr(from_index); | 786 return new DeoptMaterializedObjectRefInstr(from_index); |
710 case kMaterializeObject: return new DeoptMaterializeObjectInstr(from_index); | 787 case kMaterializeObject: return new DeoptMaterializeObjectInstr(from_index); |
711 } | 788 } |
712 UNREACHABLE(); | 789 UNREACHABLE(); |
713 return NULL; | 790 return NULL; |
714 } | 791 } |
715 | 792 |
(...skipping 72 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
788 (code.GetPcForDeoptId(deopt_id, PcDescriptors::kDeopt) != 0)); | 865 (code.GetPcForDeoptId(deopt_id, PcDescriptors::kDeopt) != 0)); |
789 #endif | 866 #endif |
790 const intptr_t object_table_index = FindOrAddObjectInTable(function); | 867 const intptr_t object_table_index = FindOrAddObjectInTable(function); |
791 ASSERT(to_index == FrameSize()); | 868 ASSERT(to_index == FrameSize()); |
792 instructions_.Add(new DeoptRetAddressInstr(object_table_index, deopt_id)); | 869 instructions_.Add(new DeoptRetAddressInstr(object_table_index, deopt_id)); |
793 } | 870 } |
794 | 871 |
795 | 872 |
796 void DeoptInfoBuilder::AddPcMarker(const Function& function, | 873 void DeoptInfoBuilder::AddPcMarker(const Function& function, |
797 intptr_t to_index) { | 874 intptr_t to_index) { |
798 // Function object was already added by AddReturnAddress, find it. | 875 intptr_t object_table_index = FindOrAddObjectInTable(function); |
799 intptr_t from_index = FindOrAddObjectInTable(function); | |
800 ASSERT(to_index == FrameSize()); | 876 ASSERT(to_index == FrameSize()); |
801 instructions_.Add(new DeoptPcMarkerInstr(from_index)); | 877 instructions_.Add(new DeoptPcMarkerInstr(object_table_index)); |
878 } | |
879 | |
880 | |
881 void DeoptInfoBuilder::AddPp(const Function& function, intptr_t to_index) { | |
882 intptr_t object_table_index = FindOrAddObjectInTable(function); | |
883 ASSERT(to_index == FrameSize()); | |
884 instructions_.Add(new DeoptPpInstr(object_table_index)); | |
802 } | 885 } |
803 | 886 |
804 | 887 |
805 void DeoptInfoBuilder::AddCopy(Value* value, | 888 void DeoptInfoBuilder::AddCopy(Value* value, |
806 const Location& from_loc, | 889 const Location& from_loc, |
807 const intptr_t to_index) { | 890 const intptr_t to_index) { |
808 DeoptInstr* deopt_instr = NULL; | 891 DeoptInstr* deopt_instr = NULL; |
809 if (from_loc.IsConstant()) { | 892 if (from_loc.IsConstant()) { |
810 intptr_t object_table_index = FindOrAddObjectInTable(from_loc.constant()); | 893 intptr_t object_table_index = FindOrAddObjectInTable(from_loc.constant()); |
811 deopt_instr = new DeoptConstantInstr(object_table_index); | 894 deopt_instr = new DeoptConstantInstr(object_table_index); |
(...skipping 45 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
857 instructions_.Add(deopt_instr); | 940 instructions_.Add(deopt_instr); |
858 } | 941 } |
859 | 942 |
860 | 943 |
861 void DeoptInfoBuilder::AddCallerFp(intptr_t to_index) { | 944 void DeoptInfoBuilder::AddCallerFp(intptr_t to_index) { |
862 ASSERT(to_index == FrameSize()); | 945 ASSERT(to_index == FrameSize()); |
863 instructions_.Add(new DeoptCallerFpInstr()); | 946 instructions_.Add(new DeoptCallerFpInstr()); |
864 } | 947 } |
865 | 948 |
866 | 949 |
950 void DeoptInfoBuilder::AddCallerPp(intptr_t to_index) { | |
951 ASSERT(to_index == FrameSize()); | |
952 instructions_.Add(new DeoptCallerPpInstr()); | |
953 } | |
954 | |
955 | |
867 void DeoptInfoBuilder::AddCallerPc(intptr_t to_index) { | 956 void DeoptInfoBuilder::AddCallerPc(intptr_t to_index) { |
868 ASSERT(to_index == FrameSize()); | 957 ASSERT(to_index == FrameSize()); |
869 instructions_.Add(new DeoptCallerPcInstr()); | 958 instructions_.Add(new DeoptCallerPcInstr()); |
870 } | 959 } |
871 | 960 |
872 | 961 |
873 void DeoptInfoBuilder::AddConstant(const Object& obj, intptr_t to_index) { | 962 void DeoptInfoBuilder::AddConstant(const Object& obj, intptr_t to_index) { |
874 ASSERT(to_index == FrameSize()); | 963 ASSERT(to_index == FrameSize()); |
875 intptr_t object_table_index = FindOrAddObjectInTable(obj); | 964 intptr_t object_table_index = FindOrAddObjectInTable(obj); |
876 instructions_.Add(new DeoptConstantInstr(object_table_index)); | 965 instructions_.Add(new DeoptConstantInstr(object_table_index)); |
(...skipping 14 matching lines...) Expand all Loading... | |
891 for (intptr_t i = 0; i < mat->InputCount(); i++) { | 980 for (intptr_t i = 0; i < mat->InputCount(); i++) { |
892 if (!mat->InputAt(i)->BindsToConstantNull()) { | 981 if (!mat->InputAt(i)->BindsToConstantNull()) { |
893 non_null_fields++; | 982 non_null_fields++; |
894 } | 983 } |
895 } | 984 } |
896 | 985 |
897 instructions_.Add(new DeoptMaterializeObjectInstr(non_null_fields)); | 986 instructions_.Add(new DeoptMaterializeObjectInstr(non_null_fields)); |
898 } | 987 } |
899 | 988 |
900 | 989 |
901 intptr_t DeoptInfoBuilder::EmitMaterializationArguments() { | 990 intptr_t DeoptInfoBuilder::EmitMaterializationArguments(intptr_t to_index) { |
902 intptr_t slot_idx = 1; // Return address is emitted at 0. | 991 ASSERT(to_index == kDartFrameFixedSize); |
903 for (intptr_t i = 0; i < materializations_.length(); i++) { | 992 for (intptr_t i = 0; i < materializations_.length(); i++) { |
904 MaterializeObjectInstr* mat = materializations_[i]; | 993 MaterializeObjectInstr* mat = materializations_[i]; |
905 AddConstant(mat->cls(), slot_idx++); // Class of the instance to allocate. | 994 AddConstant(mat->cls(), to_index++); // Class of the instance to allocate. |
906 for (intptr_t i = 0; i < mat->InputCount(); i++) { | 995 for (intptr_t i = 0; i < mat->InputCount(); i++) { |
907 if (!mat->InputAt(i)->BindsToConstantNull()) { | 996 if (!mat->InputAt(i)->BindsToConstantNull()) { |
908 // Emit field-value pair. | 997 // Emit field-value pair. |
909 AddConstant(mat->FieldAt(i), slot_idx++); | 998 AddConstant(mat->FieldAt(i), to_index++); |
910 AddCopy(mat->InputAt(i), mat->LocationAt(i), slot_idx++); | 999 AddCopy(mat->InputAt(i), mat->LocationAt(i), to_index++); |
911 } | 1000 } |
912 } | 1001 } |
913 } | 1002 } |
914 return slot_idx; | 1003 return to_index; |
915 } | 1004 } |
916 | 1005 |
917 | 1006 |
918 intptr_t DeoptInfoBuilder::FindMaterialization( | 1007 intptr_t DeoptInfoBuilder::FindMaterialization( |
919 MaterializeObjectInstr* mat) const { | 1008 MaterializeObjectInstr* mat) const { |
920 for (intptr_t i = 0; i < materializations_.length(); i++) { | 1009 for (intptr_t i = 0; i < materializations_.length(); i++) { |
921 if (materializations_[i] == mat) { | 1010 if (materializations_[i] == mat) { |
922 return i; | 1011 return i; |
923 } | 1012 } |
924 } | 1013 } |
(...skipping 82 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... | |
1007 Smi* offset, | 1096 Smi* offset, |
1008 DeoptInfo* info, | 1097 DeoptInfo* info, |
1009 Smi* reason) { | 1098 Smi* reason) { |
1010 intptr_t i = index * kEntrySize; | 1099 intptr_t i = index * kEntrySize; |
1011 *offset ^= table.At(i); | 1100 *offset ^= table.At(i); |
1012 *info ^= table.At(i + 1); | 1101 *info ^= table.At(i + 1); |
1013 *reason ^= table.At(i + 2); | 1102 *reason ^= table.At(i + 2); |
1014 } | 1103 } |
1015 | 1104 |
1016 } // namespace dart | 1105 } // namespace dart |
OLD | NEW |