已合并
shrink handle/primitive storage proportionally #15032
wwzm-jjw创建于 26 天前
shrink handle/primitive storage proportionally #15032
已合并
共 7 个文件变更+456-10
| @@ -15,6 +15,7 @@ | |||
| 15 | 15 | ||
| 16 | 16 | ||
| 17 | 17 | ||
| 18 | + | ||
| 18 | 19 | ||
| 19 | 20 | ||
| 20 | 21 | ||
| @@ -85,6 +86,7 @@ namespace panda::ecmascript { | |||
| 85 | using RandomGenerator = base::RandomGenerator; | 86 | using RandomGenerator = base::RandomGenerator; |
| 86 | using PGOProfilerManager = pgo::PGOProfilerManager; | 87 | using PGOProfilerManager = pgo::PGOProfilerManager; |
| 87 | using JitTools = ohos::JitTools; | 88 | using JitTools = ohos::JitTools; |
| 89 | + | ||
| 88 | constexpr const char* HEAP_MEM_PRESSURE_PROCESS = "ProcessHeapMemPressure"; | 90 | constexpr const char* HEAP_MEM_PRESSURE_PROCESS = "ProcessHeapMemPressure"; |
| 89 | constexpr const char* HEAP_MEM_PRESSURE_LOCAL = "LocalHeapMemPressure"; | 91 | constexpr const char* HEAP_MEM_PRESSURE_LOCAL = "LocalHeapMemPressure"; |
| 90 | constexpr const char* HEAP_MEM_PRESSURE_SHARED = "SharedHeapMemPressure"; | 92 | constexpr const char* HEAP_MEM_PRESSURE_SHARED = "SharedHeapMemPressure"; |
| @@ -1265,6 +1267,40 @@ void EcmaVM::DeleteHandleStorage() | |||
| 1265 | handleScopeStorageNext_ = handleScopeStorageEnd_ = nullptr; | 1267 | handleScopeStorageNext_ = handleScopeStorageEnd_ = nullptr; |
| 1266 | } | 1268 | } |
| 1267 | 1269 | ||
| 1270 | +bool EcmaVM::FreeStorageNodesTask::Run([[maybe_unused]] uint32_t threadIndex) | ||
| 1271 | +{ | ||
| 1272 | + std::string traceName = | ||
| 1273 | + "EcmaVM::FreeStorageNodesTask::Run freed " + std::to_string(nodes_.size()) + " nodes"; | ||
| 1274 | + ECMA_BYTRACE_NAME(HITRACE_LEVEL_COMMERCIAL, HITRACE_TAG_ARK, traceName.c_str(), ""); | ||
| 1275 | + for (auto *node : nodes_) { | ||
| 1276 | + delete node; | ||
| 1277 | + } | ||
| 1278 | + return true; | ||
| 1279 | +} | ||
| 1280 | + | ||
| 1281 | +void EcmaVM::FreeStorageNodes(std::vector<StorageNode *> &nodes, int32_t toDelete) | ||
| 1282 | +{ | ||
| 1283 | + // The caller (mutator thread) owns `nodes`, so detaching via pop_back here is | ||
| 1284 | + // race-free. Only the heap free of detached nodes is posted to the taskpool, | ||
| 1285 | + // which never touches `nodes` afterwards, avoiding vector races with the caller. | ||
| 1286 | + auto *taskpool = common::Taskpool::GetCurrentTaskpool(); | ||
| 1287 | + bool asyncFree = thread_->IsMainThreadFast() && toDelete > ASYNC_SHRINK_NODE_THRESHOLD && taskpool != nullptr; | ||
| 1288 | + if (asyncFree) { | ||
| 1289 | + std::vector<StorageNode *> toFree; | ||
| 1290 | + toFree.reserve(toDelete); | ||
| 1291 | + for (int32_t i = 0; i < toDelete; i++) { | ||
| 1292 | + toFree.push_back(nodes.back()); | ||
| 1293 | + nodes.pop_back(); | ||
| 1294 | + } | ||
| 1295 | + taskpool->PostTask(std::make_unique<FreeStorageNodesTask>(std::move(toFree))); | ||
| 1296 | + } else { | ||
| 1297 | + for (int32_t i = 0; i < toDelete; i++) { | ||
| 1298 | + delete nodes.back(); | ||
| 1299 | + nodes.pop_back(); | ||
| 1300 | + } | ||
| 1301 | + } | ||
| 1302 | +} | ||
| 1303 | + | ||
| 1268 | void EcmaVM::ShrinkHandleStorage(int prevIndex) | 1304 | void EcmaVM::ShrinkHandleStorage(int prevIndex) |
| 1269 | { | 1305 | { |
| 1270 | currentHandleStorageIndex_ = prevIndex; | 1306 | currentHandleStorageIndex_ = prevIndex; |
| @@ -1288,11 +1324,16 @@ void EcmaVM::ShrinkHandleStorage(int prevIndex) | |||
| 1288 | } | 1324 | } |
| 1289 | 1325 | ||
| 1290 | 1326 | ||
| 1291 | - if (lastIndex > MIN_HANDLE_STORAGE_SIZE && currentHandleStorageIndex_ < MIN_HANDLE_STORAGE_SIZE) { | 1327 | + if (lastIndex > MIN_HANDLE_STORAGE_SIZE) { |
| 1292 | - for (int i = MIN_HANDLE_STORAGE_SIZE; i < lastIndex; i++) { | 1328 | + int32_t totalNodes = lastIndex + 1; |
| 1293 | - auto node = handleStorageNodes_.back(); | 1329 | + int32_t currentUsage = currentHandleStorageIndex_ + 1; |
| 1294 | - delete node; | 1330 | + int32_t targetNodes = std::max(MIN_HANDLE_STORAGE_SIZE, currentUsage * SHRINK_HEADROOM_FACTOR); |
| 1295 | - handleStorageNodes_.pop_back(); | 1331 | + int32_t toDelete = totalNodes - targetNodes; |
| 1332 | + // Only shrink when at least a quarter of the nodes would be freed, to avoid churn. | ||
| 1333 | + if (toDelete >= std::max(totalNodes / SHRINK_HYSTERESIS_DIVISOR, SHRINK_MIN_FREE_NODES)) { | ||
| 1334 | + LOG_ECMA(DEBUG) << "ShrinkHandleStorage currentUsage:" << currentUsage | ||
| 1335 | + << " totalNodes:" << totalNodes << " targetNodes:" << targetNodes; | ||
| 1336 | + FreeStorageNodes(handleStorageNodes_, toDelete); | ||
| 1296 | } | 1337 | } |
| 1297 | } | 1338 | } |
| 1298 | } | 1339 | } |
| @@ -1415,11 +1456,16 @@ void EcmaVM::ShrinkPrimitiveStorage(int prevIndex) | |||
| 1415 | } | 1456 | } |
| 1416 | 1457 | ||
| 1417 | 1458 | ||
| 1418 | - if (lastIndex > MIN_PRIMITIVE_STORAGE_SIZE && currentPrimitiveStorageIndex_ < MIN_PRIMITIVE_STORAGE_SIZE) { | 1459 | + if (lastIndex > MIN_PRIMITIVE_STORAGE_SIZE) { |
| 1419 | - for (int i = MIN_PRIMITIVE_STORAGE_SIZE; i < lastIndex; i++) { | 1460 | + int32_t totalNodes = lastIndex + 1; |
| 1420 | - auto node = primitiveStorageNodes_.back(); | 1461 | + int32_t currentUsage = currentPrimitiveStorageIndex_ + 1; |
| 1421 | - delete node; | 1462 | + int32_t targetNodes = std::max(MIN_PRIMITIVE_STORAGE_SIZE, currentUsage * SHRINK_HEADROOM_FACTOR); |
| 1422 | - primitiveStorageNodes_.pop_back(); | 1463 | + int32_t toDelete = totalNodes - targetNodes; |
| 1464 | + // Only shrink when at least a quarter of the nodes would be freed, to avoid churn. | ||
| 1465 | + if (toDelete >= std::max(totalNodes / SHRINK_HYSTERESIS_DIVISOR, SHRINK_MIN_FREE_NODES)) { | ||
| 1466 | + LOG_ECMA(DEBUG) << "ShrinkPrimitiveStorage currentUsage:" << currentUsage | ||
| 1467 | + << " totalNodes:" << totalNodes << " targetNodes:" << targetNodes; | ||
| 1468 | + FreeStorageNodes(primitiveStorageNodes_, toDelete); | ||
| 1423 | } | 1469 | } |
| 1424 | } | 1470 | } |
| 1425 | } | 1471 | } |
| @@ -42,6 +42,7 @@ | |||
| 42 | 42 | ||
| 43 | 43 | ||
| 44 | 44 | ||
| 45 | + | ||
| 45 | 46 | ||
| 46 | 47 | ||
| 47 | 48 | ||
| @@ -473,6 +474,11 @@ public: | |||
| 473 | return currentHandleStorageIndex_; | 474 | return currentHandleStorageIndex_; |
| 474 | } | 475 | } |
| 475 | 476 | ||
| 477 | + size_t GetHandleStorageNodesSize() const | ||
| 478 | + { | ||
| 479 | + return handleStorageNodes_.size(); | ||
| 480 | + } | ||
| 481 | + | ||
| 476 | JSTaggedType *GetPrimitiveScopeStorageNext() const | 482 | JSTaggedType *GetPrimitiveScopeStorageNext() const |
| 477 | { | 483 | { |
| 478 | return primitiveScopeStorageNext_; | 484 | return primitiveScopeStorageNext_; |
| @@ -498,6 +504,11 @@ public: | |||
| 498 | return currentPrimitiveStorageIndex_; | 504 | return currentPrimitiveStorageIndex_; |
| 499 | } | 505 | } |
| 500 | 506 | ||
| 507 | + size_t GetPrimitiveStorageNodesSize() const | ||
| 508 | + { | ||
| 509 | + return primitiveStorageNodes_.size(); | ||
| 510 | + } | ||
| 511 | + | ||
| 501 | uintptr_t *ExpandHandleStorage(); | 512 | uintptr_t *ExpandHandleStorage(); |
| 502 | void ShrinkHandleStorage(int prevIndex); | 513 | void ShrinkHandleStorage(int prevIndex); |
| 503 | void DeleteHandleStorage(); | 514 | void DeleteHandleStorage(); |
| @@ -1940,8 +1951,30 @@ private: | |||
| 1940 | // HandleScope | 1951 | // HandleScope |
| 1941 | static const uint32_t NODE_BLOCK_SIZE_LOG2 = 10; | 1952 | static const uint32_t NODE_BLOCK_SIZE_LOG2 = 10; |
| 1942 | static const uint32_t NODE_BLOCK_SIZE = 1U << NODE_BLOCK_SIZE_LOG2; | 1953 | static const uint32_t NODE_BLOCK_SIZE = 1U << NODE_BLOCK_SIZE_LOG2; |
| 1954 | + using StorageNode = std::array<JSTaggedType, NODE_BLOCK_SIZE>; | ||
| 1943 | static constexpr uint32_t SO_LOAD_FAILURE_CAPACITY = 20; | 1955 | static constexpr uint32_t SO_LOAD_FAILURE_CAPACITY = 20; |
| 1944 | static constexpr int32_t MIN_HANDLE_STORAGE_SIZE = 2; | 1956 | static constexpr int32_t MIN_HANDLE_STORAGE_SIZE = 2; |
| 1957 | + // Retain SHRINK_HEADROOM_FACTOR * active usage nodes after a shrink. | ||
| 1958 | + static constexpr int32_t SHRINK_HEADROOM_FACTOR = 2; | ||
| 1959 | + // Only shrink when at least total / SHRINK_HYSTERESIS_DIVISOR nodes would be freed. | ||
| 1960 | + static constexpr int32_t SHRINK_HYSTERESIS_DIVISOR = 4; | ||
| 1961 | + // Never shrink for fewer than this many nodes to avoid trivial churn on small storages. | ||
| 1962 | + static constexpr int32_t SHRINK_MIN_FREE_NODES = 4; | ||
| 1963 | + // On the main thread, freeing more than this many nodes is offloaded to the taskpool. | ||
| 1964 | + static constexpr int32_t ASYNC_SHRINK_NODE_THRESHOLD = 1000; | ||
| 1965 | + void FreeStorageNodes(std::vector<StorageNode *> &nodes, int32_t toDelete); | ||
| 1966 | + // Detached storage nodes freed asynchronously by the taskpool (see FreeStorageNodes). | ||
| 1967 | + class FreeStorageNodesTask : public common::Task { | ||
| 1968 | + public: | ||
| 1969 | + explicit FreeStorageNodesTask(std::vector<StorageNode *> nodes) | ||
| 1970 | + : common::Task(common::GLOBAL_TASK_ID), nodes_(std::move(nodes)) {} | ||
| 1971 | + ~FreeStorageNodesTask() override = default; | ||
| 1972 | + bool Run(uint32_t threadIndex) override; | ||
| 1973 | + NO_COPY_SEMANTIC(FreeStorageNodesTask); | ||
| 1974 | + NO_MOVE_SEMANTIC(FreeStorageNodesTask); | ||
| 1975 | + private: | ||
| 1976 | + std::vector<StorageNode *> nodes_; | ||
| 1977 | + }; | ||
| 1945 | JSTaggedType *handleScopeStorageNext_ {nullptr}; | 1978 | JSTaggedType *handleScopeStorageNext_ {nullptr}; |
| 1946 | JSTaggedType *handleScopeStorageEnd_ {nullptr}; | 1979 | JSTaggedType *handleScopeStorageEnd_ {nullptr}; |
| 1947 | std::vector<std::array<JSTaggedType, NODE_BLOCK_SIZE> *> handleStorageNodes_ {}; | 1980 | std::vector<std::array<JSTaggedType, NODE_BLOCK_SIZE> *> handleStorageNodes_ {}; |
| @@ -173,11 +173,21 @@ void GCStats::PrintGCStatistic() | |||
| 173 | } | 173 | } |
| 174 | // print verbose gc statsistics | 174 | // print verbose gc statsistics |
| 175 | PrintVerboseGCStatistic(); | 175 | PrintVerboseGCStatistic(); |
| 176 | + PrintStorageStatistic(); | ||
| 176 | } | 177 | } |
| 177 | GCFinishTrace(); | 178 | GCFinishTrace(); |
| 178 | InitializeRecordList(); | 179 | InitializeRecordList(); |
| 179 | } | 180 | } |
| 180 | 181 | ||
| 182 | +void GCStats::PrintStorageStatistic() | ||
| 183 | +{ | ||
| 184 | + LOG_GC(INFO) << "HandleStorage currentUsage:" << (heap_->GetEcmaVM()->GetCurrentHandleStorageIndex() + 1) | ||
| 185 | + << " totalNodes:" << heap_->GetEcmaVM()->GetHandleStorageNodesSize() | ||
| 186 | + << " PrimitiveStorage currentUsage:" | ||
| 187 | + << (heap_->GetEcmaVM()->GetCurrentPrimitiveStorageIndex() + 1) | ||
| 188 | + << " totalNodes:" << heap_->GetEcmaVM()->GetPrimitiveStorageNodesSize(); | ||
| 189 | +} | ||
| 190 | + | ||
| 181 | const char *GCStats::GCReasonToString() | 191 | const char *GCStats::GCReasonToString() |
| 182 | { | 192 | { |
| 183 | return GCReasonToString(gcReason_); | 193 | return GCReasonToString(gcReason_); |
| @@ -284,6 +284,7 @@ public: | |||
| 284 | protected: | 284 | protected: |
| 285 | bool CheckIfNeedPrint(GCType type); | 285 | bool CheckIfNeedPrint(GCType type); |
| 286 | void PrintVerboseGCStatistic(); | 286 | void PrintVerboseGCStatistic(); |
| 287 | + void PrintStorageStatistic(); | ||
| 287 | void PrintGCDurationStatistic(); | 288 | void PrintGCDurationStatistic(); |
| 288 | void PrintGCSummaryStatistic(GCType type = GCType::START); | 289 | void PrintGCSummaryStatistic(GCType type = GCType::START); |
| 289 | void InitializeRecordList(); | 290 | void InitializeRecordList(); |
| @@ -1505,6 +1505,33 @@ host_unittest_action("JS_EcmaHandleScope_Test") { | |||
| 1505 | deps += hiviewdfx_deps | 1505 | deps += hiviewdfx_deps |
| 1506 | } | 1506 | } |
| 1507 | 1507 | ||
| 1508 | +host_unittest_action("JS_EcmaVMStorageShrink_Test") { | ||
| 1509 | + module_out_path = module_output_path | ||
| 1510 | + | ||
| 1511 | + sources = [ | ||
| 1512 | + # test file | ||
| 1513 | + "ecma_vm_storage_shrink_test.cpp", | ||
| 1514 | + ] | ||
| 1515 | + | ||
| 1516 | + configs = [ | ||
| 1517 | + "../../:ecma_test_config", | ||
| 1518 | + "../../:icu_path_test_config", | ||
| 1519 | + ] | ||
| 1520 | + | ||
| 1521 | + deps = [ "../../:libark_jsruntime_test" ] | ||
| 1522 | + | ||
| 1523 | + # hiviewdfx libraries | ||
| 1524 | + external_deps = hiviewdfx_ext_deps | ||
| 1525 | + external_deps += [ | ||
| 1526 | + "icu:shared_icui18n", | ||
| 1527 | + "icu:shared_icuuc", | ||
| 1528 | + "runtime_core:libarkassembler_static", | ||
| 1529 | + "runtime_core:libarkverifier", | ||
| 1530 | + "zlib:libz", | ||
| 1531 | + ] | ||
| 1532 | + deps += hiviewdfx_deps | ||
| 1533 | +} | ||
| 1534 | + | ||
| 1508 | host_unittest_action("JS_MapIterator_Test") { | 1535 | host_unittest_action("JS_MapIterator_Test") { |
| 1509 | module_out_path = module_output_path | 1536 | module_out_path = module_output_path |
| 1510 | 1537 | ||
| @@ -4098,6 +4125,7 @@ group("unittest") { | |||
| 4098 | ":JS_GlueRegs_Test", | 4125 | ":JS_GlueRegs_Test", |
| 4099 | ":JS_Handle_Test", | 4126 | ":JS_Handle_Test", |
| 4100 | ":JS_EcmaHandleScope_Test", | 4127 | ":JS_EcmaHandleScope_Test", |
| 4128 | + ":JS_EcmaVMStorageShrink_Test", | ||
| 4101 | ":JS_Hclass_Test", | 4129 | ":JS_Hclass_Test", |
| 4102 | ":JS_Iterator_Test", | 4130 | ":JS_Iterator_Test", |
| 4103 | ":JS_LayoutInfo_Test", | 4131 | ":JS_LayoutInfo_Test", |
| @@ -4297,6 +4325,7 @@ group("host_unittest") { | |||
| 4297 | ":JS_GlueRegs_TestAction", | 4325 | ":JS_GlueRegs_TestAction", |
| 4298 | ":JS_Handle_TestAction", | 4326 | ":JS_Handle_TestAction", |
| 4299 | ":JS_EcmaHandleScope_TestAction", | 4327 | ":JS_EcmaHandleScope_TestAction", |
| 4328 | + ":JS_EcmaVMStorageShrink_TestAction", | ||
| 4300 | ":JS_Hclass_TestAction", | 4329 | ":JS_Hclass_TestAction", |
| 4301 | ":JS_Iterator_TestAction", | 4330 | ":JS_Iterator_TestAction", |
| 4302 | ":JS_LayoutInfo_TestAction", | 4331 | ":JS_LayoutInfo_TestAction", |
| @@ -0,0 +1,322 @@ | |||
| 1 | +/* | ||
| 2 | + * Copyright (c) 2026 Huawei Device Co., Ltd. | ||
| 3 | + * Licensed under the Apache License, Version 2.0 (the "License"); | ||
| 4 | + * you may not use this file except in compliance with the License. | ||
| 5 | + * You may obtain a copy of the License at | ||
| 6 | + * | ||
| 7 | + * http://www.apache.org/licenses/LICENSE-2.0 | ||
| 8 | + * | ||
| 9 | + * Unless required by applicable law or agreed to in writing, software | ||
| 10 | + * distributed under the License is distributed on an "AS IS" BASIS, | ||
| 11 | + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. | ||
| 12 | + * See the License for the specific language governing permissions and | ||
| 13 | + * limitations under the License. | ||
| 14 | + */ | ||
| 15 | + | ||
| 16 | + | ||
| 17 | + | ||
| 18 | + | ||
| 19 | + | ||
| 20 | + | ||
| 21 | + | ||
| 22 | +using namespace panda::ecmascript; | ||
| 23 | + | ||
| 24 | +namespace panda::test { | ||
| 25 | +namespace { | ||
| 26 | +// Mirrors the private EcmaVM constants in ecmascript/ecma_vm.h. | ||
| 27 | +constexpr int32_t MIN_HANDLE_STORAGE_SIZE = 2; | ||
| 28 | +constexpr int32_t MIN_PRIMITIVE_STORAGE_SIZE = 2; | ||
| 29 | + | ||
| 30 | +// Shrink retains SHRINK_HEADROOM_FACTOR * active usage nodes (>= MIN). | ||
| 31 | +constexpr int32_t SHRINK_HEADROOM_FACTOR = 2; | ||
| 32 | + | ||
| 33 | +// Reset helper grows MIN + RESET_GROW_MARGIN nodes so the shrink collapses to MIN. | ||
| 34 | +// Must be large enough that toDelete >= the production fixed floor (SHRINK_MIN_FREE_NODES). | ||
| 35 | +constexpr int32_t RESET_GROW_MARGIN = 4; | ||
| 36 | +constexpr size_t RESET_BASELINE_NODES = static_cast<size_t>(MIN_HANDLE_STORAGE_SIZE); | ||
| 37 | + | ||
| 38 | +// prevIndex values driving each scenario. | ||
| 39 | +constexpr int32_t PREV_INDEX_LOW = 1; // usage 2 -> target 4 | ||
| 40 | +constexpr int32_t PREV_INDEX_PROPORTIONAL = 4; // usage 5 -> target 10 | ||
| 41 | +constexpr int32_t PREV_INDEX_HEADROOM_EXCEEDS = 9; // usage 10 -> target 20 == total | ||
| 42 | +constexpr int32_t PREV_INDEX_HYSTERESIS_BAND = 7; // usage 8 -> target 16, frees 4 < total/4=5 | ||
| 43 | + | ||
| 44 | +// Growth targets (final node counts) per scenario. | ||
| 45 | +constexpr size_t GROW_NODES_SMALL = 10; | ||
| 46 | +constexpr size_t GROW_NODES_LARGE = 30; | ||
| 47 | +constexpr size_t GROW_NODES_NO_SHRINK = 20; // headroom * (prev+1) == total | ||
| 48 | +constexpr size_t GROW_NODES_HYSTERESIS_BELOW = 13; // prev=4: frees 3 < max(3,4)=4 -> no shrink | ||
| 49 | +constexpr size_t GROW_NODES_HYSTERESIS_AT = 14; // prev=4: frees 4 >= max(3,4)=4 -> shrink | ||
| 50 | + | ||
| 51 | +// Expected retained node count after a proportional shrink to 2x active usage. | ||
| 52 | +inline size_t ShrinkTargetNodes(int32_t prevIndex, int32_t minNodes) | ||
| 53 | +{ | ||
| 54 | + return static_cast<size_t>(std::max(minNodes, (prevIndex + 1) * SHRINK_HEADROOM_FACTOR)); | ||
| 55 | +} | ||
| 56 | + | ||
| 57 | +void GrowHandleStorageTo(EcmaVM *vm, size_t target) | ||
| 58 | +{ | ||
| 59 | + while (vm->GetHandleStorageNodesSize() < target) { | ||
| 60 | + vm->ExpandHandleStorage(); | ||
| 61 | + } | ||
| 62 | +} | ||
| 63 | + | ||
| 64 | +void GrowPrimitiveStorageTo(EcmaVM *vm, size_t target) | ||
| 65 | +{ | ||
| 66 | + while (vm->GetPrimitiveStorageNodesSize() < target) { | ||
| 67 | + vm->ExpandPrimitiveStorage(); | ||
| 68 | + } | ||
| 69 | +} | ||
| 70 | + | ||
| 71 | +// Collapse to a deterministic baseline of MIN nodes (index 0) regardless of | ||
| 72 | +// the non-deterministic node count left by VM initialization. | ||
| 73 | +void ResetHandleStorageToMin(EcmaVM *vm) | ||
| 74 | +{ | ||
| 75 | + GrowHandleStorageTo(vm, static_cast<size_t>(MIN_HANDLE_STORAGE_SIZE + RESET_GROW_MARGIN)); | ||
| 76 | + vm->ShrinkHandleStorage(0); | ||
| 77 | +} | ||
| 78 | + | ||
| 79 | +void ResetPrimitiveStorageToMin(EcmaVM *vm) | ||
| 80 | +{ | ||
| 81 | + GrowPrimitiveStorageTo(vm, static_cast<size_t>(MIN_PRIMITIVE_STORAGE_SIZE + RESET_GROW_MARGIN)); | ||
| 82 | + vm->ShrinkPrimitiveStorage(0); | ||
| 83 | +} | ||
| 84 | +} // namespace | ||
| 85 | + | ||
| 86 | +class EcmaVMStorageShrinkTest : public BaseTestWithScope<false> { | ||
| 87 | +}; | ||
| 88 | + | ||
| 89 | +// --- Handle storage --- | ||
| 90 | + | ||
| 91 | +HWTEST_F_L0(EcmaVMStorageShrinkTest, HandleStorage_LowUsageShrinkToHeadroom) | ||
| 92 | +{ | ||
| 93 | + // Low usage still shrinks, but to 2x usage (floored at MIN), not to MIN+1. | ||
| 94 | + ResetHandleStorageToMin(instance); | ||
| 95 | + GrowHandleStorageTo(instance, GROW_NODES_SMALL); | ||
| 96 | + ASSERT_EQ(instance->GetHandleStorageNodesSize(), GROW_NODES_SMALL); | ||
| 97 | + | ||
| 98 | + instance->ShrinkHandleStorage(PREV_INDEX_LOW); | ||
| 99 | + | ||
| 100 | + EXPECT_EQ(instance->GetCurrentHandleStorageIndex(), PREV_INDEX_LOW); | ||
| 101 | + EXPECT_EQ(instance->GetHandleStorageNodesSize(), ShrinkTargetNodes(PREV_INDEX_LOW, MIN_HANDLE_STORAGE_SIZE)); | ||
| 102 | + EXPECT_LT(instance->GetCurrentHandleStorageIndex(), | ||
| 103 | + static_cast<int32_t>(instance->GetHandleStorageNodesSize())); | ||
| 104 | +} | ||
| 105 | + | ||
| 106 | +HWTEST_F_L0(EcmaVMStorageShrinkTest, HandleStorage_ProportionalShrink) | ||
| 107 | +{ | ||
| 108 | + // usage <= 3/8 of total -> frees >= total/4 -> shrink to 2x usage. | ||
| 109 | + ResetHandleStorageToMin(instance); | ||
| 110 | + GrowHandleStorageTo(instance, GROW_NODES_LARGE); | ||
| 111 | + ASSERT_EQ(instance->GetHandleStorageNodesSize(), GROW_NODES_LARGE); | ||
| 112 | + | ||
| 113 | + instance->ShrinkHandleStorage(PREV_INDEX_PROPORTIONAL); | ||
| 114 | + | ||
| 115 | + EXPECT_EQ(instance->GetCurrentHandleStorageIndex(), PREV_INDEX_PROPORTIONAL); | ||
| 116 | + EXPECT_EQ(instance->GetHandleStorageNodesSize(), | ||
| 117 | + ShrinkTargetNodes(PREV_INDEX_PROPORTIONAL, MIN_HANDLE_STORAGE_SIZE)); | ||
| 118 | + EXPECT_LT(instance->GetCurrentHandleStorageIndex(), | ||
| 119 | + static_cast<int32_t>(instance->GetHandleStorageNodesSize())); | ||
| 120 | +} | ||
| 121 | + | ||
| 122 | +HWTEST_F_L0(EcmaVMStorageShrinkTest, HandleStorage_NoShrinkWhenHeadroomExceedsTotal) | ||
| 123 | +{ | ||
| 124 | + // 2x usage == total -> nothing to free. | ||
| 125 | + ResetHandleStorageToMin(instance); | ||
| 126 | + GrowHandleStorageTo(instance, GROW_NODES_NO_SHRINK); | ||
| 127 | + ASSERT_EQ(instance->GetHandleStorageNodesSize(), GROW_NODES_NO_SHRINK); | ||
| 128 | + | ||
| 129 | + instance->ShrinkHandleStorage(PREV_INDEX_HEADROOM_EXCEEDS); | ||
| 130 | + | ||
| 131 | + EXPECT_EQ(instance->GetCurrentHandleStorageIndex(), PREV_INDEX_HEADROOM_EXCEEDS); | ||
| 132 | + EXPECT_EQ(instance->GetHandleStorageNodesSize(), GROW_NODES_NO_SHRINK); | ||
| 133 | + EXPECT_LT(instance->GetCurrentHandleStorageIndex(), | ||
| 134 | + static_cast<int32_t>(instance->GetHandleStorageNodesSize())); | ||
| 135 | +} | ||
| 136 | + | ||
| 137 | +HWTEST_F_L0(EcmaVMStorageShrinkTest, HandleStorage_NoShrinkBelowHysteresis) | ||
| 138 | +{ | ||
| 139 | + // Frees a positive but < total/4 amount -> hysteresis keeps it to avoid churn. | ||
| 140 | + ResetHandleStorageToMin(instance); | ||
| 141 | + GrowHandleStorageTo(instance, GROW_NODES_NO_SHRINK); | ||
| 142 | + ASSERT_EQ(instance->GetHandleStorageNodesSize(), GROW_NODES_NO_SHRINK); | ||
| 143 | + | ||
| 144 | + instance->ShrinkHandleStorage(PREV_INDEX_HYSTERESIS_BAND); | ||
| 145 | + | ||
| 146 | + EXPECT_EQ(instance->GetCurrentHandleStorageIndex(), PREV_INDEX_HYSTERESIS_BAND); | ||
| 147 | + EXPECT_EQ(instance->GetHandleStorageNodesSize(), GROW_NODES_NO_SHRINK); | ||
| 148 | + EXPECT_LT(instance->GetCurrentHandleStorageIndex(), | ||
| 149 | + static_cast<int32_t>(instance->GetHandleStorageNodesSize())); | ||
| 150 | +} | ||
| 151 | + | ||
| 152 | +HWTEST_F_L0(EcmaVMStorageShrinkTest, HandleStorage_BelowMinGuardNoShrink) | ||
| 153 | +{ | ||
| 154 | + // lastIndex <= MIN -> outer guard (lastIndex > MIN) is false, no shrink. | ||
| 155 | + ResetHandleStorageToMin(instance); | ||
| 156 | + ASSERT_EQ(instance->GetHandleStorageNodesSize(), RESET_BASELINE_NODES); | ||
| 157 | + | ||
| 158 | + instance->ShrinkHandleStorage(0); | ||
| 159 | + | ||
| 160 | + EXPECT_EQ(instance->GetCurrentHandleStorageIndex(), 0); | ||
| 161 | + EXPECT_EQ(instance->GetHandleStorageNodesSize(), RESET_BASELINE_NODES); | ||
| 162 | +} | ||
| 163 | + | ||
| 164 | +HWTEST_F_L0(EcmaVMStorageShrinkTest, HandleStorage_HysteresisBoundary) | ||
| 165 | +{ | ||
| 166 | + // prev=4 -> target 10. total=13: frees 3 < max(3,4)=4 -> no shrink. | ||
| 167 | + ResetHandleStorageToMin(instance); | ||
| 168 | + GrowHandleStorageTo(instance, GROW_NODES_HYSTERESIS_BELOW); | ||
| 169 | + ASSERT_EQ(instance->GetHandleStorageNodesSize(), GROW_NODES_HYSTERESIS_BELOW); | ||
| 170 | + instance->ShrinkHandleStorage(PREV_INDEX_PROPORTIONAL); | ||
| 171 | + EXPECT_EQ(instance->GetHandleStorageNodesSize(), GROW_NODES_HYSTERESIS_BELOW); | ||
| 172 | + EXPECT_EQ(instance->GetCurrentHandleStorageIndex(), PREV_INDEX_PROPORTIONAL); | ||
| 173 | + | ||
| 174 | + // total=14: frees 4 >= max(3,4)=4 -> shrink to 2x usage. | ||
| 175 | + GrowHandleStorageTo(instance, GROW_NODES_HYSTERESIS_AT); | ||
| 176 | + ASSERT_EQ(instance->GetHandleStorageNodesSize(), GROW_NODES_HYSTERESIS_AT); | ||
| 177 | + instance->ShrinkHandleStorage(PREV_INDEX_PROPORTIONAL); | ||
| 178 | + EXPECT_EQ(instance->GetHandleStorageNodesSize(), | ||
| 179 | + ShrinkTargetNodes(PREV_INDEX_PROPORTIONAL, MIN_HANDLE_STORAGE_SIZE)); | ||
| 180 | + EXPECT_EQ(instance->GetCurrentHandleStorageIndex(), PREV_INDEX_PROPORTIONAL); | ||
| 181 | + EXPECT_LT(instance->GetCurrentHandleStorageIndex(), | ||
| 182 | + static_cast<int32_t>(instance->GetHandleStorageNodesSize())); | ||
| 183 | +} | ||
| 184 | + | ||
| 185 | +HWTEST_F_L0(EcmaVMStorageShrinkTest, HandleStorage_IndependentOfPrimitiveIndex) | ||
| 186 | +{ | ||
| 187 | + // Handle shrink must use the handle index, never the primitive index. | ||
| 188 | + ResetHandleStorageToMin(instance); | ||
| 189 | + ResetPrimitiveStorageToMin(instance); | ||
| 190 | + // Inflate primitive storage so currentPrimitiveStorageIndex_ is large and unrelated. | ||
| 191 | + GrowPrimitiveStorageTo(instance, GROW_NODES_LARGE); | ||
| 192 | + GrowHandleStorageTo(instance, GROW_NODES_LARGE); | ||
| 193 | + ASSERT_EQ(instance->GetHandleStorageNodesSize(), GROW_NODES_LARGE); | ||
| 194 | + | ||
| 195 | + instance->ShrinkHandleStorage(PREV_INDEX_PROPORTIONAL); | ||
| 196 | + | ||
| 197 | + EXPECT_EQ(instance->GetHandleStorageNodesSize(), | ||
| 198 | + ShrinkTargetNodes(PREV_INDEX_PROPORTIONAL, MIN_HANDLE_STORAGE_SIZE)); | ||
| 199 | + EXPECT_EQ(instance->GetCurrentHandleStorageIndex(), PREV_INDEX_PROPORTIONAL); | ||
| 200 | +} | ||
| 201 | + | ||
| 202 | +// --- Primitive storage --- | ||
| 203 | + | ||
| 204 | +HWTEST_F_L0(EcmaVMStorageShrinkTest, PrimitiveStorage_LowUsageShrinkToHeadroom) | ||
| 205 | +{ | ||
| 206 | + ResetPrimitiveStorageToMin(instance); | ||
| 207 | + GrowPrimitiveStorageTo(instance, GROW_NODES_SMALL); | ||
| 208 | + ASSERT_EQ(instance->GetPrimitiveStorageNodesSize(), GROW_NODES_SMALL); | ||
| 209 | + | ||
| 210 | + instance->ShrinkPrimitiveStorage(PREV_INDEX_LOW); | ||
| 211 | + | ||
| 212 | + EXPECT_EQ(instance->GetCurrentPrimitiveStorageIndex(), PREV_INDEX_LOW); | ||
| 213 | + EXPECT_EQ(instance->GetPrimitiveStorageNodesSize(), ShrinkTargetNodes(PREV_INDEX_LOW, MIN_PRIMITIVE_STORAGE_SIZE)); | ||
| 214 | + EXPECT_LT(instance->GetCurrentPrimitiveStorageIndex(), | ||
| 215 | + static_cast<int32_t>(instance->GetPrimitiveStorageNodesSize())); | ||
| 216 | +} | ||
| 217 | + | ||
| 218 | +HWTEST_F_L0(EcmaVMStorageShrinkTest, PrimitiveStorage_ProportionalShrink) | ||
| 219 | +{ | ||
| 220 | + ResetPrimitiveStorageToMin(instance); | ||
| 221 | + GrowPrimitiveStorageTo(instance, GROW_NODES_LARGE); | ||
| 222 | + ASSERT_EQ(instance->GetPrimitiveStorageNodesSize(), GROW_NODES_LARGE); | ||
| 223 | + | ||
| 224 | + instance->ShrinkPrimitiveStorage(PREV_INDEX_PROPORTIONAL); | ||
| 225 | + | ||
| 226 | + EXPECT_EQ(instance->GetCurrentPrimitiveStorageIndex(), PREV_INDEX_PROPORTIONAL); | ||
| 227 | + EXPECT_EQ(instance->GetPrimitiveStorageNodesSize(), | ||
| 228 | + ShrinkTargetNodes(PREV_INDEX_PROPORTIONAL, MIN_PRIMITIVE_STORAGE_SIZE)); | ||
| 229 | + EXPECT_LT(instance->GetCurrentPrimitiveStorageIndex(), | ||
| 230 | + static_cast<int32_t>(instance->GetPrimitiveStorageNodesSize())); | ||
| 231 | +} | ||
| 232 | + | ||
| 233 | +HWTEST_F_L0(EcmaVMStorageShrinkTest, PrimitiveStorage_NoShrinkWhenHeadroomExceedsTotal) | ||
| 234 | +{ | ||
| 235 | + ResetPrimitiveStorageToMin(instance); | ||
| 236 | + GrowPrimitiveStorageTo(instance, GROW_NODES_NO_SHRINK); | ||
| 237 | + ASSERT_EQ(instance->GetPrimitiveStorageNodesSize(), GROW_NODES_NO_SHRINK); | ||
| 238 | + | ||
| 239 | + instance->ShrinkPrimitiveStorage(PREV_INDEX_HEADROOM_EXCEEDS); | ||
| 240 | + | ||
| 241 | + EXPECT_EQ(instance->GetCurrentPrimitiveStorageIndex(), PREV_INDEX_HEADROOM_EXCEEDS); | ||
| 242 | + EXPECT_EQ(instance->GetPrimitiveStorageNodesSize(), GROW_NODES_NO_SHRINK); | ||
| 243 | + EXPECT_LT(instance->GetCurrentPrimitiveStorageIndex(), | ||
| 244 | + static_cast<int32_t>(instance->GetPrimitiveStorageNodesSize())); | ||
| 245 | +} | ||
| 246 | + | ||
| 247 | +HWTEST_F_L0(EcmaVMStorageShrinkTest, PrimitiveStorage_NoShrinkBelowHysteresis) | ||
| 248 | +{ | ||
| 249 | + ResetPrimitiveStorageToMin(instance); | ||
| 250 | + GrowPrimitiveStorageTo(instance, GROW_NODES_NO_SHRINK); | ||
| 251 | + ASSERT_EQ(instance->GetPrimitiveStorageNodesSize(), GROW_NODES_NO_SHRINK); | ||
| 252 | + | ||
| 253 | + instance->ShrinkPrimitiveStorage(PREV_INDEX_HYSTERESIS_BAND); | ||
| 254 | + | ||
| 255 | + EXPECT_EQ(instance->GetCurrentPrimitiveStorageIndex(), PREV_INDEX_HYSTERESIS_BAND); | ||
| 256 | + EXPECT_EQ(instance->GetPrimitiveStorageNodesSize(), GROW_NODES_NO_SHRINK); | ||
| 257 | + EXPECT_LT(instance->GetCurrentPrimitiveStorageIndex(), | ||
| 258 | + static_cast<int32_t>(instance->GetPrimitiveStorageNodesSize())); | ||
| 259 | +} | ||
| 260 | + | ||
| 261 | +HWTEST_F_L0(EcmaVMStorageShrinkTest, PrimitiveStorage_BelowMinGuardNoShrink) | ||
| 262 | +{ | ||
| 263 | + // lastIndex <= MIN -> outer guard (lastIndex > MIN) is false, no shrink. | ||
| 264 | + ResetPrimitiveStorageToMin(instance); | ||
| 265 | + ASSERT_EQ(instance->GetPrimitiveStorageNodesSize(), | ||
| 266 | + static_cast<size_t>(MIN_PRIMITIVE_STORAGE_SIZE)); | ||
| 267 | + | ||
| 268 | + instance->ShrinkPrimitiveStorage(0); | ||
| 269 | + | ||
| 270 | + EXPECT_EQ(instance->GetCurrentPrimitiveStorageIndex(), 0); | ||
| 271 | + EXPECT_EQ(instance->GetPrimitiveStorageNodesSize(), | ||
| 272 | + static_cast<size_t>(MIN_PRIMITIVE_STORAGE_SIZE)); | ||
| 273 | +} | ||
| 274 | + | ||
| 275 | +HWTEST_F_L0(EcmaVMStorageShrinkTest, PrimitiveStorage_HysteresisBoundary) | ||
| 276 | +{ | ||
| 277 | + ResetPrimitiveStorageToMin(instance); | ||
| 278 | + GrowPrimitiveStorageTo(instance, GROW_NODES_HYSTERESIS_BELOW); | ||
| 279 | + ASSERT_EQ(instance->GetPrimitiveStorageNodesSize(), GROW_NODES_HYSTERESIS_BELOW); | ||
| 280 | + instance->ShrinkPrimitiveStorage(PREV_INDEX_PROPORTIONAL); | ||
| 281 | + EXPECT_EQ(instance->GetPrimitiveStorageNodesSize(), GROW_NODES_HYSTERESIS_BELOW); | ||
| 282 | + EXPECT_EQ(instance->GetCurrentPrimitiveStorageIndex(), PREV_INDEX_PROPORTIONAL); | ||
| 283 | + | ||
| 284 | + GrowPrimitiveStorageTo(instance, GROW_NODES_HYSTERESIS_AT); | ||
| 285 | + ASSERT_EQ(instance->GetPrimitiveStorageNodesSize(), GROW_NODES_HYSTERESIS_AT); | ||
| 286 | + instance->ShrinkPrimitiveStorage(PREV_INDEX_PROPORTIONAL); | ||
| 287 | + EXPECT_EQ(instance->GetPrimitiveStorageNodesSize(), | ||
| 288 | + ShrinkTargetNodes(PREV_INDEX_PROPORTIONAL, MIN_PRIMITIVE_STORAGE_SIZE)); | ||
| 289 | + EXPECT_EQ(instance->GetCurrentPrimitiveStorageIndex(), PREV_INDEX_PROPORTIONAL); | ||
| 290 | + EXPECT_LT(instance->GetCurrentPrimitiveStorageIndex(), | ||
| 291 | + static_cast<int32_t>(instance->GetPrimitiveStorageNodesSize())); | ||
| 292 | +} | ||
| 293 | + | ||
| 294 | +// P0 regression: ShrinkPrimitiveStorage must key off currentPrimitiveStorageIndex_, | ||
| 295 | +// not currentHandleStorageIndex_. The buggy version computed targetNodes from the | ||
| 296 | +// handle index; with a small handle index it deleted primitive nodes still in use | ||
| 297 | +// (use-after-free), and with a large handle index it skipped shrinking. In both | ||
| 298 | +// cases the resulting node count differs from 2x the primitive usage. | ||
| 299 | +HWTEST_F_L0(EcmaVMStorageShrinkTest, PrimitiveStorage_UsesPrimitiveIndexNotHandleIndex) | ||
| 300 | +{ | ||
| 301 | + // Pin handle storage to index 0 so the buggy code path (which read the handle | ||
| 302 | + // index) would compute a minimal target and delete the active primitive node. | ||
| 303 | + ResetHandleStorageToMin(instance); | ||
| 304 | + ResetPrimitiveStorageToMin(instance); | ||
| 305 | + GrowPrimitiveStorageTo(instance, GROW_NODES_LARGE); | ||
| 306 | + ASSERT_EQ(instance->GetPrimitiveStorageNodesSize(), GROW_NODES_LARGE); | ||
| 307 | + ASSERT_EQ(instance->GetCurrentHandleStorageIndex(), 0); | ||
| 308 | + | ||
| 309 | + instance->ShrinkPrimitiveStorage(PREV_INDEX_PROPORTIONAL); | ||
| 310 | + | ||
| 311 | + // Fixed: target = headroom * (prevIndex + 1). | ||
| 312 | + // Buggy (handle index 0): target collapses to MIN and the active primitive | ||
| 313 | + // node dangles; the node count differs from the proportional target. | ||
| 314 | + EXPECT_EQ(instance->GetPrimitiveStorageNodesSize(), | ||
| 315 | + ShrinkTargetNodes(PREV_INDEX_PROPORTIONAL, MIN_PRIMITIVE_STORAGE_SIZE)); | ||
| 316 | + EXPECT_EQ(instance->GetCurrentPrimitiveStorageIndex(), PREV_INDEX_PROPORTIONAL); | ||
| 317 | + // Active primitive index must stay within the allocated node range. | ||
| 318 | + EXPECT_LT(instance->GetCurrentPrimitiveStorageIndex(), | ||
| 319 | + static_cast<int32_t>(instance->GetPrimitiveStorageNodesSize())); | ||
| 320 | +} | ||
| 321 | + | ||
| 322 | +} // namespace panda::test | ||
| @@ -1398,6 +1398,11 @@ | |||
| 1398 | <option name="push" value="arkcompiler/ets_runtime/libark_jsruntime_test.so -> /data/test" src="out"/> | 1398 | <option name="push" value="arkcompiler/ets_runtime/libark_jsruntime_test.so -> /data/test" src="out"/> |
| 1399 | </preparer> | 1399 | </preparer> |
| 1400 | </target> | 1400 | </target> |
| 1401 | + <target name="JS_EcmaVMStorageShrink_Test"> | ||
| 1402 | + <preparer> | ||
| 1403 | + <option name="push" value="arkcompiler/ets_runtime/libark_jsruntime_test.so -> /data/test" src="out"/> | ||
| 1404 | + </preparer> | ||
| 1405 | + </target> | ||
| 1401 | <target name="JS_Map_Test"> | 1406 | <target name="JS_Map_Test"> |
| 1402 | <preparer> | 1407 | <preparer> |
| 1403 | <option name="push" value="arkcompiler/ets_runtime/libark_jsruntime_test.so -> /data/test" src="out"/> | 1408 | <option name="push" value="arkcompiler/ets_runtime/libark_jsruntime_test.so -> /data/test" src="out"/> |
新增的test文件要加到xml里面