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