Ignore:
Timestamp:
Aug 1, 2017, 6:50:16 PM (8 years ago)
Author:
[email protected]
Message:

Bmalloc and GC should put auxiliaries (butterflies, typed array backing stores) in a gigacage (separate multi-GB VM region)
https://bugs.webkit.org/show_bug.cgi?id=174727

Reviewed by Mark Lam.
Source/bmalloc:


This adds a mechanism for managing multiple isolated heaps in bmalloc. For now, these isoheaps
(isolated heaps) have a very simple relationship with each other and with the rest of bmalloc:

  • You have to choose how many isoheaps you will have statically. See numHeaps in HeapKind.h.


  • Because numHeaps is static, each isoheap gets fast thread-local allocation. Basically, we have a Cache for each heap kind.


  • Each isoheap gets its own Heap.


  • Each Heap gets a scavenger thread.


  • Some things, like Zone/VMHeap/Scavenger, are per-process.


Most of the per-HeapKind functionality is handled by PerHeapKind<>.

This approach is ideal for supporting special per-HeapKind behaviors. For now we have two heaps:
the Primary heap for normal malloc and the Gigacage. The gigacage is a 64GB-aligned 64GB virtual
region that we now use for variable-length random-access allocations. No Primary allocations will
go into the Gigacage.

  • CMakeLists.txt:
  • bmalloc.xcodeproj/project.pbxproj:
  • bmalloc/AllocationKind.h: Added.
  • bmalloc/Allocator.cpp:

(bmalloc::Allocator::Allocator):
(bmalloc::Allocator::tryAllocate):
(bmalloc::Allocator::allocateImpl):
(bmalloc::Allocator::reallocate):
(bmalloc::Allocator::refillAllocatorSlowCase):
(bmalloc::Allocator::allocateLarge):

  • bmalloc/Allocator.h:
  • bmalloc/BExport.h: Added.
  • bmalloc/Cache.cpp:

(bmalloc::Cache::scavenge):
(bmalloc::Cache::Cache):
(bmalloc::Cache::tryAllocateSlowCaseNullCache):
(bmalloc::Cache::allocateSlowCaseNullCache):
(bmalloc::Cache::deallocateSlowCaseNullCache):
(bmalloc::Cache::reallocateSlowCaseNullCache):
(bmalloc::Cache::operator new): Deleted.
(bmalloc::Cache::operator delete): Deleted.

  • bmalloc/Cache.h:

(bmalloc::Cache::tryAllocate):
(bmalloc::Cache::allocate):
(bmalloc::Cache::deallocate):
(bmalloc::Cache::reallocate):

  • bmalloc/Deallocator.cpp:

(bmalloc::Deallocator::Deallocator):
(bmalloc::Deallocator::scavenge):
(bmalloc::Deallocator::processObjectLog):
(bmalloc::Deallocator::deallocateSlowCase):

  • bmalloc/Deallocator.h:
  • bmalloc/Gigacage.cpp: Added.

(Gigacage::Callback::Callback):
(Gigacage::Callback::function):
(Gigacage::Callbacks::Callbacks):
(Gigacage::ensureGigacage):
(Gigacage::disableGigacage):
(Gigacage::addDisableCallback):
(Gigacage::removeDisableCallback):

  • bmalloc/Gigacage.h: Added.

(Gigacage::caged):
(Gigacage::isCaged):

  • bmalloc/Heap.cpp:

(bmalloc::Heap::Heap):
(bmalloc::Heap::usingGigacage):
(bmalloc::Heap::concurrentScavenge):
(bmalloc::Heap::splitAndAllocate):
(bmalloc::Heap::tryAllocateLarge):
(bmalloc::Heap::allocateLarge):
(bmalloc::Heap::shrinkLarge):
(bmalloc::Heap::deallocateLarge):

  • bmalloc/Heap.h:

(bmalloc::Heap::mutex):
(bmalloc::Heap::kind const):
(bmalloc::Heap::setScavengerThreadQOSClass): Deleted.

  • bmalloc/HeapKind.h: Added.
  • bmalloc/ObjectType.cpp:

(bmalloc::objectType):

  • bmalloc/ObjectType.h:
  • bmalloc/PerHeapKind.h: Added.

