| OLD | NEW |
| 1 // Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file | 1 // Copyright (c) 2017, 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 "platform/globals.h" | 5 #include "platform/globals.h" |
| 6 | 6 |
| 7 #if defined(DART_USE_TCMALLOC) && !defined(PRODUCT) | 7 #if defined(DART_USE_TCMALLOC) && !defined(PRODUCT) |
| 8 | 8 |
| 9 #include "vm/malloc_hooks.h" | 9 #include "vm/malloc_hooks.h" |
| 10 | 10 |
| (...skipping 144 matching lines...) Expand 10 before | Expand all | Expand 10 after Loading... |
| 155 private: | 155 private: |
| 156 // Note: sample_ is not owned by AllocationInfo, but by the SampleBuffer | 156 // Note: sample_ is not owned by AllocationInfo, but by the SampleBuffer |
| 157 // created by the profiler. As such, this is only here to track if the sample | 157 // created by the profiler. As such, this is only here to track if the sample |
| 158 // is still associated with a native allocation, and its fields are never | 158 // is still associated with a native allocation, and its fields are never |
| 159 // accessed from this class. | 159 // accessed from this class. |
| 160 Sample* sample_; | 160 Sample* sample_; |
| 161 uword address_; | 161 uword address_; |
| 162 intptr_t allocation_size_; | 162 intptr_t allocation_size_; |
| 163 }; | 163 }; |
| 164 | 164 |
| 165 | |
| 166 // Custom key/value trait specifically for address/size pairs. Unlike | 165 // Custom key/value trait specifically for address/size pairs. Unlike |
| 167 // RawPointerKeyValueTrait, the default value is -1 as 0 can be a valid entry. | 166 // RawPointerKeyValueTrait, the default value is -1 as 0 can be a valid entry. |
| 168 class AddressKeyValueTrait : public AllStatic { | 167 class AddressKeyValueTrait : public AllStatic { |
| 169 public: | 168 public: |
| 170 typedef const void* Key; | 169 typedef const void* Key; |
| 171 typedef AllocationInfo* Value; | 170 typedef AllocationInfo* Value; |
| 172 | 171 |
| 173 struct Pair { | 172 struct Pair { |
| 174 Key key; | 173 Key key; |
| 175 Value value; | 174 Value value; |
| 176 Pair() : key(NULL), value(NULL) {} | 175 Pair() : key(NULL), value(NULL) {} |
| 177 Pair(const Key key, const Value& value) : key(key), value(value) {} | 176 Pair(const Key key, const Value& value) : key(key), value(value) {} |
| 178 Pair(const Pair& other) : key(other.key), value(other.value) {} | 177 Pair(const Pair& other) : key(other.key), value(other.value) {} |
| 179 }; | 178 }; |
| 180 | 179 |
| 181 static Key KeyOf(Pair kv) { return kv.key; } | 180 static Key KeyOf(Pair kv) { return kv.key; } |
| 182 static Value ValueOf(Pair kv) { return kv.value; } | 181 static Value ValueOf(Pair kv) { return kv.value; } |
| 183 static intptr_t Hashcode(Key key) { return reinterpret_cast<intptr_t>(key); } | 182 static intptr_t Hashcode(Key key) { return reinterpret_cast<intptr_t>(key); } |
| 184 static bool IsKeyEqual(Pair kv, Key key) { return kv.key == key; } | 183 static bool IsKeyEqual(Pair kv, Key key) { return kv.key == key; } |
| 185 }; | 184 }; |
| 186 | 185 |
| 187 | |
| 188 // Map class that will be used to store mappings between ptr -> allocation size. | 186 // Map class that will be used to store mappings between ptr -> allocation size. |
| 189 class AddressMap : public MallocDirectChainedHashMap<AddressKeyValueTrait> { | 187 class AddressMap : public MallocDirectChainedHashMap<AddressKeyValueTrait> { |
| 190 public: | 188 public: |
| 191 typedef AddressKeyValueTrait::Key Key; | 189 typedef AddressKeyValueTrait::Key Key; |
| 192 typedef AddressKeyValueTrait::Value Value; | 190 typedef AddressKeyValueTrait::Value Value; |
| 193 typedef AddressKeyValueTrait::Pair Pair; | 191 typedef AddressKeyValueTrait::Pair Pair; |
| 194 | 192 |
| 195 virtual ~AddressMap() { Clear(); } | 193 virtual ~AddressMap() { Clear(); } |
| 196 | 194 |
| 197 void Insert(const Key& key, const Value& value) { | 195 void Insert(const Key& key, const Value& value) { |
| (...skipping 17 matching lines...) Expand all Loading... |
| 215 Pair* result = iter.Next(); | 213 Pair* result = iter.Next(); |
| 216 while (result != NULL) { | 214 while (result != NULL) { |
| 217 delete result->value; | 215 delete result->value; |
| 218 result->value = NULL; | 216 result->value = NULL; |
| 219 result = iter.Next(); | 217 result = iter.Next(); |
| 220 } | 218 } |
| 221 MallocDirectChainedHashMap<AddressKeyValueTrait>::Clear(); | 219 MallocDirectChainedHashMap<AddressKeyValueTrait>::Clear(); |
| 222 } | 220 } |
| 223 }; | 221 }; |
| 224 | 222 |
| 225 | |
| 226 // MallocHooks state / locks. | 223 // MallocHooks state / locks. |
| 227 bool MallocHooksState::active_ = false; | 224 bool MallocHooksState::active_ = false; |
| 228 bool MallocHooksState::stack_trace_collection_enabled_ = false; | 225 bool MallocHooksState::stack_trace_collection_enabled_ = false; |
| 229 intptr_t MallocHooksState::original_pid_ = MallocHooksState::kInvalidPid; | 226 intptr_t MallocHooksState::original_pid_ = MallocHooksState::kInvalidPid; |
| 230 Mutex* MallocHooksState::malloc_hook_mutex_ = new Mutex(); | 227 Mutex* MallocHooksState::malloc_hook_mutex_ = new Mutex(); |
| 231 ThreadId MallocHooksState::malloc_hook_mutex_owner_ = | 228 ThreadId MallocHooksState::malloc_hook_mutex_owner_ = |
| 232 OSThread::kInvalidThreadId; | 229 OSThread::kInvalidThreadId; |
| 233 | 230 |
| 234 // Memory allocation state information. | 231 // Memory allocation state information. |
| 235 intptr_t MallocHooksState::allocation_count_ = 0; | 232 intptr_t MallocHooksState::allocation_count_ = 0; |
| 236 intptr_t MallocHooksState::heap_allocated_memory_in_bytes_ = 0; | 233 intptr_t MallocHooksState::heap_allocated_memory_in_bytes_ = 0; |
| 237 AddressMap* MallocHooksState::address_map_ = NULL; | 234 AddressMap* MallocHooksState::address_map_ = NULL; |
| 238 | 235 |
| 239 | |
| 240 void MallocHooksState::Init() { | 236 void MallocHooksState::Init() { |
| 241 address_map_ = new AddressMap(); | 237 address_map_ = new AddressMap(); |
| 242 active_ = true; | 238 active_ = true; |
| 243 #if defined(DEBUG) | 239 #if defined(DEBUG) |
| 244 stack_trace_collection_enabled_ = true; | 240 stack_trace_collection_enabled_ = true; |
| 245 #else | 241 #else |
| 246 stack_trace_collection_enabled_ = false; | 242 stack_trace_collection_enabled_ = false; |
| 247 #endif // defined(DEBUG) | 243 #endif // defined(DEBUG) |
| 248 original_pid_ = OS::ProcessId(); | 244 original_pid_ = OS::ProcessId(); |
| 249 } | 245 } |
| 250 | 246 |
| 251 | |
| 252 void MallocHooksState::ResetStats() { | 247 void MallocHooksState::ResetStats() { |
| 253 ASSERT(malloc_hook_mutex()->IsOwnedByCurrentThread()); | 248 ASSERT(malloc_hook_mutex()->IsOwnedByCurrentThread()); |
| 254 allocation_count_ = 0; | 249 allocation_count_ = 0; |
| 255 heap_allocated_memory_in_bytes_ = 0; | 250 heap_allocated_memory_in_bytes_ = 0; |
| 256 address_map_->Clear(); | 251 address_map_->Clear(); |
| 257 } | 252 } |
| 258 | 253 |
| 259 | |
| 260 void MallocHooksState::TearDown() { | 254 void MallocHooksState::TearDown() { |
| 261 ASSERT(malloc_hook_mutex()->IsOwnedByCurrentThread()); | 255 ASSERT(malloc_hook_mutex()->IsOwnedByCurrentThread()); |
| 262 active_ = false; | 256 active_ = false; |
| 263 original_pid_ = kInvalidPid; | 257 original_pid_ = kInvalidPid; |
| 264 ResetStats(); | 258 ResetStats(); |
| 265 delete address_map_; | 259 delete address_map_; |
| 266 address_map_ = NULL; | 260 address_map_ = NULL; |
| 267 } | 261 } |
| 268 | 262 |
| 269 | |
| 270 void MallocHooks::InitOnce() { | 263 void MallocHooks::InitOnce() { |
| 271 if (!FLAG_profiler_native_memory || MallocHooks::Active()) { | 264 if (!FLAG_profiler_native_memory || MallocHooks::Active()) { |
| 272 return; | 265 return; |
| 273 } | 266 } |
| 274 MallocLocker ml(MallocHooksState::malloc_hook_mutex(), | 267 MallocLocker ml(MallocHooksState::malloc_hook_mutex(), |
| 275 MallocHooksState::malloc_hook_mutex_owner()); | 268 MallocHooksState::malloc_hook_mutex_owner()); |
| 276 ASSERT(!MallocHooksState::Active()); | 269 ASSERT(!MallocHooksState::Active()); |
| 277 | 270 |
| 278 MallocHooksState::Init(); | 271 MallocHooksState::Init(); |
| 279 | 272 |
| 280 // Register malloc hooks. | 273 // Register malloc hooks. |
| 281 bool success = false; | 274 bool success = false; |
| 282 success = MallocHook::AddNewHook(&MallocHooksState::RecordAllocHook); | 275 success = MallocHook::AddNewHook(&MallocHooksState::RecordAllocHook); |
| 283 ASSERT(success); | 276 ASSERT(success); |
| 284 success = MallocHook::AddDeleteHook(&MallocHooksState::RecordFreeHook); | 277 success = MallocHook::AddDeleteHook(&MallocHooksState::RecordFreeHook); |
| 285 ASSERT(success); | 278 ASSERT(success); |
| 286 } | 279 } |
| 287 | 280 |
| 288 | |
| 289 void MallocHooks::TearDown() { | 281 void MallocHooks::TearDown() { |
| 290 if (!FLAG_profiler_native_memory || !MallocHooks::Active()) { | 282 if (!FLAG_profiler_native_memory || !MallocHooks::Active()) { |
| 291 return; | 283 return; |
| 292 } | 284 } |
| 293 MallocLocker ml(MallocHooksState::malloc_hook_mutex(), | 285 MallocLocker ml(MallocHooksState::malloc_hook_mutex(), |
| 294 MallocHooksState::malloc_hook_mutex_owner()); | 286 MallocHooksState::malloc_hook_mutex_owner()); |
| 295 ASSERT(MallocHooksState::Active()); | 287 ASSERT(MallocHooksState::Active()); |
| 296 | 288 |
| 297 // Remove malloc hooks. | 289 // Remove malloc hooks. |
| 298 bool success = false; | 290 bool success = false; |
| 299 success = MallocHook::RemoveNewHook(&MallocHooksState::RecordAllocHook); | 291 success = MallocHook::RemoveNewHook(&MallocHooksState::RecordAllocHook); |
| 300 ASSERT(success); | 292 ASSERT(success); |
| 301 success = MallocHook::RemoveDeleteHook(&MallocHooksState::RecordFreeHook); | 293 success = MallocHook::RemoveDeleteHook(&MallocHooksState::RecordFreeHook); |
| 302 ASSERT(success); | 294 ASSERT(success); |
| 303 | 295 |
| 304 MallocHooksState::TearDown(); | 296 MallocHooksState::TearDown(); |
| 305 } | 297 } |
| 306 | 298 |
| 307 | |
| 308 bool MallocHooks::ProfilingEnabled() { | 299 bool MallocHooks::ProfilingEnabled() { |
| 309 return MallocHooksState::ProfilingEnabled(); | 300 return MallocHooksState::ProfilingEnabled(); |
| 310 } | 301 } |
| 311 | 302 |
| 312 | |
| 313 bool MallocHooks::stack_trace_collection_enabled() { | 303 bool MallocHooks::stack_trace_collection_enabled() { |
| 314 MallocLocker ml(MallocHooksState::malloc_hook_mutex(), | 304 MallocLocker ml(MallocHooksState::malloc_hook_mutex(), |
| 315 MallocHooksState::malloc_hook_mutex_owner()); | 305 MallocHooksState::malloc_hook_mutex_owner()); |
| 316 return MallocHooksState::stack_trace_collection_enabled(); | 306 return MallocHooksState::stack_trace_collection_enabled(); |
| 317 } | 307 } |
| 318 | 308 |
| 319 | |
| 320 void MallocHooks::set_stack_trace_collection_enabled(bool enabled) { | 309 void MallocHooks::set_stack_trace_collection_enabled(bool enabled) { |
| 321 MallocLocker ml(MallocHooksState::malloc_hook_mutex(), | 310 MallocLocker ml(MallocHooksState::malloc_hook_mutex(), |
| 322 MallocHooksState::malloc_hook_mutex_owner()); | 311 MallocHooksState::malloc_hook_mutex_owner()); |
| 323 MallocHooksState::set_stack_trace_collection_enabled(enabled); | 312 MallocHooksState::set_stack_trace_collection_enabled(enabled); |
| 324 } | 313 } |
| 325 | 314 |
| 326 | |
| 327 void MallocHooks::ResetStats() { | 315 void MallocHooks::ResetStats() { |
| 328 if (!FLAG_profiler_native_memory) { | 316 if (!FLAG_profiler_native_memory) { |
| 329 return; | 317 return; |
| 330 } | 318 } |
| 331 MallocLocker ml(MallocHooksState::malloc_hook_mutex(), | 319 MallocLocker ml(MallocHooksState::malloc_hook_mutex(), |
| 332 MallocHooksState::malloc_hook_mutex_owner()); | 320 MallocHooksState::malloc_hook_mutex_owner()); |
| 333 if (MallocHooksState::Active()) { | 321 if (MallocHooksState::Active()) { |
| 334 MallocHooksState::ResetStats(); | 322 MallocHooksState::ResetStats(); |
| 335 } | 323 } |
| 336 } | 324 } |
| 337 | 325 |
| 338 | |
| 339 bool MallocHooks::Active() { | 326 bool MallocHooks::Active() { |
| 340 if (!FLAG_profiler_native_memory) { | 327 if (!FLAG_profiler_native_memory) { |
| 341 return false; | 328 return false; |
| 342 } | 329 } |
| 343 MallocLocker ml(MallocHooksState::malloc_hook_mutex(), | 330 MallocLocker ml(MallocHooksState::malloc_hook_mutex(), |
| 344 MallocHooksState::malloc_hook_mutex_owner()); | 331 MallocHooksState::malloc_hook_mutex_owner()); |
| 345 | 332 |
| 346 return MallocHooksState::Active(); | 333 return MallocHooksState::Active(); |
| 347 } | 334 } |
| 348 | 335 |
| 349 | |
| 350 void MallocHooks::PrintToJSONObject(JSONObject* jsobj) { | 336 void MallocHooks::PrintToJSONObject(JSONObject* jsobj) { |
| 351 if (!FLAG_profiler_native_memory) { | 337 if (!FLAG_profiler_native_memory) { |
| 352 return; | 338 return; |
| 353 } | 339 } |
| 354 intptr_t allocated_memory = 0; | 340 intptr_t allocated_memory = 0; |
| 355 intptr_t allocation_count = 0; | 341 intptr_t allocation_count = 0; |
| 356 bool add_usage = false; | 342 bool add_usage = false; |
| 357 // AddProperty may call malloc which would result in an attempt | 343 // AddProperty may call malloc which would result in an attempt |
| 358 // to acquire the lock recursively so we extract the values first | 344 // to acquire the lock recursively so we extract the values first |
| 359 // and then add the JSON properties. | 345 // and then add the JSON properties. |
| 360 { | 346 { |
| 361 MallocLocker ml(MallocHooksState::malloc_hook_mutex(), | 347 MallocLocker ml(MallocHooksState::malloc_hook_mutex(), |
| 362 MallocHooksState::malloc_hook_mutex_owner()); | 348 MallocHooksState::malloc_hook_mutex_owner()); |
| 363 if (MallocHooksState::Active()) { | 349 if (MallocHooksState::Active()) { |
| 364 allocated_memory = MallocHooksState::heap_allocated_memory_in_bytes(); | 350 allocated_memory = MallocHooksState::heap_allocated_memory_in_bytes(); |
| 365 allocation_count = MallocHooksState::allocation_count(); | 351 allocation_count = MallocHooksState::allocation_count(); |
| 366 add_usage = true; | 352 add_usage = true; |
| 367 } | 353 } |
| 368 } | 354 } |
| 369 if (add_usage) { | 355 if (add_usage) { |
| 370 jsobj->AddProperty("_heapAllocatedMemoryUsage", allocated_memory); | 356 jsobj->AddProperty("_heapAllocatedMemoryUsage", allocated_memory); |
| 371 jsobj->AddProperty("_heapAllocationCount", allocation_count); | 357 jsobj->AddProperty("_heapAllocationCount", allocation_count); |
| 372 } | 358 } |
| 373 } | 359 } |
| 374 | 360 |
| 375 | |
| 376 intptr_t MallocHooks::allocation_count() { | 361 intptr_t MallocHooks::allocation_count() { |
| 377 if (!FLAG_profiler_native_memory) { | 362 if (!FLAG_profiler_native_memory) { |
| 378 return 0; | 363 return 0; |
| 379 } | 364 } |
| 380 MallocLocker ml(MallocHooksState::malloc_hook_mutex(), | 365 MallocLocker ml(MallocHooksState::malloc_hook_mutex(), |
| 381 MallocHooksState::malloc_hook_mutex_owner()); | 366 MallocHooksState::malloc_hook_mutex_owner()); |
| 382 return MallocHooksState::allocation_count(); | 367 return MallocHooksState::allocation_count(); |
| 383 } | 368 } |
| 384 | 369 |
| 385 | |
| 386 intptr_t MallocHooks::heap_allocated_memory_in_bytes() { | 370 intptr_t MallocHooks::heap_allocated_memory_in_bytes() { |
| 387 if (!FLAG_profiler_native_memory) { | 371 if (!FLAG_profiler_native_memory) { |
| 388 return 0; | 372 return 0; |
| 389 } | 373 } |
| 390 MallocLocker ml(MallocHooksState::malloc_hook_mutex(), | 374 MallocLocker ml(MallocHooksState::malloc_hook_mutex(), |
| 391 MallocHooksState::malloc_hook_mutex_owner()); | 375 MallocHooksState::malloc_hook_mutex_owner()); |
| 392 return MallocHooksState::heap_allocated_memory_in_bytes(); | 376 return MallocHooksState::heap_allocated_memory_in_bytes(); |
| 393 } | 377 } |
| 394 | 378 |
| 395 | |
| 396 Sample* MallocHooks::GetSample(const void* ptr) { | 379 Sample* MallocHooks::GetSample(const void* ptr) { |
| 397 MallocLocker ml(MallocHooksState::malloc_hook_mutex(), | 380 MallocLocker ml(MallocHooksState::malloc_hook_mutex(), |
| 398 MallocHooksState::malloc_hook_mutex_owner()); | 381 MallocHooksState::malloc_hook_mutex_owner()); |
| 399 | 382 |
| 400 ASSERT(MallocHooksState::Active()); | 383 ASSERT(MallocHooksState::Active()); |
| 401 | 384 |
| 402 if (ptr != NULL) { | 385 if (ptr != NULL) { |
| 403 AllocationInfo* allocation_info = NULL; | 386 AllocationInfo* allocation_info = NULL; |
| 404 if (MallocHooksState::address_map()->Lookup(ptr, &allocation_info)) { | 387 if (MallocHooksState::address_map()->Lookup(ptr, &allocation_info)) { |
| 405 ASSERT(allocation_info != NULL); | 388 ASSERT(allocation_info != NULL); |
| 406 return allocation_info->sample(); | 389 return allocation_info->sample(); |
| 407 } | 390 } |
| 408 } | 391 } |
| 409 return NULL; | 392 return NULL; |
| 410 } | 393 } |
| 411 | 394 |
| 412 | |
| 413 void MallocHooksState::RecordAllocHook(const void* ptr, size_t size) { | 395 void MallocHooksState::RecordAllocHook(const void* ptr, size_t size) { |
| 414 if (MallocHooksState::IsLockHeldByCurrentThread() || | 396 if (MallocHooksState::IsLockHeldByCurrentThread() || |
| 415 !MallocHooksState::IsOriginalProcess()) { | 397 !MallocHooksState::IsOriginalProcess()) { |
| 416 return; | 398 return; |
| 417 } | 399 } |
| 418 | 400 |
| 419 MallocLocker ml(MallocHooksState::malloc_hook_mutex(), | 401 MallocLocker ml(MallocHooksState::malloc_hook_mutex(), |
| 420 MallocHooksState::malloc_hook_mutex_owner()); | 402 MallocHooksState::malloc_hook_mutex_owner()); |
| 421 // Now that we hold the lock, check to make sure everything is still active. | 403 // Now that we hold the lock, check to make sure everything is still active. |
| 422 if ((ptr != NULL) && MallocHooksState::Active()) { | 404 if ((ptr != NULL) && MallocHooksState::Active()) { |
| 423 MallocHooksState::IncrementHeapAllocatedMemoryInBytes(size); | 405 MallocHooksState::IncrementHeapAllocatedMemoryInBytes(size); |
| 424 MallocHooksState::address_map()->Insert( | 406 MallocHooksState::address_map()->Insert( |
| 425 ptr, new AllocationInfo(reinterpret_cast<uword>(ptr), size)); | 407 ptr, new AllocationInfo(reinterpret_cast<uword>(ptr), size)); |
| 426 } | 408 } |
| 427 } | 409 } |
| 428 | 410 |
| 429 | |
| 430 void MallocHooksState::RecordFreeHook(const void* ptr) { | 411 void MallocHooksState::RecordFreeHook(const void* ptr) { |
| 431 if (MallocHooksState::IsLockHeldByCurrentThread() || | 412 if (MallocHooksState::IsLockHeldByCurrentThread() || |
| 432 !MallocHooksState::IsOriginalProcess()) { | 413 !MallocHooksState::IsOriginalProcess()) { |
| 433 return; | 414 return; |
| 434 } | 415 } |
| 435 | 416 |
| 436 MallocLocker ml(MallocHooksState::malloc_hook_mutex(), | 417 MallocLocker ml(MallocHooksState::malloc_hook_mutex(), |
| 437 MallocHooksState::malloc_hook_mutex_owner()); | 418 MallocHooksState::malloc_hook_mutex_owner()); |
| 438 // Now that we hold the lock, check to make sure everything is still active. | 419 // Now that we hold the lock, check to make sure everything is still active. |
| 439 if ((ptr != NULL) && MallocHooksState::Active()) { | 420 if ((ptr != NULL) && MallocHooksState::Active()) { |
| 440 AllocationInfo* allocation_info = NULL; | 421 AllocationInfo* allocation_info = NULL; |
| 441 if (MallocHooksState::address_map()->Lookup(ptr, &allocation_info)) { | 422 if (MallocHooksState::address_map()->Lookup(ptr, &allocation_info)) { |
| 442 MallocHooksState::DecrementHeapAllocatedMemoryInBytes( | 423 MallocHooksState::DecrementHeapAllocatedMemoryInBytes( |
| 443 allocation_info->allocation_size()); | 424 allocation_info->allocation_size()); |
| 444 const bool result = MallocHooksState::address_map()->Remove(ptr); | 425 const bool result = MallocHooksState::address_map()->Remove(ptr); |
| 445 ASSERT(result); | 426 ASSERT(result); |
| 446 delete allocation_info; | 427 delete allocation_info; |
| 447 } | 428 } |
| 448 } | 429 } |
| 449 } | 430 } |
| 450 | 431 |
| 451 } // namespace dart | 432 } // namespace dart |
| 452 | 433 |
| 453 #endif // defined(DART_USE_TCMALLOC) && !defined(PRODUCT) | 434 #endif // defined(DART_USE_TCMALLOC) && !defined(PRODUCT) |
| OLD | NEW |