Skip to content

Commit

Permalink
[MERGE #3846 @BoCupp] Add support for a Recycler-managed "Host Heap" …
Browse files Browse the repository at this point in the history
…to facilitate tracing of objects which relate to scriptable types

Merge pull request #3846 from BoCupp:build/pcupp/recycler_visited_host_heap

This change exposes functions that will allocate memory inside of Chakra's GC. This memory can be precisely traced or leaf, and either finalizable or not. Traced objects will have IRecyclerVisitedObject::Trace called on them during ProcessMark if they were added to a new type of mark stack during Mark. Trace implementations will use a new API to tell the marking context to add a set of pointers to the appropriate mark stack.

Finalizable objects will have Dispose called when the the object is no longer reachable, in order to perform unmanaged resource cleanup.

These objects (except for non-finalizable leaf objects) will all live inside a new heap block type. Currently there is only support for small and medium blocks.

GCStress has been updated to allocate objects of the various new types. Finalizable objects hold onto some unmanaged heap resources which would point out (via OOM) if objects are not correctly being cleaned up.

More details:
- Add support for small and medium RecyclerVisitedHostHeapBlockTypes
  - For now, add failfast for RecyclerVisitedObjects that are placed in the LargeHeapBlock
- Publish IRecyclerVisitedObject interface which adds new method (compared to FinalizableObject) for precise tracing support
  - The old FinalizableObject is now a specialization of RecyclerVisitedObject and is used internally by Chakra
  - For host allocations, the host is expected to provide an appropriate implementation of IRecyclerVisitedObject
- Publish IRecyclerHeapMarkingContext (used as the argument to IRecyclerVisitedObject::Trace)
  - The host must implement precisely tracing the (non-leaf) memory it allocates by calling IRecyclerHeapMarkingContext::MarkObjects from inside its implementation of IRecyclerVisitedObject::Trace
  - It is implemented by a stack wrapper on MarkContext that captures the parallel and interior template parameters from MarkContext::ProcessMark. We always use false for doSpecialMark, since all calls to Mark that originate from ProcessMark would pass false (the only place we pass true is when scanning the stack)
- Create Chakra exports for allocation and rootaddref/release functions
- Introduce a new RecyclerHeap lib in chakra that defines the exports in the cpp and declares them in the h
- Add support for RecyclerVisitedObjects in GCStress

Note: this change is derived from a rebase of work done by Daniel Libby - all credit to him for the work to enable this concept.
  • Loading branch information
Bo Cupp committed Oct 13, 2017
2 parents 3bd962f + 612cd34 commit 92497fa
Show file tree
Hide file tree
Showing 37 changed files with 1,189 additions and 106 deletions.
7 changes: 7 additions & 0 deletions bin/GCStress/GCStress.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -214,16 +214,23 @@ void BuildObjectCreationTable()
objectCreationTable.AddWeightedEntry(&ScannedObject<1, 50>::New, 10000);
objectCreationTable.AddWeightedEntry(&BarrierObject<1, 50>::New, 2000);
objectCreationTable.AddWeightedEntry(&TrackedObject<1, 50>::New, 2000);
#ifdef RECYCLER_VISITED_HOST
objectCreationTable.AddWeightedEntry(&RecyclerVisitedObject<1, 50>::New, 2000);
#endif

objectCreationTable.AddWeightedEntry(&LeafObject<51, 1000>::New, 10);
objectCreationTable.AddWeightedEntry(&ScannedObject<51, 1000>::New, 100);
objectCreationTable.AddWeightedEntry(&BarrierObject<51, 1000>::New, 20);
objectCreationTable.AddWeightedEntry(&TrackedObject<51, 1000>::New, 20);
#ifdef RECYCLER_VISITED_HOST
objectCreationTable.AddWeightedEntry(&RecyclerVisitedObject<51, 1000>::New, 40);
#endif

objectCreationTable.AddWeightedEntry(&LeafObject<1001, 50000>::New, 1);
objectCreationTable.AddWeightedEntry(&ScannedObject<1001, 50000>::New, 10);
objectCreationTable.AddWeightedEntry(&BarrierObject<1001, 50000>::New, 2);
// objectCreationTable.AddWeightedEntry(&TrackedObject<1001, 50000>::New, 2); // Large tracked objects are not supported
// objectCreationTable.AddWeightedEntry(&RecyclerVisitedObject<1001, 50000>::New, 2); // Large recycler visited objects are not supported
}

void BuildOperationTable()
Expand Down
1 change: 1 addition & 0 deletions bin/GCStress/RecyclerTestObject.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ size_t RecyclerTestObject::walkObjectCount = 0;
size_t RecyclerTestObject::walkScannedByteCount = 0;
size_t RecyclerTestObject::walkBarrierByteCount = 0;
size_t RecyclerTestObject::walkTrackedByteCount = 0;
size_t RecyclerTestObject::walkRecyclerVisitedByteCount = 0;
size_t RecyclerTestObject::walkLeafByteCount = 0;
size_t RecyclerTestObject::currentWalkDepth = 0;
size_t RecyclerTestObject::maxWalkDepth = 0;
Expand Down
158 changes: 147 additions & 11 deletions bin/GCStress/RecyclerTestObject.h
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,9 @@
//-------------------------------------------------------------------------------------------------------
#include "stdafx.h"

class RecyclerTestObject : public FinalizableObject
#include "Core/RecyclerHeapMarkingContext.h"

class RecyclerTestObject : public IRecyclerVisitedObject
{
protected:
RecyclerTestObject()
Expand All @@ -13,10 +15,16 @@ class RecyclerTestObject : public FinalizableObject
}

public:
// FinalizableObject implementation
// IRecyclerVisitedObject implementation. We don't use FinalizableObject here as
// RecyclerVisitedObjects need to have Trace called on them, which is not allowed for
// FinalizableObject.
virtual void Finalize(bool isShutdown) override { VerifyCondition(false); };
virtual void Dispose(bool isShutdown) override { VerifyCondition(false); };
virtual void Mark(Recycler * recycler) override { VerifyCondition(false); };
virtual void OnMark() override {}
virtual void Mark(RecyclerHeapHandle recycler) override { Mark(static_cast<Recycler*>(recycler)); };
virtual void Trace(IRecyclerHeapMarkingContext* markContext) override { VerifyCondition(false); };

virtual void Mark(Recycler * recycler) { VerifyCondition(false); };

public:
static void BeginWalk()
Expand All @@ -27,6 +35,7 @@ class RecyclerTestObject : public FinalizableObject
walkScannedByteCount = 0;
walkBarrierByteCount = 0;
walkTrackedByteCount = 0;
walkRecyclerVisitedByteCount = 0;
walkLeafByteCount = 0;
maxWalkDepth = 0;

Expand Down Expand Up @@ -66,13 +75,14 @@ class RecyclerTestObject : public FinalizableObject
VerifyCondition(currentWalkDepth == 0);

wprintf(_u("Full heap walk finished\n"));
wprintf(_u("Object Count: %12llu\n"), (unsigned long long) walkObjectCount);
wprintf(_u("Scanned Bytes: %12llu\n"), (unsigned long long) walkScannedByteCount);
wprintf(_u("Barrier Bytes: %12llu\n"), (unsigned long long) walkBarrierByteCount);
wprintf(_u("Tracked Bytes: %12llu\n"), (unsigned long long) walkTrackedByteCount);
wprintf(_u("Leaf Bytes: %12llu\n"), (unsigned long long) walkLeafByteCount);
wprintf(_u("Total Bytes: %12llu\n"), (unsigned long long) (walkScannedByteCount + walkBarrierByteCount + walkTrackedByteCount + walkLeafByteCount));
wprintf(_u("Max Depth: %12llu\n"), (unsigned long long) maxWalkDepth);
wprintf(_u("Object Count: %12llu\n"), (unsigned long long) walkObjectCount);
wprintf(_u("Scanned Bytes: %12llu\n"), (unsigned long long) walkScannedByteCount);
wprintf(_u("Barrier Bytes: %12llu\n"), (unsigned long long) walkBarrierByteCount);
wprintf(_u("Tracked Bytes: %12llu\n"), (unsigned long long) walkTrackedByteCount);
wprintf(_u("RecyclerVisited Bytes: %12llu\n"), (unsigned long long) walkRecyclerVisitedByteCount);
wprintf(_u("Leaf Bytes: %12llu\n"), (unsigned long long) walkLeafByteCount);
wprintf(_u("Total Bytes: %12llu\n"), (unsigned long long) (walkScannedByteCount + walkBarrierByteCount + walkTrackedByteCount + walkLeafByteCount + walkRecyclerVisitedByteCount));
wprintf(_u("Max Depth: %12llu\n"), (unsigned long long) maxWalkDepth);
}

// Virtual methods
Expand Down Expand Up @@ -100,6 +110,7 @@ class RecyclerTestObject : public FinalizableObject
static size_t walkLeafByteCount;
static size_t walkBarrierByteCount;
static size_t walkTrackedByteCount;
static size_t walkRecyclerVisitedByteCount;
static size_t currentWalkDepth;
static size_t maxWalkDepth;

Expand Down Expand Up @@ -232,8 +243,13 @@ class BarrierObject : public RecyclerTestObject
FieldNoBarrier(RecyclerTestObject *) references[0]; // SWB-TODO: is this correct?
};

// TrackedObject must be a FinalizableObject (in order to be 'new'ed with RecyclerNewTrackedLeafPlusZ)
// but it also must be a RecyclerTestObject to participate in GCStress. It must inherit from RecyclerTestObject
// first so that the algined pointer is returned from New.
// Fortunately, the v-tables for RecyclerTestObject and FinalizableObject line up, so the
// IRecyclerVisitedObject/FinalizableObject calls end up in the right place.
template <unsigned int minCount, unsigned int maxCount>
class TrackedObject : public RecyclerTestObject
class TrackedObject : public RecyclerTestObject, public FinalizableObject
{
private:
TrackedObject(unsigned int count) :
Expand Down Expand Up @@ -295,4 +311,124 @@ class TrackedObject : public RecyclerTestObject
FieldNoBarrier(RecyclerTestObject *) references[0]; // SWB-TODO: is this correct?
};

#ifdef RECYCLER_VISITED_HOST

template <unsigned int minCount, unsigned int maxCount>
class RecyclerVisitedObject : public RecyclerTestObject
{
public:
static RecyclerTestObject * New()
{
// Determine a random amount of RecyclerTestObject* references to influence the size of this object.
const unsigned int count = minCount + GetRandomInteger(maxCount - minCount + 1);

void* mem = nullptr;
const size_t size = sizeof(RecyclerVisitedObject) + (sizeof(RecyclerTestObject*) * count);

// Randomly select the type of object to create
AllocationType allocType = static_cast<AllocationType>(GetRandomInteger(static_cast<unsigned int>(AllocationType::Count)));
switch (allocType)
{
case AllocationType::TraceAndFinalized:
mem = RecyclerAllocVisitedHostTracedAndFinalizedZero(recyclerInstance, size);
break;
case AllocationType::TraceOnly:
mem = RecyclerAllocVisitedHostTracedZero(recyclerInstance, size);
break;
case AllocationType::FinalizeLeaf:
mem = RecyclerAllocVisitedHostFinalizedZero(recyclerInstance, size);
break;
default:
Assert(allocType == AllocationType::Leaf);
mem = RecyclerAllocLeafZero(recyclerInstance, size);
}

// Construct the v-table, allocType, and count information for the new object.
RecyclerVisitedObject* obj = new (mem) RecyclerVisitedObject(allocType, count);
return obj;
}

virtual bool TryGetRandomLocation(Location * location) override
{
// Leaf types should not return a location
if (type == AllocationType::Leaf || type == AllocationType::FinalizeLeaf)
{
return false;
}

// Get a random slot and construct a Location for it
// Make this a Tagged location so that we won't inadvertently keep objects alive
// in the case where this object gets put on the wrong mark stack.
*location = Location::Tagged(&references[GetRandomInteger(count)]);

return true;
}

virtual void Trace(IRecyclerHeapMarkingContext* markContext) override
{
VerifyCondition(type == AllocationType::TraceAndFinalized || type == AllocationType::TraceOnly);
// Note that the pointers in the references arrary are technically tagged. However, this is ok
// as the Mark that we're performing is an interior mark, which gets us to the right object(s).
markContext->MarkObjects(reinterpret_cast<void**>(&references[0]), count, this);
}

virtual void Finalize(bool isShutdown) override
{
// Only types that request finalization should have Finalize called
VerifyCondition(IsFinalizable());
}
virtual void Dispose(bool isShutdown) override
{
// Only types that request finalization should have Finalize called
VerifyCondition(IsFinalizable());
VerifyCondition(unmanagedResource != nullptr);
BOOL success = ::HeapFree(GetProcessHeap(), 0, unmanagedResource);
VerifyCondition(success != FALSE);
unmanagedResource = nullptr;
}


protected:
virtual void DoWalkObject() override
{
walkRecyclerVisitedByteCount += sizeof(RecyclerVisitedObject) + count * sizeof(RecyclerTestObject *);

for (unsigned int i = 0; i < count; i++)
{
RecyclerTestObject::WalkReference(Location::Untag(references[i]));
}
}

private:
enum class AllocationType : unsigned int
{
TraceAndFinalized = 0,
TraceOnly,
FinalizeLeaf,
Leaf,
Count,
};

bool IsFinalizable() const { return type == AllocationType::TraceAndFinalized || type == AllocationType::FinalizeLeaf; }
RecyclerVisitedObject(AllocationType allocType, unsigned int count) :
count(count),
type(allocType)
{
for (unsigned int i = 0; i < count; i++)
{
references[i] = nullptr;
}
if (IsFinalizable())
{
unmanagedResource = ::HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, GetRandomInteger(1024));
VerifyCondition(unmanagedResource != nullptr);
}
}


Field(AllocationType) type;
Field(void*) unmanagedResource;
Field(unsigned int) count;
FieldNoBarrier(RecyclerTestObject *) references[0]; // SWB-TODO: is this correct? (copied from TrackedObject)
};
#endif
4 changes: 4 additions & 0 deletions bin/ch/Helpers.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,9 @@ HRESULT Helpers::LoadScriptFromFile(LPCSTR filename, LPCSTR& contents, UINT* len
// wrongly classified as ANSI
//
{
#pragma warning(push)
// suppressing prefast warning that "readable size is bufferLength bytes but 2 may be read" as bufferLength is clearly > 2 in the code that follows
#pragma warning(disable:6385)
C_ASSERT(sizeof(WCHAR) == 2);
if (bufferLength > 2)
{
Expand All @@ -211,6 +214,7 @@ HRESULT Helpers::LoadScriptFromFile(LPCSTR filename, LPCSTR& contents, UINT* len
#pragma prefast(pop)
}
}
#pragma warning(pop)
}

contents = reinterpret_cast<LPCSTR>(pRawBytes);
Expand Down
4 changes: 4 additions & 0 deletions lib/Common/CommonDefines.h
Original file line number Diff line number Diff line change
Expand Up @@ -239,6 +239,10 @@
#error "Background page zeroing can't be turned on if freeing pages in the background is disabled"
#endif

#ifdef _WIN32
#define RECYCLER_VISITED_HOST
#endif

// JIT features

#if DISABLE_JIT
Expand Down
1 change: 1 addition & 0 deletions lib/Common/CommonMinMemory.h
Original file line number Diff line number Diff line change
Expand Up @@ -40,5 +40,6 @@ class FinalizableObject;
#include "Memory/RecyclerSweep.h"
#include "Memory/RecyclerHeuristic.h"
#include "Memory/MarkContext.h"
#include "Memory/MarkContextWrapper.h"
#include "Memory/RecyclerWatsonTelemetry.h"
#include "Memory/Recycler.h"
28 changes: 13 additions & 15 deletions lib/Common/Core/FinalizableObject.h
Original file line number Diff line number Diff line change
Expand Up @@ -3,24 +3,22 @@
// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
//-------------------------------------------------------------------------------------------------------
#pragma once
class FinalizableObject
#include "RecyclerVisitedObject.h"

class FinalizableObject : public IRecyclerVisitedObject
{
public:
// Called right after finish marking and this object is determined to be dead.
// Should contain only simple clean up code.
// Can't run another script
// Can't cause a re-entrant collection

virtual void Finalize(bool isShutdown) = 0;

// Call after sweeping is done.
// Can call other script or cause another collection.
virtual void OnMark() {}

virtual void Dispose(bool isShutdown) = 0;
void Mark(RecyclerHeapHandle recycler) final
{
Mark(static_cast<Recycler*>(recycler));
}

// Used only by TrackableObjects (created with TrackedBit on by RecyclerNew*Tracked)
virtual void Mark(Recycler * recycler) = 0;
void Trace(IRecyclerHeapMarkingContext* markingContext) final
{
AssertMsg(false, "Trace called on object that isn't implemented by the host");
}

// Special behavior on certain GC's
virtual void OnMark() {}
virtual void Mark(Recycler* recycler) = 0;
};
11 changes: 11 additions & 0 deletions lib/Common/Core/RecyclerHeapMarkingContext.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,11 @@
//-------------------------------------------------------------------------------------------------------
// Copyright (C) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
//-------------------------------------------------------------------------------------------------------
#pragma once

interface IRecyclerHeapMarkingContext
{
virtual void MarkObjects(void** objects, size_t count, void* parent) = 0;
};

31 changes: 31 additions & 0 deletions lib/Common/Core/RecyclerVisitedObject.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
//-------------------------------------------------------------------------------------------------------
// Copyright (C) Microsoft. All rights reserved.
// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
//-------------------------------------------------------------------------------------------------------
#pragma once

interface IRecyclerHeapMarkingContext;
typedef void* RecyclerHeapHandle;

interface IRecyclerVisitedObject
{
// Called right after finish marking and this object is determined to be dead.
// Should contain only simple clean up code.
// Can't run another script
// Can't cause a re-entrant collection
virtual void Finalize(bool isShutdown) = 0;

// Call after sweeping is done.
// Can call other script or cause another collection.
virtual void Dispose(bool isShutdown) = 0;

// Used only by TrackableObjects (created with TrackedBit on by RecyclerNew*Tracked)
virtual void Mark(RecyclerHeapHandle recycler) = 0;

// Special behavior on certain GC's
virtual void OnMark() = 0;

// Used only by RecyclerVisitedHost objects (created with RecyclerAllocVistedHost_Traced*)
virtual void Trace(IRecyclerHeapMarkingContext* markingContext) = 0;
};

1 change: 1 addition & 0 deletions lib/Common/Exceptions/ReportError.h
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,7 @@ enum ErrorReason
Fatal_JsReentrancy_Error = 19,
Fatal_TTDAbort = 20,
Fatal_Failed_API_Result = 21,
Fatal_RecyclerVisitedHost_LargeHeapBlock = 22,
};

extern "C" void ReportFatalException(
Expand Down
10 changes: 10 additions & 0 deletions lib/Common/Memory/HeapBlock.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,16 @@ HeapBlock::AsFinalizableBlock()
return static_cast<SmallFinalizableHeapBlockT<TBlockAttributes> *>(this);
}

#ifdef RECYCLER_VISITED_HOST
template <typename TBlockAttributes>
SmallRecyclerVisitedHostHeapBlockT<TBlockAttributes> *
HeapBlock::AsRecyclerVisitedHostBlock()
{
Assert(IsRecyclerVisitedHostBlock());
return static_cast<SmallRecyclerVisitedHostHeapBlockT<TBlockAttributes> *>(this);
}
#endif

#ifdef RECYCLER_WRITE_BARRIER
template <typename TBlockAttributes>
SmallNormalWithBarrierHeapBlockT<TBlockAttributes> *
Expand Down
Loading

0 comments on commit 92497fa

Please sign in to comment.