Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -66,7 +66,7 @@ file(GLOB PRIMITIVESCORE_SRC ${PRIMITIVESCORE_DIR}/*.cpp)
file(GLOB UNITTEST_SRC ${UNITTEST_DIR}/*.cpp)

option(USE_TAGGING "Enable immediate integers using tagging" FALSE)
set(GC_TYPE "COPYING" CACHE STRING "Select the type of GC to be used: COPYING, MARK_SWEEP, GENERATIONAL")
set(GC_TYPE "COPYING" CACHE STRING "Select the type of GC to be used: COPYING, MARK_SWEEP, GENERATIONAL, DEBUG_MARK_SWEEP")

option(CACHE_INTEGER "Enable caching of boxed integers" FALSE)
set(INT_CACHE_MIN_VALUE -5 CACHE STRING "Lower bound of cached integers")
Expand Down
76 changes: 76 additions & 0 deletions src/memory/DebugMarkSweepCollector.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
#include "DebugMarkSweepCollector.h"

#include <cstddef>
#include <vector>

#include "../memory/Heap.h"
#include "../misc/debug.h"
#include "../vm/Universe.h"
#include "../vmobjects/AbstractObject.h"
#include "../vmobjects/IntegerBox.h"
#include "../vmobjects/ObjectFormats.h"
#include "../vmobjects/VMFrame.h"
#include "DebugMarkSweepHeap.h"

#define GC_MARKED 3456

void DebugMarkSweepCollector::Collect() {
DebugLog("DebugMarkSweep Collect\n");

auto* heap = GetHeap<DebugMarkSweepHeap>();
Timer::GCTimer.Resume();
// reset collection trigger
heap->resetGCTrigger();

// now mark all reachables
markReachableObjects();

// in this survivors stack we will remember all objects that survived
auto* survivors = new vector<AbstractVMObject*>();
size_t survivorsSize = 0;

vector<AbstractVMObject*>::iterator iter;
for (iter = heap->allocatedObjects->begin();
iter != heap->allocatedObjects->end();
iter++) {
if ((*iter)->GetGCField() == GC_MARKED) {
// object ist marked -> let it survive
survivors->push_back(*iter);
survivorsSize += (*iter)->GetObjectSize();
(*iter)->SetGCField(0);
} else {
// not marked -> kill it
heap->FreeObject(*iter);
}
}

delete heap->allocatedObjects;
heap->allocatedObjects = survivors;

heap->spcAlloc = survivorsSize;
// TODO(smarr): Maybe choose another constant to calculate new
// collectionLimit here
heap->collectionLimit = 2 * survivorsSize;
Timer::GCTimer.Halt();
}

static gc_oop_t mark_object(gc_oop_t oop) {
if (IS_TAGGED(oop)) {
return oop;
}

AbstractVMObject* obj = AS_OBJ(oop);

if (obj->GetGCField() != 0) {
return oop;
}

obj->SetGCField(GC_MARKED);
obj->WalkObjects(mark_object);
return oop;
}

void DebugMarkSweepCollector::markReachableObjects() {
// This walks the globals of the universe, and the interpreter
Universe::WalkGlobals(mark_object);
}
15 changes: 15 additions & 0 deletions src/memory/DebugMarkSweepCollector.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
#pragma once

#include "../misc/defs.h"
#include "GarbageCollector.h"

class DebugMarkSweepHeap;
class DebugMarkSweepCollector : public GarbageCollector<DebugMarkSweepHeap> {
public:
explicit DebugMarkSweepCollector(DebugMarkSweepHeap* heap)
: GarbageCollector(heap) {}
void Collect() override;

private:
static void markReachableObjects();
};
43 changes: 43 additions & 0 deletions src/memory/DebugMarkSweepHeap.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
#include "DebugMarkSweepHeap.h"

#include <cstdlib>
#include <cstring>
#include <string>
#include <vector>

#include "../memory/Heap.h"
#include "../vm/Print.h"
#include "../vmobjects/AbstractObject.h"
#include "DebugMarkSweepCollector.h"

DebugMarkSweepHeap::DebugMarkSweepHeap(size_t objectSpaceSize)
: Heap<DebugMarkSweepHeap>(new DebugMarkSweepCollector(this)),
allocatedObjects(new vector<AbstractVMObject*>()),
// our initial collection limit is 90% of objectSpaceSize
collectionLimit((size_t)((double)objectSpaceSize * 0.9)) {}

DebugMarkSweepHeap::~DebugMarkSweepHeap() {
// free the tracked heap so leak checkers report only genuine leaks
for (auto* obj : *allocatedObjects) {
free(obj);
}
delete allocatedObjects;
}

AbstractVMObject* DebugMarkSweepHeap::AllocateObject(size_t size) {
auto* newObject = (AbstractVMObject*)malloc(size);
if (newObject == nullptr) {
ErrorPrint("\nFailed to allocate " + to_string(size) + " Bytes.\n");
Quit(-1);
}
spcAlloc += size;
memset((void*)newObject, 0, size);
// AbstractObjects (Integer,...) have no Size field anymore -> set within
// VMObject's new operator
allocatedObjects->push_back(newObject);
// let's see if we have to trigger the GC
if (spcAlloc >= collectionLimit) {
requestGC();
}
return newObject;
}
20 changes: 20 additions & 0 deletions src/memory/DebugMarkSweepHeap.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
#pragma once

#include "../misc/defs.h"
#include "Heap.h"

// Debug-only Mark/Sweep heap: the simple reference implementation that
// malloc()s/free()s every object and tracks them in a side list.
class DebugMarkSweepHeap : public Heap<DebugMarkSweepHeap> {
friend class DebugMarkSweepCollector;

public:
explicit DebugMarkSweepHeap(size_t objectSpaceSize = 1048576);
~DebugMarkSweepHeap();
AbstractVMObject* AllocateObject(size_t size);

private:
vector<AbstractVMObject*>* allocatedObjects;
size_t spcAlloc{0};
size_t collectionLimit;
};
13 changes: 9 additions & 4 deletions src/memory/Heap.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,11 @@

#include "../misc/defs.h"
#include "../vm/Print.h"
#include "CopyingHeap.h" // NOLINT(misc-include-cleaner)
#include "DebugCopyingHeap.h" // NOLINT(misc-include-cleaner)
#include "GenerationalHeap.h" // NOLINT(misc-include-cleaner)
#include "MarkSweepHeap.h" // NOLINT(misc-include-cleaner)
#include "CopyingHeap.h" // NOLINT(misc-include-cleaner)
#include "DebugCopyingHeap.h" // NOLINT(misc-include-cleaner)
#include "DebugMarkSweepHeap.h" // NOLINT(misc-include-cleaner)
#include "GenerationalHeap.h" // NOLINT(misc-include-cleaner)
#include "MarkSweepHeap.h" // NOLINT(misc-include-cleaner)

template <class HEAP_T>
void Heap<HEAP_T>::InitializeHeap(size_t objectSpaceSize) {
Expand Down Expand Up @@ -77,3 +78,7 @@ template Heap<CopyingHeap>::~Heap();
class MarkSweepHeap;
template MarkSweepHeap* Heap<MarkSweepHeap>::theHeap;
template Heap<MarkSweepHeap>::~Heap();

class DebugMarkSweepHeap;
template DebugMarkSweepHeap* Heap<DebugMarkSweepHeap>::theHeap;
template Heap<DebugMarkSweepHeap>::~Heap();
90 changes: 62 additions & 28 deletions src/memory/MarkSweepCollector.cpp
Original file line number Diff line number Diff line change
@@ -1,6 +1,18 @@
// A mark-and-sweep garbage collector. When memory runs low it finds every
// object the program can still reach ("mark"), and treats everything else as
// garbage whose memory can be reused ("sweep"). Objects are never moved.
//
// Marking starts from the roots - the globals and the interpreter's stack
// and follows references outward until the whole set of reachable objects has
// been visited.
//
// Sweeping is lazy. A collection itself only marks, which keeps the process
// pause short; reclaiming dead objects happens as the the program allocates.
// Reclaimed memory is recycled for new objects of the same size.
#include "MarkSweepCollector.h"

#include <cstddef>
#include <utility>
#include <vector>

#include "../memory/Heap.h"
Expand All @@ -12,65 +24,87 @@
#include "../vmobjects/VMFrame.h"
#include "MarkSweepHeap.h"

#define GC_MARKED 3456
// File scope so the static mark callback can reach them; the worklist is reused
// across collections.
static size_t s_epoch = 0;
static size_t s_markedBytes = 0;
static std::vector<AbstractVMObject*> s_markStack;

void MarkSweepCollector::Collect() {
DebugLog("MarkSweep Collect\n");

auto* heap = GetHeap<MarkSweepHeap>();
Timer::GCTimer.Resume();
// reset collection trigger
heap->resetGCTrigger();

// now mark all reachables
// New cycle. The epoch only increases, so a survivor marked in an older
// cycle is never mistaken for live now -- no mark reset needed.
heap->epoch++;
s_epoch = heap->epoch;
s_markedBytes = 0;

// Drop all free lists (re-harvested by sweeping). This is what guarantees
// no allocation into a not-yet-swept page, so any cell without the current
// epoch found while sweeping is reclaimable.
for (auto& list : heap->freeLists) {
list = nullptr;
}
for (auto& cursor : heap->sweepCursor) {
cursor = 0;
}

markReachableObjects();

// in this survivors stack we will remember all objects that survived
auto* survivors = new vector<AbstractVMObject*>();
size_t survivorsSize = 0;

vector<AbstractVMObject*>::iterator iter;
for (iter = heap->allocatedObjects->begin();
iter != heap->allocatedObjects->end();
iter++) {
if ((*iter)->GetGCField() == GC_MARKED) {
// object ist marked -> let it survive
survivors->push_back(*iter);
survivorsSize += (*iter)->GetObjectSize();
(*iter)->SetGCField(0);
// Sweep the large-object space eagerly (few objects, so cheap).
std::vector<AbstractVMObject*> survivingLarge;
for (auto* obj : heap->largeObjects) {
if (obj->GetGCField() == heap->epoch) {
survivingLarge.push_back(obj);
} else {
// not marked -> kill it
heap->FreeObject(*iter);
heap->FreeObject(obj);
}
}
heap->largeObjects = std::move(survivingLarge);

delete heap->allocatedObjects;
heap->allocatedObjects = survivors;
// Small dead objects (and empty pages) are reclaimed lazily during
// allocation, not here, keeping this pause to just the mark phase.

heap->spcAlloc = survivorsSize;
// TODO(smarr): Maybe choose another constant to calculate new
// collectionLimit here
heap->collectionLimit = 2 * survivorsSize;
heap->spcAlloc = s_markedBytes;
// Collect again after allocating ~max(live, a heap's worth). The floor
// makes the heap size (-H / objectSpaceSize) actually govern GC frequency,
// like the copying collector, instead of collecting every ~live bytes.
size_t const grown = 2 * s_markedBytes;
heap->collectionLimit =
grown > heap->minCollectionLimit ? grown : heap->minCollectionLimit;
Timer::GCTimer.Halt();
}

// Marks an object with the current epoch and queues it. Iterative (worklist),
// not recursive, so deep object graphs can't overflow the native stack.
static gc_oop_t mark_object(gc_oop_t oop) {
if (IS_TAGGED(oop)) {
return oop;
}

AbstractVMObject* obj = AS_OBJ(oop);

if (obj->GetGCField() != 0) {
if (obj->GetGCField() == s_epoch) {
return oop;
}

obj->SetGCField(GC_MARKED);
obj->WalkObjects(mark_object);
obj->SetGCField(s_epoch);
s_markedBytes += obj->GetObjectSize();
s_markStack.push_back(obj);
return oop;
}

void MarkSweepCollector::markReachableObjects() {
// This walks the globals of the universe, and the interpreter
s_markStack.clear();
Universe::WalkGlobals(mark_object);

while (!s_markStack.empty()) {
AbstractVMObject* obj = s_markStack.back();
s_markStack.pop_back();
obj->WalkObjects(mark_object);
}
}
Loading
Loading