Merge branch 'GC-Master'

* GC-Master:
  Updated with README and stuff.
  Added the Mapbox files.
  Initial check-in.
This commit is contained in:
Elf M. Sternberg 2016-12-30 10:51:00 -08:00
commit 1b7c81e469
11 changed files with 1896 additions and 0 deletions

18
.gitignore vendored Normal file
View File

@ -0,0 +1,18 @@
*#
.#*
*~
*.orig
npm-debug.log
package.yml
node_modules/*
tmp/
bin/_mocha
bin/mocha
bin/escodegen
bin/esgenerate
test-reports.xml
LisperatorLanguage
src/test.js
src/test.coffee
notes/
build/

6
CMakeLists.txt Normal file
View File

@ -0,0 +1,6 @@
project("Collector")
list(APPEND CMAKE_CXX_FLAGS "${CXXMAKE_C_FLAGS} -std=c++1y -I../src/include/ -g")
add_executable(collector src/collector.cpp)

65
README.md Normal file
View File

@ -0,0 +1,65 @@
---
**WHAT:**
This is an example of Bob Nystrom's
"[Baby's First Garbage Collector](http://journal.stuffwithstuff.com/2013/12/08/babys-first-garbage-collector/),"
which I've been wanting to implement for a while in order to understand
it better. To make the problem harder (as I always do), I decided to
write it in C++, and to make it even more fun, I've implemented it using
the new Variant container from C++17.
**WHY:**
I've never written a garbage collector before. Now I know what it is
and how it works.
**DETAILS:**
The collector Nystrom wrote is a simple bi-color mark-and-sweep
collector for a singly threaded process with distinct pauses based upon
a straightforward memory usage heuristic. That heuristic is simply, "Is
the amount of memory currently in use twice as much as the last time we
garbage collected?" If the answer is yes, the collector runs and sweeps
up the stack.
Nystrom's code is highly readable, and I hope mine is as well. Because
I used Variant, my Object class has an internal Pair class, and then the
Variant is just \<int, Pair\>, where "Pair" is a pair of pointers to
other objects. The entirety of the VM is basically a stack of
singly-linked lists which either represents integers or collections of
integers in a Lisp-like structure.
The allocator creates two kinds of objects, then: pairs, and lists. A
pair is created by pushing two other objects onto the stack, then
calling `push()`, which pops them off the stack and replaces them with a
Pair object. The VM class has two methods, both named `push()`, one of
which pushes an integer, the other a pair. Since a pair is built from
objects on the stack, the Pair version takes no arguments, and since
C++14 and beyond have move semantics that Variant honors,
Variant\<Pair\> only constructs a single pair. Pretty nice. I was also
able to use both lambda-style and constructor-style visitors in my
Variant, which was a fun little bonus.
**NOTE:**
I have included the header files for the
[Mapbox version of Variant](https://github.com/mapbox/variant) since the
C++17 committee's standards haven't quite reached the general public and
the Variant implementation is still a subject of some debate. This
implementation looks straightforward enough and is a header-only
release. It works with both GCC 4.8.5 and Clang 3.8, and that's good
enough for me.
The Mapbox variant is BSD licensed, and a copy of the license is
included in the Include directory.
**BUILDING:**
From the base directory of the project:
mkdir build
cd build
cmake ..
make
And you should be able to run the basic tests. It's just one file.

238
src/collector.c Normal file
View File

@ -0,0 +1,238 @@
#include <stdio.h>
#include <stdlib.h>
#define MAX_STACK 256
#define MAX_BARRIER 8
typedef enum {
OBJ_INT,
OBJ_PAIR
} ObjectType;
typedef struct sObject {
unsigned char marked;
struct sObject* next;
ObjectType type;
union {
int value;
struct {
struct sObject* head;
struct sObject* tail;
};
};
} Object;
typedef struct {
Object* stack[MAX_STACK];
Object* root;
int stackSize;
int numObjects;
int maxObjects;
} VM;
void assert(int condition, const char* message) {
if (!condition) {
printf("%s\n", message);
exit(1);
}
}
VM* newVM() {
VM* vm = malloc(sizeof(VM));
vm->stackSize = 0;
vm->numObjects = 0;
vm->maxObjects = MAX_BARRIER;
return vm;
}
void push(VM* vm, Object* value) {
assert(vm->stackSize < MAX_STACK, "Stack overflow!");
vm->stack[vm->stackSize++] = value;
}
Object* pop(VM* vm) {
assert(vm->stackSize > 0, "Stack underflow!");
return vm->stack[--(vm->stackSize)];
}
void mark(Object* object) {
if (object->marked) {
return;
}
object->marked = 1;
if (object->type == OBJ_PAIR) {
mark(object->head);
mark(object->tail);
}
}
void markAll(VM* vm) {
for(int i = 0; i < vm->stackSize; i++) {
mark(vm->stack[i]);
}
}
void sweep(VM* vm) {
Object** object = &vm->root;
while (*object) {
if (!(*object)->marked) {
Object* unreached = *object;
*object = unreached->next;
vm->numObjects--;
free(unreached);
} else {
(*object)->marked = 0;
object = &(*object)->next;
}
}
}
void gc(VM* vm) {
int numObjects = vm->numObjects;
markAll(vm);
sweep(vm);
vm->maxObjects = vm->numObjects * 2;
printf("Collected %d objects, %d remaining.\n", numObjects - vm->numObjects,
vm->numObjects);
}
Object *newObject(VM* vm, ObjectType type) {
if (vm->numObjects == vm->maxObjects) {
gc(vm);
}
Object* object = malloc(sizeof(Object));
object->marked = 0;
object->type = type;
object->next = vm->root;
vm->root = object;
vm->numObjects++;
return object;
}
Object* pushInt(VM* vm, int value) {
Object* object = newObject(vm, OBJ_INT);
object->value = value;
push(vm, object);
return object;
}
Object* pushPair(VM* vm) {
Object* object = newObject(vm, OBJ_PAIR);
object->head = pop(vm);
object->tail = pop(vm);
push(vm, object);
return object;
}
void freeVM(VM *vm) {
vm->stackSize = 0;
gc(vm);
free(vm);
}
void objectPrint(Object* object) {
switch(object->type) {
case OBJ_INT:
printf("%d\n", object->value);
break;
case OBJ_PAIR:
printf("(");
objectPrint(object->head);
printf(", ");
objectPrint(object->tail);
printf(")");
break;
}
}
void test1() {
printf("Test 1: Objects on stack are preserved.\n");
VM* vm = newVM();
pushInt(vm, 1);
pushInt(vm, 2);
gc(vm);
assert(vm->numObjects == 2, "Should have preserved objects.");
freeVM(vm);
}
void test2() {
printf("Test 2: Unreached objects are collected.\n");
VM* vm = newVM();
pushInt(vm, 1);
pushInt(vm, 2);
pop(vm);
pop(vm);
gc(vm);
assert(vm->numObjects == 0, "Should have collected objects.");
freeVM(vm);
}
void test3() {
printf("Test 3: Reach nested objects.\n");
VM* vm = newVM();
pushInt(vm, 1);
pushInt(vm, 2);
pushPair(vm);
pushInt(vm, 3);
pushInt(vm, 4);
pushPair(vm);
pushPair(vm);
gc(vm);
assert(vm->numObjects == 7, "Should have reached objects.");
freeVM(vm);
}
void test4() {
printf("Test 4: Handle cycles.\n");
VM* vm = newVM();
pushInt(vm, 1);
pushInt(vm, 2);
Object* a = pushPair(vm);
pushInt(vm, 3);
pushInt(vm, 4);
Object* b = pushPair(vm);
/* Set up a cycle, and also make 2 and 4 unreachable and collectible. */
a->tail = b;
b->tail = a;
gc(vm);
assert(vm->numObjects == 4, "Should have collected objects.");
freeVM(vm);
}
void perfTest() {
printf("Performance Test.\n");
VM* vm = newVM();
for (int i = 0; i < 1000; i++) {
for (int j = 0; j < 20; j++) {
pushInt(vm, i);
}
for (int k = 0; k < 20; k++) {
pop(vm);
}
}
freeVM(vm);
}
int main(int argc, const char * argv[]) {
test1();
test2();
test3();
test4();
perfTest();
return 0;
}