(bmalloc::PerHeapKindBase::PerHeapKindBase):
(bmalloc::PerHeapKindBase::size):
(bmalloc::PerHeapKindBase::at):
(bmalloc::PerHeapKindBase::at const):
(bmalloc::PerHeapKindBase::operator[]):
(bmalloc::PerHeapKindBase::operator[] const):
(bmalloc::StaticPerHeapKind::StaticPerHeapKind):
(bmalloc::PerHeapKind::PerHeapKind):
(bmalloc::PerHeapKind::~PerHeapKind):

  • bmalloc/PerThread.h:

(bmalloc::PerThread<T>::destructor):
(bmalloc::PerThread<T>::getSlowCase):
(bmalloc::PerThreadStorage<Cache>::get): Deleted.
(bmalloc::PerThreadStorage<Cache>::init): Deleted.

  • bmalloc/Scavenger.cpp: Added.

(bmalloc::Scavenger::Scavenger):
(bmalloc::Scavenger::scavenge):

  • bmalloc/Scavenger.h: Added.

(bmalloc::Scavenger::setScavengerThreadQOSClass):
(bmalloc::Scavenger::requestedScavengerThreadQOSClass const):

  • bmalloc/VMHeap.cpp:

(bmalloc::VMHeap::VMHeap):
(bmalloc::VMHeap::tryAllocateLargeChunk):

  • bmalloc/VMHeap.h:
  • bmalloc/Zone.cpp:

(bmalloc::Zone::Zone):

  • bmalloc/Zone.h:
  • bmalloc/bmalloc.h:

(bmalloc::api::tryMalloc):
(bmalloc::api::malloc):
(bmalloc::api::tryMemalign):
(bmalloc::api::memalign):
(bmalloc::api::realloc):
(bmalloc::api::tryLargeMemalignVirtual):
(bmalloc::api::free):
(bmalloc::api::freeLargeVirtual):
(bmalloc::api::scavengeThisThread):
(bmalloc::api::scavenge):
(bmalloc::api::isEnabled):
(bmalloc::api::setScavengerThreadQOSClass):

  • bmalloc/mbmalloc.cpp:

Source/JavaScriptCore:


This adopts the Gigacage for the GigacageSubspace, which we use for Auxiliary allocations. Also, in
one place in the code - the FTL codegen for butterfly and typed array access - we "cage" the accesses
themselves. Basically, we do masking to ensure that the pointer points into the gigacage.

This is neutral on JetStream.

  • CMakeLists.txt:
  • JavaScriptCore.xcodeproj/project.pbxproj:
  • b3/B3InsertionSet.cpp:

(JSC::B3::InsertionSet::execute):

  • dfg/DFGAbstractInterpreterInlines.h:

(JSC::DFG::AbstractInterpreter<AbstractStateType>::executeEffects):

  • dfg/DFGArgumentsEliminationPhase.cpp:
  • dfg/DFGClobberize.cpp:

(JSC::DFG::readsOverlap):

  • dfg/DFGClobberize.h:

(JSC::DFG::clobberize):

  • dfg/DFGDoesGC.cpp:

(JSC::DFG::doesGC):

  • dfg/DFGFixedButterflyAccessUncagingPhase.cpp: Added.

(JSC::DFG::performFixedButterflyAccessUncaging):

  • dfg/DFGFixedButterflyAccessUncagingPhase.h: Added.
  • dfg/DFGFixupPhase.cpp:

(JSC::DFG::FixupPhase::fixupNode):

  • dfg/DFGHeapLocation.cpp:

(WTF::printInternal):

  • dfg/DFGHeapLocation.h:
  • dfg/DFGNodeType.h:
  • dfg/DFGPlan.cpp:

(JSC::DFG::Plan::compileInThreadImpl):

  • dfg/DFGPredictionPropagationPhase.cpp:
  • dfg/DFGSafeToExecute.h:

(JSC::DFG::safeToExecute):

  • dfg/DFGSpeculativeJIT.cpp:

(JSC::DFG::SpeculativeJIT::compileGetButterfly):

  • dfg/DFGSpeculativeJIT32_64.cpp:

(JSC::DFG::SpeculativeJIT::compile):

  • dfg/DFGSpeculativeJIT64.cpp:

(JSC::DFG::SpeculativeJIT::compile):

  • dfg/DFGTypeCheckHoistingPhase.cpp:

(JSC::DFG::TypeCheckHoistingPhase::identifyRedundantStructureChecks):
(JSC::DFG::TypeCheckHoistingPhase::identifyRedundantArrayChecks):

  • ftl/FTLCapabilities.cpp:

(JSC::FTL::canCompile):

  • ftl/FTLLowerDFGToB3.cpp:

(JSC::FTL::DFG::LowerDFGToB3::compileNode):
(JSC::FTL::DFG::LowerDFGToB3::compileGetButterfly):
(JSC::FTL::DFG::LowerDFGToB3::compileGetIndexedPropertyStorage):
(JSC::FTL::DFG::LowerDFGToB3::compileGetByVal):
(JSC::FTL::DFG::LowerDFGToB3::compileStringCharAt):
(JSC::FTL::DFG::LowerDFGToB3::compileStringCharCodeAt):
(JSC::FTL::DFG::LowerDFGToB3::compileGetMapBucket):
(JSC::FTL::DFG::LowerDFGToB3::compileGetDirectPname):
(JSC::FTL::DFG::LowerDFGToB3::compileToLowerCase):
(JSC::FTL::DFG::LowerDFGToB3::caged):

  • heap/GigacageSubspace.cpp: Added.

(JSC::GigacageSubspace::GigacageSubspace):
(JSC::GigacageSubspace::~GigacageSubspace):
(JSC::GigacageSubspace::tryAllocateAlignedMemory):
(JSC::GigacageSubspace::freeAlignedMemory):
(JSC::GigacageSubspace::canTradeBlocksWith):

  • heap/GigacageSubspace.h: Added.
  • heap/Heap.cpp:

(JSC::Heap::Heap):
(JSC::Heap::lastChanceToFinalize):
(JSC::Heap::finalize):
(JSC::Heap::sweepInFinalize):
(JSC::Heap::updateAllocationLimits):
(JSC::Heap::shouldDoFullCollection):
(JSC::Heap::collectIfNecessaryOrDefer):
(JSC::Heap::reportWebAssemblyFastMemoriesAllocated): Deleted.
(JSC::Heap::webAssemblyFastMemoriesThisCycleAtThreshold const): Deleted.
(JSC::Heap::sweepLargeAllocations): Deleted.
(JSC::Heap::didAllocateWebAssemblyFastMemories): Deleted.

  • heap/Heap.h:
  • heap/LargeAllocation.cpp:

(JSC::LargeAllocation::tryCreate):
(JSC::LargeAllocation::destroy):

  • heap/MarkedAllocator.cpp:

(JSC::MarkedAllocator::tryAllocateWithoutCollecting):
(JSC::MarkedAllocator::tryAllocateBlock):

  • heap/MarkedBlock.cpp:

(JSC::MarkedBlock::tryCreate):
(JSC::MarkedBlock::Handle::Handle):
(JSC::MarkedBlock::Handle::~Handle):
(JSC::MarkedBlock::Handle::didAddToAllocator):
(JSC::MarkedBlock::Handle::subspace const): Deleted.

  • heap/MarkedBlock.h:

(JSC::MarkedBlock::Handle::subspace const):

  • heap/MarkedSpace.cpp:

(JSC::MarkedSpace::~MarkedSpace):
(JSC::MarkedSpace::freeMemory):
(JSC::MarkedSpace::prepareForAllocation):
(JSC::MarkedSpace::addMarkedAllocator):
(JSC::MarkedSpace::findEmptyBlockToSteal): Deleted.

  • heap/MarkedSpace.h:

(JSC::MarkedSpace::firstAllocator const):
(JSC::MarkedSpace::allocatorForEmptyAllocation const): Deleted.

  • heap/Subspace.cpp:

(JSC::Subspace::Subspace):
(JSC::Subspace::canTradeBlocksWith):
(JSC::Subspace::tryAllocateAlignedMemory):
(JSC::Subspace::freeAlignedMemory):
(JSC::Subspace::prepareForAllocation):
(JSC::Subspace::findEmptyBlockToSteal):

  • heap/Subspace.h:

(JSC::Subspace::didCreateFirstAllocator):

  • heap/SubspaceInlines.h:

(JSC::Subspace::forEachAllocator):
(JSC::Subspace::forEachMarkedBlock):
(JSC::Subspace::forEachNotEmptyMarkedBlock):

  • jit/JITPropertyAccess.cpp:

(JSC::JIT::emitDoubleLoad):
(JSC::JIT::emitContiguousLoad):
(JSC::JIT::emitArrayStorageLoad):
(JSC::JIT::emitGenericContiguousPutByVal):
(JSC::JIT::emitArrayStoragePutByVal):
(JSC::JIT::emit_op_get_from_scope):
(JSC::JIT::emit_op_put_to_scope):
(JSC::JIT::emitIntTypedArrayGetByVal):
(JSC::JIT::emitFloatTypedArrayGetByVal):
(JSC::JIT::emitIntTypedArrayPutByVal):
(JSC::JIT::emitFloatTypedArrayPutByVal):

  • jsc.cpp:

(fillBufferWithContentsOfFile):
(functionReadFile):
(gigacageDisabled):
(jscmain):

  • llint/LowLevelInterpreter64.asm:
  • runtime/ArrayBuffer.cpp:

(JSC::ArrayBufferContents::tryAllocate):
(JSC::ArrayBuffer::createAdopted):
(JSC::ArrayBuffer::createFromBytes):
(JSC::ArrayBuffer::tryCreate):

  • runtime/IndexingHeader.h:
  • runtime/InitializeThreading.cpp:

(JSC::initializeThreading):

  • runtime/JSArrayBuffer.cpp:
  • runtime/JSArrayBufferView.cpp:

(JSC::JSArrayBufferView::ConstructionContext::ConstructionContext):
(JSC::JSArrayBufferView::finalize):

  • runtime/JSLock.cpp:

(JSC::JSLock::didAcquireLock):

  • runtime/JSObject.h:
  • runtime/Options.cpp:

(JSC::recomputeDependentOptions):

  • runtime/Options.h:
  • runtime/ScopedArgumentsTable.h:
  • runtime/VM.cpp:

(JSC::VM::VM):
(JSC::VM::~VM):
(JSC::VM::gigacageDisabledCallback):
(JSC::VM::gigacageDisabled):

  • runtime/VM.h:

(JSC::VM::fireGigacageEnabledIfNecessary):
(JSC::VM::gigacageEnabled):

  • wasm/WasmB3IRGenerator.cpp:

(JSC::Wasm::B3IRGenerator::B3IRGenerator):
(JSC::Wasm::B3IRGenerator::emitCheckAndPreparePointer):

  • wasm/WasmCodeBlock.cpp:

(JSC::Wasm::CodeBlock::isSafeToRun):

  • wasm/WasmMemory.cpp:

(JSC::Wasm::makeString):
(JSC::Wasm::Memory::create):
(JSC::Wasm::Memory::~Memory):
(JSC::Wasm::Memory::addressIsInActiveFastMemory):
(JSC::Wasm::Memory::grow):
(JSC::Wasm::Memory::initializePreallocations): Deleted.
(JSC::Wasm::Memory::maxFastMemoryCount): Deleted.

  • wasm/WasmMemory.h:
  • wasm/js/JSWebAssemblyInstance.cpp:

(JSC::JSWebAssemblyInstance::create):

  • wasm/js/JSWebAssemblyMemory.cpp:

(JSC::JSWebAssemblyMemory::grow):
(JSC::JSWebAssemblyMemory::finishCreation):

  • wasm/js/JSWebAssemblyMemory.h:

(JSC::JSWebAssemblyMemory::subspaceFor):

Source/WebCore:

No new tests because no change in behavior.

Needed to teach Metal how to allocate in the Gigacage.

  • platform/graphics/cocoa/GPUBufferMetal.mm:

(WebCore::GPUBuffer::GPUBuffer):
(WebCore::GPUBuffer::contents):

Source/WebKit:


The WebProcess should never disable the Gigacage by allocating typed arrays outside the Gigacage. So,
we add a callback that crashes the process.

  • WebProcess/WebProcess.cpp:

(WebKit::gigacageDisabled):
(WebKit::m_webSQLiteDatabaseTracker):

Source/WTF:


For the Gigacage project to have minimal impact, we need to have some abstraction that allows code to
avoid having to guard itself with #if's. This adds a Gigacage abstraction that overlays the Gigacage
namespace from bmalloc, which always lets you call things like Gigacage::caged and Gigacage::tryMalloc.

Because of how many places need to possibly allocate in a gigacage, or possibly perform caged accesses,
it's better to hide the question of whether or not it's enabled inside this API.

  • WTF.xcodeproj/project.pbxproj:
  • wtf/CMakeLists.txt:
  • wtf/FastMalloc.cpp:
  • wtf/Gigacage.cpp: Added.

(Gigacage::tryMalloc):
(Gigacage::tryAllocateVirtualPages):
(Gigacage::freeVirtualPages):
(Gigacage::tryAlignedMalloc):
(Gigacage::alignedFree):
(Gigacage::free):

  • wtf/Gigacage.h: Added.

(Gigacage::ensureGigacage):
(Gigacage::disableGigacage):
(Gigacage::addDisableCallback):
(Gigacage::removeDisableCallback):
(Gigacage::caged):
(Gigacage::isCaged):
(Gigacage::tryAlignedMalloc):
(Gigacage::alignedFree):
(Gigacage::free):

File:
1 edited

Legend:

Unmodified
Added
Removed
  • trunk/Source/JavaScriptCore/ftl/FTLLowerDFGToB3.cpp

    r219981 r220118  
    8888#include <unordered_set>
    8989#include <wtf/Box.h>
     90#include <wtf/Gigacage.h>
    9091
    9192namespace JSC { namespace FTL {
     
    665666            break;
    666667        case GetButterfly:
     668        case GetButterflyWithoutCaging:
    667669            compileGetButterfly();
    668670            break;
     
    32323234    void compileGetButterfly()
    32333235    {
    3234         setStorage(m_out.loadPtr(lowCell(m_node->child1()), m_heaps.JSObject_butterfly));
     3236        LValue butterfly = m_out.loadPtr(lowCell(m_node->child1()), m_heaps.JSObject_butterfly);
     3237        if (m_node->op() != GetButterflyWithoutCaging)
     3238            butterfly = caged(butterfly);
     3239        setStorage(butterfly);
    32353240    }
    32363241
     
    32683273
    32693274        DFG_ASSERT(m_graph, m_node, isTypedView(m_node->arrayMode().typedArrayType()));
    3270         setStorage(m_out.loadPtr(cell, m_heaps.JSArrayBufferView_vector));
     3275        setStorage(caged(m_out.loadPtr(cell, m_heaps.JSArrayBufferView_vector)));
    32713276    }
    32723277   
     
    35103515                    m_out.load32NonNegative(base, m_heaps.DirectArguments_length)));
    35113516
     3517            // FIXME: I guess we need to cage DirectArguments?
     3518            // https://bugs.webkit.org/show_bug.cgi?id=174920
    35123519            TypedPointer address = m_out.baseIndex(
    35133520                m_heaps.DirectArguments_storage, base, m_out.zeroExtPtr(index));
     
    35413548            LValue arguments = m_out.loadPtr(table, m_heaps.ScopedArgumentsTable_arguments);
    35423549           
     3550            // FIXME: I guess we need to cage ScopedArguments?
     3551            // https://bugs.webkit.org/show_bug.cgi?id=174921
    35433552            TypedPointer address = m_out.baseIndex(
    35443553                m_heaps.scopedArgumentsTableArguments, arguments, m_out.zeroExtPtr(index));
     
    35493558                m_out.equal(scopeOffset, m_out.constInt32(ScopeOffset::invalidOffset)));
    35503559           
     3560            // FIXME: I guess we need to cage JSEnvironmentRecord?
     3561            // https://bugs.webkit.org/show_bug.cgi?id=174922
    35513562            address = m_out.baseIndex(
    35523563                m_heaps.JSEnvironmentRecord_variables, scope, m_out.zeroExtPtr(scopeOffset));
     
    35563567            m_out.appendTo(overflowCase, continuation);
    35573568           
     3569            // FIXME: I guess we need to cage overflow storage?
     3570            // https://bugs.webkit.org/show_bug.cgi?id=174923
    35583571            address = m_out.baseIndex(
    35593572                m_heaps.ScopedArguments_overflowStorage, base,
     
    53795392        m_out.appendTo(is8Bit, is16Bit);
    53805393           
     5394        // FIXME: Need to cage strings!
     5395        // https://bugs.webkit.org/show_bug.cgi?id=174924
    53815396        ValueFromBlock char8Bit = m_out.anchor(
    53825397            m_out.load8ZeroExt32(m_out.baseIndex(
     
    54805495        LBasicBlock lastNext = m_out.appendTo(is8Bit, is16Bit);
    54815496           
     5497        // FIXME: need to cage strings!
     5498        // https://bugs.webkit.org/show_bug.cgi?id=174924
    54825499        ValueFromBlock char8Bit = m_out.anchor(
    54835500            m_out.load8ZeroExt32(m_out.baseIndex(
     
    80768093        LValue unmaskedIndex = m_out.phi(Int32, indexStart);
    80778094        LValue index = m_out.bitAnd(mask, unmaskedIndex);
     8095        // FIXME: I think these buffers are caged?
     8096        // https://bugs.webkit.org/show_bug.cgi?id=174925
    80788097        LValue hashMapBucket = m_out.load64(m_out.baseIndex(m_heaps.properties.atAnyNumber(), buffer, m_out.zeroExt(index, Int64), ScaleEight));
    80798098        ValueFromBlock bucketResult = m_out.anchor(hashMapBucket);
     
    88518870        int32_t offsetOfFirstProperty = static_cast<int32_t>(offsetInButterfly(firstOutOfLineOffset)) * sizeof(EncodedJSValue);
    88528871        ValueFromBlock outOfLineResult = m_out.anchor(
    8853             m_out.load64(m_out.baseIndex(m_heaps.properties.atAnyNumber(), storage, realIndex, ScaleEight, offsetOfFirstProperty)));
     8872            m_out.load64(m_out.baseIndex(m_heaps.properties.atAnyNumber(), caged(storage), realIndex, ScaleEight, offsetOfFirstProperty)));
    88548873        m_out.jump(continuation);
    88558874
     
    1026910288        m_out.appendTo(loopBody, slowPath);
    1027010289
     10290        // FIXME: Strings needs to be caged.
     10291        // https://bugs.webkit.org/show_bug.cgi?id=174924
    1027110292        LValue byte = m_out.load8ZeroExt32(m_out.baseIndex(m_heaps.characters8, buffer, m_out.zeroExtPtr(index)));
    1027210293        LValue isInvalidAsciiRange = m_out.bitAnd(byte, m_out.constInt32(~0x7F));
     
    1159211613            m_out.appendTo(performStore, lastNext);
    1159311614        }
     11615    }
     11616   
     11617    LValue caged(LValue ptr)
     11618    {
     11619        if (vm().gigacageEnabled().isStillValid()) {
     11620            m_graph.watchpoints().addLazily(vm().gigacageEnabled());
     11621           
     11622            LValue basePtr = m_out.constIntPtr(g_gigacageBasePtr);
     11623            LValue mask = m_out.constIntPtr(GIGACAGE_MASK);
     11624           
     11625            // We don't have to worry about B3 messing up the bitAnd. Also, we want to get B3's excellent
     11626            // codegen for 2-operand andq on x86-64.
     11627            LValue masked = m_out.bitAnd(ptr, mask);
     11628           
     11629            // But B3 will currently mess up the code generation of this add. Basically, any offset from what we
     11630            // compute here will get reassociated and folded with g_gigacageBasePtr. There's a world in which
     11631            // moveConstants() observes that it needs to reassociate in order to hoist the big constants. But
     11632            // it's much easier to just block B3's badness here. That's what we do for now.
     11633            PatchpointValue* patchpoint = m_out.patchpoint(pointerType());
     11634            patchpoint->appendSomeRegister(basePtr);
     11635            patchpoint->appendSomeRegister(masked);
     11636            patchpoint->setGenerator(
     11637                [] (CCallHelpers& jit, const StackmapGenerationParams& params) {
     11638                    jit.addPtr(params[1].gpr(), params[2].gpr(), params[0].gpr());
     11639                });
     11640            patchpoint->effects = Effects::none();
     11641            return patchpoint;
     11642        }
     11643       
     11644        return ptr;
    1159411645    }
    1159511646   
Note: See TracChangeset for help on using the changeset viewer.