1
0
mirror of https://github.com/dart-lang/sdk synced 2024-07-08 20:16:39 +00:00

Reintroducing MallocHooks changes with fix for infinite loop in MallocHooks on Platform::Exit.

BUG=
R=zra@google.com

Review-Url: https://codereview.chromium.org/2643303003 .
This commit is contained in:
Ben Konyi 2017-01-20 15:26:58 -08:00
parent a7f7a311c0
commit 7bf5d87017
13 changed files with 510 additions and 46 deletions

View File

@ -2,6 +2,8 @@
# for details. All rights reserved. Use of this source code is governed by a
# BSD-style license that can be found in the LICENSE file.
import("runtime_args.gni")
declare_args() {
# Instead of using is_debug, we introduce a different flag for specifying a
# Debug build of Dart so that clients can still use a Release build of Dart
@ -146,6 +148,12 @@ config("dart_config") {
defines += [ "NDEBUG" ]
}
include_dirs = []
if (dart_use_tcmalloc) {
defines += [ "DART_USE_TCMALLOC" ]
include_dirs += [ "../third_party/tcmalloc/gperftools/src" ]
}
if (!is_win) {
cflags = [
"-Werror",

View File

@ -297,6 +297,10 @@ executable("gen_snapshot") {
include_dirs = [ ".." ]
if (dart_use_tcmalloc) {
deps += [ "//third_party/tcmalloc" ]
}
if (is_mac) {
libs = [
"CoreFoundation.framework",
@ -574,7 +578,6 @@ template("dart_executable") {
if (dart_use_tcmalloc) {
deps += [ "//third_party/tcmalloc" ]
defines += [ "DART_USE_TCMALLOC" ]
}
sources = [

View File

@ -1151,9 +1151,6 @@
'dependencies': [
'../third_party/tcmalloc/tcmalloc.gypi:tcmalloc',
],
'defines': [
'DART_USE_TCMALLOC'
],
}],
],
'configurations': {
@ -1348,9 +1345,6 @@
'dependencies': [
'../third_party/tcmalloc/tcmalloc.gypi:tcmalloc',
],
'defines': [
'DART_USE_TCMALLOC'
],
}],
],
'configurations': {

View File

@ -383,6 +383,7 @@ static void WriteSnapshotFile(const char* filename,
Log::PrintErr("Error: Unable to write snapshot file: %s\n\n", filename);
Dart_ExitScope();
Dart_ShutdownIsolate();
Dart_Cleanup();
exit(kErrorExitCode);
}
if (!file->WriteFully(buffer, size)) {

View File

@ -17,6 +17,7 @@
#include "vm/heap.h"
#include "vm/isolate.h"
#include "vm/kernel_isolate.h"
#include "vm/malloc_hooks.h"
#include "vm/message_handler.h"
#include "vm/metrics.h"
#include "vm/object.h"
@ -154,6 +155,7 @@ char* Dart::InitOnce(const uint8_t* vm_isolate_snapshot,
start_time_micros_ = OS::GetCurrentMonotonicMicros();
VirtualMemory::InitOnce();
OSThread::InitOnce();
MallocHooks::InitOnce();
if (FLAG_support_timeline) {
Timeline::InitOnce();
}
@ -491,7 +493,7 @@ const char* Dart::Cleanup() {
if (FLAG_trace_shutdown) {
OS::PrintErr("[+%" Pd64 "ms] SHUTDOWN: Done\n", UptimeMillis());
}
MallocHooks::TearDown();
return NULL;
}

View File

@ -33,6 +33,7 @@ class BaseDirectChainedHashMap : public B {
}
void Insert(typename KeyValueTrait::Pair kv);
bool Remove(typename KeyValueTrait::Key key);
typename KeyValueTrait::Value LookupValue(
typename KeyValueTrait::Key key) const;
@ -308,6 +309,68 @@ void BaseDirectChainedHashMap<KeyValueTrait, B, Allocator>::Insert(
}
template <typename KeyValueTrait, typename B, typename Allocator>
bool BaseDirectChainedHashMap<KeyValueTrait, B, Allocator>::Remove(
typename KeyValueTrait::Key key) {
uword pos = Bound(static_cast<uword>(KeyValueTrait::Hashcode(key)));
// Check to see if the first element in the bucket is the one we want to
// remove.
if (KeyValueTrait::KeyOf(array_[pos].kv) == key) {
if (array_[pos].next == kNil) {
array_[pos] = HashMapListElement();
} else {
intptr_t next = array_[pos].next;
array_[pos] = lists_[next];
lists_[next] = HashMapListElement();
lists_[next].next = free_list_head_;
free_list_head_ = next;
}
count_--;
return true;
}
intptr_t current = array_[pos].next;
// If there's only the single element in the bucket and it does not match the
// key to be removed, just return.
if (current == kNil) {
return false;
}
// Check the case where the second element in the bucket is the one to be
// removed.
if (KeyValueTrait::KeyOf(lists_[current].kv) == key) {
array_[pos].next = lists_[current].next;
lists_[current] = HashMapListElement();
lists_[current].next = free_list_head_;
free_list_head_ = current;
count_--;
return true;
}
// Finally, iterate through the rest of the bucket to see if we can find the
// entry that matches our key.
intptr_t previous;
while (KeyValueTrait::KeyOf(lists_[current].kv) != key) {
previous = current;
current = lists_[current].next;
if (current == kNil) {
// Could not find entry with provided key to remove.
return false;
}
}
lists_[previous].next = lists_[current].next;
lists_[current] = HashMapListElement();
lists_[current].next = free_list_head_;
free_list_head_ = current;
count_--;
return true;
}
template <typename KeyValueTrait>
class DirectChainedHashMap
: public BaseDirectChainedHashMap<KeyValueTrait, ValueObject> {

View File

@ -31,6 +31,9 @@ TEST_CASE(DirectChainedHashMap) {
EXPECT(map.LookupValue(&v1) == &v1);
EXPECT(map.LookupValue(&v2) == &v2);
EXPECT(map.LookupValue(&v3) == &v1);
EXPECT(map.Remove(&v1));
EXPECT(map.Lookup(&v1) == NULL);
map.Insert(&v1);
DirectChainedHashMap<PointerKeyValueTrait<TestValue> > map2(map);
EXPECT(map2.LookupValue(&v1) == &v1);
EXPECT(map2.LookupValue(&v2) == &v2);
@ -38,6 +41,64 @@ TEST_CASE(DirectChainedHashMap) {
}
TEST_CASE(DirectChainedHashMapInsertRemove) {
DirectChainedHashMap<PointerKeyValueTrait<TestValue> > map;
EXPECT(map.IsEmpty());
TestValue v1(1);
TestValue v2(3); // Note: v1, v2, v3 should have the same hash.
TestValue v3(5);
// Start with adding and removing the same element.
map.Insert(&v1);
EXPECT(map.LookupValue(&v1) == &v1);
EXPECT(map.Remove(&v1));
EXPECT(map.Lookup(&v1) == NULL);
// Inserting v2 first should put it at the head of the list.
map.Insert(&v2);
map.Insert(&v1);
EXPECT(map.LookupValue(&v2) == &v2);
EXPECT(map.LookupValue(&v1) == &v1);
// Check to see if removing the head of the list causes issues.
EXPECT(map.Remove(&v2));
EXPECT(map.Lookup(&v2) == NULL);
EXPECT(map.LookupValue(&v1) == &v1);
// Reinsert v2, which will place it at the back of the hash map list.
map.Insert(&v2);
EXPECT(map.LookupValue(&v2) == &v2);
// Remove from the back of the hash map list.
EXPECT(map.Remove(&v2));
EXPECT(map.Lookup(&v2) == NULL);
EXPECT(map.Remove(&v1));
EXPECT(map.Lookup(&v1) == NULL);
// Check to see that removing an invalid element returns false.
EXPECT(!map.Remove(&v1));
// One last case: remove from the middle of a hash map list.
map.Insert(&v1);
map.Insert(&v2);
map.Insert(&v3);
EXPECT(map.LookupValue(&v1) == &v1);
EXPECT(map.LookupValue(&v2) == &v2);
EXPECT(map.LookupValue(&v3) == &v3);
EXPECT(map.Remove(&v2));
EXPECT(map.LookupValue(&v1) == &v1);
EXPECT(map.Lookup(&v2) == NULL);
EXPECT(map.LookupValue(&v3) == &v3);
EXPECT(map.Remove(&v1));
EXPECT(map.Remove(&v3));
EXPECT(map.IsEmpty());
}
TEST_CASE(MallocDirectChainedHashMap) {
MallocDirectChainedHashMap<PointerKeyValueTrait<TestValue> > map;
EXPECT(map.IsEmpty());

View File

@ -2,16 +2,267 @@
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
#if defined(DART_USE_TCMALLOC)
#include "platform/globals.h"
#if defined(DART_USE_TCMALLOC) && !defined(PRODUCT)
#include "vm/malloc_hooks.h"
#include "gperftools/malloc_hook.h"
#include "platform/assert.h"
#include "vm/hash_map.h"
#include "vm/lockers.h"
namespace dart {
void MallocHooks::Init() {
// TODO(bkonyi): Implement
// A locker-type class to automatically grab and release the
// in_malloc_hook_flag_.
class MallocHookScope {
public:
static void InitMallocHookFlag() {
ASSERT(in_malloc_hook_flag_ == kUnsetThreadLocalKey);
in_malloc_hook_flag_ = OSThread::CreateThreadLocal();
OSThread::SetThreadLocal(in_malloc_hook_flag_, 0);
}
static void DestroyMallocHookFlag() {
ASSERT(in_malloc_hook_flag_ != kUnsetThreadLocalKey);
OSThread::DeleteThreadLocal(in_malloc_hook_flag_);
in_malloc_hook_flag_ = kUnsetThreadLocalKey;
}
MallocHookScope() {
ASSERT(in_malloc_hook_flag_ != kUnsetThreadLocalKey);
OSThread::SetThreadLocal(in_malloc_hook_flag_, 1);
}
~MallocHookScope() {
ASSERT(in_malloc_hook_flag_ != kUnsetThreadLocalKey);
OSThread::SetThreadLocal(in_malloc_hook_flag_, 0);
}
static bool IsInHook() {
ASSERT(in_malloc_hook_flag_ != kUnsetThreadLocalKey);
return OSThread::GetThreadLocal(in_malloc_hook_flag_);
}
private:
static ThreadLocalKey in_malloc_hook_flag_;
DISALLOW_ALLOCATION();
DISALLOW_COPY_AND_ASSIGN(MallocHookScope);
};
// Custom key/value trait specifically for address/size pairs. Unlike
// RawPointerKeyValueTrait, the default value is -1 as 0 can be a valid entry.
class AddressKeyValueTrait {
public:
typedef const void* Key;
typedef intptr_t Value;
struct Pair {
Key key;
Value value;
Pair() : key(NULL), value(-1) {}
Pair(const Key key, const Value& value) : key(key), value(value) {}
Pair(const Pair& other) : key(other.key), value(other.value) {}
};
static Key KeyOf(Pair kv) { return kv.key; }
static Value ValueOf(Pair kv) { return kv.value; }
static intptr_t Hashcode(Key key) { return reinterpret_cast<intptr_t>(key); }
static bool IsKeyEqual(Pair kv, Key key) { return kv.key == key; }
};
// Map class that will be used to store mappings between ptr -> allocation size.
class AddressMap : public MallocDirectChainedHashMap<AddressKeyValueTrait> {
public:
typedef AddressKeyValueTrait::Key Key;
typedef AddressKeyValueTrait::Value Value;
typedef AddressKeyValueTrait::Pair Pair;
inline void Insert(const Key& key, const Value& value) {
Pair pair(key, value);
MallocDirectChainedHashMap<AddressKeyValueTrait>::Insert(pair);
}
inline bool Lookup(const Key& key, Value* value) {
ASSERT(value != NULL);
Pair* pair = MallocDirectChainedHashMap<AddressKeyValueTrait>::Lookup(key);
if (pair == NULL) {
return false;
} else {
*value = pair->value;
return true;
}
}
};
class MallocHooksState {
public:
static void RecordAllocHook(const void* ptr, size_t size);
static void RecordFreeHook(const void* ptr);
static bool initialized() { return initialized_; }
static void Init() {
address_map_ = new AddressMap();
initialized_ = true;
}
static Mutex* malloc_hook_mutex() { return malloc_hook_mutex_; }
static intptr_t allocation_count() { return allocation_count_; }
static intptr_t heap_allocated_memory_in_bytes() {
return heap_allocated_memory_in_bytes_;
}
static void IncrementHeapAllocatedMemoryInBytes(intptr_t size) {
ASSERT(size >= 0);
heap_allocated_memory_in_bytes_ += size;
++allocation_count_;
}
static void DecrementHeapAllocatedMemoryInBytes(intptr_t size) {
ASSERT(size >= 0);
ASSERT(heap_allocated_memory_in_bytes_ >= size);
heap_allocated_memory_in_bytes_ -= size;
--allocation_count_;
ASSERT(allocation_count_ >= 0);
}
static AddressMap* address_map() { return address_map_; }
static void ResetStats() {
allocation_count_ = 0;
heap_allocated_memory_in_bytes_ = 0;
address_map_->Clear();
}
static void TearDown() {
initialized_ = false;
ResetStats();
delete address_map_;
}
private:
static bool initialized_;
static Mutex* malloc_hook_mutex_;
static intptr_t allocation_count_;
static intptr_t heap_allocated_memory_in_bytes_;
static AddressMap* address_map_;
private:
DISALLOW_ALLOCATION();
DISALLOW_COPY_AND_ASSIGN(MallocHooksState);
};
// MallocHooks state / locks.
ThreadLocalKey MallocHookScope::in_malloc_hook_flag_ = kUnsetThreadLocalKey;
bool MallocHooksState::initialized_ = false;
Mutex* MallocHooksState::malloc_hook_mutex_ = new Mutex();
// Memory allocation state information.
intptr_t MallocHooksState::allocation_count_ = 0;
intptr_t MallocHooksState::heap_allocated_memory_in_bytes_ = 0;
AddressMap* MallocHooksState::address_map_ = NULL;
void MallocHooks::InitOnce() {
MutexLocker ml(MallocHooksState::malloc_hook_mutex());
ASSERT(!MallocHooksState::initialized());
MallocHookScope::InitMallocHookFlag();
MallocHooksState::Init();
// Register malloc hooks.
bool success = false;
success = MallocHook::AddNewHook(&MallocHooksState::RecordAllocHook);
ASSERT(success);
success = MallocHook::AddDeleteHook(&MallocHooksState::RecordFreeHook);
ASSERT(success);
}
void MallocHooks::TearDown() {
MutexLocker ml(MallocHooksState::malloc_hook_mutex());
ASSERT(MallocHooksState::initialized());
// Remove malloc hooks.
bool success = false;
success = MallocHook::RemoveNewHook(&MallocHooksState::RecordAllocHook);
ASSERT(success);
success = MallocHook::RemoveDeleteHook(&MallocHooksState::RecordFreeHook);
ASSERT(success);
MallocHooksState::TearDown();
MallocHookScope::DestroyMallocHookFlag();
}
void MallocHooks::ResetStats() {
MutexLocker ml(MallocHooksState::malloc_hook_mutex());
ASSERT(MallocHooksState::initialized());
MallocHooksState::ResetStats();
}
intptr_t MallocHooks::allocation_count() {
MutexLocker ml(MallocHooksState::malloc_hook_mutex());
return MallocHooksState::allocation_count();
}
intptr_t MallocHooks::heap_allocated_memory_in_bytes() {
MutexLocker ml(MallocHooksState::malloc_hook_mutex());
return MallocHooksState::heap_allocated_memory_in_bytes();
}
void MallocHooksState::RecordAllocHook(const void* ptr, size_t size) {
if (MallocHookScope::IsInHook()) {
return;
}
// Set the malloc hook flag before grabbing the mutex to avoid calling hooks
// again.
MallocHookScope mhs;
MutexLocker ml(MallocHooksState::malloc_hook_mutex());
ASSERT(MallocHooksState::initialized());
if (ptr != NULL) {
MallocHooksState::IncrementHeapAllocatedMemoryInBytes(size);
MallocHooksState::address_map()->Insert(ptr, size);
}
}
void MallocHooksState::RecordFreeHook(const void* ptr) {
if (MallocHookScope::IsInHook()) {
return;
}
// Set the malloc hook flag before grabbing the mutex to avoid calling hooks
// again.
MallocHookScope mhs;
MutexLocker ml(MallocHooksState::malloc_hook_mutex());
ASSERT(MallocHooksState::initialized());
if (ptr != NULL) {
intptr_t size = 0;
if (MallocHooksState::address_map()->Lookup(ptr, &size)) {
MallocHooksState::DecrementHeapAllocatedMemoryInBytes(size);
MallocHooksState::address_map()->Remove(ptr);
}
}
}
} // namespace dart
#endif // defined(DART_USE_TCMALLOC)
#endif // defined(DART_USE_TCMALLOC) && !defined(PRODUCT)

View File

@ -10,7 +10,13 @@
namespace dart {
class MallocHooks {
static void Init();
public:
static void InitOnce();
static void TearDown();
static void ResetStats();
static intptr_t allocation_count();
static intptr_t heap_allocated_memory_in_bytes();
private:
DISALLOW_ALLOCATION();

View File

@ -0,0 +1,80 @@
// Copyright (c) 2017, the Dart project authors. Please see the AUTHORS file
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
#include "platform/globals.h"
#if defined(DART_USE_TCMALLOC) && !defined(PRODUCT)
#include "platform/assert.h"
#include "vm/class_finalizer.h"
#include "vm/globals.h"
#include "vm/malloc_hooks.h"
#include "vm/symbols.h"
#include "vm/unit_test.h"
namespace dart {
// Note: for these tests, there is no need to call MallocHooks::Init() or
// MallocHooks::TearDown() as this is all done by the VM test framework.
static void MallocHookTestBufferInitializer(volatile char* buffer,
uintptr_t size) {
// Run through the buffer and do something. If we don't do this and the memory
// in buffer isn't touched, the tcmalloc hooks won't be called.
for (uintptr_t i = 0; i < size; ++i) {
buffer[i] = i;
}
}
UNIT_TEST_CASE(BasicMallocHookTest) {
MallocHooks::ResetStats();
EXPECT_EQ(0L, MallocHooks::allocation_count());
EXPECT_EQ(0L, MallocHooks::heap_allocated_memory_in_bytes());
const intptr_t buffer_size = 10;
char* buffer = new char[buffer_size];
MallocHookTestBufferInitializer(buffer, buffer_size);
EXPECT_EQ(1L, MallocHooks::allocation_count());
EXPECT_EQ(static_cast<intptr_t>(sizeof(char) * buffer_size),
MallocHooks::heap_allocated_memory_in_bytes());
delete[] buffer;
EXPECT_EQ(0L, MallocHooks::allocation_count());
EXPECT_EQ(0L, MallocHooks::heap_allocated_memory_in_bytes());
}
UNIT_TEST_CASE(FreeUnseenMemoryMallocHookTest) {
const intptr_t pre_hook_buffer_size = 3;
char* pre_hook_buffer = new char[pre_hook_buffer_size];
MallocHookTestBufferInitializer(pre_hook_buffer, pre_hook_buffer_size);
MallocHooks::ResetStats();
EXPECT_EQ(0L, MallocHooks::allocation_count());
EXPECT_EQ(0L, MallocHooks::heap_allocated_memory_in_bytes());
const intptr_t buffer_size = 10;
volatile char* buffer = new char[buffer_size];
MallocHookTestBufferInitializer(buffer, buffer_size);
EXPECT_EQ(1L, MallocHooks::allocation_count());
EXPECT_EQ(static_cast<intptr_t>(sizeof(char) * buffer_size),
MallocHooks::heap_allocated_memory_in_bytes());
delete[] pre_hook_buffer;
EXPECT_EQ(1L, MallocHooks::allocation_count());
EXPECT_EQ(static_cast<intptr_t>(sizeof(char) * buffer_size),
MallocHooks::heap_allocated_memory_in_bytes());
delete[] buffer;
EXPECT_EQ(0L, MallocHooks::allocation_count());
EXPECT_EQ(0L, MallocHooks::heap_allocated_memory_in_bytes());
}
}; // namespace dart
#endif // defined(DART_USE_TCMALLOC) && !defined(PRODUCT)

View File

@ -2,16 +2,38 @@
// for details. All rights reserved. Use of this source code is governed by a
// BSD-style license that can be found in the LICENSE file.
#if defined(DART_USE_TCMALLOC)
#include "platform/globals.h"
#if !defined(DART_USE_TCMALLOC) || defined(PRODUCT)
#include "vm/malloc_hooks.h"
namespace dart {
void MallocHooks::Init() {
// TODO(bkonyi): Implement
void MallocHooks::InitOnce() {
// Do nothing.
}
void MallocHooks::TearDown() {
// Do nothing.
}
void MallocHooks::ResetStats() {
// Do nothing.
}
intptr_t MallocHooks::allocation_count() {
return 0;
}
intptr_t MallocHooks::heap_allocated_memory_in_bytes() {
return 0;
}
} // namespace dart
#endif // defined(DART_USE_TCMALLOC)
#endif // defined(DART_USE_TCMALLOC) || defined(PRODUCT)

View File

@ -61,13 +61,6 @@
],
},
}],
# The following condition should be kept in sync with the corresponding
# configurations in runtime/bin/bin.gypi.
['OS == "linux" and asan == 0 and msan == 0 and tsan == 0', {
'defines': [
'DART_USE_TCMALLOC'
],
}],
['OS=="android" and _toolset=="host"', {
'link_settings': {
'libraries': [
@ -112,13 +105,6 @@
],
},
}],
# The following condition should be kept in sync with the corresponding
# configurations in runtime/bin/bin.gypi.
['OS == "linux" and asan == 0 and msan == 0 and tsan == 0', {
'defines': [
'DART_USE_TCMALLOC'
],
}],
['OS=="android" and _toolset=="host"', {
'link_settings': {
'libraries': [
@ -163,13 +149,6 @@
],
},
}],
# The following condition should be kept in sync with the corresponding
# configurations in runtime/bin/bin.gypi.
['OS == "linux" and asan == 0 and msan == 0 and tsan == 0', {
'defines': [
'DART_USE_TCMALLOC'
],
}],
['OS=="android" and _toolset=="host"', {
'link_settings': {
'libraries': [
@ -215,13 +194,6 @@
],
},
}],
# The following condition should be kept in sync with the corresponding
# configurations in runtime/bin/bin.gypi.
['OS == "linux" and asan == 0 and msan == 0 and tsan == 0', {
'defines': [
'DART_USE_TCMALLOC'
],
}],
['OS=="android" and _toolset=="host"', {
'link_settings': {
'libraries': [

View File

@ -286,8 +286,9 @@
'longjump.h',
'longjump_test.cc',
'malloc_hooks.cc',
'malloc_hooks_unsupported.cc',
'malloc_hooks.h',
'malloc_hooks_test.cc',
'malloc_hooks_unsupported.cc',
'megamorphic_cache_table.cc',
'megamorphic_cache_table.h',
'memory_region.cc',