252
src/collector.cpp Normal file
View File

@ -0,0 +1,252 @@
#include <iostream>
/* Requires the mapbox header-only variant found at
https://github.com/mapbox/variant
Compiles with:
clang++ -std=c++14 -I./include/ -o collector collector.cpp
or
g++ -std=c++1y -I./include/ -g -o collector collector.cpp
where 'include' has the variant headers.
clang version 3.9.1-svn288847-1~exp1 (branches/release_39)
g++ version (Ubuntu 4.8.5-2ubuntu1~14.04.1) 4.8.5
*/
#include <mapbox/variant.hpp>
#define MAX_STACK 256
#define MAX_BARRIER 8
void my_assert(int condition, const char* message) {
if (!condition) {
std::cout << message << std::endl;
exit(1);
}
}
/* An implementation of Bob Nystrom's "Baby's First Garbage Collector"
http://journal.stuffwithstuff.com/2013/12/08/babys-first-garbage-collector/,
only in C++, and with some educational stuff along the way about
the new Variant (automagical discriminated unions) coming in
Libstdc++ version 4, part of the C++ 2017 standard.
*/
class Object {
public:
unsigned char marked;
Object *next;
Object(int v): marked(0), value(v) {}
// Variant<Pair> uses move semantics; this doesn't result in Pair being built twice.
Object(Object* head, Object* tail): marked(0), value(Pair(head, tail)) {}
class Pair {
public:
Pair(Object* h, Object* t): head(h), tail(t) {};
Object* head;
Object* tail;
};
/* This is mostly an exploration of a discriminated union, and
making one work in the context of a primitive but functional
garbage collector. */
mapbox::util::variant<int, Pair> value;
};
class VM {
public:
/* Imagine my surprise when I learned that clang doesn't bother to
zero out memory allocated on the threadstack. */
VM(): stackSize(0), numObjects(0), maxObjects(MAX_BARRIER), root(NULL) {};
Object* pop() {
my_assert(stackSize > 0, "Stack underflow!");
return stack[--stackSize];
}
/* This is basically the interface for a very primitive reverse
polish notation calculator of some kind. A garbage-collected
Forth interpreter, perhaps. */
Object* push(int v) {
return _push(insert(new Object(v)));
}
Object* push() {
return _push(insert(new Object(pop(), pop())));
}
/* Lambda-style visitors, enabling descent. */
void mark(Object *o) {
auto marker = mapbox::util::make_visitor(
[this](int) {},
[this](Object::Pair p) {
this->mark(p.head);
this->mark(p.tail);
});
if (o->marked) {
return;
}
o->marked = 1;
return mapbox::util::apply_visitor(marker, o->value);
}
/* So named because each scope resembles a collection of objects
leading horizontally from the vertical stack, creating a spine. */
void markSpine() {
for(auto i = 0; i < stackSize; i++) {
mark(stack[i]);
}
}
void collect() {
int num = numObjects;
markSpine();
sweep();
maxObjects = numObjects * 2;
#ifdef DEBUG
std::cout << "Collected " << (num - numObjects) << " objects, "
<< numObjects << " remain." << std::endl;
#endif
}
/* The saddest fact: I went with using NULL as our end-of-stack
discriminator rather than something higher-level, like an
Optional or Either-variant, because to use those I'd have to use
recursion to sweep the interpreter's stack, which means I'm at
the mercy of the C stack, complete with the cost of the unwind at
the end. Bummer. */
/* I look at this and ask, WWHSD? What Would Herb Sutter Do? */
void sweep() {
Object** o = &root;
while(*o) {
if (!(*o)->marked) {
Object* unreached = *o;
*o = unreached->next;
numObjects--;
delete unreached;
} else {
(*o)->marked = 0;
o = &(*o)->next;
}
}
}
int numObjects;
private:
/* Heh. Typo, "Stark overflow." I'll just leave Tony right there anyway... */
Object* _push(Object *o) {
my_assert(stackSize < MAX_STACK, "Stark overflow");
stack[stackSize++] = o;
return o;
}
Object* insert(Object *o) {
if (numObjects >= maxObjects) {
collect();
}
o->marked = 0;
o->next = root;
root = o;
numObjects++;
return o;
}
Object* stack[MAX_STACK];
Object* root;
int stackSize;
int maxObjects;
};
void test1() {
std::cout << "Test 1: Objects on stack are preserved." << std::endl;
VM vm;
vm.push(1);
vm.push(2);
vm.collect();
my_assert(vm.numObjects == 2, "Should have preserved objects.");
}
void test2() {
std::cout << "Test 2: Unreached objects are collected." << std::endl;
VM vm;
vm.push(1);
vm.push(2);
vm.pop();
vm.pop();
vm.collect();
my_assert(vm.numObjects == 0, "Should have collected objects.");
}
void test3() {
std::cout << "Test 3: Reach nested objects." << std::endl;
VM vm;
vm.push(1);
vm.push(2);
vm.push();
vm.push(3);
vm.push(4);
vm.push();
vm.push();
vm.collect();
my_assert(vm.numObjects == 7, "Should have reached objects.");
}
void test4() {
std::cout << "Test 4: Handle cycles." << std::endl;
VM vm;
vm.push(1);
vm.push(2);
Object* a = vm.push();
vm.push(3);
vm.push(4);
Object* b = vm.push();
/* Constructor-based variant visitor. */
struct tail_setter {
Object* tail;
tail_setter(Object *t) : tail(t) {}
inline void operator()(int &i) {}
inline void operator()(Object::Pair &p) { p.tail = tail; }
};
/* Set up a cycle, and also make 2 and 4 unreachable and collectible. */
mapbox::util::apply_visitor(tail_setter(b), a->value);
mapbox::util::apply_visitor(tail_setter(a), b->value);
vm.collect();
my_assert(vm.numObjects == 4, "Should have collected objects.");
}
void perfTest() {
std::cout << "Performance Test." << std::endl;
VM vm;
for (int i = 0; i < 1000; i++) {
for (int j = 0; j < 20; j++) {
vm.push(i);
}
for (int k = 0; k < 20; k++) {
vm.pop();
}
}
}
int main(int argc, const char * argv[]) {
test1();
test2();
test3();
test4();
perfTest();
return 0;
}

