Skip to content

Commit 92497fa

Browse files
author
Bo Cupp
committed
[MERGE #3846 @BoCupp] Add support for a Recycler-managed "Host Heap" 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.
2 parents 3bd962f + 612cd34 commit 92497fa

37 files changed

+1189
-106
lines changed

bin/GCStress/GCStress.cpp

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -214,16 +214,23 @@ void BuildObjectCreationTable()
214214
objectCreationTable.AddWeightedEntry(&ScannedObject<1, 50>::New, 10000);
215215
objectCreationTable.AddWeightedEntry(&BarrierObject<1, 50>::New, 2000);
216216
objectCreationTable.AddWeightedEntry(&TrackedObject<1, 50>::New, 2000);
217+
#ifdef RECYCLER_VISITED_HOST
218+
objectCreationTable.AddWeightedEntry(&RecyclerVisitedObject<1, 50>::New, 2000);
219+
#endif
217220

218221
objectCreationTable.AddWeightedEntry(&LeafObject<51, 1000>::New, 10);
219222
objectCreationTable.AddWeightedEntry(&ScannedObject<51, 1000>::New, 100);
220223
objectCreationTable.AddWeightedEntry(&BarrierObject<51, 1000>::New, 20);
221224
objectCreationTable.AddWeightedEntry(&TrackedObject<51, 1000>::New, 20);
225+
#ifdef RECYCLER_VISITED_HOST
226+
objectCreationTable.AddWeightedEntry(&RecyclerVisitedObject<51, 1000>::New, 40);
227+
#endif
222228

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

229236
void BuildOperationTable()

bin/GCStress/RecyclerTestObject.cpp

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -10,6 +10,7 @@ size_t RecyclerTestObject::walkObjectCount = 0;
1010
size_t RecyclerTestObject::walkScannedByteCount = 0;
1111
size_t RecyclerTestObject::walkBarrierByteCount = 0;
1212
size_t RecyclerTestObject::walkTrackedByteCount = 0;
13+
size_t RecyclerTestObject::walkRecyclerVisitedByteCount = 0;
1314
size_t RecyclerTestObject::walkLeafByteCount = 0;
1415
size_t RecyclerTestObject::currentWalkDepth = 0;
1516
size_t RecyclerTestObject::maxWalkDepth = 0;

bin/GCStress/RecyclerTestObject.h

Lines changed: 147 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -4,7 +4,9 @@
44
//-------------------------------------------------------------------------------------------------------
55
#include "stdafx.h"
66

7-
class RecyclerTestObject : public FinalizableObject
7+
#include "Core/RecyclerHeapMarkingContext.h"
8+
9+
class RecyclerTestObject : public IRecyclerVisitedObject
810
{
911
protected:
1012
RecyclerTestObject()
@@ -13,10 +15,16 @@ class RecyclerTestObject : public FinalizableObject
1315
}
1416

1517
public:
16-
// FinalizableObject implementation
18+
// IRecyclerVisitedObject implementation. We don't use FinalizableObject here as
19+
// RecyclerVisitedObjects need to have Trace called on them, which is not allowed for
20+
// FinalizableObject.
1721
virtual void Finalize(bool isShutdown) override { VerifyCondition(false); };
1822
virtual void Dispose(bool isShutdown) override { VerifyCondition(false); };
19-
virtual void Mark(Recycler * recycler) override { VerifyCondition(false); };
23+
virtual void OnMark() override {}
24+
virtual void Mark(RecyclerHeapHandle recycler) override { Mark(static_cast<Recycler*>(recycler)); };
25+
virtual void Trace(IRecyclerHeapMarkingContext* markContext) override { VerifyCondition(false); };
26+
27+
virtual void Mark(Recycler * recycler) { VerifyCondition(false); };
2028

2129
public:
2230
static void BeginWalk()
@@ -27,6 +35,7 @@ class RecyclerTestObject : public FinalizableObject
2735
walkScannedByteCount = 0;
2836
walkBarrierByteCount = 0;
2937
walkTrackedByteCount = 0;
38+
walkRecyclerVisitedByteCount = 0;
3039
walkLeafByteCount = 0;
3140
maxWalkDepth = 0;
3241

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

6877
wprintf(_u("Full heap walk finished\n"));
69-
wprintf(_u("Object Count: %12llu\n"), (unsigned long long) walkObjectCount);
70-
wprintf(_u("Scanned Bytes: %12llu\n"), (unsigned long long) walkScannedByteCount);
71-
wprintf(_u("Barrier Bytes: %12llu\n"), (unsigned long long) walkBarrierByteCount);
72-
wprintf(_u("Tracked Bytes: %12llu\n"), (unsigned long long) walkTrackedByteCount);
73-
wprintf(_u("Leaf Bytes: %12llu\n"), (unsigned long long) walkLeafByteCount);
74-
wprintf(_u("Total Bytes: %12llu\n"), (unsigned long long) (walkScannedByteCount + walkBarrierByteCount + walkTrackedByteCount + walkLeafByteCount));
75-
wprintf(_u("Max Depth: %12llu\n"), (unsigned long long) maxWalkDepth);
78+
wprintf(_u("Object Count: %12llu\n"), (unsigned long long) walkObjectCount);
79+
wprintf(_u("Scanned Bytes: %12llu\n"), (unsigned long long) walkScannedByteCount);
80+
wprintf(_u("Barrier Bytes: %12llu\n"), (unsigned long long) walkBarrierByteCount);
81+
wprintf(_u("Tracked Bytes: %12llu\n"), (unsigned long long) walkTrackedByteCount);
82+
wprintf(_u("RecyclerVisited Bytes: %12llu\n"), (unsigned long long) walkRecyclerVisitedByteCount);
83+
wprintf(_u("Leaf Bytes: %12llu\n"), (unsigned long long) walkLeafByteCount);
84+
wprintf(_u("Total Bytes: %12llu\n"), (unsigned long long) (walkScannedByteCount + walkBarrierByteCount + walkTrackedByteCount + walkLeafByteCount + walkRecyclerVisitedByteCount));
85+
wprintf(_u("Max Depth: %12llu\n"), (unsigned long long) maxWalkDepth);
7686
}
7787

7888
// Virtual methods
@@ -100,6 +110,7 @@ class RecyclerTestObject : public FinalizableObject
100110
static size_t walkLeafByteCount;
101111
static size_t walkBarrierByteCount;
102112
static size_t walkTrackedByteCount;
113+
static size_t walkRecyclerVisitedByteCount;
103114
static size_t currentWalkDepth;
104115
static size_t maxWalkDepth;
105116

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

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

314+
#ifdef RECYCLER_VISITED_HOST
315+
316+
template <unsigned int minCount, unsigned int maxCount>
317+
class RecyclerVisitedObject : public RecyclerTestObject
318+
{
319+
public:
320+
static RecyclerTestObject * New()
321+
{
322+
// Determine a random amount of RecyclerTestObject* references to influence the size of this object.
323+
const unsigned int count = minCount + GetRandomInteger(maxCount - minCount + 1);
324+
325+
void* mem = nullptr;
326+
const size_t size = sizeof(RecyclerVisitedObject) + (sizeof(RecyclerTestObject*) * count);
327+
328+
// Randomly select the type of object to create
329+
AllocationType allocType = static_cast<AllocationType>(GetRandomInteger(static_cast<unsigned int>(AllocationType::Count)));
330+
switch (allocType)
331+
{
332+
case AllocationType::TraceAndFinalized:
333+
mem = RecyclerAllocVisitedHostTracedAndFinalizedZero(recyclerInstance, size);
334+
break;
335+
case AllocationType::TraceOnly:
336+
mem = RecyclerAllocVisitedHostTracedZero(recyclerInstance, size);
337+
break;
338+
case AllocationType::FinalizeLeaf:
339+
mem = RecyclerAllocVisitedHostFinalizedZero(recyclerInstance, size);
340+
break;
341+
default:
342+
Assert(allocType == AllocationType::Leaf);
343+
mem = RecyclerAllocLeafZero(recyclerInstance, size);
344+
}
345+
346+
// Construct the v-table, allocType, and count information for the new object.
347+
RecyclerVisitedObject* obj = new (mem) RecyclerVisitedObject(allocType, count);
348+
return obj;
349+
}
350+
351+
virtual bool TryGetRandomLocation(Location * location) override
352+
{
353+
// Leaf types should not return a location
354+
if (type == AllocationType::Leaf || type == AllocationType::FinalizeLeaf)
355+
{
356+
return false;
357+
}
298358

359+
// Get a random slot and construct a Location for it
360+
// Make this a Tagged location so that we won't inadvertently keep objects alive
361+
// in the case where this object gets put on the wrong mark stack.
362+
*location = Location::Tagged(&references[GetRandomInteger(count)]);
363+
364+
return true;
365+
}
366+
367+
virtual void Trace(IRecyclerHeapMarkingContext* markContext) override
368+
{
369+
VerifyCondition(type == AllocationType::TraceAndFinalized || type == AllocationType::TraceOnly);
370+
// Note that the pointers in the references arrary are technically tagged. However, this is ok
371+
// as the Mark that we're performing is an interior mark, which gets us to the right object(s).
372+
markContext->MarkObjects(reinterpret_cast<void**>(&references[0]), count, this);
373+
}
374+
375+
virtual void Finalize(bool isShutdown) override
376+
{
377+
// Only types that request finalization should have Finalize called
378+
VerifyCondition(IsFinalizable());
379+
}
380+
virtual void Dispose(bool isShutdown) override
381+
{
382+
// Only types that request finalization should have Finalize called
383+
VerifyCondition(IsFinalizable());
384+
VerifyCondition(unmanagedResource != nullptr);
385+
BOOL success = ::HeapFree(GetProcessHeap(), 0, unmanagedResource);
386+
VerifyCondition(success != FALSE);
387+
unmanagedResource = nullptr;
388+
}
389+
390+
391+
protected:
392+
virtual void DoWalkObject() override
393+
{
394+
walkRecyclerVisitedByteCount += sizeof(RecyclerVisitedObject) + count * sizeof(RecyclerTestObject *);
395+
396+
for (unsigned int i = 0; i < count; i++)
397+
{
398+
RecyclerTestObject::WalkReference(Location::Untag(references[i]));
399+
}
400+
}
401+
402+
private:
403+
enum class AllocationType : unsigned int
404+
{
405+
TraceAndFinalized = 0,
406+
TraceOnly,
407+
FinalizeLeaf,
408+
Leaf,
409+
Count,
410+
};
411+
412+
bool IsFinalizable() const { return type == AllocationType::TraceAndFinalized || type == AllocationType::FinalizeLeaf; }
413+
RecyclerVisitedObject(AllocationType allocType, unsigned int count) :
414+
count(count),
415+
type(allocType)
416+
{
417+
for (unsigned int i = 0; i < count; i++)
418+
{
419+
references[i] = nullptr;
420+
}
421+
if (IsFinalizable())
422+
{
423+
unmanagedResource = ::HeapAlloc(GetProcessHeap(), HEAP_ZERO_MEMORY, GetRandomInteger(1024));
424+
VerifyCondition(unmanagedResource != nullptr);
425+
}
426+
}
427+
428+
429+
Field(AllocationType) type;
430+
Field(void*) unmanagedResource;
431+
Field(unsigned int) count;
432+
FieldNoBarrier(RecyclerTestObject *) references[0]; // SWB-TODO: is this correct? (copied from TrackedObject)
433+
};
434+
#endif

bin/ch/Helpers.cpp

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -191,6 +191,9 @@ HRESULT Helpers::LoadScriptFromFile(LPCSTR filename, LPCSTR& contents, UINT* len
191191
// wrongly classified as ANSI
192192
//
193193
{
194+
#pragma warning(push)
195+
// suppressing prefast warning that "readable size is bufferLength bytes but 2 may be read" as bufferLength is clearly > 2 in the code that follows
196+
#pragma warning(disable:6385)
194197
C_ASSERT(sizeof(WCHAR) == 2);
195198
if (bufferLength > 2)
196199
{
@@ -211,6 +214,7 @@ HRESULT Helpers::LoadScriptFromFile(LPCSTR filename, LPCSTR& contents, UINT* len
211214
#pragma prefast(pop)
212215
}
213216
}
217+
#pragma warning(pop)
214218
}
215219

216220
contents = reinterpret_cast<LPCSTR>(pRawBytes);

lib/Common/CommonDefines.h

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -239,6 +239,10 @@
239239
#error "Background page zeroing can't be turned on if freeing pages in the background is disabled"
240240
#endif
241241

242+
#ifdef _WIN32
243+
#define RECYCLER_VISITED_HOST
244+
#endif
245+
242246
// JIT features
243247

244248
#if DISABLE_JIT

lib/Common/CommonMinMemory.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -40,5 +40,6 @@ class FinalizableObject;
4040
#include "Memory/RecyclerSweep.h"
4141
#include "Memory/RecyclerHeuristic.h"
4242
#include "Memory/MarkContext.h"
43+
#include "Memory/MarkContextWrapper.h"
4344
#include "Memory/RecyclerWatsonTelemetry.h"
4445
#include "Memory/Recycler.h"

lib/Common/Core/FinalizableObject.h

Lines changed: 13 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -3,24 +3,22 @@
33
// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
44
//-------------------------------------------------------------------------------------------------------
55
#pragma once
6-
class FinalizableObject
6+
#include "RecyclerVisitedObject.h"
7+
8+
class FinalizableObject : public IRecyclerVisitedObject
79
{
810
public:
9-
// Called right after finish marking and this object is determined to be dead.
10-
// Should contain only simple clean up code.
11-
// Can't run another script
12-
// Can't cause a re-entrant collection
13-
14-
virtual void Finalize(bool isShutdown) = 0;
15-
16-
// Call after sweeping is done.
17-
// Can call other script or cause another collection.
11+
virtual void OnMark() {}
1812

19-
virtual void Dispose(bool isShutdown) = 0;
13+
void Mark(RecyclerHeapHandle recycler) final
14+
{
15+
Mark(static_cast<Recycler*>(recycler));
16+
}
2017

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

24-
// Special behavior on certain GC's
25-
virtual void OnMark() {}
23+
virtual void Mark(Recycler* recycler) = 0;
2624
};
Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,11 @@
1+
//-------------------------------------------------------------------------------------------------------
2+
// Copyright (C) Microsoft. All rights reserved.
3+
// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
4+
//-------------------------------------------------------------------------------------------------------
5+
#pragma once
6+
7+
interface IRecyclerHeapMarkingContext
8+
{
9+
virtual void MarkObjects(void** objects, size_t count, void* parent) = 0;
10+
};
11+
Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,31 @@
1+
//-------------------------------------------------------------------------------------------------------
2+
// Copyright (C) Microsoft. All rights reserved.
3+
// Licensed under the MIT license. See LICENSE.txt file in the project root for full license information.
4+
//-------------------------------------------------------------------------------------------------------
5+
#pragma once
6+
7+
interface IRecyclerHeapMarkingContext;
8+
typedef void* RecyclerHeapHandle;
9+
10+
interface IRecyclerVisitedObject
11+
{
12+
// Called right after finish marking and this object is determined to be dead.
13+
// Should contain only simple clean up code.
14+
// Can't run another script
15+
// Can't cause a re-entrant collection
16+
virtual void Finalize(bool isShutdown) = 0;
17+
18+
// Call after sweeping is done.
19+
// Can call other script or cause another collection.
20+
virtual void Dispose(bool isShutdown) = 0;
21+
22+
// Used only by TrackableObjects (created with TrackedBit on by RecyclerNew*Tracked)
23+
virtual void Mark(RecyclerHeapHandle recycler) = 0;
24+
25+
// Special behavior on certain GC's
26+
virtual void OnMark() = 0;
27+
28+
// Used only by RecyclerVisitedHost objects (created with RecyclerAllocVistedHost_Traced*)
29+
virtual void Trace(IRecyclerHeapMarkingContext* markingContext) = 0;
30+
};
31+

lib/Common/Exceptions/ReportError.h

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,7 @@ enum ErrorReason
2727
Fatal_JsReentrancy_Error = 19,
2828
Fatal_TTDAbort = 20,
2929
Fatal_Failed_API_Result = 21,
30+
Fatal_RecyclerVisitedHost_LargeHeapBlock = 22,
3031
};
3132

3233
extern "C" void ReportFatalException(

0 commit comments

Comments
 (0)