diff --git a/OPENCLAW.md b/OPENCLAW.md index f50076e557150..ecf74f0d06a38 100644 --- a/OPENCLAW.md +++ b/OPENCLAW.md @@ -170,6 +170,8 @@ baseline. Engine and Bun headers and libraries must be rebuilt together. ## Unreleased +- Use the all-dead sweep specialization during VM destruction while preserving weak finalization and every cell destructor. + ## Stack retention and Linux suspension (2026-10-09) - Initialize new VM entry scratch buffers and alignment padding before entering JavaScript or native callees, preventing stale stack values from retaining otherwise unreachable objects during collection. diff --git a/Source/JavaScriptCore/heap/MarkedBlock.cpp b/Source/JavaScriptCore/heap/MarkedBlock.cpp index 021f52c0ca2b6..389fbe682262b 100644 --- a/Source/JavaScriptCore/heap/MarkedBlock.cpp +++ b/Source/JavaScriptCore/heap/MarkedBlock.cpp @@ -199,7 +199,9 @@ void MarkedBlock::Handle::lastChanceToFinalize() blockHeader().m_markingVersion = heap()->objectSpace().markingVersion(); m_weakSet.lastChanceToFinalize(); blockHeader().m_newlyAllocated.clearAll(); - blockHeader().m_newlyAllocatedVersion = heap()->objectSpace().newlyAllocatedVersion(); + // Both liveness maps are empty: use the existing all-dead sweep specialization. + blockHeader().m_newlyAllocatedVersion = MarkedSpace::nullVersion; + blockHeader().m_markingVersion = MarkedSpace::nullVersion; m_directory->setIsInUse(this, true); sweep(nullptr); } diff --git a/Tools/TestWebKitAPI/CMakeLists.txt b/Tools/TestWebKitAPI/CMakeLists.txt index fb877881a9b0a..11e9fee2e4398 100644 --- a/Tools/TestWebKitAPI/CMakeLists.txt +++ b/Tools/TestWebKitAPI/CMakeLists.txt @@ -219,6 +219,7 @@ if (ENABLE_JAVASCRIPTCORE) Runner/TestsController.cpp Tests/JavaScriptCore/DisallowVMEntry.cpp + Tests/JavaScriptCore/HeapTeardown.cpp Tests/JavaScriptCore/InspectorConsoleMessage.cpp Tests/JavaScriptCore/MarkedVector.cpp Tests/JavaScriptCore/PropertySlot.cpp diff --git a/Tools/TestWebKitAPI/Tests/JavaScriptCore/HeapTeardown.cpp b/Tools/TestWebKitAPI/Tests/JavaScriptCore/HeapTeardown.cpp new file mode 100644 index 0000000000000..0468fe8f61516 --- /dev/null +++ b/Tools/TestWebKitAPI/Tests/JavaScriptCore/HeapTeardown.cpp @@ -0,0 +1,104 @@ +/* + * Copyright (C) 2026 Peter Steinberger. All rights reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in the + * documentation and/or other materials provided with the distribution. + * + * THIS SOFTWARE IS PROVIDED BY APPLE INC. AND ITS CONTRIBUTORS ``AS IS'' + * AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, + * THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR + * PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL APPLE INC. OR ITS CONTRIBUTORS + * BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS + * INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN + * CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) + * ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF + * THE POSSIBILITY OF SUCH DAMAGE. + */ + +#include "config.h" +#include +#include +#include +#include +#include +#include +#include + + +namespace TestWebKitAPI { + +struct FinalizationState { + unsigned count { 0 }; + bool shuttingDown { false }; + bool inactiveMarks { false }; + bool inactiveNewlyAllocated { false }; +}; +static void finalize(JSObjectRef object) +{ + auto& state = *static_cast(JSObjectGetPrivate(object)); + // The Structure may already be swept when this finalizer runs. + auto* cell = uncheckedToJS(object); + auto& vm = cell->vm(); + ++state.count; + state.shuttingDown = vm.heap.isShuttingDown(); + if (cell->isPreciseAllocation()) { + auto* allocation = JSC::PreciseAllocation::fromCell(cell); + state.inactiveMarks = !allocation->isMarked(); + state.inactiveNewlyAllocated = !allocation->isNewlyAllocated(); + return; + } + auto* block = JSC::MarkedBlock::blockFor(cell); + state.inactiveMarks = block->areMarksStale(vm.heap.objectSpace().markingVersion()); + state.inactiveNewlyAllocated = !block->hasAnyNewlyAllocated(); +} +static void finalizeHeap(bool collectBeforeRelease) +{ + std::array states; + auto group = JSContextGroupCreate(); + auto context = JSGlobalContextCreateInGroup(group, nullptr); + auto definition = kJSClassDefinitionEmpty; + definition.attributes = kJSClassAttributeNoAutomaticPrototype; + definition.finalize = finalize; + auto klass = JSClassCreate(&definition); + { + JSC::JSLockHolder locker(::toJS(group)); + auto global = JSContextGetGlobalObject(context); + for (unsigned i = 0; i < states.size(); ++i) { + auto object = JSObjectMake(context, klass, &states[i]); + JSObjectSetPropertyAtIndex(context, global, i, object, nullptr); + } + if (collectBeforeRelease) + ::toJS(group)->heap.collectNow(JSC::Sync, JSC::CollectionScope::Full); + for (const auto& state : states) + EXPECT_EQ(state.count, 0u); + JSGlobalContextRelease(context); + } + JSClassRelease(klass); + JSContextGroupRelease(group); + for (const auto& state : states) { + EXPECT_EQ(state.count, 1u); + EXPECT_TRUE(state.shuttingDown); + EXPECT_TRUE(state.inactiveMarks); + EXPECT_TRUE(state.inactiveNewlyAllocated); + } +} + +TEST(JavaScriptCore_HeapTeardown, FinalizesNewCellsWithoutLiveBitmaps) +{ + finalizeHeap(false); +} + +TEST(JavaScriptCore_HeapTeardown, FinalizesMarkedCellsWithoutLiveBitmaps) +{ + finalizeHeap(true); +} + +} // namespace TestWebKitAPI