From ca169d010e9f9a1ae99e1794144a7296ecb90607 Mon Sep 17 00:00:00 2001 From: Ryan Macnak Date: Wed, 12 Aug 2020 18:21:36 +0000 Subject: [PATCH] [vm, api] Add Dart_HintFreed to the embedding API. Allows an embedder (or native extension) to inform the VM it suspects memory has become unreachable. Bug: https://github.com/dart-lang/sdk/issues/42078 Change-Id: I977e14fbe760fd4b5a0cc68fd010561a66c71899 Reviewed-on: https://dart-review.googlesource.com/c/sdk/+/150503 Reviewed-by: Siva Annamalai Commit-Queue: Ryan Macnak --- runtime/include/dart_api.h | 12 +++++++++ runtime/vm/dart_api_impl.cc | 11 ++++++++ runtime/vm/dart_api_impl_test.cc | 44 +++++++++++++++++++++++++++----- runtime/vm/heap/heap.cc | 4 +++ runtime/vm/heap/heap.h | 1 + runtime/vm/heap/pages.cc | 11 ++++++++ runtime/vm/heap/pages.h | 2 ++ 7 files changed, 79 insertions(+), 6 deletions(-) diff --git a/runtime/include/dart_api.h b/runtime/include/dart_api.h index bbd3d2caa82..8eebaed2ba2 100644 --- a/runtime/include/dart_api.h +++ b/runtime/include/dart_api.h @@ -1140,6 +1140,18 @@ DART_EXPORT void Dart_EnterIsolate(Dart_Isolate isolate); */ DART_EXPORT void Dart_KillIsolate(Dart_Isolate isolate); +/** + * Notifies the VM that the embedder expects |size| bytes of memory have become + * unreachable. The VM may use this hint to adjust the garbage collector's + * growth policy. + * + * Multiple calls are interpreted as increasing, not replacing, the estimate of + * unreachable memory. + * + * Requires there to be a current isolate. + */ +DART_EXPORT void Dart_HintFreed(intptr_t size); + /** * Notifies the VM that the embedder expects to be idle until |deadline|. The VM * may use this time to perform garbage collection or other tasks to avoid diff --git a/runtime/vm/dart_api_impl.cc b/runtime/vm/dart_api_impl.cc index 5a8654d2e4e..a9dfc833aa7 100644 --- a/runtime/vm/dart_api_impl.cc +++ b/runtime/vm/dart_api_impl.cc @@ -1887,6 +1887,17 @@ DART_EXPORT Dart_Handle Dart_GetStickyError() { return Api::NewHandle(T, I->sticky_error()); } +DART_EXPORT void Dart_HintFreed(intptr_t size) { + if (size < 0) { + FATAL1("%s requires a non-negative size", CURRENT_FUNC); + } + Thread* T = Thread::Current(); + CHECK_ISOLATE(T->isolate()); + API_TIMELINE_BEGIN_END(T); + TransitionNativeToVM transition(T); + T->heap()->HintFreed(size); +} + DART_EXPORT void Dart_NotifyIdle(int64_t deadline) { Thread* T = Thread::Current(); CHECK_ISOLATE(T->isolate()); diff --git a/runtime/vm/dart_api_impl_test.cc b/runtime/vm/dart_api_impl_test.cc index 84681a8772e..2c2a4eb4336 100644 --- a/runtime/vm/dart_api_impl_test.cc +++ b/runtime/vm/dart_api_impl_test.cc @@ -8768,7 +8768,39 @@ TEST_CASE(DartAPI_TimelineAsync) { EXPECT_SUBSTRING("testAsyncEvent", js.ToCString()); } -void NotifyIdleShortNative(Dart_NativeArguments args) { +static void HintFreedNative(Dart_NativeArguments args) { + int64_t size = 0; + EXPECT_VALID(Dart_GetNativeIntegerArgument(args, 0, &size)); + Dart_HintFreed(size); +} + +static Dart_NativeFunction HintFreed_native_lookup(Dart_Handle name, + int argument_count, + bool* auto_setup_scope) { + return HintFreedNative; +} + +TEST_CASE(DartAPI_HintFreed) { + const char* kScriptChars = + "void hintFreed(int size) native 'Test_nativeFunc';\n" + "void main() {\n" + " var v;\n" + " for (var i = 0; i < 100; i++) {\n" + " var t = [];\n" + " for (var j = 0; j < 10000; j++) {\n" + " t.add(List.filled(100, null));\n" + " }\n" + " v = t;\n" + " hintFreed(100 * 10000 * 4);\n" + " }\n" + "}\n"; + Dart_Handle lib = + TestCase::LoadTestScript(kScriptChars, &HintFreed_native_lookup); + Dart_Handle result = Dart_Invoke(lib, NewString("main"), 0, NULL); + EXPECT_VALID(result); +} + +static void NotifyIdleShortNative(Dart_NativeArguments args) { Dart_NotifyIdle(Dart_TimelineGetMicros() + 10 * kMicrosecondsPerMillisecond); } @@ -8776,7 +8808,7 @@ static Dart_NativeFunction NotifyIdleShort_native_lookup( Dart_Handle name, int argument_count, bool* auto_setup_scope) { - return reinterpret_cast(&NotifyIdleShortNative); + return NotifyIdleShortNative; } TEST_CASE(DartAPI_NotifyIdleShort) { @@ -8799,7 +8831,7 @@ TEST_CASE(DartAPI_NotifyIdleShort) { EXPECT_VALID(result); } -void NotifyIdleLongNative(Dart_NativeArguments args) { +static void NotifyIdleLongNative(Dart_NativeArguments args) { Dart_NotifyIdle(Dart_TimelineGetMicros() + 100 * kMicrosecondsPerMillisecond); } @@ -8807,7 +8839,7 @@ static Dart_NativeFunction NotifyIdleLong_native_lookup( Dart_Handle name, int argument_count, bool* auto_setup_scope) { - return reinterpret_cast(&NotifyIdleLongNative); + return NotifyIdleLongNative; } TEST_CASE(DartAPI_NotifyIdleLong) { @@ -8830,7 +8862,7 @@ TEST_CASE(DartAPI_NotifyIdleLong) { EXPECT_VALID(result); } -void NotifyLowMemoryNative(Dart_NativeArguments args) { +static void NotifyLowMemoryNative(Dart_NativeArguments args) { Dart_NotifyLowMemory(); } @@ -8838,7 +8870,7 @@ static Dart_NativeFunction NotifyLowMemory_native_lookup( Dart_Handle name, int argument_count, bool* auto_setup_scope) { - return reinterpret_cast(&NotifyLowMemoryNative); + return NotifyLowMemoryNative; } TEST_CASE(DartAPI_NotifyLowMemory) { diff --git a/runtime/vm/heap/heap.cc b/runtime/vm/heap/heap.cc index 8c896919315..7e814152dab 100644 --- a/runtime/vm/heap/heap.cc +++ b/runtime/vm/heap/heap.cc @@ -405,6 +405,10 @@ void Heap::EndOldSpaceGC() { ml.NotifyAll(); } +void Heap::HintFreed(intptr_t size) { + old_space_.HintFreed(size); +} + void Heap::NotifyIdle(int64_t deadline) { Thread* thread = Thread::Current(); // Check if we want to collect new-space first, because if we want to collect diff --git a/runtime/vm/heap/heap.h b/runtime/vm/heap/heap.h index bb6652e0883..fa9d733b422 100644 --- a/runtime/vm/heap/heap.h +++ b/runtime/vm/heap/heap.h @@ -121,6 +121,7 @@ class Heap { ObjectPtr FindNewObject(FindObjectVisitor* visitor); ObjectPtr FindObject(FindObjectVisitor* visitor); + void HintFreed(intptr_t size); void NotifyIdle(int64_t deadline); void NotifyLowMemory(); diff --git a/runtime/vm/heap/pages.cc b/runtime/vm/heap/pages.cc index b2f4ae4df3e..c0a259792d2 100644 --- a/runtime/vm/heap/pages.cc +++ b/runtime/vm/heap/pages.cc @@ -1709,6 +1709,17 @@ void PageSpaceController::RecordUpdate(SpaceUsage before, } } +void PageSpaceController::HintFreed(intptr_t size) { + intptr_t size_in_words = size << kWordSizeLog2; + if (size_in_words > idle_gc_threshold_in_words_) { + idle_gc_threshold_in_words_ = 0; + } else { + idle_gc_threshold_in_words_ -= size_in_words; + } + + // TODO(rmacnak): Hasten the soft threshold at some discount? +} + void PageSpaceController::MergeFrom(PageSpaceController* donor) { last_usage_.capacity_in_words += donor->last_usage_.capacity_in_words; last_usage_.used_in_words += donor->last_usage_.used_in_words; diff --git a/runtime/vm/heap/pages.h b/runtime/vm/heap/pages.h index a49319089eb..ef5badf3c3e 100644 --- a/runtime/vm/heap/pages.h +++ b/runtime/vm/heap/pages.h @@ -228,6 +228,7 @@ class PageSpaceController { int64_t start, int64_t end); void EvaluateAfterLoading(SpaceUsage after); + void HintFreed(intptr_t size); void set_last_usage(SpaceUsage current) { last_usage_ = current; } @@ -318,6 +319,7 @@ class PageSpace { void EvaluateAfterLoading() { page_space_controller_.EvaluateAfterLoading(usage_); } + void HintFreed(intptr_t size) { page_space_controller_.HintFreed(size); } int64_t UsedInWords() const { return usage_.used_in_words; } int64_t CapacityInWords() const {