Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(1)

Side by Side Diff: src/global-handles.cc

Issue 1358703003: Changed scavenge GC to collect unmodified references (Closed) Base URL: https://chromium.googlesource.com/v8/v8.git@master
Patch Set: Checks if the object is not active inaddition to unmodified before reclaiming it. The browser test … Created 5 years, 2 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
« no previous file with comments | « src/global-handles.h ('k') | src/heap/heap.cc » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
OLDNEW
1 // Copyright 2009 the V8 project authors. All rights reserved. 1 // Copyright 2009 the V8 project authors. All rights reserved.
2 // Use of this source code is governed by a BSD-style license that can be 2 // Use of this source code is governed by a BSD-style license that can be
3 // found in the LICENSE file. 3 // found in the LICENSE file.
4 4
5 #include "src/global-handles.h" 5 #include "src/global-handles.h"
6 6
7 #include "src/api.h" 7 #include "src/api.h"
8 #include "src/v8.h" 8 #include "src/v8.h"
9 #include "src/vm-state-inl.h" 9 #include "src/vm-state-inl.h"
10 10
(...skipping 36 matching lines...) Expand 10 before | Expand all | Expand 10 after
47 DCHECK(offsetof(Node, flags_) == Internals::kNodeFlagsOffset); 47 DCHECK(offsetof(Node, flags_) == Internals::kNodeFlagsOffset);
48 STATIC_ASSERT(static_cast<int>(NodeState::kMask) == 48 STATIC_ASSERT(static_cast<int>(NodeState::kMask) ==
49 Internals::kNodeStateMask); 49 Internals::kNodeStateMask);
50 STATIC_ASSERT(WEAK == Internals::kNodeStateIsWeakValue); 50 STATIC_ASSERT(WEAK == Internals::kNodeStateIsWeakValue);
51 STATIC_ASSERT(PENDING == Internals::kNodeStateIsPendingValue); 51 STATIC_ASSERT(PENDING == Internals::kNodeStateIsPendingValue);
52 STATIC_ASSERT(NEAR_DEATH == Internals::kNodeStateIsNearDeathValue); 52 STATIC_ASSERT(NEAR_DEATH == Internals::kNodeStateIsNearDeathValue);
53 STATIC_ASSERT(static_cast<int>(IsIndependent::kShift) == 53 STATIC_ASSERT(static_cast<int>(IsIndependent::kShift) ==
54 Internals::kNodeIsIndependentShift); 54 Internals::kNodeIsIndependentShift);
55 STATIC_ASSERT(static_cast<int>(IsPartiallyDependent::kShift) == 55 STATIC_ASSERT(static_cast<int>(IsPartiallyDependent::kShift) ==
56 Internals::kNodeIsPartiallyDependentShift); 56 Internals::kNodeIsPartiallyDependentShift);
57 STATIC_ASSERT(static_cast<int>(IsInactive::kShift) ==
58 Internals::kNodeIsInactiveShift);
57 } 59 }
58 60
59 #ifdef ENABLE_HANDLE_ZAPPING 61 #ifdef ENABLE_HANDLE_ZAPPING
60 ~Node() { 62 ~Node() {
61 // TODO(1428): if it's a weak handle we should have invoked its callback. 63 // TODO(1428): if it's a weak handle we should have invoked its callback.
62 // Zap the values for eager trapping. 64 // Zap the values for eager trapping.
63 object_ = reinterpret_cast<Object*>(kGlobalHandleZapValue); 65 object_ = reinterpret_cast<Object*>(kGlobalHandleZapValue);
64 class_id_ = v8::HeapProfiler::kPersistentHandleNoClassId; 66 class_id_ = v8::HeapProfiler::kPersistentHandleNoClassId;
65 index_ = 0; 67 index_ = 0;
66 set_independent(false); 68 set_independent(false);
67 set_partially_dependent(false); 69 if (FLAG_scavenge_reclaim_unmodified_objects) {
70 set_unmodified(false);
71 set_inactive(false);
72 } else {
73 set_partially_dependent(false);
74 }
68 set_in_new_space_list(false); 75 set_in_new_space_list(false);
69 parameter_or_next_free_.next_free = NULL; 76 parameter_or_next_free_.next_free = NULL;
70 weak_callback_ = NULL; 77 weak_callback_ = NULL;
71 } 78 }
72 #endif 79 #endif
73 80
74 void Initialize(int index, Node** first_free) { 81 void Initialize(int index, Node** first_free) {
75 index_ = static_cast<uint8_t>(index); 82 index_ = static_cast<uint8_t>(index);
76 DCHECK(static_cast<int>(index_) == index); 83 DCHECK(static_cast<int>(index_) == index);
77 set_state(FREE); 84 set_state(FREE);
78 set_weakness_type(NORMAL_WEAK); 85 set_weakness_type(NORMAL_WEAK);
79 set_in_new_space_list(false); 86 set_in_new_space_list(false);
87 if (FLAG_scavenge_reclaim_unmodified_objects) {
88 set_unmodified(false);
89 set_inactive(false);
90 }
80 parameter_or_next_free_.next_free = *first_free; 91 parameter_or_next_free_.next_free = *first_free;
81 *first_free = this; 92 *first_free = this;
82 } 93 }
83 94
84 void Acquire(Object* object) { 95 void Acquire(Object* object) {
85 DCHECK(state() == FREE); 96 DCHECK(state() == FREE);
86 object_ = object; 97 object_ = object;
87 class_id_ = v8::HeapProfiler::kPersistentHandleNoClassId; 98 class_id_ = v8::HeapProfiler::kPersistentHandleNoClassId;
88 set_independent(false); 99 set_independent(false);
89 set_partially_dependent(false); 100 if (FLAG_scavenge_reclaim_unmodified_objects) {
101 set_unmodified(false);
102 set_inactive(false);
103 // set_inactive(false);
104 // set_partially_dependent(false);
105 } else {
106 set_partially_dependent(false);
107 }
90 set_state(NORMAL); 108 set_state(NORMAL);
91 parameter_or_next_free_.parameter = NULL; 109 parameter_or_next_free_.parameter = NULL;
92 weak_callback_ = NULL; 110 weak_callback_ = NULL;
93 IncreaseBlockUses(); 111 IncreaseBlockUses();
94 } 112 }
95 113
96 void Zap() { 114 void Zap() {
97 DCHECK(IsInUse()); 115 DCHECK(IsInUse());
98 // Zap the values for eager trapping. 116 // Zap the values for eager trapping.
99 object_ = reinterpret_cast<Object*>(kGlobalHandleZapValue); 117 object_ = reinterpret_cast<Object*>(kGlobalHandleZapValue);
100 } 118 }
101 119
102 void Release() { 120 void Release() {
103 DCHECK(IsInUse()); 121 DCHECK(IsInUse());
104 set_state(FREE); 122 set_state(FREE);
105 // Zap the values for eager trapping. 123 // Zap the values for eager trapping.
106 object_ = reinterpret_cast<Object*>(kGlobalHandleZapValue); 124 object_ = reinterpret_cast<Object*>(kGlobalHandleZapValue);
107 class_id_ = v8::HeapProfiler::kPersistentHandleNoClassId; 125 class_id_ = v8::HeapProfiler::kPersistentHandleNoClassId;
108 set_independent(false); 126 set_independent(false);
109 set_partially_dependent(false); 127 if (FLAG_scavenge_reclaim_unmodified_objects) {
128 set_unmodified(false);
129 set_inactive(false);
130 // set_inactive(false);
131 // set_partially_dependent(false);
132 } else {
133 set_partially_dependent(false);
134 }
110 weak_callback_ = NULL; 135 weak_callback_ = NULL;
111 DecreaseBlockUses(); 136 DecreaseBlockUses();
112 } 137 }
113 138
114 // Object slot accessors. 139 // Object slot accessors.
115 Object* object() const { return object_; } 140 Object* object() const { return object_; }
116 Object** location() { return &object_; } 141 Object** location() { return &object_; }
117 Handle<Object> handle() { return Handle<Object>(location()); } 142 Handle<Object> handle() { return Handle<Object>(location()); }
118 143
119 // Wrapper class ID accessors. 144 // Wrapper class ID accessors.
(...skipping 13 matching lines...) Expand all
133 } 158 }
134 159
135 bool is_independent() { 160 bool is_independent() {
136 return IsIndependent::decode(flags_); 161 return IsIndependent::decode(flags_);
137 } 162 }
138 void set_independent(bool v) { 163 void set_independent(bool v) {
139 flags_ = IsIndependent::update(flags_, v); 164 flags_ = IsIndependent::update(flags_, v);
140 } 165 }
141 166
142 bool is_partially_dependent() { 167 bool is_partially_dependent() {
168 CHECK(!FLAG_scavenge_reclaim_unmodified_objects);
143 return IsPartiallyDependent::decode(flags_); 169 return IsPartiallyDependent::decode(flags_);
144 } 170 }
145 void set_partially_dependent(bool v) { 171 void set_partially_dependent(bool v) {
172 CHECK(!FLAG_scavenge_reclaim_unmodified_objects);
146 flags_ = IsPartiallyDependent::update(flags_, v); 173 flags_ = IsPartiallyDependent::update(flags_, v);
147 } 174 }
148 175
176 bool is_inactive() { return IsInactive::decode(flags_); }
177 void set_inactive(bool v) { IsInactive::update(flags_, v); }
178
179 bool is_unmodified() {
180 CHECK(FLAG_scavenge_reclaim_unmodified_objects);
181 return unmodified_flag;
182 }
183 void set_unmodified(bool v) {
184 CHECK(FLAG_scavenge_reclaim_unmodified_objects);
185 unmodified_flag = v;
186 }
187
149 bool is_in_new_space_list() { 188 bool is_in_new_space_list() {
150 return IsInNewSpaceList::decode(flags_); 189 return IsInNewSpaceList::decode(flags_);
151 } 190 }
152 void set_in_new_space_list(bool v) { 191 void set_in_new_space_list(bool v) {
153 flags_ = IsInNewSpaceList::update(flags_, v); 192 flags_ = IsInNewSpaceList::update(flags_, v);
154 } 193 }
155 194
156 WeaknessType weakness_type() const { 195 WeaknessType weakness_type() const {
157 return NodeWeaknessType::decode(flags_); 196 return NodeWeaknessType::decode(flags_);
158 } 197 }
(...skipping 183 matching lines...) Expand 10 before | Expand all | Expand 10 after
342 // Wrapper class ID. 381 // Wrapper class ID.
343 uint16_t class_id_; 382 uint16_t class_id_;
344 383
345 // Index in the containing handle block. 384 // Index in the containing handle block.
346 uint8_t index_; 385 uint8_t index_;
347 386
348 // This stores three flags (independent, partially_dependent and 387 // This stores three flags (independent, partially_dependent and
349 // in_new_space_list) and a State. 388 // in_new_space_list) and a State.
350 class NodeState : public BitField<State, 0, 3> {}; 389 class NodeState : public BitField<State, 0, 3> {};
351 class IsIndependent : public BitField<bool, 3, 1> {}; 390 class IsIndependent : public BitField<bool, 3, 1> {};
391 // The following two fields are mutually exclusive
392 class IsInactive : public BitField<bool, 4, 1> {};
352 class IsPartiallyDependent : public BitField<bool, 4, 1> {}; 393 class IsPartiallyDependent : public BitField<bool, 4, 1> {};
353 class IsInNewSpaceList : public BitField<bool, 5, 1> {}; 394 class IsInNewSpaceList : public BitField<bool, 5, 1> {};
354 class NodeWeaknessType : public BitField<WeaknessType, 6, 2> {}; 395 class NodeWeaknessType : public BitField<WeaknessType, 6, 2> {};
355 396
356 uint8_t flags_; 397 uint8_t flags_;
398 bool unmodified_flag;
357 399
358 // Handle specific callback - might be a weak reference in disguise. 400 // Handle specific callback - might be a weak reference in disguise.
359 WeakCallback weak_callback_; 401 WeakCallback weak_callback_;
360 402
361 // Provided data for callback. In FREE state, this is used for 403 // Provided data for callback. In FREE state, this is used for
362 // the free list link. 404 // the free list link.
363 union { 405 union {
364 void* parameter; 406 void* parameter;
365 Node* next_free; 407 Node* next_free;
366 } parameter_or_next_free_; 408 } parameter_or_next_free_;
(...skipping 272 matching lines...) Expand 10 before | Expand all | Expand 10 after
639 if (it.node()->IsWeak() && f(it.node()->location())) { 681 if (it.node()->IsWeak() && f(it.node()->location())) {
640 it.node()->MarkPending(); 682 it.node()->MarkPending();
641 } 683 }
642 } 684 }
643 } 685 }
644 686
645 687
646 void GlobalHandles::IterateNewSpaceStrongAndDependentRoots(ObjectVisitor* v) { 688 void GlobalHandles::IterateNewSpaceStrongAndDependentRoots(ObjectVisitor* v) {
647 for (int i = 0; i < new_space_nodes_.length(); ++i) { 689 for (int i = 0; i < new_space_nodes_.length(); ++i) {
648 Node* node = new_space_nodes_[i]; 690 Node* node = new_space_nodes_[i];
649 if (node->IsStrongRetainer() || 691 if (FLAG_scavenge_reclaim_unmodified_objects) {
650 (node->IsWeakRetainer() && !node->is_independent() && 692 if (node->IsStrongRetainer() ||
651 !node->is_partially_dependent())) { 693 (node->IsWeakRetainer() && !node->is_independent() &&
694 !node->is_unmodified())) {
652 v->VisitPointer(node->location()); 695 v->VisitPointer(node->location());
696 }
697 } else {
698 if (node->IsStrongRetainer() ||
699 (node->IsWeakRetainer() && !node->is_independent() &&
700 !node->is_partially_dependent())) {
701 v->VisitPointer(node->location());
702 }
653 } 703 }
654 } 704 }
655 } 705 }
656 706
657 707
658 void GlobalHandles::IdentifyNewSpaceWeakIndependentHandles( 708 void GlobalHandles::IdentifyNewSpaceWeakIndependentHandles(
659 WeakSlotCallbackWithHeap f) { 709 WeakSlotCallbackWithHeap f) {
660 for (int i = 0; i < new_space_nodes_.length(); ++i) { 710 for (int i = 0; i < new_space_nodes_.length(); ++i) {
661 Node* node = new_space_nodes_[i]; 711 Node* node = new_space_nodes_[i];
662 DCHECK(node->is_in_new_space_list()); 712 DCHECK(node->is_in_new_space_list());
(...skipping 17 matching lines...) Expand all
680 node->CollectPhantomCallbackData(isolate(), 730 node->CollectPhantomCallbackData(isolate(),
681 &pending_phantom_callbacks_); 731 &pending_phantom_callbacks_);
682 } else { 732 } else {
683 v->VisitPointer(node->location()); 733 v->VisitPointer(node->location());
684 } 734 }
685 } 735 }
686 } 736 }
687 } 737 }
688 738
689 739
740 void GlobalHandles::IdentifyWeakUnmodifiedObjects(
741 WeakSlotCallback is_unmodified) {
742 for (int i = 0; i < new_space_nodes_.length(); ++i) {
743 Node* node = new_space_nodes_[i];
744 if (node->is_inactive()) {
745 node->set_inactive(false);
746 if (node->IsWeak() && is_unmodified(node->location())) {
747 node->set_unmodified(true);
748 }
749 }
750 }
751 }
752
753
754 void GlobalHandles::MarkNewSpaceWeakUnmodifiedObjectsPending(
755 WeakSlotCallbackWithHeap is_unscavenged) {
756 for (int i = 0; i < new_space_nodes_.length(); ++i) {
757 Node* node = new_space_nodes_[i];
758 DCHECK(node->is_in_new_space_list());
759 if ((node->is_independent() || node->is_unmodified()) && node->IsWeak() &&
760 is_unscavenged(isolate_->heap(), node->location())) {
761 node->MarkPending();
762 }
763 }
764 }
765
766
767 void GlobalHandles::IterateNewSpaceWeakUnmodifiedRoots(ObjectVisitor* v) {
768 for (int i = 0; i < new_space_nodes_.length(); ++i) {
769 Node* node = new_space_nodes_[i];
770 DCHECK(node->is_in_new_space_list());
771 if ((node->is_independent() || node->is_unmodified()) &&
772 node->IsWeakRetainer()) {
773 // Pending weak phantom handles die immediately. Everything else survives.
774 if (node->state() == Node::PENDING &&
775 node->weakness_type() != NORMAL_WEAK) {
776 node->CollectPhantomCallbackData(isolate(),
777 &pending_phantom_callbacks_);
778 } else {
779 v->VisitPointer(node->location());
780 }
781 }
782 }
783 }
784
785
690 bool GlobalHandles::IterateObjectGroups(ObjectVisitor* v, 786 bool GlobalHandles::IterateObjectGroups(ObjectVisitor* v,
691 WeakSlotCallbackWithHeap can_skip) { 787 WeakSlotCallbackWithHeap can_skip) {
692 ComputeObjectGroupsAndImplicitReferences(); 788 ComputeObjectGroupsAndImplicitReferences();
693 int last = 0; 789 int last = 0;
694 bool any_group_was_visited = false; 790 bool any_group_was_visited = false;
695 for (int i = 0; i < object_groups_.length(); i++) { 791 for (int i = 0; i < object_groups_.length(); i++) {
696 ObjectGroup* entry = object_groups_.at(i); 792 ObjectGroup* entry = object_groups_.at(i);
697 DCHECK(entry != NULL); 793 DCHECK(entry != NULL);
698 794
699 Object*** objects = entry->objects; 795 Object*** objects = entry->objects;
(...skipping 50 matching lines...) Expand 10 before | Expand all | Expand 10 after
750 const int initial_post_gc_processing_count) { 846 const int initial_post_gc_processing_count) {
751 int freed_nodes = 0; 847 int freed_nodes = 0;
752 for (int i = 0; i < new_space_nodes_.length(); ++i) { 848 for (int i = 0; i < new_space_nodes_.length(); ++i) {
753 Node* node = new_space_nodes_[i]; 849 Node* node = new_space_nodes_[i];
754 DCHECK(node->is_in_new_space_list()); 850 DCHECK(node->is_in_new_space_list());
755 if (!node->IsRetainer()) { 851 if (!node->IsRetainer()) {
756 // Free nodes do not have weak callbacks. Do not use them to compute 852 // Free nodes do not have weak callbacks. Do not use them to compute
757 // the freed_nodes. 853 // the freed_nodes.
758 continue; 854 continue;
759 } 855 }
760 // Skip dependent handles. Their weak callbacks might expect to be 856 // Skip dependent or unmodified handles. Their weak callbacks might expect
857 // to be
761 // called between two global garbage collection callbacks which 858 // called between two global garbage collection callbacks which
762 // are not called for minor collections. 859 // are not called for minor collections.
763 if (!node->is_independent() && !node->is_partially_dependent()) { 860 if (FLAG_scavenge_reclaim_unmodified_objects) {
764 continue; 861 node->set_inactive(false);
862 if (!node->is_independent() && !node->is_unmodified()) {
863 continue;
864 }
865 node->set_unmodified(false);
866 } else {
867 if (!node->is_independent() && !node->is_partially_dependent()) {
868 continue;
869 }
870 node->clear_partially_dependent();
765 } 871 }
766 node->clear_partially_dependent(); 872
767 if (node->PostGarbageCollectionProcessing(isolate_)) { 873 if (node->PostGarbageCollectionProcessing(isolate_)) {
768 if (initial_post_gc_processing_count != post_gc_processing_count_) { 874 if (initial_post_gc_processing_count != post_gc_processing_count_) {
769 // Weak callback triggered another GC and another round of 875 // Weak callback triggered another GC and another round of
770 // PostGarbageCollection processing. The current node might 876 // PostGarbageCollection processing. The current node might
771 // have been deleted in that round, so we need to bail out (or 877 // have been deleted in that round, so we need to bail out (or
772 // restart the processing). 878 // restart the processing).
773 return freed_nodes; 879 return freed_nodes;
774 } 880 }
775 } 881 }
776 if (!node->IsRetainer()) { 882 if (!node->IsRetainer()) {
777 freed_nodes++; 883 freed_nodes++;
778 } 884 }
779 } 885 }
780 return freed_nodes; 886 return freed_nodes;
781 } 887 }
782 888
783 889
784 int GlobalHandles::PostMarkSweepProcessing( 890 int GlobalHandles::PostMarkSweepProcessing(
785 const int initial_post_gc_processing_count) { 891 const int initial_post_gc_processing_count) {
786 int freed_nodes = 0; 892 int freed_nodes = 0;
787 for (NodeIterator it(this); !it.done(); it.Advance()) { 893 for (NodeIterator it(this); !it.done(); it.Advance()) {
788 if (!it.node()->IsRetainer()) { 894 if (!it.node()->IsRetainer()) {
789 // Free nodes do not have weak callbacks. Do not use them to compute 895 // Free nodes do not have weak callbacks. Do not use them to compute
790 // the freed_nodes. 896 // the freed_nodes.
791 continue; 897 continue;
792 } 898 }
793 it.node()->clear_partially_dependent(); 899 if (FLAG_scavenge_reclaim_unmodified_objects) {
900 it.node()->set_unmodified(false);
901 it.node()->set_inactive(false);
902 } else {
903 it.node()->clear_partially_dependent();
904 }
794 if (it.node()->PostGarbageCollectionProcessing(isolate_)) { 905 if (it.node()->PostGarbageCollectionProcessing(isolate_)) {
795 if (initial_post_gc_processing_count != post_gc_processing_count_) { 906 if (initial_post_gc_processing_count != post_gc_processing_count_) {
796 // See the comment above. 907 // See the comment above.
797 return freed_nodes; 908 return freed_nodes;
798 } 909 }
799 } 910 }
800 if (!it.node()->IsRetainer()) { 911 if (!it.node()->IsRetainer()) {
801 freed_nodes++; 912 freed_nodes++;
802 } 913 }
803 } 914 }
(...skipping 144 matching lines...) Expand 10 before | Expand all | Expand 10 after
948 for (int i = 0; i < new_space_nodes_.length(); ++i) { 1059 for (int i = 0; i < new_space_nodes_.length(); ++i) {
949 Node* node = new_space_nodes_[i]; 1060 Node* node = new_space_nodes_[i];
950 if (node->IsRetainer() && node->has_wrapper_class_id()) { 1061 if (node->IsRetainer() && node->has_wrapper_class_id()) {
951 v->VisitEmbedderReference(node->location(), 1062 v->VisitEmbedderReference(node->location(),
952 node->wrapper_class_id()); 1063 node->wrapper_class_id());
953 } 1064 }
954 } 1065 }
955 } 1066 }
956 1067
957 1068
1069 void GlobalHandles::IterateWeakRootsInNewSpaceWithClassIds(ObjectVisitor* v) {
1070 for (int i = 0; i < new_space_nodes_.length(); ++i) {
1071 Node* node = new_space_nodes_[i];
1072 if (node->has_wrapper_class_id() && node->IsWeak()) {
1073 v->VisitEmbedderReference(node->location(), node->wrapper_class_id());
1074 }
1075 }
1076 }
1077
1078
958 int GlobalHandles::NumberOfWeakHandles() { 1079 int GlobalHandles::NumberOfWeakHandles() {
959 int count = 0; 1080 int count = 0;
960 for (NodeIterator it(this); !it.done(); it.Advance()) { 1081 for (NodeIterator it(this); !it.done(); it.Advance()) {
961 if (it.node()->IsWeakRetainer()) { 1082 if (it.node()->IsWeakRetainer()) {
962 count++; 1083 count++;
963 } 1084 }
964 } 1085 }
965 return count; 1086 return count;
966 } 1087 }
967 1088
(...skipping 306 matching lines...) Expand 10 before | Expand all | Expand 10 after
1274 blocks_[block][offset] = object; 1395 blocks_[block][offset] = object;
1275 if (isolate->heap()->InNewSpace(object)) { 1396 if (isolate->heap()->InNewSpace(object)) {
1276 new_space_indices_.Add(size_); 1397 new_space_indices_.Add(size_);
1277 } 1398 }
1278 *index = size_++; 1399 *index = size_++;
1279 } 1400 }
1280 1401
1281 1402
1282 } // namespace internal 1403 } // namespace internal
1283 } // namespace v8 1404 } // namespace v8
OLDNEW
« no previous file with comments | « src/global-handles.h ('k') | src/heap/heap.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698