View File

@ -0,0 +1,25 @@
Copyright (c) MapBox
All rights reserved.
Redistribution and use in source and binary forms, with or without modification,
are permitted provided that the following conditions are met:
- Redistributions of source code must retain the above copyright notice, this
list of conditions and the following disclaimer.
- 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.
- Neither the name "MapBox" nor the names of its contributors may be
used to endorse or promote products derived from this software without
specific prior written permission.
THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND 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 THE COPYRIGHT HOLDER OR 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.

View File

@ -0,0 +1,74 @@
#ifndef MAPBOX_UTIL_OPTIONAL_HPP
#define MAPBOX_UTIL_OPTIONAL_HPP
#pragma message("This implementation of optional is deprecated. See https://github.com/mapbox/variant/issues/64.")
#include <type_traits>
#include <utility>
#include <mapbox/variant.hpp>
namespace mapbox {
namespace util {
template <typename T>
class optional
{
static_assert(!std::is_reference<T>::value, "optional doesn't support references");
struct none_type
{
};
variant<none_type, T> variant_;
public:
optional() = default;
optional(optional const& rhs)
{
if (this != &rhs)
{ // protect against invalid self-assignment
variant_ = rhs.variant_;
}
}
optional(T const& v) { variant_ = v; }
explicit operator bool() const noexcept { return variant_.template is<T>(); }
T const& get() const { return variant_.template get<T>(); }
T& get() { return variant_.template get<T>(); }
T const& operator*() const { return this->get(); }
T operator*() { return this->get(); }
optional& operator=(T const& v)
{
variant_ = v;
return *this;
}
optional& operator=(optional const& rhs)
{
if (this != &rhs)
{
variant_ = rhs.variant_;
}
return *this;
}
template <typename... Args>
void emplace(Args&&... args)
{
variant_ = T{std::forward<Args>(args)...};
}
void reset() { variant_ = none_type{}; }
}; // class optional
} // namespace util
} // namespace mapbox
#endif // MAPBOX_UTIL_OPTIONAL_HPP

