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 |
3 | // BSD-style license that can be found in the LICENSE file. |
4 | |
5 | #include "vm/zone.h" |
6 | |
7 | #include "platform/assert.h" |
8 | #include "platform/leak_sanitizer.h" |
9 | #include "platform/utils.h" |
10 | #include "vm/dart_api_state.h" |
11 | #include "vm/flags.h" |
12 | #include "vm/handles_impl.h" |
13 | #include "vm/heap/heap.h" |
14 | #include "vm/os.h" |
15 | #include "vm/virtual_memory.h" |
16 | |
17 | namespace dart { |
18 | |
19 | RelaxedAtomic<intptr_t> Zone::total_size_ = {0}; |
20 | |
21 | // Zone segments represent chunks of memory: They have starting |
22 | // address encoded in the this pointer and a size in bytes. They are |
23 | // chained together to form the backing storage for an expanding zone. |
24 | class Zone::Segment { |
25 | public: |
26 | Segment* next() const { return next_; } |
27 | intptr_t size() const { return size_; } |
28 | VirtualMemory* memory() const { return memory_; } |
29 | |
30 | uword start() { return address(sizeof(Segment)); } |
31 | uword end() { return address(size_); } |
32 | |
33 | // Allocate or delete individual segments. |
34 | static Segment* New(intptr_t size, Segment* next); |
35 | static void DeleteSegmentList(Segment* segment); |
36 | static void IncrementMemoryCapacity(uintptr_t size); |
37 | static void DecrementMemoryCapacity(uintptr_t size); |
38 | |
39 | private: |
40 | Segment* next_; |
41 | intptr_t size_; |
42 | VirtualMemory* memory_; |
43 | void* alignment_; |
44 | |
45 | // Computes the address of the nth byte in this segment. |
46 | uword address(intptr_t n) { return reinterpret_cast<uword>(this) + n; } |
47 | |
48 | DISALLOW_IMPLICIT_CONSTRUCTORS(Segment); |
49 | }; |
50 | |
51 | // tcmalloc and jemalloc have both been observed to hold onto lots of free'd |
52 | // zone segments (jemalloc to the point of causing OOM), so instead of using |
53 | // malloc to allocate segments, we allocate directly from mmap/zx_vmo_create/ |
54 | // VirtualAlloc, and cache a small number of the normal sized segments. |
55 | static constexpr intptr_t kSegmentCacheCapacity = 16; // 1 MB of Segments |
56 | static Mutex* segment_cache_mutex = nullptr; |
57 | static VirtualMemory* segment_cache[kSegmentCacheCapacity] = {nullptr}; |
58 | static intptr_t segment_cache_size = 0; |
59 | |
60 | void Zone::Init() { |
61 | ASSERT(segment_cache_mutex == nullptr); |
62 | segment_cache_mutex = new Mutex(NOT_IN_PRODUCT("segment_cache_mutex" )); |
63 | } |
64 | |
65 | void Zone::Cleanup() { |
66 | { |
67 | MutexLocker ml(segment_cache_mutex); |
68 | ASSERT(segment_cache_size >= 0); |
69 | ASSERT(segment_cache_size <= kSegmentCacheCapacity); |
70 | while (segment_cache_size > 0) { |
71 | delete segment_cache[--segment_cache_size]; |
72 | } |
73 | } |
74 | delete segment_cache_mutex; |
75 | segment_cache_mutex = nullptr; |
76 | } |
77 | |
78 | Zone::Segment* Zone::Segment::New(intptr_t size, Zone::Segment* next) { |
79 | size = Utils::RoundUp(size, VirtualMemory::PageSize()); |
80 | VirtualMemory* memory = nullptr; |
81 | if (size == kSegmentSize) { |
82 | MutexLocker ml(segment_cache_mutex); |
83 | ASSERT(segment_cache_size >= 0); |
84 | ASSERT(segment_cache_size <= kSegmentCacheCapacity); |
85 | if (segment_cache_size > 0) { |
86 | memory = segment_cache[--segment_cache_size]; |
87 | } |
88 | } |
89 | if (memory == nullptr) { |
90 | memory = VirtualMemory::Allocate(size, false, "dart-zone" ); |
91 | total_size_.fetch_add(size); |
92 | } |
93 | if (memory == nullptr) { |
94 | OUT_OF_MEMORY(); |
95 | } |
96 | Segment* result = reinterpret_cast<Segment*>(memory->start()); |
97 | #ifdef DEBUG |
98 | // Zap the entire allocated segment (including the header). |
99 | memset(reinterpret_cast<void*>(result), kZapUninitializedByte, size); |
100 | #endif |
101 | result->next_ = next; |
102 | result->size_ = size; |
103 | result->memory_ = memory; |
104 | result->alignment_ = nullptr; // Avoid unused variable warnings. |
105 | |
106 | LSAN_REGISTER_ROOT_REGION(result, sizeof(*result)); |
107 | |
108 | IncrementMemoryCapacity(size); |
109 | return result; |
110 | } |
111 | |
112 | void Zone::Segment::DeleteSegmentList(Segment* head) { |
113 | Segment* current = head; |
114 | while (current != NULL) { |
115 | intptr_t size = current->size(); |
116 | DecrementMemoryCapacity(size); |
117 | Segment* next = current->next(); |
118 | VirtualMemory* memory = current->memory(); |
119 | #ifdef DEBUG |
120 | // Zap the entire current segment (including the header). |
121 | memset(reinterpret_cast<void*>(current), kZapDeletedByte, current->size()); |
122 | #endif |
123 | LSAN_UNREGISTER_ROOT_REGION(current, sizeof(*current)); |
124 | |
125 | if (size == kSegmentSize) { |
126 | MutexLocker ml(segment_cache_mutex); |
127 | ASSERT(segment_cache_size >= 0); |
128 | ASSERT(segment_cache_size <= kSegmentCacheCapacity); |
129 | if (segment_cache_size < kSegmentCacheCapacity) { |
130 | segment_cache[segment_cache_size++] = memory; |
131 | memory = nullptr; |
132 | } |
133 | } |
134 | if (memory != nullptr) { |
135 | total_size_.fetch_sub(size); |
136 | delete memory; |
137 | } |
138 | current = next; |
139 | } |
140 | } |
141 | |
142 | void Zone::Segment::IncrementMemoryCapacity(uintptr_t size) { |
143 | ThreadState* current_thread = ThreadState::Current(); |
144 | if (current_thread != NULL) { |
145 | current_thread->IncrementMemoryCapacity(size); |
146 | } else if (ApiNativeScope::Current() != NULL) { |
147 | // If there is no current thread, we might be inside of a native scope. |
148 | ApiNativeScope::IncrementNativeScopeMemoryCapacity(size); |
149 | } |
150 | } |
151 | |
152 | void Zone::Segment::DecrementMemoryCapacity(uintptr_t size) { |
153 | ThreadState* current_thread = ThreadState::Current(); |
154 | if (current_thread != NULL) { |
155 | current_thread->DecrementMemoryCapacity(size); |
156 | } else if (ApiNativeScope::Current() != NULL) { |
157 | // If there is no current thread, we might be inside of a native scope. |
158 | ApiNativeScope::DecrementNativeScopeMemoryCapacity(size); |
159 | } |
160 | } |
161 | |
162 | // TODO(bkonyi): We need to account for the initial chunk size when a new zone |
163 | // is created within a new thread or ApiNativeScope when calculating high |
164 | // watermarks or memory consumption. |
165 | Zone::Zone() |
166 | : initial_buffer_(buffer_, kInitialChunkSize), |
167 | position_(initial_buffer_.start()), |
168 | limit_(initial_buffer_.end()), |
169 | head_(NULL), |
170 | large_segments_(NULL), |
171 | handles_(), |
172 | previous_(NULL) { |
173 | ASSERT(Utils::IsAligned(position_, kAlignment)); |
174 | Segment::IncrementMemoryCapacity(kInitialChunkSize); |
175 | #ifdef DEBUG |
176 | // Zap the entire initial buffer. |
177 | memset(initial_buffer_.pointer(), kZapUninitializedByte, |
178 | initial_buffer_.size()); |
179 | #endif |
180 | } |
181 | |
182 | Zone::~Zone() { |
183 | if (FLAG_trace_zones) { |
184 | DumpZoneSizes(); |
185 | } |
186 | DeleteAll(); |
187 | Segment::DecrementMemoryCapacity(kInitialChunkSize); |
188 | } |
189 | |
190 | void Zone::DeleteAll() { |
191 | // Traverse the chained list of segments, zapping (in debug mode) |
192 | // and freeing every zone segment. |
193 | if (head_ != NULL) { |
194 | Segment::DeleteSegmentList(head_); |
195 | } |
196 | if (large_segments_ != NULL) { |
197 | Segment::DeleteSegmentList(large_segments_); |
198 | } |
199 | // Reset zone state. |
200 | #ifdef DEBUG |
201 | memset(initial_buffer_.pointer(), kZapDeletedByte, initial_buffer_.size()); |
202 | #endif |
203 | position_ = initial_buffer_.start(); |
204 | limit_ = initial_buffer_.end(); |
205 | small_segment_capacity_ = 0; |
206 | head_ = NULL; |
207 | large_segments_ = NULL; |
208 | previous_ = NULL; |
209 | handles_.Reset(); |
210 | } |
211 | |
212 | uintptr_t Zone::SizeInBytes() const { |
213 | uintptr_t size = 0; |
214 | for (Segment* s = large_segments_; s != NULL; s = s->next()) { |
215 | size += s->size(); |
216 | } |
217 | if (head_ == NULL) { |
218 | return size + (position_ - initial_buffer_.start()); |
219 | } |
220 | size += initial_buffer_.size(); |
221 | for (Segment* s = head_->next(); s != NULL; s = s->next()) { |
222 | size += s->size(); |
223 | } |
224 | return size + (position_ - head_->start()); |
225 | } |
226 | |
227 | uintptr_t Zone::CapacityInBytes() const { |
228 | uintptr_t size = 0; |
229 | for (Segment* s = large_segments_; s != NULL; s = s->next()) { |
230 | size += s->size(); |
231 | } |
232 | if (head_ == NULL) { |
233 | return size + initial_buffer_.size(); |
234 | } |
235 | size += initial_buffer_.size(); |
236 | for (Segment* s = head_; s != NULL; s = s->next()) { |
237 | size += s->size(); |
238 | } |
239 | return size; |
240 | } |
241 | |
242 | uword Zone::AllocateExpand(intptr_t size) { |
243 | ASSERT(size >= 0); |
244 | if (FLAG_trace_zones) { |
245 | OS::PrintErr("*** Expanding zone 0x%" Px "\n" , |
246 | reinterpret_cast<intptr_t>(this)); |
247 | DumpZoneSizes(); |
248 | } |
249 | // Make sure the requested size is already properly aligned and that |
250 | // there isn't enough room in the Zone to satisfy the request. |
251 | ASSERT(Utils::IsAligned(size, kAlignment)); |
252 | intptr_t free_size = (limit_ - position_); |
253 | ASSERT(free_size < size); |
254 | |
255 | // First check to see if we should just chain it as a large segment. |
256 | intptr_t max_size = |
257 | Utils::RoundDown(kSegmentSize - sizeof(Segment), kAlignment); |
258 | ASSERT(max_size > 0); |
259 | if (size > max_size) { |
260 | return AllocateLargeSegment(size); |
261 | } |
262 | |
263 | const intptr_t kSuperPageSize = 2 * MB; |
264 | intptr_t next_size; |
265 | if (small_segment_capacity_ < kSuperPageSize) { |
266 | // When the Zone is small, grow linearly to reduce size and use the segment |
267 | // cache to avoid expensive mmap calls. |
268 | next_size = kSegmentSize; |
269 | } else { |
270 | // When the Zone is large, grow geometrically to avoid Page Table Entry |
271 | // exhaustion. Using 1.125 ratio. |
272 | next_size = Utils::RoundUp(small_segment_capacity_ >> 3, kSuperPageSize); |
273 | } |
274 | ASSERT(next_size >= kSegmentSize); |
275 | |
276 | // Allocate another segment and chain it up. |
277 | head_ = Segment::New(next_size, head_); |
278 | small_segment_capacity_ += next_size; |
279 | |
280 | // Recompute 'position' and 'limit' based on the new head segment. |
281 | uword result = Utils::RoundUp(head_->start(), kAlignment); |
282 | position_ = result + size; |
283 | limit_ = head_->end(); |
284 | ASSERT(position_ <= limit_); |
285 | return result; |
286 | } |
287 | |
288 | uword Zone::AllocateLargeSegment(intptr_t size) { |
289 | ASSERT(size >= 0); |
290 | // Make sure the requested size is already properly aligned and that |
291 | // there isn't enough room in the Zone to satisfy the request. |
292 | ASSERT(Utils::IsAligned(size, kAlignment)); |
293 | intptr_t free_size = (limit_ - position_); |
294 | ASSERT(free_size < size); |
295 | |
296 | // Create a new large segment and chain it up. |
297 | // Account for book keeping fields in size. |
298 | size += Utils::RoundUp(sizeof(Segment), kAlignment); |
299 | large_segments_ = Segment::New(size, large_segments_); |
300 | |
301 | uword result = Utils::RoundUp(large_segments_->start(), kAlignment); |
302 | return result; |
303 | } |
304 | |
305 | char* Zone::MakeCopyOfString(const char* str) { |
306 | intptr_t len = strlen(str) + 1; // '\0'-terminated. |
307 | char* copy = Alloc<char>(len); |
308 | strncpy(copy, str, len); |
309 | return copy; |
310 | } |
311 | |
312 | char* Zone::MakeCopyOfStringN(const char* str, intptr_t len) { |
313 | ASSERT(len >= 0); |
314 | for (intptr_t i = 0; i < len; i++) { |
315 | if (str[i] == '\0') { |
316 | len = i; |
317 | break; |
318 | } |
319 | } |
320 | char* copy = Alloc<char>(len + 1); // +1 for '\0' |
321 | strncpy(copy, str, len); |
322 | copy[len] = '\0'; |
323 | return copy; |
324 | } |
325 | |
326 | char* Zone::ConcatStrings(const char* a, const char* b, char join) { |
327 | intptr_t a_len = (a == NULL) ? 0 : strlen(a); |
328 | const intptr_t b_len = strlen(b) + 1; // '\0'-terminated. |
329 | const intptr_t len = a_len + b_len; |
330 | char* copy = Alloc<char>(len); |
331 | if (a_len > 0) { |
332 | strncpy(copy, a, a_len); |
333 | // Insert join character. |
334 | copy[a_len++] = join; |
335 | } |
336 | strncpy(©[a_len], b, b_len); |
337 | return copy; |
338 | } |
339 | |
340 | void Zone::DumpZoneSizes() { |
341 | intptr_t size = 0; |
342 | for (Segment* s = large_segments_; s != NULL; s = s->next()) { |
343 | size += s->size(); |
344 | } |
345 | OS::PrintErr("*** Zone(0x%" Px |
346 | ") size in bytes," |
347 | " Total = %" Pd " Large Segments = %" Pd "\n" , |
348 | reinterpret_cast<intptr_t>(this), SizeInBytes(), size); |
349 | } |
350 | |
351 | void Zone::VisitObjectPointers(ObjectPointerVisitor* visitor) { |
352 | Zone* zone = this; |
353 | while (zone != NULL) { |
354 | zone->handles()->VisitObjectPointers(visitor); |
355 | zone = zone->previous_; |
356 | } |
357 | } |
358 | |
359 | char* Zone::PrintToString(const char* format, ...) { |
360 | va_list args; |
361 | va_start(args, format); |
362 | char* buffer = OS::VSCreate(this, format, args); |
363 | va_end(args); |
364 | return buffer; |
365 | } |
366 | |
367 | char* Zone::VPrint(const char* format, va_list args) { |
368 | return OS::VSCreate(this, format, args); |
369 | } |
370 | |
371 | StackZone::StackZone(ThreadState* thread) |
372 | : StackResource(thread), zone_(new Zone()) { |
373 | if (FLAG_trace_zones) { |
374 | OS::PrintErr("*** Starting a new Stack zone 0x%" Px "(0x%" Px ")\n" , |
375 | reinterpret_cast<intptr_t>(this), |
376 | reinterpret_cast<intptr_t>(zone_)); |
377 | } |
378 | |
379 | // This thread must be preventing safepoints or the GC could be visiting the |
380 | // chain of handle blocks we're about the mutate. |
381 | ASSERT(Thread::Current()->MayAllocateHandles()); |
382 | |
383 | zone_->Link(thread->zone()); |
384 | thread->set_zone(zone_); |
385 | } |
386 | |
387 | StackZone::~StackZone() { |
388 | // This thread must be preventing safepoints or the GC could be visiting the |
389 | // chain of handle blocks we're about the mutate. |
390 | ASSERT(Thread::Current()->MayAllocateHandles()); |
391 | |
392 | ASSERT(thread()->zone() == zone_); |
393 | thread()->set_zone(zone_->previous_); |
394 | if (FLAG_trace_zones) { |
395 | OS::PrintErr("*** Deleting Stack zone 0x%" Px "(0x%" Px ")\n" , |
396 | reinterpret_cast<intptr_t>(this), |
397 | reinterpret_cast<intptr_t>(zone_)); |
398 | } |
399 | |
400 | delete zone_; |
401 | } |
402 | |
403 | } // namespace dart |
404 | |