diff --git a/runtime/builtins.cpp b/runtime/builtins.cpp index 05bcea140bfc95c741d564d249241fd2a8d483da..44b59a7d2d9522de6e4ca78530fd7c3dc8db6f8f 100644 --- a/runtime/builtins.cpp +++ b/runtime/builtins.cpp @@ -343,6 +343,8 @@ void Builtins::InitializeGlobalObject(const JSHandle &env, const JSHa // Global object test SetFunction(env, globalObject, "print", Global::PrintEntrypoint, 0); SetFunction(env, globalObject, "gc", Global::GCEntrypoint, 0); + + // Intrusice GC testing framework SetFunction(env, globalObject, "startGC", Global::SpecifiedGCEntrypoint, 2); SetFunction(env, globalObject, "waitForFinishGC", Global::WaitForFinishGC, 1); SetFunction(env, globalObject, "scheduleGcAfterNthAlloc", Global::ScheduleGCAfterNthAlloc, 2); @@ -355,6 +357,8 @@ void Builtins::InitializeGlobalObject(const JSHandle &env, const JSHa SetFunction(env, globalObject, "pinObject", Global::PinObject, 1); SetFunction(env, globalObject, "unpinObject", Global::UnpinObject, 1); SetFunction(env, globalObject, "getObjectAddress", Global::GetObjectAddress, 1); + InitializeGcMarker(env); + #if ECMASCRIPT_ENABLE_RUNTIME_STAT SetFunction(env, globalObject, "startRuntimeStat", Global::StartRuntimeStat, 0); SetFunction(env, globalObject, "stopRuntimeStat", Global::StopRuntimeStat, 0); @@ -3123,6 +3127,13 @@ void Builtins::InitializePluralRules(const JSHandle &env) SetFunction(env, prPrototype, "resolvedOptions", PluralRules::ResolvedOptions, FunctionLength::ZERO); } +void Builtins::InitializeGcMarker(const JSHandle &env) const +{ + JSHandle marker = factory_->NewEmptyJSObject(); + SetFunction(env, marker, "markObjectRecursively", Global::MarkObjectRecursively, FunctionLength::ONE); + env->SetGcMarker(thread_, marker.GetTaggedValue()); +} + JSHandle Builtins::InitializeArkTools(const JSHandle &env) const { JSHandle tools = factory_->NewEmptyJSObject(); diff --git a/runtime/builtins.h b/runtime/builtins.h index 9deda55c9abf57ca21508d13a8dac6ceb9a391e6..c647f27db31d10ec0ca66c1b55b383d6d0c5b2ad 100644 --- a/runtime/builtins.h +++ b/runtime/builtins.h @@ -231,6 +231,7 @@ private: const JSHandle &getter, const JSHandle &setter) const; void SetGetter(const JSHandle &obj, const JSHandle &key, const JSHandle &getter) const; + void InitializeGcMarker(const JSHandle &env) const; JSHandle InitializeArkTools(const JSHandle &env) const; JSHandle InitializeArkPrivate(const JSHandle &env) const; void SetConstantObject(const JSHandle &obj, const char *key, JSHandle &value) const; diff --git a/runtime/builtins/builtins_global.cpp b/runtime/builtins/builtins_global.cpp index 52e72c8be19f8e9ce140ff3cd537ebba616c3343..4ba8aa69211150f2dc5e07d91d2ce4ba1066027c 100644 --- a/runtime/builtins/builtins_global.cpp +++ b/runtime/builtins/builtins_global.cpp @@ -78,7 +78,8 @@ void BuiltinsGlobal::GCTaskTracker::GCPhaseStarted(mem::GCPhase phase) JSHandle fn = JSHandle::Cast(callback); JSHandle global(thread, thread->GetGlobalObject()); JSHandle new_target(thread, JSTaggedValue::Undefined()); - auto info = NewRuntimeCallInfo(thread, fn, global, JSTaggedValue::Undefined(), 0); + auto info = NewRuntimeCallInfo(thread, fn, global, JSTaggedValue::Undefined(), 1); + info->SetCallArgs(thread->GetEcmaVM()->GetGlobalEnv()->GetGcMarker()); JSFunction::Call(info.get()); } } @@ -774,7 +775,8 @@ static GCTaskCause GCCauseFromString(JSThread *thread, const JSHandleGetThread(); + BUILTINS_API_TRACE(thread, Global, MarkObjectRecursively); + mem::GC *gc = thread->GetEcmaVM()->GetGC(); + JSHandle arg = GetCallArg(msg, 0); + if (!arg->IsHeapObject()) { + THROW_TYPE_ERROR_AND_RETURN(thread, "Argument is not an object", JSTaggedValue::Exception()); + } + + ObjectHeader *obj = arg->GetHeapObject(); + PandaStack mark_stack; + gc->MarkObject(obj); + mark_stack.push(obj); + while (!mark_stack.empty()) { + obj = mark_stack.top(); + mark_stack.pop(); + mem::ObjectHelpers::TraverseAllObjects( + obj, [gc, &mark_stack]([[maybe_unused]] ObjectHeader *object, ObjectHeader *ref) { + if (ref != nullptr && gc->MarkObjectIfNotMarked(ref)) { + mark_stack.push(ref); + } + }); + } + return JSTaggedValue::Undefined(); +} + JSTaggedValue BuiltinsGlobal::GCObjectSpaceType(EcmaRuntimeCallInfo *msg) { ASSERT(msg != nullptr); diff --git a/runtime/builtins/builtins_global.h b/runtime/builtins/builtins_global.h index 5beba079283b3eaf427ab4f026dd2a7acb90c761..cb7edb3439d59a1e9884147ab12ea03b59532e02 100644 --- a/runtime/builtins/builtins_global.h +++ b/runtime/builtins/builtins_global.h @@ -56,6 +56,7 @@ public: static JSTaggedValue MarkObject(EcmaRuntimeCallInfo *msg); static JSTaggedValue GetMarkQueue(EcmaRuntimeCallInfo *msg); static JSTaggedValue ClearMarkQueue(EcmaRuntimeCallInfo *msg); + static JSTaggedValue MarkObjectRecursively(EcmaRuntimeCallInfo *msg); static JSTaggedValue GCObjectSpaceType(EcmaRuntimeCallInfo *msg); static JSTaggedValue PinObject(EcmaRuntimeCallInfo *msg); static JSTaggedValue UnpinObject(EcmaRuntimeCallInfo *msg); diff --git a/runtime/global_env.h b/runtime/global_env.h index fcc67ba63a84dc42c04a2aa693b44b56b030ae90..dcaa2356511136a4d1f7bbe5c2849dd5a98022b2 100644 --- a/runtime/global_env.h +++ b/runtime/global_env.h @@ -153,7 +153,8 @@ class JSThread; V(JSTaggedValue, NormalFunctionClass, NORMAL_FUNCTION_CLASS) \ V(JSTaggedValue, JSIntlBoundFunctionClass, JS_INTL_BOUND_FUNCTION_CLASS) \ V(JSTaggedValue, NumberFormatLocales, NUMBER_FORMAT_LOCALES_INDEX) \ - V(JSTaggedValue, DateTimeFormatLocales, DATE_TIMEFORMAT_LOCALES_INDEX) + V(JSTaggedValue, DateTimeFormatLocales, DATE_TIMEFORMAT_LOCALES_INDEX) \ + V(JSTaggedValue, GcMarker, GCMARKER_OBJECT) class GlobalEnv : public TaggedObject { public: diff --git a/runtime/ic/ic_runtime_stub-inl.h b/runtime/ic/ic_runtime_stub-inl.h index cfcc9b114cccaae9cf6c0874dcfffca1757b5f24..b5fca29861db70bff3c5ed4027e888725d5d9d82 100644 --- a/runtime/ic/ic_runtime_stub-inl.h +++ b/runtime/ic/ic_runtime_stub-inl.h @@ -170,10 +170,6 @@ JSTaggedValue ICRuntimeStub::LoadICByValue(JSThread *thread, JSTaggedValue recei } if (receiver.IsHeapObject() && first_value.IsHeapObject()) { auto hclass = receiver.GetTaggedObject()->GetClass(); - if (first_value.GetTaggedObject() == hclass) { - ASSERT(HandlerBase::IsElement(profile_type_info->Get(slot_id + 1).GetInt())); - return LoadElement(JSObject::Cast(receiver.GetHeapObject()), key); - } // Check key if (first_value == key) { JSTaggedValue hclass_value = profile_type_info->Get(slot_id + 1); @@ -189,6 +185,20 @@ JSTaggedValue ICRuntimeStub::LoadICByValue(JSThread *thread, JSTaggedValue recei } } } + // Check element + if (first_value.GetTaggedObject() == hclass) { + ASSERT(HandlerBase::IsElement(profile_type_info->Get(slot_id + 1).GetInt())); + return LoadElement(JSObject::Cast(receiver.GetHeapObject()), key); + } + size_t index = slot_id + 2; + while (index < SlotSizeForICByValue()) { + first_value = profile_type_info->Get(index); + if (first_value.GetRawData() == ToUintPtr(hclass)) { + ASSERT(HandlerBase::IsElement(profile_type_info->Get(index + 1).GetInt())); + return LoadElement(JSObject::Cast(receiver.GetHeapObject()), key); + } + index += 2; + } } return LoadMissedICByValue(thread, profile_type_info, receiver, key, slot_id); } @@ -206,11 +216,6 @@ JSTaggedValue ICRuntimeStub::StoreICByValue(JSThread *thread, JSTaggedValue rece } if (receiver.IsHeapObject() && first_value.IsHeapObject()) { auto hclass = receiver.GetTaggedObject()->GetClass(); - if (first_value.GetTaggedObject() == hclass) { - JSTaggedValue handler = profile_type_info->Get(slot_id + 1); - auto handler_info = static_cast(handler.GetInt()); - return StoreElement(thread, JSObject::Cast(receiver.GetHeapObject()), key, value, handler_info); - } // Check key if (first_value == key) { JSTaggedValue hclass_value = profile_type_info->Get(slot_id + 1); @@ -226,6 +231,22 @@ JSTaggedValue ICRuntimeStub::StoreICByValue(JSThread *thread, JSTaggedValue rece } } } + // Check element + if (first_value.GetTaggedObject() == hclass) { + JSTaggedValue handler = profile_type_info->Get(slot_id + 1); + auto handler_info = static_cast(handler.GetInt()); + return StoreElement(thread, JSObject::Cast(receiver.GetHeapObject()), key, value, handler_info); + } + size_t index = slot_id + 2; + while (index < SlotSizeForICByValue()) { + first_value = profile_type_info->Get(index); + if (first_value.GetRawData() == ToUintPtr(hclass)) { + JSTaggedValue handler = profile_type_info->Get(index + 1); + auto handler_info = static_cast(handler.GetInt()); + return StoreElement(thread, JSObject::Cast(receiver.GetHeapObject()), key, value, handler_info); + } + index += 2; + } } return StoreMissedICByValue(thread, profile_type_info, receiver, key, value, slot_id); diff --git a/runtime/ic/ic_runtime_stub.h b/runtime/ic/ic_runtime_stub.h index bd191a5e1e0527dabb3b7119a99fc906c4c54c6f..d72102925f7fd1f21a8b751bc7406ceb767c879a 100644 --- a/runtime/ic/ic_runtime_stub.h +++ b/runtime/ic/ic_runtime_stub.h @@ -37,7 +37,9 @@ public: static inline constexpr uint32_t SlotSizeForICByValue() { - return 3U; + // For key we need 3 slots. For one element we need 2 slots. + // But we store 2 pairs of elements. + return 4U; } ARK_INLINE static inline bool HaveICForFunction(JSThread *thread); diff --git a/runtime/ic/profile_type_info.cpp b/runtime/ic/profile_type_info.cpp index 08a6e05848eb75317865d33018e16fdaaffc7fff..e0eaabe59899deffe394b7feea703e521a9d0623 100644 --- a/runtime/ic/profile_type_info.cpp +++ b/runtime/ic/profile_type_info.cpp @@ -51,11 +51,14 @@ void ProfileTypeAccessor::AddHandlerWithoutKey(JSHandle dynclass, if (profileData.IsHole()) { return; } - while (!profileData.IsUndefined() && index < slot_id_ + 4U) { + while (index < slot_id_ + 4U && !profile_type_info_->Get(index).IsUndefined()) { index += 2U; } if (index == slot_id_ + 4U) { profile_type_info_->Set(thread_, slot_id_, JSTaggedValue::Hole()); + profile_type_info_->Set(thread_, slot_id_ + 1, JSTaggedValue::Hole()); + profile_type_info_->Set(thread_, slot_id_ + 2, JSTaggedValue::Hole()); + profile_type_info_->Set(thread_, slot_id_ + 3, JSTaggedValue::Hole()); return; } profile_type_info_->Set(thread_, index, dynclass.GetTaggedValue()); @@ -79,6 +82,8 @@ void ProfileTypeAccessor::AddHandlerWithKey(JSHandle key, JSHandl // for element ic, profileData may dynclass or taggedarray if (key.GetTaggedValue() != profileData) { profile_type_info_->Set(thread_, slot_id_, JSTaggedValue::Hole()); + profile_type_info_->Set(thread_, slot_id_ + 1, JSTaggedValue::Hole()); + profile_type_info_->Set(thread_, slot_id_ + 2, JSTaggedValue::Hole()); return; } } diff --git a/runtime/runtime_call_id.h b/runtime/runtime_call_id.h index 607dcdbfc481639cc24360b196d907ecf8ab4497..42e11503dcbf63feb6b00f57caa6a41db89d5c8d 100644 --- a/runtime/runtime_call_id.h +++ b/runtime/runtime_call_id.h @@ -336,6 +336,7 @@ namespace panda::ecmascript { V(Global, PinObject) \ V(Global, UnpinObject) \ V(Global, GetObjectAddress) \ + V(Global, MarkObjectRecursively) \ V(Global, AllocateArrayObject) \ V(Global, GCObjectSpaceType) \ V(Global, NewobjDynrange) \ diff --git a/tests/runtime/common/gc/CMakeLists.txt b/tests/runtime/common/gc/CMakeLists.txt index 6fb88582b95a43765c2d03e830bc3856fea72b67..f8b6bb2548e09b540d2321ff8450aa9238284863 100644 --- a/tests/runtime/common/gc/CMakeLists.txt +++ b/tests/runtime/common/gc/CMakeLists.txt @@ -52,8 +52,9 @@ endfunction() # Use --gc-trigger-type=debug-never to control GC in the test. # In the test we don't need the runtime triggers GC during allocation. -panda_add_ecma_gc_test(FILE startGc.js OPTIONS "--gc-trigger-type=debug-never") -panda_add_ecma_gc_test(FILE scheduleGc.js OPTIONS "--gc-trigger-type=debug-never" "--gc-use-nth-alloc-trigger=true") -panda_add_ecma_gc_test(FILE spaceTypeTest.js OPTIONS "--gc-type=g1-gc" "--run-gc-in-place=true") -panda_add_ecma_gc_test(FILE concurrent.js OPTIONS "--gc-type=g1-gc" "--gc-trigger-type=debug-never") -panda_add_ecma_gc_test(FILE pinObject.js OPTIONS "--gc-type=g1-gc" "--gc-trigger-type=debug-never") +panda_add_ecma_gc_test(FILE startGc.js OPTIONS "--compiler-enable-jit=false" "--gc-trigger-type=debug-never") +panda_add_ecma_gc_test(FILE scheduleGc.js OPTIONS "--compiler-enable-jit=false" "--gc-trigger-type=debug-never" "--gc-use-nth-alloc-trigger=true") +panda_add_ecma_gc_test(FILE spaceTypeTest.js OPTIONS "--compiler-enable-jit=false" "--gc-type=g1-gc" "--run-gc-in-place=true") +panda_add_ecma_gc_test(FILE concurrent.js OPTIONS "--compiler-enable-jit=false" "--gc-type=g1-gc" "--gc-trigger-type=debug-never") +panda_add_ecma_gc_test(FILE hclass_changing_during_concurrent.js OPTIONS "--compiler-enable-jit=false" "--gc-type=g1-gc" "--gc-trigger-type=debug-never" "--heap-verifier=fail_on_verification:post") +panda_add_ecma_gc_test(FILE pinObject.js OPTIONS "--compiler-enable-jit=false" "--gc-type=g1-gc" "--gc-trigger-type=debug-never") diff --git a/tests/runtime/common/gc/concurrent.js b/tests/runtime/common/gc/concurrent.js index 64c164634119b283cb9a4dd09111dcc6a5b38b77..dfd8038278be9c6861b8cca47024a91bbc290ca9 100644 --- a/tests/runtime/common/gc/concurrent.js +++ b/tests/runtime/common/gc/concurrent.js @@ -13,6 +13,9 @@ * limitations under the License. */ +/** + * Test call JS code during concurrent mark GC phase. + */ import { assert } from 'common.abc'; function alloc() { diff --git a/tests/runtime/common/gc/hclass_changing_during_concurrent.js b/tests/runtime/common/gc/hclass_changing_during_concurrent.js new file mode 100644 index 0000000000000000000000000000000000000000..05db9f9f986cb8f697182a6316f003dba284b503 --- /dev/null +++ b/tests/runtime/common/gc/hclass_changing_during_concurrent.js @@ -0,0 +1,68 @@ +/* + * Copyright (c) 2022 Huawei Device Co., Ltd. + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +/** + * Test object's hclass changing during concurrent mark. + * If runtime sets a new hclass to an object GC barrier must be called. + * Without barrier old hclass may be collected dispite it is reachable. + */ + +function assert(value, message) { + if (value) { + return; + } + print("Assertion failed: " + message); + throw Error(); +} + +function addProp(obj, prop) { + obj[prop] = undefined; +} + +function setProp(obj) { + obj.prop = 1; +} + +// Use wrapper to have ability to zero the referent +let holder = new Object(); +// Create the object HClass we are interested in. New HClass is created. +holder.ref = new Object(); +addProp(holder.ref, "prop"); +// Poison addProp's IC +// Now the interesting HClass is in addProp's IC. +// Call addProp with different objects to make IC megamorphic. +addProp(new String(), "abc"); +// Create IC for setProp +// The IC is created at the first call. We need to do it before concurrent mark to make sure +// the IC will not get into SATB. +setProp(new Object()); +// Start GC with concurrent mark +let gc = startGC("threshold", function(marker) { + let obj = holder.ref; + // Mark setProp's IC before the interested HClass gets into. + // So we sure the interested HClass is will not be marked from the IC. + marker.markObjectRecursively(setProp); + // Add obj.s HClass into setProp's IC. + // The HClass is reachable from the IC but GC doesn't mark it because IC is already marked. + setProp(obj); + // Make transition to dictionary mode. + // Object's hclass will be changed. + obj[1025] = 0; + // Forget the object. + holder.ref = undefined; + // At this moment the previous obj's HClass is reachable only from setProp's IC but is not marked. + // Concurrent mark should delete the HClass and heap verifier should find a reference to a dead object + // from setProp's IC. +}); diff --git a/tests/runtime/common/gc/scheduleGc.js b/tests/runtime/common/gc/scheduleGc.js index 8ea3b64986058770e20d3a58d9a25e93d0f9e61e..39bcfe299858365de23112c9fb1bd6f05b06f538 100644 --- a/tests/runtime/common/gc/scheduleGc.js +++ b/tests/runtime/common/gc/scheduleGc.js @@ -13,6 +13,9 @@ * limitations under the License. */ +/** + * Test scheduleGcAfterNthAlloc builtin. + */ import { assert, newWeakRefInYoung } from 'common.abc'; let ref = newWeakRefInYoung(); diff --git a/tests/runtime/common/gc/spaceTypeTest.js b/tests/runtime/common/gc/spaceTypeTest.js index 1298456e030facca42dcc0b2865d7f06970c1706..0e67b6d8c4bc7da073209dcdaa6723d490be2e18 100644 --- a/tests/runtime/common/gc/spaceTypeTest.js +++ b/tests/runtime/common/gc/spaceTypeTest.js @@ -13,6 +13,9 @@ * limitations under the License. */ +/** + * Test getObjectSpaceType builtin. + */ import { assert } from 'common.abc'; function func() { diff --git a/tests/runtime/common/gc/startGc.js b/tests/runtime/common/gc/startGc.js index 7dc69f55edaf13e722c938491c4e16de3fbf103d..22efb0eb7cc34f4d8fd74cdbe499ac9eb5107cbd 100644 --- a/tests/runtime/common/gc/startGc.js +++ b/tests/runtime/common/gc/startGc.js @@ -13,6 +13,9 @@ * limitations under the License. */ +/** + * Test startGc builtin. + */ import { assert, newWeakRefInYoung } from 'common.abc'; // allocate an object of size in bytes at least 'sizeInBytes'