View File

@ -0,0 +1,122 @@
#ifndef MAPBOX_UTIL_RECURSIVE_WRAPPER_HPP
#define MAPBOX_UTIL_RECURSIVE_WRAPPER_HPP
// Based on variant/recursive_wrapper.hpp from boost.
//
// Original license:
//
// Copyright (c) 2002-2003
// Eric Friedman, Itay Maman
//
// Distributed under the Boost Software License, Version 1.0. (See
// accompanying file LICENSE_1_0.txt or copy at
// http://www.boost.org/LICENSE_1_0.txt)
#include <cassert>
#include <utility>
namespace mapbox {
namespace util {
template <typename T>
class recursive_wrapper
{
T* p_;
void assign(T const& rhs)
{
this->get() = rhs;
}
public:
using type = T;
/**
* Default constructor default initializes the internally stored value.
* For POD types this means nothing is done and the storage is
* uninitialized.
*
* @throws std::bad_alloc if there is insufficient memory for an object
* of type T.
* @throws any exception thrown by the default constructur of T.
*/
recursive_wrapper()
: p_(new T){}
~recursive_wrapper() noexcept { delete p_; }
recursive_wrapper(recursive_wrapper const& operand)
: p_(new T(operand.get())) {}
recursive_wrapper(T const& operand)
: p_(new T(operand)) {}
recursive_wrapper(recursive_wrapper&& operand)
: p_(new T(std::move(operand.get()))) {}
recursive_wrapper(T&& operand)
: p_(new T(std::move(operand))) {}
inline recursive_wrapper& operator=(recursive_wrapper const& rhs)
{
assign(rhs.get());
return *this;
}
inline recursive_wrapper& operator=(T const& rhs)
{
assign(rhs);
return *this;
}
inline void swap(recursive_wrapper& operand) noexcept
{
T* temp = operand.p_;
operand.p_ = p_;
p_ = temp;
}
recursive_wrapper& operator=(recursive_wrapper&& rhs) noexcept
{
swap(rhs);
return *this;
}
recursive_wrapper& operator=(T&& rhs)
{
get() = std::move(rhs);
return *this;
}
T& get()
{
assert(p_);
return *get_pointer();
}
T const& get() const
{
assert(p_);
return *get_pointer();
}
T* get_pointer() { return p_; }
const T* get_pointer() const { return p_; }
operator T const&() const { return this->get(); }
operator T&() { return this->get(); }
}; // class recursive_wrapper
template <typename T>
inline void swap(recursive_wrapper<T>& lhs, recursive_wrapper<T>& rhs) noexcept
{
lhs.swap(rhs);
}
} // namespace util
} // namespace mapbox
#endif // MAPBOX_UTIL_RECURSIVE_WRAPPER_HPP

File diff suppressed because it is too large Load Diff

View File

@ -0,0 +1,45 @@
#ifndef MAPBOX_UTIL_VARIANT_IO_HPP
#define MAPBOX_UTIL_VARIANT_IO_HPP
#include <iosfwd>
#include <mapbox/variant.hpp>
namespace mapbox {
namespace util {
namespace detail {
// operator<< helper
template <typename Out>
class printer
{
public:
explicit printer(Out& out)
: out_(out) {}
printer& operator=(printer const&) = delete;
// visitor
template <typename T>
void operator()(T const& operand) const
{
out_ << operand;
}
private:
Out& out_;
};
}
// operator<<
template <typename CharT, typename Traits, typename... Types>
VARIANT_INLINE std::basic_ostream<CharT, Traits>&
operator<<(std::basic_ostream<CharT, Traits>& out, variant<Types...> const& rhs)
{
detail::printer<std::basic_ostream<CharT, Traits>> visitor(out);
apply_visitor(visitor, rhs);
return out;
}
} // namespace util
} // namespace mapbox
#endif // MAPBOX_UTIL_VARIANT_IO_HPP

View File

@ -0,0 +1,38 @@
#ifndef MAPBOX_UTIL_VARIANT_VISITOR_HPP
#define MAPBOX_UTIL_VARIANT_VISITOR_HPP
namespace mapbox {
namespace util {
template <typename... Fns>
struct visitor;
template <typename Fn>
struct visitor<Fn> : Fn
{
using type = Fn;
using Fn::operator();
visitor(Fn fn) : Fn(fn) {}
};
template <typename Fn, typename... Fns>
struct visitor<Fn, Fns...> : Fn, visitor<Fns...>
{
using type = visitor;
using Fn::operator();
using visitor<Fns...>::operator();
visitor(Fn fn, Fns... fns) : Fn(fn), visitor<Fns...>(fns...) {}
};
template <typename... Fns>
visitor<Fns...> make_visitor(Fns... fns)
{
return visitor<Fns...>(fns...);
}
} // namespace util
} // namespace mapbox
#endif // MAPBOX_UTIL_VARIANT_VISITOR_